Bladeren bron

首页拖拽穿透问题修复、改vue3等优化,辛苦萌薪大神

SW 1 jaar geleden
bovenliggende
commit
ad4db24462

+ 8 - 8
Web/src/views/home/widgets/components/about.vue

@@ -14,15 +14,15 @@
 	</el-card>
 </template>
 
-<script>
+<script lang="ts">
 export default {
-	title: '关于项目',
-	icon: 'el-icon-setting',
-	description: '点个星星支持一下',
-	data() {
-		return {};
-	},
-};
+  title: '关于项目',
+	icon: 'ele-Setting',
+	description: '点个星星支持一下'
+}
+</script>
+<script setup lang="ts" name="about">
+
 </script>
 
 <style scoped>

+ 1 - 1
Web/src/views/home/widgets/components/echarts.vue

@@ -9,7 +9,7 @@ import scEcharts from '/@/components/scEcharts/index.vue';
 
 export default {
 	title: '实时收入',
-	icon: 'el-icon-data-line',
+	icon: 'ele-DataLine',
 	description: 'Echarts组件演示',
 	components: {
 		scEcharts,

+ 0 - 8
Web/src/views/home/widgets/components/index.js

@@ -1,8 +0,0 @@
-import { markRaw } from 'vue';
-const resultComps = {};
-let requireComponent = import.meta.glob('./*.vue', { eager: true });
-Object.keys(requireComponent).forEach((fileName) => {
-	//replace(/(\.\/|\.js)/g, '')
-	resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, '$1')] = requireComponent[fileName].default;
-});
-export default markRaw(resultComps);

+ 22 - 0
Web/src/views/home/widgets/components/index.ts

@@ -0,0 +1,22 @@
+import { markRaw } from 'vue'
+// 定义组件类型
+type Component = any
+
+const resultComps: Record<string, Component> = {}
+
+// 使用 import.meta.glob 动态导入当前目录中的所有 .vue 文件,急切导入
+const requireComponent = import.meta.glob('./*.vue', { eager: true })
+console.log(requireComponent)
+
+Object.keys(requireComponent).forEach((fileName: string) => {
+    // 处理文件名,去掉开头的 './' 和结尾的文件扩展名
+    const componentName = fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
+    // 确保模块导出存在并且是默认导出
+    const componentModule = requireComponent[fileName] as { default: Component }
+    // 将组件添加到 resultComps 中,使用处理后的文件名作为键
+    resultComps[componentName] = componentModule.default
+})
+// 动态的,这里拿到title等东西是为了这里显示
+
+// 标记 resultComps 为原始对象,避免其被设为响应式
+export default markRaw(resultComps)

+ 107 - 72
Web/src/views/home/widgets/components/myapp.vue

@@ -4,28 +4,36 @@
 			<!-- <li v-for="mod in myMods" :key="mod.path" :style="{ background:mod.meta.color||'#eeeeee'}"> -->
 			<li v-for="mod in myMods" :key="mod.path">
 				<a v-if="mod.meta.type == 'link'" :href="mod.path" target="_blank">
-					<el-icon><component :is="mod.meta.icon || el - icon - menu" /></el-icon>
+					<SvgIcon :name="mod.meta.icon" style="font-size: 18px;" />
 					<p>{{ mod.meta.title }}</p>
 				</a>
 				<router-link v-else :to="{ path: mod.path }">
-					<el-icon><component :is="mod.meta.icon || el - icon - menu" /></el-icon>
+					<SvgIcon :name="mod.meta.icon" style="font-size: 18px;" />
 					<p>{{ mod.meta.title }}</p>
 				</router-link>
 			</li>
 			<li class="modItem-add" @click="addMods">
-				<a href="javascript:void(0)">
-					<el-icon><el-icon-plus :style="{ color: '#fff' }" /></el-icon>
+				<a>
+					<el-icon><ele-Plus :style="{ color: '#fff' }" /></el-icon>
 				</a>
 			</li>
 		</ul>
 
-		<el-drawer title="添加应用" v-model="modsDrawer" :size="570" destroy-on-close>
-			<div class="setMods">
+		<el-drawer title="添加应用" v-model="modsDrawer" :size="520" destroy-on-close :before-close="beforeClose">
+			<div class="setMods mt15">
 				<h4>我的常用 ( {{ myMods.length }} )</h4>
-				<draggable tag="ul" v-model="myMods" animation="200" item-key="path" group="people">
+				<draggable tag="ul"
+					v-model="myMods"
+					animation="200"
+					item-key="path"
+					group="app"
+					class="draggable-box"
+					force-fallback
+					fallback-on-body
+				>
 					<template #item="{ element }">
 						<li>
-							<el-icon><component :is="element.meta.icon || el - icon - menu" /></el-icon>
+							<SvgIcon :name="element.meta.icon" style="font-size: 18px;" />
 							<p>{{ element.meta.title }}</p>
 						</li>
 					</template>
@@ -33,85 +41,107 @@
 			</div>
 			<div class="setMods">
 				<h4>全部应用 ( {{ filterMods.length }} )</h4>
