Browse Source

用户导入,物品导入

wxxwjef 2 năm trước cách đây
mục cha
commit
a038772795

+ 10 - 3
api/src/main/java/com/wx/application/adapter/controller/GorseController.java

@@ -4,6 +4,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.alibaba.fastjson.JSONObject;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +22,7 @@ import com.wx.application.nebula.graph.service.ImportGraphService;
 import com.wx.application.nebula.graph.service.NebulaOperateService;
 
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
 
 @Slf4j
 @RequestMapping("/gorse")
@@ -116,9 +118,14 @@ public class GorseController extends BaseController {
         return success(gorseService.getRecommendByUser(gQo.getUserId(), gQo.getRecommendation(), gQo.getCategory(), gQo.getN()));
     }
 
-    @PostMapping(value = "/get_similar_item")
-    public ResponseData getSimilarItem(@RequestBody GorseQ gQo) throws Exception {
-        return success(gorseService.getSimilarItem(gQo.getItemId(), gQo.getCategory(), gQo.getN()));
+    @PutMapping(value = "/bulk/{type}")
+    public ResponseData bulkUser(@RequestParam Map<String, String> data, @RequestParam("file") MultipartFile file, @PathVariable("type") String type) throws Exception {
+        return success(gorseService.bulkUserOrItem(data, file, type));
+    }
+
+    @GetMapping(value = "/get_bulk_user")
+    public ResponseData getBulkUser() throws Exception {
+        return success(gorseService.getBulkUser());
     }
 
 

+ 54 - 0
api/src/main/java/com/wx/application/gorse4j/GorseService.java

@@ -1,17 +1,32 @@
 package com.wx.application.gorse4j;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+
 import java.io.DataOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
 
 @Service
 public class GorseService {
@@ -95,6 +110,45 @@ public class GorseService {
         return Arrays.asList(this.request("GET", url, null, Item[].class));
     }
 
+    /**
+     * 文件方式导入用户或者物品users\items
+     */
+    public JSONObject bulkUserOrItem(Map<String, String> data, MultipartFile multipartFile, String type) throws IOException {
+        File file = new File("path/to/file");
+        FileUtils.writeByteArrayToFile(file, multipartFile.getBytes());
+
+        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+        for (Map.Entry<String, String> entry : data.entrySet()) {
+            builder.addTextBody(entry.getKey(), entry.getValue());
+        }
+
+        builder.addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, multipartFile.getOriginalFilename());
+        return this.sendPostFormData(this.endpoint + "/api/bulk/" + type, builder);
+    }
+
+
+    public File getBulkUser() throws IOException {
+        return this.request("GET", this.endpoint + "/api/bulk/users", null, File.class);
+    }
+
+    public JSONObject sendPostFormData(String url, MultipartEntityBuilder builder) throws IOException {
+        JSONObject resJson;
+        HttpClient httpClient = HttpClientBuilder.create().build();
+        HttpPost httpPost = new HttpPost(url);
+
+        HttpEntity entity = builder.build();
+        httpPost.setEntity(entity);
+
+        HttpResponse response = null;
+        try {
+            response = httpClient.execute(httpPost);
+            HttpEntity responseEntity = response.getEntity();
+            resJson = JSONObject.parseObject(EntityUtils.toString(responseEntity));
+        } catch (IOException e) {
+            throw e;
+        }
+        return resJson;
+    }
 
     private <Request, Response> Response request(String method, String url, Request request, Class<Response> responseClass) throws IOException {
         HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();

+ 89 - 78
web/package-lock.json

@@ -2139,6 +2139,16 @@
           "integrity": "sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=",
           "dev": true
         },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
         "cacache": {
           "version": "13.0.1",
           "resolved": "https://registry.npm.taobao.org/cacache/download/cacache-13.0.1.tgz?cache=0&sync_timestamp=1616431241238&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacache%2Fdownload%2Fcacache-13.0.1.tgz",
@@ -2165,6 +2175,34 @@
             "unique-filename": "^1.1.1"
           }
         },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true,
