feat(layout): 添加 SVG 图标系统并更新侧边栏导航

- 引入 Sprite 组件统一管理 SVG 图标
- 创建 Icon 组件支持动态加载图标- 定义 icon-ids 类型确保图标引用安全- 更新 Sidebar 使用新图标组件替换 emoji
- 添加 HomeIcon 和 network 图标资源- 调整侧边栏样式和宽度
- 修复 Header 按钮 title 属性替代 aria-label
- 新增 IP 工具页面路由
- 添加全局阴影变量 --main-border-shadow
This commit is contained in:
Chaos
2025-11-10 08:31:56 +08:00
parent a024c4a043
commit cacb25a2ca
11 changed files with 143 additions and 18 deletions

9
package-lock.json generated
View File

@@ -867,6 +867,7 @@
"integrity": "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
@@ -906,6 +907,7 @@
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"debug": "^4.4.1",
@@ -1467,6 +1469,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2055,6 +2058,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -2097,6 +2101,7 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -2113,6 +2118,7 @@
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
@@ -2304,6 +2310,7 @@
"integrity": "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -2402,6 +2409,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -2416,6 +2424,7 @@
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",

View File

@@ -2,6 +2,7 @@
:root{
--main-bg-color: #1f1f1f;
--main-border-shadow:1px 0 3px rgba(255, 255, 255, 0.1);
}
body {

View File

@@ -1,3 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" >
<symbol id="home" viewBox="0 0 20 20">
<g fill="none">
<path fill="url(#home-SVGDx0DLj5H)" d="M13.18 2H10.5C8.768 7.023 6.823 12.62 6.823 18h6.483a1.75 1.75 0 0 0 1.485-.825l3.998-6.42a1.43 1.43 0 0 0 0-1.51L14.98 3.13A2 2 0 0 0 13.18 2"/>
<path fill="url(#home-SVGpR69Vbpe)" fill-opacity="0.5" d="M13.18 2H10.5C8.768 7.023 6.823 12.62 6.823 18h6.483a1.75 1.75 0 0 0 1.485-.825l3.998-6.42a1.43 1.43 0 0 0 0-1.51L14.98 3.13A2 2 0 0 0 13.18 2"/>
<path fill="url(#home-SVGFEeptdiY)" d="M13.25 2.001H6.69c-.601 0-1.16.308-1.48.816l-3.942 6.25a1.75 1.75 0 0 0 0 1.867L5.13 17.06c.354.56.96.91 1.619.938l.067.002h.006a2 2 0 0 0 1.969-1.662l.003-.002L11.18 3.953a2 2 0 0 1 2.069-1.952"/>
<path fill="url(#home-SVGIBIxBcZq)" fill-opacity="0.4" d="M13.25 2.001H6.69c-.601 0-1.16.308-1.48.816l-3.942 6.25a1.75 1.75 0 0 0 0 1.867L5.13 17.06c.354.56.96.91 1.619.938l.067.002h.006a2 2 0 0 0 1.969-1.662l.003-.002L11.18 3.953a2 2 0 0 1 2.069-1.952"/>
<defs>
<radialGradient id="home-SVGDx0DLj5H" cx="0" cy="0" r="1" gradientTransform="rotate(-87.881 17.698 4.836)scale(23.3302 18.6978)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffc470"/><stop offset=".251" stop-color="#ff835c"/><stop offset=".584" stop-color="#f24a9d"/><stop offset=".871" stop-color="#b339f0"/><stop offset="1" stop-color="#c354ff"/></radialGradient>
<radialGradient id="home-SVGpR69Vbpe" cx="0" cy="0" r="1" gradientTransform="matrix(-9.9932 -9.83058 9.94854 -10.1131 11.777 16.154)" gradientUnits="userSpaceOnUse"><stop offset=".709" stop-color="#ffb357" stop-opacity="0"/><stop offset=".942" stop-color="#ffb357"/></radialGradient>
<radialGradient id="home-SVGFEeptdiY" cx="0" cy="0" r="1" gradientTransform="rotate(-160.247 10.243 6.665)scale(22.9945 19.4416)" gradientUnits="userSpaceOnUse"><stop offset=".222" stop-color="#4e46e2"/><stop offset=".578" stop-color="#625df6"/><stop offset=".955" stop-color="#e37dff"/></radialGradient>
<linearGradient id="home-SVGIBIxBcZq" x1="4.823" x2="10.254" y1="8.629" y2="9.914" gradientUnits="userSpaceOnUse"><stop stop-color="#7563f7" stop-opacity="0"/><stop offset=".986" stop-color="#4916ae"/></linearGradient>
</defs>
</g>
</symbol>
<symbol id="network" viewBox="0 0 24 24"><path fill="currentColor" d="M7 7H5V6H4V4h1V3h2v1h1v2H7zm-2 4h1v3H5v1H2v-1H1v-3h1v-1h3zm4-3h1v1H9zm6 7h1v1h-1zm2-7h1v1h-1zM8 7h1v1H8zm-1 5h1v1H7z"/><path fill="currentColor" d="M16 14v-3h-1v-1h-1V9h-3v1h-1v1H9v3h1v1h1v1h3v-1h1v-1zm-5 0v-3h3v3zm5-5h1v1h-1zm0 7h1v1h-1zm5 2h1v3h-1v1h-3v-1h-1v-3h1v-1h3zm1-13v2h-1v1h-2V7h-1V5h1V4h2v1z"/></symbol>
</svg>

Before

Width:  |  Height:  |  Size: 70 B

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import type { IconId } from '$lib/types/icon-ids';
export let id: IconId;
export let size: number | string ;
export let className: string = '';
$: dimensions = typeof size === 'number' ? `${size}px` : size;
</script>
<svg {...$$restProps}
role="img"
class={className}
aria-hidden="true"
width={dimensions?dimensions:24}
height={dimensions?dimensions:24}
>
<use href={`#${id}`} />
</svg>
<style>
.app-icon {
/* 确保图标与文本基线对齐 */
vertical-align: middle;
/* 防止用户选择图标,提高用户体验 */
user-select: none;
/* 默认显示为行内块级元素 */
display: inline-block;
/* 确保它能响应 CSS 动画 */
transition: color 0.2s, transform 0.2s;
}
</style>

View File

@@ -0,0 +1,23 @@
<script>
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" {...$$restProps}>
<g fill="none">
<path fill="url(#SVGkm4eqcHj)" d="M6 9h4v5H6z" />
<path fill="url(#SVG4sqiUdhg)" d="M8.687 2.273a1 1 0 0 0-1.374 0l-4.844 4.58A1.5 1.5 0 0 0 2 7.943v4.569a1.5 1.5 0 0 0 1.5 1.5h3v-4a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v4h3a1.5 1.5 0 0 0 1.5-1.5v-4.57a1.5 1.5 0 0 0-.47-1.09z" />
<path fill="url(#SVGbhYexcuE)" fill-rule="evenodd" d="m8.004 2.636l5.731 5.41a.75.75 0 1 0 1.03-1.091L8.86 1.382a1.25 1.25 0 0 0-1.724.007L1.23 7.059a.75.75 0 0 0 1.038 1.082z" clip-rule="evenodd" />
<defs>
<linearGradient id="SVGkm4eqcHj" x1="8" x2="4.796" y1="9" y2="14.698" gradientUnits="userSpaceOnUse">
<stop stop-color="#944600" />
<stop offset="1" stop-color="#cd8e02" />
</linearGradient>
<linearGradient id="SVG4sqiUdhg" x1="3.145" x2="14.93" y1="1.413" y2="10.981" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffd394" />
<stop offset="1" stop-color="#ffb357" />
</linearGradient>
<linearGradient id="SVGbhYexcuE" x1="10.262" x2="6.945" y1="-.696" y2="7.895" gradientUnits="userSpaceOnUse">
<stop stop-color="#ff921f" />
<stop offset="1" stop-color="#eb4824" />
</linearGradient>
</defs>
</g>
</svg>

View File

@@ -11,22 +11,21 @@
</script>
<header class="custom-titlebar" data-tauri-drag-region>
<div class="app-title" data-tauri-drag-region>
</div>
<div class="app-title" data-tauri-drag-region></div>
<div class="window-controls">
<button type="button" aria-label="最小化窗口" data-tauri-drag-region="false" class="h-btn btn-minimize" on:click={minimize}></button>
<button type="button" title="最小化窗口" data-tauri-drag-region="false" class="h-btn btn-minimize" on:click={minimize}></button>
<button
type="button"
data-tauri-drag-region="false"
class="h-btn btn-max"
aria-label="最大化窗口"
title="最大化窗口"
on:click={maximize}></button>
<button type="button"
data-tauri-drag-region="false"
class="h-btn btn-close"
aria-label="关闭窗口"
title="关闭应用"
on:click={closeWindow}></button>
</div>
</header>

View File

@@ -4,26 +4,34 @@
// 模拟导航项数据
// 确保对象字面量语法正确无误
const navItems = [
{ icon: '🏠', label: '主页', path: '/' },
{ icon: '⭐', label: '收藏', path: '/favorites' },
{ icon: '💬', label: '聊天', path: '/chat' },
{ icon: '⚙️', label: '设置', path: '/settings' },
import { goto } from '$app/navigation';
import Icon from '$lib/components/Icon.svelte';
import type { IconId } from '$lib/types/icon-ids';
import HomeIcon from '$lib/components/icon/HomeIcon.svelte';
const navItems:[{icon: IconId, label: string, path: string}] = [
{ icon: 'network', label: 'IP工具', path: '/iptools' },
];
let activePath: string = '/'; // 明确声明类型
function navigate(path: string) {
activePath = path;
// goto(path); // 实际应用中使用 SvelteKit 的导航
goto(path);
console.log(`导航到: ${path}`);
}
</script>
<nav class="sidebar">
<!-- 顶部图标/Logo -->
<div class="sidebar-logo">
<span role="img" aria-label="App Logo">⚛️</span>
<div class="sidebar-logo pt-2 pb-2">
<button type="button"
class="cursor-pointer"
title="回到主页"
on:click={() => navigate('/')}>
<HomeIcon width="24" height="24" />
</button>
</div>
<!-- 中间导航图标 -->
@@ -35,7 +43,7 @@
on:click={() => navigate(item.path)}
title={item.label}
>
{item.icon}
<Icon id={item.icon} className="hover:text-neutral-400 transition-all duration-1000" size="24" />
</button>
{/each}
</div>
@@ -51,7 +59,7 @@
/* 侧边栏的基础样式 */
.sidebar {
/* 根据图片,侧边栏宽度较窄,可能在 60px 左右 */
width: 25px;
width: 35px;
min-width: 35px; /* 确保它不会收缩 */
height: 100%;
background-color: var(--main-bg-color); /* 比主背景稍亮 */
@@ -59,14 +67,12 @@
flex-direction: column; /* 垂直排列内容 */
align-items: center;
padding: 10px 0;
box-shadow: 1px 0 3px rgba(255, 255, 255, 0.1);
box-shadow: var(--main-border-shadow);
user-select: none; /* 阻止文本选择 */
}
/* Logo/顶部区域 */
.sidebar-logo {
font-size: 1rem;
margin-bottom: 20px;
cursor: pointer;
}

View File

@@ -0,0 +1,16 @@
<script>
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" >
<symbol id="network" viewBox="0 0 24 24">
<g fill="none">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 7.96c2.59-.125 4.379.274 4.625 1.193c.429 1.6-3.98 4.172-9.849 5.745c-5.868 1.572-10.972 1.55-11.401-.051c-.254-.948 1.188-2.236 3.625-3.455" />
<path fill="currentColor" fill-rule="evenodd" d="M4 12a8 8 0 1 1 15.985.491c-1.653.879-3.904 1.754-6.467 2.44c-2.874.77-5.526 1.14-7.478 1.131a12 12 0 0 1-.956-.039A7.96 7.96 0 0 1 4 12m2.766 6.05a8.003 8.003 0 0 0 12.658-3.065c-1.561.697-3.4 1.346-5.389 1.879c-2.668.715-5.208 1.115-7.269 1.186" clip-rule="evenodd" />
</g>
</symbol>
</svg>

View File

@@ -0,0 +1,3 @@
export type IconId =
| 'home'
| 'network' ;

View File

@@ -3,12 +3,15 @@
import favicon from '$lib/assets/favicon.svg';
import Header from '$lib/components/layout/Header.svelte';
import Sidebar from '$lib/components/layout/Sidebar.svelte';
import Sprite from '$lib/components/ui/Sprite.svelte';
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href={favicon} />
<Sprite />
</svelte:head>
<Header/>
<div class="app-container">

View File

@@ -0,0 +1,11 @@
<script>
</script>
<style>
</style>
<div>
IP 工具箱
</div>