wenbobowen 4 rokov pred
rodič
commit
7d2a699ddd

+ 1 - 1
package.json

@@ -63,7 +63,7 @@
     "vue-json-viewer": "^2.2.8",
     "vue-qr": "^2.2.1",
     "vue-router": "3.0.6",
-    "vuedraggable": "^2.23.2",
+    "vuedraggable": "^2.24.3",
     "vuex": "3.1.0",
     "wangeditor": "^3.1.1",
     "webpack": "^4.44.2",

+ 72 - 0
src/api/publishTask.js

@@ -0,0 +1,72 @@
+// 项目
+import request from '@/utils/request'
+import { TeamManagement } from '@/apiConfig/api'
+
+console.log(TeamManagement)
+// 获取项目列表
+const host = TeamManagement
+export function getCheckListBytask(data) {
+  return request({
+    url: `${host}/checklist/getByTask?taskId=${data.taskId}`,
+    method: 'get'
+  })
+}
+
+export function getBizBindTemList(data) {
+  return request({
+    url: `${host}/checklist/selectTemplates`,
+    method: 'post',
+    data
+  })
+}
+
+export function getOnlineBizModule(data) {
+  return request({
+    url: `${host}/checklist/selectBizModule?taskId=${data.taskId}&query=${data.query}`,
+    method: 'get',
+    data
+  })
+}
+
+export function updateChecklist(data) {
+  return request({
+    url: `${host}/checklist/updateTemplateCheckStatus`,
+    method: 'post',
+    data
+  })
+}
+
+export function createChecklist(data) {
+  return request({
+    url: `${host}/checklist/create`,
+    method: 'post',
+    data
+  })
+}
+
+// 更新checklist下模板的check状态
+export function updateTemplateCheckStatus(data) {
+  return request({
+    url: `${host}/checklist/updateTemplateCheckStatus`,
+    method: 'post',
+    data
+  })
+}
+
+// 获取评论列表
+export function getCommentList(data) {
+  return request({
+    url: `${host}/comment/list`,
+    method: 'post',
+    data
+  })
+}
+
+// 创建评论列表/comment/create
+export function createComment(data) {
+  return request({
+    url: `${host}/comment/list`,
+    method: 'post',
+    data
+  })
+}

+ 153 - 0
src/components/actionDynamic/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="actionDynamic">
+    <el-tabs v-model="activeName" @tab-click="changeTabs">
+      <el-tab-pane label="评论" name="Comments">
+        <div class="Comments">
+          <div
+            v-for="(item, index) in comments"
+            :key="index"
+          >
+            <div style="font-size: 14px; color: #333b4a; display: inline-block">
+              {{ item.commentInfo.name }}
+            </div>
+            <div
+              style="
+                margin-left: 20px;
+                display: inline-block;
+                color: #9b9b9b;
+                font-size: 12px;
+              "
+            >
+              {{ item.commentInfo.gmtCreater }}
+            </div>
+            <p
+              style="
+                font-size: 14px;
+                color: #333b4a;
+                margin: 0 0 25px 0;
+                white-space: pre-line;
+              "
+            >
+              {{ item.commentInfo.content }}
+            </p>
+          </div>
+          <el-input
+            v-model="commentContent"
+            type="textarea"
+            placeholder="请输入评论内容"
+            maxlength="1000"
+            show-word-limit
+            :autosize="{ minRows: 3, maxRows: 5 }"
+            style="margin-bottom: 2%"
+          />
+          <el-button
+            type="primary"
+            size="small"
+            style="float: right"
+            @click="$emit('addComment', commentContent, callback)"
+          >
+            发表评论
+          </el-button>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="变更记录" name="Logs">
+        <div v-for="(item,index) in changeRecord" :key="index" class="Layout_space_between sign-record">
+          <span>
+            <span class="operatorName">{{ item.operator }} : </span>
+            <span class="remark">{{ item.remark }}</span>
+          </span>
+          <span class="createTime">{{ item.createTime }}</span>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+<script>
+import '@/styles/PublicStyle/index.scss'
+export default {
+  props: {
+    comments: {
+      type: Array,
+      required: false,
+      default: () => []
+    },
+    changeRecord: {
+      type: Array,
+      required: false,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      activeName: 'Comments',
+      commentContent: ''
+      // comments: [
+      //   {
+      //     child: [],
+      //     commentInfo: {"id":11529,"joinId":4721,"bizId":null,"type":3,"fatherId":0,"name":"文博","email":"wenbobowen","content":"test111","gmtCreater":"2020-12-31 15:03:37","gmtModify":"2020-12-31 15:03:37"}
+      //   },
+      //   {
+      //     child: [],
+      //     commentInfo: {"id":11529,"joinId":4721,"bizId":null,"type":3,"fatherId":0,"name":"文博","email":"wenbobowen","content":"test222","gmtCreater":"2020-12-31 15:03:37","gmtModify":"2020-12-31 15:03:37"}
+      //   }
+      // ],
+      // changeRecord: [
+      //   {
+      //     createTime: "2020-12-29 13:48:55",
+      //     operator: "廖子君",
+      //     remark: "更新了任务状态,从【已准出】到【已上线】"
+      //   },
+      //   {
+      //     createTime: "2020-12-29 13:48:55",
+      //     operator: "廖子君",
+      //     remark: "更新了任务状态,从【已准出】到【已上线】"
+      //   },
+      //   {
+      //     createTime: "2020-12-29 13:48:55",
+      //     operator: "廖子君",
+      //     remark: "更新了任务状态,从【已准出】到【已上线】"
+      //   }
+      // ]
+    }
+  },
+  methods: {
+    changeTabs(e) {
+      console.log(e)
+    },
+    callback() {
+      console.log('chengg')
+      this.commentContent = ''
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.actionDynamic {
+  padding: 0px 30px 20px 30px;
+  .Comments {
+    padding: 0 34px 20px 34px;
+  }
+}
+.sign-record {
+  margin: 20px 0;
+  font-size:14px;
+  line-height:20px;
+  opacity:1;
+  font-family:PingFangSC-Regular;
+}
+.createTime {
+  min-width:150px;
+  color:rgba(68,68,68,1);
+}
+
+.remark {
+  min-width:500px;
+  text-align: left;
+  color:#444444;
+}
+.operatorName {
+  min-width: 60px;
+  color:rgba(51,59,74,1);
+  margin-right: 10px;
+}
+</style>

+ 54 - 0
src/components/headTitle/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="main-title">
+    <div class="title-left-icon" />
+    <div class="title-left-name">{{ title }}</div>
+    <div v-if="openEdit" class="editBtn">
+      <i class="el-icon-edit" @click="$emit('editHandle')" />
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  props: {
+    title: {
+      type: String,
+      required: true
+    },
+    openEdit: {
+      type: Boolean,
+      required: false
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.main-title {
+  align-items: center;
+  padding: 20px 30px;
+  .title-left-icon {
+    width: 4px;
+    height: 17px;
+    background: #409eff;
+    border-radius: 1px;
+    display: inline-block;
+    vertical-align: text-top;
+  }
+  .title-left-name {
+    display: inline-block;
+    width: auto;
+    height: 20px;
+    line-height: 20px;
+    font-size: 16px;
+    font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,"\5FAE\8F6F\96C5\9ED1",Arial,sans-serif;
+    color: #333b4a;
+    margin-left: 6px;
+  }
+  .editBtn {
+    display: inline-block;
+    .el-icon-edit {
+      color: #1890FF;
+      margin-left: 6px;
+    }
+  }
+}
+</style>

+ 8 - 2
src/components/input/normalArea.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <article :id="id">
+    <article :id="id" :style="styles">
       <editor :id="'tinymce_'+id" ref="editor" v-model="inputValue" :init="init" @input="changeText" />
     </article>
   </div>
@@ -10,7 +10,6 @@ import tinymce from 'tinymce/tinymce'
 import Editor from '@tinymce/tinymce-vue'
 import 'tinymce/themes/silver/theme'
 import 'tinymce/icons/default/icons'
-
 export default {
   components: {
     Editor
@@ -30,6 +29,13 @@ export default {
       type: Number,
       default: 200,
       required: false
+    },
+    styles: {
+      type: Object,
+      default: () => {
+        return { }
+      },
+      required: false
     }
   },
   data() {

+ 11 - 3
src/components/input/textArea.vue

@@ -3,6 +3,7 @@
     <article
       :id="id"
       v-loading="loading"
+      :style="styles"
       element-loading-text="数据上传中,请稍后"
       element-loading-spinner="el-icon-loading"
     >
@@ -39,6 +40,13 @@ export default {
     Editor
   },
   props: {
+    styles: {
+      type: Object,
+      default: () => {
+        return { padding: '0 30px 20px 30px' }
+      },
+      required: false
+    },
     id: {
       type: String,
       default: '',
@@ -149,9 +157,9 @@ export default {
 }
 </script>
 <style scoped lang="scss">
-article {
-  padding: 0 30px 20px 30px;
-}
+// article {
+//   padding: 0 30px 20px 30px;
+// }
 .text-edit {
   color: #666666;
   font-size: 14px;

+ 16 - 1
src/components/redTipTitle/index.vue

@@ -1,6 +1,6 @@
 <template>
   <p class="redTipword">
-    <span class="title">{{ title }}</span>
+    <span class="title" :class="isedit && 'edit'">{{ title }}</span>
   </p>
 </template>
 <script>
@@ -10,6 +10,11 @@ export default {
     title: {
       type: String,
       required: true
+    },
+    isedit: {
+      type: Boolean,
+      required: false,
+      default: false
     }
   }
 }
@@ -17,7 +22,17 @@ export default {
 <style scoped lang="scss">
 .redTipword {
   vertical-align: middle;
+  margin: 35px 0 10px 0px;
   .title {
+    font-size: 16px;
+    font-weight: 400;
+    line-height: 18px;
+    color: #444;
+  }
+  &:first-child {
+    margin-top: 0px;
+  }
+  .edit {
     &::before{
       content: '*';
       color: #F56C6C;

+ 1 - 0
src/icons/svg/add.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><defs><style>.cls-1{fill:#409eff}</style></defs><g id="添加_2_" data-name="添加 (2)" transform="translate(-138.581 -138.581)"><path id="路径_13078" data-name="路径 13078" class="cls-1" d="M271.147 491.958h-7.309c-.186 0-.328-.095-.328-.219s.142-.219.328-.219h7.309c.186 0 .328.095.328.219s-.142.219-.328.219z" transform="translate(-121.911 -346.152)"/><path id="路径_13079" data-name="路径 13079" class="cls-1" d="M491.739 271.482c-.124 0-.219-.142-.219-.329v-7.315c0-.186.095-.329.219-.329s.219.142.219.329v7.315c0 .186-.095.329-.219.329z" transform="translate(-346.158 -121.91)"/><path id="路径_13080" data-name="路径 13080" class="cls-1" d="M151.468 152.581h-11.773a1.115 1.115 0 01-1.113-1.114V139.7a1.115 1.115 0 011.113-1.114h11.773a1.115 1.115 0 011.113 1.114v11.78a1.12 1.12 0 01-1.113 1.101zm-11.773-13.231a.362.362 0 00-.346.346v11.771a.362.362 0 00.346.346h11.773a.344.344 0 00.346-.346V139.7a.362.362 0 00-.346-.346z"/></g></svg>

+ 1 - 0
src/icons/svg/del.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><defs><style>.cls-1{fill:#409eff}</style></defs><g id="添加_2_" data-name="添加 (2)" transform="translate(-138.581 -138.581)"><path id="路径_13078" data-name="路径 13078" class="cls-1" d="M272.138 492.1h-8.258c-.21 0-.371-.125-.371-.288s.161-.288.371-.288h8.258c.21 0 .371.125.371.288s-.16.288-.371.288z" transform="translate(-122.17 -346.22)"/><path id="路径_13080" data-name="路径 13080" class="cls-1" d="M151.468 152.581h-11.773a1.115 1.115 0 01-1.113-1.114V139.7a1.115 1.115 0 011.113-1.114h11.773a1.115 1.115 0 011.113 1.114v11.78a1.12 1.12 0 01-1.113 1.101zm-11.773-13.231a.362.362 0 00-.346.346v11.771a.362.362 0 00.346.346h11.773a.344.344 0 00.346-.346V139.7a.362.362 0 00-.346-.346z"/></g></svg>

+ 1 - 5
src/icons/svg/problem.svg

@@ -1,6 +1,3 @@
-<<<<<<< HEAD
-<svg xmlns="http://www.w3.org/2000/svg" width="13.308" height="14.215"><defs><linearGradient id="a" x1=".5" x2=".5" y2="1" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#409eff"/><stop offset="1" stop-color="#80c9ff"/></linearGradient></defs><g transform="translate(-201.5 -191.2)" fill="url(#a)"><path data-name="路径 13053" d="M212.3 205.415h-9.6a1.157 1.157 0 01-1.2-1.111v-11.993a1.158 1.158 0 011.2-1.111h9.6a1.157 1.157 0 011.2 1.111v1.8a.443.443 0 01-.887 0v-1.8c0-.119-.145-.222-.309-.222h-9.6c-.165 0-.309.1-.309.222V204.3c0 .119.145.222.309.222h9.6c.165 0 .309-.1.309-.222v-.7a.443.443 0 11.887 0v.709a1.156 1.156 0 01-1.2 1.106z"/><path data-name="路径 13054" d="M210.565 195.652h-6.664a.45.45 0 010-.9h6.664a.45.45 0 010 .9zm-1.332 3.095h-5.332a.439.439 0 110-.878h5.329a.439.439 0 110 .878zm1.332 3.117h-6.664a.45.45 0 010-.9h6.664a.45.45 0 010 .9z"/><path data-name="路径 13055" d="M214.363 197.923a.444.444 0 01-.443-.443.91.91 0 10-1.82 0 .444.444 0 01-.887 0 1.8 1.8 0 113.6 0 .445.445 0 01-.45.443z"/><path data-name="路径 13056" d="M213.103 201.123a.444.444 0 01-.443-.443v-.72a1.9 1.9 0 01.56-1.361 1.908 1.908 0 00.694-1.124.451.451 0 01.439-.441.437.437 0 01.443.415 2.553 2.553 0 01-.964 1.789 1 1 0 00-.29.722v.72a.442.442 0 01-.439.443z"/><path data-name="路径 13057" d="M212.62900000000002 201.912a.474.474 0 10.474-.474.474.474 0 00-.474.474z"/></g></svg>
-=======
 
 <svg xmlns="http://www.w3.org/2000/svg" width="8.272" height="9.613" viewBox="0 0 8.272 9.613">
   <g id="问题" transform="translate(-160.5 -103.5)">
@@ -8,5 +5,4 @@
     <path id="路径_433" data-name="路径 433" d="M165.2,112.347h-3.692a1.008,1.008,0,0,1-1.006-1.006v-6.835a1.008,1.008,0,0,1,1.006-1.006h6.26a1.008,1.008,0,0,1,1.006,1.006v3.8a.335.335,0,1,1-.671,0v-3.8a.336.336,0,0,0-.335-.335h-6.26a.336.336,0,0,0-.335.335v6.835a.336.336,0,0,0,.335.335H165.2a.335.335,0,1,1,0,.671Z" fill="#666"/>
     <path id="路径_434" data-name="路径 434" d="M646.23,572.333a.333.333,0,0,1-.335-.332,1.675,1.675,0,0,1,.688-1.32.518.518,0,0,0,.192-.539.538.538,0,0,0-.406-.388.589.589,0,0,0-.5.109.521.521,0,0,0-.2.406.335.335,0,1,1-.671,0,1.186,1.186,0,0,1,.449-.928,1.256,1.256,0,0,1,1.064-.241,1.2,1.2,0,0,1,.913.89,1.188,1.188,0,0,1-.433,1.224,1.014,1.014,0,0,0-.427.784A.34.34,0,0,1,646.23,572.333Zm0,.87a.335.335,0,0,1,0-.671h0a.335.335,0,1,1,0,.671Z" transform="translate(-478.799 -460.089)" fill="#666"/>
   </g>
-</svg>
->>>>>>> http_mock
+</svg>

+ 26 - 0
src/styles/detail-pages.scss

@@ -171,4 +171,30 @@
       margin-bottom: 0;
     }
   }
+}
+@mixin main-title {
+  align-items: center;
+  padding: 20px 30px;
+  .title-left-icon {
+    width: 4px;
+    height: 17px;
+    background: #409eff;
+    border-radius: 1px;
+    display: inline-block;
+    vertical-align: text-top;
+  }
+  .title-left-name {
+    display: inline-block;
+    width: auto;
+    height: 20px;
+    line-height: 20px;
+    font-size: 16px;
+    font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,"\5FAE\8F6F\96C5\9ED1",Arial,sans-serif;
+    color: #333b4a;
+    margin-left: 6px;
+  }
+}
+p {
+  padding: 0;
+  margin: 0;
 }

+ 0 - 10
src/views/projectManage/components/record.vue

@@ -4,17 +4,7 @@
     <div v-for="(item,index) in changeRecord" :key="index" class="Layout_space_between sign-record">
       <span>
         <span class="operatorName">{{ item.operator }} : </span>
-        <!-- <el-tooltip
-          v-if="item.remark.length > 50"
-          class="item"
-          effect="dark"
-          :content="item.remark"
-          placement="top-start"
-          popper-class="tip-style"
-        > -->
         <span class="remark">{{ item.remark }}</span>
-        <!-- </el-tooltip>
-        <span v-else class="remark">{{ item.remark.length > 50 ? item.remark.substring(0, 50) + '...' : item.remark }}</span> -->
       </span>
       <span class="createTime">{{ item.createTime }}</span>
     </div>

+ 46 - 0
src/views/projectManage/publishTask/components/checkboxList.vue

@@ -0,0 +1,46 @@
+<template>
+  <el-checkbox-group v-model="selectedList" @change="$emit('change', selectedList)">
+    <el-checkbox v-for="item in data" :key="item.id" class="checkbox" :label="item.id">{{ item.name }}</el-checkbox>
+  </el-checkbox-group>
+</template>
+<script>
+// const cityOptions = ['上海', '北京', '广州', '深圳', 'ss', 'qq', 'www', 'eeee', 'ssss', 'vvvv', 'ggg', 'jjj']
+export default {
+  props: {
+    selectedList: {
+      type: Array,
+      required: false,
+      default: () => []
+    },
+    data: {
+      type: Array,
+      required: false,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      checkAll: false,
+      // checkedCities: ['上海', '北京'],
+      // cities: cityOptions,
+      isIndeterminate: true
+    }
+  },
+  methods: {
+    handleCheckAllChange(val) {
+      // this.checkedCities = val ? cityOptions : []
+      this.isIndeterminate = false
+    },
+    handleCheckedCitiesChange(value) {
+      const checkedCount = value.length
+      this.checkAll = checkedCount === this.cities.length
+      this.isIndeterminate = checkedCount > 0 && checkedCount < this.cities.length
+    }
+  }
+}
+</script>
+<style scoped lang='scss'>
+.checkbox {
+  width: 18%
+}
+</style>

+ 135 - 0
src/views/projectManage/publishTask/components/multipleSelect.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="taskSelect" :style="styles">
+    <el-select
+      v-if="isedit"
+      v-model="value"
+      class="maxWidth"
+      :size="size"
+      multiple
+      filterable
+      remote
+      reserve-keyword
+      :placeholder="placeholder"
+      :remote-method="remoteMethod"
+      :loading="loading"
+      @change="clearHandle"
+    >
+      <el-option
+        v-for="item in options"
+        :key="item.id"
+        :value="item.id"
+      >
+        <p class="content" style="margin: 0">
+          <span>
+            <span class="taskId">TASK-{{ item.id }}</span>
+            <span class="ml20">{{ item.name }}</span>
+          </span>
+          <!-- <i v-if="isedit" class="el-icon-circle-close didi-hover" @click="$emit('delate', item.id)" /> -->
+        </p>
+      </el-option>
+    </el-select>
+    <div class="maxWidth">
+      <p v-for="item in data" :key="item.id" class="content">
+        <span>
+          <span class="taskId">TASK-{{ item.id }}</span>
+          <span class="ml20">{{ item.name }}</span>
+        </span>
+        <i v-if="isedit" class="el-icon-circle-close didi-hover" @click="$emit('change', item, 'del')" />
+      </p>
+    </div>
+  </div>
+</template>
+<script>
+import { taskList } from '@/api/taskIndex'
+export default {
+  name: 'MultipleSelect',
+  props: {
+    styles: {
+      type: Object,
+      required: false,
+      default: () => {
+        return { maxWidth: '690px' }
+      }
+    },
+    size: {
+      type: String,
+      required: false,
+      default: 'medium'
+    },
+    placeholder: {
+      type: String,
+      required: false,
+      default: '请输入'
+    },
+    isedit: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    data: {
+      type: Array,
+      required: false,
+      default: () => []
+    },
+    taskId: {
+      type: Number,
+      required: false,
+      default: null
+    }
+  },
+  data() {
+    return {
+      options: [],
+      value: [],
+      loading: false
+    }
+  },
+  methods: {
+    async remoteMethod(query) {
+      this.query = query
+      if (query !== '') {
+        this.loading = true
+        const res = await taskList({ taskId: this.taskId, name: query })
+        this.options = res.data
+        this.loading = false
+      } else {
+        this.options = []
+      }
+    },
+    clearHandle(val) {
+      console.log(val)
+      let item = null
+      console.log(this.data)
+      this.options.forEach(t => {
+        if (t.id === val[0]) {
+          item = t
+        }
+        console.log(t.id === val[0], t)
+      })
+      console.log(item)
+      this.$emit('change', item, 'add')
+      this.query = ''
+      this.value = null
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.taskSelect {
+  .maxWidth {
+    width: 100%;
+  }
+  .ml20 {
+    margin-left: 20px;
+  }
+}
+.content {
+  display: flex;
+  justify-content: space-between;
+  margin: 15px 0;
+  .taskId {
+    display: inline-block;
+    width: 120px;
+  }
+}
+</style>

+ 283 - 0
src/views/projectManage/publishTask/components/onlineCheckList.vue

@@ -0,0 +1,283 @@
+<template>
+  <div class="onlineCheckList">
+    <table class="table">
+      <tr>
+        <th class="">模块</th>
+        <th>模块名</th>
+      </tr>
+      <tr v-for="(d, index) in data.tableContent" :key="d.id">
+        <td>
+          <el-input v-if="isedit" v-model="d.module" placeholder="请输入模块" :size="size" />
+          <span v-else>{{ d.module }}</span>
+        </td>
+        <td>
+          <div v-if="isedit" class="addmodule">
+            <el-select
+              v-model="value"
+              class="addSelect"
+              :size="size"
+              multiple
+              filterable
+              remote
+              reserve-keyword
+              placeholder="请选择模块"
+              :remote-method="remoteMethod"
+              :loading="loading"
+              @change="clearHandle(d.module)"
+            >
+              <el-option
+                v-for="item in options"
+                :key="item"
+                :label="item"
+                :value="item"
+              />
+            </el-select>
+            <span class="addBtn" @click="addModule(d.module)">添加</span>
+          </div>
+          <div v-if="isedit">
+            <el-tag
+              v-for="tag in d.moduleNames"
+              :key="tag"
+              class="tag"
+              size="small"
+              closable
+              type="info"
+              @close="handleClose(d.module, tag)"
+            >
+              {{ tag }}
+            </el-tag>
+          </div>
+          <div v-else>
+            <span v-for="tag in d.moduleNames" :key="tag" class="tag word">{{ tag }}</span>
+          </div>
+          <div v-if="isedit" class="btnGroup">
+            <svg-icon icon-class="add" class="svg" @click="$emit('changeRow', 'add', moduleId, index)" />
+            <svg-icon v-if="data.tableContent.length !== 1" icon-class="del" class="svg" @click="$emit('changeRow', 'del', moduleId, index)" />
+          </div>
+        </td>
+      </tr>
+    </table>
+    <div class="inlineList">
+      <div class="subTitle">
+        上线顺序
+        <el-tooltip effect="dark" content="鼠标拖动模块,可调整顺序" placement="top-start">
+          <i class="el-icon-info" />
+        </el-tooltip>
+      </div>
+      <draggable
+        v-if="isedit"
+        v-model="data.onlineOrder"
+        chosen-class="chosen"
+        force-fallback="true"
+        group="people"
+        animation="1000"
+        @start="onStart"
+        @end="onEnd"
+      >
+        <transition-group>
+          <div
+            v-for="(element, index) in data.onlineOrder"
+            :key="element"
+            class="onlineItem"
+          >
+            <span class="dragNo">{{ index+ 1 }}</span>
+            <span class="dragword">{{ element }}</span>
+          </div>
+        </transition-group>
+      </draggable>
+      <div v-else>
+        <div
+          v-for="(element, index) in data.onlineOrder"
+          :key="element"
+          class="onlineItem textItem"
+        >
+          <span class="dragNo">{{ index+ 1 }}</span>
+          <span class="dragword">{{ element }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import draggable from 'vuedraggable'
+import { getOnlineBizModule } from '@/api/publishTask'
+export default {
+  components: {
+    draggable
+  },
+  props: {
+    isedit: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    data: {
+      type: Object,
+      required: false,
+      default: () => {}
+    },
+    moduleId: {
+      type: Number,
+      required: true,
+      default: null
+    },
+    taskId: {
+      type: Number,
+      required: false,
+      default: null
+    }
+  },
+  data() {
+    return {
+      drag: false,
+      options: [],
+      value: [],
+      loading: false,
+      size: 'small',
+      query: '',
+      name: ''
+    }
+  },
+  mounted() {
+    console.log(1111111111)
+  },
+  methods: {
+    async remoteMethod(query) {
+      this.query = query
+      if (query !== '') {
+        this.loading = true
+        const res = await getOnlineBizModule({ taskId: this.taskId, query })
+        this.options = res.data
+        this.loading = false
+      } else {
+        this.options = []
+      }
+    },
+    clearHandle(m) {
+      console.log(m, this.value)
+      this.$emit('changeSelectedHandle', this.moduleId, m, this.value[0], 'add')
+      this.query = ''
+      this.value = null
+    },
+    addModule(m) {
+      if (this.query) {
+        this.$emit('changeSelectedHandle', this.moduleId, m, this.query, 'add')
+      }
+    },
+    getfocus(index) {
+      console.log(index)
+      // window.elemmm[`s${index}`]=this.$refs[`s${index}`]
+      // this.$refs[`s${index}`][0].focused()
+    },
+    handleClose(m, val) {
+      this.$emit('changeSelectedHandle', this.moduleId, m, val, 'del')
+    },
+    // 开始拖拽事件
+    onStart() {
+      this.drag = true
+    },
+    // 拖拽结束事件
+    onEnd() {
+      this.drag = false
+      console.log(this.data)
+    }
+  }
+}
+</script>
+<style scoped lang='scss'>
+.onlineCheckList {
+  max-width: 690px;
+  .table {
+    width: 100%;
+    border-radius: 4px;
+    border-collapse: collapse;
+    border: none;
+    th {
+      background: #E8E8E8;
+      text-align: center;
+      color: '#333B4A';
+      font-size: '14px';
+      font-weight: '400';
+      &:first-child {
+        width: 216px;
+      }
+    }
+    th, td {
+      border: 1px solid #D1D0D0;
+      padding: 12px 10px;
+      position: relative;
+      &:first-child {
+        text-align: center;
+      }
+    }
+    .btnGroup {
+      position: absolute;
+      bottom: 0px;
+      right: -60px;
+      width: 60px;
+      text-align: center;
+      .svg {
+        margin: 0 5px;
+        font-size: 14px;
+      }
+    }
+  }
+  .inlineList {
+    .dragNo {
+      display: inline-block;
+      width: 16px;
+      height:16px;
+      line-height: 14px;
+      text-align: center;
+      border: 1px solid #999;
+      border-radius: 50%;
+      margin-right: 5px;
+    }
+    .dragword {
+      font-size: 12px;
+    }
+    .subTitle {
+      margin-top: 16px;
+      .el-icon-info {
+        color: #999;
+      }
+    }
+  }
+  .tag {
+    margin-right: 24px;
+    margin-bottom: 5px;
+    &.word {
+      margin-right: 0px;
+      &::after{
+        content: ',';
+      }
+      &:last-child::after {
+        content: '';
+      }
+    }
+  }
+  .addmodule {
+    margin-bottom: 5px;
+    .addBtn {
+      font-size: 12px;
+      color: #409eff;
+      margin-left: 6px;
+      cursor: pointer;
+    }
+  }
+}
+.onlineItem {
+  display: inline-block;
+  margin: 10px 40px 10px 0px;
+  font-size: 14px;
+  border: solid 2px transparent;
+  // margin-bottom: 10px;
+  cursor: move;
+  &.textItem{
+    cursor: text;
+  }
+}
+.chosen {
+  border: solid 2px #3089dc !important;
+}
+</style>

+ 83 - 0
src/views/projectManage/publishTask/components/step.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="stepBox">
+    <div class="line">
+      <span class="before circle" />
+      <div v-for="item in data" :key="item.id" class="subTitle" @click="$emit('goto', `s${item.id}`)">
+        <i class="el-icon-success icon" :class="getclass(item.id)" />
+        {{ item.name }}
+      </div>
+      <span class="after circle" />
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  props: {
+    data: {
+      type: Array,
+      default: () => [],
+      required: false
+    },
+    typeList: {
+      type: Array,
+      default: () => [],
+      required: false
+    }
+  },
+  methods: {
+    getclass(id) {
+      let type = 'el-icon-question'
+      this.typeList.map(t => {
+        if (t.id === id && t.isCheck) {
+          type = 'el-icon-success'
+        }
+      })
+      return type
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.line {
+  border-right: 2px solid #D1D1D1;
+  position: relative;
+  padding: 2px 0;
+  .circle {
+    display: inline-block;
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    border: 1px solid #666;
+    background-color: #fff;
+    position: absolute;
+    left: calc(100% - 2px);
+  }
+  .subTitle {
+    width: 130px;
+    text-align: center;
+    margin: 16px 0px 16px 50%;
+    background: #fff;
+    cursor: pointer;
+    position: relative;
+    .icon {
+      position: absolute;
+      left: 0px;
+      top: 4px;
+      font-size: 14px;
+    }
+    .el-icon-success {
+      color: #1890FF;
+    }
+    .el-icon-question {
+      color: #999;
+    }
+  }
+  .before {
+    top: 0px;
+    margin-bottom: 8px;
+  }
+  .after {
+    bottom: 0px;
+  }
+}
+</style>

+ 426 - 15
src/views/projectManage/publishTask/index.vue

@@ -1,34 +1,445 @@
 <template>
-  <div class="publishTask main-section">
-    <header>
-      <div class="el-main-title">
-        <div class="title-left-icon" />
-        <div class="title-left-name">checklist</div>
+  <div class="editPublishTask">
+    <section class="main-section pubconfig">
+      <div v-if="showEmpty">
+        <header>
+          <headTitle title="checklist" />
+        </header>
+        <div class="empty">
+          未设置 <span class="createBtn" @click="addAction">点击添加</span>
+        </div>
+      </div>
+      <div v-else>
+        <header>
+          <headTitle title="checklist" :open-edit="openEdit" @editHandle="editHandle" />
+        </header>
+        <div class="wrap">
+          <redTipword title="关联任务" :isedit="edit" />
+          <multipleSelect placeholder="🔍 请输入任务名称或ID" :isedit="edit" :data="data.tasks" :task-id="taskId" @change="changeTask" />
+          <div v-if="edit" style="margin-top: 30px">
+            <redTipword title="选择checklist列表" :isedit="edit" />
+            <checkboxList :selected-list="data.selectedBizTemplateIds" :data="temList" @change="updateSelectedTemHandle" />
+          </div>
+        </div>
+        <div class="moduleList wrap">
+          <redTipword v-if="!edit" title="检查项" :isedit="edit" />
+          <div v-for="(item, index) in data.templates" :key="item.id" class="item">
+            <div v-if="item.content.indexOf('s') > -1">
+              <p :id="`s${item.id}`" class="title">
+                <el-checkbox v-if="!edit" v-model="item.isCheck" :label="item.name" @change="updateCheckItemHandle(item.isCheck, item)">{{ item.name }}</el-checkbox>
+                <span v-else>{{ item.name }}</span>
+              </p>
+              <onlineCheckList
+                :module-id="item.id"
+                :isedit="edit"
+                :data="item.onlineModule"
+                :task-id="taskId"
+                @changeSelectedHandle="changeOnlineSelectedHandle"
+                @changeRow="changeOnlineModuleRow"
+                @onChangeModuleName="(val, subIdx) => onChangeModuleName(val, subIdx, index)"
+              />
+            </div>
+            <div v-else>
+              <p :id="`s${item.id}`" class="title">
+                <el-checkbox v-if="!edit" v-model="item.isCheck" :label="item.name" @change="updateCheckItemHandle(item)">{{ item.name }}</el-checkbox>
+                <span v-else>{{ item.name }}</span>
+              </p>
+              <normal-area
+                v-if="edit"
+                :id="'tem'+item.id"
+                :value.sync="item.content"
+                :empty-text="'点击'"
+                :input-button="'修改模板'"
+                :styles="{ padding: '0 0px 20px 0px', width: '690px' }"
+              />
+              <div v-else v-html="item.content" />
+            </div>
+          </div>
+        </div>
+        <div v-if="edit" class="control">
+          <el-button size="small" @click="cancel()">取消</el-button>
+          <el-button type="primary" size="small" @click="saveHandle()">
+            保存
+          </el-button>
+        </div>
+      </div>
+    </section>
+    <div v-if="!edit">
+      <section class="main-section">
+        <div>
+          <headTitle title="动态" />
+        </div>
+        <actionDynamic :comments="commentlist" @addComment="createCommentHandle" />
+      </section>
+      <div v-if="!showEmpty" class="step">
+        <step :data="data.templates" :type-list="data.templates" @goto="scrollToHandle" />
       </div>
-    </header>
-    <div class="wrap">
-      <redTipword title="关联任务" />
     </div>
-    <!-- <div>
-      <text-area :id="'pro-desc'" :value.sync="form_query.description" :empty-text="'点击'" :input-button="'添加描述'" @change="changeArea" />
-    </div> -->
   </div>
 </template>
 <script>
+const _ = require('lodash')
 import redTipword from '@/components/redTipTitle'
-import '@/styles/PublicStyle/index.scss'
+import headTitle from '@/components/headTitle'
+import multipleSelect from './components/multipleSelect'
+import checkboxList from './components/checkboxList'
+import onlineCheckList from './components/onlineCheckList'
+import actionDynamic from '@/components/actionDynamic'
+import step from './components/step'
+// 富文本
+// import textArea from '@/components/input/textArea'
+import normalArea from '@/components/input/normalArea' // 富文本
+import 'tinymce/plugins/table'// 插入表格插件
+import store from '@/store'
+import {
+  getCheckListBytask,
+  getBizBindTemList,
+  updateChecklist,
+  createChecklist,
+  updateTemplateCheckStatus,
+  getCommentList,
+  createComment
+} from '@/api/publishTask'
 export default {
   components: {
-    redTipword
+    redTipword,
+    multipleSelect,
+    checkboxList,
+    onlineCheckList,
+    normalArea,
+    headTitle,
+    actionDynamic,
+    step
+  },
+  props: {
+    taskId: {
+      type: Number,
+      required: true,
+      default: -1
+    },
+    taskName: {
+      type: String,
+      required: true,
+      default: ''
+    },
+    userNames: {
+      type: String,
+      required: true,
+      default: ''
+    },
+    userInformation: {
+      type: String,
+      required: true,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      description: '<p style="color:red;">123</p>',
+      edit: false, // 是否是编辑状态
+      showEmpty: true,
+      openEdit: true,
+      data: {},
+      temList: [],
+      commentlist: [] // 评价列表
+    }
+  },
+  mounted() {
+    // 获取模板列表
+    this.getBizBindTemList()
+    // 获取checklist详情
+    this.getList()
+    // 获取评论列表
+    this.getCommentList()
+  },
+  methods: {
+    // 获取checklist详情
+    async getList() {
+      console.log(this.taskId)
+      if (this.taskId) {
+        const res = await getCheckListBytask({ taskId: this.taskId })
+        // res.data = null
+        if (res.data) {
+          // 如果绑定过
+          this.showEmpty = false
+          this.data = res.data
+        } else {
+          this.showEmpty = true
+        }
+      }
+    },
+
+    // 获取业务线下绑定的可以选checklist列表
+    async getBizBindTemList() {
+      const { bizId = null } = store.state.global || {}
+      const res = await getBizBindTemList({ name: '', bizId, belongType: 2 })
+      this.temList = res.data
+    },
+
+    async getCommentList() {
+      const res = await getCommentList({ type: 5, joinId: this.taskId })
+      this.commentList = res.data
+      if (res.code === 200) {
+        this.comments = res.data
+      }
+    },
+
+    // 添加评论
+    async createCommentHandle(content, callback) {
+      const commentInfo = {
+        joinId: this.taskId,
+        content,
+        type: 5,
+        fatherId: 0,
+        name: this.userNames,
+        email: this.userInformation
+      }
+      const user = { name: this.userNames, ename: this.userInformation, id: '' }
+      const res = await createComment({ commentInfo, user })
+      if (res.code === 200) {
+        this.$message({ message: '评论成功', type: 'success', duration: 1000, offset: 150 })
+        this.getCommentList()
+        callback()
+      } else {
+        this.$message.warning(res.msg)
+      }
+    },
+
+    // 添加或者删除线上模板的模版名
+    changeOnlineSelectedHandle(id, name, value, type) {
+      /**
+       * id: 模块id
+       * name: 线上模块中哪个模块下的模块名称
+       * value: 模块名
+       * type: 是添加还是删除
+      **/
+      // const tem = this.data.templates
+      this.data.templates.map(t => {
+        if (t.id === id) {
+          if (type === 'del') {
+            t.onlineModule.onlineOrder = t.onlineModule.onlineOrder.filter(g => g !== value)
+            t.onlineModule.tableContent.map(t => {
+              if (t.module === name) {
+                t.moduleNames = t.moduleNames.filter(g => g !== value)
+              }
+            })
+          } else if (!t.onlineModule.onlineOrder.includes(value)) {
+            t.onlineModule.onlineOrder.push(value)
+            t.onlineModule.tableContent.map(t => {
+              if (t.module === name) {
+                t.moduleNames.push(value)
+              }
+            })
+          } else {
+            console.log('已经添加过该模块名称')
+          }
+        }
+      })
+    },
+
+    changeOnlineModuleRow(type, mId, index) {
+      console.log(type, mId, index, this.data.templates)
+      this.data.templates.map(t => {
+        if (t.id === mId) {
+          if (type === 'del') {
+            t.onlineModule.tableContent.splice(index, 1)
+          } else {
+            t.onlineModule.tableContent.splice(index + 1, 0, {})
+          }
+        }
+      })
+    },
+
+    // 保存
+    async saveHandle() {
+      if (this.data.tasks.length < 1) {
+        this.$message({
+          message: '请关联一个任务',
+          type: 'error'
+        })
+        return
+      }
+      let res = null
+      if (this.data.id) {
+        res = await updateChecklist(this.data)
+      } else {
+        res = await createChecklist(this.data)
+      }
+      if (res.code === 200) {
+        this.edit = false
+        this.openEdit = true
+        this.$message({
+          message: '保存成功',
+          type: 'success'
+        })
+      }
+    },
+
+    // 取消
+    cancel() {
+      this.edit = false
+      this.openEdit = true
+      this.data = this.copyData
+      if (!this.data || JSON.stringify(this.data) === '{}') {
+        this.showEmpty = true
+      }
+    },
+    // 点击添加
+    addAction() {
+      // 复制一份数据,以便取消时复原
+      this.copyData = this.data
+      // 编辑按钮隐藏
+      this.openEdit = false
+      // 编辑状态打开
+      this.edit = true
+      // 是否显示空状态
+      this.showEmpty = false
+      this.data = {
+        selectedBizTemplateIds: [],
+        templates: [],
+        tasks: [{
+          id: this.taskId,
+          name: this.taskName
+        }]
+      }
+      this.checkAllTem()
+    },
+
+    // 编辑
+    editHandle() {
+      // 复制一份数据,以便取消时复原
+      this.copyData = this.data
+      // 编辑状态打开
+      this.edit = true
+      // 编辑按钮隐藏
+      this.openEdit = false
+      this.checkAllTem()
+    },
+
+    // 添加和编辑checklist时 如果没有选择模版默认全选。
+    checkAllTem() {
+      const { selectedBizTemplateIds } = this.data
+      if (!selectedBizTemplateIds || selectedBizTemplateIds.length < 1) {
+        this.temList.map(t => {
+          this.data.selectedBizTemplateIds.push(t.id)
+          this.data.templates.push(t)
+        })
+      }
+    },
+
+    // 修改checklist绑定模版列表
+    updateSelectedTemHandle(checkedIds) {
+      console.log(checkedIds)
+      const selectedBizTemplateIds = []
+      const templates = []
+      checkedIds.map(checkedId => {
+        this.temList.map(t => {
+          if (t.id === checkedId) {
+            selectedBizTemplateIds.push(t.id)
+            templates.push(t)
+          }
+        })
+      })
+      this.data = { ...this.data, templates, selectedBizTemplateIds }
+    },
+
+    // 锚点
+    scrollToHandle(targe) {
+      const anchorH = document.getElementById(targe).offsetTop
+      const container = document.getElementsByClassName('main-wrapper')[0]
+      container.scrollTop = anchorH - 20
+    },
+
+    // 解绑删除任务
+    changeTask(task, type) {
+      let hasTask = false
+      let tasks = []
+      this.data.tasks.map(g => {
+        if (g.id === task.id) {
+          hasTask = true
+        }
+      })
+      if (type === 'del') {
+        tasks = this.data.tasks.filter(t => t.id !== task.id)
+      } else if (!hasTask) {
+        tasks = [...this.data.tasks, task]
+      } else {
+        console.log('已经添加过该任务')
+        return
+      }
+      this.data = { ...this.data, tasks }
+    },
+
+    // 更新检查项到数据库
+    async updateCheckItemHandle(item) {
+      console.log(item)
+      const { id: templateId, isCheck } = item
+      const res = await updateTemplateCheckStatus({ templateId, isCheck })
+      if (res.code === 200) {
+        this.$message({
+          message: '检查项状态更新成功',
+          type: 'success'
+        })
+      }
+    },
+
+    // 修改线上问题模块
+    onChangeModuleName: _.debounce(function(val, subIdx, index) {
+      console.log(val, subIdx, index, this.data)
+      this.data.templates[index].onlineModule.tableContent[subIdx].module = val
+      console.log(this.data)
+    })
   }
 }
 </script>
 <style scoped lang="scss">
 @import '@/styles/detail-pages.scss';
-.publishTask {
-  @include main-section;
+.editPublishTask {
+  min-height: 400px;
+  overflow-y: auto;
+  padding-bottom: 20px;
+  .step {
+    position: fixed;
+    top: 200px;
+    right:100px;
+  }
+  .pubconfig {
+    .control {
+      padding: 20px 0px 20px 690px;
+    }
+  }
+  .main-section {
+    @include main-section;
+  }
   .wrap{
     padding: 0 40px;
   }
+  .moduleList {
+    margin-top: 40px;
+    padding-bottom: 16px;
+    .title {
+      font-weight: 400;
+      color: #444444;
+      font-size: 14px;
+      margin-bottom: 16px;
+      margin-top: 40px;
+      &:first-child {
+        margin-top: 0px;
+      }
+    }
+  }
+}
+.empty {
+  padding: 0px 30px 40px 30px;
+  color: #444;
+  font-size: 14px;
+  .createBtn {
+    color: #409EFF;
+    margin-left: 5px;
+    cursor: pointer;
+  }
+}
+.main-title {
+  @include main-title;
 }
 </style>

+ 6 - 1
src/views/projectManage/taskList/taskViewDetail.vue

@@ -325,7 +325,12 @@
       <!-- 发布 -->
       <el-container v-if="activeName === '6'" class="is-vertical">
         <!-- <section class="main-section contain"> -->
-        <publishTask />
+        <publishTask
+          :task-id="form_query.id"
+          :task-name="form_query.name"
+          :user-names="userNames"
+          :user-information="userInformation"
+        />
         <!-- </section> -->
       </el-container>
       <!-- 发布 -->