+          "optional": true
+        },
         "cssnano": {
           "version": "4.1.11",
           "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@@ -2177,6 +2215,25 @@
             "postcss": "^7.0.0"
           }
         },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true,
+          "optional": true
+        },
+        "loader-utils": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz",
+          "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
@@ -2193,6 +2250,16 @@
             "minipass": "^3.1.1"
           }
         },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
         "terser-webpack-plugin": {
           "version": "2.3.8",
           "resolved": "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-2.3.8.tgz",
@@ -2209,6 +2276,18 @@
             "terser": "^4.6.12",
             "webpack-sources": "^1.4.3"
           }
+        },
+        "vue-loader-v16": {
+          "version": "npm:vue-loader@16.8.3",
+          "resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-16.8.3.tgz",
+          "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "chalk": "^4.1.0",
+            "hash-sum": "^2.0.0",
+            "loader-utils": "^2.0.0"
+          }
         }
       }
     },
@@ -9917,6 +9996,11 @@
       "integrity": "sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8=",
       "dev": true
     },
+    "papaparse": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz",
+      "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
+    },
     "parallel-transform": {
       "version": "1.2.0",
       "resolved": "https://registry.npm.taobao.org/parallel-transform/download/parallel-transform-1.2.0.tgz",
@@ -14521,85 +14605,12 @@
         }
       }
     },
-    "vue-loader-v16": {
-      "version": "npm:vue-loader@16.8.3",
-      "resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-16.8.3.tgz",
-      "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
-      "dev": true,
-      "optional": true,
+    "vue-papa-parse": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/vue-papa-parse/-/vue-papa-parse-3.1.0.tgz",
+      "integrity": "sha512-5YdF3Dtf49EGfaz3+IgIpUw9yYuvV3HekZkob6jrK/Ffz1aCrWjevtcQByKxrNtK7RAL39B0ca93bogKuiQQKg==",
       "requires": {
-        "chalk": "^4.1.0",
-        "hash-sum": "^2.0.0",
-        "loader-utils": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
-          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
-          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-          "dev": true,
-          "optional": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-          "dev": true,
-          "optional": true
-        },
-        "loader-utils": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz",
-          "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
-          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
+        "papaparse": "^5.3.0"
       }
     },
     "vue-quill-editor": {

+ 1 - 0
web/package.json

@@ -32,6 +32,7 @@
     "vue": "^2.6.11",
     "vue-axios": "^3.2.4",
     "vue-json-editor": "^1.4.3",
+    "vue-papa-parse": "^3.1.0",
     "vue-quill-editor": "^3.0.6",
     "vuex": "^3.1.0"
   },

+ 6 - 0
web/src/components/menus/EntrysManage.vue

@@ -19,6 +19,7 @@
       </div>
       <div style="margin: 0px 0 20px 0">
         <el-button type="primary" @click="createRow()" icon="el-icon-plus">添 加</el-button>
+        <el-button type="primary" @click="showImportItemPage()" icon="el-icon-upload2">导入条目</el-button>
       </div>
       <div>
         <el-table  :data="result.records" style="width: 100%">
@@ -217,6 +218,11 @@ export default {
         }
       });
     },
