iT邦幫忙

1

[Day 52] 留言板後台及前台(八) - 加入圖片上傳

在正文之前要說一下,
其實我覺得在留言板用文字編輯器不是個好主意,
反而應該放在心情隨筆的地方,
(但也不是不行, 譬如像Facebook這樣, 有點像結合了留言板跟心情隨筆功能)
不過因為之前有過失敗的經驗,
所以把最困難的放在最後面,
這兩天研究很久終於研究出來了...

今天要解決的是圖片上傳,
用之前的程式雖然可以成功完成文字編輯器的編輯,
但是缺少了圖片上傳的部分,
感覺好像少了什麼?
所以今天要來做圖片上傳的部分,
收集了各種各樣的資料,
剪剪貼貼修修補補之後,
終於完成了其中一種方式,
(有提供好幾種, 但有的沒想嘗試, 有的嘗試失敗)
我把參考資料放在最後面,
有興趣可以自己研究其他方式.
順帶一提,
我使用的是CKEditor 5,
跟以往的版本可能會有些許的不同.

首先js的部分加入一個自定義的物件

class MyUploadAdapter {
	constructor(loader) {
		// The file loader instance to use during the upload.
		this.loader = loader;
	}

	// Starts the upload process.
	upload() {
		return this.loader.file.then(
			file =>
				new Promise((resolve, reject) => {
					this._initRequest();
					this._initListeners(resolve, reject, file);
					this._sendRequest(file);
				})
		);
	}

	// Aborts the upload process.
	abort() {
		if (this.xhr) {
			this.xhr.abort();
		}
	}

	// Initializes the XMLHttpRequest object using the URL passed to the constructor.
	_initRequest() {
		const xhr = (this.xhr = new XMLHttpRequest());

		// Note that your request may look different. It is up to you and your editor
		// integration to choose the right communication channel. This example uses
		// a POST request with JSON as a data structure but your configuration
		// could be different.
        xhr.open("POST", "/image", true);
        xhr.setRequestHeader('X-CSRF-TOKEN', '<?PHP echo csrf_token() ?>');
		xhr.responseType = "json";
	}

	// Initializes XMLHttpRequest listeners.
	_initListeners(resolve, reject, file) {
		const xhr = this.xhr;
		const loader = this.loader;
		const genericErrorText = `無法上傳檔案: ${file.name}.`;

		xhr.addEventListener("error", () => reject(genericErrorText));
		xhr.addEventListener("abort", () => reject());
		xhr.addEventListener("load", () => {
            const response = xhr.response;
            
            console.log('response', response);

			// This example assumes the XHR server's "response" object will come with
			// an "error" which has its own "message" that can be passed to reject()
			// in the upload promise.
			//
			// Your integration may handle upload errors in a different way so make sure
			// it is done properly. The reject() function must be called when the upload fails.
			if (!response || response.error) {
				return reject(response && response.error ? response.error.message : genericErrorText);
			}

			// If the upload is successful, resolve the upload promise with an object containing
			// at least the "default" URL, pointing to the image on the server.
			// This URL will be used to display the image in the content. Learn more in the
			// UploadAdapter#upload documentation.
			resolve({
				default: response.url,
			});
		});

		// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
		// properties which are used e.g. to display the upload progress bar in the editor
		// user interface.
		if (xhr.upload) {
			xhr.upload.addEventListener("progress", evt => {
				if (evt.lengthComputable) {
					loader.uploadTotal = evt.total;
					loader.uploaded = evt.loaded;
				}
			});
		}
	}

	// Prepares the data and sends the request.
	_sendRequest(file) {
		// Prepare the form data.
		const data = new FormData();

        data.append("upload", file);
        
        console.log('file:', file);

		// Important note: This is the right place to implement security mechanisms
		// like authentication and CSRF protection. For instance, you can use
		// XMLHttpRequest.setRequestHeader() to set the request headers containing
		// the CSRF token generated earlier by your application.

		// Send the request.
		this.xhr.send(data);
	}
}

// ...

function MyCustomUploadAdapterPlugin(editor) {
	editor.plugins.get("FileRepository").createUploadAdapter = loader => {
		// Configure the URL to the upload script in your back-end here!
		return new MyUploadAdapter(loader);
	};
}

其中

xhr.open("POST", "/image", true);

裡面的路徑要寫後端上傳檔案的路徑

xhr.setRequestHeader('X-CSRF-TOKEN', '<?PHP echo csrf_token() ?>');

這是Laravel需要的SCRF的驗證

另外也可以自己修改錯誤訊息(但是除非是500 Server Error, 如果傳送成功, 錯誤訊息是從後端過來)

const genericErrorText = `無法上傳檔案: ${file.name}.`;

並且JavaScript要加入CKEditor的宣告

ClassicEditor
   .create(document.querySelector("#editor"), {
        extraPlugins: [MyCustomUploadAdapterPlugin],
        toolbar: ["heading", "|", "alignment:left", "alignment:center", "alignment:right", "alignment:adjust", "|", "bold", "italic", "blockQuote", "link", "|", "bulletedList", "numberedList", "imageUpload", "|", "undo", "redo"],
   })
   .then(editor => {
       myEditor = editor;
   })
   .catch(error => {
       console.error(error);
   });

然後要寫後端接收的部分,
首先是web.php的部分

Route::group(['prefix' => '/'], function(){
    //上傳圖片
    Route::any('/image', 'HomeController@imageProcess');
});

然後是圖片接收的函式
app/Http/Controllers/HomeController.php

//接收檔案上傳
public function imageProcess()
{
    header('Content-Type: application/pdf');

    Log::notice('接收圖片資料');
    //接收輸入資料
    $input = request()->all();
    $result = array();

    Log::notice('接收圖片'.print_r($input, true));

    if(isset($input['upload']))
    {
        $upload = $input['upload'];

        //檔案副檔名
        $extension = $upload->getClientOriginalExtension();
        //產生隨機檔案名稱
        $filename = uniqid().'.'.$extension;
        //相對路徑
        $relative_path = 'images/upload/'.$filename;
        //取得public目錄下的完整位置
        $fullpath = base_path('public_html/'.$relative_path);

        //允許的檔案格式
        switch($upload->getMimeType())
        {
            case 'image/jpeg':
            case 'image/png':
                break;
            default:
                $result['error'] = array(
                    'message' => '很抱歉,只接受JPG和PNG檔案',
                );
                echo json_encode($result);
                exit;
        }

        //移動檔案位置並改名稱
        move_uploaded_file($upload->getRealPath(),$relative_path);

        $result['url'] = '/'.$relative_path;
        echo json_encode($result);
    }
    else
    {
        $result['error'] = array(
            'message' => '很抱歉,上傳檔案失敗了',
        );
        echo json_encode($result);
    }
}

最後再附上成果圖
https://ithelp.ithome.com.tw/upload/images/20210502/201056941f1SD8cgKo.png

到這裡這個系列的文章差不多結束了,
雖然還有些東西想寫,
不過就等年底再說了.

參考資料:
Simple upload adapter(官方文件)
[筆記]CKEditor加上CKFinder上傳圖檔更方便
如何套用 CKEditor5 上傳圖片
CKEditor 5圖片的上傳方式
CKEditor 5 教學(三),上傳圖片至 Amazon S3


尚未有邦友留言

立即登入留言