1 changed files with 675 additions and 0 deletions
@ -0,0 +1,675 @@
|
||||
<template> |
||||
<div class="container"> |
||||
<!-- 标签页组件 --> |
||||
<el-tabs v-model="activeTab" class="mb-20"> |
||||
<!-- 个人极端赋分标签 --> |
||||
<el-tab-pane label="个人极端赋分" name="riskScoreRule"> |
||||
<!-- 个人极端赋分内容 --> |
||||
<div class="tab-content" v-loading="riskScoreLoading"> |
||||
<header class="mb-20"> |
||||
<div class="flex between"> |
||||
<div> |
||||
<el-button type="primary" @click="handleAddRiskScoreRule"> |
||||
<template #icon> |
||||
<icon name="el-icon-Plus" /> |
||||
</template> |
||||
新增 |
||||
</el-button> |
||||
</div> |
||||
<div style="width: 50%"> |
||||
<el-row> </el-row> |
||||
</div> |
||||
</div> |
||||
<div class="flex between mt-10 v-center"> |
||||
<div> |
||||
<div> |
||||
<span class="text-primary">个人极端暴力风险指数</span> = ((基础因素得分 × 权重)+(诱发因素得分 × 权重)+(行为因素得分 × 权重)+(管控因素得分 × 权重)) × 20 |
||||
</div> |
||||
</div> |
||||
<a |
||||
class="flex v-center gap file-link" |
||||
:href="`${BASE_PATH}/templates/【工作机制】个人极端暴力风险数字督察灵敏感知体系风险赋分及预警处置机制.doc`" |
||||
target="__blank" |
||||
> |
||||
<icon name="local-icon-pdf" :size="38" /> |
||||
<span>个人极端风险赋分规则.pdf</span> |
||||
</a> |
||||
</div> |
||||
</header> |
||||
<div class="table-container"> |
||||
<el-table |
||||
ref="riskScoreTableRef" |
||||
:default-expand-all="false" |
||||
:data="scoreRules" |
||||
:expand-row-keys="[]" |
||||
:tree-props="{ |
||||
children: 'children', |
||||
hasChildren: 'hasChildren', |
||||
}" |
||||
row-key="id" |
||||
> |
||||
<el-table-column |
||||
label="问题条目" |
||||
prop="riskName" |
||||
show-overflow-tooltip |
||||
width="180" |
||||
/> |
||||
<el-table-column |
||||
label="一级指标" |
||||
prop="riskIndex" |
||||
show-overflow-tooltip |
||||
width="180" |
||||
/> |
||||
<el-table-column |
||||
label="因素分值" |
||||
prop="score" |
||||
width="200" |
||||
align="center" |
||||
> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="赋分规则" |
||||
prop="ruleDesc" |
||||
show-overflow-tooltip |
||||
> |
||||
</el-table-column> |
||||
<el-table-column label="因素权重" width="100" align="center"> |
||||
<template #default="{ row }"> |
||||
<span v-if="row.level === 1">{{ getChildWeightSum(row) }}</span> |
||||
<span v-if="row.level === 2">{{ row.weight }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="开启状态" width="100"> |
||||
<template #default="{ row }"> |
||||
<el-tag type="success" v-if="row.status === true">开启</el-tag> |
||||
<el-tag type="danger" v-if="row.status === false">关闭</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="最后更新时间" |
||||
prop="updateTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column label="操作" width="160"> |
||||
<template #default="{ row }"> |
||||
<el-button type="primary" link @click="handleEditRiskScoreRule(row)">编辑</el-button> |
||||
<el-button type="danger" link @click="handleDeleteRiskScoreRule(row)">删除</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
</div> |
||||
</el-tab-pane> |
||||
|
||||
<!-- 问题赋分标签 --> |
||||
<el-tab-pane label="问题赋分" name="dictContent"> |
||||
<!-- 问题赋分内容 --> |
||||
<div class="tab-content" v-loading="dictContentLoading"> |
||||
<header class="mb-20"> |
||||
<div class="flex between"> |
||||
<div> |
||||
<el-button type="primary" @click="handleAddDictContent"> |
||||
<template #icon> |
||||
<icon name="el-icon-Plus" /> |
||||
</template> |
||||
新增 |
||||
</el-button> |
||||
</div> |
||||
<div style="width: 50%"> |
||||
<el-row> |
||||
<el-col :span="12"></el-col> |
||||
<el-col :span="12"> |
||||
<div class="flex end"> |
||||
<el-button type="primary" @click="handleCalculateDictContent">重新计算分值</el-button> |
||||
</div> |
||||
</el-col> |
||||
</el-row> |
||||
</div> |
||||
</div> |
||||
<div class="flex between mt-10"> |
||||
<div> |
||||
<div class="text-primary mt-10 mb-10">赋分分公式如下</div> |
||||
<div>问题风险值 = 基础风险值+(基础风险值×问题严重等级系数)+(基础风险值×问题发生频次系数)</div> |
||||
</div> |
||||
<a |
||||
class="flex v-center gap file-link" |
||||
:href="`${BASE_PATH}/templates/长沙公安数字督察灵敏感知体系问题赋分及风险预警机制.pdf`" |
||||
target="__blank" |
||||
> |
||||
<icon name="local-icon-pdf" :size="38" /> |
||||
<span>问题赋分机制.pdf</span> |
||||
</a> |
||||
</div> |
||||
</header> |
||||
<div class="table-container"> |
||||
<el-table :data="dictContents" row-key="id" :default-expand-all="false"> |
||||
<el-table-column |
||||
label="问题条目" |
||||
prop="name" |
||||
show-overflow-tooltip |
||||
/> |
||||
<el-table-column label="基础分值" prop="score" width="200" align="center"> |
||||
<template #default="{ row }"> |
||||
<span v-if="row.level === 1">{{ getScoreRange(row) }}</span> |
||||
<span v-else>{{ row.score }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="问题严重等级" |
||||
prop="isActiveLevel" |
||||
align="center" |
||||
> |
||||
<template #default="{ row }"> |
||||
<el-tag type="success" v-if="row.isActiveLevel === true">开启</el-tag> |
||||
<el-tag type="danger" v-if="row.isActiveLevel === false">关闭</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="序号" |
||||
prop="sort" |
||||
width="100" |
||||
align="center" |
||||
/> |
||||
<el-table-column label="最后更新时间" prop="updTime" /> |
||||
<el-table-column label="操作"> |
||||
<template #default="{ row }"> |
||||
<el-button type="primary" link @click="handleEditDictContent(row)">编辑</el-button> |
||||
<el-button type="danger" link @click="handleDeleteDictContent(row)">删除</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
<div class="mt-40"> |
||||
<div>基础分值设定规则:</div> |
||||
<p>1、二级问题条目设定的基础分值,作为其下属三级问题条目的默认基础分值。</p> |
||||
<p>2、若三级问题条目已单独设置基础分值,则赋分时以该三级条目的基础分值为准。</p> |
||||
<p>3、如三级问题条目未设置基础分值,则采用其所属二级问题条目的基础分值进行赋分。</p> |
||||
</div> |
||||
</div> |
||||
</el-tab-pane> |
||||
</el-tabs> |
||||
|
||||
<!-- 个人极端赋分编辑对话框 --> |
||||
<el-dialog |
||||
v-model="riskScoreShow" |
||||
:title="riskScoreMode === 'add' ? '新增赋分规则' : '编辑赋分规则'" |
||||
width="600" |
||||
> |
||||
<el-form label-width="120" ref="riskScoreFormRef" :model="riskScoreFormData"> |
||||
<el-form-item |
||||
label="父级节点" |
||||
:rules="{ |
||||
required: true, |
||||
message: '请选择父级节点', |
||||
}" |
||||
prop="pid" |
||||
> |
||||
<el-tree-select |
||||
class="flex-1" |
||||
v-model="riskScoreFormData.pid" |
||||
:data="treeOptions" |
||||
clearable |
||||
node-key="id" |
||||
:props="{ |
||||
label: 'riskName', |
||||
}" |
||||
:default-expanded-keys="[MENU_ROOT_ID]" |
||||
placeholder="请选择父级节点" |
||||
check-strictly |
||||
filterable |
||||
@node-click="(node) => riskScoreFormData.pLevel = node.level" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item |
||||
label="一级指标" |
||||
prop="riskIndex" |
||||
:rules="{ |
||||
required: true, |
||||
message: '请选择一级值班', |
||||
}" |
||||
v-if="riskScoreFormData.pLevel === 1" |
||||
> |
||||
<el-select clearable v-model="riskScoreFormData.riskIndex"> |
||||
<el-option |
||||
v-for="item in dict.riskIndex" |
||||
:key="item.id" |
||||
:label="item.dictLabel" |
||||
:value="item.dictValue" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item |
||||
label="风险因素" |
||||
prop="riskName" |
||||
:rules="{ |
||||
required: true, |
||||
message: '请输入风险因素', |
||||
}" |
||||
> |
||||
<el-input |
||||
v-model="riskScoreFormData.riskName" |
||||
placeholder="请输入" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="因素分值" prop="score"> |
||||
<el-input |
||||
v-model="riskScoreFormData.score" |
||||
placeholder="请输入序号" |
||||
type="number" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="赋分规则" prop="ruleDesc"> |
||||
<el-input |
||||
v-model="riskScoreFormData.ruleDesc" |
||||
placeholder="请输入序号" |
||||
type="textarea" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="因素权重" prop="weight"> |
||||
<el-input |
||||
v-model="riskScoreFormData.weight" |
||||
placeholder="请输入权重" |
||||
type="number" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
</el-form> |
||||
<footer class="flex end"> |
||||
<el-button @click="riskScoreShow = false">取消</el-button> |
||||
<el-button type="primary" @click="submitRiskScoreRule">确定</el-button> |
||||
</footer> |
||||
</el-dialog> |
||||
|
||||
<!-- 问题赋分编辑对话框 --> |
||||
<el-dialog |
||||
v-model="dictContentShow" |
||||
:title="dictContentMode === 'add' ? '新增问题类型' : '编辑问题类型'" |
||||
width="600" |
||||
> |
||||
<el-form label-width="120" ref="dictContentFormRef" :model="dictContentFormData"> |
||||
<el-form-item |
||||
label="父级节点" |
||||
:rules="{ |
||||
required: true, |
||||
message: '请选择父级节点', |
||||
}" |
||||
prop="parentCode" |
||||
> |
||||
<el-tree-select |
||||
class="flex-1" |
||||
v-model="dictContentFormData.parentCode" |
||||
:data="dictContentOptions" |
||||
clearable |
||||
node-key="id" |
||||
:props="{ |
||||
label: 'name', |
||||
}" |
||||
:default-expanded-keys="[DICT_CONTENT_ROOT_ID]" |
||||
placeholder="请选择父级节点" |
||||
check-strictly |
||||
filterable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item |
||||
label="问题条目" |
||||
prop="name" |
||||
:rules="{ |
||||
required: true, |
||||
message: '请输入问题条目', |
||||
}" |
||||
> |
||||
<el-input |
||||
v-model="dictContentFormData.name" |
||||
placeholder="请输入问题条目" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="基础分值" prop="score" v-if="dictContentFormData.parentCode != DICT_CONTENT_ROOT_ID"> |
||||
<el-input |
||||
v-model="dictContentFormData.score" |
||||
placeholder="请输入序号" |
||||
type="number" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="序号" prop="sort"> |
||||
<el-input |
||||
v-model="dictContentFormData.sort" |
||||
placeholder="请输入序号" |
||||
type="number" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item |
||||
label="问题严重等级" |
||||
prop="isActiveLevel" |
||||
:rules="{ |
||||
required: true, |
||||
message: '请选择问题严重等级', |
||||
trigger: ['blur'], |
||||
}" |
||||
> |
||||
<el-switch |
||||
v-model="dictContentFormData.isActiveLevel" |
||||
inline-prompt |
||||
active-text="开启" |
||||
inactive-text="关闭" |
||||
:active-value="true" |
||||
:inactive-value="false" |
||||
/> |
||||
</el-form-item> |
||||
</el-form> |
||||
<footer class="flex end"> |
||||
<el-button @click="dictContentShow = false">取消</el-button> |
||||
<el-button type="primary" @click="submitDictContent">确定</el-button> |
||||
</footer> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { ref, watch, onMounted } from 'vue'; |
||||
import { BASE_PATH } from "@/api/request"; |
||||
import feedback from "@/utils/feedback"; |
||||
import useCatchStore from "@/stores/modules/catch"; |
||||
import { MENU_ROOT_ID } from "@/enums/appEnums"; |
||||
import { DICT_CONTENT_ROOT_ID } from "@/enums/appEnums"; |
||||
import { calculateScore } from "@/api/work/negative"; |
||||
|
||||
// 导入个人极端赋分相关接口 |
||||
import { |
||||
listRiskScoreRuleTreeOld, |
||||
addRiskScoreRule, |
||||
updateRiskScoreRule, |
||||
delRiskScoreRule, |
||||
} from "@/api/sensitivePerception/riskScoreRule"; |
||||
|
||||
// 导入问题赋分相关接口 |
||||
import { |
||||
listDictContentTree, |
||||
addDictContent, |
||||
updateDictContent, |
||||
delDictContent, |
||||
} from "@/api/system/dictContent"; |
||||
|
||||
// 标签页状态 |
||||
const activeTab = ref('riskScoreRule'); |
||||
|
||||
// 个人极端赋分相关状态 |
||||
const riskScoreLoading = ref(false); |
||||
const scoreRules = ref([]); |
||||
const treeOptions = ref([ |
||||
{ |
||||
id: MENU_ROOT_ID, |
||||
riskName: "顶级", |
||||
children: [], |
||||
}, |
||||
]); |
||||
const riskScoreShow = ref(false); |
||||
const riskScoreFormData = ref({}); |
||||
const riskScoreFormRef = ref(); |
||||
const riskScoreMode = ref<string>("add"); |
||||
|
||||
// 问题赋分相关状态 |
||||
const dictContentLoading = ref(false); |
||||
const dictContents = ref([]); |
||||
const dictContentOptions = ref([ |
||||
{ |
||||
id: DICT_CONTENT_ROOT_ID, |
||||
name: "顶级", |
||||
children: [], |
||||
}, |
||||
]); |
||||
const dictContentShow = ref(false); |
||||
const dictContentFormData = ref({}); |
||||
const dictContentFormRef = ref(); |
||||
const dictContentMode = ref<string>("add"); |
||||
|
||||
// 获取字典数据 |
||||
const catchStore = useCatchStore(); |
||||
const dict = catchStore.getDicts(["riskIndex"]); |
||||
|
||||
// 个人极端赋分相关方法 |
||||
// 获取个人极端赋分列表 |
||||
function getRiskScoreList() { |
||||
riskScoreLoading.value = true; |
||||
listRiskScoreRuleTreeOld().then((data) => { |
||||
scoreRules.value = data; |
||||
treeOptions.value[0].children = data.map((item) => { |
||||
return { |
||||
id: item.id, |
||||
riskName: item.riskName, |
||||
level: item.level |
||||
}; |
||||
}); |
||||
riskScoreLoading.value = false; |
||||
}); |
||||
} |
||||
|
||||
// 监听个人极端赋分模式变化 |
||||
watch(riskScoreMode, (val) => { |
||||
if (val === "add") { |
||||
riskScoreFormData.value = {}; |
||||
if (riskScoreFormRef.value) { |
||||
riskScoreFormRef.value.resetFields(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// 提交个人极端赋分表单 |
||||
async function submitRiskScoreRule() { |
||||
await riskScoreFormRef.value.validate(); |
||||
if (riskScoreMode.value === "add") { |
||||
await addRiskScoreRule(riskScoreFormData.value); |
||||
const parentCode = riskScoreFormData.value.parentCode; |
||||
riskScoreFormData.value = {}; |
||||
riskScoreFormRef.value.resetFields(); |
||||
riskScoreFormData.value.parentCode = parentCode; |
||||
} else { |
||||
await updateRiskScoreRule(riskScoreFormData.value); |
||||
} |
||||
riskScoreShow.value = false; |
||||
getRiskScoreList(); |
||||
} |
||||
|
||||
// 新增个人极端赋分 |
||||
function handleAddRiskScoreRule() { |
||||
riskScoreShow.value = true; |
||||
riskScoreMode.value = "add"; |
||||
} |
||||
|
||||
// 编辑个人极端赋分 |
||||
function handleEditRiskScoreRule(row) { |
||||
riskScoreShow.value = true; |
||||
riskScoreMode.value = "edit"; |
||||
riskScoreFormData.value = { ...row }; |
||||
} |
||||
|
||||
// 删除个人极端赋分 |
||||
const handleDeleteRiskScoreRule = async (row) => { |
||||
await feedback.confirm(`确定要删除 "${row.name}"?`); |
||||
await delRiskScoreRule(row.id); |
||||
getRiskScoreList(); |
||||
feedback.msgSuccess("删除成功"); |
||||
}; |
||||
|
||||
// 计算子节点权重和 |
||||
function getChildWeightSum(row) { |
||||
const arr = row.children |
||||
.filter((item) => item.status) |
||||
.map((item) => item.weight || 0); |
||||
if (arr.length === 0) { |
||||
return 0; |
||||
} |
||||
return arr.reduce((a, b) => a + b).toFixed(2); |
||||
} |
||||
|
||||
// 问题赋分相关方法 |
||||
// 获取问题赋分列表 |
||||
function getDictContentList() { |
||||
dictContentLoading.value = true; |
||||
listDictContentTree().then((data) => { |
||||
dictContents.value = data; |
||||
dictContentOptions.value[0].children = data; |
||||
dictContentLoading.value = false; |
||||
}); |
||||
} |
||||
|
||||
// 监听问题赋分模式变化 |
||||
watch(dictContentMode, (val) => { |
||||
if (val === "add") { |
||||
dictContentFormData.value = {}; |
||||
if (dictContentFormRef.value) { |
||||
dictContentFormRef.value.resetFields(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// 提交问题赋分表单 |
||||
async function submitDictContent() { |
||||
await dictContentFormRef.value.validate(); |
||||
if (dictContentMode.value === "add") { |
||||
await addDictContent(dictContentFormData.value); |
||||
const parentCode = dictContentFormData.value.parentCode; |
||||
dictContentFormData.value = {}; |
||||
dictContentFormRef.value.resetFields(); |
||||
dictContentFormData.value.parentCode = parentCode; |
||||
} else { |
||||
await updateDictContent(dictContentFormData.value); |
||||
} |
||||
dictContentShow.value = false; |
||||
getDictContentList(); |
||||
} |
||||
|
||||
// 新增问题赋分 |
||||
function handleAddDictContent() { |
||||
dictContentShow.value = true; |
||||
dictContentMode.value = "add"; |
||||
} |
||||
|
||||
// 编辑问题赋分 |
||||
function handleEditDictContent(row) { |
||||
dictContentShow.value = true; |
||||
dictContentMode.value = "edit"; |
||||
dictContentFormData.value = { ...row }; |
||||
} |
||||
|
||||
// 删除问题赋分 |
||||
const handleDeleteDictContent = async (row) => { |
||||
await feedback.confirm(`确定要删除 "${row.name}"?`); |
||||
await delDictContent(row.id); |
||||
getDictContentList(); |
||||
feedback.msgSuccess("删除成功"); |
||||
}; |
||||
|
||||
// 计算分值范围 |
||||
function getScoreRange(row) { |
||||
const sorceSet = new Set(); |
||||
row.children.forEach((item) => { |
||||
if (item.score) { |
||||
sorceSet.add(item.score); |
||||
} |
||||
item.children.forEach((j) => { |
||||
if (j.score) { |
||||
sorceSet.add(j.score); |
||||
} |
||||
}); |
||||
}); |
||||
if (sorceSet.size === 0) { |
||||
return '' |
||||
} |
||||
if (sorceSet.size === 1) { |
||||
return sorceSet.values().next().value |
||||
} |
||||
const min = Math.min(...sorceSet); |
||||
const max = Math.max(...sorceSet); |
||||
return `${min}-${max}` |
||||
} |
||||
|
||||
// 重新计算风险指数 |
||||
async function handleCalculateDictContent() { |
||||
await feedback.confirm("确定要重新计算风险指数?"); |
||||
dictContentLoading.value = true |
||||
await calculateScore() |
||||
feedback.msgSuccess("风险指数计算完成"); |
||||
dictContentLoading.value = false |
||||
} |
||||
|
||||
// 页面挂载时加载数据 |
||||
onMounted(() => { |
||||
getRiskScoreList(); |
||||
getDictContentList(); |
||||
}); |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.file-link { |
||||
font-size: 16px; |
||||
text-decoration: none; |
||||
color: #19257d; |
||||
padding: 0 8px; |
||||
border-radius: 8px; |
||||
&:hover { |
||||
background-color: #eee; |
||||
} |
||||
} |
||||
|
||||
.tab-content { |
||||
background: #fff; |
||||
padding: 20px; |
||||
border-radius: 8px; |
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
||||
} |
||||
|
||||
header { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.table-container { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.flex { |
||||
display: flex; |
||||
} |
||||
|
||||
.between { |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.v-center { |
||||
align-items: center; |
||||
} |
||||
|
||||
.mt-10 { |
||||
margin-top: 10px; |
||||
} |
||||
|
||||
.mb-10 { |
||||
margin-bottom: 10px; |
||||
} |
||||
|
||||
.mb-20 { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.mt-40 { |
||||
margin-top: 40px; |
||||
} |
||||
|
||||
.gap { |
||||
gap: 8px; |
||||
} |
||||
|
||||
.text-primary { |
||||
color: #1989fa; |
||||
} |
||||
|
||||
.end { |
||||
justify-content: flex-end; |
||||
} |
||||
</style> |
||||
<!-- 这是ai生成的页面 --> |
||||
Loading…
Reference in new issue