Parcourir la source

新增了导入导出excel,以及做了一些优化

lin-xin il y a 3 ans
Parent
commit
60a293514d

+ 1 - 0
components.d.ts

@@ -24,6 +24,7 @@ declare module '@vue/runtime-core' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElLink: typeof import('element-plus/es')['ElLink']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']

+ 61 - 0
package-lock.json

@@ -322,6 +322,11 @@
 			"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
 			"dev": true
 		},
+		"adler-32": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
+			"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="
+		},
 		"anymatch": {
 			"version": "3.1.2",
 			"resolved": "https://repo.huaweicloud.com/repository/npm/anymatch/-/anymatch-3.1.2.tgz",
@@ -381,6 +386,15 @@
 				"fill-range": "^7.0.1"
 			}
 		},
+		"cfb": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
+			"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+			"requires": {
+				"adler-32": "~1.3.0",
+				"crc-32": "~1.2.0"
+			}
+		},
 		"chokidar": {
 			"version": "3.5.3",
 			"resolved": "https://repo.huaweicloud.com/repository/npm/chokidar/-/chokidar-3.5.3.tgz",
@@ -397,6 +411,11 @@
 				"readdirp": "~3.6.0"
 			}
 		},
+		"codepage": {
+			"version": "1.15.0",
+			"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
+			"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="
+		},
 		"combined-stream": {
 			"version": "1.0.8",
 			"resolved": "https://repo.huaweicloud.com/repository/npm/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -410,6 +429,11 @@
 			"resolved": "https://repo.huaweicloud.com/repository/npm/core-js-pure/-/core-js-pure-3.24.1.tgz",
 			"integrity": "sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg=="
 		},
+		"crc-32": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+			"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="
+		},
 		"cropperjs": {
 			"version": "1.5.12",
 			"resolved": "https://repo.huaweicloud.com/repository/npm/cropperjs/-/cropperjs-1.5.12.tgz",
@@ -699,6 +723,11 @@
 				"mime-types": "^2.1.12"
 			}
 		},
+		"frac": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+			"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="
+		},
 		"fsevents": {
 			"version": "2.3.2",
 			"resolved": "https://repo.huaweicloud.com/repository/npm/fsevents/-/fsevents-2.3.2.tgz",
@@ -1024,6 +1053,14 @@
 			"resolved": "https://repo.huaweicloud.com/repository/npm/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
 			"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
 		},
+		"ssf": {
+			"version": "0.11.2",
+			"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
+			"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+			"requires": {
+				"frac": "~1.1.2"
+			}
+		},
 		"strip-literal": {
 			"version": "0.4.0",
 			"resolved": "https://repo.huaweicloud.com/repository/npm/strip-literal/-/strip-literal-0.4.0.tgz",
@@ -1232,6 +1269,30 @@
 			"resolved": "https://repo.huaweicloud.com/repository/npm/webpack-virtual-modules/-/webpack-virtual-modules-0.4.4.tgz",
 			"integrity": "sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==",
 			"dev": true
+		},
+		"wmf": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+			"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="
+		},
+		"word": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
+			"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="
+		},
+		"xlsx": {
+			"version": "0.18.5",
+			"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
+			"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+			"requires": {
+				"adler-32": "~1.3.0",
+				"cfb": "~1.2.1",
+				"codepage": "~1.15.0",
+				"crc-32": "~1.2.1",
+				"ssf": "~0.11.2",
+				"wmf": "~1.0.1",
+				"word": "~0.3.0"
+			}
 		}
 	}
 }

+ 2 - 1
package.json

@@ -17,7 +17,8 @@
 		"vue-cropperjs": "^5.0.0",
 		"vue-router": "^4.1.3",
 		"vue-schart": "^2.0.0",
