Bladeren bron

drawer初步

chenyitao.0928 5 jaren geleden
bovenliggende
commit
3687322243

+ 1 - 0
src/assets/icon/edit.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="1616380247069" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1139" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M864 479.91a32 32 0 0 0-32 32V800a32 32 0 0 1-32 32H224a32 32 0 0 1-32-32V224a32 32 0 0 1 32-32h286.4a32 32 0 0 0 0-64H224a96.11 96.11 0 0 0-96 96v576a96.1 96.1 0 0 0 96 96h576a96.11 96.11 0 0 0 96-96V511.91a32 32 0 0 0-32-32z" p-id="1140" fill="#8a8a8a"></path><path d="M356.43 660.33a6.41 6.41 0 0 0 6.32 7.31 7.26 7.26 0 0 0 0.92-0.07l150-21.43 362-362a64 64 0 0 0 0-90.51l-45.27-45.29a64 64 0 0 0-90.51 0l-362 362z m81.76-119.78l346.89-346.94h0.06l45.26 45.26-346.95 346.94-52.8 7.54z" p-id="1141" fill="#8a8a8a"></path></svg>

+ 1 - 0
src/assets/icon/edit_hover.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="1616380247069" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1139" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M864 479.91a32 32 0 0 0-32 32V800a32 32 0 0 1-32 32H224a32 32 0 0 1-32-32V224a32 32 0 0 1 32-32h286.4a32 32 0 0 0 0-64H224a96.11 96.11 0 0 0-96 96v576a96.1 96.1 0 0 0 96 96h576a96.11 96.11 0 0 0 96-96V511.91a32 32 0 0 0-32-32z" p-id="1140" fill="#707070"></path><path d="M356.43 660.33a6.41 6.41 0 0 0 6.32 7.31 7.26 7.26 0 0 0 0.92-0.07l150-21.43 362-362a64 64 0 0 0 0-90.51l-45.27-45.29a64 64 0 0 0-90.51 0l-362 362z m81.76-119.78l346.89-346.94h0.06l45.26 45.26-346.95 346.94-52.8 7.54z" p-id="1141" fill="#707070"></path></svg>

+ 18 - 0
src/components/AddDrawer.vue

@@ -0,0 +1,18 @@
+<template>
+  <ElDrawer :value="showDrawer" size="600px">
+    <!-- TODO: 供展示和编辑节点详情,可在taskList中复用  -->
+  </ElDrawer>
+</template>
+
+<script>
+export default {
+  name: 'AddDrawer',
+  props: {
+    showDrawer: Boolean,
+  },
+};
+</script>
+
+<style scoped>
+
+</style>

+ 350 - 0
src/components/TaskDrawerContent.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="drawer-content">
+   <el-row class="entry-row">
+     <h3>{{title}}</h3>
+     <div class="edit-icon" @click="editEntry('title')"></div>
+   </el-row>
+    <el-row class="entry-row" v-if="isEditing.title">
+      <el-input placeholder="请输入标题" size="mini" style="width: 300px;display: inline;" v-model="editedRequirement.title"/>
+      <el-button
+        style="margin-left: 5px"
+        type="primary"
+        size="mini"
+        icon="el-icon-edit"
+        circle
+        @click="submitEdit('title')" />
+      <el-button
+        style="margin-left: 5px"
+        type="danger"
+        size="mini"
+        icon="el-icon-close"
+        circle
+        @click="cancelEdit('title')" />
+    </el-row>
+    <!-- 描述 -->
+    <el-row class="entry-row" style="margin-top: 15px">
+      <h4>需求描述</h4>
+      <div class="edit-icon" @click="editEntry('description')"></div>
+    </el-row>
+    <el-row class="entry-row">
+      <div v-if="isEditing.description">
+        <el-input placeholder="请输入描述"
+                  type="textarea"
+                  maxlength="120"
+                  show-word-limit
+                  :rows="3" style="width: 300px;" v-model="editedRequirement.description" />
+        <br/>
+        <el-button
+          style="margin-left: 5px; margin-top: 5px"
+          type="primary"
+          size="mini"
+          icon="el-icon-edit"
+          circle
+          @click="submitEdit('description')" />
+        <el-button
+          style="margin-left: 5px; margin-top: 5px"
+          type="danger"
+          size="mini"
+          icon="el-icon-close"
+          circle
+          @click="cancelEdit('description')" />
+      </div>
+      <p v-else>{{description}}</p>
+    </el-row>
+    <!-- 状态、优先级等 -->
+    <el-row class="entry-row" style="margin-top: 15px">
+      <h4>详情</h4>
+    </el-row>
+    <el-row style="margin: 3px 20px;width: 100%">
+      <el-col :span="12">
+        <div class="detail-row">
+          <span>状态:</span>
+          <div  v-if="!isEditing.state"   @click="editEntry('state')" class="detail-value">
+            <span class="state-entry">{{state}}</span>
+            <div class="edit-icon" />
+          </div>
+          <div v-else>
+          <el-select size="mini" v-model="editedRequirement.state">
+            <el-option
+              v-for="item in stateOptions"
+              :key="item.value"
+              :value="item.value"
+              :label="item.value" />
+          </el-select>
+            <el-button
+              style="margin-left: 5px"
+              type="primary"
+              size="mini"
+              icon="el-icon-edit"
+              circle
+              @click="submitEdit('state')" />
+            <el-button
+              style="margin-left: 5px"
+              type="danger"
+              size="mini"
+              icon="el-icon-close"
+              circle
+              @click="cancelEdit('state')" />
+          </div>
+        </div>
+        <div class="detail-row">
+          <span>优先级:</span>
+          <div class="detail-value"  v-if="!isEditing.priority"   @click="editEntry('priority')">
+          <span class="state-entry">{{getPriorityStr(priority)}}</span>
+            <div class="edit-icon" />
+          </div>
+          <div v-else>
+          <el-select size="mini" v-model="editedRequirement.priority">
+            <el-option
+              v-for="item in  priorityOptions"
+              :key="item.value"
+              :value="item.value"
+              :label="item.label" />
+          </el-select>
+            <el-button
+              style="margin-left: 5px"
+              type="primary"
+              size="mini"
+              icon="el-icon-edit"
+              circle
+              @click="submitEdit('priority')" />
+            <el-button
+              style="margin-left: 5px"
+              type="danger"
+              size="mini"
+              icon="el-icon-close"
+              circle
+              @click="cancelEdit('priority')" />
+          </div>
+        </div>
+        <div class="detail-row">
+          <span>经办人:</span>
+          <div class="detail-value"   v-if="!isEditing.userId"  @click="editEntry('userId')">
+          <span class="state-entry">{{getUserName(userId)}}</span>
+            <div class="edit-icon" />
+          </div>
+          <div v-else>
+          <el-select size="mini" v-model="editedRequirement.userId" >
+            <el-option
+              v-for="item in  userOptions"
+              :key="item.value"
+              :value="item.value"
+              :label="item.label" />
+          </el-select>
+            <el-button
+              style="margin-left: 5px"
+              type="primary"
+              size="mini"
+              icon="el-icon-edit"
+              circle
+              @click="submitEdit('userId')" />
+            <el-button
+              style="margin-left: 5px"
+              type="danger"
+              size="mini"
+              icon="el-icon-close"
+              circle
+              @click="cancelEdit('userId')" />
+          </div>
+        </div>
+      </el-col>
+      <el-col :span="12">
+        <div class="detail-row">
+          <span>工作量:</span>
+          <div class="detail-value" v-if="!isEditing.timeToTake" @click="editEntry('timeToTake')">
+          <span>{{timeToTake}}h</span>
+            <div class="edit-icon" />
+          </div>
+          <div v-else >
+           <el-input-number size="mini" v-model="editedRequirement.timeToTake" :min="1" :max="200"/>
+            <el-button
+              style="margin-left: 5px"
+              type="primary"
+              size="mini"
+              icon="el-icon-edit"
+              circle
+              @click="submitEdit('timeToTake')" />
+            <el-button
+              style="margin-left: 5px"
+              type="danger"
+              size="mini"
+              icon="el-icon-close"
+              circle
+              @click="cancelEdit('timeToTake')" />
+          </div>
+        </div>
+        <div class="detail-row">
+          <span>创建时间:</span>
+          <span>{{startTime}}</span>
+        </div>
+        <div class="detail-row"><span>完成时间:</span>
+          <span>{{endTime}}</span></div>
+      </el-col>
+    </el-row>
+    <el-row class="entry-row" style="margin-top: 15px">
+      <h4>相关需求</h4>
+    </el-row>
+    <el-row style="margin: 3px 25px;width: 100%">
+      <h5>父需求</h5>
+    </el-row>
+    <el-row style="margin: 0px 25px;">
+      <el-link type="primary">{{parent}}</el-link>
+    </el-row>
+    <el-row style="margin: 3px 25px;width: 100%">
+      <h5>子需求</h5>
+    </el-row>
+    <el-row style="margin: 0px 25px;" v-for="item in childs" :key="item.id">
+        <el-link @click.prevent="changeContent(item.id)" type="primary">{{item.title}}</el-link>
+    </el-row>
+    <el-row class="entry-row" style="margin-top: 15px">
+      <h4>相关缺陷</h4>
+    </el-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TaskDrawerContent',
+  props: {
+    title: String,
+    description: String,
+    state: String,
+    priority: Number,
+    userId: Number,
+    timeToTake: Number,
+    startTime: String,
+    endTime: String,
+    childs: Array,
+    parent: Number,
+    stateOptions: Array,
+    userOptions: Array,
+    priorityOptions: Array,
+  },
+  data() {
+    return {
+      isEditing: {
+        title: false,
+        description: false,
+        state: false,
+        timeToTake: false,
+      },
+      editedRequirement: {},
+    };
+  },
+  methods: {
+    getPriorityStr(value) {
+      let res;
+      this.priorityOptions.some((item) => {
+        if (item.value === value) {
+          res = item.label;
+          return true;
+        }
+        return false;
+      });
+      return res;
+    },
+    getUserName(value) {
+      let res;
+      this.userOptions.some((item) => {
+        if (item.value === value) {
+          res = item.label;
+          return true;
+        }
+        return false;
+      });
+      return res;
+    },
+    editEntry(key) {
+      this.isEditing[key] = true;
+    },
+    submitEdit(key) {
+      console.log('...');
+      this.isEditing[key] = false;
+    },
+    cancelEdit(key) {
+      this.isEditing[key] = false;
+    },
+    changeContent(id) {
+      this.$emit('change', id);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.drawer-content{
+  width: 100%;
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  align-items: flex-start;
+}
+.entry-row{
+  width: 100%;
+  margin: 3px 20px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-start;
+  .edit-icon{
+    margin-left: 5px;
+    display: none;
+    cursor: pointer;
+    width: 20px;
+    height: 20px;
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    transition: all .2s linear;
+    background-image: url("../assets/icon/edit.svg");
+    &:hover{
+      background-image: url("../assets/icon/edit_hover.svg");
+    }
+  }
+  &:hover .edit-icon{
+    display: block;
+  }
+  h3, h4{
+    color: #172b4d
+  }
+}
+.detail-row{
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  line-height: 24px;
+  margin: 3px 0;
+  .edit-icon{
+    margin-left: 5px;
+    display: none;
+    width: 20px;
+    height: 20px;
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    transition: all .2s linear;
+    background-image: url("../assets/icon/edit.svg");
+    cursor: pointer;
+    &:hover{
+      background-image: url("../assets/icon/edit_hover.svg");
+    }
+  }
+  &:hover .edit-icon{
+    display: block;
+  }
+}
+.detail-value{
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+.state-entry{
+  border-radius: 5px;
+  background-color: cornflowerblue;
+  font-size: 12px;
+  color: #FFFFFF;
+  padding: 0px 4px;
+  cursor: pointer
+}
+</style>

+ 11 - 11
src/components/Tree.vue

@@ -59,7 +59,7 @@ export default {
       this.data.forEach((item) => {
         nodeMap.set(item.id, item);
       });
-      // 将array转为树状结构;顺便拆成col
+      // 将array转为树状结构(父组件中已处理无需再处理);顺便拆成col
       let rootNode;
       const colList = [];
       for (let i = 0; i < TREE_MAX_HEIGHT; i += 1) {
@@ -71,15 +71,16 @@ export default {
         } else {
           console.warn('需求树数据有误,树高度错误');
         }
-        if (item.parent) {
-          const tempParent = nodeMap.get(item.parent);
-          if (!tempParent.childs) {
-            tempParent.childs = [];
-          }
-          tempParent.childs.push(item);
-        } else {
-          rootNode = item;
-        }
+        if (!item.parent) rootNode = item;
+        // if (item.parent) {
+        //   const tempParent = nodeMap.get(item.parent);
+        //   if (!tempParent.childs) {
+        //     tempParent.childs = [];
+        //   }
+        //   tempParent.childs.push(item);
+        // } else {
+        //   rootNode = item;
+        // }
       });
       // 计算一个节点包含他的后代所需要的空间
       const calculateNodeContentHeight = (node) => {
@@ -142,7 +143,6 @@ export default {
         this.data.forEach((item) => {
           if (!item.childs) return;
           const parentCard = this.$refs[`treenode-${item.id}`].getTreeNodeSizeAndPosition();
-          console.warn(parentCard);
           const start = {
             x: parentCard.left + parentCard.width,
             y: parentCard.top + parentCard.height / 2,

+ 6 - 0
src/element-plus.ts

@@ -27,6 +27,9 @@ import {
   ElOption,
   ElTabs,
   ElTabPane,
+  ElButtonGroup,
+  ElCol,
+  ElInputNumber,
 } from 'element-plus';
 
 export const Components = [
@@ -58,6 +61,9 @@ export const Components = [
   ElSubmenu,
   ElTabs,
   ElTabPane,
+  ElButtonGroup,
+  ElCol,
+  ElInputNumber,
 ];
 
 export const Plugins = [];

+ 33 - 2
src/views/Project/Schedule.vue

@@ -1,7 +1,15 @@
 <template>
   <Tree :data="requirements" @change="handleTreeChange" @nodeClick="handleNodeClick" ref="tree"></Tree>
-  <ElDrawer v-model="showRequirementDrawer">
+  <ElDrawer v-model="showRequirementDrawer"   size="650px">
     <!-- TODO: 供展示和编辑节点详情,可在taskList中复用  -->
+    <template #default>
+      <TaskDrawerContent
+        @change="handleDrawerContentChange"
+        v-bind="requirementInDrawer"
+        :state-options="stateOptions"
+        :user-options="userOptions"
+        :priority-options="priorityOptions">
+      </TaskDrawerContent></template>
   </ElDrawer>
 </template>
 
@@ -9,10 +17,11 @@
 import Tree from '@/components/Tree.vue';
 
 import { RequirementAPI } from '@/api';
+import TaskDrawerContent from '@/components/TaskDrawerContent.vue';
 
 export default {
   name: 'Schedule',
-  components: { Tree },
+  components: { TaskDrawerContent, Tree },
   data() {
     return {
       requirements: [{
@@ -153,13 +162,31 @@ export default {
       },
       ],
       showRequirementDrawer: false,
+      requirementInDrawer: {},
+      stateOptions: [{ value: '已完成' }, { value: '进行中' }, { value: '已创建' }],
+      priorityOptions: [{ value: 0, label: '高' }, { value: 1, label: '中' }, { value: 2, label: '低' }],
+      userOptions: [{ value: 100, label: 'abc' }, { value: 101, label: 'def' }, { value: 102, label: 'gg' }],
     };
   },
   async mounted() {
     // this.requirements = await RequirementAPI.getTreeNodesByProjectId(this.$store.state.workspace.currentProjectId);
     // this.$refs.tree.regenerateTree();
+    this.addChild2Node();
   },
   methods: {
+    addChild2Node() {
+      const nodeMap = new Map();
+      this.requirements.forEach((item) => nodeMap.set(item.id, item));
+      this.requirements.forEach((item) => {
+        if (item.parent) {
+          const tempParent = nodeMap.get(item.parent);
+          if (!tempParent.childs) {
+            tempParent.childs = [];
+          }
+          tempParent.childs.push(item);
+        }
+      });
+    },
     async handleTreeChange(msg) {
       const { type, id } = msg;
       await RequirementAPI[type](id);
@@ -170,6 +197,10 @@ export default {
       this.showRequirementDrawer = true;
       this.requirementInDrawer = Array.prototype.find.call(this.requirements, (requirement) => requirement.id === id);
     },
+    handleDrawerContentChange(id) {
+      this.requirementInDrawer = Array.prototype.find.call(this.requirements, (requirement) => requirement.id === id);
+      this.showRequirementDrawer = true;
+    },
   },
 };
 </script>