Files
DSHWatch/src/pages/index/index.ux
T

1415 lines
50 KiB
XML
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.
<template>
<div class="page">
<swiper class="swiper" id="mainSwiper" index="{{currentIndex}}" indicator="false" loop="false"
previousmargin="0px" nextmargin="0px" onchange="onSwipeChange">
<!-- 最左页:选择对话 / 工作区(一行一条,标题左 / 时间右) -->
<div class="page-item left-page">
<text class="page-title">选择对话</text>
<input class="new-session" type="button" value="+ 新会话" onclick="newSession" />
<text class="page-title small">工作区</text>
<text class="ws-name">📁 DeepSeek_Harness_Watch</text>
<div class="sel-conv {{active0 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv0">小米手环10 Pro远程控制</text>
<text class="sel-time" onclick="pickConv0">刚刚</text>
</div>
<div class="sel-conv {{active1 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv1">DSHWatch 验收</text>
<text class="sel-time" onclick="pickConv1">1小时</text>
</div>
<text class="ws-name">📁 reactor</text>
<div class="sel-conv {{active2 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv2">排查Git文件乱码问题</text>
<text class="sel-time" onclick="pickConv2">15天</text>
</div>
<text class="ws-name">📁 project</text>
<div class="sel-conv {{active3 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv3">修改API配置为固定密钥</text>
<text class="sel-time" onclick="pickConv3">8天</text>
</div>
<div class="sel-conv {{active4 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv4">检测DeepSeek API可用性</text>
<text class="sel-time" onclick="pickConv4">9天</text>
</div>
<text class="ws-name">📁 truestudio.online</text>
<div class="sel-conv {{active5 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv5">评估一下项目状态</text>
<text class="sel-time" onclick="pickConv5">9天</text>
</div>
<text class="ws-name">📁 tama-project</text>
<div class="sel-conv {{active6 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv6">旧站交接与维护指南</text>
<text class="sel-time" onclick="pickConv6">2天</text>
</div>
<div class="sel-conv {{active7 ? 'sel-active' : ''}}">
<text class="sel-title fl{{fontLevel}}" onclick="pickConv7">O-COM 云端模式开发交接</text>
<text class="sel-time" onclick="pickConv7">1小时</text>
</div>
</div>
<!-- 左页:对话(Harness 式) -->
<div class="page-item left-page">
<!-- 当前会话 / 点开选工作区 -->
<text class="page-title" onclick="toggleSelect">对话 · {{currentConv}}</text>
<!-- 工作区/会话选择器 -->
<div class="select-overlay" if="{{showSelect}}">
<div class="select-ws" for="{{workspaces}}" onclick="toggleSelect">
<text class="select-name">{{$item.name}}</text>
<div class="select-conv" for="{{$item.conversations}}" onclick="selectConversation($index,$item)">
<text class="select-conv-text">{{$item}}</text>
</div>
</div>
</div>
<!-- 消息流 -->
<scroll class="chat-scroll" scroll-y="true">
<div class="msg-row {{$item.role == 'user' ? 'row-user' : 'row-assistant'}}" for="{{messages}}">
<div class="chat-msg {{$item.role == 'user' ? 'msg-user' : 'msg-assistant'}}">
<div class="think-chip" if="{{$item.think}}">
<text>💭 思考…</text>
</div>
<text class="think-text fl{{fontLevel}}" if="{{$item.showThink}}">{{$item.think}}</text>
<text class="chat-text fl{{fontLevel}}">{{$item.collapsed ? $item.preview : $item.text}}</text>
<text class="expand-chip" if="{{$item.role == 'assistant' && $item.collapsed}}">展开 ▾</text>
<text class="expand-chip" if="{{$item.role == 'assistant' && !$item.collapsed}}">收起 ▴</text>
<image class="chat-img" src="/common/dsniang.png" if="{{$item.kind == 'image'}}" />
<div class="approve-box" if="{{$item.kind == 'approval'}}">
<input class="approve-btn allow" type="button" value="允许" />
<input class="approve-btn deny" type="button" value="拒绝" />
</div>
</div>
</div>
</scroll>
<!-- 底部输入栏 -->
<div class="input-bar">
<input class="input-field" type="text" value="{{inputText}}" placeholder="说点什么…" onclick="openInput" />
<input class="input-send" type="button" value="输入" onclick="openInput" />
</div>
</div>
<!-- 中页:主页 / 吉祥物(无按钮,点击互动) -->
<div class="page-item center-page">
<!-- 气泡区:始终占位,让气泡在头像正上方居中;只显示趣味/上轮消耗信息 -->
<div class="bubble-zone">
<div class="bubble" if="{{bubbleVisible}}">
<text class="bubble-text">{{bubbleText}}</text>
</div>
</div>
<image class="mascot step{{animStep}}" src="/common/dsniang.png" onclick="onTap" />
<text class="peak {{isPeak ? 'peak-red' : 'peak-green'}}">{{peakLabel}}</text>
<text class="stats">余额 {{balance}}</text>
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
</div>
<!-- 右页:丰富设置 -->
<div class="page-item right-page">
<text class="page-title">设置</text>
<div class="setting-row" onclick="toggleModel">
<text class="setting-label fl{{fontLevel}}">模型</text>
<text class="setting-value fl{{fontLevel}}">{{modelValue}}</text>
</div>
<div class="setting-row" onclick="toggleProvider">
<text class="setting-label fl{{fontLevel}}">提供方</text>
<text class="setting-value fl{{fontLevel}}">{{providerValue}}</text>
</div>
<div class="setting-row" onclick="toggleReason">
<text class="setting-label fl{{fontLevel}}">推理强度</text>
<text class="setting-value fl{{fontLevel}}">{{reasonValue}}</text>
</div>
<div class="setting-row" onclick="togglePerm">
<text class="setting-label fl{{fontLevel}}">权限模式</text>
<text class="setting-value fl{{fontLevel}}">{{permValue}}</text>
</div>
<div class="setting-row" onclick="toggleDialog">
<text class="setting-label fl{{fontLevel}}">对话模式</text>
<text class="setting-value fl{{fontLevel}}">{{dialogModeValue}}</text>
</div>
<text class="section-title fl{{fontLevel}}">手表端设置</text>
<div class="setting-row" onclick="toggleWatchConn">
<text class="setting-label fl{{fontLevel}}">手环连接</text>
<text class="setting-value fl{{fontLevel}}">{{watchConnValue}}</text>
</div>
<div class="setting-row" onclick="toggleServer">
<text class="setting-label fl{{fontLevel}}">服务器</text>
<text class="setting-value fl{{fontLevel}}">{{serverValue}}</text>
</div>
<div class="setting-row" onclick="openFontPicker">
<text class="setting-label fl{{fontLevel}}">字体大小</text>
<text class="setting-value fl{{fontLevel}}">{{fontLabel}}</text>
</div>
<text class="conn-text">{{connText}}</text>
</div>
</swiper>
<div class="dots">
<div class="dot {{currentIndex == 0 ? 'dot-active' : ''}}"></div>
<div class="dot {{currentIndex == 1 ? 'dot-active' : ''}}"></div>
<div class="dot {{currentIndex == 2 ? 'dot-active' : ''}}"></div>
<div class="dot {{currentIndex == 3 ? 'dot-active' : ''}}"></div>
</div>
<!-- 软键盘弹层:点空白收起;中/英切换;汉字为常用字(非预置句) -->
<div class="kb-backdrop" if="{{showKeyboard}}" onclick="kbClose"></div>
<div class="keyboard" if="{{showKeyboard}}">
<div class="kb-input">
<text class="kb-text">{{inputText}}</text>
</div>
<div class="kb-pinyin" if="{{pinyinMode && pinyinBuf}}"><text>{{pinyinBuf}}</text></div>
<div class="kb-cands" if="{{pinyinMode && candidates.length}}">
<text class="kb-cand" onclick="pickCand0">{{candidates[0]}}</text>
<text class="kb-cand" onclick="pickCand1">{{candidates[1]}}</text>
<text class="kb-cand" onclick="pickCand2">{{candidates[2]}}</text>
<text class="kb-cand" onclick="pickCand3">{{candidates[3]}}</text>
</div>
<div class="kb-keys">
<!-- 英文模式 -->
<div class="kb-row" if="{{kbMode == 'en'}}">
<text class="kb-key" onclick="kbKey('q')">q</text><text class="kb-key" onclick="kbKey('w')">w</text><text class="kb-key" onclick="kbKey('e')">e</text><text class="kb-key" onclick="kbKey('r')">r</text><text class="kb-key" onclick="kbKey('t')">t</text><text class="kb-key" onclick="kbKey('y')">y</text><text class="kb-key" onclick="kbKey('u')">u</text><text class="kb-key" onclick="kbKey('i')">i</text><text class="kb-key" onclick="kbKey('o')">o</text><text class="kb-key" onclick="kbKey('p')">p</text>
</div>
<div class="kb-row" if="{{kbMode == 'en'}}">
<text class="kb-key" onclick="kbKey('a')">a</text><text class="kb-key" onclick="kbKey('s')">s</text><text class="kb-key" onclick="kbKey('d')">d</text><text class="kb-key" onclick="kbKey('f')">f</text><text class="kb-key" onclick="kbKey('g')">g</text><text class="kb-key" onclick="kbKey('h')">h</text><text class="kb-key" onclick="kbKey('j')">j</text><text class="kb-key" onclick="kbKey('k')">k</text><text class="kb-key" onclick="kbKey('l')">l</text>
</div>
<div class="kb-row" if="{{kbMode == 'en'}}">
<text class="kb-key" onclick="kbKey('z')">z</text><text class="kb-key" onclick="kbKey('x')">x</text><text class="kb-key" onclick="kbKey('c')">c</text><text class="kb-key" onclick="kbKey('v')">v</text><text class="kb-key" onclick="kbKey('b')">b</text><text class="kb-key" onclick="kbKey('n')">n</text><text class="kb-key" onclick="kbKey('m')">m</text>
</div>
<div class="kb-row" if="{{kbMode == 'en'}}">
<text class="kb-key" onclick="kbKey('1')">1</text><text class="kb-key" onclick="kbKey('2')">2</text><text class="kb-key" onclick="kbKey('3')">3</text><text class="kb-key" onclick="kbKey('4')">4</text><text class="kb-key" onclick="kbKey('5')">5</text><text class="kb-key" onclick="kbKey('6')">6</text><text class="kb-key" onclick="kbKey('7')">7</text><text class="kb-key" onclick="kbKey('8')">8</text><text class="kb-key" onclick="kbKey('9')">9</text><text class="kb-key" onclick="kbKey('0')">0</text>
</div>
<!-- 中文常用字模式 -->
<div class="kb-row" if="{{kbMode == 'zh'}}">
<text class="kb-key kb-char" onclick="kbChar('一')">一</text><text class="kb-key kb-char" onclick="kbChar('的')">的</text><text class="kb-key kb-char" onclick="kbChar('是')">是</text><text class="kb-key kb-char" onclick="kbChar('了')">了</text><text class="kb-key kb-char" onclick="kbChar('我')">我</text><text class="kb-key kb-char" onclick="kbChar('不')">不</text><text class="kb-key kb-char" onclick="kbChar('人')">人</text><text class="kb-key kb-char" onclick="kbChar('在')">在</text><text class="kb-key kb-char" onclick="kbChar('他')">他</text><text class="kb-key kb-char" onclick="kbChar('有')">有</text>
</div>
<div class="kb-row" if="{{kbMode == 'zh'}}">
<text class="kb-key kb-char" onclick="kbChar('这')">这</text><text class="kb-key kb-char" onclick="kbChar('中')">中</text><text class="kb-key kb-char" onclick="kbChar('大')">大</text><text class="kb-key kb-char" onclick="kbChar('为')">为</text><text class="kb-key kb-char" onclick="kbChar('上')">上</text><text class="kb-key kb-char" onclick="kbChar('国')">国</text><text class="kb-key kb-char" onclick="kbChar('个')">个</text><text class="kb-key kb-char" onclick="kbChar('到')">到</text><text class="kb-key kb-char" onclick="kbChar('说')">说</text><text class="kb-key kb-char" onclick="kbChar('们')">们</text>
</div>
<div class="kb-row">
<text class="kb-key kb-fn" onclick="kbTogglePy">{{pinyinMode ? 'Abc' : '拼'}}</text>
<text class="kb-key kb-fn" onclick="kbSpace">空格</text>
<text class="kb-key kb-fn" onclick="kbBack">退格</text>
<text class="kb-key kb-fn" onclick="kbClose">收起</text>
<text class="kb-key kb-fn kb-send" onclick="kbSend">发送</text>
</div>
</div>
</div>
<!-- 选项选择弹窗 -->
<div class="popup" if="{{showOptionPicker}}">
<div class="popup-box">
<text class="popup-title">{{pickerTitle}}</text>
<div class="picker-option {{pickerIndex == 0 ? 'picker-active' : ''}}" if="{{pickerOptions[0]}}" onclick="pick0"><text>{{pickerOptions[0]}}</text></div>
<div class="picker-option {{pickerIndex == 1 ? 'picker-active' : ''}}" if="{{pickerOptions[1]}}" onclick="pick1"><text>{{pickerOptions[1]}}</text></div>
<div class="picker-option {{pickerIndex == 2 ? 'picker-active' : ''}}" if="{{pickerOptions[2]}}" onclick="pick2"><text>{{pickerOptions[2]}}</text></div>
<div class="picker-option {{pickerIndex == 3 ? 'picker-active' : ''}}" if="{{pickerOptions[3]}}" onclick="pick3"><text>{{pickerOptions[3]}}</text></div>
<div class="popup-actions">
<input class="popup-btn" type="button" value="确定" onclick="applyPicker" />
</div>
</div>
</div>
<!-- 字体大小弹窗 -->
<div class="popup" if="{{showFontPicker}}">
<div class="popup-box">
<text class="popup-title">字体大小</text>
<slider class="font-slider" min="14" max="22" step="2" value="{{fontValue}}" onchange="onFontChange" />
<text class="popup-value">{{fontLabel}}</text>
<div class="popup-actions">
<input class="popup-btn" type="button" value="确定" onclick="confirmFont" />
</div>
</div>
</div>
</div>
</template>
<script>
import bridge from '../../common/bridge.js'
// 懒加载 @system.audio,避免模拟器/无扬声器时崩
let audioModule = null
let audioTried = false
function loadAudio() {
if (audioTried) return audioModule
audioTried = true
try {
audioModule = require('@system.audio')
} catch (e) {
audioModule = null
}
return audioModule
}
function playDuckSound() {
const a = loadAudio()
if (!a) return
try {
a.src = '/common/Ya1.mp3'
a.play()
} catch (e) {}
}
export default {
private: {
currentIndex: 2,
messages: [
{ role:'user', text:'帮我看看今天有什么任务', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'好的,今天三件事:1) 回复 DSHWatch 进度;2) 调整手环 UI;3) 准备部署。', preview:'好的,今天三件事:1) 回复 DSHWatch 进度;2) 调整手环…', think:'正在从工作区任务列表整理,并对比上次状态…', kind:'normal', showThink:true, collapsed:true },
{ role:'user', text:'第一件事具体说说', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'已把进度整理成:1)修复白屏(已提交) 2)布局放大(已提交) 3)待真机验证', preview:'已把进度整理成:1)修复白屏…', think:'汇总最近 5 次提交…', kind:'normal', showThink:true, collapsed:true },
{ role:'assistant', text:'需要一个权限:允许运行网络请求到 https://api.deepseek.com/user/balance', preview:'', think:null, kind:'approval', showThink:false, collapsed:false },
{ role:'user', text:'允许', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'已查询到余额 ¥6.20,今日已用 ¥12.08。', preview:'', think:'正在调用 DeepSeek user/balance…', kind:'normal', showThink:true, collapsed:false },
{ role:'user', text:'帮我生成一张手环 UI 草图', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'生成了一张「手表主界面」草图,点击可放大。', preview:'生成了一张「手表主界面」草图…', think:'正在渲染图像…', kind:'image', showThink:true, collapsed:false },
{ role:'assistant', text:'另外建议把左边做成对话流、右边放设置,中间吉祥物可交互——这样最贴合你说的“把 Harness 串流过来”。', preview:'另外建议把左边做成对话流、右边放设置…', think:'正在思考布局建议…', kind:'normal', showThink:true, collapsed:true },
{ role:'system', text:'会话已切换工作区:reactor', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'正在调用工具 bash 执行 npm run build…', preview:'正在调用工具 bash…', think:null, kind:'tool', tag:'🔧 工具调用', showThink:false, collapsed:true },
{ role:'assistant', text:'构建失败:UxLoader 错误,缺少 system.storage 特性。', preview:'构建失败:UxLoader 错误…', think:null, kind:'error', tag:'❌ 错误', showThink:false, collapsed:false },
{ role:'assistant', text:'方案一:三页结构;方案二:四页+浮动层。', preview:'方案一:三页结构;方案二:四页+浮动层…', think:'正在比较两种布局…', kind:'plan', tag:'🧭 计划', showThink:true, collapsed:true },
{ role:'assistant', text:'const a = 1;\nconsole.log(a);', preview:'const a = 1;…', think:null, kind:'code', tag:'💻 代码', showThink:false, collapsed:true },
{ role:'assistant', text:'模型 | 价格 | 状态\nFlash | 0.1 | 可用', preview:'模型 | 价格 | 状态…', think:null, kind:'table', tag:'📊 表格', showThink:false, collapsed:true },
{ role:'assistant', text:'「把 Harness 串流过来」这个目标,建议先做最小闭环。', preview:'「把 Harness 串流过来」…', think:null, kind:'normal', showThink:false, collapsed:true }
],
workspaces: [
{ name:'DeepSeek_Harness_Watch', conversations:['小米手环10 Pro远程控制','DSHWatch 验收'] },
{ name:'reactor', conversations:['任务-001','任务-002'] },
{ name:'truestudio.online', conversations:['评估-今天'] }
],
workTree: [
{ name:'DeepSeek_Harness_Watch', convs:[ { title:'小米手环10 Pro远程控制', time:'刚刚' }, { title:'DSHWatch 验收', time:'1小时' } ] },
{ name:'reactor', convs:[ { title:'排查Git文件乱码问题', time:'15天' } ] },
{ name:'project', convs:[ { title:'修改API配置为固定密钥', time:'8天' }, { title:'检测DeepSeek API可用性', time:'9天' } ] },
{ name:'truestudio.online', convs:[ { title:'评估一下项目状态', time:'9天' } ] }
],
currentConv: '小米手环10 Pro远程控制',
convMsgs: {},
active0: true,
active1: false,
active2: false,
active3: false,
active4: false,
active5: false,
active6: false,
active7: false,
showSelect: false,
inputText: '',
isPeak: false,
peakLabel: '谷时 · 半价',
balance: '--',
todayTokens: '--',
todayCost: '--',
bubbleVisible: false,
bubbleText: '',
bubbleIndex: 0,
animStep: 0,
animLock: false,
settings: {
serverUrl: 'ws://127.0.0.1:8787',
userId: '',
token: ''
},
connText: '手机/手表未连接',
modelOptions: ['DeepSeek-V4-Flash','DeepSeek-V4-Pro','deepseek-chat','deepseek-reasoner'],
modelIdx: 0,
modelValue: 'DeepSeek-V4-Flash',
providerOptions: ['DeepSeek 官方','Ollama','OpenAI 兼容','DeepSeek 中转'],
providerIdx: 0,
providerValue: 'DeepSeek 官方',
reasonOptions: ['高','中','低','关闭'],
reasonIdx: 0,
reasonValue: '高',
permOptions: ['Workspace Write','Read Only','Ask Once','Full Access'],
permIdx: 0,
permValue: 'Workspace Write',
dialogOptions: ['对话模式','插话模式','深神模式'],
dialogIdx: 0,
dialogModeValue: '对话模式',
connOptions: ['未连接','已连接'],
connIdx: 0,
watchConnValue: '未连接',
serverOptions: ['ws://127.0.0.1:8787','ws://frp-host:18321'],
serverIdx: 0,
serverValue: 'ws://127.0.0.1:8787',
showFontPicker: false,
showKeyboard: false,
kbMode: 'en',
pinyinMode: false,
pinyinBuf: '',
candidates: [],
pinDict: {
'ni': ['你','呢','尼','泥'],
'hao': ['好','号','豪','耗'],
'wo': ['我','握','沃','卧'],
'shi': ['是','时','事','十','使','式'],
'de': ['的','得','地','德'],
'zai': ['在','再','载','仔'],
'bu': ['不','部','布','步'],
'le': ['了','乐','勒','累'],
'you': ['有','又','由','油','游'],
'he': ['和','合','河','喝','盒'],
'ta': ['他','她','它','塔'],
'men': ['们','门','闷','焖'],
'zhong': ['中','种','重','钟','衷'],
'guo': ['国','过','果','裹'],
'ge': ['个','各','歌','格','隔'],
'zhang': ['长','张','章','涨','账'],
'xiang': ['想','向','相','项','像'],
'kan': ['看','砍','坎','侃'],
'xian': ['先','现','线','县','显'],
'huo': ['或','活','火','伙','货'],
'qing': ['请','情','清','轻','青'],
'qu': ['去','取','区','曲','趣'],
'shuo': ['说','硕','烁','朔'],
'wen': ['问','文','闻','温','稳'],
'shen': ['什','深','身','神','甚'],
'me': ['么','吗','么','摩'],
'zhi': ['之','只','知','直','制'],
'dao': ['到','道','倒','岛','导'],
'jin': ['今','金','进','近','仅'],
'tian': ['天','田','添','填','甜'],
'xing': ['行','星','型','形','姓'],
'neng': ['能','宁','凝'],
'can': ['参','残','餐','灿'],
'yi': ['一','以','已','意','义','易'],
'er': ['二','而','儿','耳','尔'],
'san': ['三','散','伞','伞'],
'ri': ['日','入','肉'],
'yue': ['月','越','乐','阅'],
'zhe': ['这','着','哲','折','者'],
'xia': ['下','夏','吓','虾'],
'shang': ['上','尚','商','伤'],
'liao': ['了','聊','料','廖'],
'wenxin': ['问心','文心'],
'youxiu': ['优秀','优修'],
'nihao': ['你好','尼豪']
},
showOptionPicker: false,
pickerTitle: '',
pickerOptions: [],
pickerIndex: 0,
pickerTarget: '',
fontValue: 18,
fontLevel: 1,
fontLabel: '中',
bubblePresets: [
'我算得比你快,但没你可爱。',
'上轮消耗 ¥0.08,钱包还撑得住。',
'峰时了,省着点用哦~',
'谷时来了,冲就完事!',
'今天也要一起聪明地偷懒。',
'点我一下,解压一下。',
'上轮余额 ¥12.34,别浪~',
'思考中…其实是发呆。'
]
},
onInit() {
this.refreshPeak()
this.convMsgs = {
'小米手环10 Pro远程控制': this.messages,
'DSHWatch 验收': [
{ role:'user', text:'验收一下 DSHWatch 今天改了什么', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'今天改了 5 次:玻璃气泡、字体放大、拼音输入、会话选择、键盘收起。', preview:'今天改了 5 次:玻璃气泡…', think:'正在比对最近提交…', kind:'normal', showThink:true, collapsed:true },
{ role:'user', text:'发一张验收截图', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'已生成「验收」截图。', preview:'已生成「验收」截图…', think:'正在渲染图像…', kind:'image', showThink:true, collapsed:false },
{ role:'assistant', text:'正在调用工具 adb 截图并回传…', preview:'正在调用工具 adb 截图…', think:null, kind:'tool', tag:'🔧 工具调用', showThink:false, collapsed:true },
{ role:'system', text:'构建通过,可上模拟器。', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'user', text:'有没有 bug', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'发现一处白屏问题,待修复。', preview:'发现一处白屏问题…', think:'正在排查…', kind:'error', tag:'❌ 错误', showThink:true, collapsed:false }
],
'排查Git文件乱码问题': [
{ role:'user', text:'Git 文件乱码了,为什么?', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'多半是换行符/编码问题(CRLF vs LF、GBK vs UTF-8)。', preview:'多半是换行符/编码问题…', think:'正在检查 .gitattributes…', kind:'normal', showThink:true, collapsed:true },
{ role:'assistant', text:'git config core.autocrlf false\ngit config core.quotepath false', preview:'git config core.autocrlf false…', think:null, kind:'code', tag:'💻 代码', showThink:false, collapsed:true },
{ role:'user', text:'那怎么批量修', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'文件 | 状态 | 处理\na.txt | GBK | 转 UTF-8', preview:'文件 | 状态 | 处理…', think:null, kind:'table', tag:'📊 表格', showThink:false, collapsed:true },
{ role:'assistant', text:'建议一次性统一换行符并提交。', preview:'建议一次性统一换行符…', think:'正在评估影响面…', kind:'normal', showThink:true, collapsed:false }
],
'修改API配置为固定密钥': [
{ role:'user', text:'把 API 密钥固定到配置里', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'需要一个权限:写入 keystore.properties', preview:'', think:null, kind:'approval', showThink:false, collapsed:false },
{ role:'user', text:'允许', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'正在调用工具写入密钥…', preview:'正在写入密钥…', think:null, kind:'tool', tag:'🔧 工具调用', showThink:false, collapsed:true },
{ role:'assistant', text:'已写入(已加入 .gitignore)。', preview:'已写入(已 gitignore)…', think:'正在落盘…', kind:'normal', showThink:true, collapsed:false },
{ role:'user', text:'确认没泄露到仓库', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'已检查,未纳入版本控制。', preview:'已检查,未纳入版本控制…', think:'正在扫描提交历史…', kind:'normal', showThink:true, collapsed:true }
],
'检测DeepSeek API可用性': [
{ role:'user', text:'检测一下 DeepSeek 余额接口', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'正在调用 GET /user/balance…', preview:'正在调用余额接口…', think:'正在发起请求…', kind:'tool', tag:'🔧 工具调用', showThink:true, collapsed:true },
{ role:'assistant', text:'⚠️ API 不响应:请求超时(30s)', preview:'API 不响应:请求超时…', think:null, kind:'error', tag:'❌ 错误', showThink:false, collapsed:false },
{ role:'user', text:'重试一下', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'重试成功:200,is_available=true,余额 ¥6.20。', preview:'重试成功:200 …', think:'已完成重试…', kind:'normal', showThink:true, collapsed:false }
],
'评估一下项目状态': [
{ role:'assistant', text:'项目当前状态:UI 完成 70%,插件/服务器已通,待真机联调。', preview:'项目当前状态:UI 完成 70%…', think:'正在汇总各模块…', kind:'normal', showThink:true, collapsed:true },
{ role:'user', text:'请补充风险', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'建议先做最小闭环:插件→服务器→手机→手表。', preview:'建议先做最小闭环…', think:'正在规划优先级…', kind:'plan', tag:'🧭 计划', showThink:true, collapsed:true },
{ role:'assistant', text:'模块 | 状态\n插件 | 已通\n服务器 | 已通', preview:'模块 | 状态…', think:null, kind:'table', tag:'📊 表格', showThink:false, collapsed:true },
{ role:'assistant', text:'整体可行,下一步接真实数据。', preview:'整体可行…', think:null, kind:'normal', showThink:false, collapsed:false }
],
'旧站交接与维护指南': [
{ role:'assistant', text:'旧站维护要点:先备份,再改,最后回归。', preview:'旧站维护要点:先备份…', think:'正在检索交接文档…', kind:'normal', showThink:true, collapsed:true },
{ role:'user', text:'数据库怎么迁移', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'建议停机窗口内做全量导出+增量补丁。', preview:'建议停机窗口内…', think:'正在评估影响…', kind:'plan', tag:'🧭 计划', showThink:true, collapsed:true },
{ role:'assistant', text:'mysqldump --single-transaction old > old.sql', preview:'mysqldump --single-transaction…', think:null, kind:'code', tag:'💻 代码', showThink:false, collapsed:true }
],
'O-COM 云端模式开发交接': [
{ role:'user', text:'云端模式怎么远程控制', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'需要服务器 + 手机网关 + 手表三端。', preview:'需要服务器+手机网关+手表…', think:'正在梳理链路…', kind:'normal', showThink:true, collapsed:false },
{ role:'assistant', text:'⚠️ 服务器未部署,暂用 FRP 本地直连测试。', preview:'服务器未部署…', think:null, kind:'error', tag:'❌ 错误', showThink:false, collapsed:false },
{ role:'user', text:'那先本地串通', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'已串通:插件直连 → 手机转发 → 手表。', preview:'已串通:插件直连→手机→手表…', think:'正在验证链路…', kind:'normal', showThink:true, collapsed:true }
]
}
},
onReady() {
this._unsub = bridge.subscribe({
status: (s) => {
this.connText = s || '未连接'
},
message: (msg) => {
this.handleBridgeMessage(msg)
}
})
bridge.refreshConn()
},
onDestroy() {
if (this._unsub) this._unsub()
},
onSwipeChange(e) {
if (e && typeof e.index == 'number') {
this.currentIndex = e.index
// 离开对话页(index 1)自动收起键盘/选择器
if (this.currentIndex !== 1) {
this.showKeyboard = false
this.showOptionPicker = false
this.showFontPicker = false
}
}
},
toggleSelect() {
this.showSelect = !this.showSelect
},
selectConversation(convIndex, convName) {
this.currentConv = convName || this.currentConv
this.showSelect = false
},
selectConv(name) {
this.currentConv = name
this.currentIndex = 1
this.showSelect = false
},
newSession() {
// 弹出选择工作区小窗(预留:后续可从电脑目录/插件串流新建工作区)
this.pickerTitle = '选择工作区'
this.pickerTarget = 'ws'
this.pickerOptions = this.workTree.map((w) => w.name)
this.pickerIndex = 0
this.showOptionPicker = true
},
setActive(n) {
for (let i = 0; i <= 7; i++) this['active' + i] = (i === n)
},
goToPage(index) {
this.currentIndex = index
try {
const el = this.$element('mainSwiper')
if (el && el.swipeTo) el.swipeTo({ index: index })
} catch (e) {}
},
pickConv0() { this.currentConv = '小米手环10 Pro远程控制'; this.messages = this.convMsgs['小米手环10 Pro远程控制']; this.setActive(0); this.goToPage(1) },
pickConv1() { this.currentConv = 'DSHWatch 验收'; this.messages = this.convMsgs['DSHWatch 验收']; this.setActive(1); this.goToPage(1) },
pickConv2() { this.currentConv = '排查Git文件乱码问题'; this.messages = this.convMsgs['排查Git文件乱码问题']; this.setActive(2); this.goToPage(1) },
pickConv3() { this.currentConv = '修改API配置为固定密钥'; this.messages = this.convMsgs['修改API配置为固定密钥']; this.setActive(3); this.goToPage(1) },
pickConv4() { this.currentConv = '检测DeepSeek API可用性'; this.messages = this.convMsgs['检测DeepSeek API可用性']; this.setActive(4); this.goToPage(1) },
pickConv5() { this.currentConv = '评估一下项目状态'; this.messages = this.convMsgs['评估一下项目状态']; this.setActive(5); this.goToPage(1) },
pickConv6() { this.currentConv = '旧站交接与维护指南'; this.messages = this.convMsgs['旧站交接与维护指南']; this.setActive(6); this.goToPage(1) },
pickConv7() { this.currentConv = 'O-COM 云端模式开发交接'; this.messages = this.convMsgs['O-COM 云端模式开发交接']; this.setActive(7); this.goToPage(1) },
toggleThink(index) {
const m = this.messages[index]
if (m) m.showThink = !m.showThink
},
toggleCollapse(index) {
const m = this.messages[index]
if (m) m.collapsed = !m.collapsed
},
approve(index) {
const m = this.messages[index]
if (m) { m.kind = 'done'; m.text = '已允许 ✓' }
},
deny(index) {
const m = this.messages[index]
if (m) { m.kind = 'done'; m.text = '已拒绝 ✕' }
},
openInput() {
this.showKeyboard = true
},
kbKey(ch) {
if (this.pinyinMode) {
this.pinyinBuf = (this.pinyinBuf + ch).slice(0, 12)
this.computeCandidates()
} else {
this.inputText = (this.inputText + ch).slice(0, 80)
}
},
kbToggleZh() {
this.kbMode = this.kbMode == 'en' ? 'zh' : 'en'
},
kbTogglePy() {
this.pinyinMode = !this.pinyinMode
this.pinyinBuf = ''
this.candidates = []
},
computeCandidates() {
const buf = this.pinyinBuf
if (!buf) { this.candidates = []; return }
const out = []
if (this.pinDict[buf]) out.push(...this.pinDict[buf])
// 双音节组合词(如 ni+hao -> 你好)
for (let i = 1; i < buf.length; i++) {
const a = this.pinDict[buf.slice(0, i)]
const b = this.pinDict[buf.slice(i)]
if (a && b) out.push(a[0] + b[0])
}
this.candidates = out.slice(0, 4)
},
pickCand0() { this.candPick(0) },
pickCand1() { this.candPick(1) },
pickCand2() { this.candPick(2) },
pickCand3() { this.candPick(3) },
candPick(i) {
const c = this.candidates[i]
if (!c) return
this.inputText = (this.inputText + c).slice(0, 80)
this.pinyinBuf = ''
this.candidates = []
},
kbSpace() {
this.inputText = (this.inputText + ' ').slice(0, 80)
},
kbBack() {
this.inputText = this.inputText.slice(0, -1)
},
kbClose() {
this.showKeyboard = false
},
kbSend() {
if (this.inputText.trim().length > 0) {
bridge.sendText(this.inputText.trim())
}
this.inputText = ''
this.showKeyboard = false
},
onInputChange(e) {
this.inputText = e && e.value || ''
},
toggleModel() { this.openPicker('model') },
toggleProvider() { this.openPicker('provider') },
toggleReason() { this.openPicker('reason') },
togglePerm() { this.openPicker('perm') },
toggleDialog() { this.openPicker('dialog') },
toggleWatchConn() { this.openPicker('conn') },
toggleServer() { this.openPicker('server') },
openPicker(target) {
const titles = { model:'模型', provider:'提供方', reason:'推理强度', perm:'权限模式', dialog:'对话模式', conn:'手环连接', server:'服务器' }
const values = { model:'modelValue', provider:'providerValue', reason:'reasonValue', perm:'permValue', dialog:'dialogModeValue', conn:'watchConnValue', server:'serverValue' }
this.pickerTitle = titles[target] || '选择'
this.pickerTarget = target
this.pickerOptions = this[target + 'Options']
this.pickerIndex = this[target + 'Idx'] || 0
this.showOptionPicker = true
},
pick0() { this.pickerIndex = 0 },
pick1() { this.pickerIndex = 1 },
pick2() { this.pickerIndex = 2 },
pick3() { this.pickerIndex = 3 },
applyPicker() {
const t = this.pickerTarget
if (!t) return this.showOptionPicker = false
if (t == 'ws') {
// 预留:新建工作区(后续让插件串流电脑目录,这里先占位)
this.currentConv = '新会话'
this.showOptionPicker = false
return
}
const values = { model:'modelValue', provider:'providerValue', reason:'reasonValue', perm:'permValue', dialog:'dialogModeValue', conn:'watchConnValue', server:'serverValue' }
this[t + 'Idx'] = this.pickerIndex
this[values[t]] = this.pickerOptions[this.pickerIndex]
this.showOptionPicker = false
},
openFontPicker() {
this.showFontPicker = true
this.fontValue = 18
},
onFontChange(e) {
let v = e && typeof e.progress == 'number' ? e.progress : (e && typeof e.value == 'number' ? e.value : 18)
// 兼容 progress 是 0-100 百分比的情况
if (v >= 10 && v <= 24) {
this.fontValue = v
} else {
this.fontValue = Math.round(14 + (v / 100) * (22 - 14))
}
const lv = Math.round((this.fontValue - 14) / 2)
this.fontLevel = Math.max(0, Math.min(4, lv))
this.fontLabel = ['最小', '小', '中', '大', '特大'][this.fontLevel]
},
confirmFont() {
this.showFontPicker = false
const row = this.watchSettings.find((r) => r.label == '字体大小')
if (row) row.value = this.fontLabel
},
refreshPeak() {
// 用北京时间计算峰谷;周六/周日全天谷时
const now = Date.now()
const d = new Date(now + 8 * 3600 * 1000)
const day = d.getUTCDay() // 0=Sun
const hour = d.getUTCHours()
const isWeekend = day == 0 || day == 6
const inPeak = !isWeekend && ((hour >= 9 && hour < 12) || (hour >= 14 && hour < 18))
this.isPeak = inPeak
this.peakLabel = inPeak ? '峰时 · 全价' : '谷时 · 半价'
},
onTap() {
// 冷却:动画/气泡显示期间不重复触发
if (this.animLock) return
this.animLock = true
// Q 弹:底部不动,从上往下收缩,再回弹(scaleY 多帧平滑)
const self = this
this.animStep = 0
setTimeout(() => { self.animStep = 1 }, 0) // scaleY 0.92
setTimeout(() => { self.animStep = 2 }, 40) // scaleY 0.84
setTimeout(() => { self.animStep = 3 }, 85) // scaleY 0.95
setTimeout(() => { self.animStep = 4 }, 135) // scaleY 1.05
setTimeout(() => { self.animStep = 0 }, 200) // scaleY 1.00
// 回弹时播鸭叫
setTimeout(() => { playDuckSound() }, 150)
// 轮播趣味/上轮消耗信息
const idx = this.bubbleIndex % this.bubblePresets.length
this.bubbleIndex += 1
this.bubbleText = this.bubblePresets[idx]
this.bubbleVisible = true
// 气泡显示约 1 秒后自动消失
setTimeout(() => { self.bubbleVisible = false }, 1000)
// 消失后再等 0.5 秒才允许再次点击
setTimeout(() => { self.animLock = false }, 1500)
},
selectSession(index) {
this.currentIndex = 1
},
onServerChange(e) { this.settings.serverUrl = e && e.value || '' },
onUserChange(e) { this.settings.userId = e && e.value || '' },
onTokenChange(e) { this.settings.token = e && e.value || '' },
handleBridgeMessage(msg) {
if (!msg) return
if (msg.type == 'status' && msg.payload) {
const pl = msg.payload
if (typeof pl.balance !== 'undefined') this.balance = pl.balance
if (typeof pl.todayCost !== 'undefined') this.todayCost = pl.todayCost
if (pl.model) this.statusText = '在线 · ' + pl.model
if (typeof pl.running !== 'undefined') this.statusText = pl.running ? '执行中' : '空闲'
this.connText = pl.model ? ('在线 · ' + pl.model) : '已连接'
} else if (msg.type == 'event' && msg.payload) {
// 后续可更新会话/事件列表
}
}
}
</script>
<style>
.page {
width: 100%;
height: 100%;
flex-direction: column;
background-color: #0b1220;
}
.swiper {
flex: 1;
width: 100%;
indicator-size: 0px;
}
.page-item {
width: 100%;
height: 100%;
padding: 12px;
box-sizing: border-box;
}
.left-page,
.right-page {
flex-direction: column;
align-items: flex-start;
padding-top: 22px;
}
.center-page {
flex-direction: column;
justify-content: center;
align-items: center;
}
.bubble-zone {
width: 100%;
height: 54px;
justify-content: center;
align-items: center;
}
.page-title {
color: #e6f0ff;
font-size: 24px;
font-weight: bold;
margin-bottom: 12px;
}
.page-hint {
color: #6b7a99;
font-size: 16px;
margin-top: 20px;
}
.session-list {
width: 100%;
flex-direction: column;
}
.session-row {
width: 100%;
height: 44px;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 0 10px;
border-radius: 10px;
background-color: #152238;
}
.session-title {
color: #dbe8ff;
font-size: 17px;
}
.session-dot {
color: #33b4ff;
font-size: 16px;
}
/* 吉祥物:放大 + 底部锚定的 scaleY+translateY 补偿 Q 弹 */
.mascot {
width: 456px;
height: 403px;
transition: transform 0.12s cubic-bezier(0.33, 1.12, 0.68, 1);
}
.step0 { transform: translateY(0px) scaleY(1); }
.step1 { transform: translateY(16px) scaleY(0.92); }
.step2 { transform: translateY(36px) scaleY(0.82); }
.step3 { transform: translateY(10px) scaleY(0.95); }
.step4 { transform: translateY(-10px) scaleY(1.05); }
.peak {
width: 100%;
font-size: 32px;
font-weight: bold;
line-height: 40px;
text-align: center;
margin-top: 6px;
}
.peak-green {
color: #3ddc84;
}
.peak-red {
color: #ff5b5b;
}
.stats {
width: 100%;
color: #c4d2ea;
font-size: 22px;
line-height: 28px;
text-align: center;
margin-top: 2px;
}
/* 气泡:头像正上方居中,稍微放大 */
.bubble {
width: 324px;
flex-direction: column;
padding: 18px 20px;
border-radius: 22px;
background-color: rgba(20, 32, 52, 0.86);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.bubble-text {
color: #e6f0ff;
font-size: 17px;
line-height: 24px;
text-align: center;
}
.setting-label {
color: #8fa4c9;
font-size: 14px;
margin-top: 8px;
}
.setting-input {
width: 100%;
height: 38px;
border-radius: 8px;
background-color: #152238;
color: #e6f0ff;
font-size: 15px;
padding: 0 8px;
margin-bottom: 4px;
}
.conn-text {
color: #6b7a99;
font-size: 16px;
margin-top: 8px;
}
.dots {
flex-direction: row;
justify-content: center;
align-items: center;
height: 14px;
margin-top: 4px;
gap: 8px;
}
.dot {
width: 7px;
height: 7px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.28);
}
.dot-active {
width: 22px;
border-radius: 4px;
background-color: #33b4ff;
}
/* ==== 左页:对话 ==== */
.chat-scroll {
flex: 1;
width: 100%;
flex-direction: column;
align-items: flex-start;
}
.chat-img {
width: 100%;
height: 150px;
border-radius: 8px;
margin-top: 4px;
}
.msg-row {
width: 100%;
flex-direction: row;
margin-bottom: 6px;
}
.row-user {
justify-content: flex-end;
}
.row-assistant {
justify-content: flex-start;
}
.chat-msg {
width: auto;
max-width: 84%;
flex-direction: column;
padding: 7px 10px;
border-radius: 12px;
}
.msg-user {
margin-right: 12px;
background-color: rgba(51, 180, 255, 0.20);
border: 1px solid rgba(120, 200, 255, 0.30);
}
.msg-assistant {
margin-left: 12px;
margin-right: 12px;
background-color: rgba(58, 62, 70, 0.55);
border: 1px solid rgba(255, 255, 255, 0.10);
}
.chat-text {
color: #f0f6ff;
font-size: 17px;
line-height: 25px;
}
.think-chip {
background-color: rgba(28, 44, 71, 0.7);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 6px;
padding: 4px;
margin-bottom: 4px;
color: #9db3d8;
font-size: 14px;
}
.think-text {
color: #9db3d8;
font-size: 14px;
line-height: 20px;
margin-bottom: 4px;
}
.expand-chip {
margin-top: 4px;
color: #33b4ff;
font-size: 14px;
}
.approve-box {
flex-direction: row;
margin-top: 6px;
gap: 8px;
}
.approve-btn {
width: 82px;
height: 34px;
border-radius: 17px;
font-size: 14px;
text-align: center;
}
.approve-btn.allow {
background-color: #33b4ff;
color: #08131f;
}
.approve-btn.deny {
background-color: #ff5b5b;
color: #ffffff;
}
.input-bar {
width: 100%;
flex-direction: row;
align-items: center;
margin-top: 6px;
gap: 6px;
}
.input-field {
flex: 1;
min-height: 38px;
height: auto;
border-radius: 8px;
background-color: #152238;
color: #e6f0ff;
font-size: 15px;
padding: 6px 8px;
}
.input-send {
width: 72px;
min-height: 38px;
height: auto;
border-radius: 19px;
background-color: #33b4ff;
color: #08131f;
font-size: 16px;
text-align: center;
}
.select-overlay {
width: 100%;
margin-bottom: 8px;
padding: 8px;
border-radius: 10px;
background-color: #0f1a2c;
}
.select-ws {
margin-bottom: 8px;
}
.select-name {
color: #e6f0ff;
font-size: 15px;
font-weight: bold;
}
.select-conv {
padding: 6px 8px;
margin-top: 4px;
border-radius: 8px;
background-color: #152238;
}
.select-conv-text {
color: #b7c8e8;
font-size: 14px;
}
/* ==== 右页:设置 ==== */
.settings-scroll {
flex: 1;
width: 100%;
height: 100%;
}
.setting-row {
width: 100%;
min-height: 44px;
height: auto;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 6px 10px;
border-radius: 10px;
background-color: #152238;
margin-bottom: 6px;
}
.setting-label {
color: #9db3d8;
font-size: 17px;
}
.setting-value {
color: #e6f0ff;
font-size: 16px;
}
.section-title {
color: #33b4ff;
font-size: 18px;
font-weight: bold;
margin-top: 10px;
margin-bottom: 6px;
}
/* ==== 字体等级(随字号滑块变化) ==== */
.fl0 { font-size: 15px; line-height: 21px; }
.fl1 { font-size: 17px; line-height: 25px; }
.fl2 { font-size: 19px; line-height: 28px; }
.fl3 { font-size: 21px; line-height: 31px; }
.fl4 { font-size: 23px; line-height: 34px; }
/* ==== 字体大小弹窗 ==== */
.popup {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
}
.popup-box {
width: 300px;
min-height: 180px;
flex-direction: column;
align-items: center;
padding: 20px;
border-radius: 18px;
background-color: #0f1a2c;
border: 1px solid rgba(255, 255, 255, 0.14);
}
.popup-title {
color: #e6f0ff;
font-size: 20px;
font-weight: bold;
}
.font-slider {
width: 240px;
margin-top: 14px;
}
.popup-value {
color: #33b4ff;
font-size: 18px;
margin-top: 10px;
}
.popup-actions {
margin-top: 14px;
}
.popup-btn {
width: 120px;
height: 42px;
border-radius: 21px;
background-color: #33b4ff;
color: #08131f;
font-size: 17px;
text-align: center;
}
.picker-option {
width: 260px;
min-height: 40px;
justify-content: center;
align-items: center;
margin-bottom: 6px;
padding: 6px 10px;
border-radius: 10px;
background-color: #152238;
border: 1px solid rgba(255,255,255,0.10);
}
.picker-option text {
color: #dbe8ff;
font-size: 16px;
}
.picker-active {
background-color: rgba(51,180,255,0.22);
border-color: #33b4ff;
}
/* ==== 软键盘 ==== */
.keyboard {
position: absolute;
left: 0;
right: 0;
bottom: 0;
flex-direction: column;
background-color: rgba(11, 18, 32, 0.97);
border-top: 1px solid rgba(255,255,255,0.14);
padding: 10px;
}
.kb-input {
width: 100%;
min-height: 48px;
justify-content: center;
align-items: center;
border-radius: 10px;
background-color: #152238;
margin-bottom: 10px;
padding: 6px;
}
.kb-text {
color: #e6f0ff;
font-size: 17px;
}
.kb-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.35);
}
.kb-keys {
width: 100%;
flex-direction: column;
}
.kb-row {
width: 100%;
flex-direction: row;
justify-content: center;
margin-bottom: 6px;
}
.kb-key {
width: 34px;
height: 40px;
margin: 0 2px;
justify-content: center;
align-items: center;
border-radius: 8px;
background-color: #1c2c47;
color: #e6f0ff;
font-size: 16px;
text-align: center;
}
.kb-fn {
width: 64px;
}
.kb-char {
width: 30px;
font-size: 16px;
}
.kb-phrase {
width: auto;
min-width: 56px;
padding: 0 6px;
}
.kb-send {
background-color: #33b4ff;
color: #08131f;
}
.new-session {
width: 100%;
height: 42px;
margin-top: 6px;
margin-bottom: 8px;
border-radius: 21px;
background-color: rgba(51,180,255,0.16);
border: 1px solid rgba(120,200,255,0.28);
color: #e6f0ff;
font-size: 16px;
}
.page-title.small {
font-size: 16px;
color: #9db3d8;
margin-top: 6px;
margin-bottom: 2px;
}
.ws-group {
width: 100%;
flex-direction: column;
}
.ws-name {
color: #9db3d8;
font-size: 16px;
font-weight: bold;
margin-top: 8px;
margin-bottom: 4px;
}
.sel-conv {
width: 100%;
min-height: 42px;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 6px 10px;
border-radius: 10px;
background-color: #152238;
margin-bottom: 6px;
}
.sel-title {
color: #e6f0ff;
font-size: 16px;
flex: 1;
}
.sel-time {
color: #8895ad;
font-size: 13px;
}
.sel-active {
background-color: rgba(51,180,255,0.22);
border: 1px solid #33b4ff;
}
.sel-btn {
width: 100%;
height: 42px;
margin-bottom: 6px;
border-radius: 10px;
background-color: #152238;
color: #e6f0ff;
font-size: 15px;
text-align: left;
padding: 0 10px;
}
.sel-btn-active {
background-color: rgba(51,180,255,0.22);
border: 1px solid #33b4ff;
}
.kb-pinyin {
width: 100%;
justify-content: center;
align-items: center;
color: #7fd0ff;
font-size: 18px;
margin-bottom: 6px;
min-height: 24px;
}
.kb-cands {
width: 100%;
flex-direction: row;
justify-content: center;
margin-bottom: 8px;
}
.kb-cand {
width: 56px;
height: 38px;
margin: 0 3px;
justify-content: center;
align-items: center;
border-radius: 9px;
background-color: rgba(51,180,255,0.16);
border: 1px solid rgba(120,200,255,0.28);
color: #e6f0ff;
font-size: 18px;
text-align: center;
}
</style>