feat(app): 初始化项目配置和页面结构
- 添加 .dockerignore 和 .env.example 配置文件 - 添加 .gitignore 忽略规则配置 - 创建服务端代理API路由(_file、_modules、_server) - 集成 Ant Design Vue 组件库并配置SSR样式提取 - 定义API响应类型封装 - 创建基础布局组件(blank、console) - 实现应用中心页面和组件(AppsCenter) - 添加文章列表测试页面 - 配置控制台导航菜单结构 - 实现控制台头部组件 - 创建联系页面表单
This commit is contained in:
169
app/components/console/ConsoleHeader.vue
Normal file
169
app/components/console/ConsoleHeader.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<a-layout-header class="top-header !bg-white !p-0">
|
||||
<div class="h-full px-4 flex items-center justify-between">
|
||||
<div class="logo">
|
||||
<a-space size="large">
|
||||
<a-image
|
||||
src="https://oss.wsdns.cn/20250204/a21e034cdb2441b797f5027db5305be5.png"
|
||||
class="mb-1"
|
||||
:width="90"
|
||||
:preview="false"
|
||||
@click="navigateTo('/console')"
|
||||
/>
|
||||
<a-dropdown-button @click="handleButtonClick">
|
||||
{{ productLabel }}
|
||||
<template #overlay>
|
||||
<a-menu @click="handleProductMenuClick">
|
||||
<a-menu-item key="site">
|
||||
<a-avatar shape="square" :size="22" src="https://oss.wsdns.cn/20250215/2016c6f2da074b09b11a0e3603f5be23.png" />
|
||||
云·企业官网
|
||||
</a-menu-item>
|
||||
<a-menu-item key="mp">
|
||||
<a-avatar shape="square" :size="22" src="https://oss.wsdns.cn/20250304/e65ea719564e47a1b8da93d6eea8287a.png" />
|
||||
小程序开发
|
||||
</a-menu-item>
|
||||
<a-menu-item key="oa">
|
||||
<a-avatar shape="square" :size="22" src="https://oss.wsdns.cn/20250215/457a343dba204d019281d8a23556c4b1.png" />
|
||||
办公协同OA
|
||||
</a-menu-item>
|
||||
<a-menu-item key="developer">
|
||||
<a-avatar shape="square" :size="22" src="https://oss.wsdns.cn/20250214/d455a32a7a2043d899e079b4eb9b27b8.png" />
|
||||
开发者中心
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-dropdown placement="bottomRight" :trigger="['click']">
|
||||
<div class="user-trigger">
|
||||
<a-space>
|
||||
<a-avatar :size="28" :src="user?.avatar || user?.avatarUrl">
|
||||
<template #icon>
|
||||
<AppstoreOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<span class="user-name">
|
||||
{{ userDisplayName }}
|
||||
</span>
|
||||
</a-space>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="onUserMenuClick">
|
||||
<a-menu-item v-for="item in mergedUserMenuItems" :key="item.key">
|
||||
{{ item.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AppstoreOutlined } from '@ant-design/icons-vue'
|
||||
import type { MenuProps } from 'ant-design-vue'
|
||||
import type { User } from '@/api/system/user/model'
|
||||
|
||||
type ConsoleHeaderMenuItem = {
|
||||
key: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
productLabel?: string
|
||||
defaultJumpKey?: 'site' | 'mp' | 'oa' | 'developer'
|
||||
user: User | null
|
||||
userDisplayName: string
|
||||
userMenuItems?: ConsoleHeaderMenuItem[]
|
||||
}>(),
|
||||
{
|
||||
productLabel: '云·企业官网',
|
||||
defaultJumpKey: 'site',
|
||||
userMenuItems: () => [{ key: 'logout', label: '退出登录' }],
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'logout'): void
|
||||
(e: 'userMenuClick', key: string): void
|
||||
}>()
|
||||
|
||||
const mergedUserMenuItems = computed<ConsoleHeaderMenuItem[]>(() => {
|
||||
const items = Array.isArray(props.userMenuItems) ? props.userMenuItems.slice() : []
|
||||
if (!items.some((i) => i.key === 'account')) {
|
||||
const accountItem: ConsoleHeaderMenuItem = { key: 'account', label: '账号管理' }
|
||||
const logoutIndex = items.findIndex((i) => i.key === 'logout')
|
||||
if (logoutIndex >= 0) items.splice(logoutIndex, 0, accountItem)
|
||||
else items.push(accountItem)
|
||||
}
|
||||
return items
|
||||
})
|
||||
|
||||
const consoleJumpTargets = {
|
||||
site: '/site',
|
||||
mp: '/mp',
|
||||
oa: '/oa',
|
||||
developer: '/developer',
|
||||
} as const
|
||||
|
||||
function openExternal(url: string) {
|
||||
if (!import.meta.client) return
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
window.location.href = url
|
||||
return
|
||||
}
|
||||
void navigateTo(url)
|
||||
}
|
||||
|
||||
const handleButtonClick = () => {
|
||||
openExternal(consoleJumpTargets[props.defaultJumpKey])
|
||||
}
|
||||
|
||||
const handleProductMenuClick: MenuProps['onClick'] = (e) => {
|
||||
const key = String(e.key) as keyof typeof consoleJumpTargets
|
||||
const url = consoleJumpTargets[key]
|
||||
if (!url) return
|
||||
openExternal(url)
|
||||
}
|
||||
|
||||
function onUserMenuClick(info: { key: string }) {
|
||||
const key = String(info.key)
|
||||
emit('userMenuClick', key)
|
||||
if (key === 'account') {
|
||||
void navigateTo('/console/account')
|
||||
return
|
||||
}
|
||||
if (key === 'logout') emit('logout')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-header {
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.user-trigger {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-trigger:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.user-name {
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user