Rmengdi 4 ماه پیش
والد
کامیت
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

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است