-				<draggable tag="ul" v-model="filterMods" animation="200" item-key="path" :sort="false" group="people">
+				<draggable
+					tag="ul"
+					v-model="filterMods"
+					animation="200"
+					item-key="path"
+					group="app"
+					class="draggable-box"
+					force-fallback
+					fallback-on-body
+				>
 					<template #item="{ element }">
 						<li :style="{ background: element.meta.color || '#909399' }">
-							<el-icon><component :is="element.meta.icon || el - icon - menu" /></el-icon>
+							<SvgIcon :name="element.meta.icon" style="font-size: 18px;" />
 							<p>{{ element.meta.title }}</p>
 						</li>
 					</template>
 				</draggable>
 			</div>
 			<template #footer>
-				<el-button @click="modsDrawer = false">取消</el-button>
+				<el-button @click="beforeClose">取消</el-button>
 				<el-button type="primary" @click="saveMods">保存</el-button>
 			</template>
 		</el-drawer>
 	</el-card>
 </template>
 
-<script>
-import draggable from 'vuedraggable';
-import tool from '../tool';
-import { useRequestOldRoutes } from '/@/stores/requestOldRoutes';
-
+<script lang="ts">
 export default {
-	title: '快捷入口',
-	icon: 'el-icon-monitor',
-	description: '可以配置的快捷入口',
-	components: {
-		draggable,
-	},
-	data() {
-		return {
-			mods: [],
-			myMods: [],
-			myModsName: [],
-			filterMods: [],
-			modsDrawer: false,
-		};
-	},
-	mounted() {
-		this.getMods();
-	},
-	methods: {
-		addMods() {
-			this.modsDrawer = true;
-		},
-		getMods() {
-			this.myModsName = tool.data.get('my-mods') || [];
-			var menuTree = useRequestOldRoutes().requestOldRoutes || [];
-			this.filterMenu(menuTree);
-			this.myMods = this.mods.filter((item) => {
-				return this.myModsName.includes(item.name);
-			});
-			this.filterMods = this.mods.filter((item) => {
-				return !this.myModsName.includes(item.name);
-			});
-		},
-		filterMenu(map) {
-			map.forEach((item) => {
-				if (item.meta.isHide || item.type == 3 || item.status != 1) {
-					return false;
-				}
-				if (item.meta.isIframe) {
-					item.path = `/i/${item.name}`;
-				}
-				if (item.children && item.children.length > 0) {
-					this.filterMenu(item.children);
-				} else {
-					this.mods.push(item);
-				}
-			});
-		},
-		saveMods() {
-			const myModsName = this.myMods.map((v) => v.name);
-			tool.data.set('my-mods', myModsName);
-			this.$message.success('设置常用成功');
-			this.modsDrawer = false;
-		},
-	},
-};
+	title: "快捷入口",
+	icon: "ele-Monitor",
+	description: "可以配置的快捷入口"
+}
+</script>
+
+<script setup lang="ts" name="myapp">
+import draggable from 'vuedraggable'
+import { onMounted, ref } from 'vue'
+import { Local } from '/@/utils/storage'
+import { useRequestOldRoutes } from '/@/stores/requestOldRoutes'
+import { ElMessage } from 'element-plus'
+
+const mods = ref<any>([]) // 所有应用
+const myMods = ref<any>([]) // 我的常用
+const myModsName = ref<any>([]) // 我的常用
+const filterMods = ref<any>([]) // 过滤我的常用后的应用
+const modsDrawer = ref<boolean>(false)
+
+onMounted(() => {
+	getMods()
+})
+
+const addMods = () => {
+	modsDrawer.value = true
+}
+
+const getMods = () => {
+	myModsName.value = Local.get('my-mods') || []
+	var menuTree = useRequestOldRoutes().requestOldRoutes || []
+	filterMenu(menuTree)
+	myMods.value = mods.value.filter((item: any) => {
+		return myModsName.value.includes(item.name);
+	})
+	
+	filterMods.value = mods.value.filter((item: any) => {
+		return !myModsName.value.includes(item.name)
+	})
+}
+
+// 递归拿到所有可显示非iframe的2级菜单
+const filterMenu = (map: any) => {
+	map.forEach((item: any) => {
+		if (item.meta.isHide || item.type == 3 || item.status != 1) {
+			return false
+		}
+		if (item.meta.isIframe) {
+			item.path = `/i/${item.name}`
+		}
+		if (item.children && item.children.length > 0) {
+			filterMenu(item.children)
+		} else {
+			mods.value.push(item)
+		}
+	})
+}
+
+// 保存我的常用
+const saveMods = () => {
+	const myModsName = myMods.value.map((v: any) => v.name)
+	Local.set('my-mods', myModsName)
+	ElMessage.success('设置常用成功')
+	modsDrawer.value = false
+}
+
+// 取消
+const beforeClose = () => {
+	myModsName.value = Local.get('my-mods') || []
+	myMods.value = mods.value.filter((item: any) => {
+		return myModsName.value.includes(item.name);
+	})
+	modsDrawer.value = false
+}
 </script>
 
 <style scoped lang="scss">
@@ -171,6 +201,11 @@ export default {
 	color: #409eff !important;
 }
 
