Files
mixvideo-v2/apps/desktop/src-tauri/src/data/models/model.rs
imeepos 5cf1f8bfca feat: 实现模特管理功能
- 新增模特数据模型和数据库表结构
- 实现模特的完整CRUD操作
- 添加模特照片管理功能
- 实现素材与模特关联功能
- 创建模特管理前端界面
- 集成到主应用导航和路由
- 修复数据库连接死锁问题

功能特性:
- 模特基本信息管理(姓名、艺名、性别、年龄等)
- 照片管理和封面设置
- 标签系统
- 状态管理(活跃、不活跃、退役、暂停)
- 评分系统
- 搜索和过滤功能
- 素材关联功能
2025-07-14 01:39:14 +08:00

391 lines
10 KiB
Rust

use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
/// 模特实体模型
/// 遵循 Tauri 开发规范的数据模型设计原则
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Model {
pub id: String,
pub name: String,
pub stage_name: Option<String>, // 艺名
pub gender: Gender,
pub age: Option<u32>,
pub height: Option<u32>, // 身高(厘米)
pub weight: Option<u32>, // 体重(公斤)
pub measurements: Option<Measurements>, // 三围
pub description: Option<String>,
pub tags: Vec<String>, // 标签,如"性感"、"清纯"、"成熟"等
pub avatar_path: Option<String>, // 头像照片路径
pub photos: Vec<ModelPhoto>, // 模特照片集
pub contact_info: Option<ContactInfo>, // 联系信息
pub social_media: Option<SocialMedia>, // 社交媒体信息
pub status: ModelStatus,
pub rating: Option<f32>, // 评分 (1.0-5.0)
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub is_active: bool,
}
/// 性别枚举
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Gender {
Male,
Female,
Other,
}
/// 三围信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Measurements {
pub bust: u32, // 胸围
pub waist: u32, // 腰围
pub hips: u32, // 臀围
}
/// 模特照片
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelPhoto {
pub id: String,
pub model_id: String,
pub file_path: String,
pub file_name: String,
pub file_size: u64,
pub photo_type: PhotoType,
pub description: Option<String>,
pub tags: Vec<String>,
pub is_cover: bool, // 是否为封面照片
pub created_at: DateTime<Utc>,
}
/// 照片类型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PhotoType {
Portrait, // 肖像照
FullBody, // 全身照
Headshot, // 头像照
Artistic, // 艺术照
Commercial, // 商业照
Casual, // 生活照
Other,
}
/// 联系信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContactInfo {
pub phone: Option<String>,
pub email: Option<String>,
pub wechat: Option<String>,
pub qq: Option<String>,
pub address: Option<String>,
}
/// 社交媒体信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SocialMedia {
pub weibo: Option<String>,
pub instagram: Option<String>,
pub tiktok: Option<String>,
pub xiaohongshu: Option<String>, // 小红书
pub douyin: Option<String>, // 抖音
}
/// 模特状态
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModelStatus {
Active, // 活跃
Inactive, // 不活跃
Retired, // 退役
Suspended, // 暂停
}
impl Model {
/// 创建新的模特实例
pub fn new(
name: String,
gender: Gender,
) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
name,
stage_name: None,
gender,
age: None,
height: None,
weight: None,
measurements: None,
description: None,
tags: Vec::new(),
avatar_path: None,
photos: Vec::new(),
contact_info: None,
social_media: None,
status: ModelStatus::Active,
rating: None,
created_at: now,
updated_at: now,
is_active: true,
}
}
/// 更新模特基本信息
pub fn update_basic_info(
&mut self,
name: Option<String>,
stage_name: Option<String>,
age: Option<u32>,
height: Option<u32>,
weight: Option<u32>,
description: Option<String>,
) {
if let Some(name) = name {
self.name = name;
}
if stage_name.is_some() {
self.stage_name = stage_name;
}
if age.is_some() {
self.age = age;
}
if height.is_some() {
self.height = height;
}
if weight.is_some() {
self.weight = weight;
}
if description.is_some() {
self.description = description;
}
self.updated_at = Utc::now();
}
/// 设置三围信息
pub fn set_measurements(&mut self, measurements: Option<Measurements>) {
self.measurements = measurements;
self.updated_at = Utc::now();
}
/// 添加标签
pub fn add_tag(&mut self, tag: String) {
if !self.tags.contains(&tag) {
self.tags.push(tag);
self.updated_at = Utc::now();
}
}
/// 移除标签
pub fn remove_tag(&mut self, tag: &str) {
if let Some(pos) = self.tags.iter().position(|t| t == tag) {
self.tags.remove(pos);
self.updated_at = Utc::now();
}
}
/// 设置头像
pub fn set_avatar(&mut self, avatar_path: Option<String>) {
self.avatar_path = avatar_path;
self.updated_at = Utc::now();
}
/// 添加照片
pub fn add_photo(&mut self, photo: ModelPhoto) {
self.photos.push(photo);
self.updated_at = Utc::now();
}
/// 移除照片
pub fn remove_photo(&mut self, photo_id: &str) {
if let Some(pos) = self.photos.iter().position(|p| p.id == photo_id) {
self.photos.remove(pos);
self.updated_at = Utc::now();
}
}
/// 设置联系信息
pub fn set_contact_info(&mut self, contact_info: Option<ContactInfo>) {
self.contact_info = contact_info;
self.updated_at = Utc::now();
}
/// 设置社交媒体信息
pub fn set_social_media(&mut self, social_media: Option<SocialMedia>) {
self.social_media = social_media;
self.updated_at = Utc::now();
}
/// 更新状态
pub fn update_status(&mut self, status: ModelStatus) {
self.status = status;
self.updated_at = Utc::now();
}
/// 设置评分
pub fn set_rating(&mut self, rating: Option<f32>) {
if let Some(r) = rating {
if r >= 1.0 && r <= 5.0 {
self.rating = Some(r);
self.updated_at = Utc::now();
}
} else {
self.rating = None;
self.updated_at = Utc::now();
}
}
/// 验证模特数据
pub fn validate(&self) -> Result<(), String> {
if self.name.trim().is_empty() {
return Err("模特姓名不能为空".to_string());
}
if self.name.len() > 50 {
return Err("模特姓名不能超过50个字符".to_string());
}
if let Some(ref stage_name) = self.stage_name {
if stage_name.len() > 50 {
return Err("艺名不能超过50个字符".to_string());
}
}
if let Some(age) = self.age {
if age > 100 {
return Err("年龄不能超过100岁".to_string());
}
}
if let Some(height) = self.height {
if height < 50 || height > 250 {
return Err("身高必须在50-250厘米之间".to_string());
}
}
if let Some(weight) = self.weight {
if weight < 20 || weight > 200 {
return Err("体重必须在20-200公斤之间".to_string());
}
}
if let Some(ref description) = self.description {
if description.len() > 1000 {
return Err("描述不能超过1000个字符".to_string());
}
}
if let Some(rating) = self.rating {
if rating < 1.0 || rating > 5.0 {
return Err("评分必须在1.0-5.0之间".to_string());
}
}
Ok(())
}
/// 获取封面照片
pub fn get_cover_photo(&self) -> Option<&ModelPhoto> {
self.photos.iter().find(|p| p.is_cover)
}
/// 获取显示名称(优先使用艺名)
pub fn get_display_name(&self) -> &str {
self.stage_name.as_ref().unwrap_or(&self.name)
}
}
impl ModelPhoto {
/// 创建新的模特照片
pub fn new(
model_id: String,
file_path: String,
file_name: String,
file_size: u64,
photo_type: PhotoType,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
model_id,
file_path,
file_name,
file_size,
photo_type,
description: None,
tags: Vec::new(),
is_cover: false,
created_at: Utc::now(),
}
}
/// 设置为封面照片
pub fn set_as_cover(&mut self) {
self.is_cover = true;
}
/// 取消封面照片
pub fn unset_as_cover(&mut self) {
self.is_cover = false;
}
/// 添加标签
pub fn add_tag(&mut self, tag: String) {
if !self.tags.contains(&tag) {
self.tags.push(tag);
}
}
/// 移除标签
pub fn remove_tag(&mut self, tag: &str) {
if let Some(pos) = self.tags.iter().position(|t| t == tag) {
self.tags.remove(pos);
}
}
}
/// 创建模特请求模型
#[derive(Debug, Deserialize)]
pub struct CreateModelRequest {
pub name: String,
pub stage_name: Option<String>,
pub gender: Gender,
pub age: Option<u32>,
pub height: Option<u32>,
pub weight: Option<u32>,
pub measurements: Option<Measurements>,
pub description: Option<String>,
pub tags: Option<Vec<String>>,
pub contact_info: Option<ContactInfo>,
pub social_media: Option<SocialMedia>,
}
/// 更新模特请求模型
#[derive(Debug, Deserialize)]
pub struct UpdateModelRequest {
pub name: Option<String>,
pub stage_name: Option<String>,
pub age: Option<u32>,
pub height: Option<u32>,
pub weight: Option<u32>,
pub measurements: Option<Measurements>,
pub description: Option<String>,
pub tags: Option<Vec<String>>,
pub contact_info: Option<ContactInfo>,
pub social_media: Option<SocialMedia>,
pub status: Option<ModelStatus>,
pub rating: Option<f32>,
}
/// 模特查询参数
#[derive(Debug, Deserialize)]
pub struct ModelQueryParams {
pub name: Option<String>,
pub gender: Option<Gender>,
pub min_age: Option<u32>,
pub max_age: Option<u32>,
pub min_height: Option<u32>,
pub max_height: Option<u32>,
pub tags: Option<Vec<String>>,
pub status: Option<ModelStatus>,
pub min_rating: Option<f32>,
pub limit: Option<u32>,
pub offset: Option<u32>,
}