Browse Source

Merge branch 'develop' into prod

scorpio 2 years ago
parent
commit
810b052f93
45 changed files with 5093 additions and 945 deletions
  1. 2 0
      package.json
  2. 0 1
      src/App.vue
  3. 49 41
      src/api/axios.js
  4. 47 0
      src/api/dash/index.js
  5. 5 2
      src/api/fetch.js
  6. 3 1
      src/api/index.js
  7. 9 0
      src/api/role/index.js
  8. 1 1
      src/api/system/index.js
  9. 67 36
      src/api/task/index.js
  10. BIN
      src/assets/img/logo.png
  11. 2 2
      src/assets/style/color.scss
  12. 0 0
      src/assets/svg/task/leave.svg
  13. 1 0
      src/assets/svg/task/stay.svg
  14. 0 0
      src/assets/svg/task/trip.svg
  15. 0 0
      src/assets/svg/task/work.svg
  16. 153 0
      src/components/upload-office/index.vue
  17. 159 0
      src/components/vue-cropper/exif-js-min.js
  18. 2257 0
      src/components/vue-cropper/index.vue
  19. 1 0
      src/layout/index.vue
  20. 0 1
      src/layout/top.vue
  21. 45 0
      src/views/dash/compoents/agency.vue
  22. 77 0
      src/views/dash/compoents/data_show.vue
  23. 67 0
      src/views/dash/compoents/notice.vue
  24. 304 0
      src/views/dash/compoents/profile.vue
  25. 63 0
      src/views/dash/compoents/read.vue
  26. 63 0
      src/views/dash/index.vue
  27. 85 0
      src/views/home/component/params/params8.vue
  28. 0 129
      src/views/home/component/task.vue
  29. 3 0
      src/views/home/pro_detail.vue
  30. 1 1
      src/views/invest/components/years.vue
  31. 1 1
      src/views/invest/index.vue
  32. 13 7
      src/views/resource/component/preview.vue
  33. 0 47
      src/views/task/Index.vue
  34. 258 0
      src/views/task/component/move.vue
  35. 0 171
      src/views/task/component/my-task.vue
  36. 296 0
      src/views/task/component/task-table.vue
  37. 460 153
      src/views/task/component/task.vue
  38. 169 0
      src/views/task/component/tasker.vue
  39. 131 0
      src/views/task/component/wt-label.vue
  40. 107 0
      src/views/task/component/wt-tag.vue
  41. 0 347
      src/views/task/detail.vue
  42. 162 0
      src/views/task/index.vue
  43. 0 1
      src/views/user/manage.vue
  44. 3 3
      vite.config.js
  45. 29 0
      yarn.lock

+ 2 - 0
package.json

@@ -25,6 +25,8 @@
     "vite-plugin-pages": "^0.25.0",
     "vite-plugin-vue-layouts": "^0.7.0",
     "vue": "^3.2.37",
+    "vue-advanced-cropper": "^2.8.8",
+    "vue-cropper": "^1.0.9",
     "vue-qr": "^4.0.9",
     "vue-router": "^4.1.2",
     "vue3-eventbus": "^2.0.0"

+ 0 - 1
src/App.vue

@@ -49,7 +49,6 @@ export default {
   },
   mounted() {
     window.addEventListener('scroll', this.menu)
-    console.log(this.$route.meta.show)
   },
   methods: {
     router,

+ 49 - 41
src/api/axios.js

@@ -26,49 +26,57 @@ NProgress.configure({
   showSpinner: false
 })
 // http request拦截
-axios.interceptors.request.use(config => {
-  // 开启 progress bar
-  NProgress.start()
-  const meta = (config.meta || {})
-  const isToken = meta.isToken === false
-  config.headers.Authorization = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`
-  // 让每个请求携带token
-  if (getToken() && !isToken) {
-    config.headers['Blade-Auth'] = 'bearer ' + getToken()
+axios.interceptors.request.use(
+  config => {
+    // 开启 progress bar
+    NProgress.start()
+    const meta = config.meta || {}
+    const isToken = meta.isToken === false
+    config.headers.Authorization = `Basic ${Base64.encode(
+      `${website.clientId}:${website.clientSecret}`
+    )}`
+    // 让每个请求携带token
+    if (getToken() && !isToken) {
+      config.headers['Blade-Auth'] = 'bearer ' + getToken()
+    }
+    // headers中配置text请求
+    if (config.text === true) {
+      config.headers['Content-Type'] = 'text/plain'
+    }
+    // headers中配置serialize为true开启序列化
+    // if (config.method === 'post' && meta.isSerialize === true) {
+    //   config.data = serialize(config.data)
+    // }
+    return config
+  },
+  error => {
+    return Promise.reject(error)
   }
-  // headers中配置text请求
-  if (config.text === true) {
-    config.headers['Content-Type'] = 'text/plain'
-  }
-  // headers中配置serialize为true开启序列化
-  // if (config.method === 'post' && meta.isSerialize === true) {
-  //   config.data = serialize(config.data)
-  // }
-  return config
-}, error => {
-  return Promise.reject(error)
-})
+)
 // http response 拦截
-axios.interceptors.response.use(res => {
-  // 关闭 progress bar
-  NProgress.done()
-  // 获取状态码
-  const status = res.data.code || res.status
-  const statusWhiteList = website.statusWhiteList || []
-  // 如果在白名单里则自行catch逻辑处理
-  if (statusWhiteList.includes(status)) return Promise.reject(res)
-  // 如果是401则跳转到登录页面
-  if (status === 401) {
-    router.push('/login')
+axios.interceptors.response.use(
+  res => {
+    // 关闭 progress bar
+    NProgress.done()
+    // 获取状态码
+    const status = res.data.code || res.status
+    const statusWhiteList = website.statusWhiteList || []
+    // 如果在白名单里则自行catch逻辑处理
+    if (statusWhiteList.includes(status)) return Promise.reject(res)
+    // 如果是401则跳转到登录页面
+    if (status === 401) {
+      router.push('/login')
+    }
+    // 如果请求为非200否者默认统一处理
+    if (status !== 200) {
+      return Promise.reject(res)
+    }
+    return res
+  },
+  error => {
+    NProgress.done()
+    return Promise.reject(error)
   }
-  // 如果请求为非200否者默认统一处理
-  if (status !== 200) {
-    return Promise.reject(res)
-  }
-  return res
-}, error => {
-  NProgress.done()
-  return Promise.reject(error)
-})
+)
 
 export default axios

+ 47 - 0
src/api/dash/index.js

@@ -0,0 +1,47 @@
+import fetch from '../fetch.js'
+
+export default {
+  /**
+   * 获取我的状态
+   * @returns {Promise<unknown>}
+   */
+  workInfo() {
+    return fetch('/blade-project-manage-v2/index/v2/v2/work-status')
+  },
+  /**
+   * 更新或者修改状态
+   * @param params
+   * @returns {Promise | Promise<unknown>}
+   */
+  submit(params) {
+    return fetch(
+      '/blade-project-manage-v2/index/v2/v2/submit',
+      params,
+      'post',
+      'json'
+    )
+  },
+  /**
+   * 服务商统计数据
+   * @param params
+   * @returns {Promise<unknown>}
+   */
+  dash(params) {
+    return fetch('/blade-project-manage-v2/index/v2/v2/statistics')
+  },
+
+  /**
+   * 公众号文章
+   * @returns {Promise<unknown>}
+   */
+  mpList() {
+    return fetch('/blade-project-manage-v2/index/v2/v2/mp-list')
+  },
+  /**
+   * 头像更新
+   * @returns {Promise | Promise<unknown>}
+   */
+  updateAvatar(params) {
+    return fetch('/blade-user/update', params, 'post', 'json')
+  }
+}

+ 5 - 2
src/api/fetch.js

@@ -73,7 +73,6 @@ function fetch(
       if (status >= 200 && status <= 401) {
         if (data.code === 401 || data.code === 400) {
           // 未登录
-          console.log(data)
           removeToken()
           router.push(`/?redirect=${encodeURIComponent(window.location.href)}`)
           reject(new Error('需要登录'))
@@ -107,7 +106,11 @@ function fetch(
           ElMessage.error(msg)
         } else {
           resolve(err.data)
-          // ElMessage.error(err.data.error_description ? err.data.error_description : '网络异常,请点击重试')
+          // ElMessage.error(
+          //   err.data.error_description
+          //     ? err.data.error_description
+          //     : '网络异常,请点击重试'
+          // )
         }
       })
   })

+ 3 - 1
src/api/index.js

@@ -16,6 +16,7 @@ import msg from './msg/index.js'
 import contract from '@/api/contract/index.js'
 import role from '@/api/role/index.js'
 import resource from '@/api/resource/index.js'
+import dash from '@/api/dash/index.js'
 
 export default {
   offices: ['pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'PDF'],
@@ -37,5 +38,6 @@ export default {
   msg,
   contract,
   role,
-  resource
+  resource,
+  dash
 }

+ 9 - 0
src/api/role/index.js

@@ -10,6 +10,15 @@ export default {
     // 用户列表
     return fetch('/blade-user/noRolePage', params, 'get')
   },
+  /**
+   * 用户列表
+   * @param params
+   * @returns {Promise | Promise<unknown>}
+   */
+  userList(params) {
+    // 用户列表
+    return fetch('/blade-user/noRoleList', params, 'get')
+  },
   roleStart(params) {
     // 用户启用/停用
     return fetch('/blade-user/startOrStop', params, 'post')

+ 1 - 1
src/api/system/index.js

@@ -22,7 +22,7 @@ export default {
    * @returns {Promise | Promise<unknown>}
    */
   getDeptList(params) {
-    return fetch('/blade-system/dept/list', params)
+    return fetch('/blade-system/dept/new-list', params)
   },
   /**
    * 获取公司

+ 67 - 36
src/api/task/index.js

@@ -2,55 +2,86 @@ import fetch from '../fetch.js'
 
 export default {
   /**
-     * 创建任务
-     * @returns {Promise<unknown>}
-     */
-  add (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/createTask', params, 'post', 'json')
-  },
-  taskList (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/list', params)
-  },
-  issuedRecords (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/record', params)
+   * 创建/修改任务
+   * @returns {Promise<unknown>}
+   */
+  addTask(params) {
+    return fetch(
+      '/blade-project-manage-v2/task-management/v2/submit',
+      params,
+      'post',
+      'json'
+    )
   },
-  detail (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/detail', params)
+  /**
+   * 任务列表
+   * @param params
+   * @returns {Promise | Promise<unknown>}
+   */
+  taskList(params) {
+    return fetch('/blade-project-manage-v2/task-management/v2/page', params)
   },
   /**
-   * 任务完成详情
+   * 项目任务列表
    * @param params
    * @returns {Promise | Promise<unknown>}
    */
-  confirmDetail (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/confirmDetail', params)
+  taskListByProject(params) {
+    return fetch(
+      '/blade-project-manage-v2/task-management/v2/project-task-page',
+      params
+    )
   },
-  taskConfirm (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/confirm', params)
+  /**
+   * 任务详情
+   * @param params
+   * @returns {Promise | Promise<unknown>}
+   */
+  taskInfo(params) {
+    return fetch('/blade-project-manage-v2/task-management/v2/detail', params)
   },
-  taskRemove (params) {
-    return fetch('/blade-project-manage-v2/folder/v2/remove', params, 'post')
+  /**
+   * 任务成果文件提交
+   * @param params
+   * @returns {Promise | Promise<unknown>}
+   */
+  taskFile(params) {
+    return fetch(
+      '/blade-project-manage-v2/task-management/v2/submit-file',
+      params
+    )
   },
   /**
-     * 根据文件夹id 获取下级文件
-     * @param params
-     * @returns {Promise | Promise<unknown>}
-     */
-  fileList (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/getListByParentId', params)
+   * 任务成果文件详情
+   * @param params
+   * @returns {Promise | Promise<unknown>}
+   */
+  taskFileInfo(params) {
+    return fetch(
+      '/blade-project-manage-v2/task-management/v2/file-list',
+      params
+    )
   },
   /**
-     * 上传文件任务
-     */
-  uploadFile (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/uploadFile', params, 'post', 'json')
+   * 任务晚抽,移动文件到文件夹
+   * @param params
+   * @returns {Promise<unknown>}
+   */
+  fileMove(params) {
+    return fetch(
+      '/blade-project-manage-v2/task-management/v2/move-file',
+      params
+    )
   },
   /**
-     * 完成任务提交
-     * @param params
-     * @returns {Promise<unknown>}
-     */
-  completeTask (params) {
-    return fetch('/blade-project-manage-v2/userTask/v2/commit', params)
+   * 用户列表
+   * @param params
+   * @returns {Promise<unknown>}
+   */
+  userList(params) {
+    return fetch(
+      '/blade-project-manage-v2/task-management/v2/get-user-list',
+      params
+    )
   }
 }

BIN
src/assets/img/logo.png


+ 2 - 2
src/assets/style/color.scss

@@ -1,6 +1,6 @@
 //$primary: #7C9CFF;
-$primary: #ECAB56;
+$primary: #A3773D;
 $red: #ef0b0b;
 $blue: #409eff;
 $green: #5dc800;
-$border: #e7e7e7;
+$border: #f7f9fc;

File diff suppressed because it is too large
+ 0 - 0
src/assets/svg/task/leave.svg


+ 1 - 0
src/assets/svg/task/stay.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1692602554786" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="38282" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M752 216h33.88C891 216 976 302.072 976 408s-85.008 192-190.12 192h-76.88C660.032 676.952 573.984 728 476 728h-56C267.568 728 144 604.432 144 452V208a32 32 0 0 1 32-32h544a32 32 0 0 1 32 32v8z m0 80v156c0 23.464-2.928 46.248-8.44 68h42.32C846.584 520 896 469.968 896 408s-49.416-112-110.12-112H752z m-568 504h560a40 40 0 1 1 0 80H184a40 40 0 1 1 0-80z" fill="#8a8a8a" p-id="38283"></path></svg>

File diff suppressed because it is too large
+ 0 - 0
src/assets/svg/task/trip.svg


File diff suppressed because it is too large
+ 0 - 0
src/assets/svg/task/work.svg


+ 153 - 0
src/components/upload-office/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <div>
+    <el-upload
+      :action="action"
+      v-model:file-list="fileList"
+      :headers="headers"
+      :data="data"
+      :limit="max"
+      :accept="accept"
+      :multiple="max > 1"
+      :on-success="success"
+      :on-progress="progress"
+      :show-file-list="false"
+    >
+      <el-button type="primary" icon="Upload">{{ btnText }}</el-button>
+    </el-upload>
+    <div class="flex flex-justify-start flex-wrap mt-10">
+      <div
+        v-for="item in resultList"
+        :key="item.id"
+        class="padding-left padding-right radius-5 white grey-9-bg ml-5"
+      ></div>
+    </div>
+    <div class="upload" v-if="drawer && showProgress">
+      <div>
+        <div class="full-width flex flex-justify-end">
+          <el-icon @click="drawer = false">
+            <CircleClose />
+          </el-icon>
+        </div>
+        <div class="mt-20 file-content">
+          <div v-for="file in fileList" :key="file.name" class="mb-20">
+            <div class="flex flex-align-center">
+              <div>{{ file.name }} , ({{ bytesToSize(file.size) }})</div>
+              <div></div>
+            </div>
+            <el-progress :percentage="file.percentage" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import api from '@/api/index.js'
+import { bytesToSize } from '@/utils/tools.js'
+import website from '@/config/website.js'
+import { getToken } from '@/utils/auth.js'
+import { Base64 } from 'js-base64'
+
+export default {
+  props: {
+    btnText: {
+      type: String,
+      default: '文件上传'
+    },
+    accept: {
+      type: String,
+      default: '.pdf, .doc,.docx,.ppt, .pptx, .xls, .xlsx,.PDF'
+    },
+    showProgress: {
+      type: Boolean,
+      default: true
+    },
+    max: {
+      type: Number,
+      default: 9
+    },
+    data: Object,
+    action: {
+      type: String,
+      default: api.uploadPath
+    }
+  },
+  data() {
+    return {
+      drawer: false,
+      headers: {
+        Authorization: `Basic ${Base64.encode(
+          `${website.clientId}:${website.clientSecret}`
+        )}`,
+        'Blade-Auth': 'bearer ' + getToken()
+      },
+      fileList: [],
+      resultList: []
+    }
+  },
+  methods: {
+    bytesToSize,
+    progress(res) {
+      this.drawer = true
+    },
+    success(res) {
+      if (res.code === 200) {
+        this.resultList.push(res.data)
+        this.$emit('upload', this.resultList)
+      }
+      if (this.resultList.length === this.fileList.length) {
+        this.saveLibrary()
+      }
+    },
+    /**
+     * 保存到library
+     */
+    saveLibrary() {
+      this.resultList
+        .filter(ele => api.offices.includes(ele.suffix))
+        .forEach(ele => {
+          console.log(ele)
+          const item = {
+            category: 4,
+            fileId: ele.id,
+            parentId: this.parentId,
+            projectId: this.projectId,
+            stageId: this.stageId,
+            type: 1,
+            title: ele.originalFileName,
+            content: ''
+          }
+          this.$api.common.submit(item).then(res => {
+            if (res.code === 200) {
+              this.drawer = false
+              this.$message.success(res.msg)
+              this.$emit('success', this.resultList)
+            }
+          })
+        })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.upload {
+  position: fixed;
+  z-index: 99;
+  bottom: 0;
+  right: 0;
+  background-color: white;
+  height: 300px;
+  width: 480px;
+  border-top-left-radius: 10px;
+  border: #eeeeee solid 1px;
+  box-shadow: -2px 2px 20px rgba(0, 0, 0, 0.1);
+  padding: 20px;
+}
+
+.file-content {
+  height: 280px;
+  overflow-y: scroll;
+}
+</style>

+ 159 - 0
src/components/vue-cropper/exif-js-min.js

@@ -0,0 +1,159 @@
+const Exif = {};
+
+Exif.getData = (img) => new Promise((reslove, reject) => {
+  let obj = {};
+  getImageData(img).then(data => {
+    obj.arrayBuffer = data;
+    obj.orientation = getOrientation(data);
+    reslove(obj)
+  }).catch(error => {
+    reject(error)
+  })
+})
+
+// 这里的获取exif要将图片转ArrayBuffer对象,这里假设获取了图片的baes64
+// 步骤一
+// base64转ArrayBuffer对象
+function getImageData(img) {
+  let data = null;
+  return new Promise((reslove, reject) => {
+    if (img.src) {
+      if (/^data\:/i.test(img.src)) { // Data URI
+        data = base64ToArrayBuffer(img.src);
+        reslove(data)
+      } else if (/^blob\:/i.test(img.src)) { // Object URL
+        var fileReader = new FileReader();
+        fileReader.onload = function (e) {
+          data = e.target.result;
+          reslove(data)
+        };
+        objectURLToBlob(img.src, function (blob) {
+          fileReader.readAsArrayBuffer(blob);
+        });
+      } else {
+        var http = new XMLHttpRequest();
+        http.onload = function () {
+          if (this.status == 200 || this.status === 0) {
+            data = http.response
+            reslove(data)
+          } else {
+            throw "Could not load image";
+          }
+          http = null;
+        };
+        http.open("GET", img.src, true);
+        http.responseType = "arraybuffer";
+        http.send(null);
+      }
+    } else {
+      reject('img error')
+    }
+  })
+}
+
+function objectURLToBlob(url, callback) {
+  var http = new XMLHttpRequest();
+  http.open("GET", url, true);
+  http.responseType = "blob";
+  http.onload = function (e) {
+    if (this.status == 200 || this.status === 0) {
+      callback(this.response);
+    }
+  };
+  http.send();
+}
+
+
+
+function base64ToArrayBuffer(base64) {
+  base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
+  var binary = atob(base64);
+  var len = binary.length;
+  var buffer = new ArrayBuffer(len);
+  var view = new Uint8Array(buffer);
+  for (var i = 0; i < len; i++) {
+    view[i] = binary.charCodeAt(i);
+  }
+  return buffer;
+}
+// 步骤二,Unicode码转字符串
+// ArrayBuffer对象 Unicode码转字符串
+function getStringFromCharCode(dataView, start, length) {
+  var str = '';
+  var i;
+  for (i = start, length += start; i < length; i++) {
+    str += String.fromCharCode(dataView.getUint8(i));
+  }
+  return str;
+}
+
+// 步骤三,获取jpg图片的exif的角度(在ios体现最明显)
+function getOrientation(arrayBuffer) {
+  var dataView = new DataView(arrayBuffer);
+  var length = dataView.byteLength;
+  var orientation;
+  var exifIDCode;
+  var tiffOffset;
+  var firstIFDOffset;
+  var littleEndian;
+  var endianness;
+  var app1Start;
+  var ifdStart;
+  var offset;
+  var i;
+  // Only handle JPEG image (start by 0xFFD8)
+  if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
+    offset = 2;
+    while (offset < length) {
+      if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
+        app1Start = offset;
+        break;
+      }
+      offset++;
+    }
+  }
+  if (app1Start) {
+    exifIDCode = app1Start + 4;
+    tiffOffset = app1Start + 10;
+    if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
+      endianness = dataView.getUint16(tiffOffset);
+      littleEndian = endianness === 0x4949;
+
+      if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
+        if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
+          firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
+
+          if (firstIFDOffset >= 0x00000008) {
+            ifdStart = tiffOffset + firstIFDOffset;
+          }
+        }
+      }
+    }
+  }
+  if (ifdStart) {
+    length = dataView.getUint16(ifdStart, littleEndian);
+
+    for (i = 0; i < length; i++) {
+      offset = ifdStart + i * 12 + 2;
+      if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
+
+        // 8 is the offset of the current tag's value
+        offset += 8;
+
+        // Get the original orientation value
+        orientation = dataView.getUint16(offset, littleEndian);
+
+        // Override the orientation with its default value for Safari (#120)
+        // if (IS_SAFARI_OR_UIWEBVIEW) {
+        //   dataView.setUint16(offset, 1, littleEndian);
+        // }
+        break;
+      }
+    }
+  }
+  return orientation;
+}
+
+
+
+export default Exif

+ 2257 - 0
src/components/vue-cropper/index.vue

@@ -0,0 +1,2257 @@
+<template>
+  <div
+    class="vue-cropper"
+    ref="cropper"
+    @mouseover="scaleImg"
+    @mouseout="cancelScale"
+  >
+    <div class="cropper-box" v-if="imgs">
+      <div
+        class="cropper-box-canvas"
+        v-show="!loading"
+        :style="{
+          width: trueWidth + 'px',
+          height: trueHeight + 'px',
+          transform:
+            'scale(' +
+            scale +
+            ',' +
+            scale +
+            ') ' +
+            'translate3d(' +
+            x / scale +
+            'px,' +
+            y / scale +
+            'px,' +
+            '0)' +
+            'rotateZ(' +
+            rotate * 90 +
+            'deg)'
+        }"
+      >
+        <img :src="imgs" alt="cropper-img" ref="cropperImg" />
+      </div>
+    </div>
+    <div
+      class="cropper-drag-box"
+      :class="{
+        'cropper-move': move && !crop,
+        'cropper-crop': crop,
+        'cropper-modal': cropping
+      }"
+      @mousedown="startMove"
+      @touchstart="startMove"
+    ></div>
+    <div
+      v-show="cropping"
+      class="cropper-crop-box"
+      :style="{
+        width: cropW + 'px',
+        height: cropH + 'px',
+        transform:
+          'translate3d(' + cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'
+      }"
+    >
+      <span class="cropper-view-box">
+        <img
+          :style="{
+            width: trueWidth + 'px',
+            height: trueHeight + 'px',
+            transform:
+              'scale(' +
+              scale +
+              ',' +
+              scale +
+              ') ' +
+              'translate3d(' +
+              (x - cropOffsertX) / scale +
+              'px,' +
+              (y - cropOffsertY) / scale +
+              'px,' +
+              '0)' +
+              'rotateZ(' +
+              rotate * 90 +
+              'deg)'
+          }"
+          :src="imgs"
+          alt="cropper-img"
+        />
+      </span>
+      <span
+        class="cropper-face cropper-move"
+        @mousedown="cropMove"
+        @touchstart="cropMove"
+      ></span>
+      <span class="crop-info" v-if="info" :style="{ top: cropInfo.top }"
+        >{{ cropInfo.width }} × {{ cropInfo.height }}</span
+      >
+      <span v-if="!fixedBox">
+        <span
+          class="crop-line line-w"
+          @mousedown="changeCropSize($event, false, true, 0, 1)"
+          @touchstart="changeCropSize($event, false, true, 0, 1)"
+        ></span>
+        <span
+          class="crop-line line-a"
+          @mousedown="changeCropSize($event, true, false, 1, 0)"
+          @touchstart="changeCropSize($event, true, false, 1, 0)"
+        ></span>
+        <span
+          class="crop-line line-s"
+          @mousedown="changeCropSize($event, false, true, 0, 2)"
+          @touchstart="changeCropSize($event, false, true, 0, 2)"
+        ></span>
+        <span
+          class="crop-line line-d"
+          @mousedown="changeCropSize($event, true, false, 2, 0)"
+          @touchstart="changeCropSize($event, true, false, 2, 0)"
+        ></span>
+        <span
+          class="crop-point point1"
+          @mousedown="changeCropSize($event, true, true, 1, 1)"
+          @touchstart="changeCropSize($event, true, true, 1, 1)"
+        ></span>
+        <span
+          class="crop-point point2"
+          @mousedown="changeCropSize($event, false, true, 0, 1)"
+          @touchstart="changeCropSize($event, false, true, 0, 1)"
+        ></span>
+        <span
+          class="crop-point point3"
+          @mousedown="changeCropSize($event, true, true, 2, 1)"
+          @touchstart="changeCropSize($event, true, true, 2, 1)"
+        ></span>
+        <span
+          class="crop-point point4"
+          @mousedown="changeCropSize($event, true, false, 1, 0)"
+          @touchstart="changeCropSize($event, true, false, 1, 0)"
+        ></span>
+        <span
+          class="crop-point point5"
+          @mousedown="changeCropSize($event, true, false, 2, 0)"
+          @touchstart="changeCropSize($event, true, false, 2, 0)"
+        ></span>
+        <span
+          class="crop-point point6"
+          @mousedown="changeCropSize($event, true, true, 1, 2)"
+          @touchstart="changeCropSize($event, true, true, 1, 2)"
+        ></span>
+        <span
+          class="crop-point point7"
+          @mousedown="changeCropSize($event, false, true, 0, 2)"
+          @touchstart="changeCropSize($event, false, true, 0, 2)"
+        ></span>
+        <span
+          class="crop-point point8"
+          @mousedown="changeCropSize($event, true, true, 2, 2)"
+          @touchstart="changeCropSize($event, true, true, 2, 2)"
+        ></span>
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+import exifmin from './exif-js-min'
+
+export default {
+  data: function () {
+    return {
+      // 容器高宽
+      w: 0,
+      h: 0,
+      // 图片缩放比例
+      scale: 1,
+      // 图片偏移x轴
+      x: 0,
+      // 图片偏移y轴
+      y: 0,
+      // 图片加载
+      loading: true,
+      // 图片真实宽度
+      trueWidth: 0,
+      // 图片真实高度
+      trueHeight: 0,
+      move: true,
+      // 移动的x
+      moveX: 0,
+      // 移动的y
+      moveY: 0,
+      // 开启截图
+      crop: false,
+      // 正在截图
+      cropping: false,
+      // 裁剪框大小
+      cropW: 0,
+      cropH: 0,
+      cropOldW: 0,
+      cropOldH: 0,
+      // 判断是否能够改变
+      canChangeX: false,
+      canChangeY: false,
+      // 改变的基准点
+      changeCropTypeX: 1,
+      changeCropTypeY: 1,
+      // 裁剪框的坐标轴
+      cropX: 0,
+      cropY: 0,
+      cropChangeX: 0,
+      cropChangeY: 0,
+      cropOffsertX: 0,
+      cropOffsertY: 0,
+      // 支持的滚动事件
+      support: '',
+      // 移动端手指缩放
+      touches: [],
+      touchNow: false,
+      // 图片旋转
+      rotate: 0,
+      isIos: false,
+      orientation: 0,
+      imgs: '',
+      // 图片缩放系数
+      coe: 0.2,
+      // 是否正在多次缩放
+      scaling: false,
+      scalingSet: '',
+      coeStatus: '',
+      // 控制emit触发频率
+      isCanShow: true
+    }
+  },
+  props: {
+    img: {
+      type: [String, Blob, null, File],
+      default: ''
+    },
+    // 输出图片压缩比
+    outputSize: {
+      type: Number,
+      default: 1
+    },
+    outputType: {
+      type: String,
+      default: 'jpeg'
+    },
+    info: {
+      type: Boolean,
+      default: true
+    },
+    // 是否开启滚轮放大缩小
+    canScale: {
+      type: Boolean,
+      default: true
+    },
+    // 是否自成截图框
+    autoCrop: {
+      type: Boolean,
+      default: false
+    },
+    autoCropWidth: {
+      type: [Number, String],
+      default: 0
+    },
+    autoCropHeight: {
+      type: [Number, String],
+      default: 0
+    },
+    // 是否开启固定宽高比
+    fixed: {
+      type: Boolean,
+      default: false
+    },
+    // 宽高比 w/h
+    fixedNumber: {
+      type: Array,
+      default: () => {
+        return [1, 1]
+      }
+    },
+    // 固定大小 禁止改变截图框大小
+    fixedBox: {
+      type: Boolean,
+      default: false
+    },
+    // 输出截图是否缩放
+    full: {
+      type: Boolean,
+      default: false
+    },
+    // 是否可以拖动图片
+    canMove: {
+      type: Boolean,
+      default: true
+    },
+    // 是否可以拖动截图框
+    canMoveBox: {
+      type: Boolean,
+      default: true
+    },
+    // 上传图片按照原始比例显示
+    original: {
+      type: Boolean,
+      default: false
+    },
+    // 截图框能否超过图片
+    centerBox: {
+      type: Boolean,
+      default: false
+    },
+    // 是否根据dpr输出高清图片
+    high: {
+      type: Boolean,
+      default: true
+    },
+    // 截图框展示宽高类型
+    infoTrue: {
+      type: Boolean,
+      default: false
+    },
+    // 可以压缩图片宽高  默认不超过200
+    maxImgSize: {
+      type: [Number, String],
+      default: 2000
+    },
+    // 倍数  可渲染当前截图框的n倍 0 - 1000;
+    enlarge: {
+      type: [Number, String],
+      default: 1
+    },
+
+    // 自动预览的固定宽度
+    preW: {
+      type: [Number, String],
+      default: 0
+    },
+    /*
+      图片布局方式 mode 实现和css背景一样的效果
+      contain  居中布局 默认不会缩放 保证图片在容器里面 mode: 'contain'
+      cover    拉伸布局 填充整个容器  mode: 'cover'
+      如果仅有一个数值被给定,这个数值将作为宽度值大小,高度值将被设定为auto。 mode: '50px'
+      如果有两个数值被给定,第一个将作为宽度值大小,第二个作为高度值大小。 mode: '50px 60px'
+    */
+    mode: {
+      type: String,
+      default: 'contain'
+    },
+    // 限制最小区域,可传1以上的数字和字符串,限制长宽都是这么大
+    // 也可以传数组[90,90]
+    limitMinSize: {
+      type: [Number, Array, String],
+      default: () => {
+        return 10
+      },
+      validator: function (value) {
+        if (Array.isArray(value)) {
+          return Number(value[0]) >= 0 && Number(value[1]) >= 0
+        } else {
+          return Number(value) >= 0
+        }
+      }
+    },
+    // 导出时,填充背景颜色
+    fillColor: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    cropInfo() {
+      const obj = {}
+      obj.top = this.cropOffsertY > 21 ? '-21px' : '0px'
+      obj.width = this.cropW > 0 ? this.cropW : 0
+      obj.height = this.cropH > 0 ? this.cropH : 0
+      if (this.infoTrue) {
+        let dpr = 1
+        if (this.high && !this.full) {
+          dpr = window.devicePixelRatio
+        }
+        if ((this.enlarge !== 1) & !this.full) {
+          dpr = Math.abs(Number(this.enlarge))
+        }
+        obj.width = obj.width * dpr
+        obj.height = obj.height * dpr
+        if (this.full) {
+          obj.width = obj.width / this.scale
+          obj.height = obj.height / this.scale
+        }
+      }
+      obj.width = obj.width.toFixed(0)
+      obj.height = obj.height.toFixed(0)
+      return obj
+    },
+
+    isIE() {
+      const userAgent = navigator.userAgent // 取得浏览器的userAgent字符串
+      const isIE = !!window.ActiveXObject || 'ActiveXObject' in window // 判断是否IE浏览器
+      return isIE
+    },
+
+    passive() {
+      return this.isIE
+        ? null
+        : {
+            passive: false
+          }
+    }
+  },
+  watch: {
+    // 如果图片改变, 重新布局
+    img() {
+      // 当传入图片时, 读取图片信息同时展示
+      console.log('img')
+      this.checkedImg()
+    },
+    imgs(val) {
+      if (val === '') {
+        return
+      }
+      this.reload()
+    },
+    cropW() {
+      this.showPreview()
+    },
+    cropH() {
+      this.showPreview()
+    },
+    cropOffsertX() {
+      this.showPreview()
+    },
+    cropOffsertY() {
+      this.showPreview()
+    },
+    scale(val, oldVal) {
+      this.showPreview()
+    },
+    x() {
+      this.showPreview()
+    },
+    y() {
+      this.showPreview()
+    },
+    autoCrop(val) {
+      if (val) {
+        this.goAutoCrop()
+      }
+    },
+    // 修改了自动截图框
+    autoCropWidth() {
+      if (this.autoCrop) {
+        this.goAutoCrop()
+      }
+    },
+    autoCropHeight() {
+      if (this.autoCrop) {
+        this.goAutoCrop()
+      }
+    },
+    mode() {
+      this.checkedImg()
+    },
+    rotate() {
+      this.showPreview()
+      if (this.autoCrop) {
+        this.goAutoCrop(this.cropW, this.cropH)
+      } else {
+        if (this.cropW > 0 || this.cropH > 0) {
+          this.goAutoCrop(this.cropW, this.cropH)
+        }
+      }
+    }
+  },
+  methods: {
+    getVersion(name) {
+      const arr = navigator.userAgent.split(' ')
+      let chromeVersion = ''
+      let result = 0
+      const reg = new RegExp(name, 'i')
+      for (let i = 0; i < arr.length; i++) {
+        if (reg.test(arr[i])) chromeVersion = arr[i]
+      }
+      if (chromeVersion) {
+        result = chromeVersion.split('/')[1].split('.')
+      } else {
+        result = ['0', '0', '0']
+      }
+      return result
+    },
+    checkOrientationImage(img, orientation, width, height) {
+      // 如果是 chrome内核版本在81 safari 在 605 以上不处理图片旋转
+      // alert(navigator.userAgent)
+      if (this.getVersion('chrome')[0] >= 81) {
+        orientation = -1
+      } else {
+        if (this.getVersion('safari')[0] >= 605) {
+          const safariVersion = this.getVersion('version')
+          if (safariVersion[0] > 13 && safariVersion[1] > 1) {
+            orientation = -1
+          }
+        } else {
+          //  判断 ios 版本进行处理
+          // 针对 ios 版本大于 13.4的系统不做图片旋转
+          const isIos = navigator.userAgent
+            .toLowerCase()
+            .match(/cpu iphone os (.*?) like mac os/)
+          if (isIos) {
+            let version = isIos[1]
+            version = version.split('_')
+            if (version[0] > 13 || (version[0] >= 13 && version[1] >= 4)) {
+              orientation = -1
+            }
+          }
+        }
+      }
+
+      // alert(`当前处理的orientation${orientation}`)
+      const canvas = document.createElement('canvas')
+      const ctx = canvas.getContext('2d')
+      ctx.save()
+
+      switch (orientation) {
+        case 2:
+          canvas.width = width
+          canvas.height = height
+          // horizontal flip
+          ctx.translate(width, 0)
+          ctx.scale(-1, 1)
+          break
+        case 3:
+          canvas.width = width
+          canvas.height = height
+          // 180 graus
+          ctx.translate(width / 2, height / 2)
+          ctx.rotate((180 * Math.PI) / 180)
+          ctx.translate(-width / 2, -height / 2)
+          break
+        case 4:
+          canvas.width = width
+          canvas.height = height
+          // vertical flip
+          ctx.translate(0, height)
+          ctx.scale(1, -1)
+          break
+        case 5:
+          // vertical flip + 90 rotate right
+          canvas.height = width
+          canvas.width = height
+          ctx.rotate(0.5 * Math.PI)
+          ctx.scale(1, -1)
+          break
+        case 6:
+          canvas.width = height
+          canvas.height = width
+          // 90 graus
+          ctx.translate(height / 2, width / 2)
+          ctx.rotate((90 * Math.PI) / 180)
+          ctx.translate(-width / 2, -height / 2)
+          break
+        case 7:
+          // horizontal flip + 90 rotate right
+          canvas.height = width
+          canvas.width = height
+          ctx.rotate(0.5 * Math.PI)
+          ctx.translate(width, -height)
+          ctx.scale(-1, 1)
+          break
+        case 8:
+          canvas.height = width
+          canvas.width = height
+          // -90 graus
+          ctx.translate(height / 2, width / 2)
+          ctx.rotate((-90 * Math.PI) / 180)
+          ctx.translate(-width / 2, -height / 2)
+          break
+        default:
+          canvas.width = width
+          canvas.height = height
+      }
+
+      ctx.drawImage(img, 0, 0, width, height)
+      ctx.restore()
+      canvas.toBlob(
+        blob => {
+          const data = URL.createObjectURL(blob)
+          URL.revokeObjectURL(this.imgs)
+          this.imgs = data
+        },
+        'image/' + this.outputType,
+        1
+      )
+    },
+
+    // checkout img
+    checkedImg() {
+      if (this.img === null || this.img === '') {
+        this.imgs = ''
+        this.clearCrop()
+        return
+      }
+      this.loading = true
+      this.scale = 1
+      this.rotate = 0
+      this.clearCrop()
+      const img = new Image()
+      img.onload = () => {
+        if (this.img === '') {
+          this.$emit('imgLoad', 'error')
+          this.$emit('img-load', 'error')
+          return false
+        }
+
+        let width = img.width
+        let height = img.height
+        exifmin.getData(img).then(data => {
+          this.orientation = data.orientation || 1
+          const max = Number(this.maxImgSize)
+          if (!this.orientation && (width < max) & (height < max)) {
+            this.imgs = this.img
+            return
+          }
+
+          if (width > max) {
+            height = (height / width) * max
+            width = max
+          }
+
+          if (height > max) {
+            width = (width / height) * max
+            height = max
+          }
+          this.checkOrientationImage(img, this.orientation, width, height)
+        })
+      }
+
+      img.onerror = () => {
+        this.$emit('imgLoad', 'error')
+        this.$emit('img-load', 'error')
+      }
+
+      // 判断如果不是base64图片 再添加crossOrigin属性,否则会导致iOS低版本(10.2)无法显示图片
+      if (this.img.substr(0, 4) !== 'data') {
+        img.crossOrigin = ''
+      }
+
+      if (this.isIE) {
+        const xhr = new XMLHttpRequest()
+        xhr.onload = function () {
+          const url = URL.createObjectURL(this.response)
+          img.src = url
+        }
+        xhr.open('GET', this.img, true)
+        xhr.responseType = 'blob'
+        xhr.send()
+      } else {
+        img.src = this.img
+      }
+    },
+    // 当按下鼠标键
+    startMove(e) {
+      e.preventDefault()
+      // 如果move 为true 表示当前可以拖动
+      if (this.move && !this.crop) {
+        if (!this.canMove) {
+          return false
+        }
+        // 开始移动
+        this.moveX =
+          ('clientX' in e ? e.clientX : e.touches[0].clientX) - this.x
+        this.moveY =
+          ('clientY' in e ? e.clientY : e.touches[0].clientY) - this.y
+        if (e.touches) {
+          window.addEventListener('touchmove', this.moveImg)
+          window.addEventListener('touchend', this.leaveImg)
+          if (e.touches.length == 2) {
+            // 记录手指刚刚放上去
+            this.touches = e.touches
+            window.addEventListener('touchmove', this.touchScale)
+            window.addEventListener('touchend', this.cancelTouchScale)
+          }
+        } else {
+          window.addEventListener('mousemove', this.moveImg)
+          window.addEventListener('mouseup', this.leaveImg)
+        }
+        // 触发图片移动事件
+        this.$emit('imgMoving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+        this.$emit('img-moving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+      } else {
+        // 截图ing
+        this.cropping = true
+        // 绑定截图事件
+        window.addEventListener('mousemove', this.createCrop)
+        window.addEventListener('mouseup', this.endCrop)
+        window.addEventListener('touchmove', this.createCrop)
+        window.addEventListener('touchend', this.endCrop)
+        this.cropOffsertX = e.offsetX
+          ? e.offsetX
+          : e.touches[0].pageX - this.$refs.cropper.offsetLeft
+        this.cropOffsertY = e.offsetY
+          ? e.offsetY
+          : e.touches[0].pageY - this.$refs.cropper.offsetTop
+        this.cropX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+        this.cropY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+        this.cropChangeX = this.cropOffsertX
+        this.cropChangeY = this.cropOffsertY
+        this.cropW = 0
+        this.cropH = 0
+      }
+    },
+
+    // 移动端缩放
+    touchScale(e) {
+      e.preventDefault()
+      let scale = this.scale
+      // 记录变化量
+      // 第一根手指
+      const oldTouch1 = {
+        x: this.touches[0].clientX,
+        y: this.touches[0].clientY
+      }
+      const newTouch1 = {
+        x: e.touches[0].clientX,
+        y: e.touches[0].clientY
+      }
+      // 第二根手指
+      const oldTouch2 = {
+        x: this.touches[1].clientX,
+        y: this.touches[1].clientY
+      }
+      const newTouch2 = {
+        x: e.touches[1].clientX,
+        y: e.touches[1].clientY
+      }
+      const oldL = Math.sqrt(
+        Math.pow(oldTouch1.x - oldTouch2.x, 2) +
+          Math.pow(oldTouch1.y - oldTouch2.y, 2)
+      )
+      const newL = Math.sqrt(
+        Math.pow(newTouch1.x - newTouch2.x, 2) +
+          Math.pow(newTouch1.y - newTouch2.y, 2)
+      )
+      const cha = newL - oldL
+      // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
+      // 1px - 0.2
+      let coe = 1
+      coe =
+        coe / this.trueWidth > coe / this.trueHeight
+          ? coe / this.trueHeight
+          : coe / this.trueWidth
+      coe = coe > 0.1 ? 0.1 : coe
+      const num = coe * cha
+      if (!this.touchNow) {
+        this.touchNow = true
+        if (cha > 0) {
+          scale += Math.abs(num)
+        } else if (cha < 0) {
+          scale > Math.abs(num) ? (scale -= Math.abs(num)) : scale
+        }
+        this.touches = e.touches
+        setTimeout(() => {
+          this.touchNow = false
+        }, 8)
+        if (!this.checkoutImgAxis(this.x, this.y, scale)) {
+          return false
+        }
+        this.scale = scale
+      }
+    },
+
+    cancelTouchScale(e) {
+      window.removeEventListener('touchmove', this.touchScale)
+    },
+
+    // 移动图片
+    moveImg(e) {
+      e.preventDefault()
+      if (e.touches && e.touches.length === 2) {
+        this.touches = e.touches
+        window.addEventListener('touchmove', this.touchScale)
+        window.addEventListener('touchend', this.cancelTouchScale)
+        window.removeEventListener('touchmove', this.moveImg)
+        return false
+      }
+      const nowX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+      const nowY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+
+      let changeX, changeY
+      changeX = nowX - this.moveX
+      changeY = nowY - this.moveY
+
+      this.$nextTick(() => {
+        if (this.centerBox) {
+          const axis = this.getImgAxis(changeX, changeY, this.scale)
+          const cropAxis = this.getCropAxis()
+          const imgW = this.trueHeight * this.scale
+          const imgH = this.trueWidth * this.scale
+          let maxLeft, maxTop, maxRight, maxBottom
+          switch (this.rotate) {
+            case 1:
+            case -1:
+            case 3:
+            case -3:
+              maxLeft =
+                this.cropOffsertX -
+                (this.trueWidth * (1 - this.scale)) / 2 +
+                (imgW - imgH) / 2
+              maxTop =
+                this.cropOffsertY -
+                (this.trueHeight * (1 - this.scale)) / 2 +
+                (imgH - imgW) / 2
+              maxRight = maxLeft - imgW + this.cropW
+              maxBottom = maxTop - imgH + this.cropH
+              break
+            default:
+              maxLeft =
+                this.cropOffsertX - (this.trueWidth * (1 - this.scale)) / 2
+              maxTop =
+                this.cropOffsertY - (this.trueHeight * (1 - this.scale)) / 2
+              maxRight = maxLeft - imgH + this.cropW
+              maxBottom = maxTop - imgW + this.cropH
+              break
+          }
+
+          // 图片左边 图片不能超过截图框
+          if (axis.x1 >= cropAxis.x1) {
+            changeX = maxLeft
+          }
+
+          // 图片上边 图片不能超过截图框
+          if (axis.y1 >= cropAxis.y1) {
+            changeY = maxTop
+          }
+
+          // 图片右边
+          if (axis.x2 <= cropAxis.x2) {
+            changeX = maxRight
+          }
+
+          // 图片下边
+          if (axis.y2 <= cropAxis.y2) {
+            changeY = maxBottom
+          }
+        }
+        this.x = changeX
+        this.y = changeY
+        // 触发图片移动事件
+        this.$emit('imgMoving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+        this.$emit('img-moving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+      })
+    },
+    // 移动图片结束
+    leaveImg(e) {
+      window.removeEventListener('mousemove', this.moveImg)
+      window.removeEventListener('touchmove', this.moveImg)
+      window.removeEventListener('mouseup', this.leaveImg)
+      window.removeEventListener('touchend', this.leaveImg)
+      // 触发图片移动事件
+      this.$emit('imgMoving', {
+        moving: false,
+        axis: this.getImgAxis()
+      })
+      this.$emit('img-moving', {
+        moving: false,
+        axis: this.getImgAxis()
+      })
+    },
+    // 缩放图片
+    scaleImg() {
+      if (this.canScale) {
+        window.addEventListener(this.support, this.changeSize, this.passive)
+      }
+    },
+    // 移出框
+    cancelScale() {
+      if (this.canScale) {
+        window.removeEventListener(this.support, this.changeSize)
+      }
+    },
+    // 改变大小函数
+    changeSize(e) {
+      e.preventDefault()
+      let scale = this.scale
+      let change = e.deltaY || e.wheelDelta
+      // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
+      const isFirefox = navigator.userAgent.indexOf('Firefox')
+      change = isFirefox > 0 ? change * 30 : change
+      // 修复ie的滚动缩放
+      if (this.isIE) {
+        change = -change
+      }
+      // 1px - 0.2
+      let coe = this.coe
+      coe =
+        coe / this.trueWidth > coe / this.trueHeight
+          ? coe / this.trueHeight
+          : coe / this.trueWidth
+      const num = coe * change
+      num < 0
+        ? (scale += Math.abs(num))
+        : scale > Math.abs(num)
+        ? (scale -= Math.abs(num))
+        : scale
+      // 延迟0.1s 每次放大大或者缩小的范围
+      const status = num < 0 ? 'add' : 'reduce'
+      if (status !== this.coeStatus) {
+        this.coeStatus = status
+        this.coe = 0.2
+      }
+      if (!this.scaling) {
+        this.scalingSet = setTimeout(() => {
+          this.scaling = false
+          this.coe = this.coe += 0.01
+        }, 50)
+      }
+      this.scaling = true
+      if (!this.checkoutImgAxis(this.x, this.y, scale)) {
+        return false
+      }
+      this.scale = scale
+    },
+
+    // 修改图片大小函数
+    changeScale(num) {
+      let scale = this.scale
+      num = num || 1
+      let coe = 20
+      coe =
+        coe / this.trueWidth > coe / this.trueHeight
+          ? coe / this.trueHeight
+          : coe / this.trueWidth
+      num = num * coe
+      num > 0
+        ? (scale += Math.abs(num))
+        : scale > Math.abs(num)
+        ? (scale -= Math.abs(num))
+        : scale
+      if (!this.checkoutImgAxis(this.x, this.y, scale)) {
+        return false
+      }
+      this.scale = scale
+    },
+    // 创建截图框
+    createCrop(e) {
+      e.preventDefault()
+      // 移动生成大小
+      const nowX =
+        'clientX' in e ? e.clientX : e.touches ? e.touches[0].clientX : 0
+      const nowY =
+        'clientY' in e ? e.clientY : e.touches ? e.touches[0].clientY : 0
+      this.$nextTick(() => {
+        const fw = nowX - this.cropX
+        const fh = nowY - this.cropY
+        if (fw > 0) {
+          this.cropW =
+            fw + this.cropChangeX > this.w ? this.w - this.cropChangeX : fw
+          this.cropOffsertX = this.cropChangeX
+        } else {
+          this.cropW =
+            this.w - this.cropChangeX + Math.abs(fw) > this.w
+              ? this.cropChangeX
+              : Math.abs(fw)
+          this.cropOffsertX =
+            this.cropChangeX + fw > 0 ? this.cropChangeX + fw : 0
+        }
+
+        if (!this.fixed) {
+          if (fh > 0) {
+            this.cropH =
+              fh + this.cropChangeY > this.h ? this.h - this.cropChangeY : fh
+            this.cropOffsertY = this.cropChangeY
+          } else {
+            this.cropH =
+              this.h - this.cropChangeY + Math.abs(fh) > this.h
+                ? this.cropChangeY
+                : Math.abs(fh)
+            this.cropOffsertY =
+              this.cropChangeY + fh > 0 ? this.cropChangeY + fh : 0
+          }
+        } else {
+          const fixedHeight =
+            (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1]
+          if (fixedHeight + this.cropOffsertY > this.h) {
+            this.cropH = this.h - this.cropOffsertY
+            this.cropW =
+              (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0]
+            if (fw > 0) {
+              this.cropOffsertX = this.cropChangeX
+            } else {
+              this.cropOffsertX = this.cropChangeX - this.cropW
+            }
+          } else {
+            this.cropH = fixedHeight
+          }
+          this.cropOffsertY = this.cropOffsertY
+        }
+      })
+    },
+
+    // 改变截图框大小
+    changeCropSize(e, w, h, typeW, typeH) {
+      e.preventDefault()
+      window.addEventListener('mousemove', this.changeCropNow)
+      window.addEventListener('mouseup', this.changeCropEnd)
+      window.addEventListener('touchmove', this.changeCropNow)
+      window.addEventListener('touchend', this.changeCropEnd)
+      this.canChangeX = w
+      this.canChangeY = h
+      this.changeCropTypeX = typeW
+      this.changeCropTypeY = typeH
+      this.cropX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+      this.cropY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+      this.cropOldW = this.cropW
+      this.cropOldH = this.cropH
+      this.cropChangeX = this.cropOffsertX
+      this.cropChangeY = this.cropOffsertY
+      if (this.fixed) {
+        if (this.canChangeX && this.canChangeY) {
+          this.canChangeY = 0
+        }
+      }
+      this.$emit('changeCropSize', {
+        width: this.cropW,
+        height: this.cropH
+      })
+      this.$emit('change-crop-size', {
+        width: this.cropW,
+        height: this.cropH
+      })
+    },
+
+    // 正在改变
+    changeCropNow(e) {
+      e.preventDefault()
+      const nowX =
+        'clientX' in e ? e.clientX : e.touches ? e.touches[0].clientX : 0
+      const nowY =
+        'clientY' in e ? e.clientY : e.touches ? e.touches[0].clientY : 0
+      // 容器的宽高
+      let wrapperW = this.w
+      let wrapperH = this.h
+
+      // 不能超过的坐标轴
+      let minX = 0
+      let minY = 0
+
+      if (this.centerBox) {
+        const axis = this.getImgAxis()
+        const imgW = axis.x2
+        const imgH = axis.y2
+        minX = axis.x1 > 0 ? axis.x1 : 0
+        minY = axis.y1 > 0 ? axis.y1 : 0
+        if (wrapperW > imgW) {
+          wrapperW = imgW
+        }
+
+        if (wrapperH > imgH) {
+          wrapperH = imgH
+        }
+      }
+      const [minCropW, minCropH] = this.checkCropLimitSize()
+      this.$nextTick(() => {
+        const fw = nowX - this.cropX
+        const fh = nowY - this.cropY
+        if (this.canChangeX) {
+          if (this.changeCropTypeX === 1) {
+            if (this.cropOldW - fw < minCropW) {
+              this.cropW = minCropW
+              this.cropOffsertX =
+                this.cropOldW + this.cropChangeX - minX - minCropW
+            } else if (this.cropOldW - fw > 0) {
+              this.cropW =
+                wrapperW - this.cropChangeX - fw <= wrapperW - minX
+                  ? this.cropOldW - fw
+                  : this.cropOldW + this.cropChangeX - minX
+              this.cropOffsertX =
+                wrapperW - this.cropChangeX - fw <= wrapperW - minX
+                  ? this.cropChangeX + fw
+                  : minX
+            } else {
+              this.cropW =
+                Math.abs(fw) + this.cropChangeX <= wrapperW
+                  ? Math.abs(fw) - this.cropOldW
+                  : wrapperW - this.cropOldW - this.cropChangeX
+              this.cropOffsertX = this.cropChangeX + this.cropOldW
+            }
+          } else if (this.changeCropTypeX === 2) {
+            if (this.cropOldW + fw < minCropW) {
+              this.cropW = minCropW
+            } else if (this.cropOldW + fw > 0) {
+              this.cropW =
+                this.cropOldW + fw + this.cropOffsertX <= wrapperW
+                  ? this.cropOldW + fw
+                  : wrapperW - this.cropOffsertX
+              this.cropOffsertX = this.cropChangeX
+            } else {
+              // 右侧坐标抽 超过左侧
+              this.cropW =
+                wrapperW - this.cropChangeX + Math.abs(fw + this.cropOldW) <=
+                wrapperW - minX
+                  ? Math.abs(fw + this.cropOldW)
+                  : this.cropChangeX - minX
+              this.cropOffsertX =
+                wrapperW - this.cropChangeX + Math.abs(fw + this.cropOldW) <=
+                wrapperW - minX
+                  ? this.cropChangeX - Math.abs(fw + this.cropOldW)
+                  : minX
+            }
+          }
+        }
+
+        if (this.canChangeY) {
+          if (this.changeCropTypeY === 1) {
+            if (this.cropOldH - fh < minCropH) {
+              this.cropH = minCropH
+              this.cropOffsertY =
+                this.cropOldH + this.cropChangeY - minY - minCropH
+            } else if (this.cropOldH - fh > 0) {
+              this.cropH =
+                wrapperH - this.cropChangeY - fh <= wrapperH - minY
+                  ? this.cropOldH - fh
+                  : this.cropOldH + this.cropChangeY - minY
+              this.cropOffsertY =
+                wrapperH - this.cropChangeY - fh <= wrapperH - minY
+                  ? this.cropChangeY + fh
+                  : minY
+            } else {
+              this.cropH =
+                Math.abs(fh) + this.cropChangeY <= wrapperH
+                  ? Math.abs(fh) - this.cropOldH
+                  : wrapperH - this.cropOldH - this.cropChangeY
+              this.cropOffsertY = this.cropChangeY + this.cropOldH
+            }
+          } else if (this.changeCropTypeY === 2) {
+            if (this.cropOldH + fh < minCropH) {
+              this.cropH = minCropH
+            } else if (this.cropOldH + fh > 0) {
+              this.cropH =
+                this.cropOldH + fh + this.cropOffsertY <= wrapperH
+                  ? this.cropOldH + fh
+                  : wrapperH - this.cropOffsertY
+              this.cropOffsertY = this.cropChangeY
+            } else {
+              this.cropH =
+                wrapperH - this.cropChangeY + Math.abs(fh + this.cropOldH) <=
+                wrapperH - minY
+                  ? Math.abs(fh + this.cropOldH)
+                  : this.cropChangeY - minY
+              this.cropOffsertY =
+                wrapperH - this.cropChangeY + Math.abs(fh + this.cropOldH) <=
+                wrapperH - minY
+                  ? this.cropChangeY - Math.abs(fh + this.cropOldH)
+                  : minY
+            }
+          }
+        }
+
+        if (this.canChangeX && this.fixed) {
+          const fixedHeight =
+            (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1]
+          if (fixedHeight < minCropH) {
+            this.cropH = minCropH
+            this.cropW = (this.fixedNumber[0] * minCropH) / this.fixedNumber[1]
+            // 这里需要去修改 offsetX的值,去调整因为高度变化而导致的宽度变化
+            if (this.changeCropTypeX === 1) {
+              this.cropOffsertX =
+                this.cropChangeX + (this.cropOldW - this.cropW)
+            }
+          } else if (fixedHeight + this.cropOffsertY > wrapperH) {
+            this.cropH = wrapperH - this.cropOffsertY
+            this.cropW =
+              (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0]
+            if (this.changeCropTypeX === 1) {
+              this.cropOffsertX =
+                this.cropChangeX + (this.cropOldW - this.cropW)
+            }
+          } else {
+            this.cropH = fixedHeight
+          }
+        }
+
+        if (this.canChangeY && this.fixed) {
+          const fixedWidth =
+            (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0]
+          if (fixedWidth < minCropW) {
+            this.cropW = minCropW
+            this.cropH = (this.fixedNumber[1] * minCropW) / this.fixedNumber[0]
+          } else if (fixedWidth + this.cropOffsertX > wrapperW) {
+            this.cropW = wrapperW - this.cropOffsertX
+            this.cropH =
+              (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1]
+          } else {
+            this.cropW = fixedWidth
+          }
+        }
+        // 触发截图框改变大小事件
+        this.$emit('cropSizing', { cropW: this.cropW, cropH: this.cropH })
+        this.$emit('crop-sizing', { cropW: this.cropW, cropH: this.cropH })
+      })
+    },
+
+    checkCropLimitSize() {
+      let { cropW, cropH, limitMinSize } = this
+
+      let limitMinNum = new Array()
+      if (!Array.isArray(limitMinSize)) {
+        limitMinNum = [limitMinSize, limitMinSize]
+      } else {
+        limitMinNum = limitMinSize
+      }
+
+      // 限制最小宽度和高度
+      cropW = parseFloat(limitMinNum[0])
+      cropH = parseFloat(limitMinNum[1])
+      return [cropW, cropH]
+    },
+    // 结束改变
+    changeCropEnd(e) {
+      window.removeEventListener('mousemove', this.changeCropNow)
+      window.removeEventListener('mouseup', this.changeCropEnd)
+      window.removeEventListener('touchmove', this.changeCropNow)
+      window.removeEventListener('touchend', this.changeCropEnd)
+    },
+    // 根据比例x/y,最小宽度,最小高度,现有宽度,现有高度,得到应该有的宽度和高度
+    calculateSize(x, y, minX, minY, w, h) {
+      const ratio = x / y
+      let width = w
+      let height = h
+      // 先根据最小宽度来计算高度
+      if (width < minX) {
+        width = minX
+        height = Math.ceil(width / ratio)
+      }
+      // 如果计算出来的高度小于最小高度,则根据最小高度来重新计算宽度和高度
+      if (height < minY) {
+        height = minY
+        width = Math.ceil(height * ratio)
+        // 如果重新计算的宽度仍然小于最小宽度,则使用最小宽度,并重新计算高度
+        if (width < minX) {
+          width = minX
+          height = Math.ceil(width / ratio)
+        }
+      }
+      // 如果计算出来的宽度或高度小于输入的宽度或高度,则分别使用输入的宽度或高度
+      if (width < w) {
+        width = w
+        height = Math.ceil(width / ratio)
+      }
+      if (height < h) {
+        height = h
+        width = Math.ceil(height * ratio)
+      }
+      return { width, height }
+    },
+    // 创建完成
+    endCrop() {
+      if (this.cropW === 0 && this.cropH === 0) {
+        this.cropping = false
+      }
+      const [minCropW, minCropH] = this.checkCropLimitSize()
+      const { width, height } = this.fixed
+        ? this.calculateSize(
+            this.fixedNumber[0],
+            this.fixedNumber[1],
+            minCropW,
+            minCropH,
+            this.cropW,
+            this.cropH
+          )
+        : { width: minCropW, height: minCropH }
+      if (width > this.cropW) {
+        this.cropW = width
+        if (this.cropOffsertX + width > this.w) {
+          this.cropOffsertX = this.w - width
+        }
+      }
+      if (height > this.cropH) {
+        this.cropH = height
+        if (this.cropOffsertY + height > this.h) {
+          this.cropOffsertY = this.h - height
+        }
+      }
+      window.removeEventListener('mousemove', this.createCrop)
+      window.removeEventListener('mouseup', this.endCrop)
+      window.removeEventListener('touchmove', this.createCrop)
+      window.removeEventListener('touchend', this.endCrop)
+    },
+    // 开始截图
+    startCrop() {
+      this.crop = true
+    },
+    // 停止截图
+    stopCrop() {
+      this.crop = false
+    },
+    // 清除截图
+    clearCrop() {
+      this.cropping = false
+      this.cropW = 0
+      this.cropH = 0
+    },
+    // 截图移动
+    cropMove(e) {
+      e.preventDefault()
+      if (!this.canMoveBox) {
+        this.crop = false
+        this.startMove(e)
+        return false
+      }
+
+      if (e.touches && e.touches.length === 2) {
+        this.crop = false
+        this.startMove(e)
+        this.leaveCrop()
+        return false
+      }
+      window.addEventListener('mousemove', this.moveCrop)
+      window.addEventListener('mouseup', this.leaveCrop)
+      window.addEventListener('touchmove', this.moveCrop)
+      window.addEventListener('touchend', this.leaveCrop)
+      const x = 'clientX' in e ? e.clientX : e.touches[0].clientX
+      const y = 'clientY' in e ? e.clientY : e.touches[0].clientY
+      let newX, newY
+      newX = x - this.cropOffsertX
+      newY = y - this.cropOffsertY
+      this.cropX = newX
+      this.cropY = newY
+      // 触发截图框移动事件
+      this.$emit('cropMoving', {
+        moving: true,
+        axis: this.getCropAxis()
+      })
+      this.$emit('crop-moving', {
+        moving: true,
+        axis: this.getCropAxis()
+      })
+    },
+
+    moveCrop(e, isMove) {
+      let nowX = 0
+      let nowY = 0
+      if (e) {
+        e.preventDefault()
+        nowX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+        nowY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+      }
+      this.$nextTick(() => {
+        let cx, cy
+        let fw = nowX - this.cropX
+        let fh = nowY - this.cropY
+        if (isMove) {
+          fw = this.cropOffsertX
+          fh = this.cropOffsertY
+        }
+        // 不能超过外层容器
+        if (fw <= 0) {
+          cx = 0
+        } else if (fw + this.cropW > this.w) {
+          cx = this.w - this.cropW
+        } else {
+          cx = fw
+        }
+
+        if (fh <= 0) {
+          cy = 0
+        } else if (fh + this.cropH > this.h) {
+          cy = this.h - this.cropH
+        } else {
+          cy = fh
+        }
+
+        // 不能超过图片
+        if (this.centerBox) {
+          const axis = this.getImgAxis()
+          // 横坐标判断
+          if (cx <= axis.x1) {
+            cx = axis.x1
+          }
+
+          if (cx + this.cropW > axis.x2) {
+            cx = axis.x2 - this.cropW
+          }
+
+          // 纵坐标纵轴
+          if (cy <= axis.y1) {
+            cy = axis.y1
+          }
+
+          if (cy + this.cropH > axis.y2) {
+            cy = axis.y2 - this.cropH
+          }
+        }
+
+        this.cropOffsertX = cx
+        this.cropOffsertY = cy
+
+        // 触发截图框移动事件
+        this.$emit('cropMoving', {
+          moving: true,
+          axis: this.getCropAxis()
+        })
+        this.$emit('crop-moving', {
+          moving: true,
+          axis: this.getCropAxis()
+        })
+      })
+    },
+
+    // 算出不同场景下面 图片相对于外层容器的坐标轴
+    getImgAxis(x, y, scale) {
+      x = x || this.x
+      y = y || this.y
+      scale = scale || this.scale
+      // 如果设置了截图框在图片内, 那么限制截图框不能超过图片的坐标
+      // 图片的坐标
+      const obj = {
+        x1: 0,
+        x2: 0,
+        y1: 0,
+        y2: 0
+      }
+      const imgW = this.trueWidth * scale
+      const imgH = this.trueHeight * scale
+      switch (this.rotate) {
+        case 0:
+          obj.x1 = x + (this.trueWidth * (1 - scale)) / 2
+          obj.x2 = obj.x1 + this.trueWidth * scale
+          obj.y1 = y + (this.trueHeight * (1 - scale)) / 2
+          obj.y2 = obj.y1 + this.trueHeight * scale
+          break
+        case 1:
+        case -1:
+        case 3:
+        case -3:
+          obj.x1 = x + (this.trueWidth * (1 - scale)) / 2 + (imgW - imgH) / 2
+          obj.x2 = obj.x1 + this.trueHeight * scale
+          obj.y1 = y + (this.trueHeight * (1 - scale)) / 2 + (imgH - imgW) / 2
+          obj.y2 = obj.y1 + this.trueWidth * scale
+          break
+        default:
+          obj.x1 = x + (this.trueWidth * (1 - scale)) / 2
+          obj.x2 = obj.x1 + this.trueWidth * scale
+          obj.y1 = y + (this.trueHeight * (1 - scale)) / 2
+          obj.y2 = obj.y1 + this.trueHeight * scale
+          break
+      }
+      return obj
+    },
+
+    // 获取截图框的坐标轴
+    getCropAxis() {
+      const obj = {
+        x1: 0,
+        x2: 0,
+        y1: 0,
+        y2: 0
+      }
+      obj.x1 = this.cropOffsertX
+      obj.x2 = obj.x1 + this.cropW
+      obj.y1 = this.cropOffsertY
+      obj.y2 = obj.y1 + this.cropH
+      return obj
+    },
+
+    leaveCrop(e) {
+      window.removeEventListener('mousemove', this.moveCrop)
+      window.removeEventListener('mouseup', this.leaveCrop)
+      window.removeEventListener('touchmove', this.moveCrop)
+      window.removeEventListener('touchend', this.leaveCrop)
+      // 触发截图框移动事件
+      this.$emit('cropMoving', {
+        moving: false,
+        axis: this.getCropAxis()
+      })
+      this.$emit('crop-moving', {
+        moving: false,
+        axis: this.getCropAxis()
+      })
+    },
+
+    getCropChecked(cb) {
+      const canvas = document.createElement('canvas')
+      const img = new Image()
+      const rotate = this.rotate
+      const trueWidth = this.trueWidth
+      const trueHeight = this.trueHeight
+      const cropOffsertX = this.cropOffsertX
+      const cropOffsertY = this.cropOffsertY
+      img.onload = () => {
+        if (this.cropW !== 0) {
+          const ctx = canvas.getContext('2d')
+          let dpr = 1
+          if (this.high & !this.full) {
+            dpr = window.devicePixelRatio
+          }
+          if ((this.enlarge !== 1) & !this.full) {
+            dpr = Math.abs(Number(this.enlarge))
+          }
+          const width = this.cropW * dpr
+          const height = this.cropH * dpr
+          const imgW = trueWidth * this.scale * dpr
+          const imgH = trueHeight * this.scale * dpr
+          // 图片x轴偏移
+          let dx =
+            (this.x - cropOffsertX + (this.trueWidth * (1 - this.scale)) / 2) *
+            dpr
+          // 图片y轴偏移
+          let dy =
+            (this.y - cropOffsertY + (this.trueHeight * (1 - this.scale)) / 2) *
+            dpr
+          // 保存状态
+          setCanvasSize(width, height)
+          ctx.save()
+          // 填充背景颜色
+          if (this.fillColor) {
+            ctx.fillStyle = this.fillColor
+            ctx.fillRect(0, 0, canvas.width, canvas.height)
+          }
+          switch (rotate) {
+            case 0:
+              if (!this.full) {
+                ctx.drawImage(img, dx, dy, imgW, imgH)
+              } else {
+                // 输出原图比例截图
+                setCanvasSize(width / this.scale, height / this.scale)
+                ctx.drawImage(
+                  img,
+                  dx / this.scale,
+                  dy / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            case 1:
+            case -3:
+              if (!this.full) {
+                // 换算图片旋转后的坐标弥补
+                dx = dx + (imgW - imgH) / 2
+                dy = dy + (imgH - imgW) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(img, dy, -dx - imgH, imgW, imgH)
+              } else {
+                setCanvasSize(width / this.scale, height / this.scale)
+                // 换算图片旋转后的坐标弥补
+                dx =
+                  dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
+                dy =
+                  dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(
+                  img,
+                  dy,
+                  -dx - imgH / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            case 2:
+            case -2:
+              if (!this.full) {
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(img, -dx - imgW, -dy - imgH, imgW, imgH)
+              } else {
+                setCanvasSize(width / this.scale, height / this.scale)
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                dx = dx / this.scale
+                dy = dy / this.scale
+                ctx.drawImage(
+                  img,
+                  -dx - imgW / this.scale,
+                  -dy - imgH / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            case 3:
+            case -1:
+              if (!this.full) {
+                // 换算图片旋转后的坐标弥补
+                dx = dx + (imgW - imgH) / 2
+                dy = dy + (imgH - imgW) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(img, -dy - imgW, dx, imgW, imgH)
+              } else {
+                setCanvasSize(width / this.scale, height / this.scale)
+                // 换算图片旋转后的坐标弥补
+                dx =
+                  dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
+                dy =
+                  dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(
+                  img,
+                  -dy - imgW / this.scale,
+                  dx,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            default:
+              if (!this.full) {
+                ctx.drawImage(img, dx, dy, imgW, imgH)
+              } else {
+                // 输出原图比例截图
+                setCanvasSize(width / this.scale, height / this.scale)
+                ctx.drawImage(
+                  img,
+                  dx / this.scale,
+                  dy / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+          }
+          ctx.restore()
+        } else {
+          const width = trueWidth * this.scale
+          const height = trueHeight * this.scale
+          const ctx = canvas.getContext('2d')
+          ctx.save()
+          // 填充背景颜色
+          if (this.fillColor) {
+            ctx.fillStyle = this.fillColor
+            ctx.fillRect(0, 0, canvas.width, canvas.height)
+          }
+          switch (rotate) {
+            case 0:
+              setCanvasSize(width, height)
+              ctx.drawImage(img, 0, 0, width, height)
+              break
+            case 1:
+            case -3:
+              // 旋转90度 或者-270度 宽度和高度对调
+              setCanvasSize(height, width)
+              ctx.rotate((rotate * 90 * Math.PI) / 180)
+              ctx.drawImage(img, 0, -height, width, height)
+              break
+            case 2:
+            case -2:
+              setCanvasSize(width, height)
+              ctx.rotate((rotate * 90 * Math.PI) / 180)
+              ctx.drawImage(img, -width, -height, width, height)
+              break
+            case 3:
+            case -1:
+              setCanvasSize(height, width)
+              ctx.rotate((rotate * 90 * Math.PI) / 180)
+              ctx.drawImage(img, -width, 0, width, height)
+              break
+            default:
+              setCanvasSize(width, height)
+              ctx.drawImage(img, 0, 0, width, height)
+          }
+          ctx.restore()
+        }
+        cb(canvas)
+      }
+      // 判断图片是否是base64
+      const s = this.img.substr(0, 4)
+      if (s !== 'data') {
+        img.crossOrigin = 'Anonymous'
+      }
+      img.src = this.imgs
+
+      function setCanvasSize(width, height) {
+        canvas.width = Math.round(width)
+        canvas.height = Math.round(height)
+      }
+    },
+
+    // 获取转换成base64 的图片信息
+    getCropData(cb) {
+      this.getCropChecked(data => {
+        cb(data.toDataURL('image/' + this.outputType, this.outputSize))
+      })
+    },
+
+    // canvas获取为blob对象
+    getCropBlob(cb) {
+      this.getCropChecked(data => {
+        data.toBlob(
+          blob => cb(blob),
+          'image/' + this.outputType,
+          this.outputSize
+        )
+      })
+    },
+
+    // 自动预览函数
+    showPreview() {
+      // 优化不要多次触发
+      if (this.isCanShow) {
+        this.isCanShow = false
+        setTimeout(() => {
+          this.isCanShow = true
+        }, 16)
+      } else {
+        return false
+      }
+      const w = this.cropW
+      const h = this.cropH
+      const scale = this.scale
+      const obj = {}
+      obj.div = {
+        width: `${w}px`,
+        height: `${h}px`
+      }
+      const transformX = (this.x - this.cropOffsertX) / scale
+      const transformY = (this.y - this.cropOffsertY) / scale
+      const transformZ = 0
+      obj.w = w
+      obj.h = h
+      obj.url = this.imgs
+      obj.img = {
+        width: `${this.trueWidth}px`,
+        height: `${this.trueHeight}px`,
+        transform: `scale(${scale})translate3d(${transformX}px, ${transformY}px, ${transformZ}px)rotateZ(${
+          this.rotate * 90
+        }deg)`
+      }
+      obj.html = `
+      <div class="show-preview" style="width: ${obj.w}px; height: ${
+        obj.h
+      }px; overflow: hidden">
+        <div style="width: ${w}px; height: ${h}px">
+          <img src=${obj.url} style="width: ${this.trueWidth}px; height: ${
+        this.trueHeight
+      }px; transform:
+          scale(${scale})translate3d(${transformX}px, ${transformY}px, ${transformZ}px)rotateZ(${
+        this.rotate * 90
+      }deg)">
+        </div>
+      </div>`
+      this.$emit('realTime', obj)
+      this.$emit('real-time', obj)
+    },
+    // reload 图片布局函数
+    reload() {
+      const img = new Image()
+      img.onload = () => {
+        // 读取图片的信息原始信息, 解析是否需要旋转
+        // 读取图片的旋转信息
+        // 得到外层容器的宽度高度
+        this.w = parseFloat(window.getComputedStyle(this.$refs.cropper).width)
+        this.h = parseFloat(window.getComputedStyle(this.$refs.cropper).height)
+
+        // 存入图片真实高度
+        this.trueWidth = img.width
+        this.trueHeight = img.height
+
+        // 判断是否需要压缩大图
+        if (!this.original) {
+          // 判断布局方式 mode
+          this.scale = this.checkedMode()
+        } else {
+          this.scale = 1
+        }
+
+        this.$nextTick(() => {
+          this.x =
+            -(this.trueWidth - this.trueWidth * this.scale) / 2 +
+            (this.w - this.trueWidth * this.scale) / 2
+          this.y =
+            -(this.trueHeight - this.trueHeight * this.scale) / 2 +
+            (this.h - this.trueHeight * this.scale) / 2
+          this.loading = false
+          // // 获取是否开启了自动截图
+          if (this.autoCrop) {
+            this.goAutoCrop()
+          }
+          // 图片加载成功的回调
+          this.$emit('img-load', 'success')
+          this.$emit('imgLoad', 'success')
+          setTimeout(() => {
+            this.showPreview()
+          }, 20)
+        })
+      }
+      img.onerror = () => {
+        this.$emit('imgLoad', 'error')
+        this.$emit('img-load', 'error')
+      }
+      img.src = this.imgs
+    },
+    // 背景布局的函数
+    checkedMode() {
+      let scale = 1
+      // 通过字符串分割
+      let imgW = this.trueWidth
+      let imgH = this.trueHeight
+      const arr = this.mode.split(' ')
+      switch (arr[0]) {
+        case 'contain':
+          if (this.trueWidth > this.w) {
+            // 如果图片宽度大于容器宽度
+            scale = this.w / this.trueWidth
+          }
+
+          if (this.trueHeight * scale > this.h) {
+            scale = this.h / this.trueHeight
+          }
+          break
+        case 'cover':
+          // 扩展布局 默认填充满整个容器
+          // 图片宽度大于容器
+          imgW = this.w
+          scale = imgW / this.trueWidth
+          imgH = imgH * scale
+          // 如果扩展之后高度小于容器的外层高度 继续扩展高度
+          if (imgH < this.h) {
+            imgH = this.h
+            scale = imgH / this.trueHeight
+          }
+          break
+        default:
+          try {
+            let str = arr[0]
+            if (str.search('px') !== -1) {
+              str = str.replace('px', '')
+              imgW = parseFloat(str)
+              const scaleX = imgW / this.trueWidth
+              let scaleY = 1
+              let strH = arr[1]
+              if (strH.search('px') !== -1) {
+                strH = strH.replace('px', '')
+                imgH = parseFloat(strH)
+                scaleY = imgH / this.trueHeight
+              }
+              scale = Math.min(scaleX, scaleY)
+            }
+            if (str.search('%') !== -1) {
+              str = str.replace('%', '')
+              imgW = (parseFloat(str) / 100) * this.w
+              scale = imgW / this.trueWidth
+            }
+
+            if (arr.length === 2 && str === 'auto') {
+              let str2 = arr[1]
+              if (str2.search('px') !== -1) {
+                str2 = str2.replace('px', '')
+                imgH = parseFloat(str2)
+                scale = imgH / this.trueHeight
+              }
+              if (str2.search('%') !== -1) {
+                str2 = str2.replace('%', '')
+                imgH = (parseFloat(str2) / 100) * this.h
+                scale = imgH / this.trueHeight
+              }
+            }
+          } catch (error) {
+            scale = 1
+          }
+      }
+      return scale
+    },
+    // 自动截图函数
+    goAutoCrop(cw, ch) {
+      if (this.imgs === '' || this.imgs === null) return
+      this.clearCrop()
+      this.cropping = true
+      let maxWidth = this.w
+      let maxHeight = this.h
+      if (this.centerBox) {
+        const switchWH = Math.abs(this.rotate) % 2 > 0
+        const imgW = (switchWH ? this.trueHeight : this.trueWidth) * this.scale
+        const imgH = (switchWH ? this.trueWidth : this.trueHeight) * this.scale
+        maxWidth = imgW < maxWidth ? imgW : maxWidth
+        maxHeight = imgH < maxHeight ? imgH : maxHeight
+      }
+      // 截图框默认大小
+      // 如果为0 那么计算容器大小 默认为80%
+      let w = cw || parseFloat(this.autoCropWidth)
+      let h = ch || parseFloat(this.autoCropHeight)
+      if (w === 0 || h === 0) {
+        w = maxWidth * 0.8
+        h = maxHeight * 0.8
+      }
+      w = w > maxWidth ? maxWidth : w
+      h = h > maxHeight ? maxHeight : h
+      if (this.fixed) {
+        h = (w / this.fixedNumber[0]) * this.fixedNumber[1]
+      }
+      // 如果比例之后 高度大于h
+      if (h > this.h) {
+        h = this.h
+        w = (h / this.fixedNumber[1]) * this.fixedNumber[0]
+      }
+      this.changeCrop(w, h)
+    },
+    // 手动改变截图框大小函数
+    changeCrop(w, h) {
+      if (this.centerBox) {
+        // 修复初始化时候在centerBox=true情况下
+        const axis = this.getImgAxis()
+        if (w > axis.x2 - axis.x1) {
+          // 宽度超标
+          w = axis.x2 - axis.x1
+          h = (w / this.fixedNumber[0]) * this.fixedNumber[1]
+        }
+        if (h > axis.y2 - axis.y1) {
+          // 高度超标
+          h = axis.y2 - axis.y1
+          w = (h / this.fixedNumber[1]) * this.fixedNumber[0]
+        }
+      }
+      // 判断是否大于容器
+      this.cropW = w
+      this.cropH = h
+      this.checkCropLimitSize()
+      this.$nextTick(() => {
+        // 居中走一走
+        this.cropOffsertX = (this.w - this.cropW) / 2
+        this.cropOffsertY = (this.h - this.cropH) / 2
+        if (this.centerBox) {
+          this.moveCrop(null, true)
+        }
+      })
+    },
+    // 重置函数, 恢复组件置初始状态
+    refresh() {
+      const img = this.img
+      this.imgs = ''
+      this.scale = 1
+      this.crop = false
+      this.rotate = 0
+      this.w = 0
+      this.h = 0
+      this.trueWidth = 0
+      this.trueHeight = 0
+      this.clearCrop()
+      this.$nextTick(() => {
+        this.checkedImg()
+      })
+    },
+
+    // 向左边旋转
+    rotateLeft() {
+      this.rotate = this.rotate <= -3 ? 0 : this.rotate - 1
+    },
+
+    // 向右边旋转
+    rotateRight() {
+      this.rotate = this.rotate >= 3 ? 0 : this.rotate + 1
+    },
+
+    // 清除旋转
+    rotateClear() {
+      this.rotate = 0
+    },
+
+    // 图片坐标点校验
+    checkoutImgAxis(x, y, scale) {
+      x = x || this.x
+      y = y || this.y
+      scale = scale || this.scale
+      let canGo = true
+      // 开始校验 如果说缩放之后的坐标在截图框外 则阻止缩放
+      if (this.centerBox) {
+        const axis = this.getImgAxis(x, y, scale)
+        const cropAxis = this.getCropAxis()
+        // 左边的横坐标 图片不能超过截图框
+        if (axis.x1 >= cropAxis.x1) {
+          canGo = false
+        }
+
+        // 右边横坐标
+        if (axis.x2 <= cropAxis.x2) {
+          canGo = false
+        }
+
+        // 纵坐标上面
+        if (axis.y1 >= cropAxis.y1) {
+          canGo = false
+        }
+
+        // 纵坐标下面
+        if (axis.y2 <= cropAxis.y2) {
+          canGo = false
+        }
+      }
+      return canGo
+    }
+  },
+  mounted() {
+    this.support =
+      'onwheel' in document.createElement('div')
+        ? 'wheel'
+        : document.onmousewheel !== undefined
+        ? 'mousewheel'
+        : 'DOMMouseScroll'
+    const that = this
+    const u = navigator.userAgent
+    this.isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
+    // 兼容blob
+    if (!HTMLCanvasElement.prototype.toBlob) {
+      Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
+        value: function (callback, type, quality) {
+          const binStr = atob(this.toDataURL(type, quality).split(',')[1])
+          const len = binStr.length
+          const arr = new Uint8Array(len)
+          for (let i = 0; i < len; i++) {
+            arr[i] = binStr.charCodeAt(i)
+          }
+          callback(new Blob([arr], { type: that.type || 'image/png' }))
+        }
+      })
+    }
+    this.showPreview()
+    this.checkedImg()
+  },
+  unmounted() {
+    window.removeEventListener('mousemove', this.moveCrop)
+    window.removeEventListener('mouseup', this.leaveCrop)
+    window.removeEventListener('touchmove', this.moveCrop)
+    window.removeEventListener('touchend', this.leaveCrop)
+    this.cancelScale()
+  }
+}
+</script>
+
+<style scoped lang="css">
+.vue-cropper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  user-select: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  direction: ltr;
+  touch-action: none;
+  text-align: left;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
+}
+
+.cropper-box,
+.cropper-box-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-face {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background-color: red;
+  user-select: none;
+}
+
+.cropper-box-canvas img {
+  position: relative;
+  text-align: left;
+  user-select: none;
+  transform: none;
+  max-width: none;
+  max-height: none;
+}
+
+.cropper-box {
+  overflow: hidden;
+}
+
+.cropper-move {
+  cursor: move;
+}
+
+.cropper-crop {
+  cursor: crosshair;
+}
+
+.cropper-modal {
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.cropper-crop-box {
+  /*border: 2px solid #39f;*/
+}
+
+.cropper-view-box {
+  display: block;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  outline: 1px solid #39f;
+  outline-color: rgba(51, 153, 255, 0.75);
+  user-select: none;
+}
+
+.cropper-view-box img {
+  user-select: none;
+  text-align: left;
+  max-width: none;
+  max-height: none;
+}
+
+.cropper-face {
+  top: 0;
+  left: 0;
+  background-color: #fff;
+  opacity: 0.1;
+}
+
+.crop-info {
+  position: absolute;
+  left: 0px;
+  min-width: 65px;
+  text-align: center;
+  color: white;
+  line-height: 20px;
+  background-color: rgba(0, 0, 0, 0.8);
+  font-size: 12px;
+}
+
+.crop-line {
+  position: absolute;
+  display: block;
+  width: 100%;
+  height: 100%;
+  opacity: 0.1;
+}
+
+.line-w {
+  top: -3px;
+  left: 0;
+  height: 5px;
+  cursor: n-resize;
+}
+
+.line-a {
+  top: 0;
+  left: -3px;
+  width: 5px;
+  cursor: w-resize;
+}
+
+.line-s {
+  bottom: -3px;
+  left: 0;
+  height: 5px;
+  cursor: s-resize;
+}
+
+.line-d {
+  top: 0;
+  right: -3px;
+  width: 5px;
+  cursor: e-resize;
+}
+
+.crop-point {
+  position: absolute;
+  width: 8px;
+  height: 8px;
+  opacity: 0.75;
+  background-color: #39f;
+  border-radius: 100%;
+}
+
+.point1 {
+  top: -4px;
+  left: -4px;
+  cursor: nw-resize;
+}
+
+.point2 {
+  top: -5px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: n-resize;
+}
+
+.point3 {
+  top: -4px;
+  right: -4px;
+  cursor: ne-resize;
+}
+
+.point4 {
+  top: 50%;
+  left: -4px;
+  margin-top: -3px;
+  cursor: w-resize;
+}
+
+.point5 {
+  top: 50%;
+  right: -4px;
+  margin-top: -3px;
+  cursor: e-resize;
+}
+
+.point6 {
+  bottom: -5px;
+  left: -4px;
+  cursor: sw-resize;
+}
+
+.point7 {
+  bottom: -5px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: s-resize;
+}
+
+.point8 {
+  bottom: -5px;
+  right: -4px;
+  cursor: se-resize;
+}
+
+@media screen and (max-width: 500px) {
+  .crop-point {
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    opacity: 0.45;
+    background-color: #39f;
+    border-radius: 100%;
+  }
+
+  .point1 {
+    top: -10px;
+    left: -10px;
+  }
+
+  .point2,
+  .point4,
+  .point5,
+  .point7 {
+    display: none;
+  }
+
+  .point3 {
+    top: -10px;
+    right: -10px;
+  }
+
+  .point4 {
+    top: 0;
+    left: 0;
+  }
+
+  .point6 {
+    bottom: -10px;
+    left: -10px;
+  }
+
+  .point8 {
+    bottom: -10px;
+    right: -10px;
+  }
+}
+</style>

+ 1 - 0
src/layout/index.vue

@@ -100,6 +100,7 @@ export default {
   methods: {
     getPermission() {
       this.$api.login.getPermission().then(res => {
+        console.log(res)
         if (res.code === 200) {
           this.permission.cleanPermission()
           this.permission.addPermission(res.data)

+ 0 - 1
src/layout/top.vue

@@ -161,7 +161,6 @@ export default {
       this.dataType = tmp
     }
     this.$bus.on('navChange', res => {
-      console.log(res)
       this.dataType = res
     })
     this.$bus.on('sizeChange', res => {

+ 45 - 0
src/views/dash/compoents/agency.vue

@@ -0,0 +1,45 @@
+<template>
+  <div class="full-width flex flex-col">
+    <div class="bold font-16 full-width text-left">{{ title }}</div>
+    <el-empty class="padding-bottom" description="暂无数据"></el-empty>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'agency',
+  props: {
+    type: {
+      type: Number,
+      default: 1
+    }
+  },
+  watch: {
+    type: {
+      handler(val) {
+        if (val === 2) {
+          this.title = '内容更新'
+        }
+      },
+      immediate: true
+    }
+  },
+  data() {
+    return {
+      title: '代办事项'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.read {
+  background: #bf863c;
+  padding: 2px;
+  color: white;
+  border-radius: 3px;
+  font-size: 12px;
+  width: 40px;
+  font-weight: 500;
+}
+</style>

+ 77 - 0
src/views/dash/compoents/data_show.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="full-width flex flex-col full-height" v-if="data">
+    <div class="flex flex-center flex-justify-between">
+      <span class="bold font-16">{{
+        data.dept ? data.dept.deptName : ''
+      }}</span>
+      <span class="font-13 grey-9">数据统计时间:{{ date }}</span>
+    </div>
+    <div
+      class="flex flex-center flex-justify-between mb-15 ml-20"
+      style="margin-top: 65px"
+    >
+      <div class="flex flex-col flex-center">
+        <span class="bold">全部项目数</span>
+        <span class="mt-10 font-34 main-color"
+          >{{ data.projectCount }}<span class="font-13 black"> 个</span></span
+        >
+      </div>
+      <div class="flex flex-col flex-center">
+        <span class="bold">全部报告数</span>
+        <span class="mt-10 font-34 main-color"
+          >{{ data.fileCount }}<span class="font-13 black"> 篇</span></span
+        >
+      </div>
+      <div class="flex flex-col flex-center">
+        <span class="bold">我的项目数</span>
+        <span class="mt-10 font-34 main-color"
+          >{{ data.myProjectCount }}<span class="font-13 black"> 个</span></span
+        >
+      </div>
+      <div class="flex flex-col flex-center mr-20">
+        <span class="bold">我的报告数</span>
+        <span class="mt-10 font-34 main-color"
+          >{{ data.myFileCount }}<span class="font-13 black"> 篇</span></span
+        >
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'data_show',
+  data() {
+    return {
+      data: {},
+      date: ''
+    }
+  },
+  created() {
+    this.fetch()
+    // 获取当前日期
+    const date = new Date()
+    let nowMonth = date.getMonth() + 1
+    let strDate = date.getDate()
+    const seperator = '-'
+    if (nowMonth >= 1 && nowMonth <= 9) {
+      nowMonth = '0' + nowMonth
+    }
+    if (strDate >= 0 && strDate <= 9) {
+      strDate = '0' + strDate
+    }
+    this.date = date.getFullYear() + seperator + nowMonth + seperator + strDate
+  },
+  methods: {
+    fetch() {
+      this.$api.dash.dash().then(res => {
+        if (res.code === 200) {
+          this.data = res.data
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped></style>

+ 67 - 0
src/views/dash/compoents/notice.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="flex flex-center flex-justify-start">
+    <el-icon class="mr-10">
+      <Bell />
+    </el-icon>
+    <div class="roll full-width">
+      <ul :class="{ marquee_top: animate }">
+        <li v-for="(item, index) in msg" :key="index">
+          <span class="main-color">
+            <span>{{ item.title }}</span>
+          </span>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  data() {
+    return {
+      msg: [
+        {
+          title: '系统更新公告'
+        },
+        {
+          title: '政策性银行融资培训'
+        }
+      ],
+      animate: false,
+      setInTime: '' // 定时器
+    }
+  },
+  mounted() {
+    this.setInTime = setInterval(this.showMarquee, 5000)
+  },
+  unmounted() {
+    clearInterval(this.setInTime) // 页面销毁时清除定时器
+  },
+  methods: {
+    // 滚动栏滚动
+    showMarquee() {
+      this.animate = true
+      setTimeout(() => {
+        this.msg.push(this.msg[0])
+        this.msg.shift()
+        this.animate = false
+      }, 500)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.roll {
+  height: 20px;
+  width: 1200px;
+  text-align: left;
+  overflow: hidden;
+}
+
+.marquee_top {
+  transition: all 0.5s;
+  margin-top: -20px; /* 容器高度 */
+}
+</style>

+ 304 - 0
src/views/dash/compoents/profile.vue

@@ -0,0 +1,304 @@
+<template>
+  <div class="full-width flex">
+    <div class="flex flex-col flex-justify-around flex-center full-height">
+      <div class="bold font-16" style="margin-top: -10px">我的信息</div>
+      <div class="mt-10 ml-20" @click="show = true">
+        <el-avatar :src="user.info.avatarUrl" :size="100"></el-avatar>
+        <el-icon :size="25" style="margin-left: -30px">
+          <EditPen />
+        </el-icon>
+      </div>
+      <div class="mt-10 font-16 main-color bold ml-15">
+        {{ user.info.nickName }}
+      </div>
+    </div>
+    <div
+      class="flex flex-col flex-child-average mt-20"
+      style="margin-left: 60px"
+    >
+      <el-input
+        :placeholder="placeholder"
+        size="large"
+        maxlength="6"
+        v-model="status"
+        class="input"
+      ></el-input>
+      <div
+        class="flex flex-center flex-justify-between mt-10 padding-left padding-right"
+      >
+        <el-tooltip
+          v-for="icon in icons"
+          :key="icon.value"
+          :content="icon.tips"
+        >
+          <img
+            :src="icon.icon"
+            class="icon-task pointer"
+            @click="changeWorkStatus(icon)"
+          />
+        </el-tooltip>
+      </div>
+    </div>
+
+    <el-dialog title="修改头像" v-model="show" width="780px" @close="close">
+      <div>
+        <div>
+          <span
+            class="full-width text-left flex flex-justify-start padding-top padding-bottom"
+          >
+            新头像不允许涉及政治敏感与色情;<br />
+            图片格式必须为:png,jpeg,jpg;不可大于2M;<br />
+            建议使用png格式图片,以保持最佳效果;建议图片尺寸为144px*144px
+          </span>
+          <div class="flex flex-justify-start padding-bottom">
+            <upload-office
+              accept=".jpg,.png,.jpeg"
+              :max="1"
+              :show-progress="false"
+              @upload="upload"
+            />
+          </div>
+        </div>
+        <el-divider />
+        <div class="flex flex-justify-start">
+          <div class="ml-20 flex flex-justify-start flex-col">
+            <div class="bold text-left">头像裁剪</div>
+            <div class="mt-20 upload flex flex-center">
+              <cropper
+                ref="cropper"
+                class="cropper"
+                :src="img"
+                :stencil-props="{
+                  aspectRatio: 1 / 1,
+                  resizable: false
+                }"
+                @change="changeAvatar"
+              ></cropper>
+            </div>
+          </div>
+          <div class="ml-20 flex flex-justify-start flex-col">
+            <div class="bold text-left">头像预览</div>
+            <div class="mt-20 preview flex flex-center">
+              <el-avatar
+                v-if="previewImg"
+                :size="114"
+                fit="fill"
+                :src="previewImg"
+              ></el-avatar>
+
+              <el-avatar
+                v-if="previewImg"
+                :size="114"
+                shape="square"
+                fit="fill"
+                class="ml-20"
+                :src="previewImg"
+              ></el-avatar>
+            </div>
+          </div>
+        </div>
+        <div class="mt-20 full-width flex flex-justify-end">
+          <el-button type="primary" plain @click="show = false"
+            >取 消
+          </el-button>
+          <el-button type="primary" @click="uploadImage">确 定</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { useStore } from '@/store/user.js'
+import { Cropper } from 'vue-advanced-cropper'
+import 'vue-advanced-cropper/dist/style.css'
+import api from '@/api/index.js'
+import website from '@/config/website.js'
+import { getToken } from '@/utils/auth.js'
+import { Base64 } from 'js-base64'
+import uploadOffice from '@/components/upload-office/index.vue'
+
+export default {
+  components: {
+    Cropper,
+    uploadOffice
+  },
+  setup() {
+    const user = useStore()
+    return { user }
+  },
+
+  watch: {
+    status: {
+      handler(val) {
+        if (val.length > 0) {
+          setTimeout(() => {
+            this.update()
+          }, 2000)
+        }
+      },
+      immediate: true
+    }
+  },
+  data() {
+    return {
+      show: false,
+      status: '',
+      img: '',
+      previewImg: '',
+      placeholder: '请填写状态(最长6个字符)',
+      id: '',
+      headers: {
+        Authorization: `Basic ${Base64.encode(
+          `${website.clientId}:${website.clientSecret}`
+        )}`,
+        'Blade-Auth': 'bearer ' + getToken()
+      },
+      icons: [
+        {
+          icon: new URL('../../../assets/svg/task/work.svg', import.meta.url)
+            .href,
+          tips: '工作中',
+          value: 0
+        },
+        {
+          icon: new URL('../../../assets/svg/task/trip.svg', import.meta.url)
+            .href,
+          tips: '出差中',
+          value: 1
+        },
+        {
+          icon: new URL('../../../assets/svg/task/leave.svg', import.meta.url)
+            .href,
+          tips: '休假中',
+          value: 2
+        },
+        {
+          icon: new URL('../../../assets/svg/task/stay.svg', import.meta.url)
+            .href,
+          tips: '空闲中',
+          value: 3
+        }
+      ]
+    }
+  },
+  created() {
+    this.info()
+  },
+  methods: {
+    info() {
+      this.$api.dash.workInfo().then(res => {
+        if (res.code === 200) {
+          this.placeholder = res.data.workStatusDescribe
+            ? res.data.workStatusDescribe
+            : '请填写状态(最长6个字符)'
+          this.id = res.data.id === -1 ? '' : res.data.id
+        } else {
+          console.log(res)
+        }
+      })
+    },
+    update() {
+      if (this.status.length === 0) {
+        return
+      }
+      this.$api.dash
+        .submit({ id: this.id, workStatusDescribe: this.status })
+        .then(res => {
+          if (res.code === 200) {
+            console.log(res)
+          } else {
+            console.log(res)
+          }
+        })
+    },
+    changeWorkStatus(res) {
+      this.status = res.tips
+    },
+    upload(list) {
+      this.previewImg = list[0].filePath
+      this.img = list[0].filePath
+    },
+    changeAvatar({ coordinates, image, canvas }) {
+      this.previewImg = canvas.toDataURL()
+    },
+    uploadImage() {
+      const { canvas } = this.$refs.cropper.getResult()
+      if (canvas) {
+        const form = new FormData()
+        canvas.toBlob(blob => {
+          form.append('file', blob)
+          // You can use axios, superagent and other libraries instead here
+          fetch(api.uploadPath, {
+            method: 'POST',
+            headers: this.headers,
+            body: form
+          })
+            .then(response => response.json())
+            .then(body => {
+              if (body.code === 200) {
+                this.updateAvatar(body.data.filePath)
+              }
+            })
+        }, 'image/png')
+      }
+    },
+    updateAvatar(avatar) {
+      const data = {
+        id: this.user.info.userId,
+        deptId: this.user.info.deptId,
+        avatar
+      }
+      this.$api.dash.updateAvatar(data).then(res => {
+        if (res.code === 200) {
+          this.$message.success(res.msg)
+          this.user.info.avatarUrl = avatar
+          this.user.setUserInfo(this.user.info)
+          this.show = false
+        } else {
+          this.$message.error(res.msg)
+        }
+      })
+    },
+    close() {
+      this.img = ''
+      this.previewImg = ''
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.preview {
+  background-color: #eeeeee;
+  height: 240px;
+  width: 360px;
+  border-radius: 8px;
+}
+
+.upload {
+  background-color: #eeeeee;
+  height: 240px;
+  width: 320px;
+  border-radius: 8px;
+}
+
+.icon-task {
+  width: 36px;
+  height: 45px;
+  object-fit: fill;
+}
+
+.cropper {
+  width: 280px;
+  height: 280px;
+}
+
+.input {
+  height: 100px;
+
+  :deep(.el-input__wrapper) {
+    background-color: #eeeeee;
+  }
+}
+</style>

+ 63 - 0
src/views/dash/compoents/read.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="flex flex-col full-width mb-20">
+    <div class="full-width text-left bold font-16">今日学习</div>
+    <div class="flex flex-center">
+      <div class="flex flex-center ml-10" style="flex: 5">
+        <div
+          class="flex flex-col flex-center border radius box-shadow mr-15 mt-15"
+          v-for="item in 4"
+        >
+          <div>
+            <img
+              src="../../../assets/img/code.png"
+              style="width: 50px; height: 120px"
+            />
+          </div>
+          <div class="bold padding">
+            文章标题文章标题文章标题文章标题文章标题文章标题文章标题
+          </div>
+          <div class="lines-2 lines-height-15 grey-9 padding">
+            文章内容文章内容文章内容文章内容文章内容文章内容
+          </div>
+        </div>
+      </div>
+
+      <div class="flex flex-center flex-col" style="flex: 1">
+        <img
+          src="../../../assets/img/code.png"
+          style="width: 80px; height: 80px"
+        />
+        <span class="mt-10 font-13"
+          >微信扫描关注公众号<br />及时掌握更多资讯</span
+        >
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'read',
+  data() {
+    return {
+      data: []
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.$api.dash.mpList().then(res => {
+        if (res.code === 200) {
+          console.log(res)
+        } else {
+          console.log(res)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped></style>

+ 63 - 0
src/views/dash/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <div>
+    <el-card :shadow="hover">
+      <div class="full-width flex flex-center flex-justify-start">
+        <notice :data="[333, 44]" />
+      </div>
+    </el-card>
+    <div class="flex flex-center flex-justify-between">
+      <el-card :shadow="hover" class="flex-child-average mt-20 card1">
+        <profile />
+      </el-card>
+      <div class="padding"></div>
+      <el-card :shadow="hover" class="flex-child-average mt-20 card1">
+        <data-show />
+      </el-card>
+    </div>
+    <div class="flex flex-center flex-justify-between">
+      <el-card
+        :shadow="hover"
+        class="flex-child-average mt-20"
+        style="height: 300px"
+      >
+        <agency :type="1" />
+      </el-card>
+      <div class="padding"></div>
+      <el-card
+        :shadow="hover"
+        class="flex-child-average mt-20"
+        style="height: 300px"
+      >
+        <agency :type="2" />
+      </el-card>
+    </div>
+    <!--    <el-card :shadow="hover" class="mt-20">-->
+    <!--      <read />-->
+    <!--    </el-card>-->
+  </div>
+</template>
+
+<route>
+{
+path: '/',
+name: '首页',
+}
+</route>
+
+<script>
+import Profile from '@/views/dash/compoents/profile.vue'
+import dataShow from '@/views/dash/compoents/data_show.vue'
+import agency from '@/views/dash/compoents/agency.vue'
+import read from '@/views/dash/compoents/read.vue'
+import notice from '@/views/dash/compoents/notice.vue'
+
+export default {
+  components: { Profile, dataShow, agency, read, notice }
+}
+</script>
+
+<style lang="scss" scoped>
+.card1 {
+  height: 220px;
+}
+</style>

+ 85 - 0
src/views/home/component/params/params8.vue

@@ -0,0 +1,85 @@
+<template>
+  <div
+    class="flex flex-center full-width flex-justify-between mt-10 mb-10 flex-col"
+  >
+    <span class="full-width text-left bold mt-20 mb-10">编制任务</span>
+    <div style="width: 92%">
+      <task-table
+        :option="option"
+        :data="data"
+        :project-id="detail.id"
+        :total="total"
+        @refresh="list"
+      ></task-table>
+    </div>
+  </div>
+</template>
+
+<script>
+import TaskTable from '@/views/task/component/task-table.vue'
+
+export default {
+  components: { TaskTable },
+  props: {
+    detail: {
+      type: Object,
+      default: null
+    }
+  },
+  watch: {
+    detail: {
+      handler(val) {
+        if (val !== null && val !== undefined) {
+          this.list()
+        }
+      },
+      immediate: true
+    }
+  },
+  data() {
+    return {
+      data: [],
+      task: {},
+      total: 0,
+      option: {
+        showCheckBox: false,
+        folderChecked: true,
+        column: [
+          {
+            label: '共20个任务',
+            prop: 'title',
+            display: false,
+            width: 300
+          },
+          {
+            label: '标签',
+            prop: 'createUserName'
+          },
+          {
+            label: '时间',
+            prop: 'createUserName'
+          },
+          {
+            label: '执行人',
+            prop: 'createTime'
+          }
+        ]
+      }
+    }
+  },
+  methods: {
+    list() {
+      this.$api.task
+        .taskListByProject({ projectId: this.detail.id, level: 1 })
+        .then(res => {
+          if (res.code === 200) {
+            this.data = res.data.records
+            this.total = res.data.total
+          }
+        })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 0 - 129
src/views/home/component/task.vue

@@ -1,129 +0,0 @@
-<template>
-  <div>
-    <el-form :rules="rules" :model="form">
-      <el-form-item label="任务名称" prop="title">
-        <el-input v-model="form.title" placeholder="请填写任务名称"></el-input>
-      </el-form-item>
-      <el-form-item label="任务说明" prop="remark">
-        <el-input
-          v-model="form.remark"
-          type="textarea"
-          :rows="6"
-          placeholder="请填写任务说明"
-        ></el-input>
-      </el-form-item>
-      <el-form-item label="任务期限">
-        <el-date-picker
-          v-model="form.date"
-          type="daterange"
-          unlink-panels
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="截止日期"
-          format="YYYY-MM-DD"
-          value-format="YYYY-MM-DD"
-        />
-      </el-form-item>
-      <div class="full-width flex flex-align-center mt-10">
-        <span class="font-14 bold">生成二维码</span>
-        <el-button
-          circle
-          class="ml-20"
-          icon="Picture"
-          type="danger"
-          @click="initCode()"
-        />
-      </div>
-      <div
-        class="full-width mt-20 border-top padding-top flex flex-justify-end"
-      >
-        <el-button @click="close">取消</el-button>
-        <el-button type="primary" @click="close">关闭</el-button>
-      </div>
-    </el-form>
-    <el-dialog v-model="qrCodeShow" width="400px">
-      <div
-        class="flex flex-col flex-center"
-        style="height: 400rpx; width: 400rpx"
-      >
-        <vue-qr
-          :currentLevel="3"
-          :logoCornerRadius="4"
-          :logoScale="0.25"
-          :text="qrCodeText"
-          size="340"
-        />
-        <span>右键复制二维码,通过微信进行分享</span>
-        <span>{{ qrCodeText }}</span>
-      </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-import VueQr from 'vue-qr/src/packages/vue-qr.vue'
-import { objectToParams } from '@/utils/tools.js'
-export default {
-  name: 'task',
-  components: { VueQr },
-  props: {
-    projectId: {
-      type: String,
-      default: ''
-    },
-    folders: {
-      type: Array,
-      default: []
-    }
-  },
-  data() {
-    return {
-      qrCodeText: '',
-      qrCodeShow: false,
-      rules: {
-        title: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
-        remark: [{ required: true, message: '请输入任务说明', trigger: 'blur' }]
-      },
-      form: {
-        title: '',
-        remark: '',
-        date: '',
-        taskStartTime: '',
-        taskEndTime: ''
-      }
-    }
-  },
-  methods: {
-    initCode() {
-      // this.qrCodeShow = true
-      if (this.form.date.length !== 2) {
-        this.$message.error('请按要求选择时间段')
-        return
-      }
-      this.form.folderIds = this.folders.map(sub => sub.id)
-      this.form.taskStartTime = this.form.date[0]
-      this.form.taskEndTime = this.form.date[1]
-      this.form.projectId = this.projectId
-      this.$api.task.add(this.form).then(res => {
-        if (res.code === 200) {
-          this.$message.success(res.msg)
-          this.qrCodeShow = true
-          const data = res.data
-          this.qrCodeText =
-            'https://prod.wutongshucloud.com/apply?id=' +
-            data.qrcodeId +
-            '&' +
-            objectToParams(res.data)
-        } else {
-          this.$message.error(res.msg)
-        }
-      })
-    },
-    close() {
-      this.$emit('close')
-    }
-  }
-}
-</script>
-
-<style scoped></style>

+ 3 - 0
src/views/home/pro_detail.vue

@@ -24,6 +24,7 @@
         <basic-container v-for="(item, index) in resultList" :key="item.id">
           <div class="full-width padding-top" :id="`header` + (index + 1)">
             <params1 v-if="item.dictKey === '1'" :info="item" :detail="data" />
+            <params8 v-if="item.dictKey === '1'" :info="item" :detail="data" />
             <params2 v-if="item.dictKey === '2'" :info="item" :detail="data" />
             <params7 v-if="item.dictKey === '3'" :info="item" :detail="data" />
             <params4 v-if="item.dictKey === '4'" :info="item" :detail="data" />
@@ -64,6 +65,7 @@ import params4 from '@/views/home/component/params/params4.vue'
 import params5 from '@/views/home/component/params/params5.vue'
 import params6 from '@/views/home/component/params/params6.vue'
 import params7 from '@/views/home/component/params/params7.vue'
+import params8 from '@/views/home/component/params/params8.vue'
 import inspect1 from '@/views/home/component/inspect/Inspect1.vue'
 import dispatch from '@/views/home/component/dispatch.vue'
 
@@ -80,6 +82,7 @@ export default {
     params5,
     params6,
     params7,
+    params8,
     dispatch
   },
   data() {

+ 1 - 1
src/views/invest/components/years.vue

@@ -72,7 +72,7 @@ import basicTab from '@/components/basic-tab/index.vue'
 import wave from '@/views/invest/components/wave.vue'
 import { useStore } from '@/store/user.js'
 import { ElMessageBox } from 'element-plus'
-import index from '@/views/task/Index.vue'
+import index from '@/views/task/index.vue'
 import permissionStore from '@/store/permission.js'
 
 export default {

+ 1 - 1
src/views/invest/index.vue

@@ -113,7 +113,7 @@
 
 <route>
 {
-path: '/',
+path: '/statistics',
 name: '项目投资统计',
 }
 </route>

+ 13 - 7
src/views/resource/component/preview.vue

@@ -28,12 +28,12 @@
         </div>
         <div
           class="bottom flex flex-center flex-justify-center"
-          v-if="info.isAccess === 2"
+          v-if="info.isAccess === 2 || showAction"
         >
           <el-button type="primary" icon="Download" @click="downloadClick"
             >下 载</el-button
           >
-          <share class="ml-20" :row="info" />
+          <share class="ml-20" :row="info" v-if="info.isAccess === 2" />
         </div>
       </div>
     </el-drawer>
@@ -49,6 +49,10 @@ export default {
     share
   },
   props: {
+    showAction: {
+      type: Boolean,
+      default: false
+    },
     info: {
       type: Object,
       default: null
@@ -83,11 +87,13 @@ export default {
       }
     },
     detail() {
-      this.$api.resource.fileDetail(this.info.fileId).then(res => {
-        if (res.code === 200) {
-          this.data = res.data
-        }
-      })
+      this.$api.resource
+        .fileDetail(this.info.fileId ? this.info.fileId : this.info.id)
+        .then(res => {
+          if (res.code === 200) {
+            this.data = res.data
+          }
+        })
     },
     async downloadClick() {
       const link = document.createElement('a')

+ 0 - 47
src/views/task/Index.vue

@@ -1,47 +0,0 @@
-<template>
-  <basic-container class='margin'>
-    <el-tabs type="card" @tab-change='taskChange'>
-      <el-tab-pane label="我的任务">
-      </el-tab-pane>
-      <el-tab-pane label="我下发的任务"/>
-    </el-tabs>
-    <my-task v-if='currentTab === "0"'></my-task>
-    <task v-if='currentTab === "1"'></task>
-  </basic-container>
-</template>
-
-<route>
-{
-name: '任务列表'
-}
-</route>
-
-<script>
-import BasicContainer from '@/components/basic-container/main.vue'
-import MyTask from '@/views/task/component/my-task.vue'
-import Task from '@/views/task/component/task.vue'
-
-export default {
-  name: 'Index',
-  components: { Task, MyTask, BasicContainer },
-  data () {
-    return {
-      currentTab: '0',
-      taskShow: false,
-      showFolder: false,
-      form: {},
-      data: [],
-      taskFolder: []
-    }
-  },
-  methods: {
-    taskChange (res) {
-      this.currentTab = res
-    }
-  }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 258 - 0
src/views/task/component/move.vue

@@ -0,0 +1,258 @@
+<template>
+  <div>
+    <el-dialog v-model="show" width="800" :show-close="false">
+      <template #header>
+        <div class="full-width flex flex-center flex-justify-between">
+          <h4>移动文件</h4>
+          <div class="flex flex-center flex-justify-end">
+            <div class="flex flex-center">
+              <span class="mr-10">项目阶段:</span>
+              <el-select
+                v-model="stageId"
+                remote
+                filterable
+                clearable
+                placeholder="筛选项目阶段"
+                style="width: 200px"
+              >
+                <el-option
+                  v-for="item in stageList"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                >
+                </el-option>
+              </el-select>
+            </div>
+            <el-button
+              type="info"
+              class="ml-10"
+              circle
+              icon="Close"
+              @click="show = false"
+            >
+            </el-button>
+          </div>
+        </div>
+      </template>
+      <div>
+        <file-way
+          class="mb-10"
+          :next="currentFolder"
+          @before="goBefore"
+          @goHome="getFolderList"
+        />
+        <div class="mt-20 border-top flex flex-center flex-col">
+          <div
+            v-if="data.length === 0"
+            class="flex flex-center content flex-col full-height"
+          >
+            <el-empty
+              :description="loading ? '加载中...' : '暂无数据'"
+            ></el-empty>
+          </div>
+          <div v-else class="full-width content border-top">
+            <div
+              v-for="i in data"
+              class="padding light-green-bg row"
+              :key="i.id"
+              @click="getFileList(i)"
+            >
+              <div class="flex flex-center flex-justify-start">
+                <img
+                  v-if="i.isAccess === 1"
+                  src="../../../assets/svg/folder/see.svg"
+                />
+                <img
+                  v-if="i.isAccess === 2"
+                  src="../../../assets/svg/folder/edit.svg"
+                />
+                <img
+                  v-if="i.isAccess === 3"
+                  src="../../../assets/svg/folder/invisible.svg"
+                />
+                {{ i.title }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="full-width flex flex-justify-end flex-center mt-20">
+        <el-button type="primary" plain @click="show = false">取 消</el-button>
+        <el-button type="primary" @click="move">移动到此</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import fileWay from '@/components/file-way/index.vue'
+
+export default {
+  components: {
+    fileWay
+  },
+  props: {
+    projectId: {
+      required: true,
+      type: String,
+      default: ''
+    },
+    fileId: {
+      required: true,
+      type: String,
+      default: ''
+    }
+  },
+  watch: {
+    show: {
+      handler(val) {
+        if (val) {
+          this.getStage()
+        }
+      },
+      immediate: true
+    },
+    stageId: {
+      handler(val) {
+        this.getFolderList()
+      },
+      immediate: false
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      show: false,
+      currentFolder: null,
+      stageId: '',
+      topFolder: true,
+      data: [],
+      stageList: [],
+      page: {
+        current: 1,
+        size: 10
+      }
+    }
+  },
+  methods: {
+    showDialog() {
+      this.show = true
+    },
+    /**
+     * 获取项目阶段
+     */
+    getStage() {
+      this.$api.project
+        .includeStage({ projectId: this.projectId })
+        .then(res => {
+          if (res.code === 200) {
+            this.stageList = res.data
+            const tmp = this.stageList.find(ele => ele.isLastSelect === 1)
+            if (tmp) {
+              this.stageId = tmp.id
+            } else {
+              this.stageId = this.stageList[0].id
+            }
+            this.getFolderList()
+          }
+        })
+    },
+    /**
+     * 获取阶段对应的一级文件夹
+     */
+    getFolderList() {
+      this.loading = true
+      this.topFolder = true
+      const data = {
+        projectId: this.projectId,
+        stageId: this.stageId,
+        dictKey: 1,
+        current: this.page.current,
+        size: this.page.size
+      }
+      this.data.length = 0
+      this.loading = true
+      this.top = true
+      this.$api.resource.folderList(data).then(res => {
+        this.loading = false
+        this.loading = false
+        if (res.code === 200) {
+          this.data = res.data.records
+          this.page.total = res.data.total
+        }
+      })
+    },
+    /**
+     * 获取文件夹下面的文件及文件夹
+     * @param row
+     */
+    getFileList(row) {
+      if (row.isAccess !== 2) {
+        this.$message.error('您暂无权限访问此文件夹,如需访问请主动申请授权')
+        return
+      }
+      this.loading = true
+      this.topFolder = false
+      this.currentFolder = row
+      const item = {
+        id: row.id,
+        current: this.page.current,
+        size: this.page.size,
+        type: 2
+      }
+      this.loading = true
+      this.data.length = 0
+      this.$api.resource.fileList(item).then(res => {
+        this.loading = false
+        if (res.code === 200) {
+          this.data = res.data.records
+          this.page.total = res.data.total
+        } else {
+          this.$message.error(res.msg)
+        }
+      })
+    },
+    goBefore(res) {
+      if (!this.topFolder) {
+        this.getFileList(res)
+      } else {
+        this.getFolderList()
+      }
+    },
+    move() {
+      this.$api.task
+        .fileMove({ fileIds: this.fileId, folderId: this.currentFolder.id })
+        .then(res => {
+          if (res.code === 200) {
+            this.$message.success(res.msg)
+            this.show = false
+            this.$emit('on-success')
+          }
+        })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.content {
+  height: 500px;
+  overflow-y: scroll;
+}
+
+.row {
+  border-bottom: #eeeeee solid 1px;
+  background-color: white;
+
+  img {
+    width: 30px;
+    height: auto;
+    margin-right: 10px;
+  }
+}
+
+.row:hover {
+  background-color: #f7f9fc;
+}
+</style>

+ 0 - 171
src/views/task/component/my-task.vue

@@ -1,171 +0,0 @@
-<template>
-  <avue-crud
-      :option="option"
-      :data="data"
-      ref="crud"
-      v-model="form"
-      v-model:page="page"
-      :before-open="beforeOpen"
-      @row-del="rowDel"
-      @current-change="currentChange"
-      @size-change="sizeChange"
-      @refresh-change="refreshChange"
-      @on-load="onLoad">
-    <template #menu-left="{}">
-      <el-button-group>
-        <el-button type='primary' :plain='query.type !== "0" ' @click='changeQueryType("0")'>待完成</el-button>
-        <el-button type='primary' :plain='query.type !== "1" ' @click='changeQueryType("1")'>已完成</el-button>
-        <el-button type='primary' :plain='query.type !== "" ' @click='changeQueryType("")'>全部</el-button>
-      </el-button-group>
-    </template>
-    <!--    <template #menu="{row}">-->
-    <!--      <el-button type='primary' text icon='Position' @click='changeQueryType("0")'>提交</el-button>-->
-    <!--    </template>-->
-  </avue-crud>
-</template>
-
-<script>
-export default {
-  name: 'my-task',
-  data() {
-    return {
-      query: {
-        type: '0'
-      },
-      page: {
-        pageSize: 10,
-        currentPage: 1,
-        total: 10
-      },
-      data: [],
-      form: {},
-      option: {
-        align: 'center',
-        menuAlign: 'center',
-        menuWidth: 260,
-        size: 'mini',
-        addBtn: false,
-        viewBtn: true,
-        editBtn: false,
-        delBtn: false,
-        refreshBtn: false,
-        columnBtn: false,
-        labelWidth: 140,
-        border: true,
-        column: [
-          {
-            label: '任务名称',
-            prop: 'title'
-          },
-          {
-            label: '任务要求',
-            prop: 'remark'
-          },
-          {
-            label: '所属项目',
-            prop: 'projectName'
-          },
-          {
-            label: '下发人',
-            prop: 'dispatcherUserName'
-          },
-          {
-            label: '下发时间',
-            prop: 'createTime'
-          },
-          {
-            label: '任务截止时间',
-            prop: 'taskEndTime'
-          },
-          {
-            label: '任务状态',
-            prop: 'isCompleted',
-            type: 'select',
-            dicData: [
-              {
-                label: '待完成',
-                value: 0
-              },
-              {
-                label: '已完成',
-                value: 1
-              }
-            ]
-          },
-          {
-            label: '是否确认',
-            prop: 'isConfirmed',
-            type: 'select',
-            dicData: [
-              {
-                label: '待确认',
-                value: 0
-              },
-              {
-                label: '已确认',
-                value: 1
-              }
-            ]
-          }
-        ]
-      }
-    }
-  },
-  methods: {
-    onLoad() {
-      this.loading = true
-      this.page.current = this.page.currentPage
-      this.page.size = this.page.pageSize
-      this.$api.task.taskList(Object.assign(this.page, this.query)).then(res => {
-        this.loading = false
-        if (res.code === 200) {
-          this.data = res.data.records
-          this.page.total = res.data.total
-        }
-      })
-    },
-    beforeOpen(done, type) {
-      if (type === 'view') {
-        this.$router.push({path: '/task/detail', query: {id: this.form.id, taskId: this.form.taskId}})
-      } else {
-        done()
-      }
-    },
-    currentChange(currentPage) {
-      this.page.currentPage = currentPage
-    },
-    sizeChange(pageSize) {
-      this.page.size = pageSize
-    },
-    refreshChange() {
-      this.onLoad()
-    },
-    rowDel(row) {
-      this.$confirm('确定彻底删除所选择的文件?', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning'
-      })
-          .then(() => {
-            this.$api.recycle.recycleRemove({ids: row.id}).then(res => {
-              if (res.code === 200) {
-                this.$message.success(res.msg)
-                this.onLoad()
-              } else {
-                this.$message.error(res.msg)
-              }
-            })
-          })
-    },
-    changeQueryType(type) {
-      this.query.type = type
-      this.onLoad()
-    }
-
-  }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 296 - 0
src/views/task/component/task-table.vue

@@ -0,0 +1,296 @@
+<template>
+  <div>
+    <div class="full-width flex flex-center flex-justify-between">
+      <el-button
+        type="primary"
+        icon="el-icon-plus"
+        @click="addTask"
+        v-if="option.addBtn || option.addBtn === undefined"
+        >新增任务
+      </el-button>
+      <div v-else></div>
+      <el-button circle icon="Refresh" @click="refresh" />
+    </div>
+    <div class="header flex flex-justify-between full-width mt-10">
+      <div
+        v-for="(item, index) in option.column"
+        :key="item.label"
+        class="full-width"
+      >
+        <div
+          v-if="index === 0"
+          class="flex-child-shrink flex flex-justify-start first"
+          :style="`width:` + item.width + 'px'"
+        >
+          共 {{ total }}个任务
+        </div>
+        <div v-else class="flex-child-average">
+          {{ item.label }}
+        </div>
+      </div>
+    </div>
+    <div class="content full-width">
+      <div
+        v-for="row in data"
+        :key="row.id"
+        class="flex flex-center full-width row"
+        @click="rowClick(row)"
+      >
+        <div
+          v-for="(column, index) in option.column"
+          :key="column.label"
+          class="full-width full-height"
+        >
+          <div
+            v-if="index === 0"
+            class="full-height flex flex-center flex-justify-start"
+            :style="`width:` + column.width + 'px'"
+          >
+            <div
+              class="full-width full-height flex flex-justify-start flex-align-center"
+            >
+              <div
+                class="level"
+                :style="
+                  `background-color:` +
+                  (row && row.currentLevel ? row.currentLevel.color : 'white')
+                "
+              ></div>
+              <wt-tag
+                :data="status"
+                :status="row.taskStatus"
+                :disabled="true"
+              />
+              {{ row[column.prop] }}
+            </div>
+          </div>
+          <div v-else-if="index === 1" class="flex flex-center full-height">
+            {{ row[column.prop] }}
+          </div>
+          <div
+            v-else-if="index === 2"
+            class="flex-child-average full-height full-width flex flex-center"
+          >
+            <div>
+              <el-tag v-for="tag in row.tagsArray" :key="tag" class="mr-10"
+                >{{ tag }}
+              </el-tag>
+            </div>
+          </div>
+          <div
+            v-else-if="index === 3"
+            class="flex-child-average full-height full-width flex flex-center"
+          >
+            <div>
+              <div v-if="row.startTime.length > 0 && row.endTime.length === 0">
+                {{ row.startTime.substring(5) }} 开始
+              </div>
+              <div
+                v-else-if="row.startTime.length === 0 && row.endTime.length > 0"
+              >
+                {{ row.endTime.substring(5) }} 截止
+              </div>
+              <div v-else>
+                {{ row.startTime.substring(5) }} 至
+                {{ row.endTime.substring(5) }}
+              </div>
+            </div>
+          </div>
+          <div
+            v-else
+            class="flex-child-average full-height full-width flex flex-center"
+          >
+            <div v-if="row.users !== undefined" class="flex flex-center">
+              <div v-for="i in row.users" :key="i.id" class="flex flex-center">
+                <el-tooltip :content="i.name">
+                  <el-avatar
+                    class="ml-10 mr-10"
+                    :size="25"
+                    :src="
+                      i.avatar.length === 0
+                        ? 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
+                        : i.avatar
+                    "
+                  ></el-avatar>
+                </el-tooltip>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <task
+      ref="task"
+      :task="task"
+      :project-id="projectId"
+      @success="refresh"
+    ></task>
+  </div>
+</template>
+
+<script>
+import WtTag from '@/views/task/component/wt-tag.vue'
+import Task from '@/views/task/component/task.vue'
+
+export default {
+  components: { Task, WtTag },
+  props: {
+    projectId: {
+      type: String,
+      default: ''
+    },
+    data: {
+      type: Array,
+      default: []
+    },
+    option: {
+      type: Object,
+      default: null
+    },
+    total: {
+      type: Number,
+      default: 0
+    }
+  },
+  watch: {
+    data: {
+      handler(val) {
+        val.forEach(item => {
+          const tmp = this.level.find(ele => ele.value === item.level)
+          item.currentLevel = tmp
+        })
+      },
+      immediate: true
+    }
+  },
+  data() {
+    return {
+      task: null,
+      info: {
+        taskStatus: ''
+      },
+      status: [
+        {
+          title: '待确认',
+          value: 0,
+          color: '#D7D7D7',
+          checked: true
+        },
+        {
+          title: '进行中',
+          value: 1,
+          color: '#47A6EA',
+          checked: false
+        },
+        {
+          title: '已提交',
+          value: 2,
+          color: '#ECAB56',
+          checked: false
+        },
+        {
+          title: '已完成',
+          value: 3,
+          color: '#80B336',
+          checked: false
+        },
+        {
+          title: '已取消',
+          value: 4,
+          color: '#C72A29',
+          checked: false
+        }
+      ],
+      level: [
+        {
+          title: 'P1',
+          value: 0,
+          color: '#C72A29',
+          checked: true
+        },
+        {
+          title: 'P2',
+          value: 1,
+          color: '#E89D42',
+          checked: false
+        },
+        {
+          title: 'P3',
+          value: 2,
+          color: '#47A6EA',
+          checked: false
+        },
+        {
+          title: 'P4',
+          value: 3,
+          color: '#A0A0A0',
+          checked: false
+        }
+      ]
+    }
+  },
+  methods: {
+    fetchIndex(list, key) {
+      const index = list.findIndex(ele => ele.value === key)
+      if (index > -1) {
+        list.map(ele => {
+          ele.checked = false
+          return ele
+        })
+        list[index].checked = true
+      }
+      return list
+    },
+    addTask() {
+      this.task = null
+      this.$refs.task.show(1)
+    },
+    rowClick(item) {
+      this.getTaskInfo(item.id)
+      this.$emit('rowClick', item)
+    },
+    getTaskInfo(id) {
+      this.$api.task.taskInfo({ id }).then(res => {
+        if (res.code === 200) {
+          this.task = res.data
+          this.$nextTick(() => {
+            this.$refs.task.show(2)
+          })
+        } else {
+          this.$message.error(res.msg)
+        }
+      })
+    },
+    refresh() {
+      this.$emit('refresh')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header {
+  border-bottom: #f7f8fa solid 1px;
+  padding: 10px 0;
+}
+
+.first {
+  width: 600px;
+}
+
+.row {
+  height: 60px;
+  border-bottom: #f7f8fa solid 1px;
+}
+
+.row:hover {
+  height: 60px;
+  background-color: #f7f9fc;
+}
+
+.level {
+  width: 5px;
+  height: 100%;
+  margin-right: 10px;
+}
+</style>

+ 460 - 153
src/views/task/component/task.vue

@@ -1,188 +1,495 @@
 <template>
-  <avue-crud
-      :option="option"
-      :data="data"
-      ref="crud"
-      v-model="form"
-      v-model:page="page"
-      :before-open="beforeOpen"
-      @row-del="rowDel"
-      @current-change="currentChange"
-      @size-change="sizeChange"
-      @refresh-change="refreshChange"
-      @on-load="onLoad">
-    <template #menu-left="{}">
-      <el-button-group>
-        <el-button type='primary' :plain='query.type !== "0" ' @click='changeQueryType("0")'>待完成</el-button>
-        <el-button type='primary' :plain='query.type !== "1" ' @click='changeQueryType("1")'>已完成</el-button>
-        <el-button type='primary' :plain='query.type !== "" ' @click='changeQueryType("")'>全部</el-button>
-      </el-button-group>
+  <el-dialog v-model="showDialog" width="800" @close="close">
+    <template #header>
+      <div class="full-width flex flex-center flex-justify-between">
+        <h4>任务详情</h4>
+      </div>
     </template>
-    <template #menu="{row}">
-      <el-button type='primary' text icon='CircleCheck' @click='taskCheck(row)'>确认</el-button>
-    </template>
-  </avue-crud>
+    <div>
+      <el-input
+        v-model="form.title"
+        maxlength="20"
+        clearable
+        :disabled="!canEdit"
+        style="height: 50px"
+        class="font-16 bold"
+        placeholder="请输入任务名称(最大20个字符)"
+      ></el-input>
+      <div
+        class="full-width flex flex-justify-between flex-center padding-top mt-10"
+        @click="goProject"
+        v-if="task && task.projectId"
+      >
+        <span class="flex-center flex-justify-start title"
+          >所属项目:{{ task.projectName }}</span
+        >
+        <el-icon>
+          <ArrowRight />
+        </el-icon>
+      </div>
+      <div class="mt-20">
+        <div class="flex flex-center flex-justify-start">
+          <span class="mr-10 title flex flex-justify-start">状态:</span>
+          <wt-tag
+            :data="status"
+            :status="form.taskStatus"
+            @change="changeStatus($event, 1)"
+            :disabled="isFinish"
+          />
+        </div>
+
+        <div class="mt-20 flex flex-center flex-justify-start">
+          <span class="mr-10 title flex flex-justify-start">优先级:</span>
+          <wt-tag
+            :data="level"
+            :disabled="!canEdit"
+            :status="form.level"
+            @change="changeStatus($event, 2)"
+          />
+        </div>
+
+        <div class="mt-20 flex flex-center flex-justify-start">
+          <span class="mr-10 title flex flex-justify-start">时间:</span>
+          <div class="flex flex-center">
+            <el-date-picker
+              v-model="form.startTime"
+              type="date"
+              :disabled="!canEdit"
+              placeholder="设置开始日期"
+              format="YYYY-MM-DD"
+              value-format="YYYY-MM-DD"
+            >
+            </el-date-picker>
+            <div class="ml-5 mr-5">至</div>
+            <el-date-picker
+              v-model="form.endTime"
+              type="date"
+              :disabled="!canEdit"
+              placeholder="设置截止日期"
+              format="YYYY-MM-DD"
+              value-format="YYYY-MM-DD"
+            >
+            </el-date-picker>
+          </div>
+        </div>
+
+        <div class="mt-20 flex lex-align-start flex-justify-start">
+          <span class="mr-10 title flex flex-justify-start">标签:</span>
+          <div>
+            <wt-label
+              @submit="handleTags"
+              :ids="form.tags.split(',')"
+              :disabled="!canEdit"
+            />
+          </div>
+        </div>
+        <div class="mt-20 flex lex-align-start flex-justify-start">
+          <span class="mr-10 title flex flex-justify-start">执行者:</span>
+          <div>
+            <tasker
+              :data="executeUser === null ? [] : executeUser"
+              :disabled="!canEdit"
+              @success="selected"
+            />
+          </div>
+        </div>
+        <div class="mt-20 flex flex-align-start flex-justify-start">
+          <span class="mr-10 title flex flex-justify-start">备注:</span>
+          <el-input
+            type="textarea"
+            :rows="5"
+            v-model="form.remark"
+            :disabled="!canEdit"
+          ></el-input>
+        </div>
+
+        <div class="mt-20 flex flex-align-start flex-justify-start flex-col">
+          <div class="flex flex-center flex-justify-start">
+            <span class="mr-10 title flex flex-justify-start">关联附件:</span>
+            <filepicker
+              :project-id="projectId"
+              @submit="selection"
+              v-if="canEdit"
+            />
+          </div>
+          <div class="flex flex-center flex-justify-start full-width mt-10">
+            <div class="title mr-10"></div>
+            <div>
+              <div v-for="item in fileList" :key="item.id">
+                <div class="flex flex-center">
+                  {{ item.title }}
+                  <preview :info="item" :show-action="true" />
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="flex flex-justify-start full-width flex-col">
+          <div class="mt-10 flex flex-align-start flex-justify-start">
+            <span class="mr-10 title flex flex-justify-start">任务进展:</span>
+            <el-input
+              type="textarea"
+              :rows="5"
+              v-model="form.taskProcess"
+              :disabled="isFinish"
+            ></el-input>
+          </div>
+          <div class="flex flex-justify-start mt-10 full-width">
+            <div class="title mr-10">成果文件:</div>
+            <upload-office
+              @success="uploadResult"
+              :max="1"
+              v-if="isFinish === false"
+            />
+          </div>
+          <div class="full-width flex flex-justify-start flex-center">
+            <div class="title mr-10" />
+            <div v-for="item in resultFiles" :key="item.id">
+              <div class="flex flex-justify-start flex-center">
+                {{ item.fileVO.originalFileName }}
+                ({{ item.createUserName }})
+                <preview :info="item.fileVO" :show-action="true"></preview>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="flex flex-justify-end full-width">
+        <el-button type="primary" plain @click="showDialog = false"
+          >取 消
+        </el-button>
+        <el-button type="primary" @click="submit">确 定</el-button>
+      </div>
+      <move
+        ref="move"
+        :file-id="resultFiles.map(ele => ele.fileId).join(',')"
+        :project-id="form.projectId"
+        @on-success="moveSucc"
+      />
+    </div>
+  </el-dialog>
 </template>
 
 <script>
+import WtTag from '@/views/task/component/wt-tag.vue'
+import Tasker from '@/views/task/component/tasker.vue'
+import filepicker from '@/components/filepicker/index.vue'
+import WtLabel from '@/views/task/component/wt-label.vue'
+import { useStore } from '@/store/user.js'
+import Preview from '@/views/resource/component/preview.vue'
+import api from '@/api/index.js'
+import uploadOffice from '@/components/upload-office/index.vue'
+import move from '@/views/task/component/move.vue'
+
 export default {
-  name: 'my-task',
-  data () {
+  /**
+   * 任务添加、查看
+   */
+  computed: {
+    api() {
+      return api
+    }
+  },
+  components: {
+    Preview,
+    WtLabel,
+    Tasker,
+    WtTag,
+    filepicker,
+    uploadOffice,
+    move
+  },
+  props: {
+    projectId: {
+      type: String,
+      default: ''
+    },
+    task: {
+      type: Object,
+      default: () => {
+        return null
+      }
+    }
+  },
+  setup() {
+    const user = useStore()
+    return { user }
+  },
+  data() {
     return {
-      query: {
-        type: '0'
+      isFinish: false,
+      showDialog: false,
+      fileList: [],
+      loading: false,
+      canEdit: true,
+      executeUser: [],
+      resultFiles: [],
+      editResult: false,
+      isMove: false,
+      form: {
+        title: '',
+        taskStatus: -1,
+        level: -1,
+        remark: '',
+        startTime: '',
+        endTime: '',
+        executeUser: '',
+        tags: '',
+        taskProcess: ''
       },
-      page: {
-        pageSize: 10,
-        currentPage: 1,
-        total: 10
-      },
-      data: [],
-      form: {},
-      option: {
-        align: 'center',
-        menuAlign: 'center',
-        menuWidth: 260,
-        size: 'mini',
-        addBtn: false,
-        viewBtn: true,
-        editBtn: false,
-        delBtn: false,
-        refreshBtn: false,
-        columnBtn: false,
-        labelWidth: 140,
-        border: true,
-        column: [
-          {
-            label: '任务名称',
-            prop: 'title'
-          },
-          {
-            label: '任务要求',
-            prop: 'remark'
-          },
-          {
-            label: '所属项目',
-            prop: 'projectName'
-          },
-          {
-            label: '接收人',
-            prop: 'dispatcherToUserName'
-          },
-          {
-            label: '下发时间',
-            prop: 'createTime'
-          },
-          {
-            label: '任务截止时间',
-            prop: 'taskEndTime'
-          },
-          {
-            label: '任务状态',
-            prop: 'isCompleted',
-            type: 'select',
-            dicData: [
-              {
-                label: '待完成',
-                value: 0
-              },
-              {
-                label: '已完成',
-                value: 1
-              }
-            ]
-          },
-          {
-            label: '是否确认',
-            prop: 'isConfirmed',
-            type: 'select',
-            dicData: [
-              {
-                label: '待确认',
-                value: 0
-              },
-              {
-                label: '已确认',
-                value: 1
-              }
-            ]
-          }
-        ]
-      }
+      status: [
+        {
+          title: '待确认',
+          value: 0,
+          color: '#D7D7D7',
+          checked: true
+        },
+        {
+          title: '进行中',
+          value: 1,
+          color: '#47A6EA',
+          checked: false
+        },
+        {
+          title: '已提交',
+          value: 2,
+          color: '#ECAB56',
+          checked: false
+        },
+        {
+          title: '已完成',
+          value: 3,
+          color: '#80B336',
+          checked: false
+        },
+        {
+          title: '已取消',
+          value: 4,
+          color: '#C72A29',
+          checked: false
+        }
+      ],
+      level: [
+        {
+          title: 'P1',
+          value: 0,
+          color: '#C72A29',
+          checked: true
+        },
+        {
+          title: 'P2',
+          value: 1,
+          color: '#E89D42',
+          checked: false
+        },
+        {
+          title: 'P3',
+          value: 2,
+          color: '#47A6EA',
+          checked: false
+        },
+        {
+          title: 'P4',
+          value: 3,
+          color: '#A0A0A0',
+          checked: false
+        }
+      ]
     }
   },
   methods: {
-    onLoad () {
-      this.loading = true
-      this.page.current = this.page.currentPage
-      this.page.size = this.page.pageSize
-      this.$api.task.issuedRecords(Object.assign(this.page, this.query)).then(res => {
-        this.loading = false
-        if (res.code === 200) {
-          this.data = res.data.records
-          this.page.total = res.data.total
-        }
-      })
-    },
-    beforeOpen (done, type) {
-      if (type === 'view') {
-        this.$router.push({ path: '/task/detail', query: { id: this.form.id, taskId: this.form.taskId, view: true } })
-      } else {
-        done()
+    init() {
+      this.form = this.task
+      console.log(this.task)
+      if (this.task.users !== undefined) {
+        this.executeUser = [...this.task.users]
       }
+      this.canEdit = this.form.createUser === this.user.info.userId
+      if (this.task.taskStatus > 1) {
+        this.canEdit = false
+      }
+      if (this.task.files !== undefined && this.task.files.length > 0) {
+        this.fileList = this.task.files
+        console.log(this.fileList)
+      }
+      this.resultFileInfo()
     },
-    currentChange (currentPage) {
-      this.page.currentPage = currentPage
-    },
-    sizeChange (pageSize) {
-      this.page.size = pageSize
+    fetchIndex(list, key) {
+      const index = list.findIndex(ele => ele.value === key)
+      if (index > -1) {
+        list.map(ele => {
+          ele.checked = false
+          return ele
+        })
+        list[index].checked = true
+      }
+      return list
     },
-    refreshChange () {
-      this.onLoad()
+    /**
+     *
+     * @param type = 1 新增 2 查看
+     */
+    show(type) {
+      if (type !== 1) {
+        this.form = this.task
+        if (this.task.taskStatus === 3 || this.task.taskStatus === 4) {
+          this.isFinish = true
+        }
+        this.init(this.task)
+      } else {
+        this.form.taskStatus = 0
+        this.form.level = 0
+        this.canEdit = true
+      }
+      this.showDialog = true
     },
-    rowDel (row) {
-      this.$confirm('确定彻底删除所选择的文件?', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning'
-      })
-        .then(() => {
-          this.$api.recycle.recycleRemove({ ids: row.id }).then(res => {
-            if (res.code === 200) {
-              this.$message.success(res.msg)
-              this.onLoad()
-            } else {
-              this.$message.error(res.msg)
-            }
-          })
+    /**
+     * 获取成果文件info
+     */
+    resultFileInfo() {
+      if (this.task.id !== undefined) {
+        this.$api.task.taskFileInfo({ id: this.task.id }).then(res => {
+          if (res.code === 200) {
+            this.resultFiles = res.data
+          }
         })
+      }
     },
-    changeQueryType (type) {
-      this.query.type = type
-      this.onLoad()
+    changeStatus(res, type) {
+      if (type === 1) {
+        this.form.taskStatus = res.value
+      } else {
+        this.form.level = res.value
+      }
     },
-    taskCheck (row) {
-      if (row.isCompleted === 0) {
-        this.$message.error('该任务尚未完成,不能进行确认!')
+    submit() {
+      if (this.form.taskStatus === 2 && this.resultFiles.length === 0) {
+        this.$message.error('请上传成果文件')
+        return
+      } else {
+        this.saveResultFile()
+      }
+
+      if (
+        this.form.taskStatus === 3 &&
+        this.isMove === false &&
+        this.isFinish === false
+      ) {
+        // 已完成 需要转移文件
+        this.$refs.move.showDialog()
         return
       }
-      if (row.isConfirmed === 1) {
-        this.$message.error('该任务已经确认!')
+      if (this.isFinish) {
+        this.showDialog = false
         return
       }
-      this.$api.task.taskConfirm({ taskId: row.taskId }).then(res => {
+      // 从项目详情,添加任务
+      if (this.projectId !== undefined && this.projectId.length > 0) {
+        this.form.projectId = this.projectId
+      }
+      this.form.relatedIds = this.fileList.map(ele => ele.id).join(',')
+      this.$api.task.addTask(this.form).then(res => {
+        this.showDialog = false
         if (res.code === 200) {
           this.$message.success(res.msg)
-          this.onLoad()
         } else {
           this.$message.error(res.msg)
         }
+        this.$emit('success')
+      })
+    },
+    saveResultFile() {
+      if (this.editResult === false) {
+        return
+      }
+      this.$api.task
+        .taskFile({
+          taskId: this.form.id,
+          ids: this.resultFiles.map(ele => ele.id).join(',')
+        })
+        .then(res => {
+          if (res.code === 200) {
+            console.log(res)
+          }
+        })
+    },
+    selection(list, extra) {
+      this.fileList = list.map(ele => {
+        return ele
+      })
+    },
+    selected(list) {
+      this.form.executeUser = list.map(ele => ele.id).join(',')
+    },
+    handleTags(tags) {
+      this.form.tags = tags
+    },
+    /**
+     * 成果文件上传成果
+     * @param list
+     */
+    uploadResult(list) {
+      this.editResult = true
+      this.resultFiles = list.map(ele => {
+        return {
+          id: ele.id,
+          fileVO: ele,
+          createUserName: this.user.info.nickName
+        }
+      })
+    },
+    close() {
+      this.form = {
+        title: '',
+        taskStatus: -1,
+        level: -1,
+        remark: '',
+        startTime: '',
+        endTime: '',
+        executeUser: '',
+        tags: '',
+        taskProcess: ''
+      }
+      this.resultFiles.length = 0
+      this.executeUser.length = 0
+      this.fileList.length = 0
+      this.showDialog = false
+      this.editResult = false
+      this.isMove = false
+      this.isFinish = false
+    },
+    /**
+     * 文件移动成功
+     */
+    moveSucc() {
+      this.isMove = true
+    },
+    goProject() {
+      const routeData = this.$router.resolve({
+        path: '/home/pro_detail',
+        query: { id: this.task.projectId }
       })
+      window.open(routeData.href, '_blank')
     }
   }
 }
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
+.title {
+  min-width: 60px;
+}
+
+:deep(.el-input__wrapper) {
+  box-shadow: none;
+  font-weight: bold;
+  font-size: 16px;
+  background-color: #eeeeee;
+}
 
+:deep(.el-input__wrapper:hover) {
+  box-shadow: none;
+  background-color: #eeeeee;
+}
 </style>

+ 169 - 0
src/views/task/component/tasker.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="flex flex-align-center flex-justify-start flex-wrap">
+    <div class="flex flex-align-center">
+      <div
+        class="flex flex-center ml-10 mr-10 padding-bottom tag"
+        v-for="i in selectedList"
+        :key="i.id"
+      >
+        <el-avatar :size="15" :src="i.avatar"></el-avatar>
+        <div class="ml-5">{{ i.name }}</div>
+        <el-icon class="ml-10" @click="remove(i)" v-if="!disabled">
+          <CircleCloseFilled />
+        </el-icon>
+      </div>
+    </div>
+    <el-icon
+      size="30"
+      class="padding-left padding-right"
+      color="#A3773D"
+      @click="show = true"
+      v-if="!disabled"
+    >
+      <CirclePlusFilled />
+    </el-icon>
+
+    <el-dialog v-model="show" width="500">
+      <div>
+        <div class="flex flex-center">
+          <el-input placeholder="搜索" v-model="keyword" @keyup.enter="getUser">
+            <template #append>
+              <el-button icon="Search" @click="getUser" />
+            </template>
+          </el-input>
+        </div>
+        <div
+          class="mt-20 padding-left padding-right"
+          style="height: 300px; overflow-y: scroll"
+        >
+          <el-empty v-if="list.length === 0" description="暂无数据" />
+          <div v-else>
+            <div
+              v-for="i in list"
+              :key="i.id"
+              class="full-width flex flex-justify-between flex-center border-bottom padding-bottom padding-top"
+              @click="change(i)"
+            >
+              <div class="flex flex-center flex-justify-start">
+                <el-icon v-if="i.checked" color="#ab7630" size="18px">
+                  <CircleCheckFilled />
+                </el-icon>
+                <el-icon v-else color="grey" size="18px">
+                  <CircleCheck />
+                </el-icon>
+                <el-avatar
+                  class="ml-10 mr-10"
+                  :size="25"
+                  :src="i.avatar"
+                ></el-avatar>
+                <div class="ml-5">{{ i.name }}</div>
+              </div>
+              <el-tag v-if="i.workStatus.length > 0">{{ i.workStatus }}</el-tag>
+            </div>
+          </div>
+        </div>
+        <div class="full-width flex flex-center flex-justify-end mt-20">
+          <el-button plain type="primary" size="small" @click="show = false"
+            >取 消
+          </el-button>
+          <el-button type="primary" size="small" @click="submit"
+            >确 定
+          </el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { useStore } from '@/store/user.js'
+
+export default {
+  props: {
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    data: {
+      type: Array,
+      default: []
+    }
+  },
+  setup() {
+    const user = useStore()
+    return { user }
+  },
+  watch: {
+    data: {
+      handler(val) {
+        if (val && val.length > 0) {
+          this.selectedList = val
+        } else {
+          this.selectedList.length = 0
+        }
+      },
+      immediate: true
+    }
+  },
+  data() {
+    return {
+      show: false,
+      list: [],
+      keyword: '',
+      selectedList: []
+    }
+  },
+  methods: {
+    getUser() {
+      const data = { account: '', name: this.keyword }
+      this.$api.task.userList(data).then(res => {
+        if (res.code === 200) {
+          this.list = res.data.map(ele => {
+            ele.avatar = ele.avatar
+              ? ele.avatar
+              : 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
+            ele.checked = false
+            return ele
+          })
+        }
+      })
+    },
+    change(res) {
+      res.checked = !res.checked
+    },
+    submit() {
+      const tmps = this.list.filter(ele => ele.checked)
+      const newAddList = []
+      if (this.selectedList.length > 0) {
+        tmps.forEach(ele => {
+          const tmp = this.selectedList.findIndex(sub => sub.id === ele.id)
+          if (tmp === -1) {
+            newAddList.push(ele)
+          }
+        })
+        this.selectedList = this.selectedList.concat(newAddList)
+      } else {
+        this.selectedList = tmps
+      }
+
+      this.$emit('success', this.selectedList)
+      this.show = false
+    },
+    remove(res) {
+      this.selectedList = this.selectedList.filter(ele => ele.id !== res.id)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tag {
+  min-width: 60px;
+  height: 20px;
+  border-radius: 20px;
+  background-color: #a3773d;
+  padding: 2px 10px;
+  margin: 0 5px;
+  color: white;
+}
+</style>

+ 131 - 0
src/views/task/component/wt-label.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="flex flex-wrap flex-center">
+    <div v-if="tags.length > 0" class="flex flex-center flex-justify-start">
+      <div class="tag flex flex-center" v-for="i in tags" :key="i.id">
+        {{ i.dictValue }}
+        <el-icon class="ml-10" @click="remove(i)" v-if="!disabled">
+          <CircleCloseFilled />
+        </el-icon>
+      </div>
+    </div>
+    <el-icon
+      size="30"
+      class="padding-left padding-right"
+      color="#A3773D"
+      @click="show = true"
+      v-if="!disabled"
+    >
+      <CirclePlusFilled />
+    </el-icon>
+
+    <el-dialog v-model="show" width="500" title="选择标签">
+      <div class="flex flex-center">
+        <el-select v-model="form.tag1" class="mr-10" placeholder="请选择">
+          <el-option
+            v-for="item in options"
+            :key="item.dictKey"
+            :label="item.dictValue"
+            :value="item.dictKey"
+          />
+        </el-select>
+        <el-select v-model="form.tag2" placeholder="请选择">
+          <el-option
+            v-for="item in options1"
+            :key="item.dictKey"
+            :label="item.dictValue"
+            :value="item.dictKey"
+          />
+        </el-select>
+      </div>
+      <div class="full-width flex flex-center flex-justify-end mt-20">
+        <el-button type="primary" plain @click="show = false">取 消</el-button>
+        <el-button type="primary" @click="submit">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    // 初始化id
+    ids: {
+      type: Array,
+      default: () => {
+        return null
+      }
+    },
+    // 是否可以编辑
+    disabled: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      show: false,
+      tags: [],
+      form: {
+        tag1: '',
+        tag2: ''
+      },
+      options: [],
+      options1: []
+    }
+  },
+  created() {
+    this.init()
+  },
+  methods: {
+    init() {
+      this.$api.common.dicList({ code: 'edit_task_type' }).then(res => {
+        if (res.code === 200) {
+          this.options = res.data
+          if (this.ids.length > 0) {
+            const tmp = this.options.find(ele => ele.id === this.ids[0])
+            if (tmp) {
+              this.tags.push(tmp)
+            }
+          }
+        }
+      })
+      this.$api.common.dicList({ code: 'edit_task' }).then(res => {
+        if (res.code === 200) {
+          this.options1 = res.data
+          if (this.ids.length > 1) {
+            const tmp = this.options1.find(ele => ele.id === this.ids[1])
+            this.tags.push(tmp)
+          }
+        }
+      })
+    },
+    submit() {
+      this.tags = [
+        this.options.find(ele => ele.dictKey === this.form.tag1),
+        this.options1.find(ele => ele.dictKey === this.form.tag2)
+      ]
+      this.show = false
+      this.form = {}
+      const tmps = this.tags.map(ele => ele.id).join(',')
+      this.$emit('submit', tmps)
+    },
+    remove(res) {
+      this.tags = this.tags.filter(ele => ele.id !== res.id)
+      const tmps = this.tags.map(ele => ele.id).join(',')
+      this.$emit('submit', tmps)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tag {
+  min-width: 60px;
+  height: 20px;
+  border-radius: 20px;
+  background-color: #a3773d;
+  padding: 2px 10px;
+  margin: 0 5px;
+  color: white;
+}
+</style>

+ 107 - 0
src/views/task/component/wt-tag.vue

@@ -0,0 +1,107 @@
+<template>
+  <el-dropdown @command="dropDown" :disabled="disabled">
+    <span class="flex flex-center">
+      <div
+        v-if="current"
+        class="tag flex flex-center"
+        :style="`background-color:` + current.color"
+        style="min-width: 60px"
+      >
+        <div class="font-12 bold black white">{{ current.title }}</div>
+        <el-icon class="el-icon--right" color="white" v-if="disabled === false">
+          <arrow-down />
+        </el-icon>
+      </div>
+    </span>
+    <template #dropdown>
+      <el-dropdown-menu>
+        <el-dropdown-item
+          v-for="item in data"
+          :key="item.value"
+          :command="item"
+        >
+          <template #default>
+            <div
+              :style="`background-color:` + item.color"
+              class="black padding-left padding-right flex flex-center white"
+              style="min-width: 50px"
+            >
+              {{ item.title }}
+            </div>
+          </template>
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  props: {
+    // 需要显示的数组
+    data: {
+      require: true,
+      type: Array,
+      default: () => {
+        return []
+      }
+    },
+    // 默认状态值
+    status: {
+      type: Number,
+      default: 1
+    },
+    // 是否可以编辑
+    disabled: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    status: {
+      handler(val) {
+        setTimeout(() => {
+          if (this.val === -1) {
+            // 新增任务时候 val 为-1
+            this.current = this.data[0]
+            console.log(this.current)
+          } else {
+            this.current = this.data.find(e => e.value === val)
+          }
+        }, 500)
+      },
+      immediate: true
+    }
+  },
+  data() {
+    return {
+      current: {}
+    }
+  },
+  methods: {
+    dropDown(res) {
+      this.current = res
+      this.$emit('change', this.current)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-dropdown-link {
+  cursor: pointer;
+  color: #409eff;
+}
+
+.el-icon-arrow-down {
+  font-size: 12px;
+}
+
+.tag {
+  font-size: 12px;
+  background-color: #e7e7e7;
+  padding: 5px 12px;
+  border-radius: 2px;
+  margin-right: 10px;
+}
+</style>

+ 0 - 347
src/views/task/detail.vue

@@ -1,347 +0,0 @@
-<template>
-  <basic-container class='mt-10'>
-    <div class='flex flex-align-start flex flex-col'>
-      <span class='bold'>任务基本信息:</span>
-      <el-divider/>
-      <div v-if='taskInfo' class='flex flex-align-start flex-col '>
-        <div>
-          <span class='bold'>任务名称:</span>
-          <span>{{ taskInfo.title }}</span>
-        </div>
-        <div class='mt-10'>
-          <span class='bold'>任务说明:</span>
-          <span>{{ taskInfo.remark }}</span>
-        </div>
-        <div class='mt-10'>
-          <span class='bold'>任务时间:</span>
-          <span>{{ taskInfo.startTime }} - {{ taskInfo.endTime }}</span>
-        </div>
-        <div class='mt-10'>
-          <span class='bold'>任务状态:</span>
-          <el-tag>{{ taskInfo.isCompleted === 0 ? '未完成':'已完成' }}</el-tag>
-          <el-tag class='ml-10'>{{ taskInfo.isConfirmed === 0 ? '待确认':'已确认' }}</el-tag>
-        </div>
-      </div>
-    </div>
-  </basic-container>
-  <basic-container>
-    <div class='flex flex-align-start flex flex-col full-width '>
-      <span class='bold'>任务相关文件夹:</span>
-      <el-divider/>
-      <div class='full-width flex flex-justify-between'>
-        <el-button icon='back' type='primary'  @click='back'>
-          返回上一级
-        </el-button>
-        <el-button icon='Check' type='primary' v-if='this.folderList.length === 0 && taskInfo && taskInfo.isCompleted === 0 ' @click='completeTask'>
-          完成任务
-        </el-button>
-        <el-button icon='Check' type='primary' v-if='taskInfo && taskInfo.isConfirmed === 0 && view' @click='confirmTask'>
-          确认任务
-        </el-button>
-      </div>
-      <basic-curd class='full-width' :data='data' :option='taskOption' @row-view='rowDetail' @row-del='rowDel'>
-        <template #menu='{row}'>
-          <main-button title='提交文件' v-if='row.type === 2 && view !== true' icon='Position' @click='postFile(row)'/>
-        </template>
-      </basic-curd>
-    </div>
-<!--    upload-->
-    <el-dialog v-model='show' title='上传文件'>
-      <div>
-        <el-upload
-            drag
-            :action="action"
-            multiple
-            accept='.doc,.docx,.pdf,.xls,.xlsx,.png,.jpg,.jpeg,.ppt,pptx'
-            show-file-list
-            :headers="{'Authorization':`Basic ${clientId}`}"
-            :on-success='uploadSuccess'
-        >
-          <el-icon class="el-icon--upload">
-            <upload-filled/>
-          </el-icon>
-          <div class="el-upload__text">
-            拖拽或者 <em>点击上传文件</em>
-          </div>
-        </el-upload>
-        <el-divider/>
-        <div class='flex flex-justify-end'>
-          <el-button @click='show = false'>取 消</el-button>
-          <el-button type='primary' @click='saveFile'>确 定</el-button>
-        </div>
-      </div>
-    </el-dialog>
-    <el-image-viewer
-        v-if='showImage'
-        :url-list="imgList"
-        @close='showImage = false'
-    />
-  </basic-container>
-</template>
-<route>
-{
-name: '任务详情'
-}
-</route>
-
-<script>
-import BasicContainer from '@/components/basic-container/main.vue'
-import basicCurd from '@/components/basic-curd/index.vue'
-import MainButton from '@/components/main-button.vue'
-import api from '@/api/index.js'
-import { Base64 } from 'js-base64'
-import website from '@/config/website.js'
-
-export default {
-  name: 'detail',
-  components: { MainButton, BasicContainer, basicCurd },
-  data () {
-    return {
-      clientId: '',
-      view: false,
-      showImage: false,
-      imgList: [],
-      show: false,
-      action: api.uploadPath,
-      fileList: [],
-      id: '',
-      taskId: '',
-      page: {
-        pageSize: 10,
-        currentPage: 1,
-        total: 10
-      },
-      isAccess: 3,
-      current: null,
-      currentFolder: null,
-      taskInfo: null,
-      folderList: [],
-      resultFile: [],
-      data: [],
-      taskOption: {
-        viewBtn: true,
-        editBtn: false,
-        delBtn: false,
-        column: [
-          {
-            label: '文件夹',
-            prop: 'folderName'
-          },
-          {
-            label: '更新时间',
-            prop: 'updateTime'
-          },
-          {
-            label: '创建人',
-            prop: 'createUserName'
-          }
-        ]
-      }
-    }
-  },
-  created () {
-    this.id = this.$route.query.id
-    this.taskId = this.$route.query.taskId
-    this.clientId = Base64.encode(`${website.clientId}:${website.clientSecret}`)
-    const viewtemp = this.$route.query.view
-    if (viewtemp) {
-      this.view = true
-    }
-    if (this.view) {
-      this.confirmDetail()
-    } else {
-      this.detail()
-    }
-  },
-  methods: {
-    confirmDetail () {
-      const data = { taskId: this.taskId }
-      this.$api.task.confirmDetail(Object.assign(data, this.page)).then(res => {
-        if (res.code === 200) {
-          this.data = res.data.records.map(sub => {
-            sub.parentId = 0
-            sub.type = 2
-            return sub
-          })
-          this.current = this.data[0]
-          this.isAccess = this.data[0].isAccess
-          this.taskInfo = this.data[0]
-        } else {
-          this.$message.error(res.msg)
-        }
-      })
-    },
-    detail () {
-      const data = { taskId: this.taskId }
-      this.$api.task.detail(Object.assign(data, this.page)).then(res => {
-        if (res.code === 200) {
-          this.data = res.data.records.map(sub => {
-            sub.parentId = 0
-            sub.type = 2
-            return sub
-          })
-          this.current = this.data[0]
-          this.isAccess = this.data[0].isAccess
-          this.taskInfo = this.data[0]
-        } else {
-          this.$message.error(res.msg)
-        }
-      })
-    },
-    rowDel (row) {
-      this.$api.task.taskRemove({ taskId: this.taskId, ids: row.id }).then(res => {
-        if (res.code === 200) {
-          this.$message.success(res.msg)
-          this.data = this.data.filter(sub => sub.id !== row.id)
-        } else {
-          this.$message.error(res.msg)
-        }
-      })
-    },
-    rowDetail (row, index) {
-      if (row.type === 2) {
-        const data = { folderId: row.folderId, taskId: this.taskId }
-        this.folderList.push(row)
-        this.current = row
-        this.current.parentId = row.folderId
-        this.$api.task.fileList(data).then(res => {
-          if (res.code === 200) {
-            this.data = res.data.records.map(sub => {
-              sub.folderName = sub.title
-              // 继承上级的权限
-              sub.isAccess = row.isAccess
-              // rowDetail 需要folderId
-              sub.folderId = sub.id
-              return sub
-            })
-          } else {
-            this.$message.error(res.msg)
-          }
-        })
-      } else {
-        if (api.offices.includes(row.suffix)) {
-          const routeData = this.$router.resolve({ path: '/home/file_detail', query: { id: row.fileId } })
-          window.open(routeData.href, '_blank')
-        } else {
-          this.imgList = [row.url]
-          this.showImage = true
-        }
-      }
-    },
-    back () {
-      if (this.folderList.length === 0) {
-        this.$router.back()
-        return
-      }
-      this.folderList.pop()
-      this.current = this.folderList[this.folderList.length - 1]
-      if (this.folderList.length === 0) {
-        this.detail()
-      } else {
-        const data = { folderId: this.current.folderId }
-        this.$api.task.fileList(data).then(res => {
-          if (res.code === 200) {
-            this.data = res.data.records.map(sub => {
-              sub.folderName = sub.title
-              // 继承上级的权限
-              sub.isAccess = this.isAccess
-              // rowDetail 需要folderId
-              sub.folderId = sub.id
-              return sub
-            })
-          } else {
-            this.$message.error(res.msg)
-          }
-        })
-      }
-    },
-    postFile (row) {
-      console.log(row)
-      this.currentFolder = row
-      this.show = true
-    },
-    completeTask (row) {
-      this.$api.task.completeTask({ taskId: this.taskId }).then(res => {
-        if (res.code === 200) {
-          this.$message.success(res.msg)
-          this.taskInfo.isCompleted = 1
-        } else {
-          this.$message.error(res.msg)
-        }
-      })
-    },
-    uploadSuccess (res, file, files) {
-      this.fileList = files.map(sub => sub.response.data)
-    },
-    saveFile () {
-      if (this.fileList.length === 0) {
-        return
-      }
-      this.fileList.forEach(sub => {
-        if (['pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx'].includes(sub.suffix)) {
-          // save Library
-          const data = {
-            title: sub.originalFileName,
-            fileId: sub.id,
-            suffix: sub.suffix,
-            volume: sub.volume,
-            url: sub.filePath,
-            category: 4,
-            content: '',
-            parentId: this.currentFolder.folderId
-          }
-          this.$api.common.submit(data).then(res => {
-            if (res.code === 200) {
-              console.log(res.msg)
-            } else {
-              this.$message.error(res.msg)
-            }
-          })
-        }
-      })
-      this.addFile()
-    },
-    /**
-     * 上传文件
-     */
-    addFile () {
-      this.fileList = this.fileList.map(sub => {
-        sub.parentId = this.currentFolder.folderId
-        sub.projectId = this.currentFolder.projectId
-        sub.title = sub.originalFileName
-        sub.fileId = sub.id
-        sub.url = sub.filePath
-        return sub
-      })
-      const data = { taskId: this.taskId, dispatcherUser: this.currentFolder.dispatcherUser, folderId: this.currentFolder.folderId, files: this.fileList }
-      this.$api.task.uploadFile(data).then(res => {
-        if (res.code === 200) {
-          this.show = false
-        } else {
-          this.$message.error(res.msg)
-        }
-      })
-    },
-    confirmTask () {
-      this.$confirm('确认任务,并且向业主发送提醒', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning'
-      }).then(res => {
-        this.$api.task.taskConfirm({ taskId: this.taskId }).then(res => {
-          if (res.code === 200) {
-            this.$message.success(res.msg)
-            this.taskInfo.isConfirmed = 1
-          } else {
-            this.$message.error(res.msg)
-          }
-        })
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 162 - 0
src/views/task/index.vue

@@ -0,0 +1,162 @@
+<template>
+  <el-card shadow="hover" class="margin">
+    <div class="full-width flex flex-center flex-justify-start">
+      <el-button-group>
+        <el-button
+          type="primary"
+          :plain="type !== 1"
+          icon="el-icon-edit"
+          @click="changeType(1)"
+          >全部任务
+        </el-button>
+        <el-button
+          type="primary"
+          :plain="type !== 2"
+          icon="el-icon-share"
+          @click="changeType(2)"
+          >我创建的
+        </el-button>
+        <el-button
+          type="primary"
+          :plain="type !== 3"
+          icon="el-icon-delete"
+          @click="changeType(3)"
+          >我执行的
+        </el-button>
+      </el-button-group>
+    </div>
+    <div class="flex flex-center flex-justify-end flex-col full-width mt-20">
+      <div class="full-width flex flex-justify-start">
+        <el-dropdown @command="dropDown">
+          <span class="flex flex-center">
+            {{ isGroup === '0' ? '按时间排序' : '按项目排序' }}
+            <el-icon class="el-icon--right">
+              <arrow-down />
+            </el-icon>
+          </span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item command="0">按时间排序</el-dropdown-item>
+              <el-dropdown-item command="1">按项目排序</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+      <el-empty description="暂无数据" v-if="data.length === 0"></el-empty>
+      <div class="full-width" v-else>
+        <div class="full-width">
+          <task-table
+            :option="option"
+            :data="data"
+            :total="total"
+            @rowClick="rowClick"
+            @refresh="getTaskList"
+          ></task-table>
+        </div>
+        <el-divider />
+        <div class="flex flex-center flex-justify-end full-width">
+          <el-pagination
+            background
+            layout="prev, pager, next"
+            :total="total"
+            v-model:current-page="page.curren"
+            @current-change="pageChange"
+          />
+        </div>
+      </div>
+      <task type="view" ref="task" :task="task"></task>
+    </div>
+  </el-card>
+</template>
+
+<route>
+{
+path: '/task',
+name: '任务列表'
+}
+</route>
+
+<script>
+import TaskTable from '@/views/task/component/task-table.vue'
+import Task from '@/views/task/component/task.vue'
+
+export default {
+  components: { Task, TaskTable },
+  data() {
+    return {
+      page: {
+        current: 1,
+        size: 10
+      },
+      type: 1,
+      data: [],
+      task: [],
+      taskId: '',
+      total: 0,
+      isGroup: '0',
+      option: {
+        showCheckBox: false,
+        folderChecked: true,
+        addBtn: false,
+        column: [
+          {
+            label: '共20个任务',
+            prop: 'title',
+            display: false,
+            width: 500
+          },
+          {
+            label: '所属项目',
+            prop: 'projectName'
+          },
+          {
+            label: '标签',
+            prop: 'createUserName'
+          },
+          {
+            label: '时间',
+            prop: 'createUserName'
+          },
+          {
+            label: '执行人',
+            prop: 'createTime'
+          }
+        ]
+      }
+    }
+  },
+  created() {
+    this.getTaskList()
+  },
+  methods: {
+    changeType(type) {
+      this.type = type
+      this.page.current = 1
+      this.getTaskList()
+    },
+    getTaskList() {
+      const data = { type: this.type, isGroup: this.isGroup, ...this.page }
+      this.$api.task.taskList(data).then(res => {
+        if (res.code === 200) {
+          this.data = res.data.records
+          this.total = res.data.total
+        }
+      })
+    },
+    rowClick(res) {
+      this.taskId = res.id
+    },
+    pageChange(current) {
+      this.page.current = current
+      this.getTaskList()
+    },
+    dropDown(res) {
+      this.isGroup = res
+      this.page.current = 1
+      this.getTaskList()
+    }
+  }
+}
+</script>
+
+<style scoped></style>

+ 0 - 1
src/views/user/manage.vue

@@ -337,7 +337,6 @@ export default {
       this.page.current = this.page.currentPage
       this.page.size = this.page.pageSize
       const data = { deptId: this.user.info.deptId }
-      console.log(this.page)
       this.$api.role
         .roleList(Object.assign(this.page, data))
         .then(res => {

+ 3 - 3
vite.config.js

@@ -1,4 +1,4 @@
-import {defineConfig} from 'vite'
+import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import Pages from 'vite-plugin-pages'
 import Layouts from 'vite-plugin-vue-layouts'
@@ -57,8 +57,8 @@ export default defineConfig({
     proxy: {
       '/api': {
         // 正式环境地址
-        // target: 'https://dev.wutongresearch.club/api',
-        target: 'https://prod.wutongshucloud.com/api',
+        target: 'https://dev.wutongresearch.club/api',
+        // target: 'https://prod.wutongshucloud.com/api',
         // target: 'http://192.168.31.181:8110',
         changeOrigin: true,
         rewrite: path => path.replace(/^\/api/, '')

+ 29 - 0
yarn.lock

@@ -927,6 +927,11 @@ cidr-regex@^3.1.1:
   dependencies:
     ip-regex "^4.1.0"
 
+classnames@^2.2.6:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
+  integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
+
 clean-stack@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@@ -1059,6 +1064,11 @@ dayjs@^1.10.4, dayjs@^1.11.3:
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea"
   integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==
 
+debounce@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
+  integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
+
 debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@@ -1188,6 +1198,11 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
+easy-bem@^1.0.2:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/easy-bem/-/easy-bem-1.1.1.tgz#1bfcc10425498090bcfddc0f9c000aba91399e03"
+  integrity sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==
+
 echarts@^5.4.1:
   version "5.4.2"
   resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.2.tgz#9f38781c9c6ae323e896956178f6956952c77a48"
@@ -4032,6 +4047,20 @@ vite@^3.0.0:
   optionalDependencies:
     fsevents "~2.3.2"
 
+vue-advanced-cropper@^2.8.8:
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/vue-advanced-cropper/-/vue-advanced-cropper-2.8.8.tgz#af0e8324312be5a1a92ce9fd3aff8264d28a5b33"
+  integrity sha512-yDM7Jb/gnxcs//JdbOogBUoHr1bhCQSto7/ohgETKAe4wvRpmqIkKSppMm1huVQr+GP1YoVlX/fkjKxvYzwwDQ==
+  dependencies:
+    classnames "^2.2.6"
+    debounce "^1.2.0"
+    easy-bem "^1.0.2"
+
+vue-cropper@^1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/vue-cropper/-/vue-cropper-1.0.9.tgz#de402c57cadc221e9a2063399ff35bb04220ef22"
+  integrity sha512-JhQwxmjqmQohzI7sAp5O/Rfdxuw5HOEYkKjnp/De7iCi6c8Mv6M3N9HpMt9xgWCFchX3/DfXBv2axCZOCg3G8Q==
+
 vue-demi@*, vue-demi@>=0.14.5:
   version "0.14.5"
   resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9"

Some files were not shown because too many files changed in this diff