-		"wangeditor": "^4.7.15"
+		"wangeditor": "^4.7.15",
+		"xlsx": "^0.18.5"
 	},
 	"devDependencies": {
 		"@vitejs/plugin-vue": "^3.0.0",

BIN
public/template.xlsx


+ 7 - 1
src/App.vue

@@ -1,7 +1,13 @@
 <template>
-	<router-view />
+    <el-config-provider :locale="zhCn">
+        <router-view />
+    </el-config-provider>
 </template>
 
+<script setup lang="ts">
+import { ElConfigProvider } from 'element-plus';
+import zhCn from 'element-plus/es/locale/lang/zh-cn';
+</script>
 <style>
 @import './assets/css/main.css';
 @import './assets/css/color-dark.css';

+ 1 - 6
src/assets/css/color-dark.css

@@ -10,12 +10,7 @@
 .plugins-tips a{
     color: #20a0ff;
 }
-.el-upload--text em {
-    color: #20a0ff;
-}
-.pure-button{
-    background: #20a0ff;
-}
+
 .tags-li.active {
     border: 1px solid #409EFF;
     background-color: #409EFF;

+ 0 - 64
src/assets/css/main.css

@@ -111,70 +111,6 @@ a {
     padding-bottom: 0;
 }
 
-/*Upload*/
-
-.pure-button {
-    width: 150px;
-    height: 40px;
-    line-height: 40px;
-    text-align: center;
-    color: #fff;
-    border-radius: 3px;
-}
-
-.g-core-image-corp-container .info-aside {
-    height: 45px;
-}
-
-.el-upload--text {
-    background-color: #fff;
-    border: 1px dashed #d9d9d9;
-    border-radius: 6px;
-    box-sizing: border-box;
-    width: 360px;
-    height: 180px;
-    text-align: center;
-    cursor: pointer;
-    position: relative;
-    overflow: hidden;
-}
-
-.el-upload--text .el-icon-upload {
-    font-size: 67px;
-    color: #97a8be;
-    margin: 40px 0 16px;
-    line-height: 50px;
-}
-
-.el-upload--text {
-    color: #97a8be;
-    font-size: 14px;
-    text-align: center;
-}
-
-.el-upload--text em {
-    font-style: normal;
-}
-
-/*VueEditor*/
-
-.ql-container {
-    min-height: 400px;
-}
-
-.ql-snow .ql-tooltip {
-    transform: translateX(117.5px) translateY(10px) !important;
-}
-
-.editor-btn {
-    margin-top: 20px;
-}
-
-/*markdown*/
-
-.v-note-wrapper .v-note-panel {
-    min-height: 500px;
-}
 
 [class*=" el-icon-"], [class^=el-icon-] {
     speak: none;

+ 153 - 136
src/components/sidebar.vue

@@ -1,53 +1,53 @@
 <template>
-	<div class="sidebar">
-		<el-menu
-			class="sidebar-el-menu"
-			:default-active="onRoutes"
-			:collapse="sidebar.collapse"
-			background-color="#324157"
-			text-color="#bfcbd9"
-			active-text-color="#20a0ff"
-			unique-opened
-			router
-		>
-			<template v-for="item in items">
-				<template v-if="item.subs">
-					<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
-						<template #title>
-							<el-icon>
-								<component :is="item.icon"></component>
-							</el-icon>
-							<span>{{ item.title }}</span>
-						</template>
-						<template v-for="subItem in item.subs">
-							<el-sub-menu
-								v-if="subItem.subs"
-								:index="subItem.index"
-								:key="subItem.index"
-								v-permiss="item.permiss"
-							>
-								<template #title>{{ subItem.title }}</template>
-								<el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
-									{{ threeItem.title }}
-								</el-menu-item>
-							</el-sub-menu>
-							<el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
-								{{ subItem.title }}
-							</el-menu-item>
-						</template>
-					</el-sub-menu>
-				</template>
-				<template v-else>
-					<el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
-						<el-icon>
-							<component :is="item.icon"></component>
-						</el-icon>
-						<template #title>{{ item.title }}</template>
-					</el-menu-item>
-				</template>
-			</template>
-		</el-menu>
-	</div>
+    <div class="sidebar">
+        <el-menu
+            class="sidebar-el-menu"
+            :default-active="onRoutes"
+            :collapse="sidebar.collapse"
+            background-color="#324157"
+            text-color="#bfcbd9"
+            active-text-color="#20a0ff"
+            unique-opened
+            router
+        >
+            <template v-for="item in items">
+                <template v-if="item.subs">
+                    <el-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
+                        <template #title>
+                            <el-icon>
+                                <component :is="item.icon"></component>
+                            </el-icon>
+                            <span>{{ item.title }}</span>
+                        </template>
+                        <template v-for="subItem in item.subs">
+                            <el-sub-menu
+                                v-if="subItem.subs"
+                                :index="subItem.index"
+                                :key="subItem.index"
+                                v-permiss="item.permiss"
+                            >
+                                <template #title>{{ subItem.title }}</template>
+                                <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
+                                    {{ threeItem.title }}
+                                </el-menu-item>
+                            </el-sub-menu>
+                            <el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
+                                {{ subItem.title }}
+                            </el-menu-item>
+                        </template>
+                    </el-sub-menu>
+                </template>
+                <template v-else>
+                    <el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
+                        <el-icon>
+                            <component :is="item.icon"></component>
+                        </el-icon>
+                        <template #title>{{ item.title }}</template>
+                    </el-menu-item>
+                </template>
+            </template>
+        </el-menu>
+    </div>
 </template>
 
 <script setup lang="ts">
@@ -56,88 +56,105 @@ import { useSidebarStore } from '../store/sidebar';
 import { useRoute } from 'vue-router';
 
 const items = [
-	{
-		icon: 'Odometer',
-		index: '/dashboard',
-		title: '系统首页',
-		permiss: '1'
-	},
-	{
-		icon: 'Calendar',
-		index: '/table',
-		title: '基础表格',
-		permiss: '2'
-	},
-	{
-		icon: 'DocumentCopy',
-		index: '/tabs',
-		title: 'tab选项卡',
-		permiss: '3'
-	},
-	{
-		icon: 'Edit',
-		index: '3',
-		title: '表单相关',
-		permiss: '4',
-		subs: [
-			{
-				index: '/form',
-				title: '基本表单',
-				permiss: '5'
-			},
-			{
-				index: '/upload',
-				title: '文件上传',
-				permiss: '6'
-			},
-			{
-				index: '4',
-				title: '三级菜单',
-				permiss: '7',
-				subs: [
-					{
-						index: '/editor',
-						title: '富文本编辑器',
-						permiss: '8'
-					},
-					{
-						index: '/markdown',
-						title: 'markdown编辑器',
-						permiss: '9'
-					}
-				]
-			}
-		]
-	},
-	{
-		icon: 'Setting',
-		index: '/icon',
-		title: '自定义图标',
-		permiss: '10'
-	},
-	{
-		icon: 'PieChart',
-		index: '/charts',
-		title: 'schart图表',
-		permiss: '11'
-	},
-	{
-		icon: 'Warning',
-		index: '/permission',
-		title: '权限管理',
-		permiss: '13'
-	},
-	{
-		icon: 'CoffeeCup',
-		index: '/donate',
-		title: '支持作者',
-		permiss: '14'
-	}
+    {
+        icon: 'Odometer',
+        index: '/dashboard',
+        title: '系统首页',
+        permiss: '1',
+    },
+    {
+        icon: 'Calendar',
+        index: '1',
+        title: '表格相关',
+        permiss: '2',
+        subs: [
+            {
+                index: '/table',
+                title: '常用表格',
+                permiss: '2',
+            },
+            {
+                index: '/import',
+                title: '导入Excel',
+                permiss: '2',
+            },
+            {
+                index: '/export',
+                title: '导出Excel',
+                permiss: '2',
+            },
+        ],
+    },
+    {
+        icon: 'DocumentCopy',
+        index: '/tabs',
+        title: 'tab选项卡',
+        permiss: '3',
+    },
+    {
+        icon: 'Edit',
+        index: '3',
+        title: '表单相关',
+        permiss: '4',
+        subs: [
+            {
+                index: '/form',
+                title: '基本表单',
+                permiss: '5',
+            },
+            {
+                index: '/upload',
+                title: '文件上传',
+                permiss: '6',
+            },
+            {
+                index: '4',
+                title: '三级菜单',
+                permiss: '7',
+                subs: [
+                    {
+                        index: '/editor',
+                        title: '富文本编辑器',
+                        permiss: '8',
+                    },
+                    {
+                        index: '/markdown',
+                        title: 'markdown编辑器',
+                        permiss: '9',
+                    },
+                ],
+            },
+        ],
+    },
+    {
+        icon: 'Setting',
+        index: '/icon',
+        title: '自定义图标',
+        permiss: '10',
+    },
+    {
+        icon: 'PieChart',
+        index: '/charts',
+        title: 'schart图表',
+        permiss: '11',
+    },
+    {
+        icon: 'Warning',
+        index: '/permission',
+        title: '权限管理',
+        permiss: '13',
+    },
+    {
+        icon: 'CoffeeCup',
+        index: '/donate',
+        title: '支持作者',
+        permiss: '14',
+    },
 ];
 
 const route = useRoute();
 const onRoutes = computed(() => {
-	return route.path;
+    return route.path;
 });
 
 const sidebar = useSidebarStore();
@@ -145,20 +162,20 @@ const sidebar = useSidebarStore();
 
 <style scoped>
 .sidebar {
-	display: block;
-	position: absolute;
-	left: 0;
-	top: 70px;
-	bottom: 0;
-	overflow-y: scroll;
+    display: block;
+    position: absolute;
+    left: 0;
+    top: 70px;
+    bottom: 0;
+    overflow-y: scroll;
 }
 .sidebar::-webkit-scrollbar {
-	width: 0;
+    width: 0;
 }
 .sidebar-el-menu:not(.el-menu--collapse) {
-	width: 250px;
+    width: 250px;
 }
 .sidebar > ul {
-	height: 100%;
+    height: 100%;
 }
 </style>

+ 16 - 23
src/main.ts

@@ -1,34 +1,27 @@
-import {createApp} from 'vue'
-import { createPinia } from 'pinia'
-import ElementPlus from 'element-plus'
-import * as ElementPlusIconsVue from '@element-plus/icons-vue'
-import zhCn from 'element-plus/es/locale/lang/zh-cn'
-import App from './App.vue'
-import router from './router'
-import { usePermissStore } from './store/permiss'
-import 'element-plus/dist/index.css'
-import './assets/css/icon.css'
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
+import * as ElementPlusIconsVue from '@element-plus/icons-vue';
+import App from './App.vue';
+import router from './router';
+import { usePermissStore } from './store/permiss';
+import './assets/css/icon.css';
 
+const app = createApp(App);
+app.use(createPinia());
+app.use(router);
 
-const app = createApp(App)
-
-app.use(createPinia())
-app.use(router)
-app.use(ElementPlus, {
-    locale: zhCn,
-})
 // 注册elementplus图标
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
-    app.component(key, component)
+    app.component(key, component);
 }
 // 自定义权限指令
-const permiss = usePermissStore()
+const permiss = usePermissStore();
 app.directive('permiss', {
     mounted(el, binding) {
-        if(!permiss.key.includes(String(binding.value))){
+        if (!permiss.key.includes(String(binding.value))) {
             el['hidden'] = true;
         }
-    }
-})
+    },
+});
 
