Browse Source

充电桩上传

zuy 2 năm trước cách đây
mục cha
commit
6f6affdff2

+ 4 - 0
admin-web/package.json

@@ -12,6 +12,7 @@
 		"@element-plus/icons-vue": "^2.1.0",
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",
+		"@vueuse/core": "^10.5.0",
 		"axios": "^1.3.4",
 		"countup.js": "^2.5.0",
 		"echarts": "^5.4.1",
@@ -39,6 +40,9 @@
 		"vue-router": "^4.1.6",
 		"vue3-image-preview": "^0.2.5",
 		"vuedraggable": "^4.1.0",
+		"vxe-table": "^4.5.15",
+		"xe-utils": "^3.5.14",
+		"xlsx": "^0.18.5",
 		"yarn": "^1.22.19"
 	},
 	"devDependencies": {

+ 15 - 3
admin-web/src/views/admin/station/endpoint/index.vue

@@ -68,6 +68,11 @@
           <SvgIcon name="ele-Search"/>
           查询
         </el-button>
+
+        <el-button class="ml10" plain size="default" type="success" @click="handleUploadVisible">
+          <SvgIcon name="ele-Upload"/>
+          上传
+        </el-button>
       </el-form>
 
       <el-table
@@ -126,12 +131,12 @@
     </el-card>
   </div>
   <!--  <EquipmentInfoDialog ref="equipmentInfoDialogRef" @refresh="loadData(true)"/>-->
+  <endpoint-upload ref="endpoint_upload_ref"></endpoint-upload>
 </template>
 
 <script setup lang="ts" name="EquipmentInfoList">
 import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
 import {$body, $get} from "/@/utils/request";
-import {Msg} from "/@/utils/message";
 
 import {useRoute} from "vue-router";
 
@@ -145,14 +150,15 @@ import ExtSelect from "/@/components/form/ExtSelect.vue";
 
 import {useRouter} from "vue-router";
 const router = useRouter();
-// const EquipmentInfoDialog = defineAsyncComponent(() => import("/@/views/page/EquipmentInfoDialog.vue"));
+const EndpointUpload = defineAsyncComponent(() => import("/@/views/admin/station/endpoint/upload.vue"));
 
 //定义引用
 const queryRef = ref();
-const equipmentInfoDialogRef = ref();
+const endpoint_upload_ref = ref();
 
 //定义变量
 const state = reactive({
+  uploadVisible:false,
   formQuery: {
     stationId: ''
   },
@@ -250,6 +256,7 @@ const loadData = (refresh: boolean = false) => {
     state.tableData.loading = false;
   })
 };
+/*
 
 // 打开修改用户弹窗
 const onRowClick = (type: string, row: any) => {
@@ -266,6 +273,7 @@ const onRowDel = (row: any) => {
     })
   });
 };
+*/
 
 const handleTableSelectionChange = (selection: any) => {
   console.log("handleTableSelectionChange>>", selection)
@@ -277,6 +285,10 @@ const handleTableSortChange = (column, prop, order) => {
   // emit("on-sort-change", column)
 }
 
+const handleUploadVisible = () => {
+  endpoint_upload_ref.value.open();
+}
+
 
 //endregion
 

+ 475 - 0
admin-web/src/views/admin/station/endpoint/upload.vue

@@ -0,0 +1,475 @@
+<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:'站点名称',value:'stationName'},
+    {label:'充电桩编号',value:'shortId'},
+    {label:'车位编号',value:'parkingNo'},
+    {label:'充电桩序列号',value:'equipmentId'},
+    {label:'充电桩接口编号',value:'connectorId'},
+    {label:'状态',value:'status'},
+  ]
+})
+
+// 定义变量内容
+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(`connector/importData`, 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>