<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>多功能工具箱</title>
<link rel="stylesheet" href="https://unpkg.com/boxicons@latest/css/boxicons.min.css">
<style>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
:root {
/* 精致的配色系统 */
--primary-color: #2563eb;
--primary-light: #3b82f6;
--primary-dark: #1d4ed8;
--secondary-color: #64748b;
--accent-color: #f59e0b;
/* 背景色系 */
--bg-primary: #ffffff;
--bg-secondary: #fafbfc;
--bg-tertiary: #f1f3f5;
--bg-overlay: rgba(255, 255, 255, 0.98);
--bg-glass: rgba(255, 255, 255, 0.8);
/* 文字颜色 */
--text-primary: #0f172a;
--text-secondary: #334155;
--text-muted: #64748b;
--text-white: #ffffff;
/* 边框颜色 */
--border-light: #e2e8f0;
--border-medium: #cbd5e1;
--border-dark: #94a3af;
/* 状态颜色 */
--success-color: #059669;
--warning-color: #d97706;
--error-color: #dc2626;
--info-color: #0891b2;
/* 阴影系统 */
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.02);
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.02);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.06), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 10px 10px -5px rgba(0, 0, 0, 0.02);
/* 尺寸 */
--sidebar-width: 260px;
--sidebar-collapsed-width: 70px;
--border-radius: 8px;
--border-radius-lg: 12px;
--border-radius-xl: 16px;
/* 字体 */
--font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
/* 动画 */
--transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 暗色主题 */
.dark-theme {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--bg-overlay: rgba(15, 23, 42, 0.98);
--bg-glass: rgba(15, 23, 42, 0.8);
--text-primary: #f8fafc;
--text-secondary: #e2e8f0;
--text-muted: #94a3b8;
--border-light: #334155;
--border-medium: #475569;
--border-dark: #64748b;
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
}
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--text-primary);
background: var(--bg-secondary);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 布局容器 */
.app-container {
display: flex;
min-height: 100vh;
position: relative;
}
/* 侧边栏样式 - 优化版 */
.sidebar {
width: var(--sidebar-width);
background: var(--bg-primary);
border-right: 1px solid var(--border-light);
box-shadow: var(--shadow-lg);
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 1000;
transform: translateX(0);
transition: all var(--transition-base);
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
.sidebar.collapsed {
width: var(--sidebar-collapsed-width);
}
/* 移动端侧边栏 */
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
width: 280px;
}
.sidebar.show {
transform: translateX(0);
}
.sidebar.collapsed {
width: 280px;
transform: translateX(-100%);
}
.sidebar.collapsed.show {
transform: translateX(0);
}
}
/* 侧边栏遮罩 */
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all var(--transition-base);
backdrop-filter: blur(4px);
pointer-events: none;
}
.sidebar-overlay.show {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
/* 侧边栏头部 - 优化版 */
.sidebar-header {
padding: 20px 16px;
border-bottom: 1px solid var(--border-light);
background: var(--bg-primary);
position: sticky;
top: 0;
z-index: 10;
}
.sidebar-brand {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.brand-logo {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-white);
font-size: var(--font-size-lg);
font-weight: 700;
flex-shrink: 0;
box-shadow: var(--shadow-md);
}
.brand-info {
flex: 1;
min-width: 0;
}
.brand-info h1 {
font-size: var(--font-size-lg);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 2px;
line-height: 1.2;
letter-spacing: -0.025em;
}
.brand-info p {
font-size: var(--font-size-xs);
color: var(--text-muted);
line-height: 1.3;
}
.sidebar.collapsed .brand-info {
display: none;
}
/* 侧边栏切换按钮 */
.sidebar-toggle {
position: absolute;
top: 20px;
right: 16px;
width: 32px;
height: 32px;
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all var(--transition-fast);
z-index: 11;
}
.sidebar-toggle:hover {
background: var(--bg-tertiary);
border-color: var(--border-medium);
}
.sidebar-toggle i {
font-size: 16px;
color: var(--text-secondary);
transition: transform var(--transition-base);
}
.sidebar.collapsed .sidebar-toggle i {
transform: rotate(180deg);
}
/* 移动端切换按钮 - 优化版 */
@media (max-width: 768px) {
.sidebar-toggle {
position: fixed;
top: 16px;
left: 16px;
z-index: 1001;
background: var(--bg-primary);
box-shadow: var(--shadow-md);
opacity: 1;
visibility: visible;
}
.sidebar.show .sidebar-toggle {
left: 240px;
}
.sidebar.collapsed .sidebar-toggle i {
transform: rotate(0deg);
}
}
/* 语言选择器 - 优化版 */
.language-selector {
display: flex;
gap: 4px;
background: var(--bg-secondary);
padding: 4px;
border-radius: var(--border-radius);
border: 1px solid var(--border-light);
}
.language-btn {
flex: 1;
padding: 6px 10px;
background: transparent;
border: none;
border-radius: calc(var(--border-radius) - 2px);
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
text-align: center;
}
.language-btn:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.language-btn.active {
background: var(--primary-color);
color: var(--text-white);
box-shadow: var(--shadow-sm);
}
.sidebar.collapsed .language-selector {
display: none;
}
/* 天气信息样式 - 优化版 */
.weather-widget {
margin-top: 16px;
padding: 12px;
background: var(--bg-secondary);
border-radius: var(--border-radius);
border: 1px solid var(--border-light);
position: relative;
}
.weather-simple {
display: flex;
align-items: center;
justify-content: space-between;
}
.weather-icon {
font-size: 24px;
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.weather-temp {
font-size: var(--font-size-lg);
font-weight: 600;
}
.weather-location {
font-size: var(--font-size-xs);
color: var(--text-muted);
margin-top: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
/* 优化折叠状态下的天气组件 */
.sidebar.collapsed .weather-widget {
padding: 10px 5px;
text-align: center;
}
.sidebar.collapsed .weather-simple {
flex-direction: column;
gap: 4px;
align-items: center;
}
.sidebar.collapsed .weather-icon {
margin: 0 auto;
}
.sidebar.collapsed .weather-temp {
font-size: var(--font-size-base);
}
.sidebar.collapsed .weather-location {
display: none;
}
/* 天气详情弹窗 */
.weather-details {
position: absolute;
left: calc(100% + 16px);
top: 0;
width: 300px;
background: var(--bg-primary);
border-radius: var(--border-radius);
border: 1px solid var(--border-light);
box-shadow: var(--shadow-lg);
padding: 16px;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all var(--transition-base);
pointer-events: none;
}
.weather-widget:hover .weather-details {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.weather-details-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-light);
}
.weather-details-location {
font-weight: 600;
}
.weather-details-date {
font-size: var(--font-size-xs);
color: var(--text-muted);
}
.weather-forecast {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
}
.forecast-day {
background: var(--bg-secondary);
border-radius: var(--border-radius);
padding: 12px;
text-align: center;
}
.forecast-date {
font-size: var(--font-size-xs);
color: var(--text-muted);
margin-bottom: 8px;
}
.forecast-icon {
font-size: 24px;
margin-bottom: 8px;
color: var(--primary-color);
}
.forecast-temp {
font-weight: 600;
}
.forecast-desc {
font-size: var(--font-size-xs);
color: var(--text-secondary);
margin-top: 4px;
}
/* 导航菜单 - 优化版 */
.sidebar-nav {
padding: 16px 12px;
flex: 1;
overflow-y: auto;
}
.nav-section {
margin-bottom: 24px;
}
.nav-section:last-child {
margin-bottom: 0;
}
.nav-title {
font-size: var(--font-size-xs);
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 12px;
padding: 0 8px;
}
.sidebar.collapsed .nav-title {
display: none;
}
.nav-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 2px;
}
.nav-item {
position: relative;
}
.nav-link {
display: flex;
align-items: center;
padding: 10px 12px;
color: var(--text-secondary);
text-decoration: none;
border-radius: var(--border-radius);
transition: all var(--transition-fast);
font-size: var(--font-size-sm);
font-weight: 500;
position: relative;
overflow: hidden;
cursor: pointer;
}
.nav-link:hover {
color: var(--text-primary);
background: var(--bg-tertiary);
}
.nav-link.active {
background: var(--primary-color);
color: var(--text-white);
box-shadow: var(--shadow-md);
}
.nav-icon {
width: 20px;
height: 20px;
margin-right: 12px;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.sidebar.collapsed .nav-link {
justify-content: center;
padding: 12px;
}
.sidebar.collapsed .nav-text {
display: none;
}
.sidebar.collapsed .nav-icon {
margin-right: 0;
}
/* 工具提示 */
.nav-item .tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
background: var(--text-primary);
color: var(--bg-primary);
padding: 6px 10px;
border-radius: var(--border-radius);
font-size: var(--font-size-xs);
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all var(--transition-fast);
z-index: 1000;
margin-left: 12px;
pointer-events: none;
box-shadow: var(--shadow-lg);
}
.sidebar.collapsed .nav-item:hover .tooltip {
opacity: 1;
visibility: visible;
}
/* 侧边栏底部 */
.sidebar-footer {
padding: 16px;
background: var(--bg-primary);
border-top: 1px solid var(--border-light);
}
.theme-toggle {
width: 100%;
padding: 10px 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: var(--font-size-sm);
font-weight: 500;
}
.theme-toggle:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
border-color: var(--border-medium);
}
.sidebar.collapsed .theme-toggle {
padding: 10px;
}
.sidebar.collapsed .theme-toggle span {
display: none;
}
/* 主内容区域 */
.main-content {
flex: 1;
margin-left: var(--sidebar-width);
min-height: 100vh;
background: var(--bg-primary);
transition: margin-left var(--transition-base);
display: flex;
flex-direction: column;
}
.sidebar.collapsed + .main-content {
margin-left: var(--sidebar-collapsed-width);
}
@media (max-width: 768px) {
.main-content {
margin-left: 0;
}
.sidebar.collapsed + .main-content {
margin-left: 0;
}
}
/* 标签页 - 优化版 */
.tabs-container {
background: var(--bg-primary);
border-bottom: 1px solid var(--border-light);
padding: 0 16px;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
position: sticky;
top: 0;
z-index: 90;
box-shadow: var(--shadow-sm);
}
.tabs-container::-webkit-scrollbar {
display: none;
}
.tabs {
display: flex;
gap: 4px;
min-height: 48px;
padding: 8px 0;
}
.tab {
display: flex;
align-items: center;
padding: 0 16px;
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
cursor: pointer;
transition: all var(--transition-fast);
white-space: nowrap;
min-width: 120px;
max-width: 200px;
position: relative;
font-size: var(--font-size-sm);
color: var(--text-secondary);
height: 36px;
font-weight: 500;
}
.tab:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.tab.active {
background: var(--primary-color);
color: var(--text-white);
border-color: var(--primary-color);
}
.tab-close {
margin-left: 8px;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: all var(--transition-fast);
}
.tab-close:hover {
background: rgba(255, 255, 255, 0.2);
opacity: 1;
}
.tab:not(.active) .tab-close:hover {
background: var(--error-color);
color: var(--text-white);
opacity: 1;
}
.new-tab-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
flex-shrink: 0;
}
.new-tab-btn:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
/* 内容区域 - 优化版 */
.content-area {
padding: 24px;
flex: 1;
}
@media (max-width: 768px) {
.content-area {
padding: 16px;
}
}
.tab-pane {
display: none;
animation: fadeIn 0.3s ease;
}
.tab-pane.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 工具卡片 - 优化版 */
.tool-card {
background: var(--bg-primary);
border-radius: var(--border-radius-lg);
padding: 24px;
box-shadow: var(--shadow-sm);
transition: all var(--transition-fast);
border: 1px solid var(--border-light);
margin-bottom: 24px;
}
.tool-card:hover {
box-shadow: var(--shadow-md);
}
.tool-title {
font-size: var(--font-size-xl);
font-weight: 600;
margin-bottom: 20px;
color: var(--text-primary);
}
/* 表单元素 - 优化版 */
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
}
.form-input {
width: 100%;
padding: 12px 16px;
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
font-size: var(--font-size-sm);
color: var(--text-primary);
transition: all var(--transition-fast);
}
.form-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.form-textarea {
min-height: 120px;
resize: vertical;
font-family: var(--font-family);
}
/* 按钮 - 优化版 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 16px;
border: none;
border-radius: var(--border-radius);
font-size: var(--font-size-sm);
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
gap: 8px;
min-height: 40px;
}
.btn-primary {
background: var(--primary-color);
color: var(--text-white);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
background: var(--primary-dark);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid var(--border-light);
}
.btn-secondary:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
border-color: var(--border-medium);
}
.btn-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
/* 通知 - 优化版 */
.notification {
position: fixed;
bottom: 24px;
right: 24px;
background: var(--bg-primary);
border-left: 4px solid var(--primary-color);
border-radius: var(--border-radius);
padding: 16px 20px;
box-shadow: var(--shadow-xl);
z-index: 1000;
opacity: 0;
transform: translateY(20px);
transition: all var(--transition-base);
max-width: 320px;
font-size: var(--font-size-sm);
}
.notification.show {
opacity: 1;
transform: translateY(0);
}
.notification.success {
border-left-color: var(--success-color);
}
.notification.error {
border-left-color: var(--error-color);
}
.notification.warning {
border-left-color: var(--warning-color);
}
.notification.info {
border-left-color: var(--info-color);
}
@media (max-width: 768px) {
.notification {
bottom: 16px;
right: 16px;
left: 16px;
max-width: none;
}
}
/* 浏览器组件 - 优化版 */
.browser-container {
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.browser-toolbar {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-light);
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
}
.browser-nav {
display: flex;
gap: 8px;
}
.browser-btn {
width: 32px;
height: 32px;
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all var(--transition-fast);
color: var(--text-secondary);
}
.browser-btn:hover:not(:disabled) {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.browser-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.address-bar {
flex: 1;
display: flex;
align-items: center;
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
padding: 0 12px;
height: 36px;
position: relative;
}
.address-input {
flex: 1;
border: none;
background: transparent;
color: var(--text-primary);
font-size: var(--font-size-sm);
outline: none;
padding-right: 36px; /* 为搜索引擎图标留出空间 */
}
/* 搜索引擎选择器 */
.search-engine-selector {
cursor: pointer;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--border-radius);
transition: all var(--transition-fast);
}
.search-engine-selector:hover {
background: var(--bg-tertiary);
}
.search-engine-icon {
width: 16px;
height: 16px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.search-engine-dropdown {
position: absolute;
top: calc(100% + 5px);
left: 0;
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
box-shadow: var(--shadow-lg);
z-index: 1000;
min-width: 150px;
display: none;
}
.search-engine-dropdown.show {
display: block;
}
.search-engine-item {
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: all var(--transition-fast);
}
.search-engine-item:hover {
background: var(--bg-secondary);
}
.search-engine-item.active {
background: var(--bg-tertiary);
font-weight: 500;
}
.search-engine-item-icon {
width: 16px;
height: 16px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.browser-content {
height: 500px;
position: relative;
background: var(--bg-primary);
}
.browser-frame {
width: 100%;
height: 100%;
border: none;
}
.browser-placeholder {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: var(--text-muted);
background: var(--bg-secondary);
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-medium);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-dark);
}
/* 选择文本样式 */
::selection {
background: rgba(37, 99, 235, 0.2);
color: var(--text-primary);
}
/* 响应式优化 */
@media (max-width: 768px) {
.browser-toolbar {
flex-wrap: wrap;
gap: 8px;
}
.address-bar {
order: 3;
width: 100%;
margin-top: 8px;
}
.browser-content {
height: 400px;
}
.btn-group {
flex-direction: column;
}
/* 移动端工具布局优化 */
.tool-card {
padding: 16px;
}
/* 移动端表单布局优化 */
.form-input {
padding: 10px 12px;
}
/* 移动端网格布局优化 */
[style*="grid-template-columns"] {
grid-template-columns: 1fr !important;
}
/* 移动端HTML预览工具优化 */
#html-preview-tab > div {
grid-template-columns: 1fr !important;
height: auto !important;
}
#html-preview-tab > div > div {
height: 300px;
margin-bottom: 16px;
}
/* 移动端单位转换工具优化 */
#unit-converter-tab > div:last-child {
grid-template-columns: 1fr !important;
gap: 16px !important;
}
#swapUnits {
transform: rotate(90deg);
margin: 0 auto;
}
}
/* 工具卡片网格布局 */
.grid-layout {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.grid-card {
background: var(--bg-primary);
border-radius: var(--border-radius);
border: 1px solid var(--border-light);
padding: 16px;
transition: all var(--transition-fast);
}
.grid-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
</style>
</head>
<body>
<!-- 侧边栏遮罩 -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<div class="app-container">
<!-- 侧边栏 -->
<aside class="sidebar" id="sidebar">
<!-- 侧边栏切换按钮 -->
<button class="sidebar-toggle" id="sidebarToggle">
<i class="bx bx-chevron-left"></i>
</button>
<!-- 侧边栏头部 -->
<div class="sidebar-header">
<div class="sidebar-brand">
<div class="brand-logo">T</div>
<div class="brand-info">
<h1 id="brand-title">工具箱</h1>
<p id="brand-subtitle">高效在线工具集</p>
</div>
</div>
<!-- 语言选择器 -->
<div class="language-selector">
<button class="language-btn active" data-lang="zh-CN">中文</button>
<button class="language-btn" data-lang="en-US">EN</button>
</div>
<!-- 天气小部件 -->
<div class="weather-widget" id="weatherWidget">
<div class="weather-simple">
<div>
<div class="weather-temp" id="weatherTemp">--°C</div>
<div class="weather-location" id="weatherLocation">加载中...</div>
</div>
<div class="weather-icon" id="weatherIcon">
<i class="bx bx-cloud"></i>
</div>
</div>
<!-- 天气详情弹窗 -->
<div class="weather-details" id="weatherDetails">
<div class="weather-details-header">
<div class="weather-details-location" id="weatherDetailsLocation">--</div>
<div class="weather-details-date" id="weatherDetailsDate">--</div>
</div>
<div class="weather-forecast" id="weatherForecast">
<!-- 天气预报将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
<!-- 导航菜单 -->
<nav class="sidebar-nav">
<div class="nav-section">
<h3 class="nav-title" id="nav-title-main">常用工具</h3>
<ul class="nav-list">
<li class="nav-item">
<a href="#" class="nav-link active" data-tab="browser">
<i class="nav-icon bx bx-globe"></i>
<span class="nav-text" id="nav-browser">浏览器</span>
<div class="tooltip" id="tooltip-browser">浏览器</div>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-tab="image-converter">
<i class="nav-icon bx bx-image"></i>
<span class="nav-text" id="nav-image">图片转换</span>
<div class="tooltip" id="tooltip-image">图片转换</div>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<h3 class="nav-title" id="nav-title-dev">开发工具</h3>
<ul class="nav-list">
<li class="nav-item">
<a href="#" class="nav-link" data-tab="html-preview">
<i class="nav-icon bx bx-code"></i>
<span class="nav-text" id="nav-html">HTML预览</span>
<div class="tooltip" id="tooltip-html">HTML预览</div>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-tab="json-formatter">
<i class="nav-icon bx bx-code-curly"></i>
<span class="nav-text" id="nav-json">JSON格式化</span>
<div class="tooltip" id="tooltip-json">JSON格式化</div>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-tab="color-picker">
<i class="nav-icon bx bx-palette"></i>
<span class="nav-text" id="nav-color">颜色选择器</span>
<div class="tooltip" id="tooltip-color">颜色选择器</div>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<h3 class="nav-title" id="nav-title-utility">实用工具</h3>
<ul class="nav-list">
<li class="nav-item">
<a href="#" class="nav-link" data-tab="qrcode-generator">
<i class="nav-icon bx bx-qr"></i>
<span class="nav-text" id="nav-qrcode">二维码生成</span>
<div class="tooltip" id="tooltip-qrcode">二维码生成</div>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-tab="password-generator">
<i class="nav-icon bx bx-key"></i>
<span class="nav-text" id="nav-password">密码生成器</span>
<div class="tooltip" id="tooltip-password">密码生成器</div>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-tab="unit-converter">
<i class="nav-icon bx bx-transfer"></i>
<span class="nav-text" id="nav-unit">单位转换</span>
<div class="tooltip" id="tooltip-unit">单位转换</div>
</a>
</li>
</ul>
</div>
</nav>
<!-- 侧边栏底部 -->
<div class="sidebar-footer">
<button class="theme-toggle" id="themeToggle">
<i class="bx bx-moon"></i>
<span id="theme-text">深色模式</span>
</button>
</div>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 标签页 -->
<div class="tabs-container">
<div class="tabs" id="tabs">
<div class="tab active" data-tab="browser">
<span id="tab-browser">浏览器</span>
<div class="tab-close">
<i class="bx bx-x"></i>
</div>
</div>
<button class="new-tab-btn" id="newTabBtn">
<i class="bx bx-plus"></i>
</button>
</div>
</div>
<!-- 内容区域 -->
<div class="content-area">
<!-- 浏览器 -->
<div class="tab-pane active" id="browser-tab">
<div class="tool-card">
<h2 class="tool-title">浏览器</h2>
<div class="browser-container">
<div class="browser-toolbar">
<div class="browser-nav">
<button class="browser-btn" id="backBtn" title="后退">
<i class="bx bx-chevron-left"></i>
</button>
<button class="browser-btn" id="forwardBtn" title="前进">
<i class="bx bx-chevron-right"></i>
</button>
<button class="browser-btn" id="refreshBrowserBtn" title="刷新">
<i class="bx bx-refresh"></i>
</button>
<button class="browser-btn" id="homeBtn" title="主页">
<i class="bx bx-home"></i>
</button>
<!-- 搜索引擎选择器按钮移至此处 -->
<button class="browser-btn" id="searchEngineSelector" title="搜索引擎">
<div class="search-engine-icon" id="currentSearchEngineIcon">
<img src="https://www.google.com/favicon.ico" alt="Google" width="16" height="16">
</div>
</button>
</div>
<div class="address-bar">
<i class="bx bx-lock-alt" style="color: var(--success-color); margin-right: 8px;"></i>
<input type="text" class="address-input" id="addressInput" placeholder="输入网址或搜索内容">
<button class="browser-btn" id="goBtn" style="border: none; background: none;">
<i class="bx bx-search"></i>
</button>
</div>
<!-- 搜索引擎下拉菜单 -->
<div class="search-engine-dropdown" id="searchEngineDropdown">
<div class="search-engine-item active" data-engine="google" data-url="https://www.google.com/search?q=">
<div class="search-engine-item-icon">
<img src="https://www.google.com/favicon.ico" alt="Google" width="16" height="16">
</div>
<span>Google</span>
</div>
<div class="search-engine-item" data-engine="bing" data-url="https://www.bing.com/search?q=">
<div class="search-engine-item-icon">
<img src="https://www.bing.com/favicon.ico" alt="Bing" width="16" height="16">
</div>
<span>Bing</span>
</div>
<div class="search-engine-item" data-engine="baidu" data-url="https://www.baidu.com/s?wd=">
<div class="search-engine-item-icon">
<img src="https://www.baidu.com/favicon.ico" alt="Baidu" width="16" height="16">
</div>
<span>百度</span>
</div>
<div class="search-engine-item" data-engine="duckduckgo" data-url="https://duckduckgo.com/?q=">
<div class="search-engine-item-icon">
<img src="https://duckduckgo.com/favicon.ico" alt="DuckDuckGo" width="16" height="16">
</div>
<span>DuckDuckGo</span>
</div>
</div>
</div>
<div class="browser-content">
<iframe class="browser-frame" id="browserFrame" src="about:blank"></iframe>
<div class="browser-placeholder" id="browserPlaceholder">
<i class="bx bx-globe" style="font-size: 48px; margin-bottom: 16px;"></i>
<h3 id="browser-welcome">欢迎使用浏览器</h3>
<p id="browser-hint">在地址栏输入网址开始浏览</p>
</div>
</div>
</div>
</div>
</div>
<!-- 图片转换器 -->
<div class="tab-pane" id="image-converter-tab">
<div class="tool-card">
<h2 class="tool-title">图片格式转换</h2>
<div class="form-group">
<label class="form-label">选择图片</label>
<input type="file" id="imageInput" accept="image/*" class="form-input">
</div>
<div id="imagePreviewContainer" style="display: none; margin: 20px 0;">
<h3 style="margin-bottom: 10px;">预览</h3>
<div style="max-width: 100%; overflow: hidden; border-radius: var(--border-radius); border: 1px solid var(--border-light);">
<img id="imagePreview" style="max-width: 100%; display: block;">
</div>
<div style="margin-top: 10px; display: flex; justify-content: space-between; align-items: center;">
<span id="imageInfo">图片信息</span>
</div>
</div>
<div class="form-group">
<label class="form-label">转换为</label>
<select id="formatSelect" class="form-input">
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
<option value="webp">WebP</option>
<option value="bmp">BMP</option>
<option value="gif">GIF</option>
</select>
</div>
<div class="form-group">
<label class="form-label">图片质量 <span id="qualityValue">80%</span></label>
<input type="range" id="qualitySlider" min="1" max="100" value="80" class="form-input">
</div>
<div class="form-group">
<label class="form-label">调整大小</label>
<div style="display: flex; gap: 10px; align-items: center;">
<input type="number" id="widthInput" placeholder="宽度" class="form-input" style="width: 100px;">
<span>×</span>
<input type="number" id="heightInput" placeholder="高度" class="form-input" style="width: 100px;">
<button id="resetSizeBtn" class="btn btn-secondary" style="padding: 8px 12px;">
<i class="bx bx-reset"></i>
重置
</button>
</div>
<div style="margin-top: 10px;">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="maintainAspectRatio" checked>
保持宽高比
</label>
</div>
</div>
<div class="btn-group">
<button id="convertImageBtn" class="btn btn-primary" disabled>
<i class="bx bx-image"></i>
转换图片
</button>
<button id="downloadImageBtn" class="btn btn-secondary" disabled>
<i class="bx bx-download"></i>
下载图片
</button>
</div>
</div>
</div>
<!-- HTML预览 -->
<div class="tab-pane" id="html-preview-tab">
<div class="tool-card">
<h2 class="tool-title">HTML预览</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 500px;">
<div style="border: 1px solid var(--border-light); border-radius: var(--border-radius); overflow: hidden; display: flex; flex-direction: column;">
<div style="padding: 12px; background: var(--bg-secondary); border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between;">
<span style="font-weight: 600;">HTML编辑器</span>
<div>
<button class="btn btn-secondary" id="runHtmlBtn" style="padding: 6px 12px; min-height: 32px;">
<i class="bx bx-play"></i>
运行
</button>
<button class="btn btn-secondary" id="clearHtmlBtn" style="padding: 6px 12px; min-height: 32px; margin-left: 8px;">
<i class="bx bx-trash"></i>
清除
</button>
</div>
</div>
<textarea id="htmlEditor" style="flex: 1; padding: 16px; border: none; resize: none; outline: none; font-family: monospace; font-size: 14px;"><!DOCTYPE html>
<html>
<head>
<title>HTML预览</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
color: #333;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
max-width: 600px;
margin: 0 auto;
}
h1 {
color: #2563eb;
text-align: center;
}
.btn {
background: #2563eb;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
display: block;
margin: 20px auto;
}
.btn:hover {
background: #1d4ed8;
}
</style>
</head>
<body>
<div class="container">
<h1>HTML预览工具</h1>
<p>这是一个HTML在线预览工具,您可以在左侧编辑代码,右侧实时查看效果。</p>
<button class="btn" onclick="alert('Hello World!')">点击测试</button>
</div>
</body>
</html></textarea>
</div>
<div style="border: 1px solid var(--border-light); border-radius: var(--border-radius); overflow: hidden; display: flex; flex-direction: column;">
<div style="padding: 12px; background: var(--bg-secondary); border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between;">
<span style="font-weight: 600;">预览</span>
<div>
<button class="btn btn-secondary" id="copyHtmlBtn" style="padding: 6px 12px; min-height: 32px;">
<i class="bx bx-copy"></i>
复制
</button>
<button class="btn btn-secondary" id="downloadHtmlBtn" style="padding: 6px 12px; min-height: 32px; margin-left: 8px;">
<i class="bx bx-download"></i>
下载
</button>
</div>
</div>
<iframe id="htmlPreview" style="flex: 1; border: none;"></iframe>
</div>
</div>
</div>
</div>
<!-- JSON格式化 -->
<div class="tab-pane" id="json-formatter-tab">
<div class="tool-card">
<h2 class="tool-title">JSON格式化</h2>
<div class="form-group">
<label class="form-label">JSON数据</label>
<textarea class="form-input form-textarea" id="jsonInput" placeholder="在这里粘贴JSON数据...">{"name":"工具箱","version":"1.0.0","tools":["浏览器","图片转换","HTML预览","JSON格式化","颜色选择器","密码生成器","二维码生成","单位转换"],"author":"开发者"}</textarea>
</div>
<div class="btn-group">
<button class="btn btn-primary" id="formatJsonBtn">
<i class="bx bx-code-alt"></i>
格式化
</button>
<button class="btn btn-secondary" id="compressJsonBtn">
<i class="bx bx-compress"></i>
压缩
</button>
<button class="btn btn-secondary" id="clearJsonBtn">
<i class="bx bx-trash"></i>
清除
</button>
</div>
<div id="jsonResult" style="margin-top: 20px; display: none;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h3 style="font-size: 16px; font-weight: 600;">格式化结果</h3>
<button class="btn btn-secondary" id="copyJsonBtn">
<i class="bx bx-copy"></i>
复制
</button>
</div>
<pre id="jsonOutput" style="background: var(--bg-secondary); padding: 16px; border-radius: var(--border-radius); overflow-x: auto; font-family: monospace; font-size: 14px;"></pre>
</div>
</div>
</div>
<!-- 颜色选择器 -->
<div class="tab-pane" id="color-picker-tab">
<div class="tool-card">
<h2 class="tool-title">颜色选择器</h2>
<div class="form-group">
<label class="form-label">选择颜色</label>
<input type="color" class="form-input" id="colorPicker" value="#2563eb" style="height: 60px;">
</div>
<div class="form-group">
<label class="form-label">颜色值</label>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
<div>
<label style="font-size: 12px; color: var(--text-muted);">HEX</label>
<input type="text" class="form-input" id="hexValue" readonly>
</div>
<div>
<label style="font-size: 12px; color: var(--text-muted);">RGB</label>
<input type="text" class="form-input" id="rgbValue" readonly>
</div>
<div>
<label style="font-size: 12px; color: var(--text-muted);">HSL</label>
<input type="text" class="form-input" id="hslValue" readonly>
</div>
</div>
</div>
<div class="btn-group">
<button class="btn btn-primary" id="generatePaletteBtn">
<i class="bx bx-palette"></i>
生成配色方案
</button>
<button class="btn btn-secondary" id="copyColorBtn">
<i class="bx bx-copy"></i>
复制颜色值
</button>
</div>
<div id="colorResult" style="margin-top: 20px; display: none;">
<h3 style="font-size: 16px; font-weight: 600; margin-bottom: 16px;">配色方案</h3>
<div id="colorPalette" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 12px;"></div>
</div>
</div>
</div>
<!-- 密码生成器 -->
<div class="tab-pane" id="password-generator-tab">
<div class="tool-card">
<h2 class="tool-title">密码生成器</h2>
<div style="background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: var(--border-radius); padding: 16px; margin-bottom: 20px; position: relative;">
<span id="passwordText" style="font-family: monospace; font-size: 18px;">点击生成密码</span>
<button id="passwordCopyBtn" style="position: absolute; right: 16px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-secondary); cursor: pointer;">
<i class="bx bx-copy"></i>
</button>
</div>
<div class="form-group">
<label class="form-label">密码长度: <span id="passwordLengthValue">12</span></label>
<input type="range" class="form-input" id="passwordLength" min="4" max="50" value="12">
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="includeUppercase" checked>
<label for="includeUppercase">包含大写字母</label>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="includeLowercase" checked>
<label for="includeLowercase">包含小写字母</label>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="includeNumbers" checked>
<label for="includeNumbers">包含数字</label>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="includeSymbols">
<label for="includeSymbols">包含符号</label>
</div>
</div>
<button class="btn btn-primary" id="generatePasswordBtn">
<i class="bx bx-refresh"></i>
生成密码
</button>
</div>
</div>
<!-- 二维码生成 -->
<div class="tab-pane" id="qrcode-generator-tab">
<div class="tool-card">
<h2 class="tool-title">二维码生成</h2>
<div class="form-group">
<label class="form-label">输入内容</label>
<input type="text" class="form-input" id="qrcodeContent" placeholder="输入文本或网址" value="https://example.com">
</div>
<div class="form-group">
<label class="form-label">二维码大小: <span id="sizeValue">200x200</span></label>
<input type="range" class="form-input" id="qrcodeSize" min="100" max="500" value="200">
</div>
<div class="btn-group">
<button class="btn btn-primary" id="generateQrcodeBtn">
<i class="bx bx-qr-scan"></i>
生成二维码
</button>
<button class="btn btn-secondary" id="clearQrcodeBtn">
<i class="bx bx-trash"></i>
清除
</button>
</div>
<div id="qrcodeResult" style="margin-top: 20px; display: none; text-align: center;">
<div id="qrcodeImage"></div>
<button class="btn btn-secondary" id="downloadQrcodeBtn" style="margin-top: 16px;">
<i class="bx bx-download"></i>
下载二维码
</button>
</div>
</div>
</div>
<!-- 单位转换 -->
<div class="tab-pane" id="unit-converter-tab">
<div class="tool-card">
<h2 class="tool-title">单位转换</h2>
<div class="form-group">
<label class="form-label">转换类型</label>
<select class="form-input" id="unitType">
<option value="length">长度</option>
<option value="weight">重量</option>
<option value="temperature">温度</option>
<option value="area">面积</option>
<option value="volume">体积</option>
</select>
</div>
<div style="display: grid; grid-template-columns: 1fr auto 1fr; gap: 16px; align-items: center;">
<div>
<label class="form-label">从</label>
<select class="form-input" id="fromUnit">
<option value="m">米 (m)</option>
<option value="km">千米 (km)</option>
<option value="cm">厘米 (cm)</option>
<option value="mm">毫米 (mm)</option>
<option value="in">英寸 (in)</option>
<option value="ft">英尺 (ft)</option>
<option value="yd">码 (yd)</option>
<option value="mi">英里 (mi)</option>
</select>
<input type="number" class="form-input" id="fromValue" placeholder="输入数值" value="1" style="margin-top: 8px;">
</div>
<button style="background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer;" id="swapUnits">
<i class="bx bx-transfer-alt"></i>
</button>
<div>
<label class="form-label">到</label>
<select class="form-input" id="toUnit">
<option value="m">米 (m)</option>
<option value="km" selected>千米 (km)</option>
<option value="cm">厘米 (cm)</option>
<option value="mm">毫米 (mm)</option>
<option value="in">英寸 (in)</option>
<option value="ft">英尺 (ft)</option>
<option value="yd">码 (yd)</option>
<option value="mi">英里 (mi)</option>
</select>
<input type="text" class="form-input" id="toValue" placeholder="转换结果" readonly style="margin-top: 8px;">
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
// 国际化配置
const i18n = {
"zh-CN": {
// 品牌信息
"brand-title": "工具箱",
"brand-subtitle": "高效在线工具集",
// 导航标题
"nav-title-main": "常用工具",
"nav-title-dev": "开发工具",
"nav-title-utility": "实用工具",
// 导航项目
"nav-browser": "浏览器",
"nav-image": "图片转换",
"nav-html": "HTML预览",
"nav-json": "JSON格式化",
"nav-color": "颜色选择器",
"nav-password": "密码生成器",
"nav-qrcode": "二维码生成",
"nav-unit": "单位转换",
// 工具提示
"tooltip-browser": "浏览器",
"tooltip-image": "图片转换",
"tooltip-html": "HTML预览",
"tooltip-json": "JSON格式化",
"tooltip-color": "颜色选择器",
"tooltip-password": "密码生成器",
"tooltip-qrcode": "二维码生成",
"tooltip-unit": "单位转换",
// 主题切换
"theme-text": "深色模式",
"theme-text-light": "浅色模式",
// 标签页
"tab-browser": "浏览器",
"tab-image": "图片转换",
"tab-html": "HTML预览",
"tab-json": "JSON格式化",
"tab-color": "颜色选择器",
"tab-password": "密码生成器",
"tab-qrcode": "二维码生成",
"tab-unit": "单位转换",
// 浏览器
"browser-welcome": "欢迎使用浏览器",
"browser-hint": "在地址栏输入网址开始浏览",
// 天气
"weather-loading": "加载中...",
"weather-error": "获取天气失败",
"weather-today": "今天",
"weather-tomorrow": "明天",
"weather-day-after": "后天",
// 图片转换
"image-format": "图片格式转换",
"image-select": "选择图片",
"image-preview": "预览",
"image-info": "图片信息",
"image-convert-to": "转换为",
"image-quality": "图片质量",
"image-resize": "调整大小",
"image-width": "宽度",
"image-height": "高度",
"image-reset": "重置",
"image-maintain-ratio": "保持宽高比",
"image-convert": "转换图片",
"image-download": "下载图片",
"image-error": "图片处理错误",
"image-success": "图片转换成功",
"image-no-file": "请先选择图片",
},
"en-US": {
// Brand info
"brand-title": "Toolbox",
"brand-subtitle": "Efficient online tools",
// Navigation titles
"nav-title-main": "Common Tools",
"nav-title-dev": "Dev Tools",
"nav-title-utility": "Utilities",
// Navigation items
"nav-browser": "Browser",
"nav-image": "Image Converter",
"nav-html": "HTML Preview",
"nav-json": "JSON Formatter",
"nav-color": "Color Picker",
"nav-password": "Password Generator",
"nav-qrcode": "QR Generator",
"nav-unit": "Unit Converter",
// Tooltips
"tooltip-browser": "Browser",
"tooltip-image": "Image Converter",
"tooltip-html": "HTML Preview",
"tooltip-json": "JSON Formatter",
"tooltip-color": "Color Picker",
"tooltip-password": "Password Generator",
"tooltip-qrcode": "QR Generator",
"tooltip-unit": "Unit Converter",
// Theme toggle
"theme-text": "Dark Mode",
"theme-text-light": "Light Mode",
// Tabs
"tab-browser": "Browser",
"tab-image": "Image Converter",
"tab-html": "HTML Preview",
"tab-json": "JSON Formatter",
"tab-color": "Color Picker",
"tab-password": "Password Generator",
"tab-qrcode": "QR Generator",
"tab-unit": "Unit Converter",
// Browser
"browser-welcome": "Welcome to Browser",
"browser-hint": "Enter URL in address bar to start browsing",
// Weather
"weather-loading": "Loading...",
"weather-error": "Failed to get weather",
"weather-today": "Today",
"weather-tomorrow": "Tomorrow",
"weather-day-after": "Day after",
// Image converter
"image-format": "Image Format Converter",
"image-select": "Select Image",
"image-preview": "Preview",
"image-info": "Image Info",
"image-convert-to": "Convert to",
"image-quality": "Image Quality",
"image-resize": "Resize",
"image-width": "Width",
"image-height": "Height",
"image-reset": "Reset",
"image-maintain-ratio": "Maintain Aspect Ratio",
"image-convert": "Convert Image",
"image-download": "Download Image",
"image-error": "Image processing error",
"image-success": "Image converted successfully",
"image-no-file": "Please select an image first",
},
}
// 全局变量
let currentLanguage = "zh-CN"
let currentTab = "browser"
let isMobile = window.innerWidth <= 768
const openTabs = new Map()
// 工具标题映射
const toolTitles = {
browser: { "zh-CN": "浏览器", "en-US": "Browser" },
"image-converter": { "zh-CN": "图片转换", "en-US": "Image Converter" },
"html-preview": { "zh-CN": "HTML预览", "en-US": "HTML Preview" },
"json-formatter": { "zh-CN": "JSON格式化", "en-US": "JSON Formatter" },
"color-picker": { "zh-CN": "颜色选择器", "en-US": "Color Picker" },
"password-generator": { "zh-CN": "密码生成器", "en-US": "Password Generator" },
"qrcode-generator": { "zh-CN": "二维码生成", "en-US": "QR Generator" },
"unit-converter": { "zh-CN": "单位转换", "en-US": "Unit Converter" },
}
// 检测移动端
function checkMobile() {
isMobile = window.innerWidth <= 768
}
window.addEventListener("resize", checkMobile)
// 国际化函数
function t(key) {
return i18n[currentLanguage][key] || key
}
function updateLanguage() {
// 更新所有带有ID的文本元素
Object.keys(i18n[currentLanguage]).forEach((key) => {
const element = document.getElementById(key)
if (element) {
if (element.tagName === "INPUT" && element.type === "text") {
element.placeholder = i18n[currentLanguage][key]
} else {
element.textContent = i18n[currentLanguage][key]
}
}
})
// 更新标签页标题
document.querySelectorAll(".tab").forEach((tab) => {
const tabId = tab.dataset.tab
if (toolTitles[tabId]) {
const titleSpan = tab.querySelector("span")
if (titleSpan) {
titleSpan.textContent = toolTitles[tabId][currentLanguage]
}
}
})
// 更新地址栏占位符
const addressInput = document.getElementById("addressInput")
if (addressInput) {
addressInput.placeholder = currentLanguage === "zh-CN" ? "输入网址或搜索内容" : "Enter URL or search content"
}
}
// DOM元素
const sidebar = document.getElementById("sidebar")
const sidebarToggle = document.getElementById("sidebarToggle")
const sidebarOverlay = document.getElementById("sidebarOverlay")
const themeToggle = document.getElementById("themeToggle")
const navLinks = document.querySelectorAll(".nav-link")
const tabs = document.getElementById("tabs")
const tabPanes = document.querySelectorAll(".tab-pane")
const newTabBtn = document.getElementById("newTabBtn")
const languageBtns = document.querySelectorAll(".language-btn")
// 初始化标签页
openTabs.set("browser", {
id: "browser",
title: toolTitles["browser"][currentLanguage],
element: document.querySelector('.tab[data-tab="browser"]'),
})
// 侧边栏切换功能
sidebarToggle.addEventListener("click", () => {
if (isMobile) {
sidebar.classList.toggle("show")
sidebarOverlay.classList.toggle("show")
} else {
sidebar.classList.toggle("collapsed")
}
})
// 遮罩点击关闭侧边栏
sidebarOverlay.addEventListener("click", () => {
sidebar.classList.remove("show")
sidebarOverlay.classList.remove("show")
})
// 主题切换
themeToggle.addEventListener("click", () => {
document.body.classList.toggle("dark-theme")
const isDark = document.body.classList.contains("dark-theme")
const icon = themeToggle.querySelector("i")
const text = themeToggle.querySelector("span")
if (isDark) {
icon.className = "bx bx-sun"
text.textContent = t("theme-text-light")
} else {
icon.className = "bx bx-moon"
text.textContent = t("theme-text")
}
localStorage.setItem("theme", isDark ? "dark" : "light")
})
// 语言切换
languageBtns.forEach((btn) => {
btn.addEventListener("click", () => {
const lang = btn.dataset.lang
if (lang !== currentLanguage) {
currentLanguage = lang
// 更新按钮状态
languageBtns.forEach((b) => b.classList.remove("active"))
btn.classList.add("active")
// 更新页面语言
updateLanguage()
// 保存语言设置
localStorage.setItem("language", lang)
// 更新天气信息
fetchWeatherData()
showNotification(currentLanguage === "zh-CN" ? "已切换到中文" : "Switched to English", "success")
}
})
})
// 初始化主题
const savedTheme = localStorage.getItem("theme")
if (savedTheme === "dark") {
document.body.classList.add("dark-theme")
themeToggle.querySelector("i").className = "bx bx-sun"
themeToggle.querySelector("span").textContent = t("theme-text-light")
}
// 初始化语言
const savedLanguage = localStorage.getItem("language")
if (savedLanguage && i18n[savedLanguage]) {
currentLanguage = savedLanguage
languageBtns.forEach((btn) => {
btn.classList.toggle("active", btn.dataset.lang === currentLanguage)
})
updateLanguage()
}
// 导航菜单点击
navLinks.forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault()
const tabId = link.dataset.tab
createNewTab(tabId)
// 移动端自动关闭侧边栏
if (isMobile) {
sidebar.classList.remove("show")
sidebarOverlay.classList.remove("show")
}
})
})
// 标签页切换
function switchTab(tabId) {
// 更新导航链接状态
navLinks.forEach((link) => {
link.classList.remove("active")
if (link.dataset.tab === tabId) {
link.classList.add("active")
}
})
// 更新标签页状态
document.querySelectorAll(".tab").forEach((tab) => {
tab.classList.remove("active")
})
const targetTab = openTabs.get(tabId)
if (targetTab && targetTab.element) {
targetTab.element.classList.add("active")
}
// 更新标签页内容
tabPanes.forEach((pane) => {
pane.classList.remove("active")
})
const targetPane = document.getElementById(`${tabId}-tab`)
if (targetPane) {
targetPane.classList.add("active")
}
// 更新当前标签
currentTab = tabId
}
// 关闭标签页
function closeTab(tabId) {
// 不能关闭最后一个标签页
if (openTabs.size <= 1) {
return
}
const tab = openTabs.get(tabId)
if (tab && tab.element) {
// 如果关闭的是当前活动标签页,需要激活另一个标签页
if (tab.element.classList.contains("active")) {
const remainingTabs = Array.from(openTabs.keys()).filter((id) => id !== tabId)
if (remainingTabs.length > 0) {
switchTab(remainingTabs[0])
}
}
// 移除标签��元素
tab.element.remove()
// 从映射中删除
openTabs.delete(tabId)
showNotification(currentLanguage === "zh-CN" ? "已关闭标签页" : "Tab closed", "info")
}
}
// 新建标签页
function createNewTab(tabId) {
// 检查是否已存在该标签页
if (openTabs.has(tabId)) {
switchTab(tabId)
return
}
// 创建新标签页元素
const tab = document.createElement("div")
tab.className = "tab"
tab.dataset.tab = tabId
const titleSpan = document.createElement("span")
titleSpan.textContent = toolTitles[tabId][currentLanguage]
const closeBtn = document.createElement("div")
closeBtn.className = "tab-close"
closeBtn.innerHTML = '<i class="bx bx-x"></i>'
tab.appendChild(titleSpan)
tab.appendChild(closeBtn)
// 插入到新建按钮前
tabs.insertBefore(tab, newTabBtn)
// 添加到标签页映射
openTabs.set(tabId, {
id: tabId,
title: toolTitles[tabId][currentLanguage],
element: tab,
})
// 添加事件监听器
tab.addEventListener("click", (e) => {
if (!e.target.closest(".tab-close")) {
switchTab(tabId)
}
})
closeBtn.addEventListener("click", (e) => {
e.stopPropagation()
closeTab(tabId)
})
// 切换到新标签页
switchTab(tabId)
showNotification(currentLanguage === "zh-CN" ? "已创建新标签页" : "New tab created", "success")
}
// 新建标签按钮
newTabBtn.addEventListener("click", () => {
// 创建下拉菜单
const dropdown = document.createElement("div")
dropdown.style.cssText = `
position: absolute;
top: 100%;
right: 0;
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--border-radius);
box-shadow: var(--shadow-lg);
z-index: 1000;
min-width: 200px;
max-height: 300px;
overflow-y: auto;
`
// 添加菜单项
Object.entries(toolTitles).forEach(([id, titles]) => {
const item = document.createElement("div")
item.style.cssText = `
padding: 12px 16px;
cursor: pointer;
display: flex;
align-items: center;
border-bottom: 1px solid var(--border-light);
transition: all var(--transition-fast);
`
const icon = document.querySelector(`[data-tab="${id}"] .nav-icon`)
const iconClass = icon ? icon.className : "bx bx-file"
item.innerHTML = `
<i class="${iconClass}" style="margin-right: 12px; font-size: 18px;"></i>
${titles[currentLanguage]}
`
item.addEventListener("click", () => {
createNewTab(id)
document.body.removeChild(dropdown)
})
item.addEventListener("mouseover", () => {
item.style.background = "var(--bg-secondary)"
})
item.addEventListener("mouseout", () => {
item.style.background = "transparent"
})
dropdown.appendChild(item)
})
// 定位下拉菜单
const rect = newTabBtn.getBoundingClientRect()
dropdown.style.top = rect.bottom + "px"
dropdown.style.right = window.innerWidth - rect.right + "px"
document.body.appendChild(dropdown)
// 点击其他地方关闭下拉菜单
const closeDropdown = (e) => {
if (!dropdown.contains(e.target) && e.target !== newTabBtn) {
document.body.removeChild(dropdown)
document.removeEventListener("click", closeDropdown)
}
}
setTimeout(() => {
document.addEventListener("click", closeDropdown)
}, 0)
})
// 通知功能
function showNotification(message, type = "info") {
const notification = document.createElement("div")
notification.className = `notification ${type}`
notification.textContent = message
document.body.appendChild(notification)
setTimeout(() => {
notification.classList.add("show")
}, 100)
setTimeout(() => {
notification.classList.remove("show")
setTimeout(() => {
document.body.removeChild(notification)
}, 300)
}, 3000)
}
// 浏览器功能
const addressInput = document.getElementById("addressInput")
const browserFrame = document.getElementById("browserFrame")
const browserPlaceholder = document.getElementById("browserPlaceholder")
const backBtn = document.getElementById("backBtn")
const forwardBtn = document.getElementById("forwardBtn")
const refreshBrowserBtn = document.getElementById("refreshBrowserBtn")
const homeBtn = document.getElementById("homeBtn")
const goBtn = document.getElementById("goBtn")
const searchEngineSelector = document.getElementById("searchEngineSelector")
const searchEngineDropdown = document.getElementById("searchEngineDropdown")
const currentSearchEngineIcon = document.getElementById("currentSearchEngineIcon")
let browserHistory = []
let currentHistoryIndex = -1
let currentSearchEngine = {
name: "google",
url: "https://www.google.com/search?q=",
icon: "https://www.google.com/favicon.ico"
}
// 搜索引擎选择器
searchEngineSelector.addEventListener("click", (e) => {
e.stopPropagation()
searchEngineDropdown.style.display = searchEngineDropdown.style.display === "block" ? "none" : "block"
// 定位下拉菜单
const rect = searchEngineSelector.getBoundingClientRect()
searchEngineDropdown.style.top = rect.bottom + 5 + "px"
searchEngineDropdown.style.left = rect.left + "px"
})
// 点击其他地方关闭搜索引擎下拉菜单
document.addEventListener("click", () => {
searchEngineDropdown.style.display = "none"
})
// 搜索引擎选择
document.querySelectorAll(".search-engine-item").forEach(item => {
item.addEventListener("click", () => {
const engine = item.dataset.engine
const url = item.dataset.url
const icon = item.querySelector("img").src
// 更新当前搜索引擎
currentSearchEngine = { name: engine, url, icon }
// 更新图标
currentSearchEngineIcon.innerHTML = `<img src="${icon}" alt="${engine}" width="16" height="16">`
// 更新选中状态
document.querySelectorAll(".search-engine-item").forEach(el => {
el.classList.toggle("active", el === item)
})
// 保存设置
localStorage.setItem("searchEngine", JSON.stringify(currentSearchEngine))
// 显示通知
showNotification(
currentLanguage === "zh-CN"
? `已切换到${item.querySelector("span").textContent}搜索`
: `Switched to ${item.querySelector("span").textContent} search`,
"success"
)
})
})
// 加载保存的搜索引擎设置
const savedSearchEngine = localStorage.getItem("searchEngine")
if (savedSearchEngine) {
try {
const engine = JSON.parse(savedSearchEngine)
currentSearchEngine = engine
currentSearchEngineIcon.innerHTML = `<img src="${engine.icon}" alt="${engine.name}" width="16" height="16">`
// 更新选中状态
document.querySelectorAll(".search-engine-item").forEach(item => {
item.classList.toggle("active", item.dataset.engine === engine.name)
})
} catch (e) {
console.error("Failed to parse saved search engine", e)
}
}
// 加载页面
function loadPage(url) {
if (!url || url.trim() === "") {
showBrowserPlaceholder()
return
}
// 处理搜索查询
if (!url.includes("://") && !url.includes(".")) {
url = `${currentSearchEngine.url}${encodeURIComponent(url)}`
} else if (!url.includes("://")) {
url = "https://" + url
}
addressInput.value = url
browserFrame.src = url
browserPlaceholder.style.display = "none"
// 添加到历史记录
if (currentHistoryIndex === -1 || browserHistory[currentHistoryIndex] !== url) {
browserHistory = browserHistory.slice(0, currentHistoryIndex + 1)
browserHistory.push(url)
currentHistoryIndex = browserHistory.length - 1
}
updateNavigationButtons()
}
function showBrowserPlaceholder() {
browserPlaceholder.style.display = "flex"
browserFrame.src = "about:blank"
}
function updateNavigationButtons() {
backBtn.disabled = currentHistoryIndex <= 0
forwardBtn.disabled = currentHistoryIndex >= browserHistory.length - 1
}
// 浏览器控制
backBtn.addEventListener("click", () => {
if (currentHistoryIndex > 0) {
currentHistoryIndex--
const url = browserHistory[currentHistoryIndex]
addressInput.value = url
browserFrame.src = url
updateNavigationButtons()
}
})
forwardBtn.addEventListener("click", () => {
if (currentHistoryIndex < browserHistory.length - 1) {
currentHistoryIndex++
const url = browserHistory[currentHistoryIndex]
addressInput.value = url
browserFrame.src = url
updateNavigationButtons()
}
})
refreshBrowserBtn.addEventListener("click", () => {
if (addressInput.value) {
loadPage(addressInput.value)
} else {
browserFrame.src = browserFrame.src
}
})
homeBtn.addEventListener("click", () => {
loadPage("https://go.itab.link/")
})
goBtn.addEventListener("click", () => {
loadPage(addressInput.value)
})
addressInput.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
loadPage(addressInput.value)
}
})
// 初始化浏览器
updateNavigationButtons()
showBrowserPlaceholder()
// HTML预览功能
const htmlEditor = document.getElementById("htmlEditor")
const htmlPreview = document.getElementById("htmlPreview")
const runHtmlBtn = document.getElementById("runHtmlBtn")
const clearHtmlBtn = document.getElementById("clearHtmlBtn")
const copyHtmlBtn = document.getElementById("copyHtmlBtn")
const downloadHtmlBtn = document.getElementById("downloadHtmlBtn")
// 实时预览
function updateHtmlPreview() {
const htmlContent = htmlEditor.value
const blob = new Blob([htmlContent], { type: "text/html" })
const url = URL.createObjectURL(blob)
htmlPreview.src = url
}
runHtmlBtn.addEventListener("click", updateHtmlPreview)
clearHtmlBtn.addEventListener("click", () => {
htmlEditor.value = ""
htmlPreview.src = "about:blank"
showNotification(currentLanguage === "zh-CN" ? "已清除" : "Cleared", "info")
})
copyHtmlBtn.addEventListener("click", () => {
navigator.clipboard.writeText(htmlEditor.value).then(() => {
showNotification(currentLanguage === "zh-CN" ? "已复制到剪贴板" : "Copied to clipboard", "success")
})
})
downloadHtmlBtn.addEventListener("click", () => {
const blob = new Blob([htmlEditor.value], { type: "text/html" })
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = "preview.html"
a.click()
URL.revokeObjectURL(url)
})
// 初始化HTML预览
updateHtmlPreview()
// JSON格式化功能
const jsonInput = document.getElementById("jsonInput")
const formatJsonBtn = document.getElementById("formatJsonBtn")
const compressJsonBtn = document.getElementById("compressJsonBtn")
const clearJsonBtn = document.getElementById("clearJsonBtn")
const copyJsonBtn = document.getElementById("copyJsonBtn")
const jsonResult = document.getElementById("jsonResult")
const jsonOutput = document.getElementById("jsonOutput")
formatJsonBtn.addEventListener("click", () => {
try {
const json = JSON.parse(jsonInput.value)
const formatted = JSON.stringify(json, null, 2)
jsonOutput.textContent = formatted
jsonResult.style.display = "block"
showNotification(currentLanguage === "zh-CN" ? "格式化成功" : "Formatting successful", "success")
} catch (error) {
showNotification(currentLanguage === "zh-CN" ? "JSON格式错误" : "Invalid JSON format", "error")
}
})
compressJsonBtn.addEventListener("click", () => {
try {
const json = JSON.parse(jsonInput.value)
const compressed = JSON.stringify(json)
jsonOutput.textContent = compressed
jsonResult.style.display = "block"
showNotification(currentLanguage === "zh-CN" ? "压缩成功" : "Compression successful", "success")
} catch (error) {
showNotification(currentLanguage === "zh-CN" ? "JSON格式错误" : "Invalid JSON format", "error")
}
})
clearJsonBtn.addEventListener("click", () => {
jsonInput.value = ""
jsonResult.style.display = "none"
showNotification(currentLanguage === "zh-CN" ? "已清除" : "Cleared", "info")
})
copyJsonBtn.addEventListener("click", () => {
navigator.clipboard.writeText(jsonOutput.textContent).then(() => {
showNotification(currentLanguage === "zh-CN" ? "已复制到剪贴板" : "Copied to clipboard", "success")
})
})
// 颜色选择器功能
const colorPicker = document.getElementById("colorPicker")
const hexValue = document.getElementById("hexValue")
const rgbValue = document.getElementById("rgbValue")
const hslValue = document.getElementById("hslValue")
const generatePaletteBtn = document.getElementById("generatePaletteBtn")
const copyColorBtn = document.getElementById("copyColorBtn")
const colorResult = document.getElementById("colorResult")
const colorPalette = document.getElementById("colorPalette")
colorPicker.addEventListener("input", updateColorValues)
function updateColorValues() {
const hex = colorPicker.value
const rgb = hexToRgb(hex)
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
hexValue.value = hex.toUpperCase()
rgbValue.value = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`
hslValue.value = `hsl(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%)`
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? {
r: Number.parseInt(result[1], 16),
g: Number.parseInt(result[2], 16),
b: Number.parseInt(result[3], 16),
}
: null
}
function rgbToHsl(r, g, b) {
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h,
s,
l = (max + min) / 2
if (max === min) {
h = s = 0
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return { h: h * 360, s: s * 100, l: l * 100 }
}
function hslToHex(h, s, l) {
l /= 100
const a = (s * Math.min(l, 1 - l)) / 100
const f = (n) => {
const k = (n + h / 30) % 12
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
return Math.round(255 * color)
.toString(16)
.padStart(2, "0")
}
return `#${f(0)}${f(8)}${f(4)}`
}
generatePaletteBtn.addEventListener("click", () => {
const baseColor = colorPicker.value
const rgb = hexToRgb(baseColor)
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
const palette = []
// 生成不同亮度的颜色
for (let i = 0; i < 5; i++) {
const lightness = 20 + i * 15
const color = hslToHex(hsl.h, hsl.s, lightness)
palette.push({
hex: color,
name: `${currentLanguage === "zh-CN" ? "亮度" : "Lightness"} ${lightness}%`,
})
}
// 生成不同色相的颜色
for (let i = 1; i <= 5; i++) {
const hue = (hsl.h + i * 30) % 360
const color = hslToHex(hue, hsl.s, hsl.l)
palette.push({
hex: color,
name: `${currentLanguage === "zh-CN" ? "色相" : "Hue"} ${Math.round(hue)}°`,
})
}
colorPalette.innerHTML = palette
.map(
(color) => `
<div style="aspect-ratio: 1; background: ${color.hex}; border-radius: var(--border-radius); cursor: pointer; transition: all var(--transition-fast); border: 1px solid var(--border-light); position: relative; overflow: hidden;" onclick="copyColor('${color.hex}')">
<div style="position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.7); color: white; padding: 4px; font-size: 10px; text-align: center; transform: translateY(100%); transition: transform var(--transition-fast);">
<div>${color.hex.toUpperCase()}</div>
<div>${color.name}</div>
</div>
</div>
`,
)
.join("")
colorResult.style.display = "block"
showNotification(currentLanguage === "zh-CN" ? "配色方案已生成" : "Color scheme generated", "success")
})
window.copyColor = (hex) => {
navigator.clipboard.writeText(hex).then(() => {
showNotification(`${currentLanguage === "zh-CN" ? "已复制颜色" : "Color copied"}: ${hex}`, "success")
})
}
copyColorBtn.addEventListener("click", () => {
navigator.clipboard.writeText(hexValue.value).then(() => {
showNotification(currentLanguage === "zh-CN" ? "已复制颜色值" : "Color value copied", "success")
})
})
// 初始化颜色值
updateColorValues()
// 密码生成器功能
const passwordText = document.getElementById("passwordText")
const passwordCopyBtn = document.getElementById("passwordCopyBtn")
const passwordLength = document.getElementById("passwordLength")
const passwordLengthValue = document.getElementById("passwordLengthValue")
const generatePasswordBtn = document.getElementById("generatePasswordBtn")
const includeUppercase = document.getElementById("includeUppercase")
const includeLowercase = document.getElementById("includeLowercase")
const includeNumbers = document.getElementById("includeNumbers")
const includeSymbols = document.getElementById("includeSymbols")
passwordLength.addEventListener("input", () => {
passwordLengthValue.textContent = passwordLength.value
})
generatePasswordBtn.addEventListener("click", generatePassword)
function generatePassword() {
const length = Number.parseInt(passwordLength.value)
let charset = ""
if (includeUppercase.checked) charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
if (includeLowercase.checked) charset += "abcdefghijklmnopqrstuvwxyz"
if (includeNumbers.checked) charset += "0123456789"
if (includeSymbols.checked) charset += "!@#$%^&*()_+-=[]{}|;:,.<>?"
if (charset === "") {
showNotification(
currentLanguage === "zh-CN" ? "请至少选择一种字符类型" : "Please select at least one character type",
"error",
)
return
}
let password = ""
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length))
}
passwordText.textContent = password
showNotification(currentLanguage === "zh-CN" ? "密码已生成" : "Password generated", "success")
}
passwordCopyBtn.addEventListener("click", () => {
if (passwordText.textContent !== "点击生成密码" && passwordText.textContent !== "Click to generate password") {
navigator.clipboard.writeText(passwordText.textContent).then(() => {
showNotification(currentLanguage === "zh-CN" ? "已复制密码" : "Password copied", "success")
})
}
})
// 二维码生成功能
const qrcodeContent = document.getElementById("qrcodeContent")
const qrcodeSize = document.getElementById("qrcodeSize")
const sizeValue = document.getElementById("sizeValue")
const generateQrcodeBtn = document.getElementById("generateQrcodeBtn")
const clearQrcodeBtn = document.getElementById("clearQrcodeBtn")
const qrcodeResult = document.getElementById("qrcodeResult")
const qrcodeImage = document.getElementById("qrcodeImage")
const downloadQrcodeBtn = document.getElementById("downloadQrcodeBtn")
// 大小滑块
qrcodeSize.addEventListener("input", () => {
const size = qrcodeSize.value
sizeValue.textContent = `${size}x${size}`
})
// 生成二维码
generateQrcodeBtn.addEventListener("click", () => {
const content = qrcodeContent.value.trim()
if (!content) {
showNotification(currentLanguage === "zh-CN" ? "请输入内容" : "Please enter content", "error")
return
}
const size = Number.parseInt(qrcodeSize.value)
generateQRCode(content, size)
})
function generateQRCode(text, size) {
// 使用在线API生成二维码
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(text)}`
qrcodeImage.innerHTML = `
<img src="${qrUrl}" alt="QR Code" style="max-width: 100%; border-radius: var(--border-radius);">
`
qrcodeResult.style.display = "block"
showNotification(currentLanguage === "zh-CN" ? "二维码已生成" : "QR code generated", "success")
// 设置下载功能
downloadQrcodeBtn.onclick = () => {
const a = document.createElement("a")
a.href = qrUrl
a.download = "qrcode.png"
a.click()
}
}
clearQrcodeBtn.addEventListener("click", () => {
qrcodeContent.value = "https://example.com"
qrcodeResult.style.display = "none"
showNotification(currentLanguage === "zh-CN" ? "已清除" : "Cleared", "info")
})
// 单位转换器功能
const unitType = document.getElementById("unitType")
const fromUnit = document.getElementById("fromUnit")
const toUnit = document.getElementById("toUnit")
const fromValue = document.getElementById("fromValue")
const toValue = document.getElementById("toValue")
const swapUnits = document.getElementById("swapUnits")
const unitData = {
length: {
m: 1,
km: 0.001,
cm: 100,
mm: 1000,
in: 39.3701,
ft: 3.28084,
yd: 1.09361,
mi: 0.000621371,
},
weight: {
kg: 1,
g: 1000,
lb: 2.20462,
oz: 35.274,
ton: 0.001,
},
temperature: {
c: (val) => val,
f: (val) => (val * 9) / 5 + 32,
k: (val) => val + 273.15,
},
area: {
m2: 1,
km2: 0.000001,
cm2: 10000,
ft2: 10.7639,
in2: 1550,
},
volume: {
l: 1,
ml: 1000,
gal: 0.264172,
qt: 1.05669,
pt: 2.11338,
},
}
const unitOptions = {
length: {
"zh-CN": {
m: "米 (m)",
km: "千米 (km)",
cm: "厘米 (cm)",
mm: "毫米 (mm)",
in: "英寸 (in)",
ft: "英尺 (ft)",
yd: "码 (yd)",
mi: "英里 (mi)",
},
"en-US": {
m: "Meter (m)",
km: "Kilometer (km)",
cm: "Centimeter (cm)",
mm: "Millimeter (mm)",
in: "Inch (in)",
ft: "Foot (ft)",
yd: "Yard (yd)",
mi: "Mile (mi)",
},
},
weight: {
"zh-CN": {
kg: "千克 (kg)",
g: "克 (g)",
lb: "磅 (lb)",
oz: "盎司 (oz)",
ton: "吨 (ton)",
},
"en-US": {
kg: "Kilogram (kg)",
g: "Gram (g)",
lb: "Pound (lb)",
oz: "Ounce (oz)",
ton: "Ton (ton)",
},
},
temperature: {
"zh-CN": {
c: "摄氏度 (°C)",
f: "华氏度 (°F)",
k: "开尔文 (K)",
},
"en-US": {
c: "Celsius (°C)",
f: "Fahrenheit (°F)",
k: "Kelvin (K)",
},
},
area: {
"zh-CN": {
m2: "平方米 (m²)",
km2: "平方千米 (km²)",
cm2: "平方厘米 (cm²)",
ft2: "平方英尺 (ft²)",
in2: "平方英寸 (in²)",
},
"en-US": {
m2: "Square Meter (m²)",
km2: "Square Kilometer (km²)",
cm2: "Square Centimeter (cm²)",
ft2: "Square Foot (ft²)",
in2: "Square Inch (in²)",
},
},
volume: {
"zh-CN": {
l: "升 (L)",
ml: "毫升 (mL)",
gal: "加仑 (gal)",
qt: "夸脱 (qt)",
pt: "品脱 (pt)",
},
"en-US": {
l: "Liter (L)",
ml: "Milliliter (mL)",
gal: "Gallon (gal)",
qt: "Quart (qt)",
pt: "Pint (pt)",
},
},
}
unitType.addEventListener("change", updateUnitOptions)
fromValue.addEventListener("input", convertUnits)
fromUnit.addEventListener("change", convertUnits)
toUnit.addEventListener("change", convertUnits)
function updateUnitOptions() {
const type = unitType.value
const options = unitOptions[type][currentLanguage]
fromUnit.innerHTML = ""
toUnit.innerHTML = ""
Object.entries(options).forEach(([key, value]) => {
const option1 = new Option(value, key)
const option2 = new Option(value, key)
fromUnit.appendChild(option1)
toUnit.appendChild(option2)
})
// 设置默认选择
if (type === "length") {
fromUnit.value = "m"
toUnit.value = "km"
}
convertUnits()
}
function convertUnits() {
const type = unitType.value
const from = fromUnit.value
const to = toUnit.value
const value = Number.parseFloat(fromValue.value) || 0
if (type === "temperature") {
toValue.value = convertTemperature(value, from, to).toFixed(2)
} else {
const data = unitData[type]
const baseValue = value / data[from]
const result = baseValue * data[to]
toValue.value = result.toFixed(6).replace(/\.?0+$/, "")
}
}
function convertTemperature(value, from, to) {
// 先转换为摄氏度
let celsius
switch (from) {
case "c":
celsius = value
break
case "f":
celsius = ((value - 32) * 5) / 9
break
case "k":
celsius = value - 273.15
break
}
// 再转换为目标单位
switch (to) {
case "c":
return celsius
case "f":
return (celsius * 9) / 5 + 32
case "k":
return celsius + 273.15
}
}
swapUnits.addEventListener("click", () => {
const tempUnit = fromUnit.value
const tempValue = fromValue.value
fromUnit.value = toUnit.value
toUnit.value = tempUnit
fromValue.value = toValue.value
convertUnits()
})
// 初始化单位转换器
updateUnitOptions()
// 优化后的天气功能
const weatherTemp = document.getElementById("weatherTemp")
const weatherLocation = document.getElementById("weatherLocation")
const weatherIcon = document.getElementById("weatherIcon")
const weatherDetailsLocation = document.getElementById("weatherDetailsLocation")
const weatherDetailsDate = document.getElementById("weatherDetailsDate")
const weatherForecast = document.getElementById("weatherForecast")
// 天气图标映射 - 优化版
const weatherIcons = {
"00": "bx-sun", // 晴
"01": "bx-cloud", // 多云
"02": "bx-cloud", // 阴
"03": "bx-cloud-drizzle", // 阵雨
"04": "bx-cloud-lightning", // 雷阵雨
"05": "bx-cloud-snow", // 雨夹雪
"06": "bx-cloud-rain", // 小雨
"07": "bx-cloud-rain", // 小雨
"08": "bx-cloud-rain", // 中雨
"09": "bx-cloud-rain", // 大雨
10: "bx-cloud-rain", // 暴雨
11: "bx-cloud-rain", // 大暴雨
12: "bx-cloud-snow", // 小雪
13: "bx-cloud-snow", // 中雪
14: "bx-cloud-snow", // 大雪
15: "bx-cloud-snow", // 暴雪
16: "bx-cloud-drizzle", // 小到中雨
17: "bx-cloud-rain", // 中到大雨
18: "bx-cloud-rain", // 大到暴雨
19: "bx-cloud-snow", // 小到中雪
20: "bx-cloud-snow", // 中到大雪
21: "bx-cloud-snow", // 大到暴雪
22: "bx-cloud-lightning", // 雷阵雨
23: "bx-cloud-lightning", // 雷阵雨
24: "bx-cloud-lightning", // 雷阵雨
25: "bx-cloud-lightning", // 雷阵雨
26: "bx-cloud-lightning", // 雷阵雨
27: "bx-cloud-lightning", // 雷阵雨
28: "bx-cloud-lightning", // 雷阵雨
29: "bx-cloud-lightning", // 雷阵雨
30: "bx-cloud-lightning", // 雷阵雨
31: "bx-cloud-lightning", // 雷阵雨
53: "bx-cloud-fog", // 霾
99: "bx-cloud", // 未知
}
// 获取天气数据 - 优化版
async function fetchWeatherData() {
try {
// 显示加载状态
weatherTemp.textContent = t("weather-loading")
weatherLocation.textContent = t("weather-loading")
weatherIcon.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i>'
// 使用IP定位API获取用户位置
const ipResponse = await fetch("https://api.ipify.org?format=json")
const ipData = await ipResponse.json()
const userIp = ipData.ip
// 使用IP获取位置信息
const geoResponse = await fetch(`https://ipapi.co/${userIp}/json/`)
const geoData = await geoResponse.json()
// 使用开放天气API获取天气数据
const weatherApiKey = "5f7c50e7c5b0e9e0b8c5f5b0e9e0b8c5" // 替换为您的API密钥
const lat = geoData.latitude
const lon = geoData.longitude
const weatherResponse = await fetch(
`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=minutely,hourly&units=metric&appid=${weatherApiKey}`,
)
if (!weatherResponse.ok) {
throw new Error(`Weather API error: ${weatherResponse.status}`)
}
const weatherData = await weatherResponse.json()
// 更新当前天气
const currentWeather = weatherData.current
const temp = `${Math.round(currentWeather.temp)}°C`
const location = `${geoData.city}, ${geoData.country_name}`
// 映射天气图标
const weatherCode = currentWeather.weather[0].id
let iconClass = "bx-cloud"
// 根据OpenWeather的天气代码映射到我们的图标
if (weatherCode >= 200 && weatherCode < 300) {
iconClass = "bx-cloud-lightning" // 雷暴
} else if (weatherCode >= 300 && weatherCode < 400) {
iconClass = "bx-cloud-drizzle" // 毛毛雨
} else if (weatherCode >= 500 && weatherCode < 600) {
iconClass = "bx-cloud-rain" // 雨
} else if (weatherCode >= 600 && weatherCode < 700) {
iconClass = "bx-cloud-snow" // 雪
} else if (weatherCode >= 700 && weatherCode < 800) {
iconClass = "bx-cloud-fog" // 雾
} else if (weatherCode === 800) {
iconClass = "bx-sun" // 晴
} else if (weatherCode > 800) {
iconClass = "bx-cloud" // 多云
}
weatherTemp.textContent = temp
weatherLocation.textContent = location
weatherIcon.innerHTML = `<i class="bx ${iconClass}"></i>`
// 更新详细天气信息
weatherDetailsLocation.textContent = location
const currentDate = new Date()
weatherDetailsDate.textContent = currentDate.toLocaleDateString(currentLanguage, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})
// 生成天气预报
weatherForecast.innerHTML = ""
// 显示未来3天的天气预报
for (let i = 0; i < 3; i++) {
const forecast = weatherData.daily[i]
const date = new Date(forecast.dt * 1000)
const formattedDate = date.toLocaleDateString(currentLanguage, {
month: "short",
day: "numeric",
})
let dayLabel
if (i === 0) {
dayLabel = t("weather-today")
} else if (i === 1) {
dayLabel = t("weather-tomorrow")
} else {
dayLabel = t("weather-day-after")
}
// 映射天气图标
const forecastCode = forecast.weather[0].id
let forecastIconClass = "bx-cloud"
if (forecastCode >= 200 && forecastCode < 300) {
forecastIconClass = "bx-cloud-lightning"
} else if (forecastCode >= 300 && forecastCode < 400) {
forecastIconClass = "bx-cloud-drizzle"
} else if (forecastCode >= 500 && forecastCode < 600) {
forecastIconClass = "bx-cloud-rain"
} else if (forecastCode >= 600 && forecastCode < 700) {
forecastIconClass = "bx-cloud-snow"
} else if (forecastCode >= 700 && forecastCode < 800) {
forecastIconClass = "bx-cloud-fog"
} else if (forecastCode === 800) {
forecastIconClass = "bx-sun"
} else if (forecastCode > 800) {
forecastIconClass = "bx-cloud"
}
const forecastElement = document.createElement("div")
forecastElement.className = "forecast-day"
forecastElement.innerHTML = `
<div class="forecast-date">${formattedDate} (${dayLabel})</div>
<div class="forecast-icon"><i class="bx ${forecastIconClass}"></i></div>
<div class="forecast-temp">${Math.round(forecast.temp.min)}°C - ${Math.round(forecast.temp.max)}°C</div>
<div class="forecast-desc">${forecast.weather[0].description}</div>
`
weatherForecast.appendChild(forecastElement)
}
} catch (error) {
console.error("Error fetching weather data:", error)
// 使用备用API
try {
const backupResponse = await fetch("https://wttr.in/?format=j1")
const backupData = await backupResponse.json()
const currentWeather = backupData.current_condition[0]
const temp = `${currentWeather.temp_C}°C`
const location = backupData.nearest_area[0].areaName[0].value
weatherTemp.textContent = temp
weatherLocation.textContent = location
// 映射天气代码到图标
const weatherCode = Number.parseInt(currentWeather.weatherCode)
let iconClass = "bx-cloud"
if (weatherCode < 200) {
iconClass = "bx-sun" // 晴
} else if (weatherCode < 300) {
iconClass = "bx-cloud" // 多云
} else if (weatherCode < 400) {
iconClass = "bx-cloud-drizzle" // 毛毛雨
} else if (weatherCode < 600) {
iconClass = "bx-cloud-rain" // 雨
} else if (weatherCode < 700) {
iconClass = "bx-cloud-snow" // 雪
} else {
iconClass = "bx-cloud-fog" // 雾
}
weatherIcon.innerHTML = `<i class="bx ${iconClass}"></i>`
// 更新详细天气信息
weatherDetailsLocation.textContent = `${location}, ${backupData.nearest_area[0].country[0].value}`
weatherDetailsDate.textContent = new Date().toLocaleDateString(currentLanguage)
// 生成天气预报
weatherForecast.innerHTML = ""
// 显示未来3天的天气预报
for (let i = 0; i < 3; i++) {
const forecast = backupData.weather[i]
const date = new Date(forecast.date)
const formattedDate = date.toLocaleDateString(currentLanguage, {
month: "short",
day: "numeric",
})
let dayLabel
if (i === 0) {
dayLabel = t("weather-today")
} else if (i === 1) {
dayLabel = t("weather-tomorrow")
} else {
dayLabel = t("weather-day-after")
}
const forecastElement = document.createElement("div")
forecastElement.className = "forecast-day"
forecastElement.innerHTML = `
<div class="forecast-date">${formattedDate} (${dayLabel})</div>
<div class="forecast-icon"><i class="bx bx-cloud"></i></div>
<div class="forecast-temp">${forecast.mintempC}°C - ${forecast.maxtempC}°C</div>
<div class="forecast-desc">${forecast.hourly[4].weatherDesc[0].value}</div>
`
weatherForecast.appendChild(forecastElement)
}
} catch (backupError) {
console.error("Backup weather API failed:", backupError)
weatherTemp.textContent = "--°C"
weatherLocation.textContent = t("weather-error")
weatherIcon.innerHTML = '<i class="bx bx-error"></i>'
}
}
}
// 图片转换功能
const imageInput = document.getElementById("imageInput")
const imagePreview = document.getElementById("imagePreview")
const imagePreviewContainer = document.getElementById("imagePreviewContainer")
const imageInfo = document.getElementById("imageInfo")
const formatSelect = document.getElementById("formatSelect")
const qualitySlider = document.getElementById("qualitySlider")
const qualityValue = document.getElementById("qualityValue")
const widthInput = document.getElementById("widthInput")
const heightInput = document.getElementById("heightInput")
const resetSizeBtn = document.getElementById("resetSizeBtn")
const maintainAspectRatio = document.getElementById("maintainAspectRatio")
const convertImageBtn = document.getElementById("convertImageBtn")
const downloadImageBtn = document.getElementById("downloadImageBtn")
let originalImage = null
let originalWidth = 0
let originalHeight = 0
let aspectRatio = 1
// 质量滑块
qualitySlider.addEventListener("input", () => {
qualityValue.textContent = `${qualitySlider.value}%`
})
// 选择图片
imageInput.addEventListener("change", () => {
const file = imageInput.files[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image()
img.onload = () => {
originalImage = img
originalWidth = img.width
originalHeight = img.height
aspectRatio = originalWidth / originalHeight
// 更新预览
imagePreview.src = e.target.result
imagePreviewContainer.style.display = "block"
// 更新图片信息
imageInfo.textContent = `${file.name} (${originalWidth}x${originalHeight}, ${formatFileSize(file.size)})`
// 更新尺寸输入框
widthInput.value = originalWidth
heightInput.value = originalHeight
// 启用转换按钮
convertImageBtn.disabled = false
}
img.src = e.target.result
}
reader.readAsDataURL(file)
}
})
// 保持宽高比
widthInput.addEventListener("input", () => {
if (maintainAspectRatio.checked && originalImage) {
const newWidth = Number.parseInt(widthInput.value) || 0
heightInput.value = Math.round(newWidth / aspectRatio)
}
})
heightInput.addEventListener("input", () => {
if (maintainAspectRatio.checked && originalImage) {
const newHeight = Number.parseInt(heightInput.value) || 0
widthInput.value = Math.round(newHeight * aspectRatio)
}
})
// 重置尺寸
resetSizeBtn.addEventListener("click", () => {
if (originalImage) {
widthInput.value = originalWidth
heightInput.value = originalHeight
}
})
// 转换图片
convertImageBtn.addEventListener("click", () => {
if (!originalImage) {
showNotification(currentLanguage === "zh-CN" ? "请先选择图片" : "Please select an image first", "error")
return
}
try {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
// 设置画布尺寸
const newWidth = Number.parseInt(widthInput.value) || originalWidth
const newHeight = Number.parseInt(heightInput.value) || originalHeight
canvas.width = newWidth
canvas.height = newHeight
// 绘制图片
ctx.drawImage(originalImage, 0, 0, newWidth, newHeight)
// 获取转换后的图片
const format = formatSelect.value
const quality = Number.parseInt(qualitySlider.value) / 100
const dataURL = canvas.toDataURL(`image/${format}`, quality)
// 更新预览
imagePreview.src = dataURL
// 启用下载按钮
downloadImageBtn.disabled = false
downloadImageBtn.onclick = () => {
const a = document.createElement("a")
a.href = dataURL
a.download = `converted.${format}`
a.click()
}
showNotification(currentLanguage === "zh-CN" ? "图片转换成功" : "Image converted successfully", "success")
} catch (error) {
console.error("Image conversion error:", error)
showNotification(currentLanguage === "zh-CN" ? "图片处理错误" : "Image processing error", "error")
}
})
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + " B"
else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB"
else return (bytes / 1048576).toFixed(2) + " MB"
}
// 页面加载完成后的初始化
document.addEventListener("DOMContentLoaded", () => {
// 检查移动端
checkMobile()
// 初始化语言
updateLanguage()
// 获取天气数据
fetchWeatherData()
// 显示欢迎通知
setTimeout(() => {
showNotification(currentLanguage === "zh-CN" ? "欢迎使用工具箱" : "Welcome to Toolbox", "success")
}, 1000)
})
// 键盘快捷键
document.addEventListener("keydown", (e) => {
// Ctrl/Cmd + B 切换侧边栏
if ((e.ctrlKey || e.metaKey) && e.key === "b") {
e.preventDefault()
sidebarToggle.click()
}
// Ctrl/Cmd + T 新建标签页
if ((e.ctrlKey || e.metaKey) && e.key === "t") {
e.preventDefault()
newTabBtn.click()
}
// Ctrl/Cmd + W 关闭当前标签页
if ((e.ctrlKey || e.metaKey) && e.key === "w") {
e.preventDefault()
if (openTabs.size > 1) {
closeTab(currentTab)
}
}
})
// 定期更新天气数据(每30分钟)
setInterval(fetchWeatherData, 30 * 60 * 1000)
</script>
</body>
</html>
index.html
style.css
index.js