2 Commits

  1. 4
      src/api/qc/qc.js
  2. 4
      src/views/modules/inspection/com_inspectionRequestAttachmentTab.vue
  3. 65
      src/views/modules/inspection/inspectionPendingList.vue
  4. 59
      src/views/modules/inspection/inspectionRequestAudit.vue
  5. 443
      src/views/modules/inspection/inspectionRequestList.vue
  6. 35
      src/views/modules/inspection/inspectionRequestListSupplier.vue
  7. 22
      src/views/modules/inspection/myTodoList.vue
  8. 153
      src/views/modules/order/poOrder.vue
  9. 356
      src/views/modules/qc/qcPartAttribute.vue

4
src/api/qc/qc.js

@ -87,6 +87,10 @@ export const qcAttributeTemplateItemDetailsSearch = data => createAPI(`/qms/qc/q
export const qcAttributeTemplateItemSave = data => createAPI(`/qms/qc/qcAttributeTemplateItemSave`,'post',data)
export const qcPartAttributeEdit = data => createAPI(`/qms/qc/qcPartAttributeEdit`,'post',data)
export const overLoadSearchPartInfo = data => createAPI(`/qms/qc/overLoadSearchPartInfo`,'post',data)
// 物料模板复制:候选物料查询(status='Y')、冲突校验、执行复制 - rqrq
export const listPartAttributeTemplateCopyTargetParts = data => createAPI(`/qms/qc/listPartAttributeTemplateCopyTargetParts`,'post',data)
export const checkPartAttributeTemplateCopyConflicts = data => createAPI(`/qms/qc/checkPartAttributeTemplateCopyConflicts`,'post',data)
export const copyPartAttributeTemplateToParts = data => createAPI(`/qms/qc/copyPartAttributeTemplateToParts`,'post',data)
// ===================================== 类别属性设置 =====================================
export const qcFamilyAttributeSearch = data => createAPI(`/qms/qc/qcFamilyAttributeSearch`,'post',data)

4
src/views/modules/inspection/com_inspectionRequestAttachmentTab.vue

@ -1,5 +1,5 @@
<template>
<div class="customer-css">
<div class="fee-request-tab">
<!-- 操作按钮 -->
<el-form :inline="true" style="margin-bottom: 2px;">
<el-button type="primary" icon="el-icon-upload" @click="handleUpload" :disabled="!searchData.requestNo">上传附件</el-button>
@ -8,7 +8,7 @@
<!-- 附件文件列表表格 -->
<el-table
:data="fileList"
:height="tableHeight"
:height="tableHeight-20"
border
v-loading="loading"
style="width: 100%;">

65
src/views/modules/inspection/inspectionPendingList.vue

@ -112,23 +112,33 @@
<el-tabs v-model="activeTab" style="margin-top: 0px; width: 99%;" @tab-click="handleTabClick" class="customer-tab" type="border-card">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="base">
<inspection-request-detail :detail-data="currentRow" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-detail :detail-data="currentRow" />
</div>
</el-tab-pane>
<!-- 验货明细 -->
<el-tab-pane label="验货明细" name="detail">
<inspection-request-detail-tab ref="inspectionDetailTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-detail-tab ref="inspectionDetailTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 验货关联PO明细 -->
<el-tab-pane label="验货关联PO明细" name="poDetail">
<inspection-request-po-detail-tab ref="poDetailTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-po-detail-tab ref="poDetailTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 验货结果 -->
<el-tab-pane label="验货结果" name="result">
<inspection-result-tab ref="resultTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-result-tab ref="resultTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 附件管理 -->
<el-tab-pane label="附件管理" name="attachment">
<inspection-request-attachment-tab ref="attachmentTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-attachment-tab ref="attachmentTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
</el-tabs>
</div>
@ -166,7 +176,7 @@ export default {
requestNo: '',
supplierNo: '',
supplierName: '',
status: 'NON_DRAFT', // 稿
status: 'Scheduled', // 稿
qcOperator: '', // QC
createBy: '',
requestDateStart: '',
@ -453,6 +463,38 @@ export default {
immediate: false
}
},
computed: {
/**
* 统一页签内容容器高度单位px
* 业务意图
* - 待验货页面各页签保持统一高度减少切换时的视觉抖动
* - 兜底异常高度确保页面在极端情况下仍可正常展示内容
*/
normalizedTabPaneHeight () {
const rawHeight = Number(this.detailHeight)
if (!rawHeight || rawHeight <= 0) {
return 300
}
return Math.floor(rawHeight)
},
/**
* 页签内容统一样式
* - 固定高度
* - 超出内容在页签内部滚动不影响外层列表和分页布局
*/
tabPaneContentStyle () {
return {
height: `${this.normalizedTabPaneHeight}px`
}
},
/**
* 表格型页签高度单位px
* 预留少量内边距并设置最小值保证表格在不同分辨率下稳定显示
*/
tabTableHeight () {
return Math.max(this.normalizedTabPaneHeight - 10, 120)
}
},
mounted () {
this.$nextTick(() => {
//
@ -689,6 +731,17 @@ export default {
font-size: 13px;
}
/**
* 验货申请待验货页签统一高度容器
* 1) 固定高度避免切换不同页签时出现高度跳变
* 2) 内容超高时启用内部滚动不影响外围区域布局稳定性
*/
.tab-pane-content {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
/deep/ .customer-tab .el-tabs__content {
padding: 5px !important;
}

59
src/views/modules/inspection/inspectionRequestAudit.vue

@ -95,19 +95,27 @@
<el-tabs v-model="activeTab" style="margin-top: 0px; width: 99%;" @tab-click="handleTabClick" class="customer-tab" type="border-card">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="base">
<inspection-request-detail :detail-data="currentRow" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-detail :detail-data="currentRow" />
</div>
</el-tab-pane>
<!-- 验货明细 -->
<el-tab-pane label="验货明细" name="detail">
<inspection-request-detail-tab ref="inspectionDetailTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-detail-tab ref="inspectionDetailTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 验货关联PO明细 -->
<el-tab-pane label="验货关联PO明细" name="poDetail">
<inspection-request-po-detail-tab ref="poDetailTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-po-detail-tab ref="poDetailTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 验货结果 -->
<el-tab-pane label="验货结果" name="result">
<inspection-result-tab ref="resultTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-result-tab ref="resultTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
</el-tabs>
</div>
@ -418,6 +426,38 @@ export default {
]
}
},
computed: {
/**
* 统一页签内容容器高度单位px
* 业务意图
* - 待审核页签切换时保持一致高度避免基本信息和表格页签来回跳动
* - 当窗口高度计算异常时兜底为可用高度防止内容区域塌陷
*/
normalizedTabPaneHeight () {
const rawHeight = Number(this.detailHeight)
if (!rawHeight || rawHeight <= 0) {
return 300
}
return Math.floor(rawHeight)
},
/**
* 统一页签内容样式
* - 固定高度保证各页签视觉一致
* - 内容超出时内部滚动避免影响主页面布局
*/
tabPaneContentStyle () {
return {
height: `${this.normalizedTabPaneHeight}px`
}
},
/**
* 表格型页签高度
* 预留少量底部空间避免贴边并设置最小高度防止极端窗口尺寸下显示异常
*/
tabTableHeight () {
return Math.max(this.normalizedTabPaneHeight - 10, 120)
}
},
mounted () {
this.$nextTick(() => {
//
@ -665,6 +705,17 @@ export default {
font-size: 13px;
}
/**
* 验货申请待审核页签统一高度容器
* 1) 固定高度保证所有页签切换后保持同一视觉占位
* 2) 内部滚动避免长内容把父级区域撑高造成页面抖动
*/
.tab-pane-content {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
/deep/ .customer-tab .el-tabs__content {
padding: 5px !important;
}

443
src/views/modules/inspection/inspectionRequestList.vue

@ -166,27 +166,39 @@
<el-tabs v-model="activeTab" style="margin-top: 0px; width: 99%;" @tab-click="handleTabClick" class="customer-tab" type="border-card">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="base">
<inspection-request-detail ref="inspectionRequestDetail" :detail-data="currentRow" @update-success="getMainData"/>
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-detail ref="inspectionRequestDetail" :detail-data="currentRow" @update-success="getMainData"/>
</div>
</el-tab-pane>
<!-- 验货明细 -->
<el-tab-pane label="验货明细" name="detail">
<inspection-request-detail-tab ref="inspectionDetailTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-detail-tab ref="inspectionDetailTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 验货关联PO明细 -->
<el-tab-pane label="验货关联PO明细" name="poDetail">
<inspection-request-po-detail-tab ref="poDetailTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-po-detail-tab ref="poDetailTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 验货结果 -->
<el-tab-pane label="验货结果" name="result">
<inspection-result-tab ref="resultTab" :detail-data="currentRow" :table-height="detailHeight" />
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-result-tab ref="resultTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 费用明细 -->
<el-tab-pane label="费用明细" name="fee">
<inspection-request-fee-tab ref="feeTab" :detail-data="currentRow" :table-height="detailHeight" />
<!-- 费用明细供应商专用页不展示 -->
<el-tab-pane v-if="!isSupplierPageMode" label="费用明细" name="fee">
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-fee-tab ref="feeTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
<!-- 附件管理 -->
<el-tab-pane label="附件管理" name="attachment">
<inspection-request-attachment-tab ref="attachmentTab" :detail-data="currentRow" :table-height="detailHeight" />
<!-- 附件管理供应商专用页不展示 -->
<el-tab-pane v-if="!isSupplierPageMode" label="附件管理" name="attachment">
<div class="tab-pane-content" :style="tabPaneContentStyle">
<inspection-request-attachment-tab ref="attachmentTab" :detail-data="currentRow" :table-height="tabTableHeight" />
</div>
</el-tab-pane>
</el-tabs>
@ -519,6 +531,13 @@
<el-form-item label="供应商名称">
<el-input v-model="addFormData.supplierName" readonly style="width: 220px" />
</el-form-item>
<el-form-item label=" ">
<el-button
type="primary"
size="medium"
style="margin-top: 0px"
@click="openAddressCreateDialog">新增地址</el-button>
</el-form-item>
<!-- <el-form-item label="地址关键词">-->
<!-- <el-input-->
<!-- v-model="addressSearch.keyword"-->
@ -567,6 +586,125 @@
</div>
</el-dialog>
<!-- 新增供应商地址与供应商主数据地址页签新增弹窗保持一致 -->
<el-dialog
title="供应商地址"
:close-on-click-modal="false"
v-drag
:visible.sync="addressCreateDialogVisible"
@closed="handleAddressCreateDialogClosed"
width="600px"
append-to-body>
<el-form
ref="addressCreateFormRef"
:model="addressCreateForm"
:rules="addressCreateRules"
label-position="top"
label-width="100px">
<!-- 供应商ID / 城市简称 / 是否在用 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="供应商ID" prop="supplierId">
<el-input v-model="addressCreateForm.supplierId" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="所在城市简称" prop="cityShort">
<el-input v-model="addressCreateForm.cityShort" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="是否在用" prop="active">
<el-select v-model="addressCreateForm.active" style="width: 100%">
<el-option label="Y" value="Y" />
<el-option label="N" value="N" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 公司名称 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="公司名称" prop="companyName">
<el-input v-model="addressCreateForm.companyName" />
</el-form-item>
</el-col>
</el-row>
<!-- 联系人 / 电话 / 地址类型 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="联系人" prop="contact">
<el-input v-model="addressCreateForm.contact" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="联系电话" prop="phoneNo">
<el-input v-model="addressCreateForm.phoneNo" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="地址类型" prop="addressType">
<sys-dict-select
v-model="addressCreateForm.addressType"
dict-type="address_type"
placeholder="请选择地址类型"
width="100%"
filterable
/>
</el-form-item>
</el-col>
</el-row>
<!-- 邮箱 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="邮箱" prop="email">
<el-input v-model="addressCreateForm.email" />
</el-form-item>
</el-col>
</el-row>
<!-- 原产国 / 装运港 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="原产国" prop="originCountry">
<el-input v-model="addressCreateForm.originCountry" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="portOfLoading">
<span slot="label" style="cursor: pointer" @click="getBaseList(2018, 'addressPortOfLoading')"><a href="#">装运港</a></span>
<el-input v-model="addressCreateForm.portOfLoading" readonly />
</el-form-item>
</el-col>
</el-row>
<!-- 地址 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="地址" prop="address">
<el-input v-model="addressCreateForm.address" />
</el-form-item>
</el-col>
</el-row>
<!-- 备注 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="memo">
<el-input v-model="addressCreateForm.memo" type="textarea" :rows="1" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer" style="margin-top: 10px; display: block">
<el-button type="primary" :loading="addressCreateSaving" @click="saveCreatedAddress">保存</el-button>
<el-button :disabled="addressCreateSaving" @click="addressCreateDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog
title="从订单选择供应商"
:visible.sync="orderSupplierDialogVisible"
@ -722,8 +860,9 @@
<script>
import { searchInspectionRequestHeaderList, queryPoPage, queryPoSupplierPage, queryPoSupplierOrderPage, saveInspectionRequest, deleteInspectionRequest, confirmInspectionRequest, applyChangeInspectionRequest, getInspectionRequestDetailSubList, updateInspectionRequest, downloadTemplate, previewUpload, batchSave } from '@/api/inspection/inspectionRequestHeader.js'
import { searchSrmSupplierAddressList } from '@/api/srm/srmSupplier.js'
import { searchSrmSupplierAddressList, createSrmSupplierAddress, searchSrmSupplierList } from '@/api/srm/srmSupplier.js'
import Chooselist from '@/views/modules/common/Chooselist_eam'
import SysDictSelect from '@/components/dict/SysDictSelect.vue'
import ComInspectionRequestDetail from './com_inspectionRequestDetail.vue'
import ComInspectionRequestDetailTab from './com_inspectionRequestDetailTab.vue'
import ComInspectionRequestPoDetailTab from './com_inspectionRequestPoDetailTab.vue'
@ -735,6 +874,17 @@ import excel from '@/utils/excel-util.js'
export default {
props: {
/**
* 供应商专用模式开关
* - false通用验货申请页默认内部人员场景
* - true 供应商专用页 inspectionRequestListSupplier.vue 传入
*
* 打开后会触发
* 1) 查询条件精简
* 2) 查询强制按当前登录用户绑定供应商编码过滤
* 3) 新增时供应商固定为当前登录用户供应商
* 4) 隐藏从订单选择供应商入口及部分PO筛选项
*/
supplierMode: {
type: Boolean,
default: false
@ -742,6 +892,7 @@ export default {
},
components: {
Chooselist,
SysDictSelect,
InspectionRequestDetail: ComInspectionRequestDetail,
InspectionRequestDetailTab: ComInspectionRequestDetailTab,
InspectionRequestPoDetailTab: ComInspectionRequestPoDetailTab,
@ -757,6 +908,7 @@ export default {
currentRow: {},
tagNo: '',
searchType: '',
// /sys/user/infouser.supplierNo / user.supplierName
currentSupplierNo: '',
currentSupplierName: '',
activeTab: 'base',
@ -825,6 +977,53 @@ export default {
addressSearch: {
keyword: ''
},
//
addressCreateDialogVisible: false,
addressCreateSaving: false,
addressCreateForm: {
supplierId: '',
site: this.$store.state.user.site || this.$store.state.user.currentSite || '',
addressId: '',
companyName: '',
cityShort: '',
contact: '',
phoneNo: '',
address: '',
addressType: '验货地址',
active: 'Y',
email: '',
originCountry: '',
portOfLoading: '',
memo: '',
createBy: this.$store.state.user.name,
updateBy: this.$store.state.user.name
},
addressCreateRules: {
supplierId: [
{ required: true, validator: this.validateAddressCreateField, trigger: 'blur' }
],
site: [
{ required: true, validator: this.validateAddressCreateField, trigger: 'blur' }
],
cityShort: [
{ required: true, validator: this.validateAddressCreateField, message: ' ', trigger: 'blur' }
],
address: [
{ required: true, validator: this.validateAddressCreateField, message: ' ', trigger: 'blur' }
],
companyName: [
{ required: true, validator: this.validateAddressCreateField, message: ' ', trigger: 'blur' }
],
contact: [
{ required: true, validator: this.validateAddressCreateField, message: ' ', trigger: 'blur' }
],
phoneNo: [
{ required: true, validator: this.validateAddressCreateField, message: ' ', trigger: 'blur' }
],
addressType: [
{ required: true, validator: this.validateAddressCreateField, message: ' ', trigger: 'change' }
]
},
applyChangeDialogVisible: false,
applyChangeLoading: false,
applyChangeForm: {
@ -1139,20 +1338,59 @@ export default {
isEditDialogMode () {
return this.addDialogMode === 'edit'
},
// this.supplierMode === true
isSupplierPageMode () {
return this.supplierMode === true
},
/**
* 统一页签内容容器高度单位px
* 业务意图
* - 验货申请各页签切换时保持同一视觉高度避免忽高忽低导致页面跳动
* - 当窗口高度异常或未初始化时提供安全兜底避免出现 0 高度导致内容不可见
*/
normalizedTabPaneHeight () {
const rawHeight = Number(this.detailHeight)
if (!rawHeight || rawHeight <= 0) {
return 300
}
return Math.floor(rawHeight)
},
/**
* 页签内容区内联样式供所有 tab pane 复用
* 关键输出
* - 固定高度所有页签保持一致
* - 自动滚动当单个页签内容超出时在页签内部滚动不撑高外层布局
*/
tabPaneContentStyle () {
return {
height: `${this.normalizedTabPaneHeight}px`
}
},
/**
* 表格类页签统一高度单位px
* 设计说明
* - 预留少量内边距避免表格底部与容器边框贴边
* - 设置最小值防止极端窗口尺寸下表格渲染异常
*/
tabTableHeight () {
return Math.max(this.normalizedTabPaneHeight - 10, 120)
}
},
mounted () {
this.$nextTick(() => {
//
const calculatedHeight = (window.innerHeight - 220) / 2
const calculatedHeight = (window.innerHeight - 250) / 2
this.height = calculatedHeight
this.detailHeight = calculatedHeight
this.initPageData()
})
},
methods: {
/**
* 页面初始化入口
* - 通用模式直接查询
* - 供应商模式先加载当前用户供应商信息再按供应商过滤查询
*/
async initPageData () {
if (!this.isSupplierPageMode) {
this.getMainData()
@ -1167,6 +1405,13 @@ export default {
this.pageIndex = 1
this.getMainData()
},
/**
* 读取当前登录用户绑定的供应商信息
* 数据来源GET /sys/user/info
* 结果用途
* - currentSupplierNo查询强制过滤 + 新增固定回填
* - currentSupplierName新增供应商名称展示
*/
async loadCurrentUserSupplierContext () {
try {
const { data } = await this.$http({
@ -1199,6 +1444,10 @@ export default {
return false
}
},
/**
* 将当前用户供应商写入查询条件
* 说明supplierName 这里清空避免名称模糊匹配干扰 supplierNo 精准过滤
*/
applyCurrentSupplierToSearchFilters () {
if (!this.currentSupplierNo) {
return false
@ -1207,6 +1456,9 @@ export default {
this.searchData.supplierName = ''
return true
},
/**
* 将当前用户供应商写入新增表单只读展示
*/
applyCurrentSupplierToAddForm () {
if (!this.currentSupplierNo) {
return false
@ -1293,6 +1545,12 @@ export default {
// Druid where 1=1
conSql = " and username <> '' "
}
if (val === 2018) {
if (type === 'addressPortOfLoading') {
strVal = this.addressCreateForm.portOfLoading || ''
}
conSql = " and port_name <> '' "
}
this.$refs.baseList.init(val, strVal, conSql)
})
},
@ -1346,6 +1604,11 @@ export default {
this.orderSupplierSearch.planner = plannerVal
}
}
if (this.tagNo === 2018) {
if (this.searchType === 'addressPortOfLoading') {
this.addressCreateForm.portOfLoading = val.port_name || val.code || ''
}
}
},
//
@ -1539,6 +1802,9 @@ export default {
this.addressSearch = {
keyword: ''
}
this.addressCreateDialogVisible = false
this.addressCreateSaving = false
this.addressCreateForm = this.buildAddressCreateFormBySupplier('')
this.orderSupplierDialogVisible = false
this.orderSupplierLoading = false
this.orderSupplierList = []
@ -1643,6 +1909,147 @@ export default {
this.$refs.baseList.init(520, this.addFormData.supplierNo || '', conSql)
})
},
/**
* 新增供应商地址弹窗字段校验与供应商地址页签新增逻辑保持一致
* 规则必填字段不能为空字符串仅空格也视为无效
*/
validateAddressCreateField (rule, value, callback) {
const normalizedValue = value === null || value === undefined ? '' : String(value).trim()
if (!normalizedValue) {
callback(new Error())
return
}
callback()
},
/**
* 构建新增供应商地址弹窗的默认表单对象
* supplierId 固定使用当前验货申请选中的供应商编码避免录错供应商
*/
buildAddressCreateFormBySupplier (supplierId) {
const currentSite = String(this.$store.state.user.site || this.$store.state.user.currentSite || '').trim()
return {
supplierId: supplierId || '',
site: currentSite,
addressId: '',
companyName: '',
cityShort: '',
contact: '',
phoneNo: '',
address: '',
addressType: '验货地址',
active: 'Y',
email: '',
originCountry: '',
portOfLoading: '',
memo: '',
createBy: this.$store.state.user.name,
updateBy: this.$store.state.user.name
}
},
/**
* 选择验货地址弹窗内快速新增供应商地址
* 界面字段与 /srmSupplier-supplierList 地址页签新增弹窗保持一致
*/
openAddressCreateDialog () {
const supplierNo = (this.addFormData.supplierNo || '').trim()
if (!supplierNo) {
this.$message.warning('请先选择供应商')
return
}
this.addressCreateForm = this.buildAddressCreateFormBySupplier(supplierNo)
this.addressCreateDialogVisible = true
this.$nextTick(() => {
if (this.$refs.addressCreateFormRef) {
this.$refs.addressCreateFormRef.clearValidate()
}
})
this.fetchSupplierInfoForAddressCreate(supplierNo)
},
/**
* 根据供应商编码回填默认信息公司名称/联系人/电话/邮箱/地址
* 仅在新增地址弹窗中用于提高录入效率不覆盖用户已输入内容
*/
fetchSupplierInfoForAddressCreate (supplierNo) {
const normalizedSupplierNo = String(supplierNo || '').trim()
const currentSite = String(this.$store.state.user.site || this.$store.state.user.currentSite || '').trim()
if (!normalizedSupplierNo || !currentSite) {
return
}
searchSrmSupplierList({
site: currentSite,
supplierNo: normalizedSupplierNo,
page: 1,
limit: 1
}).then(({ data }) => {
if (!(data && data.code === 0)) {
return
}
const pageData = data.data || data.page
const supplierList = pageData && pageData.list ? pageData.list : (Array.isArray(pageData) ? pageData : [])
if (!supplierList || supplierList.length === 0) {
return
}
const supplierData = supplierList[0] || {}
this.addressCreateForm.companyName = supplierData.supplierName || supplierData.companyName || this.addressCreateForm.companyName || ''
if (!this.addressCreateForm.contact) {
this.addressCreateForm.contact = supplierData.contact || ''
}
if (!this.addressCreateForm.phoneNo) {
this.addressCreateForm.phoneNo = supplierData.phoneNo || supplierData.phoneNo1 || ''
}
if (!this.addressCreateForm.email) {
this.addressCreateForm.email = supplierData.email || supplierData.email1 || ''
}
if (!this.addressCreateForm.address) {
this.addressCreateForm.address = supplierData.address || ''
}
}).catch((error) => {
console.error('查询供应商信息失败:', error)
})
},
/**
* 保存新增供应商地址并刷新地址列表
* 保存成功后不关闭选择验货地址弹窗便于用户立即选择新地址
*/
saveCreatedAddress () {
if (!this.$refs.addressCreateFormRef) {
return
}
this.$refs.addressCreateFormRef.validate((valid) => {
if (!valid) {
return
}
this.addressCreateSaving = true
this.addressCreateForm.createBy = this.$store.state.user.name
this.addressCreateForm.updateBy = this.$store.state.user.name
createSrmSupplierAddress(this.addressCreateForm).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success('新增供应商地址成功')
this.addressCreateDialogVisible = false
this.loadAddressList()
} else {
this.$alert(data.msg || '新增供应商地址失败', '错误', { confirmButtonText: '确定' })
}
}).catch((error) => {
console.error('保存供应商地址失败:', error)
this.$message.error('保存失败: ' + (error.message || '网络错误'))
}).finally(() => {
this.addressCreateSaving = false
})
})
},
/**
* 新增地址弹窗关闭后的收尾动作
* - 清理校验状态
* - 重置为当前供应商默认表单避免残留上次输入
*/
handleAddressCreateDialogClosed () {
if (this.$refs.addressCreateFormRef) {
this.$refs.addressCreateFormRef.clearValidate()
}
this.addressCreateForm = this.buildAddressCreateFormBySupplier(this.addFormData.supplierNo || '')
this.addressCreateSaving = false
},
//
openAddressDialog () {
@ -1654,6 +2061,7 @@ export default {
this.addressPageIndex = 1
this.addressSearch.keyword = ''
this.addressCurrentRow = null
this.addressCreateForm = this.buildAddressCreateFormBySupplier(this.addFormData.supplierNo || '')
this.loadAddressList()
},
@ -2712,6 +3120,17 @@ export default {
font-size: 13px;
}
/**
* 验货申请详情页签统一高度容器
* 1) 固定高度保证基本信息/明细/结果/费用/附件视觉高度一致
* 2) 内部滚动内容超高时在当前页签滚动避免整体布局抖动
*/
.tab-pane-content {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
/deep/ .customer-tab .el-tabs__content {
padding: 5px !important;
}

35
src/views/modules/inspection/inspectionRequestListSupplier.vue

@ -0,0 +1,35 @@
<template>
<!--
供应商专用验货申请页面路由页面壳
设计说明
1) 该文件只负责路由入口模式开关不重复业务实现
2) 真实逻辑全部复用 inspectionRequestList.vue避免双份维护
3) 通过 supplier-mode=true 打开供应商专用模式触发以下差异
- 查询条件精简仅状态/申请单号/申请日期/建议验货日期
- 查询默认并强制按当前登录用户绑定的供应商编码过滤
- 新增时供应商编码/名称只读固定为当前用户供应商
- 隐藏从订单选择供应商入口
- 新增弹窗批量选择PO筛选中移除供应商名称/Planner
-->
<inspection-request-list :supplier-mode="true" />
</template>
<script>
import InspectionRequestList from './inspectionRequestList.vue'
export default {
/**
* 路由名称建议
* inspection-inspectionRequestListSupplier
*
* 菜单URL建议
* inspection/inspectionRequestListSupplier
*/
name: 'InspectionRequestListSupplier',
components: {
//
InspectionRequestList
}
}
</script>

22
src/views/modules/inspection/myTodoList.vue

@ -89,8 +89,10 @@ export default {
requestNo: task.requestNo,
poNo: task.poNo,
supplierName: task.supplierName,
supplierShortOffice: task.supplierShortOffice || '',
qcDisplay: qcDisplay,
inspectAddress: task.inspectAddress,
inspectCityShort: task.inspectCityShort || '',
planStartDate: task.planStartDate,
planEndDate: task.planEndDate
})
@ -140,15 +142,19 @@ export default {
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
getSupplierDisplayName (supplierName) {
const value = supplierName || ''
return value.length > 30 ? value.substring(0, 30) : value
},
/**
* 待办卡片单行文案生成
*
* 业务规则用户确认
* - 展示顺序固定为QC人员名称 + 供应商简称(ShortOffice) + 城市简称
* - 若某一段缺失则自动跳过仅展示有值片段
* - 当前页面是我的待办QCQC名称优先取登录用户显示名
*/
buildTaskLineText (task) {
const displayName = task.qcDisplay || this.$store.state.user.userDisplay || this.$store.state.user.name
const poNo = task.poNo || ''
const supplierName = this.getSupplierDisplayName(task.supplierName)
return [displayName, poNo, supplierName].filter(item => item).join(' | ')
const displayName = String(task.qcDisplay || this.$store.state.user.userDisplay || this.$store.state.user.name || '').trim()
const supplierShortOffice = String(task.supplierShortOffice || '').trim()
const cityShort = String(task.inspectCityShort || '').trim()
return [displayName, supplierShortOffice, cityShort].filter(item => item).join(' | ')
},
// -

153
src/views/modules/order/poOrder.vue

@ -234,7 +234,15 @@
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<!-- 导入弹窗 -->
<!--
模态框 #1PO订单导入弹窗
用途
1) 选择 Excel 文件
2) 预览后端解析结果含字段映射跳过列提示
3) 确认后批量保存
关联状态
uploadDialogVisible / uploadFile / uploadPreviewList / uploadColumnMapping / uploadSkippedColumns
-->
<el-dialog title="PO订单数据导入" :visible.sync="uploadDialogVisible" :height="height" width="800px">
<!-- 选择文件 -->
<div style="margin-bottom:15px;display:flex;align-items:center">
@ -295,7 +303,15 @@
:advanced-query-data="advancedQueryData"
@query="handleSearch" />
<!-- 新增 PO 订单主记录 + 明细暂存 -->
<!--
模态框 #2新增PO订单弹窗主记录 + 明细暂存区
用途
1) 维护主记录字段动态schema
2) 维护明细草稿列表新增/复制/修改/删除
3) 统一保存为正式PO单据
关联状态
addDialogVisible / addForm / addDetailRows / addFormSchemaFields / addFormSaving
-->
<el-dialog title="新增 PO 订单" v-drag :visible.sync="addDialogVisible" width="1180px" top="4vh"
:close-on-click-modal="false" append-to-body>
<div class="po-add-form-wrap">
@ -395,7 +411,15 @@
</div>
</el-dialog>
<!-- 新增明细弹窗 -->
<!--
模态框 #3新增/编辑/复制 明细弹窗
用途
1) 编辑单条明细草稿
2) 支持主记录同款动态字段渲染
3) 点击暂存回写到新增弹窗明细表
关联状态
addDetailDialogVisible / addDetailDialogMode / addDetailForm / addDetailEditingDraftId
-->
<el-dialog
v-drag
:title="addDetailDialogTitle"
@ -477,6 +501,14 @@
</div>
</el-dialog>
<!--
模态框 #4供应商可供物料弹窗
用途
1) 按供应商加载可供物料
2) 双击选中后回填 Part No / Item / SKU / Category
关联状态
supplierPartDialogVisible / supplierPartDialogRows / supplierPartDialogContext / supplierPartDialogTargetRow
-->
<el-dialog
title="选择供应商可供物料"
:visible.sync="supplierPartDialogVisible"
@ -583,6 +615,107 @@ const ADD_SELECT_OPTIONS_FIELD_CODE_MAP = {
advance_paid: 'yesNoOptions'
}
/**
* PO订单页面order-poOrder说明
*
* 公共参数data 顶层字段说明
* - loading: 页面级加载态用于复杂异步流程时的全屏或局部控制
* - masterRows: 主表展示数据来自 searchPoDetailPage / searchPoDetailAdvancedPage
* - currentRow: 当前选中主表行行内编辑联动选择时使用
* - searchData: 查询条件对象site/customer/buyer/poNo/orderType/partNo/supplierNo/sku/status/supplierName
* - advancedQueryData: 高级查询条件对象 schema 动态生成并参与最终查询
* - pageIndex/pageSize/totalPage: 分页三要素
* - height: 主表动态高度窗口变化时实时重算
* - editMode: 主表行内编辑模式开关
* - fieldSchemaFields/baseColumnList/columnList: 字段schema与表格列配置动态列渲染核心
* - dictSelectOptionsMap/addDictSelectOptionsMap: 字典下拉选项缓存列表/新增分开缓存
* - columnSelectOptions: 常用下拉配置集合客户买手状态/运输方式等
* - uploadDialogVisible/uploadFile/uploadPreviewList/uploadColumnMapping/uploadPreviewColumns/uploadSkippedColumns:
* 导入流程全量状态
* - addDialogVisible/addForm/addFormSchemaFields/addFormSaving:
* 新增主弹窗状态主记录
* - addDetailDialogVisible/addDetailDialogMode/addDetailForm/addDetailRows/addDetailCurrentDraftId/addDetailEditingDraftId:
* 新增明细弹窗与明细草稿列表状态
* - supplierPartDialogVisible/supplierPartDialogRows/supplierPartDialogContext/supplierPartDialogTargetRow:
* 供应商可供物料弹窗状态
* - pickContext/eamTagNo/eamPickField: 通用 Chooselist 选择上下文决定选完回填到哪里
* - selectedCustomerScope: 顶部客户选择器当前作用域切换客户时决定是否重载schema和缓存
* - rowDeparture/rowDestination/rowShipVia/rowPartNo/rowSupplierNo:
* 行编辑阶段的辅助暂存用于复杂选择组件联动
*
* 模态框与子弹层说明
* - uploadDialogVisible: PO订单导入文件上传预览批量保存
* - addDialogVisible: 新增PO订单主记录 + 明细草稿
* - addDetailDialogVisible: 新增明细新增/编辑/复制
* - supplierPartDialogVisible: 供应商可供物料选择
* - advancedQueryVisible: 高级查询抽屉/弹层由子组件 po-order-advanced-query 实现
*
* 公共方法说明按功能分组
* A. 页面与布局
* - onWindowResize, updateTableHeight, refreshTableLayout
*
* B. 列渲染与编辑权限
* - formatColumnCell, isChooserEditColumn, canEditCell
* - getSelectOptionsForColumn, getSchemaDictType, preloadDictSelectOptions
*
* C. 新增弹窗schema与字段能力
* - getAddFieldCode, getAddFieldProp, isExtSchemaField, normalizeAddFieldLabel
* - isAddCreateDateField, isAddPoPlaceDateField, isAddMasterField
* - getSchemaFieldByCode, getAddTableColumnLabel
* - getAddDetailTableColumnAlign, getAddDetailTableColumnWidth, getAddDetailTableCellDisplay
* - snakeToCamel, camelToSnake
* - isAddChooserField, openAddChooserByField, getAddChooserPlaceholder
* - getAddFieldSelectOptions, isAddDictSelectField, isAddSelectField, isAddFilterableSelectField
* - isAddDateField, isAddTextareaField, isAddNumberField, isAddFieldDisabled, getAddFieldSpan
* - onAddFieldInput, getAddTargetForm, getSupplierCodeForTargetForm
*
* D. 价格/金额联动与分段价
* - scheduleAutoFillAddPriceByTierPrice, autoFillAddPriceByTierPrice, findCurrentTierPriceByQty
* - normalizeAddNumericValue, calcPoValue, recalcAddFormValue, validatePriceAndQty
*
* E. 查询分页高级查询
* - buildQueryRequest, handleSearch, loadFieldSchema, buildBatchUpdateRow
* - getDataList, sizeChangeHandle, currentChangeHandle, buildQueryRequest
*
* F. 行操作状态流转
* - handleIssuePoOrder, handleShipPoOrder, handleClosePoOrder, handleOpenPoOrder
* - canIssuePoOrder, canShipPoOrder, canClosePoOrder, canOpenPoOrder, canEditPoColumnByStatus
*
* G. 行编辑与批量提交
* - onCellChange, toggleEditMode, cancelEditMode, batchUpdatePoOrder, isNewRow
*
* H. 选择器与主数据加载
* - openEamList, resetEamPick, openSearchBaseList, getEamData
* - openSupplierSelect, openPartNoSelect, openDepartureSelect, openDestinationSelect, openShipViaSelect
* - getPartNoOptions, getSupplierOptions, getPortOptions, getCustomerOptions, getBuyerOptions
* - getTransportOptions, handleSupplierChange
*
* I. 导入导出
* - exportModal, handleFileChange, previewUpload, refreshUploadPreviewColumns
* - confirmUpload, downloadTemplate, exportExcel
*
* J. 新增流程主记录 + 明细
* - resolveAddOrderTypeByCustomer, openAddDialog, loadAddFieldSchema
* - getTodayDateYmd, applyAddDefaultDateBySchema, applyDefaultDateBySchemaToForm
* - getFormExtValue, setFormExtValue, getAddExtValue, setAddExtValue, getAddDetailExtValue, setAddDetailExtValue
* - createEmptyAddForm, createEmptyAddDetailForm, cloneAddDraftRow, buildAddDetailRowFromForm
* - removeAddMasterFieldValuesFromDetailRow, getSelectedAddDetailRow
* - onAddDetailCurrentChange, onAddDetailRowClick
* - openAddDetailDialog, openEditAddDetailDialog, openCopyAddDetailDialog, removeAddDetailRow
* - isAddFormFieldEmpty, validateRequiredFieldGroup, validateRequiredExtFieldGroup
* - saveAddDetailDraft, buildMergedAddDetailForm, saveAddPoOrder, buildAddPayload
*
* K. 供应商可供物料联动
* - openSupplierPartDialog, handleSupplierPartRowDblClick, resetSupplierPartDialog
* - applySelectedPartToTarget, fillPartCategorySku
* - applySupplierSelection, fillSupplierShortFields
*
* L. 新增弹窗内快捷选择入口
* - openAddSupplierSelect, openAddPartNoSelect, openAddDepartureSelect, openAddDestinationSelect, openAddShipViaSelect
*
* M. 其它工具函数
* - formatCodeOptionLabel, buildDeparturePortConSql, buildDestinationPortConSql
*/
export default {
name: 'PoOrder',
components: {
@ -593,6 +726,7 @@ export default {
},
data () {
return {
// ===== =====
loading: false,
masterRows: [],
/**
@ -622,7 +756,7 @@ export default {
displayOnlyColumnProps: ['shortOffice', 'shortNpc', 'item', 'categoryName', 'sku', 'planner'],
customEditColumnProps: [],
//
// ===== =====
uploadDialogVisible: false,
uploadFileName: '',
uploadFile: null,
@ -631,6 +765,7 @@ export default {
uploadColumnMapping: {},
uploadPreviewColumns: [],
// ===== + =====
addDialogVisible: false,
addFormSaving: false,
addDialogSchemaLoading: false,
@ -658,6 +793,7 @@ export default {
addPriceLookupSeq: 0,
selectedCustomerScope: null,
// ===== =====
currentRow: null,
// select selectOptionsKey selectOptions - rqrq
columnSelectOptions: {
@ -677,6 +813,7 @@ export default {
]
},
dictSelectOptionsMap: {},
// ===== =====
searchData: {
site: this.$store.state.user.site,
customer: '',
@ -1815,6 +1952,14 @@ export default {
this.addPriceLookupTimer = null
}
},
/**
* 方法实现区
* 维护建议
* 1) 查询链路handleSearch/getDataList/buildQueryRequest改动需同步检查高级查询
* 2) schema相关方法改动需同步检查新增主弹窗+明细弹窗
* 3) 选择器回填getEamData/applySupplierSelection/applySelectedPartToTarget改动要验证联动字段
* 4) 状态流转handleIssue/Ship/Close/Open改动要校验按钮显隐与后端规则一致
*/
methods: {
canIssuePoOrder,
canShipPoOrder,

356
src/views/modules/qc/qcPartAttribute.vue

@ -83,10 +83,11 @@
fixed="right"
header-align="center"
align="center"
width="240"
width="280"
label="操作">
<template slot-scope="scope">
<a v-if="!authDetail" type="text" size="small" @click="detailModal(scope.row)">检验模板</a>
<a v-if="!authDetail" type="text" size="small" style="margin-right: 8px" @click="detailModal(scope.row)">检验模板</a>
<a v-if="!authDetail" type="text" size="small" style="margin-right: 8px" @click="openTemplateCopyDialog(scope.row)">复制</a>
<a v-if="!authUpdate" type="text" size="small" @click="updateModal(scope.row)">编辑</a>
<a v-if="!authDelete" type="text" size="small" @click="deletePropertiesModel(scope.row)">删除</a>
<!-- <a type="text" size="small" @click="sopFileList(scope.row)">SOP文件</a>-->
@ -289,6 +290,92 @@
</el-footer>
</el-dialog>
<!-- 检验模板复制按源物料把模板清单+检验标准复制到多个目标物料 -->
<el-dialog
title="复制检验模板"
:visible.sync="templateCopyDialogVisible"
:close-on-click-modal="false"
width="1280px"
v-drag
@close="handleTemplateCopyDialogClose">
<el-form :inline="true" label-position="top">
<el-form-item label="源物料编码">
<el-input v-model="templateCopySource.partNo" disabled style="width: 140px" />
</el-form-item>
<el-form-item label="Item">
<el-input v-model="templateCopySource.item" disabled style="width: 120px" />
</el-form-item>
<el-form-item label="SKU">
<el-input v-model="templateCopySource.sku" disabled style="width: 120px" />
</el-form-item>
<el-form-item label="物料名称">
<el-input v-model="templateCopySource.partDesc" disabled style="width: 260px" />
</el-form-item>
</el-form>
<el-form :inline="true" label-position="top" :model="templateCopySearch">
<el-form-item label="基准料号">
<el-input v-model="templateCopySearch.basePartNo" clearable style="width: 130px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="物料名称">
<el-input v-model="templateCopySearch.partDesc" clearable style="width: 180px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="SKU">
<el-input v-model="templateCopySearch.sku" clearable style="width: 130px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="Item">
<el-input v-model="templateCopySearch.item" clearable style="width: 130px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="物料分类1">
<el-input v-model="templateCopySearch.categoryName" clearable style="width: 130px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="物料分类2">
<el-input v-model="templateCopySearch.categoryLevel2Name" clearable style="width: 130px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="物料分类3">
<el-input v-model="templateCopySearch.categoryLevel3Name" clearable style="width: 130px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label="产品编码">
<el-input v-model="templateCopySearch.partNo" clearable style="width: 140px" @keyup.enter.native="queryTemplateCopyTargetPartList" />
</el-form-item>
<el-form-item label=" ">
<el-button type="primary" @click="queryTemplateCopyTargetPartList">查询</el-button>
</el-form-item>
<el-form-item label=" ">
<el-button @click="resetTemplateCopySearch">重置</el-button>
</el-form-item>
</el-form>
<el-table
ref="templateCopyTable"
:data="templateCopyRows"
:height="360"
border
v-loading="templateCopyDialogLoading"
@selection-change="handleTemplateCopySelectionChange"
style="width: 100%">
<el-table-column
type="selection"
width="50"
header-align="center"
align="center"
:selectable="templateCopyRowSelectable" />
<el-table-column prop="partNo" label="产品编码" min-width="160" header-align="center" align="left" />
<el-table-column prop="partDesc" label="物料名称" min-width="220" header-align="center" align="left" show-overflow-tooltip />
<el-table-column prop="item" label="Item" min-width="120" header-align="center" align="left" show-overflow-tooltip />
<el-table-column prop="sku" label="SKU" min-width="120" header-align="center" align="left" show-overflow-tooltip />
<el-table-column prop="basePartNo" label="基准料号" min-width="140" header-align="center" align="left" show-overflow-tooltip />
<el-table-column prop="categoryName" label="物料分类1" min-width="130" header-align="center" align="left" show-overflow-tooltip />
<el-table-column prop="categoryLevel2Name" label="物料分类2" min-width="130" header-align="center" align="left" show-overflow-tooltip />
<el-table-column prop="categoryLevel3Name" label="物料分类3" min-width="130" header-align="center" align="left" show-overflow-tooltip />
</el-table>
<el-footer style="height:40px;margin-top: 20px;text-align:center">
<el-button type="primary" :loading="templateCopySubmitting" @click="submitTemplateCopy">确认复制</el-button>
<el-button :disabled="templateCopySubmitting" @click="templateCopyDialogVisible = false">取消</el-button>
</el-footer>
</el-dialog>
<!-- 物料模板检验标准 -->
<el-dialog title="检验标准清单" :close-on-click-modal="false" v-drag :visible.sync="inspectionStandardListFlag" width="1200px">
<el-form :inline="true" label-position="top">
@ -800,6 +887,9 @@
getAllResourceList, //
getSiteAndBuByUserName,
searchPartInfo, //
listPartAttributeTemplateCopyTargetParts, //
checkPartAttributeTemplateCopyConflicts, //
copyPartAttributeTemplateToParts, //
} from "@/api/qc/qc.js"
import { buildQcBu } from './qcBuUtils'
import {getTableDefaultListLanguage, getTableUserListLanguage} from "@/api/table.js"
@ -1160,6 +1250,36 @@
],
detailModelFlag: false,
detailList: [],
/**
* 模板复制弹窗状态
* - templateCopySource当前被复制的源物料来自主表操作列复制
* - templateCopySearch目标物料筛选条件不按供应商仅筛在用物料
* - templateCopyRows目标候选列表
* - templateCopySelections用户勾选的目标物料集合
*/
templateCopyDialogVisible: false,
templateCopyDialogLoading: false,
templateCopySubmitting: false,
templateCopySource: {
site: '',
buNo: '',
partNo: '',
partDesc: '',
item: '',
sku: ''
},
templateCopySearch: {
basePartNo: '',
partNo: '',
partDesc: '',
item: '',
sku: '',
categoryName: '',
categoryLevel2Name: '',
categoryLevel3Name: ''
},
templateCopyRows: [],
templateCopySelections: [],
dataList: [],
dataListLoading: false,
//
@ -1918,6 +2038,238 @@
this.loadTemplateDetailList()
},
/**
* 打开复制检验模板弹窗
*
* 业务意图
* 1) 以当前行物料作为源物料
* 2) 复制源物料检验模板清单 + 检验标准明细到多个目标物料
* 3) 目标物料列表不按供应商过滤仅查在用物料status='Y'
*/
openTemplateCopyDialog (row) {
const sourcePartNo = String((row && row.partNo) || '').trim()
const sourceBuNo = String((row && (row.buNo || row.bu_no)) || '').trim()
if (!sourcePartNo) {
this.$message.warning('请先选择要复制的源物料')
return
}
if (!sourceBuNo) {
this.$message.warning('源物料缺少BU信息,无法复制')
return
}
this.templateCopySource = {
site: this.$store.state.user.site,
buNo: sourceBuNo,
partNo: sourcePartNo,
partDesc: (row && row.partDesc) || '',
item: (row && row.item) || '',
sku: (row && row.sku) || ''
}
this.templateCopyDialogVisible = true
this.resetTemplateCopySearch()
},
/**
* 组装复制接口基础请求参数
* 所有复制相关接口都要求携带 site + buNo + sourcePartNo避免后端无法定位源模板主从记录
*/
buildTemplateCopyBaseRequest () {
return {
site: this.templateCopySource.site || this.$store.state.user.site,
buNo: this.templateCopySource.buNo || '',
sourcePartNo: this.templateCopySource.partNo || ''
}
},
/**
* 重置模板复制筛选条件并重新加载候选物料
* 说明重置后立即查询减少用户重复点击查询的操作成本
*/
resetTemplateCopySearch () {
this.templateCopySearch = {
basePartNo: '',
partNo: '',
partDesc: '',
item: '',
sku: '',
categoryName: '',
categoryLevel2Name: '',
categoryLevel3Name: ''
}
this.queryTemplateCopyTargetPartList()
},
/**
* 模板复制弹窗关闭收尾
* - 清空候选列表与勾选缓存避免再次打开时残留旧状态
* - 关闭时不重置源物料信息便于调试和异常重试定位
*/
handleTemplateCopyDialogClose () {
this.templateCopyRows = []
this.templateCopySelections = []
this.templateCopyDialogLoading = false
this.templateCopySubmitting = false
},
/**
* 查询模板复制候选物料列表
*
* 后端约束
* - 仅返回 status='Y' 物料
* - 自动排除源物料
* - 支持基准料号/物料名称/SKU/item/分类1~3筛选
*/
async queryTemplateCopyTargetPartList () {
const baseReq = this.buildTemplateCopyBaseRequest()
if (!baseReq.sourcePartNo || !baseReq.buNo) {
this.templateCopyRows = []
return
}
this.templateCopyDialogLoading = true
this.templateCopySelections = []
try {
const req = Object.assign({}, baseReq, this.templateCopySearch)
const { data } = await listPartAttributeTemplateCopyTargetParts(req)
if (data && data.code === 0) {
this.templateCopyRows = data.rows || []
this.$nextTick(() => {
if (this.$refs.templateCopyTable && this.$refs.templateCopyTable.clearSelection) {
this.$refs.templateCopyTable.clearSelection()
}
})
return
}
this.templateCopyRows = []
this.$message.error((data && data.msg) || '查询候选物料失败')
} catch (err) {
this.templateCopyRows = []
this.$message.error((err && err.message) || '查询候选物料失败')
} finally {
this.templateCopyDialogLoading = false
}
},
/**
* 候选表行可选控制
* 双保险排除源物料避免用户误选自己导致自复制
*/
templateCopyRowSelectable (row) {
const sourcePartNo = String(this.templateCopySource.partNo || '').trim().toUpperCase()
const currentPartNo = String((row && row.partNo) || '').trim().toUpperCase()
if (!currentPartNo) {
return false
}
return currentPartNo !== sourcePartNo
},
/**
* 记录用户在候选表中的多选结果
*/
handleTemplateCopySelectionChange (rows) {
this.templateCopySelections = rows || []
},
/**
* 将冲突提示文本做HTML转义避免异常字符影响确认弹窗渲染
*/
escapeTemplateCopyConfirmText (val) {
return String(val || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
},
/**
* 构造已存在模板确认弹窗内容
* - 明确提示将进行覆盖
* - 列出全部冲突物料避免误覆盖
*/
buildTemplateCopyConflictConfirmHtml (conflictRows) {
const rowHtml = (conflictRows || []).map((row, index) => {
const partNo = this.escapeTemplateCopyConfirmText(row.partNo)
const partDesc = this.escapeTemplateCopyConfirmText(row.partDesc)
const item = this.escapeTemplateCopyConfirmText(row.item)
const sku = this.escapeTemplateCopyConfirmText(row.sku)
return `${index + 1}. ${partNo} / ${item} / ${sku} `
}).join('<br/>')
return `以下物料已经有检验模板,是否覆盖?<br/><div style="margin-top:8px;max-height:260px;overflow-y:auto;line-height:1.6;">${rowHtml}</div>`
},
/**
* 执行确认复制
* 1) 先调后端冲突校验
* 2) 若有冲突先让用户确认是否覆盖
* 3) 覆盖时后端先删目标主从记录再复制源主从记录
*/
async submitTemplateCopy () {
const targetPartNos = (this.templateCopySelections || [])
.map(row => String((row && row.partNo) || '').trim())
.filter(partNo => partNo)
if (targetPartNos.length === 0) {
this.$message.warning('请至少选择一个目标物料')
return
}
if (this.templateCopySubmitting) {
return
}
this.templateCopySubmitting = true
try {
const checkReq = Object.assign({}, this.buildTemplateCopyBaseRequest(), {
targetPartNos
})
const { data } = await checkPartAttributeTemplateCopyConflicts(checkReq)
if (!data || data.code !== 0) {
throw new Error((data && data.msg) || '复制冲突校验失败')
}
const conflictRows = data.rows || []
if (conflictRows.length > 0) {
this.templateCopySubmitting = false
const confirmHtml = this.buildTemplateCopyConflictConfirmHtml(conflictRows)
try {
await this.$confirm(confirmHtml, '提示', {
confirmButtonText: '确认覆盖',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
})
} catch (confirmError) {
return
}
this.templateCopySubmitting = true
await this.executeTemplateCopy(targetPartNos, true)
return
}
await this.executeTemplateCopy(targetPartNos, false)
} catch (err) {
this.$message.error((err && err.message) || '复制失败')
} finally {
this.templateCopySubmitting = false
}
},
/**
* 调用后端复制接口落库
*
* @param targetPartNos 目标物料编码集合
* @param overwriteExisting 是否覆盖目标已有模板
*/
async executeTemplateCopy (targetPartNos, overwriteExisting) {
const req = Object.assign({}, this.buildTemplateCopyBaseRequest(), {
targetPartNos,
overwriteExisting,
submitBy: this.$store.state.user.name
})
const { data } = await copyPartAttributeTemplateToParts(req)
if (data && data.code === 0) {
this.$message.success('复制成功')
this.templateCopyDialogVisible = false
return
}
throw new Error((data && data.msg) || '复制失败')
},
checkInspectionType () {
if (this.lineInspectionRoleFlag) {
this.detailData.inspectionTypeNo = this.getPqcInspectionTypeNo()

Loading…
Cancel
Save