初始化前端目录

This commit is contained in:
2025-09-03 21:42:51 +08:00
parent de83878345
commit 31ebed0b5e
68 changed files with 4230 additions and 3461 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div class="app-container">
<a-layout style="min-height: 100vh;">
<!-- 顶部导航栏无修改 -->
<!-- 顶部导航栏 -->
<a-layout-header class="header">
<div class="logo">
<img src="/my.png" class="logo-img" alt="系统Logo" />
@@ -9,14 +9,23 @@
</div>
<div class="header-actions">
<div class="search-box">
<!-- 直接使用全局注册的图标 -->
<SearchOutlined class="search-icon" />
<input type="text" placeholder="搜索" class="search-input" />
</div>
<div class="action-icons">
<a-tooltip title="收藏"><StarOutlined class="action-icon" /></a-tooltip>
<a-tooltip title="通知"><BellOutlined class="action-icon" /></a-tooltip>
<a-tooltip title="帮助"><QuestionCircleOutlined class="action-icon" /></a-tooltip>
<a-tooltip title="设置"><SettingOutlined class="action-icon" /></a-tooltip>
<a-tooltip title="收藏">
<StarOutlined class="action-icon" @click="showFavorite = true" />
</a-tooltip>
<a-tooltip title="通知">
<BellOutlined class="action-icon" @click="showNotification = true" />
</a-tooltip>
<a-tooltip title="帮助">
<QuestionCircleOutlined class="action-icon" @click="showHelp = true" />
</a-tooltip>
<a-tooltip title="设置">
<SettingOutlined class="action-icon" @click="showSettings = true" />
</a-tooltip>
</div>
<a-dropdown
placement="bottomRight"
@@ -28,10 +37,17 @@
</div>
<template #overlay>
<a-menu class="user-menu" @click="handleUserMenuClick">
<a-menu-item key="profile">个人资料</a-menu-item>
<a-menu-item key="settings">账户设置</a-menu-item>
<a-menu-item key=""> <img src="https://picsum.photos/id/1005/200/200" alt="用户头像" class="avatar-img" />{{userName}}</a-menu-item>
<a-menu-divider />
<a-menu-item key="profile">
<UserOutlined class="mr-2" /> 个人资料
</a-menu-item>
<a-menu-item key="settings">
<ToolFilled class="mr-2" /> 修改密码
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout" class="logout-menu-item">
<!-- 直接使用全局注册的图标 -->
<LogoutOutlined class="mr-2" /> 退出登录
</a-menu-item>
</a-menu>
@@ -40,9 +56,9 @@
</div>
</a-layout-header>
<!-- 中间主体布局菜单逻辑优化 -->
<!-- 中间主体布局 -->
<a-layout style="flex: 1; height: calc(100vh - 64px);">
<!-- 左侧菜单状态与路由同步优化 -->
<!-- 左侧菜单 -->
<a-layout-sider
class="side-menu-container"
width="200"
@@ -67,6 +83,7 @@
@openChange="(visible) => handleCollapsedMenuVisible(visible, module.moduleCode)"
>
<div class="parent-menu-header collapsed-mode" @click.stop>
<!-- 动态图标通过封装的图标库获取 -->
<component :is="getIconComponent(module.icon)" class="parent-menu-icon" />
<a-tooltip :title="module.moduleName" placement="right">
<span class="collapsed-tooltip-placeholder" />
@@ -81,6 +98,7 @@
v-for="menu in module.menus"
:key="menu.menuCode"
:selected="selectedKey === menu.menuCode"
:disabled="!menu.chref"
>
<component :is="getIconComponent(menu.cicon)" class="sub-menu-icon" />
<span>{{ menu.menuName }}</span>
@@ -97,8 +115,9 @@
>
<component :is="getIconComponent(module.icon)" class="parent-menu-icon" />
<span class="parent-menu-title">{{ module.moduleName }}</span>
<!-- 动态图标直接使用全局注册的图标无需映射 -->
<component
:is="isSubMenuOpen(module.moduleCode) ? CaretUpOutlined : CaretDownOutlined"
:is="isSubMenuOpen(module.moduleCode) ? 'CaretUpOutlined' : 'CaretDownOutlined'"
class="parent-menu-caret"
/>
</div>
@@ -110,8 +129,12 @@
v-for="menu in module.menus"
:key="menu.menuCode"
class="sub-menu-item"
:class="{ 'sub-menu-item-active': selectedKey === menu.menuCode }"
:class="{
'sub-menu-item-active': selectedKey === menu.menuCode,
'sub-menu-item-disabled': !menu.chref
}"
@click="handleSubMenuItemClick(menu)"
:style="{ cursor: menu.chref ? 'pointer' : 'not-allowed' }"
>
<component :is="getIconComponent(menu.cicon)" class="sub-menu-icon" />
<span>{{ menu.menuName }}</span>
@@ -121,18 +144,20 @@
</div>
</div>
<div class="collapse-trigger" @click="collapsed = !collapsed">
<component :is="collapsed ? MenuUnfoldOutlined : MenuFoldOutlined" />
<!-- 直接使用全局注册的图标 -->
<component :is="collapsed ? 'MenuUnfoldOutlined' : 'MenuFoldOutlined'" />
</div>
</a-layout-sider>
<!-- 右侧内容区核心修改用RouterView渲染路由组件 -->
<!-- 右侧内容区 -->
<a-layout-content class="right-content">
<!-- 标签页与路由绑定 -->
<!-- 标签页 -->
<div class="tab-bar">
<a-tag
:class="['console-tab', $route.path === '/console' ? 'console-tab--active' : '']"
:class="['console-tab', $route.path === '/index/console' ? 'console-tab--active' : '']"
@click="switchToConsole"
>
<!-- 直接使用全局注册的图标 -->
<HomeOutlined class="console-tab-icon" />
<span>控制台</span>
</a-tag>
@@ -174,29 +199,39 @@
:disabled="otherTabs.length === 0"
ghost
>
<!-- 直接使用全局注册的图标 -->
<CloseOutlined class="close-icon" /> 关闭所有
</a-button>
</div>
<!-- 内容区核心RouterView渲染当前路由组件 + 加载/错误状态 -->
<!-- 内容区核心Suspense + RouterView -->
<a-layout-content class="main-content">
<div class="home-content">
<!-- 加载状态路由切换时显示 -->
<div v-if="contentLoading" class="content-loading">
<a-spin size="large" />
</div>
<!-- 路由组件渲染容器 -->
<RouterView
v-else
@error="handleRouterViewError"
/>
<!-- 组件加载失败提示 -->
<Suspense>
<template #default>
<RouterView @error="handleRouterViewError" />
</template>
<template #fallback>
<div class="content-loading">
<a-spin size="large" tip="页面加载中..." />
</div>
</template>
</Suspense>
<div v-if="componentError" class="component-error">
<!-- 直接使用全局注册的图标 -->
<ExclamationCircleOutlined class="error-icon" />
<p>组件加载失败请检查路由配置或组件路径</p>
<p class="error-title">组件加载失败</p>
<p class="error-desc">请检查路由配置或页面文件是否存在</p>
<p class="error-path">失败路由{{ errorPath }}</p>
<a-button
type="primary"
size="middle"
class="retry-btn"
@click="reloadCurrentRoute"
>
重新加载
</a-button>
</div>
</div>
</a-layout-content>
@@ -204,7 +239,7 @@
</a-layout>
</a-layout>
<!-- 退出确认对话框无修改 -->
<!-- 退出确认对话框 -->
<a-modal
v-model:open="logoutVisible"
:centered="true"
@@ -227,143 +262,185 @@
</div>
</a-modal>
</div>
<!-- 放在根节点最下方即可 -->
<Favorite v-if="showFavorite" :visible="showFavorite" @close="showFavorite = false" />
<Notification v-if="showNotification" :visible="showNotification" @close="showNotification = false" />
<Help v-if="showHelp" :visible="showHelp" @close="showHelp = false" />
<Settings v-if="showSettings" :visible="showSettings" @close="showSettings = false" />
<!-- 个人资料 -->
<UserProfile v-if="showUserProfile" :visible="showUserProfile" @close="showUserProfile = false" />
<!-- 修改密码 -->
<ChangePwd v-if="showChangePwd" :visible="showChangePwd" @close="showChangePwd = false" />
</template>
<script setup lang="ts">
import { ref, computed, h, reactive, watch, onMounted } from 'vue';
import { useRouter, useRoute, RouterView } from 'vue-router'; // 引入Vue Router核心API
import { Layout, Menu, Button, Tabs, Tag, Dropdown, Modal, Tooltip, Spin } from 'ant-design-vue';
import { ref, computed, h, reactive, watch, onMounted, onUnmounted } from 'vue';
import { useRouter, useRoute, RouterView, RouteLocationNormalized } from 'vue-router';
import { Layout, Menu, Button, Tabs, Tag, Dropdown, Modal, Tooltip, Spin, message } from 'ant-design-vue';
import { getModuleMenus, ModuleItem, MenuItem } from '@/api/menu';
import { getUserName } from '@/utils/user';
import { apiLogout } from '@/api/auth'
// 图标导入(无修改
import {
ApiOutlined, UserOutlined, CloseOutlined, LinkOutlined, LogoutOutlined,
ExclamationCircleOutlined, BarChartOutlined, DownloadOutlined, SettingOutlined,
FormOutlined, HistoryOutlined, HomeOutlined, DatabaseOutlined, SearchOutlined,
StarOutlined, BellOutlined, QuestionCircleOutlined, MenuFoldOutlined,
MenuUnfoldOutlined, CaretUpOutlined, CaretDownOutlined
} from '@ant-design/icons-vue';
// 关键修改1导入封装的图标库全局注册的基础动态图标从这里获取
import icons from '@/icons';
import type { AntdIcon } from '@/icons'; // 导入图标类型确保TypeScript提示
// 1. 初始化路由实例
// 路由实例
const router = useRouter();
const route = useRoute();
// 2. 状态管理移除原component相关状态新增路由关联状态
const userName = computed(() => getUserName());
// 登录态校验守卫
const removeAuthGuard = router.beforeEach((to, from, next) => {
const userInfo = localStorage.getItem('userInfo');
// 未登录且访问非登录页时跳转登录
if (to.path !== '/login' && !userInfo) {
next('/login');
} else {
next();
}
});
// 状态管理
const collapsed = ref(false);
const logoutVisible = ref(false);
const selectedKey = ref(''); // 选中的菜单Code
const expandedParentMenus = ref<string[]>([]); // 展开的父菜单Code
const loading = ref(true); // 菜单加载状态
const contentLoading = ref(false); // 路由切换加载状态
const componentError = ref(false); // 组件加载错误
const errorPath = ref(''); // 错误路由路径
const selectedKey = ref('');
const expandedParentMenus = ref<string[]>([]);
const loading = ref(true);
const componentError = ref(false);
const errorPath = ref('');
const isNavigating = ref(false);
// 菜单配置与折叠状态
const showFavorite = ref(false)
const showNotification = ref(false)
const showHelp = ref(false)
const showSettings = ref(false)
const showUserProfile = ref(false);
const showChangePwd = ref(false);
// 菜单配置
const moduleMenusConfig = ref<ModuleItem[]>([]);
const collapsedMenuVisible = reactive<Record<string, boolean>>({});
// 3. 标签页数据结构修改关联路由Path移除component
// 标签页数据结构(初始路径为/index/console
interface TabItem {
key: string; // 建议用menuCode唯一
title: string; // 菜单名称
path: string; // 路由Path原chref
closable: boolean; // 是否可关闭
key: string;
title: string;
path: string; // 统一为/index/xxx格式
closable: boolean;
}
// 初始化标签页:控制台对应路由/console
const allTabs = ref<TabItem[]>([
{ key: 'console', title: '控制台', path: '/console', closable: false }
{ key: 'console', title: '控制台', path: '/index/console', closable: false }
]);
// 4. 计算属性(标签页过滤与索引)
// 计算属性
const otherTabs = computed(() => allTabs.value.filter(tab => tab.key !== 'console'));
const activeTabKey = computed({
get() { // 从当前路由同步activeTabKey
get() {
const matchedTab = allTabs.value.find(tab => tab.path === route.path);
return matchedTab ? matchedTab.key : 'console';
},
set(key) { // 避免手动修改(实际通过路由切换触发)
set(key) {
const matchedTab = allTabs.value.find(tab => tab.key === key);
if (matchedTab) router.push(matchedTab.path);
if (matchedTab && !isNavigating.value) {
navigateToPath(matchedTab.path);
}
}
});
const currentTabIndex = computed(() => {
return otherTabs.value.findIndex(tab => tab.path === route.path);
});
// 5. 菜单数据加载(无修改
// 菜单数据加载(标准化chref为/index/xxx
const fetchMenuData = async () => {
try {
loading.value = true;
const modules = await getModuleMenus();
moduleMenusConfig.value = modules;
// 初始化折叠菜单状态
modules.forEach(module => collapsedMenuVisible[module.moduleCode] = false);
// 路由初始化后同步菜单状态
syncMenuStateWithRoute();
// 标准化菜单路径:确保为/index/xxx格式
const normalizedModules = modules.map(module => ({
...module,
menus: module.menus.map(menu => ({
...menu,
chref: menu.chref
? menu.chref.startsWith('/index')
? menu.chref
: `/index${menu.chref.startsWith('/') ? menu.chref : `/${menu.chref}`}`
: ''
}))
}));
moduleMenusConfig.value = normalizedModules;
normalizedModules.forEach(module => collapsedMenuVisible[module.moduleCode] = false);
syncMenuStateWithRoute(route);
} catch (error) {
console.error('菜单接口请求失败:', error);
Modal.error({ title: '菜单加载失败', content: '请刷新页面重试' });
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchMenuData();
// 监听路由变化,同步菜单和标签页状态
router.beforeEach((to, from, next) => {
contentLoading.value = true; // 路由开始切换,显示加载
componentError.value = false; // 重置错误状态
next();
});
router.afterEach(() => {
contentLoading.value = false; // 路由切换完成,隐藏加载
syncMenuStateWithRoute(); // 同步菜单状态
});
});
// 6. 核心工具函数
// 6.1 图标组件获取(无修改)
const getIconComponent = (iconName: string) => {
const iconMap: Record<string, any> = {
ApiOutlined, UserOutlined, BarChartOutlined, DownloadOutlined,
SettingOutlined, FormOutlined, HistoryOutlined, HomeOutlined,
DatabaseOutlined, LinkOutlined
};
return iconMap[iconName] || ApiOutlined;
// 路由跳转封装
const navigateToPath = async (path: string) => {
if (isNavigating.value || path === route.path) return;
try {
isNavigating.value = true;
await router.push(path);
} catch (err) {
console.error('路由跳转失败:', err);
Modal.error({ title: '页面跳转失败', content: `目标路径不存在:${path}` });
} finally {
isNavigating.value = false;
}
};
// 6.2 同步菜单状态与当前路由(关键:路由变 -> 菜单变)
const syncMenuStateWithRoute = () => {
if (route.path === '/console') { // 控制台路由
// 同步菜单与路由状态
const syncMenuStateWithRoute = (currentRoute: RouteLocationNormalized) => {
// 匹配控制台路径/index/console
if (currentRoute.path === '/index/console') {
selectedKey.value = '';
expandedParentMenus.value = [];
return;
}
// 非控制台路由:找到对应菜单,更新选中和展开状态
// 匹配其他菜单路由
let matched = false;
for (const module of moduleMenusConfig.value) {
const matchedMenu = module.menus.find(menu => menu.chref === route.path);
if (matchedMenu) {
selectedKey.value = matchedMenu.menuCode;
expandedParentMenus.value = [module.moduleCode]; // 展开当前菜单的父级
const menu = module.menus.find(item => item.chref === currentRoute.path);
if (menu) {
selectedKey.value = menu.menuCode;
expandedParentMenus.value = [module.moduleCode];
matched = true;
break;
}
}
// 未匹配时跳回控制台
if (!matched && currentRoute.path !== '/index/console') {
navigateToPath('/index/console');
}
};
// 6.3 检查父菜单是否展开(无修改
// 关键修改2重写getIconComponent从封装的图标库获取图标不再依赖局部导入
const getIconComponent = (iconName: string) => {
// 图标名称格式校验确保是有效的Antd图标名称如"ApiOutlined"
const validIconName = iconName as AntdIcon;
// 从封装的图标库中获取不存在则返回默认图标ApiOutlined
return icons[validIconName] || icons.ApiOutlined;
};
// 检查父菜单是否展开
const isSubMenuOpen = (moduleCode: string) => {
return expandedParentMenus.value.includes(moduleCode);
};
// 7. 菜单交互逻辑(修改为路由跳转)
// 7.1 展开状态:切换父菜单展开/关闭
// 菜单交互
const toggleSubMenu = (moduleCode: string) => {
expandedParentMenus.value = isSubMenuOpen(moduleCode) ? [] : [moduleCode];
};
// 7.2 折叠状态:控制悬浮菜单显示
const handleCollapsedMenuVisible = (visible: boolean, moduleCode: string) => {
Object.keys(collapsedMenuVisible).forEach(key => {
if (key !== moduleCode) collapsedMenuVisible[key] = false;
@@ -371,38 +448,31 @@ const handleCollapsedMenuVisible = (visible: boolean, moduleCode: string) => {
collapsedMenuVisible[moduleCode] = visible;
};
// 7.3 展开状态:子菜单点击(核心:跳转路由 + 添加标签页)
const handleSubMenuItemClick = (menu: MenuItem) => {
const targetPath = menu.chref; // 菜单的chref即路由Path
if (!targetPath) return;
// 步骤1跳转路由
router.push(targetPath);
// 步骤2添加标签页不存在则新增
const tabExists = allTabs.value.some(tab => tab.path === targetPath);
if (!menu.chref) return;
navigateToPath(menu.chref);
// 添加标签页
const tabExists = allTabs.value.some(tab => tab.path === menu.chref);
if (!tabExists) {
allTabs.value.push({
key: menu.menuCode,
title: menu.menuName,
path: targetPath,
path: menu.chref,
closable: true
});
}
// 步骤3关闭折叠菜单若存在
Object.keys(collapsedMenuVisible).forEach(key => collapsedMenuVisible[key] = false);
};
// 7.4 折叠状态:悬浮子菜单点击(同展开状态逻辑)
const handleCollapsedSubMenuClick = (e: { key: string }, menus: MenuItem[]) => {
const menuCode = e.key;
const menu = menus.find(item => item.menuCode === menuCode);
if (menu) handleSubMenuItemClick(menu);
};
// 8. 标签页交互逻辑(与路由绑定)
// 8.1 渲染标签标题(无修改)
// 标签页交互
const renderTabTitle = (tab: TabItem) => {
return h('div', { class: 'tab-title-container' }, [
h('span', { class: 'tab-title-text' }, tab.title),
@@ -415,18 +485,17 @@ const renderTabTitle = (tab: TabItem) => {
deleteTab(tab);
}
},
h(CloseOutlined, { size: 12 })
// 关键修改:用 getIconComponent 获取 CloseOutlined 图标
h(getIconComponent('CloseOutlined'), { size: 12 })
)
]);
};
// 8.2 切换到控制台(跳转路由)
const switchToConsole = () => {
router.push('/console');
navigateToPath('/index/console');
Object.keys(collapsedMenuVisible).forEach(key => collapsedMenuVisible[key] = false);
};
// 8.3 标签页切换(上一个/下一个)
const switchTab = (direction: 'prev' | 'next') => {
const tabs = otherTabs.value;
if (tabs.length === 0) return;
@@ -435,79 +504,167 @@ const switchTab = (direction: 'prev' | 'next') => {
let targetIdx = direction === 'prev' ? currentIdx - 1 : currentIdx + 1;
targetIdx = Math.max(0, Math.min(targetIdx, tabs.length - 1));
router.push(tabs[targetIdx].path); // 跳转目标路由
navigateToPath(tabs[targetIdx].path);
};
// 8.4 关闭单个标签页
const deleteTab = (tab: TabItem) => {
if (!tab.closable) return;
const tabIndex = allTabs.value.findIndex(item => item.key === tab.key);
if (tabIndex === -1) return;
// 若关闭的是当前激活标签,跳转到前一个标签或控制台
const isActive = tab.path === route.path;
allTabs.value = allTabs.value.filter(item => item.key !== tab.key);
if (isActive) {
const tabs = otherTabs.value;
if (tabs.length > 0) {
const targetTab = tabs[Math.min(tabIndex - 1, tabs.length - 1)];
router.push(targetTab.path);
const targetTab = tabs[tabIndex - 1] || tabs[tabIndex];
navigateToPath(targetTab.path);
} else {
router.push('/console');
navigateToPath('/index/console');
}
}
};
// 8.5 关闭所有标签页
const closeAllTabs = () => {
allTabs.value = allTabs.value.filter(tab => tab.key === 'console');
router.push('/console');
navigateToPath('/index/console');
Object.keys(collapsedMenuVisible).forEach(key => collapsedMenuVisible[key] = false);
};
// 8.6 标签页切换事件(路由已同步,无需额外处理)
const handleTabChange = (key: string) => {
const matchedTab = allTabs.value.find(tab => tab.key === key);
if (matchedTab) router.push(matchedTab.path);
if (matchedTab) navigateToPath(matchedTab.path);
};
// 9. 错误处理RouterView组件加载失败
// 错误处理
const handleRouterViewError = (err: Error) => {
componentError.value = true;
errorPath.value = route.path;
console.error('路由组件加载失败:', err);
};
// 10. 用户菜单与退出登录(优化退出跳转)
const reloadCurrentRoute = () => {
componentError.value = false;
router.replace({
path: route.path,
query: { ...route.query, t: Date.now() }
});
};
// 用户菜单与退出登录
const handleDropdownVisible = (visible: boolean) => {
console.log('用户下拉菜单状态:', visible ? '显示' : '隐藏');
};
const handleUserMenuClick = ({ key }: { key: string }) => {
if (key === 'logout') handleLogout();
else console.log('用户菜单点击:', key);
switch (key) {
case 'profile':
showUserProfile.value = true;
break;
case 'settings':
showChangePwd.value = true;
break;
case 'logout':
handleLogout();
break;
default:
console.log('用户菜单点击:', key);
}
};
const handleLogout = () => {
logoutVisible.value = true;
};
const doLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
logoutVisible.value = false;
router.push('/login'); // 用路由跳转替代alert
const doLogout = async () => {
try {
// 1. 调用后端登出接口(可选)
const response = await apiLogout();
if (response.code == 200 ){
// 2. 清掉所有缓存
localStorage.clear();
sessionStorage.clear();
logoutVisible.value = false;
window.location.href = `${import.meta.env.BASE_URL}login`;
}
} catch (e) {
message.error('退出失败,请重试');
}
};
// 11. 监听菜单折叠状态变化
// 生命周期
onMounted(() => {
fetchMenuData();
router.afterEach((to) => {
componentError.value = false;
syncMenuStateWithRoute(to);
});
});
onUnmounted(() => {
removeAuthGuard();
});
watch(collapsed, (newVal) => {
if (newVal) expandedParentMenus.value = []; // 折叠时关闭所有父菜单
else syncMenuStateWithRoute(); // 展开时同步路由状态
if (newVal) expandedParentMenus.value = [];
else syncMenuStateWithRoute(route);
});
</script>
<style scoped>
@import "styles/app-layout.css";
.main-content {
padding: 16px;
height: calc(100% - 48px);
overflow: auto;
background: #f5f7fa;
}
.content-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 12px;
}
.component-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #f5222d;
gap: 16px;
padding: 24px;
text-align: center;
}
.error-icon {
font-size: 48px;
}
.error-title {
font-size: 18px;
font-weight: 500;
}
.error-desc, .error-path {
font-size: 14px;
color: #666;
}
.retry-btn {
margin-top: 8px;
}
.sub-menu-item-disabled {
color: #ccc !important;
background: transparent !important;
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<a-modal
:open="visible"
title="修改密码"
:width="modalWidth"
:centered="true"
:body-style="bodyStyle"
:footer="null"
@cancel="$emit('close')">
<p>这里是个人资料表单内容</p>
</a-modal>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
visible: boolean;
}
const props = defineProps<Props>();
const emit = defineEmits(['close']);
const modalWidth = computed(() => window.innerWidth * 0.6);
const bodyStyle = computed(() => ({
height: `${window.innerHeight * 0.6}px`,
overflowY: 'auto'
}));
</script>

View File

@@ -0,0 +1,28 @@
<template>
<a-modal
:open="visible"
title="个人资料"
:width="modalWidth"
:centered="true"
:body-style="bodyStyle"
:footer="null"
@cancel="$emit('close')">
<p>这里是个人资料表单内容</p>
</a-modal>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
visible: boolean;
}
const props = defineProps<Props>();
const emit = defineEmits(['close']);
const modalWidth = computed(() => window.innerWidth * 0.6);
const bodyStyle = computed(() => ({
height: `${window.innerHeight * 0.6}px`,
overflowY: 'auto'
}));
</script>