Files
yunzer/app/admin/view/resources/add.php
T
2025-07-03 19:31:52 +08:00

788 lines
29 KiB
PHP

{include file="public/header" /}
<div class="config-container">
<div class="config-header" style="display:flex;justify-content: space-between;">
<div>
<span>添加资源</span>
</div>
<div>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" onclick="goBack()">
<i class="layui-icon layui-icon-return"></i>返回
</button>
</div>
</div>
<form class="layui-form" action="" method="post">
<div class="layui-form-item">
<label class="layui-form-label">资源名称</label>
<div class="layui-input-block">
<input type="text" name="title" placeholder="请输入资源名称" autocomplete="off"
class="layui-input" lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类</label>
<div class="layui-input-block">
<select name="cate" lay-filter="cate">
<option value="">请选择分类</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">资源编号</label>
<div class="layui-input-block">
<input type="text" name="number" placeholder="选取分类后系统自动生成"
autocomplete="off" class="layui-input" lay-affix="clear" readonly>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="desc" placeholder="请输入资源描述" class="layui-textarea" lay-affix="clear"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">上传者</label>
<div class="layui-input-block">
<input type="text" name="uploader" placeholder="请输入上传者"
autocomplete="off" class="layui-input" lay-affix="clear" value="{$aUser['name']}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">资源图标</label>
<div class="layui-input-block">
<button type="button" class="layui-btn" id="upload-btn">
<i class="layui-icon layui-icon-upload"></i> 图标上传
</button>
<div style="width: 120px;">
<div class="layui-upload-list">
<img class="layui-upload-img" id="upload-img"
style="width: 118px; height: 118px;object-fit: cover;">
<div id="upload-text"></div>
</div>
<div class="layui-progress layui-progress-big" lay-showPercent="yes" lay-filter="icon-progress">
<div class="layui-progress-bar" lay-percent=""></div>
</div>
<input type="hidden" name="icon" id="icon" value="">
</div>
<div class="layui-form-mid layui-word-aux">建议尺寸:128px * 128px</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">资源文件</label>
<div class="layui-input-block">
<input type="text" name="fileurl" placeholder="本地资源地址" autocomplete="off" class="layui-input"
value="" style="margin-bottom: 10px;" lay-affix="clear">
<div class="layui-upload-drag" style="display: block;" id="ID-upload-demo-drag">
<i class="layui-icon layui-icon-upload"></i>
<div>点击上传,或将文件拖拽到此处</div>
<div class="layui-hide" id="ID-upload-demo-preview">
<hr>
<div class="file-info">
<i class="layui-icon layui-icon-file"></i>
<span class="file-name"></span>
<span class="file-size"></span>
</div>
</div>
</div>
<div class="layui-progress layui-progress-big" lay-showPercent="yes" lay-filter="file-progress"
style="margin-top: 10px;">
<div class="layui-progress-bar" lay-percent=""></div>
</div>
<input type="hidden" name="file" id="file" value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">资源链接</label>
<div class="layui-input-block">
<input type="text" name="url" placeholder="百度网盘、115网盘、蓝奏云等" autocomplete="off"
class="layui-input" lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分享码</label>
<div class="layui-input-block">
<input type="text" name="code" placeholder="请输入分享码" autocomplete="off" class="layui-input"
lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">解压密码</label>
<div class="layui-input-block">
<input type="text" name="zipcode" placeholder="请输入解压密码" autocomplete="off" class="layui-input"
lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="number" name="sort" value="0" class="layui-input" placeholder="数字越大越靠前" lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图片上传</label>
<div class="layui-input-block">
<div class="image-upload-container">
<button type="button" class="btn btn-primary" id="imageUpload">
<i class="fas fa-upload"></i> 多图片上传
</button>
<div class="image-preview-container" id="imagePreview">
</div>
<div class="upload-progress" id="imageProgress" style="display: none;">
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
<input type="hidden" name="images" id="images" value="">
</div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">内容</label>
<div class="layui-input-block">
<div id="editor—wrapper" id="content" name="content" style="border: 1px solid #ccc;">
<div id="toolbar-container" style="border-bottom: 1px solid #ccc;"><!-- 工具栏 --></div>
<div id="editor-container" style="height: 800px;"><!-- 编辑器 --></div>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formSubmit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
<script src="/static/js/wangeditor.js"></script>
<script>
layui.use(['form', 'layer', 'upload', 'element'], function () {
var form = layui.form;
var layer = layui.layer;
var $ = layui.$;
var upload = layui.upload;
var element = layui.element;
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 图标上传
var iconUpload = upload.render({
elem: '#upload-btn',
url: '{:url("index/upload_img")}',
before: function (obj) {
obj.preview(function (index, file, result) {
$('#upload-img').attr('src', result);
});
element.progress('icon-progress', '0%');
layer.msg('图标上传中', { icon: 16, time: 0 });
},
done: function (res) {
if (res.code > 0) {
return layer.msg('图标上传失败');
}
$('#icon').val(res.data);
$('#upload-text').html('');
layer.msg('图标上传成功', { icon: 1 });
},
error: function () {
var demoText = $('#upload-text');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function () {
iconUpload.upload();
});
},
progress: function (n, elem, e) {
element.progress('icon-progress', n + '%');
if (n == 100) {
layer.msg('图标上传完毕', { icon: 1 });
}
}
});
// 文件上传
var fileUpload = upload.render({
elem: '#ID-upload-demo-drag',
url: '{:url("index/upload_file")}',
accept: 'file',
exts: 'doc|docx|xls|xlsx|ppt|pptx|pdf|txt|zip|rar|7z',
before: function (obj) {
obj.preview(function (index, file, result) {
$('#ID-upload-demo-preview').show();
$('.file-name').text(file.name);
$('.file-size').text(formatFileSize(file.size));
});
element.progress('file-progress', '0%');
layer.msg('文件上传中', { icon: 16, time: 0 });
},
done: function (res) {
if (res.code > 0) {
return layer.msg('文件上传失败');
}
$('#file').val(res.data.src);
$('input[name="fileurl"]').val(res.data.src);
layer.msg('文件上传成功', { icon: 1 });
},
error: function () {
layer.msg('文件上传失败', { icon: 2 });
},
progress: function (n, elem, e) {
element.progress('file-progress', n + '%');
if (n == 100) {
layer.msg('文件上传完毕', { icon: 1 });
}
}
});
// 多图片上传
var uploadInst = upload.render({
elem: '#imageUpload',
url: '{:url("index/upload_img")}',
multiple: true,
accept: 'images',
before: function (obj) {
obj.preview(function (index, file, result) {
$('#imagePreview').append('<div class="layui-upload-img-item" style="display: inline-block; margin-right: 10px;"><img src="' + result + '" alt="' + file.name + '" style="width: 100px; height: 100px; object-fit: cover;"><p>' + file.name + '</p><button type="button" class="layui-btn layui-btn-xs layui-btn-danger delete-image" style="position: absolute; top: 0; right: 0;">删除</button></div>');
});
element.progress('image-progress', '0%');
layer.msg('图片上传中', { icon: 16, time: 0 });
},
done: function (res) {
if (res.code > 0) {
return layer.msg('图片上传失败');
}
var images = $('#images').val();
images = images ? images + ',' + res.data : res.data;
$('#images').val(images);
layer.msg('图片上传成功', { icon: 1 });
},
error: function () {
var demoText = $('#imagePreview');
demoText.append('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function () {
uploadInst.upload();
});
},
progress: function (n, elem, e) {
element.progress('image-progress', n + '%');
if (n == 100) {
layer.msg('图片上传完毕', { icon: 1 });
}
}
});
// 获取分类列表
function loadCategories() {
$.get('{:url("resources/getcate")}', function (res) {
if (res.code == 0) {
var html = '<option value="">请选择分类</option>';
res.data.forEach(function (item) {
var disabled = item.cid == 0 ? 'disabled' : '';
html += '<option value="' + item.id + '" ' + disabled + '>' + item.name + '</option>';
if (item.children && item.children.length > 0) {
item.children.forEach(function (child) {
html += '<option value="' + child.id + '">├─ ' + child.name + '</option>';
});
}
});
$('select[name="cate"]').html(html);
form.render('select');
window.categoryData = res.data;
} else {
layer.msg(res.msg, { icon: 2 });
}
});
}
loadCategories();
// 递归查找分类信息的函数
function findCategory(categories, targetId) {
for (let category of categories) {
if (category.id == targetId) {
return {
parent: null,
current: category,
total: category.total || 0
};
}
if (category.children && category.children.length > 0) {
for (let child of category.children) {
if (child.id == targetId) {
return {
parent: category,
current: child,
total: child.total || 0
};
}
if (child.children && child.children.length > 0) {
const result = findCategory([child], targetId);
if (result) {
return result;
}
}
}
}
}
return null;
}
// 监听分类选择变化
form.on('select(cate)', function (data) {
var selectedId = data.value;
if (!selectedId) {
$('input[name="number"]').val('');
return;
}
const categoryInfo = findCategory(window.categoryData, selectedId);
if (categoryInfo) {
var nextNumber = categoryInfo.total + 1;
var numberStr = nextNumber.toString().padStart(5, '0');
var resourceNumber = categoryInfo.parent ? categoryInfo.parent.number + categoryInfo.current.number : categoryInfo.current.number;
resourceNumber += numberStr;
$('input[name="number"]').val(resourceNumber);
}
});
// 配置 wangeditor 编辑器
const { createEditor, createToolbar } = window.wangEditor;
const editorConfig = {
MENU_CONF: {},
placeholder: '请输入内容...',
onChange(editor) {
const html = editor.getHtml();
},
};
editorConfig.MENU_CONF['uploadImage'] = {
server: '{:url("index/upload_img")}',
fieldName: 'file',
maxFileSize: 50 * 1024 * 1024,
maxNumberOfFiles: 10,
allowedFileTypes: ['image/*'],
meta: { token: 'xxx' },
metaWithUrl: true,
headers: { Accept: 'text/x-json' },
timeout: 30 * 1000,
onBeforeUpload(file) {
console.log('准备上传图片', file);
return file;
},
onProgress(progress) {
console.log('上传进度', progress);
},
onSuccess(file, res) {
console.log('上传成功', file, res);
},
onFailed(file, res) {
layer.msg('上传失败:' + res.msg, { icon: 2 });
console.log('上传失败', file, res);
},
onError(file, err, res) {
layer.msg('上传出错:' + err.message, { icon: 2 });
console.error('上传出错', file, err, res);
},
customInsert(res, insertFn) {
if (res.code === 0 && res.url) {
let imageUrl = res.url;
if (!imageUrl.startsWith('http')) {
imageUrl = 'https://' + imageUrl;
}
imageUrl = imageUrl.replace(/^https?:\/\/[^\/]+\/admin\/resources\//, 'https://www.yunzer.cn/');
insertFn(imageUrl);
} else {
layer.msg('图片上传失败:' + (res.msg || '未知错误'), { icon: 2 });
}
}
};
const editor = createEditor({
selector: '#editor-container',
html: '<p><br></p>',
config: editorConfig,
mode: 'default',
});
const toolbar = createToolbar({
editor,
selector: '#toolbar-container',
config: {},
mode: 'default',
});
// 表单提交
form.on('submit(formSubmit)', function (data) {
var content = editor.getHtml();
if (!content || content === '<p><br></p>') {
layer.msg('请输入文章内容', { icon: 2 });
return false;
}
var loadIndex = layer.load(2);
// 使用 FormData 提交数据
var formData = new FormData();
formData.append('title', $('input[name="title"]').val());
formData.append('cate', $('select[name="cate"]').val());
formData.append('number', $('input[name="number"]').val());
formData.append('desc', $('textarea[name="desc"]').val());
formData.append('uploader', $('input[name="uploader"]').val());
formData.append('icon', $('input[name="icon"]').val());
formData.append('images', $('input[name="images"]').val());
formData.append('url', $('input[name="url"]').val());
formData.append('fileurl', $('input[name="fileurl"]').val());
formData.append('code', $('input[name="code"]').val());
formData.append('zipcode', $('input[name="zipcode"]').val());
formData.append('sort', $('input[name="sort"]').val());
formData.append('content', content);
$.ajax({
url: '{:url("resources/add")}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function (res) {
layer.close(loadIndex);
if (res.code == 0) {
layer.msg(res.msg, { icon: 1 });
setTimeout(function () {
window.location.href = '{:url("resources/lists")}';
}, 1000);
} else {
layer.msg(res.msg, { icon: 2 });
}
},
error: function () {
layer.close(loadIndex);
layer.msg('提交失败,请重试', { icon: 2 });
}
});
return false;
});
// 重置按钮点击事件
$('button[type="reset"]').on('click', function () {
loadCategories();
});
});
//返回资源列表
function goBack() {
window.location.href = '{:url("resources/lists")}';
}
</script>
<!-- wangeditor编辑器脚本 -->
<script>
const { createEditor, createToolbar } = window.wangEditor
const editorConfig = {
MENU_CONF: {},
placeholder: '请输入内容...',
onChange(editor) {
const html = editor.getHtml()
},
}
// 配置图片上传
editorConfig.MENU_CONF['uploadImage'] = {
server: '{:url("index/upload_img")}',
fieldName: 'file',
maxFileSize: 50 * 1024 * 1024, // 50M
maxNumberOfFiles: 10,
allowedFileTypes: ['image/*'],
meta: {
token: 'xxx'
},
metaWithUrl: true,
headers: {
Accept: 'text/x-json'
},
timeout: 30 * 1000, // 30s
onBeforeUpload(file) {
console.log('准备上传图片', file)
return file
},
onProgress(progress) {
console.log('上传进度', progress)
},
onSuccess(file, res) {
console.log('上传成功', file, res)
},
onFailed(file, res) {
layer.msg('上传失败:' + res.msg, { icon: 2 })
console.log('上传失败', file, res)
},
onError(file, err, res) {
layer.msg('上传出错:' + err.message, { icon: 2 })
console.error('上传出错', file, err, res)
},
customInsert(res, insertFn) {
// 只使用返回的url字段,并确保使用完整的URL
if (res.code === 0 && res.url) {
// 如果URL不是以http开头,添加https://
let imageUrl = res.url;
if (!imageUrl.startsWith('http')) {
imageUrl = 'https://' + imageUrl;
}
// 移除可能存在的重复域名和路径
imageUrl = imageUrl.replace(/^https?:\/\/[^\/]+\/admin\/resources\//, 'https://www.yunzer.cn/');
insertFn(imageUrl);
} else {
layer.msg('图片上传失败:' + (res.msg || '未知错误'), { icon: 2 });
}
}
}
const editor = createEditor({
selector: '#editor-container',
html: '<p><br></p>',
config: editorConfig,
mode: 'default', // or 'simple'
})
const toolbarConfig = {}
const toolbar = createToolbar({
editor,
selector: '#toolbar-container',
config: toolbarConfig,
mode: 'default', // or 'simple'
})
</script>
<script>
//返回资源列表
function goBack() {
window.location.href = '{:url("resources/lists")}';
}
</script>
<style>
.image-upload-container {
margin: 20px 0;
}
.image-preview-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
margin-top: 15px;
}
.image-preview-item {
position: relative;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.image-preview-item img {
width: 100%;
height: 150px;
object-fit: cover;
display: block;
}
.image-preview-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s;
z-index: 1;
}
.image-preview-item:hover .image-preview-overlay {
opacity: 1;
}
.image-filename {
margin: 5px 0;
font-size: 0.9em;
text-align: center;
word-break: break-all;
}
.upload-progress {
margin-top: 10px;
height: 4px;
background: #f0f0f0;
border-radius: 2px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: #007bff;
transition: width 0.3s ease;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
}
.delete-image {
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
z-index: 2;
}
.delete-image i {
font-size: 40px;
margin-right: 4px;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
const imageUpload = document.getElementById('imageUpload');
const imagePreview = document.getElementById('imagePreview');
const imageProgress = document.getElementById('imageProgress');
const progressBar = imageProgress.querySelector('.progress-bar');
const imagesInput = document.getElementById('images');
// 处理图片上传
imageUpload.addEventListener('click', function () {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = 'image/*';
input.onchange = function (e) {
const files = e.target.files;
if (files.length === 0) return;
imageProgress.style.display = 'block';
let uploadedCount = 0;
Array.from(files).forEach(file => {
const formData = new FormData();
formData.append('file', file);
fetch('{:url("index/upload_img")}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(res => {
if (res.code === 0) {
addImagePreview(res.data);
updateImagesInput();
} else {
alert('上传失败:' + res.msg);
}
})
.catch(error => {
console.error('上传错误:', error);
alert('上传出错');
})
.finally(() => {
uploadedCount++;
const progress = (uploadedCount / files.length) * 100;
progressBar.style.width = progress + '%';
if (uploadedCount === files.length) {
setTimeout(() => {
imageProgress.style.display = 'none';
progressBar.style.width = '0%';
}, 500);
}
});
});
};
input.click();
// 清除 input 的值,防止重复触发
input.value = '';
});
// 添加图片预览
function addImagePreview(imageUrl) {
const div = document.createElement('div');
div.className = 'image-preview-item';
div.dataset.src = imageUrl;
div.innerHTML = `
<img src="${imageUrl}" alt="已上传图片">
<div class="image-preview-overlay">
<button type="button" class="btn btn-danger btn-sm delete-image">
<i class="layui-icon layui-icon-delete"></i>
</button>
</div>
<p class="image-filename">${imageUrl.split('/').pop()}</p>
`;
imagePreview.appendChild(div);
}
// 更新隐藏输入框的值
function updateImagesInput() {
const images = Array.from(imagePreview.querySelectorAll('.image-preview-item'))
.map(item => item.dataset.src);
imagesInput.value = images.join(',');
}
// 删除图片
imagePreview.addEventListener('click', function (e) {
if (e.target.closest('.delete-image')) {
const item = e.target.closest('.image-preview-item');
item.remove();
updateImagesInput();
}
});
});
</script>