更新Navbar

This commit is contained in:
洛洛希雅Lolosia 2023-08-30 10:36:17 +08:00
parent 6ff36a9436
commit 6741099695
7 changed files with 208 additions and 203 deletions

View File

@ -44,7 +44,7 @@ export default [
{ {
order_no: '@guid()', order_no: '@guid()',
timestamp: +Mock.Random.date('T'), timestamp: +Mock.Random.date('T'),
username: '@name()', userName: '@name()',
price: '@float(1000, 15000, 0, 2)', price: '@float(1000, 15000, 0, 2)',
'status|1': ['success', 'pending'] 'status|1': ['success', 'pending']
} }

View File

@ -0,0 +1,29 @@
import { Fragment } from '@vue/runtime-core'
import type { Prop, VNode } from 'vue'
import { Comment, defineComponent } from 'vue'
export default defineComponent({
name: 'NavBarAppTools',
props: {
position: {
type: String,
require: true
} as Prop<'left' | 'right' | 'center'>
},
render() {
const navbar = useBasicStore().navbar
const sorts = navbar[this.position!] as Map<string, () => VNode[]>
if (!sorts) return h(Comment, 'nav bar app tools')
let out = [] as VNode[]
for (const value of sorts.values()) {
try {
out.push(...value())
} catch (e) {
console.error(e)
}
}
if (this.$props.position === 'right') out = out.reverse()
return h(Fragment, out)
}
})

View File

@ -12,131 +12,39 @@
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<breadcrumb class="breadcrumb-container" /> <breadcrumb class="breadcrumb-container" />
</div> </div>
<component :is="item" v-for="(item, i) of navbar.left" :key="i" /> <NavBarAppTools position="left" />
</div> </div>
<div class="navbar-center"> <div class="navbar-center">
<!--导航标题--> <!--导航标题-->
<div v-if="settings.showNavbarTitle" class="heardCenterTitle">{{ settings.title }}</div> <div v-if="settings.showNavbarTitle" class="heardCenterTitle">{{ settings.title }}</div>
<component :is="item" v-for="(item, i) of navbar.center" :key="i" /> <NavBarAppTools position="center" />
</div> </div>
<div class="navbar-right"> <div class="navbar-right">
<component :is="item" v-for="(item, i) of navbar.right" :key="i" /> <NavBarAppTools position="right" />
<!-- 下拉操作菜单 --> <!-- 下拉操作菜单 -->
<debugger /> <debugger />
<div v-if="settings.ShowDropDown" class="right-menu rowSC"> <AvatarFrame />
<!-- <ScreenFull />-->
<!-- <ScreenLock />-->
<!-- <ThemeSelect />-->
<!-- <SizeSelect />-->
<!-- <LangSelect />-->
<el-dropdown trigger="click" size="default">
<div class="avatar-wrapper" :title="(userInfo?.realName || userInfo?.userName) + ' - 在线'">
<img :src="userInfo.avatar ?? userImage" alt="用户头像" class="user-avatar" />
<div class="user-avatar-status" :style="{backgroundColor: !online ? 'red' : null}">
<el-popover :visible="!online" :effect="'dark'" :width="170">
<template #reference>
<div />
</template>
<div class="online-status">
<menu-icon icon="exclamation-triangle-fill" />
<span>与服务器连接中断</span>
</div>
</el-popover>
</div>
</div>
<template #dropdown>
<el-dropdown-menu class="drop-down">
<el-dropdown-item class="welcome-user">
{{ time }}<b>{{ userInfo.realName || userImage.userName || "平台用户" }}</b>
</el-dropdown-item>
<router-link to="/">
<el-dropdown-item divided>{{ langTitle("Home") }}</el-dropdown-item>
</router-link>
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item divided @click="loginOut">{{ langTitle("login out") }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { VNode } from "vue"; import AvatarFrame from '@/layout/default/app-main/component/AvatarFrame.vue'
import { nextTick } from "vue"; import Breadcrumb from './Breadcrumb.vue'
import { useRouter } from "vue-router"; import Hamburger from './Hamburger.vue'
import Breadcrumb from "./Breadcrumb.vue"; import { useBasicStore } from '@/store/basic'
import Hamburger from "./Hamburger.vue"; import Debugger from '@/layout/default/app-main/component/Debugger.vue'
import LangSelect from "./component/LangSelect.vue"; import NavBarAppTools from '@/layout/default/app-main/NavBarAppTools'
import ScreenFull from "./component/ScreenFull.vue";
import SizeSelect from "./component/SizeSelect.vue";
import ThemeSelect from "./component/ThemeSelect.vue";
import ScreenLock from "./component/ScreenLock.vue";
import { resetState } from "@/hooks/use-permission";
import { elMessage } from "@/hooks/use-element";
import { useBasicStore } from "@/store/basic";
import { langTitle } from "@/hooks/use-common";
import userImage from "@/assets/layout/user.png";
import Debugger from "@/layout/default/app-main/component/Debugger.vue";
import MenuIcon from "@/components/MenuIcon.vue";
import { useDebuggerStore } from "@/store/debuger";
const basicStore = useBasicStore(); const basicStore = useBasicStore()
const { settings, sidebar, setToggleSideBar, userInfo } = basicStore; const { settings, sidebar, setToggleSideBar } = basicStore
const online = toRef(basicStore, "online");
const toggleSideBar = () => { const toggleSideBar = () => {
setToggleSideBar(); setToggleSideBar()
}; }
//退
const router = useRouter();
const loginOut = () => {
elMessage("退出登录成功");
router.push(`/login?redirect=/`);
nextTick(() => {
resetState();
});
};
const time = computed(() => {
const hover = new Date().getHours();
if (hover > 20) return "晚上";
if (hover > 17) return "傍晚";
if (hover > 13) return "下午";
if (hover > 11) return "中午";
if (hover > 8) return "上午";
if (hover > 5) return "早上";
return "凌晨";
});
const navbar = reactive({
left: [] as VNode[],
center: [] as VNode[],
right: [] as VNode[]
});
watch(() => basicStore.navbar.cursor, () => {
const left = navbar.left = [] as VNode[];
for (const value of basicStore.navbar.left.values()) {
left.push(...value);
}
const center = navbar.center = [] as VNode[];
for (const value of basicStore.navbar.center.values()) {
center.push(...value);
}
const right = navbar.right = [] as VNode[];
for (const value of basicStore.navbar.right.values()) {
right.push(...value);
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.online-status { .online-status {
display: flex; display: flex;
align-items: center; align-items: center;
@ -210,46 +118,6 @@ watch(() => basicStore.navbar.cursor, () => {
font-size: 20px; font-size: 20px;
transform: translate(-50%, -50%); 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;
}
.user-avatar-status {
position: absolute;
right: -2px;
bottom: 0;
height: 10px;
width: 10px;
border-radius: 50%;
background-color: #55e802;
box-shadow: 0 0 3px black;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
} }
.drop-down { .drop-down {

View File

@ -0,0 +1,28 @@
<template>
<div class="avatar-wrapper" :title="(userInfo?.realName || userInfo?.userName) + ' - 在线'">
<img :src="userInfo.avatar ?? userImage" alt="用户头像" class="user-avatar" />
<div class="user-avatar-status" :style="{ backgroundColor: !online ? 'red' : null }">
<el-popover :visible="!online" :effect="'dark'" :width="170">
<template #reference>
<div />
</template>
<div class="online-status">
<menu-icon icon="exclamation-triangle-fill" />
<span>与服务器连接中断</span>
</div>
</el-popover>
</div>
</div>
</template>
<script setup lang="ts">
import userImage from '@/assets/layout/user.png'
import { useBasicStore } from '@/store/basic'
const basicStore = useBasicStore()
const { settings, sidebar, userInfo } = basicStore
const online = toRef(basicStore, 'online')
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,81 @@
<template>
<div v-if="settings.ShowDropDown" class="right-menu rowSC">
<!-- <ScreenFull />-->
<!-- <ScreenLock />-->
<!-- <ThemeSelect />-->
<!-- <SizeSelect />-->
<!-- <LangSelect />-->
<el-dropdown trigger="click" size="default">
<Avatar />
<template #dropdown>
<el-dropdown-menu class="drop-down">
<el-dropdown-item class="welcome-user">
{{ time }}
<b>{{ userInfo.realName || userInfo.userName || '平台用户' }}</b>
</el-dropdown-item>
<router-link to="/">
<el-dropdown-item divided>{{ langTitle('Home') }}</el-dropdown-item>
</router-link>
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item divided @click="loginOut">{{ langTitle('login out') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup lang="ts">
import Avatar from '@/layout/default/app-main/component/Avatar.vue'
import { nextTick } from 'vue'
import { useRouter } from 'vue-router'
import LangSelect from './component/LangSelect.vue'
import ScreenFull from './component/ScreenFull.vue'
import SizeSelect from './component/SizeSelect.vue'
import ThemeSelect from './component/ThemeSelect.vue'
import ScreenLock from './component/ScreenLock.vue'
import { resetState } from '@/hooks/use-permission'
import { elMessage } from '@/hooks/use-element'
import { useBasicStore } from '@/store/basic'
import { langTitle } from '@/hooks/use-common'
const basicStore = useBasicStore()
const { settings, sidebar, userInfo } = basicStore
//退
const router = useRouter()
const loginOut = () => {
elMessage('退出登录成功')
router.push(`/login?redirect=/`)
nextTick(() => {
resetState()
})
}
const time = computed(() => {
const hover = new Date().getHours()
if (hover > 20) return '晚上'
if (hover > 17) return '傍晚'
if (hover > 13) return '下午'
if (hover > 11) return '中午'
if (hover > 8) return '上午'
if (hover > 5) return '早上'
return '凌晨'
})
</script>
<style scoped lang="scss">
//drop-down
.right-menu {
cursor: pointer;
margin-right: 16px !important;
position: relative;
background-color: var(--nav-bar-right-menu-background);
.switch-platform-btn {
margin: 0 0.5em 0 1em;
padding: 0 0.5em;
font-size: 1em;
}
}
</style>

View File

@ -13,7 +13,7 @@ export const useBasicStore = defineStore('basic', {
getUserInfo: false, getUserInfo: false,
online: true, online: true,
userInfo: { userInfo: {
username: '', userName: '',
realName: '', realName: '',
avatar: '', avatar: '',
phone: '', phone: '',
@ -75,7 +75,7 @@ export const useBasicStore = defineStore('basic', {
state.filterAsyncRoutes = [] state.filterAsyncRoutes = []
//reset userInfo //reset userInfo
state.userInfo = { state.userInfo = {
username: '', userName: '',
realName: '', realName: '',
avatar: '', avatar: '',
phone: '', phone: '',

View File

@ -7,11 +7,11 @@
<div class="title-container"> <div class="title-container">
<h3 class="title text-center">{{ settings.title }}</h3> <h3 class="title text-center">{{ settings.title }}</h3>
</div> </div>
<el-form-item prop="username" :rules="formRules.isNotNull('用户名')"> <el-form-item prop="userName" :rules="formRules.isNotNull('用户名')">
<span class="svg-container"> <span class="svg-container">
<ElSvgIcon name="User" :size="14" /> <ElSvgIcon name="User" :size="14" />
</span> </span>
<el-input v-model="subForm.username" placeholder="用户名" /> <el-input v-model="subForm.userName" placeholder="用户名" />
<!--占位--> <!--占位-->
</el-form-item> </el-form-item>
<el-form-item prop="password" :rules="formRules.isNotNull('密码')"> <el-form-item prop="password" :rules="formRules.isNotNull('密码')">
@ -40,117 +40,116 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import { useBasicStore } from "@/store/basic"; import { useBasicStore } from '@/store/basic'
import { elMessage, useElement } from "@/hooks/use-element"; import { elMessage, useElement } from '@/hooks/use-element'
import { getMyRole, login } from "@/api/user"; import { getMyRole, login } from '@/api/user'
import type { FormInstance, InputInstance } from "element-plus"; import type { FormInstance, InputInstance } from 'element-plus'
import { settings as viteSettings } from "@/settings"; import { settings as viteSettings } from '@/settings'
import { ElMessage } from "element-plus"; import { ElMessage } from 'element-plus'
/* listen router change and set the query */ /* listen router change and set the query */
const { settings } = useBasicStore(); const { settings } = useBasicStore()
//element valid //element valid
const formRules = useElement().formRules; const formRules = useElement().formRules
//form //form
const subForm = reactive({ const subForm = reactive({
username: "", userName: '',
password: "" password: ''
}); })
const state: any = reactive({ const state: any = reactive({
otherQuery: {}, otherQuery: {},
redirect: undefined redirect: undefined
}); })
const route = useRoute(); const route = useRoute()
function getOtherQuery(query) { function getOtherQuery(query) {
return Object.keys(query).reduce((acc, cur) => { return Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") { if (cur !== 'redirect') {
acc[cur] = query[cur]; acc[cur] = query[cur]
} }
return acc; return acc
}, {}); }, {})
} }
watch( watch(
() => route.query, () => route.query,
(query) => { (query) => {
if (query) { if (query) {
state.redirect = query.redirect; state.redirect = query.redirect
state.otherQuery = getOtherQuery(query); state.otherQuery = getOtherQuery(query)
} }
}, },
{ immediate: true } { immediate: true }
); )
/* /*
* login relative * login relative
* */ * */
let subLoading = $ref(false); let subLoading = $ref(false)
//tip message //tip message
let tipMessage = $ref(""); let tipMessage = $ref('')
//sub form //sub form
const refLoginForm: FormInstance = $ref(null); const refLoginForm: FormInstance = $ref(null)
function handleLogin() { function handleLogin() {
refLoginForm.validate((valid) => { refLoginForm.validate((valid) => {
if (valid) { if (valid) {
subLoading = true; subLoading = true
loginFunc(); loginFunc()
} else {
ElMessage.error('请检查输入是否正确')
} }
else { })
ElMessage.error("请检查输入是否正确")
}
});
} }
const router = useRouter(); const router = useRouter()
const basicStore = useBasicStore(); const basicStore = useBasicStore()
async function loginFunc() { async function loginFunc() {
try { try {
const data = await login(subForm); const data = await login(subForm)
elMessage("登录成功"); elMessage('登录成功')
basicStore.setToken(data?.Authorization); basicStore.setToken(data?.Authorization)
const role = await getMyRole(); const role = await getMyRole()
basicStore.setUserInfo({ basicStore.setUserInfo({
userInfo: data, userInfo: data,
roles: [role.roleType], roles: [role.roleType],
codes: [role.roleId] codes: [role.roleId]
}); })
// router.push("/").catch(e => console.error(e)); // router.push("/").catch(e => console.error(e));
if (route.query?.redirect) { if (route.query?.redirect) {
let url = route.query.redirect as string; let url = route.query.redirect as string
if (viteSettings.viteBasePath.endsWith("/") && url.startsWith("/")) { if (viteSettings.viteBasePath.endsWith('/') && url.startsWith('/')) {
url = url.replace(/^\//, ""); url = url.replace(/^\//, '')
} }
window.location.href = viteSettings.viteBasePath + url; window.location.href = viteSettings.viteBasePath + url
} else { } else {
window.location.href = viteSettings.viteBasePath; window.location.href = viteSettings.viteBasePath
} }
} catch (e: Error | any) { } catch (e: Error | any) {
tipMessage = e?.message; tipMessage = e?.message
} finally { } finally {
subLoading = false; subLoading = false
} }
} }
/* /*
* password show or hidden * password show or hidden
* */ * */
const passwordType = ref("password"); const passwordType = ref('password')
const refPassword = ref<InputInstance>(); const refPassword = ref<InputInstance>()
function showPwd() { function showPwd() {
if (passwordType.value === "password") { if (passwordType.value === 'password') {
passwordType.value = ""; passwordType.value = ''
} else { } else {
passwordType.value = "password"; passwordType.value = 'password'
} }
nextTick(() => { nextTick(() => {
refPassword.value!.focus(); refPassword.value!.focus()
}); })
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>