+.draggable-box {
+	border: 1px dashed var(--el-color-primary);
+	padding: 15px
+}
+
 .setMods {
 	padding: 0 20px;
 }

+ 7 - 7
Web/src/views/home/widgets/components/progress.vue → Web/src/views/home/widgets/components/progressing.vue

@@ -11,15 +11,15 @@
 	</el-card>
 </template>
 
-<script>
+<script lang="ts">
 export default {
 	title: '进度环',
-	icon: 'el-icon-odometer',
-	description: '进度环原子组件演示',
-	data() {
-		return {};
-	},
-};
+	icon: 'ele-Odometer',
+	description: '进度环原子组件演示'
+}
+</script>
+
+<script setup lang="ts" name="progressing">
 </script>
 
 <style scoped>

+ 0 - 51
Web/src/views/home/widgets/components/time.vue

@@ -1,51 +0,0 @@
-<template>
-	<el-card shadow="hover" header="时钟" class="item-background">
-		<div class="time">
-			<h2>{{ time }}</h2>
-			<p>{{ day }}</p>
-		</div>
-	</el-card>
-</template>
-
-<script>
-import tool from '../tool';
-
-export default {
-	title: '时钟',
-	icon: 'el-icon-clock',
-	description: '演示部件效果',
-	data() {
-		return {
-			time: '',
-			day: '',
-		};
-	},
-	mounted() {
-		this.showTime();
-		setInterval(() => {
-			this.showTime();
-		}, 1000);
-	},
-	methods: {
-		showTime() {
-			this.time = tool.dateFormat(new Date(), 'hh:mm:ss');
-			this.day = tool.dateFormat(new Date(), 'yyyy年MM月dd日');
-		},
-	},
-};
-</script>
-
-<style scoped>
-.item-background {
-	background: linear-gradient(to right, #8e54e9, #4776e6);
-	color: #fff;
-}
-.time h2 {
-	font-size: 40px;
-}
-.time p {
-	font-size: 14px;
-	margin-top: 13px;
-	opacity: 0.7;
-}
-</style>

+ 56 - 0
Web/src/views/home/widgets/components/timeing.vue

@@ -0,0 +1,56 @@
+<template>
+	<el-card shadow="hover" header="时钟" class="item-background">
+		<div class="time">
+			<h2>{{ time }}</h2>
+			<p>{{ day }}</p>
+		</div>
+	</el-card>
+</template>
+
+<script lang="ts">
+export default {
+	title: '时钟',
+	icon: 'ele-Timer',
+	description: '时钟原子组件演示'
+}
+</script>
+
+<script setup lang="ts" name="timeing">
+import { formatDate } from '/@/utils/formatTime'
+import { ref, onMounted, onUnmounted } from 'vue'
+const time = ref<string>('')
+const day = ref<string>('')
+const timer = ref<any>(null)
+
+onMounted(() => {
+	showTime()
+	timer.value =setInterval(() => {
+		showTime()
+	}, 1000)
+})
+
+onUnmounted(() => {
+	clearInterval(timer.value)
+})
+
+const showTime = () => {
+	time.value = formatDate(new Date(), 'HH:MM:SS')
+	day.value = formatDate(new Date(), 'YYYY年mm月dd日')
+}
+
+</script>
+
+<style scoped>
+.item-background {
+	background: linear-gradient(to right, #8e54e9, #4776e6);
+	color: #fff;
+}
+.time h2 {
+	font-size: 40px;
+}
+.time p {
+	font-size: 14px;
+	margin-top: 13px;
+	opacity: 0.7;
+}
+</style>

+ 29 - 26
Web/src/views/home/widgets/components/ver.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-card shadow="hover" header="版本信息">
 		<div style="height: 210px; text-align: center">
-			<img src="/@/assets/img/ver.svg" style="height: 140px" />
+			<img :src="verSvg" style="height: 140px" />
 			<h2 style="margin-top: 15px">Admin.Net</h2>
 			<p style="margin-top: 5px">最新版本 {{ ver }}</p>
 		</div>
@@ -12,31 +12,34 @@
 	</el-card>
 </template>
 
-<script>
-// import demo from '@/api/model/demo.js'
+<script lang="ts">
 export default {
 	title: '版本信息',
-	icon: 'el-icon-monitor',
-	description: '当前项目版本信息',
-	data() {
-		return {
-			ver: 'loading...',
-		};
-	},
-	mounted() {
-		this.getVer();
-	},
-	methods: {
-		async getVer() {
-			// const ver = await demo.ver.get()
-			this.ver = '11';
-		},
-		golog() {
-			window.open('https://gitee.com/zuohuaijun/Admin.NET/issues');
-		},
-		gogit() {
-			window.open('https://gitee.com/zuohuaijun/Admin.NET.git');
-		},
-	},
-};
+	icon: 'ele-Monitor',
+	description: '版本信息原子组件演示'
+}
+</script>
+
+<script setup lang="ts" name="ver">
+import { ref, onMounted } from 'vue'
+import verSvg from '/@/assets/img/ver.svg'
+
+const ver = ref<string>('loading...')
+
+onMounted(() => {
+	ver.value = '11'
+})
+
+const getVer = () => {
+	ver.value = '11'
+}
+
+const golog = () => {
+	window.open('https://gitee.com/zuohuaijun/Admin.NET/issues')
+}
+
+const gogit = () => {
+	window.open('https://gitee.com/zuohuaijun/Admin.NET.git')
+}
+
 </script>

+ 16 - 16
Web/src/views/home/widgets/components/welcome.vue

@@ -8,44 +8,44 @@
 			<div class="tips">
 				<div class="tips-item">
 					<div class="tips-item-icon">
-						<el-icon><el-icon-menu /></el-icon>
+						<el-icon><ele-Menu /></el-icon>
 					</div>
 					<div class="tips-item-message">这里是项目控制台,你可以点击右上方的“自定义”按钮来添加移除或者移动部件。</div>
 				</div>
 				<div class="tips-item">
 					<div class="tips-item-icon">
-						<el-icon><el-icon-promotion /></el-icon>
+						<el-icon><ele-Promotion /></el-icon>
 					</div>
 					<div class="tips-item-message">在提高前端算力、减少带宽请求和代码执行力上多次优化,并且持续着。</div>
 				</div>
 				<div class="tips-item">
 					<div class="tips-item-icon">
-						<el-icon><el-icon-milk-tea /></el-icon>
+						<el-icon><ele-MilkTea /></el-icon>
 					</div>
 					<div class="tips-item-message">项目目的:让前端工作更快乐</div>
 				</div>
 			</div>
 			<div class="actions">
-				<el-button type="primary" icon="el-icon-check" size="large" @click="godoc">文档</el-button>
+				<el-button type="primary" icon="ele-Check" size="large" @click="godoc">文档</el-button>
 			</div>
 		</div>
 	</el-card>
 </template>
 
-<script>
+<script lang="ts">
 export default {
 	title: '欢迎',
-	icon: 'el-icon-present',
-	description: '项目特色以及文档链接',
-	data() {
-		return {};
-	},
-	methods: {
-		godoc() {
-			window.open('https://gitee.com/zuohuaijun/Admin.NET.git');
-		},
-	},
-};
+	icon: 'ele-Present',
+	description: '项目特色以及文档链接'
+}
+</script>
+
+<script setup lang="ts" name="welcome">
+
+const godoc = () => {
+	window.open('https://gitee.com/zuohuaijun/Admin.NET.git')
+}
+
 </script>
 
 <style scoped>

