ow
This commit is contained in:
1 parent
eb7a9e9b88
commit
fd51982804
7 files changed
+242
-11
No files matched your search
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<section class="hero">
|
||||
<div class="hero-inner">
|
||||
<div class="hero-top">
|
||||
<div class="brand" :class="{ 'is-clickable': clickable }" @click="onBrandClick">
|
||||
<a-avatar
|
||||
shape="square"
|
||||
:size="36"
|
||||
src="/youjia.svg"
|
||||
class="brand-mark"
|
||||
/>
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<a-dropdown placement="bottomRight" :trigger="['click']">
|
||||
<div class="avatar" :title="userName">{{ avatarText }}</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="onUserMenuClick">
|
||||
<a-menu-item key="amentUserLocalName">
|
||||
<UserOutlined />
|
||||
{{ t("layout.header.amentUserLocalName") }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="amentPassword">
|
||||
<LockOutlined />
|
||||
{{ t("layout.header.amentPassword") }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="biometricButton">
|
||||
<LockOutlined />
|
||||
{{ biometricButtonLabel }}
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout">
|
||||
<LogoutOutlined />
|
||||
{{ t("layout.header.logout") }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<changePassword v-model:showModel="showChangePassword" />
|
||||
<changeLocalName v-model:showModel="showChangeUserLocalName" />
|
||||
<changeBiometricEnrollCard v-model:showModel="showBiometricEnroll" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { notification } from "ant-design-vue";
|
||||
import type { MenuProps } from "ant-design-vue";
|
||||
import { LockOutlined, LogoutOutlined, UserOutlined } from "@ant-design/icons-vue";
|
||||
import authService from "@/service/security/authService";
|
||||
import { useLoginProfileStore } from "@/store/modules/loginProfile";
|
||||
import i18n from "@/locales";
|
||||
import changePassword from "@/views/login/changePassword.vue";
|
||||
import changeLocalName from "@/views/login/changeLocalName.vue";
|
||||
import changeBiometricEnrollCard from "@/views/login/changeBiometricEnrollCard.vue";
|
||||
|
||||
/**
|
||||
* 欢迎页顶部那一条「品牌 + 头像」的渐变头图。
|
||||
*
|
||||
* 欢迎页(DashboardHero)与后台布局(layout/index.vue、layout/master.vue)共用它,
|
||||
* 两处顶栏才会长得一模一样。
|
||||
*/
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 品牌名 */
|
||||
title?: string;
|
||||
/** 品牌名是否可点(后台布局点了回工作台) */
|
||||
clickable?: boolean;
|
||||
}>(),
|
||||
{ title: "有家相册", clickable: false },
|
||||
);
|
||||
const emit = defineEmits<{ (e: "brand-click"): void }>();
|
||||
|
||||
const router = useRouter();
|
||||
const profile = useLoginProfileStore();
|
||||
const { t } = i18n.global;
|
||||
|
||||
const userName = computed(
|
||||
() => profile.getUserLocalName() || profile.getUsername() || "新朋友",
|
||||
);
|
||||
const avatarText = computed(() => {
|
||||
const u = (userName.value || "U").trim();
|
||||
return u ? u.slice(-1).toUpperCase() : "U";
|
||||
});
|
||||
|
||||
const onBrandClick = () => {
|
||||
if (!props.clickable) return;
|
||||
emit("brand-click");
|
||||
};
|
||||
|
||||
/* ==================== 头像菜单 ==================== */
|
||||
const showChangePassword = ref<boolean>(false);
|
||||
const showChangeUserLocalName = ref<boolean>(false);
|
||||
const showBiometricEnroll = ref<boolean>(false);
|
||||
|
||||
// Coarse platform detection so the button label is friendly on each device.
|
||||
const biometricButtonLabel = computed(() => {
|
||||
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
||||
if (/iPhone|iPad/.test(ua)) return "Sign in with Face ID / Touch ID";
|
||||
if (/Android/.test(ua)) return "Sign in with fingerprint";
|
||||
if (/Windows/.test(ua)) return "Sign in with Windows Hello";
|
||||
if (/Mac OS X/.test(ua)) return "Sign in with Touch ID";
|
||||
return "Sign in with fingerprint / face";
|
||||
});
|
||||
|
||||
const onUserMenuClick: MenuProps["onClick"] = (menuInfo) => {
|
||||
if (menuInfo.key === "amentUserLocalName") {
|
||||
showChangeUserLocalName.value = true;
|
||||
} else if (menuInfo.key === "amentPassword") {
|
||||
showChangePassword.value = true;
|
||||
} else if (menuInfo.key === "biometricButton") {
|
||||
showBiometricEnroll.value = true;
|
||||
} else if (menuInfo.key === "logout") {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await authService.Logout();
|
||||
profile.clearStorage();
|
||||
notification.info({
|
||||
message: t("layout.notification.message"),
|
||||
description: t("layout.notification.description"),
|
||||
placement: "topRight",
|
||||
});
|
||||
router.push("/");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* 渐变头图:与欢迎页 hero 完全一致 */
|
||||
.hero {
|
||||
/* 通栏:不管父容器是 block 还是 flex,渐变条都要铺满整宽 */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 18px 16px 36px;
|
||||
border-radius: 0 0 22px 22px;
|
||||
background: linear-gradient(135deg, #1677ff 0%, #36cfc9 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 18px rgba(22, 119, 255, 0.22);
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
&.is-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9px;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
color: #1677ff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.hero {
|
||||
padding: 22px 20px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.hero-inner {
|
||||
max-width: 1080px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 24px 24px 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user