Files
xianyan/docs/mockups/leisure_card_detail.html
Developer 355191aaf6 feat(leisure): 新增闲情逸致模块与多项功能优化
本次提交完成多项核心更新:
1. 新增闲情逸致功能模块,包含时间线、收藏标注、季节主题等基础框架
2. 替换hive为社区维护的hive_ce包,修复依赖兼容问题
3. 统一替换"开发中"提示为"当前设备不支持",优化用户提示文案
4. 新增多项功能开关与特性标志,统一管理不可用功能提示
5. 完善用户账户洞察系统,新增头像审核中状态检测
6. 优化TTS语音朗读服务,修复Android端引擎初始化问题
7. 重构知识图谱缩放手势逻辑,解决缩放不跟手问题
8. 新增精灵头像组件,替换默认聊天头像样式
9. 新增外部链接跳转确认弹窗,提升使用安全性
10. 升级后端API接口,新增签到配置获取与补签积分规则动态读取
11. 完善多语言翻译覆盖率限制,非中文语言仅显示最高50%进度
12. 新增HTTP缓存拦截器,优化网络请求性能
13. 新增恢复出厂设置选项,完善数据管理功能

同时修复了多处代码细节问题:简化字符串拼接、优化布局代码、移除多余代码等。
2026-05-27 08:06:54 +08:00