+    showImportItemPage() {
+      _this.$router.push({
+        path: "importItem"
+      });
+    },
     removeRow(item) {
       _this.$confirm('此操作将永久删除记录, 是否继续?', '提示', {
         confirmButtonText: '确定',

+ 8 - 0
web/src/components/menus/UserManage.vue

@@ -16,6 +16,7 @@
       </div>
       <div style="margin: 0px 0 20px 0">
         <el-button type="primary" @click="createRow()" icon="el-icon-plus">添 加</el-button>
+        <el-button type="primary" @click="downloadUser()" icon="el-icon-download">导出用户</el-button>
         <el-button type="primary" @click="showImportUserPage()" icon="el-icon-upload2">导入用户</el-button>
       </div>
       <div>
@@ -137,6 +138,13 @@ export default {
         path: "importUser"
       });
     },
+    downloadUser() {
+      request({
+        url: '/gorse/get_bulk_user',
+        method: 'get'
+      }).then(res => {
+      });
+    },
     linkUser(item) {
       _this.$router.push({
         path: "linkUser", query: {

+ 322 - 0
web/src/components/menus/item/ImportItem.vue

@@ -0,0 +1,322 @@
+<template>
+  <div>
+    <div class="menu-title">
+      导入条目
+      <div class="icon icon-back" @click="backBtn">
+        <i title="返回"></i>
+      </div>
+    </div>
+    <div class="block_box" style="margin-top: 20px;padding: 20px">
+      <el-form ref="cmd" label-width="100px" :rules="rules" :model="cmd" :inline="true">
+        <el-form-item label="文件" prop="file">
+          <el-upload
+              class="upload-demo"
+              action="#"
+              :auto-upload="false"
+              accept=".csv"
+              :on-change="handleFileChange"
+              :limit="1"
+              :show-file-list="false">
+            <el-button size="small" type="primary">点击上传</el-button>
+            <div slot="tip" class="el-upload__tip">只能上传csv文件</div>
+          </el-upload>
+        </el-form-item>
+        <br/>
+        <el-form-item label="字段分隔符" prop="field">
+          <el-input v-model="cmd.field" placeholder="请输入字段分隔符" style="width: 300px"
+                    @change="handleFieldChange"></el-input>
+        </el-form-item>
+        <el-form-item label="类别分隔符" prop="categories">
+          <el-input v-model="cmd.categories" placeholder="请输入类别分隔符" style="width: 300px"></el-input>
+        </el-form-item>
+        <br/>
+        <el-form-item label="唯一ID" prop="id">
+          <el-select v-model="cmd.id" style="width: 300px" @change="refreshList">
+            <template v-if="cmd.ifFirstHead">
+              <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+            <template v-if="!cmd.ifFirstHead">
+              <el-option :value="index" :label="index" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="是否隐藏" prop="labels">
+          <el-select v-model="cmd.hidden" style="width: 300px" @change="refreshList">
+            <template v-if="cmd.ifFirstHead">
+              <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+            <template v-if="!cmd.ifFirstHead">
+              <el-option :value="index" :label="index" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="类别" prop="labels">
+          <el-select v-model="cmd.categoriesColumn" style="width: 300px" @change="refreshList">
+            <template v-if="cmd.ifFirstHead">
+              <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+            <template v-if="!cmd.ifFirstHead">
+              <el-option :value="index" :label="index" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="日期" prop="labels">
+          <el-select v-model="cmd.timestamp" style="width: 300px" @change="refreshList">
+            <template v-if="cmd.ifFirstHead">
+              <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+            <template v-if="!cmd.ifFirstHead">
+              <el-option :value="index" :label="index" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="标签" prop="labels">
+          <el-select v-model="cmd.labels" style="width: 300px" @change="refreshList">
+            <template v-if="cmd.ifFirstHead">
+              <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+            <template v-if="!cmd.ifFirstHead">
+              <el-option :value="index" :label="index" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="描述" prop="labels">
+          <el-select v-model="cmd.description" style="width: 300px" @change="refreshList">
+            <template v-if="cmd.ifFirstHead">
+              <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+            <template v-if="!cmd.ifFirstHead">
+              <el-option :value="index" :label="index" v-for="(item,index) in columnList" :key="index"></el-option>
+            </template>
+          </el-select>
+        </el-form-item>
+        <br/>
+        <el-form-item label=" ">
+          <el-checkbox label="第一行为表头" v-model="cmd.ifFirstHead" @change="refreshList"></el-checkbox>
+        </el-form-item>
+      </el-form>
+      <el-table :data="list" style="width: 100%" height="400" v-show="list.length > 0">
+        <el-table-column type="index" label="行号" width="60"></el-table-column>
+        <el-table-column prop="id" :label="'ID('+(cmd.ifFirstHead ? columnList[cmd.id] : cmd.id)+')'"></el-table-column>
+        <el-table-column prop="hidden"
+                         :label="'是否隐藏('+(cmd.ifFirstHead ? columnList[cmd.hidden] : cmd.hidden)+')'"></el-table-column>
+        <el-table-column prop="categoriesColumn"
+                         :label="'类别('+(cmd.ifFirstHead ? columnList[cmd.categoriesColumn] : cmd.categoriesColumn)+')'">
+          <template v-slot="scope">
+            <el-tag v-for="item in scope.row.categoriesColumn" :key="item" v-show="item.length > 0">{{ item }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="timestamp"
+                         :label="'日期('+(cmd.ifFirstHead ? columnList[cmd.timestamp] : cmd.timestamp)+')'"></el-table-column>
+        <el-table-column prop="labels" :label="'标签('+(cmd.ifFirstHead ? columnList[cmd.labels] : cmd.labels)+')'">
+          <template v-slot="scope">
+            <label style="color: red" v-if="scope.row.errorFlag">JSON格式化异常</label>
+            <label v-if="!scope.row.errorFlag">{{ scope.row.labels }}</label>
+          </template>
+        </el-table-column>
+        <el-table-column prop="description"
+                         :label="'描述('+(cmd.ifFirstHead ? columnList[cmd.description] : cmd.description)+')'"></el-table-column>
+      </el-table>
+      <el-button type="primary" class="submit-btn" @click="confirmSubmit" :disabled="list.length === 0">确认提交
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request';
+
+var _this;
+export default {
+  name: "ImportUser",
+  data() {
+    return {
+      config: /* Papa Parse config object */ {
+        delimiter: "", // auto-detect
+        newline: "", // auto-detect
+        quoteChar: '"',
+        escapeChar: '"',
+        header: true,
+        dynamicTyping: true,
+        preview: 0,
+        encoding: "",
+        delimitersToGuess: [',']
+        // ?? callback function ??
+      },
+      cmd: {
+        field: ',',
+        categories: '|',
+        id: '',
+        hidden: '',
+        timestamp: '',
+        categoriesColumn: '',
+        description: '',
+        labels: '',
+        ifFirstHead: true
+      },
+      file: [],
+      originList: [], // 原始的可能包含表头的数据,默认存储最多21行(如果数据不止21行的话)
+      list: [], // 页面table显示的数据
+      columnList: [],// 上传csv的表头
+      rules: {
+        fid: [
+          {required: true, message: '请选择文件'}
+        ],
+        field: [
+          {required: true, message: '字段分隔符不能为空'}
+        ],
+        categories: [
+          {required: true, message: '类别分隔符不能为空'}
+        ],
+      },
+    }
+  },
+  mounted() {
+    _this = this;
+  },
+  methods: {
+    handleFileChange(file, fileList) {
+      _this.file = file.raw;
+      if(_this.cmd.field.length > 0) {
+        _this.handleFieldChange();
+      }
+    },
+    handleFieldChange() {
+      this.$papa.parse(_this.file, {
+        delimiter: _this.cmd.field,
+        complete: (results) => {
+          _this.originList = results.data.slice(0, 21);
+          // 提前填充一个默认的值给对应的ID列和标签列
+          _this.prepareFieldAndLabels();
+          _this.refreshList();
+        }
+      })
+    },
+    prepareFieldAndLabels() {
+      _this.columnList = _this.originList[0];
+      _this.cmd.id = 0;
+      _this.cmd.hidden = 1;
+      _this.cmd.categoriesColumn = 2;
+      _this.cmd.timestamp = 3;
+      _this.cmd.labels = 4;
+      _this.cmd.description = 5;
+    },
+    refreshList() {
+      // 处理原始数据,将行文本截取成数组放入list
+      _this.list = [];
+      if (_this.cmd.field.length === 0) {
+        return;
+      }
+
+      for (let i in _this.originList) {
+        if (_this.cmd.ifFirstHead && i == 0) {
+          // 第一行是表头,跳过list塞入
+          continue;
+        }
+        let row = _this.originList[i];
+        let errorFlag = false;
+
+        let _labels = row[_this.cmd.labels].replaceAll('""', "'");
+        try {
+          JSON.parse(_labels);
+        } catch (e) {
+          errorFlag = true;
+        }
+        let categoriesArr = row[_this.cmd.categoriesColumn].split(_this.cmd.categories);
+        _this.list.push({
+          id: row[_this.cmd.id],
+          labels: _labels,
+          hidden: row[_this.cmd.hidden],
+          categoriesColumn: categoriesArr,
+          timestamp: row[_this.cmd.timestamp],
+          description: row[_this.cmd.description],
+          errorFlag: errorFlag
+        });
+      }
+    },
+    confirmSubmit() { // 提交确认
+      this.$refs.cmd.validate((valid) => {
+        let formData = new FormData();
+        formData.append("sep", _this.cmd.field);
+        formData.append("has-header", _this.cmd.ifFirstHead);
+        formData.append("label-sep", _this.cmd.categories);
+        formData.append("file", _this.file);
+        let idIndex = _this.cmd.id;
+        let labelIndex = _this.cmd.labels;
+        let hiddenIndex = _this.cmd.hidden;
+        let categoriesColumnIndex = _this.cmd.categoriesColumn;
+        let timestampIndex = _this.cmd.timestamp;
+        let descriptionIndex = _this.cmd.description;
+        let _format = '';
+        for (let i = 0; i <= Math.max(idIndex, labelIndex, hiddenIndex, categoriesColumnIndex, timestampIndex, descriptionIndex); i++) {
+          if (i == idIndex) {
+            _format += "i";
+          } else if (i == hiddenIndex) {
+            _format += "h";
+          } else if (i == categoriesColumnIndex) {
+            _format += "c";
+          } else if (i == timestampIndex) {
+            _format += "t";
+          } else if (i == labelIndex) {
+            _format += "l";
+          } else if (i == descriptionIndex) {
+            _format += "d";
+          } else {
+            _format += "_";
+          }
+        }
+        console.log(_format)
+        formData.append("format", _format);
+        // request({
+        //   url: '/gorse/bulk/items',
+        //   method: 'put',
+        //   data: formData
+        // }).then(res => {
+        //   if(res.data && res.data.RowAffected) {
+        //     _this.$message.success(`成功导入${res.data.RowAffected}条记录`);
+        //   } else {
+        //     _this.$message.warning("导入异常");
+        //   }
+        // });
+      });
+    },
+    backBtn() {
+      this.$router.push({
+        path: "entrysManage", query: {
+          qo: JSON.stringify({
+            pageNo: 1,
+            pageSize: 10,
+            LIKES_fid: '',
+          })
+        }
+      });
+    },
+
+  }
+}
+</script>
+<style src="../../../css/back.css" scoped></style>
+<style scoped lang="scss">
+
+.block_box {
+  margin: 25px;
+  background: #FFFFFF;
+  border-radius: 3px;
+
+  .title {
+    border-bottom: 1px solid #ccc;
+    padding: 15px 25px;
+  }
+
+  > div {
+    padding: 25px;
+
+  }
+
+  .submit-btn {
+    margin-left: auto;
+    margin-top: 20px;
+    display: flex;
+  }
+}
+</style>

+ 64 - 33
web/src/components/menus/user/ImportUser.vue

@@ -7,29 +7,31 @@
       </div>
     </div>
     <div class="block_box" style="margin-top: 20px;padding: 20px">
-      <el-form ref="cmd" label-width="100px" :rules="rules" :model="cmd">
+      <el-form ref="cmd" label-width="100px" :rules="rules" :model="cmd" :inline="true">
         <el-form-item label="文件" prop="file">
           <el-upload
               class="upload-demo"
               action="#"
               :auto-upload="false"
+              accept=".csv"
               :on-change="handleFileChange"
               :limit="1"
-              :file-list="fileList">
+              :show-file-list="false">
             <el-button size="small" type="primary">点击上传</el-button>
             <div slot="tip" class="el-upload__tip">只能上传csv文件</div>
           </el-upload>
         </el-form-item>
+        <br/>
         <el-form-item label="字段分隔符" prop="field">
           <el-input v-model="cmd.field" placeholder="请输入字段分隔符" style="width: 300px"
-                    @change="refreshList"></el-input>
+                    @change="handleFieldChange"></el-input>
         </el-form-item>
-        <!--        <el-form-item label="类别分隔符" prop="categories">-->
-        <!--          <el-input v-model="cmd.categories" placeholder="请输入类别分隔符" style="width: 300px"></el-input>-->
-        <!--        </el-form-item>-->
+        <el-form-item label="类别分隔符" prop="categories">
+          <el-input v-model="cmd.categories" placeholder="请输入类别分隔符" style="width: 300px"></el-input>
+        </el-form-item>
+        <br/>
         <el-form-item label="唯一ID" prop="id">
-          <el-select v-model="cmd.id" style="width: 300px" v-if="cmd.ifFirstHead"
-                     @change="refreshList">
+          <el-select v-model="cmd.id" style="width: 300px" @change="refreshList">
             <template v-if="cmd.ifFirstHead">
               <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
             </template>
@@ -39,8 +41,7 @@
           </el-select>
         </el-form-item>
         <el-form-item label="标签" prop="labels">
-          <el-select v-model="cmd.labels" style="width: 300px" v-if="cmd.ifFirstHead"
-                     @change="refreshList">
+          <el-select v-model="cmd.labels" style="width: 300px" @change="refreshList">
             <template v-if="cmd.ifFirstHead">
               <el-option :value="index" :label="item" v-for="(item,index) in columnList" :key="index"></el-option>
             </template>
@@ -49,11 +50,12 @@
             </template>
           </el-select>
         </el-form-item>
-        <el-form-item label="">
+        <br/>
+        <el-form-item label=" ">
           <el-checkbox label="第一行为表头" v-model="cmd.ifFirstHead" @change="refreshList"></el-checkbox>
         </el-form-item>
       </el-form>
-      <el-table :data="list" style="width: 100%" height="400">
+      <el-table :data="list" style="width: 100%" height="400" v-show="list.length > 0">
         <el-table-column type="index" label="行号" width="60"></el-table-column>
         <el-table-column prop="id" :label="'ID('+(cmd.ifFirstHead ? columnList[cmd.id] : cmd.id)+')'"></el-table-column>
         <el-table-column prop="labels" :label="'标签('+(cmd.ifFirstHead ? columnList[cmd.labels] : cmd.labels)+')'">
@@ -63,7 +65,7 @@
           </template>
         </el-table-column>
       </el-table>
-      <el-button type="primary" class="submit-btn" @click="confirmSubmit">确认提交</el-button>
+      <el-button type="primary" class="submit-btn" @click="confirmSubmit" :disabled="list.length === 0">确认提交</el-button>
     </div>
   </div>
 </template>
@@ -83,7 +85,7 @@ export default {
         labels: '',
         ifFirstHead: true
       },
-      fileList: [],
+      file: [],
       originList: [], // 原始的可能包含表头的数据,默认存储最多21行(如果数据不止21行的话)
       list: [], // 页面table显示的数据
       columnList: [],// 上传csv的表头
@@ -105,18 +107,24 @@ export default {
   },
   methods: {
     handleFileChange(file, fileList) {
-      const fileReader = new FileReader();
-      fileReader.onload = function () {
-        const fileData = fileReader.result;
-        _this.originList = fileData.split('\n').slice(0, 21);
-        // 提前填充一个默认的值给对应的ID列和标签列
-        _this.prepareFieldAndLabels();
-        _this.refreshList();
-      };
-      fileReader.readAsText(file.raw);
+      _this.file = file.raw;
+      if(_this.cmd.field.length > 0) {
+        _this.handleFieldChange();
+      }
+    },
+    handleFieldChange() {
+      this.$papa.parse(_this.file, {
+        delimiter: _this.cmd.field,
+        complete: (results) => {
+          _this.originList = results.data.slice(0, 21);
+          // 提前填充一个默认的值给对应的ID列和标签列
+          _this.prepareFieldAndLabels();
+          _this.refreshList();
+        }
+      })
     },
     prepareFieldAndLabels() {
-      _this.columnList = _this.originList[0].split(_this.cmd.field);
+      _this.columnList = _this.originList[0];
       _this.cmd.id = 0;
       _this.cmd.labels = 1;
     },
@@ -131,7 +139,7 @@ export default {
           // 第一行是表头,跳过list塞入
           continue;
         }
-        let row = _this.originList[i].split(_this.cmd.field);
+        let row = _this.originList[i];
         let errorFlag = false;
         try {
           JSON.parse(row[_this.cmd.labels]);
@@ -146,14 +154,37 @@ export default {
       }
     },
     confirmSubmit() { // 提交确认
-      // request({
-      //   url: '/risk-user/query_unique',
-      //   method: 'post',
-      //   data: {
-      //     EQS_fid: _this.userId
-      //   }
-      // }).then(res => {
-      // });
+      this.$refs.cmd.validate((valid) => {
+        let formData = new FormData();
+        formData.append("sep", _this.cmd.field);
+        formData.append("has-header", _this.cmd.ifFirstHead);
+        formData.append("label-sep", _this.cmd.categories);
+        formData.append("file", _this.file);
+        let idIndex = _this.cmd.id;
+        let labelIndex = _this.cmd.labels;
+        let _format = '';
+        for (let i = 0; i <= Math.max(idIndex, labelIndex); i++) {
+          if (i == idIndex) {
+            _format += "u";
+          } else if (i == labelIndex) {
+            _format += "l";
+          } else {
+            _format += "_";
+          }
+        }
+        formData.append("format", _format);
+        request({
+          url: '/gorse/bulk/users',
+          method: 'put',
+          data: formData
+        }).then(res => {
+          if(res.data && res.data.RowAffected) {
+            _this.$message.success(`成功导入${res.data.RowAffected}条记录`);
+          } else {
+            _this.$message.warning("导入异常");
+          }
+        });
+      });
     },
     backBtn() {
       this.$router.push({

+ 3 - 0
web/src/main.js

@@ -21,11 +21,14 @@ import {getToken, setToken, removeToken} from '@/utils/auth'
 import '@/permission'
 import '@antv/x6-vue-shape'
 
+import VuePapaParse from 'vue-papa-parse'
+
 Vue.prototype.$echarts = echarts;
 
 
 Vue.use(ElementUI);
 Vue.use(VueQuillEditor);
+Vue.use(VuePapaParse);
 
 Vue.config.productionTip = false;
 

+ 4 - 0
web/src/router/index.js

@@ -56,6 +56,10 @@ export default new Router({
                     component: () => import('@/components/menus/EntrysManage')
                 },
                 {
+                    path: 'importItem',
+                    component: () => import('@/components/menus/item/ImportItem')
+                },
+                {
                     path: 'feedbackTypeManage',
                     component: () => import('@/components/menus/FeedbackTypeManage')
                 },

+ 2 - 2
web/src/utils/request.js

@@ -3,8 +3,8 @@ import {MessageBox, Message} from 'element-ui'
 import store from '@/store'
 import {getToken} from '@/utils/auth'
 
-// var _baseURL = "http://localhost:4026";
-var _baseURL = "http://139.9.106.207:4026";
+var _baseURL = "http://localhost:4026";
+// var _baseURL = "http://139.9.106.207:4026";
 var _fileURL = "http://139.9.106.207:4026/web/";
 // create an axios instance
 const service = axios.create({