主要变更: 1. 重构多处Provider初始化逻辑,使用Future.microtask避免build阶段修改state 2. 重命名"灵感"模块为"工作流","天气诗词"改为"情景诗词" 3. 新增插件系统页面与路由,添加speech_to_text_windows插件支持 4. 优化玻璃容器性能,添加性能节流与模糊值适配 5. 新增离线横幅、阅读体验控制器、手势控制器等组件 6. 完善头像审核状态展示与缓存管理 7. 修复键盘弹出与页面路由问题 8. 更新依赖与项目配置,优化widget默认数据
1891 lines
74 KiB
HTML
1891 lines
74 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>闲言APP — 插件系统方案预览</title>
|
||
<style>
|
||
:root {
|
||
--primary: #007AFF;
|
||
--primary-light: rgba(0,122,255,0.1);
|
||
--bg: #F2F2F7;
|
||
--card-bg: #FFFFFF;
|
||
--text: #1C1C1E;
|
||
--text-secondary: #8E8E93;
|
||
--text-hint: #AEAEB2;
|
||
--separator: rgba(60,60,67,0.12);
|
||
--green: #34C759;
|
||
--orange: #FF9500;
|
||
--red: #FF3B30;
|
||
--purple: #AF52DE;
|
||
--radius-sm: 8px;
|
||
--radius-md: 12px;
|
||
--radius-lg: 16px;
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
|
||
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
|
||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
|
||
}
|
||
|
||
* { 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);
|
||
color: var(--text);
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
.preview-header {
|
||
background: linear-gradient(135deg, #007AFF, #5856D6);
|
||
color: white;
|
||
padding: 40px 20px 30px;
|
||
text-align: center;
|
||
}
|
||
.preview-header h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
|
||
.preview-header p { font-size: 15px; opacity: 0.85; }
|
||
|
||
.section-nav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
background: rgba(255,255,255,0.85);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border-bottom: 0.5px solid var(--separator);
|
||
padding: 12px 16px;
|
||
display: flex;
|
||
gap: 8px;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.section-nav::-webkit-scrollbar { display: none; }
|
||
.nav-chip {
|
||
flex-shrink: 0;
|
||
padding: 6px 14px;
|
||
border-radius: 20px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
background: var(--primary-light);
|
||
color: var(--primary);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
border: none;
|
||
}
|
||
.nav-chip:hover, .nav-chip.active {
|
||
background: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
.container { max-width: 860px; margin: 0 auto; padding: 20px 16px 60px; }
|
||
|
||
.section-title {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
margin: 32px 0 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.section-title .emoji { font-size: 26px; }
|
||
.section-desc {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 16px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* ===== Phone Frame ===== */
|
||
.phone-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||
gap: 24px;
|
||
margin-bottom: 32px;
|
||
}
|
||
.phone-frame {
|
||
background: #000;
|
||
border-radius: 40px;
|
||
padding: 12px;
|
||
box-shadow: var(--shadow-lg);
|
||
position: relative;
|
||
}
|
||
.phone-screen {
|
||
background: var(--bg);
|
||
border-radius: 30px;
|
||
overflow: hidden;
|
||
min-height: 640px;
|
||
position: relative;
|
||
}
|
||
.phone-label {
|
||
text-align: center;
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
margin-top: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ===== iOS Nav Bar ===== */
|
||
.ios-navbar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
background: rgba(242,242,247,0.85);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border-bottom: 0.5px solid var(--separator);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
.ios-navbar .back { color: var(--primary); font-size: 17px; cursor: pointer; }
|
||
.ios-navbar .title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; }
|
||
.ios-navbar .action { color: var(--primary); font-size: 17px; }
|
||
|
||
/* ===== Plugin List Page ===== */
|
||
.plugin-list { padding: 0 16px; }
|
||
.plugin-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 0;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
background: var(--card-bg);
|
||
}
|
||
.plugin-item:active { background: rgba(0,0,0,0.04); }
|
||
.plugin-item-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 14px 16px 0;
|
||
}
|
||
.plugin-icon {
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: var(--radius-md);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 26px;
|
||
flex-shrink: 0;
|
||
}
|
||
.plugin-icon.translate { background: linear-gradient(135deg, #007AFF, #5AC8FA); }
|
||
.plugin-icon.tts { background: linear-gradient(135deg, #AF52DE, #FF9500); }
|
||
.plugin-info { flex: 1; min-width: 0; }
|
||
.plugin-name-row { display: flex; align-items: center; gap: 6px; }
|
||
.plugin-name { font-size: 16px; font-weight: 600; }
|
||
.plugin-badge {
|
||
font-size: 10px;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.3px;
|
||
}
|
||
.plugin-badge.official { background: rgba(0,122,255,0.1); color: var(--primary); }
|
||
.plugin-badge.new { background: rgba(255,59,48,0.1); color: var(--red); }
|
||
.plugin-desc { font-size: 13px; color: var(--text-secondary); margin-top: 2px; }
|
||
.plugin-status {
|
||
font-size: 12px;
|
||
padding: 3px 8px;
|
||
border-radius: 10px;
|
||
font-weight: 500;
|
||
}
|
||
.plugin-status.on { background: rgba(52,199,89,0.12); color: var(--green); }
|
||
.plugin-status.off { background: rgba(142,142,147,0.12); color: var(--text-secondary); }
|
||
.plugin-chevron { color: var(--text-hint); font-size: 14px; }
|
||
.plugin-preview {
|
||
margin: 10px 16px 12px;
|
||
border-radius: var(--radius-sm);
|
||
overflow: hidden;
|
||
height: 100px;
|
||
position: relative;
|
||
}
|
||
.plugin-preview.translate-preview {
|
||
background: linear-gradient(135deg, #007AFF 0%, #5AC8FA 50%, #64D2FF 100%);
|
||
}
|
||
.plugin-preview.tts-preview {
|
||
background: linear-gradient(135deg, #AF52DE 0%, #FF9500 50%, #FF6B6B 100%);
|
||
}
|
||
.plugin-preview-content {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
padding: 12px;
|
||
gap: 12px;
|
||
}
|
||
.plugin-preview-mock {
|
||
background: rgba(255,255,255,0.2);
|
||
border-radius: 8px;
|
||
padding: 8px 12px;
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
max-width: 45%;
|
||
}
|
||
.plugin-preview-mock .mock-label {
|
||
font-size: 10px;
|
||
opacity: 0.8;
|
||
margin-bottom: 2px;
|
||
}
|
||
.plugin-preview-mock .mock-text {
|
||
font-weight: 500;
|
||
}
|
||
.plugin-item-bottom {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 16px 14px;
|
||
}
|
||
.plugin-meta {
|
||
font-size: 12px;
|
||
color: var(--text-hint);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* ===== Plugin Detail Page ===== */
|
||
.plugin-detail-header {
|
||
padding: 20px 16px;
|
||
text-align: center;
|
||
background: var(--card-bg);
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
.plugin-detail-icon {
|
||
width: 72px;
|
||
height: 72px;
|
||
border-radius: 18px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 36px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.plugin-detail-name { font-size: 22px; font-weight: 700; }
|
||
.plugin-detail-version { font-size: 13px; color: var(--text-secondary); margin-top: 4px; }
|
||
|
||
.plugin-enable-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 16px;
|
||
background: var(--card-bg);
|
||
margin-top: 8px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
.plugin-enable-label { font-size: 16px; font-weight: 500; }
|
||
|
||
/* iOS Toggle */
|
||
.ios-toggle {
|
||
width: 51px;
|
||
height: 31px;
|
||
border-radius: 16px;
|
||
background: #E5E5EA;
|
||
position: relative;
|
||
cursor: pointer;
|
||
transition: background 0.3s;
|
||
}
|
||
.ios-toggle.on { background: var(--green); }
|
||
.ios-toggle .knob {
|
||
width: 27px;
|
||
height: 27px;
|
||
border-radius: 50%;
|
||
background: white;
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 2px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.15);
|
||
transition: transform 0.3s;
|
||
}
|
||
.ios-toggle.on .knob { transform: translateX(20px); }
|
||
|
||
/* Preview Banner */
|
||
.preview-banner {
|
||
margin: 12px 16px;
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
position: relative;
|
||
height: 160px;
|
||
}
|
||
.preview-banner.translate-banner {
|
||
background: linear-gradient(135deg, #007AFF 0%, #5AC8FA 50%, #64D2FF 100%);
|
||
}
|
||
.preview-banner.tts-banner {
|
||
background: linear-gradient(135deg, #AF52DE 0%, #FF9500 50%, #FF6B6B 100%);
|
||
}
|
||
.preview-banner-content {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
text-align: center;
|
||
padding: 16px;
|
||
}
|
||
.preview-banner-emoji { font-size: 40px; margin-bottom: 8px; }
|
||
.preview-banner-title { font-size: 18px; font-weight: 700; }
|
||
.preview-banner-subtitle { font-size: 13px; opacity: 0.85; margin-top: 4px; }
|
||
|
||
/* Plugin Config Section */
|
||
.config-section {
|
||
margin-top: 8px;
|
||
background: var(--card-bg);
|
||
}
|
||
.config-section-title {
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
padding: 8px 16px 4px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.config-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
.config-item:last-child { border-bottom: none; }
|
||
.config-item-left { display: flex; align-items: center; gap: 10px; }
|
||
.config-item-icon { font-size: 20px; width: 28px; text-align: center; }
|
||
.config-item-label { font-size: 16px; }
|
||
.config-item-value { font-size: 15px; color: var(--text-secondary); display: flex; align-items: center; gap: 4px; }
|
||
|
||
/* ===== Card Preview ===== */
|
||
.card-preview-area { padding: 16px; }
|
||
|
||
.daily-card {
|
||
background: linear-gradient(135deg, rgba(0,122,255,0.08), rgba(88,86,214,0.06));
|
||
border-radius: var(--radius-lg);
|
||
padding: 20px;
|
||
margin-bottom: 16px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.daily-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -30px;
|
||
right: -30px;
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 50%;
|
||
background: rgba(0,122,255,0.06);
|
||
}
|
||
.daily-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 12px;
|
||
}
|
||
.daily-card-text {
|
||
font-size: 17px;
|
||
font-weight: 500;
|
||
line-height: 1.7;
|
||
margin-bottom: 16px;
|
||
}
|
||
.daily-card-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.daily-card-author {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
}
|
||
.card-action-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
border: none;
|
||
background: transparent;
|
||
position: relative;
|
||
}
|
||
.card-action-btn:active { transform: scale(0.9); }
|
||
.card-action-btn.translate-btn { color: var(--primary); }
|
||
.card-action-btn.tts-btn { color: var(--purple); }
|
||
.card-action-btn.fav-btn { color: var(--orange); }
|
||
.card-action-btn.like-btn { color: var(--red); }
|
||
.card-action-btn.active { background: rgba(0,122,255,0.1); }
|
||
|
||
/* Sentence Card */
|
||
.sentence-card {
|
||
background: var(--card-bg);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
margin-bottom: 12px;
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
.sentence-card-inner {
|
||
display: flex;
|
||
}
|
||
.sentence-card-accent {
|
||
width: 3px;
|
||
background: var(--primary);
|
||
margin: 12px 0;
|
||
border-radius: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
.sentence-card-content {
|
||
flex: 1;
|
||
padding: 12px 14px;
|
||
}
|
||
.sentence-card-feed {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 6px;
|
||
}
|
||
.sentence-card-text {
|
||
font-size: 15px;
|
||
line-height: 1.6;
|
||
margin-bottom: 8px;
|
||
}
|
||
.sentence-card-stats {
|
||
display: flex;
|
||
gap: 12px;
|
||
font-size: 12px;
|
||
color: var(--text-hint);
|
||
margin-bottom: 8px;
|
||
}
|
||
.sentence-card-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.sentence-card-author {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* ===== Popup / Sheet ===== */
|
||
.popup-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.3);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
z-index: 50;
|
||
border-radius: 30px;
|
||
overflow: hidden;
|
||
}
|
||
.popup-sheet {
|
||
width: 100%;
|
||
background: var(--card-bg);
|
||
border-radius: 16px 16px 0 0;
|
||
padding: 0 0 20px;
|
||
max-height: 70%;
|
||
overflow-y: auto;
|
||
}
|
||
.popup-handle {
|
||
width: 36px;
|
||
height: 5px;
|
||
border-radius: 3px;
|
||
background: rgba(0,0,0,0.15);
|
||
margin: 8px auto 12px;
|
||
}
|
||
.popup-title {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
padding: 4px 16px 12px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
.popup-body { padding: 16px; }
|
||
|
||
.translate-result {
|
||
background: var(--primary-light);
|
||
border-radius: var(--radius-md);
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.translate-result-label {
|
||
font-size: 12px;
|
||
color: var(--primary);
|
||
font-weight: 600;
|
||
margin-bottom: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.translate-result-text {
|
||
font-size: 16px;
|
||
line-height: 1.6;
|
||
}
|
||
.translate-original {
|
||
background: rgba(142,142,147,0.08);
|
||
border-radius: var(--radius-md);
|
||
padding: 12px;
|
||
}
|
||
.translate-original-label {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 4px;
|
||
}
|
||
.translate-original-text {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* TTS Player */
|
||
.tts-player {
|
||
background: linear-gradient(135deg, rgba(175,82,222,0.08), rgba(255,149,0,0.06));
|
||
border-radius: var(--radius-md);
|
||
padding: 16px;
|
||
}
|
||
.tts-player-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.tts-player-title { font-size: 15px; font-weight: 600; }
|
||
.tts-player-text {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
line-height: 1.5;
|
||
margin-bottom: 12px;
|
||
max-height: 60px;
|
||
overflow: hidden;
|
||
}
|
||
.tts-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.tts-btn {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all 0.2s;
|
||
}
|
||
.tts-btn:active { transform: scale(0.9); }
|
||
.tts-btn.play {
|
||
background: var(--purple);
|
||
color: white;
|
||
width: 48px;
|
||
height: 48px;
|
||
font-size: 22px;
|
||
}
|
||
.tts-btn.secondary {
|
||
background: rgba(175,82,222,0.1);
|
||
color: var(--purple);
|
||
}
|
||
.tts-progress {
|
||
flex: 1;
|
||
height: 4px;
|
||
background: rgba(175,82,222,0.15);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
.tts-progress-fill {
|
||
height: 100%;
|
||
width: 35%;
|
||
background: var(--purple);
|
||
border-radius: 2px;
|
||
transition: width 0.3s;
|
||
}
|
||
.tts-speed-label {
|
||
font-size: 12px;
|
||
color: var(--purple);
|
||
font-weight: 600;
|
||
padding: 4px 10px;
|
||
background: rgba(175,82,222,0.1);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* ===== Settings Entry ===== */
|
||
.settings-entry {
|
||
background: var(--card-bg);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
margin-bottom: 16px;
|
||
}
|
||
.settings-group-title {
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
padding: 8px 16px 4px;
|
||
}
|
||
.settings-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 16px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
cursor: pointer;
|
||
}
|
||
.settings-item:last-child { border-bottom: none; }
|
||
.settings-item:active { background: rgba(0,0,0,0.04); }
|
||
.settings-item-icon {
|
||
width: 30px;
|
||
height: 30px;
|
||
border-radius: 7px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
color: white;
|
||
}
|
||
.settings-item-label { flex: 1; font-size: 16px; }
|
||
.settings-item-arrow { color: var(--text-hint); font-size: 14px; }
|
||
|
||
/* ===== Flow Diagram ===== */
|
||
.flow-diagram {
|
||
background: var(--card-bg);
|
||
border-radius: var(--radius-md);
|
||
padding: 24px;
|
||
margin-bottom: 24px;
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
.flow-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
text-align: center;
|
||
}
|
||
.flow-steps {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.flow-step {
|
||
padding: 8px 16px;
|
||
border-radius: var(--radius-sm);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
}
|
||
.flow-step.page { background: var(--primary-light); color: var(--primary); }
|
||
.flow-step.action { background: rgba(52,199,89,0.1); color: var(--green); }
|
||
.flow-step.data { background: rgba(175,82,222,0.1); color: var(--purple); }
|
||
.flow-arrow { color: var(--text-hint); font-size: 18px; }
|
||
|
||
/* ===== Feature Tags ===== */
|
||
.feature-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.feature-tag {
|
||
padding: 6px 12px;
|
||
border-radius: 16px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
.feature-tag.blue { background: var(--primary-light); color: var(--primary); }
|
||
.feature-tag.green { background: rgba(52,199,89,0.1); color: var(--green); }
|
||
.feature-tag.purple { background: rgba(175,82,222,0.1); color: var(--purple); }
|
||
.feature-tag.orange { background: rgba(255,149,0,0.1); color: var(--orange); }
|
||
|
||
/* ===== Comparison Table ===== */
|
||
.comparison-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
background: var(--card-bg);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
margin-bottom: 24px;
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
.comparison-table th {
|
||
background: rgba(0,122,255,0.06);
|
||
padding: 10px 14px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
text-align: left;
|
||
color: var(--primary);
|
||
}
|
||
.comparison-table td {
|
||
padding: 10px 14px;
|
||
font-size: 14px;
|
||
border-bottom: 0.5px solid var(--separator);
|
||
}
|
||
.comparison-table tr:last-child td { border-bottom: none; }
|
||
|
||
/* ===== Annotation ===== */
|
||
.annotation {
|
||
background: rgba(255,149,0,0.08);
|
||
border-left: 3px solid var(--orange);
|
||
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
||
padding: 12px 16px;
|
||
margin-bottom: 16px;
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
}
|
||
.annotation strong { color: var(--orange); }
|
||
|
||
/* ===== Responsive ===== */
|
||
@media (max-width: 640px) {
|
||
.phone-grid { grid-template-columns: 1fr; }
|
||
.phone-frame { max-width: 380px; margin: 0 auto; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ===== Header ===== -->
|
||
<div class="preview-header">
|
||
<h1>🧩 闲言插件系统</h1>
|
||
<p>翻译守护 · 文本朗读 — 方案预览与交互设计</p>
|
||
</div>
|
||
|
||
<!-- ===== Nav ===== -->
|
||
<div class="section-nav">
|
||
<button class="nav-chip active" onclick="scrollToSection('overview')">📋 总览</button>
|
||
<button class="nav-chip" onclick="scrollToSection('plugin-list')">🧩 插件列表</button>
|
||
<button class="nav-chip" onclick="scrollToSection('translate-detail')">🌐 翻译守护</button>
|
||
<button class="nav-chip" onclick="scrollToSection('tts-detail')">🔊 文本朗读</button>
|
||
<button class="nav-chip" onclick="scrollToSection('card-integration')">🃏 卡片集成</button>
|
||
<button class="nav-chip" onclick="scrollToSection('popup-demo')">💬 弹窗交互</button>
|
||
<button class="nav-chip" onclick="scrollToSection('architecture')">🏗️ 架构设计</button>
|
||
<button class="nav-chip" onclick="scrollToSection('api-test')">🔌 接口方案</button>
|
||
</div>
|
||
|
||
<div class="container">
|
||
|
||
<!-- ===== Section 1: Overview ===== -->
|
||
<div id="overview">
|
||
<div class="section-title"><span class="emoji">📋</span> 方案总览</div>
|
||
<div class="section-desc">
|
||
在通用设置页新增「插件」入口,点击进入插件管理页面。插件页面参考微信小程序/插件列表风格,每个插件卡片包含预览图、启用开关、功能说明和配置项。启用后的插件会在主页句子卡片和句子广场中注入功能按钮。
|
||
</div>
|
||
|
||
<div class="feature-tags">
|
||
<span class="feature-tag blue">🌐 翻译守护</span>
|
||
<span class="feature-tag purple">🔊 文本朗读</span>
|
||
<span class="feature-tag green">⚡ 插件化架构</span>
|
||
<span class="feature-tag orange">🔄 跨平台兼容</span>
|
||
<span class="feature-tag blue">📱 iOS风格</span>
|
||
<span class="feature-tag purple">🧩 可扩展</span>
|
||
</div>
|
||
|
||
<div class="flow-diagram">
|
||
<div class="flow-title">用户操作流程</div>
|
||
<div class="flow-steps">
|
||
<span class="flow-step page">通用设置</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step page">插件管理</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step action">启用插件</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step data">配置参数</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step page">主页/广场</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step action">使用功能</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="annotation">
|
||
<strong>设计原则:</strong>插件启用前,主页卡片不显示任何插件按钮(零侵入);启用后,按钮以动画方式淡入,保持界面简洁。插件状态通过 Riverpod 持久化到 KvStorage,跨平台统一。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Section 2: Plugin List Page ===== -->
|
||
<div id="plugin-list">
|
||
<div class="section-title"><span class="emoji">🧩</span> 插件列表页</div>
|
||
<div class="section-desc">参考微信「设置 → 插件」页面风格,iOS 原生列表布局,每个插件显示图标、名称、简介和启用状态。</div>
|
||
|
||
<div class="phone-grid">
|
||
<!-- Phone 1: 通用设置入口 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 返回</span>
|
||
<span class="title">通用设置</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
<div style="padding: 0 16px;">
|
||
<div class="settings-group-title">功能扩展</div>
|
||
<div class="settings-entry">
|
||
<div class="settings-item" style="border-bottom: none;">
|
||
<div class="settings-item-icon" style="background: linear-gradient(135deg, #007AFF, #AF52DE);">🧩</div>
|
||
<span class="settings-item-label">插件</span>
|
||
<span style="font-size:13px;color:var(--text-secondary);margin-right:4px;">2个可用</span>
|
||
<span class="settings-item-arrow">›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group-title" style="margin-top:16px;">交互设置</div>
|
||
<div class="settings-entry">
|
||
<div class="settings-item">
|
||
<div class="settings-item-icon" style="background:#007AFF;">🔊</div>
|
||
<span class="settings-item-label">声音</span>
|
||
<div class="ios-toggle on"><div class="knob"></div></div>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-icon" style="background:#5856D6;">📳</div>
|
||
<span class="settings-item-label">震动</span>
|
||
<span class="settings-item-arrow">›</span>
|
||
</div>
|
||
<div class="settings-item" style="border-bottom: none;">
|
||
<div class="settings-item-icon" style="background:#34C759;">🎵</div>
|
||
<span class="settings-item-label">音效反馈</span>
|
||
<div class="ios-toggle"><div class="knob"></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">通用设置 — 新增「插件」入口</div>
|
||
</div>
|
||
|
||
<!-- Phone 2: 插件列表 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 通用设置</span>
|
||
<span class="title">插件</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
<div class="plugin-list">
|
||
<div style="padding:12px 0 6px;font-size:13px;color:var(--text-secondary);">可用插件</div>
|
||
|
||
<div class="plugin-item" style="border-radius:var(--radius-md);margin-bottom:12px;overflow:hidden;">
|
||
<div class="plugin-item-top">
|
||
<div class="plugin-icon translate">🌐</div>
|
||
<div class="plugin-info">
|
||
<div class="plugin-name-row">
|
||
<span class="plugin-name">翻译守护</span>
|
||
<span class="plugin-badge official">官方</span>
|
||
</div>
|
||
<div class="plugin-desc">实时翻译句子内容,支持多语言</div>
|
||
</div>
|
||
<span class="plugin-status on">已启用</span>
|
||
</div>
|
||
<div class="plugin-preview translate-preview">
|
||
<div class="plugin-preview-content">
|
||
<div class="plugin-preview-mock">
|
||
<div class="mock-label">原文</div>
|
||
<div class="mock-text">山有木兮木有枝</div>
|
||
</div>
|
||
<div style="font-size:20px;">→</div>
|
||
<div class="plugin-preview-mock">
|
||
<div class="mock-label">English</div>
|
||
<div class="mock-text">The mountains have trees...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="plugin-item-bottom">
|
||
<div class="plugin-meta">
|
||
<span>v1.0.0</span>
|
||
<span>·</span>
|
||
<span>翻译 128 次</span>
|
||
</div>
|
||
<span class="plugin-chevron">›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plugin-item" style="border-radius:var(--radius-md);margin-bottom:12px;overflow:hidden;">
|
||
<div class="plugin-item-top">
|
||
<div class="plugin-icon tts">🔊</div>
|
||
<div class="plugin-info">
|
||
<div class="plugin-name-row">
|
||
<span class="plugin-name">文本朗读</span>
|
||
<span class="plugin-badge official">官方</span>
|
||
<span class="plugin-badge new">NEW</span>
|
||
</div>
|
||
<div class="plugin-desc">语音朗读句子,TTS + 在线双引擎</div>
|
||
</div>
|
||
<span class="plugin-status off">未启用</span>
|
||
</div>
|
||
<div class="plugin-preview tts-preview">
|
||
<div class="plugin-preview-content">
|
||
<div class="plugin-preview-mock">
|
||
<div class="mock-label">正在朗读</div>
|
||
<div class="mock-text">🎙️ 人生如逆旅...</div>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;align-items:center;gap:4px;">
|
||
<div style="width:36px;height:36px;background:rgba(255,255,255,0.3);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;">▶</div>
|
||
<div style="font-size:10px;opacity:0.8;">0:03 / 0:08</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="plugin-item-bottom">
|
||
<div class="plugin-meta">
|
||
<span>v1.0.0</span>
|
||
<span>·</span>
|
||
<span>朗读 0 次</span>
|
||
</div>
|
||
<span class="plugin-chevron">›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:20px 0 6px;font-size:13px;color:var(--text-secondary);">关于插件</div>
|
||
<div style="padding:12px 16px;background:var(--card-bg);border-radius:var(--radius-md);font-size:14px;color:var(--text-secondary);line-height:1.6;">
|
||
插件为闲言提供可选的增强功能。启用后,相关功能按钮会出现在句子卡片上。你可以随时关闭插件,关闭后按钮会自动隐藏。<br><br>⚠️ 部分插件需要网络连接,无网络时将显示暂无可用插件。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">插件列表页(有网络)</div>
|
||
</div>
|
||
|
||
<!-- Phone: 无网络状态 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 通用设置</span>
|
||
<span class="title">插件</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:400px;padding:40px;">
|
||
<div style="font-size:56px;margin-bottom:16px;">📡</div>
|
||
<div style="font-size:17px;font-weight:600;color:var(--text);margin-bottom:6px;">暂无可用插件</div>
|
||
<div style="font-size:14px;color:var(--text-secondary);text-align:center;line-height:1.5;">当前无网络连接,插件功能需要网络支持。请检查网络设置后重试。</div>
|
||
<button style="margin-top:20px;padding:10px 24px;background:var(--primary);color:white;border:none;border-radius:20px;font-size:15px;font-weight:500;cursor:pointer;">重新加载</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">插件列表页(无网络)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Section 3: Translate Plugin Detail ===== -->
|
||
<div id="translate-detail">
|
||
<div class="section-title"><span class="emoji">🌐</span> 翻译守护 — 插件详情</div>
|
||
<div class="section-desc">点击翻译守护进入详情页,展示预览图、启用开关、翻译引擎选择、目标语言配置等。</div>
|
||
|
||
<div class="phone-grid">
|
||
<!-- Phone 3: 翻译守护详情 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 插件</span>
|
||
<span class="title">翻译守护</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div class="plugin-detail-header">
|
||
<div class="plugin-detail-icon translate-banner" style="border-radius:18px;">🌐</div>
|
||
<div class="plugin-detail-name">翻译守护</div>
|
||
<div class="plugin-detail-version">v1.0.0 · 翻译增强插件</div>
|
||
</div>
|
||
|
||
<div class="preview-banner translate-banner">
|
||
<div class="preview-banner-content">
|
||
<div class="preview-banner-emoji">🌍</div>
|
||
<div class="preview-banner-title">一键翻译,跨越语言</div>
|
||
<div class="preview-banner-subtitle">支持 30+ 语言 · 自动检测 · 多引擎降级</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plugin-enable-row">
|
||
<span class="plugin-enable-label">启用翻译守护</span>
|
||
<div class="ios-toggle on" onclick="this.classList.toggle('on')"><div class="knob"></div></div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">使用预览</div>
|
||
<div style="padding:12px 16px;">
|
||
<div style="background:linear-gradient(135deg, rgba(0,122,255,0.08), rgba(88,86,214,0.06));border-radius:var(--radius-md);padding:16px;">
|
||
<div style="font-size:15px;line-height:1.7;margin-bottom:12px;">山有木兮木有枝,心悦君兮君不知。</div>
|
||
<div style="display:flex;align-items:center;gap:6px;">
|
||
<span style="flex:1;font-size:13px;color:var(--text-secondary);">—— 越人歌</span>
|
||
<span style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:16px;">🌐</span>
|
||
<span style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:var(--purple);font-size:16px;">🔊</span>
|
||
<span style="font-size:16px;">⭐</span>
|
||
<span style="font-size:16px;">❤️</span>
|
||
</div>
|
||
<div style="margin-top:12px;padding:10px;background:rgba(0,122,255,0.08);border-radius:8px;">
|
||
<div style="font-size:11px;color:var(--primary);font-weight:600;margin-bottom:4px;">🌐 英文翻译 · Bing</div>
|
||
<div style="font-size:14px;line-height:1.5;">The mountains have trees and the trees have branches, my heart delights in you but you do not know.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">翻译设置</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🔧</span>
|
||
<span class="config-item-label">翻译引擎</span>
|
||
</div>
|
||
<span class="config-item-value">Bing ›</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🎯</span>
|
||
<span class="config-item-label">目标语言</span>
|
||
</div>
|
||
<span class="config-item-value">简体中文 ›</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">⚡</span>
|
||
<span class="config-item-label">自动检测源语言</span>
|
||
</div>
|
||
<div class="ios-toggle on" onclick="this.classList.toggle('on')" style="transform:scale(0.8);"><div class="knob"></div></div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🔄</span>
|
||
<span class="config-item-label">降级策略</span>
|
||
</div>
|
||
<span class="config-item-value">Bing→MyMemory→Google ›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">显示设置</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">📍</span>
|
||
<span class="config-item-label">按钮位置</span>
|
||
</div>
|
||
<span class="config-item-value">爱心左侧 ›</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">✨</span>
|
||
<span class="config-item-label">自动翻译</span>
|
||
</div>
|
||
<div class="ios-toggle" onclick="this.classList.toggle('on')" style="transform:scale(0.8);"><div class="knob"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">翻译记录</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">📊</span>
|
||
<span class="config-item-label">累计翻译</span>
|
||
</div>
|
||
<span class="config-item-value">128 次</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🕐</span>
|
||
<div>
|
||
<span class="config-item-label" style="font-size:14px;">山有木兮木有枝...</span>
|
||
<div style="font-size:11px;color:var(--text-hint);">→ The mountains have trees... · 3分钟前</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🕐</span>
|
||
<div>
|
||
<span class="config-item-label" style="font-size:14px;">人生如逆旅...</span>
|
||
<div style="font-size:11px;color:var(--text-hint);">→ Life is like a journey... · 1小时前</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🗑️</span>
|
||
<span class="config-item-label" style="color:var(--red);">清除翻译记录</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">使用场景</div>
|
||
<div style="padding:8px 16px 12px;">
|
||
<div style="display:flex;flex-wrap:wrap;gap:8px;">
|
||
<span style="padding:6px 12px;background:var(--primary-light);color:var(--primary);border-radius:16px;font-size:13px;font-weight:500;">🃏 每日推荐卡片</span>
|
||
<span style="padding:6px 12px;background:var(--primary-light);color:var(--primary);border-radius:16px;font-size:13px;font-weight:500;">📖 句子广场列表</span>
|
||
<span style="padding:6px 12px;background:rgba(142,142,147,0.08);color:var(--text-secondary);border-radius:16px;font-size:13px;font-weight:500;">📝 笔记编辑 (即将支持)</span>
|
||
<span style="padding:6px 12px;background:rgba(142,142,147,0.08);color:var(--text-secondary);border-radius:16px;font-size:13px;font-weight:500;">🔍 搜索结果 (即将支持)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">反馈与建议</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">💬</span>
|
||
<span class="config-item-label">提交意见</span>
|
||
</div>
|
||
<span class="config-item-value">帮助我们改进 ›</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">翻译守护 — 插件详情页</div>
|
||
</div>
|
||
|
||
<!-- Phone 4: 翻译引擎选择 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 翻译守护</span>
|
||
<span class="title">翻译引擎</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div style="padding:16px;">
|
||
<div style="font-size:13px;color:var(--text-secondary);margin-bottom:12px;">选择首选翻译引擎,失败时自动降级</div>
|
||
|
||
<div style="background:var(--card-bg);border-radius:var(--radius-md);overflow:hidden;">
|
||
<div class="config-item" style="background:var(--primary-light);">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🔷</span>
|
||
<div>
|
||
<div class="config-item-label">Bing Translator</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">免费 · 自动检测 · 70+语言</div>
|
||
</div>
|
||
</div>
|
||
<span style="color:var(--primary);font-size:18px;">✓</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🟢</span>
|
||
<div>
|
||
<div class="config-item-label">Google Translate</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">免费 · 自动检测 · 100+语言</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🟡</span>
|
||
<div>
|
||
<div class="config-item-label">MyMemory</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">免费 · 无需Key · 不支持自动检测</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🟠</span>
|
||
<div>
|
||
<div class="config-item-label">AppWorlds</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">国内直连 · 免费 · 2秒频率限制</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🟣</span>
|
||
<div>
|
||
<div class="config-item-label">LibreTranslate</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">开源免费 · 自动检测 · 较慢</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">⚙️</span>
|
||
<div>
|
||
<div class="config-item-label">自定义 API</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">配置自己的翻译接口</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">翻译引擎选择</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Section 4: TTS Plugin Detail ===== -->
|
||
<div id="tts-detail">
|
||
<div class="section-title"><span class="emoji">🔊</span> 文本朗读 — 插件详情</div>
|
||
<div class="section-desc">文本朗读插件提供两种引擎:系统 TTS(离线)和在线语音合成(需网络),用户可自由切换。</div>
|
||
|
||
<div class="phone-grid">
|
||
<!-- Phone 5: 文本朗读详情 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 插件</span>
|
||
<span class="title">文本朗读</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div class="plugin-detail-header">
|
||
<div class="plugin-detail-icon tts-banner" style="border-radius:18px;">🔊</div>
|
||
<div class="plugin-detail-name">文本朗读</div>
|
||
<div class="plugin-detail-version">v1.0.0 · 语音增强插件</div>
|
||
</div>
|
||
|
||
<div class="preview-banner tts-banner">
|
||
<div class="preview-banner-content">
|
||
<div class="preview-banner-emoji">🎙️</div>
|
||
<div class="preview-banner-title">让文字被听见</div>
|
||
<div class="preview-banner-subtitle">TTS离线引擎 + 在线语音合成 · 双引擎切换</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plugin-enable-row">
|
||
<span class="plugin-enable-label">启用文本朗读</span>
|
||
<div class="ios-toggle on" onclick="this.classList.toggle('on')"><div class="knob"></div></div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">朗读引擎</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">📱</span>
|
||
<div>
|
||
<span class="config-item-label">系统 TTS</span>
|
||
<div style="font-size:11px;color:var(--green);">推荐 · 离线可用</div>
|
||
</div>
|
||
</div>
|
||
<span style="color:var(--primary);font-size:18px;">✓</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">☁️</span>
|
||
<div>
|
||
<span class="config-item-label">在线语音合成</span>
|
||
<div style="font-size:11px;color:var(--text-secondary);">需网络 · 音质更佳</div>
|
||
</div>
|
||
</div>
|
||
<span class="config-item-value">›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">朗读设置</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🗣️</span>
|
||
<span class="config-item-label">语速</span>
|
||
</div>
|
||
<span class="config-item-value">正常 ›</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🎵</span>
|
||
<span class="config-item-label">音调</span>
|
||
</div>
|
||
<span class="config-item-value">标准 ›</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🔉</span>
|
||
<span class="config-item-label">音量</span>
|
||
</div>
|
||
<span class="config-item-value">80% ›</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🇨🇳</span>
|
||
<span class="config-item-label">朗读语言</span>
|
||
</div>
|
||
<span class="config-item-value">中文 ›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">使用预览</div>
|
||
<div style="padding:12px 16px;">
|
||
<div style="background:linear-gradient(135deg, rgba(175,82,222,0.08), rgba(255,149,0,0.06));border-radius:var(--radius-md);padding:16px;">
|
||
<div style="font-size:15px;line-height:1.7;margin-bottom:12px;">人生如逆旅,我亦是行人。</div>
|
||
<div style="display:flex;align-items:center;gap:6px;">
|
||
<span style="flex:1;font-size:13px;color:var(--text-secondary);">—— 苏轼</span>
|
||
<span style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:16px;">🌐</span>
|
||
<span style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:var(--purple);font-size:16px;">🔊</span>
|
||
<span style="font-size:16px;">⭐</span>
|
||
<span style="font-size:16px;">❤️</span>
|
||
</div>
|
||
<div style="margin-top:12px;background:rgba(175,82,222,0.08);border-radius:8px;padding:12px;">
|
||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
||
<span style="font-size:16px;">🎙️</span>
|
||
<span style="font-size:13px;font-weight:600;">正在朗读</span>
|
||
<span style="font-size:11px;padding:2px 8px;background:rgba(175,82,222,0.12);color:var(--purple);border-radius:8px;">1.0x</span>
|
||
</div>
|
||
<div style="height:4px;background:rgba(175,82,222,0.15);border-radius:2px;">
|
||
<div style="width:35%;height:100%;background:var(--purple);border-radius:2px;"></div>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--text-hint);margin-top:4px;">
|
||
<span>0:03</span><span>0:08</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">朗读记录</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">📊</span>
|
||
<span class="config-item-label">累计朗读</span>
|
||
</div>
|
||
<span class="config-item-value">56 次</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🕐</span>
|
||
<div>
|
||
<span class="config-item-label" style="font-size:14px;">人生如逆旅,我亦是行人。</span>
|
||
<div style="font-size:11px;color:var(--text-hint);">系统TTS · 5分钟前</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🕐</span>
|
||
<div>
|
||
<span class="config-item-label" style="font-size:14px;">山有木兮木有枝...</span>
|
||
<div style="font-size:11px;color:var(--text-hint);">Edge TTS · 30分钟前</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🗑️</span>
|
||
<span class="config-item-label" style="color:var(--red);">清除朗读记录</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">使用场景</div>
|
||
<div style="padding:8px 16px 12px;">
|
||
<div style="display:flex;flex-wrap:wrap;gap:8px;">
|
||
<span style="padding:6px 12px;background:rgba(175,82,222,0.1);color:var(--purple);border-radius:16px;font-size:13px;font-weight:500;">🃏 每日推荐卡片</span>
|
||
<span style="padding:6px 12px;background:rgba(175,82,222,0.1);color:var(--purple);border-radius:16px;font-size:13px;font-weight:500;">📖 句子广场列表</span>
|
||
<span style="padding:6px 12px;background:rgba(142,142,147,0.08);color:var(--text-secondary);border-radius:16px;font-size:13px;font-weight:500;">📝 笔记编辑 (即将支持)</span>
|
||
<span style="padding:6px 12px;background:rgba(142,142,147,0.08);color:var(--text-secondary);border-radius:16px;font-size:13px;font-weight:500;">🌐 翻译结果朗读 (即将支持)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-section">
|
||
<div class="config-section-title">反馈与建议</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">💬</span>
|
||
<span class="config-item-label">提交意见</span>
|
||
</div>
|
||
<span class="config-item-value">帮助我们改进 ›</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">文本朗读 — 插件详情页</div>
|
||
</div>
|
||
|
||
<!-- Phone 6: 在线引擎选择 -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back">‹ 文本朗读</span>
|
||
<span class="title">在线语音合成</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div style="padding:16px;">
|
||
<div style="font-size:13px;color:var(--text-secondary);margin-bottom:12px;">选择在线语音合成服务(需网络连接)</div>
|
||
|
||
<div style="background:var(--card-bg);border-radius:var(--radius-md);overflow:hidden;">
|
||
<div class="config-item" style="background:rgba(175,82,222,0.06);">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🤖</span>
|
||
<div>
|
||
<div class="config-item-label">Edge TTS</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">微软Edge在线语音 · 高音质 · 免费</div>
|
||
</div>
|
||
</div>
|
||
<span style="color:var(--purple);font-size:18px;">✓</span>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🇨🇳</span>
|
||
<div>
|
||
<div class="config-item-label">百度语音合成</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">国内直连 · 需申请Key · 高音质</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">⚙️</span>
|
||
<div>
|
||
<div class="config-item-label">自定义 API</div>
|
||
<div style="font-size:12px;color:var(--text-secondary);">配置自己的语音合成接口</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:16px;padding:12px;background:rgba(255,149,0,0.08);border-radius:var(--radius-sm);font-size:13px;color:var(--orange);line-height:1.5;">
|
||
💡 推荐使用系统TTS作为默认引擎,在线引擎作为备选。系统TTS无需网络,响应更快;在线引擎音质更好,但需要网络连接。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">在线语音合成引擎选择</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TTS Engine Comparison -->
|
||
<table class="comparison-table">
|
||
<thead>
|
||
<tr>
|
||
<th>特性</th>
|
||
<th>📱 系统 TTS</th>
|
||
<th>☁️ 在线语音合成</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>网络要求</td><td>❌ 无需网络</td><td>✅ 需要网络</td></tr>
|
||
<tr><td>音质</td><td>⭐⭐⭐ 标准</td><td>⭐⭐⭐⭐⭐ 高品质</td></tr>
|
||
<tr><td>响应速度</td><td>⚡ 极快</td><td>🔄 需加载</td></tr>
|
||
<tr><td>跨平台</td><td>✅ 全平台</td><td>✅ 全平台</td></tr>
|
||
<tr><td>语音选择</td><td>系统内置</td><td>丰富多样</td></tr>
|
||
<tr><td>成本</td><td>免费</td><td>免费/需Key</td></tr>
|
||
<tr><td>实现方式</td><td>flutter_tts</td><td>HTTP API / WebSocket</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ===== Section 5: Card Integration ===== -->
|
||
<div id="card-integration">
|
||
<div class="section-title"><span class="emoji">🃏</span> 卡片集成效果</div>
|
||
<div class="section-desc">插件启用后,在主页每日推荐卡片和句子广场列表卡片的操作区域注入功能按钮。</div>
|
||
|
||
<div class="phone-grid">
|
||
<!-- Phone 7: Daily Card with buttons -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back"></span>
|
||
<span class="title">闲言</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div class="card-preview-area">
|
||
<div style="font-size:13px;color:var(--text-secondary);margin-bottom:8px;">✨ 每日推荐</div>
|
||
|
||
<div class="daily-card">
|
||
<div class="daily-card-header">
|
||
<span>📖 一言</span>
|
||
<span style="margin-left:auto;">1/5</span>
|
||
</div>
|
||
<div class="daily-card-text">
|
||
生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。
|
||
</div>
|
||
<div class="daily-card-footer">
|
||
<span class="daily-card-author">—— 维维安·格林</span>
|
||
<!-- NEW: Plugin buttons -->
|
||
<button class="card-action-btn translate-btn" title="翻译">🌐</button>
|
||
<button class="card-action-btn tts-btn" title="朗读">🔊</button>
|
||
<!-- Existing buttons -->
|
||
<button class="card-action-btn fav-btn" title="收藏">⭐</button>
|
||
<button class="card-action-btn like-btn" title="点赞">❤️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="font-size:13px;color:var(--text-secondary);margin:16px 0 8px;">📖 句子广场</div>
|
||
|
||
<div class="sentence-card">
|
||
<div class="sentence-card-inner">
|
||
<div class="sentence-card-accent"></div>
|
||
<div class="sentence-card-content">
|
||
<div class="sentence-card-feed">
|
||
<span>💬 一言</span>
|
||
<span style="margin-left:auto;">👁 1.2k</span>
|
||
</div>
|
||
<div class="sentence-card-text">山有木兮木有枝,心悦君兮君不知。</div>
|
||
<div class="sentence-card-stats">
|
||
<span>👍 328</span>
|
||
<span>⭐ 156</span>
|
||
</div>
|
||
<div class="sentence-card-actions">
|
||
<span class="sentence-card-author">—— 越人歌</span>
|
||
<!-- NEW: Plugin buttons -->
|
||
<button class="card-action-btn translate-btn" style="width:28px;height:28px;font-size:14px;" title="翻译">🌐</button>
|
||
<button class="card-action-btn tts-btn" style="width:28px;height:28px;font-size:14px;" title="朗读">🔊</button>
|
||
<!-- Existing buttons -->
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">⭐</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">❤️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sentence-card">
|
||
<div class="sentence-card-inner">
|
||
<div class="sentence-card-accent" style="background:#AF52DE;"></div>
|
||
<div class="sentence-card-content">
|
||
<div class="sentence-card-feed">
|
||
<span>📜 诗词</span>
|
||
<span style="margin-left:auto;">👁 856</span>
|
||
</div>
|
||
<div class="sentence-card-text">人生如逆旅,我亦是行人。</div>
|
||
<div class="sentence-card-stats">
|
||
<span>👍 512</span>
|
||
<span>⭐ 289</span>
|
||
</div>
|
||
<div class="sentence-card-actions">
|
||
<span class="sentence-card-author">—— 苏轼</span>
|
||
<button class="card-action-btn translate-btn" style="width:28px;height:28px;font-size:14px;">🌐</button>
|
||
<button class="card-action-btn tts-btn" style="width:28px;height:28px;font-size:14px;">🔊</button>
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">☆</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">🤍</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">主页 — 插件按钮注入效果</div>
|
||
</div>
|
||
|
||
<!-- Phone 8: Before/After comparison -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back"></span>
|
||
<span class="title">对比效果</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div class="card-preview-area">
|
||
<div style="font-size:13px;color:var(--red);margin-bottom:8px;font-weight:600;">❌ 插件未启用</div>
|
||
<div class="daily-card" style="opacity:0.6;">
|
||
<div class="daily-card-text" style="font-size:15px;">生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。</div>
|
||
<div class="daily-card-footer">
|
||
<span class="daily-card-author" style="font-size:13px;">—— 维维安·格林</span>
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">⭐</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">❤️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="font-size:13px;color:var(--green);margin:16px 0 8px;font-weight:600;">✅ 仅翻译守护启用</div>
|
||
<div class="daily-card">
|
||
<div class="daily-card-text" style="font-size:15px;">生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。</div>
|
||
<div class="daily-card-footer">
|
||
<span class="daily-card-author" style="font-size:13px;">—— 维维安·格林</span>
|
||
<button class="card-action-btn translate-btn active" style="width:28px;height:28px;font-size:14px;">🌐</button>
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">⭐</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">❤️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="font-size:13px;color:var(--purple);margin:16px 0 8px;font-weight:600;">✅ 两个插件都启用</div>
|
||
<div class="daily-card">
|
||
<div class="daily-card-text" style="font-size:15px;">生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。</div>
|
||
<div class="daily-card-footer">
|
||
<span class="daily-card-author" style="font-size:13px;">—— 维维安·格林</span>
|
||
<button class="card-action-btn translate-btn active" style="width:28px;height:28px;font-size:14px;">🌐</button>
|
||
<button class="card-action-btn tts-btn active" style="width:28px;height:28px;font-size:14px;">🔊</button>
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">⭐</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">❤️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">插件启用前后对比</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Section 6: Popup Demo ===== -->
|
||
<div id="popup-demo">
|
||
<div class="section-title"><span class="emoji">💬</span> 弹窗交互效果</div>
|
||
<div class="section-desc">点击翻译按钮弹出翻译结果 Sheet,点击朗读按钮弹出朗读控制 Sheet。均采用 iOS 风格底部弹出面板。</div>
|
||
|
||
<div class="phone-grid">
|
||
<!-- Phone 9: Translate Popup -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back"></span>
|
||
<span class="title">闲言</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div class="card-preview-area" style="opacity:0.5;">
|
||
<div class="daily-card">
|
||
<div class="daily-card-text" style="font-size:15px;">生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。</div>
|
||
<div class="daily-card-footer">
|
||
<span class="daily-card-author" style="font-size:13px;">—— 维维安·格林</span>
|
||
<button class="card-action-btn translate-btn active" style="width:28px;height:28px;font-size:14px;">🌐</button>
|
||
<button class="card-action-btn tts-btn" style="width:28px;height:28px;font-size:14px;">🔊</button>
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">⭐</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">❤️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Translate Popup -->
|
||
<div class="popup-overlay">
|
||
<div class="popup-sheet">
|
||
<div class="popup-handle"></div>
|
||
<div class="popup-title">🌐 翻译结果</div>
|
||
<div class="popup-body">
|
||
<div class="translate-result">
|
||
<div class="translate-result-label">🌐 英文翻译 · Bing</div>
|
||
<div class="translate-result-text">Life isn't about waiting for the storm to pass, it's about learning to dance in the rain.</div>
|
||
</div>
|
||
<div class="translate-original">
|
||
<div class="translate-original-label">📝 原文 · 自动检测: 中文</div>
|
||
<div class="translate-original-text">生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。</div>
|
||
</div>
|
||
<div style="display:flex;gap:8px;margin-top:12px;">
|
||
<button style="flex:1;padding:10px;border-radius:var(--radius-sm);background:var(--primary-light);color:var(--primary);font-size:14px;font-weight:500;border:none;cursor:pointer;">📋 复制翻译</button>
|
||
<button style="flex:1;padding:10px;border-radius:var(--radius-sm);background:rgba(175,82,222,0.1);color:var(--purple);font-size:14px;font-weight:500;border:none;cursor:pointer;">🔊 朗读翻译</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">翻译弹窗 — 底部Sheet</div>
|
||
</div>
|
||
|
||
<!-- Phone 10: TTS Popup -->
|
||
<div>
|
||
<div class="phone-frame">
|
||
<div class="phone-screen">
|
||
<div class="ios-navbar">
|
||
<span class="back"></span>
|
||
<span class="title">闲言</span>
|
||
<span class="action"></span>
|
||
</div>
|
||
|
||
<div class="card-preview-area" style="opacity:0.5;">
|
||
<div class="daily-card">
|
||
<div class="daily-card-text" style="font-size:15px;">山有木兮木有枝,心悦君兮君不知。</div>
|
||
<div class="daily-card-footer">
|
||
<span class="daily-card-author" style="font-size:13px;">—— 越人歌</span>
|
||
<button class="card-action-btn translate-btn" style="width:28px;height:28px;font-size:14px;">🌐</button>
|
||
<button class="card-action-btn tts-btn active" style="width:28px;height:28px;font-size:14px;">🔊</button>
|
||
<button class="card-action-btn fav-btn" style="width:28px;height:28px;font-size:14px;">⭐</button>
|
||
<button class="card-action-btn like-btn" style="width:28px;height:28px;font-size:14px;">❤️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TTS Popup -->
|
||
<div class="popup-overlay">
|
||
<div class="popup-sheet">
|
||
<div class="popup-handle"></div>
|
||
<div class="popup-title">🔊 文本朗读</div>
|
||
<div class="popup-body">
|
||
<div class="tts-player">
|
||
<div class="tts-player-header">
|
||
<span style="font-size:20px;">🎙️</span>
|
||
<span class="tts-player-title">正在朗读</span>
|
||
<span class="tts-speed-label">1.0x</span>
|
||
</div>
|
||
<div class="tts-player-text">山有木兮木有枝,心悦君兮君不知。</div>
|
||
<div class="tts-controls">
|
||
<button class="tts-btn secondary">⏮</button>
|
||
<button class="tts-btn play">⏸</button>
|
||
<button class="tts-btn secondary">⏭</button>
|
||
<div style="flex:1;display:flex;flex-direction:column;gap:4px;">
|
||
<div class="tts-progress">
|
||
<div class="tts-progress-fill"></div>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text-hint);">
|
||
<span>0:03</span>
|
||
<span>0:08</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:12px;background:var(--card-bg);border-radius:var(--radius-md);overflow:hidden;">
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">🗣️</span>
|
||
<span class="config-item-label">语速</span>
|
||
</div>
|
||
<div style="flex:1;margin:0 12px;height:4px;background:rgba(175,82,222,0.15);border-radius:2px;position:relative;">
|
||
<div style="position:absolute;left:0;top:0;width:50%;height:100%;background:var(--purple);border-radius:2px;"></div>
|
||
<div style="position:absolute;left:50%;top:-4px;width:12px;height:12px;background:var(--purple);border-radius:50%;transform:translateX(-50%);"></div>
|
||
</div>
|
||
</div>
|
||
<div class="config-item">
|
||
<div class="config-item-left">
|
||
<span class="config-item-icon">📱</span>
|
||
<span class="config-item-label">引擎</span>
|
||
</div>
|
||
<span class="config-item-value">系统TTS ›</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="phone-label">朗读弹窗 — 底部Sheet</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Section 7: Architecture ===== -->
|
||
<div id="architecture">
|
||
<div class="section-title"><span class="emoji">🏗️</span> 架构设计</div>
|
||
<div class="section-desc">插件系统采用 Riverpod 状态管理 + KvStorage 持久化,与现有翻译/TTS服务复用,零侵入式注入卡片按钮。</div>
|
||
|
||
<div class="flow-diagram">
|
||
<div class="flow-title">插件系统架构图</div>
|
||
<div style="text-align:center;font-size:13px;line-height:2.2;">
|
||
<div style="margin-bottom:12px;">
|
||
<span style="padding:6px 16px;background:var(--primary-light);color:var(--primary);border-radius:8px;font-weight:600;">PluginProvider (Riverpod)</span>
|
||
</div>
|
||
<div style="color:var(--text-hint);">↓ 管理插件状态</div>
|
||
<div style="display:flex;justify-content:center;gap:16px;margin:8px 0;flex-wrap:wrap;">
|
||
<span style="padding:6px 12px;background:rgba(52,199,89,0.1);color:var(--green);border-radius:8px;">TranslatePlugin</span>
|
||
<span style="padding:6px 12px;background:rgba(175,82,222,0.1);color:var(--purple);border-radius:8px;">TtsPlugin</span>
|
||
</div>
|
||
<div style="color:var(--text-hint);">↓ 复用现有服务</div>
|
||
<div style="display:flex;justify-content:center;gap:16px;margin:8px 0;flex-wrap:wrap;">
|
||
<span style="padding:6px 12px;background:rgba(255,149,0,0.1);color:var(--orange);border-radius:8px;">TranslateApiService</span>
|
||
<span style="padding:6px 12px;background:rgba(255,149,0,0.1);color:var(--orange);border-radius:8px;">TtsService</span>
|
||
<span style="padding:6px 12px;background:rgba(255,149,0,0.1);color:var(--orange);border-radius:8px;">OnlineTtsService (新)</span>
|
||
</div>
|
||
<div style="color:var(--text-hint);">↓ 注入UI组件</div>
|
||
<div style="display:flex;justify-content:center;gap:16px;margin:8px 0;flex-wrap:wrap;">
|
||
<span style="padding:6px 12px;background:rgba(0,122,255,0.1);color:var(--primary);border-radius:8px;">DailyCard</span>
|
||
<span style="padding:6px 12px;background:rgba(0,122,255,0.1);color:var(--primary);border-radius:8px;">SentenceCard</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="annotation">
|
||
<strong>关键设计决策:</strong>
|
||
<br>1. 插件状态独立于通用设置,通过 <code>PluginProvider</code> 统一管理,避免耦合
|
||
<br>2. 翻译功能复用现有 <code>TranslateApiService</code> 体系(Bing/Google/MyMemory/AppWorlds/Libre/自定义),不重复造轮子
|
||
<br>3. TTS 在现有 <code>TtsService</code> 基础上新增 <code>OnlineTtsService</code>,支持 Edge TTS 等在线引擎
|
||
<br>4. 卡片按钮通过 <code>ref.watch(pluginProvider)</code> 条件渲染,未启用时完全不构建 Widget
|
||
<br>5. 弹窗使用 <code>StupidSimpleSheet</code>(项目已有依赖),保持 iOS 风格一致性
|
||
</div>
|
||
|
||
<table class="comparison-table">
|
||
<thead>
|
||
<tr>
|
||
<th>文件</th>
|
||
<th>类型</th>
|
||
<th>说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td><code>plugin_provider.dart</code></td><td>状态管理</td><td>插件启用状态 + 配置持久化</td></tr>
|
||
<tr><td><code>plugin_page.dart</code></td><td>页面</td><td>插件列表页</td></tr>
|
||
<tr><td><code>plugin_detail_page.dart</code></td><td>页面</td><td>插件详情/配置页</td></tr>
|
||
<tr><td><code>translate_plugin.dart</code></td><td>服务</td><td>翻译插件逻辑(复用TranslateApiService)</td></tr>
|
||
<tr><td><code>online_tts_service.dart</code></td><td>服务</td><td>在线语音合成(Edge TTS等)</td></tr>
|
||
<tr><td><code>translate_sheet.dart</code></td><td>组件</td><td>翻译结果底部弹窗</td></tr>
|
||
<tr><td><code>tts_player_sheet.dart</code></td><td>组件</td><td>朗读控制底部弹窗</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ===== Section 8: API Test ===== -->
|
||
<div id="api-test">
|
||
<div class="section-title"><span class="emoji">🔌</span> 接口方案</div>
|
||
<div class="section-desc">翻译接口复用现有翻译助手的服务体系;在线TTS新增 Edge TTS 免费接口。</div>
|
||
|
||
<table class="comparison-table">
|
||
<thead>
|
||
<tr>
|
||
<th>接口</th>
|
||
<th>类型</th>
|
||
<th>端点</th>
|
||
<th>费用</th>
|
||
<th>跨平台</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Bing Translator</strong></td>
|
||
<td>翻译</td>
|
||
<td><code>api.cognitive.microsofttranslator.com</code></td>
|
||
<td>免费</td>
|
||
<td>✅</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Google Translate</strong></td>
|
||
<td>翻译</td>
|
||
<td><code>translate.googleapis.com</code></td>
|
||
<td>免费</td>
|
||
<td>✅</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Edge TTS</strong></td>
|
||
<td>在线朗读</td>
|
||
<td><code>speech.platform.bing.com/consumer/speech/synthesize</code></td>
|
||
<td>免费</td>
|
||
<td>✅</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>系统 TTS</strong></td>
|
||
<td>离线朗读</td>
|
||
<td>flutter_tts (原生)</td>
|
||
<td>免费</td>
|
||
<td>✅</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="annotation">
|
||
<strong>Edge TTS 方案说明:</strong>
|
||
<br>Edge TTS 是微软为 Edge 浏览器提供的免费在线语音合成服务,通过 WebSocket 连接 <code>speech.platform.bing.com</code>,支持 100+ 语音(含中文晓晓、云扬等),音质接近真人。无需 API Key,通过模拟 Edge 浏览器的 WebSocket 握手即可使用。该方案已被多个开源项目验证(如 edge-tts Python 库),稳定可靠。
|
||
<br><br>
|
||
<strong>降级策略:</strong>系统 TTS(离线)→ Edge TTS(在线)→ 静默失败提示
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
function scrollToSection(id) {
|
||
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
document.querySelectorAll('.nav-chip').forEach(c => c.classList.remove('active'));
|
||
event.target.classList.add('active');
|
||
}
|
||
|
||
document.querySelectorAll('.ios-toggle').forEach(toggle => {
|
||
toggle.addEventListener('click', function() {
|
||
this.classList.toggle('on');
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|