修改顶端组件实现方式,现支持左中右分区NavBar

This commit is contained in:
洛洛希雅Lolosia 2023-02-28 16:41:32 +08:00
parent 53d599c2f0
commit 4150e939a9
8 changed files with 344 additions and 242 deletions

View File

@ -5,31 +5,9 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title> <title><%= title %></title>
<style>
.app-navs-frame {
position: absolute;
right: 0;
top: 0;
height: 50px;
z-index: 2;
display: flex;
align-items: center;
}
#app-navs > * {
margin: 0 12px;
}
#app-navs .el-form-item{
margin-bottom: 0;
}
</style>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<div class="app-navs-frame">
<div id="app-navs"></div>
<div id="app-navs-system"></div>
</div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,82 @@
<template>
<div>
<slot/>
<slot v-if="false" name="left"/>
<slot v-if="false" name="center"/>
<slot v-if="false" name="right"/>
</div>
</template>
<script lang="ts">
import type { VNode } from 'vue'
import { useBasicStore } from '@/store/basic'
export default defineComponent({
name: 'AppPage',
data() {
return {
left: [] as VNode[],
right: [] as VNode[],
center: [] as VNode[],
default: [] as VNode[]
}
},
mounted() {
this.onShowing()
},
activated() {
this.onShowing()
},
beforeUnmount() {
this.onHide()
},
deactivated() {
this.onHide()
},
created() {
const { navbar } = useBasicStore()
const { left, right, center, default: d } = this.$slots
this.left = left?.() ?? []
this.right = right?.() ?? []
this.center = center?.() ?? []
this.default = d?.() ?? []
navbar.left.push(...this.left)
navbar.right.push(...this.right)
navbar.center.push(...this.center)
},
methods: {
onShowing() {
const { left, right, center } = useBasicStore().navbar
const pair = [
[left, this.left],
[right, this.right],
[center, this.center]
]
for (const [s, l] of pair) {
for (const item of s) {
if (!l.includes(item)) l.push(item)
}
}
},
onHide() {
const { left, right, center } = useBasicStore().navbar
const pair = [
[left, this.left],
[right, this.right],
[center, this.center]
]
for (const [s, l] of pair) {
for (const item of l) {
const index = s.indexOf(item)
if (index > -1) s.splice(index, 1)
}
}
}
},
})
</script>
<style scoped lang="scss">
</style>

View File

@ -12,10 +12,10 @@ defineProps({
default: false default: false
} }
}) })
const emit = defineEmits(['toggleClick']) const emit = defineEmits(['toggle-click'])
// //
const toggleClick = () => { const toggleClick = () => {
emit('toggleClick') emit('toggle-click')
} }
</script> </script>

View File

