1310 lines
48 KiB
Vue
1310 lines
48 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||
<el-form-item label="计划名称" prop="planName">
|
||
<el-input v-model="queryParams.planName" placeholder="请输入收货计划名称" clearable @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="计划日期" prop="planDate">
|
||
<el-date-picker clearable v-model="queryParams.planDate" type="date" value-format="yyyy-MM-dd"
|
||
placeholder="请选择计划日期">
|
||
</el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5">
|
||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single"
|
||
@click="handleUpdate">修改</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
|
||
@click="handleDelete">删除</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
||
</el-col>
|
||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||
</el-row>
|
||
|
||
<el-row :gutter="20" v-loading="loading">
|
||
<el-col :span="6" v-for="(row, index) in deliveryPlanList" :key="row.planId">
|
||
<el-card shadow="hover" class="delivery-plan-card">
|
||
<div class="card-header">
|
||
<el-checkbox v-model="row.selected" @change="handleCardSelectionChange(row)"></el-checkbox>
|
||
<div class="card-title">{{ row.planName }}</div>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="content-item">
|
||
<span class="label">计划日期:</span>
|
||
<span>{{ parseTime(row.planDate, '{y}-{m}-{d}') }}</span>
|
||
</div>
|
||
<div class="content-item">
|
||
<span class="label">备注:</span>
|
||
<span>{{ row.remark || '-' }}</span>
|
||
</div>
|
||
<div class="content-item">
|
||
<span class="label">创建人:</span>
|
||
<span>{{ row.createBy }}</span>
|
||
</div>
|
||
<div class="content-item">
|
||
<span class="label">更新时间:</span>
|
||
<span>{{ parseTime(row.updateTime, '{y}-{m}-{d}') }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="card-actions">
|
||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(row)">修改</el-button>
|
||
<el-button size="mini" type="text" icon="el-icon-upload2" @click="openImportDialog(row)">导入</el-button>
|
||
<el-button size="mini" type="text" icon="el-icon-notebook-2" @click="openDetailDialog(row)">明细</el-button>
|
||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(row)">删除</el-button>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||
@pagination="getList" />
|
||
|
||
<!-- 添加或修改收货计划对话框 -->
|
||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||
<el-form-item label="收货计划名称" prop="planName">
|
||
<el-input v-model="form.planName" placeholder="请输入收货计划名称" />
|
||
</el-form-item>
|
||
<el-form-item label="计划日期" prop="planDate">
|
||
<el-date-picker clearable v-model="form.planDate" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
|
||
placeholder="请选择计划日期">
|
||
</el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||
<el-button @click="cancel">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 待收货明细管理对话框 -->
|
||
<el-dialog title="待收货明细管理" :visible.sync="detailOpen" width="1400px" append-to-body @close="resetDetail">
|
||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||
<el-tab-pane label="计划入库" name="plan">
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5">
|
||
<el-button type="primary" plain icon="el-icon-plus" size="mini"
|
||
@click="openDetailAddDialog">新增</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="detailMultiple"
|
||
@click="handleDetailBatchDelete">批量删除</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="danger" plain icon="el-icon-delete" size="mini"
|
||
@click="handleDetailClear">清空</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="default" plain icon="el-icon-refresh" size="mini"
|
||
@click="getDetailList">刷新</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
<el-table v-loading="detailLoading" :data="detailList" border @selection-change="handleDetailSelectionChange">
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="warehouseArea" label="逻辑库区" width="120" />
|
||
<el-table-column prop="lotNo" label="入场卷号" width="120" />
|
||
<el-table-column prop="supplierLotNo" label="厂家卷号" width="120" />
|
||
<el-table-column prop="productLotNo" label="成品卷号" width="120" />
|
||
<el-table-column prop="productionDate" label="生产/发货日期" width="150">
|
||
<template slot-scope="scope">
|
||
{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="weight" label="重量(kg)" width="100" />
|
||
<el-table-column prop="goodsName" label="名称" width="120" />
|
||
<el-table-column prop="spec" label="规格" width="120" />
|
||
<el-table-column prop="length" label="长度(mm)" width="100" />
|
||
<el-table-column prop="materialType" label="材质" width="100" />
|
||
<el-table-column prop="manufacturer" label="生产厂家" width="120" />
|
||
<el-table-column prop="surfaceTreatment" label="表面处理" width="100" />
|
||
<el-table-column prop="zincCoating" label="锌层" width="80" />
|
||
<el-table-column prop="teamGroup" label="班组" width="100" />
|
||
<el-table-column prop="temperRolling" label="调制度" width="100" />
|
||
<el-table-column prop="coatingType" label="镀层种类" width="100" />
|
||
<el-table-column prop="goodsType" label="类型" width="100" />
|
||
<el-table-column prop="receiveStatus" label="状态" width="80">
|
||
<template slot-scope="scope">
|
||
<el-tag
|
||
:type="scope.row.receiveStatus === '0' ? 'info' : scope.row.receiveStatus === '1' ? 'success' : 'danger'">
|
||
{{ scope.row.receiveStatus === '0' ? '待收货' : scope.row.receiveStatus === '1' ? '已收货' : '异常' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" fixed="right">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" type="text" @click="openDetailEditDialog(scope.row)">修改</el-button>
|
||
<el-button size="mini" type="text" class="btn-danger"
|
||
@click="handleDetailDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<pagination v-show="detailTotal > 0" :total="detailTotal" :page.sync="detailQueryParams.pageNum"
|
||
:limit.sync="detailQueryParams.pageSize" @pagination="getDetailList" />
|
||
</el-tab-pane>
|
||
<el-tab-pane label="实际入库表格" name="actual">
|
||
<el-table v-loading="actualLoading" :data="actualList" border height="400">
|
||
<el-table-column prop="enterCoilNo" label="入场卷号" />
|
||
<el-table-column prop="currentCoilNo" label="成品卷号" />
|
||
<el-table-column prop="itemName" label="名称" width="120" />
|
||
<el-table-column prop="specification" label="规格" width="120" />
|
||
<el-table-column prop="materialType" label="材质" width="100" />
|
||
<el-table-column prop="netWeight" label="重量(kg)" width="100" />
|
||
<el-table-column prop="manufacturer" label="生产厂家" width="120" />
|
||
<el-table-column prop="zincLayer" label="镀层质量" width="80" />
|
||
<el-table-column prop="warehouseName" label="库区" width="100" />
|
||
<el-table-column prop="qualityStatus" label="质量状态" width="100" />
|
||
<el-table-column prop="createTime" label="入库时间">
|
||
<template slot-scope="scope">
|
||
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="差异对比" name="diff">
|
||
<div class="diff-config">
|
||
<el-form :model="diffConfig" inline>
|
||
<el-form-item label="重量差异阈值(kg)">
|
||
<el-input-number v-model="diffConfig.weightThreshold" :precision="2" :step="0.1" :min="0" style="width: 120px;" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" size="mini" @click="calculateDiff">重新计算差异</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<span class="config-tip">重量差异在此阈值内视为相同,超出则标记为不一致</span>
|
||
</div>
|
||
<el-row :gutter="16" class="diff-row">
|
||
<el-col :span="12">
|
||
<div class="diff-section">
|
||
<div class="section-header">
|
||
<span class="section-icon missing-icon">●</span>
|
||
<span class="section-title">计划有实际无(少收)</span>
|
||
<span class="section-count">{{ missingCoils.length }}</span>
|
||
</div>
|
||
<el-table v-loading="diffLoading" height="300" :data="missingCoils" border size="small" class="diff-table">
|
||
<el-table-column prop="lotNo" label="入场卷号" />
|
||
<el-table-column prop="goodsName" label="名称" width="100" />
|
||
<el-table-column prop="spec" label="规格" width="100" />
|
||
<el-table-column prop="materialType" label="材质" width="80" />
|
||
<el-table-column prop="weight" label="重量(kg)" width="90" />
|
||
</el-table>
|
||
<div v-if="missingCoils.length === 0" class="empty-tip">暂无少收卷</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<div class="diff-section">
|
||
<div class="section-header">
|
||
<span class="section-icon extra-icon">●</span>
|
||
<span class="section-title">实际有计划无(多收)</span>
|
||
<span class="section-count">{{ extraCoils.length }}</span>
|
||
</div>
|
||
<el-table v-loading="diffLoading" height="300" :data="extraCoils" border size="small" class="diff-table">
|
||
<el-table-column prop="enterCoilNo" label="入场卷号" />
|
||
<el-table-column prop="itemName" label="名称" width="100" />
|
||
<el-table-column prop="specification" label="规格" width="100" />
|
||
<el-table-column prop="materialType" label="材质" width="80" />
|
||
<el-table-column prop="netWeight" label="重量(kg)" width="90" />
|
||
</el-table>
|
||
<div v-if="extraCoils.length === 0" class="empty-tip">暂无多收卷</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<div class="diff-section">
|
||
<div class="section-header">
|
||
<span class="section-icon diff-icon">●</span>
|
||
<span class="section-title">字段不一致</span>
|
||
<span class="section-count">{{ diffCoils.length }}</span>
|
||
</div>
|
||
<el-table v-loading="diffLoading" height="300" :data="diffCoils" border size="small" class="diff-table">
|
||
<el-table-column prop="lotNo" label="入场卷号" width="120" />
|
||
<el-table-column label="字段差异" min-width="500">
|
||
<template #default="scope">
|
||
<div class="diff-content">
|
||
<div v-for="(diff, index) in scope.row.diffs" :key="index" class="diff-item">
|
||
<span class="field-label">{{ diff.fieldName }}</span>
|
||
<div class="value-row">
|
||
<span class="value-label">计划:</span>
|
||
<span class="plan-value">{{ diff.planValue }}</span>
|
||
</div>
|
||
<div class="value-row">
|
||
<span class="value-label">实际:</span>
|
||
<span class="actual-value">{{ diff.actualValue }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div v-if="diffCoils.length === 0" class="empty-tip">暂无字段不一致的卷</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-dialog>
|
||
|
||
<!-- 添加或修改待收货明细对话框 -->
|
||
<el-dialog :title="detailFormTitle" :visible.sync="detailFormOpen" width="600px" append-to-body
|
||
@close="resetDetailForm">
|
||
<el-form ref="detailForm" :model="detailForm" :rules="detailRules" label-width="100px">
|
||
<el-form-item label="逻辑库区" prop="warehouseArea">
|
||
<el-input v-model="detailForm.warehouseArea" placeholder="请输入逻辑库区" />
|
||
</el-form-item>
|
||
<el-form-item label="入场卷号" prop="lotNo">
|
||
<el-input v-model="detailForm.lotNo" placeholder="请输入入场卷号" />
|
||
</el-form-item>
|
||
<el-form-item label="厂家卷号" prop="supplierLotNo">
|
||
<el-input v-model="detailForm.supplierLotNo" placeholder="请输入厂家卷号" />
|
||
</el-form-item>
|
||
<el-form-item label="成品卷号" prop="productLotNo">
|
||
<el-input v-model="detailForm.productLotNo" placeholder="请输入成品卷号" />
|
||
</el-form-item>
|
||
<el-form-item label="生产/发货日期" prop="productionDate">
|
||
<el-date-picker clearable v-model="detailForm.productionDate" type="date" value-format="yyyy-MM-dd"
|
||
placeholder="请选择生产/发货日期" style="width: 100%;">
|
||
</el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item label="重量(kg)" prop="weight">
|
||
<el-input-number v-model="detailForm.weight" :precision="2" :step="0.01" style="width: 100%;" />
|
||
</el-form-item>
|
||
<el-form-item label="名称" prop="goodsName">
|
||
<el-input v-model="detailForm.goodsName" placeholder="请输入名称" />
|
||
</el-form-item>
|
||
<el-form-item label="规格" prop="spec">
|
||
<el-input v-model="detailForm.spec" placeholder="请输入规格" />
|
||
</el-form-item>
|
||
<el-form-item label="长度(mm)" prop="length">
|
||
<el-input-number v-model="detailForm.length" :precision="2" :step="0.01" style="width: 100%;" />
|
||
</el-form-item>
|
||
<el-form-item label="材质" prop="materialType">
|
||
<el-input v-model="detailForm.materialType" placeholder="请输入材质" />
|
||
</el-form-item>
|
||
<el-form-item label="生产厂家" prop="manufacturer">
|
||
<el-input v-model="detailForm.manufacturer" placeholder="请输入生产厂家" />
|
||
</el-form-item>
|
||
<el-form-item label="表面处理" prop="surfaceTreatment">
|
||
<el-input v-model="detailForm.surfaceTreatment" placeholder="请输入表面处理" />
|
||
</el-form-item>
|
||
<el-form-item label="锌层" prop="zincCoating">
|
||
<el-input v-model="detailForm.zincCoating" placeholder="请输入锌层" />
|
||
</el-form-item>
|
||
<el-form-item label="班组" prop="teamGroup">
|
||
<el-input v-model="detailForm.teamGroup" placeholder="请输入班组" />
|
||
</el-form-item>
|
||
<el-form-item label="调制度" prop="temperRolling">
|
||
<el-input v-model="detailForm.temperRolling" placeholder="请输入调制度" />
|
||
</el-form-item>
|
||
<el-form-item label="镀层种类" prop="coatingType">
|
||
<el-input v-model="detailForm.coatingType" placeholder="请输入镀层种类" />
|
||
</el-form-item>
|
||
<el-form-item label="类型" prop="goodsType">
|
||
<el-input v-model="detailForm.goodsType" placeholder="请输入类型" />
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="detailForm.remark" type="textarea" placeholder="请输入备注" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button :loading="detailButtonLoading" type="primary" @click="submitDetailForm">确 定</el-button>
|
||
<el-button @click="detailFormOpen = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 待收货明细导入对话框 -->
|
||
<el-dialog title="待收货明细导入" :visible.sync="importOpen" width="1000px" append-to-body>
|
||
<div class="import-container">
|
||
<div class="file-upload-area" :class="{ disabled: importStatus === 'processing' }">
|
||
<el-upload ref="upload" class="upload-excel" action="" :auto-upload="false" :show-file-list="false"
|
||
:on-change="handleFileChange" accept=".xlsx,.xls"
|
||
:disabled="importStatus === 'processing' || validateLoading || importLoading">
|
||
<el-button type="primary" icon="el-icon-upload2">选择Excel文件</el-button>
|
||
</el-upload>
|
||
|
||
<el-button type="success" icon="el-icon-check" @click="handleValidate" v-if="file && importStatus === 'idle'"
|
||
:disabled="!file || validateLoading" :loading="validateLoading">
|
||
校验数据
|
||
</el-button>
|
||
<el-button type="warning" icon="el-icon-circle-check" @click="startImport"
|
||
v-if="file && importStatus === 'idle'"
|
||
:disabled="!file || !isValidated || errorList.length > 0 || importLoading" :loading="importLoading">
|
||
开始导入
|
||
</el-button>
|
||
<el-button type="default" icon="el-icon-refresh" @click="resetImport"
|
||
:disabled="importStatus === 'processing' || validateLoading || importLoading">
|
||
重置
|
||
</el-button>
|
||
</div>
|
||
|
||
<div class="template-download">
|
||
<el-link type="primary" icon="el-icon-download" @click="downloadTemplate">下载导入模板</el-link>
|
||
<span class="template-tip">请按照模板格式填写数据</span>
|
||
</div>
|
||
|
||
<div v-if="errorList.length > 0" class="error-list">
|
||
<el-alert title="数据校验失败" type="error" :description="`共发现${errorList.length}条错误,请修正后重新导入`" show-icon />
|
||
<el-table :data="errorList" border size="small" max-height="200">
|
||
<el-table-column prop="rowNum" label="行号" width="80" />
|
||
<el-table-column prop="errorMsg" label="错误信息" />
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-if="tableData.length > 0 && importStatus === 'idle'" class="data-preview">
|
||
<el-alert title="数据预览" type="info" :description="`共解析出 ${tableData.length} 条有效数据`" show-icon
|
||
:closable="false" />
|
||
<el-table :data="tableData" border size="small" max-height="300" stripe>
|
||
<el-table-column prop="warehouseArea" label="逻辑库区" width="100" />
|
||
<el-table-column prop="lotNo" label="入场卷号" width="100" />
|
||
<el-table-column prop="supplierLotNo" label="厂家卷号" width="100" />
|
||
<el-table-column prop="productLotNo" label="成品卷号" width="100" />
|
||
<el-table-column prop="productionDate" label="生产/发货日期" width="120" />
|
||
<el-table-column prop="weight" label="重量(kg)" width="100" />
|
||
<el-table-column prop="goodsName" label="名称" width="120" />
|
||
<el-table-column prop="spec" label="规格" width="100" />
|
||
<el-table-column prop="length" label="长度(mm)" width="100" />
|
||
<el-table-column prop="materialType" label="材质" width="100" />
|
||
<el-table-column prop="manufacturer" label="生产厂家" width="120" />
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-if="importStatus === 'processing'" class="import-progress">
|
||
<el-alert title="正在导入数据" type="warning" :description="`当前进度:${progress}%`" show-icon :closable="false" />
|
||
<el-progress :percentage="progress" status="success" />
|
||
<p class="progress-tip">已导入 {{ importedCount }} / {{ totalCount }} 条数据</p>
|
||
</div>
|
||
|
||
<div v-if="importStatus === 'finished'" class="import-finished">
|
||
<el-alert title="导入完成" type="success" :description="`共成功导入 ${importedCount} 条数据,总计 ${totalCount} 条`"
|
||
show-icon />
|
||
</div>
|
||
|
||
<div v-if="importStatus === 'error'" class="import-error">
|
||
<el-alert title="导入失败" type="error" :description="importErrorMsg" show-icon />
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import * as XLSX from 'xlsx';
|
||
import { listDeliveryPlan, getDeliveryPlan, delDeliveryPlan, addDeliveryPlan, updateDeliveryPlan } from "@/api/wms/deliveryPlan";
|
||
import { listReceivePlan, getReceivePlan, delReceivePlan, addReceivePlan, updateReceivePlan, checkReceivePlan, delReceivePlanBatch } from "@/api/wms/receivePlan";
|
||
import { listCoilWithIds } from "@/api/wms/coil";
|
||
import { listPendingAction } from "@/api/wms/pendingAction";
|
||
|
||
const TEMPLATE_HEADERS = [
|
||
'逻辑库区', '入场卷号', '厂家卷号', '成品卷号', '生产/发货日期', '重量(kg)',
|
||
'名称', '规格', '长度(mm)', '材质', '生产厂家', '表面处理', '锌层',
|
||
'班组', '调制度', '镀层种类', '类型', '备注'
|
||
];
|
||
|
||
const HEADER_MAP = {
|
||
'逻辑库区': 'warehouseArea',
|
||
'入场卷号': 'lotNo',
|
||
'厂家卷号': 'supplierLotNo',
|
||
'成品卷号': 'productLotNo',
|
||
'生产/发货日期': 'productionDate',
|
||
'重量(kg)': 'weight',
|
||
'名称': 'goodsName',
|
||
'规格': 'spec',
|
||
'长度(mm)': 'length',
|
||
'材质': 'materialType',
|
||
'生产厂家': 'manufacturer',
|
||
'表面处理': 'surfaceTreatment',
|
||
'锌层': 'zincCoating',
|
||
'班组': 'teamGroup',
|
||
'调制度': 'temperRolling',
|
||
'镀层种类': 'coatingType',
|
||
'类型': 'goodsType',
|
||
'备注': 'remark'
|
||
};
|
||
|
||
export default {
|
||
name: "DeliveryPlan",
|
||
data() {
|
||
return {
|
||
buttonLoading: false,
|
||
loading: true,
|
||
ids: [],
|
||
single: true,
|
||
multiple: true,
|
||
showSearch: true,
|
||
total: 0,
|
||
deliveryPlanList: [],
|
||
title: "",
|
||
open: false,
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
planName: undefined,
|
||
planDate: undefined,
|
||
planType: 1
|
||
},
|
||
form: {
|
||
planName: '',
|
||
planDate: '',
|
||
},
|
||
rules: {},
|
||
|
||
detailOpen: false,
|
||
detailLoading: false,
|
||
detailList: [],
|
||
detailTotal: 0,
|
||
currentPlanId: null,
|
||
detailQueryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
planId: null
|
||
},
|
||
detailFormOpen: false,
|
||
detailFormTitle: '',
|
||
detailButtonLoading: false,
|
||
detailIds: [],
|
||
detailSingle: true,
|
||
detailMultiple: true,
|
||
detailForm: {},
|
||
detailRules: {
|
||
lotNo: [{ required: true, message: '入场卷号不能为空', trigger: 'blur' }]
|
||
},
|
||
|
||
activeTab: 'plan',
|
||
actualLoading: false,
|
||
actualList: [],
|
||
diffLoading: false,
|
||
missingCoils: [],
|
||
extraCoils: [],
|
||
diffCoils: [],
|
||
diffConfig: {
|
||
weightThreshold: 0.5
|
||
},
|
||
|
||
importOpen: false,
|
||
file: null,
|
||
rawData: [],
|
||
tableData: [],
|
||
errorList: [],
|
||
isValidated: false,
|
||
progress: 0,
|
||
importedCount: 0,
|
||
totalCount: 0,
|
||
importStatus: 'idle',
|
||
importErrorMsg: '',
|
||
validateLoading: false,
|
||
importLoading: false
|
||
};
|
||
},
|
||
created() {
|
||
this.getList();
|
||
},
|
||
methods: {
|
||
getList() {
|
||
this.loading = true;
|
||
listDeliveryPlan(this.queryParams).then(response => {
|
||
this.deliveryPlanList = response.rows.map(item => ({
|
||
...item,
|
||
selected: false
|
||
}));
|
||
this.total = response.total;
|
||
this.loading = false;
|
||
this.ids = [];
|
||
this.single = true;
|
||
this.multiple = true;
|
||
});
|
||
},
|
||
cancel() {
|
||
this.open = false;
|
||
this.reset();
|
||
},
|
||
reset() {
|
||
this.form = {
|
||
planId: undefined,
|
||
planName: undefined,
|
||
planDate: undefined,
|
||
planType: 1,
|
||
remark: undefined,
|
||
delFlag: undefined,
|
||
createTime: undefined,
|
||
createBy: undefined,
|
||
updateTime: undefined,
|
||
updateBy: undefined
|
||
};
|
||
this.resetForm("form");
|
||
},
|
||
handleQuery() {
|
||
this.queryParams.pageNum = 1;
|
||
this.getList();
|
||
},
|
||
resetQuery() {
|
||
this.resetForm("queryForm");
|
||
this.handleQuery();
|
||
},
|
||
handleCardSelectionChange(row) {
|
||
this.ids = this.deliveryPlanList.filter(item => item.selected).map(item => item.planId);
|
||
this.single = this.ids.length !== 1;
|
||
this.multiple = !this.ids.length;
|
||
},
|
||
handleAdd() {
|
||
this.form.planDate = new Date().toLocaleString().substring(0, 19).replace(/\//g, '-').replace('T', ' ');
|
||
this.form.planName = new Date().toLocaleDateString().replace(/\//g, '-') + '收货计划';
|
||
this.form.planType = 1;
|
||
this.open = true;
|
||
this.title = "添加收货计划";
|
||
},
|
||
handleUpdate(row) {
|
||
this.loading = true;
|
||
this.reset();
|
||
const planId = row.planId || this.ids[0];
|
||
getDeliveryPlan(planId).then(response => {
|
||
this.loading = false;
|
||
this.form = response.data;
|
||
this.open = true;
|
||
this.title = "修改收货计划";
|
||
});
|
||
},
|
||
submitForm() {
|
||
this.$refs["form"].validate(valid => {
|
||
if (valid) {
|
||
this.buttonLoading = true;
|
||
if (this.form.planId != null) {
|
||
updateDeliveryPlan(this.form).then(response => {
|
||
this.$modal.msgSuccess("修改成功");
|
||
this.open = false;
|
||
this.getList();
|
||
}).finally(() => {
|
||
this.buttonLoading = false;
|
||
});
|
||
} else {
|
||
addDeliveryPlan(this.form).then(response => {
|
||
this.$modal.msgSuccess("新增成功");
|
||
this.open = false;
|
||
this.getList();
|
||
}).finally(() => {
|
||
this.buttonLoading = false;
|
||
});
|
||
}
|
||
}
|
||
});
|
||
},
|
||
handleDelete(row) {
|
||
const planIds = row.planId || this.ids;
|
||
this.$modal.confirm('是否确认删除收货计划编号为"' + planIds + '"的数据项?').then(() => {
|
||
this.loading = true;
|
||
return delDeliveryPlan(planIds);
|
||
}).then(() => {
|
||
this.loading = false;
|
||
this.getList();
|
||
this.$modal.msgSuccess("删除成功");
|
||
}).catch(() => {
|
||
}).finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
handleExport() {
|
||
this.download('wms/deliveryPlan/export', {
|
||
...this.queryParams
|
||
}, `deliveryPlan_${new Date().getTime()}.xlsx`);
|
||
},
|
||
|
||
openDetailDialog(row) {
|
||
this.currentPlanId = row.planId;
|
||
this.detailQueryParams.planId = row.planId;
|
||
this.detailOpen = true;
|
||
this.getDetailList();
|
||
},
|
||
resetDetail() {
|
||
this.detailList = [];
|
||
this.currentPlanId = null;
|
||
this.activeTab = 'plan';
|
||
this.actualList = [];
|
||
this.missingCoils = [];
|
||
this.extraCoils = [];
|
||
this.diffCoils = [];
|
||
},
|
||
handleTabClick(tab) {
|
||
if (tab.name === 'actual' && this.actualList.length === 0) {
|
||
this.getActualList();
|
||
} else if (tab.name === 'diff' && (this.missingCoils.length === 0 && this.extraCoils.length === 0 && this.diffCoils.length === 0)) {
|
||
this.calculateDiff();
|
||
}
|
||
},
|
||
getActualList() {
|
||
this.actualLoading = true;
|
||
listPendingAction({
|
||
warehouseId: this.currentPlanId,
|
||
actionType: 401,
|
||
pageSize: 99999,
|
||
pageNum: 1,
|
||
}).then(response => {
|
||
const coilIds = response.rows.map(item => item.coilId).join(',');
|
||
listCoilWithIds({ coilIds }).then(actualRes => {
|
||
this.actualList = actualRes.rows;
|
||
this.actualLoading = false;
|
||
});
|
||
}).catch(() => {
|
||
this.actualLoading = false;
|
||
});
|
||
},
|
||
calculateDiff() {
|
||
this.diffLoading = true;
|
||
Promise.all([
|
||
listReceivePlan({ pageNum: 1, pageSize: 99999, planId: this.currentPlanId }),
|
||
listPendingAction({
|
||
warehouseId: this.currentPlanId,
|
||
actionType: 401,
|
||
pageSize: 99999,
|
||
pageNum: 1,
|
||
}).then(response => {
|
||
const coilIds = response.rows.map(item => item.coilId).join(',');
|
||
return listCoilWithIds({ coilIds }).then(actualRes => {
|
||
this.actualLoading = false;
|
||
return actualRes;
|
||
});
|
||
})
|
||
]).then(([planRes, actualRes]) => {
|
||
const planList = planRes.rows;
|
||
const actualList = actualRes.rows;
|
||
|
||
const planLotNoMap = new Map();
|
||
planList.forEach(item => {
|
||
planLotNoMap.set(item.lotNo, item);
|
||
});
|
||
|
||
const actualLotNoMap = new Map();
|
||
actualList.forEach(item => {
|
||
actualLotNoMap.set(item.enterCoilNo, item);
|
||
});
|
||
|
||
this.missingCoils = planList.filter(item => !actualLotNoMap.has(item.lotNo));
|
||
|
||
this.extraCoils = actualList.filter(item => !planLotNoMap.has(item.enterCoilNo));
|
||
|
||
this.diffCoils = [];
|
||
const compareFields = [
|
||
{ planField: 'goodsName', actualField: 'itemName', fieldName: '名称' },
|
||
{ planField: 'spec', actualField: 'specification', fieldName: '规格' },
|
||
{ planField: 'materialType', actualField: 'material', fieldName: '材质' },
|
||
{ planField: 'weight', actualField: 'netWeight', fieldName: '重量', isNumber: true },
|
||
{ planField: 'manufacturer', actualField: 'manufacturer', fieldName: '生产厂家' },
|
||
{ planField: 'zincCoating', actualField: 'zincLayer', fieldName: '锌层' }
|
||
];
|
||
|
||
const diffMap = new Map();
|
||
planList.forEach(planItem => {
|
||
const actualItem = actualLotNoMap.get(planItem.lotNo);
|
||
if (actualItem) {
|
||
const diffs = [];
|
||
compareFields.forEach(field => {
|
||
let planValue = String(planItem[field.planField] || '');
|
||
let actualValue = String(actualItem[field.actualField] || '');
|
||
if (field.isNumber) {
|
||
planValue = Number(planValue);
|
||
actualValue = Number(actualValue);
|
||
}
|
||
let isDiff = false;
|
||
if (field.isNumber) {
|
||
isDiff = Math.abs(planValue - actualValue) > this.diffConfig.weightThreshold;
|
||
} else {
|
||
isDiff = planValue !== actualValue;
|
||
}
|
||
if (isDiff) {
|
||
diffs.push({
|
||
fieldName: field.fieldName,
|
||
planValue,
|
||
actualValue
|
||
});
|
||
}
|
||
});
|
||
if (diffs.length > 0) {
|
||
diffMap.set(planItem.lotNo, {
|
||
lotNo: planItem.lotNo,
|
||
diffs
|
||
});
|
||
}
|
||
}
|
||
});
|
||
this.diffCoils = Array.from(diffMap.values());
|
||
|
||
this.diffLoading = false;
|
||
}).catch(() => {
|
||
this.diffLoading = false;
|
||
});
|
||
},
|
||
getDetailList() {
|
||
this.detailLoading = true;
|
||
this.detailIds = [];
|
||
this.detailSingle = true;
|
||
this.detailMultiple = true;
|
||
listReceivePlan(this.detailQueryParams).then(response => {
|
||
this.detailList = response.rows;
|
||
this.detailTotal = response.total;
|
||
this.detailLoading = false;
|
||
});
|
||
},
|
||
openDetailAddDialog() {
|
||
this.detailForm = {
|
||
planId: this.currentPlanId,
|
||
receiveStatus: '0'
|
||
};
|
||
this.detailFormTitle = '新增待收货明细';
|
||
this.detailFormOpen = true;
|
||
this.$nextTick(() => {
|
||
this.$refs.detailForm && this.$refs.detailForm.clearValidate();
|
||
});
|
||
},
|
||
openDetailEditDialog(row) {
|
||
this.detailForm = { ...row };
|
||
this.detailFormTitle = '修改待收货明细';
|
||
this.detailFormOpen = true;
|
||
this.$nextTick(() => {
|
||
this.$refs.detailForm && this.$refs.detailForm.clearValidate();
|
||
});
|
||
},
|
||
resetDetailForm() {
|
||
this.detailForm = {};
|
||
},
|
||
submitDetailForm() {
|
||
this.$refs.detailForm.validate(valid => {
|
||
if (valid) {
|
||
this.detailButtonLoading = true;
|
||
if (this.detailForm.receiveId != null) {
|
||
updateReceivePlan(this.detailForm).then(response => {
|
||
this.$modal.msgSuccess("修改成功");
|
||
this.detailFormOpen = false;
|
||
this.getDetailList();
|
||
}).finally(() => {
|
||
this.detailButtonLoading = false;
|
||
});
|
||
} else {
|
||
addReceivePlan(this.detailForm).then(response => {
|
||
this.$modal.msgSuccess("新增成功");
|
||
this.detailFormOpen = false;
|
||
this.getDetailList();
|
||
}).finally(() => {
|
||
this.detailButtonLoading = false;
|
||
});
|
||
}
|
||
}
|
||
});
|
||
},
|
||
handleDetailDelete(row) {
|
||
this.$modal.confirm('是否确认删除该待收货明细?').then(() => {
|
||
return delReceivePlan(row.receiveId);
|
||
}).then(() => {
|
||
this.getDetailList();
|
||
this.$modal.msgSuccess("删除成功");
|
||
}).catch(() => { });
|
||
},
|
||
handleDetailSelectionChange(selection) {
|
||
this.detailIds = selection.map(item => item.receiveId);
|
||
this.detailSingle = this.detailIds.length !== 1;
|
||
this.detailMultiple = !this.detailIds.length;
|
||
},
|
||
handleDetailBatchDelete() {
|
||
const receiveIds = this.detailIds.join(',');
|
||
this.$modal.confirm('是否确认删除选中的' + this.detailIds.length + '条待收货明细?').then(() => {
|
||
this.detailLoading = true;
|
||
return delReceivePlan(receiveIds);
|
||
}).then(() => {
|
||
this.getDetailList();
|
||
this.$modal.msgSuccess("删除成功");
|
||
}).catch(() => { }).finally(() => {
|
||
this.detailLoading = false;
|
||
});
|
||
},
|
||
handleDetailClear() {
|
||
this.$modal.confirm('是否确认清空当前收货计划下的所有待收货明细?此操作不可恢复!').then(() => {
|
||
this.detailLoading = true;
|
||
return listReceivePlan({ pageNum: 1, pageSize: 99999, planId: this.currentPlanId });
|
||
}).then(response => {
|
||
const allIds = response.rows.map(item => item.receiveId);
|
||
if (allIds.length === 0) {
|
||
this.$modal.msgInfo('暂无数据可清空');
|
||
return;
|
||
}
|
||
return delReceivePlanBatch(allIds);
|
||
}).then(() => {
|
||
this.getDetailList();
|
||
this.$modal.msgSuccess("清空成功");
|
||
}).catch(() => { }).finally(() => {
|
||
this.detailLoading = false;
|
||
});
|
||
},
|
||
|
||
openImportDialog(row) {
|
||
this.currentPlanId = row.planId;
|
||
this.importOpen = true;
|
||
},
|
||
handleFileChange(file) {
|
||
if (this.validateLoading || this.importLoading || this.importStatus === 'processing') {
|
||
this.$message.warning('当前有操作正在进行中,请完成后再选择新文件');
|
||
return;
|
||
}
|
||
this.file = file.raw;
|
||
this.isValidated = false;
|
||
this.readExcel();
|
||
},
|
||
async readExcel() {
|
||
if (!this.file) return;
|
||
try {
|
||
const fileReader = new FileReader();
|
||
fileReader.readAsArrayBuffer(this.file);
|
||
fileReader.onload = async (e) => {
|
||
try {
|
||
const data = new Uint8Array(e.target.result);
|
||
const workbook = XLSX.read(data, { type: 'array' });
|
||
const sheetName = workbook.SheetNames[0];
|
||
const worksheet = workbook.Sheets[sheetName];
|
||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||
this.validateHeaders(jsonData[0]);
|
||
this.rawData = jsonData.slice(1).filter(row => row.length > 0);
|
||
this.formatExcel();
|
||
this.$message.success(`成功解析Excel,共读取到 ${this.rawData.length} 条数据`);
|
||
} catch (error) {
|
||
this.handleError(`解析Excel失败:${error.message}`);
|
||
}
|
||
};
|
||
} catch (error) {
|
||
this.handleError(`读取文件失败:${error.message}`);
|
||
}
|
||
},
|
||
validateHeaders(headers) {
|
||
this.errorList = [];
|
||
if (!headers || headers.length < TEMPLATE_HEADERS.length) {
|
||
this.errorList.push({
|
||
rowNum: 1,
|
||
errorMsg: `表头数量不匹配,要求至少${TEMPLATE_HEADERS.length}列`
|
||
});
|
||
return;
|
||
}
|
||
TEMPLATE_HEADERS.forEach((header, index) => {
|
||
if (headers[index] !== header) {
|
||
this.errorList.push({
|
||
rowNum: 1,
|
||
errorMsg: `第${index + 1}列表头错误,要求:"${header}",实际:"${headers[index] || ''}"`
|
||
});
|
||
}
|
||
});
|
||
if (this.errorList.length > 0) {
|
||
this.$message.error('Excel表头格式不符合要求,请检查!');
|
||
}
|
||
},
|
||
async handleValidate() {
|
||
if (this.validateLoading) {
|
||
this.$message.warning('正在校验数据,请稍候...');
|
||
return;
|
||
}
|
||
if (!this.file) {
|
||
this.$message.warning('请先选择Excel文件');
|
||
return;
|
||
}
|
||
this.isValidated = true;
|
||
if (this.rawData.length === 0) {
|
||
this.$message.warning('暂无数据可校验');
|
||
return;
|
||
}
|
||
this.validateLoading = true;
|
||
this.errorList = [];
|
||
try {
|
||
for (let i = 0; i < this.rawData.length; i++) {
|
||
const row = this.rawData[i];
|
||
const rowNum = i + 2;
|
||
const rowObj = this.formatRowData(row, rowNum);
|
||
if (!rowObj.lotNo || rowObj.lotNo.trim() === '') {
|
||
this.errorList.push({ rowNum, errorMsg: '入场卷号不能为空' });
|
||
}
|
||
if (rowObj.weight && isNaN(Number(rowObj.weight))) {
|
||
this.errorList.push({ rowNum, errorMsg: '重量必须是数字' });
|
||
}
|
||
if (rowObj.length && isNaN(Number(rowObj.length))) {
|
||
this.errorList.push({ rowNum, errorMsg: '长度必须是数字' });
|
||
}
|
||
}
|
||
if (this.errorList.length > 0) {
|
||
this.$message.error(`数据校验失败,共发现${this.errorList.length}条错误`);
|
||
} else {
|
||
this.$message.success('数据校验通过,可以开始导入');
|
||
}
|
||
} catch (error) {
|
||
this.handleError(`校验数据时发生错误:${error.message}`);
|
||
} finally {
|
||
this.validateLoading = false;
|
||
}
|
||
},
|
||
formatExcel() {
|
||
this.tableData = [];
|
||
if (this.rawData.length === 0) return;
|
||
this.rawData.forEach((row, index) => {
|
||
const rowNum = index + 2;
|
||
const rowObj = this.formatRowData(row, rowNum);
|
||
this.tableData.push(rowObj);
|
||
});
|
||
},
|
||
formatRowData(row, rowNum) {
|
||
const rowObj = {};
|
||
TEMPLATE_HEADERS.forEach((header, index) => {
|
||
const field = HEADER_MAP[header];
|
||
rowObj[field] = row[index] != null ? row[index].toString().trim() : '';
|
||
});
|
||
rowObj.rowNum = rowNum;
|
||
return rowObj;
|
||
},
|
||
async startImport() {
|
||
if (this.importLoading) {
|
||
this.$message.warning('正在导入数据,请勿重复操作...');
|
||
return;
|
||
}
|
||
if (!this.file || this.tableData.length === 0) {
|
||
this.$message.warning('暂无数据可导入');
|
||
return;
|
||
}
|
||
const confirm = await this.$confirm(
|
||
'确认导入已校验通过的数据?导入过程中请勿刷新页面或关闭浏览器!',
|
||
'导入确认',
|
||
{
|
||
confirmButtonText: '确认导入',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).catch(() => false);
|
||
if (!confirm) return;
|
||
this.importLoading = true;
|
||
this.importStatus = 'processing';
|
||
this.progress = 0;
|
||
this.importedCount = 0;
|
||
this.totalCount = this.tableData.length;
|
||
this.importErrorMsg = '';
|
||
try {
|
||
await this.batchImport();
|
||
this.importStatus = 'finished';
|
||
this.$message.success(`导入完成!共成功导入${this.importedCount}条数据`);
|
||
} catch (error) {
|
||
this.handleError(`导入失败:${error.message},已导入${this.importedCount}条数据`);
|
||
} finally {
|
||
this.importLoading = false;
|
||
}
|
||
},
|
||
async batchImport() {
|
||
for (let i = 0; i < this.tableData.length; i++) {
|
||
if (this.importStatus === 'error') break;
|
||
const row = this.tableData[i];
|
||
try {
|
||
await this.importOneRow(row);
|
||
this.importedCount++;
|
||
const currentProgress = Math.round(((i + 1) / this.totalCount) * 100);
|
||
this.progress = currentProgress;
|
||
await new Promise(resolve => setTimeout(resolve, 50));
|
||
} catch (error) {
|
||
throw new Error(`第${row.rowNum}行导入失败:${error.message}`);
|
||
}
|
||
}
|
||
},
|
||
async importOneRow(row) {
|
||
const params = {
|
||
planId: this.currentPlanId,
|
||
warehouseArea: row.warehouseArea,
|
||
lotNo: row.lotNo,
|
||
supplierLotNo: row.supplierLotNo,
|
||
productLotNo: row.productLotNo,
|
||
productionDate: row.productionDate,
|
||
weight: row.weight ? Number(row.weight) : null,
|
||
receiveStatus: '0',
|
||
goodsName: row.goodsName,
|
||
spec: row.spec,
|
||
length: row.length ? Number(row.length) : null,
|
||
materialType: row.materialType,
|
||
manufacturer: row.manufacturer,
|
||
surfaceTreatment: row.surfaceTreatment,
|
||
zincCoating: row.zincCoating,
|
||
teamGroup: row.teamGroup,
|
||
temperRolling: row.temperRolling,
|
||
coatingType: row.coatingType,
|
||
goodsType: row.goodsType,
|
||
remark: row.remark
|
||
};
|
||
const res = await addReceivePlan(params);
|
||
if (res.code !== 200) {
|
||
throw new Error(res.msg || '接口返回异常');
|
||
}
|
||
},
|
||
resetImport() {
|
||
if (this.validateLoading || this.importLoading || this.importStatus === 'processing') {
|
||
this.$message.warning('当前有操作正在进行中,无法重置');
|
||
return;
|
||
}
|
||
if (this.file || this.tableData.length > 0 || this.errorList.length > 0) {
|
||
const confirm = this.$confirm(
|
||
'确认重置所有状态?已选择的文件、解析的数据和校验结果将被清空!',
|
||
'重置确认',
|
||
{
|
||
confirmButtonText: '确认重置',
|
||
cancelButtonText: '取消',
|
||
type: 'info'
|
||
}
|
||
).catch(() => false);
|
||
if (!confirm) return;
|
||
}
|
||
this.file = null;
|
||
this.rawData = [];
|
||
this.tableData = [];
|
||
this.errorList = [];
|
||
this.isValidated = false;
|
||
this.progress = 0;
|
||
this.importedCount = 0;
|
||
this.totalCount = 0;
|
||
this.importStatus = 'idle';
|
||
this.importErrorMsg = '';
|
||
this.$refs.upload?.clearFiles();
|
||
this.$message.success('已重置所有状态');
|
||
},
|
||
downloadTemplate() {
|
||
const templateData = [
|
||
TEMPLATE_HEADERS,
|
||
['A库区', 'LOT001', 'SUP001', 'PROD001', '2024-01-01', 1000.50, '钢板', '1.0*1000', 2000, 'Q235', '宝钢', '镀锌', '80', '甲班', '1.5', 'GI', '原料', '示例备注']
|
||
];
|
||
const wb = XLSX.utils.book_new();
|
||
const ws = XLSX.utils.aoa_to_sheet(templateData);
|
||
ws['!cols'] = [
|
||
{ wch: 12 }, { wch: 12 }, { wch: 12 }, { wch: 12 }, { wch: 15 }, { wch: 12 },
|
||
{ wch: 15 }, { wch: 12 }, { wch: 12 }, { wch: 12 }, { wch: 15 }, { wch: 12 },
|
||
{ wch: 10 }, { wch: 10 }, { wch: 10 }, { wch: 12 }, { wch: 10 }, { wch: 15 }
|
||
];
|
||
XLSX.utils.book_append_sheet(wb, ws, '导入模板');
|
||
XLSX.writeFile(wb, '待收货明细导入模板.xlsx');
|
||
},
|
||
handleError(message) {
|
||
this.$message.error(message);
|
||
this.importStatus = 'error';
|
||
this.importErrorMsg = message;
|
||
this.validateLoading = false;
|
||
this.importLoading = false;
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.delivery-plan-card {
|
||
margin-bottom: 20px;
|
||
height: 100%;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.card-content {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.content-item {
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.content-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.label {
|
||
width: 80px;
|
||
font-weight: 500;
|
||
color: #606266;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.card-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.btn-danger {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.import-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.file-upload-area {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.file-upload-area.disabled {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.template-download {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.template-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.error-list {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.data-preview {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.import-progress {
|
||
margin-bottom: 20px;
|
||
padding: 10px;
|
||
background: #f5f7fa;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.progress-tip {
|
||
margin: 10px 0 0 0;
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.import-finished,
|
||
.import-error {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.diff-config {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 16px;
|
||
border: 1px solid #e4e7ed;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.config-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.diff-section {
|
||
background: #fafafa;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin-bottom: 16px;
|
||
border: 1px solid #e4e7ed;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
}
|
||
|
||
.section-icon {
|
||
font-size: 10px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.missing-icon {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.extra-icon {
|
||
color: #67c23a;
|
||
}
|
||
|
||
.diff-icon {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.section-count {
|
||
margin-left: auto;
|
||
background: #e4e7ed;
|
||
color: #606266;
|
||
font-size: 12px;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.diff-table {
|
||
background: #fff;
|
||
}
|
||
|
||
.diff-content {
|
||
padding: 8px 0;
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
}
|
||
|
||
.diff-item {
|
||
padding: 0;
|
||
display: flex;
|
||
gap: 20px;
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
border-left: 3px solid #e6a23c;
|
||
}
|
||
|
||
.value-label {
|
||
color: #909399;
|
||
margin-right: 8px;
|
||
width: 36px;
|
||
}
|
||
|
||
.plan-value {
|
||
color: #606266;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.actual-value {
|
||
color: #f56c6c;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.empty-tip {
|
||
text-align: center;
|
||
padding: 20px;
|
||
color: #909399;
|
||
font-size: 14px;
|
||
}
|
||
</style>
|