feat(projects): add plugin excel export (#561)

This commit is contained in:
Ohh 2024-07-21 20:54:18 +08:00 committed by GitHub
parent 447bda2b48
commit c425822b3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 222 additions and 3 deletions

View File

@ -75,7 +75,8 @@
"vue-i18n": "9.13.1", "vue-i18n": "9.13.1",
"vue-router": "4.4.0", "vue-router": "4.4.0",
"wangeditor": "4.7.15", "wangeditor": "4.7.15",
"xgplayer": "3.0.17" "xgplayer": "3.0.17",
"xlsx": "0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@amap/amap-jsapi-types": "0.0.8", "@amap/amap-jsapi-types": "0.0.8",

View File

@ -196,7 +196,8 @@ const local: App.I18n.Schema = {
plugin_swiper: 'Swiper', plugin_swiper: 'Swiper',
plugin_video: 'Video', plugin_video: 'Video',
plugin_barcode: 'Barcode', plugin_barcode: 'Barcode',
plugin_pinyin: 'pinyin' plugin_pinyin: 'pinyin',
plugin_excel: 'Excel'
}, },
page: { page: {
login: { login: {

View File

@ -196,7 +196,8 @@ const local: App.I18n.Schema = {
plugin_swiper: 'Swiper', plugin_swiper: 'Swiper',
plugin_video: '视频', plugin_video: '视频',
plugin_barcode: '条形码', plugin_barcode: '条形码',
plugin_pinyin: '拼音' plugin_pinyin: '拼音',
plugin_excel: 'Excel'
}, },
page: { page: {
login: { login: {

View File

@ -41,6 +41,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
plugin_copy: () => import("@/views/plugin/copy/index.vue"), plugin_copy: () => import("@/views/plugin/copy/index.vue"),
plugin_editor_markdown: () => import("@/views/plugin/editor/markdown/index.vue"), plugin_editor_markdown: () => import("@/views/plugin/editor/markdown/index.vue"),
plugin_editor_quill: () => import("@/views/plugin/editor/quill/index.vue"), plugin_editor_quill: () => import("@/views/plugin/editor/quill/index.vue"),
plugin_excel: () => import("@/views/plugin/excel/index.vue"),
plugin_icon: () => import("@/views/plugin/icon/index.vue"), plugin_icon: () => import("@/views/plugin/icon/index.vue"),
plugin_map: () => import("@/views/plugin/map/index.vue"), plugin_map: () => import("@/views/plugin/map/index.vue"),
plugin_pinyin: () => import("@/views/plugin/pinyin/index.vue"), plugin_pinyin: () => import("@/views/plugin/pinyin/index.vue"),

View File

@ -413,6 +413,17 @@ export const generatedRoutes: GeneratedRoute[] = [
} }
] ]
}, },
{
name: 'plugin_excel',
path: '/plugin/excel',
component: 'view.plugin_excel',
meta: {
title: 'plugin_excel',
i18nKey: 'route.plugin_excel',
icon: 'ri:file-excel-2-line',
keepAlive: true
}
},
{ {
name: 'plugin_icon', name: 'plugin_icon',
path: '/plugin/icon', path: '/plugin/icon',

View File

@ -209,6 +209,7 @@ const routeMap: RouteMap = {
"plugin_editor": "/plugin/editor", "plugin_editor": "/plugin/editor",
"plugin_editor_markdown": "/plugin/editor/markdown", "plugin_editor_markdown": "/plugin/editor/markdown",
"plugin_editor_quill": "/plugin/editor/quill", "plugin_editor_quill": "/plugin/editor/quill",
"plugin_excel": "/plugin/excel",
"plugin_icon": "/plugin/icon", "plugin_icon": "/plugin/icon",
"plugin_map": "/plugin/map", "plugin_map": "/plugin/map",
"plugin_pinyin": "/plugin/pinyin", "plugin_pinyin": "/plugin/pinyin",

View File

@ -65,6 +65,7 @@ declare module "@elegant-router/types" {
"plugin_editor": "/plugin/editor"; "plugin_editor": "/plugin/editor";
"plugin_editor_markdown": "/plugin/editor/markdown"; "plugin_editor_markdown": "/plugin/editor/markdown";
"plugin_editor_quill": "/plugin/editor/quill"; "plugin_editor_quill": "/plugin/editor/quill";
"plugin_excel": "/plugin/excel";
"plugin_icon": "/plugin/icon"; "plugin_icon": "/plugin/icon";
"plugin_map": "/plugin/map"; "plugin_map": "/plugin/map";
"plugin_pinyin": "/plugin/pinyin"; "plugin_pinyin": "/plugin/pinyin";
@ -171,6 +172,7 @@ declare module "@elegant-router/types" {
| "plugin_copy" | "plugin_copy"
| "plugin_editor_markdown" | "plugin_editor_markdown"
| "plugin_editor_quill" | "plugin_editor_quill"
| "plugin_excel"
| "plugin_icon" | "plugin_icon"
| "plugin_map" | "plugin_map"
| "plugin_pinyin" | "plugin_pinyin"

View File

@ -0,0 +1,201 @@
<script setup lang="tsx">
import { NButton, NTag } from 'naive-ui';
import { utils, writeFile } from 'xlsx';
import type { DataTableBaseColumn } from 'naive-ui';
import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table';
import { fetchGetUserList } from '@/service/api';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { $t } from '@/locales';
const appStore = useAppStore();
const { columns, data, loading } = useTable({
apiFn: fetchGetUserList,
showTotal: true,
apiParams: {
current: 1,
size: 999,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
status: null,
userName: null,
userGender: null,
nickName: null,
userPhone: null,
userEmail: null
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64
},
{
key: 'userName',
title: $t('page.manage.user.userName'),
align: 'center',
minWidth: 100
},
{
key: 'userGender',
title: $t('page.manage.user.userGender'),
align: 'center',
width: 100,
render: row => {
if (row.userGender === null) {
return null;
}
const tagMap: Record<Api.SystemManage.UserGender, NaiveUI.ThemeColor> = {
1: 'primary',
2: 'error'
};
const label = $t(userGenderRecord[row.userGender]);
return <NTag type={tagMap[row.userGender]}>{label}</NTag>;
}
},
{
key: 'nickName',
title: $t('page.manage.user.nickName'),
align: 'center',
minWidth: 100
},
{
key: 'userPhone',
title: $t('page.manage.user.userPhone'),
align: 'center',
width: 120
},
{
key: 'userEmail',
title: $t('page.manage.user.userEmail'),
align: 'center',
minWidth: 200
},
{
key: 'status',
title: $t('page.manage.user.userStatus'),
align: 'center',
width: 100,
render: row => {
if (row.status === null) {
return null;
}
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'success',
2: 'warning'
};
const label = $t(enableStatusRecord[row.status]);
return <NTag type={tagMap[row.status]}>{label}</NTag>;
}
}
]
});
const tableTitleList = columns.value.slice(2) as DataTableBaseColumn[];
function exportExcel() {
const exportColumns = columns.value.slice(2);
const excelList = data.value.map(item => exportColumns.map(col => getTableValue(col, item)));
const titleList = exportColumns.map(col => (isTableColumnHasTitle(col) && col.title) || null);
excelList.unshift(titleList);
const workBook = utils.book_new();
const workSheet = utils.aoa_to_sheet(excelList);
workSheet['!cols'] = tableTitleList.map(item => ({
width: Math.round(Number(item.width) / 10 || 20)
}));
utils.book_append_sheet(workBook, workSheet, '用户列表');
writeFile(workBook, '用户数据.xlsx');
}
function getTableValue(
col: NaiveUI.TableColumn<NaiveUI.TableDataWithIndex<Api.SystemManage.User>>,
item: NaiveUI.TableDataWithIndex<Api.SystemManage.User>
) {
if (!isTableColumnHasKey(col)) {
return null;
}
const { key } = col;
if (key === 'operate') {
return null;
}
if (key === 'userRoles') {
return item.userRoles.map(role => role).join(',');
}
if (key === 'status') {
return (item.status && $t(enableStatusRecord[item.status])) || null;
}
if (key === 'userGender') {
return (item.userGender && $t(userGenderRecord[item.userGender])) || null;
}
return item[key];
}
function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
}
function isTableColumnHasTitle<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> & {
title: string;
} {
return Boolean((column as NaiveUI.TableColumnWithKey<T>).title);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<NCard title="Excel导出" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<NSpace align="end" wrap justify="end" class="lt-sm:w-200px">
<NButton size="small" ghost type="primary" @click="exportExcel">
<template #icon>
<icon-file-icons:microsoft-excel class="text-icon" />
</template>
导出excel
</NButton>
</NSpace>
</template>
<NDataTable
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="false"
:virtual-scroll="true"
class="sm:h-full"
/>
</NCard>
</div>
</template>
<style scoped></style>