agent-Specialization/static/mobile-overlay-fab.html

438 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>移动端半覆盖面板(浮动按钮)</title>
<style>
:root {
--bg: #f4efe7;
--card: #fffdf8;
--accent: #e06a3a;
--text: #2f251b;
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
font-family: 'SF Pro Display', 'PingFang SC', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--text);
display: flex;
flex-direction: column;
}
.chat-area {
flex: 1;
padding: 18px 18px calc(90px + env(safe-area-inset-bottom, 0px));
display: flex;
flex-direction: column;
gap: 12px;
}
.chat-headline {
font-size: 22px;
margin: 0 0 8px;
}
.message {
padding: 12px 14px;
border-radius: 16px;
line-height: 1.55;
max-width: 90%;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.message.bot {
background: var(--card);
}
.message.user {
background: #fff;
margin-left: auto;
}
.composer-placeholder {
position: sticky;
bottom: 0;
background: linear-gradient(180deg, rgba(244, 239, 231, 0), rgba(244, 239, 231, 0.95));
padding: 12px 0 0;
}
.composer-shell {
background: #fff;
border-radius: 18px;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border: 1px solid rgba(0, 0, 0, 0.08);
}
.composer-shell textarea {
flex: 1;
border: none;
resize: none;
font-size: 15px;
outline: none;
}
.composer-shell button {
border: none;
background: var(--accent);
color: #fff;
border-radius: 50%;
width: 36px;
height: 36px;
}
/* 悬浮主按钮 */
.fab {
position: fixed;
top: 16px;
left: 16px;
z-index: 60;
}
.fab-toggle {
width: 52px;
height: 52px;
border-radius: 26px;
border: none;
background: var(--accent);
color: #fff;
font-size: 22px;
box-shadow: 0 14px 38px rgba(224, 106, 58, 0.35);
cursor: pointer;
}
.fab-menu {
position: absolute;
top: 64px;
left: 4px;
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
border-radius: 16px;
background: rgba(47, 37, 27, 0.95);
box-shadow: 0 18px 48px rgba(8, 6, 4, 0.45);
transform-origin: top left;
transform: scale(0.8) translateY(-10px);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.fab-menu.open {
opacity: 1;
transform: scale(1) translateY(0);
pointer-events: auto;
}
.fab-menu button {
border: none;
background: transparent;
color: #fff;
font-size: 16px;
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
border-radius: 12px;
}
.fab-menu button span {
font-size: 13px;
opacity: 0.8;
}
.fab-menu button:hover,
.fab-menu button.active {
background: rgba(255, 255, 255, 0.12);
}
.sheet {
position: fixed;
inset: 0;
pointer-events: none;
visibility: hidden;
transition: visibility 0.2s;
}
.sheet.is-visible {
pointer-events: auto;
visibility: visible;
}
.sheet-backdrop {
position: absolute;
inset: 0;
background: rgba(11, 8, 5, 0.35);
opacity: 0;
transition: opacity 0.25s ease;
}
.sheet.is-visible .sheet-backdrop {
opacity: 1;
}
.sheet-panel {
position: absolute;
top: clamp(12px, 3vh, 28px);
bottom: clamp(12px, 3vh, 28px);
width: min(420px, 88vw);
border-radius: 22px;
background: #fffdf8;
box-shadow: 0 24px 60px rgba(20, 12, 4, 0.35);
display: flex;
flex-direction: column;
transform: translateX(-120%);
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.sheet[data-side='left'] .sheet-panel {
left: clamp(12px, 3vw, 24px);
}
.sheet[data-side='right'] .sheet-panel {
right: clamp(12px, 3vw, 24px);
transform: translateX(120%);
}
.sheet.is-visible .sheet-panel {
transform: translateX(0);
}
.sheet-header {
padding: 18px 22px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
}
.sheet-title {
font-size: 18px;
font-weight: 600;
}
.close-btn {
border: none;
background: rgba(0, 0, 0, 0.06);
border-radius: 999px;
width: 34px;
height: 34px;
font-size: 18px;
cursor: pointer;
}
.sheet-body {
flex: 1;
overflow-y: auto;
padding: 18px 22px 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.section-card {
border-radius: 16px;
background: rgba(47, 37, 27, 0.04);
border: 1px solid rgba(47, 37, 27, 0.08);
padding: 14px 16px;
}
.status-pill {
font-size: 13px;
color: rgba(0, 0, 0, 0.55);
}
</style>
</head>
<body>
<div class="fab">
<button class="fab-toggle" id="fabToggle"></button>
<div class="fab-menu" id="fabMenu">
<button data-open="conversation-sheet">
📚
<div>
对话记录
<span>历史 + 搜索</span>
</div>
</button>
<button data-open="workspace-sheet">
🧰
<div>
工作台
<span>文件 / 待办 / 子体</span>
</div>
</button>
<button data-open="focus-sheet">
👁️
<div>
聚焦面板
<span>最多 3 个文件</span>
</div>
</button>
</div>
</div>
<main class="chat-area">
<h1 class="chat-headline">对话区域</h1>
<div class="message bot">你好!移动端默认只显示聊天内容。</div>
<div class="message user">其它面板怎么进入?</div>
<div class="message bot">点击左上角的悬浮按钮,展开二级菜单即可。</div>
<div class="message bot">每个选项会以半覆盖弹层的方式显示对应面板。</div>
<div class="composer-placeholder">
<div class="composer-shell">
<textarea rows="1" placeholder="输入内容…"></textarea>
<button></button>
</div>
</div>
</main>
<!-- 对话记录 -->
<section class="sheet" data-side="left" id="conversation-sheet">
<div class="sheet-backdrop" data-close></div>
<div class="sheet-panel">
<div class="sheet-header">
<div>
<div class="sheet-title">对话记录</div>
<div class="status-pill">最近 10 条 · 可搜索</div>
</div>
<button class="close-btn" data-close>×</button>
</div>
<div class="sheet-body">
<div class="section-card">
<strong>新建对话</strong>
<p>用于创建新的任务空间</p>
</div>
<div class="section-card">
<p>#1243 Bug 跟踪 · 刚刚</p>
</div>
<div class="section-card">
<p>#1235 UI 迭代 · 30 分钟前</p>
</div>
</div>
</div>
</section>
<!-- 三合一工作台 -->
<section class="sheet" data-side="left" id="workspace-sheet">
<div class="sheet-backdrop" data-close></div>
<div class="sheet-panel">
<div class="sheet-header">
<div>
<div class="sheet-title">三合一工作台</div>
<div class="status-pill">Logo 卡片 + 文件 + 待办 + 子体</div>
</div>
<button class="close-btn" data-close>×</button>
</div>
<div class="sheet-body">
<div class="section-card">
<strong>AI Agent v2.4</strong>
<p>思考模式 · 已连接</p>
</div>
<div class="section-card">
<h4>项目文件</h4>
<p>/src/App.vue</p>
<p>/stores/ui.ts</p>
</div>
<div class="section-card">
<h4>待办</h4>
<p>1. 适配移动端布局</p>
<p>2. 补充交互说明</p>
</div>
<div class="section-card">
<h4>子智能体</h4>
<p>#07 日志总结 · 进行中</p>
</div>
</div>
</div>
</section>
<!-- 聚焦面板 -->
<section class="sheet" data-side="right" id="focus-sheet">
<div class="sheet-backdrop" data-close></div>
<div class="sheet-panel">
<div class="sheet-header">
<div>
<div class="sheet-title">聚焦面板</div>
<div class="status-pill">最多 3 个热点文件</div>
</div>
<button class="close-btn" data-close>×</button>
</div>
<div class="sheet-body">
<div class="section-card">
<h4>App.vue</h4>
<p>移动端 Teleport 逻辑</p>
</div>
<div class="section-card">
<h4>ui.ts</h4>
<p>activeMobileSheet 状态</p>
</div>
<div class="section-card">
<h4>styles/_responsive.scss</h4>
<p>半覆盖动画与遮罩</p>
</div>
</div>
</div>
</section>
<script>
const fabToggle = document.getElementById('fabToggle');
const fabMenu = document.getElementById('fabMenu');
const sheetButtons = document.querySelectorAll('.fab-menu [data-open]');
const sheets = document.querySelectorAll('.sheet');
const closeAllSheets = () => {
sheets.forEach(sheet => sheet.classList.remove('is-visible'));
sheetButtons.forEach(btn => btn.classList.remove('active'));
};
fabToggle.addEventListener('click', () => {
fabMenu.classList.toggle('open');
});
sheetButtons.forEach(btn => {
btn.addEventListener('click', () => {
const targetId = btn.getAttribute('data-open');
const sheet = document.getElementById(targetId);
if (!sheet) return;
const alreadyOpen = sheet.classList.contains('is-visible');
closeAllSheets();
fabMenu.classList.remove('open');
if (!alreadyOpen) {
sheet.classList.add('is-visible');
btn.classList.add('active');
}
});
});
document.querySelectorAll('[data-close]').forEach(el => {
el.addEventListener('click', () => {
const sheet = el.closest('.sheet');
if (!sheet) return;
sheet.classList.remove('is-visible');
sheetButtons.forEach(btn => {
if (btn.getAttribute('data-open') === sheet.id) {
btn.classList.remove('active');
}
});
});
});
document.addEventListener('click', event => {
if (!fabMenu.contains(event.target) && event.target !== fabToggle) {
fabMenu.classList.remove('open');
}
});
</script>
</body>
</html>