博客列表 >laravel实战-通用后台管理系统-基础设置/文章分类/文章分页列表/图片上传和嵌入富文本编辑器

laravel实战-通用后台管理系统-基础设置/文章分类/文章分页列表/图片上传和嵌入富文本编辑器

岂几岂几
岂几岂几原创
2020年06月27日 00:25:371099浏览

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() . “宏扩展”介绍点这里.

  1. /**
  2. * 扩展laravel的DB类,把查询分页的结果由对象数组改为二维数组
  3. * $perPage: 每页行数
  4. * $columns: 查询的列
  5. * $pageName: 请求参数中指定当前页的参数名
  6. * $page: 当前页
  7. */
  8. QueryBuilder::macro('pages', function($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) {
  9. /* 查询分页对象 */
  10. $paginateObj = $this->paginate($perPage, $columns, $pageName, $page);
  11. /* 查询结果对象数组 */
  12. $items = $paginateObj->items();
  13. $rtn = [];
  14. // 遍历对象数组,把对象强转成数组,后作为$rtn的元素
  15. foreach($items as $item) {
  16. $rtn[] = (array) $item;
  17. }
  18. return ['page_data' => $rtn, 'total' => $paginateObj->total()];
  19. });
  • layui提供的分页组件 laypage 渲染分页条.

    • 在布局中给要布局分页条的地方添加一个容器, 并给个id值: <div id="paginate"></div>

    • layui.use() 方法的参数回调中, 渲染分页条.

  1. <script>
  2. layui.use(['layer', 'laypage'], function() {
  3. layer = layui.layer;
  4. laypage = layui.laypage;
  5. laypage.render({
  6. elem: 'paginate', // 注意,这里的 test1 是 ID,不用加 # 号
  7. count: {
  8. {
  9. $total
  10. }
  11. }, // 查询到的记录总数,从数据库中获取
  12. limit: {
  13. {
  14. $limit
  15. }
  16. }, // 每页容纳的记录数
  17. curr: {
  18. {
  19. $currPage
  20. }
  21. }, // 当前页
  22. layout: ['prev', 'page', 'next', 'count', 'limit', 'refresh', 'skip'], // 要显示的分页条各部分, 分别是: ['上一页', '页码表', '下一页', '总页数', '每页显示记录数下拉列表', '刷新当前页', '页定位表单'], 可根据需要增删数组成员.
  23. limits: [1, 5, 10, 15, 20], // 每页显示记录数下拉列表项
  24. jump: function(obj, first) { // 获取指定页码数据的回调
  25. //obj包含了当前分页的所有参数,比如:
  26. console.log(obj.curr); //得到当前页,以便向服务端请求对应页的数据。
  27. console.log(obj.limit); //得到每页显示的条数
  28. console.log(obj);
  29. //首次不执行
  30. if (!first) {
  31. window.location.href = "?limit=" + obj.limit + "&page=" + obj.curr;
  32. }
  33. }
  34. });
  35. });
  36. </script>

2. 文章配图上传

  • 使用layui提供的上传组件 upload 处理前端显示和数据提交.

    • 在页面布局中加入一个button, 给button设置id值
  1. <div class="layui-form-item">
  2. <label for="thumb" class="layui-form-label">缩略图</label>
  3. <div class="layui-input-block">
  4. /* 上传按钮 */
  5. <button type="button" class="layui-btn" id="btn_upload">
  6. <i class="layui-icon">&#xe67c;</i>上传图片
  7. </button>
  8. /* 上传成功后显示上传图片的缩略图 */
  9. <img id="preview_img" src="" alt="" style="height: 36px;" onclick="big_img(this)">/* 点击显示大图 */
  10. </div>
  11. </div>
  • layui.use() 方法的参数回调中, 渲染文件上传组件.
  1. <script>
  2. layui.use(['form', 'layer', 'upload'], function() {
  3. layer = layui.layer;
  4. form = layui.form;
  5. upload = layui.upload;
  6. $ = layui.jquery;
  7. // 执行实例
  8. var uploadInst = upload.render({
  9. elem: '#btn_upload', // 绑定元素
  10. url: '/admin/upload/pic_upload', // 上传接口
  11. data: {
  12. _token: $('input[name="_token"]').val()
  13. }, // 额外需要上传的参数
  14. done: function(res) {
  15. // 上传完毕回调
  16. $('#preview_img').attr('src', res.data.src);
  17. // 点击缩略图看大图中的大图
  18. $('#tong > img').attr('src', res.data.src);
  19. },
  20. error: function() {
  21. // 请求异常回调
  22. }
  23. });
  24. }
  25. </script>
  • 后端使用laravel提供的 $request 对象处理上传.

    • 通过chrome的开发者工具中查看layui的 upload 插件提交的数据, 存放文件的参数名叫 file .

    • 在控制器方法中, 执行 $path = $request->file('file')->store(相对/storage/app/路径的子目录); 语句就能完成PHP原生上传的N大步骤. file() 方法的参数, 就是上面提到的参数名 file .

  1. public function picUpload(Request $request) {
  2. // file(请求中的文件参数名), store(文件存放子路径). 文件存放目录: ../storage/app/[store指定的子目录],
  3. // 为能使用软连接访问上传的图片, 强烈建议子目录以public目录开始. 建议在public中分目录, 以年份, 或者年份月份,
  4. // 或者年月日为public子目录来存放文件, 这样更方便查找. 也可以分得更细, 如: 精确到小时, 分钟等.
  5. /* 此时返回的是.../public/avatars/2020/06/18/xxxxxx.png, 实际并不能以该路径来访问 */
  6. $path = $request->file('file')->store('public/avatars/' . date('Y/m/d'));
  7. // 获取前端能访问到的真实的url地址
  8. $url = Storage::url($path);
  9. // layui的上传组件要求返回的json数据的格式
  10. $res = ['code' => 0, 'msg' => '', 'data' => ['src' => $url]];
  11. return json_encode($res);
  12. }
  • /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> 元素, 默认不显示

  1. <div id="tong" class="hide">
  2. <img src="" style="max-width: 100%; max-height: 100%">
  3. </div>
  • 点击缩略图显示大图的js处理脚本(弹出显示大图, 使用 layui.open() 方法, type属性值为1, 就表示弹出显示的元素是HTML元素容器)
  1. function big_img(img) {
  2. // 缩略图的src属性没有值时, 不处理
  3. if (img.src == undefined || img.src == "") {
  4. return;
  5. }
  6. //页面层-图片
  7. layer.open({
  8. type: 1,
  9. title: false,
  10. closeBtn: 0,
  11. area: ['auto'],
  12. skin: 'layui-layer-nobg', //没有背景色
  13. shadeClose: true,
  14. content: $('#tong')
  15. });
  16. }

3. 嵌入富文本编辑器

  • 百度的 Ueditor 是比较流行的富文本编辑器. 官网地址点这里. 在官网下载合适的 Ueditor 文件包.

  • 下载下来的 Ueditor 文件包, 放到 /public 目录中, 一般来说, 放在 /public/static/plugin/ 下较好.

  • 前端页面加载 Ueditor :

  1. <!-- ueditor-start -->
  2. <!-- 配置文件 -->
  3. <script type="text/javascript" src="/static/plugin/ueditor/ueditor.config.js"></script>
  4. <!-- 编辑器源码文件 -->
  5. <script type="text/javascript" src="/static/plugin/ueditor/ueditor.all.js"></script>
  6. <!-- ueditor-end -->
  • 在需要嵌入 Ueditor 的地方, 放一个加载编辑器的容器:
  1. <!-- 实际生成的是div. 给div添加contenteditable="true", 这个div就能编辑 -->
  2. <script id="container" name="content" type="text/plain">这里写你的初始化内容</script>
  • js代码实例化编辑器
  1. // 实例化编辑器
  2. // 第一个参数是容器的id值
  3. /* 第二个参数是配置属性对象 */
  4. /* 不要写var, 把ue声明为全局变量, 因为其他地方也用到ue对象 */
  5. /* var */
  6. ue = UE.getEditor('container', {
  7. initialFrameWidth: '100%', //初始化编辑器宽度,默认1000
  8. initialFrameHeight: '500' //初始化编辑器高度,默认320
  9. });
  • 经过上面几步骤, 就可以把 Ueditor 嵌入到网页中了.

  • js获取 Ueditor 中的内容, 使用 ue.getContent() 方法. 而设置 Ueditor 中的内容, 一般来说, 都是使用HTML标签和css来设置格式的, 所以用 {!!$content!!}Ueditor 中设置内容.

4. 代码清单

4.1 基础设置

  • 1-基础设置视图
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>站点设置</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {
  11. padding: 10px;
  12. }
  13. /* grid实现label固定宽度, 输入框充满剩下的宽度 */
  14. .site-name {
  15. display: grid;
  16. grid-template-columns: 110px auto;
  17. }
  18. .site-name > .layui-input-inline {
  19. margin: 0;
  20. width: 100%;
  21. /* box-sizing: border-box;
  22. padding-right: 10px; */
  23. }
  24. .site-name > .layui-input-inline > input{
  25. margin-right: 10px;
  26. }
  27. /* 用定位方式实现label固定宽度, 输入框充满剩下的宽度 */
  28. .site-keywords {
  29. position: relative;
  30. }
  31. .site-keywords > label {
  32. width: 110px;
  33. box-sizing: border-box;
  34. }
  35. .site-keywords > .layui-input-inline {
  36. position: absolute;
  37. width: unset;
  38. left: 110px;
  39. right: 0;
  40. margin: 0;
  41. }
  42. /* .site-keywords > .layui-input-inline input {
  43. width: 100%;
  44. } */
  45. /* 用flex布局实现 */
  46. .site-desc {
  47. display: flex;
  48. flex-flow: row nowrap;
  49. }
  50. .site-desc > .layui-form-label {
  51. width: 124px;
  52. box-sizing: border-box;
  53. }
  54. .site-desc > .layui-input-inline {
  55. width: 100%;
  56. margin: 0;
  57. }
  58. .site-desc > .layui-input-inline >textarea {
  59. box-sizing: border-box;
  60. /* border: 0; */
  61. margin: 0;
  62. }
  63. </style>
  64. </head>
  65. <body>
  66. <div class="layui-row">
  67. <div class="layui-col-lg6 layui-col-md8 layui-col-sm10 layui-col-xs12">
  68. <div class="layui-form site-form">
  69. @csrf
  70. <div class="layui-form-item site-name">
  71. <label for="title" class="layui-form-label">站点名称</label>
  72. <div class="layui-input-inline">
  73. <input type="text" name="title" id="title" class="layui-input" value="{{$site['title']}}">
  74. </div>
  75. </div>
  76. <div class="layui-form-item site-keywords">
  77. <label for="keywords" class="layui-form-label">关键字</label>
  78. <div class="layui-input-inline">
  79. <input type="text" name="keywords" id="keywords" class="layui-input" value="{{$site['keywords']}}">
  80. </div>
  81. </div>
  82. <div class="layui-form-item site-desc">
  83. <label for="descs" class="layui-form-label">描述</label>
  84. <div class="layui-input-inline">
  85. <textarea name="descs" id="descs" placeholder="请输入内容" class="layui-textarea">{{$site['descs']}}</textarea>
  86. </div>
  87. </div>
  88. <div class="layui-form-item">
  89. <label for="status" class="layui-form-label">站点状态</label>
  90. <div class="layui-input-inline">
  91. <input type="checkbox" name="status" id="status" lay-skin="switch" lay-text="启用|停用" {{$site['status'] ? '' : 'checked'}}>
  92. </div>
  93. </div>
  94. <div class="layui-form-item">
  95. <div class="layui-input-block">
  96. <button:button class="layui-btn layui-btn-success" onclick="save()">保存</button:button>
  97. </div>
  98. </div>
  99. </div>
  100. </div>
  101. </div>
  102. </body>
  103. <script>
  104. layui.use(['form', 'layer'], function() {
  105. var form = layui.form;
  106. var layer = layui.layer;
  107. $ = layui.jquery;
  108. });
  109. function save() {
  110. var data = {
  111. _token : $('input[name="_token"]').val(),
  112. title : $.trim($('#title').val()),
  113. keywords : $.trim($('#keywords').val()),
  114. descs : $.trim($('#descs').val()),
  115. status : Boolean($('#status').prop('checked')) ? 0 : 1
  116. };
  117. var checkRes = checkData(data);
  118. if(checkRes.status == 1) {
  119. return layer.alert(checkRes.message);
  120. }
  121. $.post(
  122. '/admin/setting/save',
  123. data,
  124. function(res) {
  125. if(res.status != undefined && res.status == '0') {
  126. return layer.msg(res.message, {icon: 1});
  127. } else if(res.status != undefined) {
  128. return layer.alert(res.message, {icon: 2});
  129. } else {
  130. return layer.alert('保存失败');
  131. }
  132. },
  133. 'json'
  134. );
  135. }
  136. function checkData(data) {
  137. $res = {
  138. status: 1
  139. };
  140. if(data.title == undefined || data.title == "") {
  141. $res.message = "站点名称不能为空";
  142. return $res;
  143. }
  144. if(data.keywords == undefined || data.keywords == "") {
  145. $res.message = "关键字不能为空";
  146. return $res;
  147. }
  148. if(data.descs == undefined || data.descs == "") {
  149. $res.message = "站点描述不能为空";
  150. return $res;
  151. }
  152. if($res.message == undefined) {
  153. $res.status = 0;
  154. }
  155. return $res;
  156. }
  157. </script>
  158. </html>
  • 2- 基础设置控制器
  1. <?php
  2. namespace App\Http\Controllers\admins;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Http\Request;
  6. class Setting extends Controller {
  7. public function index() {
  8. $res = DB::table('setting')->where('names', 'site')->getFirst();
  9. if($res) {
  10. $data['site'] = json_decode($res['vals'], true);
  11. } else {
  12. $data['site'] = ['title' => '', 'keywords' => '', 'descs' => '', 'status' => 1];
  13. }
  14. return view('admins/setting/index', $data);
  15. }
  16. public function save(Request $req) {
  17. $data = $req->all();
  18. unset($data['_token']);
  19. $data['status'] = (int) $data['status'];
  20. $checkRes = $this->checkData($data);
  21. if($checkRes['status'] > 0) {
  22. return json_encode($checkRes);
  23. }
  24. $site['vals'] = json_encode($data);
  25. $siteSetting = DB::table('setting')->where('names', 'site')->getFirst();
  26. if($siteSetting) {
  27. $res = DB::table('setting')->where('names', 'site')->update($site);
  28. } else {
  29. $site['names'] = 'site';
  30. $res = DB::table('setting')->insert($site);
  31. }
  32. if($res) {
  33. return json_encode(['status' => 0, 'message' => '保存成功']);
  34. } else {
  35. return json_encode(['status' => 1, 'message' => '保存失败']);
  36. }
  37. }
  38. protected function checkData($data) {
  39. $res['status'] = 1;
  40. if($data['title'] == null || $data['title'] == '') {
  41. $res['message'] = '站点名称不能为空';
  42. return $res;
  43. }
  44. if($data['keywords'] == null || $data['keywords'] == '') {
  45. $res['message'] = '关键字不能为空';
  46. return $res;
  47. }
  48. if($data['descs'] == null || $data['keywords'] == '') {
  49. $res['message'] = '站点描述不能为空';
  50. return $res;
  51. }
  52. return ['status' => 0];
  53. }
  54. }

4.2 文章分类代码

  • 1-文章分类视图

    • 文章分类列表视图
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>文章分类列表</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {
  11. padding: 10px;
  12. }
  13. .opera-area {
  14. text-align: right;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <div class="opera-area">
  20. <span class="layui-btn layui-btn-success">添加</span>
  21. </div>
  22. <table class="layui-table">
  23. <thead>
  24. <tr>
  25. <td>ID</td>
  26. <td>分类名称</td>
  27. <td>操作</td>
  28. </tr>
  29. </thead>
  30. <tbody>
  31. @if(!empty($cates))
  32. @foreach($cates as $cate)
  33. <tr>
  34. <td>{{$cate['id']}}</td>
  35. <td>{{$cate['title']}}</td>
  36. <td style="width: 150px;">
  37. <span class="layui-btn layui-btn-warm layui-btn-xs">修改</span>
  38. </td>
  39. </tr>
  40. @endforeach
  41. @else
  42. <tr>
  43. <td colspan="3">啥也没查到....</td>
  44. </tr>
  45. @endif
  46. </tbody>
  47. </table>
  48. </body>
  49. <script>
  50. layui.use(['layer'], function() {
  51. layer = layui.layer;
  52. $ = layui.jquery;
  53. $(".opera-area span").on('click', add)
  54. });
  55. function add(e) {
  56. layer.open({
  57. type: 2,
  58. title: '添加文章分类',
  59. shadeClose: false,
  60. shade: 0.8,
  61. area: ['400px', '200px'],
  62. content: '/admin/article/add_cate'
  63. });
  64. }
  65. </script>
  66. </html>
  • 添加文章分类视图
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>添加分类</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {
  11. padding: 10px;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <div class="layui-form">
  17. @csrf
  18. <div class="layui-form-item">
  19. <label for="title" class="layui-form-label">分类名称</label>
  20. <div class="layui-input-inline">
  21. <input type="text" name="title" id="title" class="layui-input" placeholder="请输入分类名称">
  22. </div>
  23. </div>
  24. <div class="layui-form-item">
  25. <div class="layui-input-block">
  26. <span class="layui-btn layui-btn-success" onclick="save()">保存</span>
  27. </div>
  28. </div>
  29. </div>
  30. </body>
  31. <script>
  32. layui.use(['layer'], function() {
  33. layer = layui.layer;
  34. $ = layui.jquery;
  35. });
  36. function save() {
  37. var title = $.trim($('#title').val());
  38. if(title == undefined || title == '') {
  39. return layer.alert('分类名称不能为空', {icon: 2});
  40. }
  41. $.post(
  42. '/admin/article/save_cate',
  43. {
  44. _token: $('input[name="_token"]').val(),
  45. title: title
  46. },
  47. function(res) {
  48. if(res.status != undefined && res.status == "0") {
  49. layer.msg(res.message, {icon: 1});
  50. setTimeout(() => {
  51. parent.window.location.reload();
  52. }, 1000);
  53. } else if(res.status != undefined) {
  54. layer.alert(res.message, {icon: 2});
  55. } else {
  56. layer.alert('操作失败', {icon: 2});
  57. }
  58. },
  59. 'json'
  60. );
  61. }
  62. </script>
  63. </html>
  • 2- 文章分类控制器跟文章控制器共用, 见4.3

4.3 文章管理代码

  • 文章管理视图

    • 1- 文章列表视图
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>文章列表</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. </head>
  10. <body style="padding: 10px;">
  11. <div style="text-align: center; color: #666;">
  12. <h2>管理员列表</h2>
  13. </div>
  14. <div style="text-align: right;">
  15. <span class="layui-btn layui-btn-success" onclick="add()">新增</span>
  16. </div>
  17. <table class="layui-table">
  18. <thead>
  19. <tr>
  20. <td>ID</td>
  21. <td>文章分类</td>
  22. <td>缩略图</td>
  23. <td>文章标题</td>
  24. <td>文章作者</td>
  25. <td>浏览量</td>
  26. <td>添加时间</td>
  27. <td>状态</td>
  28. <td>操作</td>
  29. </tr>
  30. </thead>
  31. <tbody>
  32. @if(!empty($articles))
  33. @foreach($articles as $article)
  34. <tr>
  35. <td>{{$article['id']}}</td>
  36. <td>{{$cates[$article['cid']]}}</td>
  37. <td><img src="{{$article['thumb']}}"></td>
  38. <td>{{$article['title']}}</td>
  39. <td>{{$user[$article['auth_id']]}}</td>
  40. <td>{{$article['pv']}}</td>
  41. <td>{{date('Y-m-d H:i:s', $article['add_time'])}}</td>
  42. <td>{{$article['status'] ? '已发布' : '草稿'}}</td>
  43. <td>
  44. <span class="layui-btn layui-btn-warm layui-btn-xs" onclick="edit({{$article['id']}})">修改</span>
  45. <span class="layui-btn layui-btn-danger layui-btn-xs">删除</span>
  46. </td>
  47. </tr>
  48. @endforeach
  49. @endif
  50. </tbody>
  51. </table>
  52. <div id="paginate"></div>
  53. </body>
  54. <script>
  55. layui.use(['layer', 'laypage'], function() {
  56. layer = layui.layer;
  57. laypage = layui.laypage;
  58. laypage.render({
  59. elem: 'paginate', // 注意,这里的 test1 是 ID,不用加 # 号
  60. count: {{$total}}, // 查询到的记录总数,从数据库中获取
  61. limit: {{$limit}}, // 每页容纳的记录数
  62. curr: {{$currPage}}, // 当前页
  63. first: '首页',
  64. last: '尾页',
  65. layout: ['first', 'prev', 'page', 'next', 'last', 'count', 'limit', 'refresh', 'skip'],
  66. limits: [1, 5, 10, 15, 20],
  67. jump: function(obj, first){
  68. //obj包含了当前分页的所有参数,比如:
  69. console.log(obj.curr); //得到当前页,以便向服务端请求对应页的数据。
  70. console.log(obj.limit); //得到每页显示的条数
  71. console.log(obj);
  72. //首次不执行
  73. if(!first){
  74. window.location.href = "?limit=" + obj.limit + "&page=" + obj.curr;
  75. }
  76. }
  77. });
  78. });
  79. function add() {
  80. layer.open({
  81. type: 2,
  82. title: '新增文章',
  83. shadeClose: false,
  84. shade: 0.8,
  85. area: ['100%', '100%'],
  86. content: '/admin/article/add_article',
  87. btn: ['保存'],
  88. yes: function(index, layero) {
  89. var body = layer.getChildFrame('body', index);
  90. // 得到iframe页的窗口对象
  91. var iframeWin = window[layero.find('iframe')[0]['name']];
  92. // 执行iframe页的方法: iframeWin.要调用的方法名();
  93. iframeWin.save();
  94. }
  95. });
  96. }
  97. function edit(id) {
  98. layer.open({
  99. type: 2,
  100. title: '修改文章',
  101. shadeClose: false,
  102. shade: 0.8,
  103. area: ['100%', '100%'],
  104. content: '/admin/article/edit_article?id=' + id,
  105. btn: ['保存'],
  106. yes: function(index, layero) {
  107. var body = layer.getChildFrame('body', index);
  108. // 得到iframe页的窗口对象
  109. var iframeWin = window[layero.find('iframe')[0]['name']];
  110. // 执行iframe页的方法: iframeWin.要调用的方法名();
  111. iframeWin.save();
  112. }
  113. });
  114. }
  115. </script>
  116. </html>
  • 2- 添加文章视图
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>新增文章</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <!-- ueditor-start -->
  10. <!-- 配置文件 -->
  11. <script type="text/javascript" src="/static/plugin/ueditor/ueditor.config.js"></script>
  12. <!-- 编辑器源码文件 -->
  13. <script type="text/javascript" src="/static/plugin/ueditor/ueditor.all.js"></script>
  14. <!-- ueditor-end -->
  15. <style>
  16. body {
  17. padding: 10px;
  18. }
  19. .hide {
  20. display: none;
  21. }
  22. </style>
  23. </head>
  24. <body>
  25. <div class="layui-form">
  26. @csrf
  27. <div class="layui-form-item">
  28. <label for="title" class="layui-form-label">标题</label>
  29. <div class="layui-input-block">
  30. <input type="text" class="layui-input" name="title" id="title">
  31. </div>
  32. </div>
  33. <div class="layui-form-item">
  34. <label for="subtitle" class="layui-form-label">副标题</label>
  35. <div class="layui-input-block">
  36. <input type="text" name="subtitle" id="subtitle" class="layui-input">
  37. </div>
  38. </div>
  39. <div class="layui-form-item">
  40. <label for="cid" class="layui-form-label">文章分类</label>
  41. <div class="layui-input-block">
  42. <select name="cid" id="cid" class="layui-input">
  43. <option value=""></option>
  44. @if(!empty($cates))
  45. @foreach($cates as $key => $val)
  46. <option value="{{$key}}">{{$val}}</option>
  47. @endforeach
  48. @endif
  49. </select>
  50. </div>
  51. </div>
  52. <div class="layui-form-item">
  53. <label for="thumb" class="layui-form-label">缩略图</label>
  54. <div class="layui-input-block">
  55. <button type="button" class="layui-btn" id="btn_upload">
  56. <i class="layui-icon">&#xe67c;</i>上传图片
  57. </button>
  58. <img id="preview_img" src="" alt="" style="height: 36px;" onclick="big_img(this)">
  59. </div>
  60. </div>
  61. <div class="layui-form-item">
  62. <label for="keywords" class="layui-form-label">关键字</label>
  63. <div class="layui-input-block">
  64. <input type="text" name="keywords" id="keywords" class="layui-input">
  65. </div>
  66. </div>
  67. <div class="layui-form-item">
  68. <label for="descs" class="layui-form-label">文章描述</label>
  69. <div class="layui-input-block">
  70. <textarea name="descs" id="descs" class="layui-textarea"></textarea>
  71. </div>
  72. </div>
  73. <div class="layui-form-item">
  74. <label for="status" class="layui-form-label">文章状态</label>
  75. <div class="layui-input-block">
  76. <input type="radio" name="status" id="status_0" value="0" title="草稿">
  77. <input type="radio" name="status" id="status_1" value="1" title="发布" checked>
  78. </div>
  79. </div>
  80. <div class="layui-form-item">
  81. <label for="content" class="layui-form-label">文章正文</label>
  82. <div class="layui-input-block">
  83. <!-- 加载编辑器的容器 -->
  84. <!-- 实际生成的是div. 给div添加contenteditable="true", 这个div就能编辑 -->
  85. <script id="container" name="content" type="text/plain">这里写你的初始化内容</script>
  86. </div>
  87. </div>
  88. </div>
  89. <div id="tong" class="hide" >
  90. <img src="" style="max-width: 100%; max-height: 100%">
  91. </div>
  92. </body>
  93. <script>
  94. layui.use(['form', 'layer', 'upload'], function() {
  95. layer = layui.layer;
  96. form = layui.form;
  97. upload = layui.upload;
  98. $ = layui.jquery;
  99. // 执行实例
  100. var uploadInst = upload.render({
  101. elem: '#btn_upload', // 绑定元素
  102. url: '/admin/upload/pic_upload', // 上传接口
  103. data: {
  104. _token: $('input[name="_token"]').val()
  105. },
  106. done: function(res) {
  107. // 上传完毕回调
  108. $('#preview_img').attr('src', res.data.src);
  109. $('#tong > img').attr('src', res.data.src);
  110. },
  111. error: function() {
  112. // 请求异常回调
  113. }
  114. });
  115. // 实例化编辑器
  116. /* 第二个参数是配置属性对象 */
  117. /* 不要写var, 把ue声明为全局变量, 因为其他地方也用到ue对象 */
  118. /* var */
  119. ue = UE.getEditor('container', {
  120. initialFrameWidth: '100%', //初始化编辑器宽度,默认1000
  121. initialFrameHeight: '500' //初始化编辑器高度,默认320
  122. });
  123. });
  124. function big_img(img) {
  125. if (img.src == undefined || img.src == "") {
  126. return;
  127. }
  128. //页面层-图片
  129. layer.open({
  130. type: 1,
  131. title: false,
  132. closeBtn: 0,
  133. area: ['auto'],
  134. skin: 'layui-layer-nobg', //没有背景色
  135. shadeClose: true,
  136. content: $('#tong')
  137. });
  138. }
  139. function save() {
  140. var data = {};
  141. data._token = $('input[name="_token"]').val();
  142. data.title = $.trim($('#title').val());
  143. data.subtitle = $.trim($('#subtitle').val());
  144. data.cid = parseInt($('#cid').val());
  145. data.thumb = $.trim($('#preview_img').attr('src'));
  146. data.status = $('input[name="status"]:checked').val();
  147. data.descs = $.trim($('#descs').val());
  148. data.keywords = $.trim($('#keywords').val());
  149. data.content = ue.getContent();
  150. if(data.title == '') {
  151. return layer.alert('请填写文章标题', {icon: 2});
  152. }
  153. if(data.content == '') {
  154. return layer.alert('请输入文章内容', {icon: 2});
  155. }
  156. if(isNaN(data.cid) || data.cid < 1) {
  157. return layer.alert('请选择文章分类', {icon: 2});
  158. }
  159. $.ajax({
  160. url: '/admin/article/save_article',
  161. data: data,
  162. type: 'POST',
  163. async: true,
  164. dataType: 'json',
  165. success: function(res) {
  166. if(res.status != undefined && res.status == '0') {
  167. layer.msg(res.message, {icon: 1});
  168. setTimeout(() => {
  169. window.parent.location.reload();
  170. }, 1000);
  171. } else if(res.status != undefined) {
  172. return layer.alert(res.message, {icon: 2});
  173. } else {
  174. return layer.alert('文章提交失败', {icon: 2});
  175. }
  176. }
  177. });
  178. }
  179. </script>
  180. </html>
  • 3- 文章编辑视图
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>修改文章</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <!-- ueditor-start -->
  10. <!-- 配置文件 -->
  11. <script type="text/javascript" src="/static/plugin/ueditor/ueditor.config.js"></script>
  12. <!-- 编辑器源码文件 -->
  13. <script type="text/javascript" src="/static/plugin/ueditor/ueditor.all.js"></script>
  14. <!-- ueditor-end -->
  15. <style>
  16. body {
  17. padding: 10px;
  18. }
  19. .hide {
  20. display: none;
  21. }
  22. </style>
  23. </head>
  24. <body>
  25. <div class="layui-form">
  26. @csrf
  27. <input type="hidden" name="id" value="{{$article['id']}}">
  28. <div class="layui-form-item">
  29. <label for="title" class="layui-form-label">标题</label>
  30. <div class="layui-input-block">
  31. <input type="text" class="layui-input" name="title" id="title" value="{{$article['title']}}">
  32. </div>
  33. </div>
  34. <div class="layui-form-item">
  35. <label for="subtitle" class="layui-form-label">副标题</label>
  36. <div class="layui-input-block">
  37. <input type="text" name="subtitle" id="subtitle" class="layui-input" value="{{$article['subtitle']}}">
  38. </div>
  39. </div>
  40. <div class="layui-form-item">
  41. <label for="cid" class="layui-form-label">文章分类</label>
  42. <div class="layui-input-block">
  43. <select name="cid" id="cid" class="layui-input" value="{{$article['cid']}}">
  44. <option value=""></option>
  45. @if(!empty($cates))
  46. @foreach($cates as $key => $val)
  47. <option value="{{$key}}" {{$article['cid'] == $key ? 'selected' : ''}}>{{$val}}</option>
  48. @endforeach
  49. @endif
  50. </select>
  51. </div>
  52. </div>
  53. <div class="layui-form-item">
  54. <label for="thumb" class="layui-form-label">缩略图</label>
  55. <div class="layui-input-block">
  56. <button type="button" class="layui-btn" id="btn_upload">
  57. <i class="layui-icon">&#xe67c;</i>上传图片
  58. </button>
  59. <img id="preview_img" src="{{$article['thumb']}}" alt="" style="height: 36px;" onclick="big_img(this)">
  60. </div>
  61. </div>
  62. <div class="layui-form-item">
  63. <label for="keywords" class="layui-form-label">关键字</label>
  64. <div class="layui-input-block">
  65. <input type="text" name="keywords" id="keywords" class="layui-input" value="{{$article['keywords']}}">
  66. </div>
  67. </div>
  68. <div class="layui-form-item">
  69. <label for="descs" class="layui-form-label">文章描述</label>
  70. <div class="layui-input-block">
  71. <textarea name="descs" id="descs" class="layui-textarea">{{$article['descs']}}</textarea>
  72. </div>
  73. </div>
  74. <div class="layui-form-item">
  75. <label for="status" class="layui-form-label">文章状态</label>
  76. <div class="layui-input-block">
  77. <input type="radio" name="status" id="status_0" value="0" title="草稿" {{$article['status'] == 0 ? 'checked' : ''}}>
  78. <input type="radio" name="status" id="status_1" value="1" title="发布" {{$article['status'] == 1 ? 'checked' : ''}}>
  79. </div>
  80. </div>
  81. <div class="layui-form-item">
  82. <label for="content" class="layui-form-label">文章正文</label>
  83. <div class="layui-input-block">
  84. <!-- 加载编辑器的容器 -->
  85. <!-- 实际生成的是div. 给div添加contenteditable="true", 这个div就能编辑 -->
  86. <script id="container" name="content" type="text/plain">{!!$content!!}</script>
  87. </div>
  88. </div>
  89. </div>
  90. <div id="tong" class="hide" >
  91. <img src="{{$article['thumb']}}" style="max-width: 100%; max-height: 100%">
  92. </div>
  93. </body>
  94. <script>
  95. layui.use(['form', 'layer', 'upload'], function() {
  96. layer = layui.layer;
  97. form = layui.form;
  98. upload = layui.upload;
  99. $ = layui.jquery;
  100. // 执行实例
  101. var uploadInst = upload.render({
  102. elem: '#btn_upload', // 绑定元素
  103. url: '/admin/upload/pic_upload', // 上传接口
  104. data: {
  105. _token: $('input[name="_token"]').val()
  106. },
  107. done: function(res) {
  108. // 上传完毕回调
  109. $('#preview_img').attr('src', res.data.src);
  110. $('#tong > img').attr('src', res.data.src);
  111. },
  112. error: function() {
  113. // 请求异常回调
  114. }
  115. });
  116. // 实例化编辑器
  117. /* 第二个参数是配置属性对象 */
  118. /* 不要写var, 把ue声明为全局变量, 因为其他地方也用到ue对象 */
  119. /* var */
  120. ue = UE.getEditor('container', {
  121. initialFrameWidth: '100%', //初始化编辑器宽度,默认1000
  122. initialFrameHeight: '500' //初始化编辑器高度,默认320
  123. });
  124. });
  125. // 点击上传的缩略图, 显示大图
  126. function big_img(img) {
  127. if (img.src == undefined || img.src == "") {
  128. return;
  129. }
  130. //页面层-图片
  131. layer.open({
  132. type: 1,
  133. title: false,
  134. closeBtn: 0,
  135. area: ['auto'],
  136. skin: 'layui-layer-nobg', //没有背景色
  137. shadeClose: true,
  138. content: $('#tong')
  139. });
  140. }
  141. function save() {
  142. var data = {};
  143. data._token = $('input[name="_token"]').val();
  144. data.id = parseInt($('input[name="id"]').val());
  145. data.title = $.trim($('#title').val());
  146. data.subtitle = $.trim($('#subtitle').val());
  147. data.cid = parseInt($('#cid').val());
  148. data.thumb = $.trim($('#preview_img').attr('src'));
  149. data.status = $('input[name="status"]:checked').val();
  150. data.descs = $.trim($('#descs').val());
  151. data.keywords = $.trim($('#keywords').val());
  152. data.content = ue.getContent();
  153. if(data.title == '') {
  154. return layer.alert('请填写文章标题', {icon: 2});
  155. }
  156. if(data.content == '') {
  157. return layer.alert('请输入文章内容', {icon: 2});
  158. }
  159. if(isNaN(data.cid) || data.cid < 1) {
  160. return layer.alert('请选择文章分类', {icon: 2});
  161. }
  162. if(isNaN(data.id) || data.id < 1) {
  163. return layer.alert('文章id不能为空', {icon: 2});
  164. }
  165. $.ajax({
  166. url: '/admin/article/update_article',
  167. data: data,
  168. type: 'POST',
  169. async: true,
  170. dataType: 'json',
  171. success: function(res) {
  172. if(res.status != undefined && res.status == '0') {
  173. layer.msg(res.message, {icon: 1});
  174. setTimeout(() => {
  175. window.parent.location.reload();
  176. }, 1000);
  177. } else if(res.status != undefined) {
  178. return layer.alert(res.message, {icon: 2});
  179. } else {
  180. return layer.alert('文章更新失败', {icon: 2});
  181. }
  182. }
  183. });
  184. }
  185. </script>
  186. </html>
  • 文章/文章分类控制器
  1. <?php
  2. namespace App\Http\Controllers\admins;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Support\Facades\DB;
  6. class Article extends Controller {
  7. /**
  8. * 文章列表
  9. */
  10. public function index(Request $req) {
  11. // 一页可容纳的记录数
  12. $pageSize = empty($req->limit) ? 1 : $req->limit;
  13. // 当前页
  14. $currPage = empty($req->page) ? 1 : $req->page;
  15. // dump($pageSize);dump($currPage);die;
  16. $data['cates'] = DB::table('article_cate')->keyval('id', 'title');
  17. $data['user'] = DB::table('admin')->keyval('id', 'real_name');
  18. // $data['articles'] = DB::table('article')->lists();
  19. /* 换成分页的方式(返回对象数组) */
  20. // $data['articles'] = DB::table('article')->paginate(2);
  21. /* 换成自己扩展的分页方式(返回二维数组) */
  22. $pageData = DB::table('article')->orderby('id' , 'desc')->pages($pageSize);
  23. // dump($pageData);die;
  24. $data['articles'] = $pageData['page_data'];
  25. $data['total'] = $pageData['total'];
  26. $data['limit'] = $pageSize;
  27. $data['currPage'] = $currPage;
  28. return view('/admins/article/index', $data);
  29. }
  30. /**
  31. * 跳转到新增文章界面
  32. */
  33. public function addArticle() {
  34. $data['cates'] = DB::table('article_cate')->keyval('id', 'title');
  35. return view('admins/article/add_article', $data);
  36. }
  37. public function editArticle(Request $req) {
  38. $data['cates'] = DB::table('article_cate')->keyval('id', 'title');
  39. $id = (int) $req->id;
  40. $data['article'] = DB::table('article')->where('id', $id)->getFirst();
  41. if(!$data['article']) {
  42. return json_encode(['status' => 1, 'message' => '无效的文章ID']);
  43. }
  44. $data['content'] = (DB::table('article_detail')->select('contents')->where('aid', $id)->getFirst())['contents'];
  45. return view('/admins/article/edit_article', $data);
  46. }
  47. public function saveArticle(Request $req) {
  48. $data['title'] = trim($req->title);
  49. $data['subtitle'] = trim($req->subtitle);
  50. $data['cid'] = (int) ($req->cid);
  51. $data['thumb'] = trim($req->thumb);
  52. $data['status'] = (int) $req->status;
  53. $data['descs'] = trim($req->descs);
  54. $data['keywords'] = trim($req->keywords);
  55. $data['auth_id'] = $req->loginInfo->id;
  56. $data['add_time'] = time();
  57. $content = trim($req->content);
  58. $id = DB::table('article')->insertGetId($data);
  59. if($id) {
  60. DB::table('article_detail')->insert(['aid' => $id, 'contents' => $content]);
  61. return json_encode(['status' => 0, 'message' => '文章保存成功']);
  62. }
  63. return json_encode(['status' => 1, 'message' => '文章保存失败']);
  64. }
  65. public function updateArticle(Request $req) {
  66. $data['id'] = (int) $req->id;
  67. $data['title'] = trim($req->title);
  68. $data['subtitle'] = trim($req->subtitle);
  69. $data['cid'] = (int) ($req->cid);
  70. $data['thumb'] = trim($req->thumb);
  71. $data['status'] = (int) $req->status;
  72. $data['descs'] = trim($req->descs);
  73. $data['keywords'] = trim($req->keywords);
  74. $data['auth_id'] = $req->loginInfo->id;
  75. $data['add_time'] = time();
  76. $content = trim($req->content);
  77. $id = DB::table('article')->where('id', $data['id'])->update($data);
  78. if($id) {
  79. DB::table('article_detail')->where('aid', $data['id'])->update(['contents' => $content]);
  80. return json_encode(['status' => 0, 'message' => '文章修改成功']);
  81. }
  82. return json_encode(['status' => 1, 'message' => '文章修改失败']);
  83. }
  84. /**
  85. * 文章分类列表
  86. */
  87. public function cates() {
  88. // 查询所有分类
  89. $data['cates'] = DB::table('article_cate')->lists();
  90. return view('/admins/article/cates', $data);
  91. }
  92. /**
  93. * 添加文章分类
  94. */
  95. public function addCate(Request $req) {
  96. return view('/admins/article/add_cate');
  97. }
  98. public function saveCate(Request $req) {
  99. $title = $req->title;
  100. $cate = DB::table('article_cate')->where('title', $title)->getFirst();
  101. if($cate) {
  102. return json_encode(['status' => 1, 'message' => '已存在同名分类, 请另命名分类后再重试']);
  103. }
  104. $res = DB::table('article_cate')->insertGetId(['title' => $title]);
  105. if($res) {
  106. return json_encode(['status' => 0, 'message' => '添加成功']);
  107. }
  108. return json_encode(['status' => 1, 'message' => '添加失败']);
  109. }
  110. }
  • 处理文件上传的控制器
  1. <?php
  2. namespace App\Http\Controllers\admins;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Support\Facades\Storage;
  6. class Upload extends Controller {
  7. public function picUpload(Request $request) {
  8. // file(请求中的文件参数名), store(文件存放子路径). 文件存放目录: ../storage/app/[store指定的子目录],
  9. // 为能使用软连接访问上传的图片, 强烈建议子目录以public目录开始. 建议在public中分目录, 以年份, 或者年份月份,
  10. // 或者年月日为public子目录来存放文件, 这样更方便查找. 也可以分得更细, 如: 精确到小时, 分钟等.
  11. /* 此时返回的是.../public/avatars/2020/06/18/xxxxxx.png, 实际并不能以该路径来访问 */
  12. $path = $request->file('file')->store('public/avatars/' . date('Y/m/d'));
  13. // 获取前端能访问到的真实的url地址
  14. $url = Storage::url($path);
  15. // layui的上传组件要求返回的json数据的格式
  16. $res = ['code' => 0, 'msg' => '', 'data' => ['src' => $url]];
  17. return json_encode($res);
  18. }
  19. }
声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议