Rmengdi 2 месяцев назад
Родитель
Сommit
39d608d97d
37 измененных файлов с 4003 добавлено и 1134 удалено
  1. 1 1
      .env.development
  2. 1 1
      .env.production
  3. 1 1
      .env.staging
  4. 198 0
      .eslintrc.js
  5. 26 0
      .gitignore
  6. 259 449
      package-lock.json
  7. 5 2
      package.json
  8. 0 15
      src/api/annotations/bigEntity.js
  9. 96 0
      src/api/dataMark/dataManage.js
  10. 9 0
      src/api/dataMark/dataMark.js
  11. 37 0
      src/api/dataMark/entity.js
  12. 61 0
      src/api/dataMark/relationship.js
  13. 64 64
      src/api/tests/index.js
  14. BIN
      src/assets/images/none.png
  15. 67 66
      src/components/handleImport/index.vue
  16. 19 9
      src/main.js
  17. 0 1
      src/permission.js
  18. 73 10
      src/router/index.js
  19. 1 1
      src/store/modules/permission.js
  20. 16 0
      src/styles/index.scss
  21. 83 0
      src/utils/modal.js
  22. 238 0
      src/utils/tool.js
  23. 79 79
      src/views/admin/dict.vue
  24. 37 37
      src/views/admin/info.vue
  25. 112 117
      src/views/admin/menu.vue
  26. 218 0
      src/views/data_mark/dataManage/checkMarkData.vue
  27. 243 0
      src/views/data_mark/dataManage/datasetDetail.vue
  28. 243 0
      src/views/data_mark/dataManage/index.vue
  29. 67 0
      src/views/data_mark/dataMark/index.vue
  30. 182 0
      src/views/data_mark/dataMark/markPage.scss
  31. 756 0
      src/views/data_mark/dataMark/markPage.vue
  32. 293 0
      src/views/data_mark/labelManage/index.vue
  33. 237 0
      src/views/data_mark/relationshipManage/ERDetail.vue
  34. 179 0
      src/views/data_mark/relationshipManage/index.vue
  35. 0 182
      src/views/label/entity.vue
  36. 98 96
      src/views/test/index.vue
  37. 4 3
      vue.config.js

+ 1 - 1
.env.development

@@ -2,4 +2,4 @@
 ENV = 'development'
 
 # base api
-VUE_APP_BASE_API = 'http://127.0.0.1:3000'
+VUE_APP_BASE_API = 'http://127.0.0.1:8899'

+ 1 - 1
.env.production

@@ -2,5 +2,5 @@
 ENV = 'production'
 
 # base api
-VUE_APP_BASE_API = 'http://127.0.0.1:3000'
+VUE_APP_BASE_API = 'http://127.0.0.1:8899'
 

+ 1 - 1
.env.staging

@@ -4,5 +4,5 @@ NODE_ENV = production
 ENV = 'staging'
 
 # base api
-VUE_APP_BASE_API = 'http://127.0.0.1:3000'
+VUE_APP_BASE_API = 'http://127.0.0.1:8899'
 

+ 198 - 0
.eslintrc.js

@@ -0,0 +1,198 @@
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+  // add your custom rules here
+  //it is base on https://github.com/vuejs/eslint-config-vue
+  rules: {
+    "vue/max-attributes-per-line": [2, {
+      "singleline": 10,
+      "multiline": {
+        "max": 1,
+        "allowFirstLine": false
+      }
+    }],
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/multiline-html-element-content-newline":"off",
+    "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
+    'accessor-pairs': 2,
+    'arrow-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'block-spacing': [2, 'always'],
+    'brace-style': [2, '1tbs', {
+      'allowSingleLine': true
+    }],
+    'camelcase': [0, {
+      'properties': 'always'
+    }],
+    'comma-dangle': [2, 'never'],
+    'comma-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    'curly': [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
+    'generator-star-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'handle-callback-err': [2, '^(err|error)$'],
+    'indent': [2, 2, {
+      'SwitchCase': 1
+    }],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [2, {
+      'beforeColon': false,
+      'afterColon': true
+    }],
+    'keyword-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'new-cap': [2, {
+      'newIsCap': true,
+      'capIsNew': false
+    }],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [2, {
+      'allowLoop': false,
+      'allowSwitch': false
+    }],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [2, {
+      'max': 1
+    }],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [2, {
+      'defaultAssignment': false
+    }],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [2, {
+      'vars': 'all',
+      'args': 'none'
+    }],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [2, {
+      'initialized': 'never'
+    }],
+    'operator-linebreak': [2, 'after', {
+      'overrides': {
+        '?': 'before',
+        ':': 'before'
+      }
+    }],
+    'padded-blocks': [2, 'never'],
+    'quotes': [2, 'single', {
+      'avoidEscape': true,
+      'allowTemplateLiterals': true
+    }],
+    'semi': [2, 'never'],
+    'semi-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [2, {
+      'words': true,
+      'nonwords': false
+    }],
+    'spaced-comment': [2, 'always', {
+      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+    }],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    'yoda': [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [2, 'always', {
+      objectsInObjects: false
+    }],
+    'array-bracket-spacing': [2, 'never']
+  }
+}

+ 26 - 0
.gitignore

@@ -0,0 +1,26 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+stats.html
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+!.vscode/settings.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

Разница между файлами не показана из-за своего большого размера
+ 259 - 449
package-lock.json


+ 5 - 2
package.json

@@ -21,17 +21,20 @@
     "fuse.js": "3.4.4",
     "js-cookie": "2.2.0",
     "js-md5": "^0.7.3",
+    "mark.js": "^8.11.1",
     "normalize.css": "7.0.0",
     "nprogress": "0.2.0",
     "path-to-regexp": "2.4.0",
     "pinyin": "2.9.0",
     "screenfull": "4.2.0",
