- 新增模特数据模型和数据库表结构 - 实现模特的完整CRUD操作 - 添加模特照片管理功能 - 实现素材与模特关联功能 - 创建模特管理前端界面 - 集成到主应用导航和路由 - 修复数据库连接死锁问题 功能特性: - 模特基本信息管理(姓名、艺名、性别、年龄等) - 照片管理和封面设置 - 标签系统 - 状态管理(活跃、不活跃、退役、暂停) - 评分系统 - 搜索和过滤功能 - 素材关联功能
391 lines
10 KiB
Rust
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>,
|
|
}
|