Просмотр исходного кода

【模版目录】更新管理端源码

吴昊天 3 лет назад
Родитель
Сommit
6f9fe42c17

Разница между файлами не показана из-за своего большого размера
+ 143 - 138
template/admin/package-lock.json


+ 1 - 0
template/admin/package.json

@@ -25,6 +25,7 @@
     "cos-js-sdk-v5": "^0.5.26",
     "countup": "^1.8.2",
     "cropperjs": "^1.2.2",
+    "crypto-js": "^4.1.1",
     "dayjs": "^1.7.7",
     "echarts": "^4.8.0",
     "editor": "^1.0.0",

+ 17 - 0
template/admin/src/api/common.js

@@ -10,6 +10,23 @@
 
 import request from '@/libs/request';
 
+
+export function ajCaptcha(params){
+  return request({
+    url:'ajcaptcha',
+    method:'get',
+    params:params
+  });
+}
+
+export function ajCaptchaCheck(data){
+  return request({
+    url:'ajcheck',
+    method:'post',
+    data:data
+  });
+}
+
 /**
  * @description 表格--删除
  * @param {Number} param id {Number} 配置id

+ 14 - 0
template/admin/src/api/system.js

@@ -313,6 +313,17 @@ export function filesImportApi(data) {
   });
 }
 
+/**
+ * @description 文件管理 -- 登录
+ */
+export function opendirLoginApi(data) {
+  return request({
+    url: `system/file/login`,
+    method: 'POST',
+    data,
+  });
+}
+
 /**
  * @description 文件管理 -- 列表
  */
@@ -321,6 +332,7 @@ export function opendirListApi(params) {
     url: `system/file/opendir`,
     method: 'GET',
     params,
+	file_edit:true
   });
 }
 
@@ -331,6 +343,7 @@ export function openfileApi(filepath) {
   return request({
     url: `system/file/openfile?filepath=${filepath}`,
     method: 'GET',
+	file_edit:true
   });
 }
 
@@ -342,6 +355,7 @@ export function savefileApi(data) {
     url: `system/file/savefile`,
     method: 'post',
     data,
+	file_edit:true
   });
 }
 

BIN
template/admin/src/assets/images/default.jpg


Разница между файлами не показана из-за своего большого размера
+ 493 - 0
template/admin/src/components/verifition/Verify.vue


+ 268 - 0
template/admin/src/components/verifition/Verify/VerifyPoints.vue

@@ -0,0 +1,268 @@
+<template>
+  <div
+    style="position: relative"
+  >
+    <div class="verify-img-out">
+      <div
+        class="verify-img-panel"
+        :style="{'width': setSize.imgWidth,
+                 'height': setSize.imgHeight,
+                 'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight,
+                 'margin-bottom': vSpace + 'px'}"
+      >
+        <div v-show="showRefresh" class="verify-refresh" style="z-index:3" @click="refresh">
+          <i class="iconfont icon-refresh" />
+        </div>
+        <img
+          ref="canvas"
+          :src="pointBackImgBase?('data:image/png;base64,'+pointBackImgBase):defaultImg"
+          alt=""
+          style="width:100%;height:100%;display:block"
+          @click="bindingClick?canvasClick($event):undefined"
+        >
+
+        <div
+          v-for="(tempPoint, index) in tempPoints"
+          :key="index"
+          class="point-area"
+          :style="{
+            'background-color':'#1abd6c',
+            color:'#fff',
+            'z-index':9999,
+            width:'20px',
+            height:'20px',
+            'text-align':'center',
+            'line-height':'20px',
+            'border-radius': '50%',
+            position:'absolute',
+            top:parseInt(tempPoint.y-10) + 'px',
+            left:parseInt(tempPoint.x-10) + 'px'
+          }"
+        >
+          {{ index + 1 }}
+        </div>
+      </div>
+    </div>
+    <!-- 'height': this.barSize.height, -->
+    <div
+      class="verify-bar-area"
+      :style="{'width': setSize.imgWidth,
+               'color': this.barAreaColor,
+               'border-color': this.barAreaBorderColor,
+               'line-height':this.barSize.height}"
+    >
+      <span class="verify-msg">{{ text }}</span>
+    </div>
+  </div>
+</template>
+<script type="text/babel">
+/**
+     * VerifyPoints
+     * @description 点选
+     * */
+import { resetSize, _code_chars, _code_color1, _code_color2 } from './../utils/util'
+import { aesEncrypt } from './../utils/ase'
+import {ajCaptcha, ajCaptchaCheck} from "../../../api/common";
+
+export default {
+  name: 'VerifyPoints',
+  props: {
+    // 弹出式pop,固定fixed
+    mode: {
+      type: String,
+      default: 'fixed'
+    },
+    captchaType: {
+      type: String,
+    },
+    // 间隔
+    vSpace: {
+      type: Number,
+      default: 5
+    },
+    imgSize: {
+      type: Object,
+      default() {
+        return {
+          width: '310px',
+          height: '155px'
+        }
+      }
+    },
+    barSize: {
+      type: Object,
+      default() {
+        return {
+          width: '310px',
+          height: '40px'
+        }
+      }
+    },
+    defaultImg: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      secretKey: '', // 后端返回的ase加密秘钥
+      checkNum: 3, // 默认需要点击的字数
+      fontPos: [], // 选中的坐标信息
+      checkPosArr: [], // 用户点击的坐标
+      num: 1, // 点击的记数
+      pointBackImgBase: '', // 后端获取到的背景图片
+      poinTextList: [], // 后端返回的点击字体顺序
+      backToken: '', // 后端返回的token值
+      setSize: {
+        imgHeight: 0,
+        imgWidth: 0,
+        barHeight: 0,
+        barWidth: 0
+      },
+      tempPoints: [],
+      text: '',
+      barAreaColor: undefined,
+      barAreaBorderColor: undefined,
+      showRefresh: true,
+      bindingClick: true
+    }
+  },
+  computed: {
+    resetSize() {
+      return resetSize
+    }
+  },
+  watch: {
+    // type变化则全面刷新
+    type: {
+      immediate: true,
+      handler() {
+        this.init()
+      }
+    }
+  },
+  mounted() {
+    // 禁止拖拽
+    this.$el.onselectstart = function() {
+      return false
+    }
+  },
+  methods: {
+    init() {
+      // 加载页面
+      this.fontPos.splice(0, this.fontPos.length)
+      this.checkPosArr.splice(0, this.checkPosArr.length)
+      this.num = 1
+      this.getPictrue()
+      this.$nextTick(() => {
+        this.setSize = this.resetSize(this)	// 重新设置宽度高度
+        this.$parent.$emit('ready', this)
+      })
+    },
+    canvasClick(e) {
+      this.checkPosArr.push(this.getMousePos(this.$refs.canvas, e))
+      if (this.num == this.checkNum) {
+        this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e))
+        // 按比例转换坐标值
+        this.checkPosArr = this.pointTransfrom(this.checkPosArr, this.setSize)
+        // 等创建坐标执行完
+        setTimeout(() => {
+          // var flag = this.comparePos(this.fontPos, this.checkPosArr);
+          // 发送后端请求
+          var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify(this.checkPosArr), this.secretKey) : this.backToken + '---' + JSON.stringify(this.checkPosArr)
+          const data = {
+            captchaType: this.captchaType,
+            'pointJson': this.secretKey ? aesEncrypt(JSON.stringify(this.checkPosArr), this.secretKey) : JSON.stringify(this.checkPosArr),
+            'token': this.backToken
+          }
+          ajCaptchaCheck(data).then(res => {
+            if (res.repCode == '0000') {
+              this.barAreaColor = '#4cae4c'
+              this.barAreaBorderColor = '#5cb85c'
+              this.text = '验证成功'
+              this.bindingClick = false
+              if (this.mode == 'pop') {
+                setTimeout(() => {
+                  this.$parent.clickShow = false
+                  this.refresh()
+                }, 1500)
+              }
+              this.$parent.$emit('success', { captchaVerification })
+            } else {
+              this.$parent.$emit('error', this)
+              this.barAreaColor = '#d9534f'
+              this.barAreaBorderColor = '#d9534f'
+              this.text = '验证失败'
+              setTimeout(() => {
+                this.refresh()
+              }, 700)
+            }
+          })
+        }, 400)
+      }
+      if (this.num < this.checkNum) {
+        this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e))
+      }
+    },
+
+    // 获取坐标
+    getMousePos: function(obj, e) {
+      var x = e.offsetX
+      var y = e.offsetY
+      return { x, y }
+    },
+    // 创建坐标点
+    createPoint: function(pos) {
+      this.tempPoints.push(Object.assign({}, pos))
+      return ++this.num
+    },
+    refresh: function() {
+      this.tempPoints.splice(0, this.tempPoints.length)
+      this.barAreaColor = '#000'
+      this.barAreaBorderColor = '#ddd'
+      this.bindingClick = true
+      this.fontPos.splice(0, this.fontPos.length)
+      this.checkPosArr.splice(0, this.checkPosArr.length)
+      this.num = 1
+      this.getPictrue()
+      this.text = '验证失败'
+      this.showRefresh = true
+    },
+
+    // 请求背景图片和验证图片
+    getPictrue() {
+      const data = {
+        captchaType: this.captchaType,
+        clientUid: localStorage.getItem('point'),
+        ts: Date.now(), // 现在的时间戳
+      }
+      ajCaptcha(data).then(res => {
+        if (res.repCode == '0000') {
+          this.pointBackImgBase = res.repData.originalImageBase64
+          this.backToken = res.repData.token
+          this.secretKey = res.repData.secretKey
+          this.poinTextList = res.repData.wordList
+          this.text = '请依次点击【' + this.poinTextList.join(',') + '】'
+        } else {
+          this.text = res.repMsg
+        }
+
+        // 判断接口请求次数是否失效
+        if (res.repCode == '6201') {
+          this.pointBackImgBase = null
+        }
+      })
+    },
+    // 坐标转换函数
+    pointTransfrom(pointArr, imgSize) {
+      var newPointArr = pointArr.map(p => {
+        const x = Math.round(310 * p.x / parseInt(imgSize.imgWidth))
+        const y = Math.round(155 * p.y / parseInt(imgSize.imgHeight))
+        return { x, y }
+      })
+      // console.log(newPointArr,"newPointArr");
+      return newPointArr
+    }
+  },
+}
+</script>

