Blogger Information
Blog 57
fans 3
comment 0
visits 60518
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
laravel实战-通用后台管理系统-基础设置/文章分类/文章分页列表/图片上传和嵌入富文本编辑器
岂几岂几
Original
1102 people have browsed it

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. }
Correcting teacher:天蓬老师天蓬老师

Correction status:qualified

Teacher's comments:还在认真写作业的不多了, 希望你能坚持 到最后
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post