1. 指标子集映射
This commit is contained in:
parent
75328fc0c5
commit
92fb41f419
|
|
@ -1,274 +1,189 @@
|
|||
<!-- Page body -->
|
||||
<style>
|
||||
#tree-container {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
overflow: auto;
|
||||
background: #fff;
|
||||
border-radius: .5rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.08);
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.node-rect {
|
||||
fill: #fff;
|
||||
stroke: #206bc4;
|
||||
stroke-width: 2px;
|
||||
rx: 8;
|
||||
ry: 8;
|
||||
filter: drop-shadow(0px 2px 6px #206bc420);
|
||||
}
|
||||
.node-text {
|
||||
font-size: 15px;
|
||||
fill: #206bc4;
|
||||
text-anchor: middle;
|
||||
font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
|
||||
dominant-baseline: middle;
|
||||
pointer-events: none;
|
||||
}
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: #b7c4d2;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
#tree-container {
|
||||
height: 400px;
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
||||
.center-btn {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 45%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
min-width: 140px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 18px rgba(60,72,88,0.12);
|
||||
border: 1px solid #e9ecef;
|
||||
font-size: 15px;
|
||||
padding: 4px 0;
|
||||
display: none;
|
||||
}
|
||||
.context-menu-item {
|
||||
cursor: pointer;
|
||||
padding: 8px 18px;
|
||||
transition: background 0.18s;
|
||||
color: #206bc4;
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
.context-menu-item:hover {
|
||||
background: #f1f7fe;
|
||||
color: #206bc4;
|
||||
}
|
||||
</style>
|
||||
<link href="https://unpkg.com/@tabler/core@1.0.0-beta24/dist/css/tabler.min.css" rel="stylesheet">
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<div class="page-body">
|
||||
<div class="page-body" data-page="metric">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
|
||||
<!-- 面包屑 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">指标管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">能力评估</a></li>
|
||||
<li class="breadcrumb-item"><a href="#">映射集</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-12" id="tree-container"></div>
|
||||
|
||||
<div class="row row-cards">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">指标数据映射集设置</div>
|
||||
</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 class="form-label m-0">根指标</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<select id="rootSelect" class="form-select">
|
||||
<option value="quality" selected>质量指标</option>
|
||||
<option value="safety">安全指标</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted small mt-2">切换根指标会刷新下面的“子指标映射表”。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 上传 CSV(模拟解析结果) -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<div class="card-title">2. 上传 CSV 文件</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="file" class="form-control mb-3" accept=".csv">
|
||||
<div class="fw-bold mb-2">已解析表头(模拟 50 列)</div>
|
||||
<div id="csvHeads" class="list-group list-group-flush csv-heads"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 子指标映射设置 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">3. 子指标映射设置</div>
|
||||
<div class="ms-auto">
|
||||
<div class="btn-list">
|
||||
<button class="btn btn-outline-primary" id="allFormBtn">全部切到 Form</button>
|
||||
<button class="btn btn-outline-primary" id="allCsvBtn">全部切到 CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="map-scroller">
|
||||
<table class="table table-vcenter map-table" id="mapTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-top" style="width:48px;">#</th>
|
||||
<th class="indicator-col sticky-top" style="width:200px;">子指标名称</th>
|
||||
<th class="sticky-top" style="width:130px;">来源</th>
|
||||
<th class="sticky-top" style="width:320px;">表单字段</th>
|
||||
<th class="sticky-top" style="width:320px;">CSV 表头</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mapTableBody"><!-- JS 渲染 --></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /card-body -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<div id="context-menu" class="context-menu"></div>
|
||||
<!-- 样式:滚动与粘性 -->
|
||||
<style>
|
||||
.csv-heads{ max-height:150px; overflow:auto; }
|
||||
.map-scroller{ max-height:65vh; overflow:auto; border-radius:.5rem; }
|
||||
.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; }
|
||||
.sticky-top{ position:sticky; top:0; z-index:3; background:#fff; }
|
||||
.indicator-col{ position:sticky; left:0; z-index:4; background:#fff; box-shadow:2px 0 0 rgba(0,0,0,.06); }
|
||||
.btn-group .btn{ min-width:52px; }
|
||||
</style>
|
||||
|
||||
<!-- 依赖:若你项目里已全局引入,可删掉下面两行 CDN -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select/dist/css/tom-select.bootstrap5.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/tom-select/dist/js/tom-select.complete.min.js"></script>
|
||||
|
||||
<script>
|
||||
let treeData = null;
|
||||
// ===== 静态数据 =====
|
||||
const formFields = ["qualifiedRate","defectCount","inspectionDate","checkPerson","location","comment"];
|
||||
const csvCols = Array.from({length:50}, (_,i)=>`col_${i+1}`);
|
||||
const indicatorsByRoot = {
|
||||
quality: Array.from({length:20}, (_,i)=>({ id:i+1, name:`质量_子指标_${i+1}` })),
|
||||
safety: Array.from({length:20}, (_,i)=>({ id:i+1, name:`安全_子指标_${i+1}` })),
|
||||
};
|
||||
|
||||
/**
|
||||
* 右键菜单渲染
|
||||
*/
|
||||
function showContextMenu(x, y, nodeData) {
|
||||
// 定义菜单项
|
||||
const menuItems = [
|
||||
{ text: "添加子节点", action: "add" },
|
||||
{ text: "编辑", action: "edit" },
|
||||
{ text: "删除", action: "delete" }
|
||||
];
|
||||
// CSV 表头预览
|
||||
document.getElementById('csvHeads').innerHTML =
|
||||
csvCols.map(c=>`<div class="list-group-item">${c}</div>`).join('');
|
||||
|
||||
const menu = document.getElementById("context-menu");
|
||||
menu.innerHTML = "";
|
||||
menuItems.forEach(item => {
|
||||
const btn = document.createElement("button");
|
||||
btn.className = "context-menu-item";
|
||||
btn.textContent = item.text;
|
||||
btn.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
menu.style.display = "none";
|
||||
handleMenuAction(item.action, nodeData);
|
||||
};
|
||||
menu.appendChild(btn);
|
||||
// 渲染子指标表格
|
||||
function renderTable(rootKey){
|
||||
const tbody = document.getElementById('mapTableBody');
|
||||
tbody.innerHTML = indicatorsByRoot[rootKey].map((it,idx)=>`
|
||||
<tr data-id="${it.id}">
|
||||
<td>${idx+1}</td>
|
||||
<td class="indicator-col">${it.name}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<input type="radio" class="btn-check" name="src-${it.id}" id="src-form-${it.id}" value="FORM" checked>
|
||||
<label class="btn btn-outline-primary" for="src-form-${it.id}">Form</label>
|
||||
<input type="radio" class="btn-check" name="src-${it.id}" id="src-csv-${it.id}" value="CSV">
|
||||
<label class="btn btn-outline-primary" for="src-csv-${it.id}">CSV</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm form-sel">
|
||||
<option value="">— 选择表单字段 —</option>
|
||||
${formFields.map(f=>`<option>${f}</option>`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm csv-sel" disabled>
|
||||
<option value="">— 选择 CSV 表头 —</option>
|
||||
${csvCols.map(c=>`<option>${c}</option>`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// 绑定单行切换
|
||||
tbody.querySelectorAll('input[type=radio][name^=src-]').forEach(r=>{
|
||||
r.addEventListener('change', onSourceChange);
|
||||
});
|
||||
|
||||
// 适应视窗,避免溢出
|
||||
const winW = window.innerWidth, winH = window.innerHeight;
|
||||
menu.style.left = (x + 150 > winW ? winW - 160 : x) + "px";
|
||||
menu.style.top = (y + 120 > winH ? winH - 130 : y) + "px";
|
||||
menu.style.display = "block";
|
||||
}
|
||||
|
||||
function hideContextMenu() {
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
}
|
||||
|
||||
// 右键菜单回调
|
||||
function handleMenuAction(action, nodeData) {
|
||||
if (action === "add") {
|
||||
// 简单示例: 添加子节点
|
||||
if (!nodeData.children) nodeData.children = [];
|
||||
const n = (nodeData.children.length || 0) + 1;
|
||||
nodeData.children.push({ name: `新节点${n}`, weight: "0%" });
|
||||
renderTree();
|
||||
} else if (action === "edit") {
|
||||
// 弹窗简单编辑
|
||||
const newName = prompt("请输入节点名称:", nodeData.name);
|
||||
if (newName && newName.trim()) {
|
||||
nodeData.name = newName.trim();
|
||||
renderTree();
|
||||
}
|
||||
} else if (action === "delete") {
|
||||
// 删除节点(防止删根)
|
||||
if (!nodeData.parent) {
|
||||
alert("根节点不能删除!");
|
||||
return;
|
||||
}
|
||||
const children = nodeData.parent.children;
|
||||
const idx = children.indexOf(nodeData);
|
||||
if (idx >= 0) {
|
||||
children.splice(idx, 1);
|
||||
renderTree();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// D3渲染
|
||||
function renderTree() {
|
||||
const container = d3.select("#tree-container");
|
||||
container.selectAll("*").remove();
|
||||
|
||||
hideContextMenu();
|
||||
|
||||
if (!treeData) {
|
||||
container.append("button")
|
||||
.attr("type", "button")
|
||||
.attr("class", "btn btn-primary center-btn")
|
||||
.text("添加根节点")
|
||||
.on("click", function() {
|
||||
treeData = {
|
||||
name: "根节点",
|
||||
weight: "100%",
|
||||
children: []
|
||||
};
|
||||
renderTree();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const width = Math.max(600, window.innerWidth * 0.9);
|
||||
const height = Math.max(400, window.innerHeight * 0.65);
|
||||
|
||||
const svg = container.append("svg")
|
||||
.attr("width", "100%")
|
||||
.attr("height", height)
|
||||
.attr("viewBox", [0, 0, width, height]);
|
||||
|
||||
// d3.hierarchy
|
||||
const root = d3.hierarchy(treeData);
|
||||
root.each(d => d.id = Math.random().toString(36).slice(2)); // 给每个节点一个唯一id
|
||||
|
||||
// 需要保留parent引用以便删除
|
||||
function setParent(d, parent) {
|
||||
d.parent = parent;
|
||||
if (d.children) d.children.forEach(c => setParent(c, d));
|
||||
}
|
||||
setParent(treeData, null);
|
||||
|
||||
const treeLayout = d3.tree()
|
||||
.size([width - 80, height - 100])
|
||||
.separation((a, b) => a.parent === b.parent ? 1.2 : 1.8);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
// 连线
|
||||
svg.append("g")
|
||||
.selectAll("path")
|
||||
.data(root.links())
|
||||
.join("path")
|
||||
.attr("class", "link")
|
||||
.attr("d", d3.linkVertical()
|
||||
.x(d => d.x + 40)
|
||||
.y(d => d.y)
|
||||
);
|
||||
|
||||
// 节点
|
||||
const node = svg.append("g")
|
||||
.selectAll("g")
|
||||
.data(root.descendants())
|
||||
.join("g")
|
||||
.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||
|
||||
const rectW = 80, rectH = 40;
|
||||
|
||||
// 方框
|
||||
node.append("rect")
|
||||
.attr("class", "node-rect")
|
||||
.attr("width", rectW)
|
||||
.attr("height", rectH)
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.on("contextmenu", (event, d) => {
|
||||
event.preventDefault();
|
||||
showContextMenu(event.clientX, event.clientY, d.data);
|
||||
});
|
||||
|
||||
// 名称
|
||||
node.append("text")
|
||||
.attr("class", "node-text")
|
||||
.attr("x", rectW / 2)
|
||||
.attr("y", rectH / 2 - 6)
|
||||
.text(d => d.data.name);
|
||||
|
||||
// 权重
|
||||
node.append("text")
|
||||
.attr("class", "node-text")
|
||||
.attr("x", rectW / 2)
|
||||
.attr("y", rectH / 2 + 12)
|
||||
.attr("font-size", "12px")
|
||||
.attr("fill", "#425466")
|
||||
.text(d => d.data.weight ? d.data.weight : "");
|
||||
|
||||
// 高亮交互
|
||||
node.style("cursor", "pointer")
|
||||
.on("mouseover", function() {
|
||||
d3.select(this).select("rect").attr("stroke", "#2fb344").attr("stroke-width", 3);
|
||||
})
|
||||
.on("mouseout", function() {
|
||||
d3.select(this).select("rect").attr("stroke", "#206bc4").attr("stroke-width", 2);
|
||||
// 重新初始化所有下拉的搜索(TomSelect)
|
||||
document.querySelectorAll('#mapTableBody select').forEach(sel=>{
|
||||
if(sel.tomselect){ sel.tomselect.destroy(); }
|
||||
new TomSelect(sel, { create:false, sortField:{field:"text",direction:"asc"} });
|
||||
});
|
||||
}
|
||||
|
||||
// 点击其它地方关闭菜单
|
||||
function onSourceChange(e){
|
||||
const tr = e.target.closest('tr');
|
||||
const formSel = tr.querySelector('.form-sel');
|
||||
const csvSel = tr.querySelector('.csv-sel');
|
||||
const formTS = formSel.tomselect;
|
||||
const csvTS = csvSel.tomselect;
|
||||
|
||||
if(e.target.value === 'FORM'){
|
||||
formSel.disabled=false; formTS.enable();
|
||||
csvSel.disabled=true; csvTS.disable(); csvTS.clear(true);
|
||||
}else{
|
||||
csvSel.disabled=false; csvTS.enable();
|
||||
formSel.disabled=true; formTS.disable(); formTS.clear(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量切换
|
||||
document.getElementById('allFormBtn').addEventListener('click', ()=>{
|
||||
document.querySelectorAll('input[id^="src-form-"]').forEach(r=>{ r.checked=true; r.dispatchEvent(new Event('change')); });
|
||||
});
|
||||
document.getElementById('allCsvBtn').addEventListener('click', ()=>{
|
||||
document.querySelectorAll('input[id^="src-csv-"]').forEach(r=>{ r.checked=true; r.dispatchEvent(new Event('change')); });
|
||||
});
|
||||
|
||||
</script>
|
||||
// 根指标选择(下拉)
|
||||
document.getElementById('rootSelect').addEventListener('change', (e)=>{
|
||||
renderTable(e.target.value);
|
||||
});
|
||||
|
||||
// 初始
|
||||
renderTable('quality');
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ public class DataController extends BaseController {
|
|||
@GetMapping("/list")
|
||||
@Operation(summary = "导航到数据管理页面", description = "导航到数据管理页面")
|
||||
public String index(PaginationBean request, Model model) {
|
||||
setNavigateTitle(model, "/data/list");
|
||||
List<ModelDefine> modelDefineList = modelDefineService.list(); //查询所有数据模型列表
|
||||
modelDefineList.sort(Comparator.comparing(ModelDefine::getSortOrder)); //对模型数据列表排序
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,35 @@
|
|||
package com.hshh.indicator.controller;
|
||||
|
||||
import com.hshh.indicator.entity.Indicator;
|
||||
import com.hshh.indicator.entity.IndicatorEvalItem;
|
||||
import com.hshh.indicator.service.IndicatorEvalItemService;
|
||||
import com.hshh.indicator.entity.IndicatorBottomCsvMapper;
|
||||
import com.hshh.indicator.entity.IndicatorBottomFormMapper;
|
||||
import com.hshh.indicator.entity.IndicatorCsv;
|
||||
import com.hshh.indicator.entity.IndicatorFormMapper;
|
||||
import com.hshh.indicator.service.IndicatorBottomCsvMapperService;
|
||||
import com.hshh.indicator.service.IndicatorBottomFormMapperService;
|
||||
import com.hshh.indicator.service.IndicatorCsvColumnService;
|
||||
import com.hshh.indicator.service.IndicatorCsvService;
|
||||
import com.hshh.indicator.service.IndicatorFromMapperService;
|
||||
import com.hshh.indicator.service.IndicatorService;
|
||||
import com.hshh.model.entity.FormFieldConfig;
|
||||
import com.hshh.model.entity.ModelDefine;
|
||||
import com.hshh.model.service.FormFieldConfigService;
|
||||
import com.hshh.model.service.ModelDefineService;
|
||||
import com.hshh.system.common.bean.BaseController;
|
||||
import com.hshh.system.common.bean.CheckedBean;
|
||||
import com.hshh.system.common.bean.OperateResult;
|
||||
import com.hshh.system.common.enums.ErrorCode;
|
||||
import com.hshh.system.common.enums.ErrorMessage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
|
|
@ -23,7 +39,9 @@ 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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 指标接口服务接口.
|
||||
|
|
@ -37,20 +55,46 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||
public class IndicatorController extends BaseController {
|
||||
|
||||
/**
|
||||
* 指标服务类.
|
||||
* 指标服务类. 查询所有指标列表
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorService indicatorService;
|
||||
/**
|
||||
* 模型定义服务类.
|
||||
* 模型定义服务类. 查询所有表单
|
||||
*/
|
||||
@Resource
|
||||
private ModelDefineService modelDefineService;
|
||||
/**
|
||||
* 评价集服务类.
|
||||
* 顶级指标对应的form和csv记录服务.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorEvalItemService indicatorEvalItemService;
|
||||
private IndicatorFromMapperService indicatorTopMapperService;
|
||||
/**
|
||||
* 表单中字段服务类.
|
||||
*/
|
||||
@Resource
|
||||
private FormFieldConfigService formFieldConfigService;
|
||||
/**
|
||||
* 顶级指标和csv的对应服务类.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorCsvService indicatorCsvService;
|
||||
|
||||
/**
|
||||
* 指标csv列服务.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorCsvColumnService indicatorCsvColumnService;
|
||||
/**
|
||||
* 底部指标和表单的的对应服务类.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorBottomFormMapperService bottomFormMapperService;
|
||||
/**
|
||||
* 底部指标和csv的对应关系.
|
||||
*/
|
||||
@Resource
|
||||
private IndicatorBottomCsvMapperService bottomCsvMapperService;
|
||||
|
||||
/**
|
||||
* 导航到列表页面. 获取所有的root指标,显示到页面左侧.
|
||||
|
|
@ -61,6 +105,7 @@ public class IndicatorController extends BaseController {
|
|||
*/
|
||||
@RequestMapping("/list")
|
||||
public String list(Model model, Indicator indicator) {
|
||||
setNavigateTitle(model, "/indicator/list");
|
||||
List<Indicator> rootList = indicatorService.queryRootList();
|
||||
if (rootList != null && !rootList.isEmpty()) {
|
||||
if (indicator.getId() != null) {
|
||||
|
|
@ -179,6 +224,7 @@ public class IndicatorController extends BaseController {
|
|||
*/
|
||||
@GetMapping("/evaluationList")
|
||||
public String evaluationList(Integer topIndicatorId, Integer indicatorId, Model model) {
|
||||
setNavigateTitle(model, "/indicator/evaluationList");
|
||||
List<Indicator> rootList = indicatorService.queryRootList();
|
||||
if (rootList != null && !rootList.isEmpty()) {
|
||||
if (topIndicatorId != null) {
|
||||
|
|
@ -227,16 +273,142 @@ public class IndicatorController extends BaseController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 保存评价集.
|
||||
* 导航到mapper页面. 1. 顶级指标放入session . 2. 底层指标放入session容器. 3. form表单字段放入session容器 .
|
||||
* 4.form表单列表放入session容器 5. csv对应session容器 6. csv字段session容器 7. 底部指标对应form字段session容器 8.
|
||||
* 底部指标对应csv列session容器.
|
||||
*
|
||||
* @param item
|
||||
* @param bindingResult
|
||||
* @return
|
||||
* @param model session容器
|
||||
* @param formId 表单ID
|
||||
* @param csvId csvID
|
||||
* @param indicatorTopId 指标顶级ID
|
||||
* @return /indicator/mapper.html
|
||||
*/
|
||||
// @PostMapping("/evalItem/save")
|
||||
// @ResponseBody
|
||||
// public OperateResult<IndicatorEvalItem> saveEval(@Valid @RequestBody IndicatorEvalItem item,
|
||||
// BindingResult bindingResult) {
|
||||
//
|
||||
// }
|
||||
|
||||
@GetMapping("/mapper")
|
||||
public String mapper(Model model, final Integer indicatorTopId, final Integer formId,
|
||||
final Integer csvId) {
|
||||
|
||||
setNavigateTitle(model, "/indicator/mapper"); //设置导航
|
||||
|
||||
List<Indicator> rootList = indicatorService.queryRootList(); //查询所有根指标
|
||||
setChecked(rootList, indicatorTopId); //设置根指标的选中状态
|
||||
model.addAttribute("rootList", rootList); // 1. 顶级指标放入session容器
|
||||
model.addAttribute("childrenIndicator",
|
||||
indicatorService.selectNoChildByTopId(indicatorTopId)); // 2. 底层指标放入session容器
|
||||
//处理form表单
|
||||
modelForm(model, indicatorTopId, formId); // 3. form表单字段放入session容器.4.form表单列表放入session容器
|
||||
//处理csv file
|
||||
csvFile(model, indicatorTopId); // 5. csv对应session容器 6. csv字段session容器
|
||||
|
||||
//放入底部指标和form表单字段对应关系到容器
|
||||
List<IndicatorBottomFormMapper> bottomList = bottomFormMapperService.queryListByIndicatorId(
|
||||
indicatorTopId);
|
||||
Map<Integer, IndicatorBottomFormMapper> bottomFormMap = bottomList.stream()
|
||||
.collect(Collectors.toMap(IndicatorBottomFormMapper::getFormFieldId, a -> a));
|
||||
model.addAttribute("bottomFormMap", bottomFormMap); // 7. 底部指标对应form字段session容器
|
||||
|
||||
//放入底部指标和csv列对应关系到容器
|
||||
List<IndicatorBottomCsvMapper> bottomCsvColumnList = bottomCsvMapperService.queryListByIndicatorTopId(
|
||||
indicatorTopId);
|
||||
Map<Integer, IndicatorBottomCsvMapper> bottomCsvColumnMap = bottomCsvColumnList.stream()
|
||||
.collect(Collectors.toMap(IndicatorBottomCsvMapper::getCsvColumnId, a -> a));
|
||||
model.addAttribute("bottomCsvColumnMap", bottomCsvColumnMap); // 8.底部指标对应csv列session容器
|
||||
|
||||
return "/indicator/mapper";
|
||||
}
|
||||
|
||||
private void modelForm(Model model, final Integer indicatorTopId, Integer formId) {
|
||||
//表单相关
|
||||
List<IndicatorFormMapper> mapperList = indicatorTopMapperService.selectModelAndCsvNameByIndicator(
|
||||
indicatorTopId); //查看form和顶指标映射关系
|
||||
//查询form表单字段
|
||||
if (formId != null) {
|
||||
|
||||
model.addAttribute("formFieldList",
|
||||
formFieldConfigService.getFormFieldConfigByModelId(formId));//3.form表单字段放入session容器
|
||||
} else if (!mapperList.isEmpty()) {
|
||||
List<FormFieldConfig> formFieldList = formFieldConfigService.getFormFieldConfigByModelId(
|
||||
mapperList.get(0).getIndicatorModelId());
|
||||
model.addAttribute("formFieldList", formFieldList); //3.form表单字段放入session容器
|
||||
}
|
||||
List<ModelDefine> modelList = modelDefineService.list(); //查询所有form表单列表
|
||||
setChecked(modelList, mapperList.isEmpty() ? null
|
||||
: (formId != null ? formId : mapperList.get(0).getIndicatorModelId())); //设置form表单列表的选中状态
|
||||
model.addAttribute("modelList", modelList); //4.所有form表单放入session容器
|
||||
}
|
||||
|
||||
private void csvFile(Model model, final Integer id) {
|
||||
IndicatorCsv csvMapper = indicatorCsvService.selectByIndicatorId(id); //5.csv对应放入session
|
||||
model.addAttribute("csvMapper", csvMapper);
|
||||
//查询csv列的列表
|
||||
model.addAttribute("csvColumns",
|
||||
indicatorCsvColumnService.listByIdOrderByColumn(csvMapper == null ? 0 : csvMapper.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载上一次的csv模板.
|
||||
*
|
||||
* @param id csv映射ID
|
||||
* @param response 响应
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
@GetMapping("/download/{id}")
|
||||
public void downloadFile(@PathVariable Integer id, HttpServletResponse response)
|
||||
throws IOException {
|
||||
IndicatorCsv csvFile = indicatorCsvService.getById(id);
|
||||
|
||||
// 设置响应头
|
||||
response.setContentType("text/csv; charset=UTF-8");
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename=\"" + java.net.URLEncoder.encode(csvFile.getCsvName(),
|
||||
StandardCharsets.UTF_8) + "\"");
|
||||
|
||||
// 写入文件流
|
||||
try (OutputStream os = response.getOutputStream()) {
|
||||
os.write(csvFile.getCsvData());
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传csv文件.记录顶级指标和文件及表单id的映射关系,记录在表m_data_indicator_top_mapper.
|
||||
*
|
||||
* @param file 文件
|
||||
* @param indicatorTopId 指标
|
||||
* @param formId modelID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/uploadCsvAndSetTopMapper")
|
||||
public OperateResult<Void> uploadCsvAndSetTopMapper(@RequestParam("file") MultipartFile file,
|
||||
Integer indicatorTopId, Integer formId) {
|
||||
indicatorService.saveIndicatorTopCsvMapper(indicatorTopId, formId, file);
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存指标和form表单ID的映射关系.
|
||||
*
|
||||
* @param indicatorTopId 指标顶级ID
|
||||
* @param formId 表单ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/indicatorFormMapper")
|
||||
public OperateResult<Void> saveIndicatorFormMapper(Integer indicatorTopId, Integer formId) {
|
||||
indicatorTopMapperService.saveFormMapper(indicatorTopId, formId);
|
||||
return OperateResult.success();
|
||||
}
|
||||
|
||||
private <T extends CheckedBean> void setChecked(List<T> list, Integer id) {
|
||||
if (list != null && !list.isEmpty()) {
|
||||
if (id == null || id.equals(0)) {
|
||||
list.get(0).setChecked(true);
|
||||
return;
|
||||
}
|
||||
for (T bean : list) {
|
||||
if (bean.getId().equals(id)) {
|
||||
bean.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.hshh.indicator.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 指标规则表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author liDongYu
|
||||
* @since 2025-08-04
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/indicator/indicatorEvalItem")
|
||||
public class IndicatorEvalItemController {
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ 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.system.common.bean.CheckedBean;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
|
@ -20,7 +21,7 @@ import lombok.Data;
|
|||
*/
|
||||
@TableName("m_data_indicator")
|
||||
@Data
|
||||
public class Indicator implements Serializable {
|
||||
public class Indicator extends CheckedBean {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
|
@ -39,9 +40,8 @@ public class Indicator implements Serializable {
|
|||
|
||||
private Integer topId; //顶级父ID
|
||||
private Integer sortOrder;
|
||||
@TableField(exist = false)
|
||||
private boolean checked;
|
||||
private Integer modelId;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.hshh.indicator.service;
|
|||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.hshh.indicator.entity.Indicator;
|
||||
import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 指标表 服务类.
|
||||
|
|
@ -53,4 +54,13 @@ public interface IndicatorService extends IService<Indicator> {
|
|||
* @return 指标集
|
||||
*/
|
||||
List<Indicator> selectNoChildByTopId(Integer topId);
|
||||
|
||||
/**
|
||||
* 保存顶级映射关系;记录对应的表单ID和csv信息.
|
||||
*
|
||||
* @param topId 顶级指标ID
|
||||
* @param modelId 表单ID
|
||||
* @param file csv文件
|
||||
*/
|
||||
void saveIndicatorTopCsvMapper(Integer topId, Integer modelId, MultipartFile file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import java.util.Map;
|
|||
import java.util.stream.Collectors;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
|
@ -82,4 +84,19 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
|
|||
public List<Indicator> selectNoChildByTopId(Integer topId) {
|
||||
return this.baseMapper.selectNoChildByTopId(topId);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void saveIndicatorTopCsvMapper(Integer topId, Integer modelId, MultipartFile file) {
|
||||
//删除表中m_data_indicator_csv原来topID对应记录
|
||||
|
||||
|
||||
//添加m_data_indicator_csv记录
|
||||
|
||||
//删除m_data_indicator_csv_column 原topID对应的记录
|
||||
|
||||
|
||||
//添加对应记录
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.hshh.model.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 动态表单实际对象记录表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author liDongYu
|
||||
* @since 2025-08-01
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/model/formValue")
|
||||
public class FormValueController {
|
||||
|
||||
}
|
||||
|
|
@ -82,6 +82,7 @@ public class ModelDefineController extends BaseController {
|
|||
@Operation(summary = "导航到数据模型定义页面", description = "导航到数据模型定义页面")
|
||||
@GetMapping("/list")
|
||||
public String list(ModelDefine modelDefine, Model model) {
|
||||
setNavigateTitle(model, "/model/list");
|
||||
List<ModelDefine> modelList = modelDefineService.list();
|
||||
List<FormFieldConfig> fieldList = new ArrayList<>();
|
||||
if (modelList != null) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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.system.common.bean.CheckedBean;
|
||||
import java.io.Serializable;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
|
@ -20,7 +21,7 @@ import lombok.Data;
|
|||
*/
|
||||
@TableName("m_data_model_define")
|
||||
@Data
|
||||
public class ModelDefine implements Serializable {
|
||||
public class ModelDefine extends CheckedBean {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
|
@ -37,8 +38,7 @@ public class ModelDefine implements Serializable {
|
|||
private String modelName;
|
||||
|
||||
|
||||
@TableField(exist = false)
|
||||
private boolean checked;
|
||||
|
||||
private Integer sortOrder;
|
||||
/**
|
||||
* 每行显示几个字段.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ public class FormFieldConfigServiceImpl extends
|
|||
public List<FormFieldConfig> getFormFieldConfigByModelId(Integer modelId) {
|
||||
QueryWrapper<FormFieldConfig> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("data_model_id", modelId);
|
||||
queryWrapper.orderByAsc("sort_order");
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">数据管理</a>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
|
|
|||
|
|
@ -184,4 +184,16 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div th:fragment="navigateDialog(naviList)" id="navigateDialog">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
|
||||
<li class="breadcrumb-item" th:each="m:${naviList}">
|
||||
<a href="#" th:text="${m.title}">系统管理</a>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,7 @@
|
|||
<input type="hidden" name="parentId" id="parentId" >
|
||||
<input type="hidden" name="topId" id="topId" >
|
||||
|
||||
<div class="mb-3" th:if="${modelDefineList != null}">
|
||||
<label class="form-label required">基础设施:</label>
|
||||
<select class="form-select" name="modelId" id="modelId" >
|
||||
<option th:each="item:${modelDefineList}" th:value="${item.id}" th:text="${item.getModelName()}"></option>
|
||||
</select>
|
||||
<div class="invalid-feedback" id="modelId_error_tip"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label required">指标名称:</label>
|
||||
<input type="text" class="form-control" name="name" id="name"
|
||||
|
|
|
|||
|
|
@ -11,14 +11,7 @@
|
|||
</style>
|
||||
<div class="page-body" data-page="metric">
|
||||
<div class="container-xl">
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">指标管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">评价集</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row row-cards">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
|
@ -35,13 +28,16 @@
|
|||
</label>
|
||||
|
||||
</div>
|
||||
<!-- 新增:上次上传文件 -->
|
||||
<div class="text-muted small mt-2">说明: 切换不同的指标可以设置当前指标子项的评价标准</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">指标子集</h3>
|
||||
<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}">
|
||||
|
|
|
|||
|
|
@ -128,14 +128,7 @@
|
|||
|
||||
<div class="page-body" data-page="metric">
|
||||
<div class="container-xl">
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">指标管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">指标数据</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-0 mb-4">
|
||||
<div class="col-md-3" style="min-height: 40em">
|
||||
<div class="card mb-4">
|
||||
|
|
|
|||
246
manager-admin/src/main/resources/templates/indicator/mapper.html
Normal file
246
manager-admin/src/main/resources/templates/indicator/mapper.html
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
<!-- ====== 页面主体(纯静态,无 JS/Thymeleaf) ====== -->
|
||||
<div class="page-body" data-page="mapper">
|
||||
<div class="container-xl">
|
||||
|
||||
<!-- 面包屑(静态示例) -->
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
|
||||
<div class="row row-cards">
|
||||
<div class="card">
|
||||
<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">
|
||||
|
||||
<!-- 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. 选择form表单</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">form名称</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<select id="modelId" class="form-select" onchange="formModelChange(this)">
|
||||
<option value="0">-选择form表单-</option>
|
||||
<option th:each="item:${modelList}" th:value="${item.id}"
|
||||
th:text="${item.getModelName()}" th:selected="${item.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" 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.getId()})" target="_blank"
|
||||
th:text="${csvMapper.getCsvName()}"></a>
|
||||
</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(不用写数字,CSS 计数器会生成 1..20) -->
|
||||
<tbody class="autonum">
|
||||
|
||||
|
||||
<!-- 02~20:同上复制即可(编号与名称尾号自动增长) -->
|
||||
<tr th:each="item:${childrenIndicator}">
|
||||
<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">
|
||||
<option>— 选择表单字段 —</option>
|
||||
<option th:if="${formFieldList.size()>0}"
|
||||
th:each="field:${formFieldList}" th:value="${field.id}"
|
||||
th:text="${field.getFieldName()}"></option>
|
||||
>
|
||||
</option>
|
||||
|
||||
</select></td>
|
||||
<td><select class="form-select form-select-sm">
|
||||
<option>— 选择 CSV 表头 —</option>
|
||||
<option th:if="${csvColumns.size()>0}" th:each="column:${csvColumns}"
|
||||
th:value="${column.id}"
|
||||
th:text="${column.getCsvColumnName()}"></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>
|
||||
: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);
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
function formModelChange(object) {
|
||||
document.getElementById("_indicator_mapper").setAttribute("hx-vals",
|
||||
JSON.stringify({indicatorTopId: $('#indicationId').val(), formId: object.value}))
|
||||
document.getElementById("_indicator_mapper").click();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">基础设施</a>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- 左侧字典类型 -->
|
||||
<div class="col-md-3">
|
||||
|
|
|
|||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">系统管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">字典管理</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- 左侧字典类型 -->
|
||||
<div class="col-md-5">
|
||||
|
|
|
|||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl" data-page="menu-list">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">系统管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">菜单管理</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
|
|
|||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">系统管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">权限管理</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
|
||||
<div class="col-12">
|
||||
|
|
|
|||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">系统管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">角色管理</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
|
|
|
|||
|
|
@ -2,14 +2,7 @@
|
|||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<!-- 面包屑导航 -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="#">系统管理</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a href="#">用户管理</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
|
||||
<div class="row g-4 mb-4">
|
||||
|
||||
<div class="col-12">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import java.util.List;
|
|||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
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;
|
||||
|
|
@ -46,6 +47,7 @@ public class DictItemController extends BaseController {
|
|||
@Operation(summary = "获取字典条目增加页面内容", description = "用户点击新增字典条目时执行")
|
||||
@GetMapping("/add")
|
||||
public String add() {
|
||||
|
||||
return "system/dict/add_dict_item";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ public class DictTypeController extends BaseController {
|
|||
@GetMapping("/")
|
||||
@Operation(summary = "导航到列表页面", description = "用户点击菜单时,执行")
|
||||
public String index(Model model, DictType type) {
|
||||
|
||||
setNavigateTitle(model, "/base/dict/");
|
||||
|
||||
List<DictType> dictList = dictTypeService.list();
|
||||
dictList.sort(Comparator.comparingInt(DictType::getSortOrder));
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import javax.validation.Valid;
|
|||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
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;
|
||||
|
|
@ -53,7 +54,9 @@ public class MenuController extends BaseController {
|
|||
*/
|
||||
@GetMapping("/")
|
||||
@Operation(summary = "菜单首页", description = "导航到默认页")
|
||||
public String list() {
|
||||
public String list(Model model) {
|
||||
setNavigateTitle(model, "/base/menu/");
|
||||
|
||||
return "system/menu/list";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public class PermissionController extends BaseController {
|
|||
@GetMapping("/")
|
||||
@Operation(summary = "权限首页", description = "导航到默认页")
|
||||
public String list(PaginationBean request, Model model) {
|
||||
setNavigateTitle(model, "/base/permission/");
|
||||
List<Permissions> list = permissionsService.list(request);
|
||||
Long total = permissionsService.count(request);
|
||||
//设置分页信息
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public class RoleController extends BaseController {
|
|||
@GetMapping("/")
|
||||
@Operation(summary = "角色首页", description = "导航到默认页")
|
||||
public String list(PaginationBean request, Model model) {
|
||||
setNavigateTitle(model, "/base/role/");
|
||||
List<Roles> list = rolesService.list(request);
|
||||
Long total = rolesService.count(request);
|
||||
//设置分页信息
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.hshh.system.base.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author liDongYu
|
||||
* @since 2025-07-29
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/base/tableRelations")
|
||||
public class TableRelationsController {
|
||||
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ public class UserController extends BaseController {
|
|||
@GetMapping("/")
|
||||
@Operation(summary = "用户首页", description = "导航到默认页")
|
||||
public String list(PaginationBean request, Model model) {
|
||||
setNavigateTitle(model, "/base/user/");
|
||||
List<Users> list = usersService.list(request);
|
||||
Long total = usersService.count(request);
|
||||
//设置分页信息
|
||||
|
|
|
|||
|
|
@ -42,4 +42,12 @@ public interface MenusService extends IService<Menus> {
|
|||
* @return 菜单列表
|
||||
*/
|
||||
List<Menus> queryListByPid(Integer parentId);
|
||||
|
||||
/**
|
||||
* 根据菜单地址获取菜单对象. 比如,菜单A,父亲时B,B的父亲是C.则返回列表 C,B,A.
|
||||
*
|
||||
* @param href 菜单连接地址
|
||||
* @return 菜单对象列表;从父-父...-孩子
|
||||
*/
|
||||
List<Menus> getMenus(String href);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.hshh.system.base.entity.Menus;
|
|||
import com.hshh.system.base.mapper.MenusMapper;
|
||||
import com.hshh.system.base.service.MenusService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -125,4 +126,38 @@ public class MenusServiceImpl extends ServiceImpl<MenusMapper, Menus> implements
|
|||
queryWrapper.eq("parent_id", parentId);
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Menus> getMenus(String href) {
|
||||
List<Menus> resultList = new ArrayList<>();
|
||||
List<Menus> menuList = this.list();
|
||||
menuList.forEach(a -> {
|
||||
if (a.getParentId() == null) {
|
||||
a.setParentId(0);
|
||||
}
|
||||
});
|
||||
//用key=path
|
||||
Map<String, Menus> keyIsPathMenusMap = menuList.stream().filter(a -> a.getPath() != null)
|
||||
.collect(Collectors.toMap(Menus::getPath, a -> a));
|
||||
Menus menus = keyIsPathMenusMap.get(href);
|
||||
if (menus == null) {
|
||||
return resultList;
|
||||
}
|
||||
resultList.add(menus);
|
||||
//key=id
|
||||
Map<Integer, Menus> idMap = menuList.stream()
|
||||
.collect(Collectors.toMap(Menus::getId, a -> a));
|
||||
addParent(resultList, menus.getParentId(), idMap);
|
||||
//反转
|
||||
Collections.reverse(resultList);
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private void addParent(List<Menus> resultList, Integer parentId, Map<Integer, Menus> allMenuMap) {
|
||||
if (parentId != null && allMenuMap.get(parentId) != null) {
|
||||
resultList.add(allMenuMap.get(parentId));
|
||||
addParent(resultList, allMenuMap.get(parentId).getParentId(), allMenuMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import lombok.Getter;
|
|||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 分页基础参数
|
||||
* 分页基础参数.
|
||||
*/
|
||||
public class BaseBean implements Serializable {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.hshh.system.common.bean;
|
||||
|
||||
|
||||
import com.hshh.system.base.entity.Menus;
|
||||
import com.hshh.system.base.service.impl.MenusServiceImpl;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.springframework.ui.Model;
|
||||
|
|
@ -45,4 +47,15 @@ public class BaseController {
|
|||
model.addAttribute("condition", request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置页面上的导航信息.
|
||||
*
|
||||
* @param model session容器.
|
||||
* @param href 页面地址
|
||||
*/
|
||||
protected void setNavigateTitle(Model model, String href) {
|
||||
MenusServiceImpl menuService = SpringContextHolder.getBean(MenusServiceImpl.class);
|
||||
List<Menus> menuList = menuService.getMenus(href);
|
||||
model.addAttribute("chainMenuList", menuList);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.hshh.system.common.bean;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 标记checked.
|
||||
*
|
||||
* @author LiDongYU
|
||||
* @since 2025/7/22
|
||||
*/
|
||||
@Data
|
||||
public class CheckedBean {
|
||||
|
||||
@TableField(exist = false)
|
||||
private boolean checked;
|
||||
private Integer id;
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public class CodeGenerator {
|
|||
basePath + "/src/main/resources/mapper")); // 设置mapperXml生成路径
|
||||
})
|
||||
.strategyConfig(builder -> {
|
||||
builder.addInclude("m_data_indicator_eval_item") // 设置需要生成的表名(多个用逗号分隔)
|
||||
builder.addInclude("m_data_indicator_bottom_csv_mapper") // 设置需要生成的表名(多个用逗号分隔)
|
||||
.addTablePrefix("m_data_"); // 设置过滤表前缀
|
||||
})
|
||||
.execute();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user