|
|
@@ -359,8 +359,10 @@
|
|
|
plain
|
|
|
size="mini"
|
|
|
type="success"
|
|
|
+ :loading="orderUpload.isUploading"
|
|
|
+ :disabled="orderUpload.isUploading"
|
|
|
@click="openDeliveryNote"
|
|
|
- >导入发货单
|
|
|
+ >{{ orderUpload.isUploading ? '导入中...' : '导入发货单' }}
|
|
|
</el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
@@ -832,16 +834,85 @@
|
|
|
<el-button @click="upload.open = false">取 消</el-button>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
- <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" :visible.sync="importMsgOpen"
|
|
|
- append-to-body title="导入结果" width="500px"
|
|
|
+ <el-dialog
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :visible.sync="importMsgOpen"
|
|
|
+ append-to-body
|
|
|
+ title="导入结果"
|
|
|
+ width="600px"
|
|
|
+ class="import-result-dialog"
|
|
|
>
|
|
|
- <h1>{{ importMsg }}</h1>
|
|
|
+ <div class="import-result-content">
|
|
|
+ <div class="result-summary" v-if="importSummary">
|
|
|
+ <el-alert
|
|
|
+ :title="importSummary.title"
|
|
|
+ :type="importSummary.type"
|
|
|
+ :closable="false"
|
|
|
+ show-icon
|
|
|
+ >
|
|
|
+ <template slot="default">
|
|
|
+ <div class="summary-stats">
|
|
|
+ <div class="stat-item success" v-if="importSummary.successCount > 0">
|
|
|
+ <i class="el-icon-success"></i>
|
|
|
+ <span class="label">成功:</span>
|
|
|
+ <span class="value">{{ importSummary.successCount }}条</span>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item error" v-if="importSummary.failCount > 0">
|
|
|
+ <i class="el-icon-error"></i>
|
|
|
+ <span class="label">失败:</span>
|
|
|
+ <span class="value">{{ importSummary.failCount }}条</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-alert>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="result-details" v-if="importDetails && importDetails.length > 0">
|
|
|
+ <el-divider content-position="left">详细信息</el-divider>
|
|
|
+ <div class="detail-list">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in importDetails"
|
|
|
+ :key="index"
|
|
|
+ class="detail-item"
|
|
|
+ :class="item.type"
|
|
|
+ >
|
|
|
+ <div class="item-header">
|
|
|
+ <i :class="item.type === 'success' ? 'el-icon-success' : 'el-icon-error'"></i>
|
|
|
+ <span class="item-title">{{ item.title }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="item-content" v-if="item.content">
|
|
|
+ {{ item.content }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 原始消息(备用) -->
|
|
|
+ <div class="raw-message" v-if="!importSummary && importMsg">
|
|
|
+ <el-alert
|
|
|
+ title="导入结果"
|
|
|
+ type="info"
|
|
|
+ :closable="false"
|
|
|
+ >
|
|
|
+ <div style="white-space: pre-wrap; word-break: break-all; line-height: 1.8;">
|
|
|
+ {{ importMsg }}
|
|
|
+ </div>
|
|
|
+ </el-alert>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
+ <el-button type="primary" @click="closeImportDialog">确 定</el-button>
|
|
|
+ </div>
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 批量发货 -->
|
|
|
<el-dialog
|
|
|
:before-close="handleClose"
|
|
|
:visible.sync="deliveryNoteOpen"
|
|
|
+ :close-on-click-modal="!orderUpload.isUploading"
|
|
|
+ :close-on-press-escape="!orderUpload.isUploading"
|
|
|
center
|
|
|
title="批量发货"
|
|
|
width="35%"
|
|
|
@@ -894,8 +965,16 @@
|
|
|
<div slot="tip" class="el-upload__tip" style="color:red">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
|
|
|
</el-upload>
|
|
|
<el-divider></el-divider>
|
|
|
- <el-button @click="cancelResetDeliveryNote">取 消</el-button>
|
|
|
- <el-button type="primary" @click="submitDeliveryNote('ruleForm')">确 定</el-button>
|
|
|
+ <el-button
|
|
|
+ @click="cancelResetDeliveryNote"
|
|
|
+ :disabled="orderUpload.isUploading"
|
|
|
+ >取 消</el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="submitDeliveryNote('ruleForm')"
|
|
|
+ :loading="orderUpload.isUploading"
|
|
|
+ :disabled="orderUpload.isUploading"
|
|
|
+ >{{ orderUpload.isUploading ? '导入中...' : '确 定' }}</el-button>
|
|
|
</span>
|
|
|
</el-dialog>
|
|
|
|
|
|
@@ -1211,6 +1290,8 @@ export default {
|
|
|
itemlist: [],
|
|
|
importMsgOpen: false,
|
|
|
importMsg: '',
|
|
|
+ importSummary: null, // 导入统计信息
|
|
|
+ importDetails: [], // 导入详细信息
|
|
|
scheduleOptions: [],
|
|
|
deliveryPayStatusOptions: [],
|
|
|
deliveryStatusOptions: [],
|
|
|
@@ -2536,18 +2617,123 @@ export default {
|
|
|
// 文件上传中处理
|
|
|
handleFileUploadProgress(event, file, fileList) {
|
|
|
this.upload.isUploading = true
|
|
|
+ this.orderUpload.isUploading = true
|
|
|
},
|
|
|
// 文件上传成功处理
|
|
|
handleFileSuccess(response, file, fileList) {
|
|
|
- this.upload.open = false
|
|
|
- this.upload.isUploading = false
|
|
|
- this.$refs.upload.clearFiles()
|
|
|
- this.importMsgOpen = true
|
|
|
- this.importMsg = response.msg
|
|
|
- // this.$alert(response.msg, '导入结果', {
|
|
|
- // dangerouslyUseHTMLString: true
|
|
|
- // });
|
|
|
- this.getList()
|
|
|
+ try {
|
|
|
+ this.upload.open = false
|
|
|
+ this.deliveryNoteOpen = false
|
|
|
+ this.$refs.upload.clearFiles()
|
|
|
+
|
|
|
+ // 解析导入结果消息
|
|
|
+ this.parseImportMessage(response.msg)
|
|
|
+
|
|
|
+ // 显示导入结果对话框
|
|
|
+ this.importMsgOpen = true
|
|
|
+
|
|
|
+ // 成功提示
|
|
|
+ this.$message.success('发货单导入完成')
|
|
|
+
|
|
|
+ // 刷新列表
|
|
|
+ this.getList()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('处理上传结果失败:', error)
|
|
|
+ this.$message.error('处理上传结果失败')
|
|
|
+ } finally {
|
|
|
+ // 使用延迟重置,确保动画完成和防止快速重复点击
|
|
|
+ setTimeout(() => {
|
|
|
+ this.upload.isUploading = false
|
|
|
+ this.orderUpload.isUploading = false
|
|
|
+ }, 500)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 解析导入结果消息
|
|
|
+ parseImportMessage(message) {
|
|
|
+ this.importMsg = message
|
|
|
+ this.importDetails = []
|
|
|
+ this.importSummary = null
|
|
|
+
|
|
|
+ if (!message) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 提取成功和失败数量
|
|
|
+ const successMatch = message.match(/成功\s*(\d+)\s*条/)
|
|
|
+ const failMatch = message.match(/失败\s*(\d+)\s*条/)
|
|
|
+
|
|
|
+ const successCount = successMatch ? parseInt(successMatch[1]) : 0
|
|
|
+ const failCount = failMatch ? parseInt(failMatch[1]) : 0
|
|
|
+
|
|
|
+ // 设置统计信息
|
|
|
+ this.importSummary = {
|
|
|
+ successCount,
|
|
|
+ failCount,
|
|
|
+ title: `导入完成:成功 ${successCount} 条,失败 ${failCount} 条`,
|
|
|
+ type: failCount === 0 ? 'success' : (successCount === 0 ? 'error' : 'warning')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析详细信息
|
|
|
+ const lines = message.split(/[;;]|第\d+行/)
|
|
|
+
|
|
|
+ lines.forEach((line, index) => {
|
|
|
+ line = line.trim()
|
|
|
+ if (!line || line.match(/^(导入完成|成功|失败)/)) return
|
|
|
+
|
|
|
+ // 提取订单号
|
|
|
+ const orderMatch = line.match(/订单号[::](\S+)/)
|
|
|
+ // 提取失败原因
|
|
|
+ const reasonMatch = line.match(/失败原因[::](.+?)(?:;|;|$)/)
|
|
|
+ // 提取错误信息
|
|
|
+ const errorMatch = line.match(/微信错误信息[::](.+?)(?:;|;|$)/)
|
|
|
+
|
|
|
+ if (orderMatch || reasonMatch || errorMatch) {
|
|
|
+ const detail = {
|
|
|
+ type: reasonMatch || errorMatch ? 'error' : 'success',
|
|
|
+ title: orderMatch ? `订单号: ${orderMatch[1]}` : `第 ${index + 1} 条`,
|
|
|
+ content: ''
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reasonMatch) {
|
|
|
+ detail.content = `失败原因: ${reasonMatch[1]}`
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errorMatch) {
|
|
|
+ detail.content += (detail.content ? ' | ' : '') + `错误: ${errorMatch[1]}`
|
|
|
+ }
|
|
|
+
|
|
|
+ this.importDetails.push(detail)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果没有解析出详细信息,尝试按行分割
|
|
|
+ if (this.importDetails.length === 0) {
|
|
|
+ const detailLines = message.split(/\n|;|;/).filter(l => {
|
|
|
+ const trimmed = l.trim()
|
|
|
+ return trimmed && !trimmed.match(/^(导入完成|成功|失败)/)
|
|
|
+ })
|
|
|
+
|
|
|
+ detailLines.forEach((line, index) => {
|
|
|
+ if (line.trim()) {
|
|
|
+ this.importDetails.push({
|
|
|
+ type: line.includes('失败') ? 'error' : 'info',
|
|
|
+ title: `记录 ${index + 1}`,
|
|
|
+ content: line.trim()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析导入消息失败:', error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 关闭导入结果对话框
|
|
|
+ closeImportDialog() {
|
|
|
+ this.importMsgOpen = false
|
|
|
+ this.importMsg = ''
|
|
|
+ this.importSummary = null
|
|
|
+ this.importDetails = []
|
|
|
},
|
|
|
/** 查询部门下拉树结构 */
|
|
|
getTreeselect() {
|
|
|
@@ -2578,6 +2764,12 @@ export default {
|
|
|
this.getAppList()
|
|
|
},
|
|
|
handleClose(done) {
|
|
|
+ // 如果正在上传中,阻止关闭
|
|
|
+ if (this.orderUpload.isUploading) {
|
|
|
+ this.$message.warning('发货单正在导入中,请等待完成后再关闭')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.$confirm('确认关闭?')
|
|
|
.then(_ => {
|
|
|
done()
|
|
|
@@ -2609,6 +2801,12 @@ export default {
|
|
|
},
|
|
|
// 提交发货单
|
|
|
submitDeliveryNote(formName) {
|
|
|
+ // 防止重复提交
|
|
|
+ if (this.orderUpload.isUploading) {
|
|
|
+ this.$message.warning('发货单正在导入中,请勿重复提交')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.$refs[formName].validate((valid) => {
|
|
|
if (valid) {
|
|
|
const uploadFiles = this.$refs.upload.uploadFiles
|
|
|
@@ -2616,6 +2814,10 @@ export default {
|
|
|
this.$message.error('请选择要上传的文件')
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+ // 立即设置上传状态,防止用户快速多次点击
|
|
|
+ this.orderUpload.isUploading = true
|
|
|
+
|
|
|
this.$refs.upload.submit()
|
|
|
} else {
|
|
|
return false
|
|
|
@@ -2624,10 +2826,21 @@ export default {
|
|
|
},
|
|
|
//取消重置
|
|
|
cancelResetDeliveryNote(){
|
|
|
+ // 如果正在上传中,阻止取消
|
|
|
+ if (this.orderUpload.isUploading) {
|
|
|
+ this.$message.warning('发货单正在导入中,请等待完成后再取消')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.deliveryNoteOpen = false;
|
|
|
this.ruleForm.miniAppId = null;
|
|
|
this.ruleForm.shipmentType = 4;
|
|
|
this.resetForm('ruleForm')
|
|
|
+
|
|
|
+ // 清空上传文件列表
|
|
|
+ if (this.$refs.upload) {
|
|
|
+ this.$refs.upload.clearFiles()
|
|
|
+ }
|
|
|
},
|
|
|
getProduct(value){
|
|
|
//获取商品列表
|
|
|
@@ -2678,6 +2891,140 @@ export default {
|
|
|
height: 500px;
|
|
|
overflow: auto;
|
|
|
}
|
|
|
+/* 导入结果对话框样式 */
|
|
|
+.import-result-dialog {
|
|
|
+ .import-result-content {
|
|
|
+ max-height: 500px;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .result-summary {
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .summary-stats {
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ margin-top: 10px;
|
|
|
+
|
|
|
+ .stat-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .label {
|
|
|
+ color: #606266;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.success {
|
|
|
+ .value {
|
|
|
+ color: #67c23a;
|
|
|
+ }
|
|
|
+ i {
|
|
|
+ color: #67c23a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.error {
|
|
|
+ .value {
|
|
|
+ color: #f56c6c;
|
|
|
+ }
|
|
|
+ i {
|
|
|
+ color: #f56c6c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .result-details {
|
|
|
+ .detail-list {
|
|
|
+ max-height: 300px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .detail-item {
|
|
|
+ padding: 12px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-left: 3px solid #909399;
|
|
|
+
|
|
|
+ &.success {
|
|
|
+ background-color: #f0f9ff;
|
|
|
+ border-left-color: #67c23a;
|
|
|
+
|
|
|
+ .item-header i {
|
|
|
+ color: #67c23a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.error {
|
|
|
+ background-color: #fef0f0;
|
|
|
+ border-left-color: #f56c6c;
|
|
|
+
|
|
|
+ .item-header i {
|
|
|
+ color: #f56c6c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.info {
|
|
|
+ background-color: #f4f4f5;
|
|
|
+ border-left-color: #909399;
|
|
|
+
|
|
|
+ .item-header i {
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-content {
|
|
|
+ padding-left: 24px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ line-height: 1.6;
|
|
|
+ word-break: break-all;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .raw-message {
|
|
|
+ .el-alert {
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .import-result-dialog {
|
|
|
+ .result-summary .summary-stats {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|
|
|
<style>
|
|
|
.el-descriptions-item__label.is-bordered-label {
|