feat(processSpec): 新增方案点位模板导入功能
- 在方案点位页面添加模板导入按钮 - 实现Excel文件上传和解析功能 - 添加数据校验机制包括表头验证和格式检查 - 提供数据预览和错误列表展示 - 实现批量导入功能支持进度显示 - 添加模板下载功能 - 集成导入状态管理和错误处理 - 添加导入对话框界面和相关样式
This commit is contained in:
@@ -73,9 +73,14 @@
|
|||||||
size="mini"
|
size="mini"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="el-icon-plus"
|
icon="el-icon-plus"
|
||||||
style="margin-left:auto"
|
|
||||||
@click="openPlanDialog()"
|
@click="openPlanDialog()"
|
||||||
>新建方案点位</el-button>
|
>新建方案点位</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="success"
|
||||||
|
icon="el-icon-upload2"
|
||||||
|
@click="openImportDialog()"
|
||||||
|
>模板导入</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 方案点位表 -->
|
<!-- 方案点位表 -->
|
||||||
@@ -204,10 +209,87 @@
|
|||||||
<el-button size="small" type="primary" :loading="paramSubmitLoading" @click="submitParam">确定</el-button>
|
<el-button size="small" type="primary" :loading="paramSubmitLoading" @click="submitParam">确定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 模板导入 dialog -->
|
||||||
|
<el-dialog title="模板导入" :visible.sync="importOpen" width="800px" append-to-body @close="resetImport">
|
||||||
|
<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="以下行数据不符合格式要求,请修正后重新导入:" 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="segmentType" label="段类型" width="100" />
|
||||||
|
<el-table-column prop="segmentName" label="段名称" width="120" />
|
||||||
|
<el-table-column prop="pointName" label="点位名称" width="150" />
|
||||||
|
<el-table-column prop="paramName" label="参数名称" width="150" />
|
||||||
|
<el-table-column prop="targetValue" label="设定值" width="100" />
|
||||||
|
<el-table-column prop="lowerLimit" label="下限" width="100" />
|
||||||
|
<el-table-column prop="upperLimit" label="上限" width="100" />
|
||||||
|
<el-table-column prop="unit" label="单位" width="80" />
|
||||||
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
import { listProcessPlan, addProcessPlan, updateProcessPlan, delProcessPlan } from '@/api/wms/processPlan'
|
import { listProcessPlan, addProcessPlan, updateProcessPlan, delProcessPlan } from '@/api/wms/processPlan'
|
||||||
import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam, delProcessPlanParam } from '@/api/wms/processPlanParam'
|
import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam, delProcessPlanParam } from '@/api/wms/processPlanParam'
|
||||||
|
|
||||||
@@ -218,6 +300,21 @@ const SEGMENT_FORM_OPTIONS = [
|
|||||||
{ label: '出口段', value: 'OUTLET' }
|
{ label: '出口段', value: 'OUTLET' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** Excel模板表头配置 */
|
||||||
|
const TEMPLATE_HEADERS = ['段类型', '段名称', '点位名称', '参数名称', '设定值', '上限', '下限', '单位']
|
||||||
|
|
||||||
|
/** 表头字段映射 */
|
||||||
|
const HEADER_MAP = {
|
||||||
|
'段类型': 'segmentType',
|
||||||
|
'段名称': 'segmentName',
|
||||||
|
'点位名称': 'pointName',
|
||||||
|
'参数名称': 'paramName',
|
||||||
|
'设定值': 'targetValue',
|
||||||
|
'上限': 'upperLimit',
|
||||||
|
'下限': 'lowerLimit',
|
||||||
|
'单位': 'unit'
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProcessSpecPlanSpec',
|
name: 'ProcessSpecPlanSpec',
|
||||||
data() {
|
data() {
|
||||||
@@ -254,7 +351,21 @@ export default {
|
|||||||
paramRules: {
|
paramRules: {
|
||||||
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
|
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
|
||||||
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
||||||
}
|
},
|
||||||
|
// 导入相关数据
|
||||||
|
importOpen: false,
|
||||||
|
file: null,
|
||||||
|
rawData: [],
|
||||||
|
tableData: [],
|
||||||
|
errorList: [],
|
||||||
|
isValidated: false,
|
||||||
|
progress: 0,
|
||||||
|
importedCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
importStatus: 'idle', // idle, processing, finished, error
|
||||||
|
importErrorMsg: '',
|
||||||
|
validateLoading: false,
|
||||||
|
importLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -516,6 +627,423 @@ export default {
|
|||||||
this.$modal.msgSuccess('删除成功')
|
this.$modal.msgSuccess('删除成功')
|
||||||
this.loadParams(this.selectedPlan.planId)
|
this.loadParams(this.selectedPlan.planId)
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
// ===================== 导入相关方法 =====================
|
||||||
|
/**
|
||||||
|
* 打开导入对话框
|
||||||
|
*/
|
||||||
|
openImportDialog() {
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 读取Excel文件内容
|
||||||
|
*/
|
||||||
|
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.length !== TEMPLATE_HEADERS.length) {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum: 1,
|
||||||
|
errorMsg: `表头数量不匹配,要求${TEMPLATE_HEADERS.length}列,实际${headers.length}列`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验表头名称
|
||||||
|
headers.forEach((header, index) => {
|
||||||
|
if (header !== TEMPLATE_HEADERS[index]) {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum: 1,
|
||||||
|
errorMsg: `第${index + 1}列表头错误,要求:"${TEMPLATE_HEADERS[index]}",实际:"${header}"`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.errorList.length > 0) {
|
||||||
|
this.$message.error('Excel表头格式不符合要求,请检查!')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 校验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.segmentType || rowObj.segmentType.trim() === '') {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum,
|
||||||
|
errorMsg: '段类型不能为空'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!rowObj.pointName || rowObj.pointName.trim() === '') {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum,
|
||||||
|
errorMsg: '点位名称不能为空'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!rowObj.paramName || rowObj.paramName.trim() === '') {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum,
|
||||||
|
errorMsg: '参数名称不能为空'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验段类型是否合法
|
||||||
|
if (rowObj.segmentType && !['INLET', 'PROCESS', 'OUTLET', '入口段', '工艺段', '出口段'].includes(rowObj.segmentType.trim())) {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum,
|
||||||
|
errorMsg: '段类型只能是:入口段、工艺段、出口段'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验数值字段
|
||||||
|
if (rowObj.targetValue && isNaN(Number(rowObj.targetValue))) {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum,
|
||||||
|
errorMsg: '设定值必须是数字'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (rowObj.upperLimit && isNaN(Number(rowObj.upperLimit))) {
|
||||||
|
this.errorList.push({
|
||||||
|
rowNum,
|
||||||
|
errorMsg: '上限必须是数字'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (rowObj.lowerLimit && isNaN(Number(rowObj.lowerLimit))) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 格式化Excel数据
|
||||||
|
*/
|
||||||
|
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] ? row[index].toString().trim() : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 段类型标准化
|
||||||
|
if (rowObj.segmentType === '入口段') rowObj.segmentType = 'INLET'
|
||||||
|
if (rowObj.segmentType === '工艺段') rowObj.segmentType = 'PROCESS'
|
||||||
|
if (rowObj.segmentType === '出口段') rowObj.segmentType = 'OUTLET'
|
||||||
|
|
||||||
|
// 增加行号
|
||||||
|
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}条数据`)
|
||||||
|
this.loadPlans() // 重新加载列表
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(`导入失败:${error.message},已导入${this.importedCount}条数据`)
|
||||||
|
} finally {
|
||||||
|
this.importLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 批量导入数据
|
||||||
|
*/
|
||||||
|
async batchImport() {
|
||||||
|
// <20>照点位名称分组,每个点位创建一条记录,然后添加多个参数
|
||||||
|
const pointGroups = {}
|
||||||
|
|
||||||
|
// 分组处理
|
||||||
|
this.tableData.forEach(row => {
|
||||||
|
const pointKey = `${row.segmentType}_${row.segmentName}_${row.pointName}`
|
||||||
|
if (!pointGroups[pointKey]) {
|
||||||
|
pointGroups[pointKey] = {
|
||||||
|
segmentType: row.segmentType,
|
||||||
|
segmentName: row.segmentName,
|
||||||
|
pointName: row.pointName,
|
||||||
|
pointCode: row.pointName, // 默认使用点位名称作为编码
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pointGroups[pointKey].params.push({
|
||||||
|
paramName: row.paramName,
|
||||||
|
targetValue: row.targetValue,
|
||||||
|
upperLimit: row.upperLimit,
|
||||||
|
lowerLimit: row.lowerLimit,
|
||||||
|
unit: row.unit
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 逐个导入
|
||||||
|
let index = 0
|
||||||
|
for (const pointKey in pointGroups) {
|
||||||
|
if (this.importStatus === 'error') break
|
||||||
|
|
||||||
|
const group = pointGroups[pointKey]
|
||||||
|
try {
|
||||||
|
await this.importOnePoint(group)
|
||||||
|
this.importedCount += group.params.length
|
||||||
|
|
||||||
|
// 更新进度
|
||||||
|
index += group.params.length
|
||||||
|
const currentProgress = Math.round((index / this.totalCount) * 100)
|
||||||
|
this.progress = currentProgress
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50))
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`导入点位"${group.pointName}"失败:${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 导入单个点位及其参数
|
||||||
|
*/
|
||||||
|
async importOnePoint(group) {
|
||||||
|
try {
|
||||||
|
// 1. 创建或更新点位
|
||||||
|
const planParams = {
|
||||||
|
versionId: this.versionId,
|
||||||
|
segmentType: group.segmentType,
|
||||||
|
segmentName: group.segmentName,
|
||||||
|
pointName: group.pointName,
|
||||||
|
pointCode: group.pointCode,
|
||||||
|
sortOrder: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const planRes = await addProcessPlan(planParams)
|
||||||
|
if (planRes.code !== 200) {
|
||||||
|
throw new Error(`点位创建失败:${planRes.msg || '接口返回异常'}`)
|
||||||
|
}
|
||||||
|
const planId = planRes.data
|
||||||
|
|
||||||
|
// 2. 为该点位添加参数
|
||||||
|
for (const param of group.params) {
|
||||||
|
const paramParams = {
|
||||||
|
planId: planId,
|
||||||
|
paramCode: param.paramName, // 默认使用参数名称作为编码
|
||||||
|
paramName: param.paramName,
|
||||||
|
targetValue: param.targetValue ? Number(param.targetValue) : null,
|
||||||
|
upperLimit: param.upperLimit ? Number(param.upperLimit) : null,
|
||||||
|
lowerLimit: param.lowerLimit ? Number(param.lowerLimit) : null,
|
||||||
|
unit: param.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramRes = await addProcessPlanParam(paramParams)
|
||||||
|
if (paramRes.code !== 200) {
|
||||||
|
throw new Error(`参数"${param.paramName}"创建失败:${paramRes.msg || '接口返回异常'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 重置导入状态
|
||||||
|
*/
|
||||||
|
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, // 表头
|
||||||
|
['INLET', '入口段1', '温度传感器1', '温度值', 25.5, 30, 20, '℃'], // 示例数据
|
||||||
|
['PROCESS', '工艺段1', '压力传感器1', '压力值', 1.5, 2, 1, 'MPa'],
|
||||||
|
['OUTLET', '出口段1', '流量传感器1', '流量值', 100, 120, 80, 'm³/h']
|
||||||
|
]
|
||||||
|
|
||||||
|
// 创建工作簿
|
||||||
|
const wb = XLSX.utils.book_new()
|
||||||
|
const ws = XLSX.utils.aoa_to_sheet(templateData)
|
||||||
|
|
||||||
|
// 设置列宽
|
||||||
|
ws['!cols'] = [
|
||||||
|
{ wch: 10 }, // 段类型
|
||||||
|
{ wch: 15 }, // 段名称
|
||||||
|
{ wch: 20 }, // 点位名称
|
||||||
|
{ wch: 20 }, // 参数名称
|
||||||
|
{ wch: 12 }, // 设定值
|
||||||
|
{ wch: 10 }, // 上限
|
||||||
|
{ wch: 10 }, // 下限
|
||||||
|
{ wch: 10 } // 单位
|
||||||
|
]
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,4 +1205,60 @@ export default {
|
|||||||
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
|
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
|
||||||
|
|
||||||
.btn-danger { color: #f56c6c; }
|
.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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user