<div class="layui-fluid layui-anim febs-anim" id="febs-aiCompany-Info" lay-title="编辑">
|
<div class="layui-row febs-container">
|
<div class="layui-col-md12">
|
<div class="layui-fluid" id="aiCompany-info">
|
<form class="layui-form" action="" lay-filter="aiCompany-info-form">
|
<div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
|
<ul class="layui-tab-title">
|
<li class="layui-this">基础信息</li>
|
</ul>
|
<div class="layui-tab-content">
|
<div class="upload-container">
|
<div class="upload-header">
|
<h2>大文件上传系统</h2>
|
<p>支持分片上传,最大支持10GB文件</p>
|
</div>
|
|
<!-- 上传区域 -->
|
<div class="upload-area" id="uploadArea">
|
<i class="layui-icon layui-icon-upload-drag upload-icon"></i>
|
<div class="upload-text">
|
<p>点击或拖拽文件到此处上传</p>
|
<small>支持视频文件上传</small>
|
</div>
|
</div>
|
|
<!-- 文件信息 -->
|
<div class="file-info" style="display: none;" id="fileInfo">
|
<div class="layui-row">
|
<div class="layui-col-xs6">
|
<p><strong>文件名:</strong><span id="fileName"></span></p>
|
<p><strong>文件大小:</strong><span id="fileSize"></span></p>
|
<p><strong>分片数量:</strong><span id="chunkCount"></span></p>
|
</div>
|
<div class="layui-col-xs6">
|
<p><strong>上传进度:</strong><span id="uploadProgress">0%</span></p>
|
<p><strong>状态:</strong><span id="uploadStatus">等待上传</span></p>
|
<p><strong>速度:</strong><span id="uploadSpeed">0KB/s</span></p>
|
</div>
|
</div>
|
</div>
|
|
<!-- 进度条 -->
|
<div class="progress-container" style="display: none;" id="progressContainer">
|
<div class="progress-bar">
|
<div class="progress-fill" id="progressFill">0%</div>
|
</div>
|
</div>
|
|
<!-- 操作按钮 -->
|
<div class="btn-group" id="btnGroup">
|
<button class="layui-btn layui-btn-normal" id="startUpload">开始上传</button>
|
<button class="layui-btn layui-btn-danger" id="cancelUpload">取消上传</button>
|
</div>
|
|
<!-- 播放区域 -->
|
<div class="play-container" id="playContainer">
|
<h3 style="margin-bottom: 20px; color: #333;">视频播放</h3>
|
<video id="videoPlayer" controls>
|
<source id="videoSource" src="" type="video/mp4">
|
您的浏览器不支持视频播放
|
</video>
|
<div class="layui-row layui-col-space10" style="margin-top: 20px;">
|
<div class="layui-col-xs12">
|
<button class="layui-btn layui-btn-primary" id="refreshList">刷新文件列表</button>
|
</div>
|
</div>
|
|
<!-- 服务器文件列表 -->
|
<div style="margin-top: 20px;">
|
<h4 style="margin-bottom: 10px; color: #666;">服务器文件列表</h4>
|
<table class="layui-table" id="fileList">
|
<thead>
|
<tr>
|
<th>文件名</th>
|
<th>大小</th>
|
<th>上传时间</th>
|
<th>操作</th>
|
</tr>
|
</thead>
|
<tbody>
|
<!-- 文件列表将通过JS动态生成 -->
|
</tbody>
|
</table>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="layui-form-item febs-hide">
|
<button class="layui-btn" lay-submit="" lay-filter="aiCompany-info-form-submit" id="submit">保存</button>
|
</div>
|
</form>
|
</div>
|
</div>
|
</div>
|
</div>
|
<style>
|
.upload-container {
|
max-width: 900px;
|
margin: 30px auto;
|
padding: 30px;
|
background: #f8f8f8;
|
border-radius: 8px;
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
}
|
|
.upload-header {
|
text-align: center;
|
margin-bottom: 40px;
|
}
|
|
.upload-header h2 {
|
color: #333;
|
margin-bottom: 10px;
|
font-size: 24px;
|
}
|
|
.upload-header p {
|
color: #666;
|
font-size: 14px;
|
}
|
|
.upload-area {
|
border: 2px dashed #1E9FFF;
|
border-radius: 8px;
|
padding: 50px;
|
text-align: center;
|
margin-bottom: 30px;
|
cursor: pointer;
|
transition: all 0.3s;
|
background: #fff;
|
}
|
|
.upload-area:hover {
|
border-color: #009688;
|
background: #f0f9ff;
|
}
|
|
.upload-icon {
|
font-size: 80px;
|
color: #1E9FFF;
|
margin-bottom: 20px;
|
}
|
|
.upload-text {
|
font-size: 18px;
|
color: #666;
|
}
|
|
.upload-text small {
|
font-size: 14px;
|
color: #999;
|
display: block;
|
margin-top: 10px;
|
}
|
|
.file-info {
|
margin-top: 20px;
|
padding: 20px;
|
background: #fff;
|
border-radius: 6px;
|
border-left: 4px solid #1E9FFF;
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
}
|
|
.file-info .layui-row {
|
margin: 0;
|
}
|
|
.file-info p {
|
margin: 5px 0;
|
color: #333;
|
}
|
|
.file-info strong {
|
color: #666;
|
}
|
|
.progress-container {
|
margin: 20px 0;
|
background: #fff;
|
padding: 20px;
|
border-radius: 6px;
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
}
|
|
.progress-bar {
|
height: 24px;
|
background: #f0f0f0;
|
border-radius: 12px;
|
overflow: hidden;
|
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
}
|
|
.progress-fill {
|
height: 100%;
|
background: linear-gradient(90deg, #1E9FFF, #009688);
|
border-radius: 12px;
|
width: 0%;
|
transition: width 0.3s;
|
text-align: center;
|
line-height: 24px;
|
color: #fff;
|
font-size: 12px;
|
font-weight: bold;
|
}
|
|
.btn-group {
|
margin: 20px 0;
|
text-align: center;
|
}
|
|
.btn-group .layui-btn {
|
margin: 0 10px;
|
padding: 0 30px;
|
height: 40px;
|
line-height: 40px;
|
font-size: 16px;
|
}
|
|
.play-container {
|
margin-top: 40px;
|
padding: 30px;
|
background: #fff;
|
border-radius: 8px;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
}
|
|
.play-container h3 {
|
margin-bottom: 20px;
|
color: #333;
|
font-size: 20px;
|
}
|
|
video {
|
width: 100%;
|
max-width: 800px;
|
height: auto;
|
border-radius: 4px;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
}
|
|
.list-controls {
|
margin: 20px 0;
|
text-align: center;
|
}
|
|
.file-list-section {
|
margin-top: 30px;
|
}
|
|
.file-list-section h4 {
|
margin-bottom: 15px;
|
color: #666;
|
font-size: 16px;
|
border-bottom: 1px solid #f0f0f0;
|
padding-bottom: 10px;
|
}
|
|
.layui-table {
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
}
|
|
.layui-table th {
|
background: #f8f8f8;
|
font-weight: bold;
|
}
|
|
.layui-table tr:hover {
|
background: #f9f9f9;
|
}
|
|
.layui-table .play-btn,
|
.layui-table .delete-btn {
|
margin: 0 5px;
|
}
|
|
@media (max-width: 768px) {
|
.upload-container {
|
margin: 20px;
|
padding: 20px;
|
}
|
|
.upload-area {
|
padding: 30px;
|
}
|
|
.upload-icon {
|
font-size: 60px;
|
}
|
|
.btn-group .layui-btn {
|
margin: 5px;
|
padding: 0 20px;
|
}
|
}
|
</style>
|
<!-- 表格操作栏 end -->
|
<script data-th-inline="javascript">
|
layui.use(['layer', 'jquery'], function() {
|
var layer = layui.layer,
|
$ = layui.jquery;
|
|
// 全局变量
|
var file = null;
|
var fileMd5 = '';
|
var chunkSize = 5 * 1024 * 1024; // 5MB分片
|
var chunks = 0;
|
var currentChunk = 0;
|
var isUploading = false;
|
var startTime = 0;
|
var uploadedSize = 0;
|
|
// 初始化
|
init();
|
|
function init() {
|
// 拖拽上传
|
var uploadArea = document.getElementById('uploadArea');
|
|
uploadArea.addEventListener('dragover', function(e) {
|
e.preventDefault();
|
$(this).css('border-color', '#009688');
|
});
|
|
uploadArea.addEventListener('dragleave', function(e) {
|
e.preventDefault();
|
$(this).css('border-color', '#1E9FFF');
|
});
|
|
uploadArea.addEventListener('drop', function(e) {
|
e.preventDefault();
|
$(this).css('border-color', '#1E9FFF');
|
handleFile(e.dataTransfer.files[0]);
|
});
|
|
// 点击上传
|
uploadArea.addEventListener('click', function(e) {
|
// 阻止事件冒泡,避免干扰其他按钮
|
e.stopPropagation();
|
// 确保点击的是上传区域本身,而不是其他元素
|
if (e.target === uploadArea || e.target.classList.contains('upload-icon') || e.target.classList.contains('upload-text')) {
|
console.log('上传区域被点击');
|
var input = document.createElement('input');
|
input.type = 'file';
|
input.accept = 'video/*';
|
input.onchange = function() {
|
if (this.files.length > 0) {
|
console.log('选择了文件:', this.files[0].name);
|
handleFile(this.files[0]);
|
}
|
};
|
input.click();
|
}
|
});
|
|
// 按钮事件 - 使用事件委托确保每次都能正确绑定
|
// 移除旧的事件绑定,避免重复绑定
|
$(document).off('click', '#startUpload');
|
$(document).off('click', '#cancelUpload');
|
$(document).off('click', '#refreshList');
|
|
// 重新绑定事件
|
$(document).on('click', '#startUpload', function(e) {
|
e.preventDefault();
|
e.stopPropagation();
|
console.log('开始上传按钮被点击 - 新绑定');
|
startUpload();
|
});
|
|
// 为取消上传按钮添加更强大的事件绑定
|
$(document).on('click', '#cancelUpload', function(e) {
|
e.preventDefault();
|
e.stopPropagation();
|
console.log('取消上传按钮被点击 - 新事件委托');
|
console.log('按钮状态:', $(this).prop('disabled'));
|
console.log('按钮可见性:', $(this).is(':visible'));
|
cancelUpload();
|
});
|
|
$(document).on('click', '#refreshList', function(e) {
|
e.preventDefault();
|
e.stopPropagation();
|
console.log('刷新列表按钮被点击 - 新绑定');
|
refreshFileList();
|
});
|
|
// 初始化文件列表
|
refreshFileList();
|
}
|
|
// 处理文件
|
function handleFile(selectedFile) {
|
if (!selectedFile) return;
|
|
if (!selectedFile.type.startsWith('video/')) {
|
layer.msg('请选择视频文件', {icon: 2});
|
return;
|
}
|
|
// 重置之前的状态
|
currentChunk = 0;
|
uploadedSize = 0;
|
$('#uploadProgress').text('0%');
|
$('#uploadStatus').text('等待上传');
|
$('#uploadSpeed').text('0KB/s');
|
$('#progressFill').css('width', '0%').text('0%');
|
|
file = selectedFile;
|
chunks = Math.ceil(file.size / chunkSize);
|
|
// 显示文件信息
|
$('#fileName').text(file.name);
|
$('#fileSize').text(formatFileSize(file.size));
|
$('#chunkCount').text(chunks);
|
$('#fileInfo').show();
|
$('#progressContainer').show();
|
$('#btnGroup').show();
|
|
// 计算文件MD5(用于断点续传和文件标识)
|
calculateFileMd5();
|
}
|
|
// 计算文件MD5
|
function calculateFileMd5() {
|
layer.msg('正在计算文件MD5...', {icon: 16, time: 0});
|
|
var fileReader = new FileReader();
|
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
var chunk = blobSlice.call(file, 0, Math.min(file.size, 10 * 1024 * 1024)); // 只取前10MB计算MD5
|
|
fileReader.onload = function(e) {
|
var spark = new SparkMD5.ArrayBuffer();
|
spark.append(e.target.result);
|
fileMd5 = spark.end();
|
layer.closeAll();
|
layer.msg('文件MD5计算完成', {icon: 1});
|
};
|
|
fileReader.readAsArrayBuffer(chunk);
|
}
|
|
// 开始上传
|
function startUpload() {
|
if (!file) {
|
layer.msg('请先选择文件', {icon: 2});
|
return;
|
}
|
|
if (isUploading) {
|
layer.msg('上传已在进行中', {icon: 2});
|
return;
|
}
|
|
isUploading = true;
|
startTime = Date.now();
|
uploadedSize = 0;
|
currentChunk = 0;
|
|
$('#uploadStatus').text('上传中');
|
$('#startUpload').prop('disabled', true);
|
$('#cancelUpload').prop('disabled', true);
|
|
uploadNextChunk();
|
}
|
|
// 上传下一个分片
|
function uploadNextChunk() {
|
if (!isUploading) return;
|
if (currentChunk >= chunks) {
|
// 所有分片上传完成,请求合并
|
mergeChunks();
|
return;
|
}
|
|
var start = currentChunk * chunkSize;
|
var end = Math.min(start + chunkSize, file.size);
|
var chunk = file.slice(start, end);
|
|
var formData = new FormData();
|
formData.append('file', chunk);
|
formData.append('fileName', file.name);
|
formData.append('chunk', currentChunk);
|
formData.append('chunks', chunks);
|
formData.append('fileMd5', fileMd5);
|
|
$.ajax({
|
url: '/fileUpload/uploadChunk',
|
type: 'POST',
|
data: formData,
|
processData: false,
|
contentType: false,
|
xhr: function() {
|
var xhr = new XMLHttpRequest();
|
xhr.upload.addEventListener('progress', function(e) {
|
if (e.lengthComputable) {
|
var chunkProgress = e.loaded / e.total;
|
var totalProgress = (currentChunk + chunkProgress) / chunks;
|
updateProgress(totalProgress);
|
|
// 计算上传速度
|
var elapsed = (Date.now() - startTime) / 1000;
|
var speed = (uploadedSize + e.loaded) / elapsed;
|
$('#uploadSpeed').text(formatFileSize(speed) + '/s');
|
}
|
});
|
return xhr;
|
},
|
success: function(response) {
|
if (response.code === 200 || response.success) {
|
uploadedSize += (end - start);
|
currentChunk++;
|
uploadNextChunk();
|
} else {
|
layer.msg('分片上传失败: ' + response.message, {icon: 2});
|
isUploading = false;
|
$('#uploadStatus').text('上传失败');
|
$('#startUpload').prop('disabled', false);
|
}
|
},
|
error: function() {
|
layer.msg('网络错误,请重试', {icon: 2});
|
isUploading = false;
|
$('#uploadStatus').text('上传失败');
|
$('#startUpload').prop('disabled', false);
|
}
|
});
|
}
|
|
// 合并分片
|
function mergeChunks() {
|
$.ajax({
|
url: '/fileUpload/mergeChunks',
|
type: 'POST',
|
data: {
|
fileName: file.name,
|
fileMd5: fileMd5,
|
chunks: chunks
|
},
|
success: function(response) {
|
if (response.code === 200 || response.success) {
|
layer.msg('文件上传成功', {icon: 1});
|
$('#uploadStatus').text('上传成功');
|
$('#uploadProgress').text('100%');
|
$('#progressFill').css('width', '100%').text('100%');
|
$('#playContainer').show();
|
refreshFileList();
|
} else {
|
layer.msg('文件合并失败: ' + response.message, {icon: 2});
|
$('#uploadStatus').text('上传失败');
|
}
|
},
|
error: function() {
|
layer.msg('网络错误,请重试', {icon: 2});
|
$('#uploadStatus').text('上传失败');
|
},
|
complete: function() {
|
isUploading = false;
|
$('#startUpload').prop('disabled', false);
|
$('#cancelUpload').prop('disabled', true);
|
}
|
});
|
}
|
|
// 取消上传
|
function cancelUpload() {
|
console.log('取消上传函数被调用');
|
|
// 显示成功消息
|
layer.msg('上传已取消,页面将刷新', {icon: 1, time: 1000});
|
|
// 刷新页面
|
setTimeout(function() {
|
location.reload();
|
}, 1000);
|
}
|
|
// 更新进度
|
function updateProgress(progress) {
|
var percent = Math.round(progress * 100);
|
$('#uploadProgress').text(percent + '%');
|
$('#progressFill').css('width', percent + '%').text(percent + '%');
|
}
|
|
// 刷新文件列表
|
function refreshFileList() {
|
layer.msg('正在获取文件列表...', {icon: 16, time: 0});
|
$.ajax({
|
url: '/fileUpload/list',
|
type: 'GET',
|
success: function(response) {
|
layer.closeAll();
|
if (response.code === 200 || response.success || response.data) {
|
var fileList = response.data || [];
|
var tbody = $('#fileList tbody');
|
tbody.empty();
|
|
if (fileList.length === 0) {
|
var emptyTr = $('<tr></tr>');
|
emptyTr.html(`
|
<td colspan="4" style="text-align: center; color: #999;">暂无文件</td>
|
`);
|
tbody.append(emptyTr);
|
} else {
|
fileList.forEach(function(item) {
|
var tr = $('<tr></tr>');
|
tr.html(`
|
<td>
|
<input type="radio" name="fileRadio" value="${item.fileName}">
|
${item.fileName}
|
</td>
|
<td>${formatFileSize(item.fileSize)}</td>
|
<td>${item.uploadTime}</td>
|
<td>
|
<button class="layui-btn layui-btn-xs layui-btn-normal play-btn" data-file="${item.fileName}">
|
播放
|
</button>
|
<button class="layui-btn layui-btn-xs layui-btn-danger delete-btn" data-file="${item.fileName}">
|
删除
|
</button>
|
</td>
|
`);
|
tbody.append(tr);
|
});
|
|
// 绑定播放按钮事件
|
$('.play-btn').click(function() {
|
var fileName = $(this).data('file');
|
var videoPlayer = document.getElementById('videoPlayer');
|
var videoSource = document.getElementById('videoSource');
|
|
// 根据文件扩展名设置正确的MIME类型
|
var extension = fileName.split('.').pop().toLowerCase();
|
var mimeType = 'video/mp4';
|
switch(extension) {
|
case 'avi':
|
mimeType = 'video/x-msvideo';
|
break;
|
case 'mov':
|
mimeType = 'video/quicktime';
|
break;
|
case 'wmv':
|
mimeType = 'video/x-ms-wmv';
|
break;
|
case 'flv':
|
mimeType = 'video/x-flv';
|
break;
|
case 'webm':
|
mimeType = 'video/webm';
|
break;
|
case 'mkv':
|
mimeType = 'video/x-matroska';
|
break;
|
}
|
|
videoSource.src = '/fileUpload/play/' + encodeURIComponent(fileName);
|
videoSource.type = mimeType;
|
videoPlayer.load();
|
videoPlayer.play();
|
});
|
|
// 绑定删除按钮事件
|
$('.delete-btn').click(function() {
|
var fileName = $(this).data('file');
|
layer.confirm('确定要删除此文件吗?', {
|
btn: ['确定', '取消']
|
}, function() {
|
$.ajax({
|
url: '/fileUpload/delete',
|
type: 'POST',
|
data: {fileName: fileName},
|
success: function(response) {
|
if (response.code === 200 || response.success) {
|
layer.msg('删除成功', {icon: 1});
|
refreshFileList();
|
} else {
|
layer.msg('删除失败: ' + (response.message || response.msg), {icon: 2});
|
}
|
},
|
error: function() {
|
layer.msg('网络错误,请重试', {icon: 2});
|
}
|
});
|
});
|
});
|
}
|
} else {
|
layer.msg('获取文件列表失败: ' + (response.message || response.msg), {icon: 2});
|
}
|
},
|
error: function() {
|
layer.closeAll();
|
layer.msg('网络错误,请重试', {icon: 2});
|
}
|
});
|
}
|
|
// 格式化文件大小
|
function formatFileSize(bytes) {
|
if (bytes === 0) return '0 B';
|
var k = 1024;
|
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
}
|
});
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js"></script>
|