'admin-22.11.16:同步vue-next-admin-template分支v2.3.0版本'

This commit is contained in:
lyt 2022-11-16 21:08:43 +08:00
parent d2e952e5cf
commit 22186136f5
61 changed files with 2674 additions and 1897 deletions

View File

@ -1,20 +1,26 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin-template-js不带国际化 更新日志</a>
🎉🎉🔥 `vue-next-admin-template-js` 基于 vue-next-admin-template v0.2.2 版本) vue3.x 、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
🎉🎉🔥 `vue-next-admin-template-js` 基于 vue-next-admin-template v2.3.0 版本) vue3.x 、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 2.3.0
`2022.11.16`
- 🎉 同步 vue-next-admin-template 基础版本(不带国际化) 分支 v2.3.0 版本内容,具体查看 master 分支 CHANGELOG.md
## 2.2.0
`2022.07.11`
- 🎉 同步 [vue-next-admin-template-js](https://gitee.com/lyt-top/vue-next-admin/tree/vue-next-admin-template/) 分支 v2.2.0 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
- 🎉 同步 vue-next-admin-template 基础版本(不带国际化) 分支 v2.2.0 版本内容,具体查看 master 分支 CHANGELOG.md
## 2.1.1
- 🎉 同步 [vue-next-admin-template-js](https://gitee.com/lyt-top/vue-next-admin/tree/vue-next-admin-template/) 分支 v2.1.1 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
- 🎉 同步 vue-next-admin-template 基础版本(不带国际化) 分支 v2.1.1 版本内容,具体查看 master 分支 CHANGELOG.md
## 2.0.2
- 🎉 同步 vue-next-admin-template 基础版本(不带国际化) 分支 v2.0.2 版本内容,具体查看 master CHANGELOG.md
- 🎉 同步 vue-next-admin-template 基础版本(不带国际化) 分支 v2.0.2 版本内容,具体查看 master 分支 CHANGELOG.md
## 0.1.0

View File

@ -20,7 +20,7 @@
<p>&nbsp;</p>
</div>
#### 🌈 介绍 基础版 js不带国际化基于 vue-next-admin-template V2.2.0 版
#### 🌈 介绍 基础版 js不带国际化基于 vue-next-admin-template V2.3.0 版setup 语法糖
基于 vue3.x + CompositionAPI + vite + element plus + vue-router-next适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
@ -71,9 +71,13 @@ cnpm run dev
cnpm run build
```
#### 📚 开发文档
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
#### 💯 学习交流加 QQ 群
- 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!
- 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!微信群由于只有 `7天有效` 就不放这里了。
- 查看开发文档、<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">vue-next-admin</a> 开发文档正在编写中...
- 群号码:
1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>

2671
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-js",
"version": "2.2.0",
"version": "2.3.0",
"description": "vue3 vite next admin template js setup",
"author": "lyt_20201208",
"license": "MIT",
@ -10,34 +10,34 @@
"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": {
"@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",
"@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",
"unplugin-auto-import": "^0.9.3",
"vite": "^2.9.14",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"unplugin-auto-import": "^0.11.4",
"vite": "^3.2.4",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-eslint-parser": "^9.0.3"
"vue-eslint-parser": "^9.1.0"
},
"browserslist": [
"> 1%",

View File

@ -15,11 +15,12 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
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';
import mittBus from './utils/mitt';
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
const Setings = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue'));
const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue'));
const { proxy } = getCurrentInstance();
const setingsRef = ref();
const route = useRoute();
const stores = useTagsViewRoutes();
@ -29,10 +30,6 @@ const { themeConfig } = storeToRefs(storesThemeConfig);
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();
});
//
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
//
onBeforeMount(() => {
// icon
@ -44,8 +41,8 @@ onBeforeMount(() => {
onMounted(() => {
nextTick(() => {
//
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
mittBus.on('openSetingsDrawer', () => {
setingsRef.value.openDrawer();
});
//
if (Local.get('themeConfig')) {
@ -60,7 +57,7 @@ onMounted(() => {
});
// /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

@ -62,7 +62,7 @@
</div>
</template>
<script lang="ts">
<script>
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
import initIconfont from '/@/utils/getStyleSheets';

View File

@ -8,7 +8,7 @@
<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>
<script lang="ts">
<script>
import { computed, defineComponent } from 'vue';
export default defineComponent({

View File

@ -15,10 +15,12 @@ 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';
const { proxy } = getCurrentInstance();
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
const Vertical = defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue'));
const layoutAsideScrollbarRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
@ -97,14 +99,13 @@ const initMenuFixed = (clientWidth) => {
const onAsideEnterLeave = (bool) => {
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
@ -123,22 +124,22 @@ watch(
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// (proxy.mittBus.off('setSendColumnsChildren))
// (mittBus.off('setSendColumnsChildren))
// 使
proxy.mittBus.on('setSendColumnsChildren', (res) => {
mittBus.on('setSendColumnsChildren', (res) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res) => {
mittBus.on('setSendClassicChildren', (res) => {
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) => {
mittBus.on('layoutMobileResize', (res) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});

View File

@ -50,10 +50,10 @@ import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt';
const columnsAsideOffsetTopRefs = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = getCurrentInstance();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
@ -83,11 +83,12 @@ const onColumnsAsideMenuClick = (v, k) => {
};
//
const onColumnsAsideMenuMouseenter = (v, k) => {
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);
};
@ -96,7 +97,7 @@ const onColumnsAsideMenuMouseleave = async () => {
await stores.setColumnsNavHover(false);
// store.state.routesList
setTimeout(() => {
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
}, 100);
};
//
@ -111,7 +112,7 @@ const setFilterRoutes = () => {
const resData = 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) => {
@ -156,11 +157,11 @@ watch(
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));
}
},
{
@ -171,19 +172,19 @@ watch(
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));
});
</script>
@ -194,6 +195,16 @@ onBeforeRouteUpdate((to) => {
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%;
@ -203,6 +214,9 @@ onBeforeRouteUpdate((to) => {
cursor: pointer;
position: relative;
z-index: 1;
&:hover {
@extend .layout-columns-hover;
}
.columns-vertical {
margin: auto;
.columns-vertical-title {
@ -230,16 +244,6 @@ onBeforeRouteUpdate((to) => {
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,23 +1,15 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<el-header class="layout-header" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script setup name="layoutHeader">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import NavBarsIndex from '/@/layout/navBars/index.vue';
const NavBarsIndex = defineAsyncComponent(() => import('/@/layout/navBars/index.vue'));
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';
});
</script>

View File

@ -1,82 +1,50 @@
<template>
<el-main class="layout-main">
<el-scrollbar
ref="layoutScrollbarRef"
:class="{
'layout-scrollbar':
(!isClassicOrTransverse && !state.currentRouteMeta.isLink && !state.currentRouteMeta.isIframe) ||
(!isClassicOrTransverse && currentRouteMeta.isLink && !state.currentRouteMeta.isIframe),
}"
>
<LayoutParentView
:style="{
padding: !isClassicOrTransverse || (state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe) ? '0' : '15px',
transition: 'padding 0.3s ease-in-out',
}"
/>
<Footer 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 setup name="layoutMain">
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 Footer from '/@/layout/footer/index.vue';
const { proxy } = getCurrentInstance();
const LayoutParentView = defineAsyncComponent(() => import('/@/layout/routerView/parent.vue'));
const LayoutFooter = defineAsyncComponent(() => import('/@/layout/footer/index.vue'));
const layoutMainScrollbarRef = ref('');
const route = useRoute();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive({
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();
});
//
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,
}
);
//
defineExpose({
layoutMainScrollbarRef,
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="layout-footer mt15" v-show="state.isDelayFooter">
<div class="layout-footer pb15">
<div class="layout-footer-warp">
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">版权所有深圳市xxx软件科技有限公司</div>
@ -7,22 +7,7 @@
</div>
</template>
<script setup name="layoutFooter">
import { onBeforeRouteUpdate } from 'vue-router';
const state = reactive({
isDelayFooter: true,
});
// footer
onBeforeRouteUpdate(() => {
setTimeout(() => {
state.isDelayFooter = false;
setTimeout(() => {
state.isDelayFooter = true;
}, 800);
}, 0);
});
</script>
<script setup name="layoutFooter"></script>
<style scoped lang="scss">
.layout-footer {
@ -32,7 +17,7 @@ onBeforeRouteUpdate(() => {
margin: auto;
color: var(--el-text-color-secondary);
text-align: center;
animation: logoAnimation 0.3s ease-in-out;
animation: error-num 0.3s ease;
}
}
</style>

View File

@ -1,8 +1,5 @@
<template>
<Defaults v-if="themeConfig.layout === 'defaults'" />
<Classic v-else-if="themeConfig.layout === 'classic'" />
<Transverse v-else-if="themeConfig.layout === 'transverse'" />
<Columns v-else-if="themeConfig.layout === 'columns'" />
<component :is="layouts[themeConfig.layout]"></component>
</template>
<script setup name="layout">
@ -10,13 +7,15 @@ import { defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
const Defaults = defineAsyncComponent(() => import('/@/layout/main/defaults.vue'));
const Classic = defineAsyncComponent(() => import('/@/layout/main/classic.vue'));
const Transverse = defineAsyncComponent(() => import('/@/layout/main/transverse.vue'));
const Columns = defineAsyncComponent(() => import('/@/layout/main/columns.vue'));
const layouts = {
defaults: defineAsyncComponent(() => import('/@/layout/main/defaults.vue')),
classic: defineAsyncComponent(() => import('/@/layout/main/classic.vue')),
transverse: defineAsyncComponent(() => import('/@/layout/main/transverse.vue')),
columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
};
const { proxy } = getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// ()
@ -25,12 +24,12 @@ const onLayoutResize = () => {
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

@ -65,7 +65,6 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import { formatDate } from '/@/utils/formatTime';
import { Local } from '/@/utils/storage';
const { proxy } = getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const layoutLockScreenInputRef = ref();
@ -127,7 +126,7 @@ const onEnd = () => {
//
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
state.querySelectorEl = layoutLockScreenDateRef.value;
});
};
//

View File

@ -1,25 +1,65 @@
<template>
<el-container class="layout-container flex-center">
<Header />
<LayoutHeader />
<el-container class="layout-mian-height-50">
<Aside />
<LayoutAside />
<div class="flex-center layout-backtop">
<TagsView v-if="themeConfig.isTagsview" />
<Main />
<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 setup name="layoutClassic">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
const LayoutTagsView = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue'));
const layoutMainRef = ref('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 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();
});
</script>

View File

@ -1,32 +1,66 @@
<template>
<el-container class="layout-container">
<ColumnsAside />
<div class="layout-columns-warp">
<Aside />
<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
</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 setup name="layoutColumns">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import ColumnsAside from '/@/layout/component/columnsAside.vue';
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
const ColumnsAside = defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue'));
const layoutScrollbarRef = ref('');
const layoutMainRef = ref('');
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();
});
</script>

View File

@ -1,37 +1,66 @@
<template>
<el-container class="layout-container">
<Aside />
<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
<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 setup name="layoutDefaults">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import { NextLoading } from '/@/utils/loading';
const { proxy } = getCurrentInstance();
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
const layoutScrollbarRef = ref('');
const layoutMainRef = ref('');
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);
});
</script>

View File

@ -1,12 +1,53 @@
<template>
<el-container class="layout-container flex-center layout-backtop">
<Header />
<Main />
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-container>
</template>
<script setup name="layoutTransverse">
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
const layoutMainRef = ref('');
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();
});
</script>

View File

@ -11,12 +11,13 @@
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';
const Breadcrumb = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue'));
const User = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue'));
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
const Horizontal = defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue'));
const { proxy } = getCurrentInstance();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -41,7 +42,7 @@ const setFilterRoutes = () => {
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);
}
@ -80,13 +81,13 @@ const setSendClassicChildren = (path) => {
//
onMounted(() => {
setFilterRoutes();
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
//
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
});
</script>

View File

@ -1,26 +1,28 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="state.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="state.isShowSearch" destroy-on-close :show-close="false">
<template #footer>
<el-autocomplete
v-model="state.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>
@ -83,10 +85,6 @@ const onHandleSelect = (item) => {
else router.push(path);
closeSearch();
};
// input
const onSearchBlur = () => {
closeSearch();
};
//
defineExpose({
@ -97,14 +95,21 @@ defineExpose({
<style scoped lang="scss">
.layout-search-dialog {
: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>
@ -408,8 +419,8 @@ 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';
const { proxy } = getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction();
@ -466,6 +477,10 @@ const setGraduaFun = (el, bool, color) => {
setLocalThemeConfig();
}, 200);
};
// 2 ->
const onColumnsMenuHoverPreloadChange = () => {
setLocalThemeConfig();
};
// 3 -->
const onThemeConfigChange = () => {
setDispatchThemeConfig();
@ -479,7 +494,7 @@ const onIsFixedHeaderChange = () => {
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4 --> Logo
const onIsShowLogoChange = () => {
@ -495,12 +510,12 @@ const onIsBreadcrumbChange = () => {
};
// 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 --> /
@ -551,7 +566,7 @@ const initLayoutChangeFun = () => {
onBgColorPickerChange('columnsMenuBar');
onBgColorPickerChange('columnsMenuBarColor');
};
// proxy.$refs.layoutScrollbarRef.update()
// layoutScrollbarRef.value.update()
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
@ -604,7 +619,7 @@ onMounted(() => {
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
//
proxy.mittBus.on('layoutMobileResize', (res) => {
mittBus.on('layoutMobileResize', (res) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
@ -627,7 +642,7 @@ onMounted(() => {
});
});
onUnmounted(() => {
proxy.mittBus.off('layoutMobileResize', () => {});
mittBus.off('layoutMobileResize', () => {});
});
//

View File

@ -70,10 +70,11 @@ import { Session, Local } from '/@/utils/storage';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { useThemeConfig } from '/@/stores/themeConfig';
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/layout/navBars/breadcrumb/search.vue';
import mittBus from '/@/utils/mitt';
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
const { proxy } = getCurrentInstance();
const router = useRouter();
const stores = useUserInfo();
const storesThemeConfig = useThemeConfig();
@ -108,7 +109,7 @@ const onScreenfullClick = () => {
};
// icon
const onLayoutSetingClick = () => {
proxy.mittBus.emit('openSetingsDrawer');
mittBus.emit('openSetingsDrawer');
};
//
const onHandleCommandClick = (path) => {
@ -159,28 +160,18 @@ const onComponentSizeChange = (size) => {
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;
break;
}
// /i18n
const initI18nOrSize = (value, attr) => {
state[attr] = Local.get('themeConfig')[value];
};
//
onMounted(() => {
if (Local.get('themeConfig')) {
initComponentSize();
initI18nOrSize('globalComponentSize', 'disabledSize');
}
});
</script>

View File

@ -8,8 +8,9 @@
<script setup name="layoutNavBars">
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';
const BreadcrumbIndex = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue'));
const TagsView = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue'));
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) => {
@ -57,9 +58,10 @@ 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';
const Contextmenu = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/contextmenu.vue'));
const { proxy } = getCurrentInstance();
const tagsRefs = ref([]);
const scrollbarRef = ref();
const contextmenuRef = ref();
@ -188,13 +190,21 @@ const addTagsView = (path, to) => {
// xxx/:id/:name" tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v) => v.path === to.meta.isDynamicPath)) return false;
if (state.tagsViewList.some((v) => v.path === to.meta.isDynamicPath)) {
// () tagsViewList
addBrowserSetSession(state.tagsViewList);
return false;
}
item = state.tagsViewRoutesList.find((v) => 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) => v.path === path)) return false;
if (state.tagsViewList.some((v) => v.path === path)) {
// () tagsViewList
addBrowserSetSession(state.tagsViewList);
return false;
}
item = state.tagsViewRoutesList.find((v) => v.path === path);
}
if (!item || (item.meta.isLink && !item.meta.isIframe)) return false;
@ -208,12 +218,20 @@ const addTagsView = (path, to) => {
};
// 2 tagsView
const refreshCurrentTagsView = async (fullPath) => {
const item = state.tagsViewList.find((v) => (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 = {};
state.tagsViewList.forEach((v) => {
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 tagsViewisAffix
const closeCurrentTagsView = (path) => {
@ -346,11 +364,31 @@ const onContextmenu = (v, e) => {
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(v);
};
// tasgview
const onMousedownMenu = (v, e) => {
if (!v.meta.isAffix && e.which === 2) {
const item = Object.assign({}, { contextMenuClickId: 1, ...v });
onCurrentContextmenuClick(item);
}
};
// tagsView
const onTagsClick = (v, k) => {
state.tagsRefsIndex = k;
router.push(v);
};
// urltagsview
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
const transUrlParams = (v) => {
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) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
@ -362,13 +400,9 @@ const setTagsViewHighlight = (v) => {
// xxx/:id/:name"
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
};
//
const updateScrollbar = () => {
proxy.$refs.scrollbarRef.update();
};
//
const onHandleScroll = (e) => {
proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
scrollbarRef.value.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
};
// tagsView
const tagsViewmoveToCurrentTag = () => {
@ -385,7 +419,7 @@ const tagsViewmoveToCurrentTag = () => {
// li
let liLast = tagsRefs.value[tagsRefs.value.length - 1];
//
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
let scrollRefs = scrollbarRef.value.$refs.wrap$;
//
let scrollS = scrollRefs.scrollWidth;
//
@ -419,7 +453,7 @@ const tagsViewmoveToCurrentTag = () => {
}
}
//
updateScrollbar();
scrollbarRef.value.update();
});
};
// tagsView tagsView
@ -470,15 +504,15 @@ onBeforeMount(() => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 0 1 2 3 4
proxy.mittBus.on('onCurrentContextmenuClick', (data) => {
mittBus.on('onCurrentContextmenuClick', (data) => {
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 = [];
@ -494,11 +528,11 @@ onBeforeMount(() => {
//
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);
});

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>
@ -35,7 +35,10 @@ import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { onBeforeRouteUpdate } from 'vue-router';
import SubItem from '/@/layout/navMenu/subItem.vue';
import { verifyUrl } from '/@/utils/toolsValidate';
import mittBus from '/@/utils/mitt';
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
const props = defineProps({
menuList: {
@ -43,12 +46,13 @@ const props = defineProps({
default: () => [],
},
});
const { proxy } = 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,
});
@ -59,14 +63,14 @@ const menuLists = computed(() => {
//
const onElMenuHorizontalScroll = (e) => {
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 = 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;
});
};
//
@ -104,6 +108,13 @@ const setCurrentRouterHighlight = (currentRoute) => {
else state.defaultActive = path;
}
};
//
const onALinkClick = (val) => {
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);
@ -119,7 +130,7 @@ onBeforeRouteUpdate((to) => {
// tagsView
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
</script>

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>
@ -25,15 +25,25 @@
</template>
<script setup name="navMenuSubItem">
import { useRouter } from 'vue-router';
import { verifyUrl } from '/@/utils/toolsValidate';
const props = defineProps({
chil: {
type: Array,
default: () => [],
},
});
const router = useRouter();
//
const chils = computed(() => {
return props.chil;
});
//
const onALinkClick = (val) => {
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}`);
};
</script>

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>
@ -34,7 +34,9 @@
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { onBeforeRouteUpdate } from 'vue-router';
import SubItem from '/@/layout/navMenu/subItem.vue';
import { verifyUrl } from '/@/utils/toolsValidate';
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
const props = defineProps({
menuList: {
@ -45,6 +47,7 @@ const props = defineProps({
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,
@ -75,6 +78,13 @@ watch(
immediate: true,
}
);
//
const onALinkClick = (val) => {
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}`);
};
//
onMounted(() => {
state.defaultActive = setParentHighlight(route);

View File

@ -1,54 +1,77 @@
<template>
<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="state.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 setup name="layoutIfameView">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
const props = defineProps({
refreshKey: {
type: String,
default: () => '',
},
name: {
type: String,
default: () => 'slide-right',
},
list: {
type: Array,
default: () => [],
},
});
const iframeRef = ref();
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const state = reactive({
iframeLoading: true,
iframeUrl: '',
// list
const setIframeList = computed(() => {
return props.list.filter((v) => v.meta.isIframeOpen);
});
// loading
const initIframeLoad = () => {
state.iframeUrl = 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 = props.list.find((v) => v.path === val);
if (item && !item.meta.isIframeOpen) item.meta.isIframeOpen = true;
nextTick(() => {
if (!iframeRef.value) return false;
iframeRef.value.forEach((v) => {
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,
}
);
</script>

View File

@ -1,30 +1,34 @@
<template>
<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
<a :href="state.currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin">
{{ state.currentRouteMeta.title }}{{ state.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">页面 "{{ state.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 setup name="layoutLinkView">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { verifyUrl } from '/@/utils/toolsValidate';
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
currentRouteMeta: {
isLink: '',
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,
@ -36,3 +40,51 @@ watch(
}
);
</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,12 +1,15 @@
<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="state.refreshRouterViewKey" class="w100" />
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
</keep-alive>
</transition>
</router-view>
<transition :name="setTransitionName" mode="out-in">
<Iframes class="w100" v-show="isIframePage" :refreshKey="state.iframeRefreshKey" :name="setTransitionName" :list="state.iframeList" />
</transition>
</div>
</template>
@ -15,16 +18,21 @@ import { storeToRefs } from 'pinia';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
const Iframes = defineAsyncComponent(() => import('/@/layout/routerView/iframes.vue'));
const { proxy } = 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({
refreshRouterViewKey: null,
refreshRouterViewKey: '', // iframe tagsview
iframeRefreshKey: '', // iframe tagsview
keepAliveNameList: [],
iframeList: [],
});
//
const setTransitionName = computed(() => {
@ -34,19 +42,36 @@ const setTransitionName = computed(() => {
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) => {
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) => {
mittBus.on('onTagsViewRefreshRouterView', (fullPath) => {
state.keepAliveNameList = keepAliveNames.value.filter((name) => 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(() => {
@ -57,13 +82,17 @@ onMounted(() => {
});
//
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,
}
);
</script>

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

@ -80,6 +80,8 @@ export function setCacheTagsViewRoutes() {
*/
export function setFilterRouteEnd() {
let filterRouteEnd = 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 = 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,13 @@ const { isRequestRoutes } = themeConfig.value;
*/
export const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
/**
* 说明
* 1notFoundAndNoPower 默认添加 404401 界面防止一直提示 No match found for location with path 'xxx'
* 2backEnd.ts(后端控制路由)frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404401 界面
* 防止 404401 不在 layout 布局中不设置的话404401 界面将全屏显示
*/
routes: [...notFoundAndNoPower, ...staticRoutes],
});
/**
@ -107,13 +113,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

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

View File

@ -18,8 +18,7 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
this.keepAliveNames = data;
},
async addCachedView(view) {
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) {
const index = this.cachedViews.indexOf(view.name);

View File

@ -46,6 +46,8 @@ export const useThemeConfig = defineStore('themeConfig', {
columnsMenuBarColor: '#e6e6e6',
// 是否开启分栏菜单背景颜色渐变
isColumnsMenuBarColorGradual: false,
// 是否开启分栏菜单鼠标悬停预加载(预览菜单)
isColumnsMenuHoverPreload: false,
/**
* 界面设置
@ -53,7 +55,7 @@ export const useThemeConfig = defineStore('themeConfig', {
// 是否开启菜单水平折叠效果
isCollapse: false,
// 是否开启菜单手风琴效果
isUniqueOpened: false,
isUniqueOpened: true,
// 是否开启固定 Header
isFixedHeader: false,
// 初始化变量,用于更新菜单 el-scrollbar 的高度,请勿删除
@ -87,15 +89,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',
/**
* 其它设置
@ -131,6 +133,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

@ -65,7 +65,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

@ -16,9 +16,6 @@
font-size: 12px !important;
margin-right: 5px;
}
.el-button.is-circle i.el-icon {
margin-right: unset !important;
}
/* Input 输入框InputNumber 计数器
------------------------------- */
@ -47,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 警告
@ -262,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;

View File

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

8
src/utils/mitt.js Normal file
View File

@ -0,0 +1,8 @@
// https://www.npmjs.com/package/mitt
import mitt from 'mitt';
// 类型
const emitter = mitt();
// 导出
export default emitter;

View File

@ -1,45 +1,30 @@
<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 setup name="401">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { Session } from '/@/utils/storage';
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
//
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
//
const onSetAuth = () => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
@ -53,8 +38,6 @@ const onSetAuth = () => {
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;

View File

@ -1,45 +1,29 @@
<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 setup name="404">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
const router = useRouter();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
//
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
//
const onGoHome = () => {
router.push('/');
@ -49,8 +33,6 @@ const onGoHome = () => {
<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

@ -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="state.loading.signIn">
<el-button type="primary" class="login-content-submit" round v-waves @click="onSignIn" :loading="state.loading.signIn">
<span> </span>
</el-button>
</el-form-item>
@ -54,10 +54,10 @@ import { Session } from '/@/utils/storage';
import { formatAxis } from '/@/utils/formatTime';
import { NextLoading } from '/@/utils/loading';
const route = useRoute();
const router = useRouter();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
isShowPassword: false,
ruleForm: {
@ -75,9 +75,10 @@ const currentTime = computed(() => {
});
//
const onSignIn = async () => {
state.loading.signIn = true;
// token
Session.set('token', Math.random().toString(36).substr(0));
// `/src/stores/userInfo.js`
// `/src/stores/userInfo.ts`
Cookies.set('userName', state.ruleForm.userName);
if (!themeConfig.value.isRequestRoutes) {
// 2
@ -85,6 +86,7 @@ const onSignIn = async () => {
signInSuccess();
} else {
// isRequestRoutes true
// router No match found for location with path "/"
await initBackEndControlRoutes();
// initBackEndControlRoutes signInSuccess
signInSuccess();
@ -95,7 +97,6 @@ const signInSuccess = () => {
//
let currentTimeInfo = currentTime.value;
//
// router No match found for location with path "/"
// /
if (route.query?.redirect) {
router.push({
@ -110,6 +111,7 @@ const signInSuccess = () => {
state.loading.signIn = true;
const signInText = '欢迎回来!';
ElMessage.success(`${currentTimeInfo}${signInText}`);
// loading
NextLoading.start();
};
</script>

View File

@ -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,23 +1,28 @@
<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 setup name="loginScan">
import QRCode from 'qrcodejs2-fixes';
const qrcodeRef = ref('');
const qrcodeRef = ref(null);
//
const initQrcode = () => {
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',
nextTick(() => {
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',
});
});
};
//
@ -34,7 +39,7 @@ onMounted(() => {
animation-fill-mode: forwards;
}
.login-scan-container {
padding: 20px;
padding: 0 20px 20px;
display: flex;
flex-direction: column;
text-align: center;
@ -44,6 +49,9 @@ onMounted(() => {
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,44 +1,58 @@
<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="!state.isScan">
<el-tabs v-model="state.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="state.isScan" />
<div class="login-content-main-sacn" @click="state.isScan = !state.isScan">
<i class="iconfont" :class="state.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="!state.isScan">
<el-tabs v-model="state.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="state.isScan" />
<div class="login-content-main-sacn" @click="state.isScan = !state.isScan">
<i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup name="login">
<script setup name="loginIndex">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
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';
import logoMini from '/@/assets/logo-mini.svg';
import loginIconTwo from '/@/assets/login-icon-two.svg';
import loginMain from '/@/assets/login-main.svg';
import loginBg from '/@/assets/login-bg.svg';
const Account = defineAsyncComponent(() => import('/@/views/login/component/account.vue'));
const Mobile = defineAsyncComponent(() => import('/@/views/login/component/mobile.vue'));
const Scan = defineAsyncComponent(() => import('/@/views/login/component/scan.vue'));
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -58,122 +72,176 @@ onMounted(() => {
<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">
@ -53,8 +53,9 @@
<script setup name="systemDept">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddDept from '/@/views/system/dept/component/addDept.vue';
import EditDept from '/@/views/system/dept/component/editDept.vue';
const AddDept = defineAsyncComponent(() => import('/@/views/system/dept/component/addDept.vue'));
const EditDept = defineAsyncComponent(() => import('/@/views/system/dept/component/editDept.vue'));
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">
@ -56,8 +56,9 @@
<script setup name="systemDic">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddDic from '/@/views/system/dic/component/addDic.vue';
import EditDic from '/@/views/system/dic/component/editDic.vue';
const AddDic = defineAsyncComponent(() => import('/@/views/system/dic/component/addDic.vue'));
const EditDic = defineAsyncComponent(() => import('/@/views/system/dic/component/editDic.vue'));
const addDicRef = ref();
const editDicRef = ref();

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>
@ -62,8 +62,9 @@
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { ElMessageBox, ElMessage } from 'element-plus';
import AddMenu from '/@/views/system/menu/component/addMenu.vue';
import EditMenu from '/@/views/system/menu/component/editMenu.vue';
const AddMenu = defineAsyncComponent(() => import('/@/views/system/menu/component/addMenu.vue'));
const EditMenu = defineAsyncComponent(() => import('/@/views/system/menu/component/editMenu.vue'));
const addMenuRef = ref();
const editMenuRef = ref();

View File

@ -1,20 +1,6 @@
<template>
<div class="system-role-container">
<el-form :inline="true" :model="state.formInline" class="demo-form-inline" size="default">
<el-form-item label="Approved by">
<el-input v-model="state.formInline.user" placeholder="Approved by" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="state.formInline.region" placeholder="Activity zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary">Query</el-button>
</el-form-item>
</el-form>
<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-button size="default" type="primary" class="ml10">
@ -65,16 +51,17 @@
:total="state.tableData.total"
>
</el-pagination>
</el-card>
<AddRole ref="addRoleRef" />
<EditRole ref="editRoleRef" />
<AddRole ref="addRoleRef" />
<EditRole ref="editRoleRef" />
</div>
</div>
</template>
<script setup name="systemRole">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddRole from '/@/views/system/role/component/addRole.vue';
import EditRole from '/@/views/system/role/component/editRole.vue';
const AddRole = defineAsyncComponent(() => import('/@/views/system/role/component/addRole.vue'));
const EditRole = defineAsyncComponent(() => import('/@/views/system/role/component/editRole.vue'));
const addRoleRef = ref();
const editRoleRef = ref();
@ -142,3 +129,14 @@ onMounted(() => {
initTableData();
});
</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">
@ -60,8 +60,9 @@
<script setup name="systemUser">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddUer from '/@/views/system/user/component/addUser.vue';
import EditUser from '/@/views/system/user/component/editUser.vue';
const AddUer = defineAsyncComponent(() => import('/@/views/system/user/component/addUser.vue'));
const EditUser = defineAsyncComponent(() => import('/@/views/system/user/component/editUser.vue'));
const addUserRef = ref();
const editUserRef = ref();