added excel demo
This commit is contained in:
parent
b5b8970ab5
commit
7e6bb6998e
10
README.md
10
README.md
@ -1,6 +1,6 @@
|
|||||||
# vue3-element-admin
|
# vue3-element-admin
|
||||||
|
|
||||||
这个模板使用了最新的 vue3 和 element-plus UI框架,vite 构建工具、pinia 状态管理、vue-router 路由管理、mockjs 数据模拟。功能从Vue Element Admin 移植而来,详细使用可以参考[该文档](https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html)。
|
这个模板使用了最新的 vue3 和 element-plus UI 框架,vite 构建工具、pinia 状态管理、vue-router 路由管理、mockjs 数据模拟。功能从 Vue Element Admin 移植而来,详细使用可以参考[该文档](https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html)。
|
||||||
|
|
||||||
# 在线示例
|
# 在线示例
|
||||||
|
|
||||||
@ -8,13 +8,13 @@
|
|||||||
|
|
||||||
[element plus](https://element-plus.midfar.com/)
|
[element plus](https://element-plus.midfar.com/)
|
||||||
|
|
||||||
## 推荐的IDE工具和插件
|
## 推荐的 IDE 工具和插件
|
||||||
|
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (禁用 Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (禁用 Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
## Vite构建工具配置
|
## Vite 构建工具配置
|
||||||
|
|
||||||
参考 [Vite配置](https://vitejs.dev/config/).
|
参考 [Vite 配置](https://vitejs.dev/config/).
|
||||||
|
|
||||||
## 安装依赖
|
## 安装依赖
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ npm run lint
|
|||||||
|
|
||||||
如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
|
如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
|
||||||
|
|
||||||
<img src="https://vue3-element-admin.midfar.com/midfar_pay.jpg" alt="捐赠" style="zoom: 20%;" />
|
wechat: midfar-sun
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -77,6 +77,7 @@ declare module '@vue/runtime-core' {
|
|||||||
Sticky: typeof import('./src/components/Sticky/index.vue')['default']
|
Sticky: typeof import('./src/components/Sticky/index.vue')['default']
|
||||||
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
||||||
Tinymce: typeof import('./src/components/Tinymce/index.vue')['default']
|
Tinymce: typeof import('./src/components/Tinymce/index.vue')['default']
|
||||||
|
UploadExcel: typeof import('./src/components/UploadExcel/index.vue')['default']
|
||||||
VueCountTo: typeof import('./src/components/vue-count-to/vue-countTo.vue')['default']
|
VueCountTo: typeof import('./src/components/vue-count-to/vue-countTo.vue')['default']
|
||||||
}
|
}
|
||||||
export interface ComponentCustomProperties {
|
export interface ComponentCustomProperties {
|
||||||
|
147
src/components/UploadExcel/index.vue
Normal file
147
src/components/UploadExcel/index.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
|
||||||
|
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
|
||||||
|
Drop excel file here or
|
||||||
|
<el-button :loading="loading" style="margin-left:16px;" size="small" type="primary" @click="handleUpload">
|
||||||
|
Browse
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
beforeUpload: {
|
||||||
|
type: Function,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
onSuccess: {
|
||||||
|
type: Function,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
excelData: {
|
||||||
|
header: null,
|
||||||
|
results: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateData({ header, results }) {
|
||||||
|
this.excelData.header = header;
|
||||||
|
this.excelData.results = results;
|
||||||
|
this.onSuccess && this.onSuccess(this.excelData);
|
||||||
|
},
|
||||||
|
handleDrop(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.loading) return;
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
if (files.length !== 1) {
|
||||||
|
ElMessage.error('Only support uploading one file!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rawFile = files[0]; // only use files[0]
|
||||||
|
|
||||||
|
if (!this.isExcel(rawFile)) {
|
||||||
|
ElMessage.error('Only supports upload .xlsx, .xls, .csv suffix files');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.upload(rawFile);
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
handleDragover(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
},
|
||||||
|
handleUpload() {
|
||||||
|
this.$refs['excel-upload-input'].click();
|
||||||
|
},
|
||||||
|
handleClick(e) {
|
||||||
|
const files = e.target.files;
|
||||||
|
const rawFile = files[0]; // only use files[0]
|
||||||
|
if (!rawFile) return;
|
||||||
|
this.upload(rawFile);
|
||||||
|
},
|
||||||
|
upload(rawFile) {
|
||||||
|
this.$refs['excel-upload-input'].value = null; // fix can't select the same excel
|
||||||
|
|
||||||
|
if (!this.beforeUpload) {
|
||||||
|
this.readerData(rawFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const before = this.beforeUpload(rawFile);
|
||||||
|
if (before) {
|
||||||
|
this.readerData(rawFile);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
readerData(rawFile) {
|
||||||
|
this.loading = true;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
const data = e.target.result;
|
||||||
|
const workbook = XLSX.read(data, { type: 'array' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const header = this.getHeaderRow(worksheet);
|
||||||
|
const results = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
this.generateData({ header, results });
|
||||||
|
this.loading = false;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(rawFile);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getHeaderRow(sheet) {
|
||||||
|
const headers = [];
|
||||||
|
const range = XLSX.utils.decode_range(sheet['!ref']);
|
||||||
|
let C;
|
||||||
|
const R = range.s.r;
|
||||||
|
/* start in the first row */
|
||||||
|
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
|
||||||
|
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];
|
||||||
|
/* find the cell in the first row */
|
||||||
|
let hdr = 'UNKNOWN ' + C; // <-- replace with your desired default
|
||||||
|
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);
|
||||||
|
headers.push(hdr);
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
isExcel(file) {
|
||||||
|
return /\.(xlsx|xls|csv)$/.test(file.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.excel-upload-input{
|
||||||
|
display: none;
|
||||||
|
z-index: -9999;
|
||||||
|
}
|
||||||
|
.drop{
|
||||||
|
border: 2px dashed #bbb;
|
||||||
|
width: 600px;
|
||||||
|
height: 160px;
|
||||||
|
line-height: 160px;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 24px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
color: #bbb;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
@ -7,8 +7,11 @@
|
|||||||
:class="{ 'submenu-title-noDropdown': !isNest }">
|
:class="{ 'submenu-title-noDropdown': !isNest }">
|
||||||
|
|
||||||
<!-- <item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" /> -->
|
<!-- <item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" /> -->
|
||||||
<svg-icon v-if="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
|
<template v-if="get2MetaIconPath(onlyOneChild, item)">
|
||||||
:icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
|
<svg-icon v-if="typeof get2MetaIconPath(onlyOneChild, item) === 'string'"
|
||||||
|
:icon-class="get2MetaIconPath(onlyOneChild, item)" />
|
||||||
|
<component v-else :is="get2MetaIconPath(onlyOneChild, item)" class="svg-icon el-svg-icon" />
|
||||||
|
</template>
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="text text-one">{{ onlyOneChild.meta.title }}</span>
|
<span class="text text-one">{{ onlyOneChild.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
@ -19,7 +22,11 @@
|
|||||||
<el-sub-menu class="left-sub-menu" v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
|
<el-sub-menu class="left-sub-menu" v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
|
||||||
<template v-if="item.meta" #title>
|
<template v-if="item.meta" #title>
|
||||||
<!-- <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> -->
|
<!-- <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> -->
|
||||||
<svg-icon :icon-class="(item.meta && item.meta.icon) || 'sub-el-icon'" />
|
<template v-if="getMetaIconPath(item)">
|
||||||
|
<svg-icon v-if="typeof getMetaIconPath(item) === 'string'" :icon-class="getMetaIconPath(item)" />
|
||||||
|
<component v-else :is="getMetaIconPath(item)" class="svg-icon el-svg-icon" />
|
||||||
|
</template>
|
||||||
|
<svg-icon v-else icon-class="sub-el-icon" />
|
||||||
<span class="text text-two">{{ item.meta.title }}</span>
|
<span class="text text-two">{{ item.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child"
|
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child"
|
||||||
@ -73,6 +80,12 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getMetaIconPath(item) {
|
||||||
|
return item.meta && item.meta.icon;
|
||||||
|
},
|
||||||
|
get2MetaIconPath(onlyOneChild, item) {
|
||||||
|
return onlyOneChild.meta.icon || (item.meta && item.meta.icon);
|
||||||
|
},
|
||||||
hasOneShowingChild(children = [], parent) {
|
hasOneShowingChild(children = [], parent) {
|
||||||
const showingChildren = children.filter(item => {
|
const showingChildren = children.filter(item => {
|
||||||
if (this.isItemHidden) {
|
if (this.isItemHidden) {
|
||||||
@ -119,4 +132,12 @@ export default defineComponent({
|
|||||||
.left-sub-menu :deep(.el-sub-menu__title) {
|
.left-sub-menu :deep(.el-sub-menu__title) {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { markRaw } from 'vue';
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'; // createWebHashHistory, createWebHistory
|
import { createRouter, createWebHashHistory } from 'vue-router'; // createWebHashHistory, createWebHistory
|
||||||
import type { Router, RouteRecordRaw, RouteComponent } from 'vue-router';
|
import type { Router, RouteRecordRaw, RouteComponent } from 'vue-router';
|
||||||
|
import { Help as IconHelp } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
const Layout = ():RouteComponent => import('@/layout/index.vue');
|
const Layout = ():RouteComponent => import('@/layout/index.vue');
|
||||||
@ -178,7 +180,7 @@ export const asyncRoutes:RouteRecordRaw[] = [
|
|||||||
name: 'Example',
|
name: 'Example',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Example',
|
title: 'Example',
|
||||||
icon: 'el-icon-s-help'
|
icon: markRaw(IconHelp)
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -253,42 +255,42 @@ export const asyncRoutes:RouteRecordRaw[] = [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
{
|
||||||
// path: '/excel',
|
path: '/excel',
|
||||||
// component: Layout,
|
component: Layout,
|
||||||
// redirect: '/excel/export-excel',
|
redirect: '/excel/export-excel',
|
||||||
// name: 'Excel',
|
name: 'Excel',
|
||||||
// meta: {
|
meta: {
|
||||||
// title: 'Excel',
|
title: 'Excel',
|
||||||
// icon: 'excel'
|
icon: 'excel'
|
||||||
// },
|
},
|
||||||
// children: [
|
children: [
|
||||||
// {
|
{
|
||||||
// path: 'export-excel',
|
path: 'export-excel',
|
||||||
// component: () => import('@/views/excel/export-excel'),
|
component: () => import('@/views/excel/export-excel.vue'),
|
||||||
// name: 'ExportExcel',
|
name: 'ExportExcel',
|
||||||
// meta: { title: 'Export Excel' }
|
meta: { title: 'Export Excel' }
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'export-selected-excel',
|
path: 'export-selected-excel',
|
||||||
// component: () => import('@/views/excel/select-excel'),
|
component: () => import('@/views/excel/select-excel.vue'),
|
||||||
// name: 'SelectExcel',
|
name: 'SelectExcel',
|
||||||
// meta: { title: 'Export Selected' }
|
meta: { title: 'Export Selected' }
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'export-merge-header',
|
path: 'export-merge-header',
|
||||||
// component: () => import('@/views/excel/merge-header'),
|
component: () => import('@/views/excel/merge-header.vue'),
|
||||||
// name: 'MergeHeader',
|
name: 'MergeHeader',
|
||||||
// meta: { title: 'Merge Header' }
|
meta: { title: 'Merge Header' }
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'upload-excel',
|
path: 'upload-excel',
|
||||||
// component: () => import('@/views/excel/upload-excel'),
|
component: () => import('@/views/excel/upload-excel.vue'),
|
||||||
// name: 'UploadExcel',
|
name: 'UploadExcel',
|
||||||
// meta: { title: 'Upload Excel' }
|
meta: { title: 'Upload Excel' }
|
||||||
// }
|
}
|
||||||
// ]
|
]
|
||||||
// },
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/zip',
|
path: '/zip',
|
||||||
|
36
src/views/excel/components/AutoWidthOption.vue
Normal file
36
src/views/excel/components/AutoWidthOption.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div style="display:inline-block;">
|
||||||
|
<label class="radio-label">Cell Auto-Width: </label>
|
||||||
|
<el-radio-group v-model="autoWidth">
|
||||||
|
<el-radio :label="true" border>
|
||||||
|
True
|
||||||
|
</el-radio>
|
||||||
|
<el-radio :label="false" border>
|
||||||
|
False
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
autoWidth: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
41
src/views/excel/components/BookTypeOption.vue
Normal file
41
src/views/excel/components/BookTypeOption.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div style="display:inline-block;">
|
||||||
|
<label class="radio-label">Book Type: </label>
|
||||||
|
<el-select v-model="bookType" style="width:120px;">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item"
|
||||||
|
:label="item"
|
||||||
|
:value="item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: 'xlsx'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
options: ['xlsx', 'csv', 'txt']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
bookType: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
35
src/views/excel/components/FilenameOption.vue
Normal file
35
src/views/excel/components/FilenameOption.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div style="display:inline-block;">
|
||||||
|
<label class="radio-label" style="padding-left:0;">Filename: </label>
|
||||||
|
<el-input v-model="filename" placeholder="Please enter the file name (default excel-list)" style="width:345px;" :prefix-icon="IconDocument" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, markRaw } from 'vue';
|
||||||
|
import { Document as IconDocument } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
IconDocument: markRaw(IconDocument)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filename: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
120
src/views/excel/export-excel.vue
Normal file
120
src/views/excel/export-excel.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
|
||||||
|
<div style="margin: 0 0 20px 0;">
|
||||||
|
<FilenameOption v-model="filename" />
|
||||||
|
<AutoWidthOption v-model="autoWidth" />
|
||||||
|
<BookTypeOption v-model="bookType" />
|
||||||
|
<el-button :loading="downloadLoading" style="margin:0 0 0 20px;" type="primary" :icon="IconDocument" @click="handleDownload">
|
||||||
|
Export Excel
|
||||||
|
</el-button>
|
||||||
|
<a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
|
||||||
|
<el-tag type="info" size="large">Documentation</el-tag>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table v-loading="listLoading" :data="list" element-loading-text="Loading..." border fit highlight-current-row>
|
||||||
|
<el-table-column align="center" label="Id" width="95">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.$index }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Title">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.row.title }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Author" width="110" align="center">
|
||||||
|
<template v-slot="scope">
|
||||||
|
<el-tag>{{ scope.row.author }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Readings" width="115" align="center">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.row.pageviews }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="Date" width="220">
|
||||||
|
<template v-slot="scope">
|
||||||
|
<el-icon><IconTimer /></el-icon>
|
||||||
|
<span>{{ parseTime(scope.row.timestamp, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, markRaw } from 'vue';
|
||||||
|
import { fetchList } from '@/api/article';
|
||||||
|
import { parseTime } from '@/utils';
|
||||||
|
// options components
|
||||||
|
import FilenameOption from './components/FilenameOption';
|
||||||
|
import AutoWidthOption from './components/AutoWidthOption';
|
||||||
|
import BookTypeOption from './components/BookTypeOption';
|
||||||
|
import { Document as IconDocument, Timer as IconTimer } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ExportExcel',
|
||||||
|
components: { FilenameOption, AutoWidthOption, BookTypeOption, IconTimer },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
IconDocument: markRaw(IconDocument),
|
||||||
|
list: null,
|
||||||
|
listLoading: true,
|
||||||
|
downloadLoading: false,
|
||||||
|
filename: '',
|
||||||
|
autoWidth: true,
|
||||||
|
bookType: 'xlsx'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
parseTime,
|
||||||
|
fetchData() {
|
||||||
|
this.listLoading = true;
|
||||||
|
fetchList().then(response => {
|
||||||
|
this.list = response.data.items;
|
||||||
|
this.listLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDownload() {
|
||||||
|
this.downloadLoading = true;
|
||||||
|
import('@/vendor/Export2Excel').then(excel => {
|
||||||
|
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date'];
|
||||||
|
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'];
|
||||||
|
const list = this.list;
|
||||||
|
const data = this.formatJson(filterVal, list);
|
||||||
|
excel.export_json_to_excel({
|
||||||
|
header: tHeader,
|
||||||
|
data,
|
||||||
|
filename: this.filename,
|
||||||
|
autoWidth: this.autoWidth,
|
||||||
|
bookType: this.bookType
|
||||||
|
});
|
||||||
|
this.downloadLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatJson(filterVal, jsonData) {
|
||||||
|
return jsonData.map(v => filterVal.map(j => {
|
||||||
|
if (j === 'timestamp') {
|
||||||
|
return parseTime(v[j]);
|
||||||
|
} else {
|
||||||
|
return v[j];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.radio-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 40px;
|
||||||
|
padding: 0 12px 0 30px;
|
||||||
|
}
|
||||||
|
</style>
|
106
src/views/excel/merge-header.vue
Normal file
106
src/views/excel/merge-header.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
|
||||||
|
<el-button :loading="downloadLoading" style="margin-bottom:20px" type="primary" :icon="IconDocument" @click="handleDownload">Export</el-button>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
ref="multipleTable"
|
||||||
|
v-loading="listLoading"
|
||||||
|
:data="list"
|
||||||
|
element-loading-text="Loading"
|
||||||
|
border
|
||||||
|
fit
|
||||||
|
highlight-current-row
|
||||||
|
>
|
||||||
|
<el-table-column align="center" label="Id" width="95">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.$index }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Main Information" align="center">
|
||||||
|
<el-table-column label="Title">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.row.title }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Author" width="110" align="center">
|
||||||
|
<template v-slot="scope">
|
||||||
|
<el-tag>{{ scope.row.author }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Readings" width="115" align="center">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.row.pageviews }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="Date" width="220">
|
||||||
|
<template v-slot="scope">
|
||||||
|
<el-icon><IconTimer /></el-icon>
|
||||||
|
<span>{{ parseTime(scope.row.timestamp, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, markRaw } from 'vue';
|
||||||
|
import { fetchList } from '@/api/article';
|
||||||
|
import { parseTime } from '@/utils';
|
||||||
|
import { Document as IconDocument, Timer as IconTimer } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MergeHeader',
|
||||||
|
components: { IconTimer },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
IconDocument: markRaw(IconDocument),
|
||||||
|
list: null,
|
||||||
|
listLoading: true,
|
||||||
|
downloadLoading: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
parseTime,
|
||||||
|
fetchData() {
|
||||||
|
this.listLoading = true;
|
||||||
|
fetchList(this.listQuery).then(response => {
|
||||||
|
this.list = response.data.items;
|
||||||
|
this.listLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDownload() {
|
||||||
|
this.downloadLoading = true;
|
||||||
|
import('@/vendor/Export2Excel').then(excel => {
|
||||||
|
const multiHeader = [['Id', 'Main Information', '', '', 'Date']];
|
||||||
|
const header = ['', 'Title', 'Author', 'Readings', ''];
|
||||||
|
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'];
|
||||||
|
const list = this.list;
|
||||||
|
const data = this.formatJson(filterVal, list);
|
||||||
|
const merges = ['A1:A2', 'B1:D1', 'E1:E2'];
|
||||||
|
excel.export_json_to_excel({
|
||||||
|
multiHeader,
|
||||||
|
header,
|
||||||
|
merges,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
this.downloadLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatJson(filterVal, jsonData) {
|
||||||
|
return jsonData.map(v => filterVal.map(j => {
|
||||||
|
if (j === 'timestamp') {
|
||||||
|
return parseTime(v[j]);
|
||||||
|
} else {
|
||||||
|
return v[j];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
113
src/views/excel/select-excel.vue
Normal file
113
src/views/excel/select-excel.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div style="margin-bottom:20px">
|
||||||
|
<el-input v-model="filename" placeholder="Please enter the file name (default excel-list)" style="width:350px;" :prefix-icon="IconDocument" />
|
||||||
|
<el-button :loading="downloadLoading" type="primary" :icon="IconDocument" @click="handleDownload">
|
||||||
|
Export Selected Items
|
||||||
|
</el-button>
|
||||||
|
<a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
|
||||||
|
<el-tag type="info" size="large">Documentation</el-tag>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
ref="multipleTable"
|
||||||
|
v-loading="listLoading"
|
||||||
|
:data="list"
|
||||||
|
element-loading-text="拼命加载中"
|
||||||
|
border
|
||||||
|
fit
|
||||||
|
highlight-current-row
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" align="center" />
|
||||||
|
<el-table-column align="center" label="Id" width="95">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.$index }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Title">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.row.title }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Author" width="110" align="center">
|
||||||
|
<template v-slot="scope">
|
||||||
|
<el-tag>{{ scope.row.author }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Readings" width="115" align="center">
|
||||||
|
<template v-slot="scope">
|
||||||
|
{{ scope.row.pageviews }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="PDate" width="220">
|
||||||
|
<template v-slot="scope">
|
||||||
|
<el-icon><IconTimer /></el-icon>
|
||||||
|
<span>{{ scope.row.display_time }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, markRaw } from 'vue';
|
||||||
|
import { fetchList } from '@/api/article';
|
||||||
|
import { Document as IconDocument, Timer as IconTimer } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SelectExcel',
|
||||||
|
components: { IconTimer },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
IconDocument: markRaw(IconDocument),
|
||||||
|
list: null,
|
||||||
|
listLoading: true,
|
||||||
|
multipleSelection: [],
|
||||||
|
downloadLoading: false,
|
||||||
|
filename: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
this.listLoading = true;
|
||||||
|
fetchList(this.listQuery).then(response => {
|
||||||
|
this.list = response.data.items;
|
||||||
|
this.listLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSelectionChange(val) {
|
||||||
|
this.multipleSelection = val;
|
||||||
|
},
|
||||||
|
handleDownload() {
|
||||||
|
if (this.multipleSelection.length) {
|
||||||
|
this.downloadLoading = true;
|
||||||
|
import('@/vendor/Export2Excel').then(excel => {
|
||||||
|
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date'];
|
||||||
|
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'];
|
||||||
|
const list = this.multipleSelection;
|
||||||
|
const data = this.formatJson(filterVal, list);
|
||||||
|
excel.export_json_to_excel({
|
||||||
|
header: tHeader,
|
||||||
|
data,
|
||||||
|
filename: this.filename
|
||||||
|
});
|
||||||
|
this.$refs.multipleTable.clearSelection();
|
||||||
|
this.downloadLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
message: 'Please select at least one item',
|
||||||
|
type: 'warning'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatJson(filterVal, jsonData) {
|
||||||
|
return jsonData.map(v => filterVal.map(j => v[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
43
src/views/excel/upload-excel.vue
Normal file
43
src/views/excel/upload-excel.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />
|
||||||
|
<el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;">
|
||||||
|
<el-table-column v-for="item of tableHeader" :key="item" :prop="item" :label="item" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import UploadExcelComponent from '@/components/UploadExcel/index.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UploadExcel',
|
||||||
|
components: { UploadExcelComponent },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tableData: [],
|
||||||
|
tableHeader: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
beforeUpload(file) {
|
||||||
|
const isLt1M = file.size / 1024 / 1024 < 1;
|
||||||
|
|
||||||
|
if (isLt1M) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage({
|
||||||
|
message: 'Please do not upload files larger than 1m in size.',
|
||||||
|
type: 'warning'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleSuccess({ results, header }) {
|
||||||
|
this.tableData = results;
|
||||||
|
this.tableHeader = header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user