..
This commit is contained in:
@@ -0,0 +1,273 @@
|
|||||||
|
package com.budwk.app.base.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本相似度计算工具类
|
||||||
|
*/
|
||||||
|
public class TextSimilarityUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提案与关键词的相似度
|
||||||
|
*
|
||||||
|
* @param proposal 提案数据
|
||||||
|
* @param keywords 关键词
|
||||||
|
* @param matchScope 匹配范围
|
||||||
|
* @return 相似度百分比 (0-100)
|
||||||
|
*/
|
||||||
|
public static double calculateProposalSimilarity(NutMap proposal, String keywords, String[] matchScope) {
|
||||||
|
String title = proposal.getString("name", "");
|
||||||
|
String brief = proposal.getString("brief", "");
|
||||||
|
String measures = proposal.getString("measures", "");
|
||||||
|
|
||||||
|
StringBuilder fullText = new StringBuilder();
|
||||||
|
if (ArrayUtil.isEmpty(matchScope)) {
|
||||||
|
fullText.append(title);
|
||||||
|
fullText.append(" ");
|
||||||
|
fullText.append(brief);
|
||||||
|
fullText.append(" ");
|
||||||
|
fullText.append(measures);
|
||||||
|
} else {
|
||||||
|
for (String scope : matchScope) {
|
||||||
|
String value = removeHtmlTags(proposal.getString(scope, ""));
|
||||||
|
fullText.append(value);
|
||||||
|
fullText.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算关键词匹配度
|
||||||
|
return calculateTextSimilarity(fullText.toString(), keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两个文本的相似度
|
||||||
|
*
|
||||||
|
* @param text1 文本1
|
||||||
|
* @param text2 文本2(关键词)
|
||||||
|
* @return 相似度百分比 (0-100)
|
||||||
|
*/
|
||||||
|
public static double calculateTextSimilarity(String text1, String text2) {
|
||||||
|
if (StrUtil.isBlank(text1) || StrUtil.isBlank(text2)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
text1 = text1.toLowerCase().trim();
|
||||||
|
text2 = text2.toLowerCase().trim();
|
||||||
|
|
||||||
|
// 使用多种算法综合计算相似度
|
||||||
|
double keywordSimilarity = calculateKeywordSimilarity(text1, text2);
|
||||||
|
double jaccardSimilarity = calculateJaccardSimilarity(text1, text2);
|
||||||
|
|
||||||
|
// 加权平均
|
||||||
|
return (keywordSimilarity * 0.7 + jaccardSimilarity * 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于关键词匹配的相似度计算
|
||||||
|
*/
|
||||||
|
private static double calculateKeywordSimilarity(String text1, String text2) {
|
||||||
|
// 分词处理
|
||||||
|
String[] keywords = text2.split("[\\s\\p{Punct}]+");
|
||||||
|
int matchCount = 0;
|
||||||
|
int totalKeywords = 0;
|
||||||
|
|
||||||
|
for (String keyword : keywords) {
|
||||||
|
keyword = keyword.trim();
|
||||||
|
if (!keyword.isEmpty()) {
|
||||||
|
totalKeywords++;
|
||||||
|
if (text1.contains(keyword)) {
|
||||||
|
matchCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalKeywords == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算匹配率
|
||||||
|
double matchRate = (double) matchCount / totalKeywords;
|
||||||
|
|
||||||
|
// 考虑文本长度因素,避免短文本获得过高分数
|
||||||
|
double lengthFactor = Math.min(text2.length() / (double) Math.max(text1.length(), 1), 1.0);
|
||||||
|
lengthFactor = Math.max(lengthFactor, 0.3); // 最小权重0.3
|
||||||
|
|
||||||
|
return matchRate * lengthFactor * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jaccard相似度计算
|
||||||
|
* 基于集合的交集与并集比值
|
||||||
|
*/
|
||||||
|
private static double calculateJaccardSimilarity(String text1, String text2) {
|
||||||
|
Set<String> set1 = new HashSet<>(Arrays.asList(text1.split("[\\s\\p{Punct}]+")));
|
||||||
|
Set<String> set2 = new HashSet<>(Arrays.asList(text2.split("[\\s\\p{Punct}]+")));
|
||||||
|
|
||||||
|
// 移除空字符串
|
||||||
|
set1.removeIf(String::isEmpty);
|
||||||
|
set2.removeIf(String::isEmpty);
|
||||||
|
|
||||||
|
if (set1.isEmpty() && set2.isEmpty()) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算交集
|
||||||
|
Set<String> intersection = new HashSet<>(set1);
|
||||||
|
intersection.retainAll(set2);
|
||||||
|
|
||||||
|
// 计算并集
|
||||||
|
Set<String> union = new HashSet<>(set1);
|
||||||
|
union.addAll(set2);
|
||||||
|
|
||||||
|
return union.isEmpty() ? 0.0 : (double) intersection.size() / union.size() * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取相似内容片段
|
||||||
|
*
|
||||||
|
* @param proposal 提案数据
|
||||||
|
* @param keywords 关键词
|
||||||
|
* @return 相似内容片段
|
||||||
|
*/
|
||||||
|
public static String extractSimilarContent(NutMap proposal, String keywords) {
|
||||||
|
String brief = removeHtmlTags(proposal.getString("brief", ""));
|
||||||
|
String measures = removeHtmlTags(proposal.getString("measures", ""));
|
||||||
|
String title = proposal.getString("name", "");
|
||||||
|
|
||||||
|
// 优先从标题中查找
|
||||||
|
if (containsKeywords(title, keywords)) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找包含关键词的句子
|
||||||
|
String[] sentences = (brief + " " + measures).split("[。!?\\.\\!\\?]");
|
||||||
|
|
||||||
|
for (String sentence : sentences) {
|
||||||
|
sentence = sentence.trim();
|
||||||
|
if (!sentence.isEmpty() && containsKeywords(sentence, keywords)) {
|
||||||
|
return truncateText(sentence, 150);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没找到,返回摘要的前150个字符
|
||||||
|
String fallback = !brief.isEmpty() ? brief : measures;
|
||||||
|
return truncateText(fallback, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文本是否包含关键词
|
||||||
|
*/
|
||||||
|
public static boolean containsKeywords(String text, String keywords) {
|
||||||
|
if (StrUtil.isBlank(text) || StrUtil.isBlank(keywords)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] keywordArray = keywords.toLowerCase().split("[\\s\\p{Punct}]+");
|
||||||
|
String lowerText = text.toLowerCase();
|
||||||
|
|
||||||
|
for (String keyword : keywordArray) {
|
||||||
|
keyword = keyword.trim();
|
||||||
|
if (!keyword.isEmpty() && lowerText.contains(keyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除HTML标签
|
||||||
|
*/
|
||||||
|
public static String removeHtmlTags(String html) {
|
||||||
|
if (StrUtil.isBlank(html)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return html.replaceAll("<[^>]+>", "")
|
||||||
|
.replaceAll(" ", " ")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">")
|
||||||
|
.replaceAll("&", "&")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截断文本到指定长度
|
||||||
|
*/
|
||||||
|
public static String truncateText(String text, int maxLength) {
|
||||||
|
if (StrUtil.isBlank(text)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.trim();
|
||||||
|
if (text.length() <= maxLength) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.substring(0, maxLength) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高亮关键词
|
||||||
|
*
|
||||||
|
* @param text 原文本
|
||||||
|
* @param keywords 关键词
|
||||||
|
* @return 高亮后的HTML文本
|
||||||
|
*/
|
||||||
|
public static String highlightKeywords(String text, String keywords) {
|
||||||
|
if (StrUtil.isBlank(text) || StrUtil.isBlank(keywords)) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] keywordArray = keywords.split("[\\s\\p{Punct}]+");
|
||||||
|
String result = text;
|
||||||
|
|
||||||
|
for (String keyword : keywordArray) {
|
||||||
|
keyword = keyword.trim();
|
||||||
|
if (!keyword.isEmpty()) {
|
||||||
|
// 使用正则表达式进行大小写不敏感的替换
|
||||||
|
result = result.replaceAll("(?i)(" + escapeRegex(keyword) + ")",
|
||||||
|
"<span class=\"highlight\">$1</span>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义正则表达式特殊字符
|
||||||
|
*/
|
||||||
|
private static String escapeRegex(String text) {
|
||||||
|
return text.replaceAll("([\\\\\\[\\]{}()*+?.^$|])", "\\\\$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算文本的词频
|
||||||
|
*
|
||||||
|
* @param text 文本
|
||||||
|
* @return 词频统计Map
|
||||||
|
*/
|
||||||
|
public static NutMap calculateWordFrequency(String text) {
|
||||||
|
NutMap frequency = new NutMap();
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(text)) {
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] words = text.toLowerCase().split("[\\s\\p{Punct}]+");
|
||||||
|
|
||||||
|
for (String word : words) {
|
||||||
|
word = word.trim();
|
||||||
|
if (word.length() > 1) { // 忽略单字符
|
||||||
|
frequency.put(word, frequency.getInt(word, 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
package com.budwk.app.zhgh.democratic.proposal.controller.query;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.base.utils.TextSimilarityUtil;
|
||||||
|
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Sqls;
|
||||||
|
import org.nutz.dao.sql.Sql;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
import org.nutz.mvc.annotation.At;
|
||||||
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@IocBean
|
||||||
|
@At("/platform/proposal/query/plagiarismCheck")
|
||||||
|
@Api(tags = "提案查重")
|
||||||
|
@Ok("json:full")
|
||||||
|
public class ProposalPlagiarismCheckController {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ProposalCommonService proposalCommonService;
|
||||||
|
|
||||||
|
@At("")
|
||||||
|
@Ok("beetl:/platform/zhgh/democratic/proposal/query/plagiarismCheck/index.html")
|
||||||
|
@SaCheckPermission("proposal.query.plagiarismCheck")
|
||||||
|
public void index() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询
|
||||||
|
*
|
||||||
|
* @param keywords 关键字
|
||||||
|
* @param sessionId 会话id
|
||||||
|
* @param similarityThreshold 相似度阈值
|
||||||
|
* @param typeId 类型id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("proposal.query.plagiarismCheck")
|
||||||
|
@ApiOperation(value = "查询")
|
||||||
|
public Result query(String keywords, String sessionId, Integer similarityThreshold, @Param("matchScope") String[] matchScope, String typeId) {
|
||||||
|
// 1. 获取所有提案数据
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
info.*,
|
||||||
|
COUNT(p.consolidationIds) > 0 AS isConsolidation,
|
||||||
|
type.NAME AS typeName,
|
||||||
|
tcs.fullName AS sessionName,
|
||||||
|
tcd.`name` AS delegationName,
|
||||||
|
ins.id AS instanceId,
|
||||||
|
ins.businessNo,
|
||||||
|
ins.state instanceState,
|
||||||
|
ins.variable instanceVariable,
|
||||||
|
ins.processDefineId instanceProcessDefineId,
|
||||||
|
t.displayName curTaskName,
|
||||||
|
JSON_UNQUOTE(JSON_EXTRACT(caseTasks.variable, '$.tf_hostUnitName')) AS masterUnitName,
|
||||||
|
caseTasks.variable ->> '$.tf_helpUnitNameStr' AS slaveUnitNames
|
||||||
|
FROM
|
||||||
|
proposal_info info
|
||||||
|
LEFT JOIN proposal_consolidation p ON JSON_CONTAINS(p.consolidationIds, JSON_QUOTE(info.id))
|
||||||
|
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||||
|
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
|
||||||
|
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
|
||||||
|
LEFT JOIN teacher_congress_delegate tcde ON tcde.loginName = info.createUserLoginName
|
||||||
|
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||||
|
LEFT JOIN (SELECT processInstanceId, MAX(finishTime) AS finishTime FROM wf_process_task WHERE taskName = '9846ab38-40c5-4093-bafc-a9b3b443338b' AND taskState = 20 GROUP BY processInstanceId) latestTasks ON latestTasks.processInstanceId = ins.id
|
||||||
|
LEFT JOIN wf_process_task caseTasks ON caseTasks.processInstanceId = ins.id
|
||||||
|
AND caseTasks.taskName = '9846ab38-40c5-4093-bafc-a9b3b443338b'
|
||||||
|
AND caseTasks.taskState = 20
|
||||||
|
AND caseTasks.finishTime = latestTasks.finishTime
|
||||||
|
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id
|
||||||
|
AND t.taskState = 10
|
||||||
|
$condition
|
||||||
|
""");
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.andEX("info.sessionId", "=", sessionId);
|
||||||
|
cnd.andEX("info.typeId", "=", typeId);
|
||||||
|
cnd.groupBy("info.id");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
List<NutMap> list = proposalCommonService.listMap(sql);
|
||||||
|
|
||||||
|
// 2. 进行查重分析
|
||||||
|
List<NutMap> plagiarismResults = new ArrayList<>();
|
||||||
|
|
||||||
|
for (NutMap proposal : list) {
|
||||||
|
// 计算相似度
|
||||||
|
double similarity = TextSimilarityUtil.calculateProposalSimilarity(proposal, keywords, matchScope);
|
||||||
|
System.out.println(similarity);
|
||||||
|
|
||||||
|
// 只返回相似度大于阈值的结果
|
||||||
|
if (similarity >= similarityThreshold) {
|
||||||
|
// 构建查重结果
|
||||||
|
NutMap result = new NutMap();
|
||||||
|
result.put("id", proposal.getString("id"));
|
||||||
|
result.put("title", proposal.getString("name")); // 提案标题
|
||||||
|
result.put("sessionName", proposal.getString("sessionName"));
|
||||||
|
result.put("typeName", proposal.getString("typeName"));
|
||||||
|
result.put("createUserName", proposal.getString("createUserName"));
|
||||||
|
result.put("createTime", proposal.getString("createTime"));
|
||||||
|
result.put("similarity", Math.round(similarity)); // 相似度百分比
|
||||||
|
result.put("similarContent", TextSimilarityUtil.extractSimilarContent(proposal, keywords));
|
||||||
|
result.put("delegationName", proposal.getString("delegationName"));
|
||||||
|
result.put("unitName", proposal.getString("unitName"));
|
||||||
|
|
||||||
|
plagiarismResults.add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按相似度降序排序
|
||||||
|
plagiarismResults.sort((a, b) -> Integer.compare(b.getInt("similarity"), a.getInt("similarity")));
|
||||||
|
|
||||||
|
return Result.success(plagiarismResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
package com.budwk.app.zhgh.democratic.proposal.controller.query;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.flow.entity.ProcessInstance;
|
||||||
|
import com.budwk.app.flow.entity.ProcessTask;
|
||||||
|
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||||
|
import com.budwk.app.sys.models.Sys_dict;
|
||||||
|
import com.budwk.app.sys.services.SysDictService;
|
||||||
|
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||||
|
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.json.Json;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
import org.nutz.mvc.annotation.At;
|
||||||
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@IocBean
|
||||||
|
@At("/platform/proposal/query/underTake/satisfaction")
|
||||||
|
@Ok("json:full")
|
||||||
|
@Api(tags = "提案承办单位满意度")
|
||||||
|
public class ProposalUnderTakeSatisfactionController {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
@Inject
|
||||||
|
private SysDictService sysDictService;
|
||||||
|
|
||||||
|
@At("")
|
||||||
|
@Ok("beetl:/platform/zhgh/democratic/proposal/query/underTakeSatisfaction/index.html")
|
||||||
|
@SaCheckPermission("proposal.query.underTake.satisfaction")
|
||||||
|
public void index() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("proposal.query.underTake.satisfaction")
|
||||||
|
public Result data(String sessionId){
|
||||||
|
List<ProposalInfo> infos = dao.query(ProposalInfo.class, Cnd.where(ProposalInfo::getSessionId, "=", sessionId));
|
||||||
|
List<String> infoIds = infos.stream().map(v -> v.getId()).toList();
|
||||||
|
List<ProcessInstance> instances = dao.query(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "in", infoIds));
|
||||||
|
List<Long> instIds = instances.stream().map(v -> v.getId()).toList();
|
||||||
|
|
||||||
|
// 立案任务记录
|
||||||
|
List<ProcessTask> caseTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds)
|
||||||
|
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode())
|
||||||
|
.and(ProcessTask::getTaskName, "=", "9846ab38-40c5-4093-bafc-a9b3b443338b"));
|
||||||
|
|
||||||
|
// 主办任务记录
|
||||||
|
List<ProcessTask> masterUnitTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds)
|
||||||
|
.and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.DOING.getCode()))
|
||||||
|
.and(ProcessTask::getTaskName, "in", List.of("85cf23da-2dd5-4007-a1e9-bd9bfddc68f0", "5ed5a1be-0cd3-4b47-a0d5-238d53b61f34")));
|
||||||
|
|
||||||
|
// 协办任务记录
|
||||||
|
List<ProcessTask> slaveUnitTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds)
|
||||||
|
.and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.DOING.getCode()))
|
||||||
|
.and(ProcessTask::getTaskName, "=", "c9a1586e-272b-41b8-b438-0be9f9e3781d"));
|
||||||
|
|
||||||
|
|
||||||
|
// 所有承办单位任务记录
|
||||||
|
List<ProcessTask> allUnitTasks = new ArrayList<>();
|
||||||
|
allUnitTasks.addAll(masterUnitTasks);
|
||||||
|
allUnitTasks.addAll(slaveUnitTasks);
|
||||||
|
|
||||||
|
|
||||||
|
//该届次所有涉及到的承办单位
|
||||||
|
List<String> underTakeIds = caseTasks.stream().map(v -> {
|
||||||
|
NutMap variable = Json.fromJson(NutMap.class, v.getVariable());
|
||||||
|
String hostUnitId = variable.getString("tf_hostUnitId");
|
||||||
|
List<String> helpUnitIds = variable.getAsList("tf_helpUnitIds", String.class);
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.add(hostUnitId);
|
||||||
|
list.addAll(helpUnitIds);
|
||||||
|
return list;
|
||||||
|
})
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
|
||||||
|
List<ProposalUndertake> undertakes = dao.query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "in", underTakeIds));
|
||||||
|
Map<String, ProposalUndertake> undertakeMap = undertakes.stream().collect(Collectors.toMap(ProposalUndertake::getId, v -> v));
|
||||||
|
|
||||||
|
List<NutMap> tableData = new ArrayList<>();
|
||||||
|
|
||||||
|
// 反馈评分
|
||||||
|
List<Sys_dict> proposalFeedback = sysDictService.getSubListByCode("PROPOSAL_FEEDBACK");
|
||||||
|
|
||||||
|
for (String underTakeId : underTakeIds) {
|
||||||
|
NutMap tableRow = NutMap.NEW();
|
||||||
|
tableRow.put("id", underTakeId);
|
||||||
|
if (undertakeMap.get(underTakeId) == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tableRow.put("name", undertakeMap.get(underTakeId).getName());
|
||||||
|
|
||||||
|
//找出承办提案总数
|
||||||
|
long sum = caseTasks.stream()
|
||||||
|
.filter(v -> {
|
||||||
|
NutMap variable = Json.fromJson(NutMap.class, v.getVariable());
|
||||||
|
if (variable == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String hostUnitId = variable.getString("tf_hostUnitId");
|
||||||
|
List<String> helpUnitIds = variable.getAsList("tf_helpUnitIds", String.class);
|
||||||
|
|
||||||
|
boolean isHost = underTakeId != null && underTakeId.equals(hostUnitId);
|
||||||
|
boolean isHelper = helpUnitIds != null && helpUnitIds.contains(underTakeId);
|
||||||
|
|
||||||
|
return isHost || isHelper;
|
||||||
|
})
|
||||||
|
.map(ProcessTask::getProcessInstanceId)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
|
||||||
|
// 主办提案数量
|
||||||
|
long hostSum = masterUnitTasks.stream().map(v -> Json.fromJson(NutMap.class, v.getVariable())).filter(variable -> underTakeId.equals(variable.getString("unitId")) && variable.getBoolean("isMaster")).count();
|
||||||
|
|
||||||
|
// 协办提案数量
|
||||||
|
long slaveSum = slaveUnitTasks.stream().map(v -> Json.fromJson(NutMap.class, v.getVariable())).filter(variable -> underTakeId.equals(variable.getString("unitId")) && !variable.getBoolean("isMaster")).count();
|
||||||
|
|
||||||
|
// 满意
|
||||||
|
// allUnitTasks
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+571
@@ -0,0 +1,571 @@
|
|||||||
|
<!--#
|
||||||
|
layout("/layouts/platform.html"){
|
||||||
|
#-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.plagiarism-check-container {
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similarity-level {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similarity-level .level-label {
|
||||||
|
width: 100px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similarity-high {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similarity-medium {
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similarity-low {
|
||||||
|
color: var( --color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-left: 4px solid #ddd;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card.high-similarity {
|
||||||
|
border-left-color: #f56c6c;
|
||||||
|
background-color: #fef0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card.medium-similarity {
|
||||||
|
border-left-color: #e6a23c;
|
||||||
|
background-color: #fdf6ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card.low-similarity {
|
||||||
|
border-left-color: #67c23a;
|
||||||
|
background-color: #f0f9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similarity-progress {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.similar-content {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: #ffeb3b;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-number {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-result {
|
||||||
|
text-align: center;
|
||||||
|
padding: 50px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="app" v-cloak>
|
||||||
|
<div class="plagiarism-check-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-section">
|
||||||
|
<div slot="header">
|
||||||
|
<span style="font-weight: 600">📝 提案查重检测</span>
|
||||||
|
</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-input
|
||||||
|
v-model="searchForm.keywords"
|
||||||
|
placeholder="请输入要查重的关键字或提案内容..."
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
@keyup.ctrl.enter.native="startPlagiarismCheck"
|
||||||
|
></el-input>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<div style="height: 100%; display: flex; flex-direction: column; justify-content: space-between;">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="medium"
|
||||||
|
:loading="checking"
|
||||||
|
@click="startPlagiarismCheck"
|
||||||
|
style="margin-bottom: 10px;"
|
||||||
|
>
|
||||||
|
<i class="el-icon-search"></i> 开始查重
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="medium"
|
||||||
|
@click="clearSearch"
|
||||||
|
style="margin: 0"
|
||||||
|
>
|
||||||
|
<i class="el-icon-refresh-left"></i> 清空重置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 查重设置区域 -->
|
||||||
|
<el-card shadow="never" class="settings-section">
|
||||||
|
<div slot="header">
|
||||||
|
<span style="font-weight: 600">⚙️ 查重设置</span>
|
||||||
|
</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div>
|
||||||
|
<label>教代会届次:</label>
|
||||||
|
<el-select
|
||||||
|
v-model="searchForm.sessionId"
|
||||||
|
placeholder="选择届次"
|
||||||
|
clearable
|
||||||
|
style="width: 100%; margin-top: 5px;"
|
||||||
|
>
|
||||||
|
<el-option label="全部届次" value=""></el-option>
|
||||||
|
<el-option
|
||||||
|
v-for="item in sessionOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.fullName"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div>
|
||||||
|
<label>相似度阈值:{{ searchForm.similarityThreshold }}%</label>
|
||||||
|
<el-slider
|
||||||
|
v-model="searchForm.similarityThreshold"
|
||||||
|
:min="10"
|
||||||
|
:max="90"
|
||||||
|
:step="5"
|
||||||
|
show-stops
|
||||||
|
style="margin-top: 15px;"
|
||||||
|
></el-slider>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div>
|
||||||
|
<label>匹配范围:</label>
|
||||||
|
<el-select v-model="searchForm.matchScope" multiple clearable
|
||||||
|
style="width: 100%; margin-top: 5px;">
|
||||||
|
<el-option label="全部" value="all"></el-option>
|
||||||
|
<el-option label="案名" value="name"></el-option>
|
||||||
|
<el-option label="案由" value="brief"></el-option>
|
||||||
|
<el-option label="建议措施" value="measures"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div>
|
||||||
|
<label>提案类别:</label>
|
||||||
|
<el-select
|
||||||
|
v-model="searchForm.typeId"
|
||||||
|
placeholder="选择类别"
|
||||||
|
clearable
|
||||||
|
style="width: 100%; margin-top: 5px;"
|
||||||
|
>
|
||||||
|
<el-option label="全部类别" value=""></el-option>
|
||||||
|
<el-option
|
||||||
|
v-for="item in typeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 结果统计区域 -->
|
||||||
|
<el-card shadow="never" v-if="hasSearched && !checking">
|
||||||
|
<div slot="header">
|
||||||
|
<span>📊 查重结果统计</span>
|
||||||
|
</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stats-card">
|
||||||
|
<div class="stats-number">{{ totalResults }}</div>
|
||||||
|
<div>检测到相似提案</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stats-card">
|
||||||
|
<div class="stats-number similarity-high">{{ highSimilarityCount }}</div>
|
||||||
|
<div>高度相似 (≥80%)</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stats-card">
|
||||||
|
<div class="stats-number similarity-medium">{{ mediumSimilarityCount }}</div>
|
||||||
|
<div>中度相似 (60-79%)</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stats-card">
|
||||||
|
<div class="stats-number similarity-low">{{ lowSimilarityCount }}</div>
|
||||||
|
<div>低度相似 (10-59%)</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="checking" class="loading-container">
|
||||||
|
<el-icon class="is-loading"><i class="el-icon-loading"></i></el-icon>
|
||||||
|
<p>正在进行查重检测,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查重结果展示区域 -->
|
||||||
|
<div v-if="hasSearched && !checking">
|
||||||
|
<el-card shadow="never" v-if="plagiarismResults.length === 0">
|
||||||
|
<div class="empty-result">
|
||||||
|
<i class="el-icon-document" style="font-size: 48px; color: #c0c4cc;"></i>
|
||||||
|
<p>未发现相似提案</p>
|
||||||
|
<p style="font-size: 14px; color: #909399;">您可以尝试调整查重设置或更换关键词</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<!-- 结果操作工具栏 -->
|
||||||
|
<el-card shadow="never" style="margin-bottom: 20px;">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<span style="font-weight: bold;">共找到 {{ plagiarismResults.length }} 个相似提案</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" style="text-align: right;">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button size="mini" @click="sortResults('similarity')">
|
||||||
|
<i class="el-icon-sort"></i> 按相似度排序
|
||||||
|
</el-button>
|
||||||
|
<el-button size="mini" @click="sortResults('time')">
|
||||||
|
<i class="el-icon-time"></i> 按时间排序
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 结果筛选 -->
|
||||||
|
<el-card shadow="never" style="margin-bottom: 20px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 20px;">
|
||||||
|
<span>快速筛选:</span>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:type="resultFilter === 'all' ? 'primary' : ''"
|
||||||
|
@click="filterResults('all')"
|
||||||
|
>
|
||||||
|
全部 ({{ totalResults }})
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:type="resultFilter === 'high' ? 'danger' : ''"
|
||||||
|
@click="filterResults('high')"
|
||||||
|
>
|
||||||
|
高度相似 ({{ highSimilarityCount }})
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:type="resultFilter === 'medium' ? 'warning' : ''"
|
||||||
|
@click="filterResults('medium')"
|
||||||
|
>
|
||||||
|
中度相似 ({{ mediumSimilarityCount }})
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:type="resultFilter === 'low' ? 'success' : ''"
|
||||||
|
@click="filterResults('low')"
|
||||||
|
>
|
||||||
|
低度相似 ({{ lowSimilarityCount }})
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 结果列表 -->
|
||||||
|
<el-card
|
||||||
|
v-for="(result, index) in filteredResults"
|
||||||
|
:key="index"
|
||||||
|
shadow="hover"
|
||||||
|
:class="['result-card', getSimilarityClass(result.similarity)]"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="18">
|
||||||
|
<h4 style="margin: 0 0 10px 0; color: #303133;">
|
||||||
|
{{ result.title }}
|
||||||
|
</h4>
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<el-tag size="mini" type="info">{{ result.sessionName }}</el-tag>
|
||||||
|
<el-tag size="mini" type="primary" style="margin-left: 5px;">{{ result.typeName }}
|
||||||
|
</el-tag>
|
||||||
|
<span style="margin-left: 10px; color: #909399; font-size: 12px;">
|
||||||
|
提案人: {{ result.createUserName }} | {{ result.createTime }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" style="text-align: right;">
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<span :class="getSimilarityClass(result.similarity)"
|
||||||
|
style="font-size: 18px; font-weight: bold;">
|
||||||
|
{{ result.similarity }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="result.similarity"
|
||||||
|
:color="getSimilarityColor(result.similarity)"
|
||||||
|
:show-text="false"
|
||||||
|
class="similarity-progress"
|
||||||
|
></el-progress>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<div class="similar-content" v-if="result.similarContent">
|
||||||
|
<strong>相似内容片段:</strong>
|
||||||
|
<div v-html="highlightSimilarText(result.similarContent)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<el-button size="mini" type="primary" @click="viewProposalDetail(result)">
|
||||||
|
<i class="el-icon-view"></i> 查看详情
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog title="提案详情" :visible.sync="viewDialogVisible" width="70%">
|
||||||
|
<proposal-info ref="infoRef"></proposal-info>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
<!--#include("../../common/info.js"){}#-->
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
dicts: ["PROPOSAL_TYPE", "PROPOSAL_CASE_FILING_RESULT"],
|
||||||
|
mixins: [initTableMixins],
|
||||||
|
components: {
|
||||||
|
"proposal-info": PROPOSAL_INFO
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
checking: false,
|
||||||
|
hasSearched: false,
|
||||||
|
sessionOptions: [],
|
||||||
|
typeOptions: [],
|
||||||
|
searchForm: {
|
||||||
|
keywords: '',
|
||||||
|
sessionId: '',
|
||||||
|
similarityThreshold: 10,
|
||||||
|
matchScope: [],
|
||||||
|
typeId: ''
|
||||||
|
},
|
||||||
|
plagiarismResults: [],
|
||||||
|
resultFilter: 'all',
|
||||||
|
sortBy: 'similarity',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
// 模拟数据,实际应从后端获取
|
||||||
|
mockResults: [],
|
||||||
|
|
||||||
|
viewDialogVisible: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredResults() {
|
||||||
|
let results = [...this.plagiarismResults];
|
||||||
|
|
||||||
|
// 筛选
|
||||||
|
if (this.resultFilter !== 'all') {
|
||||||
|
results = results.filter(result => {
|
||||||
|
if (this.resultFilter === 'high') return result.similarity >= 80;
|
||||||
|
if (this.resultFilter === 'medium') return result.similarity >= 60 && result.similarity < 80;
|
||||||
|
if (this.resultFilter === 'low') return result.similarity < 60;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
results.sort((a, b) => {
|
||||||
|
let aValue, bValue;
|
||||||
|
if (this.sortBy === 'similarity') {
|
||||||
|
aValue = a.similarity;
|
||||||
|
bValue = b.similarity;
|
||||||
|
} else if (this.sortBy === 'time') {
|
||||||
|
aValue = new Date(a.createTime);
|
||||||
|
bValue = new Date(b.createTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sortOrder === 'desc') {
|
||||||
|
return bValue - aValue;
|
||||||
|
} else {
|
||||||
|
return aValue - bValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
totalResults() {
|
||||||
|
return this.plagiarismResults.length;
|
||||||
|
},
|
||||||
|
highSimilarityCount() {
|
||||||
|
return this.plagiarismResults.filter(item => item.similarity >= 80).length;
|
||||||
|
},
|
||||||
|
mediumSimilarityCount() {
|
||||||
|
return this.plagiarismResults.filter(item => item.similarity >= 60 && item.similarity < 80).length;
|
||||||
|
},
|
||||||
|
lowSimilarityCount() {
|
||||||
|
return this.plagiarismResults.filter(item => item.similarity >= 10 && item.similarity < 60).length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
startPlagiarismCheck() {
|
||||||
|
if (!this.searchForm.keywords.trim()) {
|
||||||
|
this.$message.warning('请输入要查重的关键字或内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checking = true;
|
||||||
|
this.hasSearched = true;
|
||||||
|
|
||||||
|
this.$axios.post('/platform/proposal/query/plagiarismCheck/query', {
|
||||||
|
...this.searchForm,
|
||||||
|
matchScope: JSON.stringify(this.searchForm.matchScope)
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.plagiarismResults = res.data;
|
||||||
|
if (this.plagiarismResults.length > 0) {
|
||||||
|
this.$message.success('查重完成,发现 ' + this.plagiarismResults.length + ' 个相似提案');
|
||||||
|
} else {
|
||||||
|
this.$message.info('查重完成,未发现相似提案');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.checking = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSearch() {
|
||||||
|
this.searchForm.keywords = '';
|
||||||
|
this.searchForm.sessionId = '';
|
||||||
|
this.searchForm.typeId = '';
|
||||||
|
this.searchForm.similarityThreshold = 60;
|
||||||
|
this.searchForm.matchScope = [];
|
||||||
|
this.plagiarismResults = [];
|
||||||
|
this.hasSearched = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSimilarityClass(similarity) {
|
||||||
|
if (similarity >= 80) return 'high-similarity';
|
||||||
|
if (similarity >= 60) return 'medium-similarity';
|
||||||
|
return 'low-similarity';
|
||||||
|
},
|
||||||
|
|
||||||
|
getSimilarityColor(similarity) {
|
||||||
|
if (similarity >= 80) return '#f56c6c';
|
||||||
|
if (similarity >= 60) return '#e6a23c';
|
||||||
|
return '#67c23a';
|
||||||
|
},
|
||||||
|
|
||||||
|
//关键词高亮处理
|
||||||
|
highlightSimilarText(text) {
|
||||||
|
const keywords = this.searchForm.keywords.split(/\s+/).filter(k => k.trim());
|
||||||
|
let highlightedText = text;
|
||||||
|
|
||||||
|
keywords.forEach(keyword => {
|
||||||
|
const regex = new RegExp('(' + keyword + ')', 'gi');
|
||||||
|
highlightedText = highlightedText.replace(regex, '<span class="highlight">$1</span>');
|
||||||
|
});
|
||||||
|
|
||||||
|
return highlightedText;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 排序功能
|
||||||
|
sortResults(type) {
|
||||||
|
if (this.sortBy === type) {
|
||||||
|
this.sortOrder = this.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||||
|
} else {
|
||||||
|
this.sortBy = type;
|
||||||
|
this.sortOrder = 'desc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 筛选功能
|
||||||
|
filterResults(filter) {
|
||||||
|
this.resultFilter = filter;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查看提案详情功能
|
||||||
|
viewProposalDetail(result) {
|
||||||
|
this.viewDialogVisible = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.infoRef.onOpen(result)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
listSession() {
|
||||||
|
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.sessionOptions = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
listProposalType() {
|
||||||
|
this.$axios.post("/platform/proposal/common/listProposalType").then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.typeOptions = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.listSession();
|
||||||
|
this.listProposalType();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--#
|
||||||
|
}
|
||||||
|
#-->
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user