在政务信息化建设过程中,内容管理系统(CMS)需要处理大量来自不同来源的文档内容。传统的手动复制粘贴方式存在以下痛点:
针对某省级政务平台的实际需求,我们确定了以下核心功能目标:
我们对主流开源和商业编辑器进行了详细评估:
| 编辑器类型 | 代表产品 | 文档处理能力 | 信创兼容性 | 二次开发成本 |
|---|---|---|---|---|
| 开源编辑器 | TinyMCE | 需插件支持 | 一般 | 高 |
| 商业编辑器 | KindEditor | 原生支持 | 良好 | 中 |
| 国产编辑器 | UEditor | 部分支持 | 优秀 | 中 |
最终选择KindEditor企业版作为基础方案,主要基于以下考虑:
整体采用前后端分离架构:
code复制前端:Vue2 + KindEditor组件化封装
后端:PHP Laravel框架提供API服务
存储:阿里云OSS对象存储(预留华为云OBS接口)
关键技术决策点:
图片处理策略:
文档解析方案:
信创适配层:
javascript复制// 初始化编辑器配置
tinymce.init({
selector: '#editor',
plugins: 'paste',
paste_preprocess: function(plugin, args) {
// 检测Word内容特征
if (args.content.includes('urn:schemas-microsoft-com')) {
return processWordContent(args.content);
}
return args.content;
}
});
// Word内容处理函数
function processWordContent(html) {
// 1. 清理MS Office特有标签
html = html.replace(/<!\-\-\[if gte mso \d+\]>.+?<!\[endif\]\-\->/g, '');
// 2. 转换VML图形为标准HTML
html = convertVMLToHTML(html);
// 3. 提取Base64图片并上传
const images = extractBase64Images(html);
for (const img of images) {
const ossUrl = await uploadToOSS(img.data);
html = html.replace(img.src, ossUrl);
}
return html;
}
php复制class WordProcessor {
public function process($html) {
// 使用DOMDocument解析
$dom = new DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
// 处理图片节点
$images = $dom->getElementsByTagName('img');
foreach ($images as $img) {
$src = $img->getAttribute('src');
if (strpos($src, 'base64') !== false) {
$newUrl = $this->uploadBase64Image($src);
$img->setAttribute('src', $newUrl);
}
}
// 清理冗余样式
$cleaner = new HTMLPurifier();
return $cleaner->purify($dom->saveHTML());
}
private function uploadBase64Image($data) {
$data = base64_decode(preg_replace('/^data:image\/\w+;base64,/', '', $data));
$filename = 'uploads/'.uniqid().'.png';
$ossClient = new OssClient(/* 配置 */);
$ossClient->putObject('bucket', $filename, $data);
return $ossClient->signUrl('bucket', $filename, 3600);
}
}
javascript复制// 文件上传处理
document.getElementById('file-input').addEventListener('change', async (e) => {
const file = e.target.files[0];
const formData = new FormData();
formData.append('file', file);
// 显示上传进度
const progressBar = document.getElementById('progress');
const config = {
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progressBar.style.width = `${percent}%`;
}
};
try {
const res = await axios.post('/api/document/import', formData, config);
editor.setContent(res.data.html);
} catch (err) {
console.error('导入失败:', err);
}
});
php复制class DocumentImporter {
public function handle($file) {
$extension = strtolower($file->getClientOriginalExtension());
switch ($extension) {
case 'docx':
return $this->importWord($file);
case 'pdf':
return $this->importPDF($file);
case 'xlsx':
return $this->importExcel($file);
default:
throw new Exception('不支持的文档格式');
}
}
private function importWord($file) {
$phpWord = IOFactory::load($file->getPathname());
$htmlWriter = new HTMLWriter($phpWord);
// 处理嵌入图片
$zip = new ZipArchive;
if ($zip->open($file->getPathname()) === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$filename = $zip->getNameIndex($i);
if (preg_match('/word\/media\/(.*)/', $filename)) {
$imageData = $zip->getFromIndex($i);
$this->uploadImage($imageData);
}
}
$zip->close();
}
return $htmlWriter->getContent();
}
}
特殊处理要点:
javascript复制async function processWechatContent(html) {
// 1. 解析DOM
const doc = new DOMParser().parseFromString(html, 'text/html');
// 2. 处理图片
const images = doc.querySelectorAll('img[src*="mmbiz"]');
for (const img of images) {
try {
// 添加Referer绕过防盗链
const blob = await fetch(img.src, {
headers: { Referer: 'https://mp.weixin.qq.com/' }
}).then(r => r.blob());
// 上传到OSS
const ossUrl = await uploadToOSS(blob);
img.src = ossUrl;
} catch (e) {
console.error('图片处理失败:', e);
img.remove();
}
}
// 3. 清理微信特有样式
const cleanHtml = doc.body.innerHTML
.replace(/data-src=/g, 'src=')
.replace(/style="[^"]*"/g, '');
return cleanHtml;
}
javascript复制// 环境检测
const env = {
isKirin: navigator.userAgent.includes('Kirin'),
isUOS: navigator.userAgent.includes('UOS'),
isIE: !!window.ActiveXObject
};
// 应用兼容性补丁
function applyPolyfills() {
// 1. Promise polyfill
if (typeof Promise === 'undefined') {
require('es6-promise').polyfill();
}
// 2. 字体回退
if (env.isKirin) {
document.documentElement.style.fontFamily =
'"Microsoft YaHei", "SimSun", sans-serif';
}
// 3. 禁用CSS动画
if (env.isUOS || env.isKirin) {
const style = document.createElement('style');
style.textContent = '* { animation: none !important; }';
document.head.appendChild(style);
}
}
| 优化项 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| Word解析速度 | 3200ms | 1400ms | 56% |
| 图片上传成功率 | 89% | 99.2% | +10.2% |
| 内存占用 | 320MB | 185MB | 42% |
关键优化手段:
nginx复制# Nginx优化配置示例
server {
client_max_body_size 50M; # 允许大文件上传
keepalive_timeout 300; # 长连接保持
location /upload {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend;
proxy_read_timeout 300s; # 上传超时设置
}
# 静态资源缓存
location ~* \.(js|css|png)$ {
expires 365d;
add_header Cache-Control "public";
}
}
文件上传校验:
访问控制:
php复制// OSS权限策略示例
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["oss:PutObject"],
"Resource": ["acs:oss:*:*:bucket-name/upload/*"],
"Condition": {
"IpAddress": {"acs:SourceIp": ["192.168.1.0/24"]}
}
}
]
}
javascript复制// 前端预览组件
class DocumentPreview {
constructor(file) {
this.file = file;
this.previewTypes = {
'pdf': this.previewPDF,
'docx': this.previewOffice
};
}
async render() {
const handler = this.previewTypes[this.file.type] || this.previewFallback;
return handler();
}
async previewPDF() {
// 使用PDF.js渲染
const pdf = await pdfjsLib.getDocument(URL.createObjectURL(this.file));
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.0 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
return canvas;
}
}
触控优化:
响应式布局:
css复制/* 编辑器容器适配 */
.editor-container {
width: 100%;
max-width: 100%;
padding: 10px;
}
@media (max-width: 768px) {
.toolbar-button {
padding: 12px 8px;
margin: 2px;
}
.dialog-window {
width: 90vw !important;
}
}
在实际项目中,我们通过上述技术方案成功在30个地市级政务平台部署了该编辑器模块,日均处理文档超过1.2万份。其中两个关键经验值得分享: