一、新建文件src/pages/postman/Case.vue文件

<template>
  <div>
    <a-card class="container">
      <div class="tree-container">

        <div class="left">
          <div class="left-input">
            <a-input placeholder="请输入" style="width: 100px;margin-right: 2px"/>
            <a-button @click="newlyAdded" class="add">新增</a-button>
          </div>
          <!-- 树形组织架构树 -->
          <a-tree
              class="draggable-tree"
              draggable
              block-node
              :defaultExpandAll="false"
              :tree-data="treeData"
              @dragenter="onDragEnter"
              @drop="onDrop"
          >
            <template slot="custom" slot-scope="item">
              <div>
                <!-- 名称 -->
                <span class="node-title">{{ item.title }} </span>
                <span style="margin-left: 20px">
                  <!-- 新增 -->
                  <span style="margin-right: 10px;">
                    <a-icon type="plus-circle" style="color: #0080ff;margin:0 4px" @click="subordinateItem(item)"/>
                  </span>
                  <!-- 编辑 -->
                  <span style="margin-right: 10px;">
                    <a-icon type="edit" style="color: #0080ff;margin:0 4px"  @click="modifyItem(item)"/>
                  </span>
                  <!-- 删除 -->
                  <span style="margin-right: 10px;">
                    <a-popconfirm title="是否要删除此行?" @confirm="deleteItem(item)">
                      <a-icon type="delete" style="color: #0080ff;margin:0 4px" />
                    </a-popconfirm>
                  </span>
                </span>
              </div>
            </template>
          </a-tree>
        </div>
        <div class="right">
          <a-form
              :model="formState"
              name="horizontal_login"
              layout="inline"
              autocomplete="off"
              @finish="onFinish"
              @finishFailed="onFinishFailed"
          >
            <a-form-item
                label="用例名称"
                name="case_name"
                :rules="[{ required: true, message: 'Please input your case_name!' }]"
            >
              <a-input v-model="formState.casename" />
            </a-form-item>

            <a-form-item
                label="创建人"
                name="creator"
                :rules="[{ required: true, message: 'Please input your creator!' }]"
            >
              <a-select v-model="UserType" style="width:150px" allowClear>
                <a-select-option :value="item.id" v-for="(item, i) in UserArr" :key="i">
                  {{ item.name }}
                </a-select-option>
              </a-select>
            </a-form-item>

            <a-form-item>
              <a-button  type="primary" html-type="submit" @click="onSelectData" >查询</a-button>
              <a-button :style="{ marginLeft: '8px' }" @click="reset">重置</a-button>
            </a-form-item>
          </a-form>

          <div>
            <a-button type="primary" @click="showModal">+用例</a-button>
            <a-button type="primary" :disabled="!hasSelected" :loading="loading" @click="showModal">+Task</a-button>
            <a-button type="primary" :disabled="!hasSelected" :loading="loading" @click="start">批量删除</a-button>
            <a-button type="primary" :disabled="!hasSelected" :loading="loading" @click="start">批量移动用例</a-button>
            <span >
            <template v-if="hasSelected">
              {{ `Selected ${selectedRowKeys.length} items` }}
            </template>
          </span>
            <a-upload
                v-model="fileList"
                name="file"
                action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
                @change="handleChange"
            >
              <a-button type="primary" >Xmind导入用例</a-button>
            </a-upload>

            <a-modal v-model="visible" width="500px" title="添加用例" @ok="handleOk">
              <a-form-item label="目录名称" v-bind="project_name">
                <a-input v-model="project_name" />
              </a-form-item>
            </a-modal>
          </div>
          <!-- 表格开始 -->
          <a-table
              :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
              :columns="columns"
              :data-source="data"
              :rowKey='record=>record.id'
              @change="onChange" >
            <template slot="operation" slot-scope="record">
              <a-tooltip  title="详情" >
                <a-icon type="eye" style="color: #0080ff;margin:0 4px"  @click="onDetail(record)" />
              </a-tooltip>
              <a-tooltip  title="编辑" >
                <a-icon style="color: #0080ff;margin:0 4px" type="form" @click="onEdit(record)" />
              </a-tooltip>
              <a-tooltip  title="删除" >
                <a-popconfirm
                    title="Are you sure delete this case?"
                    ok-text="Yes"
                    cancel-text="No"
                    @confirm="confirm"
                    @cancel="cancel"
                >
                  <a-icon style="color: #0080ff;margin:0 4px" type="delete"  @click="onDelete(record)"/>
                </a-popconfirm>
              </a-tooltip>
            </template>
          </a-table>
          <!-- 表格结束 -->
        </div>

      </div>
    </a-card>
    <!-- 弹窗开始 -->
    <tree-lower-modules ref="treeLowerModules" @ok="onOk"/>
  </div>
</template>

<script>
import { get_case_list } from "@/services/build_history";
import {message} from "ant-design-vue";
import {getDirList, getUserList} from "@/services/postman";
import treeLowerModules from '../../components/treeModules'

const columns = [
  {
    title : '用例名称',
    dataIndex: 'case_name',
    resizable: true,        //设置 resizable 开启拖动列
    width: 20,
  },
  {
    title : '优先级',
    dataIndex: 'priority',
    resizable: true,
    width: 20,
  },
  {
    title: '用例状态',
    dataIndex: 'status',
    resizable: true,
    width: 20,
  },
  {
    title: '创建人',
    dataIndex: 'creator',
    resizable: true,
    width: 20,
  },
  {
    title: '更新时间',
    dataIndex: 'update_time',
    resizable: true,
    width: 50,
    //通过指定列的 sorter 函数即可启动排序按钮。sorter: function(rowA, rowB) { ... }, rowA、rowB 为比较的两个行数据
    sorter: (a, b) => Date.parse(a.update_time) - Date.parse(b.update_time),
    defaultSortOrder: 'descend',
    sortDirections: ['ascend', 'descend'], //sortDirections: ['ascend' | 'descend']改变每列可用的排序方式
  },
  {
    title: '操作',
    dataIndex: 'operation',
    resizable: true,
    scopedSlots: { customRender: 'operation' }, //值跟dataIndex对应,支持操作列插槽
    width: 50,
  },
];

export default {
  name: 'OrganizateTree',
  components: { treeLowerModules },
  data () {
    return {
      case_list:"http://localhost:7777/auth/case_list",
      // 组织树数据
      treeData: [],
      selectKeys: [],
      // 表格数据
      data:[],
      // 表格行
      columns: columns,
      //form
      formState: {
        creator: '',
        case_name: '',
      },
      UserArr:[],
      UserType:'',
      fileList:[],
      form: this.$form.createForm(this, {name: 'case'}),
      //table
      hasSelected:false,
      loading: false,
      visible: false,
      selectedRowKeys:[],
      project_name:"",
    }
  },
  methods: {
    onSelectData() {
      this.loading = true;
      const case_name = this.formState.case_name
      const creator = this.UserType
      console.log(case_name,creator);
      get_case_list(this.case_list, {case_name, creator})
          .then((result) => {
            this.loading = false;
            this.data = result.data.data;   //跟后端接口response对齐
          })
          .catch((err) => {
            this.data = err;
          });
    },
    reset() {
      this.UserType = ''    //清空casename
      this.formState = []   //清空创建人
      this.data = []        //清空搜索结果
      this.hasSelected = false //清空选择条数
    },
    showModal(){            //点击添加用例后展示弹窗
      this.visible = true;
    },
    handleOk(e){
      console.log(e);
      this.visible = false;
    },
    onSelectChange(selectedRowKeys){
      this.selectedRowKeys = selectedRowKeys;
      if (this.selectedRowKeys.length > 0) {
        this.hasSelected = true
      }
    },

    onChange(pagination, filters, sorter) {
      console.log('params', pagination, filters, sorter);
    },
    start(){
      this.loading = true;
      // ajax request after empty completing
      setTimeout(() => {
        this.loading = false;
        this.selectedRowKeys = [];
      }, 1000);
    },
    handleChange(info) {
      if (info.file.status !== 'uploading') {
        console.log(info.file, info.fileList);
      }
      if (info.file.status === 'done') {
        message.success(`${info.file.name} file uploaded successfully`);
      } else if (info.file.status === 'error') {
        message.error(`${info.file.name} file upload failed.`);
      }
    },
    onFinish(val) {
      console.log('endValue', val);
    },
    onFinishFailed(val) {
      console.log('endValue', val);
    },
    confirm() {},
    cancel() {},

    // 递归每一项都加 scopedSlots: { title: 'custom' }
    handleData (tree) {
      for (const item of tree) {
        item['scopedSlots'] = { title: 'custom' }
        if (item.children && item.children.length) {
          this.handleData(item.children)
        }
      }
    },

    /**
     * 组织树新增按钮
     */
    newlyAdded () {
      const item = { key: this.treeData[0].key, operation: 1 }
      this.$refs.treeLowerModules.add(item)
    },

    /**
     * 添加下級
     * @param item
     */
    subordinateItem (item) {
      item.operation = 2
      this.$refs.treeLowerModules.add(item)
    },

    /**
     * 修改
     * @param item
     */
    modifyItem (item) {
      this.$refs.treeLowerModules.edit(item)
    },

    /**
     * 確定按鈕
     * @param val
     */
    onOk (val) {
      // 1:一级新增, 3:编辑,  2:二级新增
      if (val.operation === 1) {
        this.selectKeys = [val.key]
        this.dataDriveAddSame(val.title)
      } else if (val.operation === 2) {
        this.selectKeys = [val.key]
        this.dataDriveAddSub(val.title)
      } else if (val.operation === 3) {
        this.selectKeys = [val.key]
        this.dataDriveModify(val.title)
      }
    },

    /**
     * 公共父级修改方法
     * @param childs: 组织树数据
     * @param findKey 目标key
     */
    getTreeDataByKey (childs = [], findKey) {
      let finditem = null
      for (let i = 0, len = childs.length; i < len; i++) {
        const item = childs[i]
        if (item.key !== findKey && item.children && item.children.length > 0) {
          finditem = this.getTreeDataByKey(item.children, findKey)
        }
        if (item.key === findKey) {
          finditem = item
        }
        if (finditem != null) {
          break
        }
      }
      return finditem
    },

    /**
     * 公共父级方法
     * @param childs: 组织树数据
     * @param findKey 目标key
     */
    getTreeParentChilds (childs = [], findKey) {
      let parentChilds = []
      for (let i = 0, len = childs.length; i < len; i++) {
        const item = childs[i]
        if (item.key !== findKey && item.children && item.children.length > 0) {
          parentChilds = this.getTreeParentChilds(item.children, findKey)
        }
        if (item.key === findKey) {
          parentChilds = childs
        }
        if (parentChilds.length > 0) {
          break
        }
      }
      return parentChilds
    },

    /**
     * 添加同级
     * @param title
     */
    dataDriveAddSame (title) {
      const parentChilds = this.getTreeParentChilds(
          this.treeData,
          this.selectKeys[0]
      )
      // 校验 相同的不能不可以添加
      const existence = parentChilds.find(item => { return item.key === title })
      if (!existence) {
        parentChilds.push({
          title: title,
          key: new Date().getTime(),
          scopedSlots: { title: 'custom' }
        })
      } else {
        this.$message.success('此数据已存在')
        return false
      }
    },

    /**
     * 添加下级
     * @param title
     */
    dataDriveAddSub (title) {
      const selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
      if (!selectItem.children) {
        this.$set(selectItem, 'children', [])
      }
      // 校验 相同的不能不可以添加
      const existence = selectItem.children.find(item => { return item.title === title })
      if (!existence) {
        selectItem.children.push({
          title: title,
          key: new Date().getTime(),
          scopedSlots: { title: 'custom' }
        })
      } else {
        this.$message.success('此数据已存在')
        return false
      }
      this.$forceUpdate()
    },

    /**
     * 一级修改
     * @param title
     */
    dataDriveModify (title) {
      const selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
      selectItem.title = title
    },


    /**
     * 刪除
     * @param item
     */
    deleteItem (item) {
      this.selectKeys = [item.key]
      this.dataDriveDelete()
    },

    /**
     * 删除方法
     */
    dataDriveDelete () {
      const parentChilds = this.getTreeParentChilds(
          this.treeData,
          this.selectKeys[0]
      )
      // 对删除的数据若下面有子级就给与提示不允许删除
      parentChilds.map(item => {
        console.log(item.children, '000')
        return item.children
      })
      // console.log(noeDel, 'shanchu')
      const delIndex = parentChilds.findIndex(
          (item) => item.key === this.selectKeys[0]
      )
      parentChilds.splice(delIndex, 1)
    },

    /**
     * 拖拽
     * @param info
     */
    onDragEnter (info) {
      console.log(info, '12222')
      // expandedKeys 需要受控时设置
      // this.expandedKeys = info.expandedKeys
    },

    /**
     * 拖拽
     * @param info
     */
    onDrop (info) {
      console.log(info)
      const dropKey = info.node.eventKey
      const dragKey = info.dragNode.eventKey
      const dropPos = info.node.pos.split('-')
      const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
      const loop = (data, key, callback) => {
        data.forEach((item, index, arr) => {
          if (item.key === key) {
            return callback(item, index, arr)
          }
          if (item.children) {
            return loop(item.children, key, callback)
          }
        })
      }
      const data = [...this.treeData]

      // Find dragObject
      let dragObj
      loop(data, dragKey, (item, index, arr) => {
        arr.splice(index, 1)
        dragObj = item
      })
      if (!info.dropToGap) {
        // Drop on the content
        loop(data, dropKey, item => {
          item.children = item.children || []
          // where to insert 示例添加到尾部,可以是随意位置
          item.children.push(dragObj)
        })
      } else if (
          (info.node.children || []).length > 0 && // Has children
          info.node.expanded && // Is expanded
          dropPosition === 1 // On the bottom gap
      ) {
        loop(data, dropKey, item => {
          item.children = item.children || []
          // where to insert 示例添加到尾部,可以是随意位置
          item.children.unshift(dragObj)
        })
      } else {
        let ar
        let i
        loop(data, dropKey, (item, index, arr) => {
          ar = arr
          i = index
        })
        if (dropPosition === -1) {
          ar.splice(i, 0, dragObj)
        } else {
          ar.splice(i + 1, 0, dragObj)
        }
      }
      this.treeData = data
    },
  },
  mounted() {
    const dir_id = 1 //TODO 应该传入左侧的dir_id
    getUserList().then(res => {
      this.UserArr = res.data.data         //跟后端接口response对齐
      console.log('搜索条件',this.UserArr);
    }),
        getDirList({dir_id}).then(res => {
          this.loading = false;
          this.treeData = res.data.data        //跟后端接口response对齐
          if(this.treeData !==undefined && this.treeData != null && this.treeData.length > 0) {
            console.log('getDirList: ', this.treeData);
            // 每一项都加 scopedSlots: { title: 'custom' }
            this.handleData(this.treeData)
          }
        })
  },
  watch: {
    // 监听 searchValue 属性的数据变化,只要 searchValue 的值发生变化,这个方法就会被调用
    searchValue(value) {
      console.log('watch :',value);
    }
  },
}
</script>

<style scoped lang="less">

.node-title {
  padding-right: 15px;
}

.add {
  background: #1890ff;
  color: #FFFFFF;
}

.tree-container {
  display: flex;
  flex-direction: row;
  .left-input{
    display: flex;
    flex-direction: row;
    margin-bottom: 2px;
  }
  .right{
    margin-left: 0px;
    width: 100%;
    &-btn{
      float: right;
      margin-bottom: 10px;
    }
  }
}
</style>

二、新建文件src/components/treeModules.vue文件

<template>
  <a-modal :title="title" v-model="visible" :confirmLoading="confirmLoading" @ok="handleSubmit">
    <a-form :form="form">
      <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="名称">
        <a-input :disabled="disabled" placeholder="请输入名称" v-decorator="['title', {rules: [{ required: true, message: '请输入名称' }]}]" />
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script>
import pick from 'lodash/pick'

export default {
  address: 'TreeModules',
  props: {},
  components: {},
  data () {
    return {
      visible: false,
      confirmLoading: false,
      form: this.$form.createForm(this),
      labelCol: {
        xs: { span: 24 },
        sm: { span: 5 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      },
      title: '',
      mdl: {},
      disabled: false,

      operation: 1,
      record: {}
    }
  },
  beforeCreate () {},
  created () {},
  computed: {},
  methods: {

    /**
     * 新增彈窗
     * @param record
     */
    add (record) {
      this.operation = record.operation
      if (record.operation === 1) {
        this.title = '新增'
      } else {
        this.title = '添加下级'
      }
      this.form.resetFields()
      this.visible = true
      this.disabled = false
      this.record = record
    },

    /**
     * 編輯彈窗
     * @param record
     */
    edit (record) {
      this.record = record
      this.operation = 3
      this.disabled = false
      this.title = '编辑名称'
      this.mdl = Object.assign({}, record)
      this.visible = true
      this.$nextTick(() => {
        this.form.setFieldsValue(pick(this.mdl,
            'title'
        ))
      })
    },

    /**
     * 保存
     * @param e
     */
    handleSubmit (e) {
      e.preventDefault()
      this.form.validateFields((err, values) => {
        if (!err) {
          values.operation = this.operation
          values.key = this.record.key
          this.confirmLoading = true
          this.$emit('ok', values)
          this.form.resetFields()
          this.$message.success('保存成功')
          this.visible = false
          this.confirmLoading = false
        }
      })
    }
  }
}
</script>

<style scoped lang="less">
</style>

三、在src/pages/postman/index.js添加如下内容

export async function get_case_list(url, params) {
    return request(url, METHOD.GET, params)
}

export async function getDirList(params) {
    return request(CASEDIR, METHOD.GET,params)
}

四、效果

在这里插入图片描述

五、待解决问题

1、左右两卡片联动(其实传dir_id给case接口即可)
2、可加tree的查询操作

Logo

前往低代码交流专区

更多推荐