|
|
@@ -0,0 +1,476 @@
|
|
|
+<template>
|
|
|
+ <el-dialog
|
|
|
+ :title="'导入『电桩信息』'"
|
|
|
+ v-model="state.dialog.isShowDialog"
|
|
|
+ width="75%"
|
|
|
+ destroy-on-close
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ draggable
|
|
|
+ class="pd0 dialog-padding-none"
|
|
|
+ >
|
|
|
+
|
|
|
+ <el-tabs class="demo-tabs">
|
|
|
+ <el-tab-pane>
|
|
|
+ <template #label>
|
|
|
+ <span class="custom-tabs-label">
|
|
|
+ <SvgIcon name="ele-Grid"></SvgIcon>
|
|
|
+ <span>Excel导入</span>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-button @click="handleOpenFile" plain type="success" class="mt5"> 请选择excel文件上传导入</el-button>
|
|
|
+ <el-text type="primary" class="ml5">{{ state.uploadFileName }} <SvgIcon v-if="state.uploadFileName" name="ele-Remove" @click="reset" color="var(--el-color-danger)"></SvgIcon></el-text>
|
|
|
+
|
|
|
+ <!-- <ext-upload
|
|
|
+ v-model="state.excelForm.file"
|
|
|
+ placeholder="请选择上传文件"
|
|
|
+ tips="请选择excel文件上传导入"
|
|
|
+ style="width: 100%"
|
|
|
+ :limit="1"
|
|
|
+ mime="excel"
|
|
|
+ @on-success="handleUploadFileSuccess">
|
|
|
+ </ext-upload>-->
|
|
|
+
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <div class="w100 mt5">
|
|
|
+ <div class="w100">
|
|
|
+ <el-scrollbar @scroll="handleFieldHeaderScroll" ref="fieldHeaderScrollRef" :min-size="0">
|
|
|
+ <div class="model-field-select-wrapper">
|
|
|
+ <div class="model-field-select-header" v-for="(field,idx) in state.excelForm.columns"
|
|
|
+ :key="'select_'+idx">
|
|
|
+ <template v-if="idx===0">
|
|
|
+ <div style="width: 135px;height: 32px;line-height:32px;border:1px solid #eee;text-align: center;">导入列选择</div>
|
|
|
+ </template>
|
|
|
+ <el-select-v2 v-else
|
|
|
+ v-model="state.excelForm.importFields[idx]"
|
|
|
+ :options="state.excelForm.selectFields"
|
|
|
+ style="width: 135px;height: 36px;border:none;display: inline-block;flex-shrink: 0;margin-right: 10px;"
|
|
|
+ filterable
|
|
|
+ clearable>
|
|
|
+ </el-select-v2>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <vxe-table
|
|
|
+ ref="excelRef"
|
|
|
+ border="full"
|
|
|
+ show-header-overflow
|
|
|
+ :show-footer="false"
|
|
|
+ :max-height="state.tableHeight"
|
|
|
+ :loading="false"
|
|
|
+ header-cell-class-name="task-list-header"
|
|
|
+ header-align="left"
|
|
|
+ empty-text=" "
|
|
|
+ @cell-click="handleCellClick"
|
|
|
+ :data="state.excelForm.data"
|
|
|
+ @scroll="handleDataTableScroll"
|
|
|
+ :row-config="{isHover:true,isCurrent:true}"
|
|
|
+ :tooltip-config="{zIndex:9999}"
|
|
|
+ :scroll-x="{enabled: true, gt: 100}"
|
|
|
+ :scroll-y="{enabled: true, gt: 100}">
|
|
|
+
|
|
|
+ <vxe-column
|
|
|
+ v-for="(field,idx) in state.excelForm.columns"
|
|
|
+ :key="idx"
|
|
|
+ :field="field.field"
|
|
|
+ :width="135"
|
|
|
+ :title="field.name"
|
|
|
+ show-overflow="title">
|
|
|
+ <template v-if="field.field==='idx'" #default="{row,rowIndex}">
|
|
|
+ <div class="w100 flex flex-align-items-center flex-justify-center hover-parent">
|
|
|
+
|
|
|
+ <el-dropdown @command="handleRow($event,row,rowIndex)">
|
|
|
+ <el-text type="primary" class="hover-child ml5 cursor-pointer font12"> <span>{{ rowIndex+1}}</span></el-text>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-item command="setHeader" style="color:var(--el-color-success)" class="font12">设为表头</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="delete" style="color:var(--el-color-danger)" class="font12">删除本行</el-dropdown-item>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </vxe-column>
|
|
|
+ </vxe-table>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="onClose" size="default">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="handleConfirm" :loading="state.importLoading" size="default">导入</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="EndpointImportDialog">
|
|
|
+import {reactive, onMounted, nextTick, ref, toRaw, unref} from 'vue';
|
|
|
+
|
|
|
+//定义初始变量,重置使用
|
|
|
+import {useFileDialog} from '@vueuse/core'
|
|
|
+
|
|
|
+import {$body, $post} from "/@/utils/request";
|
|
|
+import {Msg} from "/@/utils/message";
|
|
|
+import {VxeTable, VxeColumn, VxeTableInstance} from "vxe-table";
|
|
|
+import 'vxe-table/lib/style.css'
|
|
|
+import {useDebounceFn, useThrottleFn} from '@vueuse/core'
|
|
|
+
|
|
|
+import {ElScrollbar} from 'element-plus'
|
|
|
+import * as XLSX from 'xlsx'
|
|
|
+
|
|
|
+const fieldHeaderScrollRef = ref<InstanceType<typeof ElScrollbar>>();
|
|
|
+const excelRef = ref<VxeTableInstance>();
|
|
|
+
|
|
|
+
|
|
|
+const {files, open: handleOpenFile, reset, onChange:handleFileChange} = useFileDialog(
|
|
|
+ {
|
|
|
+ multiple: false,
|
|
|
+ accept: '.xls,.xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel'
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+handleFileChange((files)=>{
|
|
|
+ parseExcel()
|
|
|
+})
|
|
|
+
|
|
|
+const initState = () => ({
|
|
|
+ dialog: {
|
|
|
+ isShowDialog: false,
|
|
|
+ type: '',
|
|
|
+ title: '',
|
|
|
+ submitTxt: '',
|
|
|
+ },
|
|
|
+ initColumnList: [{field: 'idx', name: '#', fixed: 'left'}],
|
|
|
+ excelForm: {
|
|
|
+ file: '',
|
|
|
+ rowHeaderIndex: 0,
|
|
|
+ rowContentIndex: 1,
|
|
|
+ columns: [] as any[],
|
|
|
+ sheet: '',
|
|
|
+ sheets: [] as string[],
|
|
|
+ data: [],
|
|
|
+ loading: false,
|
|
|
+ importFields: [] as string[],
|
|
|
+ selectFields: []
|
|
|
+ },
|
|
|
+ uploadFileName: '' as String | null,
|
|
|
+ importLoading: false,
|
|
|
+ tableHeight: 400,
|
|
|
+ fieldList:[
|
|
|
+ {label:'序号ID',value:'id'},
|
|
|
+ {label:'站点ID',value:'stationId'},
|
|
|
+ {label:'站点名称',value:'stationName'},
|
|
|
+ {label:'设备短编号',value:'shortId'},
|
|
|
+ {label:'充电桩SN',value:'equipmentId'},
|
|
|
+ {label:'充电口SN',value:'connectorId'},
|
|
|
+ {label:'车位编号',value:'parkingNo'},
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+// 定义变量内容
|
|
|
+const state = reactive(initState());
|
|
|
+
|
|
|
+const emit = defineEmits(['on-import-finish']);
|
|
|
+
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+
|
|
|
+})
|
|
|
+
|
|
|
+const open = () => {
|
|
|
+ state.dialog.isShowDialog = true;
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ let clientHeight = document.body.clientHeight;
|
|
|
+ let th = clientHeight - 60 - 120 - 300
|
|
|
+ state.tableHeight = th;
|
|
|
+ })
|
|
|
+ state.excelForm.selectFields = state.fieldList.map((k: any) => {
|
|
|
+ return {
|
|
|
+ value: k.value,
|
|
|
+ label: k.label
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭弹窗
|
|
|
+const onClose = () => {
|
|
|
+ state.dialog.isShowDialog = false;
|
|
|
+ Object.assign(state, initState())
|
|
|
+ reset();
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+const handleCellClick = (e: any) => {
|
|
|
+ let {row, rowIndex, $rowIndex, column, columnIndex, $columnIndex} = e;
|
|
|
+ if (columnIndex === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let val = state.excelForm.data[rowIndex][column.property]
|
|
|
+ Msg.prompt(`修改导入的数据`, '修改',
|
|
|
+ {
|
|
|
+ draggable: true,
|
|
|
+ type: 'primary',
|
|
|
+ value: val
|
|
|
+ }
|
|
|
+ ).then((res: any) => {
|
|
|
+ let {value, action} = res;
|
|
|
+ if (action === 'confirm') {
|
|
|
+ state.excelForm.data[rowIndex][column.property] = value
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleConfirm = () => {
|
|
|
+ let checks: Array<string> = [];
|
|
|
+ let checkPass= true;
|
|
|
+ state.excelForm.importFields.forEach(key => {
|
|
|
+ if (key) {
|
|
|
+ if (checks.includes(key)) {
|
|
|
+ checkPass = false;
|
|
|
+ Msg.message('表头不可重复', 'error')
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ checks.push(key)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if(!checkPass)return;
|
|
|
+ debounceImport();
|
|
|
+}
|
|
|
+
|
|
|
+const debounceImport = useThrottleFn(() => {
|
|
|
+ state.importLoading = true;
|
|
|
+ let fieldIndexes: Array<number> = []
|
|
|
+ state.excelForm.importFields.forEach((key, idx) => {
|
|
|
+ if (key) {
|
|
|
+ fieldIndexes.push(idx - 1);
|
|
|
+ }
|
|
|
+ })
|
|
|
+let fields=state.excelForm.importFields.filter(k => !!k);
|
|
|
+ let dataList : Array<any> = [];
|
|
|
+ state.excelForm.data.forEach(data => {
|
|
|
+ let item ={}
|
|
|
+ let arr: Array<any> = []
|
|
|
+ fieldIndexes.forEach((idx, i) => {
|
|
|
+ item[fields[i]] = data[`f${idx}`] || ""
|
|
|
+ })
|
|
|
+ dataList.push(item)
|
|
|
+ })
|
|
|
+
|
|
|
+ let params = {
|
|
|
+ fields: state.excelForm.importFields.filter(k => !!k),
|
|
|
+ fieldIndexes,
|
|
|
+ dataList
|
|
|
+ }
|
|
|
+
|
|
|
+ $body(`station/importStation`, params).then(() => {
|
|
|
+ Msg.message(`导入成功`)
|
|
|
+ state.importLoading = false;
|
|
|
+ emit('on-import-finish')
|
|
|
+ state.dialog.isShowDialog = false;
|
|
|
+ }).catch(() => {
|
|
|
+ state.importLoading = false;
|
|
|
+ })
|
|
|
+}, 1500)
|
|
|
+
|
|
|
+//表头选择器滚动
|
|
|
+const handleFieldHeaderScroll = (ev: any) => {
|
|
|
+ let {scrollLeft, scrollTop} = ev;
|
|
|
+ // console.log(scrollLeft, scrollTop, ev)
|
|
|
+ if (scrollLeft >= 0) {
|
|
|
+ let st = excelRef.value.getScroll();
|
|
|
+ const el = excelRef.value?.$el.querySelector('.vxe-table--body-wrapper')
|
|
|
+ if (el) {
|
|
|
+ el.scrollLeft = scrollLeft
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//数据表格滚动
|
|
|
+const handleDataTableScroll = (ev: any) => {
|
|
|
+ let {scrollLeft} = ev;
|
|
|
+ // console.log(type, scrollLeft, scrollHeight)
|
|
|
+ if (scrollLeft >= 0) {
|
|
|
+ fieldHeaderScrollRef.value?.setScrollLeft(scrollLeft);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleRow = (command: string, row: any, rowIndex: number) => {
|
|
|
+ if (command === 'setHeader') {
|
|
|
+ let tmpHeaders: any[] = []
|
|
|
+ let tmpHeader = state.excelForm.data[rowIndex]
|
|
|
+ Object.keys(tmpHeader).forEach((key, i) => {
|
|
|
+ if (key.startsWith("f")) {
|
|
|
+ let header = tmpHeader[key]
|
|
|
+ let find = state.fieldList.find((k: any) => k.name == header);
|
|
|
+ if (find) {
|
|
|
+ state.excelForm.importFields[i + 1] = find.label;
|
|
|
+ } else {
|
|
|
+ state.excelForm.importFields[i + 1] = "";
|
|
|
+ }
|
|
|
+ tmpHeaders.push({
|
|
|
+ field: `f${i}`,
|
|
|
+ name: header
|
|
|
+ });
|
|
|
+ }
|
|
|
+ })
|
|
|
+ state.excelForm.columns = state.initColumnList.concat(tmpHeaders)
|
|
|
+ state.excelForm.data.splice(rowIndex, 1)
|
|
|
+ excelRef.value?.reloadData(state.excelForm.data);
|
|
|
+ } else if (command === 'delete') {
|
|
|
+ state.excelForm.data.splice(rowIndex, 1)
|
|
|
+ excelRef.value?.reloadData(state.excelForm.data);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const parseExcel = (sheetName: string = "") => {
|
|
|
+ // console.log(sheetName, state.excelForm.sheet,files)
|
|
|
+ // if(sheetName==state.excelForm.sheet&&state.excelForm.sheet){
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
+ if (!files || !files.value || !files.value[0]) {
|
|
|
+ state.excelForm.columns = []
|
|
|
+ excelRef.value?.reloadData([])
|
|
|
+ state.uploadFileName = null;
|
|
|
+ state.excelForm.sheets = []
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // console.log(files)
|
|
|
+ Msg.showLoading('解析中...')
|
|
|
+ state.uploadFileName = files.value[0].name;
|
|
|
+ let reader = new FileReader();
|
|
|
+ reader.onload = function () {
|
|
|
+ // console.log(reader)
|
|
|
+
|
|
|
+ let fileData = reader.result;
|
|
|
+ let wb = XLSX.read(fileData, {type: 'binary', cellDates: true});
|
|
|
+ state.excelForm.sheets = wb.SheetNames;
|
|
|
+ // {header:1} 取消标题列.
|
|
|
+ // console.log(wb.SheetNames)
|
|
|
+ let sheet = sheetName || wb.SheetNames[0];
|
|
|
+ state.excelForm.sheet = sheet;
|
|
|
+ let rowList: (any) = XLSX.utils.sheet_to_json(wb.Sheets[sheet], {header: 1});
|
|
|
+ // console.log(rowList)
|
|
|
+ let maxColumnLength = 1;
|
|
|
+ rowList.forEach((row: any, idx: number) => {
|
|
|
+ // console.log(row)
|
|
|
+ if (idx < 10) {
|
|
|
+ maxColumnLength = Math.max(maxColumnLength, row.length);
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // console.log(maxColumnLength, rowList)
|
|
|
+ let tmpHeaders: Array<any> = []
|
|
|
+ let headers = rowList[state.excelForm.rowHeaderIndex];
|
|
|
+ for (let i = 0; i < maxColumnLength; i++) {
|
|
|
+ let header = i >= headers.length ? '' : headers[i];
|
|
|
+ let find = state.fieldList.find((k: any) => k.name == header);
|
|
|
+ if (find) {
|
|
|
+ state.excelForm.importFields[i + 1] = find.cname;
|
|
|
+ } else {
|
|
|
+ state.excelForm.importFields[i + 1] = "";
|
|
|
+ }
|
|
|
+ tmpHeaders.push({
|
|
|
+ field: `f${i}`,
|
|
|
+ name: header
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+ state.excelForm.columns = state.initColumnList.concat(tmpHeaders)
|
|
|
+ // console.log(state.excelForm.columns)
|
|
|
+
|
|
|
+ let tmpContents: any[] = []
|
|
|
+ for (let i = state.excelForm.rowContentIndex; i < rowList.length; i++) {
|
|
|
+ let dl = rowList[i];
|
|
|
+ let rowItem = {}
|
|
|
+ dl.forEach((rowValue: any, columnIdx: number) => {
|
|
|
+ rowItem[`f${columnIdx}`] = rowValue
|
|
|
+ })
|
|
|
+ tmpContents.push(rowItem)
|
|
|
+ }
|
|
|
+ state.excelForm.data = tmpContents;
|
|
|
+ state.excelForm.loading = false;
|
|
|
+ excelRef.value?.reloadData(state.excelForm.data)
|
|
|
+ // data.exportTableData = rowObj;
|
|
|
+ Msg.hideLoading();
|
|
|
+ };
|
|
|
+ // 已二进制的形式读取文件
|
|
|
+ reader.readAsBinaryString(unref(files)[0]);
|
|
|
+ // 导入标识改为true
|
|
|
+ // data.exportSign = true;
|
|
|
+}
|
|
|
+
|
|
|
+const handleClick = (sheet: string) => {
|
|
|
+ parseExcel(sheet)
|
|
|
+ // handleRow("setHeader",state.excelForm.data[0],0)
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+onChange = ((files) => {
|
|
|
+ parseExcel();
|
|
|
+})
|
|
|
+*/
|
|
|
+
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ open
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.custom-tabs-label {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ align-content: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.model-field-select-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ display: inline-flex;
|
|
|
+}
|
|
|
+
|
|
|
+.sheet-item {
|
|
|
+ width: 135px;
|
|
|
+ border-right: 2px solid #ddd;
|
|
|
+ border-bottom: 2px solid #ddd;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.model-field-select-header {
|
|
|
+ width: 135px;
|
|
|
+ height: 36px;
|
|
|
+ //overflow-x: auto;
|
|
|
+ //display: inline-flex;
|
|
|
+ //flex-wrap: wrap;
|
|
|
+
|
|
|
+ :deep(.el-select-v2__wrapper) {
|
|
|
+ border: 1px solid #eee !important;
|
|
|
+ border-radius: 0px !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.vxe-loading) {
|
|
|
+ display: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.pd0 {
|
|
|
+ :deep(.el-dialog__body) {
|
|
|
+ padding: 0 !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.vxe-table--tooltip-wrapper{
|
|
|
+ z-index: 10000 !important;
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|