前端样式重新设计,数据面版网系颜色一致

This commit is contained in:
Your Name 2025-07-22 17:01:51 +08:00
parent fc7ea534b7
commit 0ea1a453db
14 changed files with 356 additions and 159 deletions

View File

@ -137,10 +137,7 @@ export default function BaseSettingPage() {
</FixedHeader> </FixedHeader>
<div <div
className="flex flex-col overflow-auto p-4 no-scrollbar" className="flex flex-col p-4 bg-[#eff3f4] h-auto"
style={{
height: "calc(100vh - 48px - 49px)"
}}
> >
<Card bordered={false}> <Card bordered={false}>
<Typography> <Typography>

View File

@ -1,7 +1,7 @@
import DeptEditor from "@web/src/components/models/department/dept-editor"; import DeptEditor from "@web/src/components/models/department/dept-editor";
export default function DepartmentAdminPage() { export default function DepartmentAdminPage() {
return <div className=" flex-grow bg-white rounded-xl"> return <div className=" flex-grow bg-white ">
<DeptEditor></DeptEditor> <DeptEditor></DeptEditor>
</div> </div>
} }

View File

@ -1,7 +1,7 @@
import StaffEditor from "@web/src/components/models/staff/staff-editor" import StaffEditor from "@web/src/components/models/staff/staff-editor"
export default function StaffPage() { export default function StaffPage() {
return ( return (
<div className=" bg-white rounded-xl flex-grow"> <div className=" bg-white flex-grow">
<StaffEditor></StaffEditor> <StaffEditor></StaffEditor>
</div> </div>
); );

View File

@ -150,92 +150,36 @@ const DashboardPage = () => {
}; };
}; };
// 准备网系类型故障时间分布数据 // 生成多样化颜色函数(为不同用途添加偏移)
const prepareNetworkFaultsByTimeData = () => { const generateDiverseColors = (count: number, offset: number = 0) => {
if (!devices || !networkTypeTerms) return { xAxis: [], series: [] }; const colors = [];
const hueStep = 360 / count;
const startDate = dateRange[0]; for (let i = 0; i < count; i++) {
const endDate = dateRange[1]; const hue = ((i * hueStep) + offset) % 360;
const diffDays = endDate.diff(startDate, 'day'); // 使用固定的饱和度和亮度,确保颜色鲜明且不相近
const color = `hsl(${hue}, 80%, 66%)`;
// 时间间隔逻辑 colors.push(color);
const intervals = [];
let format = '';
if (diffDays <= 14) {
format = 'MM-DD';
for (let i = 0; i <= diffDays; i++) {
intervals.push(startDate.add(i, 'day'));
}
} else if (diffDays <= 90) {
format = 'MM-DD';
const weeks = Math.ceil(diffDays / 7);
for (let i = 0; i < weeks; i++) {
const weekStart = startDate.add(i * 7, 'day');
intervals.push(weekStart);
}
} else {
format = 'YYYY-MM';
const months = Math.ceil(diffDays / 30);
for (let i = 0; i < months; i++) {
const monthStart = startDate.add(i, 'month').startOf('month');
intervals.push(monthStart);
}
} }
const timeLabels = intervals.map(date => { return colors;
if (diffDays <= 14) {
return date.format(format);
} else if (diffDays <= 90) {
return `${date.format(format)}${date.add(6, 'day').format(format)}`;
} else {
return date.format(format);
}
});
// 统计每个网系类型在每个时间段的故障数
const networkNames = networkTypeTerms.map(type => type.name);
const seriesData = networkNames.map(name => ({
name,
type: 'bar',
data: new Array(intervals.length).fill(0)
}));
devices.forEach(device => {
const deviceTerms = getDeviceTerms(device);
const networkName = deviceTerms.networkType;
const networkIndex = networkNames.indexOf(networkName);
if (networkIndex !== -1) {
const createDate = dayjs(device.createdAt);
for (let i = 0; i < intervals.length; i++) {
if (diffDays <= 14) {
if (createDate.format('YYYY-MM-DD') === intervals[i].format('YYYY-MM-DD')) {
seriesData[networkIndex].data[i]++;
}
} else if (diffDays <= 90) {
const weekEnd = intervals[i].add(6, 'day').endOf('day');
if (createDate.isAfter(intervals[i]) && createDate.isBefore(weekEnd)) {
seriesData[networkIndex].data[i]++;
}
} else {
const monthEnd = intervals[i].endOf('month');
if (createDate.isAfter(intervals[i]) && createDate.isBefore(monthEnd)) {
seriesData[networkIndex].data[i]++;
}
}
}
}
});
return {
xAxis: timeLabels,
series: seriesData
};
}; };
// 准备三层分类统计数据 // 为网系类型生成一致的颜色映射(保持不变,用于网系类型图表间的一致性)
const getNetworkTypeColors = () => {
if (!networkTypeTerms || networkTypeTerms.length === 0) return {};
const colors = generateDiverseColors(networkTypeTerms.length, 0); // 网系类型从0度开始
const colorMap = {};
networkTypeTerms.forEach((type, index) => {
colorMap[type.name] = colors[index];
});
return colorMap;
};
// 修改准备三层分类统计数据函数
const prepareHierarchicalFaultData = () => { const prepareHierarchicalFaultData = () => {
if (!devices || !networkTypeTerms || networkTypeTerms.length === 0) { if (!devices || !networkTypeTerms || networkTypeTerms.length === 0) {
return { names: [], values: [] }; return { names: [], values: [] };
@ -243,6 +187,7 @@ const DashboardPage = () => {
const hierarchicalCounts = {}; const hierarchicalCounts = {};
const totalDevices = devices.length; const totalDevices = devices.length;
const networkColors = getNetworkTypeColors(); // 使用一致的网系类型颜色
// 初始化网系类型计数 // 初始化网系类型计数
networkTypeTerms.forEach(type => { networkTypeTerms.forEach(type => {
@ -261,13 +206,16 @@ const DashboardPage = () => {
const names = Object.keys(hierarchicalCounts); const names = Object.keys(hierarchicalCounts);
const values = names.map(name => ({ const values = names.map(name => ({
value: totalDevices ? ((hierarchicalCounts[name] / totalDevices) * 100).toFixed(2) : 0, value: totalDevices ? ((hierarchicalCounts[name] / totalDevices) * 100).toFixed(2) : 0,
name name,
itemStyle: {
color: networkColors[name]
}
})); }));
return { names, values }; return { names, values };
}; };
// 修改系统类型分布数据(基于选中的网系类型,支持交互 // 修改系统类型分布数据(使用不同的颜色偏移
const prepareGroupedSystemTypeDistribution = () => { const prepareGroupedSystemTypeDistribution = () => {
if (!devices || !systemTypeTerms || !networkTypeTerms || if (!devices || !systemTypeTerms || !networkTypeTerms ||
systemTypeTerms.length === 0 || networkTypeTerms.length === 0) { systemTypeTerms.length === 0 || networkTypeTerms.length === 0) {
@ -325,7 +273,7 @@ const DashboardPage = () => {
} }
}); });
// 构建分组数据 // 构建分组数据 - 为系统类型使用不同的颜色偏移
const values: Array<{ const values: Array<{
value: number; value: number;
name: string; name: string;
@ -333,18 +281,23 @@ const DashboardPage = () => {
itemStyle: { color: string }; itemStyle: { color: string };
}> = []; }> = [];
// 为每个网系定义色系 // 收集所有有数据的系统类型
const networkColors: Record<string, string[]> = { const allSystemTypes = [];
'安全网络': ['#FF6B6B', '#FF8E8E', '#FFB1B1', '#FFD4D4'],
'办公网络': ['#4ECDC4', '#7ED7D2', '#AEE2E0', '#DFEEED'],
'生产网络': ['#45B7D1', '#6BC5D9', '#91D3E1', '#B7E1E9'],
'未分类': ['#96CEB4', '#B2D8C3', '#CDE2D2', '#E8EDE1']
};
Object.entries(networkGroups).forEach(([networkName, systemTypeDisplayNames]) => { Object.entries(networkGroups).forEach(([networkName, systemTypeDisplayNames]) => {
const colors = networkColors[networkName] || ['#DDA0DD', '#E6B3E6', '#F0C6F0', '#F9E9F9']; systemTypeDisplayNames.forEach((displayName) => {
const count = systemTypeCounts[displayName];
if (count > 0) {
allSystemTypes.push(displayName);
}
});
});
systemTypeDisplayNames.forEach((displayName, index) => { // 为系统类型生成多样化颜色使用120度偏移避免与网系类型颜色重叠
const systemColors = generateDiverseColors(allSystemTypes.length, 120);
let colorIndex = 0;
Object.entries(networkGroups).forEach(([networkName, systemTypeDisplayNames]) => {
systemTypeDisplayNames.forEach((displayName) => {
const count = systemTypeCounts[displayName]; const count = systemTypeCounts[displayName];
if (count > 0) { if (count > 0) {
values.push({ values.push({
@ -352,9 +305,10 @@ const DashboardPage = () => {
name: displayName, name: displayName,
networkType: networkName, networkType: networkName,
itemStyle: { itemStyle: {
color: colors[index % colors.length] color: systemColors[colorIndex]
} }
}); });
colorIndex++;
} }
}); });
}); });
@ -362,7 +316,95 @@ const DashboardPage = () => {
return { groups: networkGroups, values }; return { groups: networkGroups, values };
}; };
// 新增:故障类型分布数据(基于选中的系统类型) // 修改网系类型故障时间分布数据,使用一致的颜色
const prepareNetworkFaultsByTimeData = () => {
if (!devices || !networkTypeTerms) return { xAxis: [], series: [] };
const startDate = dateRange[0];
const endDate = dateRange[1];
const diffDays = endDate.diff(startDate, 'day');
// 时间间隔逻辑
const intervals = [];
let format = '';
if (diffDays <= 14) {
format = 'MM-DD';
for (let i = 0; i <= diffDays; i++) {
intervals.push(startDate.add(i, 'day'));
}
} else if (diffDays <= 90) {
format = 'MM-DD';
const weeks = Math.ceil(diffDays / 7);
for (let i = 0; i < weeks; i++) {
const weekStart = startDate.add(i * 7, 'day');
intervals.push(weekStart);
}
} else {
format = 'YYYY-MM';
const months = Math.ceil(diffDays / 30);
for (let i = 0; i < months; i++) {
const monthStart = startDate.add(i, 'month').startOf('month');
intervals.push(monthStart);
}
}
const timeLabels = intervals.map(date => {
if (diffDays <= 14) {
return date.format(format);
} else if (diffDays <= 90) {
return `${date.format(format)}${date.add(6, 'day').format(format)}`;
} else {
return date.format(format);
}
});
// 统计每个网系类型在每个时间段的故障数
const networkNames = networkTypeTerms.map(type => type.name);
const networkColors = getNetworkTypeColors(); // 使用与饼图一致的网系类型颜色
const seriesData = networkNames.map(name => ({
name,
type: 'bar',
data: new Array(intervals.length).fill(0),
color: networkColors[name] // 使用一致的网系类型颜色
}));
devices.forEach(device => {
const deviceTerms = getDeviceTerms(device);
const networkName = deviceTerms.networkType;
const networkIndex = networkNames.indexOf(networkName);
if (networkIndex !== -1) {
const createDate = dayjs(device.createdAt);
for (let i = 0; i < intervals.length; i++) {
if (diffDays <= 14) {
if (createDate.format('YYYY-MM-DD') === intervals[i].format('YYYY-MM-DD')) {
seriesData[networkIndex].data[i]++;
}
} else if (diffDays <= 90) {
const weekEnd = intervals[i].add(6, 'day').endOf('day');
if (createDate.isAfter(intervals[i]) && createDate.isBefore(weekEnd)) {
seriesData[networkIndex].data[i]++;
}
} else {
const monthEnd = intervals[i].endOf('month');
if (createDate.isAfter(intervals[i]) && createDate.isBefore(monthEnd)) {
seriesData[networkIndex].data[i]++;
}
}
}
}
});
return {
xAxis: timeLabels,
series: seriesData
};
};
// 修改故障类型分布数据,使用另一个颜色偏移
const prepareDeviceTypeDistribution = () => { const prepareDeviceTypeDistribution = () => {
if (!devices || !deviceTypeTerms || !systemTypeTerms || !selectedSystemType) if (!devices || !deviceTypeTerms || !systemTypeTerms || !selectedSystemType)
return { names: [], values: [] }; return { names: [], values: [] };
@ -388,12 +430,20 @@ const DashboardPage = () => {
}); });
const names = Object.keys(deviceCounts); const names = Object.keys(deviceCounts);
const values = names.map(name => ({ const validNames = names.filter(name => deviceCounts[name] > 0);
value: deviceCounts[name],
name
})).filter(item => item.value > 0);
return { names, values }; // 为故障类型使用240度偏移与网系类型和系统类型都不重叠
const deviceColors = generateDiverseColors(validNames.length, 240);
const values = validNames.map((name, index) => ({
value: deviceCounts[name],
name,
itemStyle: {
color: deviceColors[index]
}
}));
return { names: validNames, values };
}; };
// 准备故障处置完成率数据 // 准备故障处置完成率数据
@ -781,8 +831,8 @@ const DashboardPage = () => {
}; };
return ( return (
<div className="p-4 min-h-screen bg-gray-50"> <div className="p-4 min-h-screen bg-[#eff3f4]">
<h1 className="text-2xl font-bold mb-6"></h1> {/* <h1 className="text-2xl font-bold mb-6">故障数据可视化面板</h1> */}
<Row className="mb-4"> <Row className="mb-4">
<Col span={24}> <Col span={24}>
@ -810,7 +860,7 @@ const DashboardPage = () => {
<Card className="shadow-sm hover:shadow-md transition-shadow duration-300"> <Card className="shadow-sm hover:shadow-md transition-shadow duration-300">
<ReactECharts <ReactECharts
option={getFaultCompletionRateOption()} option={getFaultCompletionRateOption()}
style={{ height: '350px' }} style={{ height: '300px' }}
/> />
</Card> </Card>
</Col> </Col>
@ -818,7 +868,7 @@ const DashboardPage = () => {
<Card className="shadow-sm hover:shadow-md transition-shadow duration-300"> <Card className="shadow-sm hover:shadow-md transition-shadow duration-300">
<ReactECharts <ReactECharts
option={getHierarchicalFaultOption()} option={getHierarchicalFaultOption()}
style={{ height: '350px' }} style={{ height: '300px' }}
onEvents={{ onEvents={{
click: handleNetworkTypeClick click: handleNetworkTypeClick
}} }}
@ -829,7 +879,7 @@ const DashboardPage = () => {
<Card className="shadow-sm hover:shadow-md transition-shadow duration-300"> <Card className="shadow-sm hover:shadow-md transition-shadow duration-300">
<ReactECharts <ReactECharts
option={getGroupedSystemTypeOption()} option={getGroupedSystemTypeOption()}
style={{ height: '350px' }} style={{ height: '300px' }}
onEvents={{ onEvents={{
click: handleSystemTypeClick click: handleSystemTypeClick
}} }}
@ -840,7 +890,7 @@ const DashboardPage = () => {
<Card className="shadow-sm hover:shadow-md transition-shadow duration-300"> <Card className="shadow-sm hover:shadow-md transition-shadow duration-300">
<ReactECharts <ReactECharts
option={getDeviceTypeOption()} option={getDeviceTypeOption()}
style={{ height: '350px' }} style={{ height: '300px' }}
/> />
</Card> </Card>
</Col> </Col>
@ -850,7 +900,7 @@ const DashboardPage = () => {
<Card className="shadow-sm hover:shadow-md transition-shadow duration-300"> <Card className="shadow-sm hover:shadow-md transition-shadow duration-300">
<ReactECharts <ReactECharts
option={getNetworkFaultsByTimeOption()} option={getNetworkFaultsByTimeOption()}
style={{ height: '450px' }} style={{ height: '420px' }}
/> />
</Card> </Card>
</Col> </Col>

View File

@ -205,7 +205,7 @@ export default function DeviceModal() {
onCancel={handleCancel} onCancel={handleCancel}
width={1000} width={1000}
style={{ top: 20 }} style={{ top: 20 }}
bodyStyle={{ maxHeight: "calc(100vh - 200px)", overflowY: "auto" }} bodyStyle={{ maxHeight: "calc(100vh - 200px)", overflowY: "auto",overflowX: "hidden" }}
> >
<Form form={form} initialValues={formValue} layout="vertical"> <Form form={form} initialValues={formValue} layout="vertical">
<Row gutter={16}> <Row gutter={16}>
@ -236,6 +236,7 @@ export default function DeviceModal() {
value={selectedDeviceType} value={selectedDeviceType}
onChange={setSelectedDeviceType} onChange={setSelectedDeviceType}
systemTypeId={selectedSystemType} systemTypeId={selectedSystemType}
disabled={!selectedSystemType} // 没有选择系统类型时禁用
/> />
</Form.Item> </Form.Item>
</Col> </Col>

View File

@ -37,6 +37,7 @@ import {
import DepartmentSelect from "@web/src/components/models/department/department-select"; import DepartmentSelect from "@web/src/components/models/department/department-select";
import SystemTypeSelect from "@web/src/app/main/devicepage/select/System-select"; import SystemTypeSelect from "@web/src/app/main/devicepage/select/System-select";
import DeviceTypeSelect from "@web/src/app/main/devicepage/select/Device-select"; import DeviceTypeSelect from "@web/src/app/main/devicepage/select/Device-select";
import NetworkTypeSelect from "@web/src/app/main/devicepage/select/Network-type-select";
import dayjs from "dayjs"; import dayjs from "dayjs";
import FixTypeSelect from "./select/Fix-select"; import FixTypeSelect from "./select/Fix-select";
import { api } from "@nice/client"; import { api } from "@nice/client";
@ -580,7 +581,7 @@ export default function DeviceMessage() {
]; ];
return ( return (
<div className="min-h-screen bg-gray-50 p-4"> <div className="min-h-screen bg-[#eff3f4] p-4">
{/* 页面标题区域 */} {/* 页面标题区域 */}
<div className="mb-6"> <div className="mb-6">
<Card className="shadow-sm border border-gray-200"> <Card className="shadow-sm border border-gray-200">
@ -640,7 +641,7 @@ export default function DeviceMessage() {
<label className="text-sm font-medium text-gray-600"> <label className="text-sm font-medium text-gray-600">
</label> </label>
<Select <NetworkTypeSelect
value={selectedNetworkType} value={selectedNetworkType}
onChange={(value) => { onChange={(value) => {
setSelectedNetworkType(value); setSelectedNetworkType(value);
@ -649,14 +650,7 @@ export default function DeviceMessage() {
}} }}
placeholder="选择网系类型" placeholder="选择网系类型"
className="w-full" className="w-full"
allowClear />
>
{networkTypeTerms?.map(term => (
<Select.Option key={term.id} value={term.id}>
{term.name}
</Select.Option>
))}
</Select>
</div> </div>
</Col> </Col>
<Col xs={24} sm={12} md={8} lg={6}> <Col xs={24} sm={12} md={8} lg={6}>

View File

@ -663,7 +663,7 @@ export default function DeviceManager({ title }: TermManagerProps) {
}; };
return ( return (
<div className="p-6 min-h-screen bg-gray-50"> <div className="p-6 h-auto bg-gray-50">
{/* 主要内容卡片 */} {/* 主要内容卡片 */}
<Card className="shadow-sm" bodyStyle={{ padding: 0 }}> <Card className="shadow-sm" bodyStyle={{ padding: 0 }}>
{/* 统计信息区域 */} {/* 统计信息区域 */}

View File

@ -8,26 +8,26 @@ import {
export function MainFooter() { export function MainFooter() {
return ( return (
<footer className="bg-gradient-to-b from-slate-800 to-slate-900 relative z-10 text-secondary-200"> <footer className="bg-white shadow-md border-t border-gray-100 relative z-10">
<div className="container mx-auto px-4 py-6"> <div className="container mx-auto px-4 py-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* 开发组织信息 */} {/* 开发组织信息 */}
<div className="text-center md:text-left space-y-2"> <div className="text-center md:text-left space-y-2">
<h3 className="text-white font-semibold text-sm flex items-center justify-center md:justify-start"> <h3 className="text-[#0b3c7c] font-semibold text-sm flex items-center justify-center md:justify-start">
</h3> </h3>
<p className="text-gray-400 text-xs italic"></p> <p className="text-gray-500 text-xs italic"></p>
</div> </div>
{/* 联系方式 */} {/* 联系方式 */}
<div className="text-center space-y-2"> <div className="text-center space-y-2">
<div className="flex items-center justify-center space-x-2"> <div className="flex items-center justify-center space-x-2">
<PhoneOutlined className="text-gray-400" /> <PhoneOutlined className="text-[#0b3c7c]" />
<span className="text-gray-300 text-xs">628532</span> <span className="text-gray-600 text-xs">628532</span>
</div> </div>
<div className="flex items-center justify-center space-x-2"> <div className="flex items-center justify-center space-x-2">
<MailOutlined className="text-gray-400" /> <MailOutlined className="text-[#0b3c7c]" />
<span className="text-gray-300 text-xs"> <span className="text-gray-600 text-xs">
ruanjian1@tx3l.nb.kj ruanjian1@tx3l.nb.kj
</span> </span>
</div> </div>
@ -38,14 +38,14 @@ export function MainFooter() {
<div className="flex items-center justify-center md:justify-end space-x-4"> <div className="flex items-center justify-center md:justify-end space-x-4">
<a <a
href="https://27.57.72.21" href="https://27.57.72.21"
className="text-gray-400 hover:text-white transition-colors" className="text-[#0b3c7c] hover:text-blue-600 transition-colors"
title="访问门户网站" title="访问门户网站"
> >
<HomeOutlined className="text-lg" /> <HomeOutlined className="text-lg" />
</a> </a>
<a <a
href="https://27.57.72.14" href="https://27.57.72.14"
className="text-gray-400 hover:text-white transition-colors" className="text-[#0b3c7c] hover:text-blue-600 transition-colors"
title="访问烽火青云" title="访问烽火青云"
> >
<CloudOutlined className="text-lg" /> <CloudOutlined className="text-lg" />

View File

@ -25,10 +25,8 @@ export function MainHeader() {
]; ];
return ( return (
<div className="select-none w-full flex items-center justify-between bg-white shadow-md border-b border-gray-100 fixed z-30 py-2 px-4 md:px-6 h-16"> <div className="select-none w-full flex items-center justify-end bg-[#eff3f4]shadow-md py-2 px-4 md:px-6 h-auto">
<div className="flex items-center">
<span className="text-xl font-bold text-primary"></span>
</div>
{isAuthenticated && user ? ( {isAuthenticated && user ? (
<Dropdown <Dropdown
@ -36,8 +34,9 @@ export function MainHeader() {
items, items,
onClick: ({ key }) => key === "logout" && handleLogout(), onClick: ({ key }) => key === "logout" && handleLogout(),
}} }}
placement="bottomRight" placement="bottom"
arrow arrow
> >
<div className="flex items-center cursor-pointer transition-all hover:bg-gray-100 rounded-full py-1 px-3"> <div className="flex items-center cursor-pointer transition-all hover:bg-gray-100 rounded-full py-1 px-3">
<Avatar <Avatar

View File

@ -1,24 +1,38 @@
import { Layout } from "antd"; import { Layout } from "antd";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { MainHeader } from "./MainHeader";
import { MainFooter } from "./MainFooter";
import { MainProvider } from "./MainProvider";
import NavigationMenu from "./NavigationMenu"; import NavigationMenu from "./NavigationMenu";
import { UserInfoPanel } from "./UserInfoPanel";
import { MainProvider } from "./MainProvider";
import { MainFooter } from "./MainFooter";
const { Content } = Layout; const { Content } = Layout;
export function MainLayout() { export function MainLayout() {
return ( return (
<MainProvider> <MainProvider>
<div className="min-h-screen bg-gray-100"> <div className="bg-gray-100 flex min-h-screen">
<MainHeader /> {/* 左侧固定区域 */}
<div className="flex pt-16"> <div className="bg-[#fafafa] border-r-[1px] border-[#e8e8e8] flex flex-col w-[230px] justify-between h-screen fixed left-0 top-0 z-10">
<NavigationMenu /> <div>
<Content className="flex-grow bg-gray-50"> <div className="flex items-center justify-center my-3">
<Outlet /> <span className="text-2xl font-bold text-[#1e1e1f]"></span>
</Content> </div>
<NavigationMenu />
</div>
<UserInfoPanel />
</div>
{/* 右侧内容区域margin-left等于左侧宽度 */}
<div className="flex flex-col h-screen flex-1 ml-[230px]">
{/* 上方内容区,可滚动 */}
<div className="flex-1 bg-[#eff3f4] border-l-[1px] border-[#e8e8e8]">
<Outlet />
</div>
{/* 下方固定页脚 */}
{/* <div className="flex-shrink-0">
<MainFooter />
</div> */}
</div> </div>
<MainFooter />
</div> </div>
</MainProvider> </MainProvider>
); );

View File

@ -85,7 +85,7 @@ const NavigationMenu: React.FC = () => {
}, [selectedKeys]); }, [selectedKeys]);
return ( return (
<div className="w-[200px] h-full bg-#fff"> <div className="w-full h-full">
<div <div
style={{ style={{
textDecoration: "none", textDecoration: "none",
@ -102,12 +102,15 @@ const NavigationMenu: React.FC = () => {
{/* {/*
<img src={logo} className="w-[124px] h-[40px]"/> */} <img src={logo} className="w-[124px] h-[40px]"/> */}
</div> </div>
<div className="w-[200px] h-[calc(100%-74px)] overflow-y-auto overflow-x-hidden"> <div className="w-full h-full overflow-y-auto overflow-x-hidden ">
<Menu <Menu
onClick={onClick} onClick={onClick}
style={{ style={{
width: 200, width: 230,
background: "#ffffff", background:"#f9fafb",
border: "none",
// color: "#ffffff",
}} }}
selectedKeys={selectedKeys} selectedKeys={selectedKeys}
openKeys={openKeys} openKeys={openKeys}

View File

@ -0,0 +1,67 @@
// apps/web/src/app/main/layout/UserInfoPanel.tsx
import { Avatar, Button, Dropdown } from "antd";
import { UserOutlined, LogoutOutlined } from "@ant-design/icons";
import { useAuth } from "@web/src/providers/auth-provider";
import { useNavigate } from "react-router-dom";
export function UserInfoPanel() {
const { isAuthenticated, user, logout } = useAuth();
const navigate = useNavigate();
const items = [
{
key: "logout",
icon: <LogoutOutlined />,
label: "确认退出",
danger: true,
},
];
return (
<div className="flex items-center justify-start mb-14 px-4">
{isAuthenticated && user ? (
<>
{/* 左侧:头像 */}
<Avatar
size={40}
src={user.avatar}
icon={<UserOutlined />}
className="mr-1 bg-[#2563eb] text-white"
/>
{/* 右侧:用户名和已登录按钮 */}
<div className="flex flex-col">
<div className="text-sm font-medium text-[#1e1e1f] text-center ">
{user.showname || user.username}
</div>
<Dropdown
menu={{
items,
onClick: ({ key }) => key === "logout" && logout(),
}}
placement="bottom"
arrow
>
<Button
type="text"
icon={<LogoutOutlined />}
className="text-gray-500 hover:text-[#2563eb] p-0 h-auto"
>
退
</Button>
</Dropdown>
</div>
</>
) : (
<Button
type="primary"
shape="round"
icon={<UserOutlined />}
onClick={() => navigate("/login")}
>
</Button>
)}
</div>
);
}

View File

@ -159,4 +159,76 @@
.custom-table .ant-table-tbody>tr:last-child>td { .custom-table .ant-table-tbody>tr:last-child>td {
border-bottom: none; border-bottom: none;
/* 去除最后一行的底部边框 */ /* 去除最后一行的底部边框 */
} }
/* 强制保持父菜单正常透明度 */
/* 父菜单项默认状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline > .ant-menu-item,
.ant-menu.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title {
color: #000000 !important;
background: transparent !important;
opacity: 1 !important;
} */
/* 父菜单项悬浮状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline > .ant-menu-item:hover,
.ant-menu.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title:hover {
background: rgba(255, 255, 255, 0.1) !important;
color: #000000 !important;
} */
/* 父菜单项选中状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline > .ant-menu-item.ant-menu-item-selected {
background: #0b3c7c !important;
color: #ffffff !important;
} */
/* 父菜单展开/激活状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline > .ant-menu-submenu.ant-menu-submenu-active > .ant-menu-submenu-title,
.ant-menu.ant-menu-inline > .ant-menu-submenu.ant-menu-submenu-open > .ant-menu-submenu-title {
background: rgba(255, 255, 255, 0.1) !important;
color: #000000 !important;
} */
/* 子菜单容器背景 */
/* .ant-menu.ant-menu-inline .ant-menu-sub.ant-menu-inline {
background: #0b3c7c !important;
} */
/* 子菜单项默认状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline .ant-menu-sub .ant-menu-item {
color: #000000 !important;
background: transparent !important;
} */
/* 子菜单项悬浮状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline .ant-menu-sub .ant-menu-item:hover {
background: rgba(255, 255, 255, 0.1) !important;
color: #000000 !important;
} */
/* 子菜单项选中状态 - 只修改颜色 */
/* .ant-menu.ant-menu-inline .ant-menu-sub .ant-menu-item.ant-menu-item-selected {
background: #0b3c7c !important;
color: #ffffff !important;
} */
/* 确保图标颜色与文字一致 */
/* .ant-menu .ant-menu-item .anticon,
.ant-menu .ant-menu-submenu-title .anticon {
color: inherit !important;
} */
/* 移除默认的after伪元素 */
/* .ant-menu.ant-menu-inline .ant-menu-item::after,
.ant-menu.ant-menu-inline .ant-menu-submenu > .ant-menu-submenu-title::after {
display: none !important;
} */
/* 针对 inline 模式下的子菜单 */
.ant-menu.ant-menu-inline .ant-menu-sub.ant-menu-inline {
background: #f9fafb !important;
}

View File

@ -2,7 +2,7 @@ server {
# 监听80端口 # 监听80端口
listen 80; listen 80;
# 服务器域名/IP地址使用环境变量 # 服务器域名/IP地址使用环境变量
server_name 192.168.142.194; server_name localhost;
# 基础性能优化配置 # 基础性能优化配置
# 启用tcp_nopush以优化数据发送 # 启用tcp_nopush以优化数据发送
@ -100,7 +100,7 @@ server {
# 仅供内部使用 # 仅供内部使用
internal; internal;
# 代理到认证服务 # 代理到认证服务
proxy_pass http://192.168.142.194:3000/auth/file; proxy_pass http://localhost:3000/auth/file;
# 请求优化:不传递请求体 # 请求优化:不传递请求体
proxy_pass_request_body off; proxy_pass_request_body off;