laravel实战-通用后台管理系统-基础设置/文章分类/文章分页列表/图片上传和嵌入富文本编辑器
学习心得
laravel的分页查询功能, 搭配layui的分页组件, 可以简便的实现分页功能.
laravel的文件上传, 文件存放的根目录在
/storage/app/public
中, 而laravel对外开放的目录是/public
, 要把已上传的文件暴露给前端, 需要在/public
创建一个指向/storage/app/public
目录的短连接.页面中嵌入富文本编辑器, 跟其他前端组件一样, 先引入必要的js, css文件, 再通过js创建相关组件对象, 然后传入字面量对象进行初始化.
1. 基础设置/文章分类
- 这两个功能实现没有新的知识点, 仿管理员管理实现即可.
2. 文章分页列表
使用laravel提供的分页查询功能获取分页数据
使用
DB::pagenate(每页显示记录数, 查询字段, "当前页"参数名, 当前页码)
查询分页数据.- 该方法会自己从
$request
对象中获取当前页码, 获取当前页码的参数名由第三个参数指定. - 也可以自己指定要查询的页码, 直接通过第四个参数指定页码即可.
DB::pagenate()
方法返回的是对象数组, 一条记录是一个对象.
- 该方法会自己从
使用laravel提供的”宏扩展”功能扩展数据库访问类方法, 添加返回二维数组格式的结果集方法
pages()
. “宏扩展”介绍点这里.
/**
* 扩展laravel的DB类,把查询分页的结果由对象数组改为二维数组
* $perPage: 每页行数
* $columns: 查询的列
* $pageName: 请求参数中指定当前页的参数名
* $page: 当前页
*/
QueryBuilder::macro('pages', function($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) {
/* 查询分页对象 */
$paginateObj = $this->paginate($perPage, $columns, $pageName, $page);
/* 查询结果对象数组 */
$items = $paginateObj->items();
$rtn = [];
// 遍历对象数组,把对象强转成数组,后作为$rtn的元素
foreach($items as $item) {
$rtn[] = (array) $item;
}
return ['page_data' => $rtn, 'total' => $paginateObj->total()];
});
layui提供的分页组件
laypage
渲染分页条.在布局中给要布局分页条的地方添加一个容器, 并给个id值:
<div id="paginate"></div>
在
layui.use()
方法的参数回调中, 渲染分页条.
<script>
layui.use(['layer', 'laypage'], function() {
layer = layui.layer;
laypage = layui.laypage;
laypage.render({
elem: 'paginate', // 注意,这里的 test1 是 ID,不用加 # 号
count: {
{
$total
}
}, // 查询到的记录总数,从数据库中获取
limit: {
{
$limit
}
}, // 每页容纳的记录数
curr: {
{
$currPage
}
}, // 当前页
layout: ['prev', 'page', 'next', 'count', 'limit', 'refresh', 'skip'], // 要显示的分页条各部分, 分别是: ['上一页', '页码表', '下一页', '总页数', '每页显示记录数下拉列表', '刷新当前页', '页定位表单'], 可根据需要增删数组成员.
limits: [1, 5, 10, 15, 20], // 每页显示记录数下拉列表项
jump: function(obj, first) { // 获取指定页码数据的回调
//obj包含了当前分页的所有参数,比如:
console.log(obj.curr); //得到当前页,以便向服务端请求对应页的数据。
console.log(obj.limit); //得到每页显示的条数
console.log(obj);
//首次不执行
if (!first) {
window.location.href = "?limit=" + obj.limit + "&page=" + obj.curr;
}
}
});
});
</script>
- 参考资料: layui分页详细介绍
2. 文章配图上传
使用layui提供的上传组件
upload
处理前端显示和数据提交.- 在页面布局中加入一个button, 给button设置id值
<div class="layui-form-item">
<label for="thumb" class="layui-form-label">缩略图</label>
<div class="layui-input-block">
/* 上传按钮 */
<button type="button" class="layui-btn" id="btn_upload">
<i class="layui-icon"></i>上传图片
</button>
/* 上传成功后显示上传图片的缩略图 */
<img id="preview_img" src="" alt="" style="height: 36px;" onclick="big_img(this)">/* 点击显示大图 */
</div>
</div>
- 在
layui.use()
方法的参数回调中, 渲染文件上传组件.
<script>
layui.use(['form', 'layer', 'upload'], function() {
layer = layui.layer;
form = layui.form;
upload = layui.upload;
$ = layui.jquery;
// 执行实例
var uploadInst = upload.render({
elem: '#btn_upload', // 绑定元素
url: '/admin/upload/pic_upload', // 上传接口
data: {
_token: $('input[name="_token"]').val()
}, // 额外需要上传的参数
done: function(res) {
// 上传完毕回调
$('#preview_img').attr('src', res.data.src);
// 点击缩略图看大图中的大图
$('#tong > img').attr('src', res.data.src);
},
error: function() {
// 请求异常回调
}
});
}
</script>
后端使用laravel提供的
$request
对象处理上传.通过chrome的开发者工具中查看layui的
upload
插件提交的数据, 存放文件的参数名叫file
.在控制器方法中, 执行
$path = $request->file('file')->store(相对/storage/app/路径的子目录);
语句就能完成PHP原生上传的N大步骤.file()
方法的参数, 就是上面提到的参数名file
.
public function picUpload(Request $request) {
// file(请求中的文件参数名), store(文件存放子路径). 文件存放目录: ../storage/app/[store指定的子目录],
// 为能使用软连接访问上传的图片, 强烈建议子目录以public目录开始. 建议在public中分目录, 以年份, 或者年份月份,
// 或者年月日为public子目录来存放文件, 这样更方便查找. 也可以分得更细, 如: 精确到小时, 分钟等.
/* 此时返回的是.../public/avatars/2020/06/18/xxxxxx.png, 实际并不能以该路径来访问 */
$path = $request->file('file')->store('public/avatars/' . date('Y/m/d'));
// 获取前端能访问到的真实的url地址
$url = Storage::url($path);
// layui的上传组件要求返回的json数据的格式
$res = ['code' => 0, 'msg' => '', 'data' => ['src' => $url]];
return json_encode($res);
}
在
/public
目录中生成指向``目录的快捷方式/软连接.laravel文件上传的存放根目录是:
/storage/app/public
, 而lavaral项目的实际执行根目录是/public
目录, 所以要从网络上访问上传的文件, 需要在/public
目录中创建一个指向/storage/app/public
子目录的”快捷方式”, 即, 软连接.生成软连接的方法: 在laravel项目根目录(artisan文件所在目录), 执行
php artisan storage:link
命令, 该命令会在/public/
目录生成一个名叫storage
, 指向/storage/app/public
目录的快捷方式/软连接.
使用
Storage::url()
方法, 传入上传文件时返回的$path
路径, 生成可访问上传文件的真是url地址.
自己加料: 点击上传成功的缩略图, 显示大图
给显示缩略图的
<img>
元素加上点击事件的处理方法:<img id="preview_img" src="" alt="" style="height: 36px;" onclick="big_img(this)">/* 点击显示大图 */
在页面布局中放入一个显示大图用的
<img>
元素, 默认不显示
<div id="tong" class="hide">
<img src="" style="max-width: 100%; max-height: 100%">
</div>
- 点击缩略图显示大图的js处理脚本(弹出显示大图, 使用
layui.open()
方法, type属性值为1, 就表示弹出显示的元素是HTML元素容器)
function big_img(img) {
// 缩略图的src属性没有值时, 不处理
if (img.src == undefined || img.src == "") {
return;
}
//页面层-图片
layer.open({
type: 1,
title: false,
closeBtn: 0,
area: ['auto'],
skin: 'layui-layer-nobg', //没有背景色
shadeClose: true,
content: $('#tong')
});
}
3. 嵌入富文本编辑器
百度的
Ueditor
是比较流行的富文本编辑器. 官网地址点这里. 在官网下载合适的Ueditor
文件包.下载下来的
Ueditor
文件包, 放到/public
目录中, 一般来说, 放在/public/static/plugin/
下较好.前端页面加载
Ueditor
:
<!-- ueditor-start -->
<!-- 配置文件 -->
<script type="text/javascript" src="/static/plugin/ueditor/ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="/static/plugin/ueditor/ueditor.all.js"></script>
<!-- ueditor-end -->
- 在需要嵌入
Ueditor
的地方, 放一个加载编辑器的容器:
<!-- 实际生成的是div. 给div添加contenteditable="true", 这个div就能编辑 -->
<script id="container" name="content" type="text/plain">这里写你的初始化内容</script>
- js代码实例化编辑器
// 实例化编辑器
// 第一个参数是容器的id值
/* 第二个参数是配置属性对象 */
/* 不要写var, 把ue声明为全局变量, 因为其他地方也用到ue对象 */
/* var */
ue = UE.getEditor('container', {
initialFrameWidth: '100%', //初始化编辑器宽度,默认1000
initialFrameHeight: '500' //初始化编辑器高度,默认320
});
经过上面几步骤, 就可以把
Ueditor
嵌入到网页中了.js获取
Ueditor
中的内容, 使用ue.getContent()
方法. 而设置Ueditor
中的内容, 一般来说, 都是使用HTML标签和css来设置格式的, 所以用{!!$content!!}
往Ueditor
中设置内容.
4. 代码清单
4.1 基础设置
- 1-基础设置视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>站点设置</title>
<link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
<script src="/static/plugin/layui/layui.js"></script>
<style>
body {
padding: 10px;
}
/* grid实现label固定宽度, 输入框充满剩下的宽度 */
.site-name {
display: grid;
grid-template-columns: 110px auto;
}
.site-name > .layui-input-inline {
margin: 0;
width: 100%;
/* box-sizing: border-box;
padding-right: 10px; */
}
.site-name > .layui-input-inline > input{
margin-right: 10px;
}
/* 用定位方式实现label固定宽度, 输入框充满剩下的宽度 */
.site-keywords {
position: relative;
}
.site-keywords > label {
width: 110px;
box-sizing: border-box;
}
.site-keywords > .layui-input-inline {
position: absolute;
width: unset;
left: 110px;
right: 0;
margin: 0;
}
/* .site-keywords > .layui-input-inline input {
width: 100%;
} */
/* 用flex布局实现 */
.site-desc {
display: flex;
flex-flow: row nowrap;
}
.site-desc > .layui-form-label {
width: 124px;
box-sizing: border-box;
}
.site-desc > .layui-input-inline {
width: 100%;
margin: 0;
}
.site-desc > .layui-input-inline >textarea {
box-sizing: border-box;
/* border: 0; */
margin: 0;
}
</style>
</head>
<body>
<div class="layui-row">
<div class="layui-col-lg6 layui-col-md8 layui-col-sm10 layui-col-xs12">
<div class="layui-form site-form">
@csrf
<div class="layui-form-item site-name">
<label for="title" class="layui-form-label">站点名称</label>
<div class="layui-input-inline">
<input type="text" name="title" id="title" class="layui-input" value="{{$site['title']}}">
</div>
</div>
<div class="layui-form-item site-keywords">
<label for="keywords" class="layui-form-label">关键字</label>
<div class="layui-input-inline">
<input type="text" name="keywords" id="keywords" class="layui-input" value="{{$site['keywords']}}">
</div>
</div>
<div class="layui-form-item site-desc">
<label for="descs" class="layui-form-label">描述</label>
<div class="layui-input-inline">
<textarea name="descs" id="descs" placeholder="请输入内容" class="layui-textarea">{{$site['descs']}}</textarea>
</div>
</div>
<div class="layui-form-item">
<label for="status" class="layui-form-label">站点状态</label>
<div class="layui-input-inline">
<input type="checkbox" name="status" id="status" lay-skin="switch" lay-text="启用|停用" {{$site['status'] ? '' : 'checked'}}>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button:button class="layui-btn layui-btn-success" onclick="save()">保存</button:button>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
layui.use(['form', 'layer'], function() {
var form = layui.form;
var layer = layui.layer;
$ = layui.jquery;
});
function save() {
var data = {
_token : $('input[name="_token"]').val(),
title : $.trim($('#title').val()),
keywords : $.trim($('#keywords').val()),
descs : $.trim($('#descs').val()),
status : Boolean($('#status').prop('checked')) ? 0 : 1
};
var checkRes = checkData(data);
if(checkRes.status == 1) {
return layer.alert(checkRes.message);
}
$.post(
'/admin/setting/save',
data,
function(res) {
if(res.status != undefined && res.status == '0') {
return layer.msg(res.message, {icon: 1});
} else if(res.status != undefined) {
return layer.alert(res.message, {icon: 2});
} else {
return layer.alert('保存失败');
}
},
'json'
);
}
function checkData(data) {
$res = {
status: 1
};
if(data.title == undefined || data.title == "") {
$res.message = "站点名称不能为空";
return $res;
}
if(data.keywords == undefined || data.keywords == "") {
$res.message = "关键字不能为空";
return $res;
}
if(data.descs == undefined || data.descs == "") {
$res.message = "站点描述不能为空";
return $res;
}
if($res.message == undefined) {
$res.status = 0;
}
return $res;
}
</script>
</html>
- 2- 基础设置控制器
<?php
namespace App\Http\Controllers\admins;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
class Setting extends Controller {
public function index() {
$res = DB::table('setting')->where('names', 'site')->getFirst();
if($res) {
$data['site'] = json_decode($res['vals'], true);
} else {
$data['site'] = ['title' => '', 'keywords' => '', 'descs' => '', 'status' => 1];
}
return view('admins/setting/index', $data);
}
public function save(Request $req) {
$data = $req->all();
unset($data['_token']);
$data['status'] = (int) $data['status'];
$checkRes = $this->checkData($data);
if($checkRes['status'] > 0) {
return json_encode($checkRes);
}
$site['vals'] = json_encode($data);
$siteSetting = DB::table('setting')->where('names', 'site')->getFirst();
if($siteSetting) {
$res = DB::table('setting')->where('names', 'site')->update($site);
} else {
$site['names'] = 'site';
$res = DB::table('setting')->insert($site);
}
if($res) {
return json_encode(['status' => 0, 'message' => '保存成功']);
} else {
return json_encode(['status' => 1, 'message' => '保存失败']);
}
}
protected function checkData($data) {
$res['status'] = 1;
if($data['title'] == null || $data['title'] == '') {
$res['message'] = '站点名称不能为空';
return $res;
}
if($data['keywords'] == null || $data['keywords'] == '') {
$res['message'] = '关键字不能为空';
return $res;
}
if($data['descs'] == null || $data['keywords'] == '') {
$res['message'] = '站点描述不能为空';
return $res;
}
return ['status' => 0];
}
}
4.2 文章分类代码
1-文章分类视图
- 文章分类列表视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文章分类列表</title>
<link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
<script src="/static/plugin/layui/layui.js"></script>
<style>
body {
padding: 10px;
}
.opera-area {
text-align: right;
}
</style>
</head>
<body>
<div class="opera-area">
<span class="layui-btn layui-btn-success">添加</span>
</div>
<table class="layui-table">
<thead>
<tr>
<td>ID</td>
<td>分类名称</td>
<td>操作</td>
</tr>
</thead>
<tbody>
@if(!empty($cates))
@foreach($cates as $cate)
<tr>
<td>{{$cate['id']}}</td>
<td>{{$cate['title']}}</td>
<td style="width: 150px;">
<span class="layui-btn layui-btn-warm layui-btn-xs">修改</span>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="3">啥也没查到....</td>
</tr>
@endif
</tbody>
</table>
</body>
<script>
layui.use(['layer'], function() {
layer = layui.layer;
$ = layui.jquery;
$(".opera-area span").on('click', add)
});
function add(e) {
layer.open({
type: 2,
title: '添加文章分类',
shadeClose: false,
shade: 0.8,
area: ['400px', '200px'],
content: '/admin/article/add_cate'
});
}
</script>
</html>
- 添加文章分类视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>添加分类</title>
<link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
<script src="/static/plugin/layui/layui.js"></script>
<style>
body {
padding: 10px;
}
</style>
</head>
<body>
<div class="layui-form">
@csrf
<div class="layui-form-item">
<label for="title" class="layui-form-label">分类名称</label>
<div class="layui-input-inline">
<input type="text" name="title" id="title" class="layui-input" placeholder="请输入分类名称">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<span class="layui-btn layui-btn-success" onclick="save()">保存</span>
</div>
</div>
</div>
</body>
<script>
layui.use(['layer'], function() {
layer = layui.layer;
$ = layui.jquery;
});
function save() {
var title = $.trim($('#title').val());
if(title == undefined || title == '') {
return layer.alert('分类名称不能为空', {icon: 2});
}
$.post(
'/admin/article/save_cate',
{
_token: $('input[name="_token"]').val(),
title: title
},
function(res) {
if(res.status != undefined && res.status == "0") {
layer.msg(res.message, {icon: 1});
setTimeout(() => {
parent.window.location.reload();
}, 1000);
} else if(res.status != undefined) {
layer.alert(res.message, {icon: 2});
} else {
layer.alert('操作失败', {icon: 2});
}
},
'json'
);
}
</script>
</html>
- 2- 文章分类控制器跟文章控制器共用, 见4.3
4.3 文章管理代码
文章管理视图
- 1- 文章列表视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文章列表</title>
<link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
<script src="/static/plugin/layui/layui.js"></script>
</head>
<body style="padding: 10px;">
<div style="text-align: center; color: #666;">
<h2>管理员列表</h2>
</div>
<div style="text-align: right;">
<span class="layui-btn layui-btn-success" onclick="add()">新增</span>
</div>
<table class="layui-table">
<thead>
<tr>
<td>ID</td>
<td>文章分类</td>
<td>缩略图</td>
<td>文章标题</td>
<td>文章作者</td>
<td>浏览量</td>
<td>添加时间</td>
<td>状态</td>
<td>操作</td>
</tr>
</thead>
<tbody>
@if(!empty($articles))
@foreach($articles as $article)
<tr>
<td>{{$article['id']}}</td>
<td>{{$cates[$article['cid']]}}</td>
<td><img src="{{$article['thumb']}}"></td>
<td>{{$article['title']}}</td>
<td>{{$user[$article['auth_id']]}}</td>
<td>{{$article['pv']}}</td>
<td>{{date('Y-m-d H:i:s', $article['add_time'])}}</td>
<td>{{$article['status'] ? '已发布' : '草稿'}}</td>
<td>
<span class="layui-btn layui-btn-warm layui-btn-xs" onclick="edit({{$article['id']}})">修改</span>
<span class="layui-btn layui-btn-danger layui-btn-xs">删除</span>
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
<div id="paginate"></div>
</body>
<script>
layui.use(['layer', 'laypage'], function() {
layer = layui.layer;
laypage = layui.laypage;
laypage.render({
elem: 'paginate', // 注意,这里的 test1 是 ID,不用加 # 号
count: {{$total}}, // 查询到的记录总数,从数据库中获取
limit: {{$limit}}, // 每页容纳的记录数
curr: {{$currPage}}, // 当前页
first: '首页',
last: '尾页',
layout: ['first', 'prev', 'page', 'next', 'last', 'count', 'limit', 'refresh', 'skip'],
limits: [1, 5, 10, 15, 20],
jump: function(obj, first){
//obj包含了当前分页的所有参数,比如:
console.log(obj.curr); //得到当前页,以便向服务端请求对应页的数据。
console.log(obj.limit); //得到每页显示的条数
console.log(obj);
//首次不执行
if(!first){
window.location.href = "?limit=" + obj.limit + "&page=" + obj.curr;
}
}
});
});
function add() {
layer.open({
type: 2,
title: '新增文章',
shadeClose: false,
shade: 0.8,
area: ['100%', '100%'],
content: '/admin/article/add_article',
btn: ['保存'],
yes: function(index, layero) {
var body = layer.getChildFrame('body', index);
// 得到iframe页的窗口对象
var iframeWin = window[layero.find('iframe')[0]['name']];
// 执行iframe页的方法: iframeWin.要调用的方法名();
iframeWin.save();
}
});
}
function edit(id) {
layer.open({
type: 2,
title: '修改文章',
shadeClose: false,
shade: 0.8,
area: ['100%', '100%'],
content: '/admin/article/edit_article?id=' + id,
btn: ['保存'],
yes: function(index, layero) {
var body = layer.getChildFrame('body', index);
// 得到iframe页的窗口对象
var iframeWin = window[layero.find('iframe')[0]['name']];
// 执行iframe页的方法: iframeWin.要调用的方法名();
iframeWin.save();
}
});
}
</script>
</html>
- 2- 添加文章视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新增文章</title>
<link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
<script src="/static/plugin/layui/layui.js"></script>
<!-- ueditor-start -->
<!-- 配置文件 -->
<script type="text/javascript" src="/static/plugin/ueditor/ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="/static/plugin/ueditor/ueditor.all.js"></script>
<!-- ueditor-end -->
<style>
body {
padding: 10px;
}
.hide {
display: none;
}
</style>
</head>
<body>
<div class="layui-form">
@csrf
<div class="layui-form-item">
<label for="title" class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" class="layui-input" name="title" id="title">
</div>
</div>
<div class="layui-form-item">
<label for="subtitle" class="layui-form-label">副标题</label>
<div class="layui-input-block">
<input type="text" name="subtitle" id="subtitle" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label for="cid" class="layui-form-label">文章分类</label>
<div class="layui-input-block">
<select name="cid" id="cid" class="layui-input">
<option value=""></option>
@if(!empty($cates))
@foreach($cates as $key => $val)
<option value="{{$key}}">{{$val}}</option>
@endforeach
@endif
</select>
</div>
</div>
<div class="layui-form-item">
<label for="thumb" class="layui-form-label">缩略图</label>
<div class="layui-input-block">
<button type="button" class="layui-btn" id="btn_upload">
<i class="layui-icon"></i>上传图片
</button>
<img id="preview_img" src="" alt="" style="height: 36px;" onclick="big_img(this)">
</div>
</div>
<div class="layui-form-item">
<label for="keywords" class="layui-form-label">关键字</label>
<div class="layui-input-block">
<input type="text" name="keywords" id="keywords" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label for="descs" class="layui-form-label">文章描述</label>
<div class="layui-input-block">
<textarea name="descs" id="descs" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label for="status" class="layui-form-label">文章状态</label>
<div class="layui-input-block">
<input type="radio" name="status" id="status_0" value="0" title="草稿">
<input type="radio" name="status" id="status_1" value="1" title="发布" checked>
</div>
</div>
<div class="layui-form-item">
<label for="content" class="layui-form-label">文章正文</label>
<div class="layui-input-block">
<!-- 加载编辑器的容器 -->
<!-- 实际生成的是div. 给div添加contenteditable="true", 这个div就能编辑 -->
<script id="container" name="content" type="text/plain">这里写你的初始化内容</script>
</div>
</div>
</div>
<div id="tong" class="hide" >
<img src="" style="max-width: 100%; max-height: 100%">
</div>
</body>
<script>
layui.use(['form', 'layer', 'upload'], function() {
layer = layui.layer;
form = layui.form;
upload = layui.upload;
$ = layui.jquery;
// 执行实例
var uploadInst = upload.render({
elem: '#btn_upload', // 绑定元素
url: '/admin/upload/pic_upload', // 上传接口
data: {
_token: $('input[name="_token"]').val()
},
done: function(res) {
// 上传完毕回调
$('#preview_img').attr('src', res.data.src);
$('#tong > img').attr('src', res.data.src);
},
error: function() {
// 请求异常回调
}
});
// 实例化编辑器
/* 第二个参数是配置属性对象 */
/* 不要写var, 把ue声明为全局变量, 因为其他地方也用到ue对象 */
/* var */
ue = UE.getEditor('container', {
initialFrameWidth: '100%', //初始化编辑器宽度,默认1000
initialFrameHeight: '500' //初始化编辑器高度,默认320
});
});
function big_img(img) {
if (img.src == undefined || img.src == "") {
return;
}
//页面层-图片
layer.open({
type: 1,
title: false,
closeBtn: 0,
area: ['auto'],
skin: 'layui-layer-nobg', //没有背景色
shadeClose: true,
content: $('#tong')
});
}
function save() {
var data = {};
data._token = $('input[name="_token"]').val();
data.title = $.trim($('#title').val());
data.subtitle = $.trim($('#subtitle').val());
data.cid = parseInt($('#cid').val());
data.thumb = $.trim($('#preview_img').attr('src'));
data.status = $('input[name="status"]:checked').val();
data.descs = $.trim($('#descs').val());
data.keywords = $.trim($('#keywords').val());
data.content = ue.getContent();
if(data.title == '') {
return layer.alert('请填写文章标题', {icon: 2});
}
if(data.content == '') {
return layer.alert('请输入文章内容', {icon: 2});
}
if(isNaN(data.cid) || data.cid < 1) {
return layer.alert('请选择文章分类', {icon: 2});
}
$.ajax({
url: '/admin/article/save_article',
data: data,
type: 'POST',
async: true,
dataType: 'json',
success: function(res) {
if(res.status != undefined && res.status == '0') {
layer.msg(res.message, {icon: 1});
setTimeout(() => {
window.parent.location.reload();
}, 1000);
} else if(res.status != undefined) {
return layer.alert(res.message, {icon: 2});
} else {
return layer.alert('文章提交失败', {icon: 2});
}
}
});
}
</script>
</html>
- 3- 文章编辑视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改文章</title>
<link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
<script src="/static/plugin/layui/layui.js"></script>
<!-- ueditor-start -->
<!-- 配置文件 -->
<script type="text/javascript" src="/static/plugin/ueditor/ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="/static/plugin/ueditor/ueditor.all.js"></script>
<!-- ueditor-end -->
<style>
body {
padding: 10px;
}
.hide {
display: none;
}
</style>
</head>
<body>
<div class="layui-form">
@csrf
<input type="hidden" name="id" value="{{$article['id']}}">
<div class="layui-form-item">
<label for="title" class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" class="layui-input" name="title" id="title" value="{{$article['title']}}">
</div>
</div>
<div class="layui-form-item">
<label for="subtitle" class="layui-form-label">副标题</label>
<div class="layui-input-block">
<input type="text" name="subtitle" id="subtitle" class="layui-input" value="{{$article['subtitle']}}">
</div>
</div>
<div class="layui-form-item">
<label for="cid" class="layui-form-label">文章分类</label>
<div class="layui-input-block">
<select name="cid" id="cid" class="layui-input" value="{{$article['cid']}}">
<option value=""></option>
@if(!empty($cates))
@foreach($cates as $key => $val)
<option value="{{$key}}" {{$article['cid'] == $key ? 'selected' : ''}}>{{$val}}</option>
@endforeach
@endif
</select>
</div>
</div>
<div class="layui-form-item">
<label for="thumb" class="layui-form-label">缩略图</label>
<div class="layui-input-block">
<button type="button" class="layui-btn" id="btn_upload">
<i class="layui-icon"></i>上传图片
</button>
<img id="preview_img" src="{{$article['thumb']}}" alt="" style="height: 36px;" onclick="big_img(this)">
</div>
</div>
<div class="layui-form-item">
<label for="keywords" class="layui-form-label">关键字</label>
<div class="layui-input-block">
<input type="text" name="keywords" id="keywords" class="layui-input" value="{{$article['keywords']}}">
</div>
</div>
<div class="layui-form-item">
<label for="descs" class="layui-form-label">文章描述</label>
<div class="layui-input-block">
<textarea name="descs" id="descs" class="layui-textarea">{{$article['descs']}}</textarea>
</div>
</div>
<div class="layui-form-item">
<label for="status" class="layui-form-label">文章状态</label>
<div class="layui-input-block">
<input type="radio" name="status" id="status_0" value="0" title="草稿" {{$article['status'] == 0 ? 'checked' : ''}}>
<input type="radio" name="status" id="status_1" value="1" title="发布" {{$article['status'] == 1 ? 'checked' : ''}}>
</div>
</div>
<div class="layui-form-item">
<label for="content" class="layui-form-label">文章正文</label>
<div class="layui-input-block">
<!-- 加载编辑器的容器 -->
<!-- 实际生成的是div. 给div添加contenteditable="true", 这个div就能编辑 -->
<script id="container" name="content" type="text/plain">{!!$content!!}</script>
</div>
</div>
</div>
<div id="tong" class="hide" >
<img src="{{$article['thumb']}}" style="max-width: 100%; max-height: 100%">
</div>
</body>
<script>
layui.use(['form', 'layer', 'upload'], function() {
layer = layui.layer;
form = layui.form;
upload = layui.upload;
$ = layui.jquery;
// 执行实例
var uploadInst = upload.render({
elem: '#btn_upload', // 绑定元素
url: '/admin/upload/pic_upload', // 上传接口
data: {
_token: $('input[name="_token"]').val()
},
done: function(res) {
// 上传完毕回调
$('#preview_img').attr('src', res.data.src);
$('#tong > img').attr('src', res.data.src);
},
error: function() {
// 请求异常回调
}
});
// 实例化编辑器
/* 第二个参数是配置属性对象 */
/* 不要写var, 把ue声明为全局变量, 因为其他地方也用到ue对象 */
/* var */
ue = UE.getEditor('container', {
initialFrameWidth: '100%', //初始化编辑器宽度,默认1000
initialFrameHeight: '500' //初始化编辑器高度,默认320
});
});
// 点击上传的缩略图, 显示大图
function big_img(img) {
if (img.src == undefined || img.src == "") {
return;
}
//页面层-图片
layer.open({
type: 1,
title: false,
closeBtn: 0,
area: ['auto'],
skin: 'layui-layer-nobg', //没有背景色
shadeClose: true,
content: $('#tong')
});
}
function save() {
var data = {};
data._token = $('input[name="_token"]').val();
data.id = parseInt($('input[name="id"]').val());
data.title = $.trim($('#title').val());
data.subtitle = $.trim($('#subtitle').val());
data.cid = parseInt($('#cid').val());
data.thumb = $.trim($('#preview_img').attr('src'));
data.status = $('input[name="status"]:checked').val();
data.descs = $.trim($('#descs').val());
data.keywords = $.trim($('#keywords').val());
data.content = ue.getContent();
if(data.title == '') {
return layer.alert('请填写文章标题', {icon: 2});
}
if(data.content == '') {
return layer.alert('请输入文章内容', {icon: 2});
}
if(isNaN(data.cid) || data.cid < 1) {
return layer.alert('请选择文章分类', {icon: 2});
}
if(isNaN(data.id) || data.id < 1) {
return layer.alert('文章id不能为空', {icon: 2});
}
$.ajax({
url: '/admin/article/update_article',
data: data,
type: 'POST',
async: true,
dataType: 'json',
success: function(res) {
if(res.status != undefined && res.status == '0') {
layer.msg(res.message, {icon: 1});
setTimeout(() => {
window.parent.location.reload();
}, 1000);
} else if(res.status != undefined) {
return layer.alert(res.message, {icon: 2});
} else {
return layer.alert('文章更新失败', {icon: 2});
}
}
});
}
</script>
</html>
- 文章/文章分类控制器
<?php
namespace App\Http\Controllers\admins;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class Article extends Controller {
/**
* 文章列表
*/
public function index(Request $req) {
// 一页可容纳的记录数
$pageSize = empty($req->limit) ? 1 : $req->limit;
// 当前页
$currPage = empty($req->page) ? 1 : $req->page;
// dump($pageSize);dump($currPage);die;
$data['cates'] = DB::table('article_cate')->keyval('id', 'title');
$data['user'] = DB::table('admin')->keyval('id', 'real_name');
// $data['articles'] = DB::table('article')->lists();
/* 换成分页的方式(返回对象数组) */
// $data['articles'] = DB::table('article')->paginate(2);
/* 换成自己扩展的分页方式(返回二维数组) */
$pageData = DB::table('article')->orderby('id' , 'desc')->pages($pageSize);
// dump($pageData);die;
$data['articles'] = $pageData['page_data'];
$data['total'] = $pageData['total'];
$data['limit'] = $pageSize;
$data['currPage'] = $currPage;
return view('/admins/article/index', $data);
}
/**
* 跳转到新增文章界面
*/
public function addArticle() {
$data['cates'] = DB::table('article_cate')->keyval('id', 'title');
return view('admins/article/add_article', $data);
}
public function editArticle(Request $req) {
$data['cates'] = DB::table('article_cate')->keyval('id', 'title');
$id = (int) $req->id;
$data['article'] = DB::table('article')->where('id', $id)->getFirst();
if(!$data['article']) {
return json_encode(['status' => 1, 'message' => '无效的文章ID']);
}
$data['content'] = (DB::table('article_detail')->select('contents')->where('aid', $id)->getFirst())['contents'];
return view('/admins/article/edit_article', $data);
}
public function saveArticle(Request $req) {
$data['title'] = trim($req->title);
$data['subtitle'] = trim($req->subtitle);
$data['cid'] = (int) ($req->cid);
$data['thumb'] = trim($req->thumb);
$data['status'] = (int) $req->status;
$data['descs'] = trim($req->descs);
$data['keywords'] = trim($req->keywords);
$data['auth_id'] = $req->loginInfo->id;
$data['add_time'] = time();
$content = trim($req->content);
$id = DB::table('article')->insertGetId($data);
if($id) {
DB::table('article_detail')->insert(['aid' => $id, 'contents' => $content]);
return json_encode(['status' => 0, 'message' => '文章保存成功']);
}
return json_encode(['status' => 1, 'message' => '文章保存失败']);
}
public function updateArticle(Request $req) {
$data['id'] = (int) $req->id;
$data['title'] = trim($req->title);
$data['subtitle'] = trim($req->subtitle);
$data['cid'] = (int) ($req->cid);
$data['thumb'] = trim($req->thumb);
$data['status'] = (int) $req->status;
$data['descs'] = trim($req->descs);
$data['keywords'] = trim($req->keywords);
$data['auth_id'] = $req->loginInfo->id;
$data['add_time'] = time();
$content = trim($req->content);
$id = DB::table('article')->where('id', $data['id'])->update($data);
if($id) {
DB::table('article_detail')->where('aid', $data['id'])->update(['contents' => $content]);
return json_encode(['status' => 0, 'message' => '文章修改成功']);
}
return json_encode(['status' => 1, 'message' => '文章修改失败']);
}
/**
* 文章分类列表
*/
public function cates() {
// 查询所有分类
$data['cates'] = DB::table('article_cate')->lists();
return view('/admins/article/cates', $data);
}
/**
* 添加文章分类
*/
public function addCate(Request $req) {
return view('/admins/article/add_cate');
}
public function saveCate(Request $req) {
$title = $req->title;
$cate = DB::table('article_cate')->where('title', $title)->getFirst();
if($cate) {
return json_encode(['status' => 1, 'message' => '已存在同名分类, 请另命名分类后再重试']);
}
$res = DB::table('article_cate')->insertGetId(['title' => $title]);
if($res) {
return json_encode(['status' => 0, 'message' => '添加成功']);
}
return json_encode(['status' => 1, 'message' => '添加失败']);
}
}
- 处理文件上传的控制器
<?php
namespace App\Http\Controllers\admins;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class Upload extends Controller {
public function picUpload(Request $request) {
// file(请求中的文件参数名), store(文件存放子路径). 文件存放目录: ../storage/app/[store指定的子目录],
// 为能使用软连接访问上传的图片, 强烈建议子目录以public目录开始. 建议在public中分目录, 以年份, 或者年份月份,
// 或者年月日为public子目录来存放文件, 这样更方便查找. 也可以分得更细, 如: 精确到小时, 分钟等.
/* 此时返回的是.../public/avatars/2020/06/18/xxxxxx.png, 实际并不能以该路径来访问 */
$path = $request->file('file')->store('public/avatars/' . date('Y/m/d'));
// 获取前端能访问到的真实的url地址
$url = Storage::url($path);
// layui的上传组件要求返回的json数据的格式
$res = ['code' => 0, 'msg' => '', 'data' => ['src' => $url]];
return json_encode($res);
}
}