This commit is contained in:
jhd
2026-07-08 11:05:10 +08:00
122 changed files with 10782 additions and 212 deletions

View File

@@ -0,0 +1,154 @@
import request from '@/utils/request'
// ═══════════════════════════════════════
// 厚度分类
// ═══════════════════════════════════════
export function listThickClass(query) {
return request({ url: '/pltcm/thickClass/list', method: 'get', params: query })
}
export function getThickClass(classId) {
return request({ url: '/pltcm/thickClass/' + classId, method: 'get' })
}
export function addThickClass(data) {
return request({ url: '/pltcm/thickClass', method: 'post', data })
}
export function updateThickClass(data) {
return request({ url: '/pltcm/thickClass', method: 'put', data })
}
export function delThickClass(classIds) {
return request({ url: '/pltcm/thickClass/' + classIds, method: 'delete' })
}
// ═══════════════════════════════════════
// 宽度分类
// ═══════════════════════════════════════
export function listWidthClass(query) {
return request({ url: '/pltcm/widthClass/list', method: 'get', params: query })
}
export function getWidthClass(classId) {
return request({ url: '/pltcm/widthClass/' + classId, method: 'get' })
}
export function addWidthClass(data) {
return request({ url: '/pltcm/widthClass', method: 'post', data })
}
export function updateWidthClass(data) {
return request({ url: '/pltcm/widthClass', method: 'put', data })
}
export function delWidthClass(classIds) {
return request({ url: '/pltcm/widthClass/' + classIds, method: 'delete' })
}
// ═══════════════════════════════════════
// 屈服强度分类
// ═══════════════════════════════════════
export function listYieldStressClass(query) {
return request({ url: '/pltcm/yieldStressClass/list', method: 'get', params: query })
}
export function getYieldStressClass(classId) {
return request({ url: '/pltcm/yieldStressClass/' + classId, method: 'get' })
}
export function addYieldStressClass(data) {
return request({ url: '/pltcm/yieldStressClass', method: 'post', data })
}
export function updateYieldStressClass(data) {
return request({ url: '/pltcm/yieldStressClass', method: 'put', data })
}
export function delYieldStressClass(classIds) {
return request({ url: '/pltcm/yieldStressClass/' + classIds, method: 'delete' })
}
// ═══════════════════════════════════════
// 策略关联
// ═══════════════════════════════════════
export function listStrategy(query) {
return request({ url: '/pltcm/strategy/list', method: 'get', params: query })
}
export function getStrategy(id) {
return request({ url: '/pltcm/strategy/' + id, method: 'get' })
}
export function addStrategy(data) {
return request({ url: '/pltcm/strategy', method: 'post', data })
}
export function updateStrategy(data) {
return request({ url: '/pltcm/strategy', method: 'put', data })
}
export function delStrategy(ids) {
return request({ url: '/pltcm/strategy/' + ids, method: 'delete' })
}
// ═══════════════════════════════════════
// 屈服强度与钢种对应关系
// ═══════════════════════════════════════
export function listSteelgrade(query) {
return request({ url: '/pltcm/steelgrade/list', method: 'get', params: query })
}
export function getSteelgrade(steelGrade) {
return request({ url: '/pltcm/steelgrade/' + steelGrade, method: 'get' })
}
export function addSteelgrade(data) {
return request({ url: '/pltcm/steelgrade', method: 'post', data })
}
export function updateSteelgrade(data) {
return request({ url: '/pltcm/steelgrade', method: 'put', data })
}
export function delSteelgrade(steelGrades) {
return request({ url: '/pltcm/steelgrade/' + steelGrades, method: 'delete' })
}
// ═══════════════════════════════════════
// 比例标准表
// ═══════════════════════════════════════
export function listRatio(query) {
return request({ url: '/pltcm/ratio/list', method: 'get', params: query })
}
export function getRatio(id) {
return request({ url: '/pltcm/ratio/' + id, method: 'get' })
}
export function addRatio(data) {
return request({ url: '/pltcm/ratio', method: 'post', data })
}
export function updateRatio(data) {
return request({ url: '/pltcm/ratio', method: 'put', data })
}
export function delRatio(ids) {
return request({ url: '/pltcm/ratio/' + ids, method: 'delete' })
}
// ═══════════════════════════════════════
// 设备启用状态
// ═══════════════════════════════════════
export function listDeviceEnable(query) {
return request({ url: '/pltcm/deviceEnable/list', method: 'get', params: query })
}
export function getDeviceEnable(id) {
return request({ url: '/pltcm/deviceEnable/' + id, method: 'get' })
}
export function updateDeviceEnable(data) {
return request({ url: '/pltcm/deviceEnable', method: 'put', data })
}

View File

