本文目录导读:

我来介绍PHP项目中内容编辑器的开发方案,从简单到复杂,包含多种选择:
轻量级方案:纯文本编辑器
基础文本编辑器
// config/editor.php
return [
'basic' => [
'type' => 'textarea',
'rows' => 20,
'cols' => 80
]
];
// resources/views/editor/basic.blade.php
<textarea name="content" id="basic-editor" rows="20">
{{ old('content', $post->content ?? '') }}
</textarea>
Markdown编辑器
// 使用 Parsedown 库
composer require erusev/parsedown
// MarkdownEditor.php
class MarkdownEditor {
protected $parsedown;
public function __construct() {
$this->parsedown = new Parsedown();
}
public function render($content) {
return $this->parsedown->text($content);
}
public function toHtml($content) {
return $this->parsedown->text($content);
}
}
// 前端Markdown编辑器
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
<textarea id="markdown-editor" name="content"></textarea>
<script>
new SimpleMDE({
element: document.getElementById("markdown-editor"),
spellChecker: false,
toolbar: ["bold", "italic", "heading", "|",
"quote", "code", "|",
"unordered-list", "ordered-list", "|",
"link", "image", "|", "preview"]
});
</script>
中等方案:富文本编辑器
TinyMCE编辑器
// resources/views/editor/tinymce.blade.php
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.tiny.cloud/1/your-api-key/tinymce/6/tinymce.min.js"></script>
</head>
<body>
<textarea id="tinymce-editor" name="content">
{{ $post->content ?? '' }}
</textarea>
<script>
tinymce.init({
selector: '#tinymce-editor',
height: 500,
plugins: [
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap',
'preview', 'anchor', 'searchreplace', 'visualblocks', 'code',
'fullscreen', 'insertdatetime', 'media', 'table', 'help',
'wordcount'
],
toolbar: 'undo redo | blocks | ' +
'bold italic backcolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | help | image media link',
images_upload_handler: function(blobInfo, progress) {
return new Promise((resolve, reject) => {
let formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
fetch('/upload/image', {
method: 'POST',
body: formData,
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
if (data.location) {
resolve(data.location);
} else {
reject('Upload failed');
}
})
.catch(error => {
reject('Upload failed: ' + error);
});
});
}
});
</script>
</body>
</html>
Quill编辑器(推荐)
// resources/views/editor/quill.blade.php
<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
<div id="editor-container">
{!! $post->content ?? '' !!}
</div>
<textarea name="content" id="content-hidden" style="display:none;"></textarea>
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
<script>
const quill = new Quill('#editor-container', {
theme: 'snow',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['blockquote', 'code-block'],
[{ 'align': [] }],
['link', 'image', 'video'],
['clean']
]
},
placeholder: '请输入内容...'
});
// 提交表单时同步内容
document.querySelector('form').onsubmit = function() {
document.querySelector('#content-hidden').value = quill.root.innerHTML;
};
</script>
完整方案:定制编辑器
编辑器核心框架
// app/Editor/EditorService.php
namespace App\Editor;
class EditorService {
protected $plugins = [];
protected $config = [];
public function registerPlugin($name, $plugin) {
$this->plugins[$name] = $plugin;
}
public function render($content, $options = []) {
$config = array_merge($this->config, $options);
return view('editor::main', [
'content' => $content,
'plugins' => $this->plugins,
'config' => $config
])->render();
}
public function processContent($content) {
foreach ($this->plugins as $plugin) {
$content = $plugin->process($content);
}
return $content;
}
}
// app/Editor/Plugins/ImagePlugin.php
namespace App\Editor\Plugins;
class ImagePlugin {
public function process($content) {
// 处理图片路径、压缩等
return $content;
}
public function upload($file) {
// 实现图片上传逻辑
$path = $file->store('images/content', 'public');
return asset('storage/' . $path);
}
}
后端处理服务
// app/Services/ContentService.php
namespace App\Services;
use App\Models\Content;
use App\Editor\EditorService;
use Illuminate\Support\Facades\Storage;
class ContentService {
protected $editorService;
public function __construct(EditorService $editorService) {
$this->editorService = $editorService;
}
public function save(array $data) {
// 处理内容
$content = $this->processContent($data['content']);
// 提取并处理图片
$images = $this->extractImages($content);
$content = $this->processImages($content, $images);
// 保存内容
$contentModel = new Content();
$contentModel->title = $data['title'];
$contentModel->content = $content;
$contentModel->summary = $data['summary'] ?? '';
$contentModel->status = $data['status'] ?? 'draft';
$contentModel->save();
return $contentModel;
}
protected function processContent($content) {
// 安全过滤
$content = strip_tags($content, '<p><br><strong><em><ul><ol><li><a><img><h1><h2><h3><h4><h5><h6><blockquote><pre><code><table><tr><td><th><figure><figcaption><div><span><hr>');
// XSS过滤
$content = htmlspecialchars_decode($content);
return $content;
}
protected function extractImages($content) {
preg_match_all('/<img[^>]+src="([^"]+)"/', $content, $matches);
return $matches[1] ?? [];
}
protected function processImages($content, $images) {
foreach ($images as $image) {
if (strpos($image, 'base64') !== false) {
// 处理base64图片
$newPath = $this->saveBase64Image($image);
$content = str_replace($image, $newPath, $content);
}
}
return $content;
}
}
控制器实现
// app/Http/Controllers/ContentController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\ContentService;
use App\Editor\EditorService;
class ContentController extends Controller {
protected $contentService;
protected $editorService;
public function __construct(
ContentService $contentService,
EditorService $editorService
) {
$this->contentService = $contentService;
$this->editorService = $editorService;
}
public function create() {
$editor = $this->editorService->render('', [
'editor_id' => 'content-editor',
'height' => '600px'
]);
return view('content.create', ['editor' => $editor]);
}
public function store(Request $request) {
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'summary' => 'nullable|max:500',
'status' => 'in:draft,published'
]);
$content = $this->contentService->save($validated);
return redirect()->route('content.show', $content->id)
->with('success', '内容发布成功');
}
public function edit($id) {
$content = Content::findOrFail($id);
$editor = $this->editorService->render($content->content, [
'editor_id' => 'content-editor',
'height' => '600px'
]);
return view('content.edit', [
'content' => $content,
'editor' => $editor
]);
}
public function upload(Request $request) {
$request->validate([
'file' => 'required|image|max:2048'
]);
$path = $request->file('file')->store('content-images', 'public');
return response()->json([
'location' => asset('storage/' . $path)
]);
}
}
前端完善
// resources/js/editor/custom-editor.js
class CustomEditor {
constructor(options) {
this.element = options.element;
this.content = options.content || '';
this.plugins = options.plugins || [];
this.init();
}
init() {
this.createToolbar();
this.createContentArea();
this.bindEvents();
this.loadContent();
}
createToolbar() {
this.toolbar = document.createElement('div');
this.toolbar.className = 'editor-toolbar';
this.toolbar.innerHTML = `
<button data-command="bold" title="粗体"><b>B</b></button>
<button data-command="italic" title="斜体"><i>I</i></button>
<button data-command="underline" title="下划线"><u>U</u></button>
<span class="separator"></span>
<button data-command="insertUnorderedList" title="无序列表">• List</button>
<button data-command="insertOrderedList" title="有序列表">1. List</button>
<span class="separator"></span>
<button data-command="createLink" title="链接">Link</button>
<button data-command="insertImage" title="图片">Image</button>
<button data-command="formatBlock" data-value="h2" title="标题">H</button>
`;
this.element.prepend(this.toolbar);
}
createContentArea() {
this.contentArea = document.createElement('div');
this.contentArea.className = 'editor-content';
this.contentArea.contentEditable = true;
this.element.appendChild(this.contentArea);
}
bindEvents() {
// 工具栏按钮事件
this.toolbar.addEventListener('click', (e) => {
const button = e.target.closest('button');
if (button) {
this.handleCommand(button.dataset.command, button.dataset.value);
}
});
// 内容变化事件
this.contentArea.addEventListener('input', () => {
if (this.onChange) {
this.onChange(this.getContent());
}
});
}
handleCommand(command, value = null) {
document.execCommand(command, false, value);
this.contentArea.focus();
}
getContent() {
return this.contentArea.innerHTML;
}
setContent(content) {
this.contentArea.innerHTML = content;
}
loadContent() {
if (this.content) {
this.setContent(this.content);
}
}
}
// 使用示例
const editor = new CustomEditor({
element: document.getElementById('editor-wrapper'),
content: '初始内容...',
plugins: ['image', 'link', 'table']
});
安全与优化
XSS防护
// app/Helpers/SecurityHelper.php
class SecurityHelper {
public static function sanitizeContent($content) {
// HTML Purifier
$config = \HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed',
'p,br,strong,em,ul,ol,li,a[href|target],img[src|alt],' .
'h1,h2,h3,h4,h5,h6,blockquote,pre,code,table,tr,td,th,' .
'figure,figcaption,div,span,hr'
);
$config->set('HTML.AllowedAttributes',
'href,target,src,alt,class,id,style,data-*'
);
$purifier = new \HTMLPurifier($config);
return $purifier->purify($content);
}
}
图片优化
// app/Services/ImageOptimizer.php
namespace App\Services;
use Intervention\Image\ImageManager;
class ImageOptimizer {
protected $manager;
public function __construct() {
$this->manager = new ImageManager(['driver' => 'gd']);
}
public function optimize($path, $maxWidth = 1200) {
$image = $this->manager->make($path);
// 调整大小
if ($image->width() > $maxWidth) {
$image->resize($maxWidth, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
// 压缩质量
$image->save($path, 80);
return $path;
}
}
推荐方案总结
根据项目需求选择:
- 小型项目/个人博客:使用SimpleMDE(Markdown)或TinyMCE
- 企业级CMS:使用Quill或CKEditor 5
- 需要高度定制:基于Quill开发自定义编辑器
- 需要协作编辑:使用Slate.js或ProseMirror
推荐使用Quill + Laravel组合,文档完善、扩展性强、性能好。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播