'admin-21.08.01:更新修复请查看CHANGELOG.md文件或查看标签'

This commit is contained in:
lyt-Top 2021-08-01 18:30:30 +08:00
parent 1dd6b653e7
commit 28edd79b1c
29 changed files with 523 additions and 383 deletions

2
.env
View File

@ -4,5 +4,5 @@ VITE_PORT = 8888
# open 运行 npm run dev 时自动打开浏览器 # open 运行 npm run dev 时自动打开浏览器
VITE_OPEN = false VITE_OPEN = false
# public path 配置线上环境路径(打包) # public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
VITE_PUBLIC_PATH = /vue-next-admin-preview/ VITE_PUBLIC_PATH = /vue-next-admin-preview/

View File

@ -2,6 +2,19 @@
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支) 🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 1.0.14
`2021.07.29`
- 🌟 更新 依赖更新最新版本vue、vuex、vue-router,出现问题,请手动降级。版本查看:<a href="https://www.npmjs.com/" target="_blank">vnpm</a>
- 🎯 优化 数据可视化图表演示加载卡顿问题、优化有图表的演示界面
- 🎯 优化 路由参数演示界面
- 🎯 优化 tagsView 操作演示界面由于存在相同路由多标签必须要传全部参数值query 或者 params
- 🎉 新增 开启 TagsView 共用开启时多个路由菜单共用一个详情组件参数为后点击的覆盖前面点击的tagsView 中只会出现一个(不支持同时出现多个 tagsView 标签))。关闭时:(多个路由菜单共用一个详情组件,参数不同,会同时出现多个 tagsView 标签)
- 🐞 修复 tagsView 共用(单标签)时,右键菜单功能点击,参数不对的问题(第 2n+个参数未覆盖第一个参数值)
- 🐞 修复 多 tagsView 标签(参数不同)、单个 tagsView 标签公用(参数不同)所带来的刷新功能、横向自动滚动等问题
- 🐞 修复 处理全屏若干问题,<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/12" target="_blank">pr!12</a>,感谢群友@另一个前端
## 1.0.13 ## 1.0.13
`2021.07.25` `2021.07.25`

View File

@ -1,6 +1,6 @@
{ {
"name": "vue-next-admin", "name": "vue-next-admin",
"version": "1.0.13", "version": "1.0.14",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
@ -8,12 +8,12 @@
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
"countup.js": "^2.0.7", "countup.js": "^2.0.8",
"cropperjs": "^1.5.12", "cropperjs": "^1.5.12",
"echarts": "^5.1.2", "echarts": "^5.1.2",
"echarts-gl": "^2.0.6", "echarts-gl": "^2.0.7",
"echarts-wordcloud": "^2.0.0", "echarts-wordcloud": "^2.0.0",
"element-plus": "^1.0.2-beta.57", "element-plus": "^1.0.2-beta.65",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"print-js": "^1.6.0", "print-js": "^1.6.0",
@ -21,34 +21,34 @@
"screenfull": "^5.1.0", "screenfull": "^5.1.0",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"splitpanes": "^3.0.4", "splitpanes": "^3.0.4",
"vue": "^3.0.11", "vue": "^3.1.5",
"vue-clipboard3": "^1.0.1", "vue-clipboard3": "^1.0.1",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.7", "vue-i18n": "^9.1.7",
"vue-router": "^4.0.8", "vue-router": "^4.0.10",
"vue-web-screen-shot": "^1.2.0", "vue-web-screen-shot": "^1.2.0",
"vuex": "^4.0.1", "vuex": "^4.0.2",
"wangeditor": "^4.7.5" "wangeditor": "^4.7.6"
}, },
"devDependencies": { "devDependencies": {
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1", "@types/clipboard": "^2.0.1",
"@types/node": "^16.4.1", "@types/node": "^16.4.9",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^4.28.4", "@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.4", "@typescript-eslint/parser": "^4.28.5",
"@vitejs/plugin-vue": "^1.2.5", "@vitejs/plugin-vue": "^1.3.0",
"@vue/compiler-sfc": "^3.1.5", "@vue/compiler-sfc": "^3.1.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"eslint": "^7.31.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^7.14.0", "eslint-plugin-vue": "^7.15.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"sass": "^1.36.0", "sass": "^1.37.0",
"sass-loader": "^12.1.0", "sass-loader": "^12.1.0",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"vite": "^2.4.3", "vite": "^2.4.4",
"vue-eslint-parser": "^7.9.0" "vue-eslint-parser": "^7.10.0"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -2,6 +2,7 @@
<router-view v-show="getThemeConfig.lockScreenTime !== 0" /> <router-view v-show="getThemeConfig.lockScreenTime !== 0" />
<LockScreen v-if="getThemeConfig.isLockScreen" /> <LockScreen v-if="getThemeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" /> <Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
<CloseFull />
</template> </template>
<script lang="ts"> <script lang="ts">
@ -13,9 +14,10 @@ import { Local } from '/@/utils/storage';
import setIntroduction from '/@/utils/setIconfont'; import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue'; import LockScreen from '/@/layout/lockScreen/index.vue';
import Setings from '/@/layout/navBars/breadcrumb/setings.vue'; import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
export default defineComponent({ export default defineComponent({
name: 'app', name: 'app',
components: { LockScreen, Setings }, components: { LockScreen, Setings, CloseFull },
setup() { setup() {
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;
const setingsRef = ref(); const setingsRef = ref();

View File

@ -107,6 +107,7 @@ export default {
closeOther: 'closeOther', closeOther: 'closeOther',
closeAll: 'closeAll', closeAll: 'closeAll',
fullscreen: 'fullscreen', fullscreen: 'fullscreen',
closeFullscreen: 'closeFullscreen',
}, },
notFound: { notFound: {
foundTitle: 'Wrong address input, please re-enter the address~', foundTitle: 'Wrong address input, please re-enter the address~',
@ -147,6 +148,7 @@ export default {
fourIsTagsviewIcon: 'Open tagsview Icon', fourIsTagsviewIcon: 'Open tagsview Icon',
fourIsCacheTagsView: 'Enable tagsview cache', fourIsCacheTagsView: 'Enable tagsview cache',
fourIsSortableTagsView: 'Enable tagsview drag', fourIsSortableTagsView: 'Enable tagsview drag',
fourIsShareTagsView: 'Enable tagsview sharing',
fourIsFooter: 'Open footer', fourIsFooter: 'Open footer',
fourIsGrayscale: 'Grey model', fourIsGrayscale: 'Grey model',
fourIsInvert: 'Color weak mode', fourIsInvert: 'Color weak mode',

View File

@ -107,6 +107,7 @@ export default {
closeOther: '关闭其它', closeOther: '关闭其它',
closeAll: '全部关闭', closeAll: '全部关闭',
fullscreen: '当前页全屏', fullscreen: '当前页全屏',
closeFullscreen: '关闭全屏',
}, },
notFound: { notFound: {
foundTitle: '地址输入错误,请重新输入地址~', foundTitle: '地址输入错误,请重新输入地址~',
@ -147,6 +148,7 @@ export default {
fourIsTagsviewIcon: '开启 Tagsview 图标', fourIsTagsviewIcon: '开启 Tagsview 图标',
fourIsCacheTagsView: '开启 TagsView 缓存', fourIsCacheTagsView: '开启 TagsView 缓存',
fourIsSortableTagsView: '开启 TagsView 拖拽', fourIsSortableTagsView: '开启 TagsView 拖拽',
fourIsShareTagsView: '开启 TagsView 共用',
fourIsFooter: '开启 Footer', fourIsFooter: '开启 Footer',
fourIsGrayscale: '灰色模式', fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式', fourIsInvert: '色弱模式',

View File

@ -107,6 +107,7 @@ export default {
closeOther: '關閉其它', closeOther: '關閉其它',
closeAll: '全部關閉', closeAll: '全部關閉',
fullscreen: '當前頁全屏', fullscreen: '當前頁全屏',
closeFullscreen: '關閉全屏',
}, },
notFound: { notFound: {
foundTitle: '地址輸入錯誤,請重新輸入地址~', foundTitle: '地址輸入錯誤,請重新輸入地址~',
@ -147,6 +148,7 @@ export default {
fourIsTagsviewIcon: '開啟 Tagsview 圖標', fourIsTagsviewIcon: '開啟 Tagsview 圖標',
fourIsCacheTagsView: '開啟 TagsView 緩存', fourIsCacheTagsView: '開啟 TagsView 緩存',
fourIsSortableTagsView: '開啟 TagsView 拖拽', fourIsSortableTagsView: '開啟 TagsView 拖拽',
fourIsShareTagsView: '開啟 TagsView 共用',
fourIsFooter: '開啟 Footer', fourIsFooter: '開啟 Footer',
fourIsGrayscale: '灰色模式', fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式', fourIsInvert: '色弱模式',

View File

@ -1,22 +1,20 @@
<template> <template>
<template v-if="clientWidth > 1000"> <template v-if="clientWidth > 1000">
<el-aside class="layout-aside" :class="setCollapseWidth" v-show="!isCurrenFullscreen"> <el-aside class="layout-aside" :class="setCollapseWidth" v-show="!isTagsViewCurrenFull">
<Logo v-if="setShowLogo" /> <Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef"> <el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
<Vertical :menuList="menuList" :class="setCollapseWidth" /> <Vertical :menuList="menuList" :class="setCollapseWidth" />
</el-scrollbar> </el-scrollbar>
</el-aside> </el-aside>
</template> </template>
<template v-else> <el-drawer v-model="getThemeConfig.isCollapse" :with-header="false" direction="ltr" size="220px" v-else>
<el-drawer v-model="getThemeConfig.isCollapse" :with-header="false" direction="ltr" size="220px"> <el-aside class="layout-aside w100 h100">
<el-aside class="layout-aside w100 h100"> <Logo v-if="setShowLogo" />
<Logo v-if="setShowLogo" /> <el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef"> <Vertical :menuList="menuList" />
<Vertical :menuList="menuList" /> </el-scrollbar>
</el-scrollbar> </el-aside>
</el-aside> </el-drawer>
</el-drawer>
</template>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -39,8 +37,8 @@ export default {
return store.state.themeConfig.themeConfig; return store.state.themeConfig.themeConfig;
}); });
// //
const isCurrenFullscreen = computed(() => { const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isCurrenFullscreen; return store.state.tagsViewRoutes.isTagsViewCurrenFull;
}); });
// / // /
const setCollapseWidth = computed(() => { const setCollapseWidth = computed(() => {
@ -126,7 +124,7 @@ export default {
setCollapseWidth, setCollapseWidth,
setShowLogo, setShowLogo,
getThemeConfig, getThemeConfig,
isCurrenFullscreen, isTagsViewCurrenFull,
...toRefs(state), ...toRefs(state),
}; };
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="layout-columns-aside" v-show="!isCurrenFullscreen"> <div class="layout-columns-aside">
<el-scrollbar> <el-scrollbar>
<ul> <ul>
<li <li
@ -70,10 +70,6 @@ export default {
const setColumnsAsidelayout = computed(() => { const setColumnsAsidelayout = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideLayout; return store.state.themeConfig.themeConfig.columnsAsideLayout;
}); });
//
const isCurrenFullscreen = computed(() => {
return store.state.tagsViewRoutes.isCurrenFullscreen;
});
// //
const setColumnsAsideMove = (k: number) => { const setColumnsAsideMove = (k: number) => {
state.liIndex = k; state.liIndex = k;
@ -156,7 +152,6 @@ export default {
setColumnsAsideStyle, setColumnsAsideStyle,
setColumnsAsidelayout, setColumnsAsidelayout,
onColumnsAsideMenuClick, onColumnsAsideMenuClick,
isCurrenFullscreen,
...toRefs(state), ...toRefs(state),
}; };
}, },
@ -216,8 +211,8 @@ export default {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 2px; top: 2px;
height: 50px; height: 44px;
width: 60px; width: 65px;
transform: translateX(-50%); transform: translateX(-50%);
z-index: 0; z-index: 0;
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
@ -226,6 +221,7 @@ export default {
.columns-card { .columns-card {
@extend .columns-round; @extend .columns-round;
top: 0; top: 0;
height: 50px;
width: 100%; width: 100%;
border-radius: 0; border-radius: 0;
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isCurrenFullscreen"> <el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<NavBarsIndex /> <NavBarsIndex />
</el-header> </el-header>
</template> </template>
@ -20,12 +20,12 @@ export default {
else return '50px'; else return '50px';
}); });
// //
const isCurrenFullscreen = computed(() => { const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isCurrenFullscreen; return store.state.tagsViewRoutes.isTagsViewCurrenFull;
}); });
return { return {
setHeaderHeight, setHeaderHeight,
isCurrenFullscreen, isTagsViewCurrenFull,
}; };
}, },
}; };

View File

@ -0,0 +1,65 @@
<template>
<div class="layout-navbars-close-full" v-if="isTagsViewCurrenFull">
<div class="layout-navbars-close-full-box" :title="$t('message.tagsView.closeFullscreen')" @click="onCloseFullscreen">
<i class="el-icon-close"></i>
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'layoutCloseFull',
setup() {
const store = useStore();
const state: any = reactive({});
//
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
//
const onCloseFullscreen = () => {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', false);
};
return {
isTagsViewCurrenFull,
onCloseFullscreen,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.layout-navbars-close-full {
position: fixed;
z-index: 9999999999;
right: -30px;
top: -30px;
.layout-navbars-close-full-box {
width: 60px;
height: 60px;
border-radius: 100%;
position: relative;
cursor: pointer;
background: rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
i {
position: absolute;
left: 11px;
top: 35px;
color: #333333;
transition: all 0.3s ease;
}
&:hover {
background: rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
i {
color: var(--color-primary);
transition: all 0.3s ease;
}
}
}
}
</style>

View File

@ -207,6 +207,12 @@
<el-switch v-model="getThemeConfig.isSortableTagsView" @change="onSortableTagsViewChange"></el-switch> <el-switch v-model="getThemeConfig.isSortableTagsView" @change="onSortableTagsViewChange"></el-switch>
</div> </div>
</div> </div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShareTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isShareTagsView" @change="onShareTagsViewChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15"> <div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div> <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value"> <div class="layout-breadcrumb-seting-bar-flex-value">
@ -476,6 +482,11 @@ export default defineComponent({
proxy.mittBus.emit('openOrCloseSortable'); proxy.mittBus.emit('openOrCloseSortable');
setLocalThemeConfig(); setLocalThemeConfig();
}; };
// 4 --> TagsView
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4 --> / // 4 --> /
const onAddFilterChange = (attr: string) => { const onAddFilterChange = (attr: string) => {
if (attr === 'grayscale') { if (attr === 'grayscale') {
@ -654,6 +665,7 @@ export default defineComponent({
onClassicSplitMenuChange, onClassicSplitMenuChange,
onIsBreadcrumbChange, onIsBreadcrumbChange,
onSortableTagsViewChange, onSortableTagsViewChange,
onShareTagsViewChange,
onCopyConfigClick, onCopyConfigClick,
}; };
}, },

View File

@ -225,31 +225,12 @@ export default {
break; break;
} }
}; };
// F11
const initOnkeydown = () => {
document.onkeydown = (event) => {
var e = event || window.event || arguments.callee.caller.arguments[0];
if (e && e.keyCode === 122 && !state.isScreenfull) {
e.preventDefault();
onScreenfullClick();
}
};
};
// //
onMounted(() => { onMounted(() => {
initOnkeydown();
if (Local.get('themeConfig')) { if (Local.get('themeConfig')) {
initI18n(); initI18n();
initComponentSize(); initComponentSize();
} }
document.onkeydown = (event) => {
var e = event || window.event || arguments.callee.caller.arguments[0];
// F11
if (e && e.keyCode === 122 && !state.isScreenfull) {
e.preventDefault();
onScreenfullClick();
}
};
}); });
return { return {
getUserInfos, getUserInfos,

View File

@ -46,7 +46,7 @@ export default defineComponent({
icon: 'iconfont icon-fullscreen', icon: 'iconfont icon-fullscreen',
}, },
], ],
path: {}, item: {},
}); });
// x,y // x,y
const dropdowns = computed(() => { const dropdowns = computed(() => {
@ -54,11 +54,11 @@ export default defineComponent({
}); });
// //
const onCurrentContextmenuClick = (id: number) => { const onCurrentContextmenuClick = (id: number) => {
emit('currentContextmenuClick', { id, path: state.path }); emit('currentContextmenuClick', Object.assign({}, { id }, state.item));
}; };
// //
const openContextmenu = (item: any) => { const openContextmenu = (item: any) => {
state.path = item.path; state.item = item;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false); item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu(); closeContextmenu();
setTimeout(() => { setTimeout(() => {

View File

@ -1,9 +1,5 @@
<template> <template>
<div <div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
class="layout-navbars-tagsview"
:class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }"
v-show="!isCurrenFullscreen"
>
<el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll"> <el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll">
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef"> <ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
<li <li
@ -11,7 +7,7 @@
:key="k" :key="k"
class="layout-navbars-tagsview-ul-li" class="layout-navbars-tagsview-ul-li"
:data-name="v.name" :data-name="v.name"
:class="{ 'is-active': isActive(v.path) }" :class="{ 'is-active': isActive(v) }"
@contextmenu.prevent="onContextmenu(v, $event)" @contextmenu.prevent="onContextmenu(v, $event)"
@click="onTagsClick(v, k)" @click="onTagsClick(v, k)"
:ref=" :ref="
@ -20,21 +16,21 @@
} }
" "
> >
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v.path)"></i> <i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)"></i>
<i class="layout-navbars-tagsview-ul-li-iconfont" :class="v.meta.icon" v-if="!isActive(v.path) && getThemeConfig.isTagsviewIcon"></i> <i class="layout-navbars-tagsview-ul-li-iconfont" :class="v.meta.icon" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon"></i>
<span>{{ $t(v.meta.title) }}</span> <span>{{ $t(v.meta.title) }}</span>
<template v-if="isActive(v.path)"> <template v-if="isActive(v)">
<i class="el-icon-refresh-right ml5" @click.stop="refreshCurrentTagsView(v.path)"></i> <i class="el-icon-refresh-right ml5" @click.stop="refreshCurrentTagsView($route.fullPath)"></i>
<i <i
class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-active" class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-active"
v-if="!v.meta.isAffix" v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(v.path)" @click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
></i> ></i>
</template> </template>
<i <i
class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-three" class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-three"
v-if="!v.meta.isAffix" v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(v.path)" @click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
></i> ></i>
</li> </li>
</ul> </ul>
@ -46,9 +42,12 @@
<script lang="ts"> <script lang="ts">
import { toRefs, reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue'; import { toRefs, reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import Sortable from 'sortablejs';
import { ElMessage } from 'element-plus';
import { useStore } from '/@/store/index'; import { useStore } from '/@/store/index';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import Sortable from 'sortablejs'; import { NextLoading } from '/@/utils/loading';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue'; import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
export default { export default {
name: 'layoutTagsView', name: 'layoutTagsView',
@ -63,6 +62,7 @@ export default {
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const state: any = reactive({ const state: any = reactive({
routeActive: '',
routePath: route.path, routePath: route.path,
dropdown: { x: '', y: '' }, dropdown: { x: '', y: '' },
tagsRefsIndex: 0, tagsRefsIndex: 0,
@ -78,66 +78,120 @@ export default {
const getThemeConfig = computed(() => { const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig; return store.state.themeConfig.themeConfig;
}); });
// // tagsView
const isCurrenFullscreen = computed(() => { const isActive = (v) => {
return store.state.tagsViewRoutes.isCurrenFullscreen; if (getThemeConfig.value.isShareTagsView) {
}); return v.path === state.routePath;
} else {
return v.url === state.routeActive;
}
};
// tagsViewList // tagsViewList
const addBrowserSetSession = (tagsViewList: Array<object>) => { const addBrowserSetSession = (tagsViewList: Array<object>) => {
Session.set('tagsViewList', tagsViewList); Session.set('tagsViewList', tagsViewList);
}; };
// vuex tagsViewRoutes // vuex tagsViewRoutes
const getTagsViewRoutes = () => { const getTagsViewRoutes = async () => {
state.routePath = route.meta.isDynamic ? route.meta.isDynamicPath : route.path; state.routeActive = await setTagsViewHighlight(route);
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
state.tagsViewList = []; state.tagsViewList = [];
if (!store.state.themeConfig.themeConfig.isCacheTagsView) Session.remove('tagsViewList');
state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes; state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
initTagsView(); initTagsView();
}; };
// vuex isAffix // vuex isAffix
const initTagsView = () => { const initTagsView = async () => {
if (Session.get('tagsViewList') && store.state.themeConfig.themeConfig.isCacheTagsView) { if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
state.tagsViewList = Session.get('tagsViewList'); state.tagsViewList = await Session.get('tagsViewList');
} else { } else {
state.tagsViewRoutesList.map((v: any) => { await state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v }); if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
}); });
addTagsView(route.path, route); await addTagsView(route.path, route);
} }
// (li) // (li)
getTagsRefsIndex(route.path); getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
//
tagsViewmoveToCurrentTag();
}; };
// 1 tagsViewisHide tagsView // xxx/:id/:name"
const addTagsView = (path: string, to?: any) => { const solveAddTagsView = async (path: string, to?: any) => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
let item = ''; let current = state.tagsViewList.filter(
if (to && to.meta.isDynamic) { (v: any) =>
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) return false; v.path === isDynamicPath &&
item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath); isObjectValueEqual(
} else { to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
if (state.tagsViewList.some((v: any) => v.path === path)) return false; to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
item = state.tagsViewRoutesList.find((v: any) => v.path === path); )
);
if (current.length <= 0) {
// Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
let findItem = state.tagsViewRoutesList.find((v: any) => v.path === isDynamicPath);
if (findItem.meta.isAffix) return false;
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
findItem.url = setTagsViewHighlight(findItem);
state.tagsViewList.push({ ...findItem });
addBrowserSetSession(state.tagsViewList);
} }
if (item.meta.isLink && !item.meta.isIframe) return false; };
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params; // tagsViewList Session Storage
else item.query = to?.query ? to?.query : route.query; const singleAddTagsView = (path: string, to?: any) => {
state.tagsViewList.push({ ...item }); let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
addBrowserSetSession(state.tagsViewList); state.tagsViewList.forEach((v) => {
if (
v.path === isDynamicPath &&
!isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
) {
to.meta.isDynamic ? (v.params = to.params) : (v.query = to.query);
v.url = setTagsViewHighlight(v);
addBrowserSetSession(state.tagsViewList);
}
});
};
// 1 tagsViewisHide tagsView
const addTagsView = (path: string, to?: any) => {
//
nextTick(async () => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
let item = '';
if (to && to.meta.isDynamic) {
// 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;
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;
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
}
if (item.meta.isLink && !item.meta.isIframe) return false;
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
else item.query = to?.query ? to?.query : route.query;
item.url = setTagsViewHighlight(item);
await state.tagsViewList.push({ ...item });
await addBrowserSetSession(state.tagsViewList);
});
}; };
// 2 tagsView // 2 tagsView
const refreshCurrentTagsView = (path: string) => { const refreshCurrentTagsView = (fullPath: string) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', path); proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
}; };
// 3 tagsViewisAffix // 3 tagsViewisAffix
const closeCurrentTagsView = (path: string) => { const closeCurrentTagsView = (path: string) => {
state.tagsViewList.map((v: any, k: number, arr: any) => { state.tagsViewList.map((v: any, k: number, arr: any) => {
if (!v.meta.isAffix) { if (!v.meta.isAffix) {
if (v.path === path) { if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
state.tagsViewList.splice(k, 1); state.tagsViewList.splice(k, 1);
setTimeout(() => { setTimeout(() => {
if (state.tagsViewList.length === k && state.routePath === path) { if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
// //
if (arr[arr.length - 1].meta.isDynamic) { if (arr[arr.length - 1].meta.isDynamic) {
// xxx/:id/:name" // xxx/:id/:name"
@ -148,7 +202,7 @@ export default {
} }
} else { } else {
// //
if (state.tagsViewList.length !== k && state.routePath === path) { if (state.tagsViewList.length !== k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
if (arr[k].meta.isDynamic) { if (arr[k].meta.isDynamic) {
// xxx/:id/:name" // xxx/:id/:name"
router.push({ name: arr[k].name, params: arr[k].params }); router.push({ name: arr[k].name, params: arr[k].params });
@ -173,62 +227,85 @@ export default {
addTagsView(path, route); addTagsView(path, route);
}; };
// 5 tagsViewisAffix // 5 tagsViewisAffix
const closeAllTagsView = (path: string) => { const closeAllTagsView = () => {
state.tagsViewList = []; state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => { state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) { if (v.meta.isAffix && !v.meta.isHide) {
state.tagsViewList.push({ ...v }); state.tagsViewList.push({ ...v });
if (state.tagsViewList.some((v: any) => v.path === path)) router.push({ path, query: route.query }); router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
else router.push({ path: v.path, query: route.query });
} }
}); });
addBrowserSetSession(state.tagsViewList); addBrowserSetSession(state.tagsViewList);
}; };
// 6 // 6
const openCurrenFullscreen = (path: string, currentRouteInfo: object) => { const openCurrenFullscreen = async (path: string) => {
nextTick(() => { const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
const { meta, name, params, query } = currentRouteInfo; if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
if (meta.isDynamic) router.push({ name, params }); else await router.push({ name: item.name, query: item.query });
else router.push({ path, query }); NextLoading.start();
store.dispatch('tagsViewRoutes/setCurrenFullscreen', 500); setTimeout(() => {
}); nextTick(() => {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
if (store.state.tagsViewRoutes.isTagsViewCurrenFull) {
const element = document.querySelector('.layout-main .layout-view-bg-white') as HTMLElement;
if (!element) return false;
// iframes
if (route.path === '/iframes') element.style.height = `100vh`;
else element.style.height = `calc(100vh - 30px)`;
}
});
}, 1000);
}; };
// tagsView // tagsView
const getCurrentRouteItem = (path: string) => { // tagsView
if (!Session.get('tagsViewList')) return state.tagsViewList.find((v: any) => v.path === path); const getCurrentRouteItem = (path: string, cParams: { [key: string]: any }) => {
return Session.get('tagsViewList').find((v: any) => v.path === path); const itemRoute = Session.get('tagsViewList') ? Session.get('tagsViewList') : state.tagsViewList;
return itemRoute.find((v: any) => {
if (
v.path === path &&
isObjectValueEqual(
v.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
cParams && Object.keys(cParams ? cParams : {}).length > 0 ? cParams : null
)
) {
return v;
} else if (v.path === path && Object.keys(cParams ? cParams : {}).length <= 0) {
return v;
}
});
}; };
// //
const onCurrentContextmenuClick = (item) => { const onCurrentContextmenuClick = async (item) => {
const { id, path } = item; const cParams = item.meta.isDynamic ? item.params : item.query;
const currentRouteInfo = getCurrentRouteItem(path); if (!getCurrentRouteItem(item.path, cParams)) return ElMessage({ type: 'warning', message: '请正确输入路径及完整参数query、params' });
const { meta, name, params, query } = currentRouteInfo; const { path, name, params, query, meta, url } = getCurrentRouteItem(item.path, cParams);
switch (id) { switch (item.id) {
case 0: case 0:
refreshCurrentTagsView(path); //
if (meta.isDynamic) router.push({ name, params }); if (meta.isDynamic) await router.push({ name, params });
else router.push({ path, query }); else await router.push({ path, query });
refreshCurrentTagsView(route.fullPath);
break; break;
case 1: case 1:
closeCurrentTagsView(path); //
closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
break; break;
case 2: case 2:
if (meta.isDynamic) router.push({ name, params }); //
else router.push({ path, query }); if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
closeOtherTagsView(path); closeOtherTagsView(path);
break; break;
case 3: case 3:
closeAllTagsView(path); //
closeAllTagsView();
break; break;
case 4: case 4:
openCurrenFullscreen(path, currentRouteInfo); //
openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
break; break;
} }
}; };
// https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
const isActive = (path: string) => {
return path === state.routePath;
};
// x,y props // x,y props
const onContextmenu = (v: any, e: any) => { const onContextmenu = (v: any, e: any) => {
const { clientX, clientY } = e; const { clientX, clientY } = e;
@ -238,10 +315,20 @@ export default {
}; };
// tagsView // tagsView
const onTagsClick = (v: any, k: number) => { const onTagsClick = (v: any, k: number) => {
state.routePath = v.path;
state.tagsRefsIndex = k; state.tagsRefsIndex = k;
router.push(v); router.push(v);
}; };
// tagsView 使使
const setTagsViewHighlight = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
if (!params || Object.keys(params).length <= 0) return v.path;
let path = '';
for (let i in params) {
path += params[i];
}
// xxx/:id/:name"
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
};
// //
const updateScrollbar = () => { const updateScrollbar = () => {
proxy.$refs.scrollbarRef.update(); proxy.$refs.scrollbarRef.update();
@ -304,9 +391,19 @@ export default {
}; };
// tagsView tagsView // tagsView tagsView
const getTagsRefsIndex = (path: string) => { const getTagsRefsIndex = (path: string) => {
if (state.tagsViewList.length > 0) { nextTick(async () => {
state.tagsRefsIndex = state.tagsViewList.findIndex((item: any) => item.path === path); // await 使 tagsViewList
} let tagsViewList = await state.tagsViewList;
state.tagsRefsIndex = tagsViewList.findIndex((v: any) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === path;
} else {
return v.url === path;
}
});
//
tagsViewmoveToCurrentTag();
});
}; };
// tagsView // tagsView
const initSortable = () => { const initSortable = () => {
@ -335,11 +432,6 @@ export default {
else getThemeConfig.value.isSortableTagsView = true; else getThemeConfig.value.isSortableTagsView = true;
initSortable(); initSortable();
}; };
// tagsView
watch(store.state, (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
});
// //
onBeforeMount(() => { onBeforeMount(() => {
// 访 // 访
@ -354,6 +446,19 @@ export default {
proxy.mittBus.on('openOrCloseSortable', () => { proxy.mittBus.on('openOrCloseSortable', () => {
initSortable(); initSortable();
}); });
// TagsView
proxy.mittBus.on('openShareTagsView', () => {
if (getThemeConfig.value.isShareTagsView) {
router.push('/home');
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
}
});
}); });
// //
onUnmounted(() => { onUnmounted(() => {
@ -361,6 +466,8 @@ export default {
proxy.mittBus.off('onCurrentContextmenuClick'); proxy.mittBus.off('onCurrentContextmenuClick');
// / // /
proxy.mittBus.off('openOrCloseSortable'); proxy.mittBus.off('openOrCloseSortable');
// TagsView
proxy.mittBus.off('openShareTagsView');
// resize // resize
window.removeEventListener('resize', onSortableResize); window.removeEventListener('resize', onSortableResize);
}); });
@ -375,11 +482,16 @@ export default {
initSortable(); initSortable();
}); });
// //
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate(async (to) => {
state.routeActive = setTagsViewHighlight(to);
state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path; state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
addTagsView(to.path, to); await addTagsView(to.path, to);
getTagsRefsIndex(to.path); getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
tagsViewmoveToCurrentTag(); });
// tagsView
watch(store.state, (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
}); });
return { return {
isActive, isActive,
@ -392,7 +504,6 @@ export default {
tagsUlRef, tagsUlRef,
onHandleScroll, onHandleScroll,
getThemeConfig, getThemeConfig,
isCurrenFullscreen,
setTagsStyle, setTagsStyle,
refreshCurrentTagsView, refreshCurrentTagsView,
closeCurrentTagsView, closeCurrentTagsView,

View File

@ -11,7 +11,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick } from 'vue'; import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index'; import { useStore } from '/@/store/index';
export default defineComponent({ export default defineComponent({
@ -40,19 +40,11 @@ export default defineComponent({
// //
onBeforeMount(() => { onBeforeMount(() => {
state.keepAliveNameList = getKeepAliveNames.value; state.keepAliveNameList = getKeepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (path: string) => { proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
if (route.meta.isDynamic) {
// xxx/:id/:name
if (route.meta.isDynamicPath !== path) return false;
} else {
//
if (route.path !== path) return false;
}
state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name); state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = route.meta.isDynamic ? route.meta.isDynamicPath : route.path; state.refreshRouterViewKey = null;
nextTick(() => { nextTick(() => {
state.refreshRouterViewKey = null; state.refreshRouterViewKey = fullPath;
state.keepAliveNameList = getKeepAliveNames.value; state.keepAliveNameList = getKeepAliveNames.value;
}); });
}); });
@ -61,6 +53,13 @@ export default defineComponent({
onUnmounted(() => { onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView'); proxy.mittBus.off('onTagsViewRefreshRouterView');
}); });
// tagsView
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = route.fullPath;
}
);
return { return {
getThemeConfig, getThemeConfig,
getKeepAliveNames, getKeepAliveNames,

View File

@ -34,6 +34,7 @@ export interface ThemeConfigState {
isTagsviewIcon: boolean; isTagsviewIcon: boolean;
isCacheTagsView: boolean; isCacheTagsView: boolean;
isSortableTagsView: boolean; isSortableTagsView: boolean;
isShareTagsView: boolean;
isFooter: boolean; isFooter: boolean;
isGrayscale: boolean; isGrayscale: boolean;
isInvert: boolean; isInvert: boolean;
@ -65,7 +66,7 @@ export interface KeepAliveNamesState {
// TagsView 路由列表 // TagsView 路由列表
export interface TagsViewRoutesState { export interface TagsViewRoutesState {
tagsViewRoutes: Array<object>; tagsViewRoutes: Array<object>;
isCurrenFullscreen: boolean; isTagsViewCurrenFull: Boolean;
} }
// 用户信息 // 用户信息

View File

@ -1,7 +1,4 @@
import { Module } from 'vuex'; import { Module } from 'vuex';
import { ElMessage } from 'element-plus';
import screenfull from 'screenfull';
import { Local } from '/@/utils/storage';
// 此处加上 `.ts` 后缀报错,具体原因不详 // 此处加上 `.ts` 后缀报错,具体原因不详
import { TagsViewRoutesState, RootStateTypes } from '/@/store/interface/index'; import { TagsViewRoutesState, RootStateTypes } from '/@/store/interface/index';
@ -9,7 +6,7 @@ const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
namespaced: true, namespaced: true,
state: { state: {
tagsViewRoutes: [], tagsViewRoutes: [],
isCurrenFullscreen: false, isTagsViewCurrenFull: false,
}, },
mutations: { mutations: {
// 设置 TagsView 路由 // 设置 TagsView 路由
@ -17,8 +14,8 @@ const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
state.tagsViewRoutes = data; state.tagsViewRoutes = data;
}, },
// 设置卡片全屏 // 设置卡片全屏
getCurrenFullscreen(state: any, data: boolean) { getCurrenFullscreen(state: any, bool: boolean) {
state.isCurrenFullscreen = data; state.isTagsViewCurrenFull = bool;
}, },
}, },
actions: { actions: {
@ -27,34 +24,8 @@ const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
commit('getTagsViewRoutes', data); commit('getTagsViewRoutes', data);
}, },
// 设置卡片全屏 // 设置卡片全屏
setCurrenFullscreen({ commit }, timeout: number = 0) { setCurrenFullscreen({ commit }, bool: Boolean) {
const screenfulls: any = screenfull; commit('getCurrenFullscreen', bool);
if (!screenfulls.isEnabled) {
ElMessage.warning('暂不不支持全屏');
return false;
}
setTimeout(() => {
const currenFullscreenChange = () => {
const layoutViewBgWhite = document.querySelector('.layout-view-bg-white') as HTMLElement;
if (screenfulls.isFullscreen) {
commit('getCurrenFullscreen', true);
// 设置全屏时,设置有 `layout-view-bg-white` 类的高度
if (layoutViewBgWhite) layoutViewBgWhite.style.height = `calc(100vh - 30px)`;
} else {
screenfulls.off('change', currenFullscreenChange);
commit('getCurrenFullscreen', false);
if (!layoutViewBgWhite) return false;
const getThemeConfig = Local.get('themeConfig');
if (getThemeConfig) {
let { isTagsview } = getThemeConfig;
if (isTagsview) layoutViewBgWhite.style.height = `calc(100vh - 114px)`;
else layoutViewBgWhite.style.height = `calc(100vh - 80px)`;
}
}
};
screenfulls.on('change', currenFullscreenChange);
screenfulls.toggle();
}, timeout);
}, },
}, },
}; };

View File

@ -91,6 +91,8 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
isCacheTagsView: false, isCacheTagsView: false,
// 是否开启 TagsView 拖拽 // 是否开启 TagsView 拖拽
isSortableTagsView: true, isSortableTagsView: true,
// 是否开启 TagsView 共用
isShareTagsView: false,
// 是否开启 Footer 底部版权信息 // 是否开启 Footer 底部版权信息
isFooter: false, isFooter: false,
// 是否开启灰色模式 // 是否开启灰色模式

View File

@ -54,7 +54,7 @@ body,
.el-scrollbar { .el-scrollbar {
width: 100%; width: 100%;
} }
// 此字段多次用到删除如需修改请重写覆盖样式 // 此字段多次用到建议不删除如需修改请重写覆盖样式
.layout-view-bg-white { .layout-view-bg-white {
background: white; background: white;
width: 100%; width: 100%;

View File

@ -14,3 +14,28 @@ export function judementSameArr(news: Array<string>, old: Array<string>): boolea
} }
return count === leng ? true : false; return count === leng ? true : false;
} }
/**
*
* @param a
* @param b
* @returns true
*/
export function isObjectValueEqual(a: { [key: string]: any }, b: { [key: string]: any }) {
if (!a || !b) return false;
let aProps = Object.getOwnPropertyNames(a);
let bProps = Object.getOwnPropertyNames(b);
if (aProps.length != bProps.length) return false;
for (let i = 0; i < aProps.length; i++) {
let propName = aProps[i];
let propA = a[propName];
let propB = b[propName];
if (!b.hasOwnProperty(propName)) return false;
if (propA instanceof Object) {
if (!isObjectValueEqual(propA, propB)) return false;
} else if (propA !== propB) {
return false;
}
}
return true;
}

View File

@ -202,7 +202,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { toRefs, reactive, computed, onMounted, getCurrentInstance } from 'vue'; import { toRefs, reactive, computed, onMounted, getCurrentInstance, watch, nextTick } from 'vue';
import { useStore } from '/@/store/index'; import { useStore } from '/@/store/index';
import ChartHead from '/@/views/chart/head.vue'; import ChartHead from '/@/views/chart/head.vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
@ -220,6 +220,7 @@ export default {
dBtnList, dBtnList,
chartData4List, chartData4List,
earth3DBtnList, earth3DBtnList,
myCharts: [],
}); });
// //
const initTagViewHeight = computed(() => { const initTagViewHeight = computed(() => {
@ -286,9 +287,7 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// 7 // 7
const initChartsSevenDays = () => { const initChartsSevenDays = () => {
@ -333,9 +332,7 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// 30 // 30
const initChartsWarning = () => { const initChartsWarning = () => {
@ -370,9 +367,7 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initChartsMonitor = () => { const initChartsMonitor = () => {
@ -412,9 +407,7 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// 7 // 7
const initChartsInvestment = () => { const initChartsInvestment = () => {
@ -444,8 +437,14 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
state.myCharts.push(myChart);
};
// echarts resize
const initEchartsResize = () => {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
myChart.resize(); for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
}); });
}; };
// //
@ -455,7 +454,19 @@ export default {
initChartsWarning(); initChartsWarning();
initChartsMonitor(); initChartsMonitor();
initChartsInvestment(); initChartsInvestment();
initEchartsResize();
}); });
// vuex tagsview resize /
watch(
() => store.state.tagsViewRoutes.isTagsViewCurrenFull,
() => {
nextTick(() => {
for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
});
}
);
return { return {
initTagViewHeight, initTagViewHeight,
...toRefs(state), ...toRefs(state),

View File

@ -1,28 +1,10 @@
<template> <template>
<div class="fun-tagsview"> <div class="fun-tagsview">
<el-card shadow="hover" header="tagsView 非当前页演示"> <NoticeBar
<el-form :model="formInline" size="small" label-width="40px" class="tags-view-form"> text="已删除非当前页 tagsView 演示后续有时间可以再加回来tagsview 支持多标签(参数不同)、单标签共用(参数不同)"
<el-row :gutter="35"> background="#ecf5ff"
<el-col :xs="24" :sm="8" :md="8" :lg="6" :xl="4" class="tags-view-form-col"> color="#409eff"
<el-form-item label="功能"> />
<el-select v-model="formInline.selectId" placeholder="请选择" class="w100">
<el-option v-for="item in selectOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="6" :xl="4" class="tags-view-form-col">
<el-form-item label="路径">
<el-input v-model="formInline.path" placeholder="路径如:/fun/tagsView"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="6" :xl="4">
<el-form-item class="fun-tagsview-from-item">
<el-button type="primary" @click="onImplementClick" icon="el-icon-thumb">点击执行</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<el-card shadow="hover" header="tagsView 当前页演示" class="mt15"> <el-card shadow="hover" header="tagsView 当前页演示" class="mt15">
<div class="flex-warp"> <div class="flex-warp">
<div class="flex-warp-item"> <div class="flex-warp-item">
@ -57,82 +39,35 @@
<script lang="ts"> <script lang="ts">
import { getCurrentInstance, reactive, toRefs } from 'vue'; import { getCurrentInstance, reactive, toRefs } from 'vue';
import NoticeBar from '/@/components/noticeBar/index.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
export default { export default {
name: 'funTagsView', name: 'funTagsView',
components: { NoticeBar },
setup() { setup() {
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;
const route = useRoute(); const route = useRoute();
const state = reactive({ const state = reactive({});
formInline: {
path: '',
selectId: 0,
},
selectOptions: [
{
value: 0,
label: '刷新当前',
},
{
value: 1,
label: '关闭当前',
},
{
value: 2,
label: '关闭其它',
},
{
value: 3,
label: '关闭全部',
},
{
value: 4,
label: '当前页全屏',
},
],
});
// 0 1 2 3 4 // 0 1 2 3 4
// 1 tagsView // 1 tagsView
const refreshCurrentTagsView = () => { const refreshCurrentTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', { proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { id: 0, ...route }));
id: 0,
path: route.path,
});
}; };
// 2 tagsView // 2 tagsView
const closeCurrentTagsView = () => { const closeCurrentTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', { proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { id: 1, ...route }));
id: 1,
path: route.path,
});
}; };
// 3 tagsView // 3 tagsView
const closeOtherTagsView = () => { const closeOtherTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', { proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { id: 2, ...route }));
id: 2,
path: route.path,
});
}; };
// 4 tagsView // 4 tagsView
const closeAllTagsView = () => { const closeAllTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', { proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { id: 3, ...route }));
id: 3,
path: route.path,
});
}; };
// 5 // 5
const openCurrenFullscreen = () => { const openCurrenFullscreen = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', { proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { id: 4, ...route }));
id: 4,
path: route.path,
});
};
//
const onImplementClick = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', {
id: state.formInline.selectId,
path: state.formInline.path,
});
}; };
return { return {
refreshCurrentTagsView, refreshCurrentTagsView,
@ -140,7 +75,6 @@ export default {
closeOtherTagsView, closeOtherTagsView,
closeAllTagsView, closeAllTagsView,
openCurrenFullscreen, openCurrenFullscreen,
onImplementClick,
...toRefs(state), ...toRefs(state),
}; };
}, },

View File

@ -96,7 +96,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { toRefs, reactive, onMounted, nextTick, computed, getCurrentInstance } from 'vue'; import { toRefs, reactive, onMounted, nextTick, computed, getCurrentInstance, watch } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { CountUp } from 'countup.js'; import { CountUp } from 'countup.js';
import { formatAxis } from '/@/utils/formatTime'; import { formatAxis } from '/@/utils/formatTime';
@ -130,6 +130,7 @@ export default {
}, },
], ],
}, },
myCharts: [],
}); });
// vuex // vuex
const getUserInfos = computed(() => { const getUserInfos = computed(() => {
@ -234,9 +235,7 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initHomeOvertime = () => { const initHomeOvertime = () => {
@ -288,8 +287,14 @@ export default {
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
state.myCharts.push(myChart);
};
// echarts resize
const initEchartsResize = () => {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
myChart.resize(); for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
}); });
}; };
// //
@ -297,7 +302,19 @@ export default {
initNumCountUp(); initNumCountUp();
initHomeLaboratory(); initHomeLaboratory();
initHomeOvertime(); initHomeOvertime();
initEchartsResize();
}); });
// vuex tagsview resize /
watch(
() => store.state.tagsViewRoutes.isTagsViewCurrenFull,
() => {
nextTick(() => {
for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
});
}
);
return { return {
getUserInfos, getUserInfos,
currentTime, currentTime,

View File

@ -120,7 +120,7 @@ export default {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
width: 51px; width: 50px;
height: 50px; height: 50px;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
@ -134,19 +134,22 @@ export default {
border-bottom: 50px solid #ffffff; border-bottom: 50px solid #ffffff;
border-right: 50px solid transparent; border-right: 50px solid transparent;
z-index: 2; z-index: 2;
top: 0;
right: 0;
} }
&:hover { &:hover {
opacity: 1; opacity: 1;
transition: all ease 0.3s; transition: all ease 0.3s;
color: var(--color-primary);
} }
i { i {
content: ''; content: '';
width: 50px; width: 48px;
height: 50px; height: 50px;
position: absolute; position: absolute;
top: -2px; top: 0px;
right: 0px; right: 0px;
font-size: 50px; font-size: 47px;
z-index: 1; z-index: 1;
} }
} }

