织梦默认的上传功能比较传统,使用的是表单提交和iframe回传的方式,体验不佳,而HTML5上传(通常结合AJAX)可以实现无刷新、带进度条、更好的错误处理等现代化用户体验。
实现这个功能主要有两种思路:
- 完全自定义开发(推荐):完全绕过织梦默认的上传逻辑,自己编写前端HTML5代码和后端PHP处理脚本,然后将文件信息插入到织梦的数据库中,这种方式最灵活,但需要较多的代码量。
- 修改织梦核心文件(不推荐):修改织梦后台的
media_add.php等文件,使其支持AJAX请求,这种方式风险高,升级织梦时容易失效,且可能破坏原有功能。
下面,我将重点讲解第一种思路,因为它更稳定、更符合现代开发规范,并且能让你更好地理解整个流程。
方案:完全自定义HTML5上传(以文章附件上传为例)
我们将创建一个独立的页面,用于上传文件,并将上传成功后的文件信息(路径、名称等)返回给主页面,再由主页面将其添加到文章的附件列表中。
第一步:准备数据库(织梦已做好)
织梦的附件信息存储在#@__uploads(或dede_uploads)表中,这个表包含了所有上传文件的路径、名称、大小、类型等信息,我们只需要将新上传的文件信息插入到这个表中即可。
第二步:创建前端上传页面 (upload.html)
这个页面包含一个拖拽区域、一个文件选择按钮和一个隐藏的iframe(用于处理织梦的formhash和可能的回传)。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">HTML5 文件上传</title>
<style>
body { font-family: 'Microsoft YaHei', sans-serif; background-color: #f4f4f4; }
.upload-container { max-width: 600px; margin: 50px auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.upload-area {
border: 2px dashed #ccc;
border-radius: 5px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s;
}
.upload-area:hover, .upload-area.drag-over {
border-color: #007bff;
background-color: #f8f9fa;
}
.upload-area p { margin: 10px 0; color: #666; }
#fileInput { display: none; }
.progress-bar {
height: 10px;
background-color: #e0e0e0;
border-radius: 5px;
margin-top: 15px;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #007bff;
width: 0%;
transition: width 0.3s;
}
#fileList { margin-top: 20px; }
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid #eee;
margin-bottom: 10px;
border-radius: 4px;
}
.file-item .name { flex-grow: 1; }
.file-item .size { color: #999; margin-right: 10px; }
.file-item .status { color: #28a745; font-weight: bold; }
</style>
</head>
<body>
<div class="upload-container">
<h2>上传附件</h2>
<div class="upload-area" id="dropZone">
<p>拖拽文件到此处,或</p>
<button class="btn btn-primary">点击选择文件</button>
<input type="file" id="fileInput" multiple>
</div>
<div class="progress-bar" id="progressBar" style="display: none;">
<div class="progress" id="progress"></div>
</div>
<div id="fileList"></div>
</div>
<!-- 用于织梦表单验证的隐藏表单 -->
<form id="dedeForm" action="/plus/file.php" method="post" style="display: none;">
<input type="hidden" name="dopost" value="upload">
<input type="hidden" name="arcid" value="0"> <!-- 文章ID,上传时为0,后续由JS处理 -->
<input type="hidden" name="formhash" value="{dede:global.formhash /}"> <!-- 获取织梦的formhash -->
<input type="hidden" name="isadmin" value="1">
<input type="hidden" name="userid" value="{dede:global.userid /}">
<input type="hidden" name="username" value="{dede:global.username /}">
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const progressBar = document.getElementById('progressBar');
const progress = document.getElementById('progress');
const dedeForm = document.getElementById('dedeForm');
// 点击上传区域触发文件选择
dropZone.addEventListener('click', () => fileInput.click());
// 监听文件选择
fileInput.addEventListener('change', (e) => {
handleFiles(e.target.files);
});
// 拖拽事件
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.add('drag-over');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.remove('drag-over');
}, false);
});
dropZone.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
});
// 处理文件上传
function handleFiles(files) {
if (files.length === 0) return;
// 获取织梦的formhash,如果页面中没有,需要通过AJAX获取
// 这里假设我们在一个织梦模板页面中,可以直接使用{dede:global.formhash /}
const formhash = document.querySelector('input[name="formhash"]').value;
// 遍历所有文件并上传
Array.from(files).forEach(file => {
uploadFile(file, formhash);
});
}
function uploadFile(file, formhash) {
const formData = new FormData();
formData.append('Filedata', file); // 注意:Filedata是织梦PHP脚本接收的默认字段名
formData.append('dopost', 'upload');
formData.append('formhash', formhash);
formData.append('isadmin', '1');
formData.append('userid', document.querySelector('input[name="userid"]').value);
formData.append('username', document.querySelector('input[name="username"]').value);
formData.append('arcid', '0'); // 初始为0,成功后由JS更新
const item = document.createElement('div');
item.className = 'file-item';
item.innerHTML = `
<span class="name">${file.name}</span>
<span class="size">${(file.size / 1024).toFixed(2)} KB</span>
<span class="status">上传中...</span>
`;
fileList.appendChild(item);
progressBar.style.display = 'block';
axios.post('/plus/file.php', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: progressEvent => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
progress.style.width = percentCompleted + '%';
}
})
.then(response => {
// 织梦上传成功后,PHP会返回JSON格式的数据
// 格式如: {"error":0,"message":"success","url":"uploads/xxx.jpg"}
const result = response.data;
if (result.error === 0) {
item.querySelector('.status').textContent = '上传成功';
item.querySelector('.status').style.color = '#28a745';
// 这里可以触发一个自定义事件,将文件信息传递给父页面
window.parent.postMessage({
type: 'fileUploaded',
data: result
}, '*'); // 注意:在生产环境中,不要使用 '*',指定具体域名
} else {
item.querySelector('.status').textContent = `上传失败: ${result.message}`;
item.querySelector('.status').style.color = '#dc3545';
}
})
.catch(error => {
item.querySelector('.status').textContent = '上传失败: 网络错误';
item.querySelector('.status').style.color = '#dc3545';
console.error('Upload error:', error);
})
.finally(() => {
progress.style.width = '0%';
setTimeout(() => {
progressBar.style.display = 'none';
}, 1000);
});
}
});
</script>
</body>
</html>
第三步:修改织梦后端处理文件 (/plus/file.php)
织梦的/plus/file.php文件是用来处理文件上传的核心,我们需要修改它,让它能正确响应AJAX请求,并返回JSON格式的数据。
注意:在修改核心文件前,请务必备份原文件!
打开 /plus/file.php,找到类似 ShowMsg('上传文件成功!', '-1'); 这样的代码,将其修改为返回JSON。
// 在文件的开头,确保开启了输出缓冲
ob_start();
// ... (其他代码,包括权限检查等) ...
// 假设文件上传成功后的代码段
// 原来的代码可能是这样:
// $filename = $cfg_upload_path.'/'.$filename;
// if(!move_uploaded_file($uploadfile, $filename))
// {
// ShowMsg('上传文件失败!', '-1');
// exit();
// }
// $dsql->ExecuteNoneQuery("INSERT INTO `#@__uploads`(arcid,title,uptime,filepath,filesize) VALUES ('$arcid','$filename','$mtime','$filename','$filesize');");
// ShowMsg('上传文件成功!', '-1');
// 修改为:
$filename = $cfg_upload_path.'/'.$filename;
if(!move_uploaded_file($uploadfile, $filename))
{
// 返回JSON错误信息
header('Content-Type: application/json');
echo json_encode(['error' => 1, 'message' => '上传文件失败!']);
exit();
}
// 插入数据库
$dsql->ExecuteNoneQuery("INSERT INTO `#@__uploads`(arcid,title,uptime,filepath,filesize) VALUES ('$arcid','$filename','$mtime','$filename','$filesize');");
// 返回JSON成功信息
$return_data = [
'error' => 0,
'message' => 'success',
'url' => $filename, // 返回文件的相对路径
'filename' => $filename, // 返回完整路径 => $filename, // 返回文件名
'filesize' => $filesize
];
header('Content-Type: application/json');
echo json_encode($return_data);
exit();
关键点:
header('Content-Type: application/json');:告诉浏览器返回的是JSON数据。json_encode():将PHP数组转换为JSON字符串。exit():在输出JSON后立即终止脚本执行,防止织梦原有的HTML代码被输出。
第四步:在文章编辑页面调用上传组件
你需要在一个织梦模板页面(例如文章编辑页 article_add.php 的模板)中嵌入这个上传组件,并监听它发送的消息。
- 引入上传组件:在文章编辑页的模板文件中,使用
{dede:include filename='upload.html'/}或iframe来引入我们创建的upload.html。 - 监听消息:在文章编辑页的JavaScript代码中,监听
message事件。
// 在文章编辑页的JS中
window.addEventListener('message', function(event) {
// 安全检查:检查消息来源是否可信
// if (event.origin !== "https://your-dede-domain.com") {
// return;
// }
if (event.data.type === 'fileUploaded') {
const fileInfo = event.data.data;
console.log('文件上传成功:', fileInfo);
// 在这里调用织梦的JS函数,将附件添加到编辑器中
// 这需要你查看织梦编辑器(如ckeditor)的API
// 如果是DedeCMS 5.7/5.8的版本,可能会有类似下面的函数
try {
// 假设织梦有一个全局函数 addAttachmentToEditor
// addAttachmentToEditor(fileInfo.url, fileInfo.title);
// 或者,直接操作DOM,将附件信息添加到附件列表
const attachmentList = document.getElementById('myAttachmentList'); // 假设你有一个附件列表容器
const newItem = document.createElement('div');
newItem.innerHTML = `<input type="text" name="附件[]" value="${fileInfo.title}" readonly> <input type="hidden" name="附件路径[]" value="${fileInfo.url}">`;
attachmentList.appendChild(newItem);
// 显示成功提示
alert('附件 "' + fileInfo.title + '" 添加成功!');
} catch (e) {
console.error('添加附件到编辑器失败:', e);
}
}
});
总结与注意事项
- 安全性:修改核心文件有风险,确保你理解代码逻辑,并在修改前备份,在生产环境中,对上传的文件类型、大小、名称进行严格的过滤和检查,防止上传恶意文件。
- 兼容性:HTML5的
FileReader和FormData在现代浏览器中支持良好,但需要考虑旧浏览器的兼容性问题。 - 织梦版本:不同版本的织梦,其
file.php和编辑器API可能有所不同,需要根据实际情况调整代码。 - 用户体验:这个方案提供了很好的上传体验,但与织梦原有的附件管理流程是解耦的,你需要自己处理附件与文章的关联(即
arcid字段),在上传成功后,将返回的文件信息(url,title等)手动添加到文章的附件表单中,以便在保存文章时能正确关联。
这个自定义的HTML5上传方案虽然步骤稍多,但它为你提供了完全的控制权,并且能够实现一个现代化、用户友好的上传界面,是提升织梦后台体验的一个有效途径。
