更新Navbar
This commit is contained in:
parent
6ff36a9436
commit
6741099695
|
@ -44,7 +44,7 @@ export default [
|
|||
{
|
||||
order_no: '@guid()',
|
||||
timestamp: +Mock.Random.date('T'),
|
||||
username: '@name()',
|
||||
userName: '@name()',
|
||||
price: '@float(1000, 15000, 0, 2)',
|
||||
'status|1': ['success', 'pending']
|
||||
}
|
||||
|
|
29
src/layout/default/app-main/NavBarAppTools.ts
Normal file
29
src/layout/default/app-main/NavBarAppTools.ts
Normal 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)
|
||||
}
|
||||
})
|
|
@ -12,131 +12,39 @@
|
|||
<!-- 面包屑导航 -->
|
||||
<breadcrumb class="breadcrumb-container" />
|
||||
</div>
|
||||
<component :is="item" v-for="(item, i) of navbar.left" :key="i" />
|
||||
<NavBarAppTools position="left" />
|
||||
</div>
|
||||
<div class="navbar-center">
|
||||
<!--导航标题-->
|
||||
<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 class="navbar-right">
|
||||
<component :is="item" v-for="(item, i) of navbar.right" :key="i" />
|
||||
<NavBarAppTools position="right" />
|
||||
<!-- 下拉操作菜单 -->
|
||||
<debugger />
|
||||
<div v-if="settings.ShowDropDown" class="right-menu rowSC">
|
||||
<!-- <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>
|
||||
<AvatarFrame />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VNode } from "vue";
|
||||
import { nextTick } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import Breadcrumb from "./Breadcrumb.vue";
|
||||
import Hamburger from "./Hamburger.vue";
|
||||
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";
|
||||
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";
|
||||
import AvatarFrame from '@/layout/default/app-main/component/AvatarFrame.vue'
|
||||
import Breadcrumb from './Breadcrumb.vue'
|
||||
import Hamburger from './Hamburger.vue'
|
||||
import { useBasicStore } from '@/store/basic'
|
||||
import Debugger from '@/layout/default/app-main/component/Debugger.vue'
|
||||
import NavBarAppTools from '@/layout/default/app-main/NavBarAppTools'
|
||||
|
||||
const basicStore = useBasicStore();
|
||||
const { settings, sidebar, setToggleSideBar, userInfo } = basicStore;
|
||||
const online = toRef(basicStore, "online");
|
||||
const basicStore = useBasicStore()
|
||||
const { settings, sidebar, setToggleSideBar } = basicStore
|
||||
|
||||
const toggleSideBar = () => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
setToggleSideBar()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.online-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -210,46 +118,6 @@ watch(() => basicStore.navbar.cursor, () => {
|
|||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
|
28
src/layout/default/app-main/component/Avatar.vue
Normal file
28
src/layout/default/app-main/component/Avatar.vue
Normal 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>
|
81
src/layout/default/app-main/component/AvatarFrame.vue
Normal file
81
src/layout/default/app-main/component/AvatarFrame.vue
Normal 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>
|
|
@ -13,7 +13,7 @@ export const useBasicStore = defineStore('basic', {
|
|||
getUserInfo: false,
|
||||
online: true,
|
||||
userInfo: {
|
||||
username: '',
|
||||
userName: '',
|
||||
realName: '',
|
||||
avatar: '',
|
||||
phone: '',
|
||||
|
@ -75,7 +75,7 @@ export const useBasicStore = defineStore('basic', {
|
|||
state.filterAsyncRoutes = []
|
||||
//reset userInfo
|
||||
state.userInfo = {
|
||||
username: '',
|
||||
userName: '',
|
||||
realName: '',
|
||||
avatar: '',
|
||||
phone: '',
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
<div class="title-container">
|
||||
<h3 class="title text-center">{{ settings.title }}</h3>
|
||||
</div>
|
||||
<el-form-item prop="username" :rules="formRules.isNotNull('用户名')">
|
||||
<el-form-item prop="userName" :rules="formRules.isNotNull('用户名')">
|
||||
<span class="svg-container">
|
||||
<ElSvgIcon name="User" :size="14" />
|
||||
</span>
|
||||
<el-input v-model="subForm.username" placeholder="用户名" />
|
||||
<el-input v-model="subForm.userName" placeholder="用户名" />
|
||||
<!--占位-->
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" :rules="formRules.isNotNull('密码')">
|
||||
|
@ -40,117 +40,116 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useBasicStore } from "@/store/basic";
|
||||
import { elMessage, useElement } from "@/hooks/use-element";
|
||||
import { getMyRole, login } from "@/api/user";
|
||||
import type { FormInstance, InputInstance } from "element-plus";
|
||||
import { settings as viteSettings } from "@/settings";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useBasicStore } from '@/store/basic'
|
||||
import { elMessage, useElement } from '@/hooks/use-element'
|
||||
import { getMyRole, login } from '@/api/user'
|
||||
import type { FormInstance, InputInstance } from 'element-plus'
|
||||
import { settings as viteSettings } from '@/settings'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
/* listen router change and set the query */
|
||||
const { settings } = useBasicStore();
|
||||
const { settings } = useBasicStore()
|
||||
//element valid
|
||||
const formRules = useElement().formRules;
|
||||
const formRules = useElement().formRules
|
||||
//form
|
||||
const subForm = reactive({
|
||||
username: "",
|
||||
password: ""
|
||||
});
|
||||
userName: '',
|
||||
password: ''
|
||||
})
|
||||
const state: any = reactive({
|
||||
otherQuery: {},
|
||||
redirect: undefined
|
||||
});
|
||||
const route = useRoute();
|
||||
})
|
||||
const route = useRoute()
|
||||
|
||||
function getOtherQuery(query) {
|
||||
return Object.keys(query).reduce((acc, cur) => {
|
||||
if (cur !== "redirect") {
|
||||
acc[cur] = query[cur];
|
||||
if (cur !== 'redirect') {
|
||||
acc[cur] = query[cur]
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(query) => {
|
||||
if (query) {
|
||||
state.redirect = query.redirect;
|
||||
state.otherQuery = getOtherQuery(query);
|
||||
state.redirect = query.redirect
|
||||
state.otherQuery = getOtherQuery(query)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
)
|
||||
|
||||
/*
|
||||
* login relative
|
||||
* */
|
||||
let subLoading = $ref(false);
|
||||
let subLoading = $ref(false)
|
||||
//tip message
|
||||
let tipMessage = $ref("");
|
||||
let tipMessage = $ref('')
|
||||
//sub form
|
||||
const refLoginForm: FormInstance = $ref(null);
|
||||
const refLoginForm: FormInstance = $ref(null)
|
||||
|
||||
function handleLogin() {
|
||||
refLoginForm.validate((valid) => {
|
||||
if (valid) {
|
||||
subLoading = true;
|
||||
loginFunc();
|
||||
subLoading = true
|
||||
loginFunc()
|
||||
} else {
|
||||
ElMessage.error('请检查输入是否正确')
|
||||
}
|
||||
else {
|
||||
ElMessage.error("请检查输入是否正确")
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const basicStore = useBasicStore();
|
||||
const router = useRouter()
|
||||
const basicStore = useBasicStore()
|
||||
|
||||
async function loginFunc() {
|
||||
try {
|
||||
const data = await login(subForm);
|
||||
elMessage("登录成功");
|
||||
basicStore.setToken(data?.Authorization);
|
||||
const role = await getMyRole();
|
||||
const data = await login(subForm)
|
||||
elMessage('登录成功')
|
||||
basicStore.setToken(data?.Authorization)
|
||||
const role = await getMyRole()
|
||||
basicStore.setUserInfo({
|
||||
userInfo: data,
|
||||
roles: [role.roleType],
|
||||
codes: [role.roleId]
|
||||
});
|
||||
})
|
||||
// router.push("/").catch(e => console.error(e));
|
||||
if (route.query?.redirect) {
|
||||
let url = route.query.redirect as string;
|
||||
if (viteSettings.viteBasePath.endsWith("/") && url.startsWith("/")) {
|
||||
url = url.replace(/^\//, "");
|
||||
let url = route.query.redirect as string
|
||||
if (viteSettings.viteBasePath.endsWith('/') && url.startsWith('/')) {
|
||||
url = url.replace(/^\//, '')
|
||||
}
|
||||
window.location.href = viteSettings.viteBasePath + url;
|
||||
window.location.href = viteSettings.viteBasePath + url
|
||||
} else {
|
||||
window.location.href = viteSettings.viteBasePath;
|
||||
window.location.href = viteSettings.viteBasePath
|
||||
}
|
||||
} catch (e: Error | any) {
|
||||
tipMessage = e?.message;
|
||||
tipMessage = e?.message
|
||||
} finally {
|
||||
subLoading = false;
|
||||
subLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* password show or hidden
|
||||
* */
|
||||
const passwordType = ref("password");
|
||||
const refPassword = ref<InputInstance>();
|
||||
const passwordType = ref('password')
|
||||
const refPassword = ref<InputInstance>()
|
||||
|
||||
function showPwd() {
|
||||
if (passwordType.value === "password") {
|
||||
passwordType.value = "";
|
||||
if (passwordType.value === 'password') {
|
||||
passwordType.value = ''
|
||||
} else {
|
||||
passwordType.value = "password";
|
||||
passwordType.value = 'password'
|
||||
}
|
||||
nextTick(() => {
|
||||
refPassword.value!.focus();
|
||||
});
|
||||
refPassword.value!.focus()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
|
Loading…
Reference in New Issue
Block a user