upload.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <template>
  2. <el-dialog
  3. :title="'导入号码'"
  4. v-model="state.dialog.visible"
  5. width="75%"
  6. destroy-on-close
  7. :close-on-click-modal="false"
  8. :close-on-press-escape="false"
  9. draggable
  10. class="pd0 dialog-padding-none"
  11. >
  12. <el-tabs class="demo-tabs">
  13. <el-tab-pane>
  14. <template #label>
  15. <span class="custom-tabs-label">
  16. <SvgIcon name="ele-Grid"></SvgIcon>
  17. <span>Excel导入</span>
  18. </span>
  19. </template>
  20. <el-button @click="handleOpenFile" plain type="success" class="mt5"> 请选择excel文件上传导入</el-button>
  21. <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>
  22. <!-- <ext-upload
  23. v-model="state.excelForm.file"
  24. placeholder="请选择上传文件"
  25. tips="请选择excel文件上传导入"
  26. style="width: 100%"
  27. :limit="1"
  28. mime="excel"
  29. @on-success="handleUploadFileSuccess">
  30. </ext-upload>-->
  31. </el-tab-pane>
  32. </el-tabs>
  33. <div class="w100 mt5">
  34. <div class="w100">
  35. <el-scrollbar @scroll="handleFieldHeaderScroll" ref="fieldHeaderScrollRef" :min-size="0">
  36. <div class="model-field-select-wrapper">
  37. <div class="model-field-select-header" v-for="(field,idx) in state.excelForm.columns"
  38. :key="'select_'+idx">
  39. <template v-if="idx===0">
  40. <div style="width: 135px;height: 32px;line-height:32px;border:1px solid #eee;text-align: center;">导入列选择</div>
  41. </template>
  42. <el-select-v2 v-else
  43. v-model="state.excelForm.importFields[idx]"
  44. :options="state.excelForm.selectFields"
  45. style="width: 135px;height: 36px;border:none;display: inline-block;flex-shrink: 0;margin-right: 10px;"
  46. filterable
  47. clearable>
  48. </el-select-v2>
  49. </div>
  50. </div>
  51. </el-scrollbar>
  52. </div>
  53. <vxe-table
  54. ref="excelRef"
  55. border="full"
  56. show-header-overflow
  57. :show-footer="false"
  58. :max-height="state.tableHeight"
  59. :loading="false"
  60. header-cell-class-name="task-list-header"
  61. header-align="left"
  62. empty-text=" "
  63. @cell-click="handleCellClick"
  64. :data="state.excelForm.data"
  65. @scroll="handleDataTableScroll"
  66. :row-config="{isHover:true,isCurrent:true}"
  67. :tooltip-config="{zIndex:9999}"
  68. :scroll-x="{enabled: true, gt: 100}"
  69. :scroll-y="{enabled: true, gt: 100}">
  70. <vxe-column
  71. v-for="(field,idx) in state.excelForm.columns"
  72. :key="idx"
  73. :field="field.field"
  74. :width="135"
  75. :title="field.name"
  76. show-overflow="title">
  77. <template v-if="field.field==='idx'" #default="{row,rowIndex}">
  78. <div class="w100 flex flex-align-items-center flex-justify-center hover-parent">
  79. <el-dropdown @command="handleRow($event,row,rowIndex)">
  80. <el-text type="primary" class="hover-child ml5 cursor-pointer font12"> <span>{{ rowIndex+1}}</span></el-text>
  81. <template #dropdown>
  82. <el-dropdown-item command="setHeader" style="color:var(--el-color-success)" class="font12">设为表头</el-dropdown-item>
  83. <el-dropdown-item command="delete" style="color:var(--el-color-danger)" class="font12">删除本行</el-dropdown-item>
  84. </template>
  85. </el-dropdown>
  86. </div>
  87. </template>
  88. </vxe-column>
  89. </vxe-table>
  90. </div>
  91. <template #footer>
  92. <span class="dialog-footer">
  93. <el-button @click="onClose" size="default">取 消</el-button>
  94. <el-button type="primary" @click="handleConfirm" :loading="state.importLoading" size="default">导入</el-button>
  95. </span>
  96. </template>
  97. </el-dialog>
  98. </template>
  99. <script setup lang="ts" name="EndpointImportDialog">
  100. import {reactive, onMounted, nextTick, ref, toRaw, unref} from 'vue';
  101. //定义初始变量,重置使用
  102. import {useFileDialog} from '@vueuse/core'
  103. import {$body, $post} from "/@/utils/request";
  104. import {Msg} from "/@/utils/message";
  105. import {VxeTable, VxeColumn, VxeTableInstance} from "vxe-table";
  106. import 'vxe-table/lib/style.css'
  107. import {useDebounceFn, useThrottleFn} from '@vueuse/core'
  108. import {ElScrollbar} from 'element-plus'
  109. import * as XLSX from 'xlsx'
  110. const fieldHeaderScrollRef = ref<InstanceType<typeof ElScrollbar>>();
  111. const excelRef = ref<VxeTableInstance>();
  112. const {files, open: handleOpenFile, reset, onChange:handleFileChange} = useFileDialog(
  113. {
  114. multiple: false,
  115. accept: '.xls,.xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel'
  116. })
  117. handleFileChange((files)=>{
  118. parseExcel()
  119. })
  120. const initState = () => ({
  121. dialog: {
  122. visible: false,
  123. type: '',
  124. title: '',
  125. submitTxt: '',
  126. },
  127. initColumnList: [{field: 'idx', name: '#', fixed: 'left'}],
  128. excelForm: {
  129. file: '',
  130. rowHeaderIndex: 0,
  131. rowContentIndex: 1,
  132. columns: [] as any[],
  133. sheet: '',
  134. sheets: [] as string[],
  135. data: [],
  136. loading: false,
  137. importFields: [] as string[],
  138. selectFields: []
  139. },
  140. uploadFileName: '' as String | null,
  141. importLoading: false,
  142. tableHeight: 400,
  143. fieldList:[
  144. {label:'手机号',value:'mobilePhone'},
  145. ]
  146. })
  147. // 定义变量内容
  148. const state = reactive(initState());
  149. const emit = defineEmits(['on-import-finish']);
  150. onMounted(() => {
  151. })
  152. const open = () => {
  153. state.dialog.visible = true;
  154. nextTick(() => {
  155. let clientHeight = document.body.clientHeight;
  156. let th = clientHeight - 60 - 120 - 300
  157. state.tableHeight = th;
  158. })
  159. state.excelForm.selectFields = state.fieldList.map((k: any) => {
  160. return {
  161. value: k.value,
  162. label: k.label
  163. }
  164. })
  165. }
  166. // 关闭弹窗
  167. const onClose = () => {
  168. state.dialog.visible = false;
  169. Object.assign(state, initState())
  170. reset();
  171. };
  172. const handleCellClick = (e: any) => {
  173. let {row, rowIndex, $rowIndex, column, columnIndex, $columnIndex} = e;
  174. if (columnIndex === 0) {
  175. return;
  176. }
  177. let val = state.excelForm.data[rowIndex][column.property]
  178. Msg.prompt(`修改导入的数据`, '修改',
  179. {
  180. draggable: true,
  181. type: 'primary',
  182. value: val
  183. }
  184. ).then((res: any) => {
  185. let {value, action} = res;
  186. if (action === 'confirm') {
  187. state.excelForm.data[rowIndex][column.property] = value
  188. }
  189. })
  190. }
  191. const handleConfirm = () => {
  192. let checks: Array<string> = [];
  193. let checkPass= true;
  194. state.excelForm.importFields.forEach(key => {
  195. if (key) {
  196. if (checks.includes(key)) {
  197. checkPass = false;
  198. Msg.message('表头不可重复', 'error')
  199. return false;
  200. } else {
  201. checks.push(key)
  202. }
  203. }
  204. })
  205. if(!checkPass)return;
  206. debounceImport();
  207. }
  208. const debounceImport = useThrottleFn(() => {
  209. state.importLoading = true;
  210. let fieldIndexes: Array<number> = []
  211. state.excelForm.importFields.forEach((key, idx) => {
  212. if (key) {
  213. fieldIndexes.push(idx - 1);
  214. }
  215. })
  216. let fields=state.excelForm.importFields.filter(k => !!k);
  217. let dataList : Array<any> = [];
  218. state.excelForm.data.forEach(data => {
  219. let item ={}
  220. let arr: Array<any> = []
  221. fieldIndexes.forEach((idx, i) => {
  222. item[fields[i]] = data[`f${idx}`] || ""
  223. })
  224. dataList.push(item)
  225. })
  226. let params = {
  227. fields: state.excelForm.importFields.filter(k => !!k),
  228. fieldIndexes,
  229. dataList
  230. }
  231. emit('on-import-finish',params)
  232. state.dialog.visible = false;
  233. state.importLoading = false;
  234. /* $body(`station/importStation`, params).then(() => {
  235. Msg.message(`导入成功`)
  236. state.importLoading = false;
  237. emit('on-import-finish')
  238. state.dialog.visible = false;
  239. }).catch(() => {
  240. state.importLoading = false;
  241. })*/
  242. }, 1500)
  243. //表头选择器滚动
  244. const handleFieldHeaderScroll = (ev: any) => {
  245. let {scrollLeft, scrollTop} = ev;
  246. // console.log(scrollLeft, scrollTop, ev)
  247. if (scrollLeft >= 0) {
  248. let st = excelRef.value.getScroll();
  249. const el = excelRef.value?.$el.querySelector('.vxe-table--body-wrapper')
  250. if (el) {
  251. el.scrollLeft = scrollLeft
  252. }
  253. }
  254. }
  255. //数据表格滚动
  256. const handleDataTableScroll = (ev: any) => {
  257. let {scrollLeft} = ev;
  258. // console.log(type, scrollLeft, scrollHeight)
  259. if (scrollLeft >= 0) {
  260. fieldHeaderScrollRef.value?.setScrollLeft(scrollLeft);
  261. }
  262. }
  263. const handleRow = (command: string, row: any, rowIndex: number) => {
  264. if (command === 'setHeader') {
  265. let tmpHeaders: any[] = []
  266. let tmpHeader = state.excelForm.data[rowIndex]
  267. Object.keys(tmpHeader).forEach((key, i) => {
  268. if (key.startsWith("f")) {
  269. let header = tmpHeader[key]
  270. let find = state.fieldList.find((k: any) => k.name == header);
  271. if (find) {
  272. state.excelForm.importFields[i + 1] = find.label;
  273. } else {
  274. state.excelForm.importFields[i + 1] = "";
  275. }
  276. tmpHeaders.push({
  277. field: `f${i}`,
  278. name: header
  279. });
  280. }
  281. })
  282. state.excelForm.columns = state.initColumnList.concat(tmpHeaders)
  283. state.excelForm.data.splice(rowIndex, 1)
  284. excelRef.value?.reloadData(state.excelForm.data);
  285. } else if (command === 'delete') {
  286. state.excelForm.data.splice(rowIndex, 1)
  287. excelRef.value?.reloadData(state.excelForm.data);
  288. }
  289. }
  290. const parseExcel = (sheetName: string = "") => {
  291. // console.log(sheetName, state.excelForm.sheet,files)
  292. // if(sheetName==state.excelForm.sheet&&state.excelForm.sheet){
  293. // return;
  294. // }
  295. if (!files || !files.value || !files.value[0]) {
  296. state.excelForm.columns = []
  297. excelRef.value?.reloadData([])
  298. state.uploadFileName = null;
  299. state.excelForm.sheets = []
  300. return;
  301. }
  302. // console.log(files)
  303. Msg.showLoading('解析中...')
  304. state.uploadFileName = files.value[0].name;
  305. let reader = new FileReader();
  306. reader.onload = function () {
  307. // console.log(reader)
  308. let fileData = reader.result;
  309. let wb = XLSX.read(fileData, {type: 'binary', cellDates: true});
  310. state.excelForm.sheets = wb.SheetNames;
  311. // {header:1} 取消标题列.
  312. // console.log(wb.SheetNames)
  313. let sheet = sheetName || wb.SheetNames[0];
  314. state.excelForm.sheet = sheet;
  315. let rowList: (any) = XLSX.utils.sheet_to_json(wb.Sheets[sheet], {header: 1});
  316. // console.log(rowList)
  317. let maxColumnLength = 1;
  318. rowList.forEach((row: any, idx: number) => {
  319. // console.log(row)
  320. if (idx < 10) {
  321. maxColumnLength = Math.max(maxColumnLength, row.length);
  322. }
  323. })
  324. // console.log(maxColumnLength, rowList)
  325. let tmpHeaders: Array<any> = []
  326. let headers = rowList[state.excelForm.rowHeaderIndex];
  327. for (let i = 0; i < maxColumnLength; i++) {
  328. let header = i >= headers.length ? '' : headers[i];
  329. let find = state.fieldList.find((k: any) => k.name == header);
  330. if (find) {
  331. state.excelForm.importFields[i + 1] = find.cname;
  332. } else {
  333. state.excelForm.importFields[i + 1] = "";
  334. }
  335. tmpHeaders.push({
  336. field: `f${i}`,
  337. name: header
  338. });
  339. }
  340. state.excelForm.columns = state.initColumnList.concat(tmpHeaders)
  341. // console.log(state.excelForm.columns)
  342. let tmpContents: any[] = []
  343. for (let i = state.excelForm.rowContentIndex; i < rowList.length; i++) {
  344. let dl = rowList[i];
  345. let rowItem = {}
  346. dl.forEach((rowValue: any, columnIdx: number) => {
  347. rowItem[`f${columnIdx}`] = rowValue
  348. })
  349. tmpContents.push(rowItem)
  350. }
  351. state.excelForm.data = tmpContents;
  352. state.excelForm.loading = false;
  353. excelRef.value?.reloadData(state.excelForm.data)
  354. // data.exportTableData = rowObj;
  355. Msg.hideLoading();
  356. };
  357. // 已二进制的形式读取文件
  358. reader.readAsBinaryString(unref(files)[0]);
  359. // 导入标识改为true
  360. // data.exportSign = true;
  361. }
  362. const handleClick = (sheet: string) => {
  363. parseExcel(sheet)
  364. // handleRow("setHeader",state.excelForm.data[0],0)
  365. }
  366. /*
  367. onChange = ((files) => {
  368. parseExcel();
  369. })
  370. */
  371. defineExpose({
  372. open
  373. })
  374. </script>
  375. <style scoped lang="scss">
  376. .custom-tabs-label {
  377. display: inline-flex;
  378. align-items: center;
  379. align-content: center;
  380. justify-content: space-between;
  381. }
  382. .model-field-select-wrapper {
  383. width: 100%;
  384. display: inline-flex;
  385. }
  386. .sheet-item {
  387. width: 135px;
  388. border-right: 2px solid #ddd;
  389. border-bottom: 2px solid #ddd;
  390. text-align: center;
  391. cursor: pointer;
  392. }
  393. .model-field-select-header {
  394. width: 135px;
  395. height: 36px;
  396. //overflow-x: auto;
  397. //display: inline-flex;
  398. //flex-wrap: wrap;
  399. :deep(.el-select-v2__wrapper) {
  400. border: 1px solid #eee !important;
  401. border-radius: 0px !important;
  402. }
  403. }
  404. :deep(.vxe-loading) {
  405. display: none !important;
  406. }
  407. .pd0 {
  408. :deep(.el-dialog__body) {
  409. padding: 0 !important;
  410. }
  411. }
  412. .vxe-table--tooltip-wrapper{
  413. z-index: 10000 !important;
  414. }
  415. </style>