-app.mount('#app')
+app.mount('#app');

+ 105 - 73
src/router/index.ts

@@ -1,133 +1,165 @@
-import {createRouter, createWebHashHistory, RouteRecordRaw} from "vue-router";
-import { usePermissStore } from '../store/permiss'
-import Home from "../views/home.vue";
+import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
+import { usePermissStore } from '../store/permiss';
+import Home from '../views/home.vue';
 
-const routes:RouteRecordRaw[] = [
+const routes: RouteRecordRaw[] = [
     {
         path: '/',
-        redirect: '/dashboard'
-    }, {
-        path: "/",
-        name: "Home",
+        redirect: '/dashboard',
+    },
+    {
+        path: '/',
+        name: 'Home',
         component: Home,
         children: [
             {
-                path: "/dashboard",
-                name: "dashboard",
+                path: '/dashboard',
+                name: 'dashboard',
                 meta: {
                     title: '系统首页',
-                    permiss: '1'
+                    permiss: '1',
                 },
-                component: () => import ( /* webpackChunkName: "dashboard" */ "../views/dashboard.vue")
-            }, {
-                path: "/table",
-                name: "basetable",
+                component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
+            },
+            {
+                path: '/table',
+                name: 'basetable',
                 meta: {
                     title: '表格',
-                    permiss: '2'
+                    permiss: '2',
                 },
-                component: () => import ( /* webpackChunkName: "table" */ "../views/table.vue")
-            }, {
-                path: "/charts",
-                name: "basecharts",
+                component: () => import(/* webpackChunkName: "table" */ '../views/table.vue'),
+            },
+            {
+                path: '/charts',
+                name: 'basecharts',
                 meta: {
                     title: '图表',
-                    permiss: '11'
+                    permiss: '11',
                 },
-                component: () => import ( /* webpackChunkName: "charts" */ "../views/charts.vue")
-            }, {
-                path: "/form",
-                name: "baseform",
+                component: () => import(/* webpackChunkName: "charts" */ '../views/charts.vue'),
+            },
+            {
+                path: '/form',
+                name: 'baseform',
                 meta: {
                     title: '表单',
-                    permiss: '5'
+                    permiss: '5',
                 },
-                component: () => import ( /* webpackChunkName: "form" */ "../views/form.vue")
-            }, {
-                path: "/tabs",
-                name: "tabs",
+                component: () => import(/* webpackChunkName: "form" */ '../views/form.vue'),
+            },
+            {
+                path: '/tabs',
+                name: 'tabs',
                 meta: {
                     title: 'tab标签',
-                    permiss: '3'
+                    permiss: '3',
                 },
-                component: () => import ( /* webpackChunkName: "tabs" */ "../views/tabs.vue")
-            }, {
-                path: "/donate",
-                name: "donate",
+                component: () => import(/* webpackChunkName: "tabs" */ '../views/tabs.vue'),
+            },
+            {
+                path: '/donate',
+                name: 'donate',
                 meta: {
                     title: '鼓励作者',
-                    permiss: '14'
+                    permiss: '14',
                 },
-                component: () => import ( /* webpackChunkName: "donate" */ "../views/donate.vue")
-            }, {
-                path: "/permission",
-                name: "permission",
+                component: () => import(/* webpackChunkName: "donate" */ '../views/donate.vue'),
+            },
+            {
+                path: '/permission',
+                name: 'permission',
                 meta: {
                     title: '权限管理',
-                    permiss: '13'
+                    permiss: '13',
                 },
-                component: () => import ( /* webpackChunkName: "permission" */ "../views/permission.vue")
-            }, {
-                path: "/upload",
-                name: "upload",
+                component: () => import(/* webpackChunkName: "permission" */ '../views/permission.vue'),
+            },
+            {
+                path: '/upload',
+                name: 'upload',
                 meta: {
                     title: '上传插件',
-                    permiss: '6'
+                    permiss: '6',
                 },
-                component: () => import ( /* webpackChunkName: "upload" */ "../views/upload.vue")
-            }, {
-                path: "/icon",
-                name: "icon",
+                component: () => import(/* webpackChunkName: "upload" */ '../views/upload.vue'),
+            },
+            {
+                path: '/icon',
+                name: 'icon',
                 meta: {
                     title: '自定义图标',
-                    permiss: '10'
+                    permiss: '10',
                 },
-                component: () => import ( /* webpackChunkName: "icon" */ "../views/icon.vue")
-            },  {
+                component: () => import(/* webpackChunkName: "icon" */ '../views/icon.vue'),
+            },
+            {
                 path: '/user',
                 name: 'user',
                 meta: {
-                    title: '个人中心'
+                    title: '个人中心',
                 },
-                component: () => import (/* webpackChunkName: "user" */ '../views/user.vue')
-            }, {
+                component: () => import(/* webpackChunkName: "user" */ '../views/user.vue'),
+            },
+            {
                 path: '/editor',
                 name: 'editor',
                 meta: {
                     title: '富文本编辑器',
-                    permiss: '8'
+                    permiss: '8',
                 },
-                component: () => import (/* webpackChunkName: "editor" */ '../views/editor.vue')
-            }, {
+                component: () => import(/* webpackChunkName: "editor" */ '../views/editor.vue'),
+            },
+            {
                 path: '/markdown',
                 name: 'markdown',
                 meta: {
                     title: 'markdown编辑器',
-                    permiss: '9'
+                    permiss: '9',
+                },
+                component: () => import(/* webpackChunkName: "markdown" */ '../views/markdown.vue'),
+            },
+            {
+                path: '/export',
+                name: 'export',
+                meta: {
+                    title: '导出Excel',
+                    permiss: '2',
+                },
+                component: () => import(/* webpackChunkName: "export" */ '../views/export.vue'),
+            },
+            {
+                path: '/import',
+                name: 'import',
+                meta: {
+                    title: '导入Excel',
+                    permiss: '2',
                 },
-                component: () => import (/* webpackChunkName: "markdown" */ '../views/markdown.vue')
-            }
-        ]
-    }, {
-        path: "/login",
-        name: "Login",
+                component: () => import(/* webpackChunkName: "import" */ '../views/import.vue'),
+            },
+        ],
+    },
+    {
+        path: '/login',
+        name: 'Login',
         meta: {
-            title: '登录'
+            title: '登录',
         },
-        component: () => import ( /* webpackChunkName: "login" */ "../views/login.vue")
-    }, {
+        component: () => import(/* webpackChunkName: "login" */ '../views/login.vue'),
+    },
+    {
         path: '/403',
         name: '403',
         meta: {
-            title: '没有权限'
+            title: '没有权限',
         },
-        component: () => import (/* webpackChunkName: "403" */ '../views/403.vue')
+        component: () => import(/* webpackChunkName: "403" */ '../views/403.vue'),
     },
 ];
 
 const router = createRouter({
     history: createWebHashHistory(),
-    routes
+    routes,
 });
 
 router.beforeEach((to, from, next) => {
@@ -144,4 +176,4 @@ router.beforeEach((to, from, next) => {
     }
 });
 
-export default router;
+export default router;

+ 7 - 4
src/views/dashboard.vue

@@ -26,10 +26,13 @@
 						</div>
 					</template>
 					Vue
-					<el-progress :percentage="71.3" color="#42b983"></el-progress>JavaScript
-					<el-progress :percentage="24.1" color="#f1e05a"></el-progress>CSS
-					<el-progress :percentage="13.7"></el-progress>HTML
-					<el-progress :percentage="5.9" color="#f56c6c"></el-progress>
+					<el-progress :percentage="79.4" color="#42b983"></el-progress>
+					TypeScript
+					<el-progress :percentage="14" color="#f1e05a"></el-progress>
+					CSS
+					<el-progress :percentage="5.6"></el-progress>
+					HTML
+					<el-progress :percentage="1" color="#f56c6c"></el-progress>
 				</el-card>
 			</el-col>
 			<el-col :span="16">

+ 98 - 0
src/views/export.vue

@@ -0,0 +1,98 @@
+<template>
+    <div>
+        <div class="container">
+            <div class="handle-box">
+                <el-button type="primary" @click="exportXlsx">导出Excel</el-button>
+            </div>
+            <el-table :data="tableData" border class="table" header-cell-class-name="table-header">
+                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
+                <el-table-column prop="name" label="姓名"></el-table-column>
+                <el-table-column prop="sno" label="学号"></el-table-column>
+                <el-table-column prop="class" label="班级"></el-table-column>
+                <el-table-column prop="age" label="年龄"></el-table-column>
+                <el-table-column prop="sex" label="性别"></el-table-column>
+            </el-table>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts" name="export">
+import { ref } from 'vue';
+import * as XLSX from 'xlsx';
+
+interface TableItem {
+    id: number;
+    name: string;
+    sno: string;
+    class: string;
+    age: string;
+    sex: string;
+}
+
+const tableData = ref<TableItem[]>([]);
+// 获取表格数据
+const getData = () => {
+    tableData.value = [
+        {
+            id: 1,
+            name: '小明',
+            sno: 'S001',
+            class: '一班',
+            age: '10',
+            sex: '男',
+        },
+        {
+            id: 2,
+            name: '小红',
+            sno: 'S002',
+            class: '一班',
+            age: '9',
+            sex: '女',
+        },
+    ];
+};
+getData();
+
+const list = [['序号', '姓名', '学号', '班级', '年龄', '性别']];
+const exportXlsx = () => {
+    tableData.value.map((item: any, i: number) => {
+        const arr: any[] = [i + 1];
+        arr.push(...[item.name, item.sno, item.class, item.age, item.sex]);
+        list.push(arr);
+    });
+    let WorkSheet = XLSX.utils.aoa_to_sheet(list);
+    let new_workbook = XLSX.utils.book_new();
+    XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页');
+    XLSX.writeFile(new_workbook, `表格.xlsx`);
+};
+</script>
+
+<style scoped>
+.handle-box {
+    margin-bottom: 20px;
+}
+
+.handle-select {
+    width: 120px;
+}
+
+.handle-input {
+    width: 300px;
+}
+.table {
+    width: 100%;
+    font-size: 14px;
+}
+.red {
+    color: #f56c6c;
+}
+.mr10 {
+    margin-right: 10px;
+}
+.table-td-thumb {
+    display: block;
+    margin: auto;
+    width: 40px;
+    height: 40px;
+}
+</style>

+ 134 - 134
src/views/form.vue

@@ -1,66 +1,66 @@
 <template>
-	<div class="container">
-		<div class="form-box">
-			<el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
-				<el-form-item label="表单名称" prop="name">
-					<el-input v-model="form.name"></el-input>
-				</el-form-item>
-				<el-form-item label="选择器" prop="region">
-					<el-select v-model="form.region" placeholder="请选择">
-						<el-option key="bbk" label="步步高" value="bbk"></el-option>
-						<el-option key="xtc" label="小天才" value="xtc"></el-option>
-						<el-option key="imoo" label="imoo" value="imoo"></el-option>
-					</el-select>
-				</el-form-item>
-				<el-form-item label="日期时间">
-					<el-col :span="11">
-						<el-form-item prop="date1">
-							<el-date-picker
-								type="date"
-								placeholder="选择日期"
-								v-model="form.date1"
-								style="width: 100%"
-							></el-date-picker>
-						</el-form-item>
-					</el-col>
-					<el-col class="line" :span="2">-</el-col>
-					<el-col :span="11">
-						<el-form-item prop="date2">
-							<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%">
-							</el-time-picker>
-						</el-form-item>
-					</el-col>
-				</el-form-item>
-				<el-form-item label="城市级联" prop="options">
-					<el-cascader :options="options" v-model="form.options"></el-cascader>
-				</el-form-item>
-				<el-form-item label="选择开关" prop="delivery">
-					<el-switch v-model="form.delivery"></el-switch>
-				</el-form-item>
-				<el-form-item label="多选框" prop="type">
-					<el-checkbox-group v-model="form.type">
-						<el-checkbox label="步步高" name="type"></el-checkbox>
-						<el-checkbox label="小天才" name="type"></el-checkbox>
-						<el-checkbox label="imoo" name="type"></el-checkbox>
-					</el-checkbox-group>
-				</el-form-item>
-				<el-form-item label="单选框" prop="resource">
-					<el-radio-group v-model="form.resource">
-						<el-radio label="步步高"></el-radio>
-						<el-radio label="小天才"></el-radio>
-						<el-radio label="imoo"></el-radio>
-					</el-radio-group>
-				</el-form-item>
-				<el-form-item label="文本框" prop="desc">
-					<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
-				</el-form-item>
-				<el-form-item>
-					<el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
-					<el-button @click="onReset(formRef)">重置表单</el-button>
-				</el-form-item>
-			</el-form>
-		</div>
-	</div>
+    <div class="container">
+        <div class="form-box">
+            <el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
+                <el-form-item label="表单名称" prop="name">
+                    <el-input v-model="form.name"></el-input>
+                </el-form-item>
+                <el-form-item label="选择器" prop="region">
+                    <el-select v-model="form.region" placeholder="请选择">
+                        <el-option key="小明" label="小明" value="小明"></el-option>
+                        <el-option key="小红" label="小红" value="小红"></el-option>
+                        <el-option key="小白" label="小白" value="小白"></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="日期时间">
+                    <el-col :span="11">
+                        <el-form-item prop="date1">
+                            <el-date-picker
+                                type="date"
+                                placeholder="选择日期"
+                                v-model="form.date1"
+                                style="width: 100%"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </el-col>
+                    <el-col class="line" :span="2">-</el-col>
+                    <el-col :span="11">
+                        <el-form-item prop="date2">
+                            <el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%">
+                            </el-time-picker>
+                        </el-form-item>
+                    </el-col>
+                </el-form-item>
+                <el-form-item label="城市级联" prop="options">
+                    <el-cascader :options="options" v-model="form.options"></el-cascader>
+                </el-form-item>
+                <el-form-item label="选择开关" prop="delivery">
+                    <el-switch v-model="form.delivery"></el-switch>
+                </el-form-item>
+                <el-form-item label="多选框" prop="type">
+                    <el-checkbox-group v-model="form.type">
+                        <el-checkbox label="小明" name="type"></el-checkbox>
+                        <el-checkbox label="小红" name="type"></el-checkbox>
+                        <el-checkbox label="小白" name="type"></el-checkbox>
+                    </el-checkbox-group>
+                </el-form-item>
+                <el-form-item label="单选框" prop="resource">
+                    <el-radio-group v-model="form.resource">
+                        <el-radio label="小明"></el-radio>
+                        <el-radio label="小红"></el-radio>
+                        <el-radio label="小白"></el-radio>
+                    </el-radio-group>
+                </el-form-item>
+                <el-form-item label="文本框" prop="desc">
+                    <el-input type="textarea" rows="5" v-model="form.desc"></el-input>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
+                    <el-button @click="onReset(formRef)">重置表单</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+    </div>
 </template>
 
 <script setup lang="ts" name="baseform">
@@ -69,88 +69,88 @@ import { ElMessage } from 'element-plus';
 import type { FormInstance, FormRules } from 'element-plus';
 
 const options = [
-	{
-		value: 'guangdong',
-		label: '广东省',
-		children: [
-			{
-				value: 'guangzhou',
-				label: '广州市',
-				children: [
-					{
-						value: 'tianhe',
-						label: '天河区'
-					},
-					{
-						value: 'haizhu',
-						label: '海珠区'
-					}
-				]
-			},
-			{
-				value: 'dongguan',
-				label: '东莞市',
-				children: [
-					{
-						value: 'changan',
-						label: '长安镇'
-					},
-					{
-						value: 'humen',
-						label: '虎门镇'
-					}
-				]
-			}
-		]
-	},
-	{
-		value: 'hunan',
-		label: '湖南省',
-		children: [
-			{
-				value: 'changsha',
-				label: '长沙市',
-				children: [
-					{
-						value: 'yuelu',
-						label: '岳麓区'
-					}
-				]
-			}
-		]
-	}
+    {
+        value: 'guangdong',
+        label: '广东省',
+        children: [
+            {
+                value: 'guangzhou',
+                label: '广州市',
+                children: [
+                    {
+                        value: 'tianhe',
+                        label: '天河区',
+                    },
+                    {
+                        value: 'haizhu',
+                        label: '海珠区',
+                    },
+                ],
+            },
+            {
+                value: 'dongguan',
+                label: '东莞市',
+                children: [
+                    {
+                        value: 'changan',
+                        label: '长安镇',
+                    },
+                    {
+                        value: 'humen',
+                        label: '虎门镇',
+                    },
+                ],
+            },
+        ],
+    },
+    {
+        value: 'hunan',
+        label: '湖南省',
+        children: [
+            {
+                value: 'changsha',
+                label: '长沙市',
+                children: [
+                    {
+                        value: 'yuelu',
+                        label: '岳麓区',
+                    },
+                ],
+            },
+        ],
+    },
 ];
 const rules: FormRules = {
-	name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }]
+    name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
 };
 const formRef = ref<FormInstance>();
 const form = reactive({
-	name: '',
-	region: '',
-	date1: '',
-	date2: '',
-	delivery: true,
-	type: ['步步高'],
-	resource: '小天才',
-	desc: '',
-	options: []
+    name: '',
+    region: '',
+    date1: '',
+    date2: '',
+    delivery: true,
+    type: ['小明'],
+    resource: '小红',
+    desc: '',
+    options: [],
 });
 // 提交
 const onSubmit = (formEl: FormInstance | undefined) => {
-	// 表单校验
-	if (!formEl) return;
-	formEl.validate(valid => {
-		if (valid) {
-			console.log(form);
-			ElMessage.success('提交成功!');
-		} else {
-			return false;
-		}
-	});
+    // 表单校验
+    if (!formEl) return;
+    formEl.validate((valid) => {
+        if (valid) {
+            console.log(form);
+            ElMessage.success('提交成功!');
+        } else {
+            return false;
+        }
+    });
 };
 // 重置
 const onReset = (formEl: FormInstance | undefined) => {
-	if (!formEl) return;
-	formEl.resetFields();
+    if (!formEl) return;
+    formEl.resetFields();
 };
 </script>

