You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

3040 lines
113 KiB

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