+ 369 - 0
template/admin/src/components/verifition/Verify/VerifySlide.vue

@@ -0,0 +1,369 @@
+<template>
+  <div style="position: relative;">
+    <div
+      v-if="type === '2'"
+      class="verify-img-out"
+      :style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
+    >
+      <div
+        class="verify-img-panel"
+        :style="{width: setSize.imgWidth,
+                 height: setSize.imgHeight,}"
+      >
+        <img :src="backImgBase?('data:image/png;base64,'+backImgBase):defaultImg" alt="" style="width:100%;height:100%;display:block">
+        <div v-show="showRefresh" class="verify-refresh" @click="refresh"><i class="iconfont icon-refresh" />
+        </div>
+        <transition name="tips">
+          <span v-if="tipWords" class="verify-tips" :class="passFlag ?'suc-bg':'err-bg'">{{ tipWords }}</span>
+        </transition>
+      </div>
+    </div>
+    <!-- 公共部分 -->
+    <div
+      class="verify-bar-area"
+      :style="{width: setSize.imgWidth,
+               height: barSize.height,
+               'line-height':barSize.height}"
+    >
+      <span class="verify-msg" v-text="text" />
+      <div
+        class="verify-left-bar"
+        :style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}"
+      >
+        <span class="verify-msg" v-text="finishText" />
+        <div
+          class="verify-move-block"
+          :style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}"
+          @touchstart="start"
+          @mousedown="start"
+        >
+          <i
+            :class="['verify-icon iconfont', iconClass]"
+            :style="{color: iconColor}"
+          />
+          <div
+            v-if="type === '2'"
+            class="verify-sub-block"
+            :style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
+                     'height': setSize.imgHeight,
+                     'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
+                     'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
+            }"
+          >
+            <img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block">
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script type="text/babel">
+/**
+     * VerifySlide
+     * @description 滑块
+     * */
+import { aesEncrypt } from './../utils/ase'
+import { resetSize } from './../utils/util'
+import {ajCaptcha, ajCaptchaCheck} from "../../../api/common";
+
+//  "captchaType":"blockPuzzle",
+export default {
+  name: 'VerifySlide',
+  props: {
+    captchaType: {
+      type: String,
+    },
+    type: {
+      type: String,
+      default: '1'
+    },
+    // 弹出式pop,固定fixed
+    mode: {
+      type: String,
+      default: 'fixed'
+    },
+    vSpace: {
+      type: Number,
+      default: 5
+    },
+    explain: {
+      type: String,
+      default: '向右滑动完成验证'
+    },
+    imgSize: {
+      type: Object,
+      default() {
+        return {
+          width: '310px',
+          height: '155px'
+        }
+      }
+    },
+    blockSize: {
+      type: Object,
+      default() {
+        return {
+          width: '50px',
+          height: '50px'
+        }
+      }
+    },
+    barSize: {
+      type: Object,
+      default() {
+        return {
+          width: '310px',
+          height: '40px'
+        }
+      }
+    },
+    defaultImg: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      secretKey: '', // 后端返回的加密秘钥 字段
+      passFlag: '', // 是否通过的标识
+      backImgBase: '', // 验证码背景图片
+      blockBackImgBase: '', // 验证滑块的背景图片
+      backToken: '', // 后端返回的唯一token值
+      startMoveTime: '', // 移动开始的时间
+      endMovetime: '', // 移动结束的时间
+      tipsBackColor: '', // 提示词的背景颜色
+      tipWords: '',
+      text: '',
+      finishText: '',
+      setSize: {
+        imgHeight: 0,
+        imgWidth: 0,
+        barHeight: 0,
+        barWidth: 0
+      },
+      top: 0,
+      left: 0,
+      moveBlockLeft: undefined,
+      leftBarWidth: undefined,
+      // 移动中样式
+      moveBlockBackgroundColor: undefined,
+      leftBarBorderColor: '#ddd',
+      iconColor: undefined,
+      iconClass: 'icon-right',
+      status: false, // 鼠标状态
+      isEnd: false,		// 是够验证完成
+      showRefresh: true,
+      transitionLeft: '',
+      transitionWidth: ''
+    }
+  },
+  computed: {
+    barArea() {
+      return this.$el.querySelector('.verify-bar-area')
+    },
+    resetSize() {
+      return resetSize
+    }
+  },
+  watch: {
+    // type变化则全面刷新
+    type: {
+      immediate: true,
+      handler() {
+        this.init()
+      }
+    }
+  },
+  mounted() {
+    // 禁止拖拽
+    this.$el.onselectstart = function() {
+      return false
+    }
+    console.log(this.defaultImg)
+  },
+  methods: {
+    init() {
+      this.text = this.explain
+      this.getPictrue()
+      this.$nextTick(() => {
+        const setSize = this.resetSize(this)	// 重新设置宽度高度
+        for (const key in setSize) {
+          this.$set(this.setSize, key, setSize[key])
+        }
+        this.$parent.$emit('ready', this)
+      })
+
+      var _this = this
+
+      window.removeEventListener('touchmove', function(e) {
+        _this.move(e)
+      })
+      window.removeEventListener('mousemove', function(e) {
+        _this.move(e)
+      })
+
+      // 鼠标松开
+      window.removeEventListener('touchend', function() {
+        _this.end()
+      })
+      window.removeEventListener('mouseup', function() {
+        _this.end()
+      })
+
+      window.addEventListener('touchmove', function(e) {
+        _this.move(e)
+      })
+      window.addEventListener('mousemove', function(e) {
+        _this.move(e)
+      })
+
+      // 鼠标松开
+      window.addEventListener('touchend', function() {
+        _this.end()
+      })
+      window.addEventListener('mouseup', function() {
+        _this.end()
+      })
+    },
+
+    // 鼠标按下
+    start: function(e) {
+      e = e || window.event
+      if (!e.touches) { // 兼容PC端
+        var x = e.clientX
+      } else { // 兼容移动端
+        var x = e.touches[0].pageX
+      }
+      this.startLeft = Math.floor(x - this.barArea.getBoundingClientRect().left)
+      this.startMoveTime = +new Date() // 开始滑动的时间
+      if (this.isEnd == false) {
+        this.text = ''
+        this.moveBlockBackgroundColor = '#337ab7'
+        this.leftBarBorderColor = '#337AB7'
+        this.iconColor = '#fff'
+        e.stopPropagation()
+        this.status = true
+      }
+    },
+    // 鼠标移动
+    move: function(e) {
+      e = e || window.event
+      if (this.status && this.isEnd == false) {
+        if (!e.touches) { // 兼容PC端
+          var x = e.clientX
+        } else { // 兼容移动端
+          var x = e.touches[0].pageX
+        }
+        var bar_area_left = this.barArea.getBoundingClientRect().left
+        var move_block_left = x - bar_area_left // 小方块相对于父元素的left值
+        if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) {
+          move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2
+        }
+        if (move_block_left <= 0) {
+          move_block_left = parseInt(parseInt(this.blockSize.width) / 2)
+        }
+        // 拖动后小方块的left值
+        this.moveBlockLeft = (move_block_left - this.startLeft) + 'px'
+        this.leftBarWidth = (move_block_left - this.startLeft) + 'px'
+      }
+    },
+
+    // 鼠标松开
+    end: function() {
+      this.endMovetime = +new Date()
+      var _this = this
+      // 判断是否重合
+      if (this.status && this.isEnd == false) {
+        var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', ''))
+        moveLeftDistance = moveLeftDistance * 310 / parseInt(this.setSize.imgWidth)
+        const data = {
+          captchaType: this.captchaType,
+          'pointJson': this.secretKey ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
+          'token': this.backToken
+        }
+        ajCaptchaCheck(data).then(res => {
+            this.moveBlockBackgroundColor = '#5cb85c'
+            this.leftBarBorderColor = '#5cb85c'
+            this.iconColor = '#fff'
+            this.iconClass = 'icon-check'
+            this.showRefresh = false
+            this.isEnd = true
+            if (this.mode == 'pop') {
+              setTimeout(() => {
+                this.$parent.clickShow = false
+                this.refresh()
+              }, 1500)
+            }
+            this.passFlag = true
+            this.tipWords = `${((this.endMovetime - this.startMoveTime) / 1000).toFixed(2)}s验证成功`
+            var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
+            setTimeout(() => {
+              this.tipWords = ''
+              this.$parent.closeBox()
+              this.$parent.$emit('success', { captchaVerification })
+            }, 1000)
+        }).catch(res=>{
+          this.moveBlockBackgroundColor = '#d9534f'
+          this.leftBarBorderColor = '#d9534f'
+          this.iconColor = '#fff'
+          this.iconClass = 'icon-close'
+          this.passFlag = false
+          setTimeout(function() {
+            _this.refresh()
+          }, 1000)
+          this.$parent.$emit('error', this)
+          this.tipWords = '验证失败'
+          setTimeout(() => {
+            this.tipWords = ''
+          }, 1000)
+        })
+        this.status = false
+      }
+    },
+
+    refresh: function() {
+      this.showRefresh = true
+      this.finishText = ''
+
+      this.transitionLeft = 'left .3s'
+      this.moveBlockLeft = 0
+
+      this.leftBarWidth = undefined
+      this.transitionWidth = 'width .3s'
+
+      this.leftBarBorderColor = '#ddd'
+      this.moveBlockBackgroundColor = '#fff'
+      this.iconColor = '#000'
+      this.iconClass = 'icon-right'
+      this.isEnd = false
+
+      this.getPictrue()
+      setTimeout(() => {
+        this.transitionWidth = ''
+        this.transitionLeft = ''
+        this.text = this.explain
+      }, 300)
+    },
+
+    // 请求背景图片和验证图片
+    getPictrue() {
+      const data = {
+        captchaType: this.captchaType,
+        clientUid: localStorage.getItem('slider'),
+        ts: Date.now(), // 现在的时间戳
+      }
+      ajCaptcha(data).then(res => {
+          this.backImgBase = res.data.originalImageBase64
+          this.blockBackImgBase = res.data.jigsawImageBase64
+          this.backToken = res.data.token
+          this.secretKey = res.data.secretKey
+      }).catch(res =>{
+        this.tipWords = res.msg
+        this.backImgBase = null
+        this.blockBackImgBase = null
+      })
+    },
+  },
+}
+</script>
+

+ 11 - 0
template/admin/src/components/verifition/utils/ase.js

@@ -0,0 +1,11 @@
+import CryptoJS from 'crypto-js'
+/**
+ * @word 要加密的内容
+ * @keyWord String  服务器随机返回的关键字
+ *  */
+export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
+  var key = CryptoJS.enc.Utf8.parse(keyWord)
+  var srcs = CryptoJS.enc.Utf8.parse(word)
+  var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
+  return encrypted.toString()
+}

+ 36 - 0
template/admin/src/components/verifition/utils/util.js

@@ -0,0 +1,36 @@
+export function resetSize(vm) {
+  var img_width, img_height, bar_width, bar_height	// 图片的宽度、高度,移动条的宽度、高度
+
+  var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
+  var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
+
+  if (vm.imgSize.width.indexOf('%') != -1) {
+    img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px'
+  } else {
+    img_width = this.imgSize.width
+  }
+
+  if (vm.imgSize.height.indexOf('%') != -1) {
+    img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px'
+  } else {
+    img_height = this.imgSize.height
+  }
+
+  if (vm.barSize.width.indexOf('%') != -1) {
+    bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px'
+  } else {
+    bar_width = this.barSize.width
+  }
+
+  if (vm.barSize.height.indexOf('%') != -1) {
+    bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px'
+  } else {
+    bar_height = this.barSize.height
+  }
+
+  return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }
+}
+
+export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
+export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']

