[代码分享] 基于 layui 封装的后台上传功能,前端苦手的福音 [大佬慎点]

功能:查看、复制 url、替换和移除。

开发使用和用户使用都简单。

前端苦手、伸手党福音。

授人以鱼,就授人以鱼。

如果你不用 layui,也可以参考一下~

话说,社区是否考虑加一个 帖子附件 功能?可以附加小文件~

效果

分享基于 layui 封装的后台上传功能,前端苦手的福音

里面那些文字,,,如果系统中已经引入了图标库,可自行改为图标,会好看一点,当然样式也可能要相应的调整。

紫色 可自行改为自己后台的主题色~~

使用方式

模板

<!-- head 中 -->
<link rel="stylesheet" href="{{ asset('layui/css/layui.css') }}">
<link rel="stylesheet" href="{{ asset('upload.css') }}">

<!-- body 中 -->
<form action="/user/test" method="post">
  <div style="float: left; margin-right: 10px;">
    <h2>初始化值</h2>
    <!-- 使用 up-uploader 类标记 -->
    <div class="up-uploader" data-value="/uploads/7c0d7ef03a7eb04ce795b0f60e68e7e1.txt"></div>
  </div>
  <div>
    <h2>头像</h2>
    <div class="up-uploader" data-input-name="avatar"></div>
  </div>
  <div>
    <h2>相册</h2>
    <div
      class="up-uploader"
      data-input-name="photos[]"
      data-max="8"
      data-full-url="true"
    ></div>
  </div>
  <div>
    <h2><button type="submit">提交</button></h2>
  </div>
</form>

<script src="{{ asset('layui/layui.js') }}"></script>
<!-- 其实可以用 layui 的自定义模块加载,这里算了,效果一样 -->
<script src="{{ asset('upload.js') }}"></script>

上传返回

可自行调整,JS 中也要稍微调整下

return response()->json([
    'code' => 0, // 0 - 成功,其他 - 失败
    'msg' => '上传成功',
    'data' => [
        'full_url' => 'http://chat.l.com'.$storage->url('uploads/'.$filename), // 全路径,并没有用到
        'path' => '/uploads/'.$filename, // 相对路径
    ],
]);

提交后的数据

array:4 ["up_file" => null // 这个选择文件的那个 input 框的值,忽略即可
  "file" => "/uploads/7c0d7ef03a7eb04ce795b0f60e68e7e1.txt"
  "avatar" => "/uploads/61c1b32a961b0d868a78dae00e4997f9.png"
  "photos" => array:2 [0 => "http://chat.l.com/uploads/53c7473b75cf9adcb8540fdf3a2022eb.png"
    1 => "http://chat.l.com/uploads/61c1b32a961b0d868a78dae00e4997f9.png"
  ]
]

代码

JS

upload.js

