..
This commit is contained in:
+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