-    "vue": "2.6.10",
+    "uuid": "^10.0.0",
+    "vue": "^2.6.14",
     "vue-i18n": "7.3.2",
     "vue-router": "3.0.2",
     "vuex": "3.1.0"
   },
   "devDependencies": {
+    "@types/babel__core": "^7.20.5",
     "@vue/cli-plugin-babel": "4.4.4",
     "@vue/cli-plugin-eslint": "4.4.4",
     "@vue/cli-plugin-unit-jest": "4.4.4",
@@ -58,7 +61,7 @@
     "serve-static": "1.13.2",
     "svg-sprite-loader": "4.1.3",
     "svgo": "1.2.0",
-    "vue-template-compiler": "2.6.10"
+    "vue-template-compiler": "^2.6.14"
   },
   "browserslist": [
     "> 1%",

+ 0 - 15
src/api/annotations/bigEntity.js

@@ -1,15 +0,0 @@
-import request from '@/utils/axios'
-
-export function addBigEntity(data) {
-  return request({
-    path: '/annotation/addBigEntity',
-    data
-  })
-}
-
-export function getBigEntityList(data) {
-  return request({
-    path: '/annotation/getBigEntityList',
-    data
-  })
-}

+ 96 - 0
src/api/dataMark/dataManage.js

@@ -0,0 +1,96 @@
+import request from '@/utils/axios'
+// 增加
+export function addDatasetApi(data) {
+  return request({
+    path: '/dataset/addDataset',
+    data
+  })
+}
+// 获取List
+export function getDatasetListApi(data) {
+  return request({
+    path: '/dataset/getDatasetList',
+    data
+  })
+}
+// 修改
+export function updataDatasetApi(data) {
+  return request({
+    path: '/dataset/updataDataset',
+    data
+  })
+}
+// 删除
+export function delDatasetApi(data) {
+  return request({
+    path: '/dataset/delDataset',
+    data
+  })
+}
+
+// 查看导入记录
+export function getImportRecordApi(data) {
+  return request({
+    path: '/dataset/getImportRecord',
+    data
+  })
+}
+
+// 获取数据集的数据
+export function getDataListApi(data) {
+  return request({
+    path: '/dataset/getDataList',
+    data
+  })
+}
+
+// 获取单条数据
+export function getDataApi(data) {
+  return request({
+    path: '/dataset/getData',
+    data
+  })
+}
+
+// 修改数据
+export function updataDataApi(data) {
+  return request({
+    path: '/dataset/updataData',
+    data
+  })
+}
+
+// 删除数据
+export function delDataApi(data) {
+  return request({
+    path: '/dataset/delData',
+    data
+  })
+}
+
+// 获取上一条、下一条数据
+export function PNDataApi(data) {
+  return request({
+    path: '/dataset/PNData',
+    data
+  })
+}
+
+// 下载模板
+export function downloadTemplate(data) {
+  return request({
+    path: '/dataset/downloadTemplate',
+    data,
+    responseType: 'blob'
+  })
+}
+
+// 导出标注信息
+export function exportDatasetApi(data) {
+  return request({
+    path: '/dataset/exportDataset',
+    data,
+    responseType: 'blob'
+  })
+}
+

+ 9 - 0
src/api/dataMark/dataMark.js

@@ -0,0 +1,9 @@
+import request from '@/utils/axios'
+
+// 修改数据
+export function updataMarkInfoApi(data) {
+  return request({
+    path: '/dataMark/updataMarkInfo',
+    data
+  })
+}

+ 37 - 0
src/api/dataMark/entity.js

@@ -0,0 +1,37 @@
+import request from '@/utils/axios'
+// 增加实体
+export function addEntityApi(data) {
+  return request({
+    path: '/entity/addEntity',
+    data
+  })
+}
+// 获取实体List
+export function getEntityListApi(data) {
+  return request({
+    path: '/entity/getEntityList',
+    data
+  })
+}
+
+// 获取子标签List
+export function getChildListApi(data) {
+  return request({
+    path: '/entity/getChildList',
+    data
+  })
+}
+// 修改实体
+export function updataEntityApi(data) {
+  return request({
+    path: '/entity/updataEntity',
+    data
+  })
+}
+// 删除
+export function delEntityApi(data) {
+  return request({
+    path: '/entity/delEntity',
+    data
+  })
+}

+ 61 - 0
src/api/dataMark/relationship.js

@@ -0,0 +1,61 @@
+import request from '@/utils/axios'
+// 增加
+export function addRelationshipApi(data) {
+  return request({
+    path: '/relationship/addRelationship',
+    data
+  })
+}
+// 获取List
+export function getRelationshipListApi(data) {
+  return request({
+    path: '/relationship/getRelationshipList',
+    data
+  })
+}
+// 修改
+export function updataRelationshipApi(data) {
+  return request({
+    path: '/relationship/updataRelationship',
+    data
+  })
+}
+// 删除
+export function delRelationshipApi(data) {
+  return request({
+    path: '/relationship/delRelationship',
+    data
+  })
+}
+
+// 获取查看的list
+export function getERListApi(data) {
+  return request({
+    path: '/relationship/getERList',
+    data
+  })
+}
+
+// 删除ER
+export function delERApi(data) {
+  return request({
+    path: '/relationship/delER',
+    data
+  })
+}
+
+// 查找ER关系
+export function ERQueryApi(data) {
+  return request({
+    path: '/relationship/ERQuery',
+    data
+  })
+}
+
+// 增加ER
+export function addERApi(data) {
+  return request({
+    path: '/relationship/addER',
+    data
+  })
+}

+ 64 - 64
src/api/tests/index.js

@@ -1,101 +1,101 @@
-import request from "@/utils/axios";
+import request from '@/utils/axios'
 
 export function addTests(data) {
-	return request({
-		path: "/tests/addTests",
-        data
-	});
+  return request({
+    path: '/tests/addTests',
+    data
+  })
 }
 
 export function getTests(data) {
-	return request({
-		path: "/tests/getTests",
-        data
-	});
+  return request({
+    path: '/tests/getTests',
+    data
+  })
 }
 
 export function upTests(data) {
-	return request({
-		path: "/tests/upTests",
-        data
-	});
+  return request({
+    path: '/tests/upTests',
+    data
+  })
 }
 
 export function delTests(data) {
-	return request({
-		path: "/tests/delTests",
-        data
-	});
+  return request({
+    path: '/tests/delTests',
+    data
+  })
 }
-//菜单权限接口测试
+// 菜单权限接口测试
 export function checkMenu(data) {
-	return request({
-		path: "/tests/checkMenu",
-        data
-	});
+  return request({
+    path: '/tests/checkMenu',
+    data
+  })
 }
 
-//角色权限接口测试
+// 角色权限接口测试
 export function checkRole(data) {
-	return request({
-		path: "/tests/checkRole",
-        data
-	});
+  return request({
+    path: '/tests/checkRole',
+    data
+  })
 }
 
-//添加文件
+// 添加文件
 export function addFile(data) {
-	return request({
-		path: "/tests/addFile",
-        data
-	});
+  return request({
+    path: '/tests/addFile',
+    data
+  })
 }
 
-//查询图片
+// 查询图片
 export function getImg(data) {
-	return request({
-		path: "/tests/getImg",
-        data
-	});
+  return request({
+    path: '/tests/getImg',
+    data
+  })
 }
-//查询文件
+// 查询文件
 export function getFile(data) {
-	return request({
-		path: "/tests/getFile",
-        data
-	});
+  return request({
+    path: '/tests/getFile',
+    data
+  })
 }
 
-//修改文件
+// 修改文件
 export function upFileReq(data) {
-	return request({
-		path: "/tests/upFile",
-        data
-	});
+  return request({
+    path: '/tests/upFile',
+    data
+  })
 }
 
-//删除文件
+// 删除文件
 export function delFile(data) {
-	return request({
-		path: "/tests/delFile",
-        data
-	});
+  return request({
+    path: '/tests/delFile',
+    data
+  })
 }
 
-//下载模板
+// 下载模板
 export function downloadTemplate(data) {
-	return request({
-		path: "/tests/downloadTemplate",
-        data,
-		responseType: "blob"
-	});
+  return request({
+    path: '/tests/downloadTemplate',
+    data,
+    responseType: 'blob'
+  })
 }
 
-//导出测试数据
+// 导出测试数据
 export function exportTest(data) {
-	return request({
-		path: "/tests/exportTest",
-        data,
-		responseType: "blob"
-	});
+  return request({
+    path: '/tests/exportTest',
+    data,
+    responseType: 'blob'
+  })
 }

BIN
src/assets/images/none.png


+ 67 - 66
src/components/handleImport/index.vue

@@ -15,20 +15,17 @@
         drag
         :disabled="upLoading"
       >
-        <i class="el-icon-upload"></i>
+        <i class="el-icon-upload" />
         <div class="el-upload__text">
           将文件拖到此处,或
           <em>点击上传</em>
         </div>
-        <div class="el-upload__tip" slot="tip" style="text-align: right">
-          <el-link v-loading="impLoading" v-if="showTemplate" type="primary"  @click="downloadTemplate"
-          >下载模板</el-link>
-        </div>
-        <div v-if="accept" class="el-upload__tip" style="color: red" slot="tip">
-          提示:仅允许导入{{accept}}格式文件!
+        <div slot="tip" class="el-upload__tip" style="text-align: right">
+          <el-link v-if="showTemplate" v-loading="impLoading" type="primary" @click="downloadTemplate">下载模板</el-link>
         </div>
+        <div v-if="accept" slot="tip" class="el-upload__tip" style="color: red">提示:仅允许导入{{ accept }}格式文件!</div>
       </el-upload>
-      <div v-loading="upLoading" slot="footer" class="dialog-footer">
+      <div slot="footer" v-loading="upLoading" class="dialog-footer">
         <el-button type="primary" @click="submitFileForm">确 定</el-button>
         <el-button @click="open = false">取 消</el-button>
       </div>
@@ -37,101 +34,105 @@
 </template>
 
 <script>
-import {getToken} from "@/utils/auth";
+import { getToken } from '@/utils/auth'
 export default {
-  name: "HandleImport",
-  props:{
-    //一次性上传的数量
-    limit:{
-      type:Number,
-      default:1
+  name: 'HandleImport',
+  props: {
+    // 一次性上传的数量
+    limit: {
+      type: Number,
+      default: 1
     },
-    accept:{
-      type:String,
-      default:".xlsx,.xls"
+    accept: {
+      type: String,
+      default: '.xlsx,.xls,.txt'
     },
-    //上传地址
-    url:{
-      type:String,
-      default:""
+    // 上传地址
+    url: {
+      type: String,
+      default: ''
     },
-    //显示下载模板按钮
-    showTemplate:{
-      type:Boolean,
-      default:true
+    // 显示下载模板按钮
+    showTemplate: {
+      type: Boolean,
+      default: true
     },
-    //显示导入方式
-    sign:{
-      type:Boolean,
-      default:false
+    // 显示导入方式
+    sign: {
+      type: Boolean,
+      default: false
     },
-    parentParams:{
-      type:Object,
-      default:()=>{return {}}
+    parentParams: {
+      type: Object,
+      default: () => {
+        return {}
+      }
     }
   },
-  data(){
+  data() {
     return {
       // 是否显示弹出层(用户导入)
       open: false,
       // 弹出层标题(用户导入)
-      title: "文件上传",
+      title: '文件上传',
       // 是否禁用上传
       upLoading: false,
-      impLoading:false,
+      impLoading: false,
       // 设置上传的请求头部
       headers: { Token: getToken() },
       // 上传的地址
-      action: "",
-      //上传附带的参数
-      params:{
-        sign:"insert",
-        listType:"",
-        activityCode:""
+      action: '',
+      // 上传附带的参数
+      params: {
+        sign: 'insert',
+        listType: '',
+        activityCode: ''
       }
     }
   },
   created() {
-    this.action=process.env.VUE_APP_BASE_API+this.url;
+    this.action = process.env.VUE_APP_BASE_API + this.url
   },
-  methods:{
-    show(){
-      this.open=true;
+  methods: {
+    show() {
+      this.open = true
     },
     /** 下载模板操作 */
     downloadTemplate() {
-      this.impLoading=true;
-      this.$emit("downloadTemplate",{call:()=>{
-          setTimeout(()=>{this.impLoading=false;},1500);
-        }});
+      this.impLoading = true
+      this.$emit('downloadTemplate', {
+        call: () => {
+          setTimeout(() => {
+            this.impLoading = false
+          }, 1500)
+        }
+      })
     },
-    //上传失败
-    handleFileError(e){
-      this.open = false;
-      this.upLoading = false;
+    // 上传失败
+    handleFileError(e) {
+      this.open = false
+      this.upLoading = false
     },
     // 文件上传中处理
     handleFileUploadProgress(event, file, fileList) {
-      this.upLoading = true;
+      this.upLoading = true
     },
     // 文件上传成功处理
     handleFileSuccess(response, file, fileList) {
-      this.upLoading = false;
-      this.$refs.upload.clearFiles();
-      if(response.code!==1) return this.$message.error(response.msg||"导入失败!");
-      this.open = false;
-      this.$emit("importRes",response);
+      this.upLoading = false
+      this.$refs.upload.clearFiles()
+      if (response.code !== 1) return this.$message.error(response.msg || '导入失败!')
+      this.open = false
+      this.$emit('importRes', response)
     },
     // 提交上传文件
     submitFileForm() {
-      this.$set(this.params,"activityNumber",this.parentParams.activityCode);
-      this.$set(this.params,"listType",this.parentParams.listType);
-      this.$refs.upload.submit();
+      this.$set(this.params, 'activityNumber', this.parentParams.activityCode)
+      this.$set(this.params, 'listType', this.parentParams.listType)
+      this.$refs.upload.submit()
     }
   }
 }
 </script>
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 19 - 9
src/main.js

@@ -17,11 +17,13 @@ import i18n from './lang' // internationalization
 import './icons' // icon
 import './permission' // permission control
 import './utils/error-log' // error log
-import "@/utils/directive";
+import '@/utils/directive'
 import * as filters from './filters' // global filters
-import Pagination from "@/components/Pagination";
-import {getDictType} from "@/api/admin";
-import {downFile} from "@/utils";
+import Pagination from '@/components/Pagination'
+import { getDictType } from '@/api/admin'
+import { downFile } from '@/utils'
+import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from '@/utils/tool'
+import modal from '@/utils/modal'
 /**
  * If you don't want to use mock-server
  * you want to use MockJs for mock api
@@ -34,6 +36,14 @@ import {downFile} from "@/utils";
 //   const { mockXHR } = require('../mock')
 //   mockXHR()
 // }
+// 全局方法挂载
+Vue.prototype.parseTime = parseTime
+Vue.prototype.resetForm = resetForm
+Vue.prototype.addDateRange = addDateRange
+Vue.prototype.selectDictLabel = selectDictLabel
+Vue.prototype.selectDictLabels = selectDictLabels
+Vue.prototype.handleTree = handleTree
+Vue.prototype.$modal = modal
 
 Vue.use(Element, {
   size: Cookies.get('size') || 'medium', // set element-ui default size
@@ -41,12 +51,12 @@ Vue.use(Element, {
 })
 
 // register global utility filters
-Object.keys(filters).forEach(key => {
+Object.keys(filters).forEach((key) => {
   Vue.filter(key, filters[key])
 })
-Vue.prototype.$getDictType=getDictType;
-Vue.prototype.$nodeMD5="nodeMD5"; //密码加盐
-Vue.prototype.$downFile=downFile; //密码加盐
+Vue.prototype.$getDictType = getDictType
+Vue.prototype.$nodeMD5 = 'nodeMD5' //密码加盐
+Vue.prototype.$downFile = downFile //密码加盐
 Vue.config.productionTip = false
 Vue.component('Pagination', Pagination)
 new Vue({
@@ -54,5 +64,5 @@ new Vue({
   router,
   store,
   i18n,
-  render: h => h(App)
+  render: (h) => h(App)
 })

+ 0 - 1
src/permission.js

@@ -39,7 +39,6 @@ router.beforeEach(async (to, from, next) => {
           await store.dispatch('user/getInfo')
           // generate accessible routes map based on roles
           const accessRoutes = await store.dispatch('permission/generateRoutes')
-          console.log('accessRoutes', accessRoutes)
           accessRoutes.push({
             path: '*',
             redirect: '/404',

+ 73 - 10
src/router/index.js

@@ -5,7 +5,7 @@ Vue.use(Router)
 
 /* Layout */
 import Layout from '@/layout'
-
+// 公共路由
 export const constantRoutes = [
   {
     path: '/redirect',
@@ -50,19 +50,82 @@ export const constantRoutes = [
         meta: { title: 'dashboard', icon: 'dashboard', affix: true }
       }
     ]
+  },
+  {
+    path: '/data_mark/relationshipManage',
+    component: Layout,
+    redirect: '/dashboard',
+    children: [
+      {
+        path: 'ERDetail/:id',
+        component: () => import('@/views/data_mark/relationshipManage/ERDetail.vue'),
+        name: 'ERDetail',
+        hidden: true,
+        meta: {
+          title: '详情',
+          icon: 'icon',
+          noCache: true,
+          activeMenu: '/dataMark/relationshipManage'
+        }
+      }
+    ]
+  },
+  {
+    path: '/data_mark/datasetManage',
+    component: Layout,
+    redirect: '/dashboard',
+    children: [
+      {
+        path: 'datasetDetail/:id',
+        component: () => import('@/views/data_mark/dataManage/datasetDetail.vue'),
+        name: 'datasetDetail',
+        hidden: true,
+        meta: {
+          title: '数据集详情',
+          icon: 'icon',
+          noCache: true,
+          activeMenu: '/dataManage/datasetManage'
+        }
+      }
+    ]
+  },
+  {
+    path: '/data_mark',
+    component: Layout,
+    redirect: '/dashboard',
+    children: [
+      {
+        path: 'dataMark',
+        component: () => import('@/views/data_mark/dataMark/index'),
+        name: 'dataMark',
+        meta: { title: '数据标注', icon: 'el-icon-more', noCache: false }
+      }
+    ]
+  },
+  {
+    path: '/data_mark/dataMark',
+    component: Layout,
+    redirect: '/dashboard',
+    hidden: true,
+    children: [
+      {
+        path: 'markPage/:dataset_id/:id?',
+        component: () => import('@/views/data_mark/dataMark/markPage.vue'),
+        name: 'markPage',
+        meta: { title: '标注', icon: 'el-icon-more', noCache: false, activeMenu: '/data_mark/dataMark' }
+      }
+    ]
   }
 ]
 
 // 需要动态判断的路由
-export const asyncRoutes = [
-
-]
-
-const createRouter = () => new Router({
-  // mode: 'history', // require service support
-  scrollBehavior: () => ({ y: 0 }),
-  routes: constantRoutes
-})
+export const asyncRoutes = []
+const createRouter = () =>
+  new Router({
+    mode: 'history', // require service support
+    scrollBehavior: () => ({ y: 0 }),
+    routes: constantRoutes
+  })
 
 const router = createRouter()
 

+ 1 - 1
src/store/modules/permission.js

@@ -74,7 +74,7 @@ const mutations = {
 const actions = {
   async generateRoutes({ commit }) {
     const { data } = await getRouter()
-    console.log('data', data)
+
     return new Promise((resolve) => {
       let { menu, role } = filterAsyncRoutes(data.routerMenu)
       commit('SET_ROUTES', menu)

+ 16 - 0
src/styles/index.scss

@@ -189,3 +189,19 @@ aside {
 .multiselect--active {
   z-index: 1000 !important;
 }
+
+// 滚动条全局样式
+::-webkit-scrollbar{ /*滚动条整体样式*/
+  width: 5px; /*高宽分别对应横竖滚动条的尺寸*/
+  height: 1px;
+}
+::-webkit-scrollbar-thumb{ /*滚动条里面小方块*/
+  border-radius: 5px;
+  background: #cfcbcb;
+}
+::-webkit-scrollbar-track{ /*滚动条里面轨道*/
+  border-radius: 5px;
+  background: #e6e4e4;
+}
+
+

+ 83 - 0
src/utils/modal.js

@@ -0,0 +1,83 @@
+import { Message, MessageBox, Notification, Loading } from 'element-ui'
+
+let loadingInstance
+
+export default {
+  // 消息提示
+  msg(content) {
+    Message.info(content)
+  },
+  // 错误消息
+  msgError(content) {
+    Message.error(content)
+  },
+  // 成功消息
+  msgSuccess(content) {
+    Message.success(content)
+  },
+  // 警告消息
+  msgWarning(content) {
+    Message.warning(content)
+  },
+  // 弹出提示
+  alert(content) {
+    MessageBox.alert(content, '系统提示')
+  },
+  // 错误提示
+  alertError(content) {
+    MessageBox.alert(content, '系统提示', { type: 'error' })
+  },
+  // 成功提示
+  alertSuccess(content) {
+    MessageBox.alert(content, '系统提示', { type: 'success' })
+  },
+  // 警告提示
+  alertWarning(content) {
+    MessageBox.alert(content, '系统提示', { type: 'warning' })
+  },
+  // 通知提示
+  notify(content) {
+    Notification.info(content)
+  },
+  // 错误通知
+  notifyError(content) {
+    Notification.error(content)
+  },
+  // 成功通知
+  notifySuccess(content) {
+    Notification.success(content)
+  },
+  // 警告通知
+  notifyWarning(content) {
+    Notification.warning(content)
+  },
+  // 确认窗体
+  confirm(content) {
+    return MessageBox.confirm(content, '系统提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+  },
+  // 提交内容
+  prompt(content) {
+    return MessageBox.prompt(content, '系统提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+  },
+  // 打开遮罩层
+  loading(content) {
+    loadingInstance = Loading.service({
+      lock: true,
+      text: content,
+      spinner: 'el-icon-loading',
+      background: 'rgba(0, 0, 0, 0.7)'
+    })
+  },
+  // 关闭遮罩层
+  closeLoading() {
+    loadingInstance.close()
+  }
+}

+ 238 - 0
src/utils/tool.js

@@ -0,0 +1,238 @@
+/**
+ * 通用js方法封装处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+// 日期格式化
+export function parseTime(time, pattern) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
+      time = parseInt(time)
+    } else if (typeof time === 'string') {
+      time = time
+        .replace(new RegExp(/-/gm), '/')
+        .replace('T', ' ')
+        .replace(new RegExp(/\.[\d]{3}/gm), '')
+    }
+    if (typeof time === 'number' && time.toString().length === 10) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') {
+      return ['日', '一', '二', '三', '四', '五', '六'][value]
+    }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
+}
+
+// 表单重置
+export function resetForm(refName) {
+  if (this.$refs[refName]) {
+    this.$refs[refName].resetFields()
+  }
+}
+
+// 添加日期范围
+export function addDateRange(params, dateRange, propName) {
+  let search = params
+  search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
+  dateRange = Array.isArray(dateRange) ? dateRange : []
+  if (typeof propName === 'undefined') {
+    search.params['beginTime'] = dateRange[0]
+    search.params['endTime'] = dateRange[1]
+  } else {
+    search.params['begin' + propName] = dateRange[0]
+    search.params['end' + propName] = dateRange[1]
+  }
+  return search
+}
+
+// 回显数据字典
+export function selectDictLabel(datas, value) {
+  if (value === undefined) {
+    return ''
+  }
+  var actions = []
+  Object.keys(datas).some((key) => {
+    if (datas[key].value == '' + value) {
+      actions.push(datas[key].label)
+      return true
+    }
+  })
+  if (actions.length === 0) {
+    actions.push(value)
+  }
+  return actions.join('')
+}
+
+// 回显数据字典(字符串、数组)
+export function selectDictLabels(datas, value, separator) {
+  if (value === undefined || value.length === 0) {
+    return ''
+  }
+  if (Array.isArray(value)) {
+    value = value.join(',')
+  }
+  var actions = []
+  var currentSeparator = undefined === separator ? ',' : separator
+  var temp = value.split(currentSeparator)
+  Object.keys(value.split(currentSeparator)).some((val) => {
+    var match = false
+    Object.keys(datas).some((key) => {
+      if (datas[key].value == '' + temp[val]) {
+        actions.push(datas[key].label + currentSeparator)
+        match = true
+      }
+    })
+    if (!match) {
+      actions.push(temp[val] + currentSeparator)
+    }
+  })
+  return actions.join('').substring(0, actions.join('').length - 1)
+}
+
+// 字符串格式化(%s )
+export function sprintf(str) {
+  var args = arguments,
+    flag = true,
+    i = 1
+  str = str.replace(/%s/g, function () {
+    var arg = args[i++]
+    if (typeof arg === 'undefined') {
+      flag = false
+      return ''
+    }
+    return arg
+  })
+  return flag ? str : ''
+}
+
+// 转换字符串,undefined,null等转化为""
+export function parseStrEmpty(str) {
+  if (!str || str == 'undefined' || str == 'null') {
+    return ''
+  }
+  return str
+}
+
+// 数据合并
+export function mergeRecursive(source, target) {
+  for (var p in target) {
+    try {
+      if (target[p].constructor == Object) {
+        source[p] = mergeRecursive(source[p], target[p])
+      } else {
+        source[p] = target[p]
+      }
+    } catch (e) {
+      source[p] = target[p]
+    }
+  }
+  return source
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function handleTree(data, id, parentId, children) {
+  let config = {
+    id: id || 'id',
+    parentId: parentId || 'parentId',
+    childrenList: children || 'children'
+  }
+
+  var childrenListMap = {}
+  var nodeIds = {}
+  var tree = []
+
+  for (let d of data) {
+    let parentId = d[config.parentId]
+    if (childrenListMap[parentId] == null) {
+      childrenListMap[parentId] = []
+    }
+    nodeIds[d[config.id]] = d
+    childrenListMap[parentId].push(d)
+  }
+
+  for (let d of data) {
+    let parentId = d[config.parentId]
+    if (nodeIds[parentId] == null) {
+      tree.push(d)
+    }
+  }
+
+  for (let t of tree) {
+    adaptToChildrenList(t)
+  }
+
+  function adaptToChildrenList(o) {
+    if (childrenListMap[o[config.id]] !== null) {
+      o[config.childrenList] = childrenListMap[o[config.id]]
+    }
+    if (o[config.childrenList]) {
+      for (let c of o[config.childrenList]) {
+        adaptToChildrenList(c)
+      }
+    }
+  }
+  return tree
+}
+
+/**
+ * 参数处理
+ * @param {*} params  参数
+ */
+export function tansParams(params) {
+  let result = ''
+  for (const propName of Object.keys(params)) {
+    const value = params[propName]
+    var part = encodeURIComponent(propName) + '='
+    if (value !== null && value !== '' && typeof value !== 'undefined') {
+      if (typeof value === 'object') {
+        for (const key of Object.keys(value)) {
+          if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
+            let params = propName + '[' + key + ']'
+            var subPart = encodeURIComponent(params) + '='
+            result += subPart + encodeURIComponent(value[key]) + '&'
+          }
+        }
+      } else {
+        result += part + encodeURIComponent(value) + '&'
+      }
+    }
+  }
+  return result
+}
+
+// 验证是否为blob格式
+export function blobValidate(data) {
+  return data.type !== 'application/json'
+}

+ 79 - 79
src/views/admin/dict.vue

@@ -1,40 +1,40 @@
 <template>
   <div class="box">
     <el-form
-        :model="queryParams"
-        ref="queryForm"
-        :inline="true"
+      ref="queryForm"
+      :model="queryParams"
+      :inline="true"
     >
       <el-form-item label="字典名称" prop="name">
         <el-input
-            v-model="queryParams.name"
-            placeholder="请输入名称"
-            clearable
-            size="small"
+          v-model="queryParams.name"
+          placeholder="请输入名称"
+          clearable
+          size="small"
         />
       </el-form-item>
       <el-form-item>
         <el-button
-            type="primary"
-            icon="el-icon-search"
-            size="small"
-            @click="handleQuery"
+          type="primary"
+          icon="el-icon-search"
+          size="small"
+          @click="handleQuery"
         >搜索</el-button>
       </el-form-item>
     </el-form>
     <el-button icon="el-icon-plus" type="primary" plain size="small" @click="add">新增</el-button>
-    <el-table :data="list" style="margin-top: 15px" v-loading="loading">
+    <el-table v-loading="loading" :data="list" style="margin-top: 15px">
       <el-table-column label="编号" align="center" width="100" prop="id" />
-      <el-table-column label="测试账号名称" align="center" prop="name"></el-table-column>
+      <el-table-column label="测试账号名称" align="center" prop="name" />
       <el-table-column label="字典类型" align="center" prop="type">
         <template slot-scope="scope">
-          <el-link type="primary" @click="goDictList(scope.row)"> {{scope.row.type}} <i class="el-icon-view el-icon--right"></i></el-link>
+          <el-link type="primary" @click="goDictList(scope.row)"> {{ scope.row.type }} <i class="el-icon-view el-icon--right" /></el-link>
         </template>
       </el-table-column>
       <el-table-column label="备注" align="center" prop="remark" />
       <el-table-column label="更新时间" align="center" prop="updateTime" />
       <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="操作" align="center" >
+      <el-table-column label="操作" align="center">
         <template slot-scope="scope">
           <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
           <el-button size="mini" type="danger" @click="handleDelete( scope.row)">删除</el-button>
@@ -43,15 +43,15 @@
     </el-table>
 
     <pagination
-        v-show="total > 0"
-        :total="total"
-        :page.sync="queryParams.page"
-        :limit.sync="queryParams.size"
-        @pagination="getDict"
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.page"
+      :limit.sync="queryParams.size"
+      @pagination="getDict"
     />
 
     <el-dialog title="操作框" :visible.sync="open" width="40%">
-      <el-form class="demo-form-inline" label-width="100px" :model="form" :rules="rules" ref="form">
+      <el-form ref="form" class="demo-form-inline" label-width="100px" :model="form" :rules="rules">
         <el-form-item label="字典名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入字典名称" />
         </el-form-item>
@@ -59,102 +59,102 @@
           <el-input v-model="form.type" placeholder="请输入字典类型" />
         </el-form-item>
         <el-form-item label="备注说明">
-          <el-input type="textarea" v-model="form.remark" placeholder="请输入备注" />
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
         </el-form-item>
       </el-form>
 
-      <div slot="footer" class="dialog-footer" v-loading="addLoading">
+      <div slot="footer" v-loading="addLoading" class="dialog-footer">
         <el-button @click="open = false">取 消</el-button>
         <el-button :type="form.id ? 'warning' : 'primary'" @click="affirm">{{
-            form.id ? "确认修改" : "确认添加"
-          }}</el-button>
+          form.id ? "确认修改" : "确认添加"
+        }}</el-button>
       </div>
     </el-dialog>
   </div>
 </template>
 
 <script>
-import {addDict,getDict,upDict,delDict} from "@/api/admin";
-import {formatDate} from "@/utils";
+import { addDict, getDict, upDict, delDict } from '@/api/admin'
+import { formatDate } from '@/utils'
 
 export default {
-  name: "Dict",
-  data(){
+  name: 'Dict',
+  data() {
     return {
-      queryParams:{
-        page:1,
-        size:10
+      queryParams: {
+        page: 1,
+        size: 10
       },
-      total:0,
-      loading:false,
-      addLoading:false,
-      list:[],
-      open:false,
-      form:{},
-      rules:{
-        name:[
-          { required: true, message: '请输入字典名称', trigger: 'blur' },
+      total: 0,
+      loading: false,
+      addLoading: false,
+      list: [],
+      open: false,
+      form: {},
+      rules: {
+        name: [
+          { required: true, message: '请输入字典名称', trigger: 'blur' }
         ],
-        type:[
+        type: [
           { required: true, message: '请输入字典类型', trigger: 'blur' }
         ]
       }
     }
   },
   created() {
-    this.getDict();
+    this.getDict()
   },
-  methods:{
-    affirm(){
-      this.$refs.form.validate(async (validate)=>{
-        if(!validate) return;
+  methods: {
+    affirm() {
+      this.$refs.form.validate(async(validate) => {
+        if (!validate) return
         try {
-          this.addLoading=true
-          !this.form.id&&await addDict(this.form);
-          this.form.id&&await upDict(this.form);
-          this.addLoading=false;
-          this.getDict();
-          this.open=false;
-          this.$message.success(this.form.id?"修改成功!":"新增成功!");
-        }catch (e) {
-          this.addLoading=false;
+          this.addLoading = true
+          !this.form.id && await addDict(this.form)
+          this.form.id && await upDict(this.form)
+          this.addLoading = false
+          this.getDict()
+          this.open = false
+          this.$message.success(this.form.id ? '修改成功!' : '新增成功!')
+        } catch (e) {
+          this.addLoading = false
         }
       })
     },
-    add(){
-      this.form={};
-      this.open=true;
+    add() {
+      this.form = {}
+      this.open = true
     },
-    async getDict(){
-      this.loading=true;
-      let {data,total}=await getDict(this.queryParams);
-      this.loading=false;
-      this.list=data;this.total=total;
+    async getDict() {
+      this.loading = true
+      const { data, total } = await getDict(this.queryParams)
+      this.loading = false
+      this.list = data; this.total = total
     },
-    handleQuery(){
-      this.queryParams.page=1;
-      this.getDict();
+    handleQuery() {
+      this.queryParams.page = 1
+      this.getDict()
     },
-    handleEdit(row){
-      this.form={...row};
-      this.open=true;
+    handleEdit(row) {
+      this.form = { ...row }
+      this.open = true
     },
-    handleDelete(row){
+    handleDelete(row) {
       this.$confirm(`是否删除《${row.name}》字典?`, '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'warning'
-      }).then(async () => {
-        await delDict(row);
-        this.getDict();
-        this.$message.success("删除成功!");
+      }).then(async() => {
+        await delDict(row)
+        this.getDict()
+        this.$message.success('删除成功!')
       })
     },
-    goDictList(row){
-      this.$router.push({path:"/menus/dict/dictItem",query:{id:row.id}})
+    goDictList(row) {
+      this.$router.push({ path: '/menus/dict/dictItem', query: { id: row.id }})
     },
-    formatterCreateTime(row){
-      return formatDate(row.createTime);
+    formatterCreateTime(row) {
+      return formatDate(row.createTime)
     }
   }
 }

+ 37 - 37
src/views/admin/info.vue

@@ -1,15 +1,15 @@
 <template>
   <div class="box" style="text-align: center">
-    <upFile ref="upFile" :file-list="fileList" :limit="1" :size="1" accept=".jpg,.png,.gif,.jpeg"></upFile>
+    <upFile ref="upFile" :file-list="fileList" :limit="1" :size="1" accept=".jpg,.png,.gif,.jpeg" />
     <el-row type="flex" justify="center" style="margin-top: 30px">
       <el-col :span="6">
-        <el-form class="demo-form-inline" label-width="80px" :model="form" :rules="rules" ref="form">
+        <el-form ref="form" class="demo-form-inline" label-width="80px" :model="form" :rules="rules">
           <el-form-item label="登陆账号" prop="name">
             <el-input v-model="form.name" placeholder="请输入登陆账号" />
           </el-form-item>
           <el-form-item label="登陆密码" prop="pwd">
-            <el-input type="password" disabled v-model="form.pwd" placeholder="请输入登陆密码" >
-            <template slot="append"> <el-button type="danger"  @click="upUserPwdInfo" style="background: #f56c6c;color: #fff">修改密码</el-button></template>
+            <el-input v-model="form.pwd" type="password" disabled placeholder="请输入登陆密码">
+              <template slot="append"> <el-button type="danger" style="background: #f56c6c;color: #fff" @click="upUserPwdInfo">修改密码</el-button></template>
             </el-input>
           </el-form-item>
         </el-form>
@@ -20,61 +20,61 @@
 </template>
 
 <script>
-import {upUserInfo,upUserPwdInfo} from "@/api/admin";
-import upFile from "@/components/upFile";
-import md5 from 'js-md5';
+import { upUserInfo, upUserPwdInfo } from '@/api/admin'
+import upFile from '@/components/upFile'
+import md5 from 'js-md5'
 export default {
-  name: "Info",
-  data(){
+  name: 'Info',
+  components: {
+    upFile
+  },
+  data() {
     return {
-      fileList:[],
-      form:{
-        pwd:"***********",
-        name:""
+      fileList: [],
+      form: {
+        pwd: '***********',
+        name: ''
       },
-      rules:{
+      rules: {
         name: [
           { required: true, message: '请输入登陆账号', trigger: 'blur' },
           { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
         ],
         pwd: [
-          { required: true, message: '请输入登陆密码', trigger: 'blur' },
-        ],
+          { required: true, message: '请输入登陆密码', trigger: 'blur' }
+        ]
       }
     }
   },
-  mounted(){
-    this.form.name=this.$store.state.user.name;
-    this.fileList=[{name:"",url:this.$store.state.user.avatar}];
+  mounted() {
+    this.form.name = this.$store.state.user.name
+    this.fileList = [{ name: '', url: this.$store.state.user.avatar }]
   },
-  methods:{
-    upSave(){
-      this.$refs.form.validate(async (validate)=>{
-        if(!validate) return;
-        let imgArr=this.$refs.upFile.getFileRes();
-        if(imgArr.length===0) return this.$message.error("请选择头像图片!");
-        this.form.url=imgArr[0].url;
-        await upUserInfo(this.form);
-        this.$store.commit("user/SET_NAME",this.form.name);
-        this.$store.commit("user/SET_AVATAR",this.form.url);
-        this.$message.success("修改成功!");
+  methods: {
+    upSave() {
+      this.$refs.form.validate(async(validate) => {
+        if (!validate) return
+        const imgArr = this.$refs.upFile.getFileRes()
+        if (imgArr.length === 0) return this.$message.error('请选择头像图片!')
+        this.form.url = imgArr[0].url
+        await upUserInfo(this.form)
+        this.$store.commit('user/SET_NAME', this.form.name)
+        this.$store.commit('user/SET_AVATAR', this.form.url)
+        this.$message.success('修改成功!')
       })
     },
-    upUserPwdInfo(){
+    upUserPwdInfo() {
       this.$prompt('请输入需要修改密码,修改后需要重新登陆!', '修改提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         inputPattern: /^[^\u4e00-\u9fa5]{6,15}$/,
         inputErrorMessage: '密码需要6-15位不为中文密码'
-      }).then(async ({ value }) => {
-        await upUserPwdInfo({pwd:md5(this.$nodeMD5+value)});
-        this.$message.success("修改成功!");
+      }).then(async({ value }) => {
+        await upUserPwdInfo({ pwd: md5(this.$nodeMD5 + value) })
+        this.$message.success('修改成功!')
         await this.$store.dispatch('user/logout')
       })
     }
-  },
-  components:{
-    upFile
   }
 }
 </script>

+ 112 - 117
src/views/admin/menu.vue

@@ -1,18 +1,20 @@
 <template>
   <div class="box">
     <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
-    <el-button
-        type="primary"
-        icon="el-icon-search"
-        size="mini"
-        @click="getRouterSystem"
-    >刷新</el-button>
-    <el-table v-if="refreshTable" v-loading="loading" :data="routerMenu" row-key="id" :default-expand-all="isExpandAll" :tree-props="{ children: 'children' }">
-<!--      <el-table-column prop="title" label="菜单名称" :show-overflow-tooltip="true" width="160" />-->
-      <el-table-column prop="icon" label="菜单名称"  width="200">
+    <el-button type="primary" icon="el-icon-search" size="mini" @click="getRouterSystem">刷新</el-button>
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="routerMenu"
+      row-key="id"
+      :default-expand-all="isExpandAll"
+      :tree-props="{ children: 'children' }"
+    >
+      <!--      <el-table-column prop="title" label="菜单名称" :show-overflow-tooltip="true" width="160" />-->
+      <el-table-column prop="icon" label="菜单名称" width="200">
         <template slot-scope="scope">
           <svg-icon :icon-class="scope.row.icon" />
-          <span style="margin-left: 5px">{{scope.row.title}}</span>
+          <span style="margin-left: 5px">{{ scope.row.title }}</span>
         </template>
       </el-table-column>
       <el-table-column prop="sort" label="排序" width="60" />
@@ -29,9 +31,9 @@
 
       <el-table-column label="类型" align="center" prop="menuType">
         <template slot-scope="scope">
-          <el-tag type="warning" v-if="scope.row.menuType == 'M'">目录</el-tag>
-          <el-tag type="success" v-if="scope.row.menuType == 'C'">菜单</el-tag>
-          <el-tag type="info" v-if="scope.row.menuType == 'F'" >按钮</el-tag>
+          <el-tag v-if="scope.row.menuType == 'M'" type="warning">目录</el-tag>
+          <el-tag v-if="scope.row.menuType == 'C'" type="success">菜单</el-tag>
+          <el-tag v-if="scope.row.menuType == 'F'" type="info">按钮</el-tag>
         </template>
       </el-table-column>
 
@@ -52,7 +54,13 @@
           <el-row>
             <el-col :span="24">
               <el-form-item label="上级菜单" prop="parentId">
-                <treeselect v-model="form.parentId" :options="selectRouterMenu" :normalizer="normalizer" :show-count="true" placeholder="选择上级菜单" />
+                <treeselect
+                  v-model="form.parentId"
+                  :options="selectRouterMenu"
+                  :normalizer="normalizer"
+                  :show-count="true"
+                  placeholder="选择上级菜单"
+                />
               </el-form-item>
             </el-col>
             <el-col :span="24">
@@ -66,41 +74,37 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="菜单名称" prop="title">
-                <el-input placeholder="请输入菜单名称" v-model="form.title" clearable>
-                </el-input>
+                <el-input v-model="form.title" placeholder="请输入菜单名称" clearable />
               </el-form-item>
             </el-col>
             <el-col :span="12">
               <el-form-item label="权限字符">
-                <el-input  v-model="form.roleKey" placeholder="请输入权限字符"></el-input>
+                <el-input v-model="form.roleKey" placeholder="请输入权限字符" />
               </el-form-item>
             </el-col>
-            <el-col :span="12" v-if="form.menuType==='C'">
+            <el-col v-if="form.menuType === 'C'" :span="12">
               <el-form-item label="页面名称" prop="name">
-                <el-input  placeholder="请输入页面名称(Name)英文" v-model="form.name" >
-                </el-input>
+                <el-input v-model="form.name" placeholder="请输入页面名称(Name)英文" />
               </el-form-item>
             </el-col>
 
-              <el-col :span="12" v-if="form.menuType==='C'||form.menuType==='M'">
-                <el-form-item label="路由地址" prop="path">
-                  <el-input placeholder="请输入路由地址(/path)" v-model="form.path" >
-                  </el-input>
-                </el-form-item>
-              </el-col>
-              <el-col :span="12" v-if="form.menuType==='C'">
-                <el-form-item label="页面地址" prop="component">
-                  <el-input  placeholder="请输入页面地址(index/index)" v-model="form.component" clearable>
-                  </el-input>
-                </el-form-item>
-              </el-col>
+            <el-col v-if="form.menuType === 'C' || form.menuType === 'M'" :span="12">
+              <el-form-item label="路由地址" prop="path">
+                <el-input v-model="form.path" placeholder="请输入路由地址(/path)" />
+              </el-form-item>
+            </el-col>
+            <el-col v-if="form.menuType === 'C'" :span="12">
+              <el-form-item label="页面地址" prop="component">
+                <el-input v-model="form.component" placeholder="请输入页面地址(index/index)" clearable />
+              </el-form-item>
+            </el-col>
 
             <el-col :span="12">
               <el-form-item label="显示排序">
-                <el-input-number v-model="form.sort" controls-position="right" :min="0" :max="100"></el-input-number>
+                <el-input-number v-model="form.sort" controls-position="right" :min="0" :max="100" />
               </el-form-item>
             </el-col>
-            <el-col :span="24" >
+            <el-col :span="24">
               <el-form-item label="菜单图标" prop="icon">
                 <el-popover placement="bottom-start" width="460" trigger="click" @show="$refs['iconSelect'].reset()">
                   <IconSelect ref="iconSelect" @selected="selected" />
@@ -113,7 +117,7 @@
               </el-form-item>
             </el-col>
 
-            <el-col :span="12" v-if="form.menuType==='C'">
+            <el-col v-if="form.menuType === 'C'" :span="12">
               <el-form-item label="缓存状态">
                 <el-radio-group v-model="form.noCache">
                   <el-radio :label="1">否</el-radio>
@@ -121,7 +125,7 @@
                 </el-radio-group>
               </el-form-item>
             </el-col>
-            <el-col :span="12" v-if="form.menuType!=='F'">
+            <el-col v-if="form.menuType !== 'F'" :span="12">
               <el-form-item label="显示状态">
                 <el-radio-group v-model="form.hidden">
                   <el-radio :label="0">显示</el-radio>
@@ -132,7 +136,7 @@
           </el-row>
         </el-form>
       </div>
-      <div slot="footer" class="dialog-footer" v-loading="addLoading">
+      <div slot="footer" v-loading="addLoading" class="dialog-footer">
         <el-button @click="dialogVisible = false">取 消</el-button>
         <el-button type="primary" @click="affirm">确 定</el-button>
       </div>
@@ -148,25 +152,26 @@ import Treeselect from '@riophae/vue-treeselect'
 import '@riophae/vue-treeselect/dist/vue-treeselect.css'
 import IconSelect from '@/components/IconSelect'
 export default {
-  name: "Menu",
-  data () {
-    let pathValidate=(rule, value, callback)=>{
-      let reg = /^\//
-      if (!reg.test(value)) {
-        callback(new Error('首个字符必须为 /'))
-      } else {
-        callback()
-      }
-    }
+  name: 'Menu',
+  components: { Treeselect, IconSelect },
+  data() {
+    // const pathValidate = (rule, value, callback) => {
+    //   const reg = /^\//
+    //   if (!reg.test(value)) {
+    //     callback(new Error('首个字符必须为 /'))
+    //   } else {
+    //     callback()
+    //   }
+    // }
     return {
       routerMenu: [],
       selectRouterMenu: [],
       refreshTable: true,
       loading: false,
-      addLoading:false,
+      addLoading: false,
       isExpandAll: false,
       dialogVisible: false,
-      queryParams:{},
+      queryParams: {},
       form: {
         icon: '',
         sort: 0,
@@ -174,75 +179,66 @@ export default {
         hidden: 0,
         parentView: 0,
         alone: 0,
-        menuType:"C"
+        menuType: 'C'
       },
       rules: {
-        parentId: [
-          { required: true, message: '请选择父级菜单', trigger: 'change' }
-        ],
-        title: [
-          { required: true, message: '请输入菜单名称', trigger: 'blur' },
-        ],
-        name: [
-          { required: true, message: '请输入页面名称', trigger: 'blur' },
-        ],
+        parentId: [{ required: true, message: '请选择父级菜单', trigger: 'change' }],
+        title: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
+        name: [{ required: true, message: '请输入页面名称', trigger: 'blur' }],
         path: [
-          { required: true, message: '请输入路由地址', trigger: 'blur' },
+          { required: true, message: '请输入路由地址', trigger: 'blur' }
           // { validator:pathValidate, message: '首个字符必须为 /', trigger: 'blur' },
         ],
-        component: [
-          { required: true, message: '请输入页面地址', trigger: 'blur' },
-        ]
+        component: [{ required: true, message: '请输入页面地址', trigger: 'blur' }]
       }
     }
   },
-  created () {
+  created() {
     this.getRouterSystem()
   },
   methods: {
     // 选择图标
-    selected (name) {
-      this.form.icon = name;
+    selected(name) {
+      this.form.icon = name
     },
-    async affirm () {
-      this.$refs["form"].validate(async (valid) => {
-        if (!valid) return;
+    async affirm() {
+      this.$refs['form'].validate(async(valid) => {
+        if (!valid) return
         try {
-          this.addLoading=true;
-          if(this.form.menuType==="F"||this.form.menuType==="M"){
-            this.form.name="";
-            this.form.component="/";
+          this.addLoading = true
+          if (this.form.menuType === 'F' || this.form.menuType === 'M') {
+            this.form.name = ''
+            this.form.component = '/'
           }
-          !this.form.id&&await addMenu(this.form);
-          this.form.id&&await changeMenu(this.form);
-          this.addLoading=false;
-          this.$message.success(this.form.id?"修改成功":"添加成功!")
-          this.getRouterSystem();
-          this.dialogVisible = false;
-        }catch (e) {
-          this.addLoading=false;
+          !this.form.id && (await addMenu(this.form))
+          this.form.id && (await changeMenu(this.form))
+          this.addLoading = false
+          this.$message.success(this.form.id ? '修改成功' : '添加成功!')
+          this.getRouterSystem()
+          this.dialogVisible = false
+        } catch (e) {
+          this.addLoading = false
         }
-      });
-
+      })
     },
-    formatTime (date) {
-      return formatTime(new Date(date.updateTime||date.createTime))
+    formatTime(date) {
+      return formatTime(new Date(date.updateTime || date.createTime))
     },
-    async getRouterSystem () {
-      this.loading=true;
-      const {data} = await getRouterSystem(this.queryParams);
-      this.loading=false;
-      let { routerMenu } = data;
+    async getRouterSystem() {
+      this.loading = true
+      const { data } = await getRouterSystem(this.queryParams)
+      this.loading = false
+      const { routerMenu } = data
       this.routerMenu = routerMenu
-      let selectRouterMenu = deepClone(routerMenu)
+      const selectRouterMenu = deepClone(routerMenu)
       selectRouterMenu.unshift({
-        title: "一级菜单",
+        title: '一级菜单',
         id: 0
-      });
+      })
       this.selectRouterMenu = selectRouterMenu
     },
-    //处理弹窗树形结构
-    normalizer (data) {
+    // 处理弹窗树形结构
+    normalizer(data) {
       if (data.children && !data.children.length) {
         delete data.children
       }
@@ -252,47 +248,46 @@ export default {
         children: data.children
       }
     },
-    handleAdd (row) {
-      this.resetForm();
-      if (row.id !== undefined) this.form.parentId = row.id;
-      this.dialogVisible = true;
+    handleAdd(row) {
+      this.resetForm()
+      if (row.id !== undefined) this.form.parentId = row.id
+      this.dialogVisible = true
     },
-    resetForm () {
+    resetForm() {
       this.form = {
-        id:undefined,
+        id: undefined,
         icon: '',
         sort: 0,
         noCache: 1,
         hidden: 0,
         parentView: 0,
         alone: 0,
-        menuType:"C",
-        path:"/"
-      };
+        menuType: 'C',
+        path: '/'
+      }
     },
-    handleUpdate (row) {
-      this.resetForm();
-      let data={...row};
-      data.path=row.pathView;
-      this.form = data;
+    handleUpdate(row) {
+      this.resetForm()
+      const data = { ...row }
+      data.path = row.pathView
+      this.form = data
       this.dialogVisible = true
     },
-    handleDelete (row) {
+    handleDelete(row) {
       this.$confirm(`此操作将永久删除【${row.title}】, 是否继续?`, '提示', {
         confirmButtonText: '取消',
         cancelButtonText: '确定',
         type: 'warning',
         distinguishCancelAndClose: true
-      }).catch(async (err) => {
-        if (err == "cancel") {
-          await delMenu({ id: row.id });
-          this.$message.success("删除成功!");
-          this.getRouterSystem();
+      }).catch(async(err) => {
+        if (err === 'cancel') {
+          await delMenu({ id: row.id })
+          this.$message.success('删除成功!')
+          this.getRouterSystem()
         }
       })
     }
-  },
-  components: { Treeselect, IconSelect }
+  }
 }
 </script>
 <style lang="scss" scoped>

+ 218 - 0
src/views/data_mark/dataManage/checkMarkData.vue

@@ -0,0 +1,218 @@
+<template>
+  <div class="detail">
+    <div class="left">
+      <i class="el-icon-arrow-left prev" @click="previous" />
+    </div>
+    <div class="center">
+      <div class="original">
+        <div class="title">原数据</div>
+        <div class="concent">
+          <p v-if="data.state === 0">{{ data.text }}</p>
+          <!-- <p v-if="data.state === 1">展示标注过的文本</p> -->
+          <div v-if="data.state === 1" id="text-container" ref="textContainer">{{ data.text }}</div>
+        </div>
+      </div>
+      <div class="marked">
+        <div class="title">标注结果</div>
+        <div class="concent">
+          <el-empty v-if="data.state === 0" description="暂无标注结果" />
+          <div v-if="data.state === 1">
+            <el-table size="mini" :data="markInfoList" style="width: 100%" row-key="id">
+              <el-table-column prop="subject" align="center" label="主体" />
+              <el-table-column prop="object" align="center" label="客体" />
+              <el-table-column prop="relation" align="center" label="关系" />
+            </el-table>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="right">
+      <i class="el-icon-arrow-right next" @click="next" />
+    </div>
+  </div>
+</template>
+<script>
+import Mark from 'mark.js'
+import { v4 as uuidv4 } from 'uuid'
+export default {
+  name: 'CheckMarkData',
+  props: {
+    markData: {
+      type: Object,
+      required: true
+    },
+    tagLabel: {
+      type: Array,
+      required: true
+    },
+    handleChange: {
+      type: Function,
+      required: true
+    }
+  },
+  data() {
+    return {
+      data: this.markData,
+      markInfoList: [],
+      tagLabelList: this.tagLabel
+    }
+  },
+  watch: {
+    markData: {
+      handler: function(newVal, oldVal) {
+        this.data = newVal
+        this.dataShow()
+      },
+      immediate: true
+    }
+  },
+  created() {
+    this.getLabelList()
+  },
+  mounted() {
+    this.dataShow()
+  },
+  methods: {
+    // 上一条
+    previous() {
+      this.handleChange(this.markData.id, this.markData.dataset_id, 'previous')
+    },
+    // 下一条
+    next() {
+      this.handleChange(this.markData.id, this.markData.dataset_id, 'next')
+    },
+
+    // 数据显示
+    dataShow() {
+      if (this.data.state === 1) {
+        this.$nextTick(() => {
+          if (this.$refs.textContainer) {
+            this.$refs.textContainer.innerHTML = this.data.text
+            this.markInfoList = JSON.parse(this.data.markInfo)
+            this.markInfoList.forEach((item, index) => {
+              this.handleBackShow(item, index)
+            })
+          }
+        })
+      }
+    },
+
+    // 标注回显
+    handleBackShow(itemMarkInfo, index) {
+      const marker = new Mark(this.$refs.textContainer)
+      const entityData = this.tagLabelList.find(item => {
+        return item.name === itemMarkInfo.subjectClass
+      })
+      const markId = uuidv4()
+      marker.markRanges(
+        [
+          {
+            start: itemMarkInfo.subjectPosition[0] + index * 2, // 增加标注文本会增加”“两个字符,因此起始位置需要加上index*2
+            length: itemMarkInfo.subjectPosition[1] - itemMarkInfo.subjectPosition[0] // 必填
+          }
+        ],
+        {
+          className: 'text-selected',
+          element: 'span',
+          each: (element) => {
+            // 为元素添加样式和属性
+            // element.setAttribute('id', markId)
+            element.style.borderBottom = `2px solid ${entityData ? entityData.color : 'red'}`
+            element.style.color = entityData ? entityData.color : 'red'
+
+            // 创建右上角标签元素
+            const labelElement = document.createElement('span')
+            labelElement.textContent = itemMarkInfo.subjectClass
+            labelElement.style.backgroundColor = entityData ? entityData.color : 'red'
+            labelElement.style.float = 'right'
+            labelElement.style.color = '#fff'
+            labelElement.style.padding = '2px 4px'
+            labelElement.style.borderRadius = '3px'
+            labelElement.style.fontSize = '12px'
+            labelElement.style.lineHeight = '12px'
+            labelElement.style.cursor = 'pointer'
+
+            const container = document.createElement('div')
+            container.setAttribute('id', markId)
+            container.style.display = 'inline-block'
+            element.parentNode.insertBefore(container, element)
+            container.appendChild(element)
+            container.appendChild(labelElement)
+          }
+        }
+      )
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.detail {
+  width: 100%;
+  min-height: 350px;
+  display: flex;
+  .left,
+  .right {
+    width: 50px;
+    height: 100%;
+    margin: auto;
+    text-align: center;
+    font-size: 3rem;
+  }
+  .left .prev:hover{
+    cursor: pointer;
+  }
+  .right .next:hover{
+    cursor: pointer;
+  }
+  .center {
+    flex: 1;
+    display: flex;
+    border: 1px solid #ebebeb;
+    .original,
+    .marked {
+      width: 50%;
+      .title {
+        width: 100%;
+        height: 40px;
+        text-align: center;
+        line-height: 40px;
+        font-weight: 700;
+        border-bottom: 1px solid #ebebeb;
+      }
+      .concent {
+        width: 100%;
+        height: calc(100% - 60px);
+        padding: 10px 20px;
+        #text-container {
+        height: 100%;
+        overflow-y: scroll;
+        line-height: 30px;
+      }
+      }
+    }
+    .original {
+      border-right: 1px solid #ebebeb;
+      .concent {
+        p {
+          line-height: 26px;
+        }
+      }
+    }
+    .marked {
+      .concent {
+        .none {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          margin-top: 20px;
+          .img {
+            width: 40%;
+          }
+        }
+      }
+    }
+  }
+
+}
+</style>

+ 243 - 0
src/views/data_mark/dataManage/datasetDetail.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="app-container">
+    <el-form ref="queryForm" :model="queryParams" :inline="true" @submit.native.prevent>
+      <el-form-item label="文本" prop="text">
+        <el-input v-model="queryParams.text" placeholder="请输入关键词搜索" clearable size="small" @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
+    <el-button type="primary" plain icon="el-icon-delete" size="mini" @click="handleExport">导出</el-button>
+    <el-table :data="dataList" style="width: 100%" row-key="id" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="number" align="center" label="序号" width="55" />
+      <el-table-column prop="text" align="center" label="文本" />
+      <!-- <el-table-column prop="is_mark" align="center" label="是否标注" width="120">
+        <template slot-scope="scope">
+          <el-switch v-model="scope.row.is_mark" active-color="#13ce66" :active-value="1" :inactive-value="0" />
+        </template>
+      </el-table-column> -->
+      <el-table-column prop="state" align="center" label="标注状态" width="120">
+        <template slot-scope="scope">
+          <el-tag size="mini" :type="scope.row.state?'success':'danger'">{{ scope.row.state?'已标注':'未标注' }}</el-tag>
+
+        </template>
+      </el-table-column>
+      <el-table-column prop="examine" align="center" label="审核状态" width="120">
+        <template slot-scope="scope">
+          <el-tag size="mini" :type="scope.row.examine?'success':'warning'">{{ scope.row.examine?'已审核':'未审核' }}</el-tag>
+
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="200">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="handleCheck(scope.row)">查看</el-button>
+          <el-button type="text" size="small" @click="beginMark(scope.row)">标注</el-button>
+          <el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
+          <el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="40%" append-to-body :close-on-click-modal="false">
+      <el-form ref="form" label-width="80px" :model="form" :rules="rules" @submit.native.prevent>
+        <el-form-item label="文本" prop="text">
+          <el-input v-model="form.text" type="textarea" placeholder="请输入" @keyup.enter.native="submitForm" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">{{ form.id ? '确认修改' : '确认添加' }}</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog :title="title" :visible.sync="checkDialog" width="70%" append-to-body :close-on-click-modal="false">
+      <checkMarkData :mark-data="markDataDetail" :handle-change="handleChange" :tag-label="tagLabelList" />
+      <div slot="footer" class="dialog-footer">
+        <!-- <el-button @click="dialogVisible = false">删除文本</el-button> -->
+        <el-button @click="dialogVisible = false">作 废</el-button>
+        <el-button type="primary" @click="dialogVisible = false">通 过</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { getDataListApi, updataDataApi, delDataApi, PNDataApi, exportDatasetApi } from '@/api/dataMark/dataManage'
+import { getEntityListApi } from '@/api/dataMark/entity'
+import checkMarkData from './checkMarkData.vue'
+// import { getUserInfo } from '@/api/admin'
+export default {
+  name: 'DatasetDetail',
+  components: { checkMarkData },
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        dataset_id: null,
+        id: null,
+        text: null,
+        is_mark: null,
+        state: null,
+        examine: null
+      },
+      dialogVisible: false,
+      checkDialog: false,
+      // 总条数
+      total: 0,
+      // 弹出层标题
+      title: '',
+      // 表格List
+      dataList: [],
+      // 表单参数
+      form: {},
+      rules: {},
+      // 多选的ID
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 多选的序号
+      numbers: [],
+      userInfo: {},
+      // 查看标注的文本
+      markDataDetail: {},
+      tagLabelList: []
+    }
+  },
+  created() {
+    this.queryParams.dataset_id = this.$route.params.id
+    this.getList()
+    this.getLabelList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      getDataListApi(this.queryParams).then((response) => {
+        this.dataList = response.data.map((item, index) => ({
+          ...item,
+          number: index + 1
+        }))
+        console.log('this.dataList', this.dataList)
+        this.total = response.total
+        this.loading = false
+      })
+    },
+    // 获取实体类和关系
+    getLabelList() {
+      const data = {}
+      getEntityListApi(data).then((res) => {
+        this.tagLabelList = res.data
+      })
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        text: undefined,
+        is_mark: undefined,
+        state: undefined,
+        examine: undefined
+      }
+      this.resetForm('form')
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs['form'].validate((valid) => {
+        if (valid && this.form.id !== undefined && this.form.state === 0) {
+          updataDataApi(this.form).then((response) => {
+            this.$modal.msgSuccess('修改成功')
+            this.dialogVisible = false
+            this.getList()
+          })
+        } else {
+          this.$modal.msgSuccess('已标注过的文本不能被修改')
+        }
+      })
+    },
+    // 删除数据集
+    handleDelete(row) {
+      let dataIds = []
+      row.id ? (dataIds = [row.id]) : (dataIds = this.ids)
+      const numbers = row.number || this.numbers
+      this.$confirm('此操作将永久删除序号为' + numbers + '的数据, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        await delDataApi(dataIds)
+        this.$message({
+          message: '删除成功!',
+          type: 'success'
+        })
+        this.getList()
+      })
+    },
+    // 查看标注详情
+    handleCheck(row) {
+      this.markDataDetail = { ...row }
+      this.checkDialog = true
+      this.title = '详情'
+    },
+    // 标注按钮
+    beginMark(row) {
+      this.$router.push({ name: 'markPage', params: { dataset_id: row.dataset_id, id: row.id }})
+    },
+    // 上一条、下一条
+    handleChange(id, dataset_id, type) {
+      const data = { id, dataset_id, type }
+      PNDataApi(data).then((response) => {
+        if (response.code === 2) {
+          this.$message({
+            message: response.msg,
+            type: 'warning'
+          })
+        } else {
+          this.$nextTick(() => {
+            this.markDataDetail = response.data[0]
+          })
+        }
+      })
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.form = { ...row }
+      this.dialogVisible = true
+      this.title = '修改文本内容'
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.id)
+      this.numbers = selection.map((item) => item.number)
+      console.log('numbers', this.numbers)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    // 导出
+    async handleExport() {
+      try {
+        this.addLoading = true
+        const res = await exportDatasetApi(this.queryParams)
+        this.addLoading = false
+        this.$downFile(res)
+      } catch (e) {
+        this.addLoading = false
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 243 - 0
src/views/data_mark/dataManage/index.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="app-container">
+    <el-form ref="queryForm" :model="queryParams" :inline="true" @submit.native.prevent>
+      <el-form-item label="数据集名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入数据集名称" clearable size="small" @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-button icon="el-icon-plus" type="primary" plain size="small" @click="handleAdd">添加数据集</el-button>
+    <!-- <el-button type="success" plain size="small" icon="el-icon-download" @click="importEvent">导入</el-button> -->
+    <el-table :data="datasetList" style="width: 100%" row-key="relationshipId">
+      <el-table-column prop="number" align="center" label="序号" width="55" />
+      <el-table-column prop="name" align="center" label="数据集名称" />
+      <el-table-column prop="creatorName" align="center" label="创建人" />
+      <el-table-column prop="marked" align="center" label="已标注数量" />
+      <el-table-column prop="totalNumber" align="center" label="总数据量" />
+      <el-table-column prop="importRecord" align="center" label="导入记录">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="getImportRecord(scope.row)">查看</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="handleCheck(scope.row)">查看</el-button>
+          <el-button type="text" size="small" @click="beginMark(scope.row)">标注</el-button>
+          <el-button type="text" size="small" @click="importEvent(scope.row)">导入</el-button>
+          <el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
+          <el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
+
+    <!-- 导入记录弹出框 -->
+    <el-dialog :title="title" :visible.sync="importVisible" width="60%" append-to-body :close-on-click-modal="false">
+      <el-table :data="importList" style="width: 100%" :row-key="importList.id">
+        <el-table-column prop="number" align="center" label="序号" width="55" />
+        <el-table-column prop="name" align="center" label="文件名称" />
+        <el-table-column prop="importerName" align="center" label="创建人" />
+        <el-table-column prop="time" align="center" label="导入时间" />
+        <el-table-column prop="data_volume" align="center" label="文件数据量" />
+      </el-table>
+    </el-dialog>
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="40%" append-to-body :close-on-click-modal="false">
+      <el-form ref="form" label-width="100px" :model="form" :rules="rules" @submit.native.prevent>
+        <el-form-item label="数据集名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入数据集名称" @keyup.enter.native="submitForm" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button :type="form.id ? 'warning' : 'primary'" @click="submitForm">{{ form.relationshipId ? '确认修改' : '确认添加' }}</el-button>
+      </div>
+    </el-dialog>
+    <handleImport
+      ref="handleImport"
+      url="/dataset/importData"
+      :parent-params="importParsms"
+      @importRes="importRes"
+      @downloadTemplate="downloadTemplate"
+    />
+  </div>
+</template>
+<script>
+import { addDatasetApi, getDatasetListApi, updataDatasetApi, delDatasetApi, getImportRecordApi, downloadTemplate } from '@/api/dataMark/dataManage'
+import { getUserInfo } from '@/api/admin'
+import handleImport from '@/components/handleImport'
+export default {
+  name: 'DatasetManage',
+  components: { handleImport },
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        id: null,
+        name: null,
+        creatorName: null
+      },
+      dialogVisible: false,
+      importVisible: false,
+      checkDialog: false,
+      // 总条数
+      total: 0,
+      // 弹出层标题
+      title: '',
+      // 表格List
+      datasetList: [],
+      ERList: [],
+      // 表单参数
+      form: {},
+      rules: {},
+      // 多选的ID
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      userInfo: {},
+      // 导入记录
+      importList: [],
+      // 导入数据的携带参数
+      importParsms: {}
+    }
+  },
+  created() {
+    getUserInfo().then((res) => {
+      this.userInfo = res.data.user
+    })
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      getDatasetListApi(this.queryParams).then((response) => {
+        this.datasetList = response.data.map((item, index) => ({
+          ...item,
+          number: index + 1
+        }))
+        this.total = response.total
+        this.loading = false
+      })
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        name: undefined,
+        creatorId: undefined
+      }
+      this.resetForm('form')
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          if (this.form.id !== undefined) {
+            updataDatasetApi(this.form).then((response) => {
+              this.$modal.msgSuccess('修改成功')
+              this.dialogVisible = false
+              this.getList()
+            })
+          } else {
+            this.form.creatorId = this.userInfo.id
+            console.log('this.form', this.form)
+            addDatasetApi(this.form).then((response) => {
+              this.$modal.msgSuccess('新增成功')
+              this.dialogVisible = false
+              this.getList()
+            })
+          }
+        }
+      })
+    },
+    // 删除数据集
+    handleDelete(row) {
+      this.$confirm('此操作将永久删除名为 ' + row.name + ' 的数据集, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        await delDatasetApi([row.id])
+        this.$message({
+          message: '删除成功!',
+          type: 'success'
+        })
+        this.getList()
+      })
+    },
+    // 标注按钮
+    beginMark(row) {
+      console.log('row', row)
+      this.$router.push({ name: 'markPage', params: { dataset_id: row.id, id: undefined }})
+    },
+    // 查看数据集中的数据列表
+    handleCheck(row) {
+      this.$router.push({ name: 'datasetDetail', params: { id: row.id }})
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset()
+      this.dialogVisible = true
+      this.title = '添加数据集'
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.form = { ...row }
+      this.dialogVisible = true
+      this.title = '修改数据集名称'
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    // 查看导入记录
+    getImportRecord(row) {
+      this.loading = true
+      const data = {
+        dataset_id: row.id
+      }
+      getImportRecordApi(data).then((response) => {
+        console.log('response', response)
+        this.importList = response.data.map((item, index) => ({
+          ...item,
+          number: index + 1
+        }))
+        this.loading = false
+        this.importVisible = true
+      })
+    },
+    // 导入
+    importEvent(row) {
+      const listType = {
+        importer_id: this.userInfo.id,
+        time: null,
+        data_volume: null,
+        dataset_id: row.id
+      }
+      this.importParsms.listType = JSON.stringify(listType)
+      this.$refs.handleImport.show()
+    },
+    importRes() {
+      this.$message.success('导入成功!')
+      this.getList()
+    },
+    async downloadTemplate({ call }) {
+      const res = await downloadTemplate()
+      this.$downFile(res)
+      call()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 67 - 0
src/views/data_mark/dataMark/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="app-container">
+    <el-table :data="datasetList" style="width: 100%" row-key="relationshipId">
+      <el-table-column prop="number" align="center" label="序号" width="55" />
+      <el-table-column prop="name" align="center" label="数据集名称" />
+      <el-table-column prop="creatorName" align="center" label="创建人" />
+      <el-table-column prop="marked" align="center" label="已标注数量" />
+      <el-table-column prop="totalNumber" align="center" label="总数据量" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="handleCheck(scope.row)">查看</el-button>
+          <el-button type="text" size="small" @click="beginMark(scope.row)">标注</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { getDatasetListApi } from '@/api/dataMark/dataManage'
+export default {
+  name: 'DataMark',
+  components: {},
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        id: null,
+        name: null,
+        creatorName: null
+      },
+      // 总条数
+      total: 0,
+      // 表格List
+      datasetList: []
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      getDatasetListApi(this.queryParams).then((response) => {
+        this.datasetList = response.data.map((item, index) => ({
+          ...item,
+          number: index + 1
+        }))
+        this.total = response.total
+        this.loading = false
+      })
+    },
+    // 查看数据集中的数据列表
+    handleCheck(row) {
+      this.$router.push({ name: 'datasetDetail', params: { id: row.id }})
+    },
+    // 标注按钮
+    beginMark(row) {
+      this.$router.push({ name: 'markPage', params: { dataset_id: row.id, id: undefined }})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 182 - 0
src/views/data_mark/dataMark/markPage.scss

@@ -0,0 +1,182 @@
+.app-container {
+  padding: 0px 20px;
+}
+
+::v-deep .el-table .warning-row {
+  background: #fff0f0;
+}
+
+.colorShow {
+  width: 20px;
+  height: 20px;
+}
+
+::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  display: none;
+}
+
+.markPage {
+  width: 100%;
+  height: calc(100vh - 84px);
+  display: flex;
+  background-color: #f5f7f9;
+
+  .left {
+    width: 23%;
+    height: 100%;
+    background-color: white;
+    position: relative;
+
+    .table {
+      height: calc(100% - 50px);
+      overflow-y: scroll;
+      -ms-overflow-style: none;
+      /* IE/Edge */
+      scrollbar-width: none;
+
+      /* Firefox */
+      &::-webkit-scrollbar {
+        display: none; // 滚动条隐藏
+      }
+
+      // 超出两行溢出隐藏真实悬浮提示
+      ::v-deep .el-table .cell.el-tooltip {
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        line-clamp: 2;
+        -webkit-line-clamp: 2;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: normal !important;
+      }
+    }
+
+    .pagination {
+      height: 80px;
+      position: absolute;
+      bottom: 0;
+      right: 0;
+    }
+  }
+
+  .content {
+    flex: 1;
+    padding: 20px;
+    padding-bottom: 10px;
+    background-color: white;
+
+    ::v-deep .el-card {
+      overflow: visible;
+    }
+
+    .main {
+      height: calc(100% - 130px);
+      width: 100%;
+      border-radius: 15px;
+
+      position: relative;
+
+      ::v-deep .el-card__body {
+        height: calc(100% - 60px);
+      }
+
+      #text-container {
+        height: 100%;
+        overflow-y: scroll;
+        line-height: 30px;
+      }
+
+      .tag-box {
+        position: absolute;
+        z-index: 10;
+        max-width: 300px;
+        overflow-x: scroll;
+        overflow-y: scroll;
+        background: #fff;
+        border-radius: 4px;
+        box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%), 0 3px 6px -2px rgb(0 0 0 / 20%);
+
+        ::v-deep .el-cascader-menu {
+          min-width: 150px;
+        }
+      }
+
+      .cancel-tag {
+        position: absolute;
+        z-index: 20;
+        padding: 2px;
+        cursor: pointer;
+        width: 80px;
+        background: #fff;
+        border-radius: 4px;
+        text-align: center;
+        box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%), 0 3px 6px -2px rgb(0 0 0 / 20%);
+      }
+
+      .cancel-tag:hover {
+        background-color: #e9e8e8;
+      }
+
+      header {
+        position: absolute;
+        bottom: 0;
+        width: 93%;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        height: 60px;
+        border-top: 1px solid #e5e7eb;
+        user-select: none;
+        background: #fff;
+      }
+    }
+
+    .footer {
+      height: 120px;
+      padding: 10px;
+      margin-top: 10px;
+      border: 1px solid #e5e5e5;
+      border-radius: 15px;
+
+      .tag {
+        width: 100%;
+        height: calc(100% - 25px);
+        overflow: hidden;
+        overflow-y: scroll;
+
+        .labelTag {
+          margin: 5px;
+          color: black;
+          position: relative;
+
+          .closeIcon {
+            position: absolute;
+            right: -5px;
+            top: -5px;
+            color: red;
+          }
+        }
+      }
+    }
+  }
+
+  .right {
+    border-left: 1px solid #ebebeb;
+    width: 32%;
+    height: 100%;
+    background-color: white;
+
+    h4 {
+      color: #767676;
+      text-align: center;
+      padding-bottom: 10px;
+      border-bottom: 1px solid #ebebeb;
+    }
+
+    .addIcon:hover{
+      cursor: pointer;
+      color: #1890ff;
+    }
+  }
+}

+ 756 - 0
src/views/data_mark/dataMark/markPage.vue

@@ -0,0 +1,756 @@
+<template>
+  <div class="app-container">
+    <div ref="box" class="markPage">
+      <div class="left">
+        <div class="table">
+          <el-table ref="table" :data="dataList" style="width: 100%" row-key="id" highlight-current-row @current-change="handleCurrentChange">
+            <el-table-column show-overflow-tooltip prop="text" align="center" label="标注文本" />
+            <el-table-column prop="state" align="center" label="状态" width="70">
+              <template slot-scope="scope">
+                <el-tag size="mini" :type="scope.row.state?'success':'danger'">{{ scope.row.state?'已标注':'未标注' }}</el-tag>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <div class="pagination">
+          <pagination
+            v-show="total > 0"
+            :layout="layout"
+            :total="total"
+            :page.sync="queryParams.page"
+            :limit.sync="queryParams.size"
+            :background="false"
+            @pagination="getList"
+          />
+        </div>
+      </div>
+      <div class="content">
+        <el-card class="box-card main">
+          <div id="text-container">{{ markData.text }}</div>
+          <el-cascader-panel
+            v-if="tagInfo.visible"
+            ref="tagLabel"
+            :class="['tag-box']"
+            :options="tagLabelTreeList"
+            :style="{ top: tagInfo.top + 'px', left: tagInfo.left + 'px' }"
+            :props="{ label: 'name', value: 'entityId', expandTrigger: 'hover' }"
+            @change="handleSelect"
+          />
+          <!-- :style="{ top: cancelTagInfo.top + 'px', left: cancelTagInfo.left + 'px' }" -->
+          <div
+            v-if="cancelTagInfo.visible"
+            :class="['cancel-tag']"
+            :style="{ top: cancelTagInfo.top + 'px', left: cancelTagInfo.left + 'px' }"
+            @click="handleCancel"
+          >
+            取 消
+          </div>
+          <header>
+            <el-button type="primary" size="mini" @click="clearMark()"> 清空标记 </el-button>
+            <el-button type="primary" size="mini" @click="handlePrev()"> 上一条 </el-button>
+            <!-- <el-button type="primary" size="mini" @click="addRelation"> 增加关系 </el-button> -->
+            <el-button type="primary" size="mini" @click="handleNext()"> 下一条 </el-button>
+            <el-button type="primary" size="mini" @click="handleSave()"> 保存 </el-button>
+          </header>
+        </el-card>
+        <div class="footer">
+          <div class="select">
+            <el-select v-model="labelQueryParams.parentId" size="mini" clearable placeholder="请选择" @change="changeParentLabel">
+              <el-option v-for="item in parentLabel" :key="item.entityId" :label="item.name" :value="item.entityId" />
+            </el-select>
+          </div>
+          <div class="tag">
+            <el-tag
+              v-for="label in childLabel"
+              :key="label.entityId"
+              :color="label.is_show ? label.color : '#f6f4f490'"
+              class="labelTag"
+              @click="handleTag(label)"
+            >
+              {{ label.name }}
+              <i v-if="!label.is_show" class="el-icon-circle-close closeIcon" />
+            </el-tag>
+          </div>
+        </div>
+      </div>
+      <div class="right">
+        <h4>关系</h4>
+        <div v-if="markRelationList.length !== 0" class="relationResult">
+          <el-table :data="markRelationList" style="width: 100%" row-key="id">
+            <el-table-column prop="subject" align="center" label="主体">
+              <template slot-scope="scope">
+                <span style="margin-right: 8px;">{{ scope.row.subject }}</span>
+                <!-- <el-button icon="el-icon-plus" size="mini" circle /> -->
+                <i class="el-icon-circle-plus-outline addIcon" @click="addRelation(scope.row)" />
+              </template>
+            </el-table-column>
+            <el-table-column prop="object" align="center" label="客体">
+              <template slot-scope="scope">
+                <el-select v-model="scope.row.object" clearable placeholder="请选择客体" size="mini" @change="((value)=>{handleObject(value, scope.row)})">
+                  <el-option
+                    v-for="item in markTextList"
+                    :key="item.mark_id"
+                    :label="item.mark_content"
+                    :value="item.mark_content"
+                  />
+                </el-select>
+              </template>
+            </el-table-column>
+            <el-table-column prop="relation" align="center" label="关系">
+              <template slot-scope="scope">
+                <el-select v-model="scope.row.relation" clearable placeholder="请选择关系" size="mini">
+                  <el-option
+                    v-for="item in relationOption"
+                    :key="item.relationshipId"
+                    :label="item.name"
+                    :value="item.name"
+                  />
+                </el-select>
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              fixed="right"
+              label="操作"
+              width="100"
+            >
+              <template slot-scope="scope">
+                <el-button type="text" size="small" @click="handleDeleteRelation(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- <img v-else class="img" src="@/assets/images/none.png" alt="暂无标注结果" title="暂无标注结果"> -->
+        <el-empty v-else description="暂无标注结果" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getDataListApi, PNDataApi } from '@/api/dataMark/dataManage'
+import { getEntityListApi, updataEntityApi, getChildListApi } from '@/api/dataMark/entity'
+import { getRelationshipListApi } from '@/api/dataMark/relationship'
+import { updataMarkInfoApi } from '@/api/dataMark/dataMark'
+import Mark from 'mark.js'
+import { v4 as uuidv4 } from 'uuid'
+export default {
+  name: 'MarkPage',
+  components: {},
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        dataset_id: null,
+        id: null,
+        text: null,
+        is_mark: null,
+        state: null
+      },
+      labelQueryParams: {
+        parentId: null
+      },
+      layout: 'prev, pager, next',
+      // 总条数
+      total: 0,
+      // 表格List
+      dataList: [],
+      // 正在标注的文本信息
+      markData: {},
+      parentLabel: [],
+      childLabel: [],
+      selectParentLabel: null,
+      TAG_WIDTH: 208,
+      // 选择的文字以及文字位置
+      selectedText: {
+        start: 0,
+        end: 0,
+        content: ''
+      },
+      // 实体标签信息
+      tagInfo: {
+        visible: false,
+        top: 0,
+        left: 0
+      },
+      // 关系、取消标签
+      cancelTagInfo: {
+        visible: false,
+        top: 0,
+        left: 0
+      },
+      markContent: {},
+      tagLabelList: [],
+      tagLabelTreeList: [],
+      // 所有标注的list
+      markTextList: [],
+      relationOption: [],
+      markRelationList: [],
+      relationForm: {},
+      selectMarkedId: null,
+      editMarkItem: {},
+      strNum: null
+    }
+  },
+  created() {
+    this.queryParams.dataset_id = Number(this.$route.params.dataset_id)
+    this.queryParams.id = Number(this.$route.params.id)
+    this.getLabelList(true)
+    this.init()
+    // this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      getDataListApi(this.queryParams).then((response) => {
+        this.dataList = response.data
+
+        if (!this.queryParams.id) {
+          const newData = this.dataList[0]
+          this.setNewData(newData)
+        } else {
+          const flag = this.dataList.findIndex(item => {
+            return item.id === Number(this.queryParams.id)
+          })
+          if (flag !== -1) {
+            const newData = this.dataList[flag]
+            this.setNewData(newData)
+          }
+        }
+        this.total = response.total
+        this.loading = false
+      })
+    },
+
+    // 获取实体类和关系(是否重新获取List)
+    getLabelList(flag) {
+      const data = {}
+      getEntityListApi(data).then((res) => {
+        this.tagLabelTreeList = this.handleTree(res.data, 'entityId')
+        this.tagLabelList = res.data
+        // 获取标注文本
+        flag ? this.getList() : ''
+        this.parentLabel = []
+        this.childLabel = []
+        res.data.forEach((item) => {
+          if (item.parentId === 0) this.parentLabel.push(item)
+          else this.childLabel.push(item)
+        })
+      })
+      getRelationshipListApi(data).then((response) => {
+        this.relationOption = response.data
+        this.loading = false
+      })
+    },
+
+    // 行高亮
+    tableRowClassName({ row }) {
+      if (row.id === Number(this.queryParams.id)) {
+        return 'warning-row'
+      }
+      return ''
+    },
+
+    // 表格单选
+    handleCurrentChange(val) {
+      // this.queryParams.id = val.id
+      // this.loading = true
+      document.getElementById('text-container').innerHTML = ''
+      const flag = this.dataList.findIndex(item => {
+        return item.id === val.id
+      })
+      if (flag !== -1) {
+        const newData = this.dataList[flag]
+        this.setNewData(newData)
+      }
+    },
+    changeParentLabel() {
+      if (this.labelQueryParams.parentId) {
+        getChildListApi(this.labelQueryParams).then((res) => {
+          this.childLabel = []
+          this.childLabel = res.data
+        })
+      } else {
+        this.getLabelList(false)
+      }
+    },
+
+    // 控制实体类的展示
+    handleTag(row) {
+      row.is_show = !row.is_show
+      updataEntityApi(row).then((res) => {
+        this.getLabelList(false)
+      })
+    },
+
+    // 给标注区域绑定鼠标事件
+    init() {
+      // 从后端获取标注数据,进行初始化标注
+      // this.init1()
+      this.$nextTick(() => {
+        const el = document.getElementById('text-container')
+        el.addEventListener('mouseup', this.handleSelection)
+      })
+    },
+
+    // 选择
+    handleSelection(event) {
+      const selectedText = window?.getSelection()?.toString() || ''
+      if (selectedText.length > 0) {
+        this.tagInfo = {
+          visible: true,
+          top: event.offsetY + 50,
+          left: event.offsetX - 50
+        }
+        this.getSelectedTextData()
+      } else {
+        this.tagInfo.visible = false
+        this.cancelTagInfo.visible = false
+      }
+      // this.resetEditTag()
+    },
+
+    // 得到标注的文本的位置
+    getSelectedTextData() {
+      const select = window?.getSelection()
+      // 用正则将空格和回车键去除
+      const nodeValue = select.focusNode?.nodeValue.replace(/(^\s*)|(\s*$)/g, '')
+      // 开始位置和结束位置
+      const anchorOffset = select.anchorOffset
+      const focusOffset = select.focusOffset
+      const nodeValueStartIndex = this.markData.text?.indexOf(nodeValue)
+      this.selectedText.content = select.toString()
+      if (anchorOffset < focusOffset) {
+        // 从左到右标注
+        this.selectedText.start = nodeValueStartIndex + anchorOffset
+        this.selectedText.end = nodeValueStartIndex + focusOffset
+      } else {
+        // 从右到左
+        this.selectedText.start = nodeValueStartIndex + focusOffset
+        this.selectedText.end = nodeValueStartIndex + anchorOffset
+      }
+    },
+
+    // 标注文本
+    handleSelect() {
+      const tagData = this.$refs.tagLabel.getCheckedNodes(true)[0].data
+      const marker = new Mark(document.getElementById('text-container'))
+      const { color, name } = tagData
+      const markId = uuidv4()
+      this.strNum = null
+
+      const repeatFlag = this.markTextList.findIndex(item => {
+        return item.mark_content === this.selectedText.content
+      })
+      if (repeatFlag !== -1) {
+        this.$message({
+          message: '该标注内容已经存在,请重新选择',
+          type: 'warning'
+        })
+        this.tagInfo.visible = false
+        return
+      }
+      this.markTextList.forEach((item) => {
+        if (item.start <= this.selectedText.start) {
+          this.strNum += item.entityName.length
+        }
+      })
+      marker.markRanges(
+        [
+          {
+            start: this.selectedText.start + this.strNum, // 必填
+            length: this.selectedText.content.length // 必填
+          }
+        ],
+        {
+          className: 'text-selected',
+          element: 'span',
+          each: (element) => {
+            // 为元素添加样式和属性
+            // element.setAttribute('id', markId)
+            element.style.borderBottom = `2px solid ${tagData.color}`
+            element.style.color = tagData.color
+
+            // 创建右上角标签元素
+            const labelElement = document.createElement('span')
+            labelElement.textContent = name
+            labelElement.style.backgroundColor = color
+            labelElement.style.float = 'right'
+            labelElement.style.color = '#fff'
+            labelElement.style.padding = '2px 4px'
+            labelElement.style.borderRadius = '3px'
+            labelElement.style.fontSize = '12px'
+            labelElement.style.lineHeight = '12px'
+            labelElement.style.cursor = 'pointer'
+
+            const container = document.createElement('div')
+            container.setAttribute('id', markId)
+            container.style.display = 'inline-block'
+            element.parentNode.insertBefore(container, element)
+            container.appendChild(element)
+            container.appendChild(labelElement)
+            // 绑定事件
+            element.onclick = (e) => {
+              this.cancelTagInfo = {
+                visible: true,
+                top: e.offsetY + 50,
+                left: e.offsetX + 50
+              }
+              this.selectMarkedId = markId
+            }
+            element.oncontextmenu = (e) => {
+              e.preventDefault() // 阻止默认右击菜单
+            }
+          }
+        }
+      )
+      this.markTextList.push({
+        entityColor: color,
+        entityName: name,
+        start: this.selectedText.start,
+        end: this.selectedText.end,
+        mark_content: this.selectedText.content,
+        mark_id: markId
+      })
+      console.log('this.markTextList', this.markTextList)
+
+      this.markRelationList.push({
+        subject: this.selectedText.content,
+        subjectClass: name,
+        subjectPosition: [this.selectedText.start, this.selectedText.end],
+        object: '',
+        objectClass: '',
+        objectPosition: [],
+        relation: ''
+      })
+      this.tagInfo.visible = false
+    },
+
+    // 取消标注
+    handleCancel() {
+      this.cancelTagInfo.visible = false
+
+      if (!this.selectMarkedId) return
+      const selectMarkItem = this.markTextList.find(list => list.mark_id === this.selectMarkedId)
+      // console.log('item', item)
+
+      // const textContainer = document.getElementById('text-container')
+      const bigContainer = document.getElementById(this.selectMarkedId)
+
+      const textSpan = bigContainer.firstElementChild.innerText
+      const markEl = new Mark(bigContainer)
+      markEl.unmark()
+      bigContainer.insertAdjacentText('beforebegin', textSpan)
+      bigContainer.remove()
+
+      this.markTextList.splice(
+        this.markTextList?.findIndex((t) => t.mark_id === this.selectMarkedId),
+        1
+      )
+      this.markRelationList.forEach((item, index) => {
+        if (item.subject === selectMarkItem.mark_content) {
+          // this.markRelationList.splice(index, 1)
+          this.markRelationList = this.markRelationList.filter(function(item) {
+            return item.subject !== selectMarkItem.mark_content
+          })
+        }
+        if (item.object === selectMarkItem.mark_content) {
+          item.object = item.objectClass = ''
+          item.objectPosition = []
+          item.relation = ''
+        }
+      })
+    },
+
+    // 选择客体
+    handleObject(selectVal, row) {
+      if (selectVal === row.subject) {
+        this.$message({
+          message: '主体与客体不能相同,请重新选择',
+          type: 'warning'
+        })
+        this.markRelationList.forEach(item => {
+          if (item === row) {
+            item.object = ''
+          }
+        })
+        return
+      }
+      const selectOptions = this.markTextList.filter((item) =>
+        selectVal.includes(item.mark_content)
+      )
+      const selectItem = selectOptions[0]
+      this.markRelationList.forEach(item => {
+        if (item === row) {
+          item.objectClass = selectItem.entityName
+          item.objectPosition = [selectItem.start, selectItem.end]
+        }
+      })
+    },
+    addRelation(row) {
+      let selectOptions = {}; let indexPos
+      this.markRelationList.forEach((item, index) => {
+        if (item === row) {
+          selectOptions = { ...row }
+          indexPos = index
+        }
+      })
+      selectOptions.object = selectOptions.objectClass = ''
+      selectOptions.objectPosition = []
+      selectOptions.relation = ''
+      this.markRelationList.splice(indexPos + 1, 0, selectOptions)
+    },
+
+    // 更改文本
+    setNewData(newData) {
+      this.$set(this, 'markData', newData)
+      this.markTextList = [] // 标注的文本清空
+      this.markRelationList = [] // 清空关系
+      document.getElementById('text-container').innerHTML = this.markData.text
+      if (this.markData.markInfo) {
+        const markData = JSON.parse(this.markData.markInfo)
+        this.markRelationList = markData
+        // 回显时将重复的去除
+        const map = new Map()
+        for (const item of markData) {
+          if (!map.has(item.subject)) {
+            map.set(item.subject, item)
+          }
+        }
+        const arr = [...map.values()]
+        arr.forEach((item, index) => {
+          this.handleBackShow(item, index)
+        })
+      }
+      this.$refs.table.setCurrentRow(newData)
+    },
+
+    // 标注回显
+    handleBackShow(itemMarkInfo, index) {
+      const marker = new Mark(document.getElementById('text-container'))
+      const entityData = this.tagLabelList.find(item => {
+        return item.name === itemMarkInfo.subjectClass
+      })
+      const markId = uuidv4()
+      marker.markRanges(
+        [
+          {
+            start: itemMarkInfo.subjectPosition[0] + index * 2, // 增加标注文本会增加”“两个字符,因此起始位置需要加上index*2
+            length: itemMarkInfo.subjectPosition[1] - itemMarkInfo.subjectPosition[0] // 必填
+          }
+        ],
+        {
+          className: 'text-selected',
+          element: 'span',
+          each: (element) => {
+            // 为元素添加样式和属性
+            // element.setAttribute('id', markId)
+            element.style.borderBottom = `2px solid ${entityData ? entityData.color : 'red'}`
+            element.style.color = entityData ? entityData.color : 'red'
+
+            // 创建右上角标签元素
+            const labelElement = document.createElement('span')
+            labelElement.textContent = itemMarkInfo.subjectClass
+            labelElement.style.backgroundColor = entityData ? entityData.color : 'red'
+            labelElement.style.float = 'right'
+            labelElement.style.color = '#fff'
+            labelElement.style.padding = '2px 4px'
+            labelElement.style.borderRadius = '3px'
+            labelElement.style.fontSize = '12px'
+            labelElement.style.lineHeight = '12px'
+            labelElement.style.cursor = 'pointer'
+
+            const container = document.createElement('div')
+            container.setAttribute('id', markId)
+            container.style.display = 'inline-block'
+            element.parentNode.insertBefore(container, element)
+            container.appendChild(element)
+            container.appendChild(labelElement)
+            // 绑定事件
+            element.onclick = (e) => {
+              this.cancelTagInfo = {
+                visible: true,
+                top: e.offsetY + 50,
+                left: e.offsetX + 50
+              }
+              this.selectMarkedId = markId
+            }
+            element.oncontextmenu = (e) => {
+              e.preventDefault() // 阻止默认右击菜单
+            }
+          }
+        }
+      )
+      this.markTextList.push({
+        entityColor: entityData ? entityData.color : 'red',
+        entityName: itemMarkInfo.subjectClass,
+        start: itemMarkInfo.subjectPosition[0],
+        end: itemMarkInfo.subjectPosition[1],
+        mark_content: itemMarkInfo.subject,
+        mark_id: markId
+      })
+    },
+
+    // 调用上一条、下一条接口
+    handlePNChange(type) {
+      const data = { id: this.markData.id, dataset_id: this.markData.dataset_id, type }
+      PNDataApi(data).then((response) => {
+        if (response.code === 2) {
+          this.$message({
+            message: response.msg,
+            type: 'warning'
+          })
+        } else {
+          this.handleCurrentChange(response.data[0])
+        }
+      })
+    },
+
+    // 上一条
+    handlePrev() {
+      const flag = this.dataList.findIndex(item => {
+        return item.id === this.markData.id
+      })
+      if (flag !== -1 && flag === 0) {
+        if (this.queryParams.page === 1) {
+          this.$message({
+            message: '已经是第一条数据',
+            type: 'warning'
+          })
+        } else {
+          this.loading = true
+          this.queryParams.page = this.queryParams.page - 1
+          getDataListApi(this.queryParams).then((response) => {
+            this.dataList = response.data
+            this.total = response.total
+            this.handlePNChange('previous')
+            this.loading = false
+          })
+        }
+      } else {
+        this.handlePNChange('previous')
+      }
+
+      // const isSave = JSON.stringify(this.markRelationList) === this.markData.markInfo
+      // if (!isSave) {
+      //   this.$confirm(`标注内容未保存,是否保存`, '提示', {
+      //     confirmButtonText: '确定',
+      //     cancelButtonText: '取消',
+      //     type: 'warning'
+      //   }).then(() => {
+      //     this.handleSave()
+      //   })
+      // } else {
+      //   this.loading = true
+      //   this.queryParams.page = this.queryParams.page - 1
+      //   getDataListApi(this.queryParams).then((response) => {
+      //     this.dataList = response.data
+      //     this.total = response.total
+      //     this.handlePNChange('previous')
+      //     this.loading = false
+      //   })
+      // }
+    },
+
+    // 下一条
+    handleNext() {
+      const flag = this.dataList.findIndex(item => {
+        return item.id === this.markData.id
+      })
+      if (flag !== -1 && flag === this.dataList.length - 1) {
+        if (Math.ceil(this.total / this.queryParams.size) === this.queryParams.page) {
+          this.$message({
+            message: '已经是最后一条数据',
+            type: 'warning'
+          })
+        } else {
+          this.loading = true
+          this.queryParams.page = this.queryParams.page + 1
+          getDataListApi(this.queryParams).then((response) => {
+            this.dataList = response.data
+            this.total = response.total
+            this.handlePNChange('next')
+            this.loading = false
+          })
+        }
+      } else {
+        this.handlePNChange('next')
+      }
+    },
+
+    // 清空标记
+    clearMark() {
+      this.$confirm(`是否清空该数据的所有标记内容?该操作不可逆`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const textContainer = document.getElementById('text-container')
+        const markEl = new Mark(textContainer)
+        // 清除所有标注
+        markEl.unmark()
+        document.getElementById('text-container').innerHTML = this.markData.text
+        this.markTextList = [] // 标注的文本清空
+        this.markRelationList = [] // 清空关系
+      })
+    },
+
+    // 保存
+    handleSave() {
+      const data = {
+        id: this.markData.id,
+        markInfo: this.markRelationList,
+        state: 1
+      }
+      updataMarkInfoApi(data).then((response) => {
+        this.$modal.msgSuccess('保存成功')
+        getDataListApi(this.queryParams).then((response) => {
+          this.dataList = response.data
+        })
+        this.handleNext()
+      })
+    },
+
+    // 删除该条关系
+    handleDeleteRelation(row) {
+      console.log('row', row)
+
+      const delIndex = this.markRelationList.findIndex(item => {
+        return item === row
+      })
+      if (delIndex !== -1) {
+        this.$confirm(`是否删除该数据?`, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.markRelationList.splice(delIndex, 1)
+          // 判断删除后,标注中还有没有该主体,没有的话在回显中也取消标注
+          const lastFlag = this.markTextList.forEach(item => {
+            if (item.mark_content === row.subject) {
+              // true代表还有
+              return true
+            }
+          })
+          if (lastFlag !== true) {
+            const selectMarkItem = this.markTextList.find(list => list.mark_content === row.subject)
+            const bigContainer = document.getElementById(selectMarkItem.mark_id)
+
+            const textSpan = bigContainer.firstElementChild.innerText
+            const markEl = new Mark(bigContainer)
+            markEl.unmark()
+            bigContainer.insertAdjacentText('beforebegin', textSpan)
+            bigContainer.remove()
+            this.markTextList = this.markTextList.filter(item => item.mark_content !== row.subject)
+          }
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import './markPage.scss';
+</style>

+ 293 - 0
src/views/data_mark/labelManage/index.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="app-container">
+    <el-form ref="queryForm" :model="queryParams" :inline="true" @submit.native.prevent>
+      <el-form-item label="实体名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入实体名称" clearable size="small" @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-button icon="el-icon-plus" type="primary" plain size="small" @click="handleAdd">添加实体类</el-button>
+    <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
+    <el-button type="success" plain size="small" icon="el-icon-download" @click="importEvent">导入</el-button>
+    <el-table
+      :data="entityList"
+      style="width: 100%"
+      row-key="entityId"
+      lazy
+      :load="load"
+      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="name" align="center" label="实体名称" />
+      <el-table-column prop="introduce" align="center" label="介绍" />
+      <el-table-column prop="color" label="颜色" align="center">
+        <template v-if="scope.row.parentId !== 0" slot-scope="scope">
+          <el-tag :color="scope.row.color" class="tag">{{ scope.row.color }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="is_show" align="center" label="是否展示">
+        <template slot-scope="scope">
+          <el-switch v-model="scope.row.is_show" active-color="#13ce66" @change="showChange(scope.row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button v-if="scope.row.parentId === 0" type="text" size="small" @click="handleAdd(scope.row)">新增</el-button>
+          <el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
+          <el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="40%" append-to-body>
+      <el-form ref="form" label-width="100px" :model="form" :rules="rules">
+        <el-form-item label="上级实体类" prop="parentId">
+          <treeselect
+            v-model="form.parentId"
+            :options="entityOptions"
+            :normalizer="normalizer"
+            :show-count="true"
+            no-options-text="暂无数据"
+            placeholder="选择上级实体类"
+          />
+        </el-form-item>
+        <el-form-item label="实体类名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入实体类名称" />
+        </el-form-item>
+        <el-form-item label="介绍" prop="introduce">
+          <el-input v-model="form.introduce" type="textarea" placeholder="请输入实体类简介" />
+        </el-form-item>
+        <el-form-item v-if="form.parentId && form.parentId !== 0" label="颜色">
+          <el-color-picker v-model="form.color" :predefine="predefineColors" />
+        </el-form-item>
+        <el-form-item label="是否展示" prop="is_show">
+          <el-switch v-model="form.is_show" active-color="#13ce66" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button :type="form.entityId ? 'warning' : 'primary'" @click="submitForm">{{ form.entityId ? '确认修改' : '确认添加' }}</el-button>
+      </div>
+    </el-dialog>
+    <handleImport ref="handleImport" url="/dataMark/importEntity" @importRes="importRes" @downloadTemplate="downloadTemplate" />
+  </div>
+</template>
+
+<script>
+import { addEntityApi, getEntityListApi, updataEntityApi, delEntityApi } from '@/api/dataMark/entity'
+import Treeselect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+import { deepClone } from '@/utils'
+import handleImport from '@/components/handleImport'
+export default {
+  name: 'LabelManage',
+  components: { Treeselect, handleImport },
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        id: null,
+        parentId: null,
+        name: null,
+        introduce: null,
+        is_show: null,
+        color: null
+      },
+      dialogVisible: false,
+      // 总条数
+      total: 0,
+      // 弹出层标题
+      title: '',
+      // 表格List
+      entityList: [],
+      // 表单参数
+      form: {},
+      rules: {},
+      // 实体树选项
+      entityOptions: [],
+      // 多选的ID
+      ids: [],
+      // 多选的name
+      names: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      predefineColors: ['#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      getEntityListApi(this.queryParams).then((response) => {
+        // 生成树结构
+        this.entityList = this.handleTree(response.data, 'entityId')
+        // 深拷贝,为treeSelect更改内容格式
+        const data = deepClone(this.entityList)
+        data.unshift({
+          name: '一级菜单',
+          entityId: 0
+        })
+        this.entityOptions = data
+        this.total = response.total
+        this.loading = false
+      })
+    },
+    // 处理弹窗树形结构
+    normalizer(data) {
+      if (data.children && !data.children.length) {
+        delete data.children
+      }
+      return {
+        id: data.entityId,
+        label: data.name,
+        children: data.children,
+        isDisabled: !!data.parentId
+      }
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        entityId: undefined,
+        parentId: undefined,
+        name: undefined,
+        is_show: true,
+        introduce: undefined,
+        color: undefined
+      }
+      this.resetForm('form')
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          if (this.form.entityId !== undefined) {
+            console.log('form', this.form)
+            updataEntityApi(this.form).then((response) => {
+              this.$modal.msgSuccess('修改成功')
+              this.dialogVisible = false
+              this.getList()
+            })
+          } else {
+            addEntityApi(this.form).then((response) => {
+              this.$modal.msgSuccess('新增成功')
+              console.log('response', response)
+              this.dialogVisible = false
+              this.getList()
+            })
+          }
+        }
+      })
+    },
+    importEvent() {
+      this.$refs.handleImport.show()
+    },
+    // 异步获取
+    load(tree, treeNode, resolve) {
+      console.log(tree, treeNode, resolve)
+      // setTimeout(() => {
+      //   resolve([
+      //   ])
+      // }, 500)
+    },
+    showChange(row) {
+      delete row.children
+      updataEntityApi(row).then((response) => {
+        this.getList()
+      })
+    },
+    handleDelete(row) {
+      let entityIds = []
+      row.entityId ? (entityIds = [row.entityId]) : (entityIds = this.ids)
+      const names = row.name || this.names
+      console.log('entityIds', entityIds)
+      this.$confirm('此操作将永久删除名为' + names + '的实体, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        await delEntityApi(entityIds)
+        // await delEntityApi(roleIds)
+        this.$message({
+          message: '删除成功!',
+          type: 'success'
+        })
+        this.getList()
+      })
+    },
+    /** 新增按钮操作 */
+    handleAdd(row) {
+      this.reset()
+      if (row) {
+        this.form.parentId = row.entityId
+      }
+      this.dialogVisible = true
+      this.title = '添加实体'
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.form = { ...row }
+      this.dialogVisible = true
+      this.title = '修改实体'
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.entityId)
+      console.log('ids', this.ids)
+      this.names = selection.map((item) => item.name)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    importRes() {
+      this.$message.success('导入成功!')
+      this.getList()
+    },
+    async downloadTemplate({ call }) {
+      // const res = await downloadTemplate()
+      // this.$downFile(res)
+      // call()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tag {
+  color: white;
+  border-radius: 20px;
+}
+.colorCell {
+  display: inline-block;
+  position: relative;
+  width: 25px;
+  height: 25px;
+  border-radius: 5px;
+  margin: 3px;
+}
+.el-icon-check {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  font-size: 18px;
+  color: black;
+}
+</style>

+ 237 - 0
src/views/data_mark/relationshipManage/ERDetail.vue

@@ -0,0 +1,237 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent>
+      <el-form-item label="实体名称" prop="entityName">
+        <el-input v-model="queryParams.entityName" placeholder="请输入实体名称" clearable size="small" @keyup.enter.native="handleQuery" />
+        <!-- <input type="text" style="display: none" /> -->
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-button icon="el-icon-back" plain size="mini" @click="goback">返回</el-button>
+    <el-button icon="el-icon-plus" type="success" plain size="mini" @click="handleAdd">添加</el-button>
+    <el-table :data="ERList" style="width: 100%" row-key="erId">
+      <el-table-column prop="subjectName" align="center" label="主体"> </el-table-column>
+      <el-table-column prop="objectName" align="center" label="客体"> </el-table-column>
+      <el-table-column prop="relationshipName" align="center" label="关系"> </el-table-column>
+      <el-table-column prop="isShow" align="center" label="是否启用">
+        <template slot-scope="scope">
+          <el-switch v-model="scope.row.isShow" active-color="#13ce66"> </el-switch>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="180">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="40%" append-to-body :close-on-click-modal="false">
+      <el-form label-width="100px" :model="form" :rules="rules" ref="form">
+        <el-form-item label="主体" prop="subjectName">
+          <treeselect
+            v-model="form.subjectId"
+            :disable-branch-nodes="true"
+            :options="entityOptions"
+            :normalizer="normalizer"
+            :show-count="true"
+            noOptionsText="暂无数据"
+            placeholder="请选择主体" />
+        </el-form-item>
+        <el-form-item label="客体" prop="objectName">
+          <treeselect
+            v-model="form.objectId"
+            :disable-branch-nodes="true"
+            :options="entityOptions"
+            :normalizer="normalizer"
+            :show-count="true"
+            noOptionsText="暂无数据"
+            placeholder="请选择客体" />
+        </el-form-item>
+        <el-form-item label="关系" prop="relationshipName">
+          <el-input v-model="form.relationshipName" placeholder="请输入关系名称" :disabled="true" />
+        </el-form-item>
+        <el-form-item label="是否展示" prop="isShow">
+          <el-switch v-model="form.isShow" active-color="#13ce66"></el-switch>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确认添加</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { addERApi, getERListApi, delERApi, getRelationshipListApi } from '@/api/dataMark/relationship'
+import { getEntityListApi } from '@/api/dataMark/entity'
+import { deepClone } from '@/utils'
+import Treeselect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+export default {
+  name: 'ERDetail',
+  components: { Treeselect },
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        subjectName: null,
+        objectName: null,
+        relationshipName: null,
+        isShow: true,
+        relationshipName: null,
+        entityName: null,
+        relationshipId: null
+      },
+      dialogVisible: false,
+      checkDialog: false,
+      // 总条数
+      total: 0,
+      // 弹出层标题
+      title: '',
+      // 表格List
+      relationshipList: [],
+      ERList: [],
+      // 表单参数
+      form: {},
+      rules: {},
+      // 多选的ID
+      ids: [],
+      // 多选的name
+      names: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      entityOptions: []
+    }
+  },
+  created() {
+    this.queryParams.relationshipId = this.$route.params.id
+    this.getList()
+  },
+  methods: {
+    getList() {
+      console.log('this.queryParams', this.queryParams)
+      this.loading = true
+      getERListApi(this.queryParams).then((response) => {
+        this.ERList = response.data.map((item, index) => ({
+          ...item,
+          number: index + 1
+        }))
+        this.total = response.total
+        this.loading = false
+      })
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        // name: undefined
+        subjectName: undefined,
+        objectName: undefined,
+        relationshipName: undefined,
+        isShow: true,
+        entityName: undefined
+      }
+      this.resetForm('form')
+    },
+    //处理弹窗树形结构
+    normalizer(data) {
+      if (data.children && !data.children.length) {
+        delete data.children
+      }
+      return {
+        id: data.entityId,
+        label: data.name,
+        children: data.children
+        // isDisabled: data.parentId === 0 ? true : false
+      }
+    },
+    /** 提交按钮 */
+    submitForm() {
+      console.log('this.form', this.form)
+      addERApi(this.form).then((response) => {
+        this.$modal.msgSuccess('新增成功')
+        console.log('response', response)
+        this.dialogVisible = false
+        this.getList()
+      })
+    },
+    // 删除实体-关系
+    handleDelete(row) {
+      this.$confirm('此操作将永久删除主体为 “' + row.subjectName + '”、客体为“' + row.objectName + '” 的关系, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async () => {
+        await delERApi([row.ERId])
+        this.$message({
+          message: '删除成功!',
+          type: 'success'
+        })
+        this.getList()
+      })
+    },
+    // 返回
+    goback() {
+      this.$router.push({ name: 'relationshipManage' })
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset()
+      this.dialogVisible = true
+      this.title = '添加关系'
+      this.getRelationship()
+      this.getEntityList()
+    },
+    getRelationship() {
+      let data = {}
+      getRelationshipListApi(data).then((response) => {
+        let findData = response.data.find((item) => {
+          return item.relationshipId == this.queryParams.relationshipId
+        })
+        this.form.relationshipName = findData.name
+        this.form.relationshipId = findData.relationshipId
+      })
+    },
+    getEntityList() {
+      let data = {}
+      this.loading = true
+      getEntityListApi(data).then((response) => {
+        // 生成树结构
+        this.entityList = this.handleTree(response.data, 'entityId')
+        // 深拷贝,为treeSelect更改内容格式
+        this.entityOptions = deepClone(this.entityList)
+        this.loading = false
+      })
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.form = { ...row }
+      this.dialogVisible = true
+      this.title = '关系'
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.relationshipId)
+      this.names = selection.map((item) => item.name)
+      this.single = selection.length != 1
+      this.multiple = !selection.length
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 179 - 0
src/views/data_mark/relationshipManage/index.vue

@@ -0,0 +1,179 @@
+<template>
+  <div class="app-container">
+    <el-form ref="queryForm" :model="queryParams" :inline="true" @submit.native.prevent>
+      <el-form-item label="关系名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入关系名称" clearable size="small" @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-button icon="el-icon-plus" type="primary" plain size="small" @click="handleAdd">添加关系</el-button>
+    <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
+    <!-- <el-button type="success" plain size="small" icon="el-icon-download" @click="importEvent">导入</el-button> -->
+    <el-table :data="relationshipList" style="width: 100%" row-key="relationshipId" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="number" align="center" label="序号" />
+      <el-table-column prop="name" align="center" label="关系名称" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="handleCheck(scope.row)">查看该关系的实体</el-button>
+          <el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
+          <el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
+
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="40%" append-to-body :close-on-click-modal="false">
+      <el-form ref="form" label-width="100px" :model="form" :rules="rules">
+        <el-form-item label="关系名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入关系名称" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button :type="form.relationshipId ? 'warning' : 'primary'" @click="submitForm">{{
+          form.relationshipId ? '确认修改' : '确认添加'
+        }}</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { addRelationshipApi, getRelationshipListApi, updataRelationshipApi, delRelationshipApi } from '@/api/dataMark/relationship'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+export default {
+  name: 'RelationshipManage',
+  components: {},
+  data() {
+    return {
+      queryParams: {
+        page: 1,
+        size: 10,
+        relationshipId: null,
+        name: null
+      },
+      dialogVisible: false,
+      checkDialog: false,
+      // 总条数
+      total: 0,
+      // 弹出层标题
+      title: '',
+      // 表格List
+      relationshipList: [],
+      ERList: [],
+      // 表单参数
+      form: {},
+      rules: {},
+      // 多选的ID
+      ids: [],
+      // 多选的name
+      names: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      getRelationshipListApi(this.queryParams).then((response) => {
+        this.relationshipList = response.data.map((item, index) => ({
+          ...item,
+          number: index + 1
+        }))
+        this.total = response.total
+        this.loading = false
+      })
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        relationshipId: undefined,
+        name: undefined
+      }
+      this.resetForm('form')
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          if (this.form.relationshipId !== undefined) {
+            updataRelationshipApi(this.form).then((response) => {
+              this.$modal.msgSuccess('修改成功')
+              this.dialogVisible = false
+              this.getList()
+            })
+          } else {
+            addRelationshipApi(this.form).then((response) => {
+              this.$modal.msgSuccess('新增成功')
+              console.log('response', response)
+              this.dialogVisible = false
+              this.getList()
+            })
+          }
+        }
+      })
+    },
+    // 删除关系
+    handleDelete(row) {
+      let relationshipIds = []
+      row.relationshipId ? (relationshipIds = [row.relationshipId]) : (relationshipIds = this.ids)
+      const names = row.name || this.names
+      console.log('relationshipIds', relationshipIds)
+      this.$confirm('此操作将永久删除名为 ' + names + ' 的关系, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        await delRelationshipApi(relationshipIds)
+        this.$message({
+          message: '删除成功!',
+          type: 'success'
+        })
+        this.getList()
+      })
+    },
+    // 查看该关系的实体
+    handleCheck(row) {
+      this.$router.push({ name: 'ERDetail', params: { id: row.relationshipId }})
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset()
+      this.dialogVisible = true
+      this.title = '添加关系'
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.form = { ...row }
+      this.dialogVisible = true
+      this.title = '关系'
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.relationshipId)
+      this.names = selection.map((item) => item.name)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 0 - 182
src/views/label/entity.vue

@@ -1,182 +0,0 @@
-<template>
-  <div class="app-container">
-    <el-button
-      icon="el-icon-plus"
-      type="primary"
-      plain
-      size="small"
-      @click="openEntity(0)"
-      >添加实体类</el-button
-    >
-    <el-table
-      :data="entityList"
-      style="width: 100%"
-      row-key="id"
-      lazy
-      :load="load"
-      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
-      <el-table-column prop="name" label="实体名称"> </el-table-column>
-      <el-table-column prop="introduce" align="center" label="介绍">
-      </el-table-column>
-      <el-table-column prop="is_show" align="center" label="是否展示">
-        <template slot-scope="scope">
-          <el-switch v-model="scope.row.is_show" active-color="#13ce66">
-          </el-switch>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center">
-        <template slot-scope="scope">
-          <el-button type="text" size="small" @click="openEntity(1)"
-            >新增</el-button
-          >
-          <el-button
-            size="mini"
-            type="text"
-            @click="handleEdit(scope.$index, scope.row)"
-            >编辑</el-button
-          >
-          <el-button
-            size="mini"
-            type="text"
-            @click="handleDelete(scope.$index, scope.row)"
-            >删除</el-button
-          >
-        </template>
-      </el-table-column>
-    </el-table>
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      :page.sync="queryParams.page"
-      :limit.sync="queryParams.size"
-      @pagination="getList" />
-
-    <el-dialog
-      :title="title"
-      :visible.sync="dialogVisible"
-      width="30%"
-      append-to-body>
-      <el-form
-        class="demo-form-inline"
-        label-width="100px"
-        :model="form"
-        :rules="rules"
-        ref="form">
-        <el-form-item label="上级实体类" prop="parentId">
-          <treeselect
-            v-model="form.parentId"
-            :options="entityList"
-            :normalizer="normalizer"
-            placeholder="选择上级实体类" />
-        </el-form-item>
-        <el-form-item label="实体类名称" prop="name">
-          <el-input v-model="form.name" placeholder="请输入实体类名称" />
-        </el-form-item>
-        <el-form-item label="介绍" prop="introduce">
-          <el-input
-            type="textarea"
-            v-model="form.introduce"
-            placeholder="请输入实体类简介" />
-        </el-form-item>
-        <el-form-item label="是否展示" prop="is_show">
-          <el-switch v-model="form.is_show" active-color="#13ce66"> </el-switch>
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="dialogVisible = false">取 消</el-button>
-        <el-button
-          :type="form.id ? 'warning' : 'primary'"
-          @click="dialogVisible = false"
-          >{{ form.id ? '确认修改' : '确认添加' }}</el-button
-        >
-      </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-import { addBigEntity, getBigEntityList } from '@/api/annotations/bigEntity'
-import Treeselect from '@riophae/vue-treeselect'
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
-export default {
-  name: 'Entity',
-  components: { Treeselect },
-  data() {
-    return {
-      queryParams: {
-        page: 1,
-        size: 10,
-        parentId: null,
-        name: null,
-        introduce: null,
-        is_show: null
-      },
-      dialogVisible: false,
-      // 总条数
-      total: 0,
-      // 弹出层标题
-      title: '',
-      entityList: [],
-      // 表单参数
-      form: {},
-      rules: {},
-      // 实体树选项
-      entityOptions: []
-    }
-  },
-  created() {
-    this.getList()
-  },
-  methods: {
-    getList() {
-      this.loading = true
-      getBigEntityList(this.queryParams).then((response) => {
-        // this.entityList = response.data
-        this.entityList = response.data.map((item) => ({
-          ...item,
-          hasChildren: item.hasChildren === 1 // 或者使用 Boolean(item.hasChildren) 如果hasChildren是数字类型
-        }))
-        this.total = response.total
-        this.loading = false
-      })
-    },
-    // 异步获取
-    load(tree, treeNode, resolve) {
-      console.log(tree, treeNode, resolve)
-      // setTimeout(() => {
-      //   resolve([
-      //   ])
-      // }, 500)
-    },
-    handleEdit(index, row) {
-      console.log(index, row)
-    },
-    handleDelete() {
-      console.log(index, row)
-    },
-    openEntity(flag) {
-      this.dialogVisible = true
-      this.title = flag === 1 ? '新增实体' : '增加实体类'
-      console.log(this.queryParams)
-      // addBigEntity(data1).then(response => {
-      //   console.log("response",response);
-      //   this.loading = false;
-      // });
-      // this.moreArr = data;
-      // this.total=total;
-    },
-    normalizer(node) {
-      if (node.children && !node.children.length) {
-        delete node.children
-      }
-      return {
-        id: node.Id,
-        label: node.name,
-        children: node.children
-      }
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped></style>

+ 98 - 96
src/views/test/index.vue

@@ -1,48 +1,50 @@
 <template>
   <div class="box">
     <el-form
-        :model="queryParams"
-        ref="queryForm"
-        :inline="true"
+      ref="queryForm"
+      :model="queryParams"
+      :inline="true"
     >
       <el-form-item label="测试账号名称" prop="name">
         <el-input
-            v-model="queryParams.name"
-            placeholder="请输入名称"
-            clearable
-            size="small"
+          v-model="queryParams.name"
+          placeholder="请输入名称"
+          clearable
+          size="small"
         />
       </el-form-item>
       <el-form-item>
         <el-button
-            type="primary"
-            icon="el-icon-search"
-            size="small"
-            @click="handleQuery"
+          type="primary"
+          icon="el-icon-search"
+          size="small"
+          @click="handleQuery"
         >搜索</el-button>
       </el-form-item>
     </el-form>
     <el-button icon="el-icon-plus" type="primary" plain size="small" @click="openDialog">添加测试账号</el-button>
     <el-button
-        type="success"
-        plain size="small"
-        icon="el-icon-download"
-        @click="importEvent"
+      type="success"
+      plain
+      size="small"
+      icon="el-icon-download"
+      @click="importEvent"
     >导入</el-button>
     <el-button
-        v-loading="addLoading"
-        type="info"
-        plain size="small"
-        icon="el-icon-upload2"
-        @click="exportEvent"
+      v-loading="addLoading"
+      type="info"
+      plain
+      size="small"
+      icon="el-icon-upload2"
+      @click="exportEvent"
     >导出</el-button>
-    <el-table :data="moreArr" style="margin-top: 15px" v-loading="loading">
+    <el-table v-loading="loading" :data="moreArr" style="margin-top: 15px">
       <el-table-column label="编号" align="center" width="100" prop="id" />
       <el-table-column label="测试账号名称" align="center" prop="name" />
       <el-table-column label="备注" align="center" prop="remark" />
       <el-table-column label="更新时间" align="center" prop="updateTime" />
       <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="操作" align="center" >
+      <el-table-column label="操作" align="center">
         <template slot-scope="scope">
           <el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
           <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
@@ -51,15 +53,15 @@
     </el-table>
 
     <pagination
-        v-show="total > 0"
-        :total="total"
-        :page.sync="queryParams.page"
-        :limit.sync="queryParams.size"
-        @pagination="getTests"
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.page"
+      :limit.sync="queryParams.size"
+      @pagination="getTests"
     />
 
     <el-dialog title="操作框" :visible.sync="dialogVisible" width="40%">
-      <el-form class="demo-form-inline" label-width="100px" :model="form" :rules="rules" ref="ruleForm">
+      <el-form ref="ruleForm" class="demo-form-inline" label-width="100px" :model="form" :rules="rules">
         <el-form-item label="账号名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入测试账号名称" />
         </el-form-item>
@@ -75,114 +77,114 @@
         }}</el-button>
       </div>
     </el-dialog>
-    <handleImport ref="handleImport" url="/tests/importTests" @importRes="importRes" @downloadTemplate="downloadTemplate"></handleImport>
+    <handleImport ref="handleImport" url="/tests/importTests" @importRes="importRes" @downloadTemplate="downloadTemplate" />
   </div>
 </template>
 <script>
-import { addTests, getTests, upTests, delTests ,downloadTemplate,exportTest} from "@/api/tests";
-import handleImport from "@/components/handleImport";
+import { addTests, getTests, upTests, delTests, downloadTemplate, exportTest } from '@/api/tests'
+import handleImport from '@/components/handleImport'
 export default {
-  name:"TestMore",
-  data () {
+  name: 'TestMore',
+  components: {
+    handleImport
+  },
+  data() {
     return {
-      queryParams:{
-        page:1,
-        size:10
+      queryParams: {
+        page: 1,
+        size: 10
       },
-      total:0,
-      loading:false,
-      addLoading:false,
+      total: 0,
+      loading: false,
+      addLoading: false,
       moreArr: [],
       dialogVisible: false,
       form: {
-        name: "",
-        remark: ""
+        name: '',
+        remark: ''
       },
-      search: "",
+      search: '',
       rules: {
         name: [
           { required: true, message: '请输入测试账号名称', trigger: 'blur' }
         ]
-      },
+      }
     }
   },
-  created () {
-    this.getTests();
+  created() {
+    this.getTests()
   },
   methods: {
-    async getTests () {
-      this.loading=true;
-      let {data,total} = await getTests(this.queryParams);
-      this.loading=false;
-      this.moreArr = data;this.total=total;
+    async getTests() {
+      this.loading = true
+      const { data, total } = await getTests(this.queryParams)
+      this.loading = false
+      this.moreArr = data; this.total = total
     },
-    handleQuery(){
-      this.queryParams.page=1;
-      this.getTests();
+    handleQuery() {
+      this.queryParams.page = 1
+      this.getTests()
     },
-    async affirm () {
-      this.$refs['ruleForm'].validate(async (valid) => {
-        if (!valid) return;
+    async affirm() {
+      this.$refs['ruleForm'].validate(async(valid) => {
+        if (!valid) return
         try {
-          this.addLoading=true;
-          !this.form.id&& await addTests(this.form);
-          this.form.id&&await upTests({ ...this.form });
-          this.addLoading=false;
-          this.$message.success(this.form.id?"修改成功!":"添加成功!")
-          this.getTests();
-          this.dialogVisible = false;
-        }catch (e) {
-          this.addLoading=false;
+          this.addLoading = true
+          !this.form.id && await addTests(this.form)
+          this.form.id && await upTests({ ...this.form })
+          this.addLoading = false
+          this.$message.success(this.form.id ? '修改成功!' : '添加成功!')
+          this.getTests()
+          this.dialogVisible = false
+        } catch (e) {
+          this.addLoading = false
         }
-      });
+      })
     },
-    openDialog () {
-      this.form = {};
-      this.dialogVisible = true;
+    openDialog() {
+      this.form = {}
+      this.dialogVisible = true
     },
-    handleEdit (i, row) {
-      this.form={...row};
-      this.dialogVisible = true;
+    handleEdit(i, row) {
+      this.form = { ...row }
+      this.dialogVisible = true
     },
-    handleDelete (index, row) {
+    handleDelete(index, row) {
       this.$confirm('此操作将永久删除该测试账号, 是否继续?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'error'
-      }).then(async () => {
-        await delTests({ id: row.id });
-        this.getTests();
+      }).then(async() => {
+        await delTests({ id: row.id })
+        this.getTests()
         this.$message({
           message: '删除成功!',
           type: 'success'
-        });
+        })
       })
     },
-    importEvent(){
-      this.$refs.handleImport.show();
+    importEvent() {
+      this.$refs.handleImport.show()
     },
-    async exportEvent(){
+    async exportEvent() {
       try {
-        this.addLoading=true;
-        let res=await exportTest(this.queryParams);
-        this.addLoading=false;
-        this.$downFile(res);
-      }catch (e) {
-        this.addLoading=false;
+        this.addLoading = true
+        const res = await exportTest(this.queryParams)
+        this.addLoading = false
+        this.$downFile(res)
+      } catch (e) {
+        this.addLoading = false
       }
     },
-    importRes(){
-      this.$message.success("导入成功!");
-      this.getTests();
+    importRes() {
+      this.$message.success('导入成功!')
+      this.getTests()
     },
-    async downloadTemplate({call}){
-      let res=await downloadTemplate();
-      this.$downFile(res);
-      call();
+    async downloadTemplate({ call }) {
+      const res = await downloadTemplate()
+      this.$downFile(res)
+      call()
     }
-  },
-  components:{
-    handleImport
   }
 }
 </script>

+ 4 - 3
vue.config.js

@@ -13,7 +13,7 @@ const name = defaultSettings.title || '后台管理' // page title
 // For example, Mac: sudo npm run
 // You can change the port by the following method:
 // port = 9527 npm run dev OR npm run dev --port = 9527
-const port = process.env.port || process.env.npm_config_port || 2024 // dev port
+const port = process.env.port || process.env.npm_config_port || 8899 // dev port
 
 // All configuration item explanations can be find in https://cli.vuejs.org/config/
 module.exports = {
@@ -32,7 +32,7 @@ module.exports = {
   devServer: {
     host: '0.0.0.0',
     port: port,
-    open: true,
+    open: false,
     overlay: {
       warnings: false,
       errors: true
@@ -47,7 +47,8 @@ module.exports = {
       alias: {
         '@': resolve('src')
       }
-    }
+    },
+    devtool:'source map'
   },
   chainWebpack(config) {
     // it can improve the speed of the first screen, it is recommended to turn on preload

Некоторые файлы не были показаны из-за большого количества измененных файлов