@@ -84,7 +84,10 @@
</el-form-item>
<el-form-item label="业务员" prop="saleName" v-if="orderBy">
<el-input v-model="queryParams.saleName" placeholder="请输入业务员" clearable size="small" />
<el-select v-model="queryParams.saleName" placeholder="请选择业务员" clearable size="small" filterable allow-create>
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="合同号" prop="contractNo" v-if="orderBy">
@@ -303,7 +306,7 @@ export default {
DragResizePanel,
OrderDetail
},
dicts: ['coil_itemname', 'coil_material', 'coil_manufacturer', 'coil_quality_status'],
dicts: ['coil_itemname', 'coil_material', 'coil_manufacturer', 'coil_quality_status', 'wip_pack_saleman'],
props: {
// 非触发器模式下,外部控制显隐(触发器模式下无效)
visible: {

File diff suppressed because it is too large Load Diff

View File

@@ -58,6 +58,12 @@ export default {
disabled: {
type: Boolean,
default: false
},
// 下拉最多展示条数默认6条
maxSuggestions: {
type: Number,
default: 6,
validator: (value) => value > 0
}
},
data() {
@@ -118,7 +124,7 @@ export default {
});
}
cb(results); // 回调返回匹配结果
cb(results.slice(0, this.maxSuggestions)); // 回调返回匹配结果(限制条数)
},
/**

View File

@@ -23,7 +23,7 @@
<span class="top-menu-title">{{ (menu.meta && menu.meta.title) || menu.name }}</span>
</div>
</div>
<div slot="reference" class="sidebar-section-title" :class="{ 'is-collapse': isCollapse }">
<div ref="topMenuTrigger" slot="reference" class="sidebar-section-title" :class="{ 'is-collapse': isCollapse }" tabindex="0">
<div class="section-title-inner">
<div class="section-icon">
<svg-icon v-if="activeTopMenuIcon" :icon-class="activeTopMenuIcon" />
@@ -141,6 +141,11 @@ export default {
}
},
watch: {
sectionPopoverVisible: {
handler(val) {
if (!val) this.releasePopoverFocus()
}
},
$route: {
immediate: true,
handler(newRoute) {
@@ -165,8 +170,16 @@ export default {
this.$router.push('/')
this.$nextTick(() => {
this.sectionPopoverVisible = false
this.releasePopoverFocus()
})
},
releasePopoverFocus() {
if (typeof document === 'undefined') return
const el = document.activeElement
if (el && typeof el.blur === 'function') el.blur()
const trigger = this.$refs && this.$refs.topMenuTrigger
if (trigger && typeof trigger.focus === 'function') trigger.focus()
},
autoSelectTopMenu(currentPath) {
const topMenus = this.$store.state.permission.topMenuList
if (topMenus.length === 0) return

View File

@@ -42,7 +42,7 @@
<el-table-column label="逻辑库位" align="center" prop="warehouseName" />
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" />
<el-table-column label="物料类型" align="center" prop="materialType" />
<el-table-column label="产品类型" align="center" width="180">
<el-table-column label="产品类型" align="center" width="140">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row" />

View File

@@ -1,51 +1,91 @@
<template>
<div class="contract-tabs">
<div v-if="orderId" class="tabs-content">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="财务状态" name="finance" v-hasPermi="['crm:order:finance']">
<div class="order-finance" v-if="activeTab === 'finance'">
<!-- 财务状态内容 -->
<ReceiveTable :order="currentOrder" />
</div>
</el-tab-pane>
<!-- 自定义Tab头部 -->
<div class="tab-header">
<span
class="tab-item"
:class="{ active: activeTab === 'contract' }"
@click="activeTab = 'contract'"
>合同信息</span>
<span
class="tab-item"
:class="{ active: activeTab === 'finance' }"
@click="activeTab = 'finance'"
>财务状态</span>
<span
class="tab-item"
:class="{ active: activeTab === 'dispute' }"
@click="activeTab = 'dispute'"
>订单异议</span>
<span
class="tab-item"
:class="{ active: activeTab === 'product' }"
@click="activeTab = 'product'"
>生产成果</span>
<span
class="tab-item"
:class="{ active: activeTab === 'coil' }"
@click="activeTab = 'coil'"
>发货配卷</span>
<span
class="tab-item"
:class="{ active: activeTab === 'delivery' }"
@click="activeTab = 'delivery'"
>发货单据</span>
<span
class="tab-item"
:class="{ active: activeTab === 'attachment' }"
@click="activeTab = 'attachment'"
>合同附件</span>
</div>
<el-tab-pane label="订单异议" name="dispute" v-hasPermi="['crm:order:objection']">
<div class="order-dispute" v-if="activeTab === 'dispute'">
<!-- 订单异议内容 -->
<OrderObjection :order="currentOrder" />
</div>
</el-tab-pane>
<el-tab-pane label="生产成果" name="product">
<div class="order-record" v-if="activeTab === 'product'" v-loading="productCoilLoading">
<!-- 生产成果内容 -->
<CoilTable ref="productCoilTable" :data="productCoilList || []" :showSelection="true"
:pagination="productPagination" :total-statistics="productTotalStatistics"
@selection-change="handleProductSelectionChange"
@page-change="handleProductPageChange">
<template slot="filter-actions" slot-scope="{ selectedRows }">
<el-button type="primary" size="mini" icon="el-icon-refresh"
:disabled="!selectedRows.length" @click="handleBatchTransferContract(selectedRows)">
批量转单
</el-button>
</template>
</CoilTable>
</div>
</el-tab-pane>
<el-tab-pane label="发货配卷" name="coil">
<div class="order-record" v-if="activeTab === 'coil'" v-loading="deliveryCoilLoading">
<!-- 发货配卷内容 -->
<CoilTable :data="deliveryCoilList || []"
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
@page-change="handleDeliveryPageChange" />
</div>
</el-tab-pane>
<el-tab-pane label="发货单据" name="delivery">
<div class="order-record" v-if="activeTab === 'delivery'">
<!-- 发货单内容 -->
<DeliveryTable :data="deliveryWaybillList || []" />
</div>
</el-tab-pane>
<el-tab-pane label="合同附件" name="attachment" v-hasPermi="['crm:order:record']">
<!-- Tab内容区域可滚动 -->
<div class="tab-body">
<!-- 合同信息 -->
<div v-show="activeTab === 'contract'">
<ContractPreview :contract="currentOrder" @updateStatus="$emit('updateStatus', $event)" />
</div>
<!-- 财务状态 -->
<div v-show="activeTab === 'finance'">
<ReceiveTable :order="currentOrder" />
</div>
<!-- 订单异议 -->
<div v-show="activeTab === 'dispute'">
<OrderObjection :order="currentOrder" />
</div>
<!-- 生产成果 -->
<div v-show="activeTab === 'product'" v-loading="productCoilLoading">
<CoilTable ref="productCoilTable" :data="productCoilList || []" :showSelection="true"
:pagination="productPagination" :total-statistics="productTotalStatistics"
@selection-change="handleProductSelectionChange"
@page-change="handleProductPageChange">
<template slot="filter-actions" slot-scope="{ selectedRows }">
<el-button type="primary" size="mini" icon="el-icon-refresh"
:disabled="!selectedRows.length" @click="handleBatchTransferContract(selectedRows)">
批量转单
</el-button>
</template>
</CoilTable>
</div>
<!-- 发货配卷 -->
<div v-show="activeTab === 'coil'" v-loading="deliveryCoilLoading">
<CoilTable :data="deliveryCoilList || []"
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
@page-change="handleDeliveryPageChange" />
</div>
<!-- 发货单据 -->
<div v-show="activeTab === 'delivery'">
<DeliveryTable :data="deliveryWaybillList || []" />
</div>
<!-- 合同附件 -->
<div v-show="activeTab === 'attachment'">
<div class="attachment-item">
<h4>商务附件</h4>
<FileList :oss-ids="contractAttachment" />
@@ -62,8 +102,8 @@
<h4>其他附件</h4>
<FileList :oss-ids="form.annexFiles" />
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 批量转单 - 选择目标合同弹窗 -->
<el-dialog title="批量转单" :visible.sync="batchTransferDialogVisible" width="500px" append-to-body
@@ -79,7 +119,7 @@
</span>
</el-dialog>
</div>
<div v-else class="no-selection" style="display: flex; align-items: center; justify-content: center; height: 100%;">
<div v-else class="no-selection">
<el-empty description="请先选择合同" />
</div>
</div>
@@ -91,6 +131,7 @@ import ContractSelect from "@/components/KLPService/ContractSelect";
import { batchUpdateContractCoil } from "@/api/wms/coil";
import FileList from "@/components/FileList";
import DeliveryTable from "../../components/DeliveryTable.vue";
import ContractPreview from "./ContractPreview.vue";
// 导入可能需要的组件
import OrderDetail from "../../components/OrderDetail.vue";
@@ -108,6 +149,7 @@ export default {
ContractSelect,
FileList,
DeliveryTable,
ContractPreview,
OrderDetail,
OrderEdit,
ReceiveTable,
@@ -153,7 +195,7 @@ export default {
data() {
return {
// 活动tab
activeTab: "finance",
activeTab: "contract",
selectedProductRows: [],
batchTransferDialogVisible: false,
batchTargetContractId: '',
@@ -304,4 +346,57 @@ export default {
},
}
};
</script>
</script>
<style scoped>
.contract-tabs {
height: 100%;
display: flex;
flex-direction: column;
}
.tabs-content {
height: 100%;
display: flex;
flex-direction: column;
}
.tab-header {
flex-shrink: 0;
display: flex;
border-bottom: 2px solid #e4e7ed;
background: #fff;
}
.tab-item {
padding: 10px 20px;
cursor: pointer;
color: #606266;
font-size: 14px;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: color .3s, border-color .3s;
user-select: none;
}
.tab-item:hover {
color: #409eff;
}
.tab-item.active {
color: #409eff;
border-bottom-color: #409eff;
}
.tab-body {
flex: 1;
overflow-y: auto;
}
.no-selection {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>

View File

@@ -11,24 +11,11 @@
<!-- 右侧内容区域 -->
<div class="right-panel" v-if="form.orderId" style="flex: 1; display: flex; flex-direction: column;">
<!-- 右侧上方合同内容信息预览 -->
<div class="preview-panel" ref="previewPanel"
style="flex: 1; overflow-y: auto; border-bottom: 1px solid #e4e7ed;">
<ContractPreview :contract="form" @updateStatus="handleStatusChange" />
</div>
<!-- 拖拽调整条 -->
<div class="resize-handle" ref="resizeHandle"
style="height: 8px; background-color: #f5f7fa; cursor: row-resize; display: flex; align-items: center; justify-content: center;"
@mousedown="startResize">
<div style="width: 40px; height: 2px; background-color: #dcdfe6;"></div>
</div>
<!-- 右侧下方Tab标签页 -->
<div class="tab-panel" ref="tabPanel" style="flex: 1; overflow-y: auto;">
<div class="tab-panel" style="flex: 1; overflow: hidden;">
<ContractTabs :orderId="form.orderId" :deliveryWaybillList="wmsDeliveryWaybills"
:contract-attachment="form.businessAnnex" :technical-agreement="form.techAnnex"
:other-attachment="form.productionSchedule" :currentOrder="form" />
:other-attachment="form.productionSchedule" :currentOrder="form"
@updateStatus="handleStatusChange" />
</div>
</div>
<div v-else style="flex: 1; display: flex; flex-direction: column;">
@@ -234,7 +221,6 @@ import { addCustomer } from "@/api/crm/customer";
import { listDeliveryWaybill } from "@/api/wms/deliveryWaybill";
import dayjs from "dayjs";
import ContractList from "./components/ContractList.vue";
import ContractPreview from "./components/ContractPreview.vue";
import ContractTabs from "./components/ContractTabs.vue";
import ContractExportDialog from "./components/ContractExportDialog.vue";
import ProductContent from "./components/ProductContent.vue";
@@ -246,7 +232,6 @@ export default {
name: "Contract",
components: {
ContractList,
ContractPreview,
ContractTabs,
ContractExportDialog,
ProductContent,
@@ -706,34 +691,6 @@ export default {
this.customerOpen = false;
this.customerForm = {};
},
/** 开始拖拽调整 */
startResize(e) {
e.preventDefault();
const previewPanel = this.$refs.previewPanel;
const tabPanel = this.$refs.tabPanel;
const startY = e.clientY;
const startPreviewHeight = previewPanel.offsetHeight;
const parentHeight = previewPanel.parentElement.offsetHeight;
const handleMouseMove = (event) => {
const deltaY = event.clientY - startY;
const newPreviewHeight = startPreviewHeight + deltaY;
const newTabHeight = parentHeight - newPreviewHeight - 8; // 8是拖拽条的高度
if (newPreviewHeight > 100 && newTabHeight > 100) { // 最小高度限制
previewPanel.style.flex = `0 0 ${newPreviewHeight}px`;
tabPanel.style.flex = `0 0 ${newTabHeight}px`;
}
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
}
};
</script>
@@ -756,11 +713,7 @@ export default {
overflow: hidden;
}
.preview-panel {
overflow-y: auto;
}
.tab-panel {
overflow-y: auto;
overflow: hidden;
}
</style>

