Kaynağa Gözat

Merge branch 'master' of http://nas.zhengjl.com:10880/wan/recom-gorse

zhangjian 2 yıl önce
ebeveyn
işleme
68ed8e92e7

+ 93 - 78
api/src/main/java/com/wx/application/adapter/controller/GorseController.java

@@ -6,10 +6,7 @@ import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import com.wx.application.adapter.dto.qo.GorseQ;
 import com.wx.application.base.BaseController;
@@ -29,82 +26,100 @@ import lombok.extern.slf4j.Slf4j;
 @RequestMapping("/gorse")
 @RestController("gorseController")
 public class GorseController extends BaseController {
-	
-	@Autowired
-	GorseService gorseService;
-	
-	@Autowired
-	RiskUserService riskUserService;
-	
-	@Autowired
-	EntrysService entrysService;
-	
-	@Autowired
-	NebulaOperateService nebulaOperateService;
-	
-	@Autowired
-	ImportGraphService importGraphService;
-	
-	/**
-	 * 推荐根据userId
-	 * @param gQo
-	 * @return
-	 * @throws Exception
-	 */
-	@PostMapping(value = "/recommend_by_userid")
+
+    @Autowired
+    GorseService gorseService;
+
+    @Autowired
+    RiskUserService riskUserService;
+
+    @Autowired
+    EntrysService entrysService;
+
+    @Autowired
+    NebulaOperateService nebulaOperateService;
+
+    @Autowired
+    ImportGraphService importGraphService;
+
+    /**
+     * 推荐根据userId
+     *
+     * @param gQo
+     * @return
+     * @throws Exception
+     */
+    @PostMapping(value = "/recommend_by_userid")
     public ResponseData recommendByUserId(@RequestBody GorseQ gQo) throws Exception {
-		List<String> res = gorseService.getRecommend(gQo.getUserId());
-		if(res == null || res.size() == 0) {
-			return success();
-		}
-		Map mQ = new HashMap<>();
-		mQ.put("INS_fid", StringUtils.join(res, ","));
-		return success(entrysService.queryList(mQ));
-	}
-	
-	/**
-	 * 根据列表推荐
-	 * @param gQo
-	 * @return
-	 * @throws Exception
-	 */
-	@PostMapping(value = "/popular_by_category")
+        List<String> res = gorseService.getRecommend(gQo.getUserId());
+        if (res == null || res.size() == 0) {
+            return success();
+        }
+        Map mQ = new HashMap<>();
+        mQ.put("INS_fid", StringUtils.join(res, ","));
+        return success(entrysService.queryList(mQ));
+    }
+
+    /**
+     * 根据列表推荐
+     *
+     * @param gQo
+     * @return
+     * @throws Exception
+     */
+    @PostMapping(value = "/popular_by_category")
     public ResponseData popularByCategory(@RequestBody GorseQ gQo) throws Exception {
-		List<Item> res = gorseService.getPopular(gQo.getCategory());
-		return success(res);
-	}
-	
-	@PostMapping(value = "/ipt")
+        List<Item> res = gorseService.getPopular(gQo.getCategory());
+        return success(res);
+    }
+
+    @PostMapping(value = "/ipt")
     public ResponseData ipt() throws Exception {
-		importGraphService.iptUser();
-		importGraphService.iptentrys();
-		return success();
-	}
-	
-	/**
-	 * 推荐根据知识图谱
-	 * @param gQo
-	 * @return
-	 * @throws Exception
-	 */
-	@PostMapping(value = "/recommend_by_userid_with_graph")
+        importGraphService.iptUser();
+        importGraphService.iptentrys();
+        return success();
+    }
+
+    /**
+     * 推荐根据知识图谱
+     *
+     * @param gQo
+     * @return
+     * @throws Exception
+     */
+    @PostMapping(value = "/recommend_by_userid_with_graph")
     public ResponseData recommendByUserIdWithGraph(@RequestBody GorseQ gQo) throws Exception {
-		
-		NebulaModel nebulaModel = nebulaOperateService.findOnePathById("", gQo.getUserId());
-		List<NebulaNode> nodes = nebulaModel.getNodes();
-		
-		if(nodes != null && nodes.size() > 0) {
-			nodes.get(0).getVid();
-		} 
-		
-		List<String> res = gorseService.getRecommend(gQo.getUserId());
-		if(res == null || res.size() == 0) {
-			return success();
-		}
-		Map mQ = new HashMap<>();
-		mQ.put("INS_fid", StringUtils.join(res, ","));
-		return success(entrysService.queryList(mQ));
-	}
-	
-	
+
+        NebulaModel nebulaModel = nebulaOperateService.findOnePathById("", gQo.getUserId());
+        List<NebulaNode> nodes = nebulaModel.getNodes();
+
+        if (nodes != null && nodes.size() > 0) {
+            nodes.get(0).getVid();
+        }
+
+        List<String> res = gorseService.getRecommend(gQo.getUserId());
+        if (res == null || res.size() == 0) {
+            return success();
+        }
+        Map mQ = new HashMap<>();
+        mQ.put("INS_fid", StringUtils.join(res, ","));
+        return success(entrysService.queryList(mQ));
+    }
+
+    @PostMapping(value = "/get_similar_user")
+    public ResponseData getSimilarUser(@RequestBody GorseQ gQo) throws Exception {
+        return success(gorseService.getSimilarUser(gQo.getUserId()));
+    }
+
+    @PostMapping(value = "/get_recommend_by_user")
+    public ResponseData getRecommendByUser(@RequestBody GorseQ gQo) throws Exception {
+        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()));
+    }
+
+
 }

