A:剩余代码提交
This commit is contained in:
1 parent
3d63040636
commit
5813015407
8 files changed
+1326
-101
No files matched your search
@@ -1,4 +1,4 @@
|
||||
/* ú@7ÍnŒ;˜M */
|
||||
/* 基础样式重置和主题适配 */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
@@ -11,9 +11,11 @@ body {
|
||||
min-height: 100vh;
|
||||
color: var(--text-color);
|
||||
background: var(--bg-color);
|
||||
transition: color 0.3s, background-color 0.3s;
|
||||
line-height: 1.6;
|
||||
transition: color var(--transition-normal), background-color var(--transition-normal);
|
||||
line-height: var(--line-height-normal);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-normal);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -30,14 +32,14 @@ body {
|
||||
a {
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
transition: color var(--transition-normal), opacity var(--transition-normal);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 0.8;
|
||||
opacity: var(--opacity-hover);
|
||||
}
|
||||
|
||||
/* nÝ@ ¹hý(;˜ØÏ */
|
||||
/* 确保所有容器都使用主题变量 */
|
||||
.n-layout,
|
||||
.n-layout-header,
|
||||
.n-layout-sider,
|
||||
@@ -45,43 +47,91 @@ a:hover {
|
||||
.n-layout-footer {
|
||||
background-color: var(--bg-color) !important;
|
||||
color: var(--text-color) !important;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
transition: background-color var(--transition-normal), color var(--transition-normal);
|
||||
}
|
||||
|
||||
/* ñr!„Ú¨a7 */
|
||||
html.dark ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
/* 确保所有文本元素都使用主题颜色 */
|
||||
h1, h2, h3, h4, h5, h6, p, span, div {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
html.dark ::-webkit-scrollbar-track {
|
||||
background: var(--bg-color-secondary);
|
||||
/* 输入框样式适配 */
|
||||
input, textarea, select {
|
||||
background-color: var(--input-bg);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
html.dark ::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: 4px;
|
||||
/* 按钮基础样式适配 */
|
||||
button {
|
||||
background-color: var(--button-bg);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
html.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-color-secondary);
|
||||
/* 通用滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: var(--spacing-sm);
|
||||
height: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Er!„Ú¨a7 */
|
||||
html.light ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-track);
|
||||
}
|
||||
|
||||
html.light ::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
html.light ::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
html.light ::-webkit-scrollbar-thumb:hover {
|
||||
background: #a1a1a1;
|
||||
/* 文本选择样式 */
|
||||
::selection {
|
||||
background-color: var(--selection-bg);
|
||||
color: var(--selection-color);
|
||||
}
|
||||
|
||||
/* 焦点样式 */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* 禁用元素样式 */
|
||||
:disabled {
|
||||
opacity: var(--opacity-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 通用过渡效果 */
|
||||
.transition-colors {
|
||||
transition: color var(--transition-normal), background-color var(--transition-normal), border-color var(--transition-normal);
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.transition-fast {
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
/* 通用阴影类 */
|
||||
.shadow-sm {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.shadow-md {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.shadow-lg {
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/* 统一颜色管理系统 */
|
||||
|
||||
:root {
|
||||
/* ============================================
|
||||
* 基础颜色调色板
|
||||
* ============================================ */
|
||||
|
||||
/* 中性色调色板 */
|
||||
--neutral-50: #fafafa;
|
||||
--neutral-100: #f5f5f5;
|
||||
--neutral-200: #e5e5e5;
|
||||
--neutral-300: #d4d4d4;
|
||||
--neutral-400: #a3a3a3;
|
||||
--neutral-500: #737373;
|
||||
--neutral-600: #525252;
|
||||
--neutral-700: #404040;
|
||||
--neutral-800: #262626;
|
||||
--neutral-900: #171717;
|
||||
--neutral-950: #0a0a0a;
|
||||
|
||||
/* 蓝色调色板 */
|
||||
--blue-50: #eff6ff;
|
||||
--blue-100: #dbeafe;
|
||||
--blue-500: #3b82f6;
|
||||
--blue-600: #2563eb;
|
||||
--blue-700: #1d4ed8;
|
||||
|
||||
/* 红色调色板 */
|
||||
--red-50: #fef2f2;
|
||||
--red-100: #fee2e2;
|
||||
--red-400: #f87171;
|
||||
--red-500: #ef4444;
|
||||
--red-600: #dc2626;
|
||||
--red-700: #b91c1c;
|
||||
--red-danger: #d03050;
|
||||
|
||||
/* 绿色调色板 */
|
||||
--green-50: #f0fdf4;
|
||||
--green-100: #dcfce7;
|
||||
--green-500: #22c55e;
|
||||
--green-600: #16a34a;
|
||||
|
||||
/* 黄色调色板 */
|
||||
--yellow-50: #fefce8;
|
||||
--yellow-100: #fef3c7;
|
||||
--yellow-500: #eab308;
|
||||
--yellow-600: #ca8a04;
|
||||
|
||||
/* ============================================
|
||||
* 语义化颜色变量
|
||||
* ============================================ */
|
||||
|
||||
/* 状态颜色 */
|
||||
--color-success: var(--green-600);
|
||||
--color-warning: var(--yellow-500);
|
||||
--color-error: var(--red-500);
|
||||
--color-danger: var(--red-danger);
|
||||
--color-info: var(--blue-500);
|
||||
|
||||
/* 交互颜色 */
|
||||
--color-primary: var(--blue-600);
|
||||
--color-primary-hover: var(--blue-700);
|
||||
--color-secondary: var(--neutral-600);
|
||||
--color-secondary-hover: var(--neutral-700);
|
||||
|
||||
/* ============================================
|
||||
* 透明度变量
|
||||
* ============================================ */
|
||||
--opacity-disabled: 0.5;
|
||||
--opacity-hover: 0.8;
|
||||
--opacity-focus: 0.9;
|
||||
--opacity-overlay: 0.6;
|
||||
|
||||
/* ============================================
|
||||
* 阴影颜色变量
|
||||
* ============================================ */
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 浅色主题颜色映射
|
||||
* ============================================ */
|
||||
:root,
|
||||
html.light {
|
||||
/* 背景色 */
|
||||
--bg-color: #ffffff;
|
||||
--bg-color-secondary: var(--neutral-100);
|
||||
--bg-color-tertiary: var(--neutral-50);
|
||||
--bg-elevated: #ffffff;
|
||||
|
||||
/* 文字颜色 */
|
||||
--text-color: var(--neutral-900);
|
||||
--text-color-secondary: var(--neutral-600);
|
||||
--text-color-tertiary: var(--neutral-500);
|
||||
--text-color-disabled: var(--neutral-400);
|
||||
--text-color-inverse: #ffffff;
|
||||
|
||||
/* 边框颜色 */
|
||||
--border-color: var(--neutral-300);
|
||||
--border-color-light: var(--neutral-200);
|
||||
--border-color-strong: var(--neutral-400);
|
||||
|
||||
/* 交互状态颜色 */
|
||||
--hover-bg: rgba(0, 0, 0, 0.04);
|
||||
--hover-bg-secondary: rgba(0, 0, 0, 0.08);
|
||||
--active-bg: rgba(0, 0, 0, 0.12);
|
||||
--focus-ring: rgba(59, 130, 246, 0.3);
|
||||
|
||||
/* 组件背景色 */
|
||||
--header-bg: #ffffff;
|
||||
--sidebar-bg: #ffffff;
|
||||
--content-bg: var(--neutral-100);
|
||||
--card-bg: #ffffff;
|
||||
--input-bg: #ffffff;
|
||||
--button-bg: #ffffff;
|
||||
|
||||
/* 阴影颜色 */
|
||||
--shadow-color: rgba(0, 0, 0, 0.08);
|
||||
--shadow-color-light: rgba(0, 0, 0, 0.04);
|
||||
--shadow-color-strong: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 深色主题颜色映射
|
||||
* ============================================ */
|
||||
html.dark {
|
||||
/* 背景色 */
|
||||
--bg-color: var(--neutral-950);
|
||||
--bg-color-secondary: var(--neutral-900);
|
||||
--bg-color-tertiary: var(--neutral-800);
|
||||
--bg-elevated: var(--neutral-900);
|
||||
|
||||
/* 文字颜色 */
|
||||
--text-color: var(--neutral-50);
|
||||
--text-color-secondary: var(--neutral-300);
|
||||
--text-color-tertiary: var(--neutral-400);
|
||||
--text-color-disabled: var(--neutral-600);
|
||||
--text-color-inverse: var(--neutral-900);
|
||||
|
||||
/* 边框颜色 */
|
||||
--border-color: var(--neutral-700);
|
||||
--border-color-light: var(--neutral-800);
|
||||
--border-color-strong: var(--neutral-600);
|
||||
|
||||
/* 交互状态颜色 */
|
||||
--hover-bg: rgba(255, 255, 255, 0.08);
|
||||
--hover-bg-secondary: rgba(255, 255, 255, 0.12);
|
||||
--active-bg: rgba(255, 255, 255, 0.16);
|
||||
--focus-ring: rgba(59, 130, 246, 0.4);
|
||||
|
||||
/* 组件背景色 */
|
||||
--header-bg: var(--neutral-950);
|
||||
--sidebar-bg: var(--neutral-950);
|
||||
--content-bg: var(--neutral-900);
|
||||
--card-bg: var(--neutral-900);
|
||||
--input-bg: var(--neutral-900);
|
||||
--button-bg: var(--neutral-900);
|
||||
|
||||
/* 阴影颜色 */
|
||||
--shadow-color: rgba(0, 0, 0, 0.3);
|
||||
--shadow-color-light: rgba(0, 0, 0, 0.2);
|
||||
--shadow-color-strong: rgba(0, 0, 0, 0.4);
|
||||
|
||||
/* 深色模式特有颜色调整 */
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.4);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 特殊用途颜色变量
|
||||
* ============================================ */
|
||||
:root {
|
||||
/* 分割线颜色 */
|
||||
--divider-color: var(--border-color-light);
|
||||
--divider-color-strong: var(--border-color);
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay-color: rgba(0, 0, 0, var(--opacity-overlay));
|
||||
--modal-backdrop: rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* 滚动条颜色 */
|
||||
--scrollbar-track: var(--bg-color-secondary);
|
||||
--scrollbar-thumb: var(--border-color);
|
||||
--scrollbar-thumb-hover: var(--text-color-secondary);
|
||||
|
||||
/* 选择颜色 */
|
||||
--selection-bg: rgba(59, 130, 246, 0.2);
|
||||
--selection-color: var(--text-color);
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
@import "colors.css";
|
||||
@import "var.css";
|
||||
@import "base.css";
|
||||
@import "theme-override.css";
|
||||
|
||||
/* 应用特定样式 */
|
||||
|
||||
/* 禁止选中 */
|
||||
.no-select {
|
||||
user-select: none;
|
||||
@@ -10,30 +13,33 @@
|
||||
-ms-user-select: none; /* IE10+/Edge */
|
||||
}
|
||||
|
||||
/* 全局容器样式,确保主题一致性 */
|
||||
html, body {
|
||||
margin: 0;
|
||||
/* 工具类 */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* 确保所有文本元素都使用主题颜色 */
|
||||
h1, h2, h3, h4, h5, h6, p, span, div {
|
||||
color: inherit;
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 输入框样式适配 */
|
||||
input, textarea, select {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 按钮基础样式适配 */
|
||||
button {
|
||||
background-color: var(--bg-color-secondary);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1,42 +1,61 @@
|
||||
/* 应用程序特定变量 */
|
||||
:root {
|
||||
/* 布局尺寸变量 */
|
||||
--header-height: 64px;
|
||||
}
|
||||
|
||||
/* 浅色主题变量 */
|
||||
:root,
|
||||
html.light {
|
||||
--bg-color: #ffffff;
|
||||
--bg-color-secondary: #f5f5f5;
|
||||
--bg-color-tertiary: #fafafa;
|
||||
--text-color: #333333;
|
||||
--text-color-secondary: #666666;
|
||||
--text-color-tertiary: #999999;
|
||||
--border-color: #e0e0e6;
|
||||
--border-color-light: #f0f0f0;
|
||||
--shadow-color: rgba(0, 0, 0, 0.08);
|
||||
--shadow-color-light: rgba(0, 0, 0, 0.04);
|
||||
--header-bg: #ffffff;
|
||||
--sidebar-bg: #ffffff;
|
||||
--content-bg: #f5f5f5;
|
||||
--card-bg: #ffffff;
|
||||
--hover-bg: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 深色主题变量 */
|
||||
html.dark {
|
||||
--bg-color: #1a1a1a;
|
||||
--bg-color-secondary: #2d2d2d;
|
||||
--bg-color-tertiary: #3a3a3a;
|
||||
--text-color: #ffffff;
|
||||
--text-color-secondary: #cccccc;
|
||||
--text-color-tertiary: #999999;
|
||||
--border-color: #3d3d3d;
|
||||
--border-color-light: #4a4a4a;
|
||||
--shadow-color: rgba(0, 0, 0, 0.3);
|
||||
--shadow-color-light: rgba(0, 0, 0, 0.2);
|
||||
--header-bg: #1a1a1a;
|
||||
--sidebar-bg: #1a1a1a;
|
||||
--content-bg: #2d2d2d;
|
||||
--card-bg: #2d2d2d;
|
||||
--hover-bg: rgba(255, 255, 255, 0.08);
|
||||
--sidebar-width: 290px;
|
||||
--sidebar-collapsed-width: 64px;
|
||||
|
||||
/* 间距变量 */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* 圆角变量 */
|
||||
--radius-none: 0px;
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* 过渡动画变量 */
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-normal: 0.3s ease;
|
||||
--transition-slow: 0.5s ease;
|
||||
|
||||
/* 字体变量 */
|
||||
--font-size-xs: 0.75rem; /* 12px */
|
||||
--font-size-sm: 0.875rem; /* 14px */
|
||||
--font-size-base: 1rem; /* 16px */
|
||||
--font-size-lg: 1.125rem; /* 18px */
|
||||
--font-size-xl: 1.25rem; /* 20px */
|
||||
--font-size-2xl: 1.5rem; /* 24px */
|
||||
--font-size-3xl: 1.875rem; /* 30px */
|
||||
|
||||
/* 字重变量 */
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* 行高变量 */
|
||||
--line-height-none: 1;
|
||||
--line-height-tight: 1.25;
|
||||
--line-height-normal: 1.5;
|
||||
--line-height-relaxed: 1.625;
|
||||
--line-height-loose: 2;
|
||||
|
||||
/* z-index层级管理 */
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1020;
|
||||
--z-fixed: 1030;
|
||||
--z-modal-backdrop: 1040;
|
||||
--z-modal: 1050;
|
||||
--z-popover: 1060;
|
||||
--z-tooltip: 1070;
|
||||
--z-toast: 1080;
|
||||
}
|
||||
@@ -221,7 +221,7 @@ const handleMenuSelect = (key: string) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
padding: 0 var(--spacing-lg);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ const handleMenuSelect = (key: string) => {
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
margin-right: 12px;
|
||||
margin-right: var(--spacing-md);
|
||||
}
|
||||
|
||||
.logo-icon img {
|
||||
@@ -249,8 +249,8 @@ const handleMenuSelect = (key: string) => {
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -374,15 +374,36 @@ const handleMenuSelect = (key: string) => {
|
||||
|
||||
:deep(.n-menu-item) {
|
||||
color: var(--text-color) !important;
|
||||
margin-right: 12px !important;
|
||||
}
|
||||
|
||||
/* 折叠状态下的菜单项样式 */
|
||||
:deep(.n-layout-sider--collapsed .n-menu-item) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-layout-sider--collapsed .n-menu-item:hover) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-layout-sider--collapsed .n-menu-item.n-menu-item--selected) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-menu-item:hover) {
|
||||
background-color: var(--hover-bg) !important;
|
||||
margin-right: 12px !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-menu-item.n-menu-item--selected) {
|
||||
background-color: var(--hover-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
margin-right: 12px !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-input) {
|
||||
|
||||
@@ -1,11 +1,944 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
文件夹
|
||||
<div class="file-explorer">
|
||||
<!-- 左侧目录树 -->
|
||||
<div class="sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||
<div class="sidebar-header">
|
||||
<n-button quaternary circle size="small" @click="toggleSidebar">
|
||||
<template #icon>
|
||||
<n-icon><menu-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<span v-if="!sidebarCollapsed" class="sidebar-title">文件夹</span>
|
||||
</div>
|
||||
|
||||
<div v-if="!sidebarCollapsed" class="directory-tree">
|
||||
<n-tree
|
||||
:data="directoryTree"
|
||||
:selected-keys="selectedKeys"
|
||||
:expanded-keys="expandedKeys"
|
||||
selectable
|
||||
expand-on-click
|
||||
@update:selected-keys="handleDirectorySelect"
|
||||
@update:expanded-keys="handleDirectoryExpand"
|
||||
>
|
||||
<template #prefix="{ option }">
|
||||
<n-icon :color="option.type === 'folder' ? 'var(--color-warning)' : 'var(--text-color-secondary)'">
|
||||
<folder-outline v-if="option.type === 'folder'" />
|
||||
<image-outline v-else-if="option.type === 'images'" />
|
||||
<document-outline v-else />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 顶部工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<!-- 面包屑导航 -->
|
||||
<n-breadcrumb class="breadcrumb">
|
||||
<n-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbItems"
|
||||
:key="index"
|
||||
@click="navigateTo(item.path)"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon><home-outline v-if="index === 0" /><folder-outline v-else /></n-icon>
|
||||
</template>
|
||||
{{ item.label }}
|
||||
</n-breadcrumb-item>
|
||||
</n-breadcrumb>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-center">
|
||||
<!-- 搜索框 -->
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索文件和文件夹..."
|
||||
class="search-input"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon><search-outline /></n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-right">
|
||||
<!-- 视图模式切换 -->
|
||||
<n-button-group>
|
||||
<n-button
|
||||
:type="viewMode === 'grid' ? 'primary' : 'default'"
|
||||
quaternary
|
||||
@click="setViewMode('grid')"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon><grid-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
:type="viewMode === 'list' ? 'primary' : 'default'"
|
||||
quaternary
|
||||
@click="setViewMode('list')"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon><list-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
|
||||
<!-- 排序方式 -->
|
||||
<n-dropdown :options="sortOptions" @select="handleSortSelect">
|
||||
<n-button quaternary>
|
||||
<template #icon>
|
||||
<n-icon><funnel-outline /></n-icon>
|
||||
</template>
|
||||
排序
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<!-- 缩放控制 -->
|
||||
<div class="zoom-controls">
|
||||
<n-button quaternary size="small" @click="zoomOut" :disabled="zoomLevel <= 1">
|
||||
<template #icon>
|
||||
<n-icon><remove-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<span class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</span>
|
||||
<n-button quaternary size="small" @click="zoomIn" :disabled="zoomLevel >= 3">
|
||||
<template #icon>
|
||||
<n-icon><add-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 更多操作 -->
|
||||
<n-dropdown :options="moreOptions" @select="handleMoreAction">
|
||||
<n-button quaternary>
|
||||
<template #icon>
|
||||
<n-icon><ellipsis-vertical-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件显示区域 -->
|
||||
<div class="file-area" @wheel="handleWheel" @click="clearSelection">
|
||||
<!-- 网格视图 -->
|
||||
<div v-if="viewMode === 'grid'" class="file-grid" :style="{ '--zoom-level': zoomLevel }">
|
||||
<div
|
||||
v-for="file in filteredFiles"
|
||||
:key="file.id"
|
||||
class="file-item"
|
||||
:class="{
|
||||
selected: selectedFiles.includes(file.id),
|
||||
'is-image': file.type === 'image'
|
||||
}"
|
||||
@click="handleFileClick(file, $event)"
|
||||
@dblclick="handleFileDoubleClick(file)"
|
||||
>
|
||||
<!-- 图片预览 -->
|
||||
<div v-if="file.type === 'image'" class="file-preview image-preview">
|
||||
<img :src="file.thumbnail" :alt="file.name" @load="handleImageLoad" />
|
||||
<div class="image-overlay">
|
||||
<div class="image-info">
|
||||
<span class="image-resolution">{{ file.resolution }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 非图片文件图标 -->
|
||||
<div v-else class="file-preview icon-preview" :class="`file-type-${file.type}`">
|
||||
<n-icon size="48" :color="getFileTypeColor(file.type)">
|
||||
<component :is="getFileIcon(file.type)" />
|
||||
</n-icon>
|
||||
</div>
|
||||
|
||||
<div class="file-info">
|
||||
<div class="file-name" :title="file.name">{{ file.name }}</div>
|
||||
<div class="file-meta">
|
||||
<span class="file-size">{{ formatFileSize(file.size) }}</span>
|
||||
<span class="file-date">{{ formatDate(file.modifiedAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<div v-else class="file-list">
|
||||
<n-data-table
|
||||
:columns="listColumns"
|
||||
:data="filteredFiles"
|
||||
:row-key="(row) => row.id"
|
||||
:checked-row-keys="selectedFiles"
|
||||
@update:checked-row-keys="handleRowSelection"
|
||||
>
|
||||
<template #name="{ row }">
|
||||
<div class="list-file-name">
|
||||
<n-icon :color="getFileTypeColor(row.type)">
|
||||
<component :is="getFileIcon(row.type)" />
|
||||
</n-icon>
|
||||
<span>{{ row.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #size="{ row }">
|
||||
{{ formatFileSize(row.size) }}
|
||||
</template>
|
||||
<template #modifiedAt="{ row }">
|
||||
{{ formatDate(row.modifiedAt) }}
|
||||
</template>
|
||||
</n-data-table>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredFiles.length === 0" class="empty-state">
|
||||
<n-empty description="此文件夹为空">
|
||||
<template #icon>
|
||||
<n-icon size="64" color="var(--text-color-tertiary)">
|
||||
<folder-outline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览模态框 -->
|
||||
<n-modal
|
||||
v-model:show="previewVisible"
|
||||
preset="card"
|
||||
:title="currentPreviewFile?.name"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
class="preview-modal"
|
||||
>
|
||||
<div v-if="currentPreviewFile" class="preview-content">
|
||||
<img
|
||||
v-if="currentPreviewFile.type === 'image'"
|
||||
:src="currentPreviewFile.url"
|
||||
:alt="currentPreviewFile.name"
|
||||
class="preview-image"
|
||||
/>
|
||||
<div v-else class="preview-placeholder">
|
||||
<n-icon size="64" :color="getFileTypeColor(currentPreviewFile.type)">
|
||||
<component :is="getFileIcon(currentPreviewFile.type)" />
|
||||
</n-icon>
|
||||
<p>无法预览此文件类型</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-controls">
|
||||
<n-button @click="previousFile" :disabled="currentFileIndex <= 0">
|
||||
<template #icon>
|
||||
<n-icon><chevron-back-outline /></n-icon>
|
||||
</template>
|
||||
上一个
|
||||
</n-button>
|
||||
<span>{{ currentFileIndex + 1 }} / {{ filteredFiles.length }}</span>
|
||||
<n-button @click="nextFile" :disabled="currentFileIndex >= filteredFiles.length - 1">
|
||||
下一个
|
||||
<template #icon>
|
||||
<n-icon><chevron-forward-outline /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<n-dropdown
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
:x="contextMenuX"
|
||||
:y="contextMenuY"
|
||||
:options="contextMenuOptions"
|
||||
:show="showContextMenu"
|
||||
@select="handleContextMenuSelect"
|
||||
@clickoutside="showContextMenu = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import {
|
||||
NButton,
|
||||
NButtonGroup,
|
||||
NInput,
|
||||
NIcon,
|
||||
NTree,
|
||||
NBreadcrumb,
|
||||
NBreadcrumbItem,
|
||||
NDropdown,
|
||||
NDataTable,
|
||||
NEmpty,
|
||||
NModal
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
MenuOutline,
|
||||
FolderOutline,
|
||||
ImageOutline,
|
||||
DocumentOutline,
|
||||
HomeOutline,
|
||||
SearchOutline,
|
||||
GridOutline,
|
||||
ListOutline,
|
||||
FunnelOutline,
|
||||
AddOutline,
|
||||
RemoveOutline,
|
||||
EllipsisVerticalOutline,
|
||||
ChevronBackOutline,
|
||||
ChevronForwardOutline,
|
||||
VideocamOutline,
|
||||
MusicalNoteOutline,
|
||||
ArchiveOutline,
|
||||
CodeSlashOutline
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
</style>
|
||||
// 状态管理
|
||||
const sidebarCollapsed = ref(false)
|
||||
const viewMode = ref<'grid' | 'list'>('grid')
|
||||
const zoomLevel = ref(1.2)
|
||||
const searchQuery = ref('')
|
||||
const currentPath = ref('/Photos')
|
||||
const selectedFiles = ref<string[]>([])
|
||||
const selectedKeys = ref<string[]>(['photos'])
|
||||
const expandedKeys = ref<string[]>(['root'])
|
||||
const previewVisible = ref(false)
|
||||
const currentPreviewFile = ref<any>(null)
|
||||
const showContextMenu = ref(false)
|
||||
const contextMenuX = ref(0)
|
||||
const contextMenuY = ref(0)
|
||||
|
||||
// 假数据
|
||||
const directoryTree = ref([
|
||||
{
|
||||
key: 'root',
|
||||
label: '我的照片',
|
||||
type: 'folder',
|
||||
children: [
|
||||
{
|
||||
key: 'photos',
|
||||
label: '照片',
|
||||
type: 'images',
|
||||
children: [
|
||||
{ key: 'photos-2024', label: '2024', type: 'folder' },
|
||||
{ key: 'photos-2023', label: '2023', type: 'folder' }
|
||||
]
|
||||
},
|
||||
{ key: 'videos', label: '视频', type: 'folder' },
|
||||
{ key: 'documents', label: '文档', type: 'folder' },
|
||||
{ key: 'downloads', label: '下载', type: 'folder' }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
const allFiles = ref([
|
||||
{
|
||||
id: '1',
|
||||
name: 'sunset-beach.jpg',
|
||||
type: 'image',
|
||||
size: 2048576,
|
||||
modifiedAt: '2024-01-15T10:30:00Z',
|
||||
thumbnail: 'https://picsum.photos/300/200?random=1',
|
||||
url: 'https://picsum.photos/1920/1080?random=1',
|
||||
resolution: '1920×1080'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'mountain-view.jpg',
|
||||
type: 'image',
|
||||
size: 3145728,
|
||||
modifiedAt: '2024-01-14T15:45:00Z',
|
||||
thumbnail: 'https://picsum.photos/300/200?random=2',
|
||||
url: 'https://picsum.photos/1920/1080?random=2',
|
||||
resolution: '1920×1080'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'presentation.pdf',
|
||||
type: 'document',
|
||||
size: 1048576,
|
||||
modifiedAt: '2024-01-13T09:15:00Z'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'holiday-video.mp4',
|
||||
type: 'video',
|
||||
size: 52428800,
|
||||
modifiedAt: '2024-01-12T20:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'project-files.zip',
|
||||
type: 'archive',
|
||||
size: 10485760,
|
||||
modifiedAt: '2024-01-11T11:30:00Z'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
name: 'script.js',
|
||||
type: 'code',
|
||||
size: 4096,
|
||||
modifiedAt: '2024-01-10T16:20:00Z'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredFiles = computed(() => {
|
||||
let files = allFiles.value
|
||||
|
||||
if (searchQuery.value) {
|
||||
files = files.filter(file =>
|
||||
file.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
return files
|
||||
})
|
||||
|
||||
const breadcrumbItems = computed(() => {
|
||||
const parts = currentPath.value.split('/').filter(Boolean)
|
||||
const items = [{ label: '主页', path: '/' }]
|
||||
|
||||
let currentFullPath = ''
|
||||
for (const part of parts) {
|
||||
currentFullPath += '/' + part
|
||||
items.push({ label: part, path: currentFullPath })
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
const currentFileIndex = computed(() => {
|
||||
if (!currentPreviewFile.value) return -1
|
||||
return filteredFiles.value.findIndex(file => file.id === currentPreviewFile.value.id)
|
||||
})
|
||||
|
||||
// 排序和更多操作选项
|
||||
const sortOptions = ref([
|
||||
{ label: '按名称排序', key: 'name' },
|
||||
{ label: '按大小排序', key: 'size' },
|
||||
{ label: '按修改时间排序', key: 'modifiedAt' },
|
||||
{ label: '按类型排序', key: 'type' }
|
||||
])
|
||||
|
||||
const moreOptions = ref([
|
||||
{ label: '新建文件夹', key: 'new-folder' },
|
||||
{ label: '上传文件', key: 'upload' },
|
||||
{ label: '刷新', key: 'refresh' },
|
||||
{ label: '显示隐藏文件', key: 'show-hidden' }
|
||||
])
|
||||
|
||||
const contextMenuOptions = computed(() => [
|
||||
{ label: '打开', key: 'open' },
|
||||
{ label: '重命名', key: 'rename' },
|
||||
{ label: '删除', key: 'delete', disabled: selectedFiles.value.length === 0 },
|
||||
{ type: 'divider' },
|
||||
{ label: '复制', key: 'copy' },
|
||||
{ label: '剪切', key: 'cut' },
|
||||
{ label: '粘贴', key: 'paste' },
|
||||
{ type: 'divider' },
|
||||
{ label: '属性', key: 'properties' }
|
||||
])
|
||||
|
||||
// 列表视图的表格列定义
|
||||
const listColumns = [
|
||||
{ type: 'selection' },
|
||||
{ title: '名称', key: 'name', render: 'name' },
|
||||
{ title: '大小', key: 'size', render: 'size', width: 120 },
|
||||
{ title: '修改时间', key: 'modifiedAt', render: 'modifiedAt', width: 180 }
|
||||
]
|
||||
|
||||
// 方法
|
||||
const toggleSidebar = () => {
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value
|
||||
}
|
||||
|
||||
const setViewMode = (mode: 'grid' | 'list') => {
|
||||
viewMode.value = mode
|
||||
}
|
||||
|
||||
const zoomIn = () => {
|
||||
if (zoomLevel.value < 3) {
|
||||
zoomLevel.value += 0.2
|
||||
}
|
||||
}
|
||||
|
||||
const zoomOut = () => {
|
||||
if (zoomLevel.value > 0.6) {
|
||||
zoomLevel.value -= 0.2
|
||||
}
|
||||
}
|
||||
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault()
|
||||
if (e.deltaY < 0) {
|
||||
zoomIn()
|
||||
} else {
|
||||
zoomOut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleDirectorySelect = (keys: string[]) => {
|
||||
selectedKeys.value = keys
|
||||
if (keys.length > 0) {
|
||||
// 这里可以根据选中的目录加载相应的文件
|
||||
console.log('Selected directory:', keys[0])
|
||||
}
|
||||
}
|
||||
|
||||
const handleDirectoryExpand = (keys: string[]) => {
|
||||
expandedKeys.value = keys
|
||||
}
|
||||
|
||||
const navigateTo = (path: string) => {
|
||||
currentPath.value = path
|
||||
// 加载对应路径的文件
|
||||
}
|
||||
|
||||
const handleFileClick = (file: any, event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
// Ctrl+点击多选
|
||||
if (selectedFiles.value.includes(file.id)) {
|
||||
selectedFiles.value = selectedFiles.value.filter(id => id !== file.id)
|
||||
} else {
|
||||
selectedFiles.value.push(file.id)
|
||||
}
|
||||
} else if (event.shiftKey && selectedFiles.value.length > 0) {
|
||||
// Shift+点击范围选择
|
||||
const lastSelected = selectedFiles.value[selectedFiles.value.length - 1]
|
||||
const lastIndex = filteredFiles.value.findIndex(f => f.id === lastSelected)
|
||||
const currentIndex = filteredFiles.value.findIndex(f => f.id === file.id)
|
||||
|
||||
const start = Math.min(lastIndex, currentIndex)
|
||||
const end = Math.max(lastIndex, currentIndex)
|
||||
|
||||
selectedFiles.value = filteredFiles.value.slice(start, end + 1).map(f => f.id)
|
||||
} else {
|
||||
// 单选
|
||||
selectedFiles.value = [file.id]
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileDoubleClick = (file: any) => {
|
||||
if (file.type === 'image') {
|
||||
currentPreviewFile.value = file
|
||||
previewVisible.value = true
|
||||
} else {
|
||||
// 其他文件类型的打开逻辑
|
||||
console.log('Opening file:', file.name)
|
||||
}
|
||||
}
|
||||
|
||||
const clearSelection = () => {
|
||||
selectedFiles.value = []
|
||||
}
|
||||
|
||||
const handleRowSelection = (keys: string[]) => {
|
||||
selectedFiles.value = keys
|
||||
}
|
||||
|
||||
const previousFile = () => {
|
||||
if (currentFileIndex.value > 0) {
|
||||
currentPreviewFile.value = filteredFiles.value[currentFileIndex.value - 1]
|
||||
}
|
||||
}
|
||||
|
||||
const nextFile = () => {
|
||||
if (currentFileIndex.value < filteredFiles.value.length - 1) {
|
||||
currentPreviewFile.value = filteredFiles.value[currentFileIndex.value + 1]
|
||||
}
|
||||
}
|
||||
|
||||
const handleSortSelect = (key: string) => {
|
||||
console.log('Sort by:', key)
|
||||
}
|
||||
|
||||
const handleMoreAction = (key: string) => {
|
||||
console.log('More action:', key)
|
||||
}
|
||||
|
||||
const handleContextMenuSelect = (key: string) => {
|
||||
console.log('Context menu:', key)
|
||||
showContextMenu.value = false
|
||||
}
|
||||
|
||||
const handleImageLoad = (event: Event) => {
|
||||
// 图片加载完成的处理
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
const getFileIcon = (type: string) => {
|
||||
const iconMap: { [key: string]: any } = {
|
||||
image: ImageOutline,
|
||||
video: VideocamOutline,
|
||||
audio: MusicalNoteOutline,
|
||||
document: DocumentOutline,
|
||||
archive: ArchiveOutline,
|
||||
code: CodeSlashOutline,
|
||||
folder: FolderOutline
|
||||
}
|
||||
return iconMap[type] || DocumentOutline
|
||||
}
|
||||
|
||||
const getFileTypeColor = (type: string) => {
|
||||
const colorMap: { [key: string]: string } = {
|
||||
image: 'var(--color-success)',
|
||||
video: 'var(--color-info)',
|
||||
audio: 'var(--color-secondary)',
|
||||
document: 'var(--text-color-secondary)',
|
||||
archive: 'var(--color-warning)',
|
||||
code: 'var(--color-primary)',
|
||||
folder: 'var(--color-warning)'
|
||||
}
|
||||
return colorMap[type] || 'var(--text-color-secondary)'
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
if (bytes === 0) return '0 B'
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024))
|
||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 组件挂载后的初始化
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-explorer {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: var(--bg-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧边栏 */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background: var(--sidebar-bg);
|
||||
border-right: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width var(--transition-normal);
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-md);
|
||||
border-bottom: 1px solid var(--border-color-light);
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.directory-tree {
|
||||
flex: 1;
|
||||
padding: var(--spacing-sm);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--header-bg);
|
||||
border-bottom: 1px solid var(--border-color-light);
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.toolbar-center {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.breadcrumb :deep(.n-breadcrumb-item) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding: 0 var(--spacing-sm);
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-color-secondary);
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 文件区域 */
|
||||
.file-area {
|
||||
flex: 1;
|
||||
padding: var(--spacing-lg);
|
||||
overflow: auto;
|
||||
background: var(--content-bg);
|
||||
}
|
||||
|
||||
/* 网格视图 */
|
||||
.file-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(calc(160px * var(--zoom-level)), 1fr));
|
||||
gap: calc(var(--spacing-md) * var(--zoom-level));
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
background: var(--card-bg);
|
||||
border: 2px solid transparent;
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
border-color: var(--border-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.file-item.selected {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
.file-item.is-image .file-preview {
|
||||
aspect-ratio: 4/3;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
position: relative;
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
background: var(--bg-color-secondary);
|
||||
}
|
||||
|
||||
.image-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to bottom, transparent 60%, rgba(0,0,0,0.7));
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.file-item:hover .image-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.image-info {
|
||||
padding: var(--spacing-sm);
|
||||
color: white;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 4/3;
|
||||
background: var(--bg-color-tertiary);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-color);
|
||||
font-size: var(--font-size-sm);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-color-secondary);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* 列表视图 */
|
||||
.file-list {
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-file-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* 预览模态框 */
|
||||
.preview-modal {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
background: var(--bg-color-secondary);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.preview-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--border-color-light);
|
||||
}
|
||||
|
||||
/* 文件类型颜色 */
|
||||
.file-type-document {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.file-type-video {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
}
|
||||
|
||||
.file-type-audio {
|
||||
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
|
||||
}
|
||||
|
||||
.file-type-archive {
|
||||
background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
|
||||
}
|
||||
|
||||
.file-type-code {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.file-explorer {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-center {
|
||||
min-width: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.file-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -245,7 +245,7 @@ onMounted(() => {
|
||||
|
||||
.library-tag:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.path-text {
|
||||
@@ -272,7 +272,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
color: #d03050;
|
||||
color: var(--color-danger);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3049, // 设置你想要的端口号
|
||||
port: 0, // 设置你想要的端口号
|
||||
host: false, // 可选:允许外部访问
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user