<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TENGYUAN</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<style>
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
}
body {
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background-color: #0f172a;
color: #e2e8f0;
line-height: 1.6;
overflow-x: hidden;
}
.container {
display: flex;
max-width: 1600px;
height: 100vh;
margin: 0 auto;
position: relative;
background-color: #1e293b;
}
/* 左侧边栏样式 */
.left-side {
width: 280px;
border-right: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
flex-direction: column;
transition: all 0.3s ease;
background-color: #1e293b;
overflow: auto;
flex-shrink: 0;
z-index: 5;
}
/* 右侧边栏样式 */
.right-side {
width: 300px;
flex-shrink: 0;
margin-left: auto;
overflow-y: auto;
overflow-x: hidden;
background-color: #1e293b;
display: flex;
flex-direction: column;
border-left: 1px solid rgba(255, 255, 255, 0.05);
max-height: 100vh;
}
/* 主要内容区域样式 */
.main {
flex-grow: 1;
display: flex;
flex-direction: column;
background-color: #0f172a;
min-width: 0;
position: relative;
}
/* Logo样式 */
.logo {
font-family: "PingFang SC", sans-serif;
font-size: 18px;
color: #f8fafc;
font-weight: 600;
text-align: center;
height: 68px;
line-height: 68px;
letter-spacing: 4px;
position: sticky;
top: 0;
background-color: #1e293b;
z-index: 2;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
/* 侧边栏内容区域样式 */
.side-wrapper {
padding: 15px;
}
/* 底部关注区域样式 */
.follow-me {
text-decoration: none;
font-size: 14px;
display: flex;
align-items: center;
margin-top: auto;
overflow: hidden;
color: #94a3b8;
padding: 0 15px;
height: 60px;
flex-shrink: 0;
border-top: 1px solid rgba(255, 255, 255, 0.05);
position: relative;
transition: all 0.3s ease;
}
.follow-me svg {
width: 16px;
height: 16px;
margin-right: 12px;
}
.follow-text {
display: flex;
align-items: center;
transition: 0.3s;
}
.follow-me:hover .follow-text {
transform: translateY(100%);
}
.follow-me:hover .developer {
top: 0;
}
.developer {
position: absolute;
color: #ffffff;
left: 0;
top: -100%;
display: flex;
transition: 0.3s;
padding: 0 15px;
align-items: center;
background-color: rgba(30, 41, 59, 0.9);
width: 100%;
height: 100%;
}
.developer img {
border-radius: 50%;
width: 30px;
height: 30px;
object-fit: cover;
margin-right: 12px;
}
/* 浏览器风格搜索栏样式 */
.search-bar {
height: 60px;
background-color: #1e293b;
z-index: 3;
position: relative;
display: flex;
align-items: center;
padding: 0 30px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
flex-shrink: 0;
}
.search-container {
display: flex;
align-items: center;
width: 100%;
position: relative;
max-width: 600px;
margin: 0 auto;
}
.search-bar input {
height: 44px;
width: 100%;
display: block;
background-color: rgba(255, 255, 255, 0.08);
border: 2px solid rgba(255, 255, 255, 0.1);
padding: 0 50px 0 45px;
color: #ffffff;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
border-radius: 22px;
transition: all 0.3s ease;
font-size: 15px;
position: relative;
}
.search-bar input:focus {
background-color: rgba(255, 255, 255, 0.12);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3);
border-color: rgba(99, 102, 241, 0.5);
}
.search-bar input::placeholder {
color: #64748b;
font-size: 14px;
}
/* 搜索图标 */
.search-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
width: 18px;
height: 18px;
color: #94a3b8;
pointer-events: none;
z-index: 1;
}
/* 搜索按钮 */
.search-btn {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border: none;
border-radius: 18px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: white;
}
.search-btn:hover {
background: linear-gradient(135deg, #5b5bd6, #7c3aed);
transform: translateY(-50%) scale(1.05);
}
.search-btn svg {
width: 14px;
height: 14px;
}
/* 主内容容器样式 */
.main-container {
padding: 20px;
flex-grow: 1;
overflow: auto;
background-color: #0f172a;
position: relative;
}
/* 个人资料区域样式 - 修复滚动显示问题 */
.profile {
position: relative;
height: 40vh;
min-height: 280px;
max-height: 400px;
z-index: 1;
border-radius: 16px;
overflow: hidden;
margin-bottom: 20px;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(129, 140, 248, 0.3);
transform: translateY(0);
opacity: 1;
}
.profile.hidden {
transform: translateY(-20px);
opacity: 0.7;
height: 200px;
min-height: 200px;
}
.profile-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
filter: brightness(0.8);
transition: transform 0.5s ease;
}
.profile:hover .profile-cover {
transform: scale(1.05);
}
.profile-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(15, 23, 42, 0.1), rgba(15, 23, 42, 0.8));
z-index: 1;
}
/* 头像区域样式 */
.profile-avatar {
position: absolute;
display: flex;
align-items: center;
z-index: 2;
bottom: 20px;
left: 30px;
transition: all 0.3s ease;
}
.profile-img {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
border: 4px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.profile-avatar:hover .profile-img {
transform: scale(1.05);
border-color: rgba(129, 140, 248, 0.6);
}
.profile-info {
margin-left: 24px;
}
.profile-name {
font-size: 24px;
color: #ffffff;
font-weight: 600;
font-family: "PingFang SC", sans-serif;
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.profile-title {
font-size: 16px;
color: #cbd5e1;
margin-bottom: 15px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* 社交图标样式 */
.social-icons {
display: flex;
margin-top: 10px;
gap: 10px;
}
.social-icon {
color: #cbd5e1;
font-size: 18px;
transition: all 0.3s ease;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
text-decoration: none;
position: relative;
}
.social-icon:hover {
color: #ffffff;
transform: translateY(-3px);
background-color: rgba(129, 140, 248, 0.6);
box-shadow: 0 5px 15px rgba(129, 140, 248, 0.3);
}
.social-icon i {
font-size: 16px;
line-height: 1;
}
/* 时间线布局 */
.timeline {
display: flex;
padding-top: 20px;
position: relative;
z-index: 2;
}
.timeline-left {
width: 100%;
flex-shrink: 0;
}
/* 通用卡片样式 */
.box {
background-color: rgba(30, 41, 59, 0.8);
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
margin-bottom: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.box:hover {
transform: translateY(-5px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
border-color: rgba(255, 255, 255, 0.2);
}
/* 账户按钮样式 */
.account-button {
border: 0;
background: transparent;
color: #94a3b8;
padding: 8px;
cursor: pointer;
position: relative;
border-radius: 50%;
transition: all 0.3s ease;
}
.account-button:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.account-button svg {
width: 20px;
height: 20px;
}
.account-button:not(.right-side-button) + .account-button:before {
position: absolute;
right: 5px;
top: 5px;
background-color: #6366f1;
width: 8px;
height: 8px;
border-radius: 50%;
content: "";
border: 2px solid #1e293b;
}
.account-profile {
width: 32px;
height: 32px;
border-radius: 50%;
margin: 0 10px;
object-fit: cover;
border: 2px solid rgba(255, 255, 255, 0.1);
}
.account-user {
display: inline-flex;
align-items: center;
color: #94a3b8;
font-weight: 500;
font-size: 14px;
transition: all 0.3s ease;
}
.account-user:hover {
color: #ffffff;
}
.account-user span {
font-size: 12px;
font-weight: normal;
margin-left: 5px;
}
/* 账户区域样式 */
.account {
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
background-color: rgba(30, 41, 59, 0.95);
backdrop-filter: blur(10px);
z-index: 10;
flex-shrink: 0;
padding: 0 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
/* 遮罩层样式 */
.overlay {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background-color: rgba(15, 23, 42, 0.8);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: 0.3s;
z-index: 9;
}
/* 右侧边栏按钮样式 */
.right-side-button {
position: absolute;
right: 0;
top: 0;
height: 100%;
border: 0;
width: 52px;
background-color: rgba(30, 41, 59, 0.8);
border-left: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
display: none;
cursor: pointer;
transition: all 0.3s ease;
}
.right-side-button:hover {
background-color: rgba(30, 41, 59, 0.9);
}
.right-side-button:before {
content: "";
width: 10px;
height: 10px;
border-radius: 50%;
position: absolute;
background-color: #6366f1;
border: 2px solid #1e293b;
top: 13px;
right: 12px;
}
.right-side-button svg {
width: 22px;
}
/* 左侧边栏按钮样式 */
.left-side-button {
display: none;
}
/* ================ 终端相关样式 ================ */
:root {
--editor-bg: #1e293b;
--editor-secondary-bg: #334155;
--line-color: #64748b;
--text-color: #e2e8f0;
--keyword-color: #7dd3fc;
--string-color: #fca5a5;
--comment-color: #86efac;
--number-color: #b5cea8;
--function-color: #dcdcaa;
--font-family: 'JetBrains Mono', 'Consolas', 'Monaco', monospace;
--header-bg: #1e293b;
--window-btn-close: #ff5f56;
--window-btn-min: #ffbd2e;
--window-btn-max: #27c93f;
--ai-output-color: #67e8f9;
--user-input-color: #fde047;
--video-bg: #000000;
--accent-color: #818cf8;
--accent-hover: #a5b4fc;
--box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
--border-radius: 8px;
--calendar-today-bg: rgba(129, 140, 248, 0.3);
--calendar-selected-bg: var(--accent-color);
--weather-temp-high: #ff7e67;
--weather-temp-low: #70c1ff;
--birthday-color: #f472b6;
--birthday-bg: rgba(244, 114, 182, 0.15);
--success-color: #4caf50;
--warning-color: #ff9800;
--error-color: #f44336;
--tool-header-height: 40px;
--tool-padding: 16px;
}
/* 终端编辑器样式 */
.editor {
width: 100%;
background: var(--editor-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
transition: all 0.3s;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
height: 600px;
margin-bottom: 20px;
}
.editor-header {
background: var(--header-bg);
padding: 12px;
border-radius: var(--border-radius) var(--border-radius) 0 0;
display: flex;
align-items: center;
gap: 8px;
transition: background-color 0.3s;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.window-btn {
width: 12px;
height: 12px;
border-radius: 50%;
transition: all 0.2s;
position: relative;
cursor: pointer;
}
.close { background: var(--window-btn-close); }
.minimize { background: var(--window-btn-min); }
.maximize { background: var(--window-btn-max); }
.title-bar {
color: var(--line-color);
margin-left: 20px;
font-size: 0.9em;
transition: color 0.3s;
}
.editor-content {
padding: 20px;
counter-reset: line;
font-size: 14px;
overflow-x: auto;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
flex: 1;
font-family: var(--font-family);
}
.editor-content::-webkit-scrollbar {
display: none;
}
.line {
display: flex;
padding: 2px 0;
position: relative;
min-height: 1.6em;
}
.line::before {
counter-increment: line;
content: counter(line);
color: var(--line-color);
width: 2em;
text-align: right;
padding-right: 1em;
position: absolute;
transition: color 0.3s;
opacity: 0.7;
}
.line-content {
padding-left: 3em;
width: 100%;
}
.indent { margin-left: 2em; }
.indent-2 { margin-left: 4em; }
.keyword { color: var(--keyword-color); }
.string { color: var(--string-color); }
.comment { color: var(--comment-color); }
.number { color: var(--number-color); }
.function { color: var(--function-color); }
.ai-output { color: var(--ai-output-color); }
.user-input { color: var(--user-input-color); }
.input-line {
display: flex;
align-items: center;
background: var(--editor-secondary-bg);
padding: 8px 10px;
border-radius: 4px;
transition: all 0.3s;
margin: 0 20px 20px;
position: sticky;
bottom: 0;
z-index: 10;
}
.input-line::before {
content: ">";
color: var(--accent-color);
width: 2em;
text-align: right;
padding-right: 1em;
font-weight: bold;
}
.input-line input {
flex: 1;
background: transparent;
border: none;
color: var(--text-color);
font-family: var(--font-family);
font-size: 14px;
outline: none;
padding: 2px 0;
}
/* 右侧边栏卡片样式 */
.sidebar-card {
background-color: rgba(30, 41, 59, 0.8);
border-radius: 8px;
margin-bottom: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
flex-shrink: 0;
}
.sidebar-card-header {
background: var(--header-bg);
padding: 8px 12px;
border-radius: var(--border-radius) var(--border-radius) 0 0;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar-card-title {
color: var(--text-color);
font-size: 0.8em;
flex: 1;
font-weight: 600;
}
.sidebar-card-content {
padding: 8px;
max-height: 300px;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.sidebar-card-content::-webkit-scrollbar {
display: none;
}
/* 命令项样式 */
.command-item {
padding: 6px 8px;
margin-bottom: 4px;
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
transition: all 0.2s;
cursor: pointer;
}
.command-item:hover {
background-color: rgba(255, 255, 255, 0.1);
transform: translateX(2px);
}
.command-title {
font-weight: 500;
color: var(--text-color);
margin-bottom: 2px;
font-size: 0.8em;
}
.command-description {
font-size: 0.7em;
color: var(--line-color);
line-height: 1.3;
}
/* 个人信息卡片样式 */
.profile-card {
background-color: rgba(30, 41, 59, 0.8);
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
text-align: center;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.profile-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.profile-card-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin: 0 auto 15px;
object-fit: cover;
border: 3px solid rgba(129, 140, 248, 0.3);
transition: all 0.3s ease;
}
.profile-card-avatar:hover {
border-color: rgba(129, 140, 248, 0.6);
transform: scale(1.05);
}
.profile-card-name {
font-size: 18px;
font-weight: 600;
margin-bottom: 5px;
color: #ffffff;
}
.profile-card-title {
font-size: 14px;
color: #94a3b8;
margin-bottom: 15px;
}
.profile-stats {
display: flex;
justify-content: space-around;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-item {
text-align: center;
transition: all 0.3s ease;
}
.stat-item:hover {
transform: translateY(-2px);
}
.stat-number {
font-size: 16px;
font-weight: 600;
color: #ffffff;
display: block;
}
.stat-label {
font-size: 12px;
color: #94a3b8;
margin-top: 2px;
}
/* 天气组件样式 - 日历风格简约设计 */
.weather-widget {
background-color: rgba(30, 41, 59, 0.8);
border-radius: 12px;
padding: 16px;
margin-bottom: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.weather-widget:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.weather-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.weather-location {
font-size: 14px;
font-weight: 600;
color: #ffffff;
display: flex;
align-items: center;
gap: 6px;
}
.weather-location svg {
width: 14px;
height: 14px;
color: #94a3b8;
}
/* 隐藏IP地址显示 */
.weather-ip {
display: none;
}
.weather-main {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
padding: 8px 0;
}
.weather-temp-section {
display: flex;
align-items: center;
gap: 10px;
}
.weather-icon {
width: 32px;
height: 32px;
color: #94a3b8;
opacity: 0.8;
}
.weather-temp-info {
display: flex;
flex-direction: column;
}
.weather-temp {
font-size: 20px;
font-weight: 600;
color: #ffffff;
line-height: 1;
}
.weather-desc {
font-size: 11px;
color: #94a3b8;
margin-top: 2px;
}
.weather-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
font-size: 11px;
}
.weather-detail {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 6px;
background: rgba(255, 255, 255, 0.03);
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.03);
transition: all 0.2s ease;
}
.weather-detail:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.05);
}
.weather-detail-label {
color: #94a3b8;
font-weight: 500;
}
.weather-detail-value {
color: #ffffff;
font-weight: 500;
}
.weather-loading {
text-align: center;
color: #94a3b8;
font-size: 12px;
padding: 20px 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.weather-loading-spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(148, 163, 184, 0.2);
border-top: 2px solid #94a3b8;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.weather-error {
text-align: center;
color: #f87171;
font-size: 12px;
padding: 16px;
background: rgba(248, 113, 113, 0.05);
border-radius: 6px;
border: 1px solid rgba(248, 113, 113, 0.1);
}
.weather-tip {
margin-top: 8px;
padding: 6px 8px;
background: rgba(148, 163, 184, 0.05);
border-radius: 6px;
font-size: 10px;
color: #94a3b8;
line-height: 1.3;
border: 1px solid rgba(148, 163, 184, 0.1);
}
/* 日历组件样式 */
.calendar-widget {
background-color: rgba(30, 41, 59, 0.8);
border-radius: 12px;
padding: 16px;
margin-bottom: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.calendar-widget:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.calendar-title {
font-size: 16px;
font-weight: 600;
color: #ffffff;
}
.calendar-nav {
display: flex;
gap: 5px;
}
.calendar-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #94a3b8;
width: 28px;
height: 28px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.calendar-btn:hover {
background: var(--accent-color);
color: white;
transform: scale(1.05);
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
margin-bottom: 10px;
}
.calendar-weekday {
text-align: center;
font-size: 12px;
color: #94a3b8;
padding: 8px 0;
font-weight: 600;
}
.calendar-day {
text-align: center;
padding: 8px 0;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
font-size: 12px;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 32px;
font-weight: 500;
}
.calendar-day:hover {
background: rgba(255, 255, 255, 0.1);
transform: scale(1.05);
}
.calendar-day.today {
background: var(--calendar-today-bg);
font-weight: bold;
color: #ffffff;
box-shadow: 0 2px 8px rgba(129, 140, 248, 0.3);
}
.calendar-day.selected {
background: var(--calendar-selected-bg);
color: white;
font-weight: bold;
}
.calendar-day.other-month {
color: #64748b;
opacity: 0.5;
}
/* 快捷操作按钮 */
.quick-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin-top: 10px;
}
.quick-action-btn {
background: var(--accent-color);
color: white;
border: none;
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
font-size: 10px;
transition: all 0.2s;
text-align: center;
font-weight: 500;
}
.quick-action-btn:hover {
background: var(--accent-hover);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(129, 140, 248, 0.3);
}
/* ================ 统一的工具样式 ================ */
/* 通用工具容器样式 */
.tool-container {
background: var(--editor-bg);
border-radius: var(--border-radius);
margin: 15px 0;
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
}
/* 通用工具头部样式 */
.tool-header {
background: var(--header-bg);
padding: 12px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.tool-title {
color: var(--text-color);
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.tool-title svg {
width: 16px;
height: 16px;
}
.tool-close {
background: none;
border: none;
color: var(--line-color);
cursor: pointer;
transition: color 0.2s;
padding: 4px;
border-radius: 4px;
}
.tool-close:hover {
color: var(--text-color);
background: rgba(255, 255, 255, 0.1);
}
.tool-close svg {
width: 16px;
height: 16px;
}
/* 通用工具内容样式 */
.tool-content {
padding: 20px;
}
/* 通用输入区域样式 */
.tool-input-container {
position: relative;
margin-bottom: 20px;
}
.tool-input {
width: 100%;
padding: 12px 50px 12px 16px;
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
background: var(--editor-secondary-bg);
color: var(--text-color);
font-size: 14px;
outline: none;
transition: all 0.3s;
}
.tool-input:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.2);
}
.tool-input::placeholder {
color: var(--line-color);
}
/* 通用按钮样式 */
.tool-btn {
background: var(--accent-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
font-weight: 500;
}
.tool-btn:hover {
background: var(--accent-hover);
transform: translateY(-1px);
}
.tool-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.tool-btn svg {
width: 14px;
height: 14px;
}
.tool-btn-primary {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
padding: 6px 12px;
font-size: 11px;
}
/* 通用预览区域样式 */
.tool-preview-container {
position: relative;
width: 100%;
height: 200px;
border: 2px dashed rgba(255, 255, 255, 0.2);
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.tool-preview {
max-width: 100%;
max-height: 100%;
object-fit: contain;
display: none;
}
.tool-preview.active {
display: block;
}
.tool-placeholder {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--line-color);
text-align: center;
padding: 20px;
transition: all 0.3s;
}
.tool-placeholder svg {
width: 48px;
height: 48px;
margin-bottom: 12px;
opacity: 0.5;
}
/* 通用操作按钮区域 */
.tool-actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
/* 通用结果区域样式 */
.tool-result {
margin-top: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* 通用加载动画 */
.tool-loading {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
flex-direction: column;
gap: 10px;
}
.tool-loading-spinner {
display: flex;
justify-content: center;
margin: 15px 0;
}
.tool-loading-spinner div {
width: 8px;
height: 8px;
margin: 2px;
background: var(--accent-color);
border-radius: 50%;
animation: bouncing 0.6s infinite alternate;
}
@keyframes bouncing {
to {
transform: translateY(-8px);
}
}
/* 通用信息展示样式 */
.tool-info {
background: rgba(129, 140, 248, 0.1);
border: 1px solid rgba(129, 140, 248, 0.3);
border-radius: 6px;
padding: 12px;
margin: 10px 0;
color: var(--text-color);
font-size: 13px;
}
.tool-success {
background: rgba(74, 222, 128, 0.1);
border-color: rgba(74, 222, 128, 0.3);
color: #4ade80;
}
.tool-error {
background: rgba(248, 113, 113, 0.1);
border-color: rgba(248, 113, 113, 0.3);
color: #f87171;
}
/* 视频播放器样式 */
.video-container {
background: var(--video-bg);
border-radius: var(--border-radius);
margin: 15px 0;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.video-player {
position: relative;
width: 100%;
height: 300px;
background: #000;
}
.video-player video, .video-player iframe {
width: 100%;
height: 100%;
object-fit: contain;
border: none;
}
.video-controls {
background: var(--editor-secondary-bg);
padding: 10px;
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.video-button {
background: var(--accent-color);
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.video-button:hover {
background: var(--accent-hover);
}
.video-button svg {
width: 14px;
height: 14px;
}
/* 搜索结果样式 */
.search-results {
background: var(--editor-bg);
border-radius: var(--border-radius);
margin: 15px 0;
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.search-results-header {
background: var(--header-bg);
padding: 12px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.search-results-title {
color: var(--text-color);
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.search-results-content {
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.search-result-item {
padding: 12px;
margin-bottom: 10px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
cursor: pointer;
}
.search-result-item:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-1px);
}
.search-result-title {
color: var(--accent-color);
font-weight: 600;
margin-bottom: 5px;
text-decoration: none;
}
.search-result-title:hover {
text-decoration: underline;
}
.search-result-url {
color: #4ade80;
font-size: 12px;
margin-bottom: 5px;
}
.search-result-description {
color: var(--line-color);
font-size: 13px;
line-height: 1.4;
}
/* 响应式设计 - 左侧边栏 */
@media screen and (max-width: 930px) {
.left-side.active {
z-index: 10;
}
.left-side.active > *:not(.logo) {
opacity: 1;
transition: 0.3s 0.2s;
}
.left-side.active .left-side-button svg:first-child {
opacity: 0;
}
.left-side.active .left-side-button svg:last-child {
transform: translate(-50%, -50%);
opacity: 1;
}
.left-side:not(.active) {
width: 56px;
overflow: hidden;
}
.left-side:not(.active) > *:not(.logo):not(.left-side-button) {
opacity: 0;
}
.left-side:not(.active) .logo {
writing-mode: vertical-lr;
transform: rotate(180deg);
transform-origin: bottom;
display: flex;
align-items: center;
margin-top: -10px;
}
.left-side-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
height: 60px;
background-color: rgba(30, 41, 59, 0.5);
border: 0;
padding: 0;
line-height: 0;
color: #ffffff;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.left-side-button:hover {
background-color: rgba(30, 41, 59, 0.7);
}
.left-side-button svg {
transition: 0.2s;
width: 24px;
}
.left-side-button svg:last-child {
position: absolute;
left: 50%;
transform: translate(100%, -50%);
top: 50%;
opacity: 0;
}
}
/* 响应式设计 - 右侧边栏 */
@media screen and (max-width: 1210px) {
.right-side {
position: fixed;
right: 0;
top: 0;
transition: 0.3s;
height: 100%;
transform: translateX(300px);
z-index: 10;
}
.right-side.active {
transform: translatex(0);
}
.overlay.active {
z-index: 8;
opacity: 1;
visibility: visible;
pointer-events: all;
}
.right-side-button {
display: block;
}
}
/* 响应式设计 - 个人资料区域 */
@media screen and (max-width: 768px) {
.profile {
height: 280px;
min-height: 280px;
}
.profile.hidden {
height: 180px;
min-height: 180px;
}
.profile-avatar {
flex-direction: column;
align-items: center;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.profile-img {
width: 100px;
height: 100px;
}
.profile-info {
margin-left: 0;
margin-top: 15px;
}
.social-icons {
justify-content: center;
}
.weather-main {
flex-direction: column;
align-items: center;
gap: 12px;
}
.weather-details {
grid-template-columns: 1fr;
}
.search-container {
max-width: none;
}
}
/* 响应式设计 - 时间线布局 */
@media screen and (max-width: 768px) {
.timeline {
flex-wrap: wrap;
flex-direction: column-reverse;
}
.timeline-left {
width: 100%;
}
.main-container {
padding: 15px;
}
.editor {
height: 500px;
}
}
/* 响应式设计 - 小屏幕优化 */
@media screen and (max-width: 480px) {
.left-side {
width: 260px;
}
.right-side {
width: 280px;
}
.profile {
height: 250px;
min-height: 250px;
}
.profile.hidden {
height: 160px;
min-height: 160px;
}
.weather-widget {
padding: 16px;
}
.weather-temp {
font-size: 24px;
}
.calendar-widget {
padding: 12px;
}
.profile-card {
padding: 16px;
}
}
/* 隐藏所有滚动条 */
::-webkit-scrollbar {
display: none;
}
* {
scrollbar-width: none;
-ms-overflow-style: none;
}
/* 平滑滚动 */
html {
scroll-behavior: smooth;
}
/* 性能优化 */
.profile, .weather-widget, .calendar-widget, .profile-card {
will-change: transform;
}
/* 无障碍优化 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
.profile, .weather-widget, .calendar-widget, .profile-card {
border-width: 2px;
}
}
</style>
</head>
<body>
<!-- 主容器 - 使用Alpine.js管理状态 -->
<div class="container" x-data="{ rightSide: false, leftSide: false }">
<!-- 左侧边栏 - 导航菜单 + 个人信息 + 天气 + 日历 -->
<div class="left-side" :class="{'active' : leftSide}">
<!-- 左侧边栏切换按钮 - 移动端显示 -->
<div class="left-side-button" @click="leftSide = !leftSide">
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<svg stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
</div>
<!-- Logo区域 -->
<div class="logo">TENGYUAN</div>
<!-- 个人信息卡片 -->
<div class="side-wrapper">
<div class="profile-card">
<img src="https://a.520gexing.com/uploads/allimg/2019032616/3mltnpfys55.jpg" alt="头像" class="profile-card-avatar">
<div class="profile-card-name">藤原</div>
<div class="profile-card-title">职场牛马!!!</div>
<div class="profile-stats">
<div class="stat-item">
<div class="stat-number">128</div>
<div class="stat-label">文章</div>
</div>
<div class="stat-item">
<div class="stat-number">1.2K</div>
<div class="stat-label">访问</div>
</div>
<div class="stat-item">
<div class="stat-number">256</div>
<div class="stat-label">点赞</div>
</div>
</div>
</div>
</div>
<!-- 天气组件 - 日历风格简约设计 -->
<div class="side-wrapper">
<div class="weather-widget" id="weatherWidget">
<div class="weather-loading">
<div class="weather-loading-spinner"></div>
正在加载天气信息...
</div>
</div>
</div>
<!-- 日历组件 -->
<div class="side-wrapper">
<div class="calendar-widget" id="calendarWidget">
<div class="calendar-header">
<div class="calendar-title" id="calendarTitle">2024年12月</div>
<div class="calendar-nav">
<button class="calendar-btn" id="prevMonth" aria-label="上个月">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<button class="calendar-btn" id="nextMonth" aria-label="下个月">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div>
</div>
<div class="calendar-grid" id="calendarGrid">
<!-- 星期标题 -->
<div class="calendar-weekday">日</div>
<div class="calendar-weekday">一</div>
<div class="calendar-weekday">二</div>
<div class="calendar-weekday">三</div>
<div class="calendar-weekday">四</div>
<div class="calendar-weekday">五</div>
<div class="calendar-weekday">六</div>
<!-- 日期将通过JavaScript动态生成 -->
</div>
</div>
</div>
<!-- 关注我区域 -->
<a href="#" class="follow-me" target="_blank">
<span class="follow-text">
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
</svg>
关注我
</span>
<span class="developer">
<img src="https://pbs.twimg.com/profile_images/1253782473953157124/x56UURmt_400x400.jpg" alt="" class="account-profile">
开发者信息
</span>
</a>
</div>
<!-- 主内容区域 -->
<div class="main">
<!-- 浏览器风格搜索栏 -->
<div class="search-bar">
<div class="search-container">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input type="text" placeholder="搜索文章、标签或作者..." id="mainSearchInput" />
<button class="search-btn" id="searchButton" aria-label="搜索">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
</div>
<button class="right-side-button" @click="rightSide = !rightSide" aria-label="打开右侧面板">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
</button>
</div>
<!-- 主内容容器 -->
<div class="main-container" id="mainContainer">
<!-- 个人资料区域 -->
<div class="profile" id="profileSection">
<img src="https://api.yilx.net/img/pc" alt="" class="profile-cover">
<div class="profile-overlay"></div>
<div class="profile-avatar" id="profileAvatar">
<img src="https://a.520gexing.com/uploads/allimg/2019032616/3mltnpfys55.jpg" alt="" class="profile-img">
<div class="profile-info">
<div class="profile-name" id="profileName">TENG YUAN</div>
<div class="profile-title">前端开发者 & 数字创意者</div>
<div class="social-icons" id="socialIcons">
<a href="tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=2083737075" class="social-icon" title="QQ" aria-label="QQ">
<i class="fab fa-qq"></i>
</a>
<a href="https://www.zhihu.com/" class="social-icon" title="知乎" aria-label="知乎" target="_blank">
<i class="fab fa-zhihu"></i>
</a>
<a href="https://github.com/" class="social-icon" title="GitHub" aria-label="GitHub" target="_blank">
<i class="fab fa-github"></i>
</a>
<a href="mailto:2083737075@qq.com" class="social-icon" title="邮箱" aria-label="邮箱">
<i class="fas fa-envelope"></i>
</a>
<a href="weixin://" class="social-icon" title="微信" aria-label="微信">
<i class="fab fa-weixin"></i>
</a>
</div>
</div>
</div>
</div>
<!-- 时间线布局 -->
<div class="timeline">
<!-- 左侧时间线 - 终端 -->
<div class="timeline-left">
<!-- 终端编辑器 -->
<div class="editor" id="mainEditor">
<div class="editor-header">
<span class="window-btn close"></span>
<span class="window-btn minimize"></span>
<span class="window-btn maximize"></span>
<span class="title-bar">terminal.js - 藤原的个人终端</span>
</div>
<div class="editor-content" id="editorContent">
<div class="line"><div class="line-content"><span class="comment">// 个人信息配置</span></div></div>
<div class="line"><div class="line-content"><span class="keyword">const</span> <span class="function">profile</span> = {</div></div>
<div class="line"><div class="line-content indent"><span class="keyword">name</span>: <span class="string">"藤原"</span>,</div></div>
<div class="line"><div class="line-content indent"><span class="keyword">title</span>: <span class="string">"职场牛马!!!"</span>,</div></div>
<div class="line"><div class="line-content indent"><span class="keyword">contact</span>: {</div></div>
<div class="line"><div class="line-content indent-2"><span class="keyword">email</span>: <span class="string"><a href="mailto:2083737075@qq.com">"2083737075@qq.com"</a></span>,</div></div>
<div class="line"><div class="line-content indent-2"><span class="keyword">website</span>: <span class="string"><a href="http://tengyuan.icu" target="_blank">"TengYuan.icu"</a></span>,</div></div>
<div class="line"><div class="line-content indent-2"><span class="keyword">FileCodeBox</span>: <span class="string"><a href="http://wp.tengyuan.icu/" target="_blank">"wp.tengyuan.icu"</a></span>,</div></div>
<div class="line"><div class="line-content indent">},</div></div>
<div class="line"><div class="line-content indent"><span class="keyword">links</span>: {</div></div>
<div class="line"><div class="line-content indent-2"><span class="keyword">travel blog</span>: <span class="string"><a href="http://blog.tengyuan.icu/" target="_blank">"blog.tengyuan.icu"</a></span>,</div></div>
<div class="line"><div class="line-content indent-2"><span class="keyword">birthday</span>: <span class="string"><a href="http://sr.0814.cn" target="_blank">"2001/11/01"</a></span>,</div></div>
<div class="line"><div class="line-content indent">},</div></div>
<div class="line"><div class="line-content indent"><span class="comment">// 座右铭</span></div></div>
<div class="line"><div class="line-content indent"><span class="keyword">motto</span>: <span class="string">"以清简代码,筑玖维数字宇宙。"</span>,</div></div>
<div class="line"><div class="line-content indent"><span class="keyword">copyright</span>: <span class="string">"2017-<span id="year"></span> 藤原"</span>,</div></div>
<div class="line"><div class="line-content">};</div></div>
<div class="line"><div class="line-content"><span class="comment">// 终端交互</span></div></div>
<div class="line"><div class="line-content"><span class="function">console</span>.<span class="function">log</span>(<span class="string">"欢迎访问藤原的个人终端"</span>);</div></div>
<div class="line"><div class="line-content"><span class="function">console</span>.<span class="function">log</span>(<span class="string">"输入 'help' 获取可用命令"</span>);</div></div>
</div>
<!-- 输入行 -->
<div class="input-line">
<input type="text" id="userInput" placeholder="输入命令..." autocomplete="off">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧边栏 -->
<div class="right-side" :class="{ 'active': rightSide }">
<!-- 账户控制区域 -->
<div class="account">
<button class="account-button" aria-label="邮件">
<svg stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<path d="M22 6l-10 7L2 6" />
</svg>
</button>
<button class="account-button" aria-label="通知">
<svg stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
</button>
<span class="account-user">TENG YUAN
<img src="https://images.genius.com/2326b69829d58232a2521f09333da1b3.1000x1000x1.jpg" alt="" class="account-profile">
<span>▼</span>
</span>
</div>
<!-- 命令面板 -->
<div class="sidebar-card">
<div class="sidebar-card-header">
<span class="sidebar-card-title">命令面板</span>
</div>
<div class="sidebar-card-content">
<div class="command-item" onclick="setCommandInput('help')">
<div class="command-title">帮助</div>
<div class="command-description">显示所有可用命令</div>
</div>
<div class="command-item" onclick="setCommandInput('clear')">
<div class="command-title">清空终端</div>
<div class="command-description">清除终端中的所有内容</div>
</div>
<div class="command-item" onclick="setCommandInput('history')">
<div class="command-title">历史记录</div>
<div class="command-description">显示之前执行的命令</div>
</div>
<div class="command-item" onclick="setCommandInput('video')">
<div class="command-title">影视解析</div>
<div class="command-description">解析并播放长视频</div>
</div>
<div class="command-item" onclick="setCommandInput('shortvideo')">
<div class="command-title">短视频解析</div>
<div class="command-description">解析抖音/快手短视频</div>
</div>
<div class="command-item" onclick="setCommandInput('ai')">
<div class="command-title">AI助手</div>
<div class="command-description">与AI助手对话</div>
</div>
<div class="command-item" onclick="setCommandInput('image')">
<div class="command-title">图片转换</div>
<div class="command-description">转换图片格式</div>
</div>
<div class="command-item" onclick="setCommandInput('weather')">
<div class="command-title">天气查询</div>
<div class="command-description">查询当前位置天气信息</div>
</div>
<div class="command-item" onclick="setCommandInput('search')">
<div class="command-title">夸克搜索</div>
<div class="command-description">使用夸克搜索引擎搜索</div>
</div>
</div>
</div>
<!-- 快捷操作 -->
<div class="sidebar-card">
<div class="sidebar-card-header">
<span class="sidebar-card-title">快捷操作</span>
</div>
<div class="sidebar-card-content">
<div class="quick-actions">
<button class="quick-action-btn" onclick="setCommandInput('help')">帮助</button>
<button class="quick-action-btn" onclick="setCommandInput('clear')">清屏</button>
<button class="quick-action-btn" onclick="setCommandInput('video')">影视</button>
<button class="quick-action-btn" onclick="setCommandInput('shortvideo')">短视频</button>
<button class="quick-action-btn" onclick="setCommandInput('image')">图片</button>
<button class="quick-action-btn" onclick="setCommandInput('ai')">AI</button>
<button class="quick-action-btn" onclick="setCommandInput('weather')">天气</button>
<button class="quick-action-btn" onclick="setCommandInput('search')">搜索</button>
</div>
</div>
</div>
<!-- 解析统计 -->
<div class="sidebar-card">
<div class="sidebar-card-header">
<span class="sidebar-card-title">解析统计</span>
</div>
<div class="sidebar-card-content">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span style="color: #94a3b8; font-size: 0.75em;">总解析次数</span>
<span style="color: #ffffff; font-size: 0.75em;" id="totalParseCount">0</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span style="color: #94a3b8; font-size: 0.75em;">成功次数</span>
<span style="color: #4ade80; font-size: 0.75em;" id="successParseCount">0</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: #94a3b8; font-size: 0.75em;">成功率</span>
<span style="color: #fbbf24; font-size: 0.75em;" id="successRate">0%</span>
</div>
</div>
</div>
</div>
<!-- 遮罩层 -->
<div class="overlay" @click="rightSide = false; leftSide = false" :class="{ 'active': rightSide || leftSide }"></div>
</div>
<!-- 隐藏的文件输入元素 -->
<input type="file" id="fileInput" accept="image/*" style="display: none;">
<!-- Alpine.js 用于交互功能 -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script>
// ================ 全局变量 ================
let currentMonth = new Date().getMonth();
let currentYear = new Date().getFullYear();
let selectedDate = new Date();
let commandHistoryArray = JSON.parse(localStorage.getItem('commandHistory') || '[]');
let historyIndex = -1;
let totalParseCount = parseInt(localStorage.getItem('totalParseCount') || '0');
let successParseCount = parseInt(localStorage.getItem('successParseCount') || '0');
let lastScrollTop = 0;
let isScrollingDown = false;
// 设置当前年份
document.getElementById('year').textContent = new Date().getFullYear();
// 更新解析统计
function updateParseStats() {
document.getElementById('totalParseCount').textContent = totalParseCount;
document.getElementById('successParseCount').textContent = successParseCount;
const rate = totalParseCount > 0 ? Math.round((successParseCount / totalParseCount) * 100) : 0;
document.getElementById('successRate').textContent = rate + '%';
localStorage.setItem('totalParseCount', totalParseCount.toString());
localStorage.setItem('successParseCount', successParseCount.toString());
}
// 初始化统计
updateParseStats();
// ================ 滚动处理 - 修复个人资料显示问题 ================
function handleScroll() {
const mainContainer = document.getElementById('mainContainer');
const profileSection = document.getElementById('profileSection');
if (!mainContainer || !profileSection) return;
const currentScrollTop = mainContainer.scrollTop;
const scrollDelta = currentScrollTop - lastScrollTop;
// 判断滚动方向
if (scrollDelta > 5) {
// 向下滚动 - 隐藏个人资料
if (!isScrollingDown) {
isScrollingDown = true;
profileSection.classList.add('hidden');
}
} else if (scrollDelta < -5) {
// 向上滚动 - 显示个人资料
if (isScrollingDown) {
isScrollingDown = false;
profileSection.classList.remove('hidden');
}
}
lastScrollTop = currentScrollTop;
}
// 节流函数
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// ================ 终端功能 ================
const editorContent = document.getElementById('editorContent');
const userInput = document.getElementById('userInput');
const mainSearchInput = document.getElementById('mainSearchInput');
const searchButton = document.getElementById('searchButton');
const fileInput = document.getElementById('fileInput');
// API配置
const apiConfig = {
id: '10003788',
key: 'ffa8afb46dce4916c5a74fd73c8de9f6',
endpoint: 'https://cn.apihz.cn/api/ai/wxtiny.php'
};
// 新的天气API配置 - 基于IP地址的天气查询
const weatherApiConfig = {
id: '10003788',
key: 'ffa8afb46dce4916c5a74fd73c8de9f6',
endpoint: 'https://cn.apihz.cn/api/tianqi/tqybip.php'
};
// 夸克搜索引擎配置
const quarkSearchConfig = {
baseUrl: 'https://quark.sm.cn/s',
defaultParams: {
safe: 1,
snum: 0
}
};
// 影视解析接口
const videoApis = [
{ name: "默认线路【推荐】", url: "https://jx.xmflv.com/?url=" },
{ name: "线路一【推荐】", url: "https://jx.we-vip.com/?url=" },
{ name: "线路二【推荐】", url: "https://z1.m1907.top/?jx=" },
{ name: "线路三", url: "https://www.ckplayer.vip/jiexi/?url=" },
{ name: "线路五", url: "https://jx.jsonplayer.com/player/?url=" },
{ name: "线路六", url: "https://jx.4kdv.com/?url=" },
{ name: "线路七", url: "https://api.jiexi.la/?url=" },
{ name: "线路八", url: "https://jx.qqwtt.com/?url=" },
{ name: "线路九", url: "https://www.playm3u8.cn/jiexi.php?url=" },
{ name: "线路十", url: "https://www.8090g.cn/jiexi/?url=" }
];
// 帮助信息
const helpText = `
可用命令:
基础命令:
- help: 显示帮助信息
- clear: 清空终端
- history: 显示命令历史记录
搜索工具:
- search [关键词]: 使用夸克搜索引擎搜索
例如: search JavaScript教程
影视工具:
- video [url]: 解析并播放长视频(支持爱奇艺、腾讯、优酷等)
例如: video https://v.qq.com/x/cover/mzc00200mp8er9d.html
- shortvideo [url]: 解析短视频(支持抖音、快手、小红书)
例如: shortvideo https://v.douyin.com/xxxxxx
AI工具:
- ai [message]: 与AI助手对话
例如: ai 你好
图片工具:
- image [format]: 转换图片格式 (支持: jpg, png, webp, bmp)
例如: image png
天气工具:
- weather [ip]: 查询指定IP或当前位置的天气信息
例如: weather 或 weather 49.234.56.78
其他工具:
- calendar: 显示日历信息
- stats: 显示系统统计信息
`;
// ================ 夸克搜索功能 ================
function performQuarkSearch(query) {
if (!query) {
addAILine('请输入搜索关键词,例如: search JavaScript教程');
return;
}
// 构建夸克搜索URL
const searchUrl = new URL(quarkSearchConfig.baseUrl);
searchUrl.searchParams.set('q', query);
searchUrl.searchParams.set('safe', quarkSearchConfig.defaultParams.safe);
searchUrl.searchParams.set('snum', quarkSearchConfig.defaultParams.snum);
// 显示搜索状态
addAILine(`正在使用夸克搜索引擎搜索: "${query}"`);
// 创建搜索结果容器 - 使用统一的工具容器样式
const searchContainer = document.createElement('div');
searchContainer.className = 'tool-container';
// 搜索结果头部 - 使用统一的工具头部样式
const searchHeader = document.createElement('div');
searchHeader.className = 'tool-header';
const searchTitle = document.createElement('div');
searchTitle.className = 'tool-title';
searchTitle.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
夸克搜索引擎
`;
const closeBtn = document.createElement('button');
closeBtn.className = 'tool-close';
closeBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
closeBtn.addEventListener('click', () => searchContainer.remove());
searchHeader.appendChild(searchTitle);
searchHeader.appendChild(closeBtn);
// 搜索结果内容区域 - 使用统一的工具内容样式
const searchContent = document.createElement('div');
searchContent.className = 'tool-content';
// 添加搜索信息
const searchInfo = document.createElement('div');
searchInfo.className = 'tool-info';
searchInfo.innerHTML = `
<strong>搜索引擎:</strong> 夸克搜索<br>
<strong>搜索关键词:</strong> ${query}<br>
<strong>搜索结果:</strong> 实时搜索结果将在下方显示
`;
// 创建搜索结果预览容器
const previewContainer = document.createElement('div');
previewContainer.className = 'tool-preview-container';
previewContainer.style.height = '500px';
// 创建内嵌iframe显示搜索结果
const searchFrame = document.createElement('iframe');
searchFrame.src = searchUrl.toString();
searchFrame.style.cssText = `
width: 100%;
height: 100%;
border: none;
border-radius: 8px;
background: white;
`;
previewContainer.appendChild(searchFrame);
// 操作按钮区域 - 使用统一的工具操作样式
const actions = document.createElement('div');
actions.className = 'tool-actions';
const openInNewTab = document.createElement('button');
openInNewTab.className = 'tool-btn';
openInNewTab.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
在新标签页打开
`;
openInNewTab.addEventListener('click', () => {
window.open(searchUrl.toString(), '_blank');
});
const refreshBtn = document.createElement('button');
refreshBtn.className = 'tool-btn';
refreshBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="m3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
刷新搜索
`;
refreshBtn.addEventListener('click', () => {
searchFrame.src = searchFrame.src;
});
const copyUrlBtn = document.createElement('button');
copyUrlBtn.className = 'tool-btn';
copyUrlBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
复制链接
`;
copyUrlBtn.addEventListener('click', () => {
navigator.clipboard.writeText(searchUrl.toString()).then(() => {
showAlert('搜索链接已复制到剪贴板');
}).catch(() => {
showAlert('复制失败,请手动复制');
});
});
actions.appendChild(openInNewTab);
actions.appendChild(refreshBtn);
actions.appendChild(copyUrlBtn);
// 组装搜索结果容器
searchContent.appendChild(searchInfo);
searchContent.appendChild(previewContainer);
searchContent.appendChild(actions);
searchContainer.appendChild(searchHeader);
searchContainer.appendChild(searchContent);
// 添加到编辑器内容区
editorContent.appendChild(searchContainer);
editorContent.scrollTop = editorContent.scrollHeight;
addAILine(`夸克搜索结果已加载,您可以在上方查看搜索结果或使用操作按钮。`);
}
// ================ 新的天气功能 - 集成IP天气API ================
async function fetchWeatherByIP(ip = null) {
try {
let url = `${weatherApiConfig.endpoint}?id=${weatherApiConfig.id}&key=${weatherApiConfig.key}`;
if (ip) {
url += `&ip=${encodeURIComponent(ip)}`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200) {
return data;
} else {
throw new Error(data.msg || 'API返回错误状态');
}
} catch (error) {
console.error('天气API请求失败:', error);
throw error;
}
}
function getWeatherIcon(weather1, weather2) {
const weather = weather1 + (weather2 ? weather2 : '');
const iconMap = {
'晴': 'fas fa-sun',
'多云': 'fas fa-cloud-sun',
'阴': 'fas fa-cloud',
'小雨': 'fas fa-cloud-rain',
'中雨': 'fas fa-cloud-rain',
'大雨': 'fas fa-cloud-showers-heavy',
'雷阵雨': 'fas fa-bolt',
'雪': 'fas fa-snowflake',
'雾': 'fas fa-smog',
'霾': 'fas fa-smog',
'云': 'fas fa-cloud'
};
for (const key in iconMap) {
if (weather.includes(key)) {
return iconMap[key];
}
}
return 'fas fa-cloud';
}
function getWindDescription(windDirection, windScale) {
return `${windDirection} ${windScale}`;
}
async function updateWeatherWidget(ip = null) {
const weatherWidget = document.getElementById('weatherWidget');
// 显示加载状态
weatherWidget.innerHTML = `
<div class="weather-loading">
<div class="weather-loading-spinner"></div>
正在获取天气信息...
</div>
`;
try {
const weatherData = await fetchWeatherByIP(ip);
// 解析天气数据
const weatherIcon = getWeatherIcon(weatherData.weather1, weatherData.weather2);
const weatherDesc = weatherData.weather1 + (weatherData.weather2 && weatherData.weather2 !== weatherData.weather1 ? '转' + weatherData.weather2 : '');
const windDesc = getWindDescription(weatherData.windDirection, weatherData.windScale);
// 更新天气组件 - 日历风格简约设计
weatherWidget.innerHTML = `
<div class="weather-header">
<div class="weather-location">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
${weatherData.place}
</div>
</div>
<div class="weather-main">
<div class="weather-temp-section">
<i class="${weatherIcon} weather-icon"></i>
<div class="weather-temp-info">
<div class="weather-temp">${weatherData.temperature}°C</div>
<div class="weather-desc">${weatherDesc}</div>
</div>
</div>
</div>
<div class="weather-details">
<div class="weather-detail">
<span class="weather-detail-label">降水</span>
<span class="weather-detail-value">${weatherData.precipitation}mm</span>
</div>
<div class="weather-detail">
<span class="weather-detail-label">气压</span>
<span class="weather-detail-value">${weatherData.pressure}hPa</span>
</div>
<div class="weather-detail">
<span class="weather-detail-label">湿度</span>
<span class="weather-detail-value">${weatherData.humidity}%</span>
</div>
<div class="weather-detail">
<span class="weather-detail-label">风力</span>
<span class="weather-detail-value">${windDesc}</span>
</div>
</div>
<div class="weather-tip">
💡 基于位置自动获取天气信息
</div>
`;
} catch (error) {
console.error('获取天气数据失败:', error);
weatherWidget.innerHTML = `
<div class="weather-error">
获取天气信息失败<br>
<small>${error.message}</small><br>
<small>请检查网络连接或稍后重试</small>
</div>
`;
}
}
// ================ 日历功能 ================
function renderCalendar() {
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
const calendarTitle = document.getElementById('calendarTitle');
const calendarGrid = document.getElementById('calendarGrid');
calendarTitle.textContent = `${currentYear}年${monthNames[currentMonth]}`;
// 清除现有日期
const days = calendarGrid.querySelectorAll('.calendar-day');
days.forEach(day => day.remove());
const firstDay = new Date(currentYear, currentMonth, 1);
const lastDay = new Date(currentYear, currentMonth + 1, 0);
const prevMonthLastDay = new Date(currentYear, currentMonth, 0).getDate();
const firstDayIndex = firstDay.getDay();
const lastDate = lastDay.getDate();
const today = new Date();
// 上个月的日期
for (let i = firstDayIndex; i > 0; i--) {
const dayElem = document.createElement('div');
dayElem.className = 'calendar-day other-month';
dayElem.textContent = prevMonthLastDay - i + 1;
calendarGrid.appendChild(dayElem);
}
// 当前月的日期
for (let i = 1; i <= lastDate; i++) {
const dayElem = document.createElement('div');
dayElem.className = 'calendar-day';
dayElem.textContent = i;
if (i === today.getDate() && currentMonth === today.getMonth() && currentYear === today.getFullYear()) {
dayElem.classList.add('today');
}
if (i === selectedDate.getDate() && currentMonth === selectedDate.getMonth() && currentYear === selectedDate.getFullYear()) {
dayElem.classList.add('selected');
}
// 藤原生日标记
if (i === 1 && currentMonth === 10) {
dayElem.style.color = 'var(--birthday-color)';
dayElem.style.fontWeight = 'bold';
dayElem.setAttribute('title', '藤原生日');
}
dayElem.addEventListener('click', () => {
const selectedDays = calendarGrid.querySelectorAll('.calendar-day.selected');
selectedDays.forEach(day => day.classList.remove('selected'));
dayElem.classList.add('selected');
selectedDate = new Date(currentYear, currentMonth, i);
});
calendarGrid.appendChild(dayElem);
}
// 下个月的日期
const daysNeeded = 42 - (firstDayIndex + lastDate);
for (let i = 1; i <= daysNeeded; i++) {
const dayElem = document.createElement('div');
dayElem.className = 'calendar-day other-month';
dayElem.textContent = i;
calendarGrid.appendChild(dayElem);
}
}
// 日历导航
document.getElementById('prevMonth').addEventListener('click', () => {
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderCalendar();
});
document.getElementById('nextMonth').addEventListener('click', () => {
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderCalendar();
});
// 打字机效果函数
function typeWriterEffect(element, text, speed = 10) {
return new Promise((resolve) => {
let i = 0;
const typing = () => {
if (i < text.length) {
element.textContent += text.charAt(i);
i++;
setTimeout(typing, speed);
} else {
resolve();
}
};
typing();
});
}
async function addAILine(text) {
const line = document.createElement('div');
line.className = 'line';
const lineContent = document.createElement('div');
lineContent.className = 'line-content ai-output';
lineContent.textContent = '藤原: ';
line.appendChild(lineContent);
editorContent.appendChild(line);
editorContent.scrollTop = editorContent.scrollHeight;
await typeWriterEffect(lineContent, text);
return line;
}
function addUserLine(text) {
const line = document.createElement('div');
line.className = 'line';
const lineContent = document.createElement('div');
lineContent.className = 'line-content user-input';
lineContent.textContent = `> ${text}`;
line.appendChild(lineContent);
editorContent.appendChild(line);
editorContent.scrollTop = editorContent.scrollHeight;
return line;
}
function clearTerminal() {
const lines = document.querySelectorAll('.line');
lines.forEach(line => {
const content = line.querySelector('.line-content').textContent;
if (!content.includes('// 个人信息配置') &&
!content.includes('const profile') &&
!content.includes('name:') &&
!content.includes('title:') &&
!content.includes('contact:') &&
!content.includes('email:') &&
!content.includes('website:') &&
!content.includes('FileCodeBox:') &&
!content.includes('links:') &&
!content.includes('travel blog:') &&
!content.includes('birthday:') &&
!content.includes('motto:') &&
!content.includes('copyright:') &&
!content.includes('};') &&
!content.includes('// 终端交互') &&
!content.includes('console.log')) {
line.remove();
}
});
// 移除视频、图片等组件
const videoContainer = document.querySelector('.video-container');
const imageConverter = document.querySelector('.tool-container');
const shortVideoParser = document.querySelector('.tool-container');
const searchResults = document.querySelector('.search-results');
if (videoContainer) videoContainer.remove();
if (imageConverter) imageConverter.remove();
if (shortVideoParser) shortVideoParser.remove();
if (searchResults) searchResults.remove();
}
// ============== 影视解析功能 ==============
function parseVideo(url) {
// 移除现有的播放器
const existingPlayer = document.querySelector('.video-container');
if (existingPlayer) {
existingPlayer.remove();
}
// 增加解析次数统计
totalParseCount++;
updateParseStats();
// 显示加载状态
const loadingLine = document.createElement('div');
loadingLine.className = 'line';
const loadingContent = document.createElement('div');
loadingContent.className = 'line-content ai-output';
loadingContent.textContent = '藤原: 正在解析视频,请稍候...';
loadingLine.appendChild(loadingContent);
editorContent.appendChild(loadingLine);
editorContent.scrollTop = editorContent.scrollHeight;
// 检查URL是否有效
if (!url || !url.startsWith('http')) {
loadingContent.textContent = '藤原: 请输入有效的视频URL';
return;
}
// 创建视频播放器容器
const videoContainer = document.createElement('div');
videoContainer.className = 'video-container';
// 创建视频元素
const videoPlayer = document.createElement('div');
videoPlayer.className = 'video-player';
// 使用iframe来播放解析后的视频
const videoElement = document.createElement('iframe');
videoElement.id = 'terminal-video';
// 使用默认解析线路
const defaultApi = videoApis[0];
videoElement.src = defaultApi.url + encodeURIComponent(url);
videoElement.frameBorder = '0';
videoElement.allowFullscreen = true;
videoElement.style.width = '100%';
videoElement.style.height = '100%';
videoPlayer.appendChild(videoElement);
// 视频控制区域
const videoControls = document.createElement('div');
videoControls.className = 'video-controls';
// 当前线路显示
const currentLine = document.createElement('span');
currentLine.style.cssText = 'color: #94a3b8; font-size: 12px; margin-right: 10px;';
currentLine.textContent = `当前线路: ${defaultApi.name}`;
// 刷新按钮
const refreshButton = document.createElement('button');
refreshButton.className = 'video-button';
refreshButton.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="m3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg> 刷新';
refreshButton.addEventListener('click', () => {
videoElement.src = videoElement.src;
});
// 线路切换按钮
const switchButton = document.createElement('button');
switchButton.className = 'video-button';
switchButton.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> 切换线路';
switchButton.addEventListener('click', () => {
showVideoApiSelector(url);
});
// 全屏按钮
const fullscreenButton = document.createElement('button');
fullscreenButton.className = 'video-button';
fullscreenButton.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg> 全屏';
fullscreenButton.addEventListener('click', () => {
if (videoElement.requestFullscreen) {
videoElement.requestFullscreen();
}
});
// 关闭按钮
const closeButton = document.createElement('button');
closeButton.className = 'video-button';
closeButton.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> 关闭';
closeButton.addEventListener('click', () => videoContainer.remove());
videoControls.appendChild(currentLine);
videoControls.appendChild(refreshButton);
videoControls.appendChild(switchButton);
videoControls.appendChild(fullscreenButton);
videoControls.appendChild(closeButton);
// 组装整个播放器
videoContainer.appendChild(videoPlayer);
videoContainer.appendChild(videoControls);
// 添加到编辑器内容区
editorContent.appendChild(videoContainer);
editorContent.scrollTop = editorContent.scrollHeight;
// 更新加载状态
loadingContent.textContent = '藤原: 视频解析完成!';
// 增加成功统计
successParseCount++;
updateParseStats();
}
// 显示线路选择器
function showVideoApiSelector(originalUrl) {
addAILine('可用解析线路:');
videoApis.forEach((api, index) => {
const line = document.createElement('div');
line.className = 'line';
const lineContent = document.createElement('div');
lineContent.className = 'line-content';
lineContent.innerHTML = `<span class="ai-output">藤原: ${index + 1}. <span style="cursor: pointer; color: #818cf8; text-decoration: underline;" onclick="switchVideoApi('${originalUrl}', ${index})">${api.name}</span></span>`;
line.appendChild(lineContent);
editorContent.appendChild(line);
});
editorContent.scrollTop = editorContent.scrollHeight;
}
// 切换视频解析线路
function switchVideoApi(originalUrl, apiIndex) {
const api = videoApis[apiIndex];
const apiUrl = api.url + encodeURIComponent(originalUrl);
// 更新iframe的src
const videoElement = document.getElementById('terminal-video');
if (videoElement) {
videoElement.src = apiUrl;
// 更新当前线路显示
const currentLine = document.querySelector('.video-controls span');
if (currentLine) {
currentLine.textContent = `当前线路: ${api.name}`;
}
addAILine(`已切换到 ${api.name}`);
}
}
// ============== 短视频解析功能 ==============
function parseShortVideo(url) {
// 移除现有的解析结果
const existingResult = document.querySelector('.short-video-result');
if (existingResult) {
existingResult.remove();
}
// 增加解析次数统计
totalParseCount++;
updateParseStats();
// 显示加载状态
const loadingLine = document.createElement('div');
loadingLine.className = 'line';
const loadingContent = document.createElement('div');
loadingContent.className = 'line-content ai-output';
loadingContent.textContent = '藤原: 正在解析短视频,请稍候...';
loadingLine.appendChild(loadingContent);
editorContent.appendChild(loadingLine);
editorContent.scrollTop = editorContent.scrollHeight;
// 检查URL是否有效
if (!url || !url.startsWith('http')) {
loadingContent.textContent = '藤原: 请输入有效的短视频URL';
return;
}
// 模拟解析结果
setTimeout(() => {
// 随机决定是视频还是图集
const isVideo = Math.random() > 0.5;
// 创建结果容器
const resultContainer = document.createElement('div');
resultContainer.className = 'tool-container short-video-result';
// 头部
const header = document.createElement('div');
header.className = 'tool-header';
const title = document.createElement('div');
title.className = 'tool-title';
title.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>
短视频解析结果
`;
const closeBtn = document.createElement('button');
closeBtn.className = 'tool-close';
closeBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
closeBtn.addEventListener('click', () => resultContainer.remove());
header.appendChild(title);
header.appendChild(closeBtn);
// 内容区域
const content = document.createElement('div');
content.className = 'tool-content';
// 作者信息区域
const authorInfo = `
<div style="display: flex; align-items: center; margin-bottom: 15px; padding: 12px; background: rgba(255, 255, 255, 0.05); border-radius: 8px;">
<img src="https://picsum.photos/100/100?random=${Math.floor(Math.random() * 100)}" style="width: 40px; height: 40px; border-radius: 50%; margin-right: 12px;">
<div>
<div style="font-weight: 600; color: var(--text-color); margin-bottom: 2px;">示例作者</div>
<div style="font-size: 12px; color: var(--line-color);">来自 ${url.includes('douyin') ? '抖音' : url.includes('kuaishou') ? '快手' : '小红书'}</div>
</div>
</div>
`;
let mediaContent = '';
if (isVideo) {
// 视频内容
mediaContent = `
<div style="position: relative; padding-top: 56.25%; margin-bottom: 15px; border-radius: 8px; overflow: hidden;">
<video controls
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
crossorigin="anonymous">
<source src="https://example.com/video.mp4" type="video/mp4">
您的浏览器不支持视频播放
</video>
</div>
<div class="tool-actions">
<button class="tool-btn" onclick="downloadVideo('https://example.com/video.mp4', '示例短视频')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
下载视频
</button>
</div>
`;
} else {
// 图集内容
const images = [
'https://picsum.photos/800/1600?random=1',
'https://picsum.photos/800/1600?random=2',
'https://picsum.photos/800/1600?random=3'
];
const galleryHTML = images.map((img, index) => `
<div style="position: relative; margin-bottom: 10px;">
<img src="${img}"
alt="图片 ${index + 1}"
style="width: 100%; border-radius: 8px; cursor: pointer;"
onclick="showFullImage('${img}')">
<div style="position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.7); color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;">
${index + 1}/${images.length}
</div>
</div>
`).join('');
mediaContent = `
<div style="max-height: 400px; overflow-y: auto; margin-bottom: 15px;">
${galleryHTML}
</div>
<div class="tool-actions">
<button class="tool-btn" onclick="downloadAllImages(${JSON.stringify(images).replace(/"/g, '"')}, '示例图集')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
打包下��� (${images.length}张)
</button>
</div>
`;
}
// 音乐信息
const musicInfo = `
<div style="margin-top: 15px; padding: 12px; background: rgba(255, 255, 255, 0.05); border-radius: 8px;">
<h4 style="color: var(--text-color); margin-bottom: 8px; display: flex; align-items: center; gap: 8px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 16px; height: 16px;">
<path d="M9 18V5l12-2v13"></path>
<circle cx="6" cy="18" r="3"></circle>
<circle cx="18" cy="16" r="3"></circle>
</svg>
背景音乐
</h4>
<div style="font-size: 14px; color: var(--line-color);">
示例音乐 - 示例作者
</div>
</div>
`;
content.innerHTML = authorInfo + mediaContent + musicInfo;
resultContainer.appendChild(header);
resultContainer.appendChild(content);
// 添加到编辑器内容区
editorContent.appendChild(resultContainer);
editorContent.scrollTop = editorContent.scrollHeight;
// 更新加载状态
loadingContent.textContent = '藤原: 短视频解析完成!';
// 增加成功统计
successParseCount++;
updateParseStats();
}, 1500);
}
// ============== 图片转换功能 ==============
function createImageConverter(targetFormat) {
const existingConverter = document.querySelector('.image-converter');
if (existingConverter) {
existingConverter.remove();
return;
}
const converter = document.createElement('div');
converter.className = 'tool-container image-converter';
// 头部
const header = document.createElement('div');
header.className = 'tool-header';
const title = document.createElement('div');
title.className = 'tool-title';
title.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
图片格式转换器
`;
const closeBtn = document.createElement('button');
closeBtn.className = 'tool-close';
closeBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
closeBtn.addEventListener('click', () => converter.remove());
header.appendChild(title);
header.appendChild(closeBtn);
// 内容区域
const content = document.createElement('div');
content.className = 'tool-content';
// 预览区域
const previewContainer = document.createElement('div');
previewContainer.className = 'tool-preview-container';
const previewImage = document.createElement('img');
previewImage.className = 'tool-preview';
const placeholder = document.createElement('div');
placeholder.className = 'tool-placeholder';
placeholder.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
<div>请选择要转换的图片</div>
<div style="font-size:0.8em;margin-top:8px;opacity:0.7;">支持 JPG, PNG, WEBP, BMP 格式</div>
`;
previewContainer.appendChild(previewImage);
previewContainer.appendChild(placeholder);
// 格式信息
const formatInfo = document.createElement('div');
formatInfo.innerHTML = `
<div class="tool-info">
<strong>目标格式:</strong>${targetFormat.toUpperCase()}<br>
<strong>支持格式:</strong>JPG, PNG, WEBP, BMP 等主流图片格式
</div>
`;
// 操作按钮
const actions = document.createElement('div');
actions.className = 'tool-actions';
const selectBtn = document.createElement('button');
selectBtn.className = 'tool-btn';
selectBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"></path>
<line x1="16" y1="5" x2="22" y2="5"></line>
<line x1="19" y1="2" x2="19" y2="8"></line>
<circle cx="9" cy="9" r="2"></circle>
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path>
</svg>
选择图片
`;
selectBtn.addEventListener('click', () => {
fileInput.click();
});
const convertBtn = document.createElement('button');
convertBtn.className = 'tool-btn';
convertBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
转换为 ${targetFormat.toUpperCase()}
`;
convertBtn.addEventListener('click', () => {
if (!previewImage.src) {
addAILine('请先选择要转换的图片');
return;
}
convertImage(previewImage, targetFormat);
});
actions.appendChild(selectBtn);
actions.appendChild(convertBtn);
content.appendChild(previewContainer);
content.appendChild(formatInfo);
content.appendChild(actions);
converter.appendChild(header);
converter.appendChild(content);
editorContent.appendChild(converter);
editorContent.scrollTop = editorContent.scrollHeight;
// 文件选择处理
const handleFileSelect = (e) => {
const file = e.target.files[0];
if (!file) return;
if (!file.type.match('image.*')) {
addAILine('请选择有效的图片文件');
return;
}
const reader = new FileReader();
reader.onload = (event) => {
previewImage.src = event.target.result;
previewImage.classList.add('active');
placeholder.style.display = 'none';
// 显示图片信息
const img = new Image();
img.onload = () => {
addAILine(`图片已加载: ${img.width}x${img.height}px, ${(file.size / 1024).toFixed(1)}KB`);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
// 移除事件监听器避免重复绑定
fileInput.removeEventListener('change', handleFileSelect);
};
fileInput.addEventListener('change', handleFileSelect);
}
// ============== 通用工具函数 ==============
// URL提取函数
function extractURL(text) {
try {
const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*))/g;
const matches = text.match(urlRegex);
return matches ? matches[0] : null;
} catch (e) {
console.error('URL提取错误:', e);
return null;
}
}
// 显示全屏图片
function showFullImage(url) {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
cursor: zoom-out;
`;
const img = document.createElement('img');
img.src = url;
img.style.maxWidth = '90%';
img.style.maxHeight = '90%';
img.style.borderRadius = '10px';
overlay.onclick = () => overlay.remove();
overlay.appendChild(img);
document.body.appendChild(overlay);
}
// 下载所有图片
async function downloadAllImages(images, title) {
if (!window.JSZip) {
showAlert('打包下载功能需要JSZip库支持');
return;
}
try {
const zip = new JSZip();
const imgFolder = zip.folder("images");
showAlert('正在打包下载,请稍候...');
const downloadPromises = images.map(async (imgUrl, index) => {
try {
const response = await fetch(imgUrl);
if (!response.ok) throw new Error(`图片${index+1}下载失败`);
const blob = await response.blob();
imgFolder.file(`image_${index + 1}.jpg`, blob);
} catch (error) {
console.warn(`图片${index+1}下载失败:`, error);
}
});
await Promise.all(downloadPromises);
const zipBlob = await zip.generateAsync({
type: "blob",
compression: "DEFLATE",
compressionOptions: { level: 6 }
});
const cleanTitle = title.replace(/[<>:"/\\|?*]/g, '');
saveAs(zipBlob, `${cleanTitle}.zip`);
showAlert('打包下载完成!');
} catch (error) {
console.error('打包下载失败:', error);
showAlert('打包下载失败,请重试');
}
}
// 下载视频
async function downloadVideo(url, title) {
try {
showAlert('正在下载视频,请稍候...');
const response = await fetch(url);
if (!response.ok) throw new Error(`下载失败: ${response.status}`);
const blob = await response.blob();
const cleanTitle = title.replace(/[<>:"/\\|?*]/g, '');
saveAs(blob, `${cleanTitle}.mp4`);
showAlert('视频下载完成!');
} catch (error) {
console.error('视频下载失败:', error);
showAlert('视频下载失败,请重试');
}
}
// 显示提示信息
function showAlert(message) {
const alert = document.createElement('div');
alert.style.cssText = `
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: rgba(30, 41, 59, 0.95);
color: white;
padding: 12px 24px;
border-radius: 25px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
z-index: 9999;
font-size: 14px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
`;
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => alert.remove(), 3000);
}
// 转换图片
function convertImage(imgElement, format) {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布尺寸
canvas.width = imgElement.naturalWidth;
canvas.height = imgElement.naturalHeight;
// 绘制图片
ctx.drawImage(imgElement, 0, 0);
// 确定MIME类型
let mimeType;
let quality = 0.9;
switch (format.toLowerCase()) {
case 'jpg':
case 'jpeg':
mimeType = 'image/jpeg';
break;
case 'png':
mimeType = 'image/png';
break;
case 'webp':
mimeType = 'image/webp';
break;
case 'bmp':
mimeType = 'image/png';
addAILine('注意:BMP格式将转换为PNG格式');
break;
default:
mimeType = 'image/jpeg';
}
// 转换并下载
canvas.toBlob((blob) => {
if (!blob) {
addAILine('图片转换失败,请重试');
return;
}
const url = URL.createObjectURL(blob);
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = `converted-image-${Date.now()}.${format}`;
// 触发下载
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
// 清理URL对象
setTimeout(() => URL.revokeObjectURL(url), 1000);
addAILine(`图片已成功转换为 ${format.toUpperCase()} 格式并开始下载!`);
addAILine(`文件大小: ${(blob.size / 1024).toFixed(1)}KB`);
}, mimeType, quality);
} catch (error) {
addAILine(`图片转换失败: ${error.message}`);
}
}
// 创建天气组件 - 使用新的IP天气API
async function createWeather(ip = null) {
try {
addAILine(ip ? `正在查询IP ${ip} 的天气信息...` : '正在查询当前位置的天气信息...');
const weatherData = await fetchWeatherByIP(ip);
if (weatherData && weatherData.code === 200) {
const weatherDesc = weatherData.weather1 + (weatherData.weather2 && weatherData.weather2 !== weatherData.weather1 ? '转' + weatherData.weather2 : '');
const windDesc = getWindDescription(weatherData.windDirection, weatherData.windScale);
addAILine(`${weatherData.place} 天气信息:`);
addAILine(`🌤️ 天气: ${weatherDesc}`);
addAILine(`🌡️ 温度: ${weatherData.temperature}°C`);
addAILine(`💧 降水量: ${weatherData.precipitation}mm`);
addAILine(`📊 气压: ${weatherData.pressure}hPa`);
addAILine(`💨 湿度: ${weatherData.humidity}%`);
addAILine(`🌪️ 风力: ${windDesc}`);
addAILine(`🧭 风速: ${weatherData.windSpeed}m/s`);
addAILine(`📍 风向角: ${weatherData.windDirectionDegree}°`);
// 更新左侧天气组件
updateWeatherWidget(ip);
} else {
addAILine(`获取天气数据失败: ${weatherData.msg || '未知错误'}`);
}
} catch (error) {
addAILine(`获取天气数据失败: ${error.message}`);
addAILine('请检查网络连接或稍后重试');
}
}
// 创建日历
function createCalendar() {
addAILine('日历已在左侧边栏显示,您可以查看当前月份并选择日期。');
addAILine('使用左右箭头可以切换月份,点击日期可以选择特定日期。');
if (currentMonth === 10) {
addAILine('提示:11月1日是藤原的生日!🎂');
}
}
// 保存命令历史
function saveToHistory(command) {
if (commandHistoryArray.length === 0 || commandHistoryArray[commandHistoryArray.length - 1] !== command) {
commandHistoryArray.push(command);
if (commandHistoryArray.length > 20) {
commandHistoryArray.shift();
}
localStorage.setItem('commandHistory', JSON.stringify(commandHistoryArray));
}
historyIndex = -1;
}
// 显示命令历史
function showCommandHistory() {
if (commandHistoryArray.length === 0) {
addAILine('暂无命令历史记录');
return;
}
addAILine('命令历史记录:');
commandHistoryArray.slice().reverse().forEach((cmd, index) => {
addAILine(`${commandHistoryArray.length - index}. ${cmd}`);
});
}
// AI对话功能
async function sendMessage(message) {
if (!message) return;
const thinkingLine = document.createElement('div');
thinkingLine.className = 'line';
const thinkingContent = document.createElement('div');
thinkingContent.className = 'line-content ai-output';
thinkingContent.textContent = '藤原: 正在思考中...';
thinkingLine.appendChild(thinkingContent);
editorContent.appendChild(thinkingLine);
editorContent.scrollTop = editorContent.scrollHeight;
try {
const endpoint = `${apiConfig.endpoint}?id=${apiConfig.id}&key=${apiConfig.key}&words=${encodeURIComponent(message)}`;
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
thinkingLine.remove();
if (data.code === 200 && data.msg) {
const replyLine = document.createElement('div');
replyLine.className = 'line';
const replyContent = document.createElement('div');
replyContent.className = 'line-content ai-output';
replyContent.textContent = '藤原: ';
replyLine.appendChild(replyContent);
editorContent.appendChild(replyLine);
editorContent.scrollTop = editorContent.scrollHeight;
await typeWriterEffect(replyContent, data.msg);
} else {
addAILine(`AI回复失败: ${data.msg || '未知错误'}`);
}
} catch (error) {
thinkingLine.remove();
addAILine(`AI服务暂时不可用: ${error.message}`);
console.error('AI API错误:', error);
}
}
// 显示系统统计
function showStats() {
addAILine('系统统计信息:');
addAILine(`总解析次数: ${totalParseCount}`);
addAILine(`成功解析次数: ${successParseCount}`);
const rate = totalParseCount > 0 ? Math.round((successParseCount / totalParseCount) * 100) : 0;
addAILine(`成功率: ${rate}%`);
addAILine(`命令历史记录: ${commandHistoryArray.length} 条`);
addAILine(`当前时间: ${new Date().toLocaleString()}`);
addAILine(`浏览器: ${navigator.userAgent.split(' ')[0]}`);
}
// 处理命令输入
async function processCommand(input) {
const command = input.trim().toLowerCase();
const parts = input.trim().split(' ');
const cmd = parts[0].toLowerCase();
const args = parts.slice(1).join(' ');
// 保存到历史记录
saveToHistory(input.trim());
// 添加用户输入行
addUserLine(input);
// 处理不同命令
switch (cmd) {
case 'help':
addAILine(helpText);
break;
case 'clear':
clearTerminal();
addAILine('终端已清空');
break;
case 'history':
showCommandHistory();
break;
case 'search':
if (args) {
performQuarkSearch(args);
} else {
addAILine('请提供搜索关键词,例如: search JavaScript教程');
addAILine('将使用夸克搜索引擎进行搜索');
}
break;
case 'video':
if (args) {
const url = extractURL(args) || args;
parseVideo(url);
} else {
addAILine('请提供视频URL,例如: video https://v.qq.com/x/cover/xxx.html');
addAILine('支持的平台: 爱奇艺、腾讯视频、优酷、芒果TV、哔哩哔哩等');
}
break;
case 'shortvideo':
if (args) {
const url = extractURL(args) || args;
parseShortVideo(url);
} else {
addAILine('请提供短视频URL,例如: shortvideo https://v.douyin.com/xxx');
addAILine('支持的平台: 抖音、快手、小红书等');
}
break;
case 'ai':
if (args) {
await sendMessage(args);
} else {
addAILine('请输入要对话的内容,例如: ai 你好');
}
break;
case 'image':
const format = args || 'jpg';
const supportedFormats = ['jpg', 'jpeg', 'png', 'webp', 'bmp'];
if (supportedFormats.includes(format.toLowerCase())) {
createImageConverter(format.toLowerCase());
addAILine(`图片转换器已启动,目标格式: ${format.toUpperCase()}`);
} else {
addAILine(`不支持的格式: ${format}`);
addAILine(`支持的格式: ${supportedFormats.join(', ')}`);
}
break;
case 'weather':
const ip = args || null;
await createWeather(ip);
break;
case 'calendar':
createCalendar();
break;
case 'stats':
showStats();
break;
default:
// 检查是否包含URL,如果是则尝试解析
const detectedUrl = extractURL(input);
if (detectedUrl) {
if (detectedUrl.includes('douyin.com') || detectedUrl.includes('kuaishou.com') || detectedUrl.includes('xiaohongshu.com')) {
parseShortVideo(detectedUrl);
} else {
parseVideo(detectedUrl);
}
} else {
addAILine(`未知命令: ${cmd}`);
addAILine('输入 "help" 查看可用命令');
}
break;
}
}
// 设置命令输入(从右侧面板点击)
function setCommandInput(command) {
userInput.value = command;
userInput.focus();
}
// 执行搜索功能 - 集成夸克搜索
function performSearch() {
const query = mainSearchInput.value.trim();
if (query) {
addUserLine(`搜索: ${query}`);
performQuarkSearch(query);
mainSearchInput.value = ''; // 清空搜索框
}
}
// 输入框事件处理
userInput.addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const input = userInput.value.trim();
if (input) {
userInput.value = '';
await processCommand(input);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (historyIndex < commandHistoryArray.length - 1) {
historyIndex++;
userInput.value = commandHistoryArray[commandHistoryArray.length - 1 - historyIndex];
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex > 0) {
historyIndex--;
userInput.value = commandHistoryArray[commandHistoryArray.length - 1 - historyIndex];
} else if (historyIndex === 0) {
historyIndex = -1;
userInput.value = '';
}
}
});
// 搜索功能事件处理
mainSearchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
performSearch();
}
});
searchButton.addEventListener('click', performSearch);
// ================ 初始化 ================
document.addEventListener('DOMContentLoaded', function() {
// 初始化日历
renderCalendar();
// 初始化天气(默认当前IP)
updateWeatherWidget();
// 聚焦到输入框
userInput.focus();
// 绑定滚动事件处理
const mainContainer = document.getElementById('mainContainer');
if (mainContainer) {
mainContainer.addEventListener('scroll', throttle(handleScroll, 16));
}
// 显示欢迎信息
setTimeout(() => {
addAILine('欢迎使用藤原的个人终端!');
addAILine('输入 "help" 查看可用命令,或直接粘贴视频链接进行解析。');
addAILine('新功能:输入 "search 关键词" 使用夸克搜索引擎搜索!');
addAILine('个人资料区域支持滚动隐藏/显示功能。');
}, 500);
});
// 防止页面刷新时丢失焦点
window.addEventListener('beforeunload', () => {
localStorage.setItem('commandHistory', JSON.stringify(commandHistoryArray));
});
// 页面可见性变化时重新聚焦
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
setTimeout(() => userInput.focus(), 100);
}
});
// 点击终端区域时聚焦输入框
editorContent.addEventListener('click', () => {
userInput.focus();
});
// 响应式处理
function handleResize() {
if (window.innerWidth <= 768) {
// 移动端优化
document.body.style.fontSize = '14px';
} else {
document.body.style.fontSize = '16px';
}
}
window.addEventListener('resize', handleResize);
handleResize(); // 初始调用
// 错误处理
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
});
window.addEventListener('unhandledrejection', (e) => {
console.error('未处理的Promise拒绝:', e.reason);
});
// 性能监控
if ('performance' in window) {
window.addEventListener('load', () => {
setTimeout(() => {
const perfData = performance.getEntriesByType('navigation')[0];
console.log('页面加载时间:', perfData.loadEventEnd - perfData.loadEventStart, 'ms');
}, 0);
});
}
// Service Worker 注册(如果需要离线功能)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// 这里可以注册 Service Worker
// navigator.serviceWorker.register('/sw.js');
});
}
// 键盘快捷键
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K 清空终端
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
clearTerminal();
addAILine('终端已清空 (快捷键)');
}
// Ctrl/Cmd + L 聚焦输入框
if ((e.ctrlKey || e.metaKey) && e.key === 'l') {
e.preventDefault();
userInput.focus();
userInput.select();
}
// ESC 键清空当前输入
if (e.key === 'Escape') {
userInput.value = '';
userInput.focus();
}
});
// 粘贴处理
userInput.addEventListener('paste', (e) => {
setTimeout(() => {
const pastedText = userInput.value;
const url = extractURL(pastedText);
if (url && url === pastedText.trim()) {
// 如果粘贴的是纯URL,自动识别类型
setTimeout(() => {
userInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
}, 100);
}
}, 10);
});
// 触摸设备优化
if ('ontouchstart' in window) {
// 防止双击缩放
let lastTouchEnd = 0;
document.addEventListener('touchend', (e) => {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
e.preventDefault();
}
lastTouchEnd = now;
}, false);
// 优化滚动
document.body.style.webkitOverflowScrolling = 'touch';
}
// 主题切换功能(预留)
function toggleTheme() {
// 这里可以添加主题切换逻辑
console.log('主题切换功能待实现');
}
// 导出功能(预留)
function exportHistory() {
const history = {
commands: commandHistoryArray,
stats: {
totalParseCount,
successParseCount
},
timestamp: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(history, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `tengyuan-terminal-history-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}
// 全局暴露一些函数供调试使用
window.terminalDebug = {
clearTerminal,
processCommand,
showStats,
exportHistory,
toggleTheme,
updateWeatherWidget,
fetchWeatherByIP,
handleScroll,
performQuarkSearch
};
</script>
</body>
</html>
index.html
style.css
index.js