View File

@ -1,9 +1,10 @@
<template> <template>
<div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }"> <div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }">
<div class="flex-margin"> <div class="flex-margin">
<el-result icon="success" title="普通路由" subTitle="请根据提示进行操作"> <el-result icon="success" title="普通路由" subTitle="可 `开启 TagsView 共用` 进行单标签测试">
<template #extra> <template #extra>
<el-button type="primary" size="small" icon="iconfont icon-putong" @click="onGoDetailsClick">点击测试普通路由传参</el-button> <el-input v-model="value" placeholder="请输入路由参数id值" clearable></el-input>
<el-button type="primary" size="small" icon="iconfont icon-putong" class="mt15" @click="onGoDetailsClick">普通路由传参</el-button>
</template> </template>
</el-result> </el-result>
</div> </div>
@ -18,7 +19,9 @@ export default defineComponent({
name: 'paramsCommon', name: 'paramsCommon',
setup() { setup() {
const store = useStore(); const store = useStore();
const state = reactive({}); const state = reactive({
value: '',
});
const router = useRouter(); const router = useRouter();
// view // view
const setViewHeight = computed(() => { const setViewHeight = computed(() => {
@ -30,11 +33,9 @@ export default defineComponent({
const onGoDetailsClick = () => { const onGoDetailsClick = () => {
router.push({ router.push({
path: '/params/common/details', path: '/params/common/details',
query: { query: { id: state.value, name: 'vue-next-admin' },
t: new Date().getTime(),
id: Math.random(),
},
}); });
state.value = '';
}; };
return { return {
setViewHeight, setViewHeight,

View File

@ -1,9 +1,10 @@
<template> <template>
<div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }"> <div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }">
<div class="flex-margin"> <div class="flex-margin">
<el-result icon="warning" title="动态路由" subTitle="请根据提示进行操作"> <el-result icon="warning" title="动态路由" subTitle="可 `开启 TagsView 共用` 进行单标签测试">
<template #extra> <template #extra>
<el-button type="primary" size="small" icon="iconfont icon-dongtai" @click="onGoDetailsClick">点击测试动态路由传参</el-button> <el-input v-model="value" placeholder="请输入路由参数id值" clearable></el-input>
<el-button type="primary" size="small" icon="iconfont icon-dongtai" class="mt15" @click="onGoDetailsClick">动态路由传参</el-button>
</template> </template>
</el-result> </el-result>
</div> </div>
@ -18,7 +19,9 @@ export default defineComponent({
name: 'paramsDynamic', name: 'paramsDynamic',
setup() { setup() {
const store = useStore(); const store = useStore();
const state = reactive({}); const state = reactive({
value: '',
});
const router = useRouter(); const router = useRouter();
// view // view
const setViewHeight = computed(() => { const setViewHeight = computed(() => {
@ -29,15 +32,14 @@ export default defineComponent({
// //
const onGoDetailsClick = () => { const onGoDetailsClick = () => {
// name name // name name
const t = new Date().getTime();
const id = Math.random();
router.push({ router.push({
name: 'paramsDynamicDetails', name: 'paramsDynamicDetails',
params: { params: {
t, t: 'vue-next-admin',
id, id: state.value,
}, },
}); });
state.value = '';
}; };
return { return {
setViewHeight, setViewHeight,

View File

@ -106,6 +106,7 @@ export default defineComponent({
txt: '', txt: '',
fun: 0, fun: 0,
}, },
myCharts: [],
}); });
// //
const initTime = () => { const initTime = () => {
@ -233,9 +234,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
// //
const map = myChart.getModel().getComponent('bmap').getBMap(); const map = myChart.getModel().getComponent('bmap').getBMap();
@ -355,9 +354,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// A // A
const initVisualizingContentLeftBottom = () => { const initVisualizingContentLeftBottom = () => {
@ -473,9 +470,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initVisualizingContentCenterTop = () => { const initVisualizingContentCenterTop = () => {
@ -603,9 +598,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initVisualizingContentCenterBottom = () => { const initVisualizingContentCenterBottom = () => {
@ -702,9 +695,7 @@ export default defineComponent({
}, },
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initVisualizingContentRightTop = () => { const initVisualizingContentRightTop = () => {
@ -839,9 +830,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initVisualizingContentRightBottom = () => { const initVisualizingContentRightBottom = () => {
@ -923,20 +912,27 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
state.myCharts.push(myChart);
};
// echarts resize
const initEchartsResize = () => {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
myChart.resize(); for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
}); });
}; };
// //
onMounted(() => { onMounted(async () => {
initTime(); initTime();
initEchartsMap(); await initEchartsMap();
initVisualizingContentLeftTop(); await initVisualizingContentLeftTop();
initVisualizingContentLeftBottom(); await initVisualizingContentLeftBottom();
initVisualizingContentCenterTop(); await initVisualizingContentCenterTop();
initVisualizingContentCenterBottom(); await initVisualizingContentCenterBottom();
initVisualizingContentRightTop(); await initVisualizingContentRightTop();
initVisualizingContentRightBottom(); await initVisualizingContentRightBottom();
await initEchartsResize();
}); });
// //
onUnmounted(() => { onUnmounted(() => {

View File

@ -253,6 +253,7 @@ export default defineComponent({
dBtnActive: 0, dBtnActive: 0,
earth3DBtnList, earth3DBtnList,
chartData4List, chartData4List,
myCharts: [],
}); });
// //
const initTime = () => { const initTime = () => {
@ -291,9 +292,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initRightChartData4 = () => { const initRightChartData4 = () => {
@ -381,9 +380,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// 7 // 7
const initRightChartData3 = () => { const initRightChartData3 = () => {
@ -462,9 +459,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// //
const initRightChartData6 = () => { const initRightChartData6 = () => {
@ -478,7 +473,7 @@ export default defineComponent({
}, },
grid: { grid: {
top: 20, top: 20,
right: 42, right: 50,
bottom: 0, bottom: 0,
left: 80, left: 80,
}, },
@ -508,7 +503,7 @@ export default defineComponent({
data: ['施肥任务完成率', '施药任务完成率', '农事任务完成率'], data: ['施肥任务完成率', '施药任务完成率', '农事任务完成率'],
axisLabel: { axisLabel: {
color: '#A7D6F4', color: '#A7D6F4',
fontSize: 10, fontSize: 12,
}, },
}, },
], ],
@ -516,12 +511,12 @@ export default defineComponent({
{ {
name: '标准化', name: '标准化',
type: 'bar', type: 'bar',
barWidth: 12, // barWidth: 10, //
label: { label: {
show: true, show: true,
position: 'right', // position: 'right', //
color: '#A7D6F4', color: '#A7D6F4',
fontSize: 10, fontSize: 12,
distance: 15, // distance: 15, //
formatter: '{c}%', // formatter: '{c}%', //
}, // }, //
@ -551,9 +546,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// 7 // 7
const initRightChartData2 = () => { const initRightChartData2 = () => {
@ -686,9 +679,7 @@ export default defineComponent({
], ],
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener('resize', () => { state.myCharts.push(myChart);
myChart.resize();
});
}; };
// 3DEarth // 3DEarth
const init3DEarth = (globeRadius) => { const init3DEarth = (globeRadius) => {
@ -751,7 +742,7 @@ export default defineComponent({
myChart.setOption(option); myChart.setOption(option);
}; };
// //
const initAddEventListener3DEarthFun = () => { const initAddEventListener3DEarth = () => {
let w = document.body.clientWidth; let w = document.body.clientWidth;
let globeRadius = 0; let globeRadius = 0;
if (w >= 1920) globeRadius = 100; if (w >= 1920) globeRadius = 100;
@ -761,22 +752,25 @@ export default defineComponent({
else if (w < 768) globeRadius = 20; else if (w < 768) globeRadius = 20;
init3DEarth(globeRadius); init3DEarth(globeRadius);
}; };
// // echarts resize
const initAddEventListener3DEarth = () => { const initEchartsResize = () => {
initAddEventListener3DEarthFun(); initAddEventListener3DEarth();
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
initAddEventListener3DEarthFun(); for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
initAddEventListener3DEarth();
}); });
}; };
// //
onMounted(() => { onMounted(async () => {
initAddEventListener3DEarth();
initTime(); initTime();
initRightChartData1(); await initRightChartData1();
initRightChartData4(); await initRightChartData4();
initRightChartData3(); await initRightChartData3();
initRightChartData2(); await initRightChartData2();
initRightChartData6(); await initRightChartData6();
await initEchartsResize();
}); });
// //
onUnmounted(() => { onUnmounted(() => {
@ -841,7 +835,7 @@ export default defineComponent({
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
border: 1px transparent solid; border: 1px transparent solid;
border-image: linear-gradient(to right, #000718, #43bdf0) 1 10; border-image: linear-gradient(to right, rgba(0, 0, 0, 0.1), #43bdf0) 1 10;
} }
span { span {
cursor: pointer; cursor: pointer;