@ -1,131 +1,164 @@
<template> <template>
<div class="navbar rowBC reset-el-dropdown"> <div class="navbar reset-el-dropdown">
<div class="rowSC"> <div class="navbar-left">
<!-- 切换sidebar按钮 --> <div class="rowSC">
<hamburger <!-- 切换sidebar按钮 -->
v-if="settings.showHamburger" <hamburger
:is-active="sidebar.opened" v-if="settings.showHamburger"
class="hamburger-container" :is-active="sidebar.opened"
@toggleClick="toggleSideBar" class="hamburger-container"
/> @toggle-click="toggleSideBar"
<!-- 面包屑导航 --> />
<breadcrumb class="breadcrumb-container" /> <!-- 面包屑导航 -->
<breadcrumb class="breadcrumb-container" />
</div>
<component :is="item" v-for="(item, i) of navbar.left" :key="i"/>
</div> </div>
<!--导航标题--> <div class="navbar-center">
<div v-if="settings.showNavbarTitle" class="heardCenterTitle">{{ settings.title }}</div> <!--导航标题-->
<!-- 下拉操作菜单 --> <div v-if="settings.showNavbarTitle" class="heardCenterTitle">{{ settings.title }}</div>
<teleport to="#app-navs-system"> <component :is="item" v-for="(item, i) of navbar.center" :key="i"/>
</div>
<div class="navbar-right">
<component :is="item" v-for="(item, i) of navbar.right" :key="i"/>
<!-- 下拉操作菜单 -->
<div v-if="settings.ShowDropDown" class="right-menu rowSC"> <div v-if="settings.ShowDropDown" class="right-menu rowSC">
<!-- <ScreenFull />--> <!-- <ScreenFull />-->
<!-- <ScreenLock />--> <!-- <ScreenLock />-->
<!-- <ThemeSelect />--> <!-- <ThemeSelect />-->
<!-- <SizeSelect />--> <!-- <SizeSelect />-->
<!-- <LangSelect />--> <!-- <LangSelect />-->
<el-dropdown trigger="click" size="medium"> <el-dropdown trigger="click" size="medium">
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img src="https://github.jzfai.top/file/images/nav-right-logo.gif" class="user-avatar" /> <img src="https://github.jzfai.top/file/images/nav-right-logo.gif" class="user-avatar" />
<!-- <CaretBottom style="width: 1em; height: 1em; margin-left: 4px" />-->
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<router-link to="/"> <router-link to="/">
<el-dropdown-item>{{ langTitle('Home') }}</el-dropdown-item> <el-dropdown-item>{{ langTitle("Home") }}</el-dropdown-item>
</router-link> </router-link>
<a target="_blank" href="https://github.com/jzfai/vue3-admin-plus"> <a target="_blank" href="https://github.com/jzfai/vue3-admin-plus">
<el-dropdown-item>{{ langTitle('Github') }}</el-dropdown-item> <el-dropdown-item>{{ langTitle("Github") }}</el-dropdown-item>
</a> </a>
<a target="_blank" href="https://github.jzfai.top/low-code-platform"> <a target="_blank" href="https://github.jzfai.top/low-code-platform">
<el-dropdown-item>{{ langTitle('LowCodePlatFrom') }}</el-dropdown-item> <el-dropdown-item>{{ langTitle("LowCodePlatFrom") }}</el-dropdown-item>
</a> </a>
<!--<el-dropdown-item>修改密码</el-dropdown-item>--> <el-dropdown-item divided @click="loginOut">{{ langTitle("login out") }}</el-dropdown-item>
<el-dropdown-item divided @click="loginOut">{{ langTitle('login out') }}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</div> </div>
</teleport> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick } from 'vue' import { nextTick } from "vue";
import { CaretBottom } from '@element-plus/icons-vue' import { useRouter } from "vue-router";
import { useRouter } from 'vue-router' import Breadcrumb from "./Breadcrumb.vue";
import Breadcrumb from './Breadcrumb.vue' import Hamburger from "./Hamburger.vue";
import Hamburger from './Hamburger.vue' import LangSelect from "./component/LangSelect.vue";
import LangSelect from './component/LangSelect.vue' import ScreenFull from "./component/ScreenFull.vue";
import ScreenFull from './component/ScreenFull.vue' import SizeSelect from "./component/SizeSelect.vue";
import SizeSelect from './component/SizeSelect.vue' import ThemeSelect from "./component/ThemeSelect.vue";
import ThemeSelect from './component/ThemeSelect.vue' import ScreenLock from "./component/ScreenLock.vue";
import ScreenLock from './component/ScreenLock.vue' import { resetState } from "@/hooks/use-permission";
import { resetState } from '@/hooks/use-permission' import { elMessage } from "@/hooks/use-element";
import { elMessage } from '@/hooks/use-element' import { useBasicStore } from "@/store/basic";
import { useBasicStore } from '@/store/basic' import { langTitle } from "@/hooks/use-common";
import { langTitle } from '@/hooks/use-common'
const basicStore = useBasicStore();
const { settings, sidebar, setToggleSideBar, navbar } = basicStore;
const basicStore = useBasicStore()
const { settings, sidebar, setToggleSideBar } = basicStore
const toggleSideBar = () => { const toggleSideBar = () => {
setToggleSideBar() setToggleSideBar();
} };
//退 //退
const router = useRouter() const router = useRouter();
const loginOut = () => { const loginOut = () => {
elMessage('退出登录成功') elMessage("退出登录成功");
router.push(`/login?redirect=/`) router.push(`/login?redirect=/`);
nextTick(() => { nextTick(() => {
resetState() resetState();
}) });
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.navbar { .navbar {
height: var(--nav-bar-height); height: var(--nav-bar-height);
overflow: hidden; overflow: hidden;
position: relative;
background: var(--nav-bar-background); background: var(--nav-bar-background);
box-shadow: var(--nav-bar-box-shadow); box-shadow: var(--nav-bar-box-shadow);
z-index: 1; z-index: 1;
} display: flex;
align-items: center;
//logo .navbar-left{
.avatar-wrapper { flex: 1 0 0;
margin-top: 5px; display: flex;
position: relative; align-items: center;
cursor: pointer; > * {
margin-right: 1em;
.user-avatar { }
cursor: pointer; }
width: 40px; .navbar-center{
height: 40px; flex: 1 1 0;
border-radius: 10px; display: flex;
align-items: center;
justify-content: center;
> * {
margin: 0.5em;
}
}
.navbar-right{
flex: 1 0 0;
display: flex;
align-items: center;
justify-content: flex-end;
> *{
margin-left: 1em;
}
} }
.el-icon-caret-bottom { //center-title
cursor: pointer; .heardCenterTitle {
text-align: center;
position: absolute; position: absolute;
right: -20px; top: 50%;
top: 25px; left: 46%;
font-size: 12px; font-weight: 600;
font-size: 20px;
transform: translate(-50%, -50%);
}
//drop-down
.right-menu {
cursor: pointer;
margin-right: 16px;
background-color: var(--nav-bar-right-menu-background);
//logo
.avatar-wrapper {
margin-top: 5px;
position: relative;
cursor: pointer;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
} }
} }
//center-title
.heardCenterTitle {
text-align: center;
position: absolute;
top: 50%;
left: 46%;
font-weight: 600;
font-size: 20px;
transform: translate(-50%, -50%);
}
//drop-down
.right-menu {
cursor: pointer;
margin-right: 16px;
background-color: var(--nav-bar-right-menu-background);
}
</style> </style>

View File

@ -1,3 +1,4 @@
import type { VNode } from 'vue'
import { nextTick } from 'vue' import { nextTick } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { RouterTypes } from '~/basic' import type { RouterTypes } from '~/basic'
@ -22,7 +23,12 @@ export const useBasicStore = defineStore('basic', {
sidebar: { opened: true }, sidebar: { opened: true },
//axios req collection //axios req collection
axiosPromiseArr: [] as Array<ObjKeys>, axiosPromiseArr: [] as Array<ObjKeys>,
settings: defaultSettings settings: defaultSettings,
navbar: {
left: [] as VNode[],
right: [] as VNode[],
center: [] as VNode[]
}
} }
}, },
persist: { persist: {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="menuManagement-container"> <app-page class="menuManagement-container">
<!-- <vab-query-form>--> <!-- <vab-query-form>-->
<!-- <vab-query-form-left-panel :span="4">--> <!-- <vab-query-form-left-panel :span="4">-->
<!-- <last-breadcrumb class="page-breadcrumb"/>--> <!-- <last-breadcrumb class="page-breadcrumb"/>-->
@ -7,13 +7,12 @@
<!-- <vab-query-form-right-panel :span="20">--> <!-- <vab-query-form-right-panel :span="20">-->
<!-- </vab-query-form-right-panel>--> <!-- </vab-query-form-right-panel>-->
<!-- </vab-query-form>--> <!-- </vab-query-form>-->
<teleport to="#app-navs"> <template #right>
<el-button type="primary" @click="handleEdit()">添加</el-button> <el-button type="primary" @click="handleEdit()">添加</el-button>
</teleport> </template>
<div class="container-inner"> <div class="container-inner">
<el-table <el-table
v-loading="listLoading" v-loading="listLoading"
:height="height"
:data="list" :data="list"
:element-loading-text="elementLoadingText" :element-loading-text="elementLoadingText"
row-key="path" row-key="path"
@ -79,15 +78,16 @@
</el-table> </el-table>
<edit ref="edit" :menu-data="menuData" @fetch-data="fetchData"/> <edit ref="edit" :menu-data="menuData" @fetch-data="fetchData"/>
</div> </div>
</div> </app-page>
</template> </template>
<script> <script>
import { doDelete, getTree } from '@/api/menuManagement' import { doDelete, getTree } from '@/api/menuManagement'
import Edit from './components/MenuManagementEdit.vue' import Edit from './components/MenuManagementEdit.vue'
import AppPage from "@/components/AppPage.vue";
export default { export default {
name: 'MenuManagement', name: 'MenuManagement',
components: { Edit }, components: { Edit, AppPage },
data() { data() {
return { return {
data: [], data: [],
@ -101,9 +101,6 @@
} }
}, },
computed: { computed: {
height() {
return 'auto'
},
menuData() { menuData() {
return this.list return this.list
}, },

View File

@ -1,35 +1,35 @@
<template> <template>
<div class="roleManagement-container"> <app-page class="roleManagement-container">
<teleport to="#app-navs"> <template #right>
<el-form :inline="true" :model="queryForm" class="nav" @submit.prevent> <el-form :inline="true" :model="queryForm" class="nav" @submit.prevent>
<el-form-item> <el-form-item>
<el-input <el-input
v-model.trim="queryForm.roleName" v-model.trim="queryForm.roleName"
placeholder="请输入查询条件" placeholder="请输入查询条件"
@keyup.enter="queryData" @keyup.enter="queryData"
> >
<template #suffix> <template #suffix>
<i <i
class="el-icon-search el-input__icon i-search" class="el-icon-search el-input__icon i-search"
@click="queryData" @click="queryData"
/> />
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleEdit">添加</el-button> <el-button type="primary" @click="handleEdit">添加</el-button>
</el-form-item> </el-form-item>
<!-- <el-form-item>--> <!-- <el-form-item>-->
<!-- <el-button--> <!-- <el-button-->
<!-- icon="el-icon-delete"--> <!-- icon="el-icon-delete"-->
<!-- type="danger"--> <!-- type="danger"-->
<!-- @click="handleDestroy"--> <!-- @click="handleDestroy"-->
<!-- >--> <!-- >-->
<!-- 批量删除--> <!-- 批量删除-->
<!-- </el-button>--> <!-- </el-button>-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
</el-form> </el-form>
</teleport> </template>
<div class="container-inner"> <div class="container-inner">
<el-table <el-table
v-loading="listLoading" v-loading="listLoading"
@ -38,7 +38,6 @@
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:header-cell-style="{ background: '#F7F9FB', textAlign: 'center' }" :header-cell-style="{ background: '#F7F9FB', textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"
:height="height"
@selection-change="setSelectRows" @selection-change="setSelectRows"
> >
<!-- <el-table-column show-overflow-tooltip type="selection"></el-table-column>--> <!-- <el-table-column show-overflow-tooltip type="selection"></el-table-column>-->
@ -68,10 +67,10 @@
<el-table-column label="操作" width="200"> <el-table-column label="操作" width="200">
<template #default="{ row }"> <template #default="{ row }">
<el-button class="icon" type="primary" link @click="handleEdit(row)"> <el-button class="icon" type="primary" link @click="handleEdit(row)">
<i class="bi bi-pencil-square"/> <i class="bi bi-pencil-square" />
</el-button> </el-button>
<el-button class="icon" type="primary" link @click="handleDestroy(row)"> <el-button class="icon" type="primary" link @click="handleDestroy(row)">
<i class="bi bi-trash"/> <i class="bi bi-trash" />
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -86,107 +85,112 @@
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
/> />
<edit ref="edit" @fetch-data="fetchData"/> <edit ref="edit" @fetch-data="fetchData" />
</div> </div>
</div> </app-page>
</template> </template>
<script> <script>
import { doDelete, getList } from '@/api/roleManagement' import { doDelete, getList } from "@/api/roleManagement";
import Edit from './components/RoleManagementEdit.vue' import Edit from "./components/RoleManagementEdit.vue";
import AppPage from "@/components/AppPage.vue";
export default { export default {
name: 'RoleManagement', name: "RoleManagement",
components: { Edit }, components: { Edit, AppPage },
data() { data() {
return { return {
list: null, list: null,
listLoading: true, listLoading: true,
layout: 'total, sizes, prev, pager, next, jumper', layout: "total, sizes, prev, pager, next, jumper",
total: 0, total: 0,
selectRows: '', selectRows: "",
elementLoadingText: '正在加载...', elementLoadingText: "正在加载...",
queryForm: { queryForm: {
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
roleName: '', roleName: ""
}, }
};
},
created() {
this.fetchData();
},
methods: {
setSelectRows(val) {
this.selectRows = val;
},
handleEdit(row) {
if (row.id) {
this.$refs["edit"].showEdit(row);
} else {
this.$refs["edit"].showEdit();
} }
}, },
computed: { async handleDestroy(row) {
height() { // if (row.id) {
return 'auto' // this.$baseConfirm('', null, async () => {
}, // const { msg } = await doDelete({ ids: row.id })
// this.$baseMessage(msg, 'success')
// this.fetchData()
// })
// } else {
// if (this.selectRows.length > 0) {
// const ids = this.selectRows.map((item) => item.id).join()
// this.$baseConfirm('', null, async () => {
// const { msg } = await doDelete({ ids })
// this.$baseMessage(msg, 'success')
// this.fetchData()
// })
// } else {
// this.$baseMessage('', 'error')
// return false
// }
// }
const rs = await elConfirmNoCancelBtn(null, "你确定要删除当前项吗");
if (rs !== "confirm") return;
const { msg } = await doDelete({
id: row.id
});
elMessage(msg, "success");
this.fetchData();
}, },
created() { handleSizeChange(val) {
this.fetchData() this.queryForm.pageSize = val;
this.fetchData();
}, },
methods: { handleCurrentChange(val) {
setSelectRows(val) { this.queryForm.pageNo = val;
this.selectRows = val this.fetchData();
},
handleEdit(row) {
if (row.id) {
this.$refs['edit'].showEdit(row)
} else {
this.$refs['edit'].showEdit()
}
},
async handleDestroy(row) {
// if (row.id) {
// this.$baseConfirm('', null, async () => {
// const { msg } = await doDelete({ ids: row.id })
// this.$baseMessage(msg, 'success')
// this.fetchData()
// })
// } else {
// if (this.selectRows.length > 0) {
// const ids = this.selectRows.map((item) => item.id).join()
// this.$baseConfirm('', null, async () => {
// const { msg } = await doDelete({ ids })
// this.$baseMessage(msg, 'success')
// this.fetchData()
// })
// } else {
// this.$baseMessage('', 'error')
// return false
// }
// }
const rs = await elConfirmNoCancelBtn(null, '你确定要删除当前项吗');
if (rs !== "confirm") return;
const { msg } = await doDelete({
id: row.id,
})
elMessage(msg, 'success')
this.fetchData()
},
handleSizeChange(val) {
this.queryForm.pageSize = val
this.fetchData()
},
handleCurrentChange(val) {
this.queryForm.pageNo = val
this.fetchData()
},
queryData() {
this.queryForm.pageNo = 1
this.fetchData()
},
async fetchData() {
this.listLoading = true
const { data } = await getList(this.queryForm)
this.list = data.rows
this.total = data.total
setTimeout(() => {
this.listLoading = false
}, 300)
},
}, },
queryData() {
this.queryForm.pageNo = 1;
this.fetchData();
},
async fetchData() {
this.listLoading = true;
const { data } = await getList(this.queryForm);
this.list = data.rows;
this.total = data.total;
setTimeout(() => {
this.listLoading = false;
}, 300);
}
} }
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.icon{ .nav > * {
margin-bottom: 0;
margin-right: 1em;
&:last-child {
margin-right: 0;
}
}
.icon {
font-size: 1.5em !important; font-size: 1.5em !important;
padding: 5px !important; padding: 5px !important;
} }

View File

@ -7,6 +7,8 @@ export {}
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
AppPage: typeof import('./../src/components/AppPage.vue')['default']
Container: typeof import('@/components/AppPage.vue')['default']
ElSvgIcon: typeof import('./../src/components/ElSvgIcon.vue')['default'] ElSvgIcon: typeof import('./../src/components/ElSvgIcon.vue')['default']
IconSelector: typeof import('./../src/components/IconSelector.vue')['default'] IconSelector: typeof import('./../src/components/IconSelector.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']