|
|
<template> <div class="mod-config"> <el-form :inline="true" :model="searchData" label-position="top" class="search-form"> <el-form-item label="BU"> <el-select v-model="searchData.buNo" clearable filterable placeholder="全部" style="width: 130px" @change="handleSearchBuChange"> <el-option v-for="item in userBuList" :key="`searchBu_${item.buNo}`" :label="item.buDesc ? `${normalizeBuNo(item.buNo, searchData.site)}-${item.buDesc}` : normalizeBuNo(item.buNo, searchData.site)" :value="normalizeBuNo(item.buNo, searchData.site)" /> </el-select> </el-form-item> <el-form-item label="项目编码"> <el-input v-model="searchData.projectNo" clearable placeholder="请输入项目编码" style="width: 140px" /> </el-form-item> <el-form-item label="项目描述"> <el-input v-model="searchData.projectDesc" clearable placeholder="请输入项目描述" style="width: 160px" /> </el-form-item> <el-form-item label="项目物料"> <el-input v-model="searchData.testPartNo" clearable placeholder="请输入项目物料" style="width: 140px" /> </el-form-item> <el-form-item label="料号描述"> <el-input v-model="searchData.partDesc" clearable placeholder="请输入料号描述" style="width: 160px" /> </el-form-item> <el-form-item label="客户编码"> <el-input v-model="searchData.customerNo" clearable placeholder="请输入客户编码" style="width: 120px" /> </el-form-item> <el-form-item label="打样单号"> <el-input v-model="searchData.proofingNo" clearable placeholder="请输入打样单号" style="width: 150px" /> </el-form-item> <el-form-item label="项目/物料同步"> <el-select v-model="searchData.projectPartSyncFlag" clearable placeholder="全部" style="width: 120px"> <el-option label="已同步" value="Y" /> <el-option label="未同步" value="N" /> </el-select> </el-form-item> <el-form-item label="打样单同步"> <el-select v-model="searchData.proofSyncFlag" clearable placeholder="全部" style="width: 120px"> <el-option label="已同步" value="Y" /> <el-option label="未同步" value="N" /> </el-select> </el-form-item> <el-form-item label="打样状态"> <el-select v-model="searchData.proofingStatus" clearable placeholder="全部" style="width: 120px"> <el-option label="进行中" value="进行中" /> <el-option label="打样完成" value="打样完成" /> <el-option label="草稿" value="草稿" /> </el-select> <el-button type="primary" @click="getDataList('Y')">查询</el-button> <el-button @click="resetSearch">重置</el-button> </el-form-item> <el-form-item class="button-group-col"> <el-row :gutter="8" type="flex" class="button-row" > <el-button type="primary" @click="openOneKeyDialog">一键创建</el-button><!-- <el-button type="warning" @click="toggleBatchEdit">{{ batchEditMode ? '取消修改' : '批量修改' }}</el-button> <el-button v-if="batchEditMode" type="success" @click="saveBatchPlan">保存修改</el-button>--> <el-button type="warning" @click="openCreateProofDialog()">新增打样</el-button> <el-button type="success" plain :loading="syncNpiLoading" @click="syncToNpi()">同步项目物料到NPI</el-button> <el-button type="success" plain :loading="syncProofLoading" @click="syncProofToNpi()">同步打样到NPI</el-button> <el-button type="info" v-if="isAuth('833001005:set')" plain @click="openBuProcessConfigDialog">事业部进度设置</el-button> </el-row> </el-form-item> </el-form>
<el-table ref="trackingTable" :data="dataList" border class="data-table" stripe highlight-current-row height="700" v-loading="dataListLoading" @selection-change="selectionChange" @row-click="rowClick" style="width: 100%; margin-top: 8px" ><!-- <el-table-column type="selection" width="45" />--> <el-table-column prop="projectNo" label="项目编码" width="90" show-overflow-tooltip> <template slot-scope="scope"> <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.projectNo || '-' }}</span> </template> </el-table-column> <el-table-column prop="projectDesc" label="项目描述" width="120" show-overflow-tooltip> <template slot-scope="scope"> <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.projectDesc || '-' }}</span> </template> </el-table-column> <el-table-column prop="testPartNo" label="项目物料" width="90" show-overflow-tooltip> <template slot-scope="scope"> <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.testPartNo || '-' }}</span> </template> </el-table-column> <el-table-column prop="partDesc" label="物料描述" :fixed="'left'" width="150" show-overflow-tooltip> <template slot-scope="scope"> <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.partDesc || '-' }}</span> </template> </el-table-column><!-- <el-table-column prop="customerNo" label="客户编码" width="110" show-overflow-tooltip /> <el-table-column prop="customerDesc" label="客户名称" width="130" show-overflow-tooltip />--> <el-table-column prop="proofingNo" label="打样单号" width="90" show-overflow-tooltip> <template slot-scope="scope"> <span :class="getProofSyncTextClass(scope.row)">{{ scope.row.proofingNo || '-' }}</span> </template> </el-table-column> <el-table-column prop="proofingStatus" label="打样状态" width="60"> <template slot-scope="scope"> <a :style="{color: (scope.row.proofingStatus === '打样完成' || scope.row.proofingStatus === '打样结束') ? '#67c23a' : '#046e97'}"> {{ scope.row.proofingStatus || '进行中' }}</a> </template> </el-table-column>
<el-table-column v-for="item in visibleProcessColumns" :key="item.code" :label="item.label" width="120" align="center" header-align="center" > <template slot-scope="scope"> <div v-if="isProcessColumnVisibleForRow(scope.row, item)" :class="['process-cell', isProcessComplete(scope.row, item) ? 'is-complete' : '']" > <el-date-picker v-if="batchEditMode" v-model="scope.row[item.actualField]" type="date" value-format="yyyy-MM-dd" format="yyyy-MM-dd" placeholder="实际完成日期" style="width: 120px" /> <div v-else class="process-content" @mouseenter="handleProcessTooltipMouseEnter(scope.row, item, $event)" @mousemove="handleProcessTooltipMouseMove($event)" @mouseleave="handleProcessTooltipMouseLeave" > <el-date-picker v-model="scope.row[item.actualField]" type="date" value-format="yyyy-MM-dd" format="yyyy-MM-dd" placeholder="" style="width: 122px" @change="handleProcessDateChange(scope.row, item, $event)" /> <el-button class="process-complete-btn" type="success" size="mini" plain v-show="!isProcessComplete(scope.row, item)" :disabled="isProcessComplete(scope.row, item)" @click.stop="markProcessComplete(scope.row, item)" >√</el-button> </div> </div> <div v-else class="process-cell process-cell--masked"></div> </template> </el-table-column>
<el-table-column label="Comments" width="220"> <template slot-scope="scope"> <div class="comments-cell" @dblclick.stop="startCommentsEdit(scope.row)"> <template v-if="batchEditMode"> <el-input v-model="scope.row.comments" class="comments-edit-input" size="mini" clearable placeholder="请输入Comments" /> </template> <template v-else-if="isCommentsEditing(scope.row)"> <el-input v-model="commentsDraft" v-focus-comments-input="isCommentsEditing(scope.row)" :ref="'commentsInput_' + scope.row.trackingId" :id="'commentsInput_' + scope.row.trackingId" class="comments-edit-input" size="mini" clearable placeholder="请输入,回车也可保存" @keydown.enter.native.prevent="saveCommentsEdit(scope.row)" @keyup.esc.native="cancelCommentsEdit" @blur="handleCommentsBlur(scope.row)" /> <el-button class="comments-save-btn" type="success" size="mini" plain :loading="commentsSaveLoading" @mousedown.native.prevent @click.stop="saveCommentsEdit(scope.row)" >√</el-button> </template> <span v-else class="comments-text" :title="scope.row.comments || ''">{{ scope.row.comments || '-' }}</span> </div> </template> </el-table-column> <el-table-column label="操作" fixed="right" width="150" align="center"> <template slot-scope="scope"><!-- <a class="action-link" @click="syncToNpi(scope.row)">同步NPI</a>--><!-- <a class="action-link" @click="openCreateProofDialog(scope.row)">新增打样</a>--> <a @click="openOneKeyDialog('edit', scope.row)">修改</a> <a class="action-link delete-link" @click="deleteProof(scope.row)">删除</a> <a class="action-link end-link" v-if="scope.row.proofingStatus !== '打样完成'" @click="finishProof(scope.row)">打样完成</a> </template> </el-table-column> </el-table>
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper" style="margin-top: 12px; text-align: right" />
<div v-show="processTooltip.visible" class="process-history-tooltip" :style="{ left: `${processTooltip.left}px`, top: `${processTooltip.top}px` }" >{{ processTooltip.content }}</div>
<el-dialog title="事业部进度展示设置" :visible.sync="buProcessConfigDialogVisible" width="900px" :close-on-click-modal="false" custom-class="bu-process-config-dialog" > <el-table :data="buProcessConfigRows" border stripe max-height="560"> <el-table-column prop="buDesc" label="事业部名称" width="100" show-overflow-tooltip /> <el-table-column label="可见进度列表" min-width="450" class-name="bu-process-list-column"> <template slot-scope="scope"> <el-checkbox-group v-model="scope.row.processCodes" @change="handleBuProcessCodesChange(scope.row)" > <el-checkbox v-for="column in processColumns" :key="`${scope.row.buNo}_${column.code}`" :label="column.code" >{{ column.label }}</el-checkbox> </el-checkbox-group> </template> </el-table-column> </el-table>
<div slot="footer"> <el-button @click="buProcessConfigDialogVisible = false">取消</el-button> <el-button type="primary" @click="saveBuProcessConfig">保存设置</el-button> </div> </el-dialog>
<el-dialog :title="oneKeyDialogMode === 'edit' ? '修改项目/物料/打样' : '一键创建项目/物料/打样'" :visible.sync="oneKeyDialogVisible" :close-on-click-modal="false" width="1000px"> <el-form :model="oneKeyForm" label-position="top" class="one-key-grid-form"> <el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('buNo')"> <el-form-item label="BU" required> <el-select v-model="oneKeyForm.buNo" placeholder="请选择" style="width: 100%"> <el-option v-for="item in userBuList" :key="item.buNo" :label="item.buDesc" :value="item.buNo"></el-option> </el-select> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectNo')"> <el-form-item label="项目编码"> <el-input v-model="oneKeyForm.projectNo" style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="12" v-show="isOneKeyCreateFieldVisible('projectDesc')"> <el-form-item label="项目名称"> <el-input v-model="oneKeyForm.projectDesc" style="width: 100%"></el-input> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('customerNo')"> <el-form-item> <span slot="label" class="big-label"> <a href="javascript:void(0)" @click="getBaseList(509)">客户编码</a> <a href="javascript:void(0)" @click="newCustomer">(新客户)</a> </span> <el-input v-model="oneKeyForm.customerNo" @blur="customerNoBlur" style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="12" v-show="isOneKeyCreateFieldVisible('customerDesc')"> <el-form-item label="客户名称"> <el-input v-model="oneKeyForm.customerDesc" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cProjectRegion')"> <el-form-item label="区域"> <el-select v-model="oneKeyForm.cProjectRegion" placeholder="请选择" clearable style="width: 100%"> <el-option v-for="i in cProjectRegionList" :key="i.cProjectRegion" :label="i.cProjectRegion" :value="i.cProjectRegion"> </el-option> </el-select> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectCreationDate')"> <el-form-item label="立项日期"> <el-date-picker v-model="oneKeyForm.projectCreationDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('needDate')"> <el-form-item label="预计完成日期"> <el-date-picker v-model="oneKeyForm.needDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectCategory')"> <el-form-item label="项目分类"> <el-select v-model="oneKeyForm.projectCategory" placeholder="请选择" clearable style="width: 100%"> <el-option v-for="item in projectCategoryList" :key="item" :label="item" :value="item"></el-option> </el-select> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('priorityLevel')"> <el-form-item label="优先级"> <el-select v-model="oneKeyForm.priorityLevel" placeholder="请选择" clearable style="width: 100%"> <el-option v-for="item in priorityList" :key="item" :label="item" :value="item"></el-option> </el-select> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('testPartNo')"> <el-form-item label="项目料号"> <el-input v-model="oneKeyForm.testPartNo" style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="12" v-show="isOneKeyCreateFieldVisible('partDesc')"> <el-form-item label="料号描述"> <el-input v-model="oneKeyForm.partDesc" style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('partType')"> <el-form-item label="料号状态"> <el-select v-model="oneKeyForm.partType" placeholder="请选择" clearable style="width: 100%"> <el-option v-for="item in partTypeList" :key="item" :label="item" :value="item"></el-option> </el-select> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectManager')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('projectManager')">PM/Sales</a></span> <el-input v-model="oneKeyForm.projectManagerName" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectOwner')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('projectOwner')">PjM</a></span> <el-input v-model="oneKeyForm.projectOwnerName" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('engineer')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('engineer')">Engineer</a></span> <el-input v-model="oneKeyForm.engineerName" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cManufactureEngineer')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cManufactureEngineer')">MFG</a></span> <el-input v-model="oneKeyForm.cManufactureEngineerName" disabled style="width: 100%"></el-input> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer6')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer6')">IQC</a></span> <el-input v-model="oneKeyForm.cQualityEngineer6Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer1')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer1')">IPQC-Lam/Pri/Etch/Slit</a></span> <el-input v-model="oneKeyForm.cQualityEngineer1Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer2')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer2')">IPQC-Converting</a></span> <el-input v-model="oneKeyForm.cQualityEngineer2Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer4')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer4')">SQE</a></span> <el-input v-model="oneKeyForm.cQualityEngineer4Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer3')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer3')">FQC1</a></span> <el-input v-model="oneKeyForm.cQualityEngineer3Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer5')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer5')">FQC2</a></span> <el-input v-model="oneKeyForm.cQualityEngineer5Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('ipqcHardTag')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('ipqcHardTag')">IPQC-Hardtag</a></span> <el-input v-model="oneKeyForm.ipqcHardTagName" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer7')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer7')">前道工程师</a></span> <el-input v-model="oneKeyForm.cQualityEngineer7Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('docEngineer')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('docEngineer')">文档工程师</a></span> <el-input v-model="oneKeyForm.docEngineerName" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('docEngineer2')"> <el-form-item> <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('docEngineer2')">文档工程师2</a></span> <el-input v-model="oneKeyForm.docEngineer2Name" disabled style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectStatus')"> <el-form-item label="项目状态"> <el-select v-model="oneKeyForm.projectStatus" clearable style="width: 100%"> <el-option v-for="item in projectStatusList" :key="item" :label="item" :value="item" /> </el-select> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectPhase')"> <el-form-item label="项目阶段"> <el-select v-model="oneKeyForm.projectPhase" clearable style="width: 100%"> <el-option v-for="item in proofProjectPhaseList" :key="item.projectPhase" :label="item.projectPhase" :value="item.projectPhase" /> </el-select> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingNo')"> <el-form-item label="打样单号"> <el-input v-model="oneKeyForm.proofingNo" style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingStatus')"> <el-form-item label="打样状态"> <el-select v-model="oneKeyForm.proofingStatus" clearable style="width: 100%"> <el-option label="草稿" value="草稿" /> <el-option label="进行中" value="进行中" /> <el-option label="打样完成" value="打样完成" /> <el-option label="打样结束" value="打样结束" /> </el-select> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingNumber')"> <el-form-item label="数量"> <el-input v-model.number="oneKeyForm.proofingNumber" style="width: 100%"></el-input> </el-form-item> </el-col> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('planStartDate')"> <el-form-item label="打样开始日期"> <el-date-picker v-model="oneKeyForm.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6" v-show="isOneKeyCreateFieldVisible('requiredDeliveryDate')"> <el-form-item label="预计交付日期"> <el-date-picker v-model="oneKeyForm.requiredDeliveryDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> <el-col :span="isOneKeyCreateFieldVisible('requiredDeliveryDate') ? 18 : 24" v-show="isOneKeyCreateFieldVisible('remark')"> <el-form-item label="备注"> <el-input type="textarea" :rows="2" v-model="oneKeyForm.remark" style="width: 100%"></el-input> </el-form-item> </el-col> </el-row> </el-form> <el-footer style="height: 40px; margin-top: 40px; text-align: center"> <el-button @click="oneKeyDialogVisible = false">取消</el-button> <el-button type="primary" :loading="saveOneKeyLoading" @click="submitOneKey">{{ oneKeyDialogMode === 'edit' ? '保存修改' : '保存' }}</el-button> </el-footer> </el-dialog>
<el-dialog title="新增打样" :visible.sync="proofDialogVisible" width="768px" :close-on-click-modal="false"> <el-form :model="proofDialogData" label-position="top" class="proof-grid-form"> <el-row :gutter="16"> <el-col :span="6"> <el-form-item label="项目编码"> <el-input v-model="proofDialogData.projectNo" disabled style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="项目名称"> <el-input v-model="proofDialogData.projectDesc" disabled style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="BU"> <el-input v-model="proofDialogData.buNo" disabled style="width: 100%" /> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6"> <el-form-item label="客户编码"> <el-input v-model="proofDialogData.customerNo" disabled style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="客户名称"> <el-input v-model="proofDialogData.customerDesc" disabled style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="项目料号"> <el-input v-model="proofDialogData.testPartNo" disabled style="width: 100%" /> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="12"> <el-form-item label="料号描述"> <el-input v-model="proofDialogData.partDesc" disabled style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="项目分类" required> <el-select v-model="proofDialogData.cProjectTypeDb" placeholder="请选择" clearable style="width: 100%"> <el-option v-for="item in cProjectTypeDbList" :key="item.cProjectTypeDb" :label="item.cProjectTypeDb" :value="item.cProjectTypeDb"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="项目阶段" required> <el-select v-model="proofDialogData.projectPhase" placeholder="请选择" clearable style="width: 100%"> <el-option v-for="item in proofProjectPhaseList" :key="item.projectPhase" :label="item.projectPhase" :value="item.projectPhase"> </el-option> </el-select> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6"> <el-form-item label="打样单号" required> <el-input v-model="proofDialogData.proofingNo" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="数量" required> <el-input v-model="proofDialogData.proofingNumber" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="打样状态"> <el-select v-model="proofDialogData.proofingStatus" placeholder="请选择" clearable style="width: 100%"> <el-option label="草稿" value="草稿"></el-option> <el-option label="进行中" value="进行中"></el-option> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="实际完成日期"> <el-date-picker v-model="proofDialogData.actualityDeliveryDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> </el-row>
<el-row :gutter="16"> <el-col :span="6"> <el-form-item label="打样开始日期" required> <el-date-picker v-model="proofDialogData.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="预计完成日期" required> <el-date-picker v-model="proofDialogData.requiredDeliveryDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%"> </el-date-picker> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="备注"> <el-input v-model="proofDialogData.remark" style="width: 100%" type="textarea" :rows="2"></el-input> </el-form-item> </el-col> </el-row> </el-form>
<el-footer style="height: 40px; margin-top: 40px; text-align: center"> <el-button @click="proofDialogVisible = false">取 消</el-button> <el-button type="primary" :loading="proofSaveLoading" @click="saveProofRecord">保 存</el-button> </el-footer> </el-dialog>
<el-dialog title="更新工序状态" :visible.sync="processDialogVisible" width="620px" :close-on-click-modal="false"> <el-form :model="processForm" label-position="top" label-width="120px" :inline="true"> <el-form-item label="工序"> <el-input v-model="processForm.processName" disabled style="width: 180px" /> </el-form-item> <el-form-item label="状态" required> <el-select v-model="processForm.status" style="width: 180px"> <el-option label="未完成" value="未完成" /> <el-option label="进行中" value="进行中" /> <el-option label="已完成" value="已完成" /> </el-select> </el-form-item> <el-form-item label="实际完成日期"> <el-date-picker v-model="processForm.actualDate" type="date" value-format="yyyy-MM-dd" format="yyyy-MM-dd" style="width: 180px" /> </el-form-item> <el-form-item label="备注"> <el-input type="textarea" :rows="2" v-model="processForm.remark" style="width: 570px" /> </el-form-item> </el-form>
<el-table :data="processHistoryRows" border stripe height="220" style="margin-top: 32px"> <el-table-column prop="changedDate" label="修改时间" width="120" /> <el-table-column prop="changedBy" label="修改人" width="70" /> <el-table-column prop="oldStatus" label="原状态" width="60" /> <el-table-column prop="newStatus" label="新状态" width="60" /> <el-table-column prop="oldActualDate" label="原实际" width="90" /> <el-table-column prop="newActualDate" label="新实际" width="90" /> <el-table-column prop="remark" label="备注" show-overflow-tooltip /> </el-table>
<div slot="footer"> <el-button @click="processDialogVisible = false">取消</el-button> <el-button type="primary" :loading="saveProcessLoading" @click="submitProcessUpdate">保存</el-button> </div> </el-dialog>
<el-dialog title="打样完成" :visible.sync="finishDialogVisible" width="260px" :close-on-click-modal="false" custom-class="finish-proof-dialog"> <div class="finish-dialog-tip"> <i class="el-icon-info"></i> <span>确认后将直接标记为<strong class="finish-tip-highlight">打样完成</strong></span> </div> <el-form :model="finishForm" label-width="100px" class="finish-form"> <el-form-item label="实际完成日期" required> <el-date-picker v-model="finishForm.actualityDeliveryDate" type="date" value-format="yyyy-MM-dd" format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" /> </el-form-item> </el-form> <div slot="footer"> <el-button @click="finishDialogVisible = false">取消</el-button> <el-button type="primary" :loading="finishSaveLoading" @click="submitFinishProof">确认完成</el-button> </div> </el-dialog>
<el-dialog :title="roleDialogTitle" :visible.sync="roleDialogVisible" width="559px" :close-on-click-modal="false"> <el-form :inline="true" label-position="top" :model="roleSearchData" style="margin-left: 7px; margin-top: -5px;"> <el-form-item label="用户账号"> <el-input v-model="roleSearchData.username" clearable style="width: 110px"></el-input> </el-form-item> <el-form-item label="用户名"> <el-input v-model="roleSearchData.userDisplay" clearable style="width: 110px"></el-input> </el-form-item> <el-form-item label="是否在用"> <el-select filterable v-model="roleSearchData.active" clearable style="width: 140px"> <el-option label="是" value="Y"></el-option> <el-option label="否" value="N"></el-option> </el-select> </el-form-item> <el-form-item label=" "> <el-button type="primary" style="padding: 3px 12px" @click="queryRoleDialogList">查询</el-button> </el-form-item> </el-form> <el-table :data="roleDialogList" stripe border highlight-current-row @row-dblclick="pickRole" style="width: 100%"> <el-table-column prop="username" header-align="center" align="center" label="用户账号"></el-table-column> <el-table-column prop="userDisplay" header-align="center" align="center" label="用户名"></el-table-column> <el-table-column prop="active" header-align="center" align="center" label="是否在用"></el-table-column> </el-table> <el-footer style="height: 40px; margin-top: 10px; text-align: center"> <el-button @click="roleDialogVisible = false">关闭</el-button> </el-footer> </el-dialog>
<el-dialog title="新客户" :visible.sync="newCustomerFlag" width="348px" :close-on-click-modal="false" @close="closeNewCustomer"> <el-form :inline="true" label-position="top" :model="newCustomerData" style="margin-left: 7px; margin-top: -5px;"> <el-form-item label="客户名称"> <el-input v-model="newCustomerData.customerDesc" style="width: 313px"></el-input> </el-form-item> </el-form> <el-footer style="height: 40px; margin-top: 10px; text-align: center"> <el-button type="primary" @click="saveNewCustomer">保存</el-button> <el-button @click="newCustomerFlag = false">关闭</el-button> </el-footer> </el-dialog>
<Chooselist ref="baseList" @getBaseData="getBaseData"></Chooselist> </div></template>
<script>import { batchUpdateProofTrackingPlan, createProofTrackingRecord, deleteProofTracking, finishProofTracking, oneKeyCreateProofTracking, oneKeyUpdateProofTracking, queryProofTrackingBuProcessConfig, queryProofTrackingProcessHistory, saveProofTrackingBuProcessConfig, searchProjectInfoTracking, searchProjectPartTracking, searchProofTracking, syncProofTracking, syncProjectPartTracking, updateProofTrackingComments, updateProofTrackingProcess} from '@/api/sampleTracking/sampleTracking'import { getCustomerNo, saveNewCustomer } from '@/api/eam/eamProject.js'import { getSiteAndBuByUserName } from '@/api/eam/eam.js'import { searchBusinessInfo, searchBusinessInfo1, searchBusinessInfo2, searchBusinessInfo3, searchBusinessInfo4, searchBusinessInfo5, searchBusinessInfo6, searchBusinessInfo7, searchBusinessInfo8} from '@/api/factory/site.js'import { queryCustomerList } from '@/api/customer/customer'import Chooselist from '@/views/modules/common/Chooselist_eam'
function focusCommentsNativeInput (el, vnode) { const doFocus = () => { const componentInput = vnode && vnode.componentInstance && vnode.componentInstance.$refs ? vnode.componentInstance.$refs.input : null const inputEl = componentInput || (el && el.querySelector ? el.querySelector('input') : null) if (!inputEl || typeof inputEl.focus !== 'function') { return false } inputEl.focus() const textLength = inputEl.value ? String(inputEl.value).length : 0 if (typeof inputEl.setSelectionRange === 'function') { inputEl.setSelectionRange(textLength, textLength) } return true } if (doFocus()) { return } setTimeout(doFocus, 0) setTimeout(doFocus, 50) setTimeout(doFocus, 100)}
export default { name: 'ProjectProofTracking', directives: { focusCommentsInput: { inserted (el, binding, vnode) { if (binding.value) { focusCommentsNativeInput(el, vnode) } }, componentUpdated (el, binding, vnode) { if (binding.value && !binding.oldValue) { focusCommentsNativeInput(el, vnode) } } } }, components: { Chooselist }, data () { return { searchData: { site: this.$store.state.user.site, userName: this.$store.state.user.name, projectNo: '', projectDesc: '', buNo: '', testPartNo: '', partDesc: '', customerNo: '', proofingNo: '', projectPartSyncFlag: '', proofSyncFlag: '', proofingStatus: '' }, processColumns: [ { label: 'Raw Material', code: 'rawMaterial', planField: 'rawMaterialPlanDate', actualField: 'rawMaterialActualDate', statusField: 'rawMaterialStatus' }, { label: 'Cylinder/Cutter', code: 'cylinderCutter', planField: 'cylinderCutterPlanDate', actualField: 'cylinderCutterActualDate', statusField: 'cylinderCutterStatus' }, { label: 'Lamination', code: 'lamination', planField: 'laminationPlanDate', actualField: 'laminationActualDate', statusField: 'laminationStatus' }, { label: 'Printing', code: 'printing', planField: 'printingPlanDate', actualField: 'printingActualDate', statusField: 'printingStatus' }, { label: 'Etching', code: 'etching', planField: 'etchingPlanDate', actualField: 'etchingActualDate', statusField: 'etchingStatus' }, { label: 'Slitting&Packing', code: 'slittingPacking', planField: 'slittingPackingPlanDate', actualField: 'slittingPackingActualDate', statusField: 'slittingPackingStatus' }, { label: 'Bonding', code: 'bonding', planField: 'bondingPlanDate', actualField: 'bondingActualDate', statusField: 'bondingStatus' }, { label: 'CL60/CL822', code: 'cl60Cl822', planField: 'cl60Cl822PlanDate', actualField: 'cl60Cl822ActualDate', statusField: 'cl60Cl822Status' }, { label: 'Spotting', code: 'spotting', planField: 'spottingPlanDate', actualField: 'spottingActualDate', statusField: 'spottingStatus' }, { label: 'Encoding', code: 'encoding', planField: 'encodingPlanDate', actualField: 'encodingActualDate', statusField: 'encodingStatus' }, { label: 'Die cut', code: 'dieCut', planField: 'dieCutPlanDate', actualField: 'dieCutActualDate', statusField: 'dieCutStatus' }, { label: 'Inspection', code: 'inspection', planField: 'inspectionPlanDate', actualField: 'inspectionActualDate', statusField: 'inspectionStatus' }, { label: 'Delivery&Package', code: 'deliveryPackage', planField: 'deliveryPackagePlanDate', actualField: 'deliveryPackageActualDate', statusField: 'deliveryPackageStatus' } ], buProcessConfigDialogVisible: false, buProcessConfigRows: [], buProcessConfigMap: {}, projectCategoryList: ['Low Risk', 'High Risk', 'Sustaining'], cProjectRegionList: [ { cProjectRegion: 'Global' }, { cProjectRegion: 'APAC' }, { cProjectRegion: 'CHINA' }, { cProjectRegion: 'EU' }, { cProjectRegion: 'US' }, { cProjectRegion: 'Mexico' }, { cProjectRegion: 'Other' } ], priorityList: ['Low', 'Middle', 'High'], projectStatusList: ['草稿', '进行中', '已关闭'], partTypeList: ['Active', 'On hold', 'Cancel', 'EOL'], partStatusList: ['草稿', '进行中', '已量产', '正式量产'], cProjectTypeDbList: [ { cProjectTypeDb: 'Sustaining' }, { cProjectTypeDb: 'Low Risk' }, { cProjectTypeDb: 'High Risk' } ], proofProjectPhaseList: [ { projectPhase: 'Prototype' }, { projectPhase: 'Alpha' }, { projectPhase: 'Beta' }, { projectPhase: 'Pre-launch' } ], // 一键创建字段白名单(仅 create 模式生效):后续确认要隐藏哪些字段时,只改这里即可。
oneKeyCreateVisibleFieldKeys: [ 'buNo', 'projectNo', 'projectDesc', 'customerNo', 'customerDesc', 'cProjectRegion', 'projectCreationDate', 'needDate', 'projectCategory', 'priorityLevel', 'testPartNo', 'partDesc', 'partType', 'projectManager', 'projectOwner', 'engineer', 'cManufactureEngineer', 'cQualityEngineer6', 'cQualityEngineer1', 'cQualityEngineer2', 'cQualityEngineer4', 'cQualityEngineer3', 'cQualityEngineer5', 'ipqcHardTag', 'cQualityEngineer7', 'docEngineer', 'docEngineer2', 'projectStatus', 'projectPhase', 'proofingNo', 'proofingStatus', 'proofingNumber', 'planStartDate', 'requiredDeliveryDate', 'remark' ], userBuList: [], tagNo: '', newCustomerFlag: false, newCustomerData: { customerDesc: '' }, roleDialogVisible: false, roleDialogTitle: '', roleDialogField: '', roleDialogApiKey: '0', roleDialogList: [], roleSearchData: { site: this.$store.state.user.site, username: '', userDisplay: '', active: '', page: 1, limit: 50 }, roleConfig: { projectManager: { apiKey: '0', title: 'PM/Sales', nameField: 'projectManagerName' }, projectOwner: { apiKey: '1', title: 'PjM', nameField: 'projectOwnerName' }, cQualityEngineer1: { apiKey: '2', title: 'IPQC-Lam/Pri/Etch/Slit', nameField: 'cQualityEngineer1Name' }, cQualityEngineer2: { apiKey: '3', title: 'IPQC-Converting', nameField: 'cQualityEngineer2Name' }, cQualityEngineer3: { apiKey: '4', title: 'FQC1', nameField: 'cQualityEngineer3Name' }, cManufactureEngineer: { apiKey: '5', title: 'MFG', nameField: 'cManufactureEngineerName' }, engineer: { apiKey: '6', title: 'Engineer', nameField: 'engineerName' }, cQualityEngineer4: { apiKey: '7', title: 'SQE', nameField: 'cQualityEngineer4Name' }, cQualityEngineer5: { apiKey: '7', title: 'FQC2', nameField: 'cQualityEngineer5Name' }, cQualityEngineer6: { apiKey: '7', title: 'IQC', nameField: 'cQualityEngineer6Name' }, docEngineer: { apiKey: '7', title: '文档工程师', nameField: 'docEngineerName' }, ipqcHardTag: { apiKey: '7', title: 'IPQC-Hardtag', nameField: 'ipqcHardTagName' }, cQualityEngineer7: { apiKey: '7', title: '前道工程师', nameField: 'cQualityEngineer7Name' }, docEngineer2: { apiKey: '8', title: '文档工程师2', nameField: 'docEngineer2Name' } }, dataList: [], dataListLoading: false, syncNpiLoading: false, syncProofLoading: false, pageIndex: 1, pageSize: 20, totalPage: 0, currentRow: null, manualSelectedTrackingId: null, selectionRows: [], batchEditMode: false, processHistoryMap: {}, processTooltipHoverKey: '', processTooltip: { visible: false, content: '', left: 0, top: 0 }, processHistoryRows: [], commentsEditingTrackingId: null, commentsDraft: '', commentsSaveLoading: false,
oneKeyDialogVisible: false, oneKeyDialogMode: 'create', saveOneKeyLoading: false, oneKeyForm: {},
proofDialogVisible: false, proofSaveLoading: false, proofDialogData: this.getDefaultProofDialogData(),
processDialogVisible: false, saveProcessLoading: false, processForm: {},
finishDialogVisible: false, finishSaveLoading: false, finishForm: { trackingId: null, actualityDeliveryDate: '' } } }, activated () { this.loadUserBuList().then(() => { this.queryBuProcessConfig() }) this.getDataList() }, computed: { visibleProcessColumns () { const searchBuNo = this.getSelectedSearchBuNo() // 指定了查询BU时,整表进度列按该BU配置展示。
if (!searchBuNo) { return this.processColumns } const visibleCodes = this.getProcessVisibleCodesByBuNo(searchBuNo) if (!visibleCodes) { return this.processColumns } const visibleColumns = this.processColumns.filter(item => visibleCodes.indexOf(item.code) > -1) return visibleColumns.length > 0 ? visibleColumns : this.processColumns } }, methods: { loadUserBuList () { if (this.userBuList.length > 0) { this.ensureBuProcessConfigRows() return Promise.resolve(this.userBuList) } const params = { username: this.$store.state.user.name } return getSiteAndBuByUserName(params).then(({ data }) => { if (data && data.code === 0) { const excludedBuNoList = ['3_02-Hardtag', '4_04-MHM', '5_05-Alpha'] this.userBuList = (data.rows || []).filter(item => { const buNo = item && item.buNo ? String(item.buNo).trim() : '' return excludedBuNoList.indexOf(buNo) === -1 }) this.ensureBuProcessConfigRows() } return this.userBuList }).catch(() => { this.userBuList = [] this.ensureBuProcessConfigRows() return [] }) }, getAllProcessCodes () { return this.processColumns.map(item => item.code) }, normalizeBuNo (buNo, site) { const rawBuNo = buNo == null ? '' : String(buNo).trim() if (!rawBuNo) { return '' } const splitIdx = rawBuNo.indexOf('_') if (splitIdx < 1 || splitIdx >= rawBuNo.length - 1) { return rawBuNo } // BU统一按“去掉site前缀”展示与保存,例如 2_01-Label -> 01-Label。
return rawBuNo.substring(splitIdx + 1) }, getValidProcessCodes (codes) { if (!Array.isArray(codes) || codes.length === 0) { return [] } const allCodes = this.getAllProcessCodes() const normalizedCodes = [] codes.forEach(code => { const codeVal = code == null ? '' : String(code).trim() if (!codeVal || allCodes.indexOf(codeVal) === -1) { return } if (normalizedCodes.indexOf(codeVal) === -1) { normalizedCodes.push(codeVal) } }) return normalizedCodes }, getBuProcessConfigEntry (buNo, site) { const normalizedBuNo = this.normalizeBuNo(buNo, site) if (!normalizedBuNo) { return null } const config = this.buProcessConfigMap[normalizedBuNo] if (config) { return { key: normalizedBuNo, config: config } } return null }, getSelectedSearchBuNo () { return this.normalizeBuNo(this.searchData.buNo, this.searchData.site) }, handleSearchBuChange () { // BU筛选变化(含清空)后立即重新查询,并回到第一页。
this.getDataList('Y') }, getProcessVisibleCodesByBuNo (buNo, site) { const configEntry = this.getBuProcessConfigEntry(buNo, site) const config = configEntry ? configEntry.config : null // 没有配置或配置为全部显示时,返回 null 表示该 BU 工序列不过滤。
if (!config || config.showAll !== false) { return null } const validCodes = this.getValidProcessCodes(config.processCodes) if (validCodes.length === 0 || validCodes.length >= this.processColumns.length) { return null } return validCodes }, isProcessColumnVisibleForRow (row, processCol) { if (!row || !processCol) { return true } // 已指定查询BU时,列集合已经按查询BU过滤,不再按行二次隐藏。
if (this.getSelectedSearchBuNo()) { return true } const visibleCodes = this.getProcessVisibleCodesByBuNo(row.buNo, row.site) if (!visibleCodes) { return true } return visibleCodes.indexOf(processCol.code) > -1 }, queryBuProcessConfig () { const inData = { site: this.$store.state.user.site, userName: this.$store.state.user.name } queryProofTrackingBuProcessConfig(inData).then(({ data }) => { if (!(data && data.code === 0)) { this.$message.error((data && data.msg) || '加载事业部进度展示配置失败') return } const rows = data.rows || [] const nextMap = {} const allCodeCount = this.processColumns.length rows.forEach(item => { const buNo = this.normalizeBuNo(item && item.buNo ? item.buNo : '', this.$store.state.user.site) if (!buNo) { return } const processCodes = this.getValidProcessCodes(item.processCodes) const showAll = item.showAll !== false || processCodes.length === 0 || processCodes.length >= allCodeCount if (!showAll) { nextMap[buNo] = { showAll: false, processCodes: processCodes } } }) this.buProcessConfigMap = nextMap this.ensureBuProcessConfigRows() this.refreshTrackingTableLayout() }).catch(() => { this.$message.error('加载事业部进度展示配置异常') }) }, ensureBuProcessConfigRows () { const allCodes = this.getAllProcessCodes() const rows = [] const usedBuMap = {} const appendRow = (buNo, buDesc, site) => { const key = this.normalizeBuNo(buNo, site || this.$store.state.user.site) if (!key || usedBuMap[key]) { return } usedBuMap[key] = true const cfg = this.buProcessConfigMap[key] const row = { buNo: key, buDesc: buDesc || key, processCodes: allCodes.slice() } if (cfg && cfg.showAll === false) { const validCodes = this.getValidProcessCodes(cfg.processCodes) if (validCodes.length > 0 && validCodes.length < allCodes.length) { row.processCodes = validCodes } } rows.push(row) } this.userBuList.forEach(item => { if (!item || !item.buNo) { return } appendRow(item.buNo, item.buDesc || this.normalizeBuNo(item.buNo, this.$store.state.user.site), this.$store.state.user.site) }) Object.keys(this.buProcessConfigMap).forEach(buNo => { appendRow(buNo, buNo, this.$store.state.user.site) }) this.buProcessConfigRows = rows }, openBuProcessConfigDialog () { this.ensureBuProcessConfigRows() this.buProcessConfigDialogVisible = true }, refreshTrackingTableLayout () { this.$nextTick(() => { const tableRef = this.$refs.trackingTable if (tableRef && tableRef.doLayout) { tableRef.doLayout() } }) }, handleBuProcessCodesChange (row) { if (!row) { return } row.processCodes = this.getValidProcessCodes(row.processCodes) }, saveBuProcessConfig () { if (!this.buProcessConfigRows || this.buProcessConfigRows.length === 0) { this.$message.warning('暂无可配置的事业部') return } const nextMap = {} const allCodeCount = this.processColumns.length const submitRows = [] for (let i = 0; i < this.buProcessConfigRows.length; i++) { const row = this.buProcessConfigRows[i] const buNo = this.normalizeBuNo(row && row.buNo ? row.buNo : '', this.$store.state.user.site) if (!buNo) { continue } const validCodes = this.getValidProcessCodes(row.processCodes) if (validCodes.length === 0) { this.$message.warning(`事业部【${row.buDesc || buNo}】至少需要选择一个可见进度`) return } const showAll = validCodes.length >= allCodeCount if (!showAll) { nextMap[buNo] = { showAll: false, processCodes: validCodes } } submitRows.push({ buNo: buNo, showAll: showAll, processCodes: validCodes }) } const inData = { site: this.$store.state.user.site, updateBy: this.$store.state.user.name, rows: submitRows } saveProofTrackingBuProcessConfig(inData).then(({ data }) => { if (!(data && data.code === 0)) { this.$message.error((data && data.msg) || '事业部进度展示设置保存失败') return } this.buProcessConfigMap = nextMap this.ensureBuProcessConfigRows() this.buProcessConfigDialogVisible = false this.$message.success(data.msg || '事业部进度展示设置已保存') this.refreshTrackingTableLayout() }).catch(() => { this.$message.error('事业部进度展示设置保存异常') }) }, getRoleApi (apiKey) { const apiMap = { 0: searchBusinessInfo, 1: searchBusinessInfo1, 2: searchBusinessInfo2, 3: searchBusinessInfo3, 4: searchBusinessInfo4, 5: searchBusinessInfo5, 6: searchBusinessInfo6, 7: searchBusinessInfo7, 8: searchBusinessInfo8 } return apiMap[apiKey] }, fetchRoleListByApiKey (apiKey, queryData) { const apiFn = this.getRoleApi(apiKey) if (!apiFn) { return Promise.resolve([]) } const params = { site: this.$store.state.user.site, username: queryData && queryData.username ? queryData.username : '', userDisplay: queryData && queryData.userDisplay ? queryData.userDisplay : '', roleDesc: '', active: queryData && queryData.active ? queryData.active : '', page: 1, limit: 200 } return apiFn(params).then(({ data }) => { if (data && data.code === 0) { return (data.rows || []).map(item => { return { username: item.username, userDisplay: item.userDisplay || item.user_display || '', active: item.active } }) } return [] }) }, newCustomer () { this.newCustomerData.customerDesc = '' this.newCustomerFlag = true }, getBaseList (val) { this.tagNo = val this.$nextTick(() => { const strVal = val === 509 ? (this.oneKeyForm.customerNo || '') : '' this.$refs.baseList.init(val, strVal, '') }) }, getBaseData (val) { if (this.tagNo === 509) { this.oneKeyForm.customerNo = val.customer_no this.oneKeyForm.customerDesc = val.customer_desc } }, customerNoBlur () { if (!this.oneKeyForm.customerNo) { this.oneKeyForm.customerDesc = '' return } const params = { customerNo: this.oneKeyForm.customerNo, createBy: this.$store.state.user.name } queryCustomerList(params).then(({ data }) => { if (data && data.code === 0 && data.rows && data.rows.length === 1) { this.oneKeyForm.customerDesc = data.rows[0].customerDesc } else { this.oneKeyForm.customerDesc = '' } }) }, saveNewCustomer () { if (!this.newCustomerData.customerDesc) { this.$message.warning('请输入客户名称!') return } this.generateNextCustomerNo().then(customerNo => { if (!customerNo) { this.$alert('该客户已存在,请重新输入客户名称!', '提示', { confirmButtonText: '确定', type: 'warning' }) return } const params = { site: this.$store.state.user.site, customerNo: customerNo, customerDesc: this.newCustomerData.customerDesc, createBy: this.$store.state.user.name } saveNewCustomer(params).then(({ data }) => { if (data && data.code === 0) { this.oneKeyForm.customerNo = customerNo this.oneKeyForm.customerDesc = this.newCustomerData.customerDesc this.newCustomerFlag = false this.$message.success(data.msg || '新增客户成功') } else { this.$alert(data && data.msg ? data.msg : '新增客户失败', '提示', { confirmButtonText: '确定', type: 'warning' }) } }) }) }, generateNextCustomerNo () { const params = { site: this.$store.state.user.site, customerDesc: this.newCustomerData.customerDesc, createBy: this.$store.state.user.name } return new Promise(resolve => { getCustomerNo(params).then(({ data }) => { if (!(data && data.code === 0)) { resolve(false) return } if (!data.data || !data.data.customerNo || data.data === '0') { resolve('C0001') return } const lastCustomerNo = parseInt(String(data.data.customerNo).substring(1), 10) || 0 const nextCustomerNo = lastCustomerNo + 1 if (nextCustomerNo < 10) { resolve('C000' + nextCustomerNo) } else if (nextCustomerNo < 100) { resolve('C00' + nextCustomerNo) } else if (nextCustomerNo < 1000) { resolve('C0' + nextCustomerNo) } else { resolve('C' + nextCustomerNo) } }).catch(() => { resolve(false) }) }) }, closeNewCustomer () { this.newCustomerData.customerDesc = '' this.newCustomerFlag = false }, openRoleDialog (field) { const roleCfg = this.roleConfig[field] if (!roleCfg) { return } this.roleDialogField = field this.roleDialogApiKey = roleCfg.apiKey this.roleDialogTitle = roleCfg.title this.roleSearchData = { site: this.$store.state.user.site, username: '', userDisplay: '', active: '', page: 1, limit: 50 } this.roleDialogVisible = true this.queryRoleDialogList() }, queryRoleDialogList () { this.fetchRoleListByApiKey(this.roleDialogApiKey, this.roleSearchData).then(rows => { this.roleDialogList = rows }) }, pickRole (row) { const roleCfg = this.roleConfig[this.roleDialogField] if (!roleCfg) { return } const value = row.username + '-' + row.userDisplay this.oneKeyForm[this.roleDialogField] = value this.oneKeyForm[roleCfg.nameField] = row.userDisplay this.roleDialogVisible = false }, getEmptyOneKeyForm () { return { site: this.$store.state.user.site, buNo: '', projectNo: '', projectName: '', projectDesc: '', projectStatus: '草稿', projectSource: '', testPartNo: '', partDesc: '', partName: '', partSpec: '', materialNumber: '', finalPartDesc: '', finalPartNo: '', baseNo: '', revNo: '', customerNo: '', customerDesc: '', finalCustomerId: '', customerRemark: '', parentProjectNo: '', oriProjectId: '', projectCategory: '', cProjectRegion: '', projectManager: '', projectManagerName: '', projectOwner: '', projectOwnerName: '', cQualityEngineer1: '', cQualityEngineer1Name: '', cQualityEngineer2: '', cQualityEngineer2Name: '', cQualityEngineer3: '', cQualityEngineer3Name: '', cQualityEngineer4: '', cQualityEngineer4Name: '', cQualityEngineer5: '', cQualityEngineer5Name: '', cQualityEngineer6: '', cQualityEngineer6Name: '', cManufactureEngineer: '', cManufactureEngineerName: '', docEngineer: '', docEngineerName: '', docEngineer2: '', docEngineer2Name: '', ipqcHardTag: '', ipqcHardTagName: '', cQualityEngineer7: '', cQualityEngineer7Name: '', partType: '', partStatus: '草稿', projectPhase: '', tracker: '', engineer: '', engineerName: '', priorityLevel: '', proofingNo: '', proofingStatus: '进行中', proofingNumber: '', projectCreationDate: '', projectCloseDate: '', buildDate: '', closeDate: '', comments: '', planStartDate: '', requiredDeliveryDate: '', needDate: '', remark: '' } }, getDefaultProofDialogData () { return { trackingId: null, site: this.$store.state.user.site, projectId: null, projectNo: '', projectDesc: '', buNo: '', customerNo: '', customerDesc: '', projectPartId: null, testPartNo: '', partDesc: '', projectCategory: '', cProjectTypeDb: '', projectPhase: '', projectManager: '', projectOwner: '', engineer: '', priorityLevel: '', proofingNo: '', proofingNumber: '', planStartDate: '', requiredDeliveryDate: '', actualityDeliveryDate: '', proofingStatus: '草稿', remark: '', createBy: this.$store.state.user.name, updateBy: this.$store.state.user.name } }, getDataList (flag) { if (flag === 'Y') { this.pageIndex = 1 } this.searchData.site = this.$store.state.user.site this.searchData.userName = this.$store.state.user.name const inData = Object.assign({}, this.searchData, { page: this.pageIndex, limit: this.pageSize }) inData.buNo = this.normalizeBuNo(inData.buNo, inData.site) this.dataListLoading = true searchProofTracking(inData).then(({ data }) => { this.dataListLoading = false this.resetProcessTooltip() if (data && data.code === 0) { this.dataList = (data.page && data.page.list) || [] this.totalPage = (data.page && data.page.totalCount) || 0 if (this.commentsEditingTrackingId) { const hasEditingRow = this.dataList.some(item => item.trackingId === this.commentsEditingTrackingId) if (!hasEditingRow) { this.cancelCommentsEdit() } } const manualSelectedId = this.manualSelectedTrackingId const previousCurrentId = this.currentRow && this.currentRow.trackingId let nextCurrentRow = null if (manualSelectedId != null) { // 只要用户没有手动点新行,刷新后始终尝试回选上次手动选中的那一行
nextCurrentRow = this.dataList.find(item => item && item.trackingId === manualSelectedId) || null } else if (previousCurrentId != null) { nextCurrentRow = this.dataList.find(item => item && item.trackingId === previousCurrentId) || null } if (!nextCurrentRow && manualSelectedId == null && this.dataList.length > 0) { nextCurrentRow = this.dataList[0] } this.currentRow = nextCurrentRow this.$nextTick(() => { if (this.$refs.trackingTable && typeof this.$refs.trackingTable.setCurrentRow === 'function') { this.$refs.trackingTable.setCurrentRow(this.currentRow || null) } }) } else { this.dataList = [] this.totalPage = 0 this.currentRow = null this.$nextTick(() => { if (this.$refs.trackingTable && typeof this.$refs.trackingTable.setCurrentRow === 'function') { this.$refs.trackingTable.setCurrentRow(null) } }) this.$message.error(data.msg || '查询失败') } }).catch(() => { this.dataListLoading = false this.resetProcessTooltip() this.$message.error('查询异常') }) }, resetSearch () { this.searchData.projectNo = '' this.searchData.projectDesc = '' this.searchData.buNo = '' this.searchData.testPartNo = '' this.searchData.partDesc = '' this.searchData.customerNo = '' this.searchData.proofingNo = '' this.searchData.projectPartSyncFlag = '' this.searchData.proofSyncFlag = '' this.searchData.proofingStatus = '' this.getDataList('Y') }, selectionChange (rows) { this.selectionRows = rows || [] }, isCommentsEditing (row) { return !!row && row.trackingId === this.commentsEditingTrackingId }, startCommentsEdit (row) { if (this.batchEditMode || !row || !row.trackingId || this.commentsSaveLoading) { return } this.commentsEditingTrackingId = row.trackingId this.commentsDraft = row.comments || '' }, cancelCommentsEdit () { if (this.commentsSaveLoading) { return } this.commentsEditingTrackingId = null this.commentsDraft = '' }, handleCommentsBlur (row) { if (this.commentsSaveLoading || !this.isCommentsEditing(row)) { return } const comments = this.commentsDraft == null ? '' : String(this.commentsDraft) if (comments.trim()) { this.saveCommentsEdit(row, { fromBlur: true, draftComments: comments }) return } this.cancelCommentsEdit() }, saveCommentsEdit (row, options) { const saveOptions = options || {} const fromBlur = !!saveOptions.fromBlur if (this.commentsSaveLoading) { return } if (!row || !row.trackingId) { this.$message.warning('跟踪记录ID不能为空') return } const comments = Object.prototype.hasOwnProperty.call(saveOptions, 'draftComments') ? (saveOptions.draftComments == null ? '' : String(saveOptions.draftComments)) : (this.commentsDraft == null ? '' : String(this.commentsDraft)) if (comments === (row.comments || '')) { this.cancelCommentsEdit() return } if (fromBlur) { this.commentsEditingTrackingId = null this.commentsDraft = '' } this.commentsSaveLoading = true updateProofTrackingComments({ trackingId: row.trackingId, comments: comments, updateBy: this.$store.state.user.name }).then(({ data }) => { this.commentsSaveLoading = false if (data && data.code === 0) { row.comments = comments this.$message.success('Comments保存成功') this.cancelCommentsEdit() } else { this.$message.error(data.msg || 'Comments保存失败') } }).catch(() => { this.commentsSaveLoading = false this.$message.error('Comments保存异常') }) }, rowClick (row) { this.currentRow = row this.manualSelectedTrackingId = row && row.trackingId ? row.trackingId : null }, sizeChangeHandle (val) { this.pageSize = val this.pageIndex = 1 this.getDataList() }, currentChangeHandle (val) { this.pageIndex = val this.getDataList() }, getActionRow (row) { return row || this.currentRow || (this.selectionRows.length > 0 ? this.selectionRows[0] : null) }, getTrackingProjectPartId (row) { if (!row) { return null } return row.projectPartId || row.project_part_id || null }, isSyncedByIdRule (idValue) { if (idValue == null || String(idValue).trim() === '') { return false } // 约定:NPI 正式库主键 < 100000,tracking 临时主键 >= 100000。
const numericId = Number(idValue) return Number.isFinite(numericId) && numericId > 0 && numericId < 100000 }, isProjectPartSynced (row) { if (!row) { return false } const projectId = row.projectId || row.project_id || null const projectPartId = this.getTrackingProjectPartId(row) return this.isSyncedByIdRule(projectId) && this.isSyncedByIdRule(projectPartId) }, isProofSynced (row) { if (!row) { return false } const proofingId = row.proofingId || row.proofing_id || row.trackingId || null return this.isSyncedByIdRule(proofingId) }, getProjectPartSyncTextClass (row) { return this.isProjectPartSynced(row) ? 'sync-text--ok' : 'sync-text--pending' }, getProofSyncTextClass (row) { return this.isProofSynced(row) ? 'sync-text--ok' : 'sync-text--pending' }, getProjectCategoryValue (source) { if (!source) { return '' } const category = source.cProjectTypeDb || source.projectCategory || source.project_category || source.c_project_type_db || '' return this.isBlankValue(category) ? '' : String(category).trim() }, isOneKeyCreateFieldVisible (fieldKey) { if (this.oneKeyDialogMode !== 'create') { return true } return this.oneKeyCreateVisibleFieldKeys.indexOf(fieldKey) > -1 }, isBlankValue (value) { return value == null || String(value).trim() === '' }, getBuOptionValue (site, buNo) { const rawBuNo = buNo == null ? '' : String(buNo).trim() if (!rawBuNo) { return '' } if (rawBuNo.indexOf('_') > -1) { return rawBuNo } const targetSuffix = `_${rawBuNo}` const matched = this.userBuList.find(item => item && item.buNo && (item.buNo === rawBuNo || item.buNo === `${site}_${rawBuNo}` || String(item.buNo).endsWith(targetSuffix))) if (matched && matched.buNo) { return matched.buNo } return site ? `${site}_${rawBuNo}` : rawBuNo }, getRoleDisplayName (value) { if (this.isBlankValue(value)) { return '' } const strVal = String(value) const splitIdx = strVal.indexOf('-') return splitIdx > -1 ? strVal.substring(splitIdx + 1) : strVal }, buildOneKeySubmitPayload () { const inData = Object.assign({}, this.oneKeyForm, { createBy: this.$store.state.user.name, updateBy: this.$store.state.user.name }) if (!inData.projectName && inData.projectDesc) { inData.projectName = inData.projectDesc } if (!inData.projectDesc && inData.projectName) { inData.projectDesc = inData.projectName } if (!inData.tracker) { inData.tracker = inData.projectOwner } if (!inData.priorityLevel) { inData.priorityLevel = '' } // 弹窗仅展示一个立项日期输入,提交时同步到项目与物料两个日期字段,避免物料立项日期被误判为空
if (!this.isBlankValue(inData.projectCreationDate)) { inData.buildDate = inData.projectCreationDate } else if (!this.isBlankValue(inData.buildDate)) { inData.projectCreationDate = inData.buildDate } return inData }, validateOneKeySubmitPayload (inData) { if (this.isBlankValue(inData.site) || this.isBlankValue(inData.buNo)) { this.$message.error('工厂和BU不能为空') return false } if (this.oneKeyDialogMode === 'edit') { if (!inData.projectId || !inData.projectPartId || !inData.trackingId) { this.$message.error('项目/物料/打样记录ID缺失,无法修改') return false } if (this.isBlankValue(inData.projectNo)) { this.$message.error('项目编码不能为空') return false } if (this.isBlankValue(inData.projectName) && this.isBlankValue(inData.projectDesc)) { this.$message.error('项目名称不能为空') return false } if (this.isBlankValue(inData.testPartNo)) { this.$message.error('项目料号不能为空') return false } if (this.isBlankValue(inData.partDesc)) { this.$message.error('料号描述不能为空') return false } if (!this.isBlankValue(inData.proofingNumber) && !/^[1-9]\d*$/.test(String(inData.proofingNumber))) { this.$message.error('打样数量必须是正整数') return false } } return true }, buildOneKeyEditForm (current, projectData, partData) { const project = projectData || {} const part = partData || {} const form = Object.assign(this.getEmptyOneKeyForm(), { site: project.site || part.site || current.site || this.$store.state.user.site, projectId: project.projectId || current.projectId || null, projectPartId: part.projectPartId || this.getTrackingProjectPartId(current), trackingId: current.trackingId || null, buNo: this.getBuOptionValue(project.site || current.site || this.$store.state.user.site, project.buNo || current.buNo), projectNo: project.projectNo || current.projectNo || '', projectName: project.projectName || project.projectDesc || current.projectName || current.projectDesc || '', projectDesc: project.projectDesc || project.projectName || current.projectDesc || current.projectName || '', projectStatus: project.status || project.projectStatus || '草稿', projectSource: project.projectSource || '', testPartNo: part.testPartNo || current.testPartNo || '', partDesc: part.partDesc || current.partDesc || '', partName: part.partName || '', partSpec: part.partSpec || '', materialNumber: part.materialNumber || '', finalPartDesc: part.finalPartDesc || '', finalPartNo: part.finalPartNo || current.finalPartNo || '', baseNo: part.baseNo || '', revNo: part.revNo || '', customerNo: project.customerNo || part.customerNo || current.customerNo || '', customerDesc: project.customerDesc || current.customerDesc || '', finalCustomerId: project.finalCustomerId || '', customerRemark: project.customerRemark || '', parentProjectNo: project.parentProjectNo || '', oriProjectId: project.oriProjectId || '', projectCategory: project.projectCategory || part.projectCategory || current.projectCategory || '', cProjectRegion: project.cProjectRegion || current.cProjectRegion || '', projectManager: project.projectManager || part.projectManager || current.projectManager || '', projectOwner: project.projectOwner || part.projectOwner || current.projectOwner || '', cQualityEngineer1: project.cQualityEngineer1 || part.cQualityEngineer1 || '', cQualityEngineer2: project.cQualityEngineer2 || part.cQualityEngineer2 || '', cQualityEngineer3: project.cQualityEngineer3 || part.cQualityEngineer3 || '', cQualityEngineer4: project.cQualityEngineer4 || part.cQualityEngineer4 || '', cQualityEngineer5: project.cQualityEngineer5 || part.cQualityEngineer5 || '', cQualityEngineer6: project.cQualityEngineer6 || part.cQualityEngineer6 || '', cManufactureEngineer: project.cManufactureEngineer || part.cManufactureEngineer || '', docEngineer: project.docEngineer || part.docEngineer || '', docEngineer2: project.docEngineer2 || part.docEngineer2 || '', ipqcHardTag: project.ipqcHardTag || part.ipqcHardTag || '', cQualityEngineer7: project.cQualityEngineer7 || part.cQualityEngineer7 || '', partType: part.partType || '', partStatus: part.status || part.partStatus || '草稿', projectPhase: current.projectPhase || '', tracker: current.tracker || project.projectOwner || part.projectOwner || '', engineer: current.engineer || project.engineer || part.engineer || '', priorityLevel: current.priorityLevel || current.priority || project.priority || part.priority || '', proofingNo: current.proofingNo || '', proofingStatus: current.proofingStatus || '进行中', proofingNumber: current.proofingNumber, projectCreationDate: project.projectCreationDate || part.buildDate || '', projectCloseDate: project.projectCloseDate || '', buildDate: part.buildDate || project.projectCreationDate || '', closeDate: part.closeDate || '', comments: current.comments || '', planStartDate: current.planStartDate || '', requiredDeliveryDate: current.requiredDeliveryDate || '', needDate: project.needDate || part.needDate || current.needDate || '', remark: part.remark || project.remark || current.remark || '' }) form.projectManagerName = this.getRoleDisplayName(form.projectManager) form.projectOwnerName = this.getRoleDisplayName(form.projectOwner) form.engineerName = this.getRoleDisplayName(form.engineer) form.cManufactureEngineerName = this.getRoleDisplayName(form.cManufactureEngineer) form.cQualityEngineer1Name = this.getRoleDisplayName(form.cQualityEngineer1) form.cQualityEngineer2Name = this.getRoleDisplayName(form.cQualityEngineer2) form.cQualityEngineer3Name = this.getRoleDisplayName(form.cQualityEngineer3) form.cQualityEngineer4Name = this.getRoleDisplayName(form.cQualityEngineer4) form.cQualityEngineer5Name = this.getRoleDisplayName(form.cQualityEngineer5) form.cQualityEngineer6Name = this.getRoleDisplayName(form.cQualityEngineer6) form.docEngineerName = this.getRoleDisplayName(form.docEngineer) form.docEngineer2Name = this.getRoleDisplayName(form.docEngineer2) form.ipqcHardTagName = this.getRoleDisplayName(form.ipqcHardTag) form.cQualityEngineer7Name = this.getRoleDisplayName(form.cQualityEngineer7) return form }, openOneKeyDialog (mode, row) { const dialogMode = mode === 'edit' ? 'edit' : 'create' this.oneKeyDialogMode = dialogMode if (dialogMode !== 'edit') { this.oneKeyForm = this.getEmptyOneKeyForm() if (this.userBuList.length > 0) { this.oneKeyForm.buNo = this.userBuList[0].buNo || '' } this.oneKeyDialogVisible = true return } const current = this.getActionRow(row) if (!current) { this.$message.warning('请先选择一条记录后再修改项目/物料/打样') this.oneKeyDialogMode = 'create' return } const projectPartId = this.getTrackingProjectPartId(current) if (!current.projectId || !projectPartId || !current.trackingId) { this.$message.warning('当前记录缺少项目/物料/打样关键信息,无法修改') this.oneKeyDialogMode = 'create' return } const queryUser = this.$store.state.user.name const querySite = current.site || this.$store.state.user.site this.saveOneKeyLoading = true Promise.all([ searchProjectInfoTracking({ site: querySite, userName: queryUser, projectId: current.projectId, page: 1, limit: 1 }), searchProjectPartTracking({ site: querySite, userName: queryUser, projectPartId: projectPartId, page: 1, limit: 1 }) ]).then(([projectResp, partResp]) => { const projectData = projectResp && projectResp.data const partData = partResp && partResp.data if (!projectData || projectData.code !== 0) { throw new Error((projectData && projectData.msg) || '加载项目信息失败') } if (!partData || partData.code !== 0) { throw new Error((partData && partData.msg) || '加载项目物料信息失败') } const projectRows = (projectData.page && projectData.page.list) || [] const partRows = (partData.page && partData.page.list) || [] if (projectRows.length === 0) { throw new Error('未找到对应的项目信息,无法修改') } if (partRows.length === 0) { throw new Error('未找到对应的项目物料信息,无法修改') } this.oneKeyForm = this.buildOneKeyEditForm(current, projectRows[0], partRows[0]) this.oneKeyDialogVisible = true }).catch((e) => { this.$message.error((e && e.message) || '加载项目/物料/打样信息异常') this.oneKeyDialogMode = 'create' }).finally(() => { this.saveOneKeyLoading = false }) }, submitOneKey () { const inData = this.buildOneKeySubmitPayload() if (!this.validateOneKeySubmitPayload(inData)) { return } this.saveOneKeyLoading = true if (this.oneKeyDialogMode === 'edit') { oneKeyUpdateProofTracking(inData).then(({ data }) => { if (!data || data.code !== 0) { throw new Error((data && data.msg) || '修改项目/物料/打样失败') } this.$message.success('项目/物料/打样修改成功') this.oneKeyDialogVisible = false this.getDataList() }).catch((e) => { this.$message.error((e && e.message) || '项目/物料/打样修改异常') }).finally(() => { this.saveOneKeyLoading = false }) return } oneKeyCreateProofTracking(inData).then(({ data }) => { this.saveOneKeyLoading = false if (data && data.code === 0) { this.$message.success('一键创建成功') this.oneKeyDialogVisible = false this.searchData.projectNo = '' this.searchData.projectDesc = '' this.searchData.testPartNo = '' this.searchData.partDesc = '' this.searchData.customerNo = '' this.searchData.proofingNo = '' this.searchData.projectPartSyncFlag = '' this.searchData.proofSyncFlag = '' this.searchData.proofingStatus = '' this.getDataList('Y') } else { this.$message.error(data.msg || '一键创建失败') } }).catch(() => { this.saveOneKeyLoading = false this.$message.error('一键创建异常') }) }, openCreateProofDialog (row) { const current = this.getActionRow(row) if (!current) { this.$message.warning('请先选择一条记录') return } this.currentRow = current const projectPartId = this.getTrackingProjectPartId(current) || current.id const projectCategory = this.getProjectCategoryValue(current) this.proofDialogData = Object.assign(this.getDefaultProofDialogData(), { trackingId: null, site: current.site || this.$store.state.user.site, projectId: current.projectId, projectNo: current.projectNo, projectDesc: current.projectDesc, buNo: current.buNo, customerNo: current.customerNo, customerDesc: current.customerDesc, projectPartId: projectPartId, testPartNo: current.testPartNo, partDesc: current.partDesc, projectCategory: projectCategory, cProjectTypeDb: projectCategory, projectManager: current.projectManager, projectOwner: current.projectOwner, engineer: current.engineer, priorityLevel: current.priority, proofingNo: '', proofingNumber: '', planStartDate: '', requiredDeliveryDate: '', actualityDeliveryDate: '', proofingStatus: '草稿', remark: '', createBy: this.$store.state.user.name, updateBy: this.$store.state.user.name }) if (this.isBlankValue(projectCategory)) { this.$message.warning('未获取到项目分类,请手动选择') } this.proofDialogVisible = true }, validateSyncToNpiRow (row) { const projectPartId = this.getTrackingProjectPartId(row) const missingLabels = [] if (this.isBlankValue(row && row.site)) { missingLabels.push('工厂') } if (this.isBlankValue(row && row.buNo)) { missingLabels.push('BU') } if (!row || !row.projectId) { missingLabels.push('项目ID') } if (this.isBlankValue(row && row.projectNo)) { missingLabels.push('项目编码') } if (this.isBlankValue(row && row.projectName) && this.isBlankValue(row && row.projectDesc)) { missingLabels.push('项目名称') } if (!projectPartId) { missingLabels.push('项目物料ID') } if (this.isBlankValue(row && row.testPartNo)) { missingLabels.push('项目料号') } if (this.isBlankValue(row && row.partDesc)) { missingLabels.push('料号描述') } if (missingLabels.length > 0) { const missingHtml = missingLabels.map(item => `- ${item}`).join('<br/>') this.$alert(`同步到NPI前,请先完善以下字段:<br/>${missingHtml}`, '提示', { confirmButtonText: '确定', dangerouslyUseHTMLString: true }) return { passed: false, projectPartId: projectPartId } } return { passed: true, projectPartId: projectPartId } }, syncToNpi (row) { const current = this.getActionRow(row) if (!current) { this.$message.warning('请先选择一条记录') return } const syncCheck = this.validateSyncToNpiRow(current) if (!syncCheck.passed) { return } const projectPartId = syncCheck.projectPartId this.currentRow = current const projectNo = current.projectNo || '-' const testPartNo = current.testPartNo || '-' const confirmMsg = `确认将项目编码[${projectNo}]物料编码[${testPartNo}]同步到NPI吗?` this.$confirm(confirmMsg, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', dangerouslyUseHTMLString: true }).then(() => { const operator = this.$store.state.user.name this.syncNpiLoading = true syncProjectPartTracking({ projectPartId: projectPartId, updateBy: operator, createBy: operator, userName: operator }).then(({ data }) => { if (data && data.code === 0) { this.$message.success(`同步成功`) this.getDataList() } else { const failMsg = (data && data.msg) || '同步到NPI失败' if (failMsg.indexOf('缺少以下必填项') > -1) { this.$alert(failMsg, '同步校验未通过', { confirmButtonText: '确定' }) } else { this.$message.error(failMsg) } } }).catch(() => { this.$message.error('同步到NPI异常') }).finally(() => { this.syncNpiLoading = false }) }) .catch(() => {}) }, syncProofToNpi (row) { const current = this.getActionRow(row) if (!current || !current.trackingId) { this.$message.warning('请先选择一条打样记录') return } this.currentRow = current const projectNo = current.projectNo || '-' const testPartNo = current.testPartNo || '-' const proofingNo = current.proofingNo || '-' const confirmMsg = `确认将当前打样记录同步到NPI吗?<br/>项目编码:${projectNo}<br/>物料编码:${testPartNo}<br/>打样单号:${proofingNo}` this.$confirm(confirmMsg, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', dangerouslyUseHTMLString: true }).then(() => { const operator = this.$store.state.user.name this.syncProofLoading = true syncProofTracking({ trackingId: current.trackingId, updateBy: operator, createBy: operator, userName: operator }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('打样同步成功') this.getDataList() } else { const failMsg = (data && data.msg) || '同步打样到NPI失败' if (failMsg.indexOf('缺少以下必填项') > -1 || failMsg.indexOf('请先同步项目') > -1 || failMsg.indexOf('请先同步项目物料') > -1) { this.$alert(failMsg, '同步校验未通过', { confirmButtonText: '确定' }) } else { this.$message.error(failMsg) } } }).catch(() => { this.$message.error('同步打样到NPI异常') }).finally(() => { this.syncProofLoading = false }) }).catch(() => {}) }, validateProofDialogData () { if (!this.proofDialogData.projectId || !this.proofDialogData.projectPartId) { this.$message.warning('项目或项目料号信息不完整') return false } if (!this.proofDialogData.cProjectTypeDb) { this.$message.warning('项目分类不能为空') return false } if (!this.proofDialogData.projectPhase) { this.$message.warning('项目阶段不能为空') return false } if (!this.proofDialogData.proofingNo) { this.$message.warning('打样单号不能为空') return false } if (!this.proofDialogData.proofingNumber) { this.$message.warning('数量不能为空') return false } if (!/^[1-9]\d*$/.test(String(this.proofDialogData.proofingNumber))) { this.$alert('数量必需是大于等于0的正整数', '提示', { confirmButtonText: '确定' }) return false } if (!this.proofDialogData.planStartDate) { this.$message.warning('打样开始日期不能为空') return false } if (!this.proofDialogData.requiredDeliveryDate) { this.$message.warning('预计完成日期不能为空') return false } return true }, buildProofPayload () { return { site: this.proofDialogData.site, projectId: this.proofDialogData.projectId, projectPartId: this.proofDialogData.projectPartId, projectNo: this.proofDialogData.projectNo, testPartNo: this.proofDialogData.testPartNo, customerNo: this.proofDialogData.customerNo, buNo: this.proofDialogData.buNo, projectCategory: this.proofDialogData.cProjectTypeDb, projectPhase: this.proofDialogData.projectPhase, proofingNo: this.proofDialogData.proofingNo, proofingNumber: Number(this.proofDialogData.proofingNumber), planStartDate: this.proofDialogData.planStartDate, requiredDeliveryDate: this.proofDialogData.requiredDeliveryDate, actualityDeliveryDate: this.proofDialogData.actualityDeliveryDate, proofingStatus: this.proofDialogData.proofingStatus || '草稿', remark: this.proofDialogData.remark, tracker: this.proofDialogData.projectOwner, engineer: this.proofDialogData.engineer, priorityLevel: this.proofDialogData.priorityLevel, createBy: this.$store.state.user.name, updateBy: this.$store.state.user.name } }, saveProofRecord () { if (!this.validateProofDialogData()) { return } const payload = this.buildProofPayload() this.proofSaveLoading = true createProofTrackingRecord(payload).then(({ data }) => { this.proofSaveLoading = false if (data && data.code === 0) { this.$message.success(data.msg || '新增打样成功') this.proofDialogVisible = false this.getDataList() } else { this.$alert(data && data.msg ? data.msg : '新增打样失败', '错误', { confirmButtonText: '确定' }) } }).catch(() => { this.proofSaveLoading = false this.$message.error('新增打样异常') }) }, toggleBatchEdit () { this.batchEditMode = !this.batchEditMode if (this.batchEditMode) { this.cancelCommentsEdit() } this.$nextTick(() => { const tableRef = this.$refs.trackingTable if (tableRef && tableRef.doLayout) { tableRef.doLayout() } if (this.batchEditMode) { setTimeout(() => { this.scrollToRawMaterialColumn() }, 0) } }) }, scrollToRawMaterialColumn () { const tableRef = this.$refs.trackingTable if (!tableRef || !tableRef.$el) { return } const headerWrapper = tableRef.headerWrapper || tableRef.$el.querySelector('.el-table__header-wrapper') const bodyWrapper = tableRef.bodyWrapper || tableRef.$el.querySelector('.el-table__body-wrapper') if (!headerWrapper || !bodyWrapper) { return } const headerCells = headerWrapper.querySelectorAll('th') let targetCell = null for (let i = 0; i < headerCells.length; i++) { const text = (headerCells[i].innerText || '').replace(/\s+/g, ' ').trim() if (text === 'Raw Material') { targetCell = headerCells[i] break } } if (!targetCell) { return } const fixedLeftWrapper = tableRef.$el.querySelector('.el-table__fixed') const fixedLeftWidth = fixedLeftWrapper ? fixedLeftWrapper.offsetWidth : 0 const nextScrollLeft = Math.max(targetCell.offsetLeft - fixedLeftWidth - 8, 0) bodyWrapper.scrollLeft = nextScrollLeft headerWrapper.scrollLeft = nextScrollLeft }, saveBatchPlan () { const rows = this.selectionRows && this.selectionRows.length > 0 ? this.selectionRows : this.dataList if (!rows || rows.length === 0) { this.$message.warning('没有可保存的数据') return } const payloadRows = rows.map(row => ({ trackingId: row.trackingId, rawMaterialActualDate: row.rawMaterialActualDate, cylinderCutterActualDate: row.cylinderCutterActualDate, laminationActualDate: row.laminationActualDate, printingActualDate: row.printingActualDate, etchingActualDate: row.etchingActualDate, slittingPackingActualDate: row.slittingPackingActualDate, bondingActualDate: row.bondingActualDate, cl60Cl822ActualDate: row.cl60Cl822ActualDate, spottingActualDate: row.spottingActualDate, encodingActualDate: row.encodingActualDate, dieCutActualDate: row.dieCutActualDate, inspectionActualDate: row.inspectionActualDate, deliveryPackageActualDate: row.deliveryPackageActualDate, comments: row.comments == null ? '' : String(row.comments), updateBy: this.$store.state.user.name })) batchUpdateProofTrackingPlan({ updateBy: this.$store.state.user.name, rows: payloadRows }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('批量修改成功') this.batchEditMode = false this.getDataList() } else { this.$message.error(data.msg || '批量修改失败') } }).catch(() => { this.$message.error('批量修改异常') }) }, handleProcessDateChange (row, processCol, dateVal) { if (!row || !processCol) { return } const actualDate = this.formatDate(dateVal) this.updateProcessFromTable(row, processCol, { status: '已完成', actualDate: actualDate, remark: '表格直接选择日期' }, true) }, markProcessComplete (row, processCol) { if (!row || !processCol || this.isProcessComplete(row, processCol)) { return } const today = this.formatDate(new Date()) this.updateProcessFromTable(row, processCol, { status: '已完成', actualDate: today, remark: '表格一键完成' }, false, '工序已标记为完成') }, updateProcessFromTable (row, processCol, processData, silentSuccess, successMsg) { if (!row || !processCol || !row.trackingId) { this.$message.error('工序参数缺失') return } const status = processData && processData.status ? String(processData.status).trim() : '' if (!status) { this.$message.error('工序状态不能为空') return } const actualDate = processData && processData.actualDate ? this.formatDate(processData.actualDate) : '' const inData = { trackingId: row.trackingId, processCode: processCol.code, processName: processCol.label, status: status, actualDate: actualDate || null, remark: processData && processData.remark ? processData.remark : '', updateBy: this.$store.state.user.name } updateProofTrackingProcess(inData).then(({ data }) => { if (data && data.code === 0) { row[processCol.statusField] = status row[processCol.actualField] = actualDate const cacheKey = `${row.trackingId}_${processCol.code}` if (this.processHistoryMap[cacheKey]) { this.$delete(this.processHistoryMap, cacheKey) } if (!silentSuccess) { this.$message.success(successMsg || '工序更新成功') } } else { this.$message.error(data.msg || '工序更新失败') this.getDataList() } }).catch(() => { this.$message.error('工序更新异常') this.getDataList() }) }, openProcessDialog (row, processCol) { if (!row || !processCol) { return } this.currentRow = row this.processForm = { trackingId: row.trackingId, processCode: processCol.code, processName: processCol.label, status: row[processCol.statusField] || '未完成', actualDate: row[processCol.actualField] || '', remark: '' } this.processHistoryRows = [] this.processDialogVisible = true this.loadProcessHistory(row, processCol, true) }, submitProcessUpdate () { if (!this.processForm.trackingId || !this.processForm.processCode) { this.$message.error('参数缺失') return } if (!this.processForm.status) { this.$message.error('请选择状态') return } const inData = Object.assign({}, this.processForm, { updateBy: this.$store.state.user.name }) this.saveProcessLoading = true updateProofTrackingProcess(inData).then(({ data }) => { this.saveProcessLoading = false if (data && data.code === 0) { this.$message.success('工序更新成功') this.processDialogVisible = false this.processHistoryMap = {} this.getDataList() } else { this.$message.error(data.msg || '工序更新失败') } }).catch(() => { this.saveProcessLoading = false this.$message.error('工序更新异常') }) }, finishProof (row) { const current = row || this.currentRow if (!current || !current.trackingId) { this.$message.warning('请先选择记录') return } this.finishForm = { trackingId: current.trackingId, actualityDeliveryDate: current.actualityDeliveryDate || this.formatDate(new Date()) } this.finishDialogVisible = true }, deleteProof (row) { const current = row || this.currentRow if (!current || !current.trackingId) { this.$message.warning('请先选择记录') return } this.$confirm('确定删除该打样记录吗?该操作仅删除打样记录,不会删除项目和项目物料。', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { deleteProofTracking({ trackingId: current.trackingId, updateBy: this.$store.state.user.name }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('打样记录删除成功') this.processHistoryMap = {} this.resetProcessTooltip() this.getDataList() } else { this.$message.error((data && data.msg) || '删除打样记录失败') } }).catch(() => { this.$message.error('删除打样记录异常') }) }).catch(() => {}) }, submitFinishProof () { if (!this.finishForm.trackingId) { this.$message.warning('跟踪记录ID不能为空') return } if (!this.finishForm.actualityDeliveryDate) { this.$message.warning('请选择实际完成日期') return } this.finishSaveLoading = true finishProofTracking({ trackingId: this.finishForm.trackingId, actualityDeliveryDate: this.finishForm.actualityDeliveryDate, updateBy: this.$store.state.user.name }).then(({ data }) => { this.finishSaveLoading = false if (data && data.code === 0) { this.$message.success('打样完成成功') this.finishDialogVisible = false this.getDataList() } else { this.$message.error(data.msg || '打样完成失败') } }).catch(() => { this.finishSaveLoading = false this.$message.error('打样完成异常') }) }, getProcessCacheKey (row, processCol) { if (!row || !processCol || !row.trackingId) { return '' } return `${row.trackingId}_${processCol.code}` }, resetProcessTooltip () { this.processTooltipHoverKey = '' this.processTooltip.visible = false this.processTooltip.content = '' }, hideProcessTooltip () { this.processTooltip.visible = false this.processTooltip.content = '' }, updateProcessTooltipPosition (event) { if (!event) { return } const offsetX = 12 const offsetY = 16 let left = event.clientX + offsetX let top = event.clientY + offsetY const maxWidth = 220 const maxHeight = 120 if (left + maxWidth > window.innerWidth - 8) { left = window.innerWidth - maxWidth - 8 } if (top + maxHeight > window.innerHeight - 8) { top = event.clientY - maxHeight - 8 } this.processTooltip.left = Math.max(8, left) this.processTooltip.top = Math.max(8, top) }, handleProcessTooltipMouseMove (event) { if (!this.processTooltip.visible) { return } this.updateProcessTooltipPosition(event) }, handleProcessTooltipMouseLeave () { this.processTooltipHoverKey = '' this.hideProcessTooltip() }, handleProcessTooltipMouseEnter (row, processCol, event) { const cacheKey = this.getProcessCacheKey(row, processCol) if (!cacheKey) { return } this.processTooltipHoverKey = cacheKey this.updateProcessTooltipPosition(event) const showTooltip = () => { if (this.processTooltipHoverKey !== cacheKey) { return } if (!this.hasProcessHistory(row, processCol)) { this.hideProcessTooltip() return } const content = this.getProcessTooltip(row, processCol) if (!content) { this.hideProcessTooltip() return } this.processTooltip.content = content this.processTooltip.visible = true } if (Object.prototype.hasOwnProperty.call(this.processHistoryMap, cacheKey)) { showTooltip() return } this.loadProcessHistory(row, processCol, false).then(() => { showTooltip() }) }, loadProcessHistory (row, processCol, refreshDialog) { if (!row || !row.trackingId) { return Promise.resolve([]) } const cacheKey = this.getProcessCacheKey(row, processCol) if (Object.prototype.hasOwnProperty.call(this.processHistoryMap, cacheKey)) { const cachedRows = this.processHistoryMap[cacheKey] || [] if (refreshDialog) { this.processHistoryRows = cachedRows } return Promise.resolve(cachedRows) } return queryProofTrackingProcessHistory({ trackingId: row.trackingId, processCode: processCol.code }).then(({ data }) => { let rows = [] if (data && data.code === 0) { rows = data.rows || [] // Vue2 对对象新增 key 需要使用 $set 才能触发视图更新(tooltip 才会刷新历史列表)
this.$set(this.processHistoryMap, cacheKey, rows) if (refreshDialog) { this.processHistoryRows = rows } } return rows }).catch(() => { if (refreshDialog) { this.processHistoryRows = [] } this.$set(this.processHistoryMap, cacheKey, []) return [] }) }, hasProcessHistory (row, processCol) { if (!row || !processCol || !row.trackingId) { return false } const cacheKey = `${row.trackingId}_${processCol.code}` const rows = this.processHistoryMap[cacheKey] || [] return rows.length > 0 }, getProcessTooltip (row, processCol) { if (!row || !processCol) { return '' } const cacheKey = row.trackingId ? `${row.trackingId}_${processCol.code}` : '' const rows = this.processHistoryMap[cacheKey] || []
const dateList = [] const pushDate = (dateVal) => { const formatted = this.formatDate(dateVal) if (!formatted) { return } if (dateList.indexOf(formatted) === -1) { dateList.push(formatted) } }
// 先放当前实际完成日期,再放历史变更日期(实际)
pushDate(row[processCol.actualField]) rows.forEach(item => { pushDate(item.newActualDate) pushDate(item.oldActualDate) })
if (dateList.length === 0) { return '' } return dateList.join('\n') }, isProcessComplete (row, processCol) { if (!row || !processCol) { return false } const statusVal = row[processCol.statusField] const status = statusVal ? String(statusVal).trim() : ''
if (!status) { // 兼容历史数据:若没有状态但有实际完成日期,也按完成处理
return !!this.formatDate(row[processCol.actualField]) } if (status === '未完成' || status === '进行中') { return false } if (status === '已完成' || status === '打样完成') { return true } return status.indexOf('完成') > -1 && status.indexOf('未') === -1 }, formatDate (val) { if (!val) { return '' } if (typeof val === 'string') { return val.length > 10 ? val.substring(0, 10) : val } const date = new Date(val) if (isNaN(date.getTime())) { return '' } const y = date.getFullYear() const m = `${date.getMonth() + 1}`.padStart(2, '0') const d = `${date.getDate()}`.padStart(2, '0') return `${y}-${m}-${d}` }, formatDateTime (val) { if (!val) { return '' } if (typeof val === 'string') { return val.length > 19 ? val.substring(0, 19) : val } const date = new Date(val) if (isNaN(date.getTime())) { return '' } const y = date.getFullYear() const m = `${date.getMonth() + 1}`.padStart(2, '0') const d = `${date.getDate()}`.padStart(2, '0') const hh = `${date.getHours()}`.padStart(2, '0') const mm = `${date.getMinutes()}`.padStart(2, '0') const ss = `${date.getSeconds()}`.padStart(2, '0') return `${y}-${m}-${d} ${hh}:${mm}:${ss}` } }}</script>
<style >.data-table .cell { line-height: 26px; font-size: 12px; height: 26px;}
.search-form { margin-bottom: 0;}
.button-group-col { width: 100%; margin-top: 16px;}
.button-row { width: 100%; flex-wrap: wrap;}
.bu-process-config-dialog .el-checkbox-group { display: grid; grid-template-columns: repeat(5, minmax(110px, 1fr)); column-gap: 12px; row-gap: 8px; align-items: center;}
.bu-process-config-dialog .bu-process-list-column .cell { white-space: normal !important; height: auto !important; line-height: 20px; padding-top: 6px; padding-bottom: 6px; overflow: hidden;}
.bu-process-config-dialog .bu-process-list-column { vertical-align: top;}
.bu-process-config-dialog .bu-process-list-column .el-checkbox,.bu-process-config-dialog .bu-process-list-column .el-checkbox + .el-checkbox { width: 100%; margin: 0 !important;}
.bu-process-config-dialog .bu-process-list-column .el-checkbox__label { white-space: nowrap;}
.button-col { margin-bottom: 8px;}
.button-col .el-button { width: 100%;}
.one-key-grid-form { padding: 0 8px;}
.one-key-grid-form .el-form-item { margin-bottom: 10px;}
.proof-grid-form { padding: 0 8px;}
.proof-grid-form .el-form-item { margin-bottom: 10px;}
.big-label a { color: #35b4b4;}
.sync-text--ok { color: #67c23a; font-weight: 600;}
.sync-text--pending { color: #e6a23c;}
.process-cell { background: #fff; min-height: 44px; padding: 4px 2px;}
.process-cell.is-complete { background: #ececec;}
.process-cell.is-complete .link-date { color: #8c8c8c;}
.process-cell.is-complete .link-date:hover,.process-cell.is-complete .link-date:focus { color: #8c8c8c;}
.process-cell--masked { background: transparent !important;}
.process-content { display: flex; align-items: center; justify-content: center; gap: 4px; text-align: center; line-height: 18px;}
.process-complete-btn { padding: 4px 8px; min-width: 12px;}
.comments-cell { display: flex; align-items: center; gap: 4px; min-height: 26px;}
.comments-edit-input { flex: 1;}
.comments-save-btn { padding: 4px 7px; min-width: 28px;}
.comments-text { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
.process-date-tooltip { white-space: pre-line;}
.process-history-tooltip { position: fixed; z-index: 3000; max-width: 220px; padding: 10px 12px; border-radius: 4px; background: rgba(48, 49, 51, 0.95); color: #fff; font-size: 12px; line-height: 1.5; white-space: pre-line; pointer-events: none;}
.link-date { color: #0c4dbb; text-decoration: none;}
.link-date:hover,.link-date:focus { text-decoration: none;}
.actual-date { margin-top: 2px; color: #666;}
.finish-dialog-tip { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 10px; border-radius: 4px; background: #f4f8ff; color: #3a4b5f; font-size: 12px;}
.finish-dialog-tip i { color: #409eff;}
.finish-tip-highlight { font-size: 13px; font-weight: 700; color: #1f2d3d;}
.finish-proof-dialog .el-dialog__body { padding-bottom: 8px;}
.finish-form .el-form-item { margin-bottom: 14px;}
.action-link { color: #0c4dbb; margin: 0 4px; cursor: pointer;}
.edit-link { color: #e6a23c;}
.delete-link { color: #f56c6c;}
.end-link { color: #a303ff;}</style>
|