1519 lines
39 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>
<!--
创建时间: 2026-05-27
更新时间: 2026-05-27
名称: 闲情逸致 — 卡片详情Sheet
作用: 模拟点击卡片后弹出的可拖拽底部Sheet详情页
上次更新: 初始创建,含拖拽交互、毛玻璃、暗色模式、标注区、外部搜索
-->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>闲情逸致 — 卡片详情</title>
<style>
:root {
--primary: #6C63FF;
--primary-light: #8B83FF;
--accent: #4ECDC4;
--secondary: #FF6B6B;
--success: #10B981;
--warning: #F59E0B;
--error: #EF4444;
--info: #3B82F6;
--bg-primary: #FAFAFA;
--bg-secondary: #F5F5F5;
--bg-card: #FFFFFF;
--bg-elevated: #FFFFFF;
--text-primary: #1A1A2E;
--text-secondary: #6B7280;
--text-hint: #9CA3AF;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: 20px;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--spring: #4ECDC4;
--summer: #FF6B6B;
--autumn: #F59E0B;
--winter: #3B82F6;
--sheet-bg: rgba(255, 255, 255, 0.82);
--sheet-border: rgba(0, 0, 0, 0.06);
--overlay-bg: rgba(0, 0, 0, 0.35);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
overflow: hidden;
height: 100vh;
width: 100vw;
}
.phone-frame {
max-width: 430px;
margin: 0 auto;
height: 100vh;
position: relative;
background: var(--bg-primary);
overflow: hidden;
}
/* ===== 背景时间线(模糊) ===== */
.bg-timeline {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
filter: blur(8px);
-webkit-filter: blur(8px);
transform: scale(1.02);
opacity: 0.6;
overflow: hidden;
z-index: 1;
pointer-events: none;
}
.bg-timeline .appbar {
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(250, 250, 250, 0.78);
border-bottom: 0.5px solid rgba(0,0,0,0.08);
padding: 12px var(--space-md);
display: flex;
align-items: center;
justify-content: space-between;
}
.bg-timeline .appbar-title {
font-size: 17px;
font-weight: 600;
letter-spacing: -0.2px;
}
.bg-timeline .appbar-subtitle {
font-size: 12px;
color: var(--text-hint);
margin-top: 1px;
}
.bg-timeline .timeline-line {
position: absolute;
left: 50%;
top: 60px;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, transparent, var(--primary) 5%, var(--primary) 95%, transparent);
transform: translateX(-50%);
opacity: 0.15;
}
.bg-timeline .bg-card-row {
display: flex;
gap: 10px;
margin: 16px var(--space-md);
}
.bg-timeline .bg-card-col {
flex: 1;
min-width: 0;
}
.bg-timeline .bg-card {
background: var(--bg-card);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
padding: 12px;
border: 0.5px solid rgba(0,0,0,0.04);
}
.bg-timeline .bg-card-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.bg-timeline .bg-card-desc {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.4;
}
.bg-timeline .bg-card-loc {
font-size: 11px;
color: var(--text-hint);
margin-top: 6px;
}
/* ===== 遮罩层 ===== */
.sheet-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--overlay-bg);
z-index: 10;
opacity: 0;
transition: opacity 0.35s ease;
pointer-events: none;
}
.sheet-overlay.visible {
opacity: 1;
pointer-events: auto;
}
/* ===== 底部Sheet ===== */
.bottom-sheet {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
background: var(--sheet-bg);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
box-shadow: 0 -4px 30px rgba(0, 0, 0, 0.12);
border-top: 0.5px solid var(--sheet-border);
transition: height 0.35s cubic-bezier(0.32, 0.72, 0, 1);
display: flex;
flex-direction: column;
overflow: hidden;
will-change: height;
}
.bottom-sheet.dragging {
transition: none;
}
/* ===== 拖拽手柄 ===== */
.sheet-handle-area {
padding: 8px 0 4px;
cursor: grab;
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
-webkit-user-select: none;
user-select: none;
}
.sheet-handle-area:active {
cursor: grabbing;
}
.sheet-handle {
width: 36px;
height: 5px;
border-radius: 3px;
background: rgba(0, 0, 0, 0.18);
transition: background 0.2s, width 0.2s;
}
.sheet-handle-area:hover .sheet-handle {
width: 42px;
background: rgba(0, 0, 0, 0.28);
}
/* ===== Sheet Header ===== */
.sheet-header {
padding: 4px var(--space-lg) var(--space-md);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.sheet-header-left {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.sheet-back-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
cursor: pointer;
border: none;
color: var(--text-primary);
transition: all 0.2s;
}
.sheet-back-btn:active {
transform: scale(0.92);
background: rgba(0,0,0,0.06);
}
.sheet-title {
font-size: 17px;
font-weight: 600;
letter-spacing: -0.2px;
}
.sheet-header-actions {
display: flex;
gap: var(--space-sm);
}
.sheet-header-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
cursor: pointer;
border: none;
color: var(--text-secondary);
transition: all 0.2s;
}
.sheet-header-btn:active {
transform: scale(0.92);
}
/* ===== Sheet Content (scrollable) ===== */
.sheet-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 0 var(--space-lg) var(--space-xl);
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
}
.sheet-content::-webkit-scrollbar {
width: 3px;
}
.sheet-content::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.12);
border-radius: 3px;
}
/* ===== 卡片详情 Hero ===== */
.detail-hero {
position: relative;
border-radius: var(--radius-xl);
overflow: hidden;
margin-bottom: var(--space-lg);
}
.detail-hero-bg {
height: 140px;
background: linear-gradient(135deg, #FF6B6B 0%, #ee5a24 50%, #f0932b 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.detail-hero-bg::after {
content: '';
position: absolute;
top: -30px;
right: -30px;
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(255,255,255,0.1);
}
.detail-hero-bg::before {
content: '';
position: absolute;
bottom: -20px;
left: -20px;
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255,255,255,0.08);
}
.detail-hero-emoji {
font-size: 56px;
position: relative;
z-index: 2;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15));
}
.detail-hero-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-md) var(--space-lg);
background: linear-gradient(transparent, rgba(0,0,0,0.45));
display: flex;
align-items: flex-end;
justify-content: space-between;
}
.detail-hero-title {
font-size: 22px;
font-weight: 700;
color: white;
text-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
.detail-hero-season {
font-size: 12px;
padding: 3px 10px;
border-radius: 12px;
background: rgba(255,255,255,0.25);
backdrop-filter: blur(10px);
color: white;
font-weight: 600;
white-space: nowrap;
}
/* ===== 信息区 ===== */
.detail-section {
margin-bottom: var(--space-lg);
}
.detail-section-title {
font-size: 13px;
font-weight: 600;
color: var(--text-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--space-sm);
display: flex;
align-items: center;
gap: var(--space-xs);
}
.detail-desc {
font-size: 15px;
color: var(--text-primary);
line-height: 1.6;
margin-bottom: var(--space-md);
}
/* ===== 信息网格 ===== */
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-sm);
}
.info-item {
background: var(--bg-secondary);
border-radius: var(--radius-lg);
padding: var(--space-md);
display: flex;
align-items: center;
gap: var(--space-sm);
transition: all 0.2s;
}
.info-item:active {
transform: scale(0.98);
}
.info-item-icon {
font-size: 20px;
flex-shrink: 0;
}
.info-item-content {
min-width: 0;
}
.info-item-label {
font-size: 11px;
color: var(--text-hint);
font-weight: 500;
}
.info-item-value {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-top: 1px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.info-item.full-width {
grid-column: 1 / -1;
}
/* ===== 风险提示 ===== */
.risk-banner {
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.15);
border-radius: var(--radius-lg);
padding: var(--space-md);
display: flex;
align-items: center;
gap: var(--space-sm);
margin-bottom: var(--space-lg);
}
.risk-banner-icon {
font-size: 20px;
flex-shrink: 0;
}
.risk-banner-text {
font-size: 13px;
color: var(--error);
font-weight: 500;
line-height: 1.4;
}
/* ===== 季节色标签 ===== */
.season-tags {
display: flex;
gap: var(--space-sm);
flex-wrap: wrap;
margin-bottom: var(--space-lg);
}
.season-tag {
font-size: 12px;
padding: 5px 12px;
border-radius: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.2s;
cursor: default;
}
.season-tag:active {
transform: scale(0.95);
}
.season-tag-summer {
background: rgba(255,107,107,0.12);
color: var(--summer);
}
.season-tag-food {
background: rgba(245,158,11,0.12);
color: var(--warning);
}
.season-tag-early-summer {
background: rgba(78,205,196,0.12);
color: var(--accent);
}
.season-tag-fruit {
background: rgba(108,99,255,0.12);
color: var(--primary);
}
/* ===== 操作按钮区 ===== */
.action-buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-sm);
margin-bottom: var(--space-lg);
}
.action-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: var(--space-md) var(--space-xs);
border-radius: var(--radius-lg);
background: var(--bg-secondary);
cursor: pointer;
border: none;
transition: all 0.2s;
-webkit-user-select: none;
user-select: none;
}
.action-card:active {
transform: scale(0.94);
}
.action-card.active {
background: rgba(108,99,255,0.1);
}
.action-card-icon {
font-size: 22px;
}
.action-card-label {
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
}
.action-card.active .action-card-label {
color: var(--primary);
font-weight: 600;
}
/* ===== 多选标注区 ===== */
.annotation-area {
margin-bottom: var(--space-lg);
}
.annotation-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-sm);
}
.annotation-item {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 10px;
border-radius: var(--radius-md);
background: var(--bg-secondary);
cursor: pointer;
transition: all 0.2s;
border: 1.5px solid transparent;
-webkit-user-select: none;
user-select: none;
}
.annotation-item:active {
transform: scale(0.96);
}
.annotation-item.checked {
background: rgba(108,99,255,0.08);
border-color: rgba(108,99,255,0.3);
}
.annotation-checkbox {
width: 18px;
height: 18px;
border-radius: 5px;
border: 2px solid var(--text-hint);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s;
font-size: 11px;
color: transparent;
}
.annotation-item.checked .annotation-checkbox {
background: var(--primary);
border-color: var(--primary);
color: white;
}
.annotation-label {
font-size: 12px;
color: var(--text-secondary);
font-weight: 500;
}
.annotation-item.checked .annotation-label {
color: var(--primary);
font-weight: 600;
}
/* ===== 已选标注展示 ===== */
.selected-annotations {
display: flex;
gap: 6px;
flex-wrap: wrap;
margin-top: var(--space-sm);
min-height: 28px;
}
.selected-tag {
font-size: 11px;
padding: 3px 10px;
border-radius: 10px;
background: rgba(108,99,255,0.1);
color: var(--primary);
font-weight: 600;
display: flex;
align-items: center;
gap: 4px;
animation: tagAppear 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes tagAppear {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
.selected-tag-remove {
cursor: pointer;
font-size: 10px;
opacity: 0.6;
transition: opacity 0.2s;
}
.selected-tag-remove:hover {
opacity: 1;
}
/* ===== 外部搜索按钮组 ===== */
.search-group {
margin-bottom: var(--space-lg);
}
.search-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--space-sm);
}
.search-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
padding: 10px 4px;
border-radius: var(--radius-lg);
background: var(--bg-secondary);
cursor: pointer;
border: 1px solid transparent;
transition: all 0.2s;
-webkit-user-select: none;
user-select: none;
}
.search-btn:active {
transform: scale(0.94);
border-color: rgba(108,99,255,0.2);
}
.search-btn-icon {
font-size: 24px;
}
.search-btn-name {
font-size: 10px;
font-weight: 500;
color: var(--text-secondary);
}
/* ===== 相关推荐 ===== */
.recommend-list {
display: flex;
gap: var(--space-sm);
overflow-x: auto;
padding-bottom: var(--space-sm);
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
}
.recommend-list::-webkit-scrollbar {
display: none;
}
.recommend-card {
flex-shrink: 0;
width: 150px;
background: var(--bg-secondary);
border-radius: var(--radius-lg);
padding: var(--space-md);
cursor: pointer;
transition: all 0.2s;
scroll-snap-align: start;
border: 0.5px solid rgba(0,0,0,0.04);
}
.recommend-card:active {
transform: scale(0.96);
}
.recommend-card-emoji {
font-size: 28px;
margin-bottom: 6px;
}
.recommend-card-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 3px;
}
.recommend-card-desc {
font-size: 11px;
color: var(--text-hint);
line-height: 1.3;
}
.recommend-card-season {
font-size: 10px;
padding: 2px 6px;
border-radius: 6px;
margin-top: 6px;
display: inline-block;
font-weight: 600;
}
.rec-season-summer {
background: rgba(255,107,107,0.12);
color: var(--summer);
}
.rec-season-spring {
background: rgba(78,205,196,0.12);
color: var(--spring);
}
/* ===== Toast 提示 ===== */
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.85);
background: rgba(0,0,0,0.78);
backdrop-filter: blur(10px);
color: white;
padding: 14px 24px;
border-radius: var(--radius-lg);
font-size: 14px;
font-weight: 500;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
text-align: center;
max-width: 260px;
}
.toast.show {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
/* ===== 分隔线 ===== */
.divider {
height: 0.5px;
background: var(--sheet-border);
margin: var(--space-md) 0;
}
/* ===== 底部安全区 ===== */
.bottom-safe {
height: env(safe-area-inset-bottom, 0px);
flex-shrink: 0;
}
/* ===== 暗色模式 ===== */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1A1A2E;
--bg-secondary: #16213E;
--bg-card: #2D2D44;
--bg-elevated: #333355;
--text-primary: #E5E5E5;
--text-secondary: #9CA3AF;
--text-hint: #6B7280;
--sheet-bg: rgba(45, 45, 68, 0.85);
--sheet-border: rgba(255,255,255,0.06);
--overlay-bg: rgba(0, 0, 0, 0.55);
}
.sheet-handle {
background: rgba(255, 255, 255, 0.25);
}
.sheet-handle-area:hover .sheet-handle {
background: rgba(255, 255, 255, 0.4);
}
.bg-timeline .appbar {
background: rgba(26,26,46,0.78);
border-bottom-color: rgba(255,255,255,0.06);
}
.bg-timeline .bg-card {
border-color: rgba(255,255,255,0.06);
}
.risk-banner {
background: rgba(239, 68, 68, 0.12);
border-color: rgba(239, 68, 68, 0.2);
}
.info-item {
background: var(--bg-secondary);
}
.sheet-content::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.15);
}
}
body.dark-mode {
--bg-primary: #1A1A2E;
--bg-secondary: #16213E;
--bg-card: #2D2D44;
--bg-elevated: #333355;
--text-primary: #E5E5E5;
--text-secondary: #9CA3AF;
--text-hint: #6B7280;
--sheet-bg: rgba(45, 45, 68, 0.85);
--sheet-border: rgba(255,255,255,0.06);
--overlay-bg: rgba(0, 0, 0, 0.55);
}
body.dark-mode .sheet-handle {
background: rgba(255, 255, 255, 0.25);
}
body.dark-mode .sheet-handle-area:hover .sheet-handle {
background: rgba(255, 255, 255, 0.4);
}
body.dark-mode .bg-timeline .appbar {
background: rgba(26,26,46,0.78);
border-bottom-color: rgba(255,255,255,0.06);
}
body.dark-mode .bg-timeline .bg-card {
border-color: rgba(255,255,255,0.06);
}
body.dark-mode .risk-banner {
background: rgba(239, 68, 68, 0.12);
border-color: rgba(239, 68, 68, 0.2);
}
body.dark-mode .sheet-content::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.15);
}
/* ===== 入场动画 ===== */
.sheet-enter {
animation: sheetSlideUp 0.45s cubic-bezier(0.32, 0.72, 0, 1) forwards;
}
@keyframes sheetSlideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.overlay-enter {
animation: overlayFadeIn 0.35s ease forwards;
}
@keyframes overlayFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* ===== 调试面板 ===== */
.debug-panel {
position: fixed;
top: 8px;
right: 8px;
z-index: 200;
display: flex;
gap: 6px;
}
.debug-btn {
font-size: 11px;
padding: 4px 10px;
border-radius: 8px;
background: rgba(0,0,0,0.6);
color: white;
border: none;
cursor: pointer;
backdrop-filter: blur(10px);
transition: all 0.2s;
}
.debug-btn:active {
transform: scale(0.95);
}
.debug-info {
position: fixed;
bottom: 8px;
right: 8px;
z-index: 200;
font-size: 10px;
padding: 4px 8px;
border-radius: 6px;
background: rgba(0,0,0,0.5);
color: rgba(255,255,255,0.7);
backdrop-filter: blur(10px);
font-family: 'SF Mono', Menlo, monospace;
}
</style>
</head>
<body>
<div class="phone-frame" id="phoneFrame">
<!-- 背景时间线(模糊) -->
<div class="bg-timeline">
<div class="appbar">
<div>
<div class="appbar-title">🌸 闲情逸致</div>
<div class="appbar-subtitle">闲时与你立黄昏,灶前笑问粥可温</div>
</div>
</div>
<div class="timeline-line"></div>
<div class="bg-card-row">
<div class="bg-card-col">
<div class="bg-card">
<div class="bg-card-title">杨梅季 🫐</div>
<div class="bg-card-desc">仙居杨梅·东魁·荸荠种</div>
<div class="bg-card-loc">📍 浙江·仙居</div>
</div>
</div>
<div class="bg-card-col">
<div class="bg-card">
<div class="bg-card-title">西湖夜游 🌙</div>
<div class="bg-card-desc">印象西湖·断桥残雪</div>
<div class="bg-card-loc">📍 浙江·杭州</div>
</div>
</div>
</div>
<div class="bg-card-row">
<div class="bg-card-col">
<div class="bg-card">
<div class="bg-card-title">小龙虾 🦞</div>
<div class="bg-card-desc">盱眙十三香·潜江油焖</div>
<div class="bg-card-loc">📍 江苏·盱眙</div>
</div>
</div>
<div class="bg-card-col">
<div class="bg-card">
<div class="bg-card-title">薰衣草花海 💜</div>
<div class="bg-card-desc">伊犁河谷薰衣草初开</div>
<div class="bg-card-loc">📍 新疆·伊犁</div>
</div>
</div>
</div>
</div>
<!-- 遮罩层 -->
<div class="sheet-overlay" id="sheetOverlay"></div>
<!-- 底部Sheet -->
<div class="bottom-sheet sheet-enter" id="bottomSheet">
<!-- 拖拽手柄 -->
<div class="sheet-handle-area" id="sheetHandle">
<div class="sheet-handle"></div>
</div>
<!-- Sheet Header -->
<div class="sheet-header">
<div class="sheet-header-left">
<button class="sheet-back-btn" onclick="goBack()" title="返回"></button>
<span class="sheet-title">卡片详情</span>
</div>
<div class="sheet-header-actions">
<button class="sheet-header-btn" onclick="toggleDarkMode()" title="暗色模式" id="darkBtn">🌙</button>
<button class="sheet-header-btn" onclick="showToast('更多操作开发中…')" title="更多"></button>
</div>
</div>
<!-- Sheet Content -->
<div class="sheet-content" id="sheetContent">
<!-- Hero区 -->
<div class="detail-hero">
<div class="detail-hero-bg">
<span class="detail-hero-emoji">🫐</span>
<div class="detail-hero-overlay">
<div class="detail-hero-title">杨梅季</div>
<span class="detail-hero-season">☀️ 夏季限定</span>
</div>
</div>
</div>
<!-- 描述 -->
<div class="detail-desc">
仙居杨梅·东魁·荸荠种酸甜多汁初夏限定美味。每年6月中上旬为最佳采摘期果大核小汁水丰盈是浙江仙居的金字招牌。
</div>
<!-- 季节色标签 -->
<div class="season-tags">
<span class="season-tag season-tag-summer">☀️ 夏</span>
<span class="season-tag season-tag-food">🍜 美食</span>
<span class="season-tag season-tag-early-summer">🌿 初夏限定</span>
<span class="season-tag season-tag-fruit">🍇 水果</span>
<span class="season-tag season-tag-food">🫐 杨梅</span>
</div>
<!-- 信息网格 -->
<div class="detail-section">
<div class="detail-section-title">📋 基本信息</div>
<div class="info-grid">
<div class="info-item">
<span class="info-item-icon">📍</span>
<div class="info-item-content">
<div class="info-item-label">地点</div>
<div class="info-item-value">浙江·仙居</div>
</div>
</div>
<div class="info-item">
<span class="info-item-icon">🏔️</span>
<div class="info-item-content">
<div class="info-item-label">海拔</div>
<div class="info-item-value">约50m</div>
</div>
</div>
<div class="info-item">
<span class="info-item-icon">🌅</span>
<div class="info-item-content">
<div class="info-item-label">日出</div>
<div class="info-item-value">05:12</div>
</div>
</div>
<div class="info-item">
<span class="info-item-icon">🌇</span>
<div class="info-item-content">
<div class="info-item-label">日落</div>
<div class="info-item-value">19:12</div>
</div>
</div>
<div class="info-item">
<span class="info-item-icon">💰</span>
<div class="info-item-content">
<div class="info-item-label">价格类型</div>
<div class="info-item-value" style="color: var(--success);">免费</div>
</div>
</div>
<div class="info-item">
<span class="info-item-icon">📅</span>
<div class="info-item-content">
<div class="info-item-label">最佳时期</div>
<div class="info-item-value">6月中上旬</div>
</div>
</div>
</div>
</div>
<!-- 风险提示 -->
<div class="risk-banner">
<span class="risk-banner-icon">⚠️</span>
<span class="risk-banner-text">杨梅采摘注意防蚊虫叮咬,雨天山路湿滑请小心行走。杨梅不宜与黄瓜、牛奶同食。</span>
</div>
<div class="divider"></div>
<!-- 操作按钮区 -->
<div class="detail-section">
<div class="detail-section-title">⚡ 快捷操作</div>
<div class="action-buttons">
<div class="action-card" onclick="toggleAction(this, '已收藏到笔记')">
<span class="action-card-icon">🔖</span>
<span class="action-card-label">收藏笔记</span>
</div>
<div class="action-card" onclick="shareCard()">
<span class="action-card-icon">📤</span>
<span class="action-card-label">分享卡片</span>
</div>
<div class="action-card" onclick="showToast('正在跳转外部搜索…')">
<span class="action-card-icon">🔍</span>
<span class="action-card-label">外部搜索</span>
</div>
<div class="action-card" onclick="toggleAction(this, '标注已添加')">
<span class="action-card-icon">🏷️</span>
<span class="action-card-label">添加标注</span>
</div>
</div>
</div>
<div class="divider"></div>
<!-- 多选标注区 -->
<div class="annotation-area">
<div class="detail-section-title">🏷️ 选择标注</div>
<div class="annotation-grid" id="annotationGrid">
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🫐 杨梅</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🌿 初夏</span>
</div>
<div class="annotation-item checked" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🍇 水果</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🆓 免费</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">👨‍👩‍👧 亲子</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">📸 拍照</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🧺 采摘</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🚗 自驾</span>
</div>
<div class="annotation-item" onclick="toggleAnnotation(this)">
<div class="annotation-checkbox"></div>
<span class="annotation-label">🌧️ 雨季</span>
</div>
</div>
<!-- 已选标注展示 -->
<div class="selected-annotations" id="selectedAnnotations"></div>
</div>
<div class="divider"></div>
<!-- 外部搜索按钮组 -->
<div class="search-group">
<div class="detail-section-title">🌐 外部搜索</div>
<div class="search-grid">
<div class="search-btn" onclick="openExternal('百度')">
<span class="search-btn-icon">🔵</span>
<span class="search-btn-name">百度</span>
</div>
<div class="search-btn" onclick="openExternal('高德地图')">
<span class="search-btn-icon">🗺️</span>
<span class="search-btn-name">高德</span>
</div>
<div class="search-btn" onclick="openExternal('大众点评')">
<span class="search-btn-icon"></span>
<span class="search-btn-name">点评</span>
</div>
<div class="search-btn" onclick="openExternal('小红书')">
<span class="search-btn-icon">📕</span>
<span class="search-btn-name">小红书</span>
</div>
<div class="search-btn" onclick="openExternal('抖音')">
<span class="search-btn-icon">🎵</span>
<span class="search-btn-name">抖音</span>
</div>
</div>
</div>
<div class="divider"></div>
<!-- 相关推荐 -->
<div class="detail-section">
<div class="detail-section-title">💡 相关推荐</div>
<div class="recommend-list">
<div class="recommend-card" onclick="showToast('查看: 荔枝初上市')">
<div class="recommend-card-emoji">🫒</div>
<div class="recommend-card-title">荔枝初上市</div>
<div class="recommend-card-desc">妃子笑·桂味·糯米糍</div>
<span class="recommend-card-season rec-season-summer">☀️ 夏</span>
</div>
<div class="recommend-card" onclick="showToast('查看: 西湖夜游')">
<div class="recommend-card-emoji">🌙</div>
<div class="recommend-card-title">西湖夜游</div>
<div class="recommend-card-desc">印象西湖·断桥残雪</div>
<span class="recommend-card-season rec-season-summer">☀️ 夏</span>
</div>
<div class="recommend-card" onclick="showToast('查看: 龙井问茶')">
<div class="recommend-card-emoji">🍵</div>
<div class="recommend-card-title">龙井问茶</div>
<div class="recommend-card-desc">明前龙井·虎跑泉</div>
<span class="recommend-card-season rec-season-spring">🌿 春</span>
</div>
<div class="recommend-card" onclick="showToast('查看: 乌镇水乡')">
<div class="recommend-card-emoji">🏘️</div>
<div class="recommend-card-title">乌镇水乡</div>
<div class="recommend-card-desc">江南水乡·夜游西栅</div>
<span class="recommend-card-season rec-season-spring">🌿 春</span>
</div>
<div class="recommend-card" onclick="showToast('查看: 小龙虾')">
<div class="recommend-card-emoji">🦞</div>
<div class="recommend-card-title">小龙虾季</div>
<div class="recommend-card-desc">盱眙十三香·潜江油焖</div>
<span class="recommend-card-season rec-season-summer">☀️ 夏</span>
</div>
</div>
</div>
<!-- 底部安全区占位 -->
<div class="bottom-safe"></div>
</div>
</div>
</div><!-- /phone-frame -->
<!-- Toast -->
<div class="toast" id="toast"></div>
<!-- 调试面板 -->
<div class="debug-panel">
<button class="debug-btn" onclick="collapseSheet()">收起</button>
<button class="debug-btn" onclick="expandSheet()">展开</button>
<button class="debug-btn" onclick="toggleDarkMode()">🌓</button>
</div>
<div class="debug-info" id="debugInfo">Sheet: 33%</div>
<script>
/* ===== 常量与状态 ===== */
var MIN_HEIGHT_RATIO = 0.33;
var MAX_HEIGHT_RATIO = 0.90;
var DISMISS_RATIO = 0.15;
var sheet = document.getElementById('bottomSheet');
var overlay = document.getElementById('sheetOverlay');
var handle = document.getElementById('sheetHandle');
var content = document.getElementById('sheetContent');
var debugInfo = document.getElementById('debugInfo');
var currentRatio = MIN_HEIGHT_RATIO;
var isDragging = false;
var startY = 0;
var startHeight = 0;
/* ===== 初始化 ===== */
function init() {
setSheetHeight(currentRatio);
overlay.classList.add('visible', 'overlay-enter');
updateSelectedAnnotations();
}
/* ===== 设置Sheet高度 ===== */
function setSheetHeight(ratio) {
ratio = Math.max(MIN_HEIGHT_RATIO, Math.min(MAX_HEIGHT_RATIO, ratio));
currentRatio = ratio;
var vh = window.innerHeight;
var h = Math.round(vh * ratio);
sheet.style.height = h + 'px';
updateDebugInfo();
}
/* ===== 拖拽交互 ===== */
function onDragStart(clientY) {
isDragging = true;
startY = clientY;
startHeight = sheet.offsetHeight;
sheet.classList.add('dragging');
}
function onDragMove(clientY) {
if (!isDragging) return;
var deltaY = startY - clientY;
var newHeight = startHeight + deltaY;
var vh = window.innerHeight;
var newRatio = newHeight / vh;
setSheetHeight(newRatio);
}
function onDragEnd() {
if (!isDragging) return;
isDragging = false;
sheet.classList.remove('dragging');
if (currentRatio < DISMISS_RATIO) {
goBack();
} else if (currentRatio < 0.45) {
animateSheetTo(MIN_HEIGHT_RATIO);
} else if (currentRatio > 0.7) {
animateSheetTo(MAX_HEIGHT_RATIO);
} else {
animateSheetTo(currentRatio);
}
}
/* ===== 鼠标事件 ===== */
handle.addEventListener('mousedown', function(e) {
e.preventDefault();
onDragStart(e.clientY);
});
document.addEventListener('mousemove', function(e) {
onDragMove(e.clientY);
});
document.addEventListener('mouseup', function() {
onDragEnd();
});
/* ===== 触摸事件 ===== */
handle.addEventListener('touchstart', function(e) {
e.preventDefault();
onDragStart(e.touches[0].clientY);
}, { passive: false });
document.addEventListener('touchmove', function(e) {
if (isDragging) {
e.preventDefault();
onDragMove(e.touches[0].clientY);
}
}, { passive: false });
document.addEventListener('touchend', function() {
onDragEnd();
});
/* ===== 动画过渡到目标高度 ===== */
function animateSheetTo(targetRatio) {
sheet.classList.remove('dragging');
setSheetHeight(targetRatio);
}
/* ===== 快捷操作 ===== */
function expandSheet() {
animateSheetTo(MAX_HEIGHT_RATIO);
}
function collapseSheet() {
animateSheetTo(MIN_HEIGHT_RATIO);
}
/* ===== 返回 ===== */
function goBack() {
sheet.style.transition = 'transform 0.35s cubic-bezier(0.32, 0.72, 0, 1)';
sheet.style.transform = 'translateY(100%)';
overlay.style.transition = 'opacity 0.3s ease';
overlay.style.opacity = '0';
setTimeout(function() {
window.location.href = 'leisure_timeline.html';
}, 350);
}
/* ===== 暗色模式 ===== */
function toggleDarkMode() {
document.body.classList.toggle('dark-mode');
var btn = document.getElementById('darkBtn');
btn.innerHTML = document.body.classList.contains('dark-mode') ? '☀️' : '🌙';
}
/* ===== 操作按钮 ===== */
function toggleAction(el, msg) {
el.classList.toggle('active');
if (el.classList.contains('active')) {
showToast(msg || '已操作');
}
}
function shareCard() {
showToast('正在生成分享卡片…');
setTimeout(function() {
window.location.href = 'leisure_share_card.html';
}, 800);
}
/* ===== 标注交互 ===== */
function toggleAnnotation(el) {
el.classList.toggle('checked');
updateSelectedAnnotations();
}
function updateSelectedAnnotations() {
var container = document.getElementById('selectedAnnotations');
container.innerHTML = '';
var items = document.querySelectorAll('.annotation-item.checked');
items.forEach(function(item) {
var label = item.querySelector('.annotation-label').textContent;
var tag = document.createElement('span');
tag.className = 'selected-tag';
tag.innerHTML = label + ' <span class="selected-tag-remove" onclick="removeAnnotation(this, \'' + label.trim() + '\')">✕</span>';
container.appendChild(tag);
});
}
function removeAnnotation(removeEl, labelText) {
var items = document.querySelectorAll('.annotation-item.checked');
items.forEach(function(item) {
var label = item.querySelector('.annotation-label').textContent.trim();
if (label === labelText) {
item.classList.remove('checked');
}
});
updateSelectedAnnotations();
}
/* ===== 外部搜索 ===== */
function openExternal(platform) {
var urls = {
'百度': 'https://www.baidu.com/s?wd=仙居杨梅',
'高德地图': 'https://www.amap.com/search?query=仙居杨梅',
'大众点评': 'https://www.dianping.com/search/keyword/0/0_%E6%9D%A8%E6%A2%85',
'小红书': 'https://www.xiaohongshu.com/search_result?keyword=仙居杨梅',
'抖音': 'https://www.douyin.com/search/仙居杨梅'
};
showToast('🔍 ' + platform + ': 仙居杨梅\n' + (urls[platform] || ''));
}
/* ===== Toast ===== */
var toastTimer = null;
function showToast(msg) {
var toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(function() {
toast.classList.remove('show');
}, 2000);
}
/* ===== 调试信息 ===== */
function updateDebugInfo() {
var pct = Math.round(currentRatio * 100);
debugInfo.textContent = 'Sheet: ' + pct + '% | H: ' + sheet.offsetHeight + 'px';
}
/* ===== 窗口大小变化 ===== */
window.addEventListener('resize', function() {
setSheetHeight(currentRatio);
});
/* ===== 键盘快捷键 ===== */
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') goBack();
if (e.key === 'ArrowUp') expandSheet();
if (e.key === 'ArrowDown') collapseSheet();
});
/* ===== 启动 ===== */
init();
</script>
</body>
</html>