View File

@@ -19,7 +19,7 @@
</p>
</div>
<statistic-group />
<quick-access-group />
</el-col>
<!-- 右栏 -->
@@ -37,10 +37,6 @@
</el-row>
</div>
<!-- <div>
<statistic-group />
</div> -->
<!-- <AllApplications />
<el-row :gutter="10">
<el-col :span="18">
@@ -54,7 +50,7 @@
</template>
<script>
import StatisticGroup from '@/components/HomeModules/StatisticGroup.vue'
import QuickAccessGroup from '@/components/HomeModules/QuickAccessGroup.vue'
// import AllApplications from '@/components/HomeModules/AllApplications.vue'
// import MiniCalendar from '@/components/HomeModules/MiniCalendar.vue'
@@ -62,7 +58,7 @@ export default {
name: 'Index',
components: {
// PanelGroup,
StatisticGroup,
QuickAccessGroup,
// AllApplications,
// MiniCalendar,
},
@@ -193,4 +189,4 @@ export default {
}
}
}
</style>
</style>

View File

@@ -344,27 +344,24 @@ export default {
loadMainDetails() {
if (this.mainList.length === 0) return
const promises = this.mainList.map(main => {
return listInspectionDetail({ mainId: main.mainId, pageNum: 1, pageSize: 9999 }).then(response => {
const details = response.rows || []
details.forEach(detail => {
if (main._items[detail.itemName]) {
main._items[detail.itemName] = {
detailId: detail.detailId,
mainId: detail.mainId,
itemName: detail.itemName,
itemValue: detail.itemValue || '',
itemUnit: detail.itemUnit || main._items[detail.itemName].itemUnit,
upperLimit: detail.upperLimit,
lowerLimit: detail.lowerLimit,
rangeDesc: detail.rangeDesc
}
this.mainList.forEach(main => {
const details = main.detailList || []
details.forEach(detail => {
if (main._items[detail.itemName]) {
main._items[detail.itemName] = {
detailId: detail.detailId,
mainId: detail.mainId,
itemName: detail.itemName,
itemValue: detail.itemValue || '',
itemUnit: detail.itemUnit || main._items[detail.itemName].itemUnit,
upperLimit: detail.upperLimit,
lowerLimit: detail.lowerLimit,
rangeDesc: detail.rangeDesc
}
})
main._originalItems = JSON.parse(JSON.stringify(main._items))
}).catch(() => {})
}
})
main._originalItems = JSON.parse(JSON.stringify(main._items))
})
return Promise.all(promises)
},
handleAddMain() {

View File

@@ -411,7 +411,7 @@ import { listWarehouse } from '@/api/wms/warehouse'
export default {
name: 'QualityReview',
components: { CoilSelector },
dicts: ['quality_review_status', 'regrade_quality_type', 'coil_quality_status'],
dicts: ['quality_review_status', 'coil_quality_status'],
data() {
return {
// 列表

View File

@@ -93,7 +93,7 @@ import { listQualityReview, approveQualityReview, rejectQualityReview, getQualit
export default {
name: 'QualityReviewTodo',
dicts: ['quality_review_status', 'regrade_quality_type', 'coil_quality_status'],
dicts: ['quality_review_status', 'coil_quality_status'],
data() {
return {
loading: false,

File diff suppressed because it is too large Load Diff

View File

@@ -54,6 +54,10 @@
<i class="el-icon-alarm-clock"></i>
<span slot="title">app</span>
</el-menu-item>
<el-menu-item index="model">
<i class="el-icon-coin"></i>
<span slot="title">模型</span>
</el-menu-item>
</el-menu>
</div>
<div style="flex: 1; overflow: hidden;">
@@ -76,6 +80,7 @@ import Stoppage from '@/views/timing/stoppage/index.vue';
import AcidTiming from '@/views/lines/acid/index.vue';
import TrackingView from './components/TrackingView.vue';
import ProcessSpec from './components/ProcessSpec.vue';
import Model from './components/Model.vue';
export default {
name: 'AcidSystem',
@@ -92,7 +97,8 @@ export default {
Stoppage,
AcidTiming,
TrackingView,
ProcessSpec
ProcessSpec,
Model,
},
data() {
return {
@@ -115,6 +121,7 @@ export default {
acidTiming: 'AcidTiming',
tracking: 'TrackingView',
processSpec: 'ProcessSpec',
model: 'Model',
};
return componentMap[this.activeMenu];
},

View File

@@ -83,7 +83,10 @@
</el-form-item>
<el-form-item label="业务员" prop="saleName">
<el-input v-model="queryParams.saleName" placeholder="请输入业务员" clearable size="small" />
<el-select v-model="queryParams.saleName" placeholder="请选择业务员" clearable size="small" filterable allow-create>
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="合同号" prop="contractNo">
@@ -997,7 +1000,7 @@ export default {
ContractSelect,
ExceptionManager,
},
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer', 'coil_quality_status', 'wms_next_warehouse', 'coil_business_purpose'],
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer', 'coil_quality_status', 'wms_next_warehouse', 'coil_business_purpose', 'wip_pack_saleman'],
props: {
qrcode: {
type: Boolean,

View File

@@ -2,11 +2,10 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="调拨单号" prop="transferNo">
<el-input v-model="queryParams.transferNo" placeholder="请输入调拨单号" clearable @keyup.enter.native="handleQuery" />
<MemoInput storageKey="transferNo" v-model="queryParams.transferNo" placeholder="请输入调拨单号" />
</el-form-item>
<el-form-item label="调拨单名称" prop="transferName">
<el-input v-model="queryParams.transferName" placeholder="请输入调拨单名称,用于帮助记忆与沟通" clearable
@keyup.enter.native="handleQuery" />
<MemoInput storageKey="transferName" v-model="queryParams.transferName" placeholder="请输入调拨单名称,用于帮助记忆与沟通" />
</el-form-item>
<el-form-item label="调拨时间" prop="transferTime">
<el-date-picker clearable v-model="queryParams.transferTime" type="date" value-format="yyyy-MM-dd"
@@ -41,7 +40,7 @@
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
@click="handleDelete">删除</el-button>
v-hasRole="['glc']" @click="handleDelete">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
@@ -80,8 +79,8 @@
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
:disabled="!canEdit(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
:disabled="!canDelete(scope.row)">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" v-hasRole="['glc']"
@click="handleDelete(scope.row)">删除</el-button>
<el-button size="mini" plain type="text" @click="handleSubmitapprove(scope.row)"
v-if="(scope.row.approveStatus || '0') == '0'" icon="el-icon-check">提交</el-button>
<el-button size="mini" plain type="text" @click="handleWithdrawapprove(scope.row)"
@@ -103,10 +102,10 @@
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="调拨单号" prop="transferNo">
<el-input v-model="form.transferNo" placeholder="请输入调拨单号" />
<MemoInput storageKey="transferNo" v-model="form.transferNo" placeholder="请输入调拨单号" />
</el-form-item>
<el-form-item label="调拨单名称" prop="transferName">
<el-input v-model="form.transferName" placeholder="请输入调拨单名称,用于帮助记忆与沟通" />
<MemoInput storageKey="transferName" v-model="form.transferName" placeholder="请输入调拨单名称,用于帮助记忆与沟通" />
</el-form-item>
<el-form-item label="调拨时间" prop="transferTime">
<el-date-picker clearable v-model="form.transferTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
@@ -297,12 +296,6 @@ export default {
const status = row.approveStatus || '0';
return status === '0' || status === '1' || status === '3';
},
/** 判断是否可以删除 */
canDelete(row) {
// 如果没有状态字段,则视为未提交
const status = row.approveStatus || '0';
return status === '0';
},
/** 提交审批 */
handleSubmitapprove(row) {
this.$confirm('确定要提交审批吗?', '提示', {
@@ -604,14 +597,10 @@ export default {
/** 删除按钮操作 */
handleDelete(row) {
const orderIds = row.orderId || this.ids;
this.$modal.confirm('是否确认删除调拨单主编号为"' + orderIds + '"的数据项?').then(() => {
this.loading = true;
return delTransferOrder(orderIds);
}).then(() => {
this.loading = false;
this.loading = true;
delTransferOrder(orderIds).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
}).finally(() => {
this.loading = false;
});

View File

@@ -167,45 +167,29 @@ export default {
if (newVal !== oldVal) {
// 预处理数据
this.tableData = newVal.map(item => {
const isEmpty = this.isEmpty(item);
if (isEmpty) {
return {
...item,
oBefore: {
manufacturer: item.manufacturerBefore,
specification: item.specificationBefore,
material: item.materialBefore,
itemName: item.materialNameBefore,
surfaceTreatmentDesc: item.surfaceTreatmentBefore,
zincLayer: item.zincLayerBefore
},
oAfter: {
manufacturer: item.manufacturerAfter,
specification: item.specificationAfter,
material: item.materialAfter,
itemName: item.materialNameAfter,
surfaceTreatmentDesc: item.surfaceTreatmentAfter,
zincLayer: item.zincLayerAfter
},
isConfirmed: this.isConfirmed(item)
}
} else {
return {
...item,
oBefore: {
manufacturer: item.manufacturerBefore,
specification: item.specificationBefore,
material: item.materialBefore,
itemName: item.materialNameBefore,
surfaceTreatmentDesc: item.surfaceTreatmentBefore,
zincLayer: item.zincLayerBefore
},
// 将三个after字段设置为与before相同
itemIdAfter: item.itemIdBefore,
warehouseIdAfter: item.warehouseIdBefore,
materialTypeAfter: item.materialTypeBefore,
isConfirmed: this.isConfirmed(item)
}
return {
...item,
oBefore: {
manufacturer: item.manufacturerBefore,
specification: item.specificationBefore,
material: item.materialBefore,
itemName: item.materialNameBefore,
surfaceTreatmentDesc: item.surfaceTreatmentBefore,
zincLayer: item.zincLayerBefore
},
oAfter: {
manufacturer: item.manufacturerAfter ?? item.manufacturerBefore,
specification: item.specificationAfter ?? item.specificationBefore,
material: item.materialAfter ?? item.materialBefore,
itemName: item.materialNameAfter ?? item.materialNameBefore,
surfaceTreatmentDesc: item.surfaceTreatmentAfter ?? item.surfaceTreatmentBefore,
zincLayer: item.zincLayerAfter ?? item.zincLayerBefore
},
// 每个字段独立判断如果after有值则用after否则回退到before
itemIdAfter: item.itemIdAfter ?? item.itemIdBefore,
warehouseIdAfter: item.warehouseIdAfter ?? item.warehouseIdBefore,
materialTypeAfter: item.materialTypeAfter ?? item.materialTypeBefore,
isConfirmed: this.isConfirmed(item)
}
});
}
@@ -226,10 +210,6 @@ export default {
// return item && item.itemIdAfter && item.warehouseIdAfter && item.materialTypeAfter
return item.isTransferred == 1
},
// isEmpty 是否为空
isEmpty(item) {
return item && item.itemIdAfter && item.warehouseIdAfter && item.materialTypeAfter
},
handleUpdate(row) {
updateTransferOrderItem(row).then(res => {
this.$message({

View File

@@ -17,11 +17,11 @@
</WarehouseSelect>
</el-form-item>
<el-form-item label="调拨单号">
<el-select v-model="queryParams.transferNo" filterable remote clearable
<el-select v-model="queryParams.transferId" filterable remote clearable
remote-method="remoteSearchTransferOrder" :remote-loading="transferOrderLoading" placeholder="请输入调拨单号搜索"
@change="handleRegularSearch" style="width:220px">
<el-option v-for="item in transferOrderOptions" :key="item.orderId" :label="item.transferNo"
:value="item.transferNo" />
:value="item.orderId" />
</el-select>
</el-form-item>
<el-form-item label="生效状态">
@@ -32,7 +32,7 @@
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" plain @click="handleRegularSearch">查询</el-button>
<el-button type="success" icon="el-icon-check" plain @click="handleConfirmBatch">批量执行</el-button>
<el-button type="success" icon="el-icon-check" plain :loading="batchConfirmLoading" @click="handleConfirmBatch">批量执行</el-button>
<el-button @click="handleReset">清空</el-button>
</el-form-item>
</el-form>
@@ -46,7 +46,7 @@
<!-- 调拨表格, 显示调拨记录, 常规筛选作用下生效 -->
<TransferItemTable :data="transferList" :canEdit="false" :canConfirm="true" v-loading="loading"
ref="transferItemTable" :refreshLabel="refreshLabel" :transferRemark="transferRemark"
@refreshData="handleRegularSearch"></TransferItemTable>
@refreshData="handleBatchConfirmDone"></TransferItemTable>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="handleRegularSearch" />
@@ -103,7 +103,7 @@ export default {
currentCoilNo: '',
warehouseIdBefore: '',
warehouseIdAfter: '',
transferNo: '',
transferId: '',
isTransferred: '0',
pageSize: 20,
pageNum: 1,
@@ -114,6 +114,7 @@ export default {
transferOrderOptions: [],
transferOrderLoading: false,
loading: false,
batchConfirmLoading: false,
total: 0,
dialogVisible: false,
};
@@ -171,7 +172,7 @@ export default {
currentCoilNo: '',
warehouseIdBefore: '',
warehouseIdAfter: '',
transferNo: '',
transferId: '',
isTransferred: '0',
pageSize: 20,
pageNum: 1,
@@ -192,7 +193,18 @@ export default {
}
},
handleConfirmBatch() {
this.$refs.transferItemTable.handleBatchConfirm();
this.$confirm('确定要批量执行调拨吗?', '确认操作', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.batchConfirmLoading = true;
this.$refs.transferItemTable.handleBatchConfirm();
}).catch(() => {});
},
handleBatchConfirmDone() {
this.batchConfirmLoading = false;
this.handleRegularSearch();
},
},
mounted() {

View File

@@ -30,11 +30,11 @@
</WarehouseSelect>
</el-form-item>
<el-form-item label="调拨单号">
<el-select v-model="queryParams.transferNo" filterable remote clearable
<el-select v-model="queryParams.transferId" filterable remote clearable
remote-method="remoteSearchTransferOrder" :remote-loading="transferOrderLoading"
placeholder="请输入调拨单号搜索" @change="handleRegularSearch" style="width:220px">
<el-option v-for="item in transferOrderOptions" :key="item.orderId"
:label="item.transferNo" :value="item.transferNo" />
:label="item.transferNo" :value="item.orderId" />
</el-select>
</el-form-item>
<el-form-item label="生效状态">
@@ -105,7 +105,7 @@ export default {
currentCoilNo: '',
warehouseIdBefore: '',
warehouseIdAfter: '',
transferNo: '',
transferId: '',
isTransferred: '',
pageSize: 20,
pageNum: 1,
@@ -173,7 +173,7 @@ export default {
currentCoilNo: '',
warehouseIdBefore: '',
warehouseIdAfter: '',
transferNo: '',
transferId: '',
isTransferred: '',
pageSize: 20,
pageNum: 1,

View File

@@ -58,6 +58,8 @@
<el-dropdown-item command="exportDataMore">导出产出钢卷(多行)</el-dropdown-item>
<el-dropdown-item command="exportLossDataMore">导出消耗钢卷(多行)</el-dropdown-item>
<el-dropdown-item command="exportDataMerged" divided>导出产出钢卷(多行合并)</el-dropdown-item>
<el-dropdown-item command="exportLossDataMerged">导出消耗钢卷(多行合并)</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-form-item>
@@ -144,6 +146,11 @@ import CoilTable from "@/views/wms/report/components/coilTable";
import ColumnsSetting from "@/views/wms/report/components/setting/columns";
import TimeRangePicker from "@/views/wms/report/components/timeRangePicker.vue";
import { saveReportFile } from "@/views/wms/report/js/reportFile";
import { mergeCoilCells } from "@/views/wms/report/js/mergeExcelCells";
import { saveAs } from 'file-saver'
import request from '@/utils/request'
import { tansParams } from "@/utils/klp";
import { Loading } from 'element-ui'
export default {
name: 'MergeTemplate',
@@ -362,6 +369,54 @@ export default {
abnormalExportCount: 0
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
// 导出产出钢卷(多行合并) — 二次合并钢卷列单元格
async exportDataMerged() {
if (this.outList.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
await this.exportWithMerge('wms/materialCoil/exportAbnormal', {
coilIds: this.outList.map(item => item.coilId).join(','),
abnormalExportCount: 0
}, `abnormalCoil_${new Date().getTime()}.xlsx`)
},
// 导出消耗钢卷(多行合并) — 二次合并钢卷列单元格
async exportLossDataMerged() {
if (this.lossList.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
await this.exportWithMerge('wms/materialCoil/export', {
coilIds: this.lossList.map(item => item.coilId).join(','),
abnormalExportCount: 0
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
// 通用:导出多行 → ExcelJS 二次合并钢卷列 → 保存
async exportWithMerge(url, params, filename) {
const loading = Loading.service({
text: '正在导出并合并单元格,请稍候',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
const data = await request.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob'
})
// data 是拦截器返回的 res.dataBlob与 download 函数一致
const blob = new Blob([data])
const mergedBlob = await mergeCoilCells(blob, 26)
saveAs(mergedBlob, filename)
} catch (e) {
console.error('导出合并失败', e)
this.$message.error('导出失败,请联系管理员')
} finally {
loading.close()
}
},
// 处理命令
handleCommand(command) {
if (command === 'exportData') {
@@ -376,6 +431,10 @@ export default {
this.exportDataMore()
} else if (command === 'exportLossDataMore') {
this.exportLossDataMore()
} else if (command === 'exportDataMerged') {
this.exportDataMerged()
} else if (command === 'exportLossDataMerged') {
this.exportLossDataMerged()
}
},
// 加载列设置

View File

@@ -0,0 +1,74 @@
import ExcelJS from 'exceljs'
/**
* 对异常钢卷报表导出的 Excel 进行二次合并处理
* 将钢卷信息列前26列A-Z中连续相同值的单元格合并
*
* 原理:
* 1. 用 row.values 数组代替逐格 getCell避免 9万+ 次对象创建
* 2. 合并单元格后用列级别样式统一居中,代替逐格设置
*
* @param {Blob} blob - 原始 Excel 文件 Blob
* @param {number} mergeColumnCount - 需要合并的钢卷列数,默认 26A-Z列
* @returns {Promise<Blob>} - 合并后的新 Blob
*/
export async function mergeCoilCells(blob, mergeColumnCount = 26) {
const workbook = new ExcelJS.Workbook()
const arrayBuffer = await blob.arrayBuffer()
await workbook.xlsx.load(arrayBuffer)
const worksheet = workbook.worksheets[0]
const rowCount = worksheet.rowCount
if (rowCount <= 1) return blob
// === 第一步:按钢卷身份标识分组 ===
// 使用 row.values 数组1-based字符拼接一次性比较避免逐格 getCell
const groups = []
let groupStart = 2
let prevFingerprint = rowFingerprint(worksheet.getRow(2), mergeColumnCount)
for (let row = 3; row <= rowCount; row++) {
const curFingerprint = rowFingerprint(worksheet.getRow(row), mergeColumnCount)
if (curFingerprint !== prevFingerprint) {
groups.push({ start: groupStart, end: row - 1 })
groupStart = row
prevFingerprint = curFingerprint
}
}
groups.push({ start: groupStart, end: rowCount })
// === 第二步:合并单元格 ===
for (const group of groups) {
if (group.end > group.start) {
for (let col = 1; col <= mergeColumnCount; col++) {
worksheet.mergeCells(group.start, col, group.end, col)
}
}
}
// === 第三步列级别统一居中替换逐格设置26 次 vs 9万次 ===
for (let col = 1; col <= mergeColumnCount; col++) {
const column = worksheet.getColumn(col)
column.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }
}
// === 第四步:写回 Blob ===
const buffer = await workbook.xlsx.writeBuffer()
return new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
}
/**
* 生成行的"指纹"字符串,用于快速比较两行钢卷列是否相同
* row.values 是 ExcelJS 的 1-based 数组values[1]=A列, values[2]=B列 ...
*/
function rowFingerprint(row, colCount) {
const vals = row.values || []
let fp = ''
for (let i = 1; i <= colCount; i++) {
fp += String(vals[i] ?? '') + '\x00'
}
return fp
}