1. 指标子集映射
This commit is contained in:
parent
c1a401fcc9
commit
49f0bbcc01
|
|
@ -1,240 +1,205 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>AHP 两两比较矩阵(Tabler风格)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!-- Tabler Core CSS(如果项目已全局引入,可移除) -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css" rel="stylesheet"/>
|
||||
<title>指标设置 Demo</title>
|
||||
|
||||
<!-- Tabler 样式(也可换成你项目内置的 tabler.min.css) -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css"/>
|
||||
|
||||
<style>
|
||||
.matrix-wrap {
|
||||
max-height: 60vh;
|
||||
overflow: auto; /* 同时允许横向+纵向滚动 */
|
||||
border: 1px solid #e3e8ee;
|
||||
border-radius: .5rem;
|
||||
background: #fff;
|
||||
:root{
|
||||
/* 你的主题主色,可按项目实际调整 */
|
||||
--accent: #206bc4;
|
||||
}
|
||||
table.matrix { min-width: 720px; table-layout: fixed; }
|
||||
.matrix thead th {
|
||||
position: sticky; top: 0; z-index: 3;
|
||||
background: #fff; box-shadow: 0 2px 0 rgba(0,0,0,.03);
|
||||
|
||||
/* 强调卡片 */
|
||||
.card-priority{
|
||||
border: 2px solid var(--accent);
|
||||
box-shadow: 0 10px 30px rgba(32,107,196,.16);
|
||||
background: linear-gradient(180deg, rgba(32,107,196,.04), rgba(32,107,196,.02));
|
||||
position: sticky; /* 吸顶 */
|
||||
top: 12px;
|
||||
z-index: 3;
|
||||
transition: box-shadow .18s ease, transform .18s ease;
|
||||
}
|
||||
.matrix th:first-child, .matrix td:first-child {
|
||||
position: sticky; left: 0; z-index: 2; background: #fff;
|
||||
box-shadow: 2px 0 0 rgba(0,0,0,.03);
|
||||
.card-priority .card-header{
|
||||
background: linear-gradient(90deg, rgba(32,107,196,.10), rgba(32,107,196,0));
|
||||
border-bottom: 1px solid rgba(32,107,196,.2);
|
||||
}
|
||||
.priority-dot{
|
||||
width: 10px; height: 10px; border-radius: 999px;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 4px rgba(32,107,196,.15);
|
||||
margin-right: .5rem;
|
||||
}
|
||||
.card-priority:hover,
|
||||
.card-priority:focus-within{
|
||||
box-shadow: 0 14px 40px rgba(32,107,196,.24);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 切换时的短暂高亮 */
|
||||
@keyframes flashHighlight{
|
||||
0% { box-shadow: 0 0 0 0 rgba(32,107,196,0); }
|
||||
20% { box-shadow: 0 0 0 6px rgba(32,107,196,.25); }
|
||||
100% { box-shadow: 0 10px 30px rgba(32,107,196,.16); }
|
||||
}
|
||||
.card-priority.flash{ animation: flashHighlight .9s ease-out; }
|
||||
|
||||
/* 次级卡片更弱一些,拉开层级 */
|
||||
.card-secondary{
|
||||
border: 1px solid rgba(0,0,0,.06);
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,.06);
|
||||
}
|
||||
.matrix .diag { background: #f6f8fb; text-align: center; font-weight: 600; }
|
||||
.w-col { width: 110px; }
|
||||
.select-judge { width: 100%; }
|
||||
.cell-readonly { background: #fafbfc; color:#666; }
|
||||
.small-note { font-size: .875rem; color: #666; }
|
||||
.badge-cr { margin-left: .5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="layout-fluid">
|
||||
<body class="theme-light">
|
||||
<div class="page">
|
||||
<header class="navbar navbar-expand-md navbar-light d-print-none" style="border-bottom:1px solid rgba(0,0,0,.06);">
|
||||
<div class="container-xl">
|
||||
<h2 class="navbar-brand">海上评估系统</h2>
|
||||
<div class="ms-auto text-muted">管理员</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">两两比较矩阵</h3>
|
||||
<!-- 重点卡片:选择指标 -->
|
||||
<div class="card card-priority mb-3" id="cardPriority">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<span class="priority-dot"></span>
|
||||
<h3 class="card-title mb-0">选择指标
|
||||
<span class="badge bg-primary ms-2">必填</span>
|
||||
</h3>
|
||||
<div class="ms-auto">
|
||||
<span class="small-note">选择“行 相对 列”的重要性(1=同等,3/5/7/9=逐级更重要)</span>
|
||||
<button class="btn btn-primary" id="btnNext">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div id="matrixContainer" class="matrix-wrap">
|
||||
<table id="matrixTable" class="table table-vcenter card-table matrix">
|
||||
<!-- JS 动态生成 -->
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex flex-wrap gap-2">
|
||||
<button id="btnEven" class="btn btn-outline-secondary">上三角置1(同等重要)</button>
|
||||
<button id="btnReset" class="btn btn-outline-warning">重置</button>
|
||||
<button id="btnExport" class="btn btn-outline-primary">导出矩阵JSON</button>
|
||||
<span id="crBadge" class="badge badge-cr">CR: --</span>
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="metricSelect" class="col-form-label">指标列表</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-lg-4">
|
||||
<select id="metricSelect" class="form-select">
|
||||
<option value="" selected disabled>请选择一个指标</option>
|
||||
<option value="aaa">AAA</option>
|
||||
<option value="bbb">BBB</option>
|
||||
<option value="ccc">CCC</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-lg-auto">
|
||||
<span class="form-hint">选择后将影响下方所有设置。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 次级卡片:评价集设置 -->
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title mb-0">评价集设置</h3>
|
||||
<div class="ms-auto">
|
||||
<button class="btn btn-outline-primary" id="btnAdd">增加评价</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<!-- 左侧:子集列表 -->
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="list-group">
|
||||
<label class="list-group-item">
|
||||
<input class="form-check-input me-2" type="radio" name="subset" value="x1"> x1
|
||||
</label>
|
||||
<label class="list-group-item">
|
||||
<input class="form-check-input me-2" type="radio" name="subset" value="c2" checked> c2
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:表格占位 -->
|
||||
<div class="col-12 col-md-9">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>名称</th>
|
||||
<th>符号</th>
|
||||
<th>下限值</th>
|
||||
<th>符号</th>
|
||||
<th>上限值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rulesBody">
|
||||
<tr class="text-muted">
|
||||
<td colspan="6">尚未添加评价规则,点击右上角“增加评价”。</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- row -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 占位内容,方便滚动测试吸顶 -->
|
||||
<div style="height: 40vh;"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery(如已全局引入可删) -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script>
|
||||
/** ===== 示例:你的指标列表(id + name) ===== */
|
||||
const indicators = [
|
||||
{ id: 'A', name: '性能' },
|
||||
{ id: 'B', name: '价格' },
|
||||
{ id: 'C', name: '服务' },
|
||||
{ id: 'D', name: '交付周期' }
|
||||
];
|
||||
// 切换指标时,给重点卡片一个轻微高亮动画
|
||||
const card = document.getElementById('cardPriority');
|
||||
const select = document.getElementById('metricSelect');
|
||||
select.addEventListener('change', () => {
|
||||
card.classList.add('flash');
|
||||
setTimeout(() => card.classList.remove('flash'), 900);
|
||||
});
|
||||
|
||||
/** ===== AHP 工具:几何平均法权重 + CI/CR ===== */
|
||||
function geometricMeanWeights(A) {
|
||||
const n = A.length;
|
||||
const gm = new Array(n).fill(0);
|
||||
for (let i=0;i<n;i++) {
|
||||
let prod = 1;
|
||||
for (let j=0;j<n;j++) prod *= A[i][j];
|
||||
gm[i] = Math.pow(prod, 1/n);
|
||||
}
|
||||
const sum = gm.reduce((a,b)=>a+b,0);
|
||||
return gm.map(x=>x/sum);
|
||||
}
|
||||
function lambdaMaxApprox(A, w) {
|
||||
// 近似 λmax = 平均((A w)_i / w_i)
|
||||
const n = A.length;
|
||||
const Aw = new Array(n).fill(0);
|
||||
for (let i=0;i<n;i++) {
|
||||
let s=0; for (let j=0;j<n;j++) s += A[i][j]*w[j];
|
||||
Aw[i] = s;
|
||||
}
|
||||
let acc = 0;
|
||||
for (let i=0;i<n;i++) acc += Aw[i]/w[i];
|
||||
return acc/n;
|
||||
}
|
||||
function ci(lambdaMax, n){ return (lambdaMax - n) / (n - 1); }
|
||||
function cr(lambdaMax, n){
|
||||
// Saaty RI(n=1..10)
|
||||
const RI = [0.00,0.00,0.58,0.90,1.12,1.24,1.32,1.41,1.45,1.49];
|
||||
if (n<1 || n>10) return NaN;
|
||||
const v = ci(lambdaMax, n);
|
||||
return RI[n-1]===0 ? 0 : v/RI[n-1];
|
||||
}
|
||||
|
||||
/** ===== UI:1..9 标度(上三角选择),自动倒数到下三角 ===== */
|
||||
const scaleOptions = [
|
||||
{v:1/9, t:'1/9 极端不如'},
|
||||
{v:1/8, t:'1/8'},
|
||||
{v:1/7, t:'1/7 很不如'},
|
||||
{v:1/6, t:'1/6'},
|
||||
{v:1/5, t:'1/5 明显不如'},
|
||||
{v:1/4, t:'1/4'},
|
||||
{v:1/3, t:'1/3 略不如'},
|
||||
{v:1/2, t:'1/2'},
|
||||
{v:1, t:'1 同等'},
|
||||
{v:2, t:'2'},
|
||||
{v:3, t:'3 略重要'},
|
||||
{v:4, t:'4'},
|
||||
{v:5, t:'5 明显重要'},
|
||||
{v:6, t:'6'},
|
||||
{v:7, t:'7 很重要'},
|
||||
{v:8, t:'8'},
|
||||
{v:9, t:'9 极端重要'}
|
||||
];
|
||||
|
||||
function buildMatrix(list){
|
||||
const n = list.length;
|
||||
const $table = $("#matrixTable");
|
||||
let thead = '<thead><tr><th style="min-width:200px;"></th>';
|
||||
for (let j=0;j<n;j++) thead += `<th title="${list[j].id}">${list[j].name}</th>`;
|
||||
thead += `<th class="w-col">权重</th></tr></thead>`;
|
||||
|
||||
let tbody = '<tbody>';
|
||||
for (let i=0;i<n;i++){
|
||||
tbody += `<tr data-row="${i}"><th>${list[i].name}</th>`;
|
||||
for (let j=0;j<n;j++){
|
||||
if (i===j) {
|
||||
tbody += `<td class="diag">1</td>`;
|
||||
} else if (i<j) {
|
||||
// 上三角:选择器
|
||||
tbody += `<td data-cell="${i}-${j}">
|
||||
<select class="form-select form-select-sm select-judge" data-i="${i}" data-j="${j}">
|
||||
${scaleOptions.map(o=>`<option value="${o.v}">${o.t}</option>`).join('')}
|
||||
</select>
|
||||
</td>`;
|
||||
} else { // 下三角:只读,显示倒数
|
||||
tbody += `<td data-cell="${i}-${j}" class="cell-readonly text-end">—</td>`;
|
||||
}
|
||||
}
|
||||
tbody += `<td class="w-col text-end" data-weight="${i}">—</td>`;
|
||||
tbody += `</tr>`;
|
||||
}
|
||||
tbody += '</tbody>';
|
||||
|
||||
$table.html(thead + tbody);
|
||||
|
||||
// 默认上三角全 1(同等)
|
||||
$table.find('.select-judge').val('1');
|
||||
|
||||
// 绑定联动
|
||||
$table.on('change', '.select-judge', function(){
|
||||
const i = parseInt(this.dataset.i,10), j = parseInt(this.dataset.j,10);
|
||||
const v = parseFloat(this.value);
|
||||
// 设置下三角为倒数
|
||||
const recip = 1 / v;
|
||||
const $mirror = $table.find(`[data-cell="${j}-${i}"]`);
|
||||
$mirror.text(recip.toFixed(6));
|
||||
recalcWeights();
|
||||
});
|
||||
|
||||
// 初次同步一次下三角并计算
|
||||
$table.find('.select-judge').trigger('change');
|
||||
}
|
||||
|
||||
/** 读取矩阵为二维数组 */
|
||||
function readMatrix(){
|
||||
const n = $("#matrixTable thead th").length - 2; // 去掉左上空格和“权重”列
|
||||
const A = Array.from({length:n}, ()=>Array(n).fill(1));
|
||||
for (let i=0;i<n;i++){
|
||||
for (let j=0;j<n;j++){
|
||||
if (i===j) { A[i][j]=1; continue; }
|
||||
if (i<j){
|
||||
const v = parseFloat($(`.select-judge[data-i="${i}"][data-j="${j}"]`).val());
|
||||
A[i][j] = v;
|
||||
A[j][i] = 1/v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** 计算权重/CR 并渲染 */
|
||||
function recalcWeights(){
|
||||
const A = readMatrix();
|
||||
const w = geometricMeanWeights(A);
|
||||
const lm = lambdaMaxApprox(A, w);
|
||||
const CR = cr(lm, A.length);
|
||||
|
||||
// 渲染权重列
|
||||
for (let i=0;i<w.length;i++){
|
||||
$(`[data-weight="${i}"]`).text(w[i].toFixed(4));
|
||||
}
|
||||
// 渲染 CR
|
||||
const $b = $("#crBadge");
|
||||
$b.text(`CR: ${isNaN(CR) ? '--' : CR.toFixed(4)}`);
|
||||
$b.removeClass('bg-green bg-red bg-blue');
|
||||
if (!isNaN(CR)) {
|
||||
$b.addClass(CR < 0.10 ? 'bg-green' : 'bg-red');
|
||||
// 下一步按钮:如果未选择则聚焦并闪烁
|
||||
document.getElementById('btnNext').addEventListener('click', () => {
|
||||
if (!select.value) {
|
||||
select.focus();
|
||||
card.classList.add('flash');
|
||||
setTimeout(() => card.classList.remove('flash'), 900);
|
||||
} else {
|
||||
$b.addClass('bg-blue');
|
||||
// 这里写你的跳转或展开逻辑
|
||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/** 事件:置同等、重置、导出 */
|
||||
$(function(){
|
||||
buildMatrix(indicators);
|
||||
|
||||
$("#btnEven").on('click', function(){
|
||||
$('.select-judge').val('1').trigger('change');
|
||||
});
|
||||
|
||||
$("#btnReset").on('click', function(){
|
||||
$("#matrixTable").off(); // 解绑旧事件
|
||||
buildMatrix(indicators); // 重建
|
||||
});
|
||||
|
||||
$("#btnExport").on('click', function(){
|
||||
const A = readMatrix();
|
||||
console.log('Matrix:', A);
|
||||
alert('矩阵已输出到控制台。');
|
||||
});
|
||||
// 增加评价:简单追加一行示例
|
||||
document.getElementById('btnAdd').addEventListener('click', () => {
|
||||
const body = document.getElementById('rulesBody');
|
||||
if (body.firstElementChild && body.firstElementChild.classList.contains('text-muted')) {
|
||||
body.innerHTML = '';
|
||||
}
|
||||
const idx = body.children.length + 1;
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${idx}</td>
|
||||
<td><input class="form-control form-control-sm" placeholder="名称"/></td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm">
|
||||
<option>>=</option><option>></option><option>=</option>
|
||||
<option><=</option><option><</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input class="form-control form-control-sm" placeholder="下限"/></td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm">
|
||||
<option><=</option><option><</option><option>=</option>
|
||||
<option>>=</option><option>></option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input class="form-control form-control-sm" placeholder="上限"/></td>
|
||||
`;
|
||||
body.appendChild(tr);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.hshh.evaluation.bean;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 模板权重暂存数据.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class DraftWeightData {
|
||||
|
||||
/**
|
||||
* 页面定义的临时key.
|
||||
*/
|
||||
private String key;
|
||||
/**
|
||||
* 父指标ID.
|
||||
*/
|
||||
private Integer parentIndicationId;
|
||||
/**
|
||||
* 模板ID.
|
||||
*/
|
||||
private Integer templateId;
|
||||
|
||||
/**
|
||||
* 表头map linkedMap. key是指标id,value是 {id,name}
|
||||
*/
|
||||
private Map<Integer, MetricTableHeaderBean> headerMap;
|
||||
|
||||
/**
|
||||
* 页面中指标表的权重信息设置.key 为fromIndicatorId+"_"+toIndicatorId.
|
||||
*/
|
||||
private List<List<MetricMapperWeightBean>> weight;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.hshh.evaluation.bean;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 动态返回表格.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class DynamicTable {
|
||||
|
||||
|
||||
/**
|
||||
* 表头map linkedMap.
|
||||
*/
|
||||
private Map<Integer, MetricTableHeaderBean> headerMap;
|
||||
|
||||
|
||||
private List<List<MetricMapperWeightBean>> weight;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.hshh.evaluation.bean;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 计算指标的请求对象.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class MetricComputeRequest {
|
||||
|
||||
private List<String> metric;
|
||||
private List<List<MetricMapperWeightBean>> weightList;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.hshh.evaluation.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 指标计算后的结果.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class MetricComputerResponse {
|
||||
|
||||
private String id;
|
||||
private String weight;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.hshh.evaluation.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 指标权重映射描述.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class MetricMapperWeightBean {
|
||||
|
||||
/**
|
||||
* 从那个指标开始.
|
||||
*/
|
||||
private int rowId;
|
||||
/**
|
||||
* 到那个指标.
|
||||
*/
|
||||
private int colId;
|
||||
/**
|
||||
* 权重.
|
||||
*/
|
||||
private String value;
|
||||
/**
|
||||
* 父指标ID.
|
||||
*/
|
||||
private int parentId;
|
||||
/**
|
||||
* 行号.
|
||||
*/
|
||||
private int rowNum;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.hshh.evaluation.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 指标动态表头.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class MetricTableHeaderBean {
|
||||
|
||||
private int id;
|
||||
private String name;
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package com.hshh.evaluation.controller;
|
||||
|
||||
import com.hshh.evaluation.bean.DraftWeightData;
|
||||
import com.hshh.evaluation.bean.MetricMapperWeightBean;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplate;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
|
||||
import com.hshh.indicator.entity.Indicator;
|
||||
import com.hshh.system.common.bean.BaseController;
|
||||
import com.hshh.system.common.util.DraftStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 评估模板类辅助方法.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
public class AssistantTemplateController extends BaseController {
|
||||
|
||||
/**
|
||||
* 转化指标权重列表到double二维数组.
|
||||
*
|
||||
* @param weightList 指标权重列表
|
||||
* @return list string数组
|
||||
*/
|
||||
protected List<String[]> convertMetricMapperWeightBeanListToStrArray(
|
||||
List<List<MetricMapperWeightBean>> weightList) {
|
||||
|
||||
List<String[]> data = new ArrayList<>();
|
||||
weightList.forEach(weight -> {
|
||||
String[] strArr = new String[weight.size()];
|
||||
data.add(strArr);
|
||||
for (int i = 0; i < weight.size(); i++) {
|
||||
strArr[i] = weight.get(i).getValue();
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装历史权重数据.
|
||||
*
|
||||
* @param evaluationTemplate 当前模板数据
|
||||
* @return 评估数据列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<Integer, DraftWeightData> unitWeightMap(
|
||||
EvaluationTemplate evaluationTemplate) {
|
||||
|
||||
// 获取缓存中的数据
|
||||
Map<Integer, DraftWeightData> cacheData = DraftStore.get(evaluationTemplate.getDraftKey(),
|
||||
Map.class);
|
||||
if (cacheData == null) {
|
||||
if (evaluationTemplate.getCurrentPagePartData() != null) {
|
||||
|
||||
Map<Integer, DraftWeightData> defaultMap = new LinkedHashMap<>();
|
||||
DraftWeightData weightData = createDefaultDraftWeightData(evaluationTemplate);
|
||||
defaultMap.put(weightData.getParentIndicationId(), weightData);
|
||||
return defaultMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//cacheData数据结构 父ID-->当前id下子指标的权重信息
|
||||
//查看当前页面上传的最后一次指标权重信息;用当前页面的数据更新缓存或者增加缓存
|
||||
if (evaluationTemplate.getCurrentPagePartData() != null) {
|
||||
|
||||
int parentId = evaluationTemplate.getCurrentPagePartData().get(0).get(0).getParentId();
|
||||
cacheData.remove(parentId);
|
||||
//构建一个简易的只包含实际当前表格中的数据的暂存对象类,供后面合并
|
||||
DraftWeightData draftWeightData = createDefaultDraftWeightData(evaluationTemplate);
|
||||
|
||||
cacheData.put(parentId, draftWeightData);
|
||||
}
|
||||
|
||||
return cacheData;
|
||||
}
|
||||
|
||||
private DraftWeightData createDefaultDraftWeightData(EvaluationTemplate evaluationTemplate) {
|
||||
int pid = evaluationTemplate.getCurrentPagePartData().get(0).get(0).getParentId();
|
||||
DraftWeightData draftWeightData = new DraftWeightData();
|
||||
draftWeightData.setTemplateId(evaluationTemplate.getId());
|
||||
draftWeightData.setWeight(evaluationTemplate.getCurrentPagePartData());
|
||||
draftWeightData.setParentIndicationId(pid);
|
||||
draftWeightData.setKey(evaluationTemplate.getDraftKey());
|
||||
return draftWeightData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当一个指标下面只有一个孩子时,填充默认权重1.
|
||||
*
|
||||
* @param lonelyChild 只有自己,没有其他节点和他同级
|
||||
* @param unitWeightDataMap 当前已经设置的权重信息.
|
||||
*/
|
||||
|
||||
protected void paddingDefaultValueWhenParentHasOnlyLonelyChild(List<Indicator> lonelyChild,
|
||||
Map<Integer, DraftWeightData> unitWeightDataMap) {
|
||||
for (Indicator indicator : lonelyChild) {
|
||||
if (!unitWeightDataMap.containsKey(indicator.getParentId())) {
|
||||
List<List<MetricMapperWeightBean>> singleRowList = new ArrayList<>();
|
||||
List<MetricMapperWeightBean> list = new ArrayList<>();
|
||||
MetricMapperWeightBean weightBean = new MetricMapperWeightBean();
|
||||
list.add(weightBean);
|
||||
singleRowList.add(list);
|
||||
weightBean.setParentId(indicator.getParentId());
|
||||
weightBean.setValue("1");
|
||||
weightBean.setRowId(indicator.getId());
|
||||
weightBean.setColId(indicator.getId());
|
||||
weightBean.setRowNum(1);
|
||||
DraftWeightData draftWeightData = new DraftWeightData();
|
||||
draftWeightData.setParentIndicationId(indicator.getParentId());
|
||||
draftWeightData.setWeight(singleRowList);
|
||||
;
|
||||
unitWeightDataMap.put(indicator.getParentId(), draftWeightData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
package com.hshh.evaluation.controller;
|
||||
|
||||
import com.hshh.evaluation.entity.EvaluationProject;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplate;
|
||||
import com.hshh.evaluation.service.EvaluationProjectService;
|
||||
import com.hshh.indicator.entity.Indicator;
|
||||
import com.hshh.indicator.service.IndicatorService;
|
||||
import com.hshh.evaluation.service.EvaluationTemplateService;
|
||||
import com.hshh.system.base.entity.TableRelations;
|
||||
import com.hshh.system.base.service.TableRelationsService;
|
||||
import com.hshh.system.common.bean.BaseController;
|
||||
import com.hshh.system.common.bean.JsTree;
|
||||
import com.hshh.system.common.bean.OperateResult;
|
||||
import com.hshh.system.common.bean.PaginationBean;
|
||||
import com.hshh.system.common.enums.ErrorCode;
|
||||
|
|
@ -37,8 +38,16 @@ public class EvaluationProjectController extends BaseController {
|
|||
|
||||
@Resource
|
||||
private EvaluationProjectService evaluationProjectService;
|
||||
/**
|
||||
* 模板服务类.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorService indicatorService;
|
||||
private EvaluationTemplateService evaluationTemplateService;
|
||||
/**
|
||||
* 数据库引用关系记录服务类.
|
||||
*/
|
||||
@Resource
|
||||
private TableRelationsService tableRelationsService;
|
||||
|
||||
/**
|
||||
* 默认页.
|
||||
|
|
@ -53,7 +62,7 @@ public class EvaluationProjectController extends BaseController {
|
|||
Long total = evaluationProjectService.count(request);
|
||||
//设置分页信息
|
||||
setPaginationInfo(request, list, total, model);
|
||||
return "evaluation/list";
|
||||
return "project/list";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,9 +73,9 @@ public class EvaluationProjectController extends BaseController {
|
|||
*/
|
||||
@GetMapping("/add")
|
||||
public String add(Model model) {
|
||||
List<Indicator> rootList = indicatorService.queryRootList();
|
||||
List<EvaluationTemplate> rootList = evaluationTemplateService.list();
|
||||
model.addAttribute("rootList", rootList);
|
||||
return "evaluation/add";
|
||||
return "project/add";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -83,58 +92,42 @@ public class EvaluationProjectController extends BaseController {
|
|||
if (bindingResult.hasErrors()) {
|
||||
return errorsInputHandle(bindingResult);
|
||||
}
|
||||
//查询是否有重名的
|
||||
List<EvaluationProject> projectList = evaluationProjectService.queryListByName(
|
||||
project.getProjectName());
|
||||
if (project.getId() == null) {
|
||||
if (!projectList.isEmpty()) {
|
||||
|
||||
if (!projectList.isEmpty()) {
|
||||
if (!projectList.get(0).getId().equals(project.getId())) {
|
||||
return OperateResult.error(null, ErrorMessage.NAME_OR_CODE_EXIT.getMessage(),
|
||||
ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
project.setCreateDate(LocalDateTime.now());
|
||||
evaluationProjectService.save(project);
|
||||
} else {
|
||||
if (!projectList.isEmpty()) {
|
||||
if (!project.getId().equals(projectList.get(0).getId())) {
|
||||
return OperateResult.error(null, ErrorMessage.NAME_OR_CODE_EXIT.getMessage(),
|
||||
ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
evaluationProjectService.updateById(project);
|
||||
}
|
||||
}
|
||||
//设置创建时间
|
||||
if (project.getId() == null) {
|
||||
project.setCreateDate(LocalDateTime.now());
|
||||
}
|
||||
evaluationProjectService.saveWhole(project);
|
||||
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始评估页面.
|
||||
* 删除工程接口.
|
||||
*
|
||||
* @return 开始评估页面
|
||||
* @param id 要删除的ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@GetMapping("/startEvaluation/{id}")
|
||||
public String startEvaluation(@PathVariable("id") Integer projectId, Model model) {
|
||||
|
||||
EvaluationProject project = evaluationProjectService.getById(projectId);
|
||||
|
||||
List<Indicator> children = indicatorService.queryChildren(project.getIndicatorTopId());
|
||||
model.addAttribute("children", children);
|
||||
return "evaluation/start_weight_evaluation";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指标树.
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 指标树
|
||||
*/
|
||||
@GetMapping("/metricTree/{id}")
|
||||
@ResponseBody
|
||||
public OperateResult<List<JsTree>> metricTree(@PathVariable("id") Integer projectId) {
|
||||
EvaluationProject project = evaluationProjectService.getById(projectId);
|
||||
if (project == null) {
|
||||
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),
|
||||
@GetMapping("/remove/{id}")
|
||||
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
|
||||
List<TableRelations> reList = tableRelationsService.queryRel(id,
|
||||
"m_data_evaluation_project");
|
||||
if (!reList.isEmpty()) {
|
||||
return OperateResult.error(null, ErrorMessage.OBJ_ALREADY_TAKEN.getMessage(),
|
||||
ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
|
||||
return OperateResult.success(indicatorService.metricTree(project.getIndicatorTopId())
|
||||
);
|
||||
evaluationProjectService.removeById(id);
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,48 @@
|
|||
package com.hshh.evaluation.controller;
|
||||
|
||||
import com.hshh.evaluation.bean.DraftWeightData;
|
||||
import com.hshh.evaluation.bean.DynamicTable;
|
||||
import com.hshh.evaluation.bean.MetricComputeRequest;
|
||||
import com.hshh.evaluation.bean.MetricComputerResponse;
|
||||
import com.hshh.evaluation.bean.MetricMapperWeightBean;
|
||||
import com.hshh.evaluation.bean.MetricTableHeaderBean;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplate;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
|
||||
import com.hshh.evaluation.service.EvaluationTemplateService;
|
||||
import com.hshh.system.common.bean.BaseController;
|
||||
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
|
||||
import com.hshh.indicator.entity.Indicator;
|
||||
import com.hshh.indicator.service.IndicatorService;
|
||||
import com.hshh.system.base.entity.TableRelations;
|
||||
import com.hshh.system.base.service.TableRelationsService;
|
||||
import com.hshh.system.common.Strings.StringOperationUtil;
|
||||
import com.hshh.system.common.algorithm.AhpNode;
|
||||
import com.hshh.system.common.algorithm.AhpTreeCompute;
|
||||
import com.hshh.system.common.bean.JsTree;
|
||||
import com.hshh.system.common.bean.OperateResult;
|
||||
import com.hshh.system.common.bean.PaginationBean;
|
||||
import com.hshh.system.common.enums.ErrorCode;
|
||||
import com.hshh.system.common.enums.ErrorMessage;
|
||||
import com.hshh.system.common.util.DraftStore;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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.ResponseBody;
|
||||
|
||||
/**
|
||||
* 评估工程模板表 前端控制器.
|
||||
|
|
@ -20,11 +52,33 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
*/
|
||||
@Controller
|
||||
@RequestMapping("/evaluation/evaluationTemplate")
|
||||
public class EvaluationTemplateController extends BaseController {
|
||||
@Slf4j
|
||||
public class EvaluationTemplateController extends AssistantTemplateController {
|
||||
|
||||
/**
|
||||
* 模板服务类.
|
||||
*/
|
||||
@Resource
|
||||
private EvaluationTemplateService evaluationTemplateService;
|
||||
|
||||
/**
|
||||
* 指标服务类.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorService indicatorService;
|
||||
|
||||
/**
|
||||
* 数据库引用关系记录服务类.
|
||||
*/
|
||||
@Resource
|
||||
private TableRelationsService tableRelationsService;
|
||||
|
||||
/**
|
||||
* 权重服务类.
|
||||
*/
|
||||
@Resource
|
||||
private EvaluationTemplateWeightService evaluationTemplateWeightService;
|
||||
|
||||
/**
|
||||
* 默认页.
|
||||
*
|
||||
|
|
@ -35,9 +89,393 @@ public class EvaluationTemplateController extends BaseController {
|
|||
public String list(PaginationBean request, Model model) {
|
||||
setNavigateTitle(model, "/evaluation/evaluationTemplate/");
|
||||
List<EvaluationTemplate> list = evaluationTemplateService.list(request);
|
||||
|
||||
Long total = evaluationTemplateService.count(request);
|
||||
//设置分页信息
|
||||
setPaginationInfo(request, list, total, model);
|
||||
return "evaluation_template/list";
|
||||
return "project_template/list";
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航到增加页面.
|
||||
*
|
||||
* @param model session容器
|
||||
*/
|
||||
@GetMapping("/add")
|
||||
public String add(Model model) {
|
||||
List<Indicator> rootList = indicatorService.queryRootList();
|
||||
model.addAttribute("rootList", rootList);
|
||||
return "project_template/add";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指标树.
|
||||
*
|
||||
* @param id 项目ID
|
||||
* @return 指标树
|
||||
*/
|
||||
@GetMapping("/metricTree/{id}")
|
||||
@ResponseBody
|
||||
public OperateResult<List<JsTree>> metricTree(@PathVariable("id") Integer id, Model model) {
|
||||
|
||||
return OperateResult.success(indicatorService.metricTree(id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存模板. 必须设置指标;设置完成权重,才成成功提交
|
||||
*
|
||||
* @param evaluationTemplate 模板数据
|
||||
* @param bindingResult 验证的错误信息
|
||||
* @return 操作结果
|
||||
*/
|
||||
@ResponseBody
|
||||
@PostMapping("/save")
|
||||
public OperateResult<Object> save(@Valid @RequestBody EvaluationTemplate evaluationTemplate,
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return errorsInputHandle(bindingResult);
|
||||
}
|
||||
//查询是否有重名
|
||||
List<EvaluationTemplate> list = evaluationTemplateService.queryByName(
|
||||
evaluationTemplate.getTemplateName());
|
||||
if (!list.isEmpty()) {
|
||||
if (!list.get(0).getId().equals(evaluationTemplate.getId())) {
|
||||
return OperateResult.error(null, ErrorMessage.NAME_OR_CODE_EXIT.getMessage(),
|
||||
ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
}
|
||||
//设置
|
||||
if (evaluationTemplate.getId() == null) {
|
||||
evaluationTemplate.setCreateTime(LocalDateTime.now());
|
||||
|
||||
}
|
||||
|
||||
//1.合并权重数据
|
||||
Map<Integer, DraftWeightData> unitWeightDataMap = unitWeightMap(evaluationTemplate);
|
||||
|
||||
//2.填充数据;如果一个指标下面只有一个孩子,则默认填充为1
|
||||
List<Indicator> lonelyChild = indicatorService.queryLonelyChild(
|
||||
evaluationTemplate.getIndicatorTopId());
|
||||
|
||||
paddingDefaultValueWhenParentHasOnlyLonelyChild(lonelyChild, unitWeightDataMap);
|
||||
|
||||
//3.开始验证完整性/一致性
|
||||
String validateWholeMessage = validate(evaluationTemplate.getIndicatorTopId(),
|
||||
unitWeightDataMap);
|
||||
if (!validateWholeMessage.isEmpty()) {
|
||||
return OperateResult.error(null, validateWholeMessage, ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
//4. 提交
|
||||
evaluationTemplateService.saveWhole(evaluationTemplate, unitWeightDataMap);
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id删除指标.
|
||||
*
|
||||
* @param id 要删除的ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@GetMapping("/remove/{id}")
|
||||
@ResponseBody
|
||||
public OperateResult<Object> remove(@PathVariable("id") Integer id) {
|
||||
List<TableRelations> reledList = tableRelationsService.queryRel(id,
|
||||
"m_data_evaluation_template");
|
||||
if (!reledList.isEmpty()) {
|
||||
return OperateResult.error(null, ErrorMessage.OBJ_ALREADY_TAKEN.getMessage(),
|
||||
ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
evaluationTemplateService.deleteTemplate(evaluationTemplateService.getById(id));
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板基础信息.
|
||||
*
|
||||
* @param id 模板ID
|
||||
* @return 模板数据
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@ResponseBody
|
||||
public OperateResult<EvaluationTemplate> view(@PathVariable("id") Integer id) {
|
||||
EvaluationTemplate template = evaluationTemplateService.getById(id);
|
||||
if (template == null) {
|
||||
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),
|
||||
ErrorCode.BUSINESS_ERROR.getCode());
|
||||
}
|
||||
return OperateResult.success(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂存权重数据.
|
||||
*
|
||||
* @param data 权重数据 页面传入的权重map数据key为rowId_colId(from指标id_to指标ID)
|
||||
* value为{rowId:a,colId:b,value:0.1}类似
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/receiveDraft")
|
||||
@ResponseBody
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized OperateResult<Void> receiveDraft(@RequestBody DraftWeightData data) {
|
||||
log.info("receive draft data: pId= {}", data.getParentIndicationId());
|
||||
//根据key获取已经缓存的信息
|
||||
Map<Integer, DraftWeightData> dataMap = DraftStore.get(data.getKey(), Map.class);
|
||||
//如果缓存不存在
|
||||
if (dataMap == null) {
|
||||
dataMap = new HashMap<>();
|
||||
}
|
||||
//key为上级指标
|
||||
dataMap.put(data.getParentIndicationId(), data);
|
||||
//更新缓存
|
||||
DraftStore.put(data.getKey(), dataMap);
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取暂存数据.
|
||||
*
|
||||
* @param requestData 请求数据.
|
||||
* @return 表格和数据
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@PostMapping("/getDraftData")
|
||||
public synchronized String getDraftData(@RequestBody DraftWeightData requestData, Model model) {
|
||||
//查看key 是否有缓存记录
|
||||
Map<Integer, Object> cacheData = DraftStore.get(requestData.getKey(), Map.class);
|
||||
//如果没有记录
|
||||
if (cacheData == null) { //从数据库中获取
|
||||
model.addAttribute("data", createNewDynamicFromDatabase(requestData));
|
||||
return "project_template/table";
|
||||
}
|
||||
//如果有记录查看对应的指标是否有记录
|
||||
Object cacheWeightObj = cacheData.get(requestData.getParentIndicationId());
|
||||
//如果对应指标没有记录
|
||||
if (cacheWeightObj == null) { //从数据库读取
|
||||
model.addAttribute("data", createNewDynamicFromDatabase(requestData));
|
||||
return "project_template/table";
|
||||
}
|
||||
DraftWeightData data = (DraftWeightData) cacheWeightObj;
|
||||
|
||||
model.addAttribute("data", createDynamicFromCache(data));
|
||||
return "project_template/table";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库创建动态表.
|
||||
*
|
||||
* @param requestData 用户请求数据
|
||||
* @return 动态表
|
||||
*/
|
||||
private DynamicTable createNewDynamicFromDatabase(DraftWeightData requestData) {
|
||||
DynamicTable dynamicTable = new DynamicTable();
|
||||
//查询当前指标的子指标
|
||||
List<Indicator> children = indicatorService.queryChildren(requestData.getParentIndicationId());
|
||||
//获取指标权重信息
|
||||
List<EvaluationTemplateWeight> weightInDbList = evaluationTemplateWeightService.queryListByIndicatorParentIdAndTemplateId(
|
||||
requestData.getParentIndicationId(), requestData.getTemplateId());
|
||||
//转化为key=fromId+"_"+toId,value=self
|
||||
Map<String, EvaluationTemplateWeight> weightInMap = weightInDbList.stream()
|
||||
.collect(
|
||||
Collectors.toMap(a -> a.getFromIndicatorId() + "_" + a.getToIndicatorId(), a -> a));
|
||||
//设置头信息
|
||||
dynamicTable.setHeaderMap(createTableHeaderMap(children));
|
||||
|
||||
//设置权重信息
|
||||
List<List<MetricMapperWeightBean>> weight = new ArrayList<>();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
List<MetricMapperWeightBean> innerList = createEmptyMapperList(requestData, children, i,
|
||||
weightInMap);
|
||||
weight.add(innerList);
|
||||
}
|
||||
|
||||
dynamicTable.setWeight(weight);
|
||||
return dynamicTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表格头.
|
||||
*
|
||||
* @param children 子指标
|
||||
* @return 表格头信息
|
||||
*/
|
||||
private Map<Integer, MetricTableHeaderBean> createTableHeaderMap(List<Indicator> children) {
|
||||
List<MetricTableHeaderBean> headerBeans = new ArrayList<>();
|
||||
children.forEach(a -> {
|
||||
MetricTableHeaderBean bean = new MetricTableHeaderBean();
|
||||
bean.setId(a.getId());
|
||||
bean.setName(a.getName());
|
||||
headerBeans.add(bean);
|
||||
});
|
||||
Map<Integer, MetricTableHeaderBean> headerMap = new LinkedHashMap<>();
|
||||
headerBeans.forEach(header -> {
|
||||
headerMap.put(header.getId(), header);
|
||||
});
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库创建一个空的默认映射列表.
|
||||
*
|
||||
* @param requestData 请求数据
|
||||
* @param children 子指标
|
||||
* @param i 行号
|
||||
* @return 默认映射列表
|
||||
*/
|
||||
private static List<MetricMapperWeightBean> createEmptyMapperList(DraftWeightData requestData,
|
||||
List<Indicator> children, int i, Map<String, EvaluationTemplateWeight> weightInMap) {
|
||||
List<MetricMapperWeightBean> innerList = new ArrayList<>();
|
||||
for (Indicator child : children) {
|
||||
MetricMapperWeightBean weightBean = new MetricMapperWeightBean();
|
||||
//行ID
|
||||
weightBean.setRowId(children.get(i).getId());
|
||||
//列Id
|
||||
weightBean.setColId(child.getId());
|
||||
EvaluationTemplateWeight evaluationTemplateWeight = weightInMap.get(
|
||||
weightBean.getRowId() + "_" + weightBean.getColId());
|
||||
//初始默认为1
|
||||
weightBean.setValue(
|
||||
evaluationTemplateWeight == null ? "1" : evaluationTemplateWeight.getWeight() + "");
|
||||
|
||||
weightBean.setRowNum(evaluationTemplateWeight == null ? (i + 1)
|
||||
: evaluationTemplateWeight.getRowNum());
|
||||
weightBean.setParentId(requestData.getParentIndicationId());
|
||||
innerList.add(weightBean);
|
||||
}
|
||||
return innerList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中创建动态表.
|
||||
*
|
||||
* @param data 缓存数据
|
||||
* @return 动态表
|
||||
*/
|
||||
protected DynamicTable createDynamicFromCache(DraftWeightData data) {
|
||||
DynamicTable dynamicTable = new DynamicTable();
|
||||
dynamicTable.setHeaderMap(data.getHeaderMap());
|
||||
dynamicTable.setWeight(data.getWeight());
|
||||
return dynamicTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指标,当设置变化时触发.
|
||||
*
|
||||
* @param requests 计算请求,包含 指标id,当前表格数据
|
||||
* @return 计算结果
|
||||
*/
|
||||
@PostMapping("/computer")
|
||||
@ResponseBody
|
||||
public synchronized OperateResult<List<MetricComputerResponse>> computerWeight(
|
||||
@RequestBody MetricComputeRequest requests) {
|
||||
//如果没有值,直接静默返回
|
||||
if (requests.getWeightList() == null || requests.getWeightList().isEmpty()) {
|
||||
return OperateResult.success();
|
||||
}
|
||||
final List<MetricComputerResponse> responseList = new ArrayList<>();
|
||||
|
||||
List<String> metricList = requests.getMetric();
|
||||
final AhpNode H = new AhpNode("H");
|
||||
metricList.forEach(metric -> {
|
||||
AhpNode m = new AhpNode(metric);
|
||||
H.add(m);
|
||||
});
|
||||
List<List<MetricMapperWeightBean>> weightList = requests.getWeightList();
|
||||
|
||||
double[][] data = StringOperationUtil.toDoubleArray(
|
||||
convertMetricMapperWeightBeanListToStrArray(weightList));
|
||||
H.setLocalMatrix(data);
|
||||
H.globalWeight = 1; // 根节点全局权重=1
|
||||
AhpTreeCompute.computeTree(H);
|
||||
H.children.forEach(child -> {
|
||||
MetricComputerResponse response = new MetricComputerResponse();
|
||||
response.setId(child.name);
|
||||
response.setWeight(String.format("%.2f", child.globalWeight));
|
||||
responseList.add(response);
|
||||
});
|
||||
return OperateResult.success(responseList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证指标权重完整性/一致性.
|
||||
*
|
||||
* @param indicatorTopId 指标顶级ID
|
||||
* @param weightByParentIdMap 已经设置的权重Map
|
||||
* @return 验证提示结果
|
||||
*/
|
||||
private String validate(Integer indicatorTopId,
|
||||
Map<Integer, DraftWeightData> weightByParentIdMap) {
|
||||
//所有topId=indicatorTopId列表
|
||||
List<Indicator> list = indicatorService.queryByTopId(indicatorTopId);
|
||||
// key=指标ID value=指标数据
|
||||
Map<Integer, Indicator> indicatorMap = list.stream()
|
||||
.collect(Collectors.toMap(Indicator::getId, a -> a));
|
||||
Map<Integer, List<Indicator>> parentMap = list.stream().filter(a -> a.getParentId() != null)
|
||||
.collect(Collectors.groupingBy(Indicator::getParentId));
|
||||
StringBuffer sb = new StringBuffer();
|
||||
parentMap.forEach((indicatorId, children) -> {
|
||||
//完整性
|
||||
if (!weightByParentIdMap.containsKey(indicatorId)) {
|
||||
String indicatorName =
|
||||
indicatorMap.get(indicatorId) == null ? "" : indicatorMap.get(indicatorId).getName();
|
||||
sb.append(indicatorName).append("没有设置权重<br>\n");
|
||||
}
|
||||
if (weightByParentIdMap.get(indicatorId) != null) {
|
||||
//一致性
|
||||
List<List<MetricMapperWeightBean>> childWeightList = weightByParentIdMap.get(indicatorId)
|
||||
.getWeight();
|
||||
consistencyValidate(sb, childWeightList, indicatorMap);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证一致性. 假设有三个指标 A,B,C A--AA AB,AC |B--BB BA BC| C--CC CA CB.
|
||||
*
|
||||
* @param sb 提示消息
|
||||
* @param childWeightList 要验证的数据
|
||||
* @param indicatorMap 指标map key=id value=指标
|
||||
*/
|
||||
private void consistencyValidate(StringBuffer sb,
|
||||
List<List<MetricMapperWeightBean>> childWeightList, Map<Integer, Indicator> indicatorMap) {
|
||||
if (childWeightList != null && childWeightList.size() > 1) {
|
||||
//获取第一行记录作为标准
|
||||
List<MetricMapperWeightBean> firstRow = childWeightList.get(0);
|
||||
//获取A指标ID
|
||||
Integer firstFormIndicatorId = firstRow.get(0).getRowId();
|
||||
//把第一行的数据,用rowId+"_+colId作为键,value为值
|
||||
Map<String, Double> firstRowMap = new HashMap<>();
|
||||
|
||||
firstRow.forEach(child -> {
|
||||
firstRowMap.put(child.getRowId() + "_" + child.getColId(),
|
||||
Double.parseDouble(child.getValue()));
|
||||
});
|
||||
for (int i = 1; i < childWeightList.size(); i++) {
|
||||
List<MetricMapperWeightBean> weightBeanList = childWeightList.get(i);
|
||||
for (int j = 1; j < weightBeanList.size(); j++) {
|
||||
//获取第一行她的from指标值 AB
|
||||
Double abValue = firstRowMap.get(
|
||||
firstFormIndicatorId + "_" + weightBeanList.get(j).getRowId());
|
||||
//获取AC
|
||||
Double acValue = firstRowMap.get(
|
||||
firstFormIndicatorId + "_" + weightBeanList.get(j).getColId());
|
||||
//获取BC
|
||||
double bcValue = Double.parseDouble(weightBeanList.get(j).getValue());
|
||||
if (bcValue > 1 && acValue.compareTo(abValue) > 0) {
|
||||
sb.append(indicatorMap.get(weightBeanList.get(j).getRowId())).append("必须小于1")
|
||||
.append("<br>\n");
|
||||
}
|
||||
if (bcValue < 1 && acValue.compareTo(abValue) < 0) {
|
||||
sb.append(indicatorMap.get(weightBeanList.get(j).getRowId())).append("必须大于1")
|
||||
.append("<br>\n");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ public class EvaluationProject extends BaseBean {
|
|||
private String projectMemo;
|
||||
|
||||
|
||||
private Integer indicatorTopId;
|
||||
private Integer templateId;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String indicatorTopName;
|
||||
private String templateName;
|
||||
|
||||
private LocalDateTime createDate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
|||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.hshh.evaluation.bean.MetricMapperWeightBean;
|
||||
import com.hshh.system.common.bean.BaseBean;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
|
|
@ -22,19 +27,19 @@ public class EvaluationTemplate extends BaseBean {
|
|||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
|
||||
@NotBlank(message = "模板名称不能为空")
|
||||
@Size(max = 50, message = "不能超过50字符")
|
||||
private String templateName;
|
||||
|
||||
|
||||
//顶级指标ID
|
||||
@NotNull(message = "必须选择指标")
|
||||
private Integer indicatorTopId;
|
||||
|
||||
|
||||
private Integer indicatorWeightId;
|
||||
|
||||
|
||||
//模板状态
|
||||
private Integer templateStatus;
|
||||
|
||||
|
||||
@Size(max = 255, message = "不能超过255字符")
|
||||
private String templateMemo;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
|
@ -42,4 +47,12 @@ public class EvaluationTemplate extends BaseBean {
|
|||
private String indicatorTopName;
|
||||
@TableField(exist = false)
|
||||
private String templateStatusName;
|
||||
//提交时,当前页面对应的指标id,其余指标从缓存取
|
||||
@TableField(exist = false)
|
||||
private List<List<MetricMapperWeightBean>> currentPagePartData;
|
||||
//暂存指标权重的key
|
||||
@TableField(exist = false)
|
||||
private String draftKey;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,11 @@ public interface EvaluationProjectService extends IService<EvaluationProject> {
|
|||
* @return 查询结果
|
||||
*/
|
||||
List<EvaluationProject> queryListByName(String name);
|
||||
|
||||
/**
|
||||
* 保存工程信息.
|
||||
*
|
||||
* @param evaluationProject 工程数据
|
||||
*/
|
||||
void saveWhole(EvaluationProject evaluationProject);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package com.hshh.evaluation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.hshh.evaluation.bean.DraftWeightData;
|
||||
import com.hshh.evaluation.bean.MetricMapperWeightBean;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplate;
|
||||
import com.hshh.system.common.bean.PaginationBean;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 评估工程模板表 服务类.
|
||||
|
|
@ -28,4 +31,22 @@ public interface EvaluationTemplateService extends IService<EvaluationTemplate>
|
|||
* @return 总数
|
||||
*/
|
||||
Long count(PaginationBean search);
|
||||
|
||||
/**
|
||||
* 保存评估模板数据.
|
||||
*
|
||||
* @param evaluationTemplate 评估模板
|
||||
* @param weightData 权重
|
||||
*/
|
||||
void saveWhole(EvaluationTemplate evaluationTemplate,
|
||||
Map<Integer, DraftWeightData> weightData);
|
||||
|
||||
/**
|
||||
* 根据名称查询列表.
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 模板列表
|
||||
*/
|
||||
List<EvaluationTemplate> queryByName(String name);
|
||||
void deleteTemplate(EvaluationTemplate evaluationTemplate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,13 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||
import com.hshh.evaluation.entity.EvaluationProject;
|
||||
import com.hshh.evaluation.mapper.EvaluationProjectMapper;
|
||||
import com.hshh.evaluation.service.EvaluationProjectService;
|
||||
import com.hshh.system.base.service.TableRelationsService;
|
||||
import com.hshh.system.common.bean.PaginationBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 服务实现类.
|
||||
|
|
@ -20,6 +24,12 @@ public class EvaluationProjectServiceImpl extends
|
|||
ServiceImpl<EvaluationProjectMapper, EvaluationProject> implements
|
||||
EvaluationProjectService {
|
||||
|
||||
/**
|
||||
* 数据库引用关系记录服务类.
|
||||
*/
|
||||
@Resource
|
||||
private TableRelationsService tableRelationsService;
|
||||
|
||||
@Override
|
||||
public List<EvaluationProject> list(PaginationBean paginationBean) {
|
||||
return this.baseMapper.list(paginationBean);
|
||||
|
|
@ -37,4 +47,15 @@ public class EvaluationProjectServiceImpl extends
|
|||
return this.list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveWhole(EvaluationProject evaluationProject) {
|
||||
this.saveOrUpdate(evaluationProject);
|
||||
tableRelationsService.removeRel(evaluationProject.getId(), "m_data_evaluation_project",
|
||||
"m_data_evaluation_template");
|
||||
List<Integer> relList = new ArrayList<>();
|
||||
relList.add(evaluationProject.getTemplateId());
|
||||
tableRelationsService.addRel(evaluationProject.getId(), "m_data_evaluation_project", relList,
|
||||
"m_data_evaluation_template");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
package com.hshh.evaluation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.hshh.evaluation.bean.DraftWeightData;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplate;
|
||||
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
|
||||
import com.hshh.evaluation.mapper.EvaluationTemplateMapper;
|
||||
import com.hshh.evaluation.service.EvaluationTemplateService;
|
||||
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
|
||||
import com.hshh.system.common.bean.PaginationBean;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 评估工程模板表 服务实现类.
|
||||
|
|
@ -16,8 +23,10 @@ import org.springframework.stereotype.Service;
|
|||
*/
|
||||
@Service
|
||||
public class EvaluationTemplateServiceImpl extends
|
||||
ServiceImpl<EvaluationTemplateMapper, EvaluationTemplate> implements
|
||||
EvaluationTemplateService {
|
||||
ServiceImpl<EvaluationTemplateMapper, EvaluationTemplate> implements EvaluationTemplateService {
|
||||
|
||||
@Resource
|
||||
private EvaluationTemplateWeightService evaluationTemplateWeightService;
|
||||
|
||||
@Override
|
||||
public List<EvaluationTemplate> list(PaginationBean search) {
|
||||
|
|
@ -28,4 +37,59 @@ public class EvaluationTemplateServiceImpl extends
|
|||
public Long count(PaginationBean search) {
|
||||
return this.baseMapper.count(search);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void saveWhole(EvaluationTemplate evaluationTemplate,
|
||||
Map<Integer, DraftWeightData> weightDataMap) {
|
||||
saveOrUpdate(evaluationTemplate);
|
||||
if (weightDataMap != null && !weightDataMap.isEmpty()) {
|
||||
|
||||
weightDataMap.forEach((k, v) -> {
|
||||
//先按模板ID+上级指标ID
|
||||
evaluationTemplateWeightService.deleteEvaluationTemplateWeightWithTemplateIdAndIndicatorId(
|
||||
evaluationTemplate.getId(), evaluationTemplate.getIndicatorTopId(), k);
|
||||
v.getWeight().forEach(weightBeanList -> {
|
||||
weightBeanList.forEach(weightBean -> {
|
||||
EvaluationTemplateWeight evaluationTemplateWeight = new EvaluationTemplateWeight();
|
||||
//设置模板ID
|
||||
evaluationTemplateWeight.setTemplateId(evaluationTemplate.getId());
|
||||
//设置权重
|
||||
evaluationTemplateWeight.setWeight(Double.valueOf(weightBean.getValue()));
|
||||
//设置顶级指标ID
|
||||
evaluationTemplateWeight.setIndicatorTopId(evaluationTemplate.getIndicatorTopId());
|
||||
//fromIndicatorId
|
||||
evaluationTemplateWeight.setFromIndicatorId(weightBean.getRowId());
|
||||
//toIndicatorId
|
||||
evaluationTemplateWeight.setToIndicatorId(weightBean.getColId());
|
||||
//设置上级指标ID
|
||||
evaluationTemplateWeight.setIndicatorParentId(weightBean.getParentId());
|
||||
//设置行号
|
||||
evaluationTemplateWeight.setRowNum(weightBean.getRowNum());
|
||||
//保存
|
||||
evaluationTemplateWeightService.save(evaluationTemplateWeight);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EvaluationTemplate> queryByName(String name) {
|
||||
QueryWrapper<EvaluationTemplate> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("template_name", name);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void deleteTemplate(EvaluationTemplate evaluationTemplate) {
|
||||
evaluationTemplateWeightService.deleteEvaluationTemplateWeightWithTemplateId(
|
||||
evaluationTemplate.getId());
|
||||
this.removeById(evaluationTemplate.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class EvaluationController extends BaseController {
|
|||
*/
|
||||
@GetMapping("/evaluationList")
|
||||
public String evaluationList(Integer topIndicatorId, Integer indicatorId, Model model) {
|
||||
setNavigateTitle(model, "/indicator/evaluationList");
|
||||
setNavigateTitle(model, "/evaluation/evaluationList");
|
||||
List<Indicator> rootList = indicatorService.queryRootList();
|
||||
if (rootList != null && !rootList.isEmpty()) {
|
||||
if (topIndicatorId != null) {
|
||||
|
|
|
|||
|
|
@ -83,4 +83,20 @@ public interface IndicatorService extends IService<Indicator> {
|
|||
* @return 树数据
|
||||
*/
|
||||
List<JsTree> metricTree(Integer topId);
|
||||
|
||||
/**
|
||||
* 查询只有一个孩子的指标列表.
|
||||
*
|
||||
* @param topId 指标ID
|
||||
* @return 指标列表
|
||||
*/
|
||||
List<Indicator> queryLonelyChild(Integer topId);
|
||||
|
||||
/**
|
||||
* 按照顶级指标查询所有指标.
|
||||
*
|
||||
* @param topId 顶级ID
|
||||
* @return 指标列表,topId一致
|
||||
*/
|
||||
List<Indicator> queryByTopId(Integer topId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,4 +165,26 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
|
|||
|
||||
return JsTree.getJsTree(rootList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Indicator> queryLonelyChild(Integer topId) {
|
||||
Map<Integer, List<Indicator>> groupByPidMap = this.list()
|
||||
.stream().filter(a -> a.getParentId() != null)
|
||||
.collect(Collectors.groupingBy(Indicator::getParentId));
|
||||
List<Indicator> list = new ArrayList<>();
|
||||
groupByPidMap.forEach((k, v) -> {
|
||||
if (v.size() == 1) {
|
||||
list.add(v.get(0));
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Indicator> queryByTopId(Integer topId) {
|
||||
QueryWrapper<Indicator> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("top_id", topId);
|
||||
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ mybatis-plus:
|
|||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
configuration:
|
||||
database-id: mysql
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
|
||||
global-config:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
@rownum := @rownum + 1 AS seq,
|
||||
t.*
|
||||
FROM (
|
||||
SELECT T1.*,T2.name as indicatorTopName FROM m_data_evaluation_project T1 left join m_data_indicator T2 on T1.indicator_top_id=T2.id
|
||||
SELECT T1.*,T2.template_name as templateName FROM m_data_evaluation_project T1 left join m_data_evaluation_template T2 on T1.template_id=T2.id
|
||||
<where>
|
||||
<if test="search != null and search !='' ">
|
||||
T1.project_name LIKE CONCAT('%',#{search},'%')
|
||||
|
|
@ -24,8 +24,8 @@
|
|||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY id ASC) AS seq,
|
||||
a.*,
|
||||
a1.name as indicatorTopName
|
||||
FROM m_data_evaluation_project a left join m_data_indicator a1 on a.indicator_top_id=a1.name
|
||||
a1.template_name as templateName
|
||||
FROM m_data_evaluation_project a left join m_data_evaluation_template a1 on a.template_id=a1.name
|
||||
<where>
|
||||
<if test="search != null and search !='' ">
|
||||
a.project_name LIKE '%'||#{search}||'%'
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
T1.template_name LIKE CONCAT('%',#{search},'%')
|
||||
</if>
|
||||
</where>
|
||||
order by T1.id asc ) t, ( SELECT @rownum := #{start} ) r limit
|
||||
order by T1.id desc ) t, ( SELECT @rownum := #{start} ) r limit
|
||||
#{start},#{pageSize}
|
||||
</select>
|
||||
<select id="list" resultType="com.hshh.evaluation.entity.EvaluationTemplate"
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
t.*
|
||||
FROM (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY id ASC) AS seq,
|
||||
ROW_NUMBER() OVER (ORDER BY id desc) AS seq,
|
||||
a.*,
|
||||
a1.name as indicatorTopName
|
||||
FROM m_data_evaluation_template a left join m_data_indicator a1 on a.indicator_top_id=a1.name
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class HttpClient {
|
|||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
let response = xhr.responseText;
|
||||
let contentType = xhr.getResponseHeader('Content-Type');
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
|
|
@ -54,24 +55,24 @@ class HttpClient {
|
|||
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
if (response.code === 0) {
|
||||
callback(null, response, xhr);
|
||||
} else {
|
||||
if (formId) {
|
||||
removeValidCss(formId);
|
||||
}
|
||||
//字段验证提示错误
|
||||
if (formId ) {
|
||||
errorsHandler(response, formId, dialogId);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON 解析失败,保持原始文本
|
||||
}
|
||||
}
|
||||
if (response.code === 0) {
|
||||
if (contentType && contentType.indexOf('text/html') !== -1) {
|
||||
callback(null, response, xhr);
|
||||
} else {
|
||||
if (formId) {
|
||||
removeValidCss(formId);
|
||||
}
|
||||
|
||||
//字段验证提示错误
|
||||
if (formId && dialogId) {
|
||||
errorsHandler(response, formId, dialogId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
callback(new Error(`HTTP POST Error: ${xhr.status}`), null, xhr);
|
||||
}
|
||||
|
|
@ -79,6 +80,7 @@ class HttpClient {
|
|||
};
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交 new formData数据
|
||||
* @param url
|
||||
|
|
@ -86,6 +88,7 @@ class HttpClient {
|
|||
* @param callback
|
||||
* @param formId
|
||||
* @param dialogId
|
||||
* @param offFlag
|
||||
*/
|
||||
postFormData(url, data, callback, formId, dialogId) {
|
||||
$.ajax({
|
||||
|
|
@ -107,7 +110,7 @@ class HttpClient {
|
|||
if (res.code === 10001) {
|
||||
errorsHandler(res, formId, dialogId);
|
||||
}
|
||||
if(res.code===0){
|
||||
if (res.code === 0) {
|
||||
callback(null, res, null);
|
||||
}
|
||||
},
|
||||
|
|
@ -119,6 +122,7 @@ class HttpClient {
|
|||
|
||||
function errorsHandler(response, formId, dialogId) {
|
||||
|
||||
|
||||
if (response.code === -1) {//输入字段提示
|
||||
let errors = response.errors;
|
||||
let formElement = document.getElementById(formId);
|
||||
|
|
@ -135,7 +139,7 @@ function errorsHandler(response, formId, dialogId) {
|
|||
}
|
||||
//业务错误
|
||||
if (response.code === 10001) {
|
||||
if (dialogId) {
|
||||
if (dialogId ) {
|
||||
closeDialog(dialogId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
* @param formId 所属formId
|
||||
*/
|
||||
function removeValidCss(formId) {
|
||||
if (document.getElementById("error_message")) {
|
||||
document.getElementById("error_message").style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
let form = document.getElementById(formId);
|
||||
let elements = form.elements;
|
||||
|
|
@ -354,5 +352,26 @@ function hideContextMenu() {
|
|||
}
|
||||
|
||||
}
|
||||
function generateDraftKey() {
|
||||
// 优先用 crypto.randomUUID 生成
|
||||
if (window.crypto && window.crypto.randomUUID) {
|
||||
return 'tpl:new:' + window.crypto.randomUUID();
|
||||
}
|
||||
// 兼容老浏览器
|
||||
return 'tpl:new:' + Date.now().toString(36) + Math.random().toString(36).substring(2, 10);
|
||||
}
|
||||
|
||||
|
||||
function mapToObject(map) {
|
||||
const obj = {};
|
||||
for (const [k, v] of map) {
|
||||
obj[k] = v;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
function showFieldErrorTip(formId,fieldId,message){
|
||||
let formElement = document.getElementById(formId);
|
||||
formElement.elements[fieldId].classList.add(
|
||||
"is-invalid");
|
||||
document.getElementById(
|
||||
fieldId + "_error_tip").innerHTML =message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
<div class="row g-4" data-page="start_evaluation">
|
||||
<div class="card-body">
|
||||
<ul class="steps steps-green my-4">
|
||||
<li class="step-item active">设置/加载 权重</li>
|
||||
<li class="step-item ">选择/上传 数据集</li>
|
||||
<li class="step-item">执行评估</li>
|
||||
<li class="step-item">本次结果</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4" id="evaluation-start-page">
|
||||
<div class="col-4" id="project_metric_tree" style="overflow-y: scroll; min-height: 600px;">
|
||||
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="card" style="overflow-y: scroll; min-height: 600px;">
|
||||
<div class="card-header">
|
||||
<div class="card-title">权重设置区</div>
|
||||
<div class="card-actions">
|
||||
<a href="#" class="btn btn-primary">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 5l0 14"></path>
|
||||
<path d="M5 12l14 0"></path>
|
||||
</svg>
|
||||
保存
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="map-scroller">
|
||||
<table class="table table-vcenter table-striped table-hover align-middle map-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th th:each="item:${children}" th:text="${item.name}" style="font-weight: bold;"></th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="autonum">
|
||||
<tr th:each="item:${children}">
|
||||
|
||||
<td th:text="${item.name}" style="font-weight: bold;"></td>
|
||||
<td th:each="innerItem:${children}" >
|
||||
<select class="form-select" >
|
||||
<option value="9" >极端重要(9)</option>
|
||||
<option value="8">更强烈重要(8)</option>
|
||||
<option value="7">强烈重要(7)</option>
|
||||
<option value="6">十分重要(6)</option>
|
||||
<option value="5">明显重要(5)</option>
|
||||
<option value="4">更为重要(4)</option>
|
||||
<option value="3">稍微重要(3)</option>
|
||||
<option value="2">微小重要(2)</option>
|
||||
<option value="1" th:selected="${item.id==innerItem.id}">同样重要(1)</option>
|
||||
<option value="0.5">微小次要(0.5)</option>
|
||||
<option value="0.3333">稍微次要(0.3333)</option>
|
||||
<option value="0.25">更为次要(0.25)</option>
|
||||
<option value="0.2">明显次要(0.2)</option>
|
||||
<option value="0.16667">十分次要(0.16667)</option>
|
||||
<option value="0.14286">强烈次要(0.14286)</option>
|
||||
<option value="0.125">更强烈次要(0.125)</option>
|
||||
<option value="0.11111">极端次要(0.11111)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">评估模板列表</h3>
|
||||
<div class="card-actions">
|
||||
<a href="javascript:void(0)" class="btn btn-primary" onclick="_toAdd()">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 5l0 14"></path>
|
||||
<path d="M5 12l14 0"></path>
|
||||
</svg>
|
||||
新增
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div th:replace="fragments/dialog::searchConditionDialog(${condition})"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table card-table table-vcenter text-nowrap datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th class="w-1">No.
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-up -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-sm icon-thick" width="24"
|
||||
height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"
|
||||
fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 15l6 -6l6 6"></path>
|
||||
</svg>
|
||||
</th>
|
||||
<th>模板名称</th>
|
||||
<th>对应指标</th>
|
||||
<th>模板状态</th>
|
||||
<th>模板描述</th>
|
||||
<th>创建时间</th>
|
||||
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:if="${result.list!=null}" th:each="project : ${result.list}">
|
||||
<td th:text="${project.seq}"></td>
|
||||
<td th:text="${project.templateName}"></td>
|
||||
<td th:text="${project.indicatorTopName}"></td>
|
||||
<td th:text="${project.templateStatusName}"></td>
|
||||
<td th:text="${project.templateMemo}"></td>
|
||||
<td th:text="${#temporals.format(project.createDate, 'yyyy-MM-dd HH:mm:ss')}"></td>
|
||||
|
||||
<td>
|
||||
<div class="btn-list flex-nowrap">
|
||||
<a href="javascript:void(0)" class="btn btn-primary"
|
||||
th:onclick="|_startEvaluation('${project.id}')|">
|
||||
编辑
|
||||
</a>
|
||||
|
||||
<a href="javascript:void(0)" class=" btn btn-danger"
|
||||
th:onclick="|_projectDelete('${project.id}')|">
|
||||
删除
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div th:replace="fragments/dialog::paginationDialog(${result})"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:replace="fragments/dialog::confirmationDialog"></div>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
</script>
|
||||
|
|
@ -24,163 +24,203 @@
|
|||
|
||||
<style>
|
||||
|
||||
|
||||
@keyframes notif-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* =========================
|
||||
基础变量 & 全局
|
||||
========================= */
|
||||
:root{
|
||||
--accent: #206bc4; /* 主题主色 */
|
||||
--border: #e0e6ed;
|
||||
--muted: #f6f8fb;
|
||||
--card-bg: #fff;
|
||||
--head-bg: #f3f6fa;
|
||||
--sticky-bg:#ffffff; /* 粘列背景,防止覆盖时变暗 */
|
||||
--col-index-w: 48px; /* 表格首列(粘列1)宽度 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@keyframes pop-in {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.4;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
body, .page-body{
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
#contextMenu ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* =========================
|
||||
卡片(hero / section)
|
||||
========================= */
|
||||
.card.card-hero{
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: var(--card-bg);
|
||||
box-shadow: 0 14px 36px rgba(0,0,0,.08);
|
||||
}
|
||||
.card.card-hero > .card-header{
|
||||
background: linear-gradient(90deg, rgba(32,107,196,.10), rgba(32,107,196,0));
|
||||
border-bottom: 1px solid rgba(32,107,196,.25);
|
||||
border-top-left-radius: 14px;
|
||||
border-top-right-radius: 14px;
|
||||
}
|
||||
.card.card-hero .card-title{
|
||||
font-weight: 800;
|
||||
border-left: 6px solid var(--accent);
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
#contextMenu li {
|
||||
.card.section{
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--card-bg);
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,.06);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.card.section .card-header{
|
||||
background: var(--head-bg);
|
||||
border-bottom: 1px solid #e9edf3;
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
}
|
||||
.card.section .card-title{
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
表单(输入/下拉/勾选)
|
||||
========================= */
|
||||
.form-select:focus, .form-control:focus{
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 .2rem rgba(32,107,196,.15);
|
||||
}
|
||||
|
||||
.form-check-input[type="radio"],
|
||||
.form-check-input[type="checkbox"]{
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 1px var(--accent) inset;
|
||||
background-color: #fff;
|
||||
transition: all .15s ease-in-out;
|
||||
}
|
||||
.form-check-input[type="radio"]:hover,
|
||||
.form-check-input[type="checkbox"]:hover{
|
||||
background-color: rgba(32,107,196,.10);
|
||||
box-shadow: 0 0 0 2px rgba(32,107,196,.25);
|
||||
}
|
||||
.form-check-input:checked{
|
||||
background-color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
CSV 列表
|
||||
========================= */
|
||||
.csv-heads .list-group-item{
|
||||
background: #fff;
|
||||
border: 1px solid #eef2f7;
|
||||
margin-bottom: .25rem;
|
||||
border-radius: 8px;
|
||||
padding: .5rem .75rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.04);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
AHP 表:容器与表格
|
||||
========================= */
|
||||
.map-scroller{
|
||||
overflow: auto;
|
||||
border: 1px solid #e9edf3;
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 1px 0 rgba(0,0,0,.02);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.map-table{
|
||||
min-width: 980px; /* 横向滚动更友好 */
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
/* 确保粗体切片可用(有些字体 700 看不粗) */
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Arial, "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
/* ---- 表头吸顶 ---- */
|
||||
.map-table thead th.sticky-header{
|
||||
position: sticky; top: 0; z-index: 4;
|
||||
background: var(--head-bg);
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* ---- 粘左两列(表头 + 表体) ---- */
|
||||
.map-table thead th.sticky-col-1,
|
||||
.map-table tbody td.sticky-body-col-1{
|
||||
position: sticky; left: 0; z-index: 3;
|
||||
background: var(--sticky-bg);
|
||||
box-shadow: 1px 0 0 #e9edf3;
|
||||
}
|
||||
.map-table thead th.sticky-col-2,
|
||||
.map-table tbody td.sticky-body-col-2{
|
||||
position: sticky; left: var(--col-index-w); z-index: 3; /* 用变量替代 48px */
|
||||
background: var(--sticky-bg);
|
||||
box-shadow: 1px 0 0 #e9edf3;
|
||||
}
|
||||
/* 粘列在表头处层级更高,避免被后列覆盖 */
|
||||
.map-table thead th.sticky-col-1,
|
||||
.map-table thead th.sticky-col-2{
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* ---- 条纹 & 悬停 ---- */
|
||||
.map-table tbody tr:nth-child(odd){ background: #fcfdfe; }
|
||||
.map-table tbody tr:hover{ background: #f3f8ff; }
|
||||
|
||||
/* ---- 紧凑列 ---- */
|
||||
.map-table th.w-compact,
|
||||
.map-table td.w-compact{
|
||||
width: 1%; white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---- 加粗规则(表头 + 第一列标签)---- */
|
||||
/* Tabler/Bootstrap 有时会把表头设为 400;这里强制 700 */
|
||||
.map-table thead th{
|
||||
font-weight: 700 !important;
|
||||
font-size: 16px; /* 可按需调整 */
|
||||
color: inherit !important; /* 继承父级颜色 */
|
||||
}
|
||||
.map-table tbody td:first-child{
|
||||
font-weight: 700 !important;
|
||||
font-size: 16px; /* 可选,同步视觉 */
|
||||
}
|
||||
|
||||
/* =========================
|
||||
动画/菜单/通用
|
||||
========================= */
|
||||
@keyframes flashHighlight{
|
||||
0%{ box-shadow:0 0 0 0 rgba(32,107,196,0); }
|
||||
20%{ box-shadow:0 0 0 8px rgba(32,107,196,.22); }
|
||||
100%{ box-shadow:0 14px 36px rgba(0,0,0,.08); }
|
||||
}
|
||||
.flash{ animation: flashHighlight .9s ease-out; }
|
||||
|
||||
@keyframes pop-in{
|
||||
0%{ transform: scale(0.8); opacity: .4; }
|
||||
100%{ transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
#contextMenu ul{ list-style:none; margin:0; padding:0; }
|
||||
#contextMenu li{
|
||||
padding: 9px 18px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
transition: background 0.18s, color 0.18s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: background .18s, color .18s;
|
||||
display: flex; align-items: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
#contextMenu li:last-child{ border-bottom: none; }
|
||||
#contextMenu li:hover{ background: #ffcd38; color: #fff; }
|
||||
#contextMenu li i{ margin-right: 8px; font-size: 15px; opacity: .85; }
|
||||
|
||||
#contextMenu li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#contextMenu li:hover {
|
||||
background: #ffcd38;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#contextMenu li i {
|
||||
margin-right: 8px;
|
||||
font-size: 15px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.dropdown-item.active {
|
||||
background-color: #206bc4 !important; /* Tabler 蓝色 */
|
||||
.dropdown-item.active{
|
||||
background-color: var(--accent) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.alert{ margin-bottom: 1rem; }
|
||||
|
||||
.alert {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
:root {
|
||||
--col-index-w: 48px; /* 首列宽度(与 th/td 一致) */
|
||||
}
|
||||
|
||||
/* CSV 预览区域 */
|
||||
.csv-heads {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 滚动容器 */
|
||||
.map-scroller {
|
||||
max-height: 65vh;
|
||||
overflow: auto;
|
||||
border-radius: .5rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 表格与单元格 */
|
||||
.map-table {
|
||||
table-layout: fixed;
|
||||
width: max-content;
|
||||
min-width: 1200px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.map-table th, .map-table td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-top: .55rem;
|
||||
padding-bottom: .55rem;
|
||||
}
|
||||
|
||||
/* ===== 表头:粘顶 + 粘左(前两列) —— 层级最高 ===== */
|
||||
.sticky-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, .04);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sticky-header.sticky-col-1 {
|
||||
left: 0;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.sticky-header.sticky-col-2 {
|
||||
left: var(--col-index-w);
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
/* ===== 表体:粘左(前两列) —— 层级低于表头 ===== */
|
||||
.sticky-body-col-1 {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sticky-body-col-2 {
|
||||
position: sticky;
|
||||
left: var(--col-index-w);
|
||||
z-index: 4;
|
||||
background: #fff;
|
||||
box-shadow: 2px 0 0 rgba(0, 0, 0, .06); /* 分隔线效果 */
|
||||
}
|
||||
|
||||
/* 自动编号:tbody 开始计数,tr 自增;序号/名称自动输出数值 */
|
||||
.autonum {
|
||||
counter-reset: row;
|
||||
}
|
||||
|
||||
.autonum tr {
|
||||
counter-increment: row;
|
||||
}
|
||||
|
||||
.autonum .col-index::before {
|
||||
content: counter(row);
|
||||
}
|
||||
|
||||
.autonum .col-name::after {
|
||||
|
||||
}
|
||||
.map-table th {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -228,6 +268,7 @@
|
|||
<script th:src="@{/js/HttpClient.js}"></script>
|
||||
<script th:src="@{/js/d3.v7.min.js}"></script>
|
||||
<script>
|
||||
|
||||
let d3TreeData = null;
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// 1. 支持多种菜单项,逗号分隔多个选择器
|
||||
|
|
@ -296,6 +337,11 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
const RECIP = {
|
||||
"9":"0.11111","8":"0.125","7":"0.14286","6":"0.16667","5":"0.2","4":"0.25","3":"0.3333","2":"0.5","1":"1",
|
||||
"0.5":"2","0.3333":"3","0.25":"4","0.2":"5","0.16667":"6","0.14286":"7","0.125":"8","0.11111":"9"
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,81 +1,73 @@
|
|||
<style>
|
||||
.radios-scroll-x {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
padding-bottom: 4px; /* 防止滚动条遮住内容 */
|
||||
}
|
||||
|
||||
.radios-scroll-x .form-check-inline {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="page-body" data-page="evaluation_list">
|
||||
<div class="container-xl">
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
|
||||
<div class="row row-cards">
|
||||
<div class="card">
|
||||
|
||||
<!-- 顶部重点卡片 -->
|
||||
<div class="card card-priority">
|
||||
<div class="card-header">
|
||||
<div class="card-title">指标数据评价集设置</div>
|
||||
<h3 class="card-title title-accent">选择评价指标
|
||||
<span class="badge bg-primary ms-2">必填</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
|
||||
<div class="radios-scroll-x">
|
||||
<label class="form-check form-check-inline" th:each="item:${rootList}">
|
||||
<input class="form-check-input" type="radio" name="radios-inline"
|
||||
th:checked="${item.checked}" th:value="${item.id}">
|
||||
<span class="form-check-label" th:text="${item.getName()}"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<!-- 新增:上次上传文件 -->
|
||||
<div class="text-muted small mt-2">说明: 切换不同的指标可以设置当前指标子项的评价标准
|
||||
<div class="mb-3 row align-items-center">
|
||||
<label class="col-3 col-form-label fw-semibold" for="indicationTopId">指标列表</label>
|
||||
<div class="col">
|
||||
<select class="form-select" name="indicationTopId" id="indicationTopId">
|
||||
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.name}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto text-muted small">选定后,下方“评价集设置”将随之变化</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title" th:each="item : ${rootList}"
|
||||
th:if="${item.checked == true}">[[${item.name}]]子集</h3>
|
||||
</div>
|
||||
<div class="list-group list-group-flush overflow-auto" style="max-height: 35rem">
|
||||
<div class="list-group-item" th:each="item:${indicatorListWithoutChildren}">
|
||||
<div class="row">
|
||||
<label class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio"
|
||||
name="radios-inline-indicator-no-child" th:value="${item.getId()}"
|
||||
th:checked="${item.checked}" onclick="changeIndicator()">
|
||||
<span class="form-check-label" th:text="${item.getName()}"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- 次级卡片 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title fw-bold">评价集设置</div>
|
||||
<div class="ms-auto">
|
||||
<a href="#" class="btn btn-primary" onclick="evaluation_add()">增加评价</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
|
||||
<!-- 左:子集列表 -->
|
||||
<div class="col-md-3">
|
||||
<div class="subset-panel">
|
||||
<div class="subset-scroll list-group list-group-flush">
|
||||
<div class="list-group-item bg-transparent border-0 py-2"
|
||||
th:each="item:${indicatorListWithoutChildren}">
|
||||
<label class="form-check form-check-inline m-0">
|
||||
<input class="form-check-input" type="radio"
|
||||
name="radios-inline-indicator-no-child" th:value="${item.getId()}"
|
||||
th:checked="${item.checked}" onclick="changeIndicator()">
|
||||
<span class="form-check-label ms-2" th:text="${item.getName()}"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="row">
|
||||
|
||||
<div class="text-end">
|
||||
<a href="#" class="btn btn-primary active w-10" onclick="evaluation_add()">
|
||||
增加评价
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右:表格 -->
|
||||
<div class="col-md-9">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th class="w-1">序号</th>
|
||||
<th>名称</th>
|
||||
<th>符号</th>
|
||||
<th>下限值</th>
|
||||
<th>符号</th>
|
||||
<th>上限值</th>
|
||||
<th ></th>
|
||||
<th class="w-1">符号</th>
|
||||
<th class="w-1">下限值</th>
|
||||
<th class="w-1">符号</th>
|
||||
<th class="w-1">上限值</th>
|
||||
<th class="text-end w-10"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -86,8 +78,8 @@
|
|||
<td th:text="${item.getMinValue()}"></td>
|
||||
<td th:text="${item.getMaxSymbol()}"></td>
|
||||
<td th:text="${item.getMaxValue()}"></td>
|
||||
<td class="w-15">
|
||||
<a href="javascript:void(0)"
|
||||
<td class="text-end">
|
||||
<a href="javascript:void(0)" class="me-2"
|
||||
th:onclick="|editEvaluation('${item.id}')|">编辑</a>
|
||||
<a href="javascript:void(0)"
|
||||
th:onclick="|removeEvaluation('${item.id}')|">删除</a>
|
||||
|
|
@ -97,49 +89,52 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- row -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="evaluationForm">
|
||||
<div th:replace="fragments/dialog::addSimpleFormDialog"></div>
|
||||
</form>
|
||||
<div th:replace="fragments/dialog::confirmationDialog"></div>
|
||||
|
||||
<script>
|
||||
// 交互时给重点卡片一个轻微高亮
|
||||
(function(){
|
||||
const card = document.querySelector('.card-priority');
|
||||
const topSel = document.getElementById('indicationTopId');
|
||||
topSel?.addEventListener('change', ()=>{
|
||||
card.classList.add('flash');
|
||||
setTimeout(()=>card.classList.remove('flash'), 900);
|
||||
});
|
||||
})();
|
||||
|
||||
function evaluation_add(data) {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/evaluationAdd";
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (err, res, xhr) {
|
||||
|
||||
document.getElementById("simpleFormBody").innerHTML = res;
|
||||
openDialog("simple-form-model");
|
||||
if (data) {
|
||||
fillData(data);
|
||||
} else {
|
||||
//设置指标祖先ID,指标ID
|
||||
let topId = $(
|
||||
'input[name="radios-inline"]:checked').val();
|
||||
let indicationId = $(
|
||||
'input[name="radios-inline-indicator-no-child"]:checked').val();
|
||||
let topId = $('input[name="radios-inline"]:checked').val();
|
||||
let indicationId = $('input[name="radios-inline-indicator-no-child"]:checked').val();
|
||||
document.getElementById("evaluationForm")['indicatorTopId'].value = topId;
|
||||
document.getElementById("evaluationForm")['indicatorId'].value = indicationId;
|
||||
}
|
||||
document.getElementById("SimpleFormDialog_save").onclick = function () {
|
||||
evaluation_save();
|
||||
}
|
||||
|
||||
}, "evaluationForm")
|
||||
|
||||
}
|
||||
|
||||
function fillData(data) {
|
||||
fillForm('evaluationForm', data);
|
||||
}
|
||||
function fillData(data) { fillForm('evaluationForm', data); }
|
||||
|
||||
function evaluation_save() {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/save";
|
||||
|
|
@ -147,48 +142,37 @@
|
|||
let form = document.getElementById("evaluationForm");
|
||||
http.post(url, formObjToObject(form), function (err, res, xhr) {
|
||||
closeDialog("simple-form-model");
|
||||
//重新刷新页面
|
||||
let topId = $('input[type=radio][name="radios-inline"]:checked').val();
|
||||
let subId = $(
|
||||
'input[name="radios-inline-indicator-no-child"]:checked').val();
|
||||
document.getElementById("_evaluation_evaluationList").setAttribute("hx-vals",
|
||||
JSON.stringify({topIndicatorId: topId, indicatorId: subId}))
|
||||
let subId = $('input[name="radios-inline-indicator-no-child"]:checked').val();
|
||||
document.getElementById("_evaluation_evaluationList")
|
||||
.setAttribute("hx-vals", JSON.stringify({topIndicatorId: topId, indicatorId: subId}));
|
||||
document.getElementById("_evaluation_evaluationList").click();
|
||||
}, "evaluationForm", "simple-form-model");
|
||||
}
|
||||
//编辑
|
||||
|
||||
function editEvaluation(id) {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/" + id;
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (error, res, xhr) {
|
||||
evaluation_add(res.result);
|
||||
}, "evaluationForm", "simple-form-model");
|
||||
http.get(url, function (error, res, xhr) { evaluation_add(res.result); },
|
||||
"evaluationForm", "simple-form-model");
|
||||
}
|
||||
//删除
|
||||
function removeEvaluation(id) {
|
||||
|
||||
function removeEvaluation(id) {
|
||||
openDialog("modal-danger");
|
||||
document.getElementById('delete-confirm-9999').onclick = function () {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/remove/" + id;
|
||||
let http = new HttpClient();
|
||||
http.get(url, (err, res, xhr) => {
|
||||
if (!err) {
|
||||
|
||||
//模拟点击菜单
|
||||
document.getElementById("_evaluation_evaluationList").click();
|
||||
|
||||
}
|
||||
if (!err) document.getElementById("_evaluation_evaluationList").click();
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function changeIndicator(){
|
||||
let topId = $('input[type=radio][name="radios-inline"]:checked').val();
|
||||
let subId = $(
|
||||
'input[name="radios-inline-indicator-no-child"]:checked').val();
|
||||
document.getElementById("_evaluation_evaluationList").setAttribute("hx-vals",
|
||||
JSON.stringify({topIndicatorId: topId, indicatorId: subId}))
|
||||
let topId = $('input[name="radios-inline"]:checked').val();
|
||||
let subId = $('input[name="radios-inline-indicator-no-child"]:checked').val();
|
||||
document.getElementById("_evaluation_evaluationList")
|
||||
.setAttribute("hx-vals", JSON.stringify({topIndicatorId: topId, indicatorId: subId}));
|
||||
document.getElementById("_evaluation_evaluationList").click();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,162 +1,164 @@
|
|||
<!-- ====== 页面主体(纯静态,无 JS/Thymeleaf) ====== -->
|
||||
<div class="page-body" data-page="indicator_mapper">
|
||||
<div class="container-xl">
|
||||
<style>
|
||||
|
||||
<!-- 面包屑(静态示例) -->
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<div class="row row-cards">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">指标数据映射集设置</div>
|
||||
<div class="card-actions">
|
||||
<a href="javascript:void(0)" class="btn btn-primary" onclick="mapperSave()">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 5l0 14"></path>
|
||||
<path d="M5 12l14 0"></path>
|
||||
</svg>
|
||||
保存映射关系
|
||||
</a>
|
||||
<div class="page">
|
||||
<div class="page-body" data-page="indicator_mapper">
|
||||
<div class="container-xl">
|
||||
|
||||
<!-- 面包屑(静态示例/Thymeleaf) -->
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
|
||||
<div class="row row-cards">
|
||||
<!-- 顶层主卡片:加上 card-hero -->
|
||||
<div class="card card-hero">
|
||||
<div class="card-header">
|
||||
<div class="card-title">指标数据映射集设置</div>
|
||||
<div class="card-actions">
|
||||
<a href="javascript:void(0)" class="btn btn-primary" onclick="mapperSave()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 5l0 14"></path>
|
||||
<path d="M5 12l14 0"></path>
|
||||
</svg>
|
||||
保存映射关系
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<!-- 1. 根指标 -->
|
||||
<div class="card section mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">1. 选择指标(根指标)</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="indicationId" class="form-label m-0">根指标</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
|
||||
<select id="indicationId" class="form-select" name="indicationId">
|
||||
<option value="0">-选择根指标-</option>
|
||||
<option th:each="item:${rootList}" th:value="${item.id}"
|
||||
th:text="${item.getName()}" th:selected="${item.checked}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto text-muted small">选择根指标后,再选择设施与 CSV 以建立映射。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 选择设施 -->
|
||||
<div class="card section mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">2. 选择设施</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="modelId" class="form-label m-0">设施名称</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
|
||||
<select id="modelId" class="form-select" onchange="formModelChange(this)">
|
||||
<option value="0">-选择form表单-</option>
|
||||
<option th:each="modelItem:${modelList}" th:value="${modelItem.id}"
|
||||
th:text="${modelItem.getModelName()}"
|
||||
th:selected="${modelItem.checked}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 上传 CSV -->
|
||||
<div class="card section mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">3. 上传 CSV 文件</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="file" id="csvFile" class="form-control mb-3" accept=".csv">
|
||||
|
||||
<!-- 上次上传文件 -->
|
||||
<div class="mb-2" th:if="${csvMapper!=null}">
|
||||
<span class="fw-bold">上次上传文件:</span>
|
||||
<a th:href="@{/indicator/download/{id}(id=${csvMapper.id})}" target="_blank"
|
||||
th:text="${csvMapper.getCsvName()}"></a>
|
||||
</div>
|
||||
|
||||
<div class="fw-bold mb-2" id="csvHeader-total-num"></div>
|
||||
|
||||
<div class="list-group list-group-flush csv-heads" id="csvHeaders">
|
||||
<!-- 解析后在这里填充 CSV 列名 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 子指标映射 -->
|
||||
<div class="card section">
|
||||
<div class="card-header">
|
||||
<div class="card-title">4. 子指标映射设置</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="map-scroller">
|
||||
<table class="table table-vcenter table-striped table-hover align-middle map-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-header sticky-col-1 w-compact" style="width:48px;">#</th>
|
||||
<th class="sticky-header sticky-col-2" style="width:220px;">子指标名称</th>
|
||||
<th class="sticky-header" style="width:320px;">设施字段</th>
|
||||
<th class="sticky-header" style="width:320px;">CSV 表头</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="autonum">
|
||||
<tr th:each="item:${childrenIndicator}">
|
||||
<input type="hidden" name="indicatorId" th:value="${item.getId()}">
|
||||
<td class="sticky-body-col-1 w-compact col-index"></td>
|
||||
<td class="sticky-body-col-2 col-name" th:text="${item.getName()}"></td>
|
||||
|
||||
<td>
|
||||
<select class="form-select form-select-sm" name="formField">
|
||||
<option th:if="${formFieldList.size()>0}"
|
||||
th:each="field:${formFieldList}" th:value="${field.id}"
|
||||
th:text="${field.fieldLabel}"
|
||||
th:selected="${field.id == (bottomFormMap != null ? bottomFormMap[item.id] : '')}">
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<select class="form-select form-select-sm" name="csvField">
|
||||
<option th:if="${csvColumns.size()>0}" th:each="column:${csvColumns}"
|
||||
th:value="${column.id}"
|
||||
th:text="${column.getCsvColumnName()}"
|
||||
th:selected="${column.id == (bottomCsvColumnMap != null ? bottomCsvColumnMap[item.id] : '')}">
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-muted small mt-2">
|
||||
说明:为每个子指标选择表单字段或 CSV 列,系统会保存映射关系用于后续处理。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /card-body -->
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<!-- 1. 根指标(静态下拉) -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">1. 选择指标(根指标)</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="indicationId" class="form-label m-0">根指标</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<select id="indicationId" class="form-select" name="indicationId">
|
||||
<option value="0">-选择根指标-</option>
|
||||
<option th:each="item:${rootList}" th:value="${item.id}"
|
||||
th:text="${item.getName()}" th:selected="${item.checked}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- 1. 根指标(静态下拉) -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">2. 选择设施</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="modelId" class="form-label m-0">设施名称</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<select id="modelId" class="form-select" onchange="formModelChange(this)">
|
||||
<option value="0">-选择form表单-</option>
|
||||
<option th:each="modelItem:${modelList}" th:value="${modelItem.id}"
|
||||
th:text="${modelItem.getModelName()}"
|
||||
th:selected="${modelItem.checked}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. CSV 表头(静态 50 列) -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">3. 上传 CSV 文件</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="file" id="csvFile" class="form-control mb-3" accept=".csv">
|
||||
<!-- 新增:上次上传文件 -->
|
||||
<div class="mb-2" th:if="${csvMapper!=null}">
|
||||
<span class="fw-bold">上次上传文件:</span>
|
||||
<a th:href="@{/indicator/download/{id}(id=${csvMapper.id})}" target="_blank"
|
||||
th:text="${csvMapper.getCsvName()}"></a>
|
||||
</div>
|
||||
<div class="fw-bold mb-2" id="csvHeader-total-num"></div>
|
||||
<div class="list-group list-group-flush csv-heads" id="csvHeaders">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 子指标映射(静态 20 行;首两列粘左;自动编号) -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">4. 子指标映射设置</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="map-scroller">
|
||||
<table class="table table-vcenter table-striped table-hover align-middle map-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-header sticky-col-1" style="width:48px;">#</th>
|
||||
<th class="sticky-header sticky-col-2" style="width:200px;">子指标名称</th>
|
||||
|
||||
<th class="sticky-header" style="width:320px;">设施字段</th>
|
||||
<th class="sticky-header" style="width:320px;">CSV 表头</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
||||
<tbody class="autonum">
|
||||
|
||||
|
||||
<tr th:each="item:${childrenIndicator}">
|
||||
<input type="hidden" name="indicatorId" th:value="${item.getId()}">
|
||||
<td class="sticky-body-col-1 col-index"></td>
|
||||
<td class="sticky-body-col-2 col-name" th:text="${item.getName()}"></td>
|
||||
|
||||
<td><select class="form-select form-select-sm" name="formField">
|
||||
|
||||
<option th:if="${formFieldList.size()>0}"
|
||||
th:each="field:${formFieldList}" th:value="${field.id}"
|
||||
th:text="${field.fieldLabel}" th:selected="${field.id == (bottomFormMap != null ? bottomFormMap[item.id] : '')}"></option>
|
||||
>
|
||||
</option>
|
||||
|
||||
</select></td>
|
||||
<td><select class="form-select form-select-sm" name="csvField">
|
||||
|
||||
<option th:if="${csvColumns.size()>0}" th:each="column:${csvColumns}"
|
||||
th:value="${column.id}"
|
||||
th:text="${column.getCsvColumnName()}" th:selected="${column.id == (bottomCsvColumnMap != null ? bottomCsvColumnMap[item.id] : '')}"></option>
|
||||
>
|
||||
|
||||
</option>
|
||||
|
||||
</select></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-muted small mt-2">说明:选择子指标,对应表单字段或者CSV某列</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /card-body -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== 样式(关键:表头与表体分层;首两列分别粘左) ====== -->
|
||||
<style>
|
||||
|
||||
|
||||
</style>
|
||||
<!-- ====== JS:保持你的原有逻辑 ====== -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||
<script>
|
||||
function formModelChange(object) {
|
||||
let postUrl = document.getElementById('_rootPath').value + "indicator/indicatorFormMapper";
|
||||
|
|
@ -171,19 +173,15 @@
|
|||
$.each(data, function (_, opt) {
|
||||
$sel.append('<option value="' + opt.id + '">' + opt.fieldLabel + '</option>');
|
||||
});
|
||||
// 交互高亮一下主卡片
|
||||
flashHero();
|
||||
}, null, null)
|
||||
|
||||
}
|
||||
|
||||
function csvListen() {
|
||||
|
||||
$('#csvFile').on('change', function () {
|
||||
|
||||
const f = this.files[0];
|
||||
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
if (!f) return;
|
||||
let indicatorTopId = $('#indicationId').val();
|
||||
const fd = new FormData();
|
||||
fd.append('file', f);
|
||||
|
|
@ -191,61 +189,76 @@
|
|||
let postUrl = document.getElementById('_rootPath').value + "indicator/uploadCsv";
|
||||
let http = new HttpClient();
|
||||
http.postFormData(postUrl, fd, function (error, response, xhr) {
|
||||
let data = response.result;
|
||||
let data = response.result || [];
|
||||
let len = data.length;
|
||||
document.getElementById("csvHeader-total-num").innerHTML = "已解析CSV文件:" + len + "列";
|
||||
document.getElementById("csvHeaders").innerHTML = "";
|
||||
|
||||
let html = "";
|
||||
document.getElementById("csvHeader-total-num").innerHTML = "已解析 CSV 文件:" + len + " 列";
|
||||
const list = document.getElementById("csvHeaders");
|
||||
list.innerHTML = "";
|
||||
if (len > 0) {
|
||||
const frag = document.createDocumentFragment();
|
||||
data.forEach(item => {
|
||||
html = html + " <div class=\"list-group-item\">" + item.csvColumnName + "</div>";
|
||||
})
|
||||
document.getElementById("csvHeaders").innerHTML = html;
|
||||
//处理指标映射那里的selected选项
|
||||
const div = document.createElement("div");
|
||||
div.className = "list-group-item";
|
||||
div.textContent = item.csvColumnName;
|
||||
frag.appendChild(div);
|
||||
});
|
||||
list.appendChild(frag);
|
||||
// 填充右侧下拉
|
||||
let $sel = $('select[name="csvField"]');
|
||||
$sel.empty();
|
||||
$.each(data, function (_, opt) {
|
||||
$sel.append('<option value="' + opt.id + '">' + opt.csvColumnName + '</option>');
|
||||
});
|
||||
}
|
||||
flashHero();
|
||||
}, null, null)
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function mapperSave() {
|
||||
|
||||
let obj = {};
|
||||
obj.indicatorTopId = $('#indicationId').val();
|
||||
//指标ID集合
|
||||
let indicatorElements = document.getElementsByName('indicatorId');
|
||||
let formMapperElements = document.getElementsByName('formField');
|
||||
let csvElements = document.getElementsByName('csvField');
|
||||
let formList = [];
|
||||
let csvList = [];
|
||||
let formList = [], csvList = [];
|
||||
for (let i = 0; i < indicatorElements.length; i++) {
|
||||
//form映射
|
||||
let formOneMapper = {};
|
||||
formOneMapper.indicatorId = parseInt(indicatorElements[i].value);
|
||||
formOneMapper.formFieldId = parseInt(formMapperElements[i].value);
|
||||
formOneMapper.indicatorTopId = obj.indicatorTopId;
|
||||
let formOneMapper = {
|
||||
indicatorId: parseInt(indicatorElements[i].value),
|
||||
formFieldId: parseInt(formMapperElements[i].value),
|
||||
indicatorTopId: obj.indicatorTopId
|
||||
};
|
||||
formList.push(formOneMapper);
|
||||
//csv映射
|
||||
let csvOneMapper = {};
|
||||
csvOneMapper.indicatorTopId = obj.indicatorTopId;
|
||||
csvOneMapper.indicatorId = parseInt(indicatorElements[i].value);
|
||||
csvOneMapper.csvColumnId = parseInt(csvElements[i].value);
|
||||
csvList.push(csvOneMapper);
|
||||
|
||||
let csvOneMapper = {
|
||||
indicatorTopId: obj.indicatorTopId,
|
||||
indicatorId: parseInt(indicatorElements[i].value),
|
||||
csvColumnId: parseInt(csvElements[i].value)
|
||||
};
|
||||
csvList.push(csvOneMapper);
|
||||
}
|
||||
obj.csvList = csvList;
|
||||
obj.formList = formList;
|
||||
//开始提交
|
||||
|
||||
let http = new HttpClient();
|
||||
let url = document.getElementById('_rootPath').value + "indicator/saveMapper";
|
||||
http.post(url,obj, function (error, response, xhr) {
|
||||
showAlert("success","保存成功")
|
||||
},null,null);
|
||||
http.post(url, obj, function (error, response, xhr) {
|
||||
showAlert("success","保存成功");
|
||||
flashHero();
|
||||
}, null, null);
|
||||
}
|
||||
</script>
|
||||
|
||||
// 根指标变化时也高亮主卡
|
||||
$('#indicationId').on('change', flashHero);
|
||||
|
||||
// 初始化 CSV 监听
|
||||
csvListen();
|
||||
|
||||
// 轻微高亮动画(与 CSS 中 .flash 对应)
|
||||
function flashHero(){
|
||||
const hero = document.querySelector('.card-hero');
|
||||
if(!hero) return;
|
||||
hero.classList.add('flash');
|
||||
setTimeout(()=>hero.classList.remove('flash'), 900);
|
||||
}
|
||||
</script>
|
||||
|
|
@ -9,9 +9,9 @@
|
|||
<div class="invalid-feedback" id="projectName_error_tip"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="indicatorTopId">指标:</label>
|
||||
<select name="indicatorTopId" id="indicatorTopId" class="form-control">
|
||||
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.name}"></option>
|
||||
<label class="form-label required" for="templateId">评估模板:</label>
|
||||
<select name="templateId" id="templateId" class="form-control">
|
||||
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.templateName}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
</svg>
|
||||
</th>
|
||||
<th>工程名称</th>
|
||||
<th>指标名称</th>
|
||||
<th>模板名称</th>
|
||||
<th>创建时间</th>
|
||||
|
||||
<th></th>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<tr th:if="${result.list!=null}" th:each="project : ${result.list}">
|
||||
<td th:text="${project.seq}"></td>
|
||||
<td th:text="${project.projectName}"></td>
|
||||
<td th:text="${project.indicatorTopName}"></td>
|
||||
<td th:text="${project.templateName}"></td>
|
||||
<td th:text="${#temporals.format(project.createDate, 'yyyy-MM-dd HH:mm:ss')}"></td>
|
||||
|
||||
<td>
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
</a>
|
||||
<a href="javascript:void(0)" class="btn btn-info"
|
||||
th:onclick="|_projectHistory('${project.id}')|">
|
||||
评估结果
|
||||
评估历史
|
||||
</a>
|
||||
<a href="javascript:void(0)" class=" btn btn-danger"
|
||||
th:onclick="|_projectDelete('${project.id}')|">
|
||||
|
|
@ -96,17 +96,14 @@
|
|||
document.getElementById("_evaluation_project_").click();
|
||||
}
|
||||
|
||||
function _toAdd(data) {
|
||||
function _toAdd() {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/project/add";
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (error, res, xhr) {
|
||||
document.getElementById("simpleFormBody").innerHTML = res;
|
||||
openDialog("simple-form-model");
|
||||
|
||||
if (data) {
|
||||
//给窗体赋值
|
||||
fillData(data);
|
||||
}
|
||||
|
||||
document.getElementById("SimpleFormDialog_save").onclick = function () {
|
||||
saveProject();
|
||||
}
|
||||
|
|
@ -123,51 +120,28 @@
|
|||
}, "projectForm", "simple-form-model")
|
||||
}
|
||||
|
||||
//第一步加载指标树
|
||||
function _startEvaluation(projectId) {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/project/startEvaluation/"+projectId;
|
||||
let http = new HttpClient();
|
||||
document.getElementById("stepForm")["projectId"].value = projectId;
|
||||
http.get(url, function (error, response, xhr) {
|
||||
|
||||
document.getElementById("add-full-screen-form-modal-body").innerHTML = response;
|
||||
openDialog("modal-full-width");
|
||||
initTreeMetric();
|
||||
}, "stepForm")
|
||||
}
|
||||
|
||||
function _projectDelete(id) {
|
||||
|
||||
openDialog("modal-danger");
|
||||
document.getElementById('delete-confirm-9999').onclick = function () {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/project/remove/"
|
||||
+ id;
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (error, res, xhr) {
|
||||
_pageSearch();
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function _projectEdit(id) {
|
||||
|
||||
}
|
||||
|
||||
function _projectHistory(id) {
|
||||
|
||||
}
|
||||
|
||||
//初始化指标树
|
||||
function initTreeMetric() {
|
||||
let projectId = document.getElementById("stepForm")['projectId'].value;
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/project/metricTree/"
|
||||
+ projectId;
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (error, response, body) {
|
||||
function _startEvaluation(id){
|
||||
|
||||
$('#project_metric_tree').jstree({
|
||||
'core': {
|
||||
'data': response.result
|
||||
}
|
||||
}).on("select_node.jstree", function (e, data) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
}, null);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
<div class="card card-hero">
|
||||
<input type="hidden" id="draftKey" name="draftKey" value="">
|
||||
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">1.基本信息</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="hidden" name="id" id="id" value="">
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="templateName">模板名称</label>
|
||||
<input type="text" id="templateName" class="form-control" name="templateName"
|
||||
placeholder="模板名称">
|
||||
<div class="invalid-feedback" id="templateName_error_tip"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label " for="templateMemo">备注</label>
|
||||
<textarea id="templateMemo" class="form-control" name="templateMemo"
|
||||
placeholder="说明信息" rows="5"></textarea>
|
||||
<div class="invalid-feedback" id="templateMemo_error_tip"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="card card-hero">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">2.指标权重</h3>
|
||||
</div>
|
||||
<div class="card-body" style="min-height: 40em;">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-3 col-form-label" for="indicatorTopId">指标</label>
|
||||
<div class="col">
|
||||
<select class="form-select" name="indicatorTopId" id="indicatorTopId" onchange="metricTree(this.value)">
|
||||
<option value="">--请选择--</option>
|
||||
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.name}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row" id="metric_tree">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9" id="table_area">
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">评估模板列表</h3>
|
||||
<div class="card-actions">
|
||||
<a href="javascript:void(0)" class="btn btn-primary" onclick="_toAdd()">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 5l0 14"></path>
|
||||
<path d="M5 12l14 0"></path>
|
||||
</svg>
|
||||
新增
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div th:replace="fragments/dialog::searchConditionDialog(${condition})"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table card-table table-vcenter text-nowrap datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th class="w-1">No.
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-up -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-sm icon-thick" width="24"
|
||||
height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"
|
||||
fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 15l6 -6l6 6"></path>
|
||||
</svg>
|
||||
</th>
|
||||
<th>模板名称</th>
|
||||
<th>对应指标</th>
|
||||
|
||||
<th>模板描述</th>
|
||||
<th>创建时间</th>
|
||||
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:if="${result.list!=null}" th:each="project : ${result.list}">
|
||||
<td th:text="${project.seq}"></td>
|
||||
<td th:text="${project.templateName}"></td>
|
||||
<td th:text="${project.indicatorTopName}"></td>
|
||||
|
||||
<td th:text="${project.templateMemo}"></td>
|
||||
<td th:text="${#temporals.format(project.createTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
|
||||
|
||||
<td>
|
||||
<div class="btn-list flex-nowrap">
|
||||
<a href="javascript:void(0)" class="btn btn-primary"
|
||||
th:onclick="|_templateEdit('${project.id}')|">
|
||||
编辑
|
||||
</a>
|
||||
|
||||
<a href="javascript:void(0)" class=" btn btn-danger"
|
||||
th:onclick="|_templateDelete('${project.id}')|">
|
||||
删除
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div th:replace="fragments/dialog::paginationDialog(${result})"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:replace="fragments/dialog::confirmationDialog"></div>
|
||||
<form id="templateForm">
|
||||
<input type="hidden" id="projectId" name="projectId">
|
||||
<div th:replace="fragments/dialog::addFullScreenFormDialog"></div>
|
||||
</form>
|
||||
<script>
|
||||
|
||||
|
||||
function _toAdd(data) {
|
||||
const url = document.getElementById("_rootPath").value + "evaluation/evaluationTemplate/add";
|
||||
const http = new HttpClient();
|
||||
http.get(url, function (error, res, xhr) {
|
||||
|
||||
document.getElementById("add-full-screen-form-modal-body").innerHTML = res;
|
||||
openDialog("modal-full-width");
|
||||
|
||||
document.getElementById("templateForm")["draftKey"].value = generateDraftKey();
|
||||
|
||||
if (data) {
|
||||
fillData(data);
|
||||
}
|
||||
document.getElementById("addFormDialog-btn").onclick = function () {
|
||||
saveTemplate();
|
||||
};
|
||||
}, "templateForm");
|
||||
}
|
||||
|
||||
function fillData(data) {
|
||||
|
||||
fillForm('templateForm', data);
|
||||
metricTree(data.indicatorTopId);
|
||||
}
|
||||
|
||||
function _templateDelete(id) {
|
||||
openDialog("modal-danger");
|
||||
document.getElementById('delete-confirm-9999').onclick = function () {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/evaluationTemplate/remove/"
|
||||
+ id;
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (error, res, xhr) {
|
||||
_pageSearch();
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function _pageSearch() {
|
||||
|
||||
let query = formObjToObject(document.getElementById("searchForm"));
|
||||
document.getElementById("_evaluation_evaluationTemplate_").setAttribute("hx-vals",
|
||||
JSON.stringify(query))
|
||||
document.getElementById("_evaluation_evaluationTemplate_").click();
|
||||
}
|
||||
|
||||
function _templateEdit(id) {
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/evaluationTemplate/" + id;
|
||||
let http = new HttpClient();
|
||||
http.get(url, function (error, res, xhr) {
|
||||
|
||||
_toAdd(res.result);
|
||||
}, 'templateForm')
|
||||
}
|
||||
|
||||
function saveTemplate() {
|
||||
let obj = {};
|
||||
obj.id = document.getElementById("templateForm")["id"].value;
|
||||
obj.templateName = document.getElementById("templateForm")["templateName"].value;
|
||||
obj.templateMemo = document.getElementById("templateForm")["templateMemo"].value;
|
||||
obj.indicatorTopId = document.getElementById("templateForm")["indicatorTopId"].value;
|
||||
obj.draftKey = document.getElementById("templateForm")["draftKey"].value;
|
||||
let pageMapData = getRowsTableData();
|
||||
|
||||
if (pageMapData && pageMapData.length > 0) {
|
||||
|
||||
obj.currentPagePartData = pageMapData;
|
||||
}
|
||||
let url = document.getElementById("_rootPath").value + "evaluation/evaluationTemplate/save";
|
||||
let http = new HttpClient();
|
||||
http.post(url, obj, function (error, res, xhr) {
|
||||
closeDialog("modal-full-width");
|
||||
document.getElementById("_evaluation_evaluationTemplate_").click();
|
||||
|
||||
}, "templateForm", null)
|
||||
}
|
||||
|
||||
// 根据指标ID获取指标树
|
||||
function metricTree(id) {
|
||||
|
||||
const url = document.getElementById("_rootPath").value
|
||||
+ "evaluation/evaluationTemplate/metricTree/" + id;
|
||||
const http = new HttpClient();
|
||||
|
||||
http.get(url, function (error, res, xhr) {
|
||||
const $tree = $('#metric_tree');
|
||||
|
||||
// 如已有实例,先销毁并清空
|
||||
if ($.jstree.reference($tree)) {
|
||||
|
||||
$tree.jstree(true).destroy();
|
||||
$tree.empty();
|
||||
}
|
||||
|
||||
// 重新初始化
|
||||
$tree.jstree({
|
||||
core: {data: res.result, check_callback: true}
|
||||
});
|
||||
|
||||
// 树加载完 → 自动选中第一个根节点(触发 select 事件,从而 drawTable 一次)
|
||||
$tree.off("loaded.jstree").on("loaded.jstree", function (e, data) {
|
||||
const roots = data.instance.get_node('#').children || [];
|
||||
if (roots.length > 0) {
|
||||
data.instance.select_node(roots[0]); // 会触发 select_node.jstree
|
||||
}
|
||||
});
|
||||
|
||||
// 绑定选择事件(避免重复绑定)
|
||||
$tree.off("select_node.jstree").on("select_node.jstree", function (e, data) {
|
||||
//暂存数据
|
||||
|
||||
let id = data.node.id || "";
|
||||
id = id.replace(/^tree_/, "");
|
||||
draftDataAddAndGetData(id);
|
||||
|
||||
});
|
||||
|
||||
}, "templateForm");
|
||||
}
|
||||
|
||||
//暂存旧数据+获取表数据
|
||||
function draftDataAddAndGetData(id) {
|
||||
|
||||
//首先判断是否存在历史表格.
|
||||
let headerData = getTableHeadData();
|
||||
|
||||
if (!headerData || headerData.size === 0) {
|
||||
|
||||
//去数据
|
||||
getTablePage(id);
|
||||
|
||||
} else {
|
||||
|
||||
let obj = {};
|
||||
obj.templateId = document.getElementById("templateForm")["id"].value;
|
||||
obj.key = document.getElementById("templateForm")["draftKey"].value;
|
||||
obj.headerMap = mapToObject(headerData);
|
||||
|
||||
obj.weight = getRowsTableData();
|
||||
|
||||
if (obj.weight.length > 0) {
|
||||
|
||||
//获取父ID
|
||||
obj.parentIndicationId = obj.weight[0][0].parentId;
|
||||
let url = document.getElementById("_rootPath").value
|
||||
+ "evaluation/evaluationTemplate/receiveDraft";
|
||||
let http = new HttpClient();
|
||||
//暂存数据
|
||||
http.post(url, obj, function (error, res, xhr) {
|
||||
getTablePage(id);
|
||||
|
||||
}, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//获取表头元素
|
||||
function getTableHeadData() {
|
||||
let tr = document.getElementById("dynamic_header_tr");
|
||||
if (!tr) {
|
||||
return null;
|
||||
}
|
||||
let thList = tr.querySelectorAll('th');
|
||||
|
||||
let map = new Map();
|
||||
for (let i = 1; i < thList.length; i++) {
|
||||
let th = thList[i];
|
||||
|
||||
let obj = {};
|
||||
obj.id = parseInt(th.getAttribute('data-value'));
|
||||
obj.name = th.getAttribute('data-name');
|
||||
map.set(obj.id, obj);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
//绘制表格
|
||||
function getTablePage(indicatorId) {
|
||||
const templateId = document.getElementById("templateForm")["id"].value;
|
||||
const url = document.getElementById("_rootPath").value
|
||||
+ "evaluation/evaluationTemplate/getDraftData";
|
||||
let obj = {};
|
||||
//页面key
|
||||
obj.key = document.getElementById("templateForm")["draftKey"].value;
|
||||
//父ID
|
||||
obj.parentIndicationId = indicatorId;
|
||||
//模板ID
|
||||
obj.templateId = templateId;
|
||||
|
||||
let http = new HttpClient();
|
||||
http.post(url, obj, function (error, res, xhr) {
|
||||
|
||||
document.getElementById("table_area").innerHTML = res;
|
||||
}, null, null)
|
||||
|
||||
}
|
||||
|
||||
// 前端验证是否符合一致性
|
||||
// 如果符合一致性,算出权重值,修改树节点名称
|
||||
function mapperChange(obj) {
|
||||
removeValidCss('templateForm')
|
||||
//此处验证提示
|
||||
if (validateConsistency(obj.id, obj.value)) {
|
||||
|
||||
//实时计算指标值
|
||||
let url = document.getElementById("_rootPath").value
|
||||
+ "evaluation/evaluationTemplate/computer";
|
||||
let http = new HttpClient();
|
||||
|
||||
let id = obj.id;
|
||||
let value = obj.value;
|
||||
//分割id 用_
|
||||
let ids = id.split("_");
|
||||
let reversId = ids[1] + "_" + ids[0];
|
||||
//获取当前value对应的1/x值
|
||||
document.getElementById("templateForm")[reversId].value = RECIP[value];
|
||||
let postObj = {};
|
||||
let metric = [];
|
||||
let header = getTableHeadData();
|
||||
header.forEach(element => {
|
||||
metric.push(element.id + "");
|
||||
})
|
||||
postObj.metric = metric;
|
||||
postObj.weightList = getRowsTableData();
|
||||
http.post(url, postObj, function (error, res, xhr) {
|
||||
const $tree = $('#metric_tree');
|
||||
res.result.forEach(element => {
|
||||
let id = "tree_" + element.id;
|
||||
let node = $tree.jstree(true).get_node(id, false);
|
||||
let name = node.text.replace(/\([^)]*\)/g, "");
|
||||
name = name + "(" + element.weight + ")";
|
||||
|
||||
$tree.jstree(true).rename_node(node, name);
|
||||
})
|
||||
}, null, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//获取树当前选中节点的权重数据
|
||||
function getRowsTableData() {
|
||||
let weightList = [];
|
||||
if (!document.getElementById("dynamic_tbody_tr")) {
|
||||
return weightList;
|
||||
}
|
||||
if (!document.getElementById("dynamic_tbody_tr").querySelectorAll('tr')) {
|
||||
return weightList;
|
||||
}
|
||||
let trList = document.getElementById("dynamic_tbody_tr").querySelectorAll('tr');
|
||||
for (let i = 0; i < trList.length; i++) {
|
||||
let tr = trList[i];
|
||||
let tdList = tr.querySelectorAll('td');
|
||||
let row = [];
|
||||
for (let j = 1; j < tdList.length; j++) {
|
||||
let td = tdList[j];
|
||||
let select = td.querySelector('select');
|
||||
let selectIdInfo = select.id.split('_');
|
||||
let obj = {};
|
||||
obj.rowId = selectIdInfo[0];
|
||||
obj.colId = selectIdInfo[1];
|
||||
obj.value = select.value;
|
||||
//行号
|
||||
obj.rowNum = i + 1;
|
||||
//父指标ID
|
||||
obj.parentId = select.getAttribute("data-pid");
|
||||
row.push(obj);
|
||||
}
|
||||
weightList.push(row);
|
||||
}
|
||||
return weightList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证一致性;只关注大小
|
||||
* @param id 当前 select框id
|
||||
* @param value 值
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function validateConsistency(id, value) {
|
||||
let firstRowMap = getTableFirstRowMap();
|
||||
//获取第一行中fromId
|
||||
let [firstKey, firstValue] = firstRowMap.entries().next().value;
|
||||
let firstRowFromId = firstKey.split('_')[0];
|
||||
//获取当前id的from和to
|
||||
let currentIds = id.split('_');
|
||||
//当前id 的from
|
||||
let currentFrom = currentIds[0];
|
||||
//当前id的to
|
||||
let currentTo = currentIds[1];
|
||||
//获取当前id在第一列中firstRowFromId+"_"+currentFrom值
|
||||
let firstRowFromVal = parseFloat(firstRowMap.get(firstRowFromId + "_" + currentFrom).value);
|
||||
//获取当前id在第一列中firstRowFromId+"_"+currentTo
|
||||
let firstRowToVal = parseFloat(firstRowMap.get(firstRowFromId + "_" + currentTo).value);
|
||||
//获取当前值
|
||||
let currentVal = parseFloat(value);
|
||||
if (firstRowFromVal > firstRowToVal) {
|
||||
if (currentVal > 1) {
|
||||
|
||||
showFieldErrorTip('templateForm', id, "权重必须小于1");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (firstRowToVal < firstRowToVal) {
|
||||
if (currentVal < 1) {
|
||||
|
||||
showFieldErrorTip('templateForm', id, "权重必须大于1");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getTableFirstRowMap() {
|
||||
let trList = document.getElementById("dynamic_tbody_tr").querySelectorAll('tr');
|
||||
let tdList = trList[0].querySelectorAll('td');
|
||||
let map = new Map();
|
||||
for (let i = 1; i < tdList.length; i++) {
|
||||
let td = tdList[i];
|
||||
let select = td.querySelector('select');
|
||||
map.set(select.id, select);
|
||||
|
||||
}
|
||||
return map;
|
||||
}
|
||||
</script>
|
||||
|
|
@ -68,8 +68,12 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.11.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,16 @@ public interface TableRelationsService extends IService<TableRelations> {
|
|||
*/
|
||||
void removeRel(Integer srcId, String srcTable, String dstTable);
|
||||
|
||||
/**
|
||||
* 删除一个引用.
|
||||
*
|
||||
* @param srcId 源表ID
|
||||
* @param srcTable 原表名称
|
||||
* @param dstId 目标表ID
|
||||
* @param dstTable 目标表名称
|
||||
*/
|
||||
void removeRel(Integer srcId, String srcTable, Integer dstId, String dstTable);
|
||||
|
||||
/**
|
||||
* 增加引用关系.
|
||||
*
|
||||
|
|
@ -39,4 +49,5 @@ public interface TableRelationsService extends IService<TableRelations> {
|
|||
* @param dstTable 引用表
|
||||
*/
|
||||
void addRel(Integer srcId, String srcTable, List<Integer> dstIdList, String dstTable);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,19 @@ public class TableRelationsServiceImpl extends
|
|||
this.remove(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRel(Integer srcId, String srcTable, Integer dstId, String dstTable) {
|
||||
if (srcId == null || srcTable == null || dstTable == null || dstId == null) {
|
||||
throw new IllegalArgumentException(ErrorMessage.IllegalArgumentException.getMessage());
|
||||
}
|
||||
QueryWrapper<TableRelations> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dst_table_name", dstTable);
|
||||
queryWrapper.eq("src_data_id", srcId);
|
||||
queryWrapper.eq("dst_data_id", dstId);
|
||||
queryWrapper.eq("src_table_name", srcTable);
|
||||
this.remove(queryWrapper);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void addRel(Integer srcId, String srcTable, List<Integer> dstIdList, String dstTable) {
|
||||
|
|
@ -58,4 +71,6 @@ public class TableRelationsServiceImpl extends
|
|||
this.save(tableRelations);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
package com.hshh.system.common.Strings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Extractor
|
||||
* Strings string 相关的工具.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @date 2025/7/18
|
||||
* @description
|
||||
*/
|
||||
public class Extractor {
|
||||
public class StringOperationUtil {
|
||||
|
||||
public static String[] extractOption(String input) {
|
||||
// 匹配形如 xx(xx) 的内容
|
||||
|
|
@ -27,4 +28,18 @@ public class Extractor {
|
|||
// 如果不匹配,返回空数组或抛异常
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
public static double[][] toDoubleArray(List<String[]> list) {
|
||||
int rows = list.size();
|
||||
int cols = list.get(0).length;
|
||||
double[][] arr = new double[rows][cols];
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
String[] row = list.get(i);
|
||||
for (int j = 0; j < cols; j++) {
|
||||
arr[i][j] = Double.parseDouble(row[j]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.hshh.system.common.algorithm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 树节点:每个非叶子节点可附带“对子节点的判断矩阵”
|
||||
*/
|
||||
|
||||
public class AhpNode {
|
||||
|
||||
public final String name;
|
||||
public final List<AhpNode> children = new ArrayList<>();
|
||||
|
||||
// 对子节点的两两比较矩阵(仅在 children.size()>1 时需要)
|
||||
double[][] localMatrix;
|
||||
|
||||
// 计算得到
|
||||
double[] localWeights; // 对“本节点”的子节点局部权重(和=1)
|
||||
double lambdaMax = Double.NaN, ci = Double.NaN, cr = Double.NaN;
|
||||
|
||||
public double globalWeight = Double.NaN; // 本节点的全局权重(根设为1,叶子递推得到)
|
||||
|
||||
public AhpNode(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public AhpNode setLocalMatrix(double[][] m) {
|
||||
this.localMatrix = m;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AhpNode add(AhpNode... nodes) {
|
||||
this.children.addAll(Arrays.asList(nodes));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.hshh.system.common.algorithm;
|
||||
|
||||
public class AhpTreeCompute {
|
||||
|
||||
/**
|
||||
* 递归计算:从 root 开始,将局部权重通过父节点全局权重向下传播
|
||||
*/
|
||||
public static void computeTree(AhpNode root) {
|
||||
if (Double.isNaN(root.globalWeight)) {
|
||||
root.globalWeight = 1.0; // 根默认 1
|
||||
}
|
||||
|
||||
int k = root.children.size();
|
||||
if (k == 0) {
|
||||
return; // 叶子
|
||||
}
|
||||
|
||||
if (k == 1) {
|
||||
// 单子节点不需要矩阵,权重=1,直接传播
|
||||
root.localWeights = new double[]{1.0};
|
||||
AhpNode c = root.children.get(0);
|
||||
c.globalWeight = root.globalWeight; // 乘1
|
||||
computeTree(c);
|
||||
root.lambdaMax = 1;
|
||||
root.ci = 0;
|
||||
root.cr = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.localMatrix == null || root.localMatrix.length != k
|
||||
|| root.localMatrix[0].length != k) {
|
||||
throw new IllegalStateException(
|
||||
"Node '" + root.name + "' needs a " + k + "x" + k + " localMatrix.");
|
||||
}
|
||||
|
||||
// AHP:对子节点判断矩阵 -> 局部权重 + 一致性
|
||||
Ahp.EigenResult er = Ahp.principalEigen(root.localMatrix, 1e-12, 10000);
|
||||
root.localWeights = er.w;
|
||||
root.lambdaMax = er.lambdaMax;
|
||||
root.ci = Ahp.consistencyIndex(er.lambdaMax, k);
|
||||
root.cr = Ahp.consistencyRatio(er.lambdaMax, k);
|
||||
|
||||
// 传播:子全局 = 父全局 × 局部权重
|
||||
for (int i = 0; i < k; i++) {
|
||||
AhpNode child = root.children.get(i);
|
||||
child.globalWeight = root.globalWeight * root.localWeights[i];
|
||||
computeTree(child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印树(缩进展示),显示局部/全局权重与一致性
|
||||
*/
|
||||
static void printTree(AhpNode node, int depth) {
|
||||
String indent = " ".repeat(depth);
|
||||
String info = String.format("%s- %s (global=%.4f)", indent, node.name,
|
||||
Double.isNaN(node.globalWeight) ? 0.0 : node.globalWeight);
|
||||
|
||||
|
||||
|
||||
|
||||
for (AhpNode c : node.children) {
|
||||
printTree(c, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- 完整示例 ----------------
|
||||
public static void main(String[] args) {
|
||||
// 层次:H -> A,B ; A -> A1,A2,A3 ; B -> B1,B2
|
||||
AhpNode H = new AhpNode("H"); // 目标
|
||||
AhpNode A = new AhpNode("A"); // 准则组 A
|
||||
AhpNode B = new AhpNode("B"); // 准则组 B
|
||||
// AhpNode A1 = new AhpNode("A1");
|
||||
// AhpNode A2 = new AhpNode("A2");
|
||||
// AhpNode A3 = new AhpNode("A3");
|
||||
// AhpNode B1 = new AhpNode("B1");
|
||||
// AhpNode B2 = new AhpNode("B2");
|
||||
|
||||
// 组装树
|
||||
H.add(A, B);
|
||||
// A.add(A1, A2, A3);
|
||||
// B.add(B1, B2);
|
||||
|
||||
// 顶层 H 的判断矩阵(A vs B)
|
||||
H.setLocalMatrix(new double[][]{
|
||||
{1, 3},
|
||||
{1.0/3, 1}
|
||||
});
|
||||
|
||||
// // A 组内(A1,A2,A3)
|
||||
// A.setLocalMatrix(new double[][]{
|
||||
// {1, 2, 4},
|
||||
// {0.5, 1, 3},
|
||||
// {0.25, 1.0/3, 1}
|
||||
// });
|
||||
//
|
||||
// // B 组内(B1,B2)
|
||||
// B.setLocalMatrix(new double[][]{
|
||||
// {1, 0.5},
|
||||
// {2, 1}
|
||||
// });
|
||||
|
||||
// 计算
|
||||
H.globalWeight = 1.0; // 根节点全局权重=1
|
||||
computeTree(H);
|
||||
|
||||
// 打印结果
|
||||
printTree(H, 0);
|
||||
|
||||
// // 校验:所有叶子全局之和应为 1
|
||||
// double sumLeaves = sumLeaves(H);
|
||||
// System.out.printf("%nSum of leaf global weights = %.6f%n", sumLeaves);
|
||||
}
|
||||
|
||||
static double sumLeaves(AhpNode n) {
|
||||
if (n.children.isEmpty()) {
|
||||
return n.globalWeight;
|
||||
}
|
||||
double s = 0.0;
|
||||
for (AhpNode c : n.children) {
|
||||
s += sumLeaves(c);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
package com.hshh.system.common.algorithm;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/** 树节点:每个非叶子节点可附带“对子节点的判断矩阵” */
|
||||
class AhpNode {
|
||||
final String name;
|
||||
final List<AhpNode> children = new ArrayList<>();
|
||||
|
||||
// 对子节点的两两比较矩阵(仅在 children.size()>1 时需要)
|
||||
double[][] localMatrix;
|
||||
|
||||
// 计算得到
|
||||
double[] localWeights; // 对“本节点”的子节点局部权重(和=1)
|
||||
double lambdaMax = Double.NaN, ci = Double.NaN, cr = Double.NaN;
|
||||
|
||||
double globalWeight = Double.NaN; // 本节点的全局权重(根设为1,叶子递推得到)
|
||||
|
||||
AhpNode(String name) { this.name = name; }
|
||||
|
||||
AhpNode setLocalMatrix(double[][] m) { this.localMatrix = m; return this; }
|
||||
AhpNode add(AhpNode... nodes) { this.children.addAll(Arrays.asList(nodes)); return this; }
|
||||
}
|
||||
|
||||
public class AhpTreeDemo {
|
||||
|
||||
/** 递归计算:从 root 开始,将局部权重通过父节点全局权重向下传播 */
|
||||
static void computeTree(AhpNode root) {
|
||||
if (Double.isNaN(root.globalWeight)) root.globalWeight = 1.0; // 根默认 1
|
||||
|
||||
int k = root.children.size();
|
||||
if (k == 0) return; // 叶子
|
||||
|
||||
if (k == 1) {
|
||||
// 单子节点不需要矩阵,权重=1,直接传播
|
||||
root.localWeights = new double[]{1.0};
|
||||
AhpNode c = root.children.get(0);
|
||||
c.globalWeight = root.globalWeight; // 乘1
|
||||
computeTree(c);
|
||||
root.lambdaMax = 1; root.ci = 0; root.cr = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.localMatrix == null || root.localMatrix.length != k || root.localMatrix[0].length != k) {
|
||||
throw new IllegalStateException("Node '"+root.name+"' needs a "+k+"x"+k+" localMatrix.");
|
||||
}
|
||||
|
||||
// AHP:对子节点判断矩阵 -> 局部权重 + 一致性
|
||||
Ahp.EigenResult er = Ahp.principalEigen(root.localMatrix, 1e-12, 10000);
|
||||
root.localWeights = er.w;
|
||||
root.lambdaMax = er.lambdaMax;
|
||||
root.ci = Ahp.consistencyIndex(er.lambdaMax, k);
|
||||
root.cr = Ahp.consistencyRatio(er.lambdaMax, k);
|
||||
|
||||
// 传播:子全局 = 父全局 × 局部权重
|
||||
for (int i = 0; i < k; i++) {
|
||||
AhpNode child = root.children.get(i);
|
||||
child.globalWeight = root.globalWeight * root.localWeights[i];
|
||||
computeTree(child);
|
||||
}
|
||||
}
|
||||
|
||||
/** 打印树(缩进展示),显示局部/全局权重与一致性 */
|
||||
static void printTree(AhpNode node, int depth) {
|
||||
String indent = " ".repeat(depth);
|
||||
String info = String.format("%s- %s (global=%.4f)", indent, node.name,
|
||||
Double.isNaN(node.globalWeight) ? 0.0 : node.globalWeight);
|
||||
System.out.println(info);
|
||||
|
||||
if (node.children.size() > 1) {
|
||||
System.out.printf("%s local weights = %s | λmax=%.6f CI=%.6f CR=%.6f%n",
|
||||
indent, Ahp.fmt(node.localWeights), node.lambdaMax, node.ci, node.cr);
|
||||
}
|
||||
|
||||
for (AhpNode c : node.children) printTree(c, depth+1);
|
||||
}
|
||||
|
||||
// ---------------- 完整示例 ----------------
|
||||
// public static void main(String[] args) {
|
||||
// // 层次:H -> A,B ; A -> A1,A2,A3 ; B -> B1,B2
|
||||
// AhpNode H = new AhpNode("H"); // 目标
|
||||
// AhpNode A = new AhpNode("A"); // 准则组 A
|
||||
// AhpNode B = new AhpNode("B"); // 准则组 B
|
||||
// AhpNode A1 = new AhpNode("A1");
|
||||
// AhpNode A2 = new AhpNode("A2");
|
||||
// AhpNode A3 = new AhpNode("A3");
|
||||
// AhpNode B1 = new AhpNode("B1");
|
||||
// AhpNode B2 = new AhpNode("B2");
|
||||
//
|
||||
// // 组装树
|
||||
// H.add(A, B);
|
||||
// A.add(A1, A2, A3);
|
||||
// B.add(B1, B2);
|
||||
//
|
||||
// // 顶层 H 的判断矩阵(A vs B)
|
||||
// H.setLocalMatrix(new double[][]{
|
||||
// {1, 3},
|
||||
// {1.0/3, 1}
|
||||
// });
|
||||
//
|
||||
// // A 组内(A1,A2,A3)
|
||||
// A.setLocalMatrix(new double[][]{
|
||||
// {1, 2, 4},
|
||||
// {0.5, 1, 3},
|
||||
// {0.25, 1.0/3, 1}
|
||||
// });
|
||||
//
|
||||
// // B 组内(B1,B2)
|
||||
// B.setLocalMatrix(new double[][]{
|
||||
// {1, 0.5},
|
||||
// {2, 1}
|
||||
// });
|
||||
//
|
||||
// // 计算
|
||||
// H.globalWeight = 1.0; // 根节点全局权重=1
|
||||
// computeTree(H);
|
||||
//
|
||||
// // 打印结果
|
||||
// printTree(H, 0);
|
||||
//
|
||||
// // 校验:所有叶子全局之和应为 1
|
||||
// double sumLeaves = sumLeaves(H);
|
||||
// System.out.printf("%nSum of leaf global weights = %.6f%n", sumLeaves);
|
||||
// }
|
||||
|
||||
static double sumLeaves(AhpNode n) {
|
||||
if (n.children.isEmpty()) return n.globalWeight;
|
||||
double s = 0.0;
|
||||
for (AhpNode c : n.children) s += sumLeaves(c);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.hshh.system.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 评估模板状态枚举.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
public enum EvaluationTemplateStatus {
|
||||
|
||||
DATA_WHOLE(1, "数据完整"),
|
||||
NOT_SET_INDICATOR(-1, "指标未设置"),
|
||||
INDICATOR_WEIGHT_NOT_WELL(-2, "指标权重不完整");
|
||||
@Getter
|
||||
final int code;
|
||||
@Getter
|
||||
final String desc;
|
||||
|
||||
EvaluationTemplateStatus(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取说明信息.
|
||||
*
|
||||
* @param code 编码
|
||||
* @return 说明
|
||||
*/
|
||||
public static String getMessage(int code) {
|
||||
if (code == 1) {
|
||||
return DATA_WHOLE.desc;
|
||||
}
|
||||
if (code == -1) {
|
||||
return NOT_SET_INDICATOR.desc;
|
||||
}
|
||||
if (code == -2) {
|
||||
return INDICATOR_WEIGHT_NOT_WELL.desc;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.hshh.system.common.util;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* [类的简要说明]
|
||||
* <p>
|
||||
* [详细描述,可选]
|
||||
* <p>
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
public final class DraftStore {
|
||||
|
||||
private static final Cache<String, Object> CACHE =
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterAccess(Duration.ofHours(2)) // 2小时不访问即过期
|
||||
.maximumSize(100_000) // 视内存设置上限
|
||||
.recordStats()
|
||||
.build();
|
||||
|
||||
public static void put(String key, Object value) {
|
||||
CACHE.put(key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T get(String key, Class<T> type) {
|
||||
Object v = CACHE.getIfPresent(key);
|
||||
return (type.isInstance(v) ? (T) v : null);
|
||||
}
|
||||
|
||||
public static boolean remove(String key) {
|
||||
Object prev = CACHE.asMap().remove(key);
|
||||
return prev != null;
|
||||
}
|
||||
|
||||
public static int removeByPrefix(String prefix) {
|
||||
int n = 0;
|
||||
for (String k : CACHE.asMap().keySet()) {
|
||||
if (k.startsWith(prefix)) {
|
||||
CACHE.invalidate(k);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
|
@ -54,5 +54,6 @@
|
|||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class CodeGenerator {
|
|||
basePath + "/src/main/resources/mapper")); // 设置mapperXml生成路径
|
||||
})
|
||||
.strategyConfig(builder -> {
|
||||
builder.addInclude("m_data_evaluation_template") // 设置需要生成的表名(多个用逗号分隔)
|
||||
builder.addInclude("m_data_evaluation_template_weight") // 设置需要生成的表名(多个用逗号分隔)
|
||||
.addTablePrefix("m_data_"); // 设置过滤表前缀
|
||||
})
|
||||
.execute();
|
||||
|
|
|
|||
6
pom.xml
6
pom.xml
|
|
@ -119,7 +119,13 @@
|
|||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.1.8</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user