+ 212 - 221
Web/src/views/home/widgets/index.vue

@@ -1,225 +1,216 @@
 <template>
-	<div :class="['widgets-home', customizing ? 'customizing' : '']" ref="main">
-		<div class="widgets-content">
-			<div class="widgets-top">
-				<div class="widgets-top-title">控制台</div>
-				<div class="widgets-top-actions">
-					<el-button v-if="customizing" type="primary" icon="el-icon-check" round @click="save">完成</el-button>
-					<el-button v-else type="primary" icon="el-icon-edit" round @click="custom">自定义</el-button>
-				</div>
-			</div>
-			<div class="widgets" ref="widgets">
-				<div class="widgets-wrapper">
-					<div v-if="nowCompsList.length <= 0" class="no-widgets">
-						<el-empty image="img/no-widgets.svg" description="没有部件啦" :image-size="280"></el-empty>
-					</div>
-					<el-row :gutter="15">
-						<el-col v-for="(item, index) in grid.layout" v-bind:key="index" :md="item" :xs="24">
-							<draggable
-								v-model="grid.copmsList[index]"
-								animation="200"
-								handle=".customize-overlay"
-								group="people"
-								item-key="com"
-								dragClass="aaaaa"
-								force-fallback
-								fallbackOnBody
-								class="draggable-box"
-							>
-								<template #item="{ element }">
-									<div class="widgets-item">
-										<component :is="allComps[element]"></component>
-										<div v-if="customizing" class="customize-overlay">
-											<el-button class="close" type="danger" plain icon="el-icon-close" size="small" @click="remove(element)"></el-button>
-											<label
-												><el-icon><component :is="allComps[element].icon" /></el-icon>{{ allComps[element].title }}</label
-											>
-										</div>
-									</div>
-								</template>
-							</draggable>
-						</el-col>
-					</el-row>
-				</div>
-			</div>
-		</div>
-		<div v-if="customizing" class="widgets-aside">
-			<el-container>
-				<el-header>
-					<div class="widgets-aside-title">
-						<el-icon><el-icon-circle-plus-filled /></el-icon>添加部件
-					</div>
-					<div class="widgets-aside-close" @click="close()">
-						<el-icon><el-icon-close /></el-icon>
-					</div>
-				</el-header>
-				<el-header style="height: auto">
-					<div class="selectLayout">
-						<div class="selectLayout-item item01" :class="{ active: grid.layout.join(',') == '12,6,6' }" @click="setLayout([12, 6, 6])">
-							<el-row :gutter="2">
-								<el-col :span="12"><span></span></el-col>
-								<el-col :span="6"><span></span></el-col>
-								<el-col :span="6"><span></span></el-col>
-							</el-row>
-						</div>
-						<div class="selectLayout-item item02" :class="{ active: grid.layout.join(',') == '24,16,8' }" @click="setLayout([24, 16, 8])">
-							<el-row :gutter="2">
-								<el-col :span="24"><span></span></el-col>
-								<el-col :span="16"><span></span></el-col>
-								<el-col :span="8"><span></span></el-col>
-							</el-row>
-						</div>
-						<div class="selectLayout-item item03" :class="{ active: grid.layout.join(',') == '24' }" @click="setLayout([24])">
-							<el-row :gutter="2">
-								<el-col :span="24"><span></span></el-col>
-								<el-col :span="24"><span></span></el-col>
-								<el-col :span="24"><span></span></el-col>
-							</el-row>
-						</div>
-					</div>
-				</el-header>
-				<el-main class="nopadding">
-					<div class="widgets-list">
-						<div v-if="myCompsList.length <= 0" class="widgets-list-nodata">
-							<el-empty description="没有部件啦" :image-size="60"></el-empty>
-						</div>
-						<div v-for="item in myCompsList" :key="item.title" class="widgets-list-item">
-							<div class="item-logo">
-								<el-icon><component :is="item.icon" /></el-icon>
-							</div>
-							<div class="item-info">
-								<h2>{{ item.title }}</h2>
-								<p>{{ item.description }}</p>
-							</div>
-							<div class="item-actions">
-								<el-button type="primary" icon="el-icon-plus" size="small" @click="push(item)"></el-button>
-							</div>
-						</div>
-					</div>
-				</el-main>
-				<el-footer style="height: 51px">
-					<el-button size="small" @click="backDefaul()">恢复默认</el-button>
-				</el-footer>
-			</el-container>
-		</div>
-	</div>
+  <div :class="['widgets-home', customizing ? 'customizing' : '']" ref="main">
+    <div class="widgets-content">
+      <div class="widgets-top">
+        <div class="widgets-top-title">控制台</div>
+        <div class="widgets-top-actions">
+          <el-button v-if="customizing" type="primary" icon="ele-Check" round @click="save">完成</el-button>
+          <el-button v-else type="primary" icon="ele-Edit" round @click="custom">自定义</el-button>
+        </div>
+      </div>
+      <div class="widgets" ref="widgetsRef">
+        <div class="widgets-wrapper">
+          <div v-if="nowCompsList.length <= 0" class="no-widgets">
+            <el-empty description="没有部件啦" :image-size="300"></el-empty>
+          </div>
+          <el-row :gutter="15">
+            <el-col v-for="(item, index) in grid.layout" :key="index" :md="item" :xs="24">
+              <draggable
+                v-model="grid.copmsList[index]"
+                animation="200"
+                handle=".customize-overlay"
+                group="people"
+                item-key="com"
+                drag-class="aaaaa"
+                force-fallback
+                fallback-on-body
+                class="draggable-box"
+              >
+                <template #item="{ element }">
+                  <div class="widgets-item mb15">
+                    <component :is="allComps[element]"></component>
+                    <div v-if="customizing" class="customize-overlay">
+                      <el-button class="close" type="danger" plain icon="ele-Close" @click="remove(element)"></el-button>
+                      <label>
+                        <el-icon><component :is="allComps[element].icon" /></el-icon>{{ allComps[element].title }}
+                      </label>
+                    </div>
+                  </div>
+                </template>
+              </draggable>
+            </el-col>
+          </el-row>
+        </div>
+      </div>
+    </div>
+    <div v-if="customizing" class="widgets-aside">
+      <el-container>
+        <el-header>
+          <div class="widgets-aside-title">
+            <el-icon><ele-CirclePlusFilled /></el-icon>添加部件
+          </div>
+          <div class="widgets-aside-close" @click="close">
+            <el-icon><ele-Close /></el-icon>
+          </div>
+        </el-header>
+        <el-header style="height: auto">
+          <div class="selectLayout">
+            <div class="selectLayout-item item01" :class="{ active: grid.layout.join(',') === '12,6,6' }" @click="setLayout([12, 6, 6])">
+              <el-row :gutter="2">
+                <el-col :span="12"><span></span></el-col>
+                <el-col :span="6"><span></span></el-col>
+                <el-col :span="6"><span></span></el-col>
+              </el-row>
+            </div>
+            <div class="selectLayout-item item02" :class="{ active: grid.layout.join(',') === '24,16,8' }" @click="setLayout([24, 16, 8])">
+              <el-row :gutter="2">
+                <el-col :span="24"><span></span></el-col>
+                <el-col :span="16"><span></span></el-col>
+                <el-col :span="8"><span></span></el-col>
+              </el-row>
+            </div>
+            <div class="selectLayout-item item03" :class="{ active: grid.layout.join(',') === '24' }" @click="setLayout([24])">
+              <el-row :gutter="2">
+                <el-col :span="24"><span></span></el-col>
+                <el-col :span="24"><span></span></el-col>
+                <el-col :span="24"><span></span></el-col>
+              </el-row>
+            </div>
+          </div>
+        </el-header>
+        <el-main class="nopadding">
+          <div class="widgets-list">
+            <div v-if="myCompsList.length <= 0" class="widgets-list-nodata">
+              <el-empty description="没有部件啦" :image-size="60"></el-empty>
+            </div>
+            <div v-for="item in myCompsList" :key="item.title" class="widgets-list-item">
+              <div class="item-logo">
+                <el-icon><component :is="item.icon" /></el-icon>
+              </div>
+              <div class="item-info">
+                <h2>{{ item.title }}</h2>
+                <p>{{ item.description }}</p>
+              </div>
+              <div class="item-actions">
+                <el-button type="primary" icon="ele-Plus" @click="push(item)"></el-button>
+              </div>
+            </div>
+          </div>
+        </el-main>
+        <el-footer style="height: 51px">
+          <el-button @click="backDefault">恢复默认</el-button>
+        </el-footer>
+      </el-container>
+    </div>
+  </div>
 </template>
 
