fix
This commit is contained in:
parent
50ca8c1e3a
commit
01b6603eec
|
|
@ -28,4 +28,5 @@ tauri-plugin-fs = "2"
|
|||
tauri-plugin-shell = "2"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
base64 = "0.21"
|
||||
anyhow = "1.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
use std::fs;
|
||||
use std::path::Path;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use tauri::command;
|
||||
|
||||
/// 读取图片文件并返回base64编码的数据URL
|
||||
#[command]
|
||||
pub async fn read_image_as_data_url(file_path: String) -> Result<String, String> {
|
||||
let path = Path::new(&file_path);
|
||||
|
||||
// 检查文件是否存在
|
||||
if !path.exists() {
|
||||
return Err("File does not exist".to_string());
|
||||
}
|
||||
|
||||
// 检查是否是图片文件
|
||||
let extension = path.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.map(|ext| ext.to_lowercase())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mime_type = match extension.as_str() {
|
||||
"jpg" | "jpeg" => "image/jpeg",
|
||||
"png" => "image/png",
|
||||
"gif" => "image/gif",
|
||||
"bmp" => "image/bmp",
|
||||
"webp" => "image/webp",
|
||||
"svg" => "image/svg+xml",
|
||||
_ => return Err("Unsupported image format".to_string()),
|
||||
};
|
||||
|
||||
// 读取文件内容
|
||||
match fs::read(&file_path) {
|
||||
Ok(file_data) => {
|
||||
// 编码为base64
|
||||
let base64_data = general_purpose::STANDARD.encode(&file_data);
|
||||
// 返回data URL格式
|
||||
Ok(format!("data:{};base64,{}", mime_type, base64_data))
|
||||
}
|
||||
Err(e) => Err(format!("Failed to read file: {}", e)),
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ pub mod ai_video;
|
|||
pub mod file_system;
|
||||
pub mod project;
|
||||
pub mod template;
|
||||
pub mod file_utils;
|
||||
pub mod resource_category;
|
||||
|
||||
// Re-export all commands for easy access
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ pub fn run() {
|
|||
commands::update_project,
|
||||
commands::delete_project,
|
||||
commands::search_projects,
|
||||
commands::open_project_directory
|
||||
commands::open_project_directory,
|
||||
commands::file_utils::read_image_as_data_url
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ const Sidebar: React.FC = () => {
|
|||
{ path: '/settings', icon: Settings, label: '设置' },
|
||||
]
|
||||
|
||||
const isAIVideoPage = location.pathname === '/ai-video'
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'processing': return <Clock size={14} className="text-blue-500" />
|
||||
|
|
@ -61,24 +59,6 @@ const Sidebar: React.FC = () => {
|
|||
>
|
||||
导航
|
||||
</button>
|
||||
{isAIVideoPage && (
|
||||
<button
|
||||
onClick={() => setActiveTab('tasks')}
|
||||
className={clsx(
|
||||
'flex-1 px-4 py-3 text-sm font-medium border-b-2 transition-colors relative',
|
||||
activeTab === 'tasks'
|
||||
? 'border-primary-500 text-primary-600 bg-primary-50'
|
||||
: 'border-transparent text-secondary-600 hover:text-secondary-900 hover:bg-secondary-50'
|
||||
)}
|
||||
>
|
||||
任务列表
|
||||
{jobs.length > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-primary-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
|
||||
{jobs.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Plus, Edit, Trash2, Search, Save, X, FolderOpen, Package } from 'lucide-react'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { ProjectService, Project } from '../services/projectService'
|
||||
|
||||
const ProjectManagePage: React.FC = () => {
|
||||
|
|
@ -17,16 +17,67 @@ const ProjectManagePage: React.FC = () => {
|
|||
product_image: ''
|
||||
})
|
||||
|
||||
// 辅助函数:转换图片路径为Tauri可访问的URL
|
||||
const getImageSrc = (imagePath: string): string => {
|
||||
if (!imagePath) return ''
|
||||
if (imagePath.startsWith('http')) return imagePath
|
||||
try {
|
||||
return convertFileSrc(imagePath)
|
||||
} catch (error) {
|
||||
console.error('Failed to convert file src:', error)
|
||||
return ''
|
||||
// 图片组件:处理异步图片加载
|
||||
const ImageComponent: React.FC<{
|
||||
imagePath: string
|
||||
alt: string
|
||||
className: string
|
||||
onError?: () => void
|
||||
}> = ({ imagePath, alt, className, onError }) => {
|
||||
const [imageSrc, setImageSrc] = useState<string>('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const loadImage = async () => {
|
||||
if (!imagePath) {
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (imagePath.startsWith('http') || imagePath.startsWith('data:')) {
|
||||
setImageSrc(imagePath)
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const dataUrl = await invoke<string>('read_image_as_data_url', { filePath: imagePath })
|
||||
setImageSrc(dataUrl)
|
||||
} catch (error) {
|
||||
console.error('Failed to read image:', error)
|
||||
onError?.()
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadImage()
|
||||
}, [imagePath])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={`${className} bg-gray-200 flex items-center justify-center`}>
|
||||
<div className="text-xs text-gray-400">加载中...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!imageSrc) {
|
||||
return (
|
||||
<div className={`${className} bg-gray-200 flex items-center justify-center`}>
|
||||
<Package size={32} className="text-gray-400" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={alt}
|
||||
className={className}
|
||||
onError={onError}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -219,30 +270,11 @@ const ProjectManagePage: React.FC = () => {
|
|||
<div key={project.id} className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||
{/* 商品图片 */}
|
||||
<div className="h-32 bg-gray-100 overflow-hidden">
|
||||
{project.product_image ? (
|
||||
<>
|
||||
<img
|
||||
src={getImageSrc(project.product_image)}
|
||||
alt={project.product_name || project.name}
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement
|
||||
target.style.display = 'none'
|
||||
const fallback = target.parentElement?.querySelector('.image-fallback') as HTMLElement
|
||||
if (fallback) {
|
||||
fallback.style.display = 'flex'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="image-fallback hidden w-full h-full bg-gray-200 flex items-center justify-center">
|
||||
<Package size={32} className="text-gray-400" />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-full bg-gray-200 flex items-center justify-center">
|
||||
<Package size={32} className="text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
<ImageComponent
|
||||
imagePath={project.product_image}
|
||||
alt={project.product_name || project.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
|
|
@ -411,23 +443,12 @@ const ProjectManagePage: React.FC = () => {
|
|||
{formData.product_image && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-gray-500 mb-1">预览:</p>
|
||||
<div className="w-20 h-20 border border-gray-200 rounded-lg overflow-hidden bg-gray-50 flex items-center justify-center">
|
||||
<img
|
||||
src={getImageSrc(formData.product_image)}
|
||||
<div className="w-20 h-20 border border-gray-200 rounded-lg overflow-hidden">
|
||||
<ImageComponent
|
||||
imagePath={formData.product_image}
|
||||
alt="商品图片预览"
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement
|
||||
target.style.display = 'none'
|
||||
const fallback = target.parentElement?.querySelector('.fallback-text') as HTMLElement
|
||||
if (fallback) {
|
||||
fallback.style.display = 'block'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="fallback-text hidden text-xs text-gray-400 text-center p-2">
|
||||
无法预览
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue