ソースを参照

feat: 新增数据集标签功能

新增数据集标签功能
hong.yang 1 年間 前
コミット
8384004d4b

+ 7 - 0
data-room-ui/packages/DataSetLabelManagement/index.js

@@ -0,0 +1,7 @@
+import LabelConfig from './src/index.vue'
+
+LabelConfig.install = function (Vue) {
+  Vue.component(LabelConfig.name, LabelConfig)
+}
+
+export default LabelConfig

+ 382 - 0
data-room-ui/packages/DataSetLabelManagement/src/LabelConfigAddOrUpdate.vue

@@ -0,0 +1,382 @@
+<template>
+  <el-dialog
+    :append-to-body="true"
+    :before-close="handleClose"
+    :title="dataForm.id!==''?'编辑标签':'新增标签'"
+    :visible.sync="dialogFormVisible"
+    :width="relVisible?'1100px':'450px'"
+  >
+    <el-row>
+
+      <el-col :span="relVisible?8:24">
+        <el-divider content-position="left">属性信息</el-divider>
+
+        <el-form ref="ruleForm" :model="dataForm" :rules="rules" label-position="right" label-width="90px">
+
+          <el-form-item label="标签名称" prop="labelName">
+            <el-input v-model="dataForm.labelName" clearable maxlength="200"/>
+          </el-form-item>
+
+          <el-form-item label="标签类型" prop="labelType">
+            <el-select ref="searchSelect"
+                       v-model="dataForm.labelType"
+                       allow-create
+                       clearable
+                       filterable
+                       placeholder="请选择或输入标签类型"
+                       @blur="selectBlur"
+                       @input.native="filterData">
+              <el-option v-for="(item,K) in labelTypeList" :key="K" :label="item"
+                         :value="item"/>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="标签说明" prop="labelDesc">
+            <el-input v-model="dataForm.labelDesc" clearable type="text"/>
+          </el-form-item>
+
+        </el-form>
+
+        <el-divider content-position="left">关联数据集信息</el-divider>
+        <el-form>
+          <el-form-item align="center">
+            <el-tag effect="plain">标签</el-tag>
+            <span>—————</span>
+            <el-button round size="mini" type="primary" @click="buildRel">添加关联</el-button>
+          </el-form-item>
+        </el-form>
+      </el-col>
+
+      <el-col v-if="relVisible" :span="8">
+        <div>
+          <el-divider content-position="left">添加关联</el-divider>
+          <div class="tree-box full-box--position" style="padding: 0 8px 24px 0">
+            <Tree
+              ref="tree"
+              :treeData="categoryData"
+              style="height: 300px;overflow: auto"
+              @handleNodeClick="handleNodeClick"
+            >
+            </Tree>
+          </div>
+        </div>
+      </el-col>
+
+      <el-col v-if="relVisible" :span="8">
+        <el-divider content-position="left">数据集列表:</el-divider>
+        <div>
+          <el-table
+            ref="mytable"
+            v-loading="loading"
+            :data="datasetList"
+            height="300"
+            @select="handleSelect"
+            @select-all="handleSelectionAll"
+          >
+            <el-table-column
+              type="selection"
+              width="55">
+            </el-table-column>
+
+            <el-table-column
+              label="数据集名称"
+              prop="name"
+              show-overflow-tooltip
+            ></el-table-column>
+          </el-table>
+        </div>
+      </el-col>
+
+    </el-row>
+
+    <span slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="submitForm('ruleForm')">确定</el-button>
+      </span>
+  </el-dialog>
+</template>
+
+<script>
+import {pageMixins} from 'packages/js/mixins/page'
+import Tree from './Tree'
+import {addOrUpdateLabel, checkRepeatLabel, getDataSetIdListByLabelId} from 'packages/js/utils/LabelConfigService'
+import {datasetList, getCategoryTree} from 'packages/js/utils/datasetConfigService'
+
+export default {
+  name: "labelConfigAddOrUpdate",
+  mixins: [pageMixins],
+  data() {
+    return {
+      loading: false,
+      datasetList: [],
+      typeId: '',
+      dataForm: {
+        id: '',
+        labelName: '',
+        labelType: '',
+        labelDesc: '',
+        relList: []
+      },
+      dialogFormVisible: false,
+      rules: {
+        labelName: [
+          {required: true, message: '标签名称不能为空', trigger: 'blur'},
+          {validator: this.validateLabelName, trigger: 'blur'}
+        ],
+        labelType: [
+          {required: true, message: '标签类型不能为空', trigger: 'change'},
+        ],
+      },
+      // 分类树数据
+      categoryData: [],
+      relVisible: false,
+      // 标签分类列表
+      labelTypeList: [],
+      // 选中的数据集id列表
+      datasetIdList: []
+    }
+  },
+  components: {
+    Tree
+  },
+  watch: {
+    "dataForm.labelType": function (val) {
+      if (val.length > 20) {
+        this.dataForm.labelType = val.substring(0, 20);
+      }
+    },
+    // datasetList变化时,根据datasetIdList设置其选中状态
+    datasetList: {
+      handler: function (val) {
+        this.$nextTick(() => {
+          if (this.$refs.mytable) {
+            this.$refs.mytable.clearSelection();
+          }
+          this.datasetList.forEach((item) => {
+            if (this.datasetIdList.includes(item.id)) {
+              this.$refs.mytable.toggleRowSelection(item, true);
+            }
+          })
+        })
+      },
+      deep: true
+    }
+  },
+  methods: {
+    /**
+     * 初始化
+     * @param row 标签信息
+     */
+    init(row) {
+      this.dataForm.id = row ? row.id : '';
+      this.dialogFormVisible = true;
+      if (row) {
+        this.dataForm.id = row.id;
+        this.dataForm.labelName = row.labelName;
+        this.dataForm.labelType = row.labelType;
+        this.dataForm.labelDesc = row.labelDesc;
+        // 获取选中的数据集id列表
+        getDataSetIdListByLabelId(row.id).then((list) => {
+          this.datasetIdList = list;
+          this.buildRel();
+        })
+      }
+      this.$nextTick(() => {
+        this.getDataList();
+      })
+    },
+    /**
+     * 获取数据集列表
+     */
+    getDataList() {
+      this.loading = true;
+      let params = {
+        typeId: this.typeId
+      };
+      datasetList(params).then((list) => {
+        this.datasetList = list;
+        if (!this.relVisible) {
+          this.loading = false;
+          return
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      })
+    },
+
+    /**
+     * 获取分类树
+     */
+    getTreeList() {
+      getCategoryTree({type: 'dataset'}).then((categoryTree) => {
+        this.categoryData = categoryTree;
+      })
+    },
+    /**
+     * 标签名称校验
+     * @param rule
+     * @param value
+     * @param callback
+     */
+    validateLabelName(rule, value, callback) {
+      checkRepeatLabel({'id': this.dataForm.id, 'labelName': this.dataForm.labelName}).then(repeat => {
+        if (repeat) {
+          callback(new Error('标签名称已存在'))
+        } else {
+          callback();
+        }
+      });
+    },
+    /**
+     * 树节点点击事件
+     * @param row
+     * @param value
+     */
+    handleNodeClick(row, value) {
+      this.$nextTick(() => {
+        this.typeId = row.id;
+        this.getDataList();
+      })
+    },
+    /**
+     * 选中数据集
+     * @param selection 选中的数据集列表
+     * @param row 操作行
+     */
+    handleSelect(selection, row) {
+      // 如row.id存在于datasetIdList中,则将其从datasetIdList中删除
+      if (this.datasetIdList.includes(row.id)) {
+        const index = this.datasetIdList.indexOf(row.id);
+        if (index > -1) {
+          this.datasetIdList.splice(index, 1);
+        }
+        return
+      }
+      // 如row.id不存在于datasetIdList中,则将其添加到datasetIdList中
+      if (!this.datasetIdList.includes(row.id)) {
+        this.datasetIdList.push(row.id);
+      }
+    },
+    /**
+     * 数据集全选
+     * @param selection
+     */
+    handleSelectionAll(selection) {
+      // 选中项为空,将datasetList中所有项从datasetIdList中删除
+      if (selection.length === 0) {
+        this.datasetList.forEach((dataset) => {
+          const index = this.datasetIdList.indexOf(dataset.id);
+          if (index > -1) {
+            this.datasetIdList.splice(index, 1);
+          }
+        });
+        return
+      }
+      // 选中项不为空,将datasetList中所有项添加到datasetIdList中
+      if (selection.length > 0) {
+        this.datasetList.forEach((dataset) => {
+          if (!this.datasetIdList.includes(dataset.id)) {
+            this.datasetIdList.push(dataset.id);
+          }
+        });
+      }
+    },
+
+    /**
+     * 表单关闭
+     */
+    handleClose() {
+      this.$parent.addOrUpdateVisible = false
+    },
+    /**
+     * 取消按钮
+     */
+    cancel() {
+      this.dialogFormVisible = false;
+      this.$nextTick(() => {
+        this.handleClose();
+      })
+    },
+    /**
+     * 提交按钮
+     * @param formName
+     */
+    submitForm(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          this.saveForm(true);
+        } else {
+          return false;
+        }
+      });
+    },
+    /**
+     * 保存标签信息
+     * @param flag
+     */
+    saveForm(flag) {
+      this.dataForm.relList = [];
+      this.datasetIdList.forEach(id => {
+        let param = {
+          'datasetId': id,
+          'labelId': this.dataForm.id
+        };
+        this.dataForm.relList.push(param);
+      });
+      addOrUpdateLabel(this.dataForm).then((r) => {
+        this.$message.success('保存成功');
+        this.cancel();
+        this.$parent.getDataList();
+        //更新一下类型
+        this.$parent.getLabelType();
+      })
+    },
+    /**
+     * 添加关联按钮
+     */
+    buildRel() {
+      this.relVisible = !this.relVisible;
+      if (this.relVisible) {
+        this.getTreeList();
+        this.$nextTick(() => {
+          this.getDataList();
+        })
+      }
+    },
+    filterNode(value, data) {
+      if (!value) return true;
+      return data.name.indexOf(value) !== -1
+    },
+    ellipsis(value, len) {
+      if (!value) return '';
+      if (value.length > len) {
+        return value.slice(0, len) + '...'
+      }
+      return value
+    },
+    selectBlur(e) {
+      this.dataForm.labelType = e.target.value
+    },
+    // 对输入字符串控制
+    filterData() {
+      // 此属性得到输入的文字
+      var str = this.$refs.searchSelect.$data.selectedLabel;
+      // 控制的js
+      if (str.length > 20) {
+        this.$refs.searchSelect.$data.selectedLabel = str.substr(0, 20)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.el-col {
+  height: 358px;
+}
+
+.tree-box {
+  overflow-x: auto;
+}
+</style>

+ 299 - 0
data-room-ui/packages/DataSetLabelManagement/src/LabelConfigDetails.vue

@@ -0,0 +1,299 @@
+<template>
+  <div
+    v-if="dialogFormVisible"
+    class="inner-container"
+  >
+    <el-page-header
+      style="padding-top: 8px"
+      @back="goBack"
+    />
+
+    <el-divider content-position="left">
+      属性信息
+    </el-divider>
+
+    <el-row :gutter="5">
+      <el-col
+        :span="8"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.labelName.length > 20"
+          :content="dataForm.labelName"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>标签名称: {{ ellipsis(dataForm.labelName, 20) }}</span>
+        </el-tooltip>
+        <span v-else>标签名称: {{ ellipsis(dataForm.labelName, 20) }}</span>
+      </el-col>
+      <el-col
+        :span="8"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.labelType.length > 20"
+          :content="dataForm.labelType"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>标签类型: {{ ellipsis(dataForm.labelType, 20) }}</span>
+        </el-tooltip>
+        <span v-else>标签类型: {{ ellipsis(dataForm.labelType, 20) }}</span>
+      </el-col>
+      <el-col
+        :span="8"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.labelDesc.length > 20"
+          :content="dataForm.labelDesc"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>标签说明: {{ ellipsis(dataForm.labelDesc, 20) }}</span>
+        </el-tooltip>
+        <span v-else>标签说明: {{ ellipsis(dataForm.labelDesc, 20) }}</span>
+      </el-col>
+      <el-col
+        :span="8"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.createBy && dataForm.createBy.length > 20"
+          :content="dataForm.createBy"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>创建人: {{ ellipsis(dataForm.createBy, 20) }}</span>
+        </el-tooltip>
+        <span v-else>创建人: {{ ellipsis(dataForm.createBy, 20) }}</span>
+      </el-col>
+      <el-col
+        :span="16"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.createDate.length > 20"
+          :content="dataForm.createDate"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>创建时间: {{ ellipsis(dataForm.createDate, 20) }}</span>
+        </el-tooltip>
+        <span v-else>创建时间: {{ ellipsis(dataForm.createDate, 20) }}</span>
+      </el-col>
+      <el-col
+        :span="8"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.updateBy && dataForm.updateBy.length > 20"
+          :content="dataForm.updateBy"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>修改人: {{ ellipsis(dataForm.updateBy, 20) }}</span>
+        </el-tooltip>
+        <span v-else>修改人: {{ ellipsis(dataForm.updateBy, 20) }}</span>
+      </el-col>
+      <el-col
+        :span="8"
+        class="attrInfo"
+      >
+        <el-tooltip
+          v-if="dataForm.updateDate.length > 20"
+          :content="dataForm.updateDate"
+          class="item"
+          effect="dark"
+          placement="bottom-start"
+        >
+          <span>修改时间: {{ ellipsis(dataForm.updateDate, 20) }}</span>
+        </el-tooltip>
+        <span v-else>修改时间: {{ ellipsis(dataForm.updateDate, 20) }}</span>
+      </el-col>
+    </el-row>
+
+    <el-divider content-position="left">
+      关联数据集信息
+    </el-divider>
+
+    <div style="width:90%" id="container" />
+  </div>
+</template>
+
+<script>
+import G6 from '@antv/g6'
+import { getLabelDetail } from 'packages/js/utils/LabelConfigService'
+
+export default {
+  name: 'LabelConfigDetails',
+  data () {
+    return {
+      dialogFormVisible: false,
+      dataForm: {
+        createBy: '',
+        createDate: '',
+        updateBy: '',
+        updateDate: '',
+        labelDesc: '',
+        labelName: '',
+        labelType: ''
+      },
+      jsonData: {},
+      chartHeight: 0,
+      chartWidth: 0
+    }
+  },
+  methods: {
+    ellipsis (value, len) {
+      if (!value) return ''
+      if (value.length > len) {
+        return value.slice(0, len) + '...'
+      }
+      return value
+    },
+    init (row) {
+      this.dialogFormVisible = true
+      getLabelDetail(row.id).then((r) => {
+        this.jsonData = r.jsonData
+        if (r.jsonData.nodes.length > 1) {
+          this.chartHeight = r.jsonData.nodes.length * 100
+        } else {
+          this.chartHeight = 200
+        }
+        this.dataForm = r
+
+        this.chartWidth = r.labelName.length * 20
+
+        this.$nextTick(() => {
+          this.initG6(r.jsonData)
+        })
+      })
+    },
+    goBack () {
+      this.dialogFormVisible = false
+      this.$nextTick(() => {
+        this.$parent.labelVisible = true
+        this.$parent.getDataList()
+      })
+    },
+    nodeEach (nodes) {
+      nodes.forEach(node => {
+        if (!node.style) {
+          node.style = {}
+        }
+        switch (node.class) {
+          case 'c1': {
+            node.shape = 'circle'
+            node.size = 40
+            break
+          }
+          case 'c3': {
+            node.shape = 'rect'
+            // node.size = [80, 50];
+            node.style = {
+              stroke: '#FFFFFF',
+              fill: '#DFE1E3'
+            }
+            break
+          }
+          case 'c0': {
+            node.shape = 'ellipse'
+            node.size = [80, 40]
+            break
+          }
+          case 'c2': {
+            node.shape = 'diamond'
+            node.size = [60, 60]
+            break
+          }
+        }
+      })
+    },
+    // 初始化G6
+    initG6 (json) {
+      const data = json
+      const width = document.getElementById('container').scrollWidth
+
+      this.nodeEach(data.nodes)
+      const tooltip = new G6.Tooltip({
+        offsetX: 10,
+        offsetY: 0,
+        fixToNode: [1, 0],
+        itemTypes: ['node', 'edge'],
+        getContent: (e) => {
+          const outDiv = document.createElement('div')
+          outDiv.style.width = 'fit-content'
+          outDiv.innerHTML = `<div style='width: 200px;'>${e.item.getModel()._label}</div>`
+          return outDiv
+        },
+        shouldBegin: (e) => {
+          let res = true
+          if (e.item.getModel()._label && e.item.getModel()._label.length > 12) {
+            res = true
+          } else {
+            res = false
+          }
+          return res
+        }
+      })
+
+      const graph = new G6.Graph({
+        container: 'container',
+        // width: '800',
+        height: this.chartHeight,
+        plugins: [tooltip],
+        // modes: {
+        //   default: ['drag-canvas', 'zoom-canvas', 'click-select']
+        // },
+        defaultNode: {
+          type: 'rect',
+          size: [150, 50],
+          style: {
+            fill: '#9ACAFF',
+            stroke: '#FFFFFF'
+          }
+        },
+        // 连线类型及样式
+        defaultEdge: {
+          type: 'line',
+          style: {
+            offset: 25,
+            endArrow: true,
+            lineWidth: 2,
+            stroke: '#333'
+          }
+        }
+      })
+      data.nodes.forEach(node => {
+        node._label = node.label
+        if (node.label.length > 12) {
+          node.label = node.label.substr(0, 9) + '...'
+        }
+      })
+      graph.data(data)
+      graph.render()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.inner-container{
+  overflow-x: hidden;
+}
+.el-col {
+  height: 35px;
+}
+
+.attrInfo {
+  padding-left: 20px !important;
+}
+</style>

+ 345 - 0
data-room-ui/packages/DataSetLabelManagement/src/LabelSelect.vue

@@ -0,0 +1,345 @@
+<template>
+  <div>
+    <el-tag
+      v-for="label in selectLabelListInitial"
+      :key="label.id"
+      :closable="isEdit"
+      :disable-transitions="false"
+      @close="handleCloseTag(label)"
+    >
+      {{ label.labelName }}
+    </el-tag>
+    <el-tooltip
+      class="item"
+      content="添加关联标签"
+      effect="dark"
+      placement="right"
+    >
+      <el-button
+        circle
+        icon="el-icon-plus"
+        style="margin-left: 10px"
+        @click="addLabel"
+      />
+    </el-tooltip>
+    <!-- 标签列表弹窗 -->
+    <el-dialog
+      :append-to-body="true"
+      :before-close="handleClose"
+      :visible.sync="dialogFormVisible"
+      title="选择标签"
+      width="1000px"
+    >
+      <div v-loading="labelCheckLoading">
+        <el-form
+          :inline="true"
+          class="filter-container"
+        >
+          <el-form-item label="标签名称">
+            <el-input
+              v-model="searchForm.labelName"
+              clearable
+              placeholder="请输入标签名称"
+            />
+          </el-form-item>
+
+          <el-form-item label="标签类型">
+            <el-select
+              v-model="searchForm.labelType"
+              clearable
+              filterable
+              placeholder="请选择标签类型"
+              @change="selectLabelType"
+            >
+              <el-option
+                label="全部"
+                value=""
+              />
+              <el-option
+                v-for="(item, index) in labelTypeList"
+                :key="index"
+                :label="item"
+                :value="item"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button
+              type="primary"
+              @click="getDataList"
+            >
+              查询
+            </el-button>
+          </el-form-item>
+        </el-form>
+        <!--  标签选项组   -->
+        <el-checkbox-group
+          v-model="checkLabelList"
+          style="padding-bottom: 10px"
+        >
+          <el-row :gutter="2">
+            <el-col
+              v-for="label in labelList"
+              :key="label.id"
+              :span="4"
+              style="padding-top: 10px"
+            >
+              <el-tooltip
+                v-if="label.labelDesc || getByteLength(label.labelName) > 18"
+                effect="light"
+                placement="top-start"
+              >
+                <div slot="content">
+                  <div v-if="getByteLength(label.labelName) > 18">
+                    名称: {{ label.labelName }}
+                  </div>
+                  <div v-if="label.labelDesc">
+                    描述: {{ label.labelDesc }}
+                  </div>
+                </div>
+                <el-checkbox
+                  :label="label.id"
+                  @change="labelCheckChange(label)"
+                >
+                  {{ getByteLength(label.labelName) > 18 ? ellipsis(label.labelName, 18) : label.labelName }}
+                </el-checkbox>
+              </el-tooltip>
+              <el-checkbox
+                v-else
+                :label="label.id"
+                @change="labelCheckChange(label)"
+              >{{label.labelName}}</el-checkbox>
+            </el-col>
+          </el-row>
+        </el-checkbox-group>
+
+        <div class="page-container">
+          <el-pagination
+            :current-page="current"
+            :page-size="sizeLabel"
+            :page-sizes="[20, 40, 60, 80]"
+            :total="totalCount"
+            background
+            layout="total, prev, pager, next,sizes,jumper"
+            @size-change="sizeChangeHandle"
+            @current-change="currentChangeHandle"
+          />
+        </div>
+
+        <div align="center">
+          <el-button @click="handleClose">
+            取消
+          </el-button>
+          <el-button
+            type="primary"
+            @click="commitLabel"
+          >
+            确定
+          </el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+
+
+</template>
+
+<script>
+import { pageMixins } from 'packages/js/mixins/page'
+import { getLabelType, labelList, getLabelListByDatasetId } from 'packages/js/utils/LabelConfigService'
+
+export default {
+  name: 'LabelSelect',
+  components: {},
+  mixins: [pageMixins],
+  props: {
+    // 选中的标签id列表
+    idList: {
+      type: Array,
+      default: () => []
+    },
+    isEdit: {
+      type: Boolean,
+      default: true
+    },
+    datasetId: {
+      type: String,
+      default: ''
+    }
+  },
+  data () {
+    return {
+      idListCopy: this.idList,
+      selectLabelList: [],
+      // 初始选中的标签列表
+      selectLabelListInitial : [],
+      labelList: [],
+      dialogFormVisible: false,
+      searchForm: {
+        labelName: '',
+        labelType: ''
+      },
+      checkLabelList: [],
+      sizeLabel: 20,
+      labelTypeList: [],
+      labelCheckLoading: false
+    }
+  },
+  mounted() {
+    // 根据数据集id获取关联的标签列表
+    if (this.datasetId) {
+      getLabelListByDatasetId(this.datasetId).then((data) => {
+        this.selectLabelListInitial = _.cloneDeep(data)
+        this.selectLabelList = _.cloneDeep(data)
+      })
+    }
+  },
+  watch: {
+    // labelList变化时,根据selectLabelList中项的id,设置选中状态
+    labelList: {
+      handler (val) {
+        this.checkLabelList = []
+        val.forEach((label) => {
+          this.selectLabelList.forEach((selected) => {
+            if (label.id === selected.id) {
+              this.checkLabelList.push(label.id)
+            }
+          })
+        })
+      },
+      deep: true
+    },
+    // 根据selectLabelList的变化,将id赋值给idList
+    selectLabelList: {
+      handler (val) {
+        this.checkLabelList = []
+        this.idListCopy = []
+        val.forEach((item) => {
+          this.idListCopy.push(item.id)
+          this.checkLabelList.push(item.id)
+        })
+      },
+      deep: true
+    }
+
+  },
+  methods: {
+    /**
+     * 初始化方法
+     */
+    init () {
+      this.dialogFormVisible = true
+      this.getDataList()
+      this.getLabelType()
+    },
+    /**
+     * 获取标签类型列表
+     */
+    getLabelType () {
+      getLabelType().then((data) => {
+        this.labelTypeList = data
+      })
+    },
+    /**
+     * 获取标签列表
+     */
+    getDataList () {
+      this.labelCheckLoading = true
+      let params = {
+        current: this.current,
+        size: this.sizeLabel,
+        labelName: this.searchForm.labelName,
+        labelType: this.searchForm.labelType
+      }
+      labelList(params).then((data) => {
+        this.totalCount = data.totalCount
+        this.labelList = data.list
+        this.labelCheckLoading = false
+      }).catch(() => {
+        this.labelCheckLoading = false
+      })
+    },
+    /**
+     * 标签选项组选中事件
+     */
+    labelCheckChange(label) {
+      // 如果selectLabelList中包含id相同的项,则从selectLabelList中移除
+      if (this.selectLabelList.some(item => item.id === label.id)) {
+        this.selectLabelList = this.selectLabelList.filter(item => item.id !== label.id)
+      } else {
+        // 否则,将该项添加到selectLabelList中
+        this.selectLabelList.push(label)
+      }
+    },
+    /**
+     * 移除选中的标签
+     */
+    handleCloseTag (label) {
+      this.selectLabelListInitial.forEach((item, index) => {
+        if (item.id === label.id) {
+          this.selectLabelListInitial.splice(index, 1)
+        }
+      })
+      this.selectLabelList.forEach((item, index) => {
+        if (item.id === label.id) {
+          this.selectLabelList.splice(index, 1)
+        }
+      })
+    },
+    /**
+     * 点击添加标签按钮
+     */
+    addLabel () {
+      // 初始化
+      this.init()
+    },
+    /**
+     * 选中标签类型
+     */
+    selectLabelType () {
+      this.getDataList()
+    },
+    /**
+     * 弹窗关闭
+     */
+    handleClose () {
+      this.selectLabelList = _.cloneDeep(this.selectLabelListInitial)
+      this.dialogFormVisible = false
+    },
+    /**
+     * 确认按钮
+     */
+    commitLabel () {
+      this.labelCheckLoading = false
+      this.dialogFormVisible = false
+      this.selectLabelListInitial = _.cloneDeep(this.selectLabelList)
+      this.$emit('commit', this.idListCopy)
+    },
+    getByteLength (str) {
+      return unescape(encodeURIComponent(str)).length
+    },
+    ellipsis (str, len) {
+      if ((!str && typeof (str) !== 'undefined')) {
+        return ''
+      }
+      var num = 0
+      var str1 = str
+      var str = ''
+      for (var i = 0, lens = str1.length; i < lens; i++) {
+        num += ((str1.charCodeAt(i) > 255) ? 2 : 1)
+        if (num > len - 3) {
+          break
+        } else {
+          str = str1.substring(0, i + 1)
+        }
+      }
+      return str + '...'
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 85 - 0
data-room-ui/packages/DataSetLabelManagement/src/LabelTypeEdit.vue

@@ -0,0 +1,85 @@
+<template>
+  <el-dialog
+    :append-to-body="true"
+    :before-close="handleClose"
+    :visible.sync="dialogFormVisible"
+    class="dialogClass"
+    title="标签类型修改"
+    width="500px"
+  >
+    <div style="margin: 20px;">
+      <el-form ref="ruleForm" :model="dataForm" :rules="rules" label-position="left" label-width="90px">
+        <el-form-item label="标签类型" prop="labelType">
+          <el-input v-model="dataForm.labelType"/>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <span slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="submitForm('ruleForm')">确定</el-button>
+      </span>
+  </el-dialog>
+</template>
+
+<script>
+import {updateLabelType} from 'packages/js/utils/LabelConfigService'
+
+export default {
+  name: "labelTypeEdit",
+  data() {
+    return {
+      dialogFormVisible: false,
+      dataForm: {
+        labelType: '',
+        oldLabelType: ''
+      },
+      rules: {
+        labelType: [
+          {required: true, message: '标签类型不能为空', trigger: 'blur'},
+        ]
+      }
+    }
+  },
+  methods: {
+    init(labelType) {
+      this.dataForm.labelType = labelType;
+      this.dataForm.oldLabelType = labelType;
+    },
+    handleClose() {
+      this.$parent.labelTypeEditVisible = false
+    },
+    cancel() {
+      this.dialogFormVisible = false;
+      this.$nextTick(() => {
+        this.handleClose();
+      })
+    },
+    submitForm(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          updateLabelType(this.dataForm).then(() => {
+
+            if (this.$parent.queryForm.labelType !== '') {
+              this.$parent.queryForm.labelType = this.dataForm.labelType;
+            }
+            this.$parent.reSearch()
+            this.$parent.getLabelType();
+
+            this.cancel();
+            this.$message.success("保存成功")
+          });
+        } else {
+          return false;
+        }
+      });
+    },
+  }
+}
+</script>
+
+<style>
+.dialogClass .el-dialog__body {
+  min-height: auto;
+}
+</style>

+ 162 - 0
data-room-ui/packages/DataSetLabelManagement/src/Tree/index.vue

@@ -0,0 +1,162 @@
+<template>
+  <el-tree
+    :ref="treeRef"
+    :data="treeData"
+    :default-expand-all="expandAll"
+    :expand-on-click-node="false"
+    :filter-node-method="filterNode"
+    :highlight-current="true"
+    :indent="0"
+    :props="defaultProps"
+    node-key="id"
+    style="width: fit-content;min-width: 100%;"
+    @node-click="handleNodeClick"
+    @node-contextmenu="rihgtClick"
+  >
+    <span
+      slot-scope="{ node,data }"
+      class="custom-tree-node"
+      style="width: 100%;position: relative;"
+      @mouseenter="mouseEnter(data)"
+      @mouseleave="mouseLeave(data)"
+    >
+      <span :style="data.children && data.children.length ? {} : {'padding-left': '12px'}">
+        <i :class="data.children && data.children.length ? 'el-icon el-icon-folder': 'el-icon el-icon-document'"
+           style="margin-right: 8px;"></i>
+        <span class="nodeText">{{ data.name }}</span>
+      </span>
+      <span v-if="optionShow" class="options">
+        <el-dropdown @command="(command) => { treeCommand(command, data) }">
+          <i class="el-icon-more"></i>
+          <el-dropdown-menu slot="dropdown">
+            <slot name="options"></slot>
+          </el-dropdown-menu>
+        </el-dropdown>
+      </span>
+    </span>
+
+  </el-tree>
+</template>
+
+<script>
+export default {
+  name: 'Tree',
+  props: {
+    treeData: {
+      type: Array,
+      default: () => ([])
+    },
+    treeRef: {
+      type: String,
+      default: 'tree'
+    },
+    nodeWidth: {
+      type: String,
+      default: '280px'
+    },
+    expandAll: {
+      type: Boolean,
+      default: true
+    },
+    filterText: {
+      type: String,
+      default: ''
+    },
+    optionShow: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      defaultProps: {
+        children: 'children',
+        label: 'name'
+      }
+    }
+  },
+  watch: {
+    'treeData': function (val) {
+      // console.log('valx', val)
+    },
+    filterText(val) {
+      this.$refs[this.treeRef].filter(val)
+    }
+  },
+  methods: {
+    treeCommand(command, nodeData) {
+      this.$emit('treeCommand', command, nodeData)
+    },
+    filterNode(value, data) {
+      if (!value) return true
+      return data.name.indexOf(value) !== -1
+    },
+    // 节点点击
+    handleNodeClick(row, value) {
+      this.$emit('handleNodeClick', row, value)
+    },
+    // 节点右键
+    rihgtClick(event, object, value, element) {
+      this.$emit('rihgtClick', event, object, value, element)
+    },
+    mouseEnter(data) {
+      // this.$set(data, 'show', true)
+      // 弹射节点数据
+      this.$emit('mouseEnter', data)
+    },
+    mouseLeave(data) {
+      // this.$set(data, 'show', false)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+/deep/ .el-tree-node {
+  display: block;
+  min-width: 270px;
+
+  .el-tree-node__content {
+    width: 270px;
+    overflow: hidden;
+  }
+
+  .el-tree-node__children {
+    .el-tree-node {
+      display: block;
+      min-width: 270px;
+
+      .el-tree-node__content {
+        width: 270px;
+        overflow: hidden;
+      }
+    }
+  }
+}
+
+.custom-tree-node {
+  position: relative;
+
+  .nodeText {
+    display: inline-block;
+    width: 225px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    line-height: 14px;
+  }
+
+  .options {
+    position: absolute;
+    right: 30px;
+    height: 16px;
+    top: 2px;
+    line-height: 16px;
+    transform: rotate(90deg);
+  }
+}
+
+// /deep/ .is-current .options{
+//   background: #fff;
+// }
+</style>

+ 297 - 0
data-room-ui/packages/DataSetLabelManagement/src/index.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="db-container">
+    <div
+      v-if="labelVisible"
+      class="inner-container"
+    >
+      <el-form
+        ref="queryForm"
+        :model="queryForm"
+        class="filter-container"
+      >
+        <el-form-item
+          class="filter-item"
+          prop="labelName"
+        >
+          标签名称
+          <el-input
+            v-model="queryForm.labelName"
+            clearable
+            placeholder="请输入标签名称"
+            @clear="reSearch()"
+            @keyup.enter.native="reSearch()"
+          />
+        </el-form-item>
+        <el-form-item
+          class="filter-item"
+          prop="labelType"
+        >
+          标签类型
+          <el-select
+            v-model="queryForm.labelType"
+            clearable
+            filterable
+            placeholder="请选择标签类型"
+            @change="reSearch()"
+          >
+            <el-option
+              key="all"
+              label="全部"
+              value=""
+            />
+            <el-option
+              v-for="labelType in labelTypeList"
+              :key="labelType"
+              :label="labelType"
+              :value="labelType"
+            >
+              <span>
+                {{ labelType }}
+              </span>
+              <span style="float: right;padding-right: 20px">
+                <el-button
+                  icon="el-icon-edit"
+                  type="text"
+                  @click.stop="editLabelType(labelType)"
+                />
+                <el-button
+                  icon="el-icon-delete"
+                  type="text"
+                  @click.stop="deleteLabelType(labelType)"
+                />
+              </span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="filter-item">
+          <el-button
+            :loading="dataListLoading"
+            icon="el-icon-search"
+            type="primary"
+            @click="reSearch"
+          >
+            查询
+          </el-button>
+        </el-form-item>
+        <el-form-item
+          class="filter-item"
+        >
+          <el-button
+            type="primary"
+            @click="addOrUpdateLabel(undefined)"
+          >
+            新增
+          </el-button>
+        </el-form-item>
+      </el-form>
+      <div class="db-table-box">
+        <el-table
+          v-table
+          v-loading="dataListLoading"
+          height="0"
+          :data="tableData"
+          class="db-el-table db-scrollbar"
+          :element-loading-text="loadingText"
+          :header-cell-style="sortStyle"
+          @sort-change="reSort"
+        >
+          <el-empty slot="empty" />
+          <el-table-column
+            label="标签名称"
+            prop="labelName"
+            show-overflow-tooltip
+          />
+          <el-table-column
+            label="标签类型"
+            prop="labelType"
+            show-overflow-tooltip
+          />
+          <el-table-column
+            label="标签说明"
+            prop="labelDesc"
+            show-overflow-tooltip
+          />
+          <el-table-column
+            align="center"
+            label="操作"
+            width="200"
+          >
+            <template slot-scope="scope">
+              <el-button @click="getDetail(scope.row)">
+                详情
+              </el-button>
+              <el-button
+                @click="addOrUpdateLabel(scope.row)"
+              >
+                编辑
+              </el-button>
+              <el-button
+                @click="handleDelete(scope.row.id)"
+              >
+                删除
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="db-pagination">
+        <el-pagination
+          class="db-el-pagination"
+          popper-class="db-el-pagination"
+          :current-page="current"
+          :next-text="nextText"
+          :page-size="size"
+          :page-sizes="[10, 20, 50, 100]"
+          :prev-text="prevText"
+          :total="totalCount"
+          background
+          layout="total, prev, pager, next, sizes"
+          @size-change="sizeChangeHandle"
+          @current-change="currentChangeHandle"
+        />
+      </div>
+    </div>
+    <label-config-add-or-update
+      v-if="addOrUpdateVisible"
+      ref="LabelConfigAddOrUpdate"
+    />
+    <label-type-edit
+      v-if="labelTypeEditVisible"
+      ref="LabelTypeEdit"
+    />
+    <label-config-details
+      ref="LabelConfigDetails"
+    >
+    </label-config-details>
+  </div>
+</template>
+
+<script>
+import table from 'packages/js/utils/table.js'
+import { pageMixins } from 'packages/js/mixins/page'
+import LabelConfigAddOrUpdate from './LabelConfigAddOrUpdate.vue'
+import LabelConfigDetails from './LabelConfigDetails.vue'
+import LabelTypeEdit from './LabelTypeEdit.vue'
+import { getLabelType, labelList, removeLabel, removeLabelByType } from 'packages/js/utils/LabelConfigService'
+
+export default {
+  name: 'LabelConfig',
+  directives: {
+    table // 注册自定义指令
+  },
+  mixins: [pageMixins],
+  components: {
+    LabelConfigDetails,
+    LabelConfigAddOrUpdate,
+    LabelTypeEdit
+  },
+  data () {
+    return {
+      tableData: [],
+      addOrUpdateVisible: false,
+      addOrUpdateDetailVisible: false,
+      labelTypeEditVisible: false,
+      labelVisible: true,
+      labelTypeList: [],
+      dataListLoading: false,
+      loadingText: '',
+      queryForm: {
+        labelName: '',
+        labelType: ''
+      }
+    }
+  },
+  watch: {},
+  mounted () {
+    this.getDataList()
+    this.getLabelType()
+  },
+  methods: {
+    deleteLabelType (labelType) {
+      this.$confirm('是否删除当前标签类型? ', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        removeLabelByType({ labelType: labelType }).then(() => {
+          this.$nextTick(() => {
+            this.reSearch()
+            this.getLabelType()
+            this.$message.success('删除成功')
+          })
+        })
+      })
+    },
+    editLabelType (labelType) {
+      this.labelTypeEditVisible = true
+      this.$nextTick(() => {
+        this.$refs.LabelTypeEdit.dialogFormVisible = true
+        this.$refs.LabelTypeEdit.init(labelType)
+      })
+    },
+    handleDelete (id) {
+      this.$confirm('确定删除当前标签吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        removeLabel(id).then(() => {
+          this.getDataList()
+          this.$message.success('删除成功')
+        })
+      }).catch(() => {
+      })
+    },
+    // 新增/编辑
+    addOrUpdateLabel (row) {
+      this.addOrUpdateVisible = true
+      this.$nextTick(() => {
+        this.$refs.LabelConfigAddOrUpdate.labelTypeList = this.labelTypeList
+        this.$refs.LabelConfigAddOrUpdate.init(row)
+      })
+    },
+    // 查看详情
+    getDetail (row) {
+      this.addOrUpdateDetailVisible = true
+      this.labelVisible = false
+      this.$nextTick(() => {
+        this.$refs.LabelConfigDetails.init(row)
+      })
+    },
+    // 获取标签列表
+    getLabelType () {
+      getLabelType().then((data) => {
+        this.labelTypeList = data
+      })
+    },
+    // 获取列表方法
+    getDataList () {
+      this.dataListLoading = true
+      this.loadingText = '正在查询数据...'
+      const params = {
+        current: this.current,
+        size: this.size,
+        ...this.queryForm
+      }
+      labelList(params).then((data) => {
+        this.totalCount = data.totalCount
+        this.tableData = data.list
+        this.dataListLoading = false
+      }).catch(() => {
+        this.dataListLoading = false
+      })
+    },
+    // 查询事件
+    reSearch () {
+      // 从第一页
+      this.current = 1
+      this.getDataList()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep .el-table{
+  border-color: var(--db-el-border) !important;
+}
+</style>

+ 29 - 1
data-room-ui/packages/DataSetManagement/src/index.vue

@@ -73,6 +73,28 @@
               @clear="handleSearch()"
             />
           </el-form-item>
+          <el-form-item
+            class="filter-item"
+            prop="labelIds"
+          >
+            <el-select
+              class="bs-el-select"
+              v-model="queryForm.labelIds"
+              clearable
+              filterable
+              multiple
+              collapse-tags
+              placeholder="请选择数据集关联标签"
+              @clear="handleSearch()"
+            >
+              <el-option
+                v-for="labelItem in labelList"
+                :key="labelItem.id"
+                :label="labelItem.labelName"
+                :value="labelItem.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
           <el-form-item class="filter-item">
             <el-button
               :loading="dataListLoading"
@@ -225,6 +247,7 @@ import OriginalEditForm from './OriginalEditForm.vue'
 import DatasetTypeDialog from './DatasetTypeDialog.vue'
 import StoredProcedureEditForm from './StoredProcedureEditForm.vue'
 import { datasetPage, datasetRemove } from 'packages/js/utils/datasetConfigService'
+import { getLabelList } from 'packages/js/utils/LabelConfigService'
 export default {
   name: 'DataSetManagement',
   directives: {
@@ -276,10 +299,12 @@ export default {
       queryForm: {
         name: '',
         datasetType: '',
-        typeId: '' // 分类id
+        typeId: '', // 分类id
+        labelIds: []
       }, // 查询条件
       // 数据集类型
       datasetTypeList: [],
+      labelList: [],
       isPackUpTree: false,
       transition: 0.1,
       loadingText: '正在加载数据',
@@ -488,6 +513,9 @@ export default {
         })
       }
       this.getDataList()
+      getLabelList().then(res => {
+        this.labelList = res
+      })
     },
     // 新增数据集
     addDataset () {

+ 92 - 0
data-room-ui/packages/js/utils/LabelConfigService.js

@@ -0,0 +1,92 @@
+/*!
+ * 标签管理
+ */
+import { get, post } from 'packages/js/utils/http'
+
+/**
+ * 获取标签列表
+ * @returns {*}
+ */
+const getLabelList = () => get(`/label/getLabelList`)
+
+/**
+ * 获取标签
+ * @param data
+ * @returns {*}
+ */
+const labelList = (data) => get(`/label/list`, data)
+
+/**
+ * 获取标签分类
+ * @returns {*}
+ */
+const getLabelType = () => get(`/label/getLabelType`)
+
+/**
+ * 根据种类移除标签
+ * @param data
+ * @returns {*}
+ */
+const removeLabelByType = (data) => post(`/label/removeLabelByType`, data)
+
+/**
+ * 移除标签
+ * @param id
+ * @returns {*}
+ */
+const removeLabel = (id = '-1') => get(`/label/removeLabel/${id}`)
+
+/**
+ * 检查重复标签
+ * @param data
+ * @returns {*}
+ */
+const checkRepeatLabel = (data) => post(`/label/checkRepeat`, data)
+
+/**
+ * 新增/修改标签
+ * @param data
+ * @returns {*}
+ */
+const addOrUpdateLabel = (data) => post(`/label/addOrUpdateLabel`, data)
+
+/**
+ * 获取标签详情
+ * @param id
+ * @returns {*}
+ */
+const getLabelDetail = (id = '-1') => get(`/label/getLabelDetail/${id}`)
+
+/**
+ * 修改标签种类
+ * @param data
+ * @returns {*}
+ */
+const updateLabelType = (data) => post(`/label/updateLabelType`, data)
+
+
+/**
+ * 根据标签id获取数据集id列表
+ * @param id
+ */
+const getDataSetIdListByLabelId = (id = '-1') => get(`/label/queryDataSetIdList/${id}`)
+
+/**
+ * 根据数据集id获取标签列表
+ * @param id
+ */
+const getLabelListByDatasetId = (id = '-1') => get(`/label/queryDataSetLabelList/${id}`)
+
+export {
+  getLabelList,
+  labelList,
+  getLabelType,
+  removeLabelByType,
+  removeLabel,
+  checkRepeatLabel,
+  addOrUpdateLabel,
+  getLabelDetail,
+  updateLabelType,
+  getDataSetIdListByLabelId,
+  getLabelListByDatasetId
+}