From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/layout/components/UserInfo/src/UserInfo.vue |  113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 113 insertions(+), 0 deletions(-)

diff --git a/src/layout/components/UserInfo/src/UserInfo.vue b/src/layout/components/UserInfo/src/UserInfo.vue
new file mode 100644
index 0000000..355aabc
--- /dev/null
+++ b/src/layout/components/UserInfo/src/UserInfo.vue
@@ -0,0 +1,113 @@
+<script lang="ts" setup>
+import { ElMessageBox } from 'element-plus'
+
+import avatarImg from '@/assets/imgs/avatar.gif'
+import { useDesign } from '@/hooks/web/useDesign'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import { useUserStore } from '@/store/modules/user'
+import LockDialog from './components/LockDialog.vue'
+import LockPage from './components/LockPage.vue'
+import { useLockStore } from '@/store/modules/lock'
+
+defineOptions({ name: 'UserInfo' })
+
+const { t } = useI18n()
+
+const { push, replace } = useRouter()
+
+const userStore = useUserStore()
+
+const tagsViewStore = useTagsViewStore()
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('user-info')
+
+const avatar = computed(() => userStore.user.avatar || avatarImg)
+const userName = computed(() => userStore.user.nickname ?? 'Admin')
+
+// 閿佸畾灞忓箷
+const lockStore = useLockStore()
+const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
+const dialogVisible = ref<boolean>(false)
+const lockScreen = () => {
+  dialogVisible.value = true
+}
+
+const loginOut = async () => {
+  try {
+    await ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
+      confirmButtonText: t('common.ok'),
+      cancelButtonText: t('common.cancel'),
+      type: 'warning'
+    })
+    await userStore.loginOut()
+    tagsViewStore.delAllViews()
+    replace('/login?redirect=/index')
+  } catch {}
+}
+const toProfile = async () => {
+  push('/user/profile')
+}
+const toDocument = () => {
+  window.open('https://doc.iocoder.cn/')
+}
+</script>
+
+<template>
+  <ElDropdown class="custom-hover" :class="prefixCls" trigger="click">
+    <div class="flex items-center">
+      <ElAvatar :src="avatar" alt="" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" />
+      <span class="pl-[5px] text-14px text-[var(--top-header-text-color)] <lg:hidden">
+        {{ userName }}
+      </span>
+    </div>
+    <template #dropdown>
+      <ElDropdownMenu>
+        <ElDropdownItem>
+          <Icon icon="ep:tools" />
+          <div @click="toProfile">{{ t('common.profile') }}</div>
+        </ElDropdownItem>
+        <ElDropdownItem>
+          <Icon icon="ep:menu" />
+          <div @click="toDocument">{{ t('common.document') }}</div>
+        </ElDropdownItem>
+        <ElDropdownItem divided>
+          <Icon icon="ep:lock" />
+          <div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
+        </ElDropdownItem>
+        <ElDropdownItem divided @click="loginOut">
+          <Icon icon="ep:switch-button" />
+          <div>{{ t('common.loginOut') }}</div>
+        </ElDropdownItem>
+      </ElDropdownMenu>
+    </template>
+  </ElDropdown>
+
+  <LockDialog v-if="dialogVisible" v-model="dialogVisible" />
+
+  <teleport to="body">
+    <transition name="fade-bottom" mode="out-in">
+      <LockPage v-if="getIsLock" />
+    </transition>
+  </teleport>
+</template>
+
+<style scoped lang="scss">
+.fade-bottom-enter-active,
+.fade-bottom-leave-active {
+  transition:
+    opacity 0.25s,
+    transform 0.3s;
+}
+
+.fade-bottom-enter-from {
+  opacity: 0;
+  transform: translateY(-10%);
+}
+
+.fade-bottom-leave-to {
+  opacity: 0;
+  transform: translateY(10%);
+}
+</style>

--
Gitblit v1.8.0