+ 118 - 0
src/views/import.vue

@@ -0,0 +1,118 @@
+<template>
+    <div>
+        <div class="container">
+            <div class="handle-box">
+                <el-upload
+                    action="#"
+                    :limit="1"
+                    accept=".xlsx, .xls"
+                    :show-file-list="false"
+                    :before-upload="beforeUpload"
+                    :http-request="handleMany"
+                >
+                    <el-button class="mr10" type="success">批量导入</el-button>
+                </el-upload>
+                <el-link href="/template.xlsx" target="_blank">下载模板</el-link>
+            </div>
+            <el-table :data="tableData" border class="table" header-cell-class-name="table-header">
+                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
+                <el-table-column prop="name" label="姓名"></el-table-column>
+                <el-table-column prop="sno" label="学号"></el-table-column>
+                <el-table-column prop="class" label="班级"></el-table-column>
+                <el-table-column prop="age" label="年龄"></el-table-column>
+                <el-table-column prop="sex" label="性别"></el-table-column>
+            </el-table>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts" name="import">
+import { UploadProps } from 'element-plus';
+import { ref, reactive } from 'vue';
+import * as XLSX from 'xlsx';
+
+interface TableItem {
+    id: number;
+    name: string;
+    sno: string;
+    class: string;
+    age: string;
+    sex: string;
+}
+
+const tableData = ref<TableItem[]>([]);
+// 获取表格数据
+const getData = () => {
+    tableData.value = [
+        {
+            id: 1,
+            name: '小明',
+            sno: 'S001',
+            class: '一班',
+            age: '10',
+            sex: '男',
+        },
+        {
+            id: 2,
+            name: '小红',
+            sno: 'S002',
+            class: '一班',
+            age: '9',
+            sex: '女',
+        },
+    ];
+};
+getData();
+
+const importList = ref<any>([]);
+const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
+    importList.value = await analysisExcel(rawFile);
+    return true;
+};
+const analysisExcel = (file: any) => {
+    return new Promise(function (resolve, reject) {
+        const reader = new FileReader();
+        reader.onload = function (e: any) {
+            const data = e.target.result;
+            let datajson = XLSX.read(data, {
+                type: 'binary',
+            });
+
+            const sheetName = datajson.SheetNames[0];
+            const result = XLSX.utils.sheet_to_json(datajson.Sheets[sheetName]);
+            resolve(result);
+        };
+        reader.readAsBinaryString(file);
+    });
+};
+
+const handleMany = async () => {
+    // 把数据传给服务器后获取最新列表,这里只是示例,不做请求
+    const list = importList.value.map((item: any, index: number) => {
+        return {
+            id: index,
+            name: item['姓名'],
+            sno: item['学号'],
+            class: item['班级'],
+            age: item['年龄'],
+            sex: item['性别'],
+        };
+    });
+    tableData.value.push(...list);
+};
+</script>
+
+<style scoped>
+.handle-box {
+    display: flex;
+    margin-bottom: 20px;
+}
+
+.table {
+    width: 100%;
+    font-size: 14px;
+}
+.mr10 {
+    margin-right: 10px;
+}
+</style>

