team: 组建产品和需求团队

新增产品和需求团队:
- 张婉琳 - 产品总监(前字节抖音产品总监)
- 李明哲 - 需求分析师(前腾讯微信需求分析师)
- 王思远 - 架构师(前阿里淘宝资深架构师)

更新管理架构:
- 产品线:产品总监 → 需求分析师 → PRD/需求文档
- 技术线:PM + 架构师 → 开发团队 → QA 团队
- 工作流程:产品规划 → 需求分析 → 架构设计 → 需求评审 → 开发实现 → 质量保证

团队职责:
- 产品总监:制定产品战略和路线图
- 需求分析师:细化需求,编写需求规格说明
- 架构师:设计技术方案,编写技术设计文档
This commit is contained in:
showen
2026-03-12 06:38:37 +08:00
parent def75d3d02
commit 62c02b541c
13 changed files with 508 additions and 30 deletions

View File

@@ -31,6 +31,12 @@ impl BlePlugin {
}
}
impl Default for BlePlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for BlePlugin {
fn id(&self) -> &'static str {
"ble"

View File

@@ -111,6 +111,12 @@ impl HttpPlugin {
}
}
impl Default for HttpPlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for HttpPlugin {
fn id(&self) -> &'static str { "http" }

View File

@@ -771,9 +771,7 @@ fn list_video_files(dir: &Path) -> Vec<VideoFileInfo> {
}
fn sanitize_filename(name: &str) -> String {
name.replace('/', "_")
.replace('\\', "_")
.replace("..", "_")
name.replace(['/', '\\'], "_").replace("..", "_")
}
fn video_dir(config: &AppConfig) -> PathBuf {

View File

@@ -98,6 +98,12 @@ impl ScreenPlugin {
}
}
impl Default for ScreenPlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for ScreenPlugin {
fn id(&self) -> &'static str {
"screen"

View File

@@ -82,6 +82,12 @@ impl VideoPlugin {
}
}
impl Default for VideoPlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for VideoPlugin {
fn id(&self) -> &'static str {
"video"
@@ -142,7 +148,7 @@ impl Plugin for VideoPlugin {
processor.pause();
}
PlayerCommand::Next => {
processor.next()?;
processor.next_video()?;
}
PlayerCommand::Previous => {
processor.previous()?;

View File

@@ -16,6 +16,9 @@ use std::collections::HashSet;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Instant;
type WindowRectKey = (String, i32, i32, i32, i32);
type LoggedSizes = ((i32, i32), (i32, i32), (i32, i32));
fn log_mat_size(label: &str, frame: &Mat) {
use std::sync::Mutex;
@@ -31,7 +34,7 @@ fn log_mat_size(label: &str, frame: &Mat) {
fn log_window_image_rect(window_name: &str) {
use std::sync::Mutex;
static SEEN: Mutex<Option<HashSet<(String, i32, i32, i32, i32)>>> = Mutex::new(None);
static SEEN: Mutex<Option<HashSet<WindowRectKey>>> = Mutex::new(None);
match highgui::get_window_image_rect(window_name) {
Ok(rect) => {
let key = (
@@ -464,7 +467,7 @@ pub struct VideoProcessor {
config: AppConfig,
transformer: VideoTransformer,
output_size: Size,
last_logged_sizes: Option<((i32, i32), (i32, i32), (i32, i32))>,
last_logged_sizes: Option<LoggedSizes>,
transition_enabled: bool,
transition: TransitionEffect,
playlist: Vec<VideoItem>,
@@ -623,7 +626,7 @@ impl VideoProcessor {
self.paused = false;
}
pub fn next(&mut self) -> Result<()> {
pub fn next_video(&mut self) -> Result<()> {
if self.playlist.is_empty() {
return Ok(());
}
@@ -1240,7 +1243,7 @@ impl VideoProcessor {
Ok(true)
}
110 | 78 => {
self.next()?;
self.next_video()?;
Ok(self.running)
}
112 | 80 => {

View File

@@ -112,16 +112,12 @@ impl StateMachine {
let target_state = state
.transitions
.iter()
.filter_map(|transition| match &transition.trigger {
.filter(|transition| match &transition.trigger {
TriggerType::Random { probability } => {
let probability = probability.clamp(0.0, 1.0);
if rng.gen_bool(probability) {
Some(transition)
} else {
None
}
rng.gen_bool(probability)
}
_ => None,
_ => false,
})
.max_by_key(|transition| transition.priority)
.map(|transition| transition.target_state.clone());

View File

@@ -214,6 +214,12 @@ impl WifiPlugin {
}
}
impl Default for WifiPlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for WifiPlugin {
fn id(&self) -> &'static str {
"wifi"