+ 6 - 0
api/src/main/java/com/wx/application/adapter/dto/qo/GorseQ.java

@@ -8,6 +8,12 @@ import lombok.EqualsAndHashCode;
 public class GorseQ {
 	
 	private String userId;
+
+	private String itemId;
 	
 	private String category;
+
+	private Integer n;
+
+	private String recommendation;
 }

+ 5 - 0
api/src/main/java/com/wx/application/core/controller/RiskUserController.java

@@ -34,4 +34,9 @@ public class RiskUserController extends BaseController {
         return success(userService.queryPage(entrysQ));
     }
 
+    @PostMapping(value = "/query_unique")
+    public ResponseData queryUnique(@RequestBody Map entrysQ) {
+        return success(userService.queryUnique(entrysQ));
+    }
+
 }

+ 33 - 5
api/src/main/java/com/wx/application/gorse4j/GorseService.java

@@ -10,6 +10,7 @@ import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -23,7 +24,7 @@ public class GorseService {
         //Gorse client = new Gorse("", "");
         //System.out.println(client.getRecommend("1265177464"));
     }
-    
+
 
     public RowAffected insertUser(User user) throws IOException {
         return this.request("POST", this.endpoint + "/api/user", user, RowAffected.class);
@@ -60,13 +61,40 @@ public class GorseService {
     public List<String> getRecommend(String userId) throws IOException {
         return Arrays.asList(this.request("GET", this.endpoint + "/api/recommend/" + userId, null, String[].class));
     }
-    
-    
+
+
     public List<Item> getPopular(String category) throws IOException {
         return Arrays.asList(this.request("GET", this.endpoint + "/api/dashboard/popular/" + category, null, Item[].class));
     }
-    
-    
+
+    /**
+     * 相似(获取相似用户)
+     */
+    public List<User> getSimilarUser(String userId) throws IOException {
+        return Arrays.asList(this.request("GET", this.endpoint + "/api/dashboard/user/" + userId + "/neighbors", null, User[].class));
+    }
+
+    /**
+     * 洞悉(根据用户获取推荐)
+     */
+    public List<Item> getRecommendByUser(String userId, String recommendation, String category, Integer n) throws IOException {
+        return Arrays.asList(this.request("GET", this.endpoint + "/api/dashboard/recommend/" + userId
+                + "/" + recommendation + "/" + category + "?n=" + ((n == null || n <= 0) ? 10 : n), null, Item[].class));
+    }
+
+    /**
+     * 相似物品(根据物品ID和分类获取相似物品)
+     */
+    public List<Item> getSimilarItem(String itemId, String category, Integer n) throws IOException {
+        String url = this.endpoint + "/api/dashboard/item/" + itemId + "/neighbors";
+        if (StringUtils.isNotBlank(category)) {
+            // 当默认查询10条
+            url += "/" + category + "?n=" + ((n == null || n <= 0) ? 10 : n);
+        }
+        System.out.println(url);
+        return Arrays.asList(this.request("GET", url, null, Item[].class));
+    }
+
 
     private <Request, Response> Response request(String method, String url, Request request, Class<Response> responseClass) throws IOException {
         HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();

+ 3 - 3
api/src/main/java/com/wx/application/gorse4j/Item.java

@@ -14,12 +14,12 @@ public class Item {
     private String timestamp;
     private String comment;
     
-    private Integer score;
+    private Float score;
     
     public Item() {
     }
 
-    public Item(String itemId, Boolean isHidden, List<String> labels, List<String> categories, String timestamp, String comment,Integer score) {
+    public Item(String itemId, Boolean isHidden, List<String> labels, List<String> categories, String timestamp, String comment,Float score) {
         this.itemId = itemId;
         this.isHidden = isHidden;
         this.labels = labels;
@@ -60,7 +60,7 @@ public class Item {
     }
 
     @JsonProperty("Score")
-    public Integer getScore() {
+    public Float getScore() {
 		return score;
 	}
 

+ 7 - 0
api/src/main/java/com/wx/application/gorse4j/User.java

@@ -12,6 +12,8 @@ public class User {
     private String userId;
     private List<String> labels;
 
+    private Float score;
+
     public User() {
     }
 
@@ -30,6 +32,11 @@ public class User {
         return labels;
     }
 
+    @JsonProperty("Score")
+    public Float getScore() {
+        return score;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;

+ 16 - 1
web/src/components/menus/EntrysManage.vue

@@ -51,11 +51,14 @@
             </template>
           </el-table-column>
           <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
-          <el-table-column label="操作" width="100">
+          <el-table-column label="操作" width="150">
             <template slot-scope="scope">
               <el-button @click.native.prevent="modifyRow(scope.row)" type="text" size="small">
                 编辑
               </el-button>
+              <el-button @click.native.prevent="similarItem(scope.row)" type="text" size="small">
+                相似条目
+              </el-button>
               <el-button @click.native.prevent="removeRow(scope.row)" type="text" size="small">
                 删除
               </el-button>
@@ -180,6 +183,10 @@ export default {
   },
   mounted() {
     _this = this;
+    let _qo = _this.$route.query.qo;
+    if (_qo) {
+      _this.qo = JSON.parse(_qo);
+    }
     _this.queryData();
   },
   methods: {
@@ -202,6 +209,14 @@ export default {
       _this.qo.pageNo = val;
       _this.queryData();
     },
+    similarItem(item) {
+      _this.$router.push({
+        path: "similarItem", query: {
+          itemId: item.fid,
+          qo: JSON.stringify(_this.qo)
+        }
+      });
+    },
     removeRow(item) {
       _this.$confirm('此操作将永久删除记录, 是否继续?', '提示', {
         confirmButtonText: '确定',

+ 40 - 4
web/src/components/menus/UserManage.vue

@@ -21,9 +21,22 @@
         <el-table :data="result.records" style="width: 100%">
           <el-table-column type="index" label="行号" width="60"></el-table-column>
           <el-table-column prop="fid" label="fid"></el-table-column>
-          <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
-          <el-table-column label="操作" width="100">
+          <el-table-column prop="labels" label="标签">
+            <div slot-scope="scope">
+              <template v-for="(tag,index) in scope.row.labelsArr">
+                <el-tag :key="index" v-if="index < 5">{{ tag }}</el-tag>
+              </template>
+              <el-tag v-if="scope.row.labelsArr.length > 5">...</el-tag>
+            </div>
+          </el-table-column>
+          <el-table-column label="操作" width="180">
             <template slot-scope="scope">
+              <el-button @click.native.prevent="similarUser(scope.row)" type="text" size="small">
+                相似
+              </el-button>
+              <el-button @click.native.prevent="recommendItem(scope.row)" type="text" size="small">
+                洞悉
+              </el-button>
               <el-button @click.native.prevent="modifyRow(scope.row)" type="text" size="small">
                 编辑
               </el-button>
@@ -90,6 +103,10 @@ export default {
   },
   mounted() {
     _this = this;
+    let _qo = _this.$route.query.qo;
+    if (_qo) {
+      _this.qo = JSON.parse(_qo);
+    }
     _this.queryData();
   },
   methods: {
@@ -99,6 +116,9 @@ export default {
         method: 'post',
         data: _this.qo
       }).then(res => {
+        res.data.records.forEach(row => {
+          row.labelsArr = row.labels ? row.labels.split(',') : [];
+        });
         _this.result.records = res.data.records;
         _this.result.total = res.data.total;
       });
@@ -107,6 +127,22 @@ export default {
       _this.qo.pageNo = val;
       _this.queryData();
     },
+    similarUser(item) {
+      _this.$router.push({
+        path: "similarUser", query: {
+          userId: item.fid,
+          qo: JSON.stringify(_this.qo)
+        }
+      });
+    },
+    recommendItem(item) {
+      _this.$router.push({
+        path: "recommendItem", query: {
+          userId: item.fid,
+          qo: JSON.stringify(_this.qo)
+        }
+      });
+    },
     removeRow(item) {
       _this.$confirm('此操作将永久删除记录, 是否继续?', '提示', {
         confirmButtonText: '确定',
@@ -126,7 +162,7 @@ export default {
     createRow() {
       _this.dialogName = '新建';
       _this.cmdDialogVisible = true;
-      _this.$nextTick(_=> {
+      _this.$nextTick(_ => {
         _this.$refs['cmd'].clearValidate();
         _this.cmd = {
           fid: '',
@@ -136,7 +172,7 @@ export default {
     modifyRow(item) {
       _this.dialogName = '编辑';
       _this.cmdDialogVisible = true;
-      _this.$nextTick(_=> {
+      _this.$nextTick(_ => {
         _this.$refs['cmd'].clearValidate();
         _this.cmd = JSON.parse(JSON.stringify(item));
       })

+ 178 - 0
web/src/components/menus/item/RecommendItem.vue

@@ -0,0 +1,178 @@
+<template>
+  <div style="height: 100%">
+    <div class="menu-title">
+      洞悉({{ gorseQo.userId }})
+      <div class="icon icon-back" @click="backBtn">
+        <i title="返回"></i>
+      </div>
+    </div>
+    <div class="block_box" style="margin-top: 0">
+      <div class="title">推荐</div>
+      <el-form :inline="true" class="demo-form-inline">
+        <el-form-item>
+          <el-select v-model="gorseQo.recommendation" @change="getRecommendItem">
+            <el-option :key="item.value" :value="item.value" :label="item.label"
+                       v-for="item in recommendList"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="categories">
+          <el-select v-model="gorseQo.category" @change="getRecommendItem">
+            <el-option value="" label="无"></el-option>
+            <el-option :key="item.id" :value="item.name" :label="item.name"
+                       v-for="item in categoryList"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div class="line-item-box">
+        <div class="line-item" v-for="item in list" :key="item.id">
+          <div class="title-time">
+            <div>{{ item.ItemId }}</div>
+            <div>{{ item.Timestamp }}</div>
+          </div>
+          <div class="description">{{ item.Comment }}</div>
+          <div class="labels">
+            <el-tag v-for="la in item.Labels">{{ la }}</el-tag>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request';
+
+var _this;
+export default {
+  name: "RecommendItem",
+  data() {
+    return {
+      qo: "",
+      gorseQo: {
+        userId: '',
+        n: 10,
+        recommendation: '_',
+        category: ''
+      },
+      user: {},
+      categoryList: [],
+      recommendList: [{
+        label: 'Recommendation',
+        value: '_'
+      }, {
+        label: 'Offline Recommendation',
+        value: 'offline'
+      }, {
+        label: 'Collaborative Recommendation',
+        value: 'collaborative'
+      }, {
+        label: 'Item-based Recommendation',
+        value: 'item_based'
+      }, {
+        label: 'User-based Recommendation',
+        value: 'user_based'
+      }],
+      list: []
+    }
+  },
+  mounted() {
+    _this = this;
+    _this.qo = _this.$route.query.qo;
+    _this.gorseQo.userId = _this.$route.query.userId;
+    _this.queryCategoryList();
+    _this.getRecommendItem();
+  },
+  methods: {
+    queryCategoryList() {
+      request({
+        url: '/category/query_list',
+        method: 'post',
+        data: {}
+      }).then(res => {
+        _this.categoryList = res.data;
+      });
+    },
+    getRecommendItem() {
+      request({
+        url: '/gorse/get_recommend_by_user',
+        method: 'post',
+        data: _this.gorseQo
+      }).then(res => {
+        res.data.forEach(row => {
+          row.labelsArr = row.labels ? row.labels.split(',') : [];
+        });
+        _this.list = res.data;
+      });
+    },
+    backBtn() {
+      this.$router.push({
+        path: "userManage", query: {
+          qo: _this.qo
+        }
+      });
+    },
+  }
+}
+</script>
+<style src="../../../css/back.css" scoped></style>
+<style scoped lang="scss">
+
+.block_box {
+  margin: 25px;
+  background: #FFFFFF;
+  border-radius: 3px;
+  height: calc(100% - 160px);
+
+  .title {
+    border-bottom: 1px solid #ccc;
+    padding: 15px 25px;
+  }
+
+}
+
+.demo-form-inline {
+  padding: 25px 25px 0 25px;
+}
+
+.line-item-box {
+  height: calc(100% - 140px);
+  overflow-y: auto;
+
+  .line-item {
+    font-size: 14px;
+    line-height: 32px;
+    padding: 0 25px 10px 25px;
+    margin: 20px 0;
+    border-bottom: 1px solid #dee0e2;
+
+
+    .title-time {
+      display: flex;
+
+      div:first-child {
+        font-weight: bold;
+        font-size: 18px;
+        color: #9da2a8;
+      }
+
+      div:last-child {
+        margin-left: auto;
+        color: #bbbcbd;
+      }
+    }
+
+    .description {
+      color: #bbbcbd;
+    }
+
+    .labels {
+      color: #9da2a8;
+
+      .el-tag {
+        margin: 0 10px 10px 0;
+      }
+    }
+  }
+
+}
+</style>

+ 175 - 0
web/src/components/menus/item/SimilarItem.vue

@@ -0,0 +1,175 @@
+<template>
+  <div class="similar-item-box">
+    <div class="menu-title">
+      相似用户({{ gorseQo.itemId }})
+      <div class="icon icon-back" @click="backBtn">
+        <i title="返回"></i>
+      </div>
+    </div>
+    <div class="block_box" style="margin-top: 0">
+      <div class="title">信息</div>
+      <div >
+        <el-form label-width="50px">
+          <el-form-item label="时间">{{ item.createTime }}</el-form-item>
+          <el-form-item label="类别">
+            <template v-for="(tag,index) in item.categoriesArr">
+              <el-tag :key="index">{{ tag }}</el-tag>
+            </template>
+          </el-form-item>
+          <el-form-item label="标签">
+            <template v-for="(tag,index) in item.labelsArr">
+              <el-tag :key="index">{{ tag }}</el-tag>
+            </template>
+          </el-form-item>
+          <el-form-item label="描述">
+            {{ item.description }}
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <div class="block_box">
+      <div class="title">Related Items</div>
+      <el-form :inline="true" class="demo-form-inline">
+        <el-form-item label="categories">
+          <el-select v-model="gorseQo.category" @change="getSimilarItem">
+            <el-option value="" label="无"></el-option>
+            <el-option :key="item.id" :value="item.name" :label="item.name"
+                       v-for="item in categoryList"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div style="padding-top: 0">
+        <el-table :data="list" style="width: 100%">
+          <el-table-column type="index" label="行号" width="60"></el-table-column>
+          <el-table-column prop="ItemId" label="fid"></el-table-column>
+          <el-table-column prop="labels" label="类别">
+            <div slot-scope="scope">
+              <template v-for="(tag,index) in scope.row.Categories">
+                <el-tag :key="index" v-if="index < 5">{{ tag }}</el-tag>
+              </template>
+              <el-tag v-if="scope.row.Categories.length > 5">...</el-tag>
+            </div>
+          </el-table-column>
+          <el-table-column prop="labels" label="标签">
+            <div slot-scope="scope">
+              <template v-for="(tag,index) in scope.row.Labels">
+                <el-tag :key="index" v-if="index < 5">{{ tag }}</el-tag>
+              </template>
+              <el-tag v-if="scope.row.Labels.length > 5">...</el-tag>
+            </div>
+          </el-table-column>
+          <el-table-column prop="Comment" label="描述">
+            <template slot-scope="scope" v-if="scope.row.Comment">
+              {{ scope.row.Comment.substring(0, 100) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="Score" label="Score" width="100">
+            <template slot-scope="scope">
+              {{ scope.row.Score.toFixed(5) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="Timestamp" label="创建时间" width="200"></el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request';
+
+var _this;
+export default {
+  name: "similarItem",
+  data() {
+    return {
+      qo: "",
+      gorseQo: {
+        itemId: '',
+        category: '',
+        n: 10
+      },
+      item: {},
+      list: [],
+      categoryList: []
+    }
+  },
+  mounted() {
+    _this = this;
+    _this.qo = _this.$route.query.qo;
+    _this.gorseQo.itemId = _this.$route.query.itemId;
+    _this.queryCategoryList();
+    _this.queryItem();
+    _this.getSimilarItem();
+  },
+  methods: {
+    queryCategoryList() {
+      request({
+        url: '/category/query_list',
+        method: 'post',
+        data: {}
+      }).then(res => {
+        _this.categoryList = res.data;
+      });
+    },
+    queryItem() {
+      request({
+        url: '/entrys/query_unique',
+        method: 'post',
+        data: {
+          EQS_fid: _this.gorseQo.itemId
+        }
+      }).then(res => {
+        res.data.labelsArr = res.data.labels ? res.data.labels.split(',') : [];
+        res.data.categoriesArr = res.data.categories ? res.data.categories.split(',') : [];
+        _this.item = res.data;
+      });
+    },
+    getSimilarItem() {
+      request({
+        url: '/gorse/get_similar_item',
+        method: 'post',
+        data: _this.gorseQo
+      }).then(res => {
+        _this.list = res.data;
+      });
+    },
+    backBtn() {
+      this.$router.push({
+        path: "entrysManage", query: {
+          qo: _this.qo
+        }
+      });
+    },
+  }
+}
+</script>
+<style src="../../../css/back.css" scoped></style>
+<style scoped lang="scss">
+
+.similar-item-box {
+  height: 100%;
+  overflow-y: auto;
+}
+
+
+.block_box {
+  margin: 25px;
+  background: #FFFFFF;
+  border-radius: 3px;
+
+  .title {
+    border-bottom: 1px solid #ccc;
+    padding: 15px 25px;
+  }
+
+  > div {
+    padding: 25px;
+
+  }
+}
+
+.demo-form-inline {
+  padding: 25px 25px 0 25px;
+}
+</style>

+ 122 - 0
web/src/components/menus/user/SimilarUser.vue

@@ -0,0 +1,122 @@
+<template>
+  <div>
+    <div class="menu-title">
+      相似用户({{ userId }})
+      <div class="icon icon-back" @click="backBtn">
+        <i title="返回"></i>
+      </div>
+    </div>
+    <div class="block_box" style="margin-top: 0">
+      <div class="title">信息</div>
+      <div>
+        <el-form label-width="100px">
+          <el-form-item label="标签">
+            <template v-for="(tag,index) in user.labelsArr">
+              <el-tag :key="index">{{ tag }}</el-tag>
+            </template>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <div class="block_box">
+      <div class="title">Related Items</div>
+      <div>
+        <el-table :data="list" style="width: 100%">
+          <el-table-column type="index" label="行号" width="60"></el-table-column>
+          <el-table-column prop="UserId" label="UserId"></el-table-column>
+          <el-table-column prop="labels" label="标签">
+            <div slot-scope="scope">
+              <template v-for="(tag,index) in scope.row.labelsArr">
+                <el-tag :key="index" v-if="index < 5">{{ tag }}</el-tag>
+              </template>
+              <el-tag v-if="scope.row.labelsArr.length > 5">...</el-tag>
+            </div>
+          </el-table-column>
+          <el-table-column prop="Score" label="Score">
+            <template slot-scope="scope">
+              {{scope.row.Score.toFixed(5)}}
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request';
+
+var _this;
+export default {
+  name: "similarUser",
+  data() {
+    return {
+      qo: "",
+      userId: '',
+      user: {},
+      list: []
+    }
+  },
+  mounted() {
+    _this = this;
+    _this.qo = _this.$route.query.qo;
+    _this.userId = _this.$route.query.userId;
+    _this.queryUser();
+    _this.getSimilarUser();
+  },
+  methods: {
+    queryUser() {
+      request({
+        url: '/risk-user/query_unique',
+        method: 'post',
+        data: {
+          EQS_fid: _this.userId
+        }
+      }).then(res => {
+        res.data.labelsArr = res.data.labels ? res.data.labels.split(',') : [];
+        _this.user = res.data;
+      });
+    },
+    getSimilarUser() {
+      request({
+        url: '/gorse/get_similar_user',
+        method: 'post',
+        data: {
+          userId: _this.userId
+        }
+      }).then(res => {
+        res.data.forEach(row => {
+          row.labelsArr = row.labels ? row.labels.split(',') : [];
+        });
+        _this.list = res.data;
+      });
+    },
+    backBtn() {
+      this.$router.push({
+        path: "userManage", query: {
+          qo: _this.qo
+        }
+      });
+    },
+  }
+}
+</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;
+
+  }
+}
+</style>

+ 25 - 0
web/src/css/back.css

@@ -0,0 +1,25 @@
+div.icon {
+  display: inline-block;
+  cursor: pointer;
+  height: 50px;
+  background-color: #fff;
+  border-radius: 2.08333vw;
+  float: right;
+  width: 50px;
+  text-align: center;
+  line-height: 50px;
+  margin-right: 80px;
+  margin-top: 9px;
+}
+
+div.icon i {
+  width: 25px;
+  height: 25px;
+  margin: auto;
+  display: inline-block;
+  vertical-align: sub;
+}
+
+.icon-back i {
+  background: url("../assets/image/icon/fanhui.png") no-repeat center/contain;
+}

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

@@ -64,6 +64,18 @@ export default new Router({
                     component: () => import('@/components/menus/IntelligenceRecommend')
                 },
                 {
+                    path: 'similarUser',
+                    component: () => import('@/components/menus/user/SimilarUser')
+                },
+                {
+                    path: 'recommendItem',
+                    component: () => import('@/components/menus/item/RecommendItem')
+                },
+                {
+                    path: 'similarItem',
+                    component: () => import('@/components/menus/item/SimilarItem')
+                },
+                {
                     path: '/',
                     redirect: "homePage"
                 },