+ 1 - 1
src/views/table.vue

@@ -177,7 +177,7 @@ const saveEdit = () => {
 	font-size: 14px;
 }
 .red {
-	color: #ff0000;
+	color: #F56C6C;
 }
 .mr10 {
 	margin-right: 10px;

+ 36 - 64
src/views/upload.vue

@@ -1,76 +1,48 @@
 <template>
-	<div class="container">
-		<div class="content-title">支持拖拽</div>
-		<div class="plugins-tips">
-			Element Plus自带上传组件。 访问地址:
-			<a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
-		</div>
-		<el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple :on-change="handle">
-			<el-icon class="el-icon--upload"><upload-filled /></el-icon>
-			<div class="el-upload__text">
-				将文件拖到此处,或
-				<em>点击上传</em>
-			</div>
-			<template #tip>
-				<div class="el-upload__tip">只能上传 jpg/png 文件,且不超过 500kb</div>
-			</template>
-		</el-upload>
+    <div class="container">
+        <div class="content-title">支持拖拽</div>
+        <div class="plugins-tips">
+            Element Plus自带上传组件。 访问地址:
+            <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
+        </div>
+        <el-upload
+            class="upload-demo"
+            drag
+            action="http://jsonplaceholder.typicode.com/api/posts/"
+            multiple
+            :on-change="handle"
+        >
+            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+            <div class="el-upload__text">
+                将文件拖到此处,或
+                <em>点击上传</em>
+            </div>
+        </el-upload>
 
-		<div class="content-title">支持裁剪</div>
-		<div class="plugins-tips">
-			vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。 访问地址:
-			<a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>。 示例请查看
-			<router-link to="/user">个人中心</router-link>
-		</div>
-	</div>
+        <div class="content-title">支持裁剪</div>
+        <div class="plugins-tips">
+            vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。 访问地址:
+            <a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>。 示例请查看
+            <router-link to="/user">个人中心</router-link>
+        </div>
+    </div>
 </template>
 
 <script setup lang="ts">
-const handle = (rawFile:any)=>{
-	console.log(rawFile);
-}
+const handle = (rawFile: any) => {
+    console.log(rawFile);
+};
 </script>
 
 <style scoped>
 .content-title {
-	font-weight: 400;
-	line-height: 50px;
-	margin: 10px 0;
-	font-size: 22px;
-	color: #1f2f3d;
-}
-
-.pre-img {
-	width: 100px;
-	height: 100px;
-	background: #f8f8f8;
-	border: 1px solid #eee;
-	border-radius: 5px;
-}
-.crop-demo {
-	display: flex;
-	align-items: flex-end;
-}
-.crop-demo-btn {
-	position: relative;
-	width: 100px;
-	height: 40px;
-	line-height: 40px;
-	padding: 0 20px;
-	margin-left: 30px;
-	background-color: #409eff;
-	color: #fff;
-	font-size: 14px;
-	border-radius: 4px;
-	box-sizing: border-box;
+    font-weight: 400;
+    line-height: 50px;
+    margin: 10px 0;
+    font-size: 22px;
+    color: #1f2f3d;
 }
-.crop-input {
-	position: absolute;
-	width: 100px;
-	height: 40px;
-	left: 0;
-	top: 0;
-	opacity: 0;
-	cursor: pointer;
+.upload-demo {
+    width: 360px;
 }
 </style>