Plupload 是強大的前端上傳組件,支援 HTML5/Flash/Silverlight 多運行時、分塊上傳、客戶端縮圖。本文展示 Yii2 完整整合方案:Widget 封裝、Asset 管理、JS 交互、後端模型驗證與縮圖處理。
common\widgets\Plupload - 渲染上傳區域、註冊 JS 初始化PluploadAsset(核心庫)、UploadAsset(業務 JS)FileController::actionUpload() - 接收上傳、回傳 JSONcommon\models\ImageUpload - 驗證、儲存、縮圖核心邏輯// views/article/_form.php
use common\widgets\Plupload;
echo Plupload::widget([
'model' => $model,
'attribute' => 'cover_img',
'url' => '/file/upload', // 上傳處理端點
]);
namespace common\widgets;
use backend\assets\UploadAsset;
use yii\base\Exception;
use yii\base\Widget;
use yii\helpers\Html;
class Plupload extends Widget {
public $model;
public $attribute;
public $url;
public function init() {
parent::init();
if (!$this->url) throw new Exception('url 參數必填');
if (!$this->model) throw new Exception('model 屬性必填');
if (!$this->attribute) throw new Exception('attribute 屬性必填');
}
public function run() {
$model = $this->model;
$attr = $this->attribute;
$value = $model->$attr ?: '/images/noimage.gif';
$html = '';
$html .= Html::activeLabel($model, $attr);
$html .= Html::activeHiddenInput($model, $attr, ['id' => 'hidden_input', 'value' => $value]);
$html .= '
';
$html .= '';
UploadAsset::register($this->view);
$csrf = Yii::$app->request->csrfToken;
$this->view->registerJs(
"initCoverImageUploader('pickfiles','container','2mb','{$this->url}','{$csrf}');"
);
return $html;
}
}
// PluploadAsset - 核心庫
namespace backend\assets;
use yii\web\AssetBundle;
class PluploadAsset extends AssetBundle {
public $sourcePath = '@vendor/moxiecode/plupload';
public $js = ['js/plupload.full.min.js'];
public $depends = ['yii\web\JqueryAsset'];
}
// UploadAsset - 業務邏輯 JS
class UploadAsset extends AssetBundle {
public $basePath = '@webroot';
public $baseUrl = '@web';
public $js = ['js/upload.js'];
public $depends = ['backend\assets\PluploadAsset'];
}
function initCoverImageUploader(btnId, containerId, maxSize, url, csrfToken) {
var uploader = new plupload.Uploader({
runtimes: 'html5,flash,silverlight,html4',
browse_button: btnId,
container: containerId,
url: url,
flash_swf_url: '@vendor/moxiecode/plupload/js/Moxie.swf',
silverlight_xap_url: '@vendor/moxiecode/plupload/js/Moxie.xap',
filters: {
max_file_size: maxSize,
mime_types: [
{title: "Image files", extensions: "jpg,gif,png"},
{title: "Zip files", extensions: "zip"}
]
},
multipart_params: { '_csrf': csrfToken },
init: {
FilesAdded: function(up, files) { up.start(); },
UploadProgress: function(up, file) {
$('#' + containerId + ' p').text('已上傳: ' + file.percent + '%');
},
FileUploaded: function(up, file, result) {
var res = JSON.parse(result.response);
if (res.code == 0) {
$('#' + btnId).html('
');
$('#hidden_input').val(res.path);
}
},
Error: function(up, err) {
console.error("Plupload Error #" + err.code + ": " + err.message);
}
}
});
uploader.init();
}
namespace backend\controllers;
use yii\web\Controller;
use yii\web\Response;
use common\models\ImageUpload;
class FileController extends Controller {
public function actionUpload() {
Yii::$app->response->format = Response::FORMAT_JSON;
$model = new ImageUpload();
$model->fileInputName = 'file'; // 對應 Plupload 預設檔案欄位名
if ($model->save()) {
return ['code' => 0, 'message' => $model->getMessage(), 'path' => $model->getUrlPath()];
}
return ['code' => 1, 'message' => $model->getMessage()];
}
}
namespace common\models;
use yii\base\Exception;
use yii\helpers\FileHelper;
use yii\web\UploadedFile;
use yii\base\Object;
class ImageUpload extends Object {
public $fileInputName = 'file';
public $savePath = '@backend/web/uploads/images';
public $allowExt = ['jpg','png','jpeg','gif','bmp'];
public $maxFileSize = 2097152; // 2MB
public $allowType = ['image/jpeg','image/png','image/gif','image/bmp','image/pjpeg','image/x-png'];
private $_uploadFile;
private $filePath;
private $urlPath;
private $res = false;
private $message = '';
public function init() {
if (!$this->fileInputName) throw new Exception('fileInputName 必填');
$this->savePath = Yii::getAlias(rtrim($this->savePath, '/'));
if (!file_exists($this->savePath)) {
FileHelper::createDirectory($this->savePath);
}
$this->_uploadFile = UploadedFile::getInstanceByName($this->fileInputName);
if (!$this->_uploadFile) { $this->message = '未找到上傳檔案'; return; }
if ($this->_uploadFile->error) { $this->message = '上傳失敗'; return; }
if (!in_array($this->extension, $this->allowExt) || !in_array($this->type, $this->allowType)) {
$this->message = '檔案類型不允許'; return;
}
if ($this->_uploadFile->size > $this->maxFileSize) { $this->message = '檔案過大'; return; }
$dateDir = date('Y-m');
$absDir = $this->savePath . '/' . $dateDir;
if (!file_exists($absDir)) FileHelper::createDirectory($absDir);
$rand = substr(Yii::$app->security->generateRandomString(), -4, 4);
$this->filePath = "$absDir/{$this->baseName}--$rand.$this->extension";
$this->urlPath = "/uploads/images/$dateDir/{$this->baseName}--$rand.$this->extension";
}
public function save() {
if ($this->_uploadFile->saveAs($this->filePath)) {
self::CreateThumbnail($this->filePath); // 縮圖
$this->res = true;
$this->message = $this->_uploadFile->baseName . '.' . $this->_uploadFile->extension . ' 上傳成功';
} else {
$this->message = '上傳失敗';
}
return $this->res;
}
// --- 縮圖核心:等比縮放、支援 jpg/png/gif、統一輸出 jpg ---
public static function CreateThumbnail($srcFile, $toFile = "", $toW = 100, $toH = 100) {
if ($toFile == "") $toFile = $srcFile;
$data = getimagesize($srcFile);
if (!$data) return false;
switch ($data[2]) {
case 1: $im = imagecreatefromgif($srcFile); break;
case 2: $im = imagecreatefromjpeg($srcFile); break;
case 3: $im = imagecreatefrompng($srcFile); break;
default: return false;
}
$srcW = imagesx($im); $srcH = imagesy($im);
$toWH = $toW / $toH; $srcWH = $srcW / $srcH;
if ($toWH <= $srcWH) {
$ftoW = $toW; $ftoH = (int)($ftoW * ($srcH / $srcW));
} else {
$ftoH = $toH; $ftoW = (int)($ftoH * ($srcW / $srcH));
}
$ni = function_exists("imagecreatetruecolor")
? imagecreatetruecolor($ftoW, $ftoH)
: imagecreate($ftoW, $ftoH);
if (function_exists("imagecopyresampled")) {
imagecopyresampled($ni, $im, 0, 0, 0, 0, $ftoW, $ftoH, $srcW, $srcH);
} else {
imagecopyresized($ni, $im, 0, 0, 0, 0, $ftoW, $ftoH, $srcW, $srcH);
}
switch ($data[2]) {
case 1: imagegif($ni, $toFile); break;
case 2: imagejpeg($ni, $toFile, 90); break;
case 3: imagepng($ni, $toFile); break;
}
imagedestroy($ni); imagedestroy($im);
return $toFile;
}
// Getter 方法...
public function getMessage() { return $this->message; }
public function getUrlPath() { return $this->urlPath; }
public function getBaseName() { return $this->_uploadFile ? $this->_uploadFile->baseName : null; }
public function getExtension() { return $this->_uploadFile ? $this->_uploadFile->extension : null; }
public function getType() { return $this->_uploadFile ? $this->_uploadFile->type : null; }
}
imagecopyresampled 高品質重採樣、自動計算等比尺寸、統一品質 90%。chunk_size 參數)