+ 7 - 2
template/admin/src/libs/request.js

@@ -34,9 +34,14 @@ service.interceptors.request.use(
     }
     const token = getCookies('token');
     const kefuToken = getCookies('kefu_token');
-    if (token || kefuToken) {
-      config.headers['Authori-zation'] = config.kefu ? 'Bearer ' + kefuToken : 'Bearer ' + token;
+	const fileToken = getCookies('file_token');
+    if (token || kefuToken || fileToken) {
+      config.headers['Authori-zation'] = config.file_edit ? (config.kefu ? 'Bearer ' + kefuToken : 'Bearer ' + fileToken) : (config.kefu ? 'Bearer ' + kefuToken : 'Bearer ' + token);
     }
+	if(fileToken)
+	{
+		config.headers['Invalid-zation'] = 'Bearer ' + fileToken;
+	}
     return config;
   },
   (error) => {

+ 18 - 29
template/admin/src/pages/account/login/index.vue

@@ -52,25 +52,14 @@
         </Form>
       </div>
     </div>
-    <!-- <Modal
-      v-model="modals"
-      scrollable
-      footer-hide
-      closable
-      title="请完成安全校验"
-      :mask-closable="false"
-      :z-index="2"
-      width="342"
-    >
-      <div class="captchaBox">
-        <div id="captcha" style="position: relative" ref="captcha"></div>
-        <div id="msg"></div>
-      </div>
-    </Modal> -->
-    <vcode :show="isShow" @success="closeModel()" @close="closeModel()" successText="验证通过" />
-    <div class="footer">
-      <div class="pull-right">Copyright © 2022 <span id="copyright">CRMEB</span> 版本号:<span>我是版本号</span></div>
-    </div>
+
+    <Verify
+        @success="success"
+        captchaType="blockPuzzle"
+        :imgSize="{ width: '330px', height: '155px' }"
+      ref="verify"
+    ></Verify>
+
   </div>
 </template>
 <script>
@@ -80,12 +69,11 @@ import { getWorkermanUrl } from '@/api/kefu';
 import Setting from '@/setting';
 import { setCookies } from '@/libs/util';
 import '../../../assets/js/canvas-nest.min';
-// import '../../../assets/js/jigsaw.js';
-import Vcode from 'vue-puzzle-vcode';
+import Verify from "@/components/verifition/Verify";
+
 export default {
-  // mixins: [mixins],
   components: {
-    Vcode,
+    Verify,
   },
   data() {
     return {
@@ -185,8 +173,11 @@ export default {
           this.swiperList = [{ slide: this.defaultSwiperList }];
         });
     },
+    success(params){
+      this.closeModel(params);
+    },
     // 关闭模态框
-    closeModel() {
+    closeModel(params) {
       this.isShow = false;
       let msg = this.$Message.loading({
         content: '登录中...',
@@ -198,6 +189,8 @@ export default {
         pwd: this.formInline.password,
         imgcode: this.formInline.code,
         key: this.key,
+        captchaType: 'blockPuzzle',
+        captchaVerification: params.captchaVerification,
       })
         .then(async (res) => {
           msg();
@@ -313,11 +306,7 @@ export default {
     handleSubmit(name) {
       this.$refs[name].validate((valid) => {
         if (valid) {
-          if (this.errorNum >= 2) {
-            this.isShow = true;
-          } else {
-            this.closeModel();
-          }
+          this.$refs.verify.show()
         }
       });
     },

+ 11 - 1
template/admin/src/pages/setting/user/index.vue

@@ -6,7 +6,7 @@
       </div>
     </div>
     <Card :bordered="false" dis-hover class="ivu-mt">
-      <Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="120" label-position="right">
+      <Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="160" label-position="right">
         <FormItem label="账号" prop="">
           <Input type="text" v-model="account" :disabled="true" class="input"></Input>
         </FormItem>
@@ -22,6 +22,12 @@
         <FormItem label="确认新密码" prop="conf_pwd">
           <Input type="password" v-model="formValidate.conf_pwd" class="input"></Input>
         </FormItem>
+		<FormItem label="文件管理新密码" prop="file_pwd">
+		  <Input type="password" v-model="formValidate.file_pwd" class="input"></Input>
+		</FormItem>
+		<FormItem label="文件管理确认新密码" prop="conf_file_pwd">
+		  <Input type="password" v-model="formValidate.conf_file_pwd" class="input"></Input>
+		</FormItem>
         <FormItem>
           <Button type="primary" @click="handleSubmit('formValidate')">提交</Button>
         </FormItem>
@@ -53,12 +59,16 @@ export default {
         pwd: '',
         new_pwd: '',
         conf_pwd: '',
+		file_pwd: '',
+		conf_file_pwd: '',
       },
       ruleValidate: {
         real_name: [{ required: true, message: '您的姓名不能为空', trigger: 'blur' }],
         pwd: [{ required: true, message: '请输入您的原始密码', trigger: 'blur' }],
         new_pwd: [{ required: true, message: '请输入您的新密码', trigger: 'blur' }],
         conf_pwd: [{ required: true, message: '请确认您的新密码', trigger: 'blur' }],
+		file_pwd: [{ required: true, message: '请输入您的文件管理新密码', trigger: 'blur' }],
+		conf_file_pwd: [{ required: true, message: '请确认您的文件管理新密码', trigger: 'blur' }],
       },
     };
   },

+ 160 - 0
template/admin/src/pages/system/maintain/systemFile/components/codemirror.vue

@@ -0,0 +1,160 @@
+<template>
+	<Modal v-model="modals_son" scrollable footer-hide closable :title="title" :mask-closable="false" width="900">
+
+		<Button type="primary" id="savefile" class="mr5 mb15" @click="savefile">保存</Button>
+		<Button id="undo" class="mr5 mb15" @click="undofile">撤销</Button>
+		<Button id="redo" class="mr5 mb15" @click="redofile">回退</Button>
+		<Button id="refresh" class="mb15" @click="refreshfile">刷新</Button>
+		<textarea ref="mycode" class="codesql public_text" v-model="code" style="height: 80vh"></textarea>
+		<Spin size="large" fix v-if="spinShow"></Spin>
+	</Modal>
+</template>
+
+<script>
+	import {
+		opendirLoginApi
+	} from '@/api/system';
+	import CodeMirror from 'codemirror/lib/codemirror';
+	import 'codemirror/theme/ambiance.css';
+	import {
+		setCookies,
+		getCookies,
+		removeCookies
+	} from '@/libs/util';
+
+
+	// 核心样式
+	// import 'codemirror/lib/codemirror.css'
+	// 引入主题后还需要在 options 中指定主题才会生效
+	import 'codemirror/theme/cobalt.css'
+
+	// 需要引入具体的语法高亮库才会有对应的语法高亮效果
+	// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
+	// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
+	// import 'codemirror/mode/javascript/javascript.js'
+	// import 'codemirror/mode/css/css.js'
+	// import 'codemirror/mode/xml/xml.js'
+	// import 'codemirror/mode/clike/clike.js'
+	// import 'codemirror/mode/markdown/markdown.js'
+	// import 'codemirror/mode/python/python.js'
+	// import 'codemirror/mode/r/r.js'
+	// import 'codemirror/mode/shell/shell.js'
+	// import 'codemirror/mode/sql/sql.js'
+	// import 'codemirror/mode/swift/swift.js'
+	// import 'codemirror/mode/vue/vue.js'
+
+
+	require('codemirror/mode/javascript/javascript');
+	export default {
+		name: 'opendir',
+		props: {
+			rows: {
+				type: Object,
+				default: {}
+			},
+			code: {
+				type: String,
+				default: ' '
+			},
+			modals: {
+				type: Boolean,
+				default: false
+			},
+			title: {
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				editor: '',
+				isShowLogn: false, // 登录
+				isShowList: false, // 登录之后列表
+				spinShow: false,
+				loading: false,
+
+				formItem: {
+					dir: '',
+					superior: 0,
+					filedir: '',
+				},
+				pathname: '',
+				modals_son : this.modals
+			};
+		},
+		watch: {
+			code: {
+				handler(newValue, oldValue) {
+					this.editor.setValue(newValue);
+				},
+				deep: true // 默认值是 false,代表是否深度监听
+			},
+			modals: {
+				handler(newValue, oldValue) {
+					this.modals_son = newValue
+				},
+				deep: true // 默认值是 false,代表是否深度监听
+			},
+		},
+		mounted() {
+			this.editor = CodeMirror.fromTextArea(this.$refs.mycode, {
+				value: 'http://www.crmeb.com', // 文本域默认显示的文本
+				mode: 'text/javascript',
+				theme: 'ambiance', // CSS样式选择
+				indentUnit: 8, // 缩进单位,默认2
+				smartIndent: true, // 是否智能缩进
+				tabSize: 4, // Tab缩进,默认4
+				readOnly: false, // 是否只读,默认false
+				showCursorWhenSelecting: true,
+				lineNumbers: true, // 是否显示行号
+
+				indentWithTabs: true,
+				matchBrackets: true,
+				extraKeys: {
+					'Ctrl': 'autocomplete'
+				}, //自定义快捷键
+			});
+			//代码自动提示功能,记住使用cursorActivity事件不要使用change事件,这是一个坑,那样页面直接会卡死
+			editor.on('cursorActivity', function() {
+				editor.showHint()
+			})
+		},
+		created() {
+			// this.getList();
+			this.onIsLogin();
+		},
+		methods: {
+
+			// 保存
+			savefile() {
+				let data = {
+					comment: this.editor.getValue(),
+					filepath: this.pathname,
+				};
+				savefileApi(data)
+					.then(async (res) => {
+						this.$Message.success(res.msg);
+						this.modals = false;
+					})
+					.catch((res) => {
+						this.$Message.error(res.msg);
+					});
+			},
+			// 撤销
+			undofile() {
+				this.editor.undo();
+			},
+			redofile() {
+				this.editor.redo();
+			},
+			// 刷新
+			refreshfile() {
+				this.editor.refresh();
+			},
+		},
+	};
+</script>
+<style scoped lang="stylus">
+>>>.CodeMirror 
+	height: 70vh !important
+</style>

+ 108 - 0
template/admin/src/pages/system/maintain/systemFile/components/loginFrom.vue

@@ -0,0 +1,108 @@
+<template>
+  <Row type="flex">
+    <Col span="24">
+      <div class="index_from page-account-container">
+        <div class="page-account-top">
+          <span class="page-account-top-tit">文件管理登录</span>
+        </div>
+        <Form
+          ref="formInline"
+          :model="formInline"
+          :rules="ruleInline"
+          @submit.native.prevent
+        >
+          <!-- <FormItem prop="sms_account" class="maxInpt">
+            <Input type="text" v-model="formInline.account" prefix="ios-contact-outline" placeholder="请输入手机号" />
+          </FormItem> -->
+          <FormItem prop="sms_token" class="maxInpt">
+            <Input type="password" v-model="formInline.password" prefix="ios-lock-outline" placeholder="请输入密码" />
+          </FormItem>
+          <FormItem class="maxInpt">
+            <Button type="primary" long size="large" @click="handleSubmit('formInline')" class="btn">登录</Button>
+          </FormItem>
+        </Form>
+      </div>
+    </Col>
+  </Row>
+</template>
+
+<script>
+import {
+		opendirLoginApi
+	} from '@/api/system';
+export default {
+  name: 'file_login',
+  data() {
+    const validatePhone = (rule, value, callback) => {
+      if (!value) {
+        return callback(new Error('请填写手机号'));
+      } else if (!/^1[3456789]\d{9}$/.test(value)) {
+        callback(new Error('手机号格式不正确!'));
+      } else {
+        callback();
+      }
+    };
+    return {
+      formInline: {
+        // account: '',
+        password: '',
+      },
+      ruleInline: {
+        // account: [{ required: true, validator: validatePhone, trigger: 'blur' }],
+        password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+      },
+    };
+  },
+  created() {
+    var _this = this;
+    document.onkeydown = function (e) {
+      let key = window.event.keyCode;
+      if (key === 13) {
+        _this.handleSubmit('formInline');
+      }
+    };
+  },
+  methods: {
+    handleSubmit(name) {
+      this.$refs[name].validate((valid) => {
+        if (valid) {
+          opendirLoginApi(this.formInline)
+            .then(async (res) => {
+              this.$Message.success('登录成功!');
+              this.$emit('on-Login',res.data);
+            })
+            .catch((res) => {
+              this.$Message.error(res.msg);
+            });
+        } else {
+          return false;
+        }
+      });
+    },
+  },
+};
+</script>
+
+<style scoped lang="stylus">
+.maxInpt{
+    max-width 400px
+    margin-left auto
+    margin-right auto
+}
+.page-account-container{
+    text-align center
+    padding 50px 0
+}
+.page-account-top{
+    margin-bottom 20px
+}
+.page-account-top-tit
+    font-size 21px
+    color #1890FF
+.page-account-other
+    text-align center
+    color #1890FF
+    font-size 12px
+    span
+        cursor pointer
+</style>

+ 387 - 204
template/admin/src/pages/system/maintain/systemFile/opendir.vue

@@ -1,212 +1,379 @@
 <template>
-  <div>
-    <div class="i-layout-page-header">
-      <div class="i-layout-page-header">
-        <span class="ivu-page-header-title">{{ $route.meta.title }}</span>
-      </div>
-    </div>
-    <Card :bordered="false" dis-hover class="ivu-mt">
-      <div class="backs" @click="goBack"><Icon type="ios-folder-outline" class="mr5" /><span>返回上级</span></div>
-      <Table
-        ref="selection"
-        :columns="columns4"
-        :data="tabList"
-        :loading="loading"
-        no-data-text="暂无数据"
-        highlight-row
-        class="mt20"
-        @on-current-change="currentChange"
-        no-filtered-data-text="暂无筛选结果"
-      >
-        <template slot-scope="{ row }" slot="filename">
-          <Icon type="ios-folder-outline" v-if="row.isDir" class="mr5" />
-          <Icon type="ios-document-outline" v-else class="mr5" />
-          <span>{{ row.filename }}</span>
-        </template>
-        <template slot-scope="{ row }" slot="isWritable">
-          <span v-text="row.isWritable ? '是' : '否'"></span>
-        </template>
-        <template slot-scope="{ row, index }" slot="action">
-          <a @click="open(row)" v-if="row.isDir">打开</a>
-          <a @click="edit(row)" v-else>编辑</a>
-        </template>
-      </Table>
-    </Card>
-    <Modal v-model="modals" scrollable footer-hide closable :title="title" :mask-closable="false" width="900">
-      <Button type="primary" id="savefile" class="mr5 mb15" @click="savefile">保存</Button>
-      <Button id="undo" class="mr5 mb15" @click="undofile">撤销</Button>
-      <!--            <Button id="redo" class="mr5 mb15" @click="redofile">回退</Button>-->
-      <!--            <Button id="refresh" class="mb15" @click="refreshfile">刷新</Button>-->
-      <textarea ref="mycode" class="codesql public_text" v-model="code"></textarea>
-      <Spin size="large" fix v-if="spinShow"></Spin>
-    </Modal>
-  </div>
+	<div>
+		<div class="i-layout-page-header">
+			<div class="i-layout-page-header">
+				<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
+			</div>
+		</div>
+		<Card :bordered="false" dis-hover class="ivu-mt">
+			<login-from v-if="isShowLogn" @on-Login="onLogin"></login-from>
+			<div v-if="isShowList" class="backs" @click="goBack">
+				<Icon type="ios-folder-outline" class="mr5" /><span>返回上级</span>
+			</div>
+			<Table v-if="isShowList" ref="selection" :columns="columns4" :data="tabList" :loading="loading"
+				no-data-text="暂无数据" highlight-row class="mt20" @on-current-change="currentChange"
+				no-filtered-data-text="暂无筛选结果">
+				<template slot-scope="{ row }" slot="filename">
+					<Icon type="ios-folder-outline" v-if="row.isDir" class="mr5" />
+					<Icon type="ios-document-outline" v-else class="mr5" />
+					<span>{{ row.filename }}</span>
+				</template>
+				<template slot-scope="{ row }" slot="isWritable">
+					<span v-text="row.isWritable ? '是' : '否'"></span>
+				</template>
+				<template slot-scope="{ row, index }" slot="action">
+					<a @click="open(row)" v-if="row.isDir">打开</a>
+					<a @click="edit(row)" v-else>编辑</a>
+				</template>
+			</Table>
+		</Card>
+		<!-- <codemirror :rows='rows' :code='code' :modals='modals' :title='title'></codemirror> -->
+		<Modal v-model="modals" scrollable footer-hide closable :title="title" :mask-closable="false" width="900">
+			<Button type="primary" id="savefile" class="mr5 mb15" @click="savefile">保存</Button>
+			<Button id="undo" class="mr5 mb15" @click="undofile">撤销</Button>
+			<Button id="redo" class="mr5 mb15" @click="redofile">回退</Button>
+			<Button id="refresh" class="mb15" @click="refreshfile">刷新</Button>
+			<div class="file-box">
+				<div class="file-left cm-s-ambiance CodeMirror">
+					<Tree :data="navList" :render="renderContent" :load-data="loadData" expand-node></Tree>
+				</div>
+				<div class="file-content">
+					<textarea ref="mycode" class="codesql public_text" v-model="code"></textarea>
+				</div>
+				<Spin size="large" fix v-if="spinShow"></Spin>
+			</div>
+
+		</Modal>
+	</div>
 </template>
 
 <script>
-import { opendirListApi, openfileApi, savefileApi } from '@/api/system';
-import CodeMirror from 'codemirror/lib/codemirror';
-import 'codemirror/theme/ambiance.css';
-require('codemirror/mode/javascript/javascript');
-export default {
-  name: 'opendir',
-  data() {
-    return {
-      code: '',
-      modals: false,
-      spinShow: false,
-      loading: false,
-      tabList: [],
-      columns4: [
-        {
-          title: '文件/文件夹名',
-          slot: 'filename',
-          minWidth: 150,
-          back: '返回上级',
-        },
-        {
-          title: '文件/文件夹路径',
-          key: 'real_path',
-          minWidth: 150,
-        },
-        {
-          title: '文件/文件夹大小',
-          key: 'size',
-          minWidth: 100,
-        },
-        {
-          title: '是否可写',
-          slot: 'isWritable',
-          minWidth: 100,
-        },
-        {
-          title: '更新时间',
-          key: 'mtime',
-          minWidth: 150,
-        },
-        {
-          title: 'Action',
-          slot: 'action',
-          minWidth: 150,
-        },
-      ],
-      formItem: {
-        dir: '',
-        superior: 0,
-        filedir: '',
-      },
-      rows: {},
-      pathname: '',
-      title: '',
-    };
-  },
-  mounted() {
-    this.editor = CodeMirror.fromTextArea(this.$refs.mycode, {
-      value: 'http://www.crmeb.com', // 文本域默认显示的文本
-      mode: 'text/javascript',
-      theme: 'ambiance', // CSS样式选择
-      indentUnit: 4, // 缩进单位,默认2
-      smartIndent: true, // 是否智能缩进
-      tabSize: 4, // Tab缩进,默认4
-      readOnly: false, // 是否只读,默认false
-      showCursorWhenSelecting: true,
-      lineNumbers: true, // 是否显示行号
-    });
-  },
-  created() {
-    this.getList();
-  },
-  methods: {
-    // 点击行
-    currentChange(currentRow) {
-      if (currentRow.isDir) {
-        this.open(currentRow);
-      } else {
-        this.edit(currentRow);
-      }
-    },
-    // 列表
-    getList() {
-      this.loading = true;
-      opendirListApi(this.formItem)
-        .then(async (res) => {
-          let data = res.data;
-          this.tabList = data.list;
-          this.dir = data.dir;
-          this.loading = false;
-        })
-        .catch((res) => {
-          this.loading = false;
-          this.$Message.error(res.msg);
-        });
-    },
-    // 返回上级
-    goBack() {
-      this.formItem = {
-        dir: this.dir,
-        superior: 1,
-        filedir: '',
-      };
-      this.getList();
-    },
-    // 打开
-    open(row) {
-      this.rows = row;
-      this.formItem = {
-        dir: row.path,
-        superior: 0,
-        filedir: row.filename,
-      };
-      this.getList();
-    },
-    // 编辑
-    edit(row) {
-      this.modals = true;
-      this.spinShow = true;
-      this.pathname = row.pathname;
-      this.title = row.filename;
-      openfileApi(row.pathname)
-        .then(async (res) => {
-          let data = res.data;
-          this.code = data.content;
-          this.editor.setValue(this.code);
-          this.spinShow = false;
-        })
-        .catch((res) => {
-          this.spinShow = false;
-          this.$Message.error(res.msg);
-        });
-    },
-    // 保存
-    savefile() {
-      let data = {
-        comment: this.editor.getValue(),
-        filepath: this.pathname,
-      };
-      savefileApi(data)
-        .then(async (res) => {
-          this.$Message.success(res.msg);
-          this.modals = false;
-        })
-        .catch((res) => {
-          this.$Message.error(res.msg);
-        });
-    },
-    // 撤销
-    undofile() {
-      this.editor.undo();
-    },
-    redofile() {
-      this.editor.redo();
-    },
-    // 刷新
-    refreshfile() {
-      this.editor.refresh();
-    },
-  },
-};
-</script>
+	import { resolveComponent } from 'vue'
+	import {
+		opendirListApi,
+		openfileApi,
+		savefileApi,
+		opendirLoginApi
+	} from '@/api/system';
+	import CodeMirror from 'codemirror/lib/codemirror';
+	import loginFrom from './components/loginFrom';
+	import codemirror from './components/codemirror';
+	import 'codemirror/theme/ambiance.css';
+	import {
+		setCookies,
+		getCookies,
+		removeCookies
+	} from '@/libs/util';
+
+
+	// 核心样式
+	// import 'codemirror/lib/codemirror.css'
+	// 引入主题后还需要在 options 中指定主题才会生效
+	import 'codemirror/theme/cobalt.css'
+
+	// 需要引入具体的语法高亮库才会有对应的语法高亮效果
+	// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
+	// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
+	// import 'codemirror/mode/javascript/javascript.js'
+	// import 'codemirror/mode/css/css.js'
+	// import 'codemirror/mode/xml/xml.js'
+	// import 'codemirror/mode/clike/clike.js'
+	// import 'codemirror/mode/markdown/markdown.js'
+	// import 'codemirror/mode/python/python.js'
+	// import 'codemirror/mode/r/r.js'
+	// import 'codemirror/mode/shell/shell.js'
+	// import 'codemirror/mode/sql/sql.js'
+	// import 'codemirror/mode/swift/swift.js'
+	// import 'codemirror/mode/vue/vue.js'
+	import 'codemirror/addon/edit/closebrackets.js'
+
+
+	require('codemirror/mode/javascript/javascript');
+	// import { resolveComponent } from 'vue'
+	export default {
+		name: 'opendir',
+		data() {
+			return {
+				editor: '',
+				isShowLogn: false, // 登录
+				isShowList: false, // 登录之后列表
+				code: '',
+				modals: false,
+				spinShow: false,
+				loading: false,
+				tabList: [],
+				columns4: [{
+						title: '文件/文件夹名',
+						slot: 'filename',
+						minWidth: 150,
+						back: '返回上级',
+					},
+					{
+						title: '文件/文件夹路径',
+						key: 'real_path',
+						minWidth: 150,
+					},
+					{
+						title: '文件/文件夹大小',
+						key: 'size',
+						minWidth: 100,
+					},
+					{
+						title: '是否可写',
+						slot: 'isWritable',
+						minWidth: 100,
+					},
+					{
+						title: '更新时间',
+						key: 'mtime',
+						minWidth: 150,
+					},
+					{
+						title: '操作',
+						slot: 'action',
+						minWidth: 150,
+					},
+				],
+				formItem: {
+					dir: '',
+					superior: 0,
+					filedir: '',
+				},
+				rows: {},
+				pathname: '',
+				title: '',
+				navList: []
+			};
+		},
+		components: {
+			loginFrom,
+			codemirror
+		},
+		mounted() {
+			this.editor = CodeMirror.fromTextArea(this.$refs.mycode, {
+				value: 'http://www.crmeb.com', // 文本域默认显示的文本
+				mode: 'text/javascript',
+				theme: 'ambiance', // CSS样式选择
+				indentUnit: 8, // 缩进单位,默认2
+				smartIndent: true, // 是否智能缩进
+				tabSize: 4, // Tab缩进,默认4
+				readOnly: false, // 是否只读,默认false
+				showCursorWhenSelecting: true,
+				lineNumbers: true, // 是否显示行号
+				lineWrapping: true,   //内容超过编辑器的宽时,应该滚动显示还是换行显示
+				autoCloseBrackets:true,   //代码自动补全
+				indentWithTabs: true,
+				matchBrackets: true,
+				extraKeys: {
+					'Ctrl': 'autocomplete'
+				}, //自定义快捷键
+				gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
+			});
+			// 初始化
+			// this._initialize()
 
+			//代码自动提示功能,记住使用cursorActivity事件不要使用change事件,这是一个坑,那样页面直接会卡死
+			editor.on('cursorActivity', function() {
+				editor.showHint()
+			})
+		},
+		created() {
+			this.getList();
+			// this.onIsLogin();
+		},
+		methods: {
+			
+			
+			
+			// 点击行
+			currentChange(currentRow) {
+				if (currentRow.isDir) {
+					this.open(currentRow);
+				} else {
+					this.edit(currentRow);
+				}
+			},
+			// 列表
+			getList() {
+				this.loading = true;
+				opendirListApi(this.formItem)
+					.then(async (res) => {
+						let data = res.data;
+						this.tabList = data.list;
+						this.navList = data.navList
+						this.dir = data.dir;
+						this.isShowLogn = false;
+						this.isShowList = true;
+						this.loading = false;
+					})
+					.catch((res) => {
+						if (res.status == 110008) {
+							this.$Message.error(res.msg);
+							this.isShowLogn = true;
+							this.isShowList = false;
+							this.loading = false;
+						} else {
+							this.loading = false;
+							this.$Message.error(res.msg);
+						}
+
+					});
+			},
+			// 返回上级
+			goBack() {
+				this.formItem = {
+					dir: this.dir,
+					superior: 1,
+					filedir: '',
+				};
+				this.getList();
+			},
+			// 打开
+			open(row) {
+				this.rows = row;
+				this.formItem = {
+					dir: row.path,
+					superior: 0,
+					filedir: row.filename,
+				};
+				this.getList();
+			},
+			// 编辑
+			edit(row) {
+
+				this.spinShow = true;
+				this.pathname = row.pathname;
+				this.title = row.filename;
+				openfileApi(row.pathname)
+					.then(async (res) => {
+						let data = res.data;
+						this.code = res.data.content;
+						if(data.mode) this.editor.setOption(data.mode);
+						this.editor.setValue(this.code);
+						this.editor.refresh();
+						this.modals = true;
+						this.spinShow = false;
+					})
+					.catch((res) => {
+						this.spinShow = false;
+						this.$Message.error(res.msg);
+					});
+			},
+			// 保存
+			savefile() {
+				let data = {
+					comment: this.editor.getValue(),
+					filepath: this.pathname,
+				};
+				savefileApi(data)
+					.then(async (res) => {
+						this.$Message.success(res.msg);
+						this.modals = false;
+					})
+					.catch((res) => {
+						this.$Message.error(res.msg);
+					});
+			},
+			// 撤销
+			undofile() {
+				this.editor.undo();
+			},
+			redofile() {
+				this.editor.redo();
+			},
+			// 刷新
+			refreshfile() {
+				this.editor.refresh();
+			},
+
+			// 查看是否登录
+			onIsLogin() {
+				this.spinShow = true;
+				let file_login_status = window.localStorage.getItem("file_login_status"); //保存数据
+				if (file_login_status) {
+					this.getList();
+				} else {
+					this.isShowLogn = true;
+					this.spinShow = false;
+					this.isShowList = false;
+				}
+
+
+			},
+			// 登录跳转
+			onLogin(data) {
+				let expires = this.getExpiresTime(data.expires_time);
+				// 记录用户登陆信息
+				setCookies('file_token', data.token, expires);
+				this.getList();
+			},
+			getExpiresTime(expiresTime) {
+				let nowTimeNum = Math.round(new Date() / 1000);
+				let expiresTimeNum = expiresTime - nowTimeNum;
+				return parseFloat(parseFloat(parseFloat(expiresTimeNum / 60) / 60) / 24);
+			},
+			// 侧边栏异步加载
+			loadData (item, callback) {
+				if(item.isDir)
+				{
+					this.formItem = {
+						dir: item.path,
+						superior: 0,
+						filedir: item.title,
+					};
+					opendirListApi(this.formItem)
+						.then(async (res) => {
+							callback(res.data.navList);
+						})
+						.catch((res) => {
+							if (res.status == 110008) {
+								this.$Message.error(res.msg);
+								this.isShowLogn = true;
+								this.isShowList = false;
+								this.loading = false;
+							} else {
+								this.loading = false;
+								this.$Message.error(res.msg);
+							}
+						});
+				}
+			},
+			// 自定义显示
+			renderContent (h, { root, node, data }) {
+				return h('span', {
+					style: {
+						display: "inline-block",
+						cursor: "pointer",
+						userSelect: 'none'
+					},
+					on: {
+						click: () => {
+						  this.clickDir(data);
+						}
+					}
+				},data.title);
+			},
+			clickDir(data){
+				if(!data.isDir)
+				{
+					openfileApi(data.pathname)
+						.then(async (res) => {
+							let data = res.data;
+							this.code = res.data.content;
+							if(data.mode) this.editor.setOption(data.mode);
+							this.editor.setValue(this.code);
+							this.editor.refresh();
+						})
+						.catch((res) => {
+							this.spinShow = false;
+							this.$Message.error(res.msg);
+						});
+				}
+			}
+			
+		},
+	};
+</script>
 <style scoped lang="stylus">
 .mt20
     >>>.ivu-icon-ios-folder-outline
@@ -220,4 +387,20 @@ export default {
 .backs
    cursor pointer
    display inline-block
+>>>.CodeMirror
+ height: 70vh !important
+ 
+.file-box
+	display: flex
+	align-items: center
+	justify-content: space-between
+.file-box
+	.file-left
+		width:25%
+		max-width: 400px
+		overflow: auto
+.file-box
+	.file-content
+		flex: 75%
+		overflow: hidden
 </style>

+ 1 - 0
template/admin/src/utils/modalForm.js

@@ -22,6 +22,7 @@ function getModalInstance(render = undefined) {
       maskClosable: false,
       footerHide: true,
       render: render,
+      // zIndex: 2000,
     });
 
   return modalInstance;