first commit
12
apps/miniprogram/.env.example
Normal file
@@ -0,0 +1,12 @@
|
||||
# MiniProgram 运维配置(不对用户展示)
|
||||
GATEWAY_URL=wss://gateway.example.com
|
||||
GATEWAY_TOKEN=replace-with-strong-random-token
|
||||
|
||||
# 可选:其余参数不填则使用代码默认值
|
||||
HOST_KEY_POLICY=strict
|
||||
CREDENTIAL_MEMORY_POLICY=remember
|
||||
GATEWAY_CONNECT_TIMEOUT_MS=12000
|
||||
WAIT_FOR_CONNECTED_TIMEOUT_MS=15000
|
||||
TERMINAL_BUFFER_MAX_ENTRIES=5000
|
||||
TERMINAL_BUFFER_MAX_BYTES=4194304
|
||||
MASK_SECRETS=true
|
||||
82
apps/miniprogram/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# RemoteConn MiniProgram(稳定基线 v3.0.1)
|
||||
|
||||
状态:`已对齐核心功能,当前稳定基线为 v3.0.1`
|
||||
目标版本:`v3.0.1`
|
||||
|
||||
本次 `v3.0.1` 主要收口小程序使用手册与主题口径:使用手册补成图文版,正文前置“为什么需要这个APP?”,并接入 `guide-mobile-*` 配图;主题预设补齐到与 Web 一致的 `21` 套,主题名称支持随语言切换且尽量压短为单词短名。当前版本不引入新的同步协议、终端协议或配置字段,继续沿用 `v3.0.0` 已明确的能力边界。
|
||||
|
||||
## 1. 当前已完成范围
|
||||
|
||||
1. 页面骨架:`connect`、`server settings`、`terminal`、`logs`、`records`、`settings`、`plugins`。
|
||||
2. 数据基础层:服务器/设置/闪念以“本地 storage + Gateway 同步”双层持久化,日志与插件运行记录仍保留本地存储;已补齐 `updatedAt / category / contextLabel / processed / discarded`。
|
||||
3. 传输:`wx.connectSocket` 网关连接、心跳、收发、断开。
|
||||
4. 分页:日志与闪念均为 15 条/页。
|
||||
5. 语音区:Frame2256 交互 + `/ws/asr` 实时采集上行 + 结果回填,支持分类选择、未连接可记录闪念、写入 `服务器名称-项目名` 快照。
|
||||
6. 闪念页:搜索、分类过滤、左滑废弃 / 已处理 / 复制 / 删除、正文编辑、分类快速改写、导出。
|
||||
7. 配置页:`记录 -> 闪念分类` 管理,支持新增、设默认、删除、按住拖动排序。
|
||||
8. 服务器:配置页支持自定义标签(逗号分隔),服务器卡片底部展示项目标签与自定义标签,并支持左滑复制 / 删除单台服务器。
|
||||
9. 插件:运行时启停/重载/移除、JSON 导入导出、命令执行、运行日志。
|
||||
10. 资源:图标路径统一版本 query(`v=2026022701`)。
|
||||
11. 终端:已按 xterm 风格完成 cell 光标模型重构,补齐真实 `cols/rows`、宽字符 continuation、自绘 caret、固定列宽渲染与双 probe 宽度测量,当前中文/英文混排输入已稳定。
|
||||
12. 会话续接:终端页返回其他页面后默认保活 `15` 分钟,可在设置页调到 `1~60` 分钟;回到同一服务器会优先复用原会话并恢复尾部缓冲。首次恢复优先使用保存时的 `lines + bufferCols / bufferRows` 还原当前屏幕,仅在后续真实几何变化时再使用 `replayText` 重建,避免顶部空白与裸露 `5;2H` 一类控制串回归。
|
||||
13. 连接反馈:服务器列表“连接”按钮与底栏 `shell` 按钮在活动连接态统一切到高饱和实底高亮,便于快速识别当前连接状态。
|
||||
14. 跳转主机:服务器配置页已支持跳板机主机、端口、用户名、认证方式与独立凭据;目录选择与终端连接链路均可透传第一跳/第二跳配置。
|
||||
15. AI 快速启动:服务器列表 `AI` 按钮和终端左上角 AI 按钮都会按“全局配置 -> 连接 -> AI连接”的默认 AI 直接启动;终端页支持 Codex 目录预检、二进制预检与连接后自动启动。当前同一条终端会话内新增了 AI 前台互斥保护,AI 运行期间重复点击会直接提示而不再把启动命令写进前台 TUI;当 Codex / Copilot 正常退出回到 shell 后,保护会自动解除。
|
||||
16. 终端语音与主题视觉已继续收口:展开语音区按钮默认全透明、分类胶囊改为贴文字高度、录音中输入框上方新增脉冲提示;终端顶栏与语音区按钮继续按 UI / Shell 双域规则渲染。
|
||||
|
||||
## 2. 当前说明
|
||||
|
||||
1. 机读对齐清单已更新为 `done`,见 `apps/miniprogram/parity.v2.6.0.json`。
|
||||
2. 小程序终端 VT 能力已进入 P0 基线:双缓冲、备用屏幕切换、`DSR / CPR / DA1 / DA2 / DECSTR`、基础局部重绘和移动端常用原始按键编码已接入。
|
||||
3. normal buffer 的 live tail 与最大滚动值已统一到同一口径,回到底部后不会再继续把当前命令行往上推。
|
||||
4. 当前仍建议通过微信开发者工具和真机做交互验收,尤其是服务器卡片左滑复制 / 删除、闪念左滑废弃 / 已处理 / 复制 / 删除、分类切换、语音记录链路,以及 `top / less / vim` 一类终端程序的抽样验证。
|
||||
5. 终端字号修改后仍存在偶发“吃字/显示不完整”遗留问题,当前版本在设置页增加了“修改字号后建议断开重连”的提示,作为临时规避方案。
|
||||
6. 当前业务数据采用“双层持久化”:
|
||||
服务器配置、用户设置与闪念记录会先写入小程序本地 storage,并在同步配置完整时通过 Gateway + SQLite 同步到服务端;
|
||||
日志、插件运行记录与终端会话缓冲仍仅保留在本地。
|
||||
通过 `npm run mini` 生成的 preview 预览二维码,不作为正式版跨设备同步与本地缓存连续性的验证依据。
|
||||
7. Gateway 的同步 SQLite 目前启用 `WAL` 模式:
|
||||
`data/remoteconn-sync.db-wal` 是写前日志,文件体积不直接等于当前有效同步数据量;
|
||||
需要结合 checkpoint 后的主库体积、`user_settings / user_servers / user_records` 行数与字段大小一起判断。
|
||||
8. 当前版本最重要的交互修复是:小程序终端在 `Codex` 持续输出期间,底部提示块缺行、状态行被裁掉与区域反复闪动的问题已收口;normal buffer viewport 会保留光标行之后仍真实存在的 footer,`CSI ? 2026 h/l` 同步刷新窗口也已做兼容。
|
||||
9. 同一轮交互修复还收口了高亮块透底细线问题:像 `> Use /skills to list available skills` 和代码块这类整行统一背景区域,背景优先提升到 line 层绘制,不再在行与行之间露出底色细缝,也没有新增渲染节点。
|
||||
10. 此前点击 Codex 连接选项后的首回显迟滞与等待期间按钮阻塞问题已收敛,当前版本不再将其作为已知遗留问题保留。
|
||||
11. 当前另记录一个低频连接时序遗留问题:偶发新连接后首屏只显示光标、提示符稍后才出现;初步定位为 `connected` 状态与光标可见时机早于首个可见 `stdout / prompt`,后续再继续优化。
|
||||
12. 会话续接恢复口径已明确:首次回到终端页时,以挂起前屏幕快照和当时终端几何作为权威来源;被裁剪过的 `replayText` 不再参与首次恢复,避免历史区顶部空白和定位参数残片再次出现。
|
||||
13. 时延诊断浮窗已收口成单张双轴平滑曲线图:左轴显示网关响应,右轴显示网络时延;顶部只保留两张摘要卡,面板配色跟随终端主题反相推导,深色终端会自动切到更深的蓝橙曲线与指标色;同一服务器断开重连后会尽量延续最近 `30` 个采样点。
|
||||
14. 当前小程序终端语音播报仍属于待优化能力:播报文本提取与轮次稳定判定还不够准确,长时间 `Codex` 交互时也会放大小程序端响应压力;现阶段暂不建议默认使用,先作为遗留问题保留。
|
||||
15. 当前另记录一个 AI 交互期输入遗留问题:在 `Codex` 等 AI 持续输出期间,点击 shell 激活区弹出软键盘后,输入框/激活区仍可能发生跳跃,导致无法稳定连续输入;该问题尚未完成稳定修复,先按已知遗留问题登记。
|
||||
|
||||
## 3. 对齐清单
|
||||
|
||||
机读清单:`apps/miniprogram/parity.v2.6.0.json`
|
||||
|
||||
## 4. 实施依据
|
||||
|
||||
1. `docs/records-enhancement-plan-2026-03-06.md`
|
||||
2. `docs/miniprogram-config-implementation-plan-2026-02-28.md`
|
||||
3. `docs/xterm-cursor-algorithm-2026-03-07.md`
|
||||
4. `docs/miniprogram-terminal-cursor-gap-analysis-2026-03-07.md`
|
||||
5. `docs/ssh-jump-encryption-diagram-2026-03-07.md`
|
||||
6. `docs/miniprogram-terminal-vt-guardrails-2026-03-08.md`
|
||||
7. `docs/miniprogram-terminal-vt-implementation-plan-2026-03-08.md`
|
||||
8. `docs/miniprogram-codex-footer-flicker-optimization-plan-2026-03-11.md`
|
||||
|
||||
## 5. 本地调试
|
||||
|
||||
1. 使用微信开发者工具打开 `apps/miniprogram` 目录。
|
||||
2. 如需连通网关,在 `apps/miniprogram/.env` 中配置运维配置:至少包含 `GATEWAY_URL` 和 `GATEWAY_TOKEN`(可参考 `apps/miniprogram/.env.example`,该配置不对最终用户展示)。
|
||||
3. 回到“服务器”页创建服务器并进入终端测试连通性。
|
||||
|
||||
## 6. 命令行预览
|
||||
|
||||
在仓库根目录可直接使用以下脚本:
|
||||
|
||||
1. `npm run mini`
|
||||
在终端输出小程序预览二维码;若终端二维码首次渲染失败,会自动在 shell 中重画一次。
|
||||
|
||||
说明:
|
||||
|
||||
1. 该命令会先读取 `apps/miniprogram/.env`,并生成 `apps/miniprogram/utils/opsEnv.js` 供小程序运行时使用。
|
||||
2. 默认使用仓库根目录私钥:`./private.wxa0e7e5a27599cf6c.key`。
|
||||
3. 执行前,需要在微信公众平台完成上传密钥下载和 IP 白名单配置。
|
||||
23
apps/miniprogram/app.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/* global App, console, require */
|
||||
|
||||
const { ensureSyncBootstrap } = require("./utils/syncService");
|
||||
|
||||
/**
|
||||
* 微信小程序应用入口。
|
||||
* 说明:
|
||||
* 1. 当前对外版本口径统一为 v3.0.0;
|
||||
* 2. 服务器、设置与闪念采用“本地 storage + Gateway 同步”双层持久化;
|
||||
* 3. 日志、插件运行记录与终端运行态仍仅保留本地。
|
||||
*/
|
||||
App({
|
||||
globalData: {
|
||||
appVersion: "3.0.0",
|
||||
platform: "wechat-miniprogram"
|
||||
},
|
||||
|
||||
onLaunch() {
|
||||
// 启动阶段仅记录版本,用于后续灰度/埋点标记。
|
||||
console.info("[RemoteConn MiniProgram] launch", this.globalData);
|
||||
ensureSyncBootstrap();
|
||||
}
|
||||
});
|
||||
27
apps/miniprogram/app.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/connect/index",
|
||||
"pages/server-settings/index",
|
||||
"pages/terminal/index",
|
||||
"pages/logs/index",
|
||||
"pages/records/index",
|
||||
"pages/settings/index",
|
||||
"pages/plugins/index",
|
||||
"pages/about/index",
|
||||
"pages/about-manual/index",
|
||||
"pages/about-feedback/index",
|
||||
"pages/about-privacy/index",
|
||||
"pages/about-changelog/index",
|
||||
"pages/about-app/index"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTitleText": "RemoteConn",
|
||||
"navigationBarBackgroundColor": "#192b4d",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#192b4d",
|
||||
"backgroundTextStyle": "light"
|
||||
},
|
||||
"style": "v2",
|
||||
"lazyCodeLoading": "requiredComponents",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
391
apps/miniprogram/app.wxss
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 小程序全局样式:
|
||||
* 1. 颜色变量与当前小程序基线 v2.9.1 保持一致;
|
||||
* 2. 抽取页面骨架、工具栏、面板、按钮、底栏等通用样式;
|
||||
* 3. 页面只做局部差异,尽量复用这里的语义类。
|
||||
*/
|
||||
page {
|
||||
--bg: #192b4d;
|
||||
--shell-bg: #192b4d;
|
||||
--shell-text: #e6f0ff;
|
||||
--shell-accent: #9ca9bf;
|
||||
--shell-font-family: JetBrains Mono, "SFMono-Regular", Menlo, monospace;
|
||||
--shell-font-size: 15px;
|
||||
--shell-line-height: 1.4;
|
||||
--surface: rgba(20, 32, 56, 0.64);
|
||||
--surface-border: rgba(118, 156, 213, 0.2);
|
||||
--surface-shadow: rgba(91, 210, 255, 0.18);
|
||||
--text: #e6f0ff;
|
||||
--muted: #9cb1cf;
|
||||
--accent: #5bd2ff;
|
||||
--btn: #adb9cd;
|
||||
--btn-border: #7e8ca4;
|
||||
--btn-border-strong: #95a2b9;
|
||||
--btn-bg: #374767;
|
||||
--btn-bg-strong: #4b5b79;
|
||||
--btn-bg-active: #3f506e;
|
||||
--btn-text: #dce6f6;
|
||||
--btn-danger-border: #4eb1db;
|
||||
--btn-danger-bg: #285074;
|
||||
--chip-bg: #2b5a7f;
|
||||
--chip-text: #e6f0ff;
|
||||
--accent-divider: rgba(91, 210, 255, 0.6);
|
||||
--switch-on-bg: #6f7d97;
|
||||
--switch-off-bg: #4a5a78;
|
||||
--switch-knob: #f1f7ff;
|
||||
--icon-btn-bg: #2e3f5f;
|
||||
--icon-btn-bg-strong: #3d4d6c;
|
||||
--accent-bg: #25496d;
|
||||
--accent-bg-strong: #2e6086;
|
||||
--accent-border: #49a3cd;
|
||||
--accent-ring: rgba(91, 210, 255, 0.22);
|
||||
--accent-shadow: rgba(91, 210, 255, 0.28);
|
||||
--shell-btn-bg: #50607d;
|
||||
--shell-btn-text: #c1cddf;
|
||||
--shell-accent-bg: #314262;
|
||||
--shell-accent-bg-strong: #435371;
|
||||
--shell-accent-border: #7a88a1;
|
||||
--shell-accent-ring: rgba(156, 169, 191, 0.24);
|
||||
--shell-accent-shadow: rgba(156, 169, 191, 0.3);
|
||||
--terminal-touch-tools-bg: rgba(25, 43, 77, 0.8);
|
||||
--danger: #ff7f92;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 26rpx;
|
||||
font-family: "PingFang SC", "SF Pro Text", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
view,
|
||||
text,
|
||||
input,
|
||||
textarea,
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
button::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
bottom: -8px;
|
||||
left: -8px;
|
||||
}
|
||||
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-toolbar {
|
||||
flex: 0 0 72rpx;
|
||||
height: 72rpx;
|
||||
background: var(--bg);
|
||||
border-bottom: 1rpx solid var(--accent-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.toolbar-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.settings-save-status {
|
||||
font-size: 22rpx;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
padding: 16rpx 16rpx 0;
|
||||
}
|
||||
|
||||
.surface-panel {
|
||||
background: var(--bg);
|
||||
border-top: 1rpx solid transparent;
|
||||
border-bottom: 1rpx solid transparent;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.surface-scroll {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.list-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1rpx solid var(--surface-border);
|
||||
background: var(--surface);
|
||||
border-radius: 20rpx;
|
||||
padding: 18rpx;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row.between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 48rpx !important;
|
||||
height: 48rpx !important;
|
||||
min-width: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
color: inherit !important;
|
||||
padding: 0 !important;
|
||||
line-height: 1 !important;
|
||||
font-size: 0 !important;
|
||||
display: inline-flex !important;
|
||||
overflow: visible !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.icon-btn.disabled,
|
||||
.icon-btn.wx-button-disabled {
|
||||
opacity: 0.45 !important;
|
||||
}
|
||||
|
||||
.icon-img {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一 SVG 按钮按压反馈:
|
||||
* 1. 所有页面共用同一套 hover-class / touch 状态,不再各页散写重复动画;
|
||||
* 2. 默认只负责过渡和图标缩放,不强行改常态背景,具体底板/阴影由各页面覆写变量;
|
||||
* 3. 带文字按钮也能复用,只需覆写圆角、缩放和 hover 背景变量。
|
||||
*/
|
||||
.svg-press-btn {
|
||||
--svg-press-active-radius: 999rpx;
|
||||
--svg-press-active-bg: transparent;
|
||||
--svg-press-active-shadow: none;
|
||||
--svg-press-active-scale: 0.92;
|
||||
--svg-press-icon-opacity: 1;
|
||||
--svg-press-icon-active-opacity: 0.7;
|
||||
--svg-press-icon-active-scale: 0.9;
|
||||
transition:
|
||||
border-radius 160ms ease,
|
||||
transform 140ms ease,
|
||||
background-color 140ms ease,
|
||||
box-shadow 140ms ease,
|
||||
opacity 140ms ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.svg-press-btn .svg-press-icon {
|
||||
opacity: var(--svg-press-icon-opacity);
|
||||
transform: translateY(0) scale(1);
|
||||
transition:
|
||||
transform 140ms ease,
|
||||
opacity 140ms ease;
|
||||
}
|
||||
|
||||
.svg-press-btn:active,
|
||||
.svg-press-btn-hover {
|
||||
border-radius: var(--svg-press-active-radius) !important;
|
||||
background: var(--svg-press-active-bg) !important;
|
||||
background-color: var(--svg-press-active-bg) !important;
|
||||
box-shadow: var(--svg-press-active-shadow) !important;
|
||||
transform: scale(var(--svg-press-active-scale));
|
||||
}
|
||||
|
||||
.svg-press-btn:active .svg-press-icon,
|
||||
.svg-press-btn-hover .svg-press-icon {
|
||||
opacity: var(--svg-press-icon-active-opacity);
|
||||
transform: translateY(1rpx) scale(var(--svg-press-icon-active-scale));
|
||||
}
|
||||
|
||||
.svg-press-btn.disabled,
|
||||
.svg-press-btn.wx-button-disabled {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: 1rpx solid var(--btn-border) !important;
|
||||
background: var(--btn-bg) !important;
|
||||
background-color: var(--btn-bg) !important;
|
||||
color: var(--btn-text) !important;
|
||||
border-radius: 16rpx;
|
||||
padding: 8rpx 14rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: var(--btn-bg-strong);
|
||||
border-color: var(--btn-border-strong);
|
||||
}
|
||||
|
||||
.btn.danger {
|
||||
border-color: var(--btn-danger-border);
|
||||
background: var(--btn-danger-bg);
|
||||
}
|
||||
|
||||
.input,
|
||||
.textarea {
|
||||
width: 100%;
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid rgba(141, 187, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--text);
|
||||
padding: 14rpx 16rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.field-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
width: calc((100% - 12rpx) / 2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.field.wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field text {
|
||||
color: var(--muted);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.state-chip {
|
||||
border-radius: 999rpx;
|
||||
padding: 4rpx 10rpx;
|
||||
border: 1rpx solid var(--btn-border);
|
||||
background: var(--btn-bg);
|
||||
font-size: 20rpx;
|
||||
color: var(--btn-text);
|
||||
}
|
||||
|
||||
.state-connected {
|
||||
border-color: var(--accent-border);
|
||||
background: var(--accent-bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.state-error {
|
||||
border-color: var(--danger);
|
||||
background: var(--btn-danger-bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
padding: 28rpx 0;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
flex: 0 0 104rpx;
|
||||
height: 104rpx;
|
||||
background: var(--bg);
|
||||
border-top: 1rpx solid var(--accent-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 64rpx 0 32rpx;
|
||||
}
|
||||
|
||||
.bottom-right-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.bottom-nav-btn.active {
|
||||
background: var(--btn-bg-active);
|
||||
}
|
||||
|
||||
.records-pagination {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.records-pagination-text {
|
||||
min-width: 180rpx;
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: var(--muted);
|
||||
}
|
||||
BIN
apps/miniprogram/assets/guide/guide-mobile-01-server-list.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
apps/miniprogram/assets/guide/guide-mobile-02-server-config.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 60 KiB |
BIN
apps/miniprogram/assets/guide/guide-mobile-05-settings-ui.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 36 KiB |
BIN
apps/miniprogram/assets/guide/guide-mobile-09-records.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
apps/miniprogram/assets/guide/guide-mobile-10-about.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
3
apps/miniprogram/assets/icons/about.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#FFC16E" d="M11 22c6.075 0 11-4.925 11-11S17.075 0 11 0 0 4.925 0 11s4.925 11 11 11ZM9.281 5.5a1.719 1.719 0 0 1 3.438 0v.687a1.719 1.719 0 0 1-3.438 0V5.5Zm0 5.5a1.719 1.719 0 0 1 3.438 0v5.5a1.719 1.719 0 0 1-3.438 0V11Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 343 B |
3
apps/miniprogram/assets/icons/add.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M11.005 0C4.93 0 0 4.924 0 11c0 6.074 4.93 11 11.005 11 6.071 0 11-4.926 11-11 0-6.076-4.929-11-11-11Zm5.5 12.373h-4.129V16.5H9.63v-4.127H5.506v-2.75h4.123V5.5h2.747v4.124h4.13v2.75Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
4
apps/miniprogram/assets/icons/ai.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M10.962 0a10.909 10.909 0 0 0-6.083 1.845A10.988 10.988 0 0 0 .84 6.775a11.05 11.05 0 0 0-.633 6.353 11.017 11.017 0 0 0 2.985 5.636 10.93 10.93 0 0 0 5.599 3.02c2.122.429 4.323.216 6.324-.613a10.958 10.958 0 0 0 4.92-4.04 11.038 11.038 0 0 0 1.858-6.107v-.015A11.033 11.033 0 0 0 21.07 6.8a10.988 10.988 0 0 0-2.364-3.57A10.928 10.928 0 0 0 15.16.842 10.885 10.885 0 0 0 10.975 0h-.013Zm.614 14.925-.772-1.833H7.8l-.772 1.833H5.053l3.276-7.96h1.935l3.278 7.96h-1.966Zm4.511 0H14.4v-7.96h1.687v7.96Z"/>
|
||||
<path fill="#67D1FF" d="M8.18 11.668h2.255l-1.127-2.78-1.128 2.78Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 704 B |
3
apps/miniprogram/assets/icons/ai矩连.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="24" fill="none" viewBox="0 0 52 24">
|
||||
<path fill="#FFC16E" d="M7.902 0c.033 0 .55 7.989 1.554 23.967L9.423 24H6.708c-.055 0-.29-3.621-.704-10.863H3.452c-.436 7.198-.665 10.814-.687 10.846L2.732 24h-2.7L0 23.967C.992 7.99 1.505 0 1.538 0h6.364Zm-3.78 2.29-.49 8.573h2.192l-.49-8.573H4.122ZM15.642 0c.022 0 .038.016.05.05v23.9c0 .012-.017.028-.05.05h-2.716c-.01 0-.027-.016-.049-.05V.05c0-.023.017-.039.05-.05h2.715Zm3.021 0h2.204v1.102h3.954v1.802h-2.178v5.628h2.178v1.815h-2.204c-.086.64-.13.977-.13 1.011h.623c.026 0 .06.169.104.506.008.008.73 3.963 2.165 11.864.026.086.043.177.052.272h-1.984c-.017 0-.056-.195-.117-.584l-1.452-7.87h-.013L20.672 24h-2.01c.01-.043.022-.13.04-.26.008-.068.017-.12.025-.155.009-.06.013-.117.013-.169.044-.293.087-.574.13-.842.104-.787.216-1.595.337-2.425l.117-.843a4.64 4.64 0 0 1 .039-.246l.013-.13.025-.142c.035-.225.061-.415.078-.57a.335.335 0 0 0 .013-.105l.013-.064.013-.065a2.54 2.54 0 0 1 .04-.285l.038-.26.039-.26c.017-.172.043-.37.078-.595l.026-.13.013-.13.039-.233.363-2.606c.051-.337.108-.726.168-1.167.017-.139.043-.325.078-.558l.039-.272.039-.26.116-.881h-1.931V8.532h1.957V2.904h-1.957V0Zm7.26 0h7.34v1.815h-5.317V8.22a96.79 96.79 0 0 1 1.919-.557l.233-.078.22-.065c.182-.052.33-.09.442-.116.328-.104.613-.186.855-.247.165-.052.342-.103.532-.155.07-.026.147-.052.233-.078l.117-.026.104-.039c.104-.026.199-.052.285-.078l.104-.026a.501.501 0 0 1 .065-.026v.013c0 .649.004.994.013 1.038a172.39 172.39 0 0 0-.013 2.333v1.064c0 1.288.004 2.442.013 3.462a7.516 7.516 0 0 0-.013.583L27.946 16.7v5.485h5.316V24h-7.339V0Zm2.023 14.807 3.099-.895V9.206l-3.099.895v4.706ZM39.37 0c.008 0 .016.009.025.026v3.267c0 .018-.009.03-.026.04h-2.152c-.009 0-.017-.014-.026-.04V.026c0-.009.008-.017.026-.026h2.152Zm5.47 0c.018 0 .027.009.027.026l-.208 1.141c.009.009.013.022.013.039h6.704c.008 0 .021.009.038.026v1.75c0 .009-.013.018-.038.026h-7.04c-.736 4.218-1.107 6.384-1.116 6.496h2.256V4.707c0-.018.009-.03.026-.04h2.152c.018 0 .03.014.04.04v4.797h3.474c.017 0 .03.013.039.039v1.737c0 .018-.013.03-.039.04h-3.475v2.813l3.423-.7.013.013v2.035c0 .026-.168.065-.505.117-.087.026-1.064.233-2.93.622v4.5c0 .008-.014.017-.04.026h-2.152c-.009 0-.017-.01-.026-.026v-4.046h-.013c-.017 0-1.5.307-4.447.92 0-.017-.005-.025-.013-.025v-2.023c0-.026.276-.09.83-.194.008-.009 1.223-.26 3.643-.752v-3.28h-4.434c-.009 0-.022-.014-.04-.04V9.57c.018-.121.394-2.308 1.129-6.56h-1.297c-.008 0-.017-.01-.026-.027v-1.75c0-.009.009-.017.026-.026h1.608c.009 0 .065-.285.169-.856.017-.233.047-.35.09-.35h2.14Zm-5.108 5.29c.017 0 .03.013.04.04v15.22c0 .043.09.138.271.285.96.9 1.457 1.349 1.491 1.349h9.816c.017 0 .03.013.038.039v1.737c0 .017-.012.03-.038.039H40.277c-.035 0-.22-.164-.558-.493a27.372 27.372 0 0 1-.428-.376h-.026c-.147.484-.237.77-.272.856a.115.115 0 0 1-.052.013h-.233c-.58 0-1.202-.004-1.867-.013l-.013-.026c.458-1.53.7-2.368.726-2.515V7.106h-.674c-.044 0-.065-.022-.065-.065V5.329c0-.017.013-.03.039-.039h2.878Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
4
apps/miniprogram/assets/icons/back.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="#67D1FF" d="M17.053 10.8h-7.24l3.043-3.045a1.216 1.216 0 0 0 .024-1.718 1.216 1.216 0 0 0-1.72.02l-5.064 5.066c-.011.01-.026.013-.037.024a1.185 1.185 0 0 0-.342.866c0 .305.112.611.344.841.01.01.022.013.032.021l5.067 5.067c.482.48 1.251.493 1.72.024a1.216 1.216 0 0 0-.024-1.718L9.811 13.2h7.242c.68 0 1.232-.538 1.232-1.2 0-.662-.552-1.2-1.232-1.2Z"/>
|
||||
<path fill="#67D1FF" d="M12 0A11.998 11.998 0 0 0 0 12c0 6.629 5.371 12 12 12s12-5.371 12-12S18.629 0 12 0Zm0 21.6a9.599 9.599 0 1 1 0-19.198A9.599 9.599 0 0 1 12 21.6Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 643 B |
3
apps/miniprogram/assets/icons/backspace.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" fill="none" viewBox="0 0 20 16">
|
||||
<path fill="#FFC16E" d="m15.82 10.519-2.968-3.01 2.969-3.011-1.172-1.173-2.969 3.01-3.008-3.01-1.171 1.173 3.007 3.01-3.007 3.01 1.171 1.173 3.008-3.01 2.97 3.01 1.17-1.172ZM18.32 0c.444 0 .834.17 1.172.508.339.338.508.73.508 1.173v11.652c0 .443-.17.834-.508 1.173-.338.338-.728.508-1.171.508H5.82c-.521 0-.964-.248-1.329-.744L0 7.507 4.492.743C4.857.248 5.3 0 5.821 0h12.5Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
3
apps/miniprogram/assets/icons/cancel.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M10.937 0C4.897 0 0 4.896 0 10.937c0 6.04 4.896 10.936 10.937 10.936 6.04 0 10.936-4.896 10.936-10.936S16.977 0 10.937 0Zm5.694 14.507a1.364 1.364 0 0 1 0 1.923l-.481.48a1.364 1.364 0 0 1-1.923 0l-3.43-3.43-3.43 3.43a1.364 1.364 0 0 1-1.924 0l-.48-.48a1.364 1.364 0 0 1 0-1.923l3.43-3.43-3.71-3.71a1.364 1.364 0 0 1 0-1.924l.48-.48a1.364 1.364 0 0 1 1.924 0l3.71 3.71 3.71-3.71a1.364 1.364 0 0 1 1.923 0l.48.48a1.364 1.364 0 0 1 0 1.923l-3.71 3.71 3.43 3.43Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 591 B |
3
apps/miniprogram/assets/icons/clear-input.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="24" fill="none" viewBox="0 0 22 24">
|
||||
<path fill="#67D1FF" d="M14.071.652a3.268 3.268 0 0 0-1.694-.614c-.444-.03-.89-.042-1.335-.036h-.27c-.43-.008-.862.004-1.292.036a3.266 3.266 0 0 0-1.726.64c-.346.276-.633.62-.841 1.011-.2.35-.398.786-.622 1.28l-.39.85h-4.81a1.091 1.091 0 1 0 0 2.183h.818V19.91A4.09 4.09 0 0 0 6 24h9.818a4.09 4.09 0 0 0 4.09-4.09V6.002h.818a1.092 1.092 0 0 0 0-2.18h-4.71l-.465-.961c-.195-.42-.408-.833-.638-1.235a3.254 3.254 0 0 0-.841-.974Zm-.48 3.17H8.3c.154-.358.323-.708.507-1.051a1.003 1.003 0 0 1 .87-.56c.291-.026.67-.026 1.27-.026.586 0 .955 0 1.237.026a1.004 1.004 0 0 1 .859.539c.144.237.303.56.55 1.071Zm-5.41 14.41a.818.818 0 0 1-.818-.817V10.87a.818.818 0 0 1 1.636 0v6.545a.818.818 0 0 1-.818.818Zm6.273-7.362v6.545a.818.818 0 0 1-1.636 0V10.87a.818.818 0 0 1 1.636 0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
3
apps/miniprogram/assets/icons/clear.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="23" fill="none" viewBox="0 0 24 23">
|
||||
<path fill="#E6F0FF" d="M22.313 6.835c0-1.14-.91-2.05-2.05-2.05h-4.098V1.94c0-1.14-.89-1.94-2.03-1.94-1.14 0-2.07.8-2.07 1.94v2.846H8.199c-1.14 0-2.05.91-2.05 2.05v2.049h16.274v-2.05h-.11Zm0 4.1H6.039S4.899 23 0 23h5.809c2.28 0 3.298-6.597 3.298-6.597s1.019 5.918.11 6.597h3.528c1.589-.23 1.819-7.506 1.819-7.506s1.589 7.397 1.37 7.397h-3.189 5.009c1.479-.34 1.588-5.688 1.588-5.688s.679 5.688.46 5.688h-2.158 2.729c4.098.109 4.329-5.578 1.94-11.957Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
4
apps/miniprogram/assets/icons/codex.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="22" fill="none" viewBox="0 0 24 22">
|
||||
<path fill="#E6F0FF" d="M9.7 8.708c-.15-.458-.25-.916-.4-1.375-.3 1.146-.65 2.384-1 3.484l-.35 1.1h2.65l-.35-1.1a32.72 32.72 0 0 0-.55-2.109Z"/>
|
||||
<path fill="#E6F0FF" d="M24 6.417v-5.5C24 .412 23.55 0 23 0h-6v1.833H7V0H1C.45 0 0 .412 0 .917v5.5h2v9.166H0v5.5C0 21.588.45 22 1 22h6v-1.833h10V22h6c.55 0 1-.413 1-.917v-5.5h-2V6.417h2Zm-9.3 10.129-.05.046H12.1c-.05 0-.05 0-.05-.046l-.85-2.796H7.45l-.85 2.796c0 .046-.05.046-.05.046H4.1s-.05 0-.05-.046V16.5L7.9 5.454c0-.046.05-.046.05-.046h2.85c.05 0 .05 0 .05.046L14.7 16.5v.046Zm3.85-.046c0 .046-.05.046-.1.046h-2.4c-.05 0-.1-.046-.1-.046V5.454c0-.046.05-.046.1-.046h2.4c.05 0 .1.046.1.046V16.5Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 755 B |
3
apps/miniprogram/assets/icons/config.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="#67D1FF" d="M22.286 9.429h-1.213a9.45 9.45 0 0 0-.857-2.023l.857-.857a1.715 1.715 0 0 0 0-2.422L19.877 2.91a1.714 1.714 0 0 0-2.421 0l-.857.857a9.226 9.226 0 0 0-2.028-.836V1.714A1.713 1.713 0 0 0 12.857 0h-1.714a1.714 1.714 0 0 0-1.714 1.714v1.217a9.227 9.227 0 0 0-2.023.836l-.857-.857a1.714 1.714 0 0 0-2.422 0L2.91 4.123a1.714 1.714 0 0 0 0 2.421l.857.857a9.45 9.45 0 0 0-.857 2.023H1.714A1.714 1.714 0 0 0 0 11.14v1.714a1.714 1.714 0 0 0 1.714 1.714h1.213a9.45 9.45 0 0 0 .857 2.023l-.857.857a1.714 1.714 0 0 0 0 2.422l1.213 1.21a1.714 1.714 0 0 0 2.421 0l.858-.857c.634.36 1.309.644 2.01.845v1.217A1.714 1.714 0 0 0 11.143 24h1.714a1.713 1.713 0 0 0 1.714-1.714v-1.217a9.233 9.233 0 0 0 2.023-.836l.857.857a1.714 1.714 0 0 0 2.422 0l1.213-1.213a1.714 1.714 0 0 0 0-2.421l-.857-.857a9.45 9.45 0 0 0 .857-2.023h1.2A1.714 1.714 0 0 0 24 12.86v-1.718a1.714 1.714 0 0 0-1.714-1.714Zm-7.822 4.954a3.429 3.429 0 1 1-4.93-4.767 3.429 3.429 0 0 1 4.93 4.767Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
7
apps/miniprogram/assets/icons/connect.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M11.023 0c6.08.04 10.993 4.985 10.964 11.031C21.957 17.103 17 22.035 10.96 22 4.86 21.966-.057 16.988 0 10.908.06 4.868 5.02-.04 11.024 0ZM4.145 14.27c.096 1.317.73 2.444 2.067 3.066 1.308.61 2.626.497 3.711-.45 1.142-.995 2.204-2.094 3.21-3.229.831-.938.926-2.095.55-3.277-.247-.77-.71-1.066-1.318-.872-.575.185-.77.655-.556 1.417.18.643.135 1.24-.344 1.732-.864.89-1.727 1.783-2.63 2.63-.696.65-1.661.612-2.268-.013-.625-.642-.628-1.629.01-2.33.308-.34.655-.646.968-.982.4-.428.398-1.026.018-1.397-.362-.352-.942-.363-1.355.019a17.04 17.04 0 0 0-1.22 1.24c-.574.658-.835 1.444-.843 2.446Zm4.118-3.929c.014.135.027.396.072.65.03.176.093.347.159.514.254.642.763.924 1.302.73.497-.178.725-.72.525-1.343-.244-.767-.076-1.41.488-1.973.793-.79 1.575-1.594 2.374-2.38.687-.676 1.679-.714 2.318-.108.665.629.682 1.635.025 2.37-.293.328-.625.62-.925.942-.426.458-.437.999-.047 1.39.406.407.947.434 1.387.014.462-.442.92-.895 1.312-1.396 1.093-1.394.914-3.464-.372-4.695-1.305-1.25-3.353-1.363-4.696-.181-1.034.91-2.005 1.896-2.966 2.885-.65.671-.95 1.516-.956 2.581Z"/>
|
||||
<path fill="#67D1FF" d="M20.166 5.268c0-.11-.002-.219-.004-.328A11.05 11.05 0 0 0 11.022 0C5.019-.04.059 4.87 0 10.908c-.037 3.828 1.898 7.217 4.86 9.212.148.004.297.006.445.006 8.207.002 14.86-6.65 14.86-14.858Zm-7.033 8.39c-1.007 1.133-2.068 2.233-3.21 3.23-1.085.945-2.404 1.057-3.711.449-1.338-.622-1.97-1.75-2.067-3.067.007-1.002.269-1.788.845-2.444a17.04 17.04 0 0 1 1.219-1.24c.413-.382.993-.373 1.355-.02.38.373.382.97-.018 1.399-.313.336-.66.643-.969.983-.637.7-.634 1.687-.009 2.33.607.624 1.574.663 2.267.011.905-.847 1.766-1.74 2.63-2.629.48-.492.526-1.09.345-1.732-.215-.762-.019-1.232.556-1.417.607-.194 1.072.102 1.317.872.376 1.18.281 2.337-.55 3.275Zm1.421-2.524c-.391-.392-.38-.933.047-1.39.3-.323.632-.615.925-.943.657-.735.64-1.74-.025-2.37-.638-.604-1.631-.566-2.318.108-.8.786-1.58 1.59-2.374 2.38-.564.561-.732 1.206-.488 1.973.199.624-.028 1.164-.525 1.343-.54.194-1.048-.09-1.302-.73a2.59 2.59 0 0 1-.16-.513c-.044-.255-.057-.516-.07-.65.006-1.065.306-1.909.957-2.58.961-.99 1.933-1.974 2.967-2.885 1.343-1.184 3.39-1.07 4.696.18 1.286 1.231 1.465 3.302.372 4.696-.393.5-.852.954-1.312 1.396-.443.418-.984.39-1.39-.015Z"/>
|
||||
<path fill="#67D1FF" d="M11.73 10.517c-.045-.524.17-.86.635-1.008.109-.036.223-.056.337-.058a14.81 14.81 0 0 0 2.111-3.4c-.545-.138-1.16.025-1.629.488-.799.786-1.58 1.59-2.374 2.38-.564.561-.732 1.206-.487 1.973.105.33.09.634-.02.876.503-.387.98-.804 1.427-1.251Z"/>
|
||||
<path fill="#67D1FF" d="M.002 10.908c-.014 1.342.22 2.674.69 3.93 1.169.044 2.339-.05 3.486-.279a4.1 4.1 0 0 1-.031-.291c.006-1.003.268-1.789.845-2.445a17.04 17.04 0 0 1 1.218-1.24c.413-.382.994-.373 1.355-.019.381.373.382.97-.017 1.398-.313.336-.66.643-.97.983-.293.324-.45.708-.473 1.09a14.72 14.72 0 0 0 3.485-1.75c-.46.063-.875-.223-1.095-.781a2.593 2.593 0 0 1-.159-.513c-.044-.255-.058-.516-.071-.651.006-1.065.306-1.908.958-2.58.96-.988 1.932-1.974 2.966-2.885.925-.815 2.183-1.015 3.303-.652.283-.955.474-1.95.559-2.976A11.018 11.018 0 0 0 11.026.002C5.02-.04.06 4.869.002 10.908Z"/>
|
||||
<path fill="#67D1FF" d="M.127 9.362c5.187-.924 9.441-4.54 11.27-9.35A15.24 15.24 0 0 0 11.023 0C5.543-.036.933 4.053.127 9.362Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
10
apps/miniprogram/assets/icons/copy.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path
|
||||
fill="#67D1FF"
|
||||
d="M8.25 1.833c0-1.012.821-1.833 1.833-1.833h8.25c1.012 0 1.833.821 1.833 1.833v10.084a1.833 1.833 0 0 1-1.833 1.833H17.05V1.833H8.25Z"
|
||||
/>
|
||||
<path
|
||||
fill="#67D1FF"
|
||||
d="M3.667 4.583c0-1.012.821-1.833 1.833-1.833h8.25c1.012 0 1.833.821 1.833 1.833v13.75A1.833 1.833 0 0 1 13.75 20.167H5.5a1.833 1.833 0 0 1-1.833-1.834V4.583Zm2.75 1.834v1.833H12.833V6.417H6.417Zm0 4.583v1.833H12.833V11H6.417Zm0 4.583v1.834h4.583v-1.834H6.417Z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 573 B |
3
apps/miniprogram/assets/icons/create.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M11.005 0C4.93 0 0 4.924 0 11c0 6.074 4.93 11 11.005 11 6.071 0 11-4.926 11-11 0-6.076-4.929-11-11-11Zm5.5 12.373h-4.129V16.5H9.63v-4.127H5.506v-2.75h4.123V5.5h2.747v4.124h4.13v2.75Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
4
apps/miniprogram/assets/icons/ctrlc.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="20" fill="none" viewBox="0 0 19 20">
|
||||
<path fill="#FFC16E" d="M7.154 1.265h1.994c.008 0 .011.006.011.016v2.562h1.703v1.851H5.788c-.008 0-.012-.005-.012-.015V3.858c0-.01.004-.015.012-.015h1.366V1.265Zm0 4.846H9.16v2.083c0 .335.04.625.118.872.09.185.276.278.56.278.25 0 .517-.075.8-.224.169 1.27.253 1.922.253 1.953h-.006c-.392.272-.933.408-1.624.408h-.022c-.982 0-1.604-.416-1.865-1.25-.146-.396-.219-.99-.219-1.782V6.111Zm8.62-2.276c.235 0 .446.026.633.077v.008c0 .025-.077.81-.23 2.353a1.844 1.844 0 0 0-.711-.139c-.68 0-1.225.324-1.636.972h-.005v-2.16c.201-.458.47-.772.806-.941.194-.114.575-.17 1.143-.17Zm-4.324.139h2.09v7.507h-2.09V3.974ZM16.967 0h2.022c.007 0 .011.005.011.015v11.459h-2.033V0ZM2.879 2.984h.084c.672 0 1.234.244 1.686.733.086.098.179.226.28.386.15.262.26.52.33.771.075.273.13.587.168.942l-1.87.254c-.049-.34-.124-.589-.224-.748-.124-.175-.271-.262-.443-.262-.007 0-.011-.006-.011-.016v-2.06Zm-.275.008v2.106c-.09.016-.2.11-.33.286-.198.334-.297.861-.297 1.581v.108c.004.921.163 1.513.476 1.775a.686.686 0 0 0 .387.124c.388 0 .634-.286.739-.857l.022-.224H5.49c-.019.252-.05.502-.095.749a4.103 4.103 0 0 1-.415 1.196c-.467.807-1.182 1.211-2.145 1.211-.866 0-1.548-.324-2.044-.972C.263 9.38 0 8.377 0 7.065c0-1.717.452-2.924 1.355-3.618a2.6 2.6 0 0 1 .622-.324c.18-.062.388-.106.627-.131Z"/>
|
||||
<path fill="#5BD2FF" d="M10.821 11.937h.084c.673 0 1.234.244 1.686.732.086.098.18.227.28.386.15.263.26.52.33.772.075.273.131.586.169.941l-1.87.255c-.05-.34-.124-.589-.225-.748-.123-.175-.27-.263-.443-.263-.007 0-.01-.005-.01-.015v-2.06Zm-.274.007v2.107c-.09.015-.2.11-.33.285-.198.335-.297.862-.297 1.582v.108c.003.92.162 1.512.476 1.775a.685.685 0 0 0 .386.123c.389 0 .635-.285.74-.856l.022-.224h1.887c-.018.252-.05.502-.095.748a4.102 4.102 0 0 1-.414 1.196C12.455 19.596 11.74 20 10.776 20c-.866 0-1.547-.324-2.044-.972-.526-.695-.79-1.698-.79-3.01 0-1.718.452-2.924 1.356-3.619.15-.113.357-.22.622-.324a2.88 2.88 0 0 1 .627-.13Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
3
apps/miniprogram/assets/icons/delete.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M11.005 0C4.93 0 0 4.924 0 11c0 6.074 4.93 11 11.005 11 6.071 0 11-4.926 11-11 0-6.076-4.929-11-11-11Zm5.5 12.373H5.506v-2.75h11v2.75Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
4
apps/miniprogram/assets/icons/down.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#FFC16E" d="M10 0c5.523 0 10 4.477 10 10s-4.477 10-10 10S0 15.523 0 10 4.477 0 10 0Zm0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 8 8 0 0 0 8-8 8 8 0 0 0-8-8Z"/>
|
||||
<path fill="#FFC16E" d="M10.714 12.66a1.001 1.001 0 0 1-1.457.047L5.72 9.172a1 1 0 0 1 1.414-1.415l2.827 2.828 2.831-2.832a1 1 0 0 1 1.414 1.415l-3.493 3.493Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 430 B |
3
apps/miniprogram/assets/icons/enter.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="17" fill="none" viewBox="0 0 21 17">
|
||||
<path fill="#FFC16E" stroke="#FFC16E" stroke-width=".5" d="m14.422.25.312.008a6.495 6.495 0 0 1 4.156 1.75c1.187 1.126 1.858 2.655 1.86 4.254l-.008.3a5.89 5.89 0 0 1-1.852 3.955 6.509 6.509 0 0 1-4.468 1.757H3.134l3.248 3.079H6.38a.783.783 0 0 1 .016 1.166.846.846 0 0 1-.603.231.867.867 0 0 1-.588-.247L.5 12.044a.795.795 0 0 1-.25-.576c0-.219.092-.425.25-.575L5.206 6.43l.007-.006a.857.857 0 0 1 1.154.02.794.794 0 0 1 .25.56.792.792 0 0 1-.23.57l-.005.007H6.38l-3.246 3.077h11.287a4.793 4.793 0 0 0 3.295-1.292 4.278 4.278 0 0 0 1.357-3.104 4.278 4.278 0 0 0-1.357-3.105 4.794 4.794 0 0 0-3.295-1.293H5.793a.855.855 0 0 1-.588-.231.794.794 0 0 1-.25-.576c0-.219.092-.426.25-.577a.855.855 0 0 1 .588-.23h8.629Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 821 B |
3
apps/miniprogram/assets/icons/esc.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="10" fill="none" viewBox="0 0 19 10">
|
||||
<path fill="#FFC16E" d="M2.98 0h.132C4.068 0 4.81.496 5.34 1.489c.004.019.044.105.12.257.302.75.453 1.641.453 2.672v1.05H2.258V3.702h1.756a3.7 3.7 0 0 0-.108-.82c-.135-.471-.422-.706-.86-.706-.498 0-.826.32-.985.963a6.815 6.815 0 0 0-.114 1.346c0 .502.028 1.027.084 1.574.075.458.187.77.334.935.156.178.37.268.645.268.474 0 .779-.274.914-.821a.381.381 0 0 1 .024-.105h1.923a7.59 7.59 0 0 1-.179.954c-.243.853-.635 1.447-1.176 1.785-.41.273-.906.41-1.488.41C1.611 9.472.685 8.677.251 7.099.084 6.463 0 5.748 0 4.952V4.82c0-1.304.211-2.376.633-3.216C1.195.534 1.977 0 2.981 0Zm6.398 0v2.118a.83.83 0 0 0-.466.163.648.648 0 0 0-.197.486v.048c0 .286.165.48.495.582.16.051.62.178 1.38.382.434.152.779.318 1.033.496.618.452.926 1.167.926 2.147v.143c0 1.082-.306 1.874-.92 2.376-.473.388-1.134.582-1.983.582h-.012c-1.557 0-2.536-.658-2.938-1.975a4.659 4.659 0 0 1-.185-1.174h2.072c.004 0 .02.067.048.2.032.122.078.233.137.335.172.26.458.39.86.39.518 0 .777-.215.777-.648v-.086c0-.324-.18-.537-.538-.64-.736-.158-1.268-.301-1.594-.429a3.225 3.225 0 0 1-.849-.505c-.521-.484-.782-1.174-.782-2.071 0-1.107.348-1.912 1.045-2.414A2.853 2.853 0 0 1 9.342.01h.006l.03-.01Zm.298.01h.006c.593 0 1.145.168 1.655.505.167.128.336.293.507.497.208.311.325.521.353.63.095.222.175.492.239.81l.041.287-.011.01c-.156.018-.83.114-2.025.286-.048-.287-.132-.506-.251-.659a.607.607 0 0 0-.317-.21c-.012-.013-.077-.028-.197-.048V.01Zm6.541.018h.09c.716 0 1.315.303 1.797.907.092.12.191.28.299.477.159.325.277.643.352.954.08.337.14.726.18 1.164l-1.996.315c-.051-.42-.131-.728-.239-.925-.131-.217-.288-.325-.471-.325-.008 0-.012-.006-.012-.019V.028Zm-.293.01v2.605c-.096.02-.213.137-.352.353-.211.414-.317 1.066-.317 1.956v.134c.004 1.138.173 1.87.508 2.194a.664.664 0 0 0 .412.153c.414 0 .677-.353.788-1.059l.024-.277H19c-.02.312-.054.62-.102.926a5.637 5.637 0 0 1-.442 1.479C17.96 9.5 17.196 10 16.17 10c-.924 0-1.65-.4-2.18-1.202-.562-.86-.843-2.1-.843-3.722 0-2.124.482-3.616 1.446-4.475.16-.14.38-.274.663-.4.191-.077.414-.131.669-.163Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
3
apps/miniprogram/assets/icons/home.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#FFC16E" d="M19.765 6.768 10.321.102a.556.556 0 0 0-.642 0L.235 6.768A.557.557 0 0 0 0 7.222v12.222A.555.555 0 0 0 .556 20h6.11a.555.555 0 0 0 .556-.556v-6.666h5.556v6.666a.556.556 0 0 0 .555.556h6.111a.556.556 0 0 0 .556-.556V7.222a.557.557 0 0 0-.235-.454Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 379 B |
3
apps/miniprogram/assets/icons/keyboard.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" fill="none" viewBox="0 0 23 22">
|
||||
<path fill="#E6F0FF" d="M10.82 2.827a3.266 3.266 0 0 1 .457-1.634A1.635 1.635 0 0 1 12.65.425a4.707 4.707 0 0 1 1.487.245c.507.164 1.063.392 1.635.621a20.07 20.07 0 0 0 2.043.752 3.465 3.465 0 0 0 2.14.098 2.565 2.565 0 0 0 1.39-1.863L21.427 0h.278l.555.163h.294l-.081.278a3.58 3.58 0 0 1-2.076 2.648 4.674 4.674 0 0 1-2.942 0l-1.078-.262-1.046-.424a7.649 7.649 0 0 0-2.615-.785.621.621 0 0 0-.54.36 2.32 2.32 0 0 0-.18.85h.31l.377 1.928h-2.534l.376-1.929h.295ZM2.73 4.772h16.425a2.746 2.746 0 0 1 2.73 2.73v11.767a2.746 2.746 0 0 1-2.73 2.73H2.811A2.73 2.73 0 0 1 0 19.269V7.502a2.73 2.73 0 0 1 2.73-2.73Zm13.728 12.389a.458.458 0 0 0-.441.458v1.258a.458.458 0 0 0 .441.458h1.88a.457.457 0 0 0 .441-.458V17.62a.457.457 0 0 0-.441-.458h-1.88Zm-9.3 0a.621.621 0 0 0-.604.605v.964a.62.62 0 0 0 .605.605h7.387a.62.62 0 0 0 .605-.605v-.964a.621.621 0 0 0-.605-.605H7.159Zm6.162-9.038a.458.458 0 0 0-.408.458v1.274a.44.44 0 0 0 .44.442H15.2a.44.44 0 0 0 .441-.442V8.581a.458.458 0 0 0-.441-.458h-1.88Zm-3.677 0a.458.458 0 0 0-.441.458v1.274a.441.441 0 0 0 .44.442h1.913a.441.441 0 0 0 .441-.442V8.581a.458.458 0 0 0-.44-.458H9.642Zm-6.015 0a.458.458 0 0 0-.441.458v1.274a.441.441 0 0 0 .441.442h4.56a.441.441 0 0 0 .442-.442V8.581a.458.458 0 0 0-.442-.458h-4.56Zm6.783 3.04a.458.458 0 0 0-.441.457v1.259a.458.458 0 0 0 .441.458h1.88a.458.458 0 0 0 .44-.458V11.62a.458.458 0 0 0-.44-.458h-1.88Zm-3.465 0a.474.474 0 0 0-.457.457v1.259a.474.474 0 0 0 .457.458h1.88a.458.458 0 0 0 .44-.458V11.62a.458.458 0 0 0-.44-.458h-1.88Zm-3.383 0a.457.457 0 0 0-.458.457v1.259a.458.458 0 0 0 .442.458h1.895a.458.458 0 0 0 .442-.458V11.62a.458.458 0 0 0-.442-.458h-1.88Zm9.349 2.909a.441.441 0 0 0-.442.441v1.275a.44.44 0 0 0 .442.441h1.928a.441.441 0 0 0 .441-.44v-1.276a.441.441 0 0 0-.44-.441h-1.93Zm-3.498 0a.44.44 0 0 0-.441.441v1.275a.441.441 0 0 0 .441.441h1.863a.44.44 0 0 0 .442-.44v-1.276a.441.441 0 0 0-.442-.441H9.414Zm-5.933 0a.441.441 0 0 0-.376.441v1.275a.441.441 0 0 0 .442.441H7.86a.44.44 0 0 0 .442-.44v-1.276a.442.442 0 0 0-.442-.441H3.48Zm0 3.089a.457.457 0 0 0-.441.458v1.258a.458.458 0 0 0 .441.458H5.41a.458.458 0 0 0 .441-.458V17.62a.458.458 0 0 0-.441-.458H3.48Zm10.722-5.998a.458.458 0 0 0-.441.457v1.259a.458.458 0 0 0 .44.458h4.495V8.515a.457.457 0 0 0-.457-.441h-1.26a.457.457 0 0 0-.457.441v2.648h-2.321Zm2.272 2.909a.44.44 0 0 0-.442.441v1.275a.44.44 0 0 0 .442.441h1.88a.458.458 0 0 0 .457-.44v-1.276a.458.458 0 0 0-.458-.441h-1.88Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
4
apps/miniprogram/assets/icons/left.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#FFC16E" d="M20 10c0 5.523-4.477 10-10 10S0 15.523 0 10 4.477 0 10 0s10 4.477 10 10Zm-2 0a8 8 0 0 0-8-8 8 8 0 0 0-8 8 8 8 0 0 0 8 8 8 8 0 0 0 8-8Z"/>
|
||||
<path fill="#FFC16E" d="M7.34 10.714a1 1 0 0 1-.047-1.457l3.535-3.536a1 1 0 1 1 1.415 1.414L9.415 9.962l2.832 2.831a1 1 0 0 1-1.415 1.414l-3.493-3.493Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 424 B |
6
apps/miniprogram/assets/icons/log.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" fill-rule="evenodd" d="M11.299 14.804c1.045 0 1.901.316 2.545.961.646.647.956 1.537.956 2.646 0 1.031-.271 1.873-.833 2.506l-.116.124c-.64.645-1.491.959-2.535.959-.987 0-1.803-.275-2.429-.838l-.122-.116c-.64-.642-.95-1.518-.95-2.603 0-.69.105-1.282.327-1.769.164-.356.39-.676.672-.959.196-.2.422-.37.669-.505l.264-.128.016-.005c.45-.184.964-.273 1.536-.273Zm2.181 6.32a2.888 2.888 0 0 1 .119-.101l-.119.101Zm-4.865-.544a2.745 2.745 0 0 0-.017-.024l.017.024Zm2.697-4.265c-.517 0-.92.168-1.227.502-.303.329-.471.844-.471 1.58 0 .723.171 1.237.485 1.576.318.346.718.517 1.213.517.496 0 .893-.17 1.206-.511.308-.335.479-.857.479-1.599 0-.642-.128-1.114-.36-1.439l-.105-.13c-.301-.329-.702-.496-1.22-.496Zm-3.29 1.702c-.003.034-.004.069-.006.104l.01-.162-.004.058Zm5.896-1.886a2.836 2.836 0 0 1-.102-.121l.102.121Zm4.908-1.327c.868 0 1.575.176 2.094.551.515.373.845.888.99 1.535l.042.187-.194.035-1.365.247-.174.032-.046-.166a1.225 1.225 0 0 0-.466-.665l-.09-.058a1.542 1.542 0 0 0-.79-.19c-.557 0-.98.17-1.293.495v-.001c-.31.323-.48.818-.48 1.518 0 .665.134 1.158.38 1.5l.11.138.002.001c.318.349.735.525 1.267.525.263 0 .527-.049.795-.151h.003a2.79 2.79 0 0 0 .62-.323v-.555h-1.318l-.258.188v-1.67H22v2.894l-.058.055c-.313.293-.755.543-1.315.754v-.001A4.786 4.786 0 0 1 18.9 22h-.001c-.74-.001-1.394-.15-1.957-.458a3.006 3.006 0 0 1-1.264-1.308l-.004-.009-.003-.007-.097-.214a4.038 4.038 0 0 1-.316-1.355l-.005-.233v-.038c0-.714.155-1.355.468-1.92a3.177 3.177 0 0 1 1.37-1.299l.007-.003.018-.008c.47-.233 1.043-.344 1.71-.344Zm-3.19 2.321-.01.034a2.55 2.55 0 0 1 .07-.197l-.06.163Z" clip-rule="evenodd"/>
|
||||
<path fill="#67D1FF" d="M4.079 14.97v5.435h3.417v1.483H2.051l.272-.263V14.97h1.756Z"/>
|
||||
<path fill="#67D1FF" fill-rule="evenodd" d="M16.812.004c1.383.048 2.591 1.306 2.591 2.734v11.467h-1.957V2.501a.6.6 0 0 0-.227-.444.777.777 0 0 0-.489-.194H2.562c-.315 0-.596.272-.596.638v19.186l-.002.183H0l.008-.193c.012-.254.013-.867.01-1.422a170.26 170.26 0 0 0-.006-.727c0-.095-.002-.173-.003-.227V2.736C.008 1.294 1.132 0 2.56 0h14.248l.003.004Z" clip-rule="evenodd"/>
|
||||
<path fill="#67D1FF" d="M16.24 11.13H3.177V9.22H16.24v1.91Zm0-4.61H3.177V4.61H16.24v1.91Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
7
apps/miniprogram/assets/icons/logo.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="56" height="56" fill="none" viewBox="0 0 56 56">
|
||||
<circle cx="28" cy="28" r="28" fill="#192B4D" fill-opacity=".7"/>
|
||||
<path fill="#0F5" d="m18.706 30.834.799.943 3.2-3.777-3.2-3.777-.8.943L21.109 28l-2.402 2.834Z"/>
|
||||
<path fill="#0F5" d="M42.785 44h-28.63c-1.934 0-3.508-1.463-3.508-3.261V19.167c0-1.799 1.574-3.262 3.509-3.262h28.63c1.934 0 3.508 1.463 3.508 3.261V40.74c0 1.798-1.574 3.261-3.508 3.261Zm-28.63-25.684c-.537 0-.975.381-.975.85V40.74c0 .47.438.85.976.85h28.63c.538 0 .975-.38.975-.85V19.167c0-.47-.438-.851-.976-.851h-28.63ZM28.47 8c1.345 0 2.435 1.038 2.435 2.317 0 1.28-1.09 2.317-2.434 2.317-1.345 0-2.435-1.037-2.435-2.317 0-1.28 1.09-2.317 2.435-2.317Z"/>
|
||||
<path fill="#0F5" d="M28.47 17.736c-.7 0-1.266-.54-1.266-1.206v-4.017c0-.665.567-1.205 1.267-1.205s1.266.54 1.266 1.205v4.017c0 .666-.567 1.206-1.266 1.206ZM7.268 36.88c-.7 0-1.267-.54-1.267-1.205V24.963c0-.666.567-1.206 1.267-1.206s1.266.54 1.266 1.206v10.712c0 .665-.567 1.205-1.266 1.205Zm42.407 0c-.7 0-1.266-.54-1.266-1.205V24.963c0-.666.567-1.206 1.267-1.206s1.266.54 1.266 1.206v10.712c0 .665-.567 1.205-1.267 1.205Zm-25.087-2.762c.76 1.26 2.161 1.423 3.765 1.423s3.005-.163 3.764-1.423v2c-.759 1.26-2.16 1.294-3.764 1.294-1.604 0-3.006-.035-3.765-1.294v-2Z"/>
|
||||
<circle cx="35.647" cy="28" r="1.882" fill="#0F5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
apps/miniprogram/assets/icons/move.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="21" fill="none" viewBox="0 0 12 21">
|
||||
<path fill="#FFC16E" d="M2.098 4.195a2.099 2.099 0 1 1 .001-4.197 2.099 2.099 0 0 1-.001 4.197Zm0-1.399a.699.699 0 1 0 0-1.397.699.699 0 0 0 0 1.397Zm6.993 1.4a2.099 2.099 0 1 1 .002-4.198 2.099 2.099 0 0 1-.002 4.197Zm0-1.4a.699.699 0 1 0 0-1.397.699.699 0 0 0 0 1.397Zm-6.993 9.79a2.099 2.099 0 1 1 .001-4.197 2.099 2.099 0 0 1-.001 4.197Zm0-1.4a.699.699 0 1 0 0-1.397.699.699 0 0 0 0 1.398Zm6.993 1.4a2.099 2.099 0 1 1 .002-4.197 2.099 2.099 0 0 1-.002 4.197Zm0-1.4a.699.699 0 1 0 0-1.397.699.699 0 0 0 0 1.398Zm-6.993 9.79a2.099 2.099 0 0 1 0-4.195 2.099 2.099 0 0 1 0 4.196Zm0-1.399a.699.699 0 1 0 0-1.397.699.699 0 0 0 0 1.397Zm6.993 1.4a2.099 2.099 0 0 1 0-4.196 2.099 2.099 0 0 1 0 4.196Zm0-1.4a.699.699 0 1 0 0-1.397.699.699 0 0 0 0 1.397Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 857 B |
3
apps/miniprogram/assets/icons/paste.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="20" fill="none" viewBox="0 0 19 20">
|
||||
<path fill="#FFC16E" d="M17 9h-7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2Zm1-2V4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4V9a2 2 0 0 1 2-2h10ZM6 4V2h6v2H6Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 326 B |
3
apps/miniprogram/assets/icons/plugins.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M9.002 0a3 3 0 0 1 2.828 4.002h4.171c1.106 0 2 .894 2 1.998v4.171a3 3 0 1 1 0 5.658v4.17a2 2 0 0 1-2 2.001h-4.17a3 3 0 1 0-5.658 0H2a2 2 0 0 1-2-2v-4.171a3 3 0 1 0 0-5.658v-4.17a2 2 0 0 1 2-2h4.171a3 3 0 0 1 2.833-4L9.002 0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 357 B |
3
apps/miniprogram/assets/icons/record.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="#67D1FF" d="M.003 22.986a.433.433 0 0 0 .608.45l5.55-2.423-5.49-4.045c-.207 1.825-.638 5.72-.668 6.018Zm14.992-5.252c0 .464.377.84.84.84h6.72a.84.84 0 0 0 .84-.84.84.84 0 0 0-.84-.837h-6.72a.84.84 0 0 0-.84.837ZM18.51 5.132a1.203 1.203 0 0 0-.255-1.683L13.892.235a1.205 1.205 0 0 0-1.685.255l-.357.485 6.303 4.642.357-.485Zm-7.16-3.479L1.204 15.417l6.303 4.644L17.653 6.296 11.35 1.653ZM8.028 16.91a.73.73 0 1 1-1.182-.852l6.83-9.47a.73.73 0 1 1 1.181.852l-6.829 9.47Zm14.632 4.91H9.23a.838.838 0 1 0 0 1.678h13.43a.84.84 0 1 0 0-1.678Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 657 B |
3
apps/miniprogram/assets/icons/recordmanager.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" d="M20.977 9.325a.963.963 0 0 0-.963.965v8.815a.966.966 0 0 1-.963.965H4.538a.966.966 0 0 1-.963-.965v-4.897h.685a.963.963 0 0 0 .963-.965.963.963 0 0 0-.963-.965H.963a.963.963 0 0 0-.963.965c0 .534.43.965.963.965h.685v4.897A2.897 2.897 0 0 0 4.538 22H19.05c1.592 0 2.89-1.3 2.89-2.895V10.29a.963.963 0 0 0-.964-.965ZM8.164 9.61l-1.278 3.419c-.218.583-.066 1.233.396 1.696.323.324.736.496 1.154.496.182 0 .364-.033.54-.1l3.411-1.28c.33-.124.621-.31.867-.557l8.153-8.17c.405-.405.613-.982.568-1.578a2.358 2.358 0 0 0-.694-1.486L19.935.7A2.35 2.35 0 0 0 18.45.007a2.006 2.006 0 0 0-1.575.568L8.72 8.741a2.427 2.427 0 0 0-.556.869Zm1.804.678a.487.487 0 0 1 .114-.18l8.155-8.172a.118.118 0 0 1 .052-.008h.017a.447.447 0 0 1 .265.135l1.347 1.349c.079.08.128.178.134.266.003.043-.006.066-.008.068L11.89 11.92a.505.505 0 0 1-.18.114L8.924 13.08l1.044-2.792ZM.963 6.9H4.26a.963.963 0 0 0 .963-.965.963.963 0 0 0-.963-.965h-.685V2.934c0-.532.432-.966.963-.966h7.256a.963.963 0 0 0 .963-.965.963.963 0 0 0-.963-.965H4.538c-1.593 0-2.89 1.3-2.89 2.896V4.97H.963A.963.963 0 0 0 0 5.936c0 .534.43.965.963.965Zm0 3.653H4.26a.963.963 0 0 0 .963-.965.963.963 0 0 0-.963-.966H.963A.963.963 0 0 0 0 9.59c0 .534.43.965.963.965Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
apps/miniprogram/assets/icons/remoteconn.svg
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
4
apps/miniprogram/assets/icons/right.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#FFC16E" d="M0 10C0 4.477 4.477 0 10 0s10 4.477 10 10-4.477 10-10 10S0 15.523 0 10Zm2 0a8 8 0 0 0 8 8 8 8 0 0 0 8-8 8 8 0 0 0-8-8 8 8 0 0 0-8 8Z"/>
|
||||
<path fill="#FFC16E" d="M12.66 9.286a1 1 0 0 1 .047 1.457L9.172 14.28a1 1 0 0 1-1.415-1.414l2.828-2.827-2.832-2.831a1 1 0 1 1 1.415-1.414l3.493 3.493Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
3
apps/miniprogram/assets/icons/save.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#67D1FF" d="M1.111 0h14.445l4.118 4.119c.209.208.326.49.326.785V18.89A1.111 1.111 0 0 1 18.889 20H1.11A1.111 1.111 0 0 1 0 18.889V1.11A1.111 1.111 0 0 1 1.111 0ZM10 16.667A3.333 3.333 0 1 0 10 10a3.333 3.333 0 0 0 0 6.667ZM2.222 2.222v4.445h11.111V2.222H2.223Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 381 B |
3
apps/miniprogram/assets/icons/search.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="none" viewBox="0 0 15 15">
|
||||
<path fill="#67D1FF" d="m2.363 14.276 4.084-4.081-2.112-2.112-4.084 4.084a.868.868 0 0 0 0 1.222l.887.887a.872.872 0 0 0 1.225 0Zm2.883-6.101 1.106 1.107 1.26-1.26a4.41 4.41 0 0 0 5.626-.505 4.409 4.409 0 0 0 0-6.228 4.406 4.406 0 0 0-6.228 0 4.41 4.41 0 0 0-.504 5.626l-1.26 1.26Zm2.63-6.032a3.186 3.186 0 0 1 4.508 0 3.186 3.186 0 0 1 0 4.508 3.186 3.186 0 0 1-4.508 0 3.186 3.186 0 0 1 0-4.508Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 506 B |
3
apps/miniprogram/assets/icons/selectall.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#67D1FF" fill-rule="evenodd" d="M20.428 16.62c.866 0 1.572.602 1.572 1.346v2.688c0 .742-.703 1.346-1.572 1.346H1.572C.706 22 0 21.398 0 20.654v-2.691c0-.742.703-1.344 1.572-1.344h18.856Zm-4.086 2.61-1.292-1.098-1.05.891L16.326 21 20 17.879 18.966 17l-2.624 2.23ZM5.235 17.963c-.866 0-1.572.601-1.572 1.346 0 .744.703 1.345 1.572 1.345.867 0 1.574-.601 1.574-1.345 0-.745-.704-1.346-1.574-1.346Zm15.193-9.651c.866 0 1.572.601 1.572 1.345v2.689c0 .741-.703 1.345-1.572 1.345H1.572C.706 13.691 0 13.09 0 12.346V9.654c0-.741.703-1.342 1.572-1.342h18.856Zm-4.086 2.918-1.292-1.098-1.05.891L16.326 13 20 9.879 18.966 9l-2.624 2.23ZM5.235 9.653c-.866 0-1.572.602-1.572 1.346 0 .744.703 1.346 1.572 1.346.867 0 1.574-.602 1.574-1.346 0-.744-.704-1.346-1.574-1.346ZM20.428 0C21.294 0 22 .602 22 1.346v2.688c0 .742-.703 1.347-1.572 1.347H1.572C.706 5.38 0 4.778 0 4.034V1.343C0 .6.703 0 1.572 0h18.856Zm-4.086 3.23L15.05 2.131 14 3.023 16.326 5 20 1.879 18.966 1l-2.624 2.23ZM5.235 1.342c-.866 0-1.572.602-1.572 1.345 0 .745.703 1.346 1.572 1.346.867 0 1.574-.601 1.574-1.346 0-.744-.704-1.345-1.574-1.345Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
apps/miniprogram/assets/icons/sent.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="#67D1FF" d="m0 24 2.496-11.112 13.8-.864-13.8-.936L0 0l24 12L0 24Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 187 B |
3
apps/miniprogram/assets/icons/serverlist.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="20" fill="none" viewBox="0 0 22 20">
|
||||
<path fill="#67D1FF" d="M2.2 4.4a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4ZM8.25.55a1.65 1.65 0 0 0 0 3.3h12.1a1.65 1.65 0 1 0 0-3.3H8.25ZM6.6 9.9a1.65 1.65 0 0 1 1.65-1.65h12.1a1.65 1.65 0 1 1 0 3.3H8.25A1.65 1.65 0 0 1 6.6 9.9Zm0 7.7a1.65 1.65 0 0 1 1.65-1.65h12.1a1.65 1.65 0 1 1 0 3.3H8.25A1.65 1.65 0 0 1 6.6 17.6ZM4.4 9.9a2.2 2.2 0 1 1-4.4 0 2.2 2.2 0 0 1 4.4 0Zm-2.2 9.9a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z" opacity=".99"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 539 B |
5
apps/miniprogram/assets/icons/share.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 18 18">
|
||||
<path stroke="#FFC16E" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.5 2.25h5.25V7.5"/>
|
||||
<path stroke="#FFC16E" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 3 8.625 9.375"/>
|
||||
<path stroke="#FFC16E" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.75 9.75V12A5.25 5.25 0 0 1 10.5 17.25H6A5.25 5.25 0 0 1 .75 12V7.5A5.25 5.25 0 0 1 6 2.25h2.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 531 B |
3
apps/miniprogram/assets/icons/shell.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#FFC16E" d="M11 0a11 11 0 1 0 0 22 11 11 0 0 0 0-22ZM5.958 15.732l-1.304-1.35L7.99 11 4.654 7.618l1.304-1.412 4.654 4.654-4.654 4.872Zm11.775-.295H11.28v-1.94h6.47l-.016 1.94Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 296 B |
3
apps/miniprogram/assets/icons/shift.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="18" fill="none" viewBox="0 0 22 18">
|
||||
<path fill="#FFC16E" d="M7.845 18h6.31c1.432 0 2.2-.795 2.2-2.076V11.58h4.162c.828 0 1.483-.485 1.483-1.222 0-.456-.236-.796-.614-1.144L12.263.592C11.874.214 11.455 0 11.005 0c-.46 0-.87.214-1.269.592L.614 9.214C.225 9.582 0 9.902 0 10.358c0 .737.655 1.222 1.493 1.222h4.153v4.344c0 1.28.766 2.076 2.199 2.076Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 419 B |
3
apps/miniprogram/assets/icons/tab.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="14" fill="none" viewBox="0 0 19 14">
|
||||
<path fill="#FFC16E" d="M1.497 1.583h2.166c.008 0 .012.006.012.018v3.11h1.85v2.247H.011C.004 6.958 0 6.952 0 6.939V4.73c0-.012.004-.019.012-.019h1.485V1.583Zm0 5.88h2.178v2.529c0 .406.042.758.127 1.058.098.225.3.337.609.337.272 0 .562-.09.87-.271.182 1.542.274 2.331.274 2.369h-.006c-.426.33-1.014.496-1.765.496H3.76c-1.067 0-1.742-.505-2.026-1.517-.158-.48-.237-1.202-.237-2.163V7.463Zm7.982-2.762c1.37 0 2.24.612 2.61 1.835.146.512.219 1.071.219 1.677V14H10.25v-1.19c-.385.525-.75.86-1.095 1.003a2.05 2.05 0 0 1-.84.178c-.81 0-1.415-.356-1.812-1.068-.227-.424-.341-.949-.341-1.573v-.019c0-1.423.606-2.329 1.82-2.716.194-.068.498-.13.912-.187.198-.019.531-.044.997-.075v1.714c-.462.025-.768.06-.918.103a1.402 1.402 0 0 0-.408.206c-.19.175-.286.415-.286.721v.028c0 .524.244.787.73.787.515 0 .874-.29 1.077-.871.033-.138.05-.206.055-.206h.006v-.038c.02-.187.03-.33.03-.43.008-1.274.012-2.092.012-2.454 0-.506-.146-.84-.438-1.002a.825.825 0 0 0-.37-.084c-.418 0-.69.2-.816.6a3.907 3.907 0 0 0-.055.27h-.006a97.59 97.59 0 0 1-1.977-.215 4.55 4.55 0 0 1 .158-.796c.183-.668.538-1.183 1.065-1.545.462-.293 1.038-.44 1.728-.44ZM12.916 0h2.044c-.004 2.997-.006 5.076-.006 6.237v1.498c0 1.523.008 2.285.024 2.285.033.375.084.662.153.862.158.43.424.646.797.646.527 0 .87-.422 1.028-1.264a4.96 4.96 0 0 0 .073-.871v-.15c0-.525-.065-.977-.195-1.358-.195-.512-.493-.768-.894-.768-.134 0-.365.09-.694.272v-2.06c.15-.163.416-.328.797-.497.231-.087.426-.131.584-.131h.012c.666 0 1.26.415 1.783 1.245.385.693.578 1.73.578 3.11v.243c0 1.342-.217 2.457-.651 3.343-.454.85-1.059 1.274-1.813 1.274h-.03c-.69 0-1.223-.381-1.6-1.143v1.133c0 .013-.005.02-.013.02h-1.977V0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
4
apps/miniprogram/assets/icons/terminal-key-ctrlc.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="22" fill="none" viewBox="0 0 36 22">
|
||||
<text x="18" y="9" fill="#FFC16E" font-family="Play, PingFang SC, sans-serif" font-size="9" font-weight="700" text-anchor="middle">ctrl</text>
|
||||
<text x="18" y="19" fill="#5BD2FF" font-family="Play, PingFang SC, sans-serif" font-size="11" font-weight="700" text-anchor="middle">C</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
4
apps/miniprogram/assets/icons/terminal-key-delete.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="18" fill="none" viewBox="0 0 22 18">
|
||||
<path fill="#FFC16E" d="M7.364 1.25a1.75 1.75 0 0 1 1.237-.512H19.75a1.75 1.75 0 0 1 1.75 1.75v12.024a1.75 1.75 0 0 1-1.75 1.75H8.601a1.75 1.75 0 0 1-1.237-.512L1.763 10.15a1.75 1.75 0 0 1 0-2.475l5.6-5.6Zm1.237 1.738L3 8.912l5.601 5.6H19.75V2.988H8.601Z"/>
|
||||
<path fill="#FFC16E" d="M10.302 6.302a.95.95 0 0 1 1.343 0L13.5 8.157l1.855-1.855a.95.95 0 1 1 1.343 1.343L14.843 9.5l1.855 1.855a.95.95 0 1 1-1.343 1.343L13.5 10.843l-1.855 1.855a.95.95 0 1 1-1.343-1.343L12.157 9.5l-1.855-1.855a.95.95 0 0 1 0-1.343Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 619 B |
4
apps/miniprogram/assets/icons/terminal-key-down.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#FFC16E" d="M11 .5c5.799 0 10.5 4.701 10.5 10.5S16.799 21.5 11 21.5.5 16.799.5 11 5.201.5 11 .5Zm0 2C6.306 2.5 2.5 6.306 2.5 11S6.306 19.5 11 19.5 19.5 15.694 19.5 11 15.694 2.5 11 2.5Z"/>
|
||||
<path fill="#FFC16E" d="M11.777 14.388a1.1 1.1 0 0 1-1.554 0l-4.11-4.112a1.1 1.1 0 1 1 1.554-1.556L11 12.054l3.333-3.334a1.1 1.1 0 1 1 1.554 1.556l-4.11 4.112Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 471 B |
3
apps/miniprogram/assets/icons/terminal-key-enter.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="18" fill="none" viewBox="0 0 22 18">
|
||||
<path fill="#FFC16E" stroke="#FFC16E" stroke-width=".6" d="m15.083.5.356.01a6.969 6.969 0 0 1 4.456 1.876c1.273 1.208 1.992 2.849 1.995 4.565l-.01.322a6.318 6.318 0 0 1-1.986 4.242A6.98 6.98 0 0 1 15.083 13.4H2.977l3.484 3.303-.002-.002a.84.84 0 0 1 .018 1.25.91.91 0 0 1-.647.248.93.93 0 0 1-.631-.265L.5 13.144a.853.853 0 0 1 0-1.234L5.2 7.119l.008-.007a.92.92 0 0 1 1.239.023.851.851 0 0 1 .268.602.848.848 0 0 1-.248.61l-.006.008h-.002l-3.482 3.3h12.106a5.141 5.141 0 0 0 3.533-1.386 4.59 4.59 0 0 0 1.455-3.33 4.59 4.59 0 0 0-1.455-3.33 5.142 5.142 0 0 0-3.533-1.386H5.83a.92.92 0 0 1-.631-.248A.85.85 0 0 1 4.93 1.36c0-.235.099-.456.268-.617A.92.92 0 0 1 5.83.5h9.253Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 783 B |
3
apps/miniprogram/assets/icons/terminal-key-esc.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="16" fill="none" viewBox="0 0 32 16">
|
||||
<text x="16" y="12" fill="#FFC16E" font-family="Play, PingFang SC, sans-serif" font-size="11" font-weight="700" text-anchor="middle">esc</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 249 B |
4
apps/miniprogram/assets/icons/terminal-key-left.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#FFC16E" d="M11 21.5C5.201 21.5.5 16.799.5 11S5.201.5 11 .5 21.5 5.201 21.5 11 16.799 21.5 11 21.5Zm0-2C15.694 19.5 19.5 15.694 19.5 11S15.694 2.5 11 2.5 2.5 6.306 2.5 11 6.306 19.5 11 19.5Z"/>
|
||||
<path fill="#FFC16E" d="M7.612 11.777a1.1 1.1 0 0 1 0-1.554l4.112-4.11a1.1 1.1 0 1 1 1.556 1.554L9.946 11l3.334 3.333a1.1 1.1 0 0 1-1.556 1.554l-4.112-4.11Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 473 B |
3
apps/miniprogram/assets/icons/terminal-key-paste.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="21" fill="none" viewBox="0 0 20 21">
|
||||
<path fill="#FFC16E" d="M17.895 9.433h-7.37c-1.16 0-2.105.943-2.105 2.105v7.37c0 1.161.944 2.104 2.105 2.104h7.37c1.16 0 2.105-.943 2.105-2.105v-7.37c0-1.16-.944-2.104-2.105-2.104Zm1.053-2.106V4.17c0-1.161-.944-2.105-2.105-2.105h-2.106A2.106 2.106 0 0 0 12.633-.04H6.316A2.106 2.106 0 0 0 4.21 2.065H2.105C.944 2.065 0 3.009 0 4.17v12.633c0 1.161.944 2.105 2.105 2.105h4.21V9.433c0-1.161.944-2.106 2.105-2.106h10.528ZM6.316 4.17V2.065h6.317V4.17H6.316Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
4
apps/miniprogram/assets/icons/terminal-key-right.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#FFC16E" d="M11 21.5C5.201 21.5.5 16.799.5 11S5.201.5 11 .5 21.5 5.201 21.5 11 16.799 21.5 11 21.5Zm0-2C15.694 19.5 19.5 15.694 19.5 11S15.694 2.5 11 2.5 2.5 6.306 2.5 11 6.306 19.5 11 19.5Z"/>
|
||||
<path fill="#FFC16E" d="M14.388 10.223a1.1 1.1 0 0 1 0 1.554l-4.112 4.11a1.1 1.1 0 0 1-1.556-1.554L12.054 11 8.72 7.667a1.1 1.1 0 0 1 1.556-1.554l4.112 4.11Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
3
apps/miniprogram/assets/icons/terminal-key-tab.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="16" fill="none" viewBox="0 0 32 16">
|
||||
<text x="16" y="12" fill="#FFC16E" font-family="Play, PingFang SC, sans-serif" font-size="11" font-weight="700" text-anchor="middle">tab</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 249 B |
4
apps/miniprogram/assets/icons/terminal-key-up.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 22 22">
|
||||
<path fill="#FFC16E" d="M11 21.5C5.201 21.5.5 16.799.5 11S5.201.5 11 .5 21.5 5.201 21.5 11 16.799 21.5 11 21.5Zm0-2C15.694 19.5 19.5 15.694 19.5 11S15.694 2.5 11 2.5 2.5 6.306 2.5 11 6.306 19.5 11 19.5Z"/>
|
||||
<path fill="#FFC16E" d="M10.223 7.612a1.1 1.1 0 0 1 1.554 0l4.11 4.112a1.1 1.1 0 1 1-1.554 1.556L11 9.946 7.667 13.28a1.1 1.1 0 0 1-1.554-1.556l4.11-4.112Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
5
apps/miniprogram/assets/icons/terminal-keyboard.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" fill="none" viewBox="0 0 23 22">
|
||||
<rect x="1.5" y="4.5" width="20" height="14" rx="3" stroke="#6D7895" stroke-width="1.5"/>
|
||||
<path stroke="#6D7895" stroke-linecap="round" stroke-width="1.5" d="M5.2 8h1.6m2 0h1.6m2.1 0H14m2.1 0h1.7M5.2 11h1.6m2 0h1.6m2.1 0H14m2.1 0h1.7M7.4 14h8.2"/>
|
||||
<path fill="#6D7895" d="M8.6 2.25a2.9 2.9 0 0 1 .306-.962C9.215.79 9.68.5 10.196.5c.32 0 .67.05 1.03.146.35.095.734.228 1.129.39.48.196.953.374 1.41.506.455.13.883.166 1.281.085.554-.113 1.008-.47 1.224-1.09L16.356 0h.3l.53.146h.285l-.08.285A3.062 3.062 0 0 1 15.5 2.07c-.8.287-1.7.256-2.629 0l-.9-.248-.875-.35c-.585-.23-1.145-.427-1.595-.49a.43.43 0 0 0-.355.23c-.102.167-.152.38-.156.634h.282l.31 1.404H8.294l.307-1.404H8.6Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 788 B |
4
apps/miniprogram/assets/icons/up.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#FFC16E" d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10Zm0-2a8 8 0 0 0 8-8 8 8 0 0 0-8-8 8 8 0 0 0-8 8 8 8 0 0 0 8 8Z"/>
|
||||
<path fill="#FFC16E" d="M9.286 7.34a1 1 0 0 1 1.457-.047l3.536 3.535a1 1 0 0 1-1.414 1.415l-2.827-2.828-2.831 2.832a1 1 0 0 1-1.414-1.415l3.493-3.493Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 423 B |
6
apps/miniprogram/assets/icons/voice.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="36" fill="none" viewBox="0 0 27 36">
|
||||
<g opacity="1">
|
||||
<path fill="#67D1FF" d="M13.326 26.661a8.254 8.254 0 0 0 8.246-8.246V8.245a8.246 8.246 0 1 0-16.491 0v10.17a8.254 8.254 0 0 0 8.245 8.246Z"/>
|
||||
<path fill="#67D1FF" d="M22.759 26.932a13.487 13.487 0 0 0 3.894-9.47V11.64a1.47 1.47 0 1 0-2.941 0v5.822a10.453 10.453 0 0 1-10.386 10.513A10.458 10.458 0 0 1 2.941 17.462V11.64a1.47 1.47 0 0 0-2.941 0v5.822a13.483 13.483 0 0 0 3.898 9.47 13.242 13.242 0 0 0 7.966 3.882v2.245h-6.16a1.47 1.47 0 0 0 0 2.941h15.254a1.47 1.47 0 1 0 0-2.94h-6.161v-2.246a13.263 13.263 0 0 0 7.962-3.882Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 666 B |
351
apps/miniprogram/components/bottom-nav/index.js
Normal file
@@ -0,0 +1,351 @@
|
||||
/* global Component, getCurrentPages, wx, require, console */
|
||||
|
||||
const { getSettings, listServers } = require("../../utils/storage");
|
||||
const { getTerminalSessionSnapshot } = require("../../utils/terminalSession");
|
||||
const { buildButtonIconThemeMaps, resolveButtonIcon } = require("../../utils/themedIcons");
|
||||
const { resolvePageNavigationMethod } = require("../../utils/navigationPolicy");
|
||||
const {
|
||||
hasActiveTerminalSession,
|
||||
openTerminalPage,
|
||||
resolveActiveTerminalServerId
|
||||
} = require("../../utils/terminalNavigation");
|
||||
const { buildPageCopy, normalizeUiLanguage, t } = require("../../utils/i18n");
|
||||
const { subscribeLocaleChange } = require("../../utils/localeBus");
|
||||
const { subscribeSyncConfigApplied } = require("../../utils/syncConfigBus");
|
||||
const { buildSvgButtonPressData, createSvgButtonPressMethods } = require("../../utils/svgButtonFeedback");
|
||||
|
||||
const SHELL_BUTTON_ACTION = "open-terminal-shell";
|
||||
const SHELL_BUTTON_ITEM = Object.freeze({
|
||||
action: SHELL_BUTTON_ACTION,
|
||||
path: "/pages/terminal/index",
|
||||
icon: "/assets/icons/shell.svg",
|
||||
isTab: false
|
||||
});
|
||||
|
||||
const loggedRenderProbeKeys = new Set();
|
||||
|
||||
function shouldUseTextIcons() {
|
||||
if (!wx || typeof wx.getAppBaseInfo !== "function") return false;
|
||||
try {
|
||||
const info = wx.getAppBaseInfo() || {};
|
||||
return (
|
||||
String(info.platform || "")
|
||||
.trim()
|
||||
.toLowerCase() === "devtools"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function inspectRenderPayload(payload) {
|
||||
const stats = {
|
||||
stringCount: 0,
|
||||
dataImageCount: 0,
|
||||
svgPathCount: 0,
|
||||
urlCount: 0,
|
||||
maxLength: 0,
|
||||
samples: []
|
||||
};
|
||||
const walk = (value, path, depth) => {
|
||||
if (depth > 5) return;
|
||||
if (typeof value === "string") {
|
||||
stats.stringCount += 1;
|
||||
if (value.includes("data:image")) stats.dataImageCount += 1;
|
||||
if (value.includes(".svg")) stats.svgPathCount += 1;
|
||||
if (value.includes("url(")) stats.urlCount += 1;
|
||||
if (value.length > stats.maxLength) stats.maxLength = value.length;
|
||||
if (
|
||||
stats.samples.length < 6 &&
|
||||
(value.includes("data:image") ||
|
||||
value.includes(".svg") ||
|
||||
value.includes("url(") ||
|
||||
value.length >= 120)
|
||||
) {
|
||||
stats.samples.push({
|
||||
path,
|
||||
length: value.length,
|
||||
preview: value.slice(0, 120)
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!value || typeof value !== "object") return;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => walk(item, `${path}[${index}]`, depth + 1));
|
||||
return;
|
||||
}
|
||||
Object.keys(value).forEach((key) => walk(value[key], path ? `${path}.${key}` : key, depth + 1));
|
||||
};
|
||||
walk(payload, "", 0);
|
||||
return stats;
|
||||
}
|
||||
|
||||
function logRenderProbeOnce(key, label, payload) {
|
||||
const normalizedKey = String(key || label || "");
|
||||
if (!normalizedKey || loggedRenderProbeKeys.has(normalizedKey)) return;
|
||||
loggedRenderProbeKeys.add(normalizedKey);
|
||||
console.warn(`[render_probe] ${label}`, inspectRenderPayload(payload));
|
||||
}
|
||||
|
||||
function prependShellItem(page, items) {
|
||||
if (String(page || "").trim() === "terminal") {
|
||||
return items;
|
||||
}
|
||||
return [SHELL_BUTTON_ITEM, ...items];
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局底部工具条组件:
|
||||
* 1. 复刻 Web 底栏语义:左侧返回,右侧按页面上下文展示图标按钮;
|
||||
* 2. 图标顺序按 Figma 页面 annotation 固定,不做自动重排;
|
||||
* 3. records 页面自身不展示 records 入口,避免重复高亮。
|
||||
*/
|
||||
Component({
|
||||
properties: {
|
||||
page: {
|
||||
type: String,
|
||||
value: ""
|
||||
}
|
||||
},
|
||||
|
||||
data: {
|
||||
...buildSvgButtonPressData(),
|
||||
canGoBack: false,
|
||||
backIcon: "/assets/icons/back.svg",
|
||||
backPressedIcon: "/assets/icons/back.svg",
|
||||
backLabel: t("zh-Hans", "bottomNav.backText"),
|
||||
textIconMode: false,
|
||||
items: []
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.syncCanGoBack();
|
||||
this.syncItems();
|
||||
this.localeUnsub = subscribeLocaleChange(() => {
|
||||
this.syncItems();
|
||||
});
|
||||
this.syncConfigUnsub = subscribeSyncConfigApplied(() => {
|
||||
this.syncCanGoBack();
|
||||
this.syncItems();
|
||||
});
|
||||
},
|
||||
|
||||
detached() {
|
||||
if (typeof this.localeUnsub === "function") {
|
||||
this.localeUnsub();
|
||||
this.localeUnsub = null;
|
||||
}
|
||||
if (typeof this.syncConfigUnsub === "function") {
|
||||
this.syncConfigUnsub();
|
||||
this.syncConfigUnsub = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
pageLifetimes: {
|
||||
show() {
|
||||
this.syncCanGoBack();
|
||||
this.syncItems();
|
||||
}
|
||||
},
|
||||
|
||||
observers: {
|
||||
page() {
|
||||
this.syncItems();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
currentPathByPage(page) {
|
||||
const name = String(page || "").trim();
|
||||
if (!name) return "";
|
||||
if (name === "about" || name.indexOf("about-") === 0) {
|
||||
return "/pages/about/index";
|
||||
}
|
||||
return `/pages/${name}/index`;
|
||||
},
|
||||
|
||||
syncCanGoBack() {
|
||||
const pages = getCurrentPages();
|
||||
this.setData({ canGoBack: pages.length > 1 });
|
||||
},
|
||||
|
||||
rawItemsByPage(page) {
|
||||
const aboutItem = { path: "/pages/about/index", icon: "/assets/icons/about.svg", isTab: false };
|
||||
if (page === "about" || String(page || "").indexOf("about-") === 0) {
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{ path: "/pages/logs/index", icon: "/assets/icons/log.svg", isTab: false },
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false }
|
||||
]);
|
||||
}
|
||||
if (page === "connect") {
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/logs/index", icon: "/assets/icons/log.svg", isTab: false },
|
||||
{
|
||||
path: "/pages/records/index",
|
||||
icon: "/assets/icons/recordmanager.svg",
|
||||
isTab: false
|
||||
},
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false },
|
||||
aboutItem
|
||||
]);
|
||||
}
|
||||
if (page === "settings") {
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{ path: "/pages/logs/index", icon: "/assets/icons/log.svg", isTab: false },
|
||||
{ path: "/pages/plugins/index", icon: "/assets/icons/plugins.svg", isTab: false },
|
||||
{ path: "/pages/records/index", icon: "/assets/icons/recordmanager.svg", isTab: false },
|
||||
aboutItem
|
||||
]);
|
||||
}
|
||||
if (page === "plugins") {
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{
|
||||
path: "/pages/records/index",
|
||||
icon: "/assets/icons/recordmanager.svg",
|
||||
isTab: false
|
||||
},
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false },
|
||||
aboutItem
|
||||
]);
|
||||
}
|
||||
if (page === "terminal") {
|
||||
return [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{ path: "/pages/logs/index", icon: "/assets/icons/log.svg", isTab: false },
|
||||
{
|
||||
path: "/pages/records/index",
|
||||
icon: "/assets/icons/recordmanager.svg",
|
||||
isTab: false
|
||||
},
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false },
|
||||
aboutItem
|
||||
];
|
||||
}
|
||||
if (page === "records") {
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{ path: "/pages/logs/index", icon: "/assets/icons/log.svg", isTab: false },
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false },
|
||||
aboutItem
|
||||
]);
|
||||
}
|
||||
if (page === "logs") {
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{
|
||||
path: "/pages/records/index",
|
||||
icon: "/assets/icons/recordmanager.svg",
|
||||
isTab: false
|
||||
},
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false },
|
||||
aboutItem
|
||||
]);
|
||||
}
|
||||
return prependShellItem(page, [
|
||||
{ path: "/pages/connect/index", icon: "/assets/icons/serverlist.svg", isTab: false },
|
||||
{ path: "/pages/settings/index", icon: "/assets/icons/config.svg", isTab: false },
|
||||
aboutItem
|
||||
]);
|
||||
},
|
||||
|
||||
syncItems() {
|
||||
const page = this.data.page;
|
||||
const settings = getSettings();
|
||||
const language = normalizeUiLanguage(settings.uiLanguage);
|
||||
const copy = buildPageCopy(language, "bottomNav");
|
||||
const { icons: iconMap, activeIcons: activeIconMap, accentIcons: accentIconMap } =
|
||||
buildButtonIconThemeMaps(settings);
|
||||
const currentPath = this.currentPathByPage(page);
|
||||
const sessionSnapshot = getTerminalSessionSnapshot();
|
||||
const shellActive = hasActiveTerminalSession(sessionSnapshot, listServers());
|
||||
const list = this.rawItemsByPage(page).map((item) => ({
|
||||
...item,
|
||||
id: String(item.action || item.path || item.icon || ""),
|
||||
pressKey: `bottom-nav:${String(item.action || item.path || item.icon || "").trim()}`,
|
||||
icon:
|
||||
item.action === SHELL_BUTTON_ACTION && shellActive
|
||||
? resolveButtonIcon(item.icon, activeIconMap)
|
||||
: resolveButtonIcon(item.icon, iconMap),
|
||||
pressedIcon: resolveButtonIcon(
|
||||
item.icon,
|
||||
item.action === SHELL_BUTTON_ACTION && shellActive ? activeIconMap : accentIconMap
|
||||
),
|
||||
textLabel: this.resolveTextLabel(item.action || item.path, copy),
|
||||
active: item.action === SHELL_BUTTON_ACTION ? shellActive : item.path === currentPath,
|
||||
connectionActive: item.action === SHELL_BUTTON_ACTION && shellActive
|
||||
}));
|
||||
const payload = {
|
||||
backIcon: iconMap.back || "/assets/icons/back.svg",
|
||||
backPressedIcon: accentIconMap.back || iconMap.back || "/assets/icons/back.svg",
|
||||
backLabel: copy.backText || t(language, "bottomNav.backText"),
|
||||
textIconMode: shouldUseTextIcons() && Object.keys(iconMap).length === 0,
|
||||
items: list
|
||||
};
|
||||
logRenderProbeOnce("bottom-nav.syncItems", "bottom-nav.syncItems", payload);
|
||||
this.setData(payload);
|
||||
},
|
||||
|
||||
onBack() {
|
||||
if (!this.data.canGoBack) return;
|
||||
wx.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
wx.redirectTo({ url: "/pages/connect/index" });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onNavTap(event) {
|
||||
const action = String(event.currentTarget.dataset.action || "").trim();
|
||||
if (action === SHELL_BUTTON_ACTION) {
|
||||
this.onShellTap();
|
||||
return;
|
||||
}
|
||||
const path = event.currentTarget.dataset.path;
|
||||
if (!path) return;
|
||||
const currentPath = this.currentPathByPage(this.data.page);
|
||||
const method = resolvePageNavigationMethod(currentPath, path);
|
||||
if (method === "noop") return;
|
||||
if (method === "redirectTo") {
|
||||
wx.redirectTo({
|
||||
url: path,
|
||||
fail: () => {
|
||||
wx.navigateTo({ url: path });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
wx.navigateTo({ url: path });
|
||||
},
|
||||
|
||||
onShellTap() {
|
||||
const sessionSnapshot = getTerminalSessionSnapshot();
|
||||
const serverId = resolveActiveTerminalServerId(sessionSnapshot, listServers());
|
||||
const language = normalizeUiLanguage(getSettings().uiLanguage);
|
||||
if (!serverId) {
|
||||
wx.showModal({
|
||||
title: t(language, "bottomNav.modal.noTerminalTitle"),
|
||||
content: t(language, "bottomNav.modal.noTerminalContent"),
|
||||
showCancel: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
openTerminalPage(serverId, true);
|
||||
},
|
||||
|
||||
resolveTextLabel(key, copyInput) {
|
||||
const copy = copyInput && typeof copyInput === "object" ? copyInput : buildPageCopy("zh-Hans", "bottomNav");
|
||||
const pageTextLabels = (copy && copy.pageTextLabels) || {};
|
||||
const normalized = String(key || "").trim();
|
||||
return pageTextLabels[normalized] || pageTextLabels.default || "页";
|
||||
},
|
||||
|
||||
...createSvgButtonPressMethods()
|
||||
}
|
||||
});
|
||||
4
apps/miniprogram/components/bottom-nav/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"styleIsolation": "apply-shared"
|
||||
}
|
||||
49
apps/miniprogram/components/bottom-nav/index.wxml
Normal file
@@ -0,0 +1,49 @@
|
||||
<view class="bottom-bar">
|
||||
<button
|
||||
class="icon-btn bottom-nav-btn svg-press-btn {{!canGoBack ? 'is-disabled' : ''}}"
|
||||
hover-class="svg-press-btn-hover"
|
||||
hover-start-time="0"
|
||||
hover-stay-time="80"
|
||||
data-press-key="bottom-nav:back"
|
||||
disabled="{{!canGoBack}}"
|
||||
bindtouchstart="onSvgButtonTouchStart"
|
||||
bindtouchend="onSvgButtonTouchEnd"
|
||||
bindtouchcancel="onSvgButtonTouchEnd"
|
||||
bindtap="onBack"
|
||||
>
|
||||
<image
|
||||
wx:if="{{!textIconMode}}"
|
||||
class="icon-img svg-press-icon"
|
||||
src="{{pressedSvgButtonKey === 'bottom-nav:back' ? backPressedIcon : backIcon}}"
|
||||
mode="aspectFit"
|
||||
style="width:44rpx;height:44rpx;display:block;"
|
||||
/>
|
||||
<text wx:else class="bottom-nav-text">{{backLabel}}</text>
|
||||
</button>
|
||||
<view class="bottom-right-actions">
|
||||
<block wx:for="{{items}}" wx:key="id">
|
||||
<button
|
||||
class="icon-btn bottom-nav-btn svg-press-btn {{item.active ? 'active' : ''}} {{item.connectionActive ? 'connection-active' : ''}}"
|
||||
hover-class="svg-press-btn-hover"
|
||||
hover-start-time="0"
|
||||
hover-stay-time="80"
|
||||
data-press-key="{{item.pressKey}}"
|
||||
data-action="{{item.action}}"
|
||||
data-path="{{item.path}}"
|
||||
bindtouchstart="onSvgButtonTouchStart"
|
||||
bindtouchend="onSvgButtonTouchEnd"
|
||||
bindtouchcancel="onSvgButtonTouchEnd"
|
||||
bindtap="onNavTap"
|
||||
>
|
||||
<image
|
||||
wx:if="{{!textIconMode}}"
|
||||
class="icon-img svg-press-icon"
|
||||
src="{{pressedSvgButtonKey === item.pressKey ? item.pressedIcon : item.icon}}"
|
||||
mode="aspectFit"
|
||||
style="width:44rpx;height:44rpx;display:block;"
|
||||
/>
|
||||
<text wx:else class="bottom-nav-text">{{item.textLabel}}</text>
|
||||
</button>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
94
apps/miniprogram/components/bottom-nav/index.wxss
Normal file
@@ -0,0 +1,94 @@
|
||||
.bottom-bar {
|
||||
flex: 0 0 104rpx;
|
||||
height: 104rpx;
|
||||
background: var(--bg);
|
||||
border-top: 1rpx solid var(--accent-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 64rpx 0 32rpx;
|
||||
}
|
||||
|
||||
.bottom-right-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 48rpx !important;
|
||||
height: 48rpx !important;
|
||||
min-width: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
color: inherit !important;
|
||||
padding: 0 !important;
|
||||
line-height: 1 !important;
|
||||
font-size: 0 !important;
|
||||
display: inline-flex !important;
|
||||
overflow: visible !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.bottom-nav-btn {
|
||||
border-radius: 999rpx !important;
|
||||
--svg-press-active-radius: 999rpx;
|
||||
--svg-press-active-bg: var(--icon-btn-bg-strong);
|
||||
--svg-press-active-shadow: 0 0 0 8rpx var(--accent-ring);
|
||||
--svg-press-active-scale: 0.9;
|
||||
--svg-press-icon-opacity: 0.96;
|
||||
--svg-press-icon-active-opacity: 0.68;
|
||||
--svg-press-icon-active-scale: 0.88;
|
||||
}
|
||||
|
||||
.icon-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.icon-btn.is-disabled {
|
||||
opacity: 0.45 !important;
|
||||
}
|
||||
|
||||
.icon-btn.wx-button-disabled {
|
||||
opacity: 0.45 !important;
|
||||
}
|
||||
|
||||
.icon-img {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bottom-nav-text {
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
color: var(--btn-text);
|
||||
}
|
||||
|
||||
.bottom-nav-btn.active {
|
||||
background: var(--icon-btn-bg) !important;
|
||||
background-color: var(--icon-btn-bg) !important;
|
||||
box-shadow: inset 0 0 0 1rpx var(--accent-border);
|
||||
}
|
||||
|
||||
.bottom-nav-btn.connection-active {
|
||||
background: var(--accent) !important;
|
||||
background-color: var(--accent) !important;
|
||||
box-shadow: 0 10rpx 24rpx var(--accent-shadow) !important;
|
||||
--svg-press-active-bg: var(--accent);
|
||||
--svg-press-active-shadow:
|
||||
0 0 0 8rpx var(--accent-ring),
|
||||
0 10rpx 24rpx var(--accent-shadow);
|
||||
--svg-press-icon-active-opacity: 0.92;
|
||||
--svg-press-icon-active-scale: 0.94;
|
||||
}
|
||||
|
||||
.bottom-nav-btn.connection-active .bottom-nav-text {
|
||||
color: var(--text);
|
||||
}
|
||||
115
apps/miniprogram/pages/about-app/index.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/* global Page, wx, require */
|
||||
|
||||
const { getSettings } = require("../../utils/storage");
|
||||
const { buildThemeStyle, applyNavigationBarTheme } = require("../../utils/themeStyle");
|
||||
const {
|
||||
getAboutBrand,
|
||||
getAboutDetailContent,
|
||||
getAboutFooterLinks,
|
||||
getAboutUiCopy
|
||||
} = require("../../utils/aboutContent");
|
||||
const { normalizeUiLanguage } = require("../../utils/i18n");
|
||||
const { buildButtonIconThemeMaps } = require("../../utils/themedIcons");
|
||||
const { buildSvgButtonPressData, createSvgButtonPressMethods } = require("../../utils/svgButtonFeedback");
|
||||
|
||||
// “关于”页分享出去后应回到小程序首页,而不是再次落到 about 详情页。
|
||||
const ABOUT_APP_SHARE_HOME_PATH = "/pages/connect/index";
|
||||
|
||||
/**
|
||||
* 将“关于”详情页按 Figma Frame 2223 落地:
|
||||
* 1. 保留关于首页的 5 个入口结构不变;
|
||||
* 2. 当前页只重排品牌区、信息卡、分享按钮和底部跳转;
|
||||
* 3. 中间信息继续复用统一数据源,避免文案在多个页面分叉。
|
||||
*/
|
||||
function buildInfoRows(section) {
|
||||
const bullets = Array.isArray(section && section.bullets) ? section.bullets : [];
|
||||
return bullets.map((line, index) => {
|
||||
const text = String(line || "").trim();
|
||||
const matched = text.match(/^([^::]+)[::]\s*(.+)$/);
|
||||
if (!matched) {
|
||||
return {
|
||||
key: `row-${index}`,
|
||||
label: "",
|
||||
value: text
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: `row-${index}`,
|
||||
label: `${matched[1]}:`,
|
||||
value: matched[2]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
...buildSvgButtonPressData(),
|
||||
brand: getAboutBrand("zh-Hans"),
|
||||
pageContent: getAboutDetailContent("app", "zh-Hans"),
|
||||
infoRows: [],
|
||||
versionLine: "",
|
||||
themeStyle: "",
|
||||
footerLinks: getAboutFooterLinks("zh-Hans"),
|
||||
uiCopy: getAboutUiCopy("zh-Hans"),
|
||||
icons: {},
|
||||
accentIcons: {}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const brand = getAboutBrand("zh-Hans");
|
||||
const pageContent = getAboutDetailContent("app", "zh-Hans");
|
||||
const primarySection = Array.isArray(pageContent.sections) ? pageContent.sections[0] : null;
|
||||
wx.setNavigationBarTitle({ title: pageContent.title || "关于" });
|
||||
this.setData({
|
||||
brand,
|
||||
pageContent,
|
||||
infoRows: buildInfoRows(primarySection),
|
||||
versionLine: `${brand.version}·wechat·${brand.updatedAtCompact}`,
|
||||
footerLinks: getAboutFooterLinks("zh-Hans"),
|
||||
uiCopy: getAboutUiCopy("zh-Hans")
|
||||
});
|
||||
this.applyThemeStyle();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.applyThemeStyle();
|
||||
},
|
||||
|
||||
applyThemeStyle() {
|
||||
const settings = getSettings();
|
||||
const language = normalizeUiLanguage(settings.uiLanguage);
|
||||
const brand = getAboutBrand(language);
|
||||
const pageContent = getAboutDetailContent("app", language);
|
||||
const primarySection = Array.isArray(pageContent.sections) ? pageContent.sections[0] : null;
|
||||
const { icons, accentIcons } = buildButtonIconThemeMaps(settings);
|
||||
applyNavigationBarTheme(settings);
|
||||
wx.setNavigationBarTitle({ title: pageContent.title || "关于" });
|
||||
this.setData({
|
||||
brand,
|
||||
pageContent,
|
||||
infoRows: buildInfoRows(primarySection),
|
||||
versionLine: `${brand.version}·wechat·${brand.updatedAtCompact}`,
|
||||
footerLinks: getAboutFooterLinks(language),
|
||||
uiCopy: getAboutUiCopy(language),
|
||||
icons,
|
||||
accentIcons,
|
||||
themeStyle: buildThemeStyle(settings)
|
||||
});
|
||||
},
|
||||
|
||||
onOpenLink(event) {
|
||||
const path = String(event.currentTarget.dataset.path || "").trim();
|
||||
if (!path) return;
|
||||
wx.navigateTo({ url: path });
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const brand = this.data.brand || getAboutBrand("zh-Hans");
|
||||
return {
|
||||
title: `${brand.productName} ${brand.version}`,
|
||||
path: ABOUT_APP_SHARE_HOME_PATH
|
||||
};
|
||||
},
|
||||
|
||||
...createSvgButtonPressMethods()
|
||||
});
|
||||
9
apps/miniprogram/pages/about-app/index.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "关于",
|
||||
"navigationBarBackgroundColor": "#f4f3ef",
|
||||
"navigationBarTextStyle": "black",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
59
apps/miniprogram/pages/about-app/index.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type SharePayload = {
|
||||
path: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type AboutAppPageOptions = {
|
||||
onShareAppMessage?: () => SharePayload;
|
||||
};
|
||||
|
||||
type MiniprogramGlobals = typeof globalThis & {
|
||||
Page?: (options: AboutAppPageOptions) => void;
|
||||
wx?: {
|
||||
setNavigationBarTitle?: (options: { title: string }) => void;
|
||||
};
|
||||
};
|
||||
|
||||
describe("about-app page", () => {
|
||||
const globalState = globalThis as MiniprogramGlobals;
|
||||
const originalPage = globalState.Page;
|
||||
const originalWx = globalState.wx;
|
||||
let capturedPageOptions: AboutAppPageOptions | null = null;
|
||||
|
||||
beforeEach(() => {
|
||||
capturedPageOptions = null;
|
||||
vi.resetModules();
|
||||
globalState.Page = vi.fn((options: AboutAppPageOptions) => {
|
||||
capturedPageOptions = options;
|
||||
});
|
||||
globalState.wx = {
|
||||
setNavigationBarTitle: vi.fn()
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalPage) {
|
||||
globalState.Page = originalPage;
|
||||
} else {
|
||||
delete globalState.Page;
|
||||
}
|
||||
if (originalWx) {
|
||||
globalState.wx = originalWx;
|
||||
} else {
|
||||
delete globalState.wx;
|
||||
}
|
||||
});
|
||||
|
||||
it("分享后应落到首页而不是 about 详情页", () => {
|
||||
require("./index.js");
|
||||
|
||||
expect(capturedPageOptions).toBeTruthy();
|
||||
expect(capturedPageOptions?.onShareAppMessage).toBeTypeOf("function");
|
||||
expect(capturedPageOptions?.onShareAppMessage?.()).toEqual({
|
||||
title: "RemoteConn v3.0.0",
|
||||
path: "/pages/connect/index"
|
||||
});
|
||||
});
|
||||
});
|
||||
61
apps/miniprogram/pages/about-app/index.wxml
Normal file
@@ -0,0 +1,61 @@
|
||||
<view class="about-app-page" style="{{themeStyle}}">
|
||||
<scroll-view class="about-app-scroll" scroll-y="true">
|
||||
<view class="about-app-shell">
|
||||
<view class="about-bg-orb about-bg-orb-left"></view>
|
||||
<view class="about-bg-orb about-bg-orb-right"></view>
|
||||
|
||||
<view class="about-app-brand">
|
||||
<image class="about-app-logo" src="/assets/icons/logo.svg" mode="aspectFit" />
|
||||
<image class="about-app-wordmark" src="/assets/icons/remoteconn.svg" mode="widthFix" />
|
||||
<image class="about-app-submark" src="/assets/icons/ai矩连.svg" mode="widthFix" />
|
||||
<text class="about-app-version">{{versionLine}}</text>
|
||||
</view>
|
||||
|
||||
<view class="about-app-card">
|
||||
<view class="about-app-card-inner">
|
||||
<text class="about-app-card-title">{{pageContent.sections[0].title}}</text>
|
||||
<text wx:if="{{pageContent.lead}}" class="about-app-card-lead">{{pageContent.lead}}</text>
|
||||
|
||||
<view class="about-app-info-list">
|
||||
<view wx:for="{{infoRows}}" wx:key="key" class="about-app-info-row">
|
||||
<text wx:if="{{item.label}}" class="about-app-info-label">{{item.label}}</text>
|
||||
<text class="about-app-info-value">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="about-app-share svg-press-btn"
|
||||
hover-class="svg-press-btn-hover"
|
||||
hover-start-time="0"
|
||||
hover-stay-time="80"
|
||||
open-type="share"
|
||||
data-press-key="about-app:share"
|
||||
bindtouchstart="onSvgButtonTouchStart"
|
||||
bindtouchend="onSvgButtonTouchEnd"
|
||||
bindtouchcancel="onSvgButtonTouchEnd"
|
||||
>
|
||||
<image
|
||||
class="about-app-share-icon svg-press-icon"
|
||||
src="{{pressedSvgButtonKey === 'about-app:share' ? (accentIcons.share || icons.share || '/assets/icons/share.svg') : (icons.share || '/assets/icons/share.svg')}}"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="about-app-share-text">{{uiCopy.shareButton}}</text>
|
||||
</button>
|
||||
|
||||
<view class="about-app-footer">
|
||||
<text
|
||||
wx:for="{{footerLinks}}"
|
||||
wx:key="key"
|
||||
class="about-app-footer-link"
|
||||
data-path="{{item.path}}"
|
||||
bindtap="onOpenLink"
|
||||
>
|
||||
{{item.title}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<bottom-nav page="about-app" />
|
||||
</view>
|
||||
185
apps/miniprogram/pages/about-app/index.wxss
Normal file
@@ -0,0 +1,185 @@
|
||||
@import "../about/common.wxss";
|
||||
|
||||
/**
|
||||
* 当前页沿用 about 共用色板,只保留“关于”详情页自身的布局差异,
|
||||
* 避免再维护第二套颜色常量。
|
||||
*/
|
||||
|
||||
.about-app-page {
|
||||
height: 100vh;
|
||||
background: var(--bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.about-app-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.about-app-shell {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding: 40rpx 28rpx 136rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.about-app-brand {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 0 0 34rpx 16rpx;
|
||||
}
|
||||
|
||||
.about-app-logo {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.about-app-wordmark {
|
||||
width: 563rpx;
|
||||
display: block;
|
||||
margin-top: -96rpx;
|
||||
margin-left: 112rpx;
|
||||
}
|
||||
|
||||
.about-app-submark {
|
||||
width: 88rpx;
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
margin-left: 112rpx;
|
||||
}
|
||||
|
||||
.about-app-version {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
margin-left: 112rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.about-app-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
min-height: 622rpx;
|
||||
border-radius: 28rpx;
|
||||
background: var(--about-surface);
|
||||
border: 1rpx solid var(--about-surface-border);
|
||||
box-shadow: 0 16rpx 38rpx var(--about-glow);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.about-app-card-inner {
|
||||
padding: 30rpx 28rpx 34rpx;
|
||||
}
|
||||
|
||||
.about-app-card-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.about-app-card-lead {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.65;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.about-app-info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-top: 26rpx;
|
||||
}
|
||||
|
||||
.about-app-info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.about-app-info-label {
|
||||
flex: 0 0 auto;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
font-weight: 700;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.about-app-info-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.about-app-share {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-self: flex-start;
|
||||
margin: 30rpx 0 0 16rpx !important;
|
||||
padding: 18rpx 24rpx !important;
|
||||
border: 1rpx solid var(--about-action-border) !important;
|
||||
border-radius: 999rpx !important;
|
||||
background: var(--about-action-bg) !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
box-shadow: 0 10rpx 24rpx var(--about-glow);
|
||||
--svg-press-active-radius: 999rpx;
|
||||
--svg-press-active-bg: var(--about-action-bg);
|
||||
--svg-press-active-shadow:
|
||||
0 14rpx 28rpx var(--about-glow),
|
||||
inset 0 0 0 1rpx var(--about-action-border);
|
||||
--svg-press-active-scale: 0.96;
|
||||
--svg-press-icon-opacity: 0.94;
|
||||
--svg-press-icon-active-opacity: 1;
|
||||
--svg-press-icon-active-scale: 1.08;
|
||||
}
|
||||
|
||||
.about-app-share::after {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.about-app-share-icon {
|
||||
width: 31rpx;
|
||||
height: 31rpx;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.about-app-share-text {
|
||||
font-size: 24rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
color: var(--about-action-text);
|
||||
}
|
||||
|
||||
.about-app-footer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 34rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 24rpx;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
|
||||
.about-app-footer-link {
|
||||
padding: 10rpx 0;
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
color: var(--about-accent);
|
||||
}
|
||||
3
apps/miniprogram/pages/about-changelog/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { createAboutDetailPage } = require("../../utils/aboutPageFactory");
|
||||
|
||||
Page(createAboutDetailPage("changelog"));
|
||||
9
apps/miniprogram/pages/about-changelog/index.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "变更记录",
|
||||
"navigationBarBackgroundColor": "#f4f3ef",
|
||||
"navigationBarTextStyle": "black",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
28
apps/miniprogram/pages/about-changelog/index.wxml
Normal file
@@ -0,0 +1,28 @@
|
||||
<view class="about-page" style="{{themeStyle}}">
|
||||
<scroll-view class="about-scroll" scroll-y="true">
|
||||
<view class="about-shell">
|
||||
<view class="about-bg-orb about-bg-orb-left"></view>
|
||||
<view class="about-bg-orb about-bg-orb-right"></view>
|
||||
|
||||
<view class="about-stack">
|
||||
<view class="detail-chip">{{brand.chineseName}}</view>
|
||||
<view class="detail-card">
|
||||
<text class="detail-title">{{pageContent.title}}</text>
|
||||
<text class="detail-lead">{{pageContent.lead}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{pageContent.sections}}" wx:key="title" class="detail-card detail-section-list">
|
||||
<text class="detail-section-title">{{item.title}}</text>
|
||||
<text wx:for="{{item.paragraphs}}" wx:key="index" class="detail-paragraph">{{item}}</text>
|
||||
<view wx:if="{{item.bullets && item.bullets.length}}" class="detail-bullet-list">
|
||||
<view wx:for="{{item.bullets}}" wx:key="index" class="detail-bullet-row">
|
||||
<text class="detail-bullet-dot">•</text>
|
||||
<text class="detail-bullet-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<bottom-nav page="about-changelog" />
|
||||
</view>
|
||||
1
apps/miniprogram/pages/about-changelog/index.wxss
Normal file
@@ -0,0 +1 @@
|
||||
@import "../about/common.wxss";
|
||||
3
apps/miniprogram/pages/about-feedback/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { createAboutDetailPage } = require("../../utils/aboutPageFactory");
|
||||
|
||||
Page(createAboutDetailPage("feedback"));
|
||||
9
apps/miniprogram/pages/about-feedback/index.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "问题反馈",
|
||||
"navigationBarBackgroundColor": "#f4f3ef",
|
||||
"navigationBarTextStyle": "black",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
35
apps/miniprogram/pages/about-feedback/index.wxml
Normal file
@@ -0,0 +1,35 @@
|
||||
<view class="about-page" style="{{themeStyle}}">
|
||||
<scroll-view class="about-scroll" scroll-y="true">
|
||||
<view class="about-shell">
|
||||
<view class="about-bg-orb about-bg-orb-left"></view>
|
||||
<view class="about-bg-orb about-bg-orb-right"></view>
|
||||
|
||||
<view class="about-stack">
|
||||
<view class="detail-chip">{{brand.chineseName}}</view>
|
||||
<view class="detail-card">
|
||||
<text class="detail-title">{{pageContent.title}}</text>
|
||||
<text class="detail-lead">{{pageContent.lead}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{pageContent.sections}}" wx:key="title" class="detail-card detail-section-list">
|
||||
<view class="detail-section-head">
|
||||
<text class="detail-section-title">{{item.title}}</text>
|
||||
<button
|
||||
wx:if="{{item.actionLabel}}"
|
||||
class="detail-bubble-action"
|
||||
bindtap="onCopyFeedbackEmail"
|
||||
>{{item.actionLabel}}</button>
|
||||
</view>
|
||||
<text wx:for="{{item.paragraphs}}" wx:key="index" class="detail-paragraph">{{item}}</text>
|
||||
<view wx:if="{{item.bullets && item.bullets.length}}" class="detail-bullet-list">
|
||||
<view wx:for="{{item.bullets}}" wx:key="index" class="detail-bullet-row">
|
||||
<text class="detail-bullet-dot">•</text>
|
||||
<text class="detail-bullet-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<bottom-nav page="about-feedback" />
|
||||
</view>
|
||||
1
apps/miniprogram/pages/about-feedback/index.wxss
Normal file
@@ -0,0 +1 @@
|
||||
@import "../about/common.wxss";
|
||||
3
apps/miniprogram/pages/about-manual/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { createAboutDetailPage } = require("../../utils/aboutPageFactory");
|
||||
|
||||
Page(createAboutDetailPage("manual"));
|
||||
9
apps/miniprogram/pages/about-manual/index.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "使用手册",
|
||||
"navigationBarBackgroundColor": "#f4f3ef",
|
||||
"navigationBarTextStyle": "black",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
39
apps/miniprogram/pages/about-manual/index.wxml
Normal file
@@ -0,0 +1,39 @@
|
||||
<view class="about-page" style="{{themeStyle}}">
|
||||
<scroll-view class="about-scroll" scroll-y="true">
|
||||
<view class="about-shell">
|
||||
<view class="about-bg-orb about-bg-orb-left"></view>
|
||||
<view class="about-bg-orb about-bg-orb-right"></view>
|
||||
|
||||
<view class="about-stack">
|
||||
<view class="detail-chip">{{brand.chineseName}}</view>
|
||||
<view class="detail-card">
|
||||
<text class="detail-title">{{pageContent.title}}</text>
|
||||
<text class="detail-lead">{{pageContent.lead}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{pageContent.sections}}" wx:key="title" class="detail-card detail-section-list">
|
||||
<text class="detail-section-title">{{item.title}}</text>
|
||||
<view wx:if="{{item.mediaItems && item.mediaItems.length}}" class="detail-media-list">
|
||||
<view wx:for="{{item.mediaItems}}" wx:for-item="media" wx:key="src" class="detail-media-card">
|
||||
<image
|
||||
class="detail-media-image"
|
||||
src="{{media.src}}"
|
||||
mode="widthFix"
|
||||
lazy-load="true"
|
||||
show-menu-by-longpress="true"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<text wx:for="{{item.paragraphs}}" wx:key="index" class="detail-paragraph">{{item}}</text>
|
||||
<view wx:if="{{item.bullets && item.bullets.length}}" class="detail-bullet-list">
|
||||
<view wx:for="{{item.bullets}}" wx:key="index" class="detail-bullet-row">
|
||||
<text class="detail-bullet-dot">•</text>
|
||||
<text class="detail-bullet-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<bottom-nav page="about-manual" />
|
||||
</view>
|
||||
1
apps/miniprogram/pages/about-manual/index.wxss
Normal file
@@ -0,0 +1 @@
|
||||
@import "../about/common.wxss";
|
||||
3
apps/miniprogram/pages/about-privacy/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { createAboutDetailPage } = require("../../utils/aboutPageFactory");
|
||||
|
||||
Page(createAboutDetailPage("privacy"));
|
||||
9
apps/miniprogram/pages/about-privacy/index.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "隐私政策",
|
||||
"navigationBarBackgroundColor": "#f4f3ef",
|
||||
"navigationBarTextStyle": "black",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
28
apps/miniprogram/pages/about-privacy/index.wxml
Normal file
@@ -0,0 +1,28 @@
|
||||
<view class="about-page" style="{{themeStyle}}">
|
||||
<scroll-view class="about-scroll" scroll-y="true">
|
||||
<view class="about-shell">
|
||||
<view class="about-bg-orb about-bg-orb-left"></view>
|
||||
<view class="about-bg-orb about-bg-orb-right"></view>
|
||||
|
||||
<view class="about-stack">
|
||||
<view class="detail-chip">{{brand.chineseName}}</view>
|
||||
<view class="detail-card">
|
||||
<text class="detail-title">{{pageContent.title}}</text>
|
||||
<text class="detail-lead">{{pageContent.lead}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{pageContent.sections}}" wx:key="title" class="detail-card detail-section-list">
|
||||
<text class="detail-section-title">{{item.title}}</text>
|
||||
<text wx:for="{{item.paragraphs}}" wx:key="index" class="detail-paragraph">{{item}}</text>
|
||||
<view wx:if="{{item.bullets && item.bullets.length}}" class="detail-bullet-list">
|
||||
<view wx:for="{{item.bullets}}" wx:key="index" class="detail-bullet-row">
|
||||
<text class="detail-bullet-dot">•</text>
|
||||
<text class="detail-bullet-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<bottom-nav page="about-privacy" />
|
||||
</view>
|
||||
1
apps/miniprogram/pages/about-privacy/index.wxss
Normal file
@@ -0,0 +1 @@
|
||||
@import "../about/common.wxss";
|
||||
332
apps/miniprogram/pages/about/common.wxss
Normal file
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* About 页仍保留独立编排,但配色必须走界面配置推导出的 token:
|
||||
* 1. 顶层背景、文字、卡片、强调色全部由 themeStyle 下发;
|
||||
* 2. 不再覆写 page 级固定米白主题,避免和主流程页脱节;
|
||||
* 3. 光斑只做氛围层,颜色同样从当前主题推导。
|
||||
*/
|
||||
|
||||
.about-page {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.about-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.about-shell {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding: 36rpx 28rpx 56rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.about-bg-orb {
|
||||
position: absolute;
|
||||
border-radius: 999rpx;
|
||||
pointer-events: none;
|
||||
animation: about-orb-float 8.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.about-bg-orb-left {
|
||||
width: 630rpx;
|
||||
height: 630rpx;
|
||||
left: -150rpx;
|
||||
bottom: -240rpx;
|
||||
background: radial-gradient(
|
||||
circle at 35% 35%,
|
||||
var(--about-orb-left-start) 0%,
|
||||
var(--about-orb-left-end) 72%,
|
||||
transparent 100%
|
||||
);
|
||||
opacity: 0.52;
|
||||
animation-duration: 9.4s;
|
||||
}
|
||||
|
||||
.about-bg-orb-right {
|
||||
width: 840rpx;
|
||||
height: 840rpx;
|
||||
right: -390rpx;
|
||||
bottom: -500rpx;
|
||||
background: radial-gradient(
|
||||
circle at 35% 35%,
|
||||
var(--about-orb-right-start) 0%,
|
||||
var(--about-orb-right-end) 62%,
|
||||
transparent 100%
|
||||
);
|
||||
opacity: 0.46;
|
||||
animation-duration: 7.6s;
|
||||
animation-delay: -2.2s;
|
||||
}
|
||||
|
||||
.about-stack {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22rpx;
|
||||
}
|
||||
|
||||
.about-hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
padding: 8rpx 8rpx 24rpx;
|
||||
}
|
||||
|
||||
.about-brand-en {
|
||||
font-size: 86rpx;
|
||||
line-height: 0.96;
|
||||
font-weight: 700;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.about-brand-version {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.about-brand-cn {
|
||||
font-size: 22rpx;
|
||||
line-height: 1.4;
|
||||
color: var(--about-text-muted);
|
||||
}
|
||||
|
||||
.about-intro {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.about-card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.about-entry {
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: 1rpx solid var(--about-surface-border) !important;
|
||||
border-radius: 28rpx !important;
|
||||
background: var(--about-surface) !important;
|
||||
box-shadow: 0 16rpx 38rpx var(--about-glow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.about-entry.svg-press-btn {
|
||||
--svg-press-active-radius: 36rpx;
|
||||
--svg-press-active-bg: var(--about-surface);
|
||||
--svg-press-active-shadow: 0 22rpx 42rpx var(--about-glow), inset 0 0 0 1rpx var(--about-surface-border);
|
||||
--svg-press-active-scale: 0.985;
|
||||
--svg-press-icon-opacity: 0.92;
|
||||
--svg-press-icon-active-opacity: 1;
|
||||
--svg-press-icon-active-scale: 1.08;
|
||||
}
|
||||
|
||||
.about-entry::after,
|
||||
.about-copy-btn::after {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.about-entry-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
padding: 28rpx 26rpx;
|
||||
}
|
||||
|
||||
.about-entry-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.about-entry-title {
|
||||
font-size: 30rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.about-entry-subtitle {
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
color: var(--about-text-muted);
|
||||
}
|
||||
|
||||
.about-entry-arrow {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.detail-chip {
|
||||
align-self: flex-start;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background: var(--about-accent-soft);
|
||||
color: var(--about-accent);
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
border-radius: 28rpx;
|
||||
background: var(--about-surface);
|
||||
border: 1rpx solid var(--about-surface-border);
|
||||
box-shadow: 0 16rpx 38rpx var(--about-glow);
|
||||
padding: 28rpx 26rpx;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 38rpx;
|
||||
line-height: 1.12;
|
||||
font-weight: 700;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.detail-lead {
|
||||
margin-top: 16rpx;
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.detail-section-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.detail-section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.detail-paragraph {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.detail-media-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.detail-media-card {
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid var(--about-surface-border);
|
||||
background: var(--about-surface);
|
||||
box-shadow: 0 12rpx 24rpx var(--about-glow);
|
||||
}
|
||||
|
||||
.detail-media-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-bullet-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.detail-bullet-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.detail-bullet-dot {
|
||||
flex: 0 0 auto;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: var(--about-text-strong);
|
||||
}
|
||||
|
||||
.detail-bullet-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.65;
|
||||
color: var(--about-text);
|
||||
}
|
||||
|
||||
.about-copy-btn {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
align-self: flex-start;
|
||||
margin: 0 !important;
|
||||
padding: 16rpx 22rpx !important;
|
||||
border-radius: 999rpx !important;
|
||||
border: 1rpx solid var(--about-action-border) !important;
|
||||
background: var(--about-action-bg) !important;
|
||||
color: var(--about-action-text) !important;
|
||||
font-size: 22rpx !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.detail-bubble-action {
|
||||
width: 92rpx !important;
|
||||
height: 92rpx !important;
|
||||
min-width: 92rpx !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: 1rpx solid var(--about-action-border) !important;
|
||||
border-radius: 999rpx !important;
|
||||
background: var(--about-action-bg) !important;
|
||||
color: var(--about-action-text) !important;
|
||||
font-size: 22rpx !important;
|
||||
line-height: 92rpx !important;
|
||||
text-align: center !important;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.detail-bubble-action::after {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
@keyframes about-orb-float {
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translate3d(14rpx, -18rpx, 0) scale(1.03);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
}
|
||||
60
apps/miniprogram/pages/about/index.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/* global Page, wx, require */
|
||||
|
||||
const { getSettings } = require("../../utils/storage");
|
||||
const { buildThemeStyle, applyNavigationBarTheme } = require("../../utils/themeStyle");
|
||||
const { getAboutBrand, getAboutDetailContent, getAboutHomeItems } = require("../../utils/aboutContent");
|
||||
const { normalizeUiLanguage } = require("../../utils/i18n");
|
||||
const { buildButtonIconThemeMaps } = require("../../utils/themedIcons");
|
||||
const { buildSvgButtonPressData, createSvgButtonPressMethods } = require("../../utils/svgButtonFeedback");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
...buildSvgButtonPressData(),
|
||||
brand: getAboutBrand("zh-Hans"),
|
||||
items: [],
|
||||
icons: {},
|
||||
accentIcons: {},
|
||||
themeStyle: "",
|
||||
// 首页头部只保留纯版本号,不叠加平台和时间戳。
|
||||
homeVersionLine: getAboutBrand("zh-Hans").version
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.applyThemeStyle();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.applyThemeStyle();
|
||||
},
|
||||
|
||||
applyThemeStyle() {
|
||||
const settings = getSettings();
|
||||
const language = normalizeUiLanguage(settings.uiLanguage);
|
||||
const brand = getAboutBrand(language);
|
||||
const { icons, accentIcons } = buildButtonIconThemeMaps(settings);
|
||||
const items = getAboutHomeItems(language).map((item) => ({
|
||||
...item,
|
||||
// about 首页入口目前固定 5 项,用业务 key 生成 press key,后续增删项也无需改模板判断。
|
||||
pressKey: `about:${item.key || item.path || item.title || "entry"}`
|
||||
}));
|
||||
const homeTitle = getAboutDetailContent("app", language).title || "About";
|
||||
applyNavigationBarTheme(settings);
|
||||
wx.setNavigationBarTitle({ title: homeTitle });
|
||||
this.setData({
|
||||
brand,
|
||||
items,
|
||||
icons,
|
||||
accentIcons,
|
||||
themeStyle: buildThemeStyle(settings),
|
||||
homeVersionLine: brand.version
|
||||
});
|
||||
},
|
||||
|
||||
onOpenItem(event) {
|
||||
const path = String(event.currentTarget.dataset.path || "").trim();
|
||||
if (!path) return;
|
||||
wx.navigateTo({ url: path });
|
||||
},
|
||||
|
||||
...createSvgButtonPressMethods()
|
||||
});
|
||||
9
apps/miniprogram/pages/about/index.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "关于",
|
||||
"navigationBarBackgroundColor": "#f4f3ef",
|
||||
"navigationBarTextStyle": "black",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
49
apps/miniprogram/pages/about/index.wxml
Normal file
@@ -0,0 +1,49 @@
|
||||
<view class="about-page" style="{{themeStyle}}">
|
||||
<scroll-view class="about-scroll" scroll-y="true">
|
||||
<view class="about-shell">
|
||||
<view class="about-bg-orb about-bg-orb-left"></view>
|
||||
<view class="about-bg-orb about-bg-orb-right"></view>
|
||||
|
||||
<view class="about-stack">
|
||||
<view class="about-hero">
|
||||
<view class="about-home-brand">
|
||||
<image class="about-home-logo" src="/assets/icons/logo.svg" mode="aspectFit" />
|
||||
<image class="about-home-wordmark" src="/assets/icons/remoteconn.svg" mode="widthFix" />
|
||||
<image class="about-home-submark" src="/assets/icons/ai矩连.svg" mode="widthFix" />
|
||||
<text class="about-home-version">{{homeVersionLine}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="about-card-list">
|
||||
<button
|
||||
wx:for="{{items}}"
|
||||
wx:key="key"
|
||||
class="about-entry svg-press-btn"
|
||||
hover-class="svg-press-btn-hover"
|
||||
hover-start-time="0"
|
||||
hover-stay-time="80"
|
||||
data-path="{{item.path}}"
|
||||
data-press-key="{{item.pressKey}}"
|
||||
bindtouchstart="onSvgButtonTouchStart"
|
||||
bindtouchend="onSvgButtonTouchEnd"
|
||||
bindtouchcancel="onSvgButtonTouchEnd"
|
||||
bindtap="onOpenItem"
|
||||
>
|
||||
<view class="about-entry-inner">
|
||||
<view class="about-entry-main">
|
||||
<text class="about-entry-title">{{item.title}}</text>
|
||||
<text class="about-entry-subtitle">{{item.subtitle}}</text>
|
||||
</view>
|
||||
<image
|
||||
class="about-entry-arrow svg-press-icon"
|
||||
src="{{pressedSvgButtonKey === item.pressKey ? (accentIcons.right || icons.right || '/assets/icons/right.svg') : (icons.right || '/assets/icons/right.svg')}}"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<bottom-nav page="about" />
|
||||
</view>
|
||||
36
apps/miniprogram/pages/about/index.wxss
Normal file
@@ -0,0 +1,36 @@
|
||||
@import "./common.wxss";
|
||||
|
||||
.about-home-brand {
|
||||
position: relative;
|
||||
padding: 0 0 10rpx 8rpx;
|
||||
}
|
||||
|
||||
.about-home-logo {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.about-home-wordmark {
|
||||
width: 563rpx;
|
||||
display: block;
|
||||
margin-top: -96rpx;
|
||||
margin-left: 112rpx;
|
||||
}
|
||||
|
||||
.about-home-submark {
|
||||
width: 88rpx;
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
margin-left: 112rpx;
|
||||
}
|
||||
|
||||
.about-home-version {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
margin-left: 112rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: var(--about-text);
|
||||
}
|
||||
925
apps/miniprogram/pages/connect/index.js
Normal file
@@ -0,0 +1,925 @@
|
||||
/* global Page, wx, require, console, module */
|
||||
|
||||
const {
|
||||
createServerSeed,
|
||||
listServers,
|
||||
saveServers,
|
||||
upsertServer,
|
||||
removeServer,
|
||||
markServerConnected,
|
||||
appendLog,
|
||||
getSettings
|
||||
} = require("../../utils/storage");
|
||||
const { getTerminalSessionSnapshot } = require("../../utils/terminalSession");
|
||||
const {
|
||||
isTerminalSessionAiHighlighted,
|
||||
isTerminalSessionConnecting,
|
||||
isTerminalSessionHighlighted
|
||||
} = require("../../utils/terminalSessionState");
|
||||
const { buildThemeStyle, applyNavigationBarTheme } = require("../../utils/themeStyle");
|
||||
const { buildButtonIconThemeMaps, resolveButtonIcon } = require("../../utils/themedIcons");
|
||||
const { openTerminalPage: navigateTerminalPage } = require("../../utils/terminalNavigation");
|
||||
const { buildPageCopy, formatTemplate, normalizeUiLanguage } = require("../../utils/i18n");
|
||||
const { subscribeSyncConfigApplied } = require("../../utils/syncConfigBus");
|
||||
const { buildSvgButtonPressData, createSvgButtonPressMethods } = require("../../utils/svgButtonFeedback");
|
||||
const { getWindowMetrics } = require("../../utils/systemInfoCompat");
|
||||
|
||||
const SWIPE_AXIS_LOCK_THRESHOLD_PX = 8;
|
||||
const SERVER_SWIPE_ACTION_WIDTH_RPX = 240;
|
||||
const SERVER_SWIPE_FALLBACK_WIDTH_PX = 120;
|
||||
|
||||
function resolveTouchClientPoint(event) {
|
||||
const point =
|
||||
(event && event.touches && event.touches[0]) ||
|
||||
(event && event.changedTouches && event.changedTouches[0]) ||
|
||||
null;
|
||||
if (!point) return null;
|
||||
const x = Number(point.clientX);
|
||||
const y = Number(point.clientY);
|
||||
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function resolveTouchClientY(event) {
|
||||
const point = resolveTouchClientPoint(event);
|
||||
return point ? point.y : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器左滑动作区露出“复制 + 删除”两个按钮,整体宽度按设计稿 `240rpx` 换算:
|
||||
* 1. 统一在 JS 内转成 px,便于与 touch `clientX` 直接比较;
|
||||
* 2. 老环境拿不到窗口宽度时回退到保守值,避免手势完全失效。
|
||||
*/
|
||||
function resolveServerSwipeRevealPx(windowWidth) {
|
||||
const width = Number(windowWidth);
|
||||
if (!Number.isFinite(width) || width <= 0) {
|
||||
return SERVER_SWIPE_FALLBACK_WIDTH_PX;
|
||||
}
|
||||
return Math.round((width * SERVER_SWIPE_ACTION_WIDTH_RPX) / 750);
|
||||
}
|
||||
|
||||
function clampServerSwipeOffset(offset, revealPx) {
|
||||
const numeric = Number(offset);
|
||||
if (!Number.isFinite(numeric)) return 0;
|
||||
if (numeric < -revealPx) return -revealPx;
|
||||
if (numeric > 0) return 0;
|
||||
return numeric;
|
||||
}
|
||||
|
||||
function shouldOpenServerSwipe(offset, revealPx) {
|
||||
return clampServerSwipeOffset(offset, revealPx) <= -revealPx * 0.45;
|
||||
}
|
||||
|
||||
const loggedRenderProbeKeys = new Set();
|
||||
|
||||
function shouldUseTextIcons() {
|
||||
if (!wx || typeof wx.getAppBaseInfo !== "function") return false;
|
||||
try {
|
||||
const info = wx.getAppBaseInfo() || {};
|
||||
return (
|
||||
String(info.platform || "")
|
||||
.trim()
|
||||
.toLowerCase() === "devtools"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function inspectRenderPayload(payload) {
|
||||
const stats = {
|
||||
stringCount: 0,
|
||||
dataImageCount: 0,
|
||||
svgPathCount: 0,
|
||||
urlCount: 0,
|
||||
maxLength: 0,
|
||||
samples: []
|
||||
};
|
||||
const walk = (value, path, depth) => {
|
||||
if (depth > 5) return;
|
||||
if (typeof value === "string") {
|
||||
stats.stringCount += 1;
|
||||
if (value.includes("data:image")) stats.dataImageCount += 1;
|
||||
if (value.includes(".svg")) stats.svgPathCount += 1;
|
||||
if (value.includes("url(")) stats.urlCount += 1;
|
||||
if (value.length > stats.maxLength) stats.maxLength = value.length;
|
||||
if (
|
||||
stats.samples.length < 6 &&
|
||||
(value.includes("data:image") ||
|
||||
value.includes(".svg") ||
|
||||
value.includes("url(") ||
|
||||
value.length >= 120)
|
||||
) {
|
||||
stats.samples.push({
|
||||
path,
|
||||
length: value.length,
|
||||
preview: value.slice(0, 120)
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!value || typeof value !== "object") return;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => walk(item, `${path}[${index}]`, depth + 1));
|
||||
return;
|
||||
}
|
||||
Object.keys(value).forEach((key) => walk(value[key], path ? `${path}.${key}` : key, depth + 1));
|
||||
};
|
||||
walk(payload, "", 0);
|
||||
return stats;
|
||||
}
|
||||
|
||||
function logRenderProbeOnce(key, label, payload) {
|
||||
const normalizedKey = String(key || label || "");
|
||||
if (!normalizedKey || loggedRenderProbeKeys.has(normalizedKey)) return;
|
||||
loggedRenderProbeKeys.add(normalizedKey);
|
||||
console.warn(`[render_probe] ${label}`, inspectRenderPayload(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器列表页(对齐 Web ConnectView):
|
||||
* 1. 顶部三图标:新增、删除已选、全选/取消全选;
|
||||
* 2. 搜索框 + 单层列表;
|
||||
* 3. 每行保留 ai/connect 图标位,排序改为长按拖拽。
|
||||
*/
|
||||
const connectPageOptions = {
|
||||
data: {
|
||||
...buildSvgButtonPressData(),
|
||||
themeStyle: "",
|
||||
icons: {},
|
||||
activeIcons: {},
|
||||
accentIcons: {},
|
||||
copy: buildPageCopy("zh-Hans", "connect"),
|
||||
textIconMode: false,
|
||||
query: "",
|
||||
servers: [],
|
||||
filteredServers: [],
|
||||
selectedServerIds: [],
|
||||
isAllSelected: false,
|
||||
activeServerId: "",
|
||||
connectingServerId: "",
|
||||
dragActive: false,
|
||||
dragServerId: ""
|
||||
},
|
||||
|
||||
dragRuntime: null,
|
||||
dragTapLockUntil: 0,
|
||||
syncConfigUnsub: null,
|
||||
swipeRuntime: null,
|
||||
swipeOffsets: null,
|
||||
serverSwipeRevealPx: 0,
|
||||
|
||||
onLoad() {
|
||||
/**
|
||||
* 首次启动时,云端 bootstrap 可能晚于首页首帧完成:
|
||||
* 1. 首页先按旧本地快照渲染是正常的;
|
||||
* 2. 一旦 bootstrap 合并回 storage,需要立刻重读服务器列表和主题;
|
||||
* 3. 否则用户会误以为同步没生效,必须手动重进页面才看到更新。
|
||||
*/
|
||||
this.syncConfigUnsub = subscribeSyncConfigApplied(() => {
|
||||
this.applyThemeStyle();
|
||||
this.reloadServers();
|
||||
});
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.applyThemeStyle();
|
||||
this.reloadServers();
|
||||
},
|
||||
|
||||
onHide() {
|
||||
this.swipeRuntime = null;
|
||||
this.closeAllServerRows();
|
||||
if (this.data.dragActive) {
|
||||
this.clearDragState();
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (typeof this.syncConfigUnsub === "function") {
|
||||
this.syncConfigUnsub();
|
||||
this.syncConfigUnsub = null;
|
||||
}
|
||||
},
|
||||
|
||||
applyThemeStyle() {
|
||||
const settings = getSettings();
|
||||
const language = normalizeUiLanguage(settings.uiLanguage);
|
||||
const copy = buildPageCopy(language, "connect");
|
||||
const { icons, activeIcons, accentIcons } = buildButtonIconThemeMaps(settings);
|
||||
applyNavigationBarTheme(settings);
|
||||
wx.setNavigationBarTitle({ title: copy.navTitle || "服务器" });
|
||||
const payload = {
|
||||
themeStyle: buildThemeStyle(settings),
|
||||
icons,
|
||||
activeIcons,
|
||||
accentIcons,
|
||||
copy,
|
||||
textIconMode: shouldUseTextIcons() && Object.keys(icons).length === 0
|
||||
};
|
||||
logRenderProbeOnce("connect.applyThemeStyle", "connect.applyThemeStyle", payload);
|
||||
this.setData(payload);
|
||||
},
|
||||
|
||||
reloadServers() {
|
||||
const rows = listServers();
|
||||
this.reconcileServerSwipeState(rows);
|
||||
this.setData({ servers: rows }, () => {
|
||||
this.applyFilter(this.data.query);
|
||||
this.syncSelectState();
|
||||
});
|
||||
},
|
||||
|
||||
applyFilter(query) {
|
||||
const text = String(query || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const selected = new Set(this.data.selectedServerIds);
|
||||
const sessionSnapshot = getTerminalSessionSnapshot();
|
||||
const fallbackThemeMaps = buildButtonIconThemeMaps(getSettings());
|
||||
const iconMap =
|
||||
this.data.icons && Object.keys(this.data.icons).length ? this.data.icons : fallbackThemeMaps.icons;
|
||||
const activeIconMap =
|
||||
this.data.activeIcons && Object.keys(this.data.activeIcons).length
|
||||
? this.data.activeIcons
|
||||
: fallbackThemeMaps.activeIcons;
|
||||
const accentIconMap =
|
||||
this.data.accentIcons && Object.keys(this.data.accentIcons).length
|
||||
? this.data.accentIcons
|
||||
: fallbackThemeMaps.accentIcons;
|
||||
this.reconcileServerSwipeState(this.data.servers);
|
||||
const next = this.data.servers
|
||||
.filter((item) => {
|
||||
if (!text) return true;
|
||||
return [
|
||||
item.name,
|
||||
item.host,
|
||||
item.username,
|
||||
String(item.port),
|
||||
item.authType,
|
||||
this.resolveDisplayTags(item)
|
||||
.map((tag) => tag.label)
|
||||
.join(" ")
|
||||
]
|
||||
.join(" ")
|
||||
.toLowerCase()
|
||||
.includes(text);
|
||||
})
|
||||
.map((item) => {
|
||||
const tags = this.resolveTags(item);
|
||||
const displayTags = this.resolveDisplayTags(item);
|
||||
const isConnected = isTerminalSessionHighlighted(sessionSnapshot, item.id);
|
||||
const isAiConnected = isTerminalSessionAiHighlighted(sessionSnapshot, item.id);
|
||||
return {
|
||||
...item,
|
||||
selected: selected.has(item.id),
|
||||
swipeOffsetX:
|
||||
this.swipeOffsets && Number.isFinite(Number(this.swipeOffsets[item.id]))
|
||||
? this.clampServerSwipeOffset(this.swipeOffsets[item.id])
|
||||
: 0,
|
||||
tags,
|
||||
displayTags,
|
||||
lastConnectedText: this.formatLastConnected(item.lastConnectedAt),
|
||||
authTypeLabel:
|
||||
(this.data.copy &&
|
||||
this.data.copy.authTypeLabels &&
|
||||
this.data.copy.authTypeLabels[item.authType]) ||
|
||||
item.authType ||
|
||||
"-",
|
||||
isConnected,
|
||||
isAiConnected,
|
||||
isConnecting:
|
||||
this.data.connectingServerId === item.id || isTerminalSessionConnecting(sessionSnapshot, item.id),
|
||||
aiPressKey: `connect-ai:${item.id}`,
|
||||
connectPressKey: `connect-open:${item.id}`,
|
||||
aiIcon: resolveButtonIcon("/assets/icons/ai.svg", isAiConnected ? activeIconMap : iconMap),
|
||||
aiPressedIcon: resolveButtonIcon(
|
||||
"/assets/icons/ai.svg",
|
||||
isAiConnected ? activeIconMap : accentIconMap
|
||||
),
|
||||
connectIcon: resolveButtonIcon("/assets/icons/connect.svg", isConnected ? activeIconMap : iconMap),
|
||||
connectPressedIcon: resolveButtonIcon(
|
||||
"/assets/icons/connect.svg",
|
||||
isConnected ? activeIconMap : accentIconMap
|
||||
),
|
||||
dragOffsetY: 0,
|
||||
dragging: false
|
||||
};
|
||||
});
|
||||
const payload = { query, filteredServers: next };
|
||||
logRenderProbeOnce("connect.applyFilter", "connect.applyFilter", payload);
|
||||
this.setData(payload, () => this.refreshDragVisual());
|
||||
},
|
||||
|
||||
syncSelectState() {
|
||||
const ids = this.data.servers.map((item) => item.id);
|
||||
const selected = this.data.selectedServerIds.filter((id) => ids.includes(id));
|
||||
const isAllSelected = ids.length > 0 && selected.length === ids.length;
|
||||
this.setData({ selectedServerIds: selected, isAllSelected }, () => this.applyFilter(this.data.query));
|
||||
},
|
||||
|
||||
onQueryInput(event) {
|
||||
this.closeAllServerRows();
|
||||
this.applyFilter(event.detail.value || "");
|
||||
},
|
||||
|
||||
onSearchTap() {
|
||||
this.closeAllServerRows();
|
||||
this.applyFilter(this.data.query);
|
||||
},
|
||||
|
||||
onCreateServer() {
|
||||
this.closeAllServerRows();
|
||||
const seed = createServerSeed();
|
||||
const prefix =
|
||||
(this.data.copy && this.data.copy.fallback && this.data.copy.fallback.newServerPrefix) || "server";
|
||||
upsertServer({ ...seed, name: `${prefix}-${this.data.servers.length + 1}` });
|
||||
this.reloadServers();
|
||||
wx.navigateTo({ url: `/pages/server-settings/index?id=${seed.id}` });
|
||||
},
|
||||
|
||||
onToggleServerSelect(event) {
|
||||
this.closeAllServerRows();
|
||||
const id = event.currentTarget.dataset.id;
|
||||
if (!id) return;
|
||||
const selected = new Set(this.data.selectedServerIds);
|
||||
if (selected.has(id)) {
|
||||
selected.delete(id);
|
||||
} else {
|
||||
selected.add(id);
|
||||
}
|
||||
this.setData({ selectedServerIds: [...selected] }, () => this.syncSelectState());
|
||||
},
|
||||
|
||||
onToggleSelectAll() {
|
||||
this.closeAllServerRows();
|
||||
if (this.data.isAllSelected) {
|
||||
this.setData({ selectedServerIds: [] }, () => this.syncSelectState());
|
||||
return;
|
||||
}
|
||||
const all = this.data.servers.map((item) => item.id);
|
||||
this.setData({ selectedServerIds: all }, () => this.syncSelectState());
|
||||
},
|
||||
|
||||
onRemoveSelected() {
|
||||
this.closeAllServerRows();
|
||||
const targets = this.data.selectedServerIds;
|
||||
if (!targets.length) return;
|
||||
const copy = this.data.copy || {};
|
||||
wx.showModal({
|
||||
title: copy?.modal?.removeTitle || "删除服务器",
|
||||
content: formatTemplate(copy?.modal?.removeContent, { count: targets.length }),
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
targets.forEach((id) => removeServer(id));
|
||||
this.setData({ selectedServerIds: [] });
|
||||
this.reloadServers();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onOpenSettings(event) {
|
||||
if (this.data.dragActive || Date.now() < this.dragTapLockUntil) return;
|
||||
this.closeAllServerRows();
|
||||
const serverId = event.currentTarget.dataset.id;
|
||||
this.setData({ activeServerId: serverId || "" });
|
||||
wx.navigateTo({ url: `/pages/server-settings/index?id=${serverId}` });
|
||||
},
|
||||
|
||||
onConnect(event) {
|
||||
if (this.data.dragActive || Date.now() < this.dragTapLockUntil) return;
|
||||
this.closeAllServerRows();
|
||||
const serverId = event.currentTarget.dataset.id;
|
||||
if (!serverId) return;
|
||||
|
||||
const sessionSnapshot = getTerminalSessionSnapshot();
|
||||
if (
|
||||
isTerminalSessionHighlighted(sessionSnapshot, serverId) ||
|
||||
isTerminalSessionConnecting(sessionSnapshot, serverId)
|
||||
) {
|
||||
this.setData({ activeServerId: serverId }, () => this.applyFilter(this.data.query));
|
||||
this.openTerminalPage(serverId, true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ activeServerId: serverId, connectingServerId: serverId }, () =>
|
||||
this.applyFilter(this.data.query)
|
||||
);
|
||||
markServerConnected(serverId);
|
||||
appendLog({
|
||||
serverId,
|
||||
status: "connecting",
|
||||
summary: (this.data.copy && this.data.copy.summary && this.data.copy.summary.connectFromList) || ""
|
||||
});
|
||||
this.setData({ connectingServerId: "" });
|
||||
this.openTerminalPage(serverId, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* 若当前页下方已经是终端页,优先直接返回,避免重复压入新的终端实例。
|
||||
*/
|
||||
openTerminalPage(serverId, reuseExisting, options) {
|
||||
navigateTerminalPage(serverId, reuseExisting, options);
|
||||
},
|
||||
|
||||
onAiTap(event) {
|
||||
if (this.data.dragActive || Date.now() < this.dragTapLockUntil) return;
|
||||
this.closeAllServerRows();
|
||||
const serverId = event.currentTarget.dataset.id;
|
||||
if (!serverId) return;
|
||||
|
||||
const server = this.data.servers.find((item) => item.id === serverId);
|
||||
if (!server) {
|
||||
wx.showToast({ title: this.data.copy?.toast?.serverNotFound || "服务器不存在", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionSnapshot = getTerminalSessionSnapshot();
|
||||
this.setData({ activeServerId: serverId }, () => this.applyFilter(this.data.query));
|
||||
|
||||
if (
|
||||
isTerminalSessionHighlighted(sessionSnapshot, serverId) ||
|
||||
isTerminalSessionConnecting(sessionSnapshot, serverId)
|
||||
) {
|
||||
this.openTerminalPage(serverId, true, { openCodex: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ connectingServerId: serverId }, () => this.applyFilter(this.data.query));
|
||||
markServerConnected(serverId);
|
||||
appendLog({
|
||||
serverId,
|
||||
status: "connecting",
|
||||
summary: (this.data.copy && this.data.copy.summary && this.data.copy.summary.aiFromList) || ""
|
||||
});
|
||||
this.setData({ connectingServerId: "" });
|
||||
this.openTerminalPage(serverId, false, { openCodex: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* 复制服务器配置(含认证信息):
|
||||
* 1. 基于当前服务器快照复制全部字段;
|
||||
* 2. 重新生成唯一 ID,避免覆盖原记录;
|
||||
* 3. 名称按“原服务器名+copy”落库,便于用户二次编辑。
|
||||
*/
|
||||
onCopyServer(event) {
|
||||
if (this.data.dragActive || Date.now() < this.dragTapLockUntil) return;
|
||||
this.closeAllServerRows();
|
||||
const serverId = event.currentTarget.dataset.id;
|
||||
if (!serverId) return;
|
||||
|
||||
const source = this.data.servers.find((item) => item.id === serverId);
|
||||
if (!source) {
|
||||
wx.showToast({
|
||||
title: this.data.copy?.toast?.serverToCopyNotFound || "未找到待复制服务器",
|
||||
icon: "none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const seed = createServerSeed();
|
||||
const copySuffix = String(this.data.copy?.labels?.copyNameSuffix || " copy");
|
||||
const copied = {
|
||||
...source,
|
||||
id: seed.id,
|
||||
name: `${String(source.name || this.data.copy?.unnamedServer || "未命名服务器")}${copySuffix}`,
|
||||
sortOrder: Date.now(),
|
||||
lastConnectedAt: ""
|
||||
};
|
||||
upsertServer(copied);
|
||||
this.setData({ activeServerId: copied.id }, () => this.reloadServers());
|
||||
wx.showToast({ title: this.data.copy?.toast?.serverCopied || "服务器已复制", icon: "none" });
|
||||
},
|
||||
|
||||
onStartDrag(event) {
|
||||
this.closeAllServerRows();
|
||||
this.swipeRuntime = null;
|
||||
const serverId = event.currentTarget.dataset.id;
|
||||
if (!serverId) return;
|
||||
if (this.data.dragActive) return;
|
||||
if (String(this.data.query || "").trim()) {
|
||||
wx.showToast({
|
||||
title: this.data.copy?.toast?.clearSearchBeforeSort || "请清空搜索后再调整顺序",
|
||||
icon: "none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.data.filteredServers.length <= 1) return;
|
||||
const fromIndex = this.data.filteredServers.findIndex((item) => item.id === serverId);
|
||||
if (fromIndex < 0) return;
|
||||
|
||||
const query = wx.createSelectorQuery().in(this);
|
||||
query.selectAll(".server-list-row").boundingClientRect((rects) => {
|
||||
if (!Array.isArray(rects) || rects.length !== this.data.filteredServers.length) return;
|
||||
const startRect = rects[fromIndex];
|
||||
if (!startRect) return;
|
||||
const startY = resolveTouchClientY(event) || startRect.top + startRect.height / 2;
|
||||
this.dragRuntime = {
|
||||
serverId,
|
||||
fromIndex,
|
||||
toIndex: fromIndex,
|
||||
startY,
|
||||
offsetY: 0,
|
||||
orderIds: this.data.filteredServers.map((item) => item.id),
|
||||
rects: rects.map((item) => ({
|
||||
top: Number(item.top) || 0,
|
||||
height: Number(item.height) || 0,
|
||||
center: (Number(item.top) || 0) + (Number(item.height) || 0) / 2
|
||||
}))
|
||||
};
|
||||
this.setData(
|
||||
{
|
||||
dragActive: true,
|
||||
dragServerId: serverId
|
||||
},
|
||||
() => this.refreshDragVisual()
|
||||
);
|
||||
});
|
||||
query.exec();
|
||||
},
|
||||
|
||||
onDragTouchMove(event) {
|
||||
if (!this.data.dragActive || !this.dragRuntime) return;
|
||||
const touchY = resolveTouchClientY(event);
|
||||
if (touchY == null) return;
|
||||
|
||||
const runtime = this.dragRuntime;
|
||||
runtime.offsetY = touchY - runtime.startY;
|
||||
|
||||
const sourceRect = runtime.rects[runtime.fromIndex];
|
||||
if (!sourceRect) return;
|
||||
const center = sourceRect.center + runtime.offsetY;
|
||||
let targetIndex = runtime.fromIndex;
|
||||
let minDistance = Number.POSITIVE_INFINITY;
|
||||
for (let i = 0; i < runtime.rects.length; i += 1) {
|
||||
const distance = Math.abs(center - runtime.rects[i].center);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
targetIndex = i;
|
||||
}
|
||||
}
|
||||
runtime.toIndex = targetIndex;
|
||||
this.refreshDragVisual();
|
||||
},
|
||||
|
||||
onDragTouchEnd() {
|
||||
if (!this.data.dragActive || !this.dragRuntime) return;
|
||||
const runtime = this.dragRuntime;
|
||||
const moved = runtime.toIndex !== runtime.fromIndex;
|
||||
const dragServerId = runtime.serverId;
|
||||
this.clearDragState();
|
||||
|
||||
if (!moved || !dragServerId) return;
|
||||
|
||||
const rows = this.data.servers.slice();
|
||||
const fromIndex = rows.findIndex((item) => item.id === dragServerId);
|
||||
if (fromIndex < 0) return;
|
||||
const [current] = rows.splice(fromIndex, 1);
|
||||
if (!current) return;
|
||||
const targetIndex = Math.max(0, Math.min(rows.length, runtime.toIndex));
|
||||
rows.splice(targetIndex, 0, current);
|
||||
this.persistServerOrder(rows, dragServerId);
|
||||
},
|
||||
|
||||
clearDragState() {
|
||||
this.dragRuntime = null;
|
||||
this.dragTapLockUntil = Date.now() + 240;
|
||||
const next = this.data.filteredServers.map((item) => {
|
||||
if (!item.dragOffsetY && !item.dragging) return item;
|
||||
return {
|
||||
...item,
|
||||
dragOffsetY: 0,
|
||||
dragging: false
|
||||
};
|
||||
});
|
||||
this.setData({
|
||||
dragActive: false,
|
||||
dragServerId: "",
|
||||
filteredServers: next
|
||||
});
|
||||
},
|
||||
|
||||
buildDragOffsetMap() {
|
||||
if (!this.data.dragActive || !this.dragRuntime) return {};
|
||||
const runtime = this.dragRuntime;
|
||||
const from = runtime.fromIndex;
|
||||
const to = runtime.toIndex;
|
||||
const offsets = {
|
||||
[runtime.serverId]: runtime.offsetY
|
||||
};
|
||||
if (to > from) {
|
||||
for (let i = from + 1; i <= to; i += 1) {
|
||||
const id = runtime.orderIds[i];
|
||||
if (!id) continue;
|
||||
const prev = runtime.rects[i - 1];
|
||||
const current = runtime.rects[i];
|
||||
offsets[id] = (prev ? prev.top : 0) - (current ? current.top : 0);
|
||||
}
|
||||
} else if (to < from) {
|
||||
for (let i = to; i < from; i += 1) {
|
||||
const id = runtime.orderIds[i];
|
||||
if (!id) continue;
|
||||
const current = runtime.rects[i];
|
||||
const next = runtime.rects[i + 1];
|
||||
offsets[id] = (next ? next.top : 0) - (current ? current.top : 0);
|
||||
}
|
||||
}
|
||||
return offsets;
|
||||
},
|
||||
|
||||
refreshDragVisual() {
|
||||
if (!this.data.dragActive || !this.dragRuntime) return;
|
||||
const offsets = this.buildDragOffsetMap();
|
||||
const dragId = this.data.dragServerId;
|
||||
const next = this.data.filteredServers.map((item) => {
|
||||
const dragOffsetY = Number(offsets[item.id] || 0);
|
||||
const dragging = item.id === dragId;
|
||||
if (item.dragOffsetY === dragOffsetY && item.dragging === dragging) {
|
||||
return item;
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
dragOffsetY,
|
||||
dragging
|
||||
};
|
||||
});
|
||||
this.setData({ filteredServers: next });
|
||||
},
|
||||
|
||||
persistServerOrder(rows, activeServerId) {
|
||||
const base = Date.now();
|
||||
const next = rows.map((item, index) => ({
|
||||
...item,
|
||||
sortOrder: base + index
|
||||
}));
|
||||
saveServers(next);
|
||||
this.setData(
|
||||
{
|
||||
servers: next,
|
||||
activeServerId: activeServerId || this.data.activeServerId
|
||||
},
|
||||
() => {
|
||||
this.applyFilter(this.data.query);
|
||||
this.syncSelectState();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
resolveTouchPoint(event) {
|
||||
return resolveTouchClientPoint(event);
|
||||
},
|
||||
|
||||
getServerSwipeRevealPx() {
|
||||
if (Number.isFinite(this.serverSwipeRevealPx) && this.serverSwipeRevealPx > 0) {
|
||||
return this.serverSwipeRevealPx;
|
||||
}
|
||||
const metrics = getWindowMetrics(wx);
|
||||
this.serverSwipeRevealPx = resolveServerSwipeRevealPx(metrics.windowWidth);
|
||||
return this.serverSwipeRevealPx;
|
||||
},
|
||||
|
||||
clampServerSwipeOffset(offset) {
|
||||
return clampServerSwipeOffset(offset, this.getServerSwipeRevealPx());
|
||||
},
|
||||
|
||||
reconcileServerSwipeState(rows) {
|
||||
const list = Array.isArray(rows) ? rows : [];
|
||||
const validIds = new Set(list.map((item) => item.id));
|
||||
const nextOffsets = {};
|
||||
Object.keys(this.swipeOffsets || {}).forEach((id) => {
|
||||
if (!validIds.has(id)) return;
|
||||
nextOffsets[id] = this.clampServerSwipeOffset(this.swipeOffsets[id]);
|
||||
});
|
||||
this.swipeOffsets = nextOffsets;
|
||||
if (this.swipeRuntime && !validIds.has(this.swipeRuntime.id)) {
|
||||
this.swipeRuntime = null;
|
||||
}
|
||||
},
|
||||
|
||||
findFilteredServerIndexById(id) {
|
||||
return this.data.filteredServers.findIndex((item) => item.id === id);
|
||||
},
|
||||
|
||||
updateServerSwipeOffset(id, offset) {
|
||||
const index = this.findFilteredServerIndexById(id);
|
||||
if (index < 0) return;
|
||||
const normalized = this.clampServerSwipeOffset(offset);
|
||||
this.swipeOffsets = this.swipeOffsets || {};
|
||||
this.swipeOffsets[id] = normalized;
|
||||
this.setData({ [`filteredServers[${index}].swipeOffsetX`]: normalized });
|
||||
},
|
||||
|
||||
closeOtherServerRows(exceptId) {
|
||||
this.swipeOffsets = this.swipeOffsets || {};
|
||||
const updates = {};
|
||||
this.data.filteredServers.forEach((item, index) => {
|
||||
if (item.id === exceptId) return;
|
||||
if (item.swipeOffsetX === 0) return;
|
||||
this.swipeOffsets[item.id] = 0;
|
||||
updates[`filteredServers[${index}].swipeOffsetX`] = 0;
|
||||
});
|
||||
if (Object.keys(updates).length > 0) {
|
||||
this.setData(updates);
|
||||
}
|
||||
},
|
||||
|
||||
closeAllServerRows() {
|
||||
this.swipeOffsets = this.swipeOffsets || {};
|
||||
const updates = {};
|
||||
this.data.filteredServers.forEach((item, index) => {
|
||||
if (item.swipeOffsetX === 0) return;
|
||||
this.swipeOffsets[item.id] = 0;
|
||||
updates[`filteredServers[${index}].swipeOffsetX`] = 0;
|
||||
});
|
||||
if (Object.keys(updates).length > 0) {
|
||||
this.setData(updates);
|
||||
}
|
||||
},
|
||||
|
||||
onListTap() {
|
||||
this.closeAllServerRows();
|
||||
},
|
||||
|
||||
/**
|
||||
* 服务器列表沿用闪念页的横向手势模型:
|
||||
* 1. `touchstart` 仅记录起点和当前开合态;
|
||||
* 2. `touchmove` 再做横纵轴锁定,避免和列表纵向滚动打架;
|
||||
* 3. 只允许向左露出删除按钮,不支持向右拖出正偏移。
|
||||
*/
|
||||
onServerTouchStart(event) {
|
||||
if (this.data.dragActive) return;
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id) return;
|
||||
const point = this.resolveTouchPoint(event);
|
||||
if (!point) return;
|
||||
this.closeOtherServerRows(id);
|
||||
this.swipeOffsets = this.swipeOffsets || {};
|
||||
this.swipeRuntime = {
|
||||
id,
|
||||
startX: point.x,
|
||||
startY: point.y,
|
||||
startOffsetX: this.clampServerSwipeOffset(this.swipeOffsets[id] || 0),
|
||||
dragging: false,
|
||||
blocked: false
|
||||
};
|
||||
},
|
||||
|
||||
onServerTouchMove(event) {
|
||||
if (this.data.dragActive) return;
|
||||
const runtime = this.swipeRuntime;
|
||||
if (!runtime || runtime.blocked) return;
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id || id !== runtime.id) return;
|
||||
const point = this.resolveTouchPoint(event);
|
||||
if (!point) return;
|
||||
|
||||
const deltaX = point.x - runtime.startX;
|
||||
const deltaY = point.y - runtime.startY;
|
||||
if (!runtime.dragging) {
|
||||
if (
|
||||
Math.abs(deltaX) < SWIPE_AXIS_LOCK_THRESHOLD_PX &&
|
||||
Math.abs(deltaY) < SWIPE_AXIS_LOCK_THRESHOLD_PX
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (Math.abs(deltaY) > Math.abs(deltaX)) {
|
||||
runtime.blocked = true;
|
||||
return;
|
||||
}
|
||||
runtime.dragging = true;
|
||||
}
|
||||
|
||||
this.updateServerSwipeOffset(id, runtime.startOffsetX + deltaX);
|
||||
},
|
||||
|
||||
onServerTouchEnd(event) {
|
||||
if (this.data.dragActive) return;
|
||||
const runtime = this.swipeRuntime;
|
||||
this.swipeRuntime = null;
|
||||
if (!runtime || runtime.blocked) return;
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id || id !== runtime.id) return;
|
||||
const current = this.clampServerSwipeOffset((this.swipeOffsets && this.swipeOffsets[id]) || 0);
|
||||
const shouldOpen = shouldOpenServerSwipe(current, this.getServerSwipeRevealPx());
|
||||
this.updateServerSwipeOffset(id, shouldOpen ? -this.getServerSwipeRevealPx() : 0);
|
||||
},
|
||||
|
||||
onSwipeDeleteServer(event) {
|
||||
const serverId = String(event.currentTarget.dataset.id || "");
|
||||
if (!serverId) return;
|
||||
const server = this.data.servers.find((item) => item.id === serverId);
|
||||
const serverName = String(
|
||||
(server && server.name) || this.data.copy?.unnamedServer || event.currentTarget.dataset.name || ""
|
||||
).trim();
|
||||
const copy = this.data.copy || {};
|
||||
wx.showModal({
|
||||
title: copy?.modal?.removeTitle || "删除服务器",
|
||||
content: formatTemplate(copy?.modal?.removeSingleContent, {
|
||||
name: serverName || copy?.unnamedServer || "未命名服务器"
|
||||
}),
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
removeServer(serverId);
|
||||
if (this.swipeOffsets) {
|
||||
delete this.swipeOffsets[serverId];
|
||||
}
|
||||
this.setData(
|
||||
{
|
||||
activeServerId: this.data.activeServerId === serverId ? "" : this.data.activeServerId,
|
||||
selectedServerIds: this.data.selectedServerIds.filter((id) => id !== serverId)
|
||||
},
|
||||
() => {
|
||||
this.reloadServers();
|
||||
wx.showToast({
|
||||
title: copy?.toast?.serverDeleted || "服务器已删除",
|
||||
icon: "success"
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 服务器标签对齐 Web 语义:
|
||||
* 1. 只展示用户明确配置的 tags;
|
||||
* 2. 不再按服务器名称猜测标签,避免与真实标签混淆。
|
||||
*/
|
||||
resolveTags(server) {
|
||||
return Array.isArray(server && server.tags)
|
||||
? server.tags.map((item) => String(item || "").trim()).filter((item) => !!item)
|
||||
: [];
|
||||
},
|
||||
|
||||
/**
|
||||
* 提取项目目录最后一级名称:
|
||||
* 1. 先清理空白与尾部斜杠;
|
||||
* 2. 同时兼容 Unix/Windows 路径;
|
||||
* 3. 列表里只展示短目录名,避免胶囊过长。
|
||||
*/
|
||||
resolveProjectDirectoryName(projectPath) {
|
||||
const normalized = String(projectPath || "")
|
||||
.trim()
|
||||
.replace(/[\\/]+$/g, "");
|
||||
if (!normalized) return "";
|
||||
const segments = normalized.split(/[\\/]+/).filter(Boolean);
|
||||
if (!segments.length) {
|
||||
return normalized === "~" ? "~" : "";
|
||||
}
|
||||
return segments[segments.length - 1] || "";
|
||||
},
|
||||
|
||||
/**
|
||||
* 卡片底部标签组装:
|
||||
* 1. project 胶囊固定排在最前面;
|
||||
* 2. 原始 tags 继续跟在后面;
|
||||
* 3. 返回对象数组,便于模板按类型切换底色。
|
||||
*/
|
||||
resolveDisplayTags(server) {
|
||||
const displayTags = [];
|
||||
const projectDirectoryName = this.resolveProjectDirectoryName(server && server.projectPath);
|
||||
if (projectDirectoryName) {
|
||||
displayTags.push({
|
||||
type: "project",
|
||||
label: `${(this.data.copy && this.data.copy.display && this.data.copy.display.projectPrefix) || "pro"}:${projectDirectoryName}`
|
||||
});
|
||||
}
|
||||
this.resolveTags(server).forEach((tag) => {
|
||||
displayTags.push({
|
||||
type: "tag",
|
||||
label: tag
|
||||
});
|
||||
});
|
||||
return displayTags;
|
||||
},
|
||||
|
||||
/**
|
||||
* 最近连接时间统一格式:
|
||||
* - 空值显示“无连接”;
|
||||
* - 非法时间显示“无连接”;
|
||||
* - 合法时间输出 YYYY-MM-DD HH:mm:ss。
|
||||
*/
|
||||
formatLastConnected(input) {
|
||||
if (!input) return this.data.copy?.fallback?.noConnection || "无连接";
|
||||
const date = new Date(input);
|
||||
if (Number.isNaN(+date)) return this.data.copy?.fallback?.noConnection || "无连接";
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
},
|
||||
|
||||
...createSvgButtonPressMethods()
|
||||
};
|
||||
|
||||
Page(connectPageOptions);
|
||||
|
||||
module.exports = {
|
||||
__test__: {
|
||||
pageOptions: connectPageOptions,
|
||||
SWIPE_AXIS_LOCK_THRESHOLD_PX,
|
||||
SERVER_SWIPE_ACTION_WIDTH_RPX,
|
||||
SERVER_SWIPE_FALLBACK_WIDTH_PX,
|
||||
resolveServerSwipeRevealPx,
|
||||
clampServerSwipeOffset,
|
||||
shouldOpenServerSwipe
|
||||
}
|
||||
};
|
||||
7
apps/miniprogram/pages/connect/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "服务器",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||