'admin-22.11.16:同步master分支v2.3.0版本内容'

This commit is contained in:
lyt
2022-11-16 17:57:38 +08:00
parent 1c6f5b7d42
commit 1fa0d67637
64 changed files with 2539 additions and 1706 deletions

View File

@ -2,6 +2,12 @@
🎉🎉🔥 `vue-next-admin-template` 基于 vue-next-admin-v1.1.2 版本) vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 2.3.0
`2022.11.16`
- 🎉 同步 master 分支 v2.3.0 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.2.0
`2022.07.11`

View File

@ -80,10 +80,13 @@ cnpm run build
- 在 github 远程端删除一个分支:`git push origin :newBranch (分支名前的冒号代表删除)`
- 注意删除远程分支后,如果有对应的本地分支,本地分支并不会同步删除!
#### 📚 开发文档
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
#### 💯 学习交流加 QQ 群
- 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
- 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!微信群由于只有 `7天有效` 就不放这里了。
- 群号码:
1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>

2150
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "vue-next-admin-template",
"version": "2.2.0",
"version": "2.3.0",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
@ -10,39 +10,37 @@
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.6",
"axios": "^0.27.2",
"echarts": "^5.3.3",
"element-plus": "^2.2.9",
"@element-plus/icons-vue": "^2.0.10",
"axios": "^1.1.3",
"echarts": "^5.4.0",
"element-plus": "^2.2.21",
"js-cookie": "^3.0.1",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.16",
"pinia": "^2.0.23",
"qrcodejs2-fixes": "^0.0.2",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"vue": "^3.2.37",
"vue": "^3.2.45",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.1.2"
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1",
"@types/node": "^18.0.5",
"@types/node": "^18.11.9",
"@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@vitejs/plugin-vue": "^2.3.3",
"@vue/compiler-sfc": "^3.2.37",
"dotenv": "^16.0.1",
"eslint": "^8.19.0",
"eslint-plugin-vue": "^9.2.0",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vue/compiler-sfc": "^3.2.45",
"dotenv": "^16.0.3",
"eslint": "^8.27.0",
"eslint-plugin-vue": "^9.7.0",
"prettier": "^2.7.1",
"sass": "^1.53.0",
"sass-loader": "^13.0.2",
"typescript": "^4.7.4",
"vite": "^2.9.14",
"vue-eslint-parser": "^9.0.3"
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vue-eslint-parser": "^9.1.0"
},
"browserslist": [
"> 1%",

View File

@ -8,7 +8,7 @@
</template>
<script lang="ts">
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
import { useRoute } from 'vue-router';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import { storeToRefs } from 'pinia';
@ -16,16 +16,17 @@ import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue';
import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
export default defineComponent({
name: 'app',
components: { LockScreen, Setings, CloseFull },
components: {
LockScreen: defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')),
Setings: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue')),
CloseFull: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const setingsRef = ref();
const route = useRoute();
const stores = useTagsViewRoutes();
@ -35,10 +36,6 @@ export default defineComponent({
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();
});
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
@ -50,8 +47,8 @@ export default defineComponent({
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
mittBus.on('openSetingsDrawer', () => {
setingsRef.value.openDrawer();
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
@ -66,7 +63,7 @@ export default defineComponent({
});
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
proxy.mittBus.off('openSetingsDrawer', () => {});
mittBus.off('openSetingsDrawer', () => {});
});
// 监听路由的变化,设置网站标题
watch(

19
src/assets/login-bg.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -10,20 +10,22 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, defineComponent } from 'vue';
import { defineAsyncComponent, toRefs, reactive, computed, watch, onBeforeMount, defineComponent, ref } from 'vue';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import Logo from '/@/layout/logo/index.vue';
import Vertical from '/@/layout/navMenu/vertical.vue';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutAside',
components: { Logo, Vertical },
components: {
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
Vertical: defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutAsideScrollbarRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
@ -105,14 +107,13 @@ export default defineComponent({
const onAsideEnterLeave = (bool: Boolean) => {
let { layout } = themeConfig.value;
if (layout !== 'columns') return false;
if (!bool) proxy.mittBus.emit('restoreDefault');
if (!bool) mittBus.emit('restoreDefault');
stores.setColumnsMenuHover(bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(themeConfig.value, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
proxy.$refs.layoutAsideScrollbarRef.update();
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
});
// 监听vuex值的变化动态赋值给菜单中
@ -131,27 +132,28 @@ export default defineComponent({
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
// 此界面不需要取消监听(mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
mittBus.on('setSendClassicChildren', (res: any) => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
proxy.mittBus.on('layoutMobileResize', (res: any) => {
mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
return {
layoutAsideScrollbarRef,
setCollapseStyle,
setShowLogo,
isTagsViewCurrenFull,

View File

@ -45,12 +45,13 @@
</template>
<script lang="ts">
import { reactive, toRefs, ref, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
import { reactive, toRefs, ref, onMounted, nextTick, watch, onUnmounted, defineComponent } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface ColumnsAsideState {
@ -68,7 +69,6 @@ export default defineComponent({
setup() {
const columnsAsideOffsetTopRefs: any = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = <any>getCurrentInstance();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
@ -98,11 +98,12 @@ export default defineComponent({
};
// 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
mittBus.emit('setSendColumnsChildren', setSendChildren(path));
stores.setColumnsMenuHover(false);
stores.setColumnsNavHover(true);
};
@ -111,7 +112,7 @@ export default defineComponent({
await stores.setColumnsNavHover(false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
}, 100);
};
// 设置高亮动态位置
@ -126,7 +127,7 @@ export default defineComponent({
const resData: any = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
proxy.mittBus.emit('setSendColumnsChildren', resData);
mittBus.emit('setSendColumnsChildren', resData);
};
// 传送当前子级数据到菜单中
const setSendChildren = (path: string) => {
@ -171,11 +172,11 @@ export default defineComponent({
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
state.liHoverIndex = null;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
},
{
@ -186,19 +187,19 @@ export default defineComponent({
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
proxy.mittBus.on('restoreDefault', () => {
mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('restoreDefault', () => {});
mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
return {
themeConfig,
@ -221,6 +222,16 @@ export default defineComponent({
background: var(--next-bg-columnsMenuBar);
ul {
position: relative;
.layout-columns-active {
color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
li {
color: var(--next-bg-columnsMenuBarColor);
width: 100%;
@ -230,6 +241,9 @@ export default defineComponent({
cursor: pointer;
position: relative;
z-index: 1;
&:hover {
@extend .layout-columns-hover;
}
.columns-vertical {
margin: auto;
.columns-vertical-title {
@ -257,16 +271,6 @@ export default defineComponent({
color: var(--next-bg-columnsMenuBarColor);
}
}
.layout-columns-active {
color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
.columns-round {
background: var(--el-color-primary);
color: var(--el-color-white);

View File

@ -1,32 +1,23 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<el-header class="layout-header" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { defineAsyncComponent, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import NavBarsIndex from '/@/layout/navBars/index.vue';
export default defineComponent({
name: 'layoutHeader',
components: { NavBarsIndex },
components: {
NavBarsIndex: defineAsyncComponent(() => import('/@/layout/navBars/index.vue')),
},
setup() {
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = themeConfig.value;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
return {
setHeaderHeight,
isTagsViewCurrenFull,
};
},

View File

@ -1,100 +1,58 @@
<template>
<el-main class="layout-main">
<el-scrollbar
ref="layoutScrollbarRef"
:class="{
'layout-scrollbar':
(!isClassicOrTransverse && !currentRouteMeta.isLink && !currentRouteMeta.isIframe) ||
(!isClassicOrTransverse && currentRouteMeta.isLink && !currentRouteMeta.isIframe),
}"
>
<LayoutParentView
:style="{
padding: !isClassicOrTransverse || (currentRouteMeta.isLink && currentRouteMeta.isIframe) ? '0' : '15px',
transition: 'padding 0.3s ease-in-out',
}"
/>
<Footers v-if="themeConfig.isFooter" />
<el-main class="layout-main" :style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll" wrap-class="layout-main-scroll" view-class="layout-main-scroll">
<LayoutParentView />
<LayoutFooter v-if="isFooter" />
</el-scrollbar>
<el-backtop target=".layout-backtop .el-scrollbar__wrap" />
</el-main>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted, computed } from 'vue';
import { defineAsyncComponent, defineComponent, onMounted, computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import { NextLoading } from '/@/utils/loading';
import LayoutParentView from '/@/layout/routerView/parent.vue';
import Footers from '/@/layout/footer/index.vue';
// 定义接口来定义对象的类型
interface MainState {
headerHeight: string | number;
currentRouteMeta: any;
}
export default defineComponent({
name: 'layoutMain',
components: { LayoutParentView, Footers },
components: {
LayoutParentView: defineAsyncComponent(() => import('/@/layout/routerView/parent.vue')),
LayoutFooter: defineAsyncComponent(() => import('/@/layout/footer/index.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutMainScrollbarRef = ref('');
const route = useRoute();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive<MainState>({
headerHeight: '',
currentRouteMeta: {},
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置 footer 显示/隐藏
const isFooter = computed(() => {
return themeConfig.value.isFooter && !route.meta.isIframe;
});
// 判断布局
const isClassicOrTransverse = computed(() => {
const { layout } = themeConfig.value;
return layout === 'classic' || layout === 'transverse';
// 设置 header 固定
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
});
// 设置主内容区的高度
const setMainHeight = computed(() => {
if (isTagsViewCurrenFull.value) return '0px';
const { isTagsview, layout } = themeConfig.value;
if (isTagsview && layout !== 'classic') return '85px';
else return '51px';
});
// 设置 main 的高度
const initHeaderHeight = () => {
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
let { isTagsview } = themeConfig.value;
if (isTagsview) return (state.headerHeight = bool ? `86px` : `115px`);
else return (state.headerHeight = `80px`);
};
// 初始化获取当前路由 meta用于设置 iframes padding
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 页面加载前
onMounted(async () => {
await initGetMeta();
initHeaderHeight();
NextLoading.done();
onMounted(() => {
NextLoading.done(600);
});
// 监听路由变化
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
state.headerHeight = bool ? `86px` : `115px`;
proxy.$refs.layoutScrollbarRef.update();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
(val) => {
state.currentRouteMeta = route.meta;
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
state.headerHeight = val.isTagsview ? (bool ? `86px` : `115px`) : '51px';
proxy.$refs?.layoutScrollbarRef?.update();
},
{
deep: true,
}
);
return {
themeConfig,
isClassicOrTransverse,
...toRefs(state),
layoutMainScrollbarRef,
isFooter,
isFixedHeader,
setMainHeight,
};
},
});

View File

@ -1,5 +1,5 @@
<template>
<div class="layout-footer mt15" v-show="isDelayFooter">
<div class="layout-footer pb15">
<div class="layout-footer-warp">
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">深圳市 xxx 公司版权所有</div>
@ -8,28 +8,10 @@
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import { onBeforeRouteUpdate } from 'vue-router';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'layoutFooter',
setup() {
const state = reactive({
isDelayFooter: true,
});
// 路由改变时,等主界面动画加载完毕再显示 footer
onBeforeRouteUpdate(() => {
setTimeout(() => {
state.isDelayFooter = false;
setTimeout(() => {
state.isDelayFooter = true;
}, 800);
}, 0);
});
return {
...toRefs(state),
};
},
});
</script>
@ -41,7 +23,7 @@ export default defineComponent({
margin: auto;
color: var(--el-text-color-secondary);
text-align: center;
animation: error-num 1s ease-in-out;
animation: error-num 0.3s ease;
}
}
</style>

View File

@ -3,10 +3,11 @@
</template>
<script lang="ts">
import { onBeforeMount, onUnmounted, getCurrentInstance, defineComponent, defineAsyncComponent } from 'vue';
import { onBeforeMount, onUnmounted, defineComponent, defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layout',
@ -17,7 +18,6 @@ export default defineComponent({
columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 窗口大小改变时(适配移动端)
@ -26,12 +26,12 @@ export default defineComponent({
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
themeConfig.value.isCollapse = false;
proxy.mittBus.emit('layoutMobileResize', {
mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
} else {
proxy.mittBus.emit('layoutMobileResize', {
mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
clientWidth,
});

View File

@ -60,7 +60,7 @@
</template>
<script lang="ts">
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, defineComponent } from 'vue';
import { formatDate } from '/@/utils/formatTime';
import { Local } from '/@/utils/storage';
import { storeToRefs } from 'pinia';
@ -88,7 +88,7 @@ interface LockScreenState {
export default defineComponent({
name: 'layoutLockScreen',
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutLockScreenDateRef = ref();
const layoutLockScreenInputRef = ref();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -150,7 +150,7 @@ export default defineComponent({
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
state.querySelectorEl = layoutLockScreenDateRef.value;
});
};
// 时间初始化
@ -204,6 +204,7 @@ export default defineComponent({
window.clearInterval(state.isShowLockScreenIntervalTime);
});
return {
layoutLockScreenDateRef,
layoutLockScreenInputRef,
onDown,
onMove,

View File

@ -1,34 +1,76 @@
<template>
<el-container class="layout-container flex-center">
<Headers />
<LayoutHeader />
<el-container class="layout-mian-height-50">
<Asides />
<LayoutAside />
<div class="flex-center layout-backtop">
<TagsView v-if="themeConfig.isTagsview" />
<Mains />
<LayoutTagsView v-if="isTagsview" />
<LayoutMain ref="layoutMainRef" />
</div>
</el-container>
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineAsyncComponent, defineComponent, computed, ref, watch, nextTick, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Asides from '/@/layout/component/aside.vue';
import Headers from '/@/layout/component/header.vue';
import Mains from '/@/layout/component/main.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default defineComponent({
name: 'layoutClassic',
components: { Asides, Headers, Mains, TagsView },
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
LayoutTagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
},
setup() {
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
return {
// 判断是否显示 tasgview
const isTagsview = computed(() => {
return themeConfig.value.isTagsview;
});
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutMainRef,
isTagsview,
};
},
});

View File

@ -1,40 +1,77 @@
<template>
<el-container class="layout-container">
<ColumnsAside />
<div class="layout-columns-warp">
<Asides />
<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
<Headers v-if="isFixedHeader" />
<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
<Headers v-if="!isFixedHeader" />
<Mains />
</el-scrollbar>
</el-container>
</div>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
<el-container class="layout-columns-warp layout-container-view h100">
<LayoutAside />
<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-scrollbar>
</el-container>
</el-container>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Asides from '/@/layout/component/aside.vue';
import Headers from '/@/layout/component/header.vue';
import Mains from '/@/layout/component/main.vue';
import ColumnsAside from '/@/layout/component/columnsAside.vue';
export default defineComponent({
name: 'layoutColumns',
components: { Asides, Headers, Mains, ColumnsAside },
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
ColumnsAside: defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue')),
},
setup() {
const layoutScrollbarRef = ref<any>('');
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrap$.scrollTop = 0;
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
isFixedHeader,
layoutScrollbarRef,
layoutMainRef,
};
},
});

View File

@ -1,46 +1,77 @@
<template>
<el-container class="layout-container">
<Asides />
<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
<Headers v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
<Headers v-if="!isFixedHeader" />
<Mains />
<LayoutAside />
<el-container class="layout-container-view h100">
<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-scrollbar>
</el-container>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts">
import { computed, getCurrentInstance, watch, defineComponent } from 'vue';
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Asides from '/@/layout/component/aside.vue';
import Headers from '/@/layout/component/header.vue';
import Mains from '/@/layout/component/main.vue';
import { NextLoading } from '/@/utils/loading';
export default defineComponent({
name: 'layoutDefaults',
components: { Asides, Headers, Mains },
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutScrollbarRef = ref<any>('');
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
});
// 监听路由的变化
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrap$.scrollTop = 0;
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
NextLoading.done(600);
});
return {
isFixedHeader,
layoutScrollbarRef,
layoutMainRef,
};
},
});

View File

@ -1,17 +1,64 @@
<template>
<el-container class="layout-container flex-center layout-backtop">
<Headers />
<Mains />
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-container>
</template>
<script lang="ts">
import Headers from '/@/layout/component/header.vue';
import Mains from '/@/layout/component/main.vue';
import { defineAsyncComponent, defineComponent, ref, watch, nextTick, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
export default {
export default defineComponent({
name: 'layoutTransverse',
components: { Headers, Mains },
};
components: {
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
},
setup() {
const layoutMainRef = ref<any>('');
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutMainRef,
};
},
});
</script>

View File

@ -8,15 +8,12 @@
</template>
<script lang="ts">
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
import { defineAsyncComponent, computed, reactive, toRefs, onMounted, onUnmounted, defineComponent } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import Breadcrumb from '/@/layout/navBars/breadcrumb/breadcrumb.vue';
import User from '/@/layout/navBars/breadcrumb/user.vue';
import Logo from '/@/layout/logo/index.vue';
import Horizontal from '/@/layout/navMenu/horizontal.vue';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface IndexState {
@ -25,9 +22,13 @@ interface IndexState {
export default defineComponent({
name: 'layoutBreadcrumbIndex',
components: { Breadcrumb, User, Logo, Horizontal },
components: {
Breadcrumb: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue')),
User: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue')),
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
Horizontal: defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -52,7 +53,7 @@ export default defineComponent({
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
const resData = setSendClassicChildren(route.path);
proxy.mittBus.emit('setSendClassicChildren', resData);
mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(routesList.value);
}
@ -91,13 +92,13 @@ export default defineComponent({
// 页面加载时
onMounted(() => {
setFilterRoutes();
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
});
return {
setIsShowLogo,

View File

@ -1,26 +1,28 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
placeholder="菜单搜索:支持中文、路由路径"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"
>
<template #prefix>
<el-icon class="el-input__icon">
<ele-Search />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ item.meta.title }}
</div>
</template>
</el-autocomplete>
<el-dialog v-model="isShowSearch" destroy-on-close :show-close="false">
<template #footer>
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
placeholder="菜单搜索:支持中文、路由路径"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
:fit-input-width="true"
>
<template #prefix>
<el-icon class="el-input__icon">
<ele-Search />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ item.meta.title }}
</div>
</template>
</el-autocomplete>
</template>
</el-dialog>
</div>
</template>
@ -101,17 +103,12 @@ export default defineComponent({
else router.push(path);
closeSearch();
};
// input 失去焦点时
const onSearchBlur = () => {
closeSearch();
};
return {
layoutMenuAutocompleteRef,
openSearch,
closeSearch,
menuSearch,
onHandleSelect,
onSearchBlur,
...toRefs(state),
};
},
@ -120,15 +117,23 @@ export default defineComponent({
<style scoped lang="scss">
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {
box-shadow: unset !important;
border-radius: 0 !important;
background: rgba(0, 0, 0, 0.5);
.el-dialog__header,
.el-dialog__body {
display: none;
}
.el-dialog__footer {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -53vh;
}
}
:deep(.el-autocomplete) {
width: 560px;
position: absolute;
top: 100px;
top: 150px;
left: 50%;
transform: translateX(-50%);
}

View File

@ -96,6 +96,17 @@
></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单鼠标悬停预加载</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="getThemeConfig.isColumnsMenuHoverPreload"
size="small"
@change="onColumnsMenuHoverPreloadChange"
:disabled="getThemeConfig.layout !== 'columns'"
></el-switch>
</div>
</div>
<!-- 界面设置 -->
<el-divider content-position="left">界面设置</el-divider>
@ -399,7 +410,7 @@
</template>
<script lang="ts">
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
import { nextTick, onUnmounted, onMounted, defineComponent, computed, reactive, toRefs } from 'vue';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
@ -409,11 +420,11 @@ import { Local } from '/@/utils/storage';
import Watermark from '/@/utils/wartermark';
import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutBreadcrumbSeting',
setup() {
const { proxy } = <any>getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction();
@ -470,6 +481,10 @@ export default defineComponent({
setLocalThemeConfig();
}, 200);
};
// 2、分栏设置 ->
const onColumnsMenuHoverPreloadChange = () => {
setLocalThemeConfig();
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
setDispatchThemeConfig();
@ -483,7 +498,7 @@ export default defineComponent({
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
@ -499,12 +514,12 @@ export default defineComponent({
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
proxy.mittBus.emit('openOrCloseSortable');
mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
@ -556,7 +571,7 @@ export default defineComponent({
onBgColorPickerChange('columnsMenuBar');
onBgColorPickerChange('columnsMenuBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
// 关闭弹窗时,初始化变量。变量用于处理 layoutScrollbarRef.value.update()
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
@ -609,7 +624,7 @@ export default defineComponent({
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
proxy.mittBus.on('layoutMobileResize', (res: any) => {
mittBus.on('layoutMobileResize', (res: any) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
@ -632,7 +647,7 @@ export default defineComponent({
});
});
onUnmounted(() => {
proxy.mittBus.off('layoutMobileResize', () => {});
mittBus.off('layoutMobileResize', () => {});
});
return {
openDrawer,
@ -641,6 +656,7 @@ export default defineComponent({
onTopBarGradualChange,
onMenuBarGradualChange,
onColumnsMenuBarGradualChange,
onColumnsMenuHoverPreloadChange,
onThemeConfigChange,
onIsFixedHeaderChange,
onIsShowLogoChange,

View File

@ -60,7 +60,7 @@
</template>
<script lang="ts">
import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
import { defineAsyncComponent, ref, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
@ -68,14 +68,15 @@ import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session, Local } from '/@/utils/storage';
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/layout/navBars/breadcrumb/search.vue';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutBreadcrumbUser',
components: { UserNews, Search },
components: {
UserNews: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue')),
Search: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const router = useRouter();
const stores = useUserInfo();
const storesThemeConfig = useThemeConfig();
@ -110,7 +111,7 @@ export default defineComponent({
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
proxy.mittBus.emit('openSetingsDrawer');
mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path: string) => {
@ -161,27 +162,17 @@ export default defineComponent({
Local.remove('themeConfig');
themeConfig.value.globalComponentSize = size;
Local.set('themeConfig', themeConfig.value);
initComponentSize();
initI18nOrSize('globalComponentSize', 'disabledSize');
window.location.reload();
};
// 初始化全局组件大小
const initComponentSize = () => {
switch (Local.get('themeConfig').globalComponentSize) {
case 'large':
state.disabledSize = 'large';
break;
case 'default':
state.disabledSize = 'default';
break;
case 'small':
state.disabledSize = 'small';
break;
}
// 初始化组件大小/i18n
const initI18nOrSize = (value: string, attr: string) => {
(<any>state)[attr] = Local.get('themeConfig')[value];
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initComponentSize();
initI18nOrSize('globalComponentSize', 'disabledSize');
}
});
return {

View File

@ -6,15 +6,16 @@
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { defineAsyncComponent, computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default defineComponent({
name: 'layoutNavBars',
components: { BreadcrumbIndex, TagsView },
components: {
BreadcrumbIndex: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue')),
TagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
},
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);

View File

@ -9,6 +9,7 @@
:data-url="v.url"
:class="{ 'is-active': isActive(v) }"
@contextmenu.prevent="onContextmenu(v, $event)"
@mousedown="onMousedownMenu(v, $event)"
@click="onTagsClick(v, k)"
:ref="
(el) => {
@ -47,6 +48,7 @@
<script lang="ts">
import {
defineAsyncComponent,
toRefs,
reactive,
onMounted,
@ -56,7 +58,6 @@ import {
onBeforeUpdate,
onBeforeMount,
onUnmounted,
getCurrentInstance,
watch,
defineComponent,
} from 'vue';
@ -71,7 +72,7 @@ import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other';
import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface TagsViewState {
@ -104,9 +105,10 @@ interface CurrentContextmenu {
export default defineComponent({
name: 'layoutTagsView',
components: { Contextmenu },
components: {
Contextmenu: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/contextmenu.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const tagsRefs = ref<any[]>([]);
const scrollbarRef = ref();
const contextmenuRef = ref();
@ -237,13 +239,21 @@ export default defineComponent({
// 动态路由xxx/:id/:name"):参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) return false;
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) {
// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
addBrowserSetSession(state.tagsViewList);
return false;
}
item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath);
} else {
// 普通路由:参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v: any) => v.path === path)) return false;
if (state.tagsViewList.some((v: any) => v.path === path)) {
// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
addBrowserSetSession(state.tagsViewList);
return false;
}
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
}
if (!item) return false;
@ -258,12 +268,20 @@ export default defineComponent({
};
// 2、刷新当前 tagsView
const refreshCurrentTagsView = async (fullPath: string) => {
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === fullPath : v.url === fullPath));
if (item != null) {
await storesKeepALiveNames.delCachedView(item);
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
}
const decodeURIPath = decodeURI(fullPath);
let item: any = {};
state.tagsViewList.forEach((v: any) => {
v.transUrl = transUrlParams(v);
if (v.transUrl) {
if (v.transUrl === transUrlParams(v)) item = v;
} else {
if (v.path === decodeURIPath) item = v;
}
});
if (!item) return false;
await storesKeepALiveNames.delCachedView(item);
mittBus.emit('onTagsViewRefreshRouterView', fullPath);
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
};
// 3、关闭当前 tagsView如果是设置了固定的isAffix不可以关闭
const closeCurrentTagsView = (path: string) => {
@ -396,11 +414,31 @@ export default defineComponent({
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(v);
};
// 鼠标按下时,判断是鼠标中键就关闭当前 tasgview
const onMousedownMenu = (v: any, e: any) => {
if (!v.meta.isAffix && e.which === 2) {
const item = Object.assign({}, { contextMenuClickId: 1, ...v });
onCurrentContextmenuClick(item);
}
};
// 当前的 tagsView 项点击时
const onTagsClick = (v: any, k: number) => {
state.tagsRefsIndex = k;
router.push(v);
};
// 处理 url地址栏链接有参数时tagsview 右键菜单刷新功能失效问题
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
const transUrlParams = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
if (!params) return '';
let path = '';
for (let [key, value] of Object.entries(params)) {
if (v.meta.isDynamic) path += `/${value}`;
else path += `&${key}=${value}`;
}
// 判断是否是动态路由xxx/:id/:name"isDynamic
return v.meta.isDynamic ? `${v.path.split(':')[0]}${path.replace(/^\//, '')}` : `${v.path}${path.replace(/^&/, '?')}`;
};
// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
const setTagsViewHighlight = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
@ -412,13 +450,9 @@ export default defineComponent({
// 判断是否是动态路由xxx/:id/:name"
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
};
// 更新滚动条显示
const updateScrollbar = () => {
proxy.$refs.scrollbarRef.update();
};
// 鼠标滚轮滚动
const onHandleScroll = (e: any) => {
proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
scrollbarRef.value.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
};
// tagsView 横向滚动
const tagsViewmoveToCurrentTag = () => {
@ -435,7 +469,7 @@ export default defineComponent({
// 最后 li
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
// 当前滚动条的值
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
let scrollRefs = scrollbarRef.value.$refs.wrap$;
// 当前滚动条滚动宽度
let scrollS = scrollRefs.scrollWidth;
// 当前滚动条偏移宽度
@ -469,7 +503,7 @@ export default defineComponent({
}
}
// 更新滚动条,防止不出现
updateScrollbar();
scrollbarRef.value.update();
});
};
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
@ -520,15 +554,15 @@ export default defineComponent({
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏
proxy.mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
onCurrentContextmenuClick(data);
});
// 监听布局配置界面开启/关闭拖拽
proxy.mittBus.on('openOrCloseSortable', () => {
mittBus.on('openOrCloseSortable', () => {
initSortable();
});
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
proxy.mittBus.on('openShareTagsView', () => {
mittBus.on('openShareTagsView', () => {
if (getThemeConfig.value.isShareTagsView) {
router.push('/home');
state.tagsViewList = [];
@ -544,11 +578,11 @@ export default defineComponent({
// 页面卸载时
onUnmounted(() => {
// 取消非本页面调用监听
proxy.mittBus.off('onCurrentContextmenuClick', () => {});
mittBus.off('onCurrentContextmenuClick', () => {});
// 取消监听布局配置界面开启/关闭拖拽
proxy.mittBus.off('openOrCloseSortable', () => {});
mittBus.off('openOrCloseSortable', () => {});
// 取消监听布局配置开启 TagsView 共用
proxy.mittBus.off('openShareTagsView', () => {});
mittBus.off('openShareTagsView', () => {});
// 取消窗口 resize 监听
window.removeEventListener('resize', onSortableResize);
});
@ -583,6 +617,7 @@ export default defineComponent({
return {
isActive,
onContextmenu,
onMousedownMenu,
onTagsClick,
tagsRefs,
contextmenuRef,

View File

@ -17,7 +17,7 @@
{{ val.meta.title }}
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
</a>
@ -31,16 +31,19 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, nextTick, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import SubItem from '/@/layout/navMenu/subItem.vue';
import { verifyUrl } from '/@/utils/toolsValidate';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'navMenuHorizontal',
components: { SubItem },
components: {
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
},
props: {
menuList: {
type: Array,
@ -48,12 +51,13 @@ export default defineComponent({
},
},
setup(props) {
const { proxy } = <any>getCurrentInstance();
const elMenuHorizontalScrollRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
defaultActive: null,
});
@ -64,14 +68,14 @@ export default defineComponent({
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: any) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft + eventDelta / 4;
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = els.offsetLeft;
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = els.offsetLeft;
});
};
// 路由过滤递归函数
@ -109,6 +113,13 @@ export default defineComponent({
else state.defaultActive = path;
}
};
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
@ -124,12 +135,14 @@ export default defineComponent({
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
return {
elMenuHorizontalScrollRef,
menuLists,
onElMenuHorizontalScroll,
onALinkClick,
...toRefs(state),
};
},

View File

@ -14,7 +14,7 @@
<span>{{ val.meta.title }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
</a>
@ -26,6 +26,8 @@
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { verifyUrl } from '/@/utils/toolsValidate';
export default defineComponent({
name: 'navMenuSubItem',
@ -36,12 +38,21 @@ export default defineComponent({
},
},
setup(props) {
const router = useRouter();
// 获取父级菜单数据
const chils = computed(() => {
return <any>props.chil;
});
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
return {
chils,
onALinkClick,
};
},
});

View File

@ -22,7 +22,7 @@
<span>{{ val.meta.title }}</span>
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">{{ val.meta.title }}</a>
<a class="w100" @click.prevent="onALinkClick(val)">{{ val.meta.title }}</a>
</template>
</el-menu-item>
</template>
@ -31,15 +31,17 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import SubItem from '/@/layout/navMenu/subItem.vue';
import { verifyUrl } from '/@/utils/toolsValidate';
export default defineComponent({
name: 'navMenuVertical',
components: { SubItem },
components: {
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
},
props: {
menuList: {
type: Array,
@ -50,6 +52,7 @@ export default defineComponent({
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
@ -70,6 +73,13 @@ export default defineComponent({
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
// 设置菜单的收起/展开
watch(
themeConfig.value,
@ -94,6 +104,7 @@ export default defineComponent({
return {
menuLists,
getThemeConfig,
onALinkClick,
...toRefs(state),
};
},

View File

@ -1,65 +1,89 @@
<template>
<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="iframeLoading">
<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" ref="iframeDom" v-show="!iframeLoading"></iframe>
<div class="layout-padding layout-padding-unset layout-iframe">
<div class="layout-padding-auto layout-padding-view">
<div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white">
<transition-group :name="name" mode="out-in">
<iframe
:src="v.meta.isLink"
:key="v.path"
frameborder="0"
height="100%"
width="100%"
style="position: absolute"
:data-url="v.path"
v-show="getRoutePath === v.path"
ref="iframeRef"
/>
</transition-group>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { defineComponent, computed, watch, ref, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: 'layoutIfameView',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
name: 'layoutIframeView',
props: {
refreshKey: {
type: String,
default: () => '',
},
name: {
type: String,
default: () => 'slide-right',
},
list: {
type: Array,
default: () => [],
},
},
setup(props) {
const iframeRef = ref();
const route = useRoute();
const state = reactive({
iframeDom: null as HTMLIFrameElement | null,
iframeLoading: true,
iframeUrl: '',
// 处理 list 列表,当打开时,才进行加载
const setIframeList = computed(() => {
return (<any>props.list).filter((v: any) => v.meta.isIframeOpen);
});
// 初始化页面加载 loading
const initIframeLoad = () => {
state.iframeUrl = <any>route.meta.isLink;
nextTick(() => {
state.iframeLoading = true;
const iframe = state.iframeDom;
if (!iframe) return false;
iframe.onload = () => {
state.iframeLoading = false;
};
});
};
// 设置 iframe 的高度
const setIframeHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `1px`;
} else {
if (isTagsview) return `86px`;
else return `51px`;
}
// 获取 iframe 当前路由 path
const getRoutePath = computed(() => {
return route.path;
});
// 页面加载时
onMounted(() => {
initIframeLoad();
});
// 监听路由变化,多个 iframe 时使用
// 监听路由变化,初始化 iframe 数据,防止多个 iframe 时,切换不生效
watch(
() => route.path,
() => {
initIframeLoad();
() => route.fullPath,
(val) => {
const item: any = props.list.find((v: any) => v.path === val);
if (item && !item.meta.isIframeOpen) item.meta.isIframeOpen = true;
nextTick(() => {
if (!iframeRef.value) return false;
iframeRef.value.forEach((v: any) => {
if (v.dataset.url === val) {
v.onload = () => {
if (item && item.meta.isIframeOpen && item.meta.loading) item.meta.loading = false;
};
}
});
});
},
{
immediate: true,
}
);
// 监听 iframe refreshKey 变化,用于 tagsview 右键菜单刷新
watch(
() => props.refreshKey,
() => {},
{
deep: true,
}
);
return {
setIframeHeight,
...toRefs(state),
iframeRef,
setIframeList,
getRoutePath,
};
},
});

View File

@ -1,16 +1,22 @@
<template>
<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
<a :href="currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin">
{{ currentRouteMeta.title }}{{ currentRouteMeta.isLink }}
</a>
<div class="layout-padding layout-link-container">
<div class="layout-padding-auto layout-padding-view">
<div class="layout-link-warp">
<i class="layout-link-icon iconfont icon-xingqiu"></i>
<div class="layout-link-msg">页面 "{{ currentRouteMeta.title }}" 已在新窗口中打开</div>
<el-button class="mt30" round size="default" @click="onGotoFullPage">
<i class="iconfont icon-lianjie"></i>
<span>立即前往体验</span>
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
import { defineComponent, toRefs, reactive, watch } from 'vue';
import { useRoute, RouteMeta } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { verifyUrl } from '/@/utils/toolsValidate';
// 定义接口来定义对象的类型
interface LinkViewState {
@ -27,8 +33,6 @@ interface LinkViewRouteMeta extends RouteMeta {
export default defineComponent({
name: 'layoutLinkView',
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive<LinkViewState>({
currentRouteMeta: {
@ -36,12 +40,12 @@ export default defineComponent({
title: '',
},
});
// 设置 link 的高度
const setLinkHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsview) return `115px`;
else return `80px`;
});
// 立即前往
const onGotoFullPage = () => {
const { origin, pathname } = window.location;
if (verifyUrl(state.currentRouteMeta.isLink)) window.open(state.currentRouteMeta.isLink);
else window.open(`${origin}${pathname}#${state.currentRouteMeta.isLink}`);
};
// 监听路由的变化,设置内容
watch(
() => route.path,
@ -53,9 +57,57 @@ export default defineComponent({
}
);
return {
setLinkHeight,
onGotoFullPage,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-link-container {
.layout-link-warp {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i.layout-link-icon {
position: relative;
font-size: 100px;
color: var(--el-color-primary);
&::after {
content: '';
position: absolute;
left: 50px;
top: 0;
width: 15px;
height: 100px;
background: linear-gradient(
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(235, 255, 255, 0.5),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01)
);
transform: rotate(-15deg);
animation: toRight 5s linear infinite;
}
}
.layout-link-msg {
font-size: 12px;
color: var(--next-bg-topBarColor);
opacity: 0.7;
margin-top: 15px;
}
}
}
</style>

View File

@ -1,41 +1,52 @@
<template>
<div class="h100">
<div class="layout-parent">
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="getKeepAliveNames">
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
<component :is="Component" :key="refreshRouterViewKey" class="w100" v-show="!isIframePage" />
</keep-alive>
</transition>
</router-view>
<transition :name="setTransitionName" mode="out-in">
<Iframes class="w100" v-show="isIframePage" :refreshKey="iframeRefreshKey" :name="setTransitionName" :list="iframeList" />
</transition>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { defineAsyncComponent, computed, defineComponent, toRefs, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface ParentViewState {
refreshRouterViewKey: null | string;
refreshRouterViewKey: string;
iframeRefreshKey: string;
keepAliveNameList: string[];
iframeList: string[];
}
export default defineComponent({
name: 'layoutParentView',
components: {
Iframes: defineAsyncComponent(() => import('/@/layout/routerView/iframes.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const route = useRoute();
const router = useRouter();
const storesKeepAliveNames = useKeepALiveNames();
const storesThemeConfig = useThemeConfig();
const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<ParentViewState>({
refreshRouterViewKey: null,
refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
keepAliveNameList: [],
iframeList: [],
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
@ -45,20 +56,37 @@ export default defineComponent({
const getKeepAliveNames = computed(() => {
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
});
// 设置 iframe 显示/隐藏
const isIframePage = computed(() => {
return route.meta.isIframe;
});
// 获取 iframe 组件列表(未进行渲染)
const getIframeListRoutes = async () => {
router.getRoutes().forEach((v: any) => {
if (v.meta.isIframe) {
v.meta.isIframeOpen = false;
v.meta.loading = true;
state.iframeList.push({ ...v });
}
});
};
// 页面加载前,处理缓存,页面刷新时路由缓存处理
onBeforeMount(() => {
state.keepAliveNameList = keepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = null;
state.refreshRouterViewKey = '';
state.iframeRefreshKey = '';
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.iframeRefreshKey = fullPath;
state.keepAliveNameList = keepAliveNames.value;
});
});
});
// 页面加载时
onMounted(() => {
getIframeListRoutes();
// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
nextTick(() => {
@ -69,18 +97,24 @@ export default defineComponent({
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
mittBus.off('onTagsViewRefreshRouterView', () => {});
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = decodeURI(route.fullPath);
},
{
immediate: true,
}
);
return {
route,
setTransitionName,
getKeepAliveNames,
isIframePage,
...toRefs(state),
};
},

View File

@ -8,7 +8,6 @@ import other from '/@/utils/other';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import '/@/theme/index.scss';
import mitt from 'mitt';
const app = createApp(App);
@ -16,5 +15,3 @@ directive(app);
other.elSvg(app);
app.use(pinia).use(router).use(ElementPlus).mount('#app');
app.config.globalProperties.mittBus = mitt();

View File

@ -81,6 +81,8 @@ export function setCacheTagsViewRoutes() {
*/
export function setFilterRouteEnd() {
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
// notFoundAndNoPower 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
// 关联问题 No match found for location with path 'xxx'
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
return filterRouteEnd;
}

View File

@ -63,6 +63,8 @@ export async function frontEndsResetRoute() {
*/
export function setFilterRouteEnd() {
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
// notFoundAndNoPower 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
// 关联问题 No match found for location with path 'xxx'
filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), ...notFoundAndNoPower];
return filterRouteEnd;
}

View File

@ -7,7 +7,7 @@ import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import { staticRoutes } from '/@/router/route';
import { staticRoutes, notFoundAndNoPower } from '/@/router/route';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
@ -32,7 +32,12 @@ const { isRequestRoutes } = themeConfig.value;
*/
export const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
/**
* 说明:
* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
* 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
*/ routes: [...notFoundAndNoPower, ...staticRoutes],
});
/**
@ -107,13 +112,12 @@ router.beforeEach(async (to, from, next) => {
if (isRequestRoutes) {
// 后端控制路由:路由数据初始化,防止刷新时丢失
await initBackEndControlRoutes();
// 动态添加路由:防止非首页刷新时跳转回首页的问题
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
next({ ...to, replace: true });
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
next({ path: to.path });
} else {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
await initFrontEndControlRoutes();
next({ ...to, replace: true });
next({ path: to.path });
}
} else {
next();

View File

@ -4,7 +4,7 @@ import { RouteRecordRaw } from 'vue-router';
* 路由meta对象参数说明
* meta: {
* title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化)
* isLink 是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空`
* isLink 是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空 2、isIframe:false`
* isHide 是否隐藏此路由
* isKeepAlive 是否缓存组件状态
* isAffix 是否固定在 tagsView 栏上

View File

@ -52,6 +52,7 @@ export interface ThemeConfigState {
columnsMenuBar: string;
columnsMenuBarColor: string;
isColumnsMenuBarColorGradual: boolean;
isColumnsMenuHoverPreload: boolean;
isCollapse: boolean;
isUniqueOpened: boolean;
isFixedHeader: boolean;
@ -82,6 +83,7 @@ export interface ThemeConfigState {
isRequestRoutes: boolean;
globalTitle: string;
globalViceTitle: string;
globalViceTitleMsg: string;
globalI18n: string;
globalComponentSize: string;
}

View File

@ -19,8 +19,7 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
this.keepAliveNames = data;
},
async addCachedView(view: any) {
if (this.cachedViews.includes(view.name)) return;
if (view.meta.isKeepAlive) this.cachedViews.push(view.name);
if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
},
async delCachedView(view: any) {
const index = this.cachedViews.indexOf(view.name);

View File

@ -47,6 +47,8 @@ export const useThemeConfig = defineStore('themeConfig', {
columnsMenuBarColor: '#e6e6e6',
// 是否开启分栏菜单背景颜色渐变
isColumnsMenuBarColorGradual: false,
// 是否开启分栏菜单鼠标悬停预加载(预览菜单)
isColumnsMenuHoverPreload: false,
/**
* 界面设置
@ -54,7 +56,7 @@ export const useThemeConfig = defineStore('themeConfig', {
// 是否开启菜单水平折叠效果
isCollapse: false,
// 是否开启菜单手风琴效果
isUniqueOpened: false,
isUniqueOpened: true,
// 是否开启固定 Header
isFixedHeader: false,
// 初始化变量,用于更新菜单 el-scrollbar 的高度,请勿删除
@ -88,15 +90,15 @@ export const useThemeConfig = defineStore('themeConfig', {
// 是否开启 TagsView 共用
isShareTagsView: false,
// 是否开启 Footer 底部版权信息
isFooter: false,
isFooter: true,
// 是否开启灰色模式
isGrayscale: false,
// 是否开启色弱模式
isInvert: false,
// 是否开启水印
isWartermark: false,
isWartermark: true,
// 水印文案
wartermarkText: 'small@小柒',
wartermarkText: 'vue-next-admin',
/**
* 其它设置
@ -132,6 +134,8 @@ export const useThemeConfig = defineStore('themeConfig', {
globalTitle: 'vue-next-admin',
// 网站副标题(登录页顶部文字)
globalViceTitle: 'vueNextAdmin',
// 网站副标题(登录页顶部文字)
globalViceTitleMsg: '专注、免费、开源、维护、解疑',
// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
globalI18n: 'zh-cn',
// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'

View File

@ -66,7 +66,7 @@ export const useUserInfo = defineStore('userInfo', {
// 存储用户信息到浏览器缓存
Session.set('userInfo', userInfos);
resolve(userInfos);
}, 3000);
}, 0);
});
},
},

View File

@ -46,6 +46,14 @@ body,
.layout-container {
width: 100%;
height: 100%;
.layout-pd {
padding: 15px !important;
}
.layout-flex {
display: flex;
flex-direction: column;
flex: 1;
}
.layout-aside {
background: var(--next-bg-menuBar);
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
@ -61,24 +69,63 @@ body,
}
.layout-header {
padding: 0 !important;
height: auto !important;
}
.layout-main {
padding: 0 !important;
overflow: hidden;
width: 100%;
background-color: var(--next-bg-main-color);
display: flex;
flex-direction: column;
// 内层 el-scrollbar样式用于界面高度自适应main.vue
.layout-main-scroll {
@extend .layout-flex;
.layout-parent {
@extend .layout-flex;
position: relative;
}
}
}
// 用于界面高度自适应
.layout-padding {
@extend .layout-pd;
position: absolute;
left: 0;
top: 0;
height: 100%;
overflow: hidden;
@extend .layout-flex;
&-auto {
height: inherit;
@extend .layout-flex;
}
&-view {
background: var(--el-color-white);
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid var(--el-border-color-light, #ebeef5);
overflow: hidden;
}
}
// 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
.layout-padding-unset {
padding: 0 !important;
&-view {
border-radius: 0 !important;
border: none !important;
}
}
// 用于设置 iframe loading 时的高度loading 垂直居中显示)
.layout-iframe {
.el-loading-parent--relative {
height: 100%;
}
}
.el-scrollbar {
width: 100%;
}
// 此字段多次用到,建议不删除,如需修改,请重写覆盖样式
.layout-view-bg-white {
background: var(--el-color-white);
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid var(--el-border-color-light, #ebeef5);
}
.layout-el-aside-br-color {
border-right: 1px solid var(--el-border-color-light, #ebeef5);
}
@ -122,10 +169,6 @@ body,
z-index: 9999998;
animation: error-img 0.3s;
}
.layout-scrollbar {
@extend .el-scrollbar;
padding: 15px;
}
.layout-mian-height-50 {
height: calc(100vh - 50px);
}

View File

@ -92,3 +92,56 @@
opacity: 0;
}
}
/* 登录页动画
------------------------------- */
@keyframes loginLeft {
0% {
left: -100%;
}
50%,
100% {
left: 100%;
}
}
@keyframes loginTop {
0% {
top: -100%;
}
50%,
100% {
top: 100%;
}
}
@keyframes loginRight {
0% {
right: -100%;
}
50%,
100% {
right: 100%;
}
}
@keyframes loginBottom {
0% {
bottom: -100%;
}
50%,
100% {
bottom: 100%;
}
}
/* 左右左 link.vue
------------------------------- */
@keyframes toRight {
0% {
left: -5px;
}
50% {
left: 100%;
}
100% {
left: -5px;
}
}

View File

@ -233,4 +233,9 @@
border-color: var(--el-border-color-lighter) !important;
}
}
// loading
.el-loading-mask {
background-color: var(--next-bg-main) !important;
}
}

View File

@ -44,6 +44,10 @@
margin-bottom: 18px !important;
}
}
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
.el-form-item .el-form-item__label .el-icon {
margin-right: 0px;
}
}
/* Alert 警告
@ -259,17 +263,24 @@
.el-scrollbar__bar {
z-index: 4;
}
/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
.el-scrollbar__wrap {
max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
max-height: 100%;
}
.el-select-dropdown .el-scrollbar__wrap {
overflow-x: scroll !important;
}
/*修复Select 选择器高度问题*/
.el-select-dropdown__wrap {
max-height: 274px !important; /*修复Select 选择器高度问题*/
max-height: 274px !important;
}
/*修复Cascader 级联选择器高度问题*/
.el-cascader-menu__wrap.el-scrollbar__wrap {
height: 204px !important; /*修复Cascader 级联选择器高度问题*/
height: 204px !important;
}
/*用于界面高度自适应main.vue区分 scrollbar__view防止其它使用 scrollbar 的地方出现滚动条消失*/
.layout-container-view .el-scrollbar__view {
height: 100%;
}
/* Drawer 抽屉

View File

@ -13,6 +13,7 @@
margin-left: 0 !important;
}
.el-form-item {
// 响应式表单时,登录页需要重新处理
display: unset !important;
}
}

View File

@ -1,32 +1,23 @@
@import './index.scss';
/* 页面宽度小于992px
/* 页面宽度小于1200px
------------------------------- */
@media screen and (max-width: $lg) {
@media screen and (max-width: $lg) and (min-width: $xs) {
.login-container {
.login-icon-group {
&::before {
content: '';
height: 70% !important;
transition: all 0.3s ease;
}
&::after {
content: '';
width: 100px !important;
height: 200px !important;
transition: all 0.3s ease;
.login-left {
.login-left-img {
top: 90% !important;
left: 12% !important;
width: 30% !important;
height: 18% !important;
}
}
}
}
/* 页面宽度小于992px
------------------------------- */
@media screen and (max-width: $md) {
.login-content {
right: unset !important;
left: 50% !important;
transform: translate(-50%, -50%) translate3d(0, 0, 0) !important;
.login-right {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
@ -34,19 +25,34 @@
------------------------------- */
@media screen and (max-width: $xs) {
.login-container {
.login-icon-group {
display: none !important;
.login-left {
display: none;
}
.login-content {
.login-right {
width: 100% !important;
height: 100% !important;
padding: 20px 0 !important;
border-radius: 0 !important;
box-shadow: unset !important;
border: none !important;
}
.el-form-item {
display: flex !important;
.login-right-warp {
width: 100% !important;
height: 100% !important;
border: none !important;
.login-right-warp-mian {
.el-form-item {
display: flex !important;
}
.login-right-warp-main-title {
font-size: 20px !important;
}
}
.login-right-warp-one {
&::after {
right: 0 !important;
}
}
.login-right-warp-two {
&::before {
bottom: 1px !important;
}
}
}
}
}
}
@ -55,9 +61,14 @@
------------------------------- */
@media screen and (max-width: $us) {
.login-container {
.login-content-title {
font-size: 18px !important;
transition: all 0.3s ease;
.login-right {
.login-right-warp {
.login-right-warp-mian {
.login-right-warp-main-title {
font-size: 18px !important;
}
}
}
}
}
}

View File

@ -1,7 +1,7 @@
/* wangeditor富文本编辑器
/* wangeditor 富文本编辑器
------------------------------- */
.editor-container {
z-index: 9999;
z-index: 10; // 用于 wangeditor 点击全屏时
.w-e-toolbar {
border: 1px solid var(--el-border-color-light, #ebeef5) !important;
border-bottom: 1px solid var(--el-border-color-light, #ebeef5) !important;

13
src/types/mitt.ts Normal file
View File

@ -0,0 +1,13 @@
// mitt 事件类型定义
export type MittType = {
openSetingsDrawer?: string; // 打开布局设置弹窗
restoreDefault?: string; // 分栏布局,鼠标移入、移出数据显示
setSendColumnsChildren?: string; // 分栏布局,鼠标移入、移出菜单数据传入到 navMenu 下的菜单中
setSendClassicChildren?: string; // 经典布局,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
getBreadcrumbIndexSetFilterRoutes?: string; // 布局设置弹窗,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
layoutMobileResize?: object; // 浏览器窗口改变时,用于适配移动端界面显示
openOrCloseSortable?: string; // 布局设置弹窗,开启 TagsView 拖拽
openShareTagsView?: string; // 布局设置弹窗,开启 TagsView 共用
onTagsViewRefreshRouterView?: any; // tagsview 刷新界面
onCurrentContextmenuClick?: any; // tagsview 右键菜单每项点击时
};

View File

@ -32,11 +32,13 @@ export const NextLoading = {
window.nextLoading = true;
},
// 移除 loading
done: () => {
done: (time: number = 0) => {
nextTick(() => {
setTimeout(() => {
window.nextLoading = false;
const el = <HTMLElement>document.querySelector('.loading-next');
el?.parentNode?.removeChild(el);
});
});
}, time);
},
};

9
src/utils/mitt.ts Normal file
View File

@ -0,0 +1,9 @@
// https://www.npmjs.com/package/mitt
import mitt, { Emitter } from 'mitt';
import { MittType } from '/@/types/mitt';
// 类型
const emitter: Emitter<MittType> = mitt<MittType>();
// 导出
export default emitter;

View File

@ -1,39 +1,34 @@
<template>
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">您未被授权没有操作权限~</div>
<div class="left-item-animation left-item-msg">联系方式加QQ群探讨 665452019</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onSetAuth">重新授权</el-button>
<div class="error layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">您未被授权没有操作权限~</div>
<div class="left-item-animation left-item-msg">联系方式加QQ群探讨 665452019</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" size="default" round @click="onSetAuth">重新授权</el-button>
</div>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { defineComponent } from 'vue';
import { Session } from '/@/utils/storage';
export default defineComponent({
name: '401',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const onSetAuth = () => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
// 清除缓存/token等
@ -41,19 +36,8 @@ export default defineComponent({
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
};
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
onSetAuth,
initTagViewHeight,
};
},
});
@ -62,8 +46,6 @@ export default defineComponent({
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;

View File

@ -1,56 +1,40 @@
<template>
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">地址输入错误请重新输入地址~</div>
<div class="left-item-animation left-item-msg">您可以先检查网址然后重新输入或给我们反馈问题</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onGoHome">返回首页</el-button>
<div class="error layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">地址输入错误重新输入地址~</div>
<div class="left-item-animation left-item-msg">您可以先检查网址然后重新输入或给我们反馈问题</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" size="default" round @click="onGoHome">返回首页</el-button>
</div>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: '404',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const router = useRouter();
const onGoHome = () => {
router.push('/');
};
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
onGoHome,
initTagViewHeight,
};
},
});
@ -59,8 +43,6 @@ export default defineComponent({
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;

View File

@ -1,5 +1,5 @@
<template>
<div class="home-container">
<div class="home-container layout-pd">
<el-row :gutter="15" class="home-card-one mb15">
<el-col
:xs="24"

View File

@ -1,7 +1,7 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1"
><el-input type="text" placeholder="用户名 admin 或不输均为 common" v-model="ruleForm.userName" clearable autocomplete="off">
<el-form-item class="login-animation1">
<el-input text placeholder="用户名 admin 或不输均为 common" v-model="ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-User /></el-icon>
</template>
@ -24,7 +24,7 @@
</el-form-item>
<el-form-item class="login-animation3">
<el-col :span="15">
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<el-input text maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
@ -32,11 +32,11 @@
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button class="login-content-code">1234</el-button>
<el-button class="login-content-code" v-waves>1234</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation4">
<el-button type="primary" class="login-content-submit" round @click="onSignIn" :loading="loading.signIn">
<el-button type="primary" class="login-content-submit" round v-waves @click="onSignIn" :loading="loading.signIn">
<span> </span>
</el-button>
</el-form-item>
@ -116,6 +116,7 @@ export default defineComponent({
state.loading.signIn = true;
const signInText = '欢迎回来!';
ElMessage.success(`${currentTimeInfo}${signInText}`);
// 添加 loading防止第一次进入界面时出现短暂空白
NextLoading.start();
};
return {

View File

@ -1,7 +1,7 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1">
<el-input type="text" placeholder="请输入手机号" v-model="ruleForm.userName" clearable autocomplete="off">
<el-input text placeholder="请输入手机号" v-model="ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<i class="iconfont icon-dianhua el-input__icon"></i>
</template>
@ -9,7 +9,7 @@
</el-form-item>
<el-form-item class="login-animation2">
<el-col :span="15">
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<el-input text maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
@ -17,11 +17,11 @@
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button class="login-content-code">获取验证码</el-button>
<el-button v-waves class="login-content-code">获取验证码</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation3">
<el-button round type="primary" class="login-content-submit">
<el-button round type="primary" v-waves class="login-content-submit">
<span> </span>
</el-button>
</el-form-item>

View File

@ -1,12 +1,15 @@
<template>
<div class="login-scan-container">
<div ref="qrcodeRef"></div>
<div class="font12 mt20 login-msg">打开手机扫一扫快速登录/注册</div>
<div class="font12 mt20 login-msg">
<i class="iconfont icon-saoyisao mr5"></i>
<span>打开手机扫一扫快速登录/注册</span>
</div>
</div>
</template>
<script lang="ts">
import { ref, defineComponent, onMounted } from 'vue';
import { defineComponent, ref, onMounted, nextTick } from 'vue';
import QRCode from 'qrcodejs2-fixes';
export default defineComponent({
@ -15,20 +18,24 @@ export default defineComponent({
const qrcodeRef = ref<HTMLElement | null>(null);
// 初始化生成二维码
const initQrcode = () => {
(qrcodeRef.value as HTMLElement).innerHTML = '';
new QRCode(qrcodeRef.value, {
text: `https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi`,
width: 260,
height: 260,
colorDark: '#000000',
colorLight: '#ffffff',
nextTick(() => {
(<HTMLElement>qrcodeRef.value).innerHTML = '';
new QRCode(qrcodeRef.value, {
text: `https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi`,
width: 260,
height: 260,
colorDark: '#000000',
colorLight: '#ffffff',
});
});
};
// 页面加载时
onMounted(() => {
initQrcode();
});
return { qrcodeRef };
return {
qrcodeRef,
};
},
});
</script>
@ -41,7 +48,7 @@ export default defineComponent({
animation-fill-mode: forwards;
}
.login-scan-container {
padding: 20px;
padding: 0 20px 20px;
display: flex;
flex-direction: column;
text-align: center;
@ -51,6 +58,9 @@ export default defineComponent({
margin: auto;
}
.login-msg {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-placeholder);
@extend .login-scan-animation;
animation-delay: 0.2s;

View File

@ -1,29 +1,41 @@
<template>
<div class="login-container">
<div class="login-icon-group">
<div class="login-icon-group-title">
<div class="login-container flex">
<div class="login-left">
<div class="login-left-logo">
<img :src="logoMini" />
<div class="login-icon-group-title-text font25">{{ getThemeConfig.globalViceTitle }}</div>
</div>
<img :src="loginIconTwo" class="login-icon-group-icon" />
</div>
<div class="login-content">
<div class="login-content-main">
<h4 class="login-content-title ml15">{{ getThemeConfig.globalTitle }}后台模板</h4>
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName">
<el-tab-pane label="账号密码登录" name="account">
<Account />
</el-tab-pane>
<el-tab-pane label="手机号登录" name="mobile">
<Mobile />
</el-tab-pane>
</el-tabs>
<div class="login-left-logo-text">
<span>{{ getThemeConfig.globalViceTitle }}</span>
<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
</div>
<Scan v-if="isScan" />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
<div class="login-left-img">
<img :src="loginMain" />
</div>
<img :src="loginBg" class="login-left-waves" />
</div>
<div class="login-right flex">
<div class="login-right-warp flex-margin">
<span class="login-right-warp-one"></span>
<span class="login-right-warp-two"></span>
<div class="login-right-warp-mian">
<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您</div>
<div class="login-right-warp-main-form">
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName">
<el-tab-pane label="账号密码登录" name="account">
<Account />
</el-tab-pane>
<el-tab-pane label="手机号登录" name="mobile">
<Mobile />
</el-tab-pane>
</el-tabs>
</div>
<Scan v-if="isScan" />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
</div>
</div>
</div>
@ -31,29 +43,25 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, onMounted } from 'vue';
import { defineAsyncComponent, defineComponent, onMounted, reactive, toRefs, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg';
import loginIconTwo from '/@/assets/login-icon-two.svg';
import { NextLoading } from '/@/utils/loading';
import Account from '/@/views/login/component/account.vue';
import Mobile from '/@/views/login/component/mobile.vue';
import Scan from '/@/views/login/component/scan.vue';
// 定义接口来定义对象的类型
interface LoginState {
tabsActiveName: string;
isScan: boolean;
}
import logoMini from '/@/assets/logo-mini.svg';
import loginMain from '/@/assets/login-main.svg';
import loginBg from '/@/assets/login-bg.svg';
export default defineComponent({
name: 'loginIndex',
components: { Account, Mobile, Scan },
components: {
Account: defineAsyncComponent(() => import('/@/views/login/component/account.vue')),
Mobile: defineAsyncComponent(() => import('/@/views/login/component/mobile.vue')),
Scan: defineAsyncComponent(() => import('/@/views/login/component/scan.vue')),
},
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<LoginState>({
const state = reactive({
tabsActiveName: 'account',
isScan: false,
});
@ -67,7 +75,8 @@ export default defineComponent({
});
return {
logoMini,
loginIconTwo,
loginBg,
loginMain,
getThemeConfig,
...toRefs(state),
};
@ -77,122 +86,176 @@ export default defineComponent({
<style scoped lang="scss">
.login-container {
width: 100%;
height: 100%;
position: relative;
background: var(--el-color-white);
.login-icon-group {
width: 100%;
height: 100%;
.login-left {
flex: 1;
position: relative;
.login-icon-group-title {
background-color: rgba(211, 239, 255, 1);
margin-right: 100px;
.login-left-logo {
display: flex;
align-items: center;
position: absolute;
top: 50px;
left: 80px;
display: flex;
align-items: center;
z-index: 1;
animation: logoAnimation 0.3s ease;
img {
width: 30px;
height: 30px;
width: 52px;
height: 52px;
}
&-text {
padding-left: 15px;
color: var(--el-color-primary);
.login-left-logo-text {
display: flex;
flex-direction: column;
span {
margin-left: 10px;
font-size: 28px;
color: #26a59a;
}
.login-left-logo-text-msg {
font-size: 12px;
color: #32a99e;
}
}
}
&::before {
content: '';
.login-left-img {
position: absolute;
bottom: 0;
left: 0;
width: 60%;
overflow: hidden;
height: 80%;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='1200' height='770' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cg%3E%3Cpath id='svg_1' d='M58.4 47.77C104.6 59.51 135.26 67.37 162.11 78.04C188.97 88.72 226.33 102.69 265.92 123.55C305.51 144.4 366.96 167.09 441.43 121.52C515.9 75.95 546.48 61.01 577.69 46.27C608.9 31.53 625.86 23.69 680.26 12.28C734.65 0.87 837.29 10.7 867.29 21.8C897.29 32.9 935.51 51.9 962.21 95.45C988.9 139.01 972.91 177.36 951.37 221.39C929.83 265.43 883.49 306 890.44 337.33C897.4 368.66 974.73 412.18 974.73 411.47C974.73 412.18 1066.36 457.62 1106.36 491.06C1146.36 524.5 1178.8 563.36 1184.03 579.63C1189.26 595.9 1200.4 622.49 1181.55 676.88C1162.71 731.26 1127.16 764.32 1115.31 778.64C1103.45 792.96 5.34 783.61 4.32 784.63C3.3 785.65 -172.34 2.38 1.13 35.04L58.4 47.77L58.4 47.77Z' fill='%23409eff'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background: var(--el-color-primary-light-5);
transition: all 0.3s ease;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 52%;
img {
width: 100%;
height: 100%;
animation: error-num 0.6s ease;
}
}
&::after {
content: '';
width: 150px;
height: 300px;
.login-left-waves {
position: absolute;
right: 0;
top: 0;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='150' height='300' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cg%3E%3Cpath id='svg_1' d='M-0.56 -0.28C41.94 36.17 67.73 18.94 93.33 33.96C118.93 48.98 107.58 73.56 101.94 89.76C96.29 105.96 50.09 217.83 47.87 231.18C45.64 244.52 46.02 255.2 64.4 270.05C82.79 284.91 121.99 292.31 111.98 289.81C101.97 287.32 153.96 301.48 151.83 299.9C149.69 298.32 149.98 -1.36 149.71 -1.18C149.98 -1.36 -43.06 -36.74 -0.56 -0.28L-0.56 -0.28Z' fill='%23409eff'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background: var(--el-color-primary-light-5);
transition: all 0.3s ease;
}
&-icon {
width: 60%;
height: 70%;
position: absolute;
left: 0;
bottom: 0;
right: -100px;
}
}
.login-content {
width: 500px;
padding: 20px;
position: absolute;
right: 200px;
top: 50%;
transform: translateY(-50%) translate3d(0, 0, 0);
background-color: var(--el-color-white);
border: 5px solid var(--el-color-primary-light-8);
border-radius: 5px;
overflow: hidden;
z-index: 1;
height: 460px;
.login-content-main {
margin: 0 auto;
width: 80%;
.login-content-title {
color: var(--el-text-color-primary);
font-weight: 500;
font-size: 22px;
text-align: center;
letter-spacing: 4px;
margin: 15px 0 30px;
white-space: nowrap;
z-index: 5;
position: relative;
transition: all 0.3s ease;
}
}
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 50px;
.login-right {
width: 700px;
.login-right-warp {
border: 1px solid var(--el-color-primary-light-3);
border-radius: 3px;
width: 500px;
height: 500px;
position: relative;
overflow: hidden;
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-text-color-primary);
&-delta {
background-color: var(--el-color-white);
.login-right-warp-one,
.login-right-warp-two {
position: absolute;
width: 35px;
height: 70px;
z-index: 2;
top: 2px;
right: 21px;
background: var(--el-color-white);
transform: rotate(-45deg);
display: block;
width: inherit;
height: inherit;
&::before,
&::after {
content: '';
position: absolute;
z-index: 1;
}
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
.login-right-warp-one {
&::before {
filter: hue-rotate(0deg);
top: 0px;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, var(--el-color-primary));
animation: loginLeft 3s linear infinite;
}
&::after {
filter: hue-rotate(60deg);
top: -100%;
right: 2px;
width: 3px;
height: 100%;
background: linear-gradient(180deg, transparent, var(--el-color-primary));
animation: loginTop 3s linear infinite;
animation-delay: 0.7s;
}
}
i {
width: 47px;
height: 50px;
display: inline-block;
font-size: 48px;
position: absolute;
right: 2px;
top: -1px;
.login-right-warp-two {
&::before {
filter: hue-rotate(120deg);
bottom: 2px;
right: -100%;
width: 100%;
height: 3px;
background: linear-gradient(270deg, transparent, var(--el-color-primary));
animation: loginRight 3s linear infinite;
animation-delay: 1.4s;
}
&::after {
filter: hue-rotate(300deg);
bottom: -100%;
left: 0px;
width: 3px;
height: 100%;
background: linear-gradient(360deg, transparent, var(--el-color-primary));
animation: loginBottom 3s linear infinite;
animation-delay: 2.1s;
}
}
.login-right-warp-mian {
display: flex;
flex-direction: column;
height: 100%;
.login-right-warp-main-title {
height: 130px;
line-height: 130px;
font-size: 27px;
text-align: center;
letter-spacing: 3px;
animation: logoAnimation 0.3s ease;
animation-delay: 0.3s;
}
.login-right-warp-main-form {
flex: 1;
padding: 0 50px 50px;
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 50px;
overflow: hidden;
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-color-primary);
&-delta {
position: absolute;
width: 35px;
height: 70px;
z-index: 2;
top: 2px;
right: 21px;
background: var(--el-color-white);
transform: rotate(-45deg);
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
}
i {
width: 47px;
height: 50px;
display: inline-block;
font-size: 48px;
position: absolute;
right: 1px;
top: 0px;
}
}
}
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<div class="system-dept-container">
<el-card shadow="hover">
<div class="system-dept-container layout-padding">
<el-card shadow="hover" class="layout-padding-auto">
<div class="system-dept-search mb15">
<el-input size="default" placeholder="请输入部门名称" style="max-width: 180px"> </el-input>
<el-button size="default" type="primary" class="ml10">
@ -52,10 +52,8 @@
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
import { defineAsyncComponent, ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import AddDept from '/@/views/system/dept/component/addDept.vue';
import EditDept from '/@/views/system/dept/component/editDept.vue';
// 定义接口来定义对象的类型
interface TableDataRow {
@ -81,7 +79,10 @@ interface TableDataState {
export default defineComponent({
name: 'systemDept',
components: { AddDept, EditDept },
components: {
AddDept: defineAsyncComponent(() => import('/@/views/system/dept/component/addDept.vue')),
EditDept: defineAsyncComponent(() => import('/@/views/system/dept/component/editDept.vue')),
},
setup() {
const addDeptRef = ref();
const editDeptRef = ref();

View File

@ -1,6 +1,6 @@
<template>
<div class="system-dic-container">
<el-card shadow="hover">
<div class="system-dic-container layout-padding">
<el-card shadow="hover" class="layout-padding-auto">
<div class="system-user-search mb15">
<el-input size="default" placeholder="请输入字典名称" style="max-width: 180px"> </el-input>
<el-button size="default" type="primary" class="ml10">
@ -55,10 +55,8 @@
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
import { defineAsyncComponent, toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import AddDic from '/@/views/system/dic/component/addDic.vue';
import EditDic from '/@/views/system/dic/component/editDic.vue';
// 定义接口来定义对象的类型
interface TableDataRow {
@ -82,7 +80,10 @@ interface TableDataState {
export default defineComponent({
name: 'systemDic',
components: { AddDic, EditDic },
components: {
AddDic: defineAsyncComponent(() => import('/@/views/system/dic/component/addDic.vue')),
EditDic: defineAsyncComponent(() => import('/@/views/system/dic/component/editDic.vue')),
},
setup() {
const addDicRef = ref();
const editDicRef = ref();

View File

@ -141,15 +141,17 @@
</template>
<script lang="ts">
import { reactive, toRefs, onMounted, defineComponent } from 'vue';
import { defineAsyncComponent, reactive, toRefs, onMounted, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import IconSelector from '/@/components/iconSelector/index.vue';
import { i18n } from '/@/i18n/index';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
export default defineComponent({
name: 'systemAddMenu',
components: { IconSelector },
components: {
IconSelector: defineAsyncComponent(() => import('/@/components/iconSelector/index.vue')),
},
setup() {
const stores = useRoutesList();
const { routesList } = storeToRefs(stores);
@ -183,7 +185,7 @@ export default defineComponent({
const getMenuData = (routes: any) => {
const arr: any = [];
routes.map((val: any) => {
val['title'] = val.meta.title;
val['title'] = i18n.global.t(val.meta.title);
val['id'] = Math.random();
arr.push({ ...val });
if (val.children) getMenuData(val.children);

View File

@ -141,15 +141,17 @@
</template>
<script lang="ts">
import { reactive, toRefs, onMounted, defineComponent } from 'vue';
import { defineAsyncComponent, reactive, toRefs, onMounted, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import IconSelector from '/@/components/iconSelector/index.vue';
import { i18n } from '/@/i18n/index';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
export default defineComponent({
name: 'systemEditMenu',
components: { IconSelector },
components: {
IconSelector: defineAsyncComponent(() => import('/@/components/iconSelector/index.vue')),
},
setup() {
const stores = useRoutesList();
const { routesList } = storeToRefs(stores);
@ -183,7 +185,7 @@ export default defineComponent({
const getMenuData = (routes: any) => {
const arr: any = [];
routes.map((val: any) => {
val['title'] = val.meta.title;
val['title'] = i18n.global.t(val.meta.title);
val['id'] = Math.random();
arr.push({ ...val });
if (val.children) getMenuData(val.children);

View File

@ -1,5 +1,5 @@
<template>
<div class="system-menu-container">
<div class="system-menu-container layout-pd">
<el-card shadow="hover">
<div class="system-menu-search mb15">
<el-input size="default" placeholder="请输入菜单名称" style="max-width: 180px"> </el-input>
@ -59,17 +59,18 @@
</template>
<script lang="ts">
import { ref, toRefs, reactive, computed, defineComponent } from 'vue';
import { defineAsyncComponent, ref, toRefs, reactive, computed, defineComponent } from 'vue';
import { RouteRecordRaw } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import AddMenu from '/@/views/system/menu/component/addMenu.vue';
import EditMenu from '/@/views/system/menu/component/editMenu.vue';
export default defineComponent({
name: 'systemMenu',
components: { AddMenu, EditMenu },
components: {
AddMenu: defineAsyncComponent(() => import('/@/views/system/menu/component/addMenu.vue')),
EditMenu: defineAsyncComponent(() => import('/@/views/system/menu/component/editMenu.vue')),
},
setup() {
const stores = useRoutesList();
const { routesList } = storeToRefs(stores);

View File

@ -1,8 +1,8 @@
<template>
<div class="system-role-container">
<el-card shadow="hover">
<div class="system-role-container layout-padding">
<div class="system-role-padding layout-padding-auto layout-padding-view">
<div class="system-user-search mb15">
<el-input size="default" placeholder="请输入角色名称" style="max-width: 180px"> </el-input>
<el-input v-model="tableData.param.search" size="default" placeholder="请输入角色名称" style="max-width: 180px"> </el-input>
<el-button size="default" type="primary" class="ml10">
<el-icon>
<ele-Search />
@ -51,17 +51,15 @@
:total="tableData.total"
>
</el-pagination>
</el-card>
</div>
<AddRole ref="addRoleRef" />
<EditRole ref="editRoleRef" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
import { defineAsyncComponent, toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import AddRole from '/@/views/system/role/component/addRole.vue';
import EditRole from '/@/views/system/role/component/editRole.vue';
// 定义接口来定义对象的类型
interface TableData {
@ -78,6 +76,7 @@ interface TableDataState {
total: number;
loading: boolean;
param: {
search: string;
pageNum: number;
pageSize: number;
};
@ -86,7 +85,10 @@ interface TableDataState {
export default defineComponent({
name: 'systemRole',
components: { AddRole, EditRole },
components: {
AddRole: defineAsyncComponent(() => import('/@/views/system/role/component/addRole.vue')),
EditRole: defineAsyncComponent(() => import('/@/views/system/role/component/editRole.vue')),
},
setup() {
const addRoleRef = ref();
const editRoleRef = ref();
@ -96,6 +98,7 @@ export default defineComponent({
total: 0,
loading: false,
param: {
search: '',
pageNum: 1,
pageSize: 10,
},
@ -104,7 +107,7 @@ export default defineComponent({
// 初始化表格数据
const initTableData = () => {
const data: Array<TableData> = [];
for (let i = 0; i < 2; i++) {
for (let i = 0; i < 20; i++) {
data.push({
roleName: i === 0 ? '超级管理员' : '普通用户',
roleSign: i === 0 ? 'admin' : 'common',
@ -162,3 +165,14 @@ export default defineComponent({
},
});
</script>
<style scoped lang="scss">
.system-role-container {
.system-role-padding {
padding: 15px;
.el-table {
flex: 1;
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="system-user-container">
<el-card shadow="hover">
<div class="system-user-container layout-padding">
<el-card shadow="hover" class="layout-padding-auto">
<div class="system-user-search mb15">
<el-input size="default" placeholder="请输入用户名称" style="max-width: 180px"> </el-input>
<el-button size="default" type="primary" class="ml10">
@ -59,10 +59,8 @@
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
import { defineAsyncComponent, toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import AddUer from '/@/views/system/user/component/addUser.vue';
import EditUser from '/@/views/system/user/component/editUser.vue';
// 定义接口来定义对象的类型
interface TableDataRow {
@ -93,7 +91,10 @@ interface TableDataState {
export default defineComponent({
name: 'systemUser',
components: { AddUer, EditUser },
components: {
AddUer: defineAsyncComponent(() => import('/@/views/system/user/component/addUser.vue')),
EditUser: defineAsyncComponent(() => import('/@/views/system/user/component/editUser.vue')),
},
setup() {
const addUserRef = ref();
const editUserRef = ref();
@ -175,3 +176,17 @@ export default defineComponent({
},
});
</script>
<style scoped lang="scss">
.system-user-container {
:deep(.el-card__body) {
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
.el-table {
flex: 1;
}
}
}
</style>

View File

@ -36,9 +36,9 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
entryFileNames: `assets/[name].${new Date().getTime()}.js`,
chunkFileNames: `assets/[name].${new Date().getTime()}.js`,
assetFileNames: `assets/[name].${new Date().getTime()}.[ext]`,
entryFileNames: `assets/[name].[hash].js`,
chunkFileNames: `assets/[name].[hash].js`,
assetFileNames: `assets/[name].[hash].[ext]`,
compact: true,
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],