更新webpack配置、精工车间工序工价/生产计划/派工及移动端界面,新增菜单迁移与工价公式调整SQL脚本

This commit is contained in:
Developer
2026-08-08 16:02:24 +08:00
parent fd84f9d3ca
commit 9fc3aae3e9
15 changed files with 2007 additions and 97 deletions

View File

@@ -215,12 +215,14 @@
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="单装数量(件)" prop="单装数量(件)">
<el-input-number v-model="form['单装数量(件)']" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%;" />
<!-- 修改人:Ld 修改时间:2026-08-08 10:24:44; 单装数量参与标准产能公式,变化后立即重算 -->
<el-input-number v-model="form['单装数量(件)']" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%;" @change="refreshPriceFormula()" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="操作台数(台/人)" prop="操作台数">
<el-input-number v-model="form.操作台数" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%;" />
<!-- 修改人:Ld 修改时间:2026-08-08 10:24:44; 操作台数参与工价定额公式,变化后立即重算 -->
<el-input-number v-model="form.操作台数" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%;" @change="refreshPriceFormula()" />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -499,7 +501,7 @@ export default {
this.form.总时间s = this.getTotalTime(this.form)
this.refreshPriceFormula()
},
// 修改人:Ld 修改时间:2026-08-04 09:39:18; 按截图公式计算标准产能和工价定额,并同步旧字段兼容后端编辑过程
// 修改人:Ld 修改时间:2026-08-08 10:24:44; 按截图公式 I=F*3600/E*A、J=H/B/I 计算,并同步旧字段兼容后端编辑过程
refreshPriceFormula() {
if (!this.form) {
return
@@ -507,8 +509,10 @@ export default {
var totalTime = Number(this.form.总时间s || 0)
var workTime = Number(this.form['工作时间(时)'] || 0)
var dayWage = Number(this.form['日工资(元)'] || 0)
var capacity = totalTime === 0 ? 0 : workTime * 3600 / totalTime
var priceQuota = capacity === 0 ? 0 : dayWage / capacity
var loadingQuantity = Number(this.form['单装数量(件)'] || 0)
var operationCount = Number(this.form.操作台数 || 0)
var capacity = totalTime === 0 ? 0 : workTime * 3600 / totalTime * loadingQuantity
var priceQuota = capacity === 0 || operationCount === 0 ? 0 : dayWage / operationCount / capacity
this.form['标准产能(件/天)'] = Number(capacity.toFixed(4))
this.form.工价定额 = Number(priceQuota.toFixed(4))
this.form['日产能(件)'] = this.form['标准产能(件/天)']
@@ -872,6 +876,10 @@ export default {
param[15] = ['备注', this.form.备注]
param[16] = ['工序流水号', this.form.工序流水号]
param[17] = ['修改人', this.id]
// 修改人:Ld 修改时间:2026-08-08 10:24:44; 保存截图公式的 F、G、H 输入值,后端按新公式统一计算 I、J
param[18] = ['工作时间时', this.form['工作时间(时)']]
param[19] = ['宽放时间时', this.form['宽放时间(时)']]
param[20] = ['日工资元', this.form['日工资(元)']]
var Data = this.CreateData('12', '车间生产管理工艺_工序工价_编辑数据', param)
this.ExecDatabase(Data).then(response => {
if (response.data && response.data[0] && response.data[0].result === '1') {

View File

@@ -232,22 +232,6 @@
<!-- 修改人:Ld 修改时间:2026-08-04 09:16:54; 弹窗改为APS排产格式,并按照操作者分组展示。 -->
<el-dialog :visible.sync="machineTaskVisible" :close-on-click-modal="false" :title="machineTaskTitle" width="1200px" top="50px">
<div v-loading="machineTaskLoading" class="machine-aps">
<div class="machine-aps-toolbar">
<span class="machine-aps-toolbar-label">时间筛选</span>
<el-date-picker
v-model="machineTaskDateRange"
:default-time="['00:00:00', '23:59:59']"
end-placeholder="结束日期"
range-separator="至"
size="small"
start-placeholder="开始日期"
style="width: 310px"
type="daterange"
value-format="yyyy-MM-dd"
@change="changeMachineTaskDateRange"/>
<el-button size="small" plain icon="el-icon-refresh-left" style="margin-left: 10px" @click="resetMachineTaskDateRange">重置</el-button>
<span class="machine-aps-toolbar-tip">按任务派工与完工时间区间筛选</span>
</div>
<div v-if="machineTaskDisplayData.length === 0 && !machineTaskLoading" class="machine-aps-empty">暂无后续已派工任务</div>
<div v-else class="machine-aps-scroll">
<div :style="{ width: machineApsInnerWidth + 'px' }" class="machine-aps-inner">
@@ -341,7 +325,6 @@ export default {
machineTaskLoading: false,
machineTaskTitle: '机床后续已派工任务',
machineTaskData: [],
machineTaskDateRange: [],
// 修改人:Ld 修改时间:2026-08-04 09:16:54; APS排产弹窗的固定轴尺寸,保持展示稳定。
machineApsDayWidth: 88,
machineApsPersonWidth: 118,
@@ -503,27 +486,19 @@ export default {
}
})
},
// 修改人:Ld 修改时间:2026-08-04 14:30:24; 弹窗时间筛选后的任务集合,按派工时间与完工期限区间过滤。
// 修改人:Ld 修改时间:2026-08-05 13:24:30; 方案三弹窗只显示今天及之后的任务,之前的任务不再展示。
machineTaskDisplayData() {
if (!this.machineTaskDateRange || this.machineTaskDateRange.length !== 2) {
return this.machineTaskData
}
var startDate = this.parseApsDate(this.machineTaskDateRange[0])
var endDate = this.parseApsDate(this.machineTaskDateRange[1])
if (!startDate || !endDate) {
return this.machineTaskData
}
if (endDate.getTime() < startDate.getTime()) {
var tempDate = startDate
startDate = endDate
endDate = tempDate
}
var today = this.clearApsDate(new Date())
return this.machineTaskData.filter(row => {
var rowStart = this.parseApsDate(row.分配时间) || this.parseApsDate(row.精工期限)
var rowEnd = this.parseApsDate(row.精工期限) || rowStart
if (!rowStart) return false
if (!rowEnd) rowEnd = rowStart
return rowStart.getTime() <= endDate.getTime() && rowEnd.getTime() >= startDate.getTime()
return this.clearApsDate(rowEnd).getTime() >= today.getTime()
}).sort((a, b) => {
var aDate = this.parseApsDate(a.分配时间) || this.parseApsDate(a.精工期限) || today
var bDate = this.parseApsDate(b.分配时间) || this.parseApsDate(b.精工期限) || today
return this.clearApsDate(aDate).getTime() - this.clearApsDate(bDate).getTime()
})
}
},
@@ -607,6 +582,8 @@ export default {
_completedProcesses: 0,
// 修改人:Ld 修改时间:2026-08-04 08:56:51; 记录每个OP工序是否完成,用于单元格标绿。
_completedOpMap: {},
// 修改人:Ld 修改时间:2026-08-05 10:18:41; 记录正在生产中的OP工序,用于单元格标黄。
_producingOpMap: {},
...this.initializeProcessFields()
}
},
@@ -644,6 +621,9 @@ export default {
if (row._completedOpMap && row._completedOpMap[opKey]) {
return 'op-process-completed'
}
if (row._producingOpMap && row._producingOpMap[opKey]) {
return 'op-process-producing'
}
return ''
},
// 修改人:Ld 修改时间:2026-08-04 14:57:36; 合同编号优先取方案一真实关联合同号,没有时保留原提取关联值。
@@ -679,7 +659,6 @@ export default {
this.machineTaskData = []
this.machineTaskVisible = true
this.machineTaskLoading = true
this.machineTaskDateRange = []
var Data = this.CreateData('11', '精工车间_工况_分页查询', param)
this.ExecDatabase(Data).then(response => {
if (response.data && response.data.result) {
@@ -703,14 +682,6 @@ export default {
}
return value
},
// 修改人:Ld 修改时间:2026-08-04 14:30:24; 切换弹窗时间范围后刷新APS排产展示。
changeMachineTaskDateRange() {
this.$forceUpdate()
},
resetMachineTaskDateRange() {
this.machineTaskDateRange = []
this.$forceUpdate()
},
// 修改人:Ld 修改时间:2026-08-04 09:16:54; APS排产视图日期解析,兼容后端日期字符串。
parseApsDate(value) {
if (!value) return null
@@ -763,9 +734,22 @@ export default {
} else {
group.状态 = '生产中'
}
this.markProducingProcess(group)
delete group._totalProcesses
delete group._completedProcesses
},
// 修改人:Ld 修改时间:2026-08-05 10:18:41; 生产中订单将第一个未完成OP工序标记为正在生产。
markProducingProcess(group) {
group._producingOpMap = {}
if (group.状态 !== '生产中') return
for (let i = 1; i <= this.processConfig.maxProcessCount; i++) {
var opKey = this.processConfig.processNamePattern.replace('{index}', i)
if (group[opKey] && !(group._completedOpMap && group._completedOpMap[opKey])) {
group._producingOpMap[opKey] = true
return
}
}
},
// 数据分组主逻辑
groupDataByOrder(data) {
const grouped = {}
@@ -803,7 +787,7 @@ export default {
this.filteredTableData.forEach((row, index) => {
data.push(this.buildProductionPlanExportRow(row, index))
})
var ws = XLSX.utils.aoa_to_sheet(data)
var ws = this.getProductionPlanSheetFromArray(data)
var range = XLSX.utils.decode_range(ws['!ref'])
ws['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: headers.length - 1 }}]
ws['!cols'] = this.getProductionPlanExportCols()
@@ -819,17 +803,56 @@ export default {
var opKey = headers[colIndex]
if (exportRow && exportRow._completedOpMap && exportRow._completedOpMap[opKey]) {
ws[cellAddress].s = this.getProductionPlanCompletedOpExportStyle()
} else if (exportRow && exportRow._producingOpMap && exportRow._producingOpMap[opKey]) {
ws[cellAddress].s = this.getProductionPlanProducingOpExportStyle()
}
}
}
}
ws['!autofilter'] = { ref: XLSX.utils.encode_range({ s: { r: 1, c: 0 }, e: { r: Math.max(1, range.e.r), c: headers.length - 1 }}) }
var wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, this.getProductionPlanExportSheetName())
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'array' })
FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), this.getProductionPlanExportFileName())
var sheetName = this.getProductionPlanExportSheetName()
var wb = { SheetNames: [sheetName], Sheets: {}, Props: {}}
wb.Sheets[sheetName] = ws
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' })
FileSaver.saveAs(new Blob([this.productionPlanStringToArrayBuffer(wbout)], { type: 'application/octet-stream' }), this.getProductionPlanExportFileName())
this.$message.success('导出成功')
},
// 修改人:Ld 修改时间:2026-08-05 08:13:25; 兼容项目当前 xlsx-style 版本,手动将二维数组转换为 worksheet。
getProductionPlanSheetFromArray(data) {
var ws = {}
var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 }}
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
for (let colIndex = 0; colIndex < data[rowIndex].length; colIndex++) {
if (range.s.r > rowIndex) range.s.r = rowIndex
if (range.s.c > colIndex) range.s.c = colIndex
if (range.e.r < rowIndex) range.e.r = rowIndex
if (range.e.c < colIndex) range.e.c = colIndex
var value = data[rowIndex][colIndex]
if (value === null || value === undefined) continue
var cell = { v: value }
if (typeof value === 'number') {
cell.t = 'n'
} else if (typeof value === 'boolean') {
cell.t = 'b'
} else {
cell.t = 's'
}
ws[XLSX.utils.encode_cell({ r: rowIndex, c: colIndex })] = cell
}
}
if (range.s.c < 10000000) {
ws['!ref'] = XLSX.utils.encode_range(range)
}
return ws
},
productionPlanStringToArrayBuffer(value) {
var buffer = new ArrayBuffer(value.length)
var view = new Uint8Array(buffer)
for (let i = 0; i < value.length; i++) {
view[i] = value.charCodeAt(i) & 0xFF
}
return buffer
},
getProductionPlanExportHeaders() {
return ['序号', '状态', '合同编号', '订单编号', '收货人', '投产日期', '订单交货日期', '数量', '总工序', '实际 工序', '产品名称', '零件名称', '零件图号', '材料', '完成率', '当前状态或设备', 'OP010', 'OP020', 'OP030', 'OP040', 'OP050', 'OP060', 'OP070', 'OP080', 'OP090', 'OP100', 'OP110']
},
@@ -935,7 +958,7 @@ export default {
}
if (rowIndex === 1) {
baseStyle.font.bold = false
baseStyle.fill = { fgColor: { rgb: 'FFFFFF' }}
baseStyle.fill = { patternType: 'solid', fgColor: { rgb: 'FFFFFF' }}
return baseStyle
}
if (colIndex === 11 || colIndex === 12) {
@@ -958,9 +981,24 @@ export default {
return baseStyle
},
getProductionPlanCompletedOpExportStyle() {
// 修改人:Ld 修改时间:2026-08-05 08:13:25; xlsx-style 导出填充色指定 solid,保证已完成工序绿色显示。
return {
font: { name: '微软雅黑', sz: 10, bold: false, color: { rgb: 'FFFFFF' }},
fill: { fgColor: { rgb: '67C23A' }},
fill: { patternType: 'solid', fgColor: { rgb: '67C23A' }},
alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
border: {
top: { style: 'thin', color: { rgb: '000000' }},
bottom: { style: 'thin', color: { rgb: '000000' }},
left: { style: 'thin', color: { rgb: '000000' }},
right: { style: 'thin', color: { rgb: '000000' }}
}
}
},
// 修改人:Ld 修改时间:2026-08-05 10:18:41; xlsx-style 导出填充色指定 solid,保证正在生产工序黄色显示。
getProductionPlanProducingOpExportStyle() {
return {
font: { name: '微软雅黑', sz: 10, bold: true, color: { rgb: '000000' }},
fill: { patternType: 'solid', fgColor: { rgb: 'FFC000' }},
alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
border: {
top: { style: 'thin', color: { rgb: '000000' }},
@@ -1278,6 +1316,17 @@ export default {
border-radius: 2px;
}
/* 修改人:Ld 修改时间:2026-08-05 10:18:41; 生产中OP工序标黄,不改变原表格布局。*/
.op-process-producing {
display: inline-block;
width: 100%;
min-height: 20px;
line-height: 20px;
color: #303133;
background-color: #FFC000;
border-radius: 2px;
}
/* 修改人:Ld 修改时间:2026-08-04 09:07:29; 保持当前状态列宽不变,仅增加机床可点击提示。 */
.machine-task-link {
width: 100%;
@@ -1294,27 +1343,6 @@ export default {
background: #fff;
}
.machine-aps-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-bottom: 1px solid #DCDFE6;
background: #FAFBFC;
}
.machine-aps-toolbar-label {
color: #303133;
font-size: 12px;
font-weight: bold;
}
.machine-aps-toolbar-tip {
margin-left: 4px;
color: #909399;
font-size: 12px;
}
.machine-aps-empty {
height: 460px;
line-height: 460px;

View File

@@ -596,6 +596,8 @@ export default {
// 确保orderName1已加载
if (this.orderName1.length === 0) {
await new Promise(resolve => {
// 修改人:Ld 修改时间:2026-08-05 09:35:00; 补全订单列表查询参数初始化,避免跳转查询时报 param 未定义。
var param = []
var Data = this.CreateData('11', '生产任务查询_订单信息_列表_查询数据', param)
this.ExecDatabase(Data).then(response => {
for (let i = 0; i < response.data.length; i++) {
@@ -861,6 +863,11 @@ export default {
submit() {
this.$refs['form'].validate((valid) => {
if (valid) {
// 修改人:Ld 修改时间:2026-08-05 09:35:00; 投产数修改时同步校验有效数值,避免空值绕过前端表单。
if (!Number.isFinite(Number(this.form.投产数)) || Number(this.form.投产数) <= 0) {
this.$message.warning('投产数不能为空且必须大于0')
return
}
if (this.productionNum === this.form.投产数) {
this.$message.warning('数量未发生改变')
} else {
@@ -908,7 +915,9 @@ export default {
this.$message.warning('设备不能为空')
return
}
if (row.分配数 === 0) {
// 修改人:Ld 修改时间:2026-08-05 09:35:00; 派工数量为空或非正数时直接拦截,避免空数量进入任务分配表。
if (!Number.isFinite(Number(row.分配数)) || Number(row.分配数) <= 0) {
this.$message.warning('分配数不能为空且必须大于0')
return
}
if (this.loading2) {

View File

@@ -4,21 +4,30 @@
<div class="dispatch-shell">
<section class="filter-panel">
<div class="filter-grid">
<el-select
v-model="orderNameValue"
:remote-method="getOrderName"
placeholder="订单号"
size="small"
filterable
clearable
remote
@change="pageCurrent = 1; getProductName(); searchTable()">
<el-option
v-for="item in orderName1"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<div class="native-order-field">
<!-- 修改人:Ld 修改时间:2026-08-08 14:53:45; 订单号改为原生输入框,确保手机软键盘可唤起。 -->
<input
v-model="orderNameText"
class="native-order-input"
type="search"
inputmode="search"
autocomplete="off"
placeholder="订单号"
@focus="orderSuggestVisible = true"
@input="handleOrderInput(orderNameText)"
@keydown.enter.prevent="handleOrderEnter">
<button v-if="orderNameText" class="native-order-clear" type="button" @click="handleOrderClear">×</button>
<div v-if="orderSuggestVisible && filteredOrderList.length > 0" class="native-order-list">
<button
v-for="item in filteredOrderList"
:key="item.orderValue"
type="button"
class="native-order-option"
@mousedown.prevent="handleOrderSelect(item)">
{{ item.label }}
</button>
</div>
</div>
<el-select
v-model="productNameValue"
placeholder="产品/部件"
@@ -233,7 +242,10 @@ export default {
return {
// 修改人:Ld 修改时间:2026-07-14 13:12:40; 移动端派工组件状态独立维护,避免影响桌面端页面。
activeTab: 'available',
// 修改人:Ld 修改时间:2026-08-08 14:53:45; 订单号移动端改为原生输入框,解决 Element 输入组件无法唤起手机软键盘问题。
orderNameText: '',
orderNameValue: '',
orderSuggestVisible: false,
orderName1: [],
productNameValue: '',
productName: [],
@@ -275,7 +287,19 @@ export default {
computed: {
...mapGetters([
'id'
])
]),
filteredOrderList() {
const query = this.orderNameText ? String(this.orderNameText).toLowerCase() : ''
return this.orderName1.filter(item => {
return !query || String(item.label).toLowerCase().indexOf(query) > -1
}).slice(0, 50).map(item => {
return {
label: item.label,
value: item.label,
orderValue: item.value
}
})
}
},
watch: {
machineValue(val) {
@@ -338,6 +362,46 @@ export default {
}
})
},
handleOrderSelect(item) {
this.orderNameText = item.label
this.orderNameValue = item.orderValue
this.orderSuggestVisible = false
this.pageCurrent = 1
this.getProductName()
this.searchTable()
},
handleOrderInput(value) {
const selected = this.orderName1.find(item => item.label === value)
if (selected) {
this.orderNameValue = selected.value
} else {
this.orderNameValue = ''
this.productNameValue = ''
this.productName = []
}
},
handleOrderClear() {
this.orderNameText = ''
this.orderNameValue = ''
this.productNameValue = ''
this.productName = []
this.orderSuggestVisible = false
this.pageCurrent = 1
this.searchTable()
},
handleOrderEnter() {
const selected = this.orderName1.find(item => item.label === this.orderNameText)
if (selected) {
this.handleOrderSelect({
label: selected.label,
orderValue: selected.value
})
} else {
this.orderSuggestVisible = false
this.pageCurrent = 1
this.searchTable()
}
},
getProductName() {
this.productNameValue = ''
this.productName = []
@@ -466,8 +530,9 @@ export default {
this.$message.warning('设备不能为空')
return
}
if (Number(row.分配数) === 0) {
this.$message.warning('分配数不能为0')
// 修改人:Ld 修改时间:2026-08-05 09:35:00; 派工数量为空或非正数时直接拦截,避免空数量进入任务分配表。
if (!Number.isFinite(Number(row.分配数)) || Number(row.分配数) <= 0) {
this.$message.warning('分配数不能为空且必须大于0')
return
}
if (this.loadingSubmit) {
@@ -534,6 +599,11 @@ export default {
submit() {
this.$refs['form'].validate((valid) => {
if (valid) {
// 修改人:Ld 修改时间:2026-08-05 09:35:00; 投产数修改时同步校验有效数值,避免空值绕过前端表单。
if (!Number.isFinite(Number(this.form.投产数)) || Number(this.form.投产数) <= 0) {
this.$message.warning('投产数不能为空且必须大于0')
return
}
if (this.productionNum === this.form.投产数) {
this.$message.warning('数量未发生改变')
return
@@ -689,6 +759,83 @@ export default {
min-width: 0;
}
.native-order-field {
position: relative;
z-index: 20;
width: 100%;
min-width: 0;
box-sizing: border-box;
}
.native-order-input {
width: 100%;
height: 32px;
min-width: 0;
padding: 0 34px 0 15px;
border: 1px solid #dcdfe6;
border-radius: 4px;
outline: none;
box-sizing: border-box;
color: #606266;
font-size: 13px;
line-height: 32px;
background: #fff;
-webkit-appearance: none;
}
.native-order-input:focus {
border-color: #409eff;
}
.native-order-clear {
position: absolute;
top: 1px;
right: 4px;
width: 30px;
height: 30px;
padding: 0;
border: none;
outline: none;
color: #c0c4cc;
font-size: 20px;
line-height: 30px;
background: transparent;
}
.native-order-list {
position: absolute;
top: 36px;
right: 0;
left: 0;
z-index: 30;
max-height: 260px;
overflow: auto;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
background: #fff;
}
.native-order-option {
display: block;
width: 100%;
min-height: 34px;
padding: 0 12px;
border: none;
border-bottom: 1px solid #f0f2f5;
outline: none;
box-sizing: border-box;
color: #606266;
font-size: 13px;
line-height: 34px;
text-align: left;
background: #fff;
}
.native-order-option:active {
background: #ecf5ff;
}
.quick-actions {
display: flex;
flex-wrap: wrap;