OSS功能提交
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useRef } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ChevronLeft, Plus, X, Image as ImageIcon, UploadCloud, Link, Video, FileText, Layers, CalendarDays, ChevronDown } from "lucide-react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
@@ -44,21 +44,64 @@ export default function NewMaterialPage({ params }: { params: { id: string } })
|
||||
const [publishTime, setPublishTime] = useState("")
|
||||
const [comment, setComment] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
// 图片上传
|
||||
const handleUploadImage = () => {
|
||||
const mockImageUrls = [
|
||||
"https://picsum.photos/id/237/200/300",
|
||||
"https://picsum.photos/id/238/200/300",
|
||||
"https://picsum.photos/id/239/200/300"
|
||||
]
|
||||
const randomIndex = Math.floor(Math.random() * mockImageUrls.length)
|
||||
const newImage = mockImageUrls[randomIndex]
|
||||
if (!images.includes(newImage)) {
|
||||
setImages([...images, newImage])
|
||||
setPreviewUrls([...previewUrls, newImage])
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
|
||||
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
showToast("请选择图片文件", "error")
|
||||
return
|
||||
}
|
||||
|
||||
const loadingToast = showToast("正在上传图片...", "loading", true)
|
||||
setLoading(true)
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append("file", file)
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: HeadersInit = {
|
||||
// 浏览器会自动为 FormData 设置 Content-Type 为 multipart/form-data,无需手动设置
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/attachment/upload`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result: ApiResponse = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data?.url) {
|
||||
setImages((prev) => [...prev, result.data.url]);
|
||||
setPreviewUrls((prev) => [...prev, result.data.url]);
|
||||
showToast("图片上传成功", "success");
|
||||
} else {
|
||||
showToast(result.msg || "图片上传失败", "error");
|
||||
}
|
||||
} catch (error: any) {
|
||||
showToast(error?.message || "图片上传失败", "error")
|
||||
} finally {
|
||||
loadingToast.remove && loadingToast.remove()
|
||||
setLoading(false)
|
||||
// 清空文件输入框,以便再次上传同一文件
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveImage = (indexToRemove: number) => {
|
||||
setImages(images.filter((_, index) => index !== indexToRemove))
|
||||
setPreviewUrls(previewUrls.filter((_, index) => index !== indexToRemove))
|
||||
@@ -309,11 +352,19 @@ export default function NewMaterialPage({ params }: { params: { id: string } })
|
||||
variant="outline"
|
||||
onClick={handleUploadImage}
|
||||
className="w-full py-8 flex flex-col items-center justify-center rounded-2xl border-2 border-dashed border-blue-300 bg-white hover:bg-blue-50"
|
||||
disabled={loading}
|
||||
>
|
||||
<UploadCloud className="h-8 w-8 mb-2 text-gray-400" />
|
||||
<span>点击上传图片</span>
|
||||
<span className="text-xs text-gray-500 mt-1">支持 JPG、PNG 格式</span>
|
||||
</Button>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
accept="image/*"
|
||||
/>
|
||||
</div>
|
||||
{previewUrls.length > 0 && (
|
||||
<div className="mt-2">
|
||||
@@ -369,7 +420,13 @@ export default function NewMaterialPage({ params }: { params: { id: string } })
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleUploadImage}
|
||||
onClick={() => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
input.onchange = handleUploadImage;
|
||||
input.click();
|
||||
}}
|
||||
className="w-full py-4 flex flex-col items-center justify-center rounded-2xl border-2 border-dashed border-blue-300 bg-white hover:bg-blue-50"
|
||||
>
|
||||
<UploadCloud className="h-6 w-6 mb-2 text-gray-400" />
|
||||
|
||||
@@ -28,8 +28,8 @@ class Attachment extends Controller
|
||||
$validate = \think\facade\Validate::rule([
|
||||
'file' => [
|
||||
'fileSize' => 10485760, // 10MB
|
||||
'fileExt' => 'jpg,jpeg,png,gif,doc,docx,pdf,zip,rar',
|
||||
'fileMime' => 'image/jpeg,image/png,image/gif,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf,application/zip,application/x-rar-compressed'
|
||||
'fileExt' => 'jpg,jpeg,png,gif,doc,docx,pdf,zip,rar,mp4,mp3',
|
||||
'fileMime' => 'image/jpeg,image/png,image/gif,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf,application/zip,application/x-rar-compressed,video/mp4,audio/mp3'
|
||||
]
|
||||
]);
|
||||
|
||||
@@ -57,12 +57,15 @@ class Attachment extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 生成OSS对象名称
|
||||
$objectName = AliyunOSS::generateObjectName($file->getOriginalName());
|
||||
$objectName = AliyunOSS::generateObjectName($file->getInfo('name'));
|
||||
|
||||
// 上传到OSS
|
||||
$result = AliyunOSS::uploadFile($file->getRealPath(), $objectName);
|
||||
|
||||
|
||||
if (!$result['success']) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
@@ -72,17 +75,17 @@ class Attachment extends Controller
|
||||
|
||||
// 保存到数据库
|
||||
$attachmentData = [
|
||||
'name' => Request::param('name') ?: $file->getOriginalName(),
|
||||
'name' => Request::param('name') ?: $file->getInfo('name'),
|
||||
'hash_key' => $hashKey,
|
||||
'server' => 'aliyun_oss',
|
||||
'source' => $result['url'],
|
||||
'size' => $result['size'],
|
||||
'suffix' => pathinfo($file->getOriginalName(), PATHINFO_EXTENSION)
|
||||
'suffix' => pathinfo($file->getInfo('name'), PATHINFO_EXTENSION)
|
||||
];
|
||||
|
||||
$attachmentId = AttachmentModel::addAttachment($attachmentData);
|
||||
$attachment = AttachmentModel::addAttachment($attachmentData);
|
||||
|
||||
if (!$attachmentId) {
|
||||
if (!$attachment) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '保存附件信息失败'
|
||||
@@ -93,7 +96,7 @@ class Attachment extends Controller
|
||||
'code' => 200,
|
||||
'msg' => '上传成功',
|
||||
'data' => [
|
||||
'id' => $attachmentId,
|
||||
'id' => $attachment->id,
|
||||
'name' => $attachmentData['name'],
|
||||
'url' => $attachmentData['source']
|
||||
]
|
||||
|
||||
@@ -14,4 +14,10 @@ class Attachment extends Model
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
|
||||
public static function addAttachment($attachmentData)
|
||||
{
|
||||
return self::create($attachmentData);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ class AliyunOSS
|
||||
const ACCESS_KEY_SECRET = '0WUo8r6BT4I8ZVUQxflmD8rLHrFNHO';
|
||||
const ENDPOINT = 'oss-cn-shenzhen.aliyuncs.com';
|
||||
const BUCKET = 'karuosiyujzk';
|
||||
const ossUrl = 'https://res.quwanzhi.com';
|
||||
|
||||
/**
|
||||
* 获取OSS客户端实例
|
||||
|
||||
Reference in New Issue
Block a user