layui
  .define(['upload', 'laytpl', 'layer'], function (exports) {
    var log = console.log // 开发测试时的简写

    var upload = layui.upload
    var laytpl = layui.laytpl
    var layer = layui.layer
    var $ = layui.jquery

    var domain = location.origin // 自行修改,注意末尾的斜杠
    var uploadUrl = domain + '/upload/test' // 同上

    /**
     * 上传完成后,渲染的 html 元素 模板
     * @type {string}
     */
    var itemTpl =
      '<div class="up-item">' +
      '    {{# if (d.isImage) { }}' +
      '        <img src="{{ d.src }}" class="up-image">' +
      '    {{# } else { }}' +
      '        <div class="up-file">已上传文件</div>' +
      '    {{# } }}' +
      '    <div class="up-actions">' +
      '        <span class="up-action up-copy" title="复制:{{ d.src }}">复制</span>' +
      '        <a ' +
      '            href="{{ d.src }}" target="_blank" title="查看" ' +
      '            class="up-action {{# if (d.isImage) { }} up-preview {{# } }}"' +
      '        >查看</a>' +
      '        {{# if (!d.readOnly) { }}' +
      '            <span class="up-action up-replace" title="替换">替换</span>' +
      '            <span class="up-action up-remove" title="移除">移除</span>' +
      '        {{# } }}' +
      '    </div>' +
      '    <input name="{{ d.inputName }}" hidden value="{{ d.val }}">' +
      '</div>'

    /**
     * 简单判断是不是图片地址
     * @param {string} path
     * @return {boolean}
     */
    var isImage = function (path) {
      var t = path.toLowerCase().split('.')
      var ext = t.length <= 1
        ? ''
        : t[t.length - 1]

      var imageExts = [
        'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico', 'webp', 'svg', 'tiff',
      ]

      return imageExts.indexOf(ext) !== -1
    }

    /**
     * 简单判断是不是全路径地址
     * @param {string} url
     * @return {boolean}
     */
    var isFullUrl = function (url) {
      url = url.toLowerCase()
      return (url.indexOf('https://') === 0) ||
        (url.indexOf('http://') === 0)
    }

    /**
     * 复制文本到剪贴板
     * @param {string} text
     * @return {boolean}
     */
    var copyText = function (text) {
      var $copyInput = $('<input style="position: fixed !important; top: -9999px !important;" value="' + text + '">')
      $('body').append($copyInput)

      $copyInput.select()
      var res = document.execCommand('copy')

      $copyInput.remove()

      return res
    }

    /**
     * 手动渲染一个上传器
     * @param $wrapper 一个 jQuery 元素
     * @param overOptions 可选,覆盖 html 元素中设置的配置
     */
    var renderUploader = function ($wrapper, overOptions) {
      // 覆盖的配置
      if (overOptions) {
        var keys = Object.keys(overOptions)
        for (var i = 0; i < keys.length; i++) {
          var k = keys[i]
          $wrapper.data(k, overOptions[k])
        }
      }

      // 可选配置的默认配置
      // 有的是 layui upload 的,可参考 layui 文档
      var defaultOptions = {
        field: 'up_file',
        size: 2048,
        accept: 'images',
        acceptMime: 'image/*',
        max: 1, // 最多上传多少张
        fullUrl: false, // input 的值,是否设置为全路径
        inputName: 'file', // input 的 name 属性,尽量别和前面的 field 一样,虽然没影响
        readOnly: false, // 只读情况下,只有查看和复制操作
        value: '', // 默认的文件 url 值,用于初始化显示,多个值用英文逗号分隔
      }
      var options = $.extend(defaultOptions, $wrapper.data())
      /**
       * layui 上传实例
       */
      var uploaderIns
      /**
       * 获取当前文件已经上传了多少个
       *
       * @return {number}
       */
      var getItemsCount = function () {
        return $itemsWrapper.children().length
      }
      /**
       * 判断是否达到最大上传数
       * @return {boolean}
       */
      var isMax = function () {
        return (options.max != 0) && (getItemsCount() >= options.max)
      }
      /**
       * 根据当前已经上传和最大上传,设置选择器的显隐
       */
      var setPickerVisible = function () {
        isMax() ? $picker.hide() : $picker.show()
      }

      /**
       * 生成上传后新元素的的 html
       * @param val 上传后文件的地址
       * @param callback html 生成后的回调
       */
      var renderItemHtml = function (val, callback) {
        var src = isFullUrl(val) ? val : domain + val
        laytpl(itemTpl).render({
          val: options.fullUrl ? src : val,
          src: src,
          isImage: isImage(val),
          inputName: options.inputName,
          readOnly: options.readOnly,
        }, function (html) {
          var $item = $(html)

          // 复制
          $item.find('.up-copy').click(function () {
            copyText(src)
            layer.msg('复制成功', { icon: 1 })
          })

          // 替换
          if (!options.readOnly) {
            renderReplaceUploader($item)
          }

          callback && callback($item)
        })
      }

      /**
       * 添加新的已上传元素
       * @param {string} val
       */
      var appendItem = function (val) {
        renderItemHtml(val, function ($item) {
          $itemsWrapper.append($item)
          setPickerVisible()
        })
      }

      /**
       * 替换图片的上传
       * @param $item
       */
      var renderReplaceUploader = function ($item) {
        var replaceOptions = $.extend({}, options, {
          elem: $item.find('.up-replace'),
          multiple: false,
          number: 1,
          done: function (res) {
            layer.closeAll('loading')

            if (res.code !== 0) {
              layer.msg(res.msg, { icon: 2 })
              return
            }

            renderItemHtml(res.data.path, function ($newItem) {
              $item.replaceWith($newItem)
            })
          },
          error: function () {
            layer.closeAll('loading')
          },
        })

        upload.render(replaceOptions)
      }

      $wrapper.html('<div class="up-items-wrapper"></div>')

      if (!options.readOnly) {
        $wrapper.append('<div class="up-picker">点击上传</div>')
      }

      var $picker = $wrapper.find('.up-picker')
      var $itemsWrapper = $wrapper.find('.up-items-wrapper')

      // 固定配置
      options = $.extend(options, {
        elem: $picker,
        url: uploadUrl,
        auto: true,
        method: 'post',
        multiple: (options.max > 1) || (options.max == 0),
        number: options.max,
        before: function () {
          layer.load(2)
        },
        done: function (res) {
          layer.closeAll('loading')

          if (res.code !== 0) {
            layer.msg(res.msg, { icon: 2 })
            return
          }

          // layui 的自动上传,好像不能提前终止
          // 所以,如果上传数量超过设定的值,只能在这里丢弃返回的结果
          if (isMax()) {
            return
          }

          appendItem(res.data.path)
        },
        error: function () {
          layer.closeAll('loading')
        },
      })

      // 初始化默认值的情况
      var value = options.value
      if (value) {
        value = value.split(',')
        for (var i = 0; i < value.length; i++) {
          if (isMax()) {
            break
          }
          appendItem(value[i])
        }
      }

      // 只读的情况下,不需要渲染上传器
      if (!options.readOnly) {
        uploaderIns = upload.render(options)
      }

      $wrapper.on('click', '.up-remove', function () {
        $(this).parents('.up-item').remove()
        setPickerVisible()
      })
    }

    // 初始化页面上的上传器,使用 .up-uploader 选择器
    $('.up-uploader').each(function () {
      renderUploader($(this))
    })

    // 图片弹框预览
    $(document).on('click', '.up-preview', function () {
      var img = $(this).parents('.up-item').find('.up-image')[0]
      var src = img.src

      if (!img) {
        layer.msg('没有可显示的图片', { icon: 2 })
        return
      }

      var width = img.naturalWidth
      var height = img.naturalHeight

      if (!width || !height) {
        layer.msg('图片尺寸错误', { icon: 2 })
        return
      }
      var hwRatio = height / width

      var maxHeight = window.innerHeight * 0.9
      var maxWidth = window.innerWidth * 0.9

      if (width > maxWidth) {
        width = maxWidth
        height = width * hwRatio
      }

      if (height > maxHeight) {
        height = maxHeight
        width = height / hwRatio
      }

      layer.open({
        type: 1,
        title: false,
        area: [width + 'px', height + 'px'],
        shadeClose: true,
        content:
          '<a href="' + src + '" target="_blank">' +
          '    <img src="' + src + '" style="height: 100%; width: 100%;">' +
          '</a>',
      })

      return false
    })

    exports('up-uploader', {
      // 导出该函数,可在其他地方,手动渲染动态加入的 html 元素
      // 如何导入,可查看 layui 的自定义模块文档
      renderUploader: renderUploader,
    })
  })

css

upload.css

/* 听说这是最强清除浮动的搞法? */
.up-uploader::after {
  content: '';
  display: block;
  clear: both;
}

.up-picker {
  float: left;
  border: 1px solid #cfdadd;
  width: 120px;
  height: 120px;
  font-size: 20px;
  color: #8e999c;
  text-align: center;
  line-height: 120px;
  cursor: pointer;
}

.up-picker:hover {
  border-color: #23b7e5;
}

.up-items-wrapper {
  float: left;
}

.up-item {
  position: relative;
  line-height: 116px;
  float: left;
  border: 1px solid #cfdadd;
  width: 120px;
  height: 120px;
  text-align: center;
  margin-right: 5px;
}

.up-item:hover .up-actions {
  display: initial;
}

.up-image {
  max-width: 100%;
  max-height: 100%;
}

.up-file {
  font-size: 20px;
  color: #7266ba;
  line-height: 120px;
}

.up-actions {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(204, 204, 204, .9);
  line-height: 120px;
}

.up-actions .up-action {
  font-size: 12px;
  font-weight: bolder;
  cursor: pointer;
  color: #6254b2;
}

.up-actions .up-remove {
  color: #ee3939;
}
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1

感谢分享,在用layui,先收藏了

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!