-<script>
-import draggable from 'vuedraggable';
-import allComps from './components';
-import tool from './tool';
-export default {
-	components: {
-		draggable,
-	},
-	data() {
-		return {
-			customizing: false,
-			allComps: allComps,
-			selectLayout: [],
-			defaultGrid: {
-				// 默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6]
-				layout: [12, 6, 6],
-				// 小组件分布,com取值:views/home/components 文件名
-				copmsList: [['welcome'], ['about', 'ver'], ['time', 'progress']],
-			},
-			grid: [],
-		};
-	},
-	created() {
-		this.grid = tool.data.get('grid') || JSON.parse(JSON.stringify(this.defaultGrid));
-	},
-	mounted() {
-		// this.$emit('on-mounted');
-	},
-	computed: {
-		allCompsList() {
-			var allCompsList = [];
-			for (var key in this.allComps) {
-				allCompsList.push({
-					key: key,
-					title: allComps[key].title,
-					icon: allComps[key].icon,
-					description: allComps[key].description,
-				});
-			}
-			var myCopmsList = this.grid.copmsList.reduce(function (a, b) {
-				return a.concat(b);
-			});
-			for (let comp of allCompsList) {
-				const _item = myCopmsList.find((item) => {
-					return item === comp.key;
-				});
-				if (_item) {
-					comp.disabled = true; // 如果界面有,则右边不可选
-				}
-			}
-			return allCompsList;
-		},
-		myCompsList() {
-			var myGrid = tool.data.get('DASHBOARDGRID');
-			if (!myGrid) myGrid = ['welcome', 'myapp', 'ver', 'time', 'progress', 'echarts', 'about'];
-			return this.allCompsList.filter((item) => !item.disabled && myGrid.includes(item.key));
-		},
-		nowCompsList() {
-			return this.grid.copmsList.reduce(function (a, b) {
-				return a.concat(b);
-			});
-		},
-	},
-	methods: {
-		// 开启自定义
-		custom() {
-			this.customizing = true;
-			const oldWidth = this.$refs.widgets.offsetWidth;
-			this.$nextTick(() => {
-				const scale = this.$refs.widgets.offsetWidth / oldWidth;
-				this.$refs.widgets.style.setProperty('transform', `scale(${scale})`);
-			});
-		},
-		// 设置布局
-		setLayout(layout) {
-			this.grid.layout = layout;
-			if (layout.join(',') == '24') {
-				this.grid.copmsList[0] = [...this.grid.copmsList[0], ...this.grid.copmsList[1], ...this.grid.copmsList[2]];
-				this.grid.copmsList[1] = [];
-				this.grid.copmsList[2] = [];
-			}
-		},
-		// 追加
-		push(item) {
-			let target = this.grid.copmsList[0];
-			target.push(item.key);
-		},
-		// 隐藏组件
-		remove(item) {
-			var newCopmsList = this.grid.copmsList;
-			newCopmsList.forEach((obj, index) => {
-				var newObj = obj.filter((o) => o != item);
-				newCopmsList[index] = newObj;
-			});
-		},
-		// 保存
-		save() {
-			this.customizing = false;
-			this.$refs.widgets.style.removeProperty('transform');
-			tool.data.set('grid', this.grid);
-		},
-		// 恢复默认
-		backDefaul() {
-			this.customizing = false;
-			this.$refs.widgets.style.removeProperty('transform');
-			this.grid = JSON.parse(JSON.stringify(this.defaultGrid));
-			tool.data.remove('grid');
-		},
-		// 关闭
-		close() {
-			this.customizing = false;
-			this.$refs.widgets.style.removeProperty('transform');
-		},
-	},
-};
+<script setup lang="ts">
+import { ref, reactive, computed, onMounted, nextTick } from 'vue'
+import draggable from 'vuedraggable'
+import allComps from './components/index'
+import { Local } from '/@/utils/storage'
+
+interface Grid {
+  layout: number[]
+  copmsList: string[][]
+}
+const defaultGrid = reactive<Grid>({
+  layout: [12, 6, 6],
+  copmsList: [['welcome'], ['about', 'ver'], ['timeing', 'progressing']]
+})
+
+const customizing = ref<boolean>(false)
+const allCompsList = ref(allComps)
+const widgetsRef = ref<HTMLElement | null>(null)
+const grid = ref<Grid>(defaultGrid)
+  
+
+onMounted(() => {
+  const savedGrid = Local.get('grid')
+  if (savedGrid) {
+    grid.value = savedGrid
+  }
+})
+
+const availableCompsList = computed(() => {
+  const compsList = []
+  for (const key in allCompsList.value) {
+    const comp = allCompsList.value[key]
+    compsList.push({
+      key,
+      title: comp.title,
+      icon: comp.icon,
+      description: comp.description,
+    })
+  }
+  const activeComps = grid.value.copmsList.flat()
+  return compsList.map(comp => ({
+    ...comp,
+    disabled: activeComps.includes(comp.key),
+  }))
+})
+
+const myCompsList = computed(() => {
+  const myGrid = Local.get('DASHBOARDGRID') || ['welcome', 'myapp', 'ver', 'timeing', 'progressing', 'echarts', 'about']
+  return availableCompsList.value.filter(comp => !comp.disabled && myGrid.includes(comp.key))
+})
+
+const nowCompsList = computed(() => grid.value.copmsList.flat())
+
+// 开启自定义
+const custom = () => {
+  customizing.value = true
+  const oldWidth = widgetsRef.value?.offsetWidth || 0
+  nextTick(() => {
+    if (widgetsRef.value) {
+      const scale = widgetsRef.value.offsetWidth / oldWidth;
+      widgetsRef.value.style.setProperty('transform', `scale(${scale})`)
+    }
+  })
+}
+
+// 设置布局
+const setLayout = (layout: number[]) => {
+  grid.value.layout = layout;
+  if (layout.join(',') === '24') {
+    grid.value.copmsList[0] = [...grid.value.copmsList[0], ...grid.value.copmsList[1], ...grid.value.copmsList[2]]
+    grid.value.copmsList[1] = []
+    grid.value.copmsList[2] = []
+  }
+}
+
+// 追加
+const push = (item: any) => {
+  grid.value.copmsList[0].push(item.key)
+}
+
+// 隐藏组件
+const remove = (item: string) => {
+  grid.value.copmsList = grid.value.copmsList.map(list => list.filter(comp => comp !== item));
+}
+
+// 保存
+const save = () => {
+  customizing.value = false
+  widgetsRef.value?.style.removeProperty('transform')
+  Local.set('grid', grid.value)
+}
+
+// 恢复默认
+const backDefault = () => {
+  customizing.value = false
+  widgetsRef.value?.style.removeProperty('transform')
+  grid.value = JSON.parse(JSON.stringify(defaultGrid))
+  Local.remove('grid')
+}
+
+// 关闭
+const close = () => {
+  customizing.value = false
+  widgetsRef.value?.style.removeProperty('transform')
+  grid.value = Local.get('grid')
+}
 </script>
 
 <style scoped lang="scss">
