docs: 整理项目文档并统一界面表述
将项目说明按主题集中到 docs,补齐根目录 README 和 AGENTS,移除弃用方案并保留有效测试记录与未实现提案。同步文档索引、引用及改动日志生成路径,README 增加 GitHub Markdown 导航和 Mermaid 图。 - fix(client): 统一状态与操作文案,录制和截图成功后再显示完成提示 - test(client): 按文件名核对媒体类型,避免依赖时间戳排序 验证:23 份项目文档、144 处本地引用、27 条外链、2 张 Mermaid 图;前端构建和 12 项测试;截图与录制的 4 条异步反馈路径;Windows Rust 编译检查和后端测试。后端 40 项返回成功,其中 3 项 ffmpeg 集成用例因本机缺少工具而未实际执行。
This commit is contained in:
@@ -76,7 +76,7 @@ pub fn config_path(state: Shared<'_>) -> String {
|
||||
pub fn unlock(password: String, state: Shared<'_>) -> Result<bool, String> {
|
||||
state
|
||||
.unlock(&password)
|
||||
.map_err(|err| format!("存下来的密码哈希读不出来:{err}"))
|
||||
.map_err(|err| format!("无法读取已保存的密码哈希:{err}"))
|
||||
}
|
||||
|
||||
/// 手动重新上锁(并顺手把发现列表关掉)。
|
||||
@@ -305,7 +305,7 @@ pub fn screenshot(camera: u32, state: Shared<'_>) -> Result<String, String> {
|
||||
let frame = state
|
||||
.latest_frames()
|
||||
.latest(camera)
|
||||
.ok_or_else(|| format!("还没有收到 0x{camera:08X} 的画面,暂时截不了"))?;
|
||||
.ok_or_else(|| format!("尚未收到 0x{camera:08X} 的画面,暂时无法截图"))?;
|
||||
|
||||
let config = state.config();
|
||||
let dir = config.recordings_dir().join("shots");
|
||||
@@ -329,7 +329,7 @@ pub fn start_recording(
|
||||
state: Shared<'_>,
|
||||
) -> Result<Vec<cam_core::snapshot::RecordingDto>, String> {
|
||||
if cameras.is_empty() {
|
||||
return Err("没有指定要录哪一路".into());
|
||||
return Err("请先选择要录制的画面".into());
|
||||
}
|
||||
|
||||
let config = state.config();
|
||||
@@ -347,8 +347,8 @@ pub fn start_recording(
|
||||
Some(measured) if measured > 1.0 => measured,
|
||||
_ => {
|
||||
log::warn!(
|
||||
"0x{camera:08X} 还没有测到帧率,录制先按 {FALLBACK_INPUT_FPS} fps 走;\
|
||||
回放速度可能不准,可以改用 [recording] fps 明确指定"
|
||||
"尚未测得 0x{camera:08X} 的帧率,录制暂用 {FALLBACK_INPUT_FPS} fps;\
|
||||
回放速度可能不准确,可在 [recording] 中设置 fps"
|
||||
);
|
||||
FALLBACK_INPUT_FPS
|
||||
}
|
||||
@@ -365,7 +365,7 @@ pub fn start_recording(
|
||||
config.recording.crf,
|
||||
&config.recording.preset,
|
||||
)
|
||||
.map_err(|err| format!("起不了录制:{err:#}"))?;
|
||||
.map_err(|err| format!("无法开始录制:{err:#}"))?;
|
||||
|
||||
started.push(dto);
|
||||
}
|
||||
@@ -405,7 +405,7 @@ pub fn reveal(path: String, state: Shared<'_>) -> Result<(), String> {
|
||||
}
|
||||
|
||||
if !target.exists() {
|
||||
return Err(format!("{} 不存在了", target.display()));
|
||||
return Err(format!("文件不存在:{}", target.display()));
|
||||
}
|
||||
|
||||
tauri_plugin_opener::reveal_item_in_dir(&target)
|
||||
@@ -427,7 +427,7 @@ pub fn open_file(path: String, state: Shared<'_>) -> Result<(), String> {
|
||||
}
|
||||
|
||||
if !target.exists() {
|
||||
return Err(format!("{} 不存在了", target.display()));
|
||||
return Err(format!("文件不存在:{}", target.display()));
|
||||
}
|
||||
|
||||
tauri_plugin_opener::open_path(&target, None::<&str>)
|
||||
@@ -470,7 +470,7 @@ pub fn clear_config(password: Option<String>, state: Shared<'_>) -> Result<Strin
|
||||
if path.exists() {
|
||||
let backup = PathBuf::from(format!("{}.bak", path.display()));
|
||||
std::fs::rename(&path, &backup)
|
||||
.map_err(|err| format!("挪走 {} 失败:{err}", path.display()))?;
|
||||
.map_err(|err| format!("移动文件 {} 失败:{err}", path.display()))?;
|
||||
|
||||
log::warn!("配置已重置,旧文件留在 {}", backup.display());
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||
log::debug!("stream: {}", state.stream_info().url);
|
||||
|
||||
if args.demo {
|
||||
state.note("演示模式已打开:画面与血量由本机合成,不是场上的板子。");
|
||||
state.note("演示模式已开启:画面与血量由本机合成。");
|
||||
demo::spawn(Arc::clone(&state));
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ impl Recorder {
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| {
|
||||
format!("could not start ffmpeg ({ffmpeg}); 用 [recording] ffmpeg 指定它的路径")
|
||||
format!("无法启动 ffmpeg({ffmpeg}),请在 [recording] 中设置 ffmpeg 路径")
|
||||
})?;
|
||||
|
||||
let stdin = child
|
||||
@@ -268,11 +268,11 @@ pub fn save_screenshot(jpeg: &[u8], dir: &Path, camera: u32) -> Result<PathBuf>
|
||||
);
|
||||
|
||||
let image = image::load_from_memory_with_format(jpeg, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| anyhow!("这一帧不是能解码的 JPEG:{err}"))?;
|
||||
.map_err(|err| anyhow!("无法解码当前 JPEG 帧:{err}"))?;
|
||||
|
||||
image
|
||||
.save_with_format(&path, image::ImageFormat::Png)
|
||||
.map_err(|err| anyhow!("写不了 {}:{err}", path.display()))?;
|
||||
.map_err(|err| anyhow!("无法写入文件 {}:{err}", path.display()))?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
@@ -290,9 +290,9 @@ fn pump(
|
||||
// 读出来拼进错误里:只说"Broken pipe"对现场毫无帮助。
|
||||
let reason = child_stderr(child);
|
||||
return Err(anyhow!(
|
||||
"writing to ffmpeg failed: {err}{}",
|
||||
"向 ffmpeg 写入画面失败:{err}{}",
|
||||
reason
|
||||
.map(|r| format!(";ffmpeg 说:{r}"))
|
||||
.map(|r| format!(";ffmpeg 输出:{r}"))
|
||||
.unwrap_or_default()
|
||||
));
|
||||
}
|
||||
@@ -776,7 +776,10 @@ mod tests {
|
||||
names.contains(&"b.PNG"),
|
||||
"后缀大小写不该决定它是不是一张截图"
|
||||
);
|
||||
assert_eq!(files[0].kind, "video");
|
||||
let video = files.iter().find(|file| file.name == "a.mp4").unwrap();
|
||||
let image = files.iter().find(|file| file.name == "b.PNG").unwrap();
|
||||
assert_eq!(video.kind, "video");
|
||||
assert_eq!(image.kind, "image");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -375,13 +375,13 @@ impl AppState {
|
||||
match self.unlock(presented) {
|
||||
Ok(true) => return Ok(()),
|
||||
Ok(false) => {
|
||||
return Err("密码不对。改摄像头/装甲板绑定、切换队伍都需要密码。".into())
|
||||
return Err("密码错误。修改图传板、装甲板绑定或队伍号需要密码。".into())
|
||||
}
|
||||
Err(err) => return Err(format!("存下来的密码哈希读不出来:{err}")),
|
||||
Err(err) => return Err(format!("无法读取已保存的密码哈希:{err}")),
|
||||
}
|
||||
}
|
||||
|
||||
Err("这个改动需要密码。先解锁(密码只是防止现场随手改绑,见 README 的说明)。".into())
|
||||
Err("修改设置需要密码,请先解锁。".into())
|
||||
}
|
||||
|
||||
/// 打开/关闭设置面板带来的发现列表。
|
||||
|
||||
Reference in New Issue
Block a user