@@ -301,8 +292,8 @@ export default {
 }
 .customizing .widgets-item {
 	position: relative;
-	margin-bottom: 15px;
 }
+
 .customize-overlay {
 	position: absolute;
 	top: 0;
@@ -444,4 +435,4 @@ export default {
 		margin-right: 0;
 	}
 }
-</style>
+</style>

+ 0 - 226
Web/src/views/home/widgets/tool.js

@@ -1,226 +0,0 @@
-/*
- * @Descripttion: 工具集
- * @version: 1.2
- * @LastEditors: sakuya
- * @LastEditTime: 2022年5月24日00:28:56
- */
-
-// import CryptoJS from 'crypto-js';
-
-const tool = {};
-
-/* localStorage */
-tool.data = {
-	set(key, data, datetime = 0) {
-		let cacheValue = {
-			content: data,
-			datetime: parseInt(datetime) === 0 ? 0 : new Date().getTime() + parseInt(datetime) * 1000,
-		};
-		return localStorage.setItem(key, JSON.stringify(cacheValue));
-	},
-	get(key) {
-		try {
-			const value = JSON.parse(localStorage.getItem(key));
-			if (value) {
-				let nowTime = new Date().getTime();
-				if (nowTime > value.datetime && value.datetime != 0) {
-					localStorage.removeItem(key);
-					return null;
-				}
-				return value.content;
-			}
-			return null;
-		} catch (err) {
-			return null;
-		}
-	},
-	remove(key) {
-		return localStorage.removeItem(key);
-	},
-	clear() {
-		return localStorage.clear();
-	},
-};
-
-/*sessionStorage*/
-tool.session = {
-	set(table, settings) {
-		var _set = JSON.stringify(settings);
-		return sessionStorage.setItem(table, _set);
-	},
-	get(table) {
-		var data = sessionStorage.getItem(table);
-		try {
-			data = JSON.parse(data);
-		} catch (err) {
-			return null;
-		}
-		return data;
-	},
-	remove(table) {
-		return sessionStorage.removeItem(table);
-	},
-	clear() {
-		return sessionStorage.clear();
-	},
-};
-
-/*cookie*/
-tool.cookie = {
-	set(name, value, config = {}) {
-		var cfg = {
-			expires: null,
-			path: null,
-			domain: null,
-			secure: false,
-			httpOnly: false,
-			...config,
-		};
-		var cookieStr = `${name}=${escape(value)}`;
-		if (cfg.expires) {
-			var exp = new Date();
-			exp.setTime(exp.getTime() + parseInt(cfg.expires) * 1000);
-			cookieStr += `;expires=${exp.toGMTString()}`;
-		}
-		if (cfg.path) {
-			cookieStr += `;path=${cfg.path}`;
-		}
-		if (cfg.domain) {
-			cookieStr += `;domain=${cfg.domain}`;
-		}
-		document.cookie = cookieStr;
-	},
-	get(name) {
-		var arr = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)(;|$)'));
-		if (arr != null) {
-			return unescape(arr[2]);
-		} else {
-			return null;
-		}
-	},
-	remove(name) {
-		var exp = new Date();
-		exp.setTime(exp.getTime() - 1);
-		document.cookie = `${name}=;expires=${exp.toGMTString()}`;
-	},
-};
-
-/* Fullscreen */
-tool.screen = function (element) {
-	var isFull = !!(document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement);
-	if (isFull) {
-		if (document.exitFullscreen) {
-			document.exitFullscreen();
-		} else if (document.msExitFullscreen) {
-			document.msExitFullscreen();
-		} else if (document.mozCancelFullScreen) {
-			document.mozCancelFullScreen();
-		} else if (document.webkitExitFullscreen) {
-			document.webkitExitFullscreen();
-		}
-	} else {
-		if (element.requestFullscreen) {
-			element.requestFullscreen();
-		} else if (element.msRequestFullscreen) {
-			element.msRequestFullscreen();
-		} else if (element.mozRequestFullScreen) {
-			element.mozRequestFullScreen();
-		} else if (element.webkitRequestFullscreen) {
-			element.webkitRequestFullscreen();
-		}
-	}
-};
-
-/* 复制对象 */
-tool.objCopy = function (obj) {
-	return JSON.parse(JSON.stringify(obj));
-};
-
-/* 日期格式化 */
-tool.dateFormat = function (date, fmt = 'yyyy-MM-dd hh:mm:ss') {
-	date = new Date(date);
-	var o = {
-		'M+': date.getMonth() + 1, //月份
-		'd+': date.getDate(), //日
-		'h+': date.getHours(), //小时
-		'm+': date.getMinutes(), //分
-		's+': date.getSeconds(), //秒
-		'q+': Math.floor((date.getMonth() + 3) / 3), //季度
-		S: date.getMilliseconds(), //毫秒
-	};
-	if (/(y+)/.test(fmt)) {
-		fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
-	}
-	for (var k in o) {
-		if (new RegExp('(' + k + ')').test(fmt)) {
-			fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length));
-		}
-	}
-	return fmt;
-};
-
-/* 千分符 */
-tool.groupSeparator = function (num) {
-	num = num + '';
-	if (!num.includes('.')) {
-		num += '.';
-	}
-	return num
-		.replace(/(\d)(?=(\d{3})+\.)/g, function ($0, $1) {
-			return $1 + ',';
-		})
-		.replace(/\.$/, '');
-};
-
-// /* 常用加解密 */
-// tool.crypto = {
-// 	//MD5加密
-// 	MD5(data){
-// 		return CryptoJS.MD5(data).toString()
-// 	},
-// 	//BASE64加解密
-// 	BASE64: {
-// 		encrypt(data){
-// 			return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(data))
-// 		},
-// 		decrypt(cipher){
-// 			return CryptoJS.enc.Base64.parse(cipher).toString(CryptoJS.enc.Utf8)
-// 		}
-// 	},
-// 	//AES加解密
-// 	AES: {
-// 		encrypt(data, secretKey, config={}){
-// 			if(secretKey.length % 8 != 0){
-// 				console.warn("[SCUI error]: 秘钥长度需为8的倍数,否则解密将会失败。")
-// 			}
-// 			const result = CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse(secretKey), {
-// 				iv: CryptoJS.enc.Utf8.parse(config.iv || ""),
-// 				mode: CryptoJS.mode[config.mode || "ECB"],
-// 				padding: CryptoJS.pad[config.padding || "Pkcs7"]
-// 			})
-// 			return result.toString()
-// 		},
-// 		decrypt(cipher, secretKey, config={}){
-// 			const result = CryptoJS.AES.decrypt(cipher, CryptoJS.enc.Utf8.parse(secretKey), {
-// 				iv: CryptoJS.enc.Utf8.parse(config.iv || ""),
-// 				mode: CryptoJS.mode[config.mode || "ECB"],
-// 				padding: CryptoJS.pad[config.padding || "Pkcs7"]
-// 			})
-// 			return CryptoJS.enc.Utf8.stringify(result);
-// 		}
-// 	}
-// }
-
-// 查找树
-tool.treeFind = (tree, func) => {
-	for (const data of tree) {
-		if (func(data)) return data;
-		if (data.children) {
-			const res = tool.treeFind(data.children, func);
-			if (res) return res;
-		}
-	}
-	return null;
-};
-
-export default tool;