1. 指标子集映射

This commit is contained in:
李玉东 2025-08-21 16:00:41 +08:00
parent e2d86ae384
commit ba1a3bcae4
70 changed files with 2712 additions and 676 deletions

View File

@ -25,6 +25,11 @@
<artifactId>system</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>

View File

@ -1,206 +1,670 @@
<!doctype html>
<html lang="zh-CN">
<!--
* Tabler - Premium and Open Source dashboard template with responsive and high quality UI.
* @version 1.0.0-beta19
* @link https://tabler.io
* Copyright 2018-2023 The Tabler Authors
* Copyright 2018-2023 codecalm.net Paweł Kuna
* Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)
-->
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<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"/>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>Logs - Tabler - Premium and Open Source dashboard template with responsive and high quality UI.</title>
<!-- CSS files -->
<link href="./dist/css/tabler.min.css?1684106062" rel="stylesheet"/>
<link href="./dist/css/tabler-flags.min.css?1684106062" rel="stylesheet"/>
<link href="./dist/css/tabler-payments.min.css?1684106062" rel="stylesheet"/>
<link href="./dist/css/tabler-vendors.min.css?1684106062" rel="stylesheet"/>
<link href="./dist/css/demo.min.css?1684106062" rel="stylesheet"/>
<style>
:root{
/* 你的主题主色,可按项目实际调整 */
--accent: #206bc4;
@import url('https://rsms.me/inter/inter.css');
:root {
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
}
/* 强调卡片 */
.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;
}
.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);
body {
font-feature-settings: "cv03", "cv04", "cv11";
}
</style>
</head>
<body class="theme-light">
<body >
<script src="./dist/js/demo-theme.min.js?1684106062"></script>
<div class="page">
<header class="navbar navbar-expand-md navbar-light d-print-none" style="border-bottom:1px solid rgba(0,0,0,.06);">
<!-- Navbar -->
<header class="navbar navbar-expand-md d-print-none" >
<div class="container-xl">
<h2 class="navbar-brand">海上评估系统</h2>
<div class="ms-auto text-muted">管理员</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<a href=".">
<img src="./static/logo.svg" width="110" height="32" alt="Tabler" class="navbar-brand-image">
</a>
</h1>
<div class="navbar-nav flex-row order-md-last">
<div class="nav-item d-none d-md-flex me-3">
<div class="btn-list">
<a href="https://github.com/tabler/tabler" class="btn" target="_blank" rel="noreferrer">
<!-- Download SVG icon from http://tabler-icons.io/i/brand-github -->
<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 d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" /></svg>
Source code
</a>
<a href="https://github.com/sponsors/codecalm" class="btn" target="_blank" rel="noreferrer">
<!-- Download SVG icon from http://tabler-icons.io/i/heart -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-pink" 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 d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>
Sponsor
</a>
</div>
</div>
<div class="d-none d-md-flex">
<a href="?theme=dark" class="nav-link px-0 hide-theme-dark" title="Enable dark mode" data-bs-toggle="tooltip"
data-bs-placement="bottom">
<!-- Download SVG icon from http://tabler-icons.io/i/moon -->
<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 d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" /></svg>
</a>
<a href="?theme=light" class="nav-link px-0 hide-theme-light" title="Enable light mode" data-bs-toggle="tooltip"
data-bs-placement="bottom">
<!-- Download SVG icon from http://tabler-icons.io/i/sun -->
<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 d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" /></svg>
</a>
<div class="nav-item dropdown d-none d-md-flex me-3">
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show notifications">
<!-- Download SVG icon from http://tabler-icons.io/i/bell -->
<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 d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" /><path d="M9 17v1a3 3 0 0 0 6 0v-1" /></svg>
<span class="badge bg-red"></span>
</a>
<div class="dropdown-menu dropdown-menu-arrow dropdown-menu-end dropdown-menu-card">
<div class="card">
<div class="card-header">
<h3 class="card-title">Last updates</h3>
</div>
<div class="list-group list-group-flush list-group-hoverable">
<div class="list-group-item">
<div class="row align-items-center">
<div class="col-auto"><span class="status-dot status-dot-animated bg-red d-block"></span></div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">Example 1</a>
<div class="d-block text-muted text-truncate mt-n1">
Change deprecated html tags to text decoration classes (#29604)
</div>
</div>
<div class="col-auto">
<a href="#" class="list-group-item-actions">
<!-- Download SVG icon from http://tabler-icons.io/i/star -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-muted" 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 d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
</a>
</div>
</div>
</div>
<div class="list-group-item">
<div class="row align-items-center">
<div class="col-auto"><span class="status-dot d-block"></span></div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">Example 2</a>
<div class="d-block text-muted text-truncate mt-n1">
justify-content:between ⇒ justify-content:space-between (#29734)
</div>
</div>
<div class="col-auto">
<a href="#" class="list-group-item-actions show">
<!-- Download SVG icon from http://tabler-icons.io/i/star -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-yellow" 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 d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
</a>
</div>
</div>
</div>
<div class="list-group-item">
<div class="row align-items-center">
<div class="col-auto"><span class="status-dot d-block"></span></div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">Example 3</a>
<div class="d-block text-muted text-truncate mt-n1">
Update change-version.js (#29736)
</div>
</div>
<div class="col-auto">
<a href="#" class="list-group-item-actions">
<!-- Download SVG icon from http://tabler-icons.io/i/star -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-muted" 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 d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
</a>
</div>
</div>
</div>
<div class="list-group-item">
<div class="row align-items-center">
<div class="col-auto"><span class="status-dot status-dot-animated bg-green d-block"></span></div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">Example 4</a>
<div class="d-block text-muted text-truncate mt-n1">
Regenerate package-lock.json (#29730)
</div>
</div>
<div class="col-auto">
<a href="#" class="list-group-item-actions">
<!-- Download SVG icon from http://tabler-icons.io/i/star -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-muted" 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 d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="nav-item dropdown">
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
<span class="avatar avatar-sm" style="background-image: url(./static/avatars/000m.jpg)"></span>
<div class="d-none d-xl-block ps-2">
<div>Paweł Kuna</div>
<div class="mt-1 small text-muted">UI Designer</div>
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
<a href="#" class="dropdown-item">Status</a>
<a href="./profile.html" class="dropdown-item">Profile</a>
<a href="#" class="dropdown-item">Feedback</a>
<div class="dropdown-divider"></div>
<a href="./settings.html" class="dropdown-item">Settings</a>
<a href="./sign-in.html" class="dropdown-item">Logout</a>
</div>
</div>
</div>
</div>
</header>
<div class="page-body">
<div class="container-xl">
<!-- 重点卡片:选择指标 -->
<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">
<button class="btn btn-primary" id="btnNext">下一步</button>
</div>
</div>
<div class="card-body">
<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>
<header class="navbar-expand-md">
<div class="collapse navbar-collapse" id="navbar-menu">
<div class="navbar">
<div class="container-xl">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="./" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<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 d="M5 12l-2 0l9 -9l9 9l-2 0" /><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" /><path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" /></svg>
</span>
<span class="nav-link-title">
Home
</span>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-base" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/package -->
<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 d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5" /><path d="M12 12l8 -4.5" /><path d="M12 12l0 9" /><path d="M12 12l-8 -4.5" /><path d="M16 5.25l-8 4.5" /></svg>
</span>
<span class="nav-link-title">
Interface
</span>
</a>
<div class="dropdown-menu">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
<a class="dropdown-item" href="./accordion.html">
Accordion
</a>
<a class="dropdown-item" href="./blank.html">
Blank page
</a>
<a class="dropdown-item" href="./badges.html">
Badges
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./buttons.html">
Buttons
</a>
<div class="dropend">
<a class="dropdown-item dropdown-toggle" href="#sidebar-cards" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
Cards
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<div class="dropdown-menu">
<a href="./cards.html" class="dropdown-item">
Sample cards
</a>
<a href="./card-actions.html" class="dropdown-item">
Card actions
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a href="./cards-masonry.html" class="dropdown-item">
Cards Masonry
</a>
</div>
</div>
<a class="dropdown-item" href="./colors.html">
Colors
</a>
<a class="dropdown-item" href="./datagrid.html">
Data grid
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./datatables.html">
Datatables
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./dropdowns.html">
Dropdowns
</a>
<a class="dropdown-item" href="./modals.html">
Modals
</a>
<a class="dropdown-item" href="./maps.html">
Maps
</a>
<a class="dropdown-item" href="./map-fullsize.html">
Map fullsize
</a>
<a class="dropdown-item" href="./maps-vector.html">
Vector maps
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./navigation.html">
Navigation
</a>
<a class="dropdown-item" href="./charts.html">
Charts
</a>
<a class="dropdown-item" href="./pagination.html">
<!-- Download SVG icon from http://tabler-icons.io/i/pie-chart -->
Pagination
</a>
</div>
<div class="dropdown-menu-column">
<a class="dropdown-item" href="./placeholder.html">
Placeholder
</a>
<a class="dropdown-item" href="./steps.html">
Steps
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./stars-rating.html">
Stars rating
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./tabs.html">
Tabs
</a>
<a class="dropdown-item" href="./tables.html">
Tables
</a>
<a class="dropdown-item" href="./carousel.html">
Carousel
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./lists.html">
Lists
</a>
<a class="dropdown-item" href="./typography.html">
Typography
</a>
<a class="dropdown-item" href="./offcanvas.html">
Offcanvas
</a>
<a class="dropdown-item" href="./markdown.html">
Markdown
</a>
<a class="dropdown-item" href="./dropzone.html">
Dropzone
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./lightbox.html">
Lightbox
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./tinymce.html">
TinyMCE
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./inline-player.html">
Inline player
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<div class="dropend">
<a class="dropdown-item dropdown-toggle" href="#sidebar-authentication" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
Authentication
</a>
<div class="dropdown-menu">
<a href="./sign-in.html" class="dropdown-item">
Sign in
</a>
<a href="./sign-in-link.html" class="dropdown-item">
Sign in link
</a>
<a href="./sign-in-illustration.html" class="dropdown-item">
Sign in with illustration
</a>
<a href="./sign-in-cover.html" class="dropdown-item">
Sign in with cover
</a>
<a href="./sign-up.html" class="dropdown-item">
Sign up
</a>
<a href="./forgot-password.html" class="dropdown-item">
Forgot password
</a>
<a href="./terms-of-service.html" class="dropdown-item">
Terms of service
</a>
<a href="./auth-lock.html" class="dropdown-item">
Lock screen
</a>
</div>
</div>
<div class="dropend">
<a class="dropdown-item dropdown-toggle" href="#sidebar-error" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
<!-- Download SVG icon from http://tabler-icons.io/i/file-minus -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-inline me-1" 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 d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" /><path d="M9 14l6 0" /></svg>
Error pages
</a>
<div class="dropdown-menu">
<a href="./error-404.html" class="dropdown-item">
404 page
</a>
<a href="./error-500.html" class="dropdown-item">
500 page
</a>
<a href="./error-maintenance.html" class="dropdown-item">
Maintenance page
</a>
</div>
</div>
</div>
</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="./form-elements.html" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/checkbox -->
<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 d="M9 11l3 3l8 -8" /><path d="M20 12v6a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h9" /></svg>
</span>
<span class="nav-link-title">
Form elements
</span>
</a>
</li>
<li class="nav-item active dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-extra" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/star -->
<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 d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
</span>
<span class="nav-link-title">
Extra
</span>
</a>
<div class="dropdown-menu">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
<a class="dropdown-item" href="./empty.html">
Empty page
</a>
<a class="dropdown-item" href="./cookie-banner.html">
Cookie banner
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./activity.html">
Activity
</a>
<a class="dropdown-item" href="./gallery.html">
Gallery
</a>
<a class="dropdown-item" href="./invoice.html">
Invoice
</a>
<a class="dropdown-item" href="./search-results.html">
Search results
</a>
<a class="dropdown-item" href="./pricing.html">
Pricing cards
</a>
<a class="dropdown-item" href="./pricing-table.html">
Pricing table
</a>
<a class="dropdown-item" href="./faq.html">
FAQ
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./users.html">
Users
</a>
<a class="dropdown-item" href="./license.html">
License
</a>
</div>
<div class="dropdown-menu-column">
<a class="dropdown-item active" href="./logs.html">
Logs
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./music.html">
Music
</a>
<a class="dropdown-item" href="./photogrid.html">
Photogrid
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./tasks.html">
Tasks
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./uptime.html">
Uptime monitor
</a>
<a class="dropdown-item" href="./widgets.html">
Widgets
</a>
<a class="dropdown-item" href="./wizard.html">
Wizard
</a>
<a class="dropdown-item" href="./settings.html">
Settings
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./trial-ended.html">
Trial ended
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./job-listing.html">
Job listing
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./page-loader.html">
Page loader
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
</div>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-layout" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/layout-2 -->
<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 d="M4 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M4 13m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M14 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M14 15m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /></svg>
</span>
<span class="nav-link-title">
Layout
</span>
</a>
<div class="dropdown-menu">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
<a class="dropdown-item" href="./layout-horizontal.html">
Horizontal
</a>
<a class="dropdown-item" href="./layout-boxed.html">
Boxed
<span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>
</a>
<a class="dropdown-item" href="./layout-vertical.html">
Vertical
</a>
<a class="dropdown-item" href="./layout-vertical-transparent.html">
Vertical transparent
</a>
<a class="dropdown-item" href="./layout-vertical-right.html">
Right vertical
</a>
<a class="dropdown-item" href="./layout-condensed.html">
Condensed
</a>
<a class="dropdown-item" href="./layout-combo.html">
Combined
</a>
</div>
<div class="dropdown-menu-column">
<a class="dropdown-item" href="./layout-navbar-dark.html">
Navbar dark
</a>
<a class="dropdown-item" href="./layout-navbar-sticky.html">
Navbar sticky
</a>
<a class="dropdown-item" href="./layout-navbar-overlap.html">
Navbar overlap
</a>
<a class="dropdown-item" href="./layout-rtl.html">
RTL mode
</a>
<a class="dropdown-item" href="./layout-fluid.html">
Fluid
</a>
<a class="dropdown-item" href="./layout-fluid-vertical.html">
Fluid vertical
</a>
</div>
</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="./icons.html" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/ghost -->
<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 d="M5 11a7 7 0 0 1 14 0v7a1.78 1.78 0 0 1 -3.1 1.4a1.65 1.65 0 0 0 -2.6 0a1.65 1.65 0 0 1 -2.6 0a1.65 1.65 0 0 0 -2.6 0a1.78 1.78 0 0 1 -3.1 -1.4v-7" /><path d="M10 10l.01 0" /><path d="M14 10l.01 0" /><path d="M10 14a3.5 3.5 0 0 0 4 0" /></svg>
</span>
<span class="nav-link-title">
4158 icons
</span>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-help" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/lifebuoy -->
<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 d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M15 15l3.35 3.35" /><path d="M9 15l-3.35 3.35" /><path d="M5.65 5.65l3.35 3.35" /><path d="M18.35 5.65l-3.35 3.35" /></svg>
</span>
<span class="nav-link-title">
Help
</span>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="https://tabler.io/docs" target="_blank" rel="noopener">
Documentation
</a>
<a class="dropdown-item" href="./changelog.html">
Changelog
</a>
<a class="dropdown-item" href="https://github.com/tabler/tabler" target="_blank" rel="noopener">
Source code
</a>
<a class="dropdown-item text-pink" href="https://github.com/sponsors/codecalm" target="_blank" rel="noopener">
<!-- Download SVG icon from http://tabler-icons.io/i/heart -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-inline me-1" 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 d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>
Sponsor project!
</a>
</div>
</li>
</ul>
<div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">
<form action="./" method="get" autocomplete="off" novalidate>
<div class="input-icon">
<span class="input-icon-addon">
<!-- Download SVG icon from http://tabler-icons.io/i/search -->
<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 d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /><path d="M21 21l-6 -6" /></svg>
</span>
<input type="text" value="" class="form-control" placeholder="Search…" aria-label="Search in website">
</div>
</form>
</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>
</header>
<div class="page-wrapper">
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
Logs
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="card-body">
<h4>
Checked URL
</h4>
<div>
<pre><code>GET <a class="text-reset" target="_blank" href="https://preview.tabler.io">https://preview.tabler.io</a></code></pre>
</div>
<h4>Request Timing</h4>
<div>
<pre>Effective URL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a class="text-reset" target="_blank" href="https://preview.tabler.io">https://preview.tabler.io</a><br>Redirect count&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0<br>Name lookup time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3.4e-05<br>Connect time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.000521<br>Pre-transfer time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0<br>Start-transfer time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0<br>App connect time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0<br>Redirect time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0<br>Total time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;28.000601<br>Response code&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0<br>Return keyword&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;operation_timedout</pre>
</div>
<h4>Response Headers</h4>
<div>
<pre>HTTP/1.1 200 Connection established</pre>
</div>
</div>
<div class="card-footer">
<h4>Escalation</h4>
<div>Entire team</div>
</div>
</div>
</div>
</div>
<footer class="footer footer-transparent d-print-none">
<div class="container-xl">
<div class="row text-center align-items-center flex-row-reverse">
<div class="col-lg-auto ms-lg-auto">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item"><a href="https://tabler.io/docs" target="_blank" class="link-secondary" rel="noopener">Documentation</a></li>
<li class="list-inline-item"><a href="./license.html" class="link-secondary">License</a></li>
<li class="list-inline-item"><a href="https://github.com/tabler/tabler" target="_blank" class="link-secondary" rel="noopener">Source code</a></li>
<li class="list-inline-item">
<a href="https://github.com/sponsors/codecalm" target="_blank" class="link-secondary" rel="noopener">
<!-- Download SVG icon from http://tabler-icons.io/i/heart -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-pink icon-filled icon-inline" 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 d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>
Sponsor
</a>
</li>
</ul>
</div>
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item">
Copyright &copy; 2023
<a href="." class="link-secondary">Tabler</a>.
All rights reserved.
</li>
<li class="list-inline-item">
<a href="./changelog.html" class="link-secondary" rel="noopener">
v1.0.0-beta19
</a>
</li>
</ul>
</div>
</div>
</div>
</footer>
</div>
</div>
<script>
// 切换指标时,给重点卡片一个轻微高亮动画
const card = document.getElementById('cardPriority');
const select = document.getElementById('metricSelect');
select.addEventListener('change', () => {
card.classList.add('flash');
setTimeout(() => card.classList.remove('flash'), 900);
});
// 下一步按钮:如果未选择则聚焦并闪烁
document.getElementById('btnNext').addEventListener('click', () => {
if (!select.value) {
select.focus();
card.classList.add('flash');
setTimeout(() => card.classList.remove('flash'), 900);
} else {
// 这里写你的跳转或展开逻辑
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
}
});
// 增加评价:简单追加一行示例
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>&gt;=</option><option>&gt;</option><option>=</option>
<option>&lt;=</option><option>&lt;</option>
</select>
</td>
<td><input class="form-control form-control-sm" placeholder="下限"/></td>
<td>
<select class="form-select form-select-sm">
<option>&lt;=</option><option>&lt;</option><option>=</option>
<option>&gt;=</option><option>&gt;</option>
</select>
</td>
<td><input class="form-control form-control-sm" placeholder="上限"/></td>
`;
body.appendChild(tr);
});
</script>
<!-- Libs JS -->
<!-- Tabler Core -->
<script src="./dist/js/tabler.min.js?1684106062" defer></script>
<script src="./dist/js/demo.min.js?1684106062" defer></script>
</body>
</html>
</html>

View File

@ -24,4 +24,5 @@ public class Application {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,40 @@
package com.hshh;
import com.hshh.system.common.threadpool.ThreadPools;
import com.hshh.thread.HandleCmdThread;
import java.util.concurrent.ExecutorService;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 系统初始化类.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Component
@Slf4j
public class Init {
private static final ExecutorService cpu = ThreadPools.newCpuBoundPool("cpu-pool");
private static final int evaluationThreadNum = 5;
/**
* 初始化系统相关函数.
*/
@PostConstruct
public void init() {
startEvaluationThread();
}
private void startEvaluationThread() {
try {
for (int i = 0; i < evaluationThreadNum; i++) {
cpu.execute(new Thread(new HandleCmdThread()));
}
} catch (Exception e) {
log.error("error::", e);
}
}
}

View File

@ -84,7 +84,7 @@ public class DataController extends BaseController {
if (!modelDefineList.isEmpty()) {
List<FormFieldConfig> formConfigList = formFieldConfigService.getFormFieldConfigByModelId(
modelDefineList.get(0).getId()); //查询模型表头
(request.getId() == null ? modelDefineList.get(0).getId() : request.getId())); //查询模型表头
sortFieldList(formConfigList); //排序
Map<String, String> headerMap = new LinkedHashMap<>(); //定义表头兑现
formConfigList.forEach(formFieldConfig -> {
@ -131,10 +131,13 @@ public class DataController extends BaseController {
model.addAttribute("currentModelDefine", activeModelDefine.get());
} else {
//设置默认第一个选中
modelDefineList.get(0).setChecked(true);
//设置查询参数为第一个
request.setId(modelDefineList.get(0).getId());
model.addAttribute("currentModelDefine", modelDefineList.get(0));
if (!modelDefineList.isEmpty()) {
modelDefineList.get(0).setChecked(true);
//设置查询参数为第一个
request.setId(modelDefineList.get(0).getId());
model.addAttribute("currentModelDefine", modelDefineList.get(0));
}
}
}

View File

@ -0,0 +1,20 @@
package com.hshh.evaluation.bean;
import java.io.Serializable;
import lombok.Data;
/**
* 描述请求数据到类型类.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class DatasourceRequest implements Serializable {
private static final long serialVersionUID = 1L;
//数据源类型 数据库||Csv文件
private String datasourceType;
//工程ID
private Integer projectId;
}

View File

@ -0,0 +1,28 @@
package com.hshh.evaluation.bean;
import com.hshh.indicator.entity.IndicatorEvalItem;
import java.util.List;
import lombok.Data;
/**
* 描述评估字段对应的信息包含权重和评价集.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class EvalItemToFieldInfo {
/**
* 字段名称.
*/
private String name;
/**
* 权重.
*/
private double weight;
/**
* 评价集列表.
*/
private List<IndicatorEvalItem> evalItemList;
}

View File

@ -0,0 +1,42 @@
package com.hshh.evaluation.bean;
import lombok.Data;
/**
* 描述评估请求对象.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class EvaluationRequest {
/**
* database | csv.
*/
private String datasourceType;
/**
* 基础设施ID.
*/
private Integer modelId;
/**
* 工程ID.
*/
private Integer projectId;
/**
* 查询关键子.
*/
private String search;
/**
* 模板ID.
*/
private Integer templateId;
/**
* 随机字符.
*/
private String randomKey;
/**
* 计算方式
*/
private String method;
}

View File

@ -1,9 +1,15 @@
package com.hshh.evaluation.controller;
import com.hshh.evaluation.bean.EvaluationRequest;
import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.evaluation.entity.EvaluationTemplate;
import com.hshh.evaluation.service.EvaluationProjectService;
import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.model.entity.FormValue;
import com.hshh.model.service.FormFieldConfigService;
import com.hshh.model.service.FormValueService;
import com.hshh.system.base.entity.TableRelations;
import com.hshh.system.base.service.TableRelationsService;
import com.hshh.system.common.bean.BaseController;
@ -13,7 +19,9 @@ import com.hshh.system.common.enums.ErrorCode;
import com.hshh.system.common.enums.ErrorMessage;
import io.swagger.v3.oas.annotations.Operation;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
@ -38,17 +46,36 @@ public class EvaluationProjectController extends BaseController {
@Resource
private EvaluationProjectService evaluationProjectService;
/**
* 模板服务类.
*/
@Resource
private EvaluationTemplateService evaluationTemplateService;
/**
* 数据库引用关系记录服务类.
*/
@Resource
private TableRelationsService tableRelationsService;
/**
* 基础设施服务类.
*/
@Resource
private FormFieldConfigService formFieldConfigService;
/**
* 数据查询服务类.
*/
@Resource
private FormValueService formValueService;
/**
* 指标服务类.
*/
@Resource
private IndicatorService indicatorService;
/**
* 默认页.
*
@ -120,8 +147,7 @@ public class EvaluationProjectController extends BaseController {
@ResponseBody
@GetMapping("/remove/{id}")
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
List<TableRelations> reList = tableRelationsService.queryRel(id,
"m_data_evaluation_project");
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());
@ -130,4 +156,106 @@ public class EvaluationProjectController extends BaseController {
return OperateResult.success();
}
/**
* 选择数据源类型.
*
* @param projectId 工程ID
* @param model session容器
* @return project/datasource_select.html
*/
@GetMapping("/selectDatasourceType")
public String selectDatasourceType(Integer projectId, String randomKey, Model model) {
model.addAttribute("projectId", projectId);
model.addAttribute("randomKey", randomKey);
return "project/datasource_select";
}
/**
* 导航到数据库记录展现界面.
*
* @param request 查询请求
* @param model session容器
* @return project/datasource_db.html
*/
@GetMapping("/database")
public String databasePage(PaginationBean request, Model model) {
//获取工程
//获取基础设施ID
Long modelId = evaluationProjectService.selectModelIdByProjectId(request.getId());
//设置数据源页面中的隐藏属性
addDatasourceModelAttribute(modelId, request, model);
//获取表头
Map<String, String> headerMap = formFieldConfigService.getHeaderMap(modelId.intValue());
model.addAttribute("headerMap", headerMap);
//查询数据
//替换为modelId查询
request.setId(modelId.intValue());
List<FormValue> formDataList = formValueService.list(request);
List<Map<String, Object>> listMap = new ArrayList<>();
formDataList.forEach(formData -> {
listMap.add(formData.toMap()); //转为map对象放入listMap中
});
Long total = formValueService.count(request);
setPaginationInfo(request, listMap, total, model);
return "project/datasource_db";
}
private void addDatasourceModelAttribute(Long modelId, PaginationBean request, Model model) {
EvaluationProject project = evaluationProjectService.getById(request.getId());
if (project != null) {
model.addAttribute("templateId", project.getTemplateId());
}
//缓存modelId,projectId
model.addAttribute("modelId", modelId);
model.addAttribute("projectId", request.getId());
model.addAttribute("search", request.getSearch());
model.addAttribute("randomKey", request.getRandomKey());
model.addAttribute("method", request.getMethod());
}
/**
* 导航到csv记录呈现界面.
*
* @param request 查询请求
* @param model session容器
* @return project/datasource_csv.html
*/
@GetMapping("/csv")
public String csvPage(PaginationBean request, Model model) {
return "project/datasource_csv";
}
/**
* 导航的数据评估执行页面.
*
* @param request 评估请求
* @param model session容器
* @return 评估处理页面
*/
@PostMapping("/processEvaluation")
public String processEvaluationPage(@RequestBody EvaluationRequest request, Model model) {
addProcessEvaluationModelAttribute(request, model);
return "project/project_evaluation_process";
}
private void addProcessEvaluationModelAttribute(EvaluationRequest request, Model model) {
//获取工程名称
EvaluationProject project = evaluationProjectService.getById(request.getProjectId());
if (project != null) {
model.addAttribute("projectName", project.getProjectName());
}
Indicator indicator = indicatorService.selectByTemplateId(
request.getTemplateId());
//获取指标名称
if (indicator != null) {
model.addAttribute("indicatorName", indicator.getName());
}
//设置数据源类型
model.addAttribute("datasourceName",
(request.getDatasourceType().equals("database") ? "数据库" : "CSV文件"));
}
}

View File

@ -8,6 +8,7 @@ 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.EvaluationTemplateIndicatorWeightService;
import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
import com.hshh.indicator.entity.Indicator;
@ -74,10 +75,16 @@ public class EvaluationTemplateController extends AssistantTemplateController {
private TableRelationsService tableRelationsService;
/**
* 权重服务.
* 权重服务详情.
*/
@Resource
private EvaluationTemplateWeightService evaluationTemplateWeightService;
private EvaluationTemplateWeightService evaluationTemplateDetailWeightService;
/**
* 指标权重类.
*/
@Resource
private EvaluationTemplateIndicatorWeightService evaluationTemplateIndicatorWeightService;
/**
* 默认页.
@ -111,16 +118,40 @@ public class EvaluationTemplateController extends AssistantTemplateController {
/**
* 获取指标树.
*
* @param id 项目ID
* @param id 指标ID
* @return 指标树
*/
@GetMapping("/metricTree/{id}")
@GetMapping("/metricTree/{id}/{templateId}")
@ResponseBody
public OperateResult<List<JsTree>> metricTree(@PathVariable("id") Integer id, Model model) {
return OperateResult.success(indicatorService.metricTree(id));
public OperateResult<List<JsTree>> metricTree(@PathVariable("id") Integer id,
@PathVariable("templateId") Integer templateId) {
if (id == null) {
return OperateResult.success(new ArrayList<>());
}
//查询指标权重树
Map<Integer, Double> weightMap = evaluationTemplateIndicatorWeightService.getEvaluationTemplateIndicatorWeightMap(
id, templateId);
Map<Integer, List<EvaluationTemplateWeight>> weightDetailMap = evaluationTemplateDetailWeightService.groupByParentIndicatorId(
id, templateId);
List<JsTree> jsTreeList = indicatorService.metricTree(id);
//开始给指标设置权重指标名称后挂在权重信息;并在data属性中增加子节点-子节点的权重信息
setWeightToTree(jsTreeList, weightMap, weightDetailMap);
return OperateResult.success(jsTreeList);
}
private void setWeightToTree(List<JsTree> jsTreeList, Map<Integer, Double> weightMap,
Map<Integer, List<EvaluationTemplateWeight>> weightDetailMap) {
jsTreeList.forEach(tree -> {
if (weightMap.containsKey((int) tree.getOriginalId())) {
tree.getData().put("weight", weightMap.get((int) tree.getOriginalId()));
}
if(weightDetailMap.containsKey((int) tree.getOriginalId())) {
tree.getData().put("weightDetail", weightDetailMap.get((int) tree.getOriginalId()));
}
setWeightToTree(tree.getChildren(), weightMap, weightDetailMap);
});
}
/**
* 保存模板. 必须设置指标设置完成权重才成成功提交
@ -207,157 +238,7 @@ public class EvaluationTemplateController extends AssistantTemplateController {
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;
}
/**
* 计算指标当设置变化时触发.

View File

@ -8,13 +8,13 @@ import lombok.Data;
/**
* <p>
* 模板指标权重
* 模板指标权重详情.
* </p>
*
* @author liDongYu
* @since 2025-08-14
*/
@TableName("m_data_evaluation_template_weight")
@TableName("m_data_evaluation_template_weight_detail")
@Data
public class EvaluationTemplateWeight implements Serializable {

View File

@ -28,4 +28,12 @@ public interface EvaluationProjectMapper extends BaseMapper<EvaluationProject> {
* @return 总条数
*/
Long count(PaginationBean paginationBean);
/**
* 根据工程ID获取到对应的基础设施ID.
*
* @param projectId 工程ID
* @return 基础设施ID
*/
Long selectModelIdByProjectId(Integer projectId);
}

View File

@ -28,4 +28,6 @@ public interface EvaluationTemplateMapper extends BaseMapper<EvaluationTemplate>
* @return 总数
*/
Long count(PaginationBean search);
}

View File

@ -1,6 +1,7 @@
package com.hshh.evaluation.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.bean.EvaluationRequest;
import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.system.common.bean.PaginationBean;
import java.util.List;
@ -43,4 +44,20 @@ public interface EvaluationProjectService extends IService<EvaluationProject> {
* @param evaluationProject 工程数据
*/
void saveWhole(EvaluationProject evaluationProject);
/**
* 根据工程ID获取到对应的基础设施ID.
*
* @param projectId 工程ID
* @return 基础设施ID
*/
Long selectModelIdByProjectId(Integer projectId);
/**
* 评估动作.
*
* @param request 评估请求
* @param userId 用户ID
*/
void evaluate(EvaluationRequest request, Integer userId);
}

View File

@ -2,7 +2,6 @@ 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;
@ -48,5 +47,13 @@ public interface EvaluationTemplateService extends IService<EvaluationTemplate>
* @return 模板列表
*/
List<EvaluationTemplate> queryByName(String name);
/**
* 删除模板(事务删除).
*
* @param evaluationTemplate 模板数据
*/
void deleteTemplate(EvaluationTemplate evaluationTemplate);
}

View File

@ -3,6 +3,7 @@ package com.hshh.evaluation.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import java.util.List;
import java.util.Map;
/**
* 模板指标权重表 服务类.
@ -40,4 +41,14 @@ public interface EvaluationTemplateWeightService extends IService<EvaluationTemp
*/
List<EvaluationTemplateWeight> queryListByIndicatorParentIdAndTemplateId(
Integer indicatorParentId, Integer templateId);
/**
* 指标对应的权重详情按照父指标ID分类.
*
* @param indicatorParentId 父指标ID
* @param templateId 模板ID
* @return 权重map信息
*/
Map<Integer, List<EvaluationTemplateWeight>> groupByParentIndicatorId(Integer indicatorParentId,
Integer templateId);
}

View File

@ -2,14 +2,27 @@ 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.EvaluationRequest;
import com.hshh.evaluation.entity.EvaluationHistory;
import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.evaluation.entity.EvaluationTemplate;
import com.hshh.evaluation.mapper.EvaluationProjectMapper;
import com.hshh.evaluation.service.EvaluationHistoryService;
import com.hshh.evaluation.service.EvaluationProjectService;
import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.model.entity.FormValue;
import com.hshh.model.service.FormValueService;
import com.hshh.system.base.service.TableRelationsService;
import com.hshh.system.common.bean.PaginationBean;
import com.hshh.system.ws.WsEvaluationServer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -20,15 +33,33 @@ import org.springframework.transaction.annotation.Transactional;
* @since 2025-08-11
*/
@Service
@Slf4j
public class EvaluationProjectServiceImpl extends
ServiceImpl<EvaluationProjectMapper, EvaluationProject> implements
EvaluationProjectService {
ServiceImpl<EvaluationProjectMapper, EvaluationProject> implements EvaluationProjectService {
private static final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//每次评估一批数据的长度
private static final int pageSize = 100;
/**
* 数据库引用关系记录服务类.
*/
@Resource
private TableRelationsService tableRelationsService;
/**
* 评估历史服务类.
*/
@Resource
private EvaluationHistoryService evaluationHistoryService;
/**
* 评估模板服务类.
*/
@Resource
private EvaluationTemplateService evaluationTemplateService;
/**
* 数据查询服务类.
*/
@Resource
private FormValueService formValueService;
@Override
public List<EvaluationProject> list(PaginationBean paginationBean) {
@ -58,4 +89,139 @@ public class EvaluationProjectServiceImpl extends
tableRelationsService.addRel(evaluationProject.getId(), "m_data_evaluation_project", relList,
"m_data_evaluation_template");
}
@Override
public Long selectModelIdByProjectId(Integer projectId) {
return this.baseMapper.selectModelIdByProjectId(projectId);
}
/**
* 评估操作.
*
* @param request 评估请求
* @param userId 用户ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void evaluate(EvaluationRequest request, Integer userId) {
//查询模板然后获取指标ID
EvaluationTemplate template = evaluationTemplateService.getById(request.getTemplateId());
//发送开始处理消息
sendTips("开始处理", request.getRandomKey());
//插入评估历史表
Integer historyId = insertEvaluationHistory(request, userId);
//处理来自于数据库的记录
if (request.getDatasourceType().equals("database")) {
handleDataBaseData(request, template.getIndicatorTopId(), historyId, userId);
}
//处理来自于 csv导入的记录
if (request.getDatasourceType().equals("csv")) {
handleCsvData(request, template.getIndicatorTopId(), historyId, userId);
}
}
//根据searchKey查询
/**
* 处理数据库的记录.
*
* @param request 评估请求.
* @param indicatorTopId 指标ID
* @param historyId 历史记录ID
* @param userId 用户ID
*/
private void handleDataBaseData(EvaluationRequest request, Integer indicatorTopId,
Integer historyId, Integer userId) {
sendTips("数据来源:数据库记录", request.getRandomKey());
AtomicInteger count = new AtomicInteger(0);
//查询记录总条数
PaginationBean pageBean = new PaginationBean();
pageBean.setId(request.getModelId());
pageBean.setSearch(request.getSearch());
Long total = formValueService.count(pageBean);
sendTips("数据记录条数" + total, request.getRandomKey());
int totalPage = (int) Math.ceil(total / (double) pageSize);
//开始循坏评估
for (int i = 1; i <= totalPage; i++) {
pageBean.setCurrentPage(1);
pageBean.setPageSize(pageSize);
pageBean.setSearch(request.getSearch());
//查询
List<FormValue> originalList = formValueService.list(pageBean);
count.addAndGet(originalList.size());
//把表单数据映射为一个列表
List<String> dataList = originalList.stream()
.map(FormValue::getModelData).collect(Collectors.toList());
evaluationOneBatchData(dataList, request, indicatorTopId, historyId, userId);
sendTips(originalList.size() + "数据评估完成,累计完成(" + count.get() + ")",
request.getRandomKey());
}
sendTips("数据全部评估完成,累计评估(" + count.get() + ")", request.getRandomKey());
}
/**
* 处理CSV的记录.
*
* @param request 评估请求.
* @param indicatorTopId 指标ID
* @param historyId 历史记录ID
* @param userId 用户ID
*/
private void handleCsvData(EvaluationRequest request, Integer indicatorTopId, Integer historyId,
Integer userId) {
}
/**
* 评估一批数据.
*
* @param data 待评估数据
* @param request 请求信息
* @param indicatorTopId 指标ID
* @param historyId 历史记录ID
* @param userId 用户ID
*/
private void evaluationOneBatchData(List<String> data, EvaluationRequest request,
Integer indicatorTopId, Integer historyId, Integer userId) {
//结果集定义
List<String> resultList = new ArrayList<>();
for (String rawData : data) {
}
}
/**
* 插入评估历史表返回ID.
*
* @param request 评估请求
*/
private Integer insertEvaluationHistory(EvaluationRequest request, Integer userId) {
EvaluationHistory history = new EvaluationHistory();
history.setProjectId(request.getProjectId());
history.setCreateTime(LocalDateTime.now());
history.setUserId(userId);
history.setRandomKey(request.getRandomKey());
evaluationHistoryService.save(history);
return history.getId();
}
void sendTips(String message, String randomKey) {
//发送开始处理消息
LocalDateTime start = LocalDateTime.now();
WsEvaluationServer.sendToKey(randomKey, start.format(df) + " " + message);
}
}

View File

@ -92,4 +92,6 @@ public class EvaluationTemplateServiceImpl extends
evaluationTemplate.getId());
this.removeById(evaluationTemplate.getId());
}
}

View File

@ -6,6 +6,8 @@ import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import com.hshh.evaluation.mapper.EvaluationTemplateWeightMapper;
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -51,4 +53,16 @@ public class EvaluationTemplateWeightServiceImpl extends
queryWrapper.eq("indicator_parent_id", indicatorParentId);
return this.list(queryWrapper);
}
@Override
public Map<Integer, List<EvaluationTemplateWeight>> groupByParentIndicatorId(
Integer indicatorParentId, Integer templateId) {
List<EvaluationTemplateWeight> list = this.queryListByIndicatorParentIdAndTemplateId(
indicatorParentId, templateId);
if (!list.isEmpty()) {
return list.stream()
.collect(Collectors.groupingBy(EvaluationTemplateWeight::getIndicatorParentId));
}
return Map.of();
}
}

View File

@ -56,38 +56,36 @@ public class EvaluationController extends BaseController {
if (topIndicatorId != null) {
for (Indicator indicator : rootList) {
if (indicator.getId().equals(topIndicatorId)) {
//回显上次选择的
indicator.setChecked(true);
}
}
} else {
topIndicatorId = rootList.get(0).getId();
rootList.get(0).setChecked(true);
}
}
//查询所有没有孩子的子节点
List<Indicator> indicatorListWithoutChildren = indicatorService.selectNoChildByTopId(
topIndicatorId);
if (!indicatorListWithoutChildren.isEmpty()) {
if (indicatorId != null) {
for (Indicator indicator : indicatorListWithoutChildren) {
if (indicator.getId().equals(indicatorId)) {
indicator.setChecked(true);
if (topIndicatorId != null) {
List<Indicator> indicatorListWithoutChildren = indicatorService.selectNoChildByTopId(
topIndicatorId);
if (!indicatorListWithoutChildren.isEmpty()) {
{
for (Indicator indicator : indicatorListWithoutChildren) {
if (indicator.getId().equals(indicatorId)) {
indicator.setChecked(true);
}
}
}
} else {
indicatorListWithoutChildren.get(0).setChecked(true);
//查询子指标的评价集
List<IndicatorEvalItem> evaluationList = indicatorEvalItemService.queryListByIndicatorId(
(indicatorId == null ? indicatorListWithoutChildren.get(0).getId() : indicatorId)
);
model.addAttribute("evaluationList", evaluationList);
model.addAttribute("indicatorListWithoutChildren", indicatorListWithoutChildren);
}
}
//查询子指标的评价集
List<IndicatorEvalItem> evaluationList = indicatorEvalItemService.queryListByIndicatorId(
indicatorId == null ? (indicatorListWithoutChildren.isEmpty() ? 0
: indicatorListWithoutChildren.get(0).getId()) : indicatorId);
model.addAttribute("evaluationList", evaluationList);
model.addAttribute("rootList", rootList);
model.addAttribute("indicatorListWithoutChildren", indicatorListWithoutChildren);
return "indicator/evaluation_list";
}

View File

@ -121,7 +121,7 @@ public class IndicatorController extends BaseController {
}
}
model.addAttribute("rootList", rootList);
return "/indicator/list";
return "indicator/list";
}
@ -236,14 +236,15 @@ public class IndicatorController extends BaseController {
List<Indicator> rootList = indicatorService.queryRootList();
//设置根指标的选中状态
setChecked(rootList, indicatorTopId, true);
setChecked(rootList, indicatorTopId, false);
// 顶级指标放入session容器
model.addAttribute("rootList", rootList);
// 底层指标放入session容器
model.addAttribute("childrenIndicator", indicatorService.selectNoChildByTopId(
indicatorTopId == null ? (rootList.isEmpty() ? 0 : rootList.get(0).getId())
: indicatorTopId));
if (indicatorTopId != null) {
model.addAttribute("childrenIndicator", indicatorService.selectNoChildByTopId(
indicatorTopId));
}
// form表单和顶级指标对应列表放入session容器
modelForm(model, (indicatorTopId == null ? (rootList.isEmpty() ? 0 : rootList.get(0).getId())

View File

@ -45,5 +45,7 @@ public class Indicator extends CheckedBean {
return this.name;
}
@TableField(exist = false)
private double weight;
}

View File

@ -52,6 +52,7 @@ public class IndicatorEvalItem implements Serializable {
@NotNull(message = "值不能为空")
@Size(max = 10, message = "值不能超过10字符")
private String maxValue;
@NotNull(message = "值不能为空")
private Double grade;
}

View File

@ -19,5 +19,13 @@ public interface IndicatorMapper extends BaseMapper<Indicator> {
* @param topId 祖先指标ID
* @return 指标集
*/
public List<Indicator> selectNoChildByTopId(@Param("topId") Integer topId);
List<Indicator> selectNoChildByTopId(@Param("topId") Integer topId);
/**
* 根据评估模板获取指标.
*
* @param templateId 模板 ID
* @return 指标列表
*/
List<Indicator> selectByTemplateId(@Param("templateId") Integer templateId);
}

View File

@ -1,8 +1,10 @@
package com.hshh.indicator.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.bean.EvalItemToFieldInfo;
import com.hshh.indicator.entity.IndicatorEvalItem;
import java.util.List;
import java.util.Map;
/**
* 指标规则表 服务类.
@ -28,4 +30,20 @@ public interface IndicatorEvalItemService extends IService<IndicatorEvalItem> {
* @return 评价集
*/
List<IndicatorEvalItem> queryListByIndicatorId(Integer indicatorId);
/**
* 查询数据类型为从数据库读取的字段评价集及权重.
*
* @param indicatorTopId 指标顶级ID
* @return 对应map key为字段名称
*/
Map<String, EvalItemToFieldInfo> evalMapForDatabaseTypeData(Integer indicatorTopId);
/**
* 查询数据类型为从csv读取的字段评价集及权重.
*
* @param indicatorTopId 指标顶级ID
* @return 对应map key为字段名称
*/
Map<String, EvalItemToFieldInfo> evalMapForCsvType(Integer indicatorTopId);
}

View File

@ -7,6 +7,7 @@ import com.hshh.indicator.entity.IndicatorCsvColumn;
import com.hshh.system.common.bean.JsTree;
import java.io.IOException;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.springframework.web.multipart.MultipartFile;
/**
@ -99,4 +100,14 @@ public interface IndicatorService extends IService<Indicator> {
* @return 指标列表topId一致
*/
List<Indicator> queryByTopId(Integer topId);
/**
* 根据评估模板获取指标.
*
* @param templateId 模板 ID
* @return 指标列表
*/
Indicator selectByTemplateId(@Param("templateId") Integer templateId);
}

View File

@ -2,10 +2,12 @@ package com.hshh.indicator.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.evaluation.bean.EvalItemToFieldInfo;
import com.hshh.indicator.entity.IndicatorEvalItem;
import com.hshh.indicator.mapper.IndicatorEvalItemMapper;
import com.hshh.indicator.service.IndicatorEvalItemService;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
/**
@ -35,4 +37,19 @@ public class IndicatorEvalItemServiceImpl extends
return this.list(queryWrapper);
}
@Override
public Map<String, EvalItemToFieldInfo> evalMapForDatabaseTypeData(Integer indicatorTopId) {
return Map.of();
}
@Override
public Map<String, EvalItemToFieldInfo> evalMapForCsvType(Integer indicatorTopId) {
return Map.of();
}
}

View File

@ -187,4 +187,15 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
return this.list(queryWrapper);
}
@Override
public Indicator selectByTemplateId(Integer templateId) {
List<Indicator> list = this.baseMapper.selectByTemplateId(templateId);
if (list.isEmpty()) {
return null;
}
return list.get(0);
}
}

View File

@ -99,10 +99,13 @@ public class ModelDefineController extends BaseController {
modelDefine.getId()));
} else {
fieldList.addAll(formFieldConfigService.getFormFieldConfigByModelId(
modelList.get(0).getId()));
if (!modelList.isEmpty()) {
fieldList.addAll(formFieldConfigService.getFormFieldConfigByModelId(
modelList.get(0).getId()));
modelList.get(0).setChecked(true);
}
modelList.get(0).setChecked(true);
}
}

View File

@ -34,12 +34,10 @@ public class FormFieldConfig implements Serializable {
@NotBlank(message = "字段名称不能为空")
@Size(max = 50, message = "字段名称不能超过50字符")
@Pattern(regexp = "^[A-Za-z_]+$", message = "字段ID只能包含英文字符")
@Pattern(regexp = "^[A-Za-z_0-9]+$", message = "字段ID只能包含英文和数字字符")
private String fieldName;
@NotBlank(message = "字段ID不能为空")
@Size(max = 50, message = "字段ID不能超过50字符")
@Pattern(regexp = "^[A-Za-z_]+$", message = "字段ID只能包含英文字符")
private String fieldId;
@NotBlank(message = "字段标签不能为空")
@Size(max = 50, message = "字段标签不能超过50字符")

View File

@ -3,6 +3,7 @@ package com.hshh.model.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.model.entity.FormFieldConfig;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotNull;
/**
@ -19,7 +20,7 @@ public interface FormFieldConfigService extends IService<FormFieldConfig> {
* @param modelId 模型ID
* @return 字段列表
*/
public List<FormFieldConfig> getFormFieldConfigByModelId(Integer modelId);
List<FormFieldConfig> getFormFieldConfigByModelId(Integer modelId);
/**
* 查询符合条件的字段列表.
@ -30,6 +31,14 @@ public interface FormFieldConfigService extends IService<FormFieldConfig> {
* @param id id
* @return 结果列表
*/
public List<FormFieldConfig> getFormFieldConfigByLabelOrNameOrId(@NotNull Integer modelId,
List<FormFieldConfig> getFormFieldConfigByLabelOrNameOrId(@NotNull Integer modelId,
@NotNull String label, @NotNull String name, @NotNull String id);
/**
* 获取基础设施的字段信息按照map返回linkedHashMap key=fieldName,value=fieldLabel.
*
* @param modelId 基础设施ID
* @return 字段map
*/
Map<String, String> getHeaderMap(Integer modelId);
}

View File

@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.model.entity.FormFieldConfig;
import com.hshh.model.mapper.FormFieldConfigMapper;
import com.hshh.model.service.FormFieldConfigService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
/**
@ -36,4 +38,14 @@ public class FormFieldConfigServiceImpl extends
.eq("field_id", id));
return this.list(queryWrapper);
}
@Override
public Map<String, String> getHeaderMap(Integer modelId) {
Map<String, String> headerMap = new HashMap<>();
List<FormFieldConfig> list = getFormFieldConfigByModelId(modelId);
list.forEach(formFieldConfig -> {
headerMap.put(formFieldConfig.getFieldName(), formFieldConfig.getFieldLabel());
});
return headerMap;
}
}

View File

@ -0,0 +1,44 @@
package com.hshh.thread;
import com.alibaba.fastjson2.JSON;
import com.hshh.evaluation.bean.EvaluationRequest;
import com.hshh.evaluation.service.impl.EvaluationProjectServiceImpl;
import com.hshh.system.Global;
import com.hshh.system.common.bean.SpringContextHolder;
import com.hshh.system.common.cmd.CmdEnum;
import com.hshh.system.common.cmd.CmdInfo;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
/**
* 处理系统中的任务线程.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Slf4j
public class HandleCmdThread implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) {
try {
CmdInfo cmd = Global.cmdQueue.take();
if (Objects.requireNonNull(cmd.getType()) == CmdEnum.EVALUATION) {
evaluation(cmd);
}
} catch (Exception e) {
log.error("error:", e);
}
}
}
//评估
private void evaluation(CmdInfo cmd) {
SpringContextHolder.getBean(EvaluationProjectServiceImpl.class)
.evaluate(JSON.parseObject(cmd.getCmd(),
EvaluationRequest.class), cmd.getUserId());
}
}

View File

@ -26,7 +26,7 @@ mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
database-id: mysql
database-id: mysql
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

View File

@ -50,4 +50,12 @@
</if>
</where>
</select>
<select id="selectModelIdByProjectId" resultType="java.lang.Long">
SELECT t3.indicator_model_id
FROM m_data_evaluation_project t1
LEFT JOIN m_data_evaluation_template t2 ON t1.template_id = t2.id
LEFT JOIN m_data_indicator_form t3 ON t2.indicator_top_id = t3.indicator_top_id
where t1.id = #{projectId}
and t3.indicator_model_id is not null
</select>
</mapper>

View File

@ -50,4 +50,5 @@
</if>
</where>
</select>
</mapper>

View File

@ -10,4 +10,12 @@
WHERE t2.top_id = #{topId} and t2.parent_id IS NOT NULL)
order by sort_order
</select>
<select id="selectByTemplateId" resultType="com.hshh.indicator.entity.Indicator">
SELECT
t1.*
FROM
m_data_indicator t1
inner join m_data_evaluation_template t2 ON t1.id = t2.indicator_top_id
where t2.id=#{templateId}
</select>
</mapper>

View File

@ -4,7 +4,6 @@
*/
function removeValidCss(formId) {
let form = document.getElementById(formId);
let elements = form.elements;
for (const element of elements) {
@ -63,17 +62,19 @@ function openDialog(id) {
return;
}
}
function disabledForm(formId) {
let form = document.getElementById(formId);
// 只读 input 和 textarea
form.querySelectorAll('input, textarea').forEach(function(el) {
form.querySelectorAll('input, textarea').forEach(function (el) {
el.readOnly = true;
});
// 禁用 select 和 button
form.querySelectorAll('select').forEach(function(el) {
form.querySelectorAll('select').forEach(function (el) {
el.disabled = true;
});
}
/**
* 自动填充表单根据 JSON 数据中的 key 和嵌套对象自动拼接 name 属性进行匹配
* @param {string} formId - 表单的 ID例如 "myForm"
@ -88,8 +89,6 @@ function fillForm(formId, data) {
return;
}
/**
* 递归处理 JSON 对象, 将嵌套对象的 key 拼接为点连接的形式
* @param {Object} obj - 当前处理的 JSON 对象
@ -102,7 +101,6 @@ function fillForm(formId, data) {
var fullKey = prefix ? prefix + "." + key : key;
var value = obj[key];
// 如果值为对象,但不为数组,则递归处理
if (value && typeof value === "object" && !Array.isArray(value)) {
@ -130,7 +128,7 @@ function fillForm(formId, data) {
field.value = value;
}
}else{
} else {
}
}
@ -220,7 +218,7 @@ function common_batchRemove() {
}
//分页相关
//普通静态数据分页相关
function _next() {
let currentPageNum = document.getElementById("_currentPage").value;
_setPage(parseInt(currentPageNum) + 1);
@ -246,6 +244,17 @@ function _resetPageNoSearch() {
_search();
}
//动态数据,动态表头,动态表格 header=map record=list(map)
function _dynamic_next(form) {
}
function _dynamic_pre(form) {
}
function _dynamic_resetPageNoSearch(form){
}
document.addEventListener('DOMContentLoaded', function () {
if (document.getElementById('uploadIcon') && document.getElementById(
'fileInput')) {
@ -352,13 +361,15 @@ 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);
return 'tpl:new:' + Date.now().toString(36) + Math.random().toString(
36).substring(2, 10);
}
function mapToObject(map) {
@ -368,10 +379,33 @@ function mapToObject(map) {
}
return obj;
}
function showFieldErrorTip(formId,fieldId,message){
function showFieldErrorTip(formId, fieldId, message) {
let formElement = document.getElementById(formId);
formElement.elements[fieldId].classList.add(
"is-invalid");
document.getElementById(
fieldId + "_error_tip").innerHTML =message;
fieldId + "_error_tip").innerHTML = message;
}
function connectWs(url, {
onOpen = () => {},
onClose = () => {},
onMessage = (msg) => {},
onError = (err) => {}
} = {}) {
url = wsUrl(url);
const ws = new WebSocket(url);
ws.onopen = (ev) => { onOpen(ev, ws); };
ws.onclose = (ev) => { onClose(ev, ws); };
ws.onmessage = (ev) => { onMessage(ev.data, ws); };
ws.onerror = (err) => { onError(err, ws); };
return ws;
}
function wsUrl(path) {
const loc = window.location; // 当前页面的地址
const scheme = loc.protocol === "https:" ? "wss:" : "ws:";
return scheme + "//" + loc.host + path;
}

View File

@ -3,83 +3,82 @@
<div class="container-xl">
<!-- 面包屑导航 -->
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
<div class="row">
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" data-bs-toggle="tabs" role="tablist">
<li class="nav-item" role="presentation" th:each="item ,stat: ${modelDefineList}">
<a
href="javascript:void(0)"
th:class="'nav-link '+${item.checked?'active':''}"
data-bs-toggle="tab"
th:text="${item.getModelName()}"
th:onclick="|_tabChange('${item.id}')|"></a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="tab-Content">
<div class="tab-pane active show" role="tabpanel">
<div class="row">
<div class="ms-auto text-end">
<a href="javascript:void(0)" class="btn btn-primary"
th:onclick="|addModeDataRecord('${currentModelDefine?.id}')|">
<!-- 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>
<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:each="item:${headerMap}" th:text="${item.value}"></th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${result.getList()}">
<td th:text="${item['seq']}"></td>
<td th:each="entry : ${headerMap}" th:text="${item[entry.key]}"></td>
<td>
<a href="javascript:void(0)"
th:onclick="|editModelFieldRecord('${item['id']}')|">编辑</a>
<a href="javascript:void(0)"
th:onclick="|removeModelFieldRecord('${item['id']}')|">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<div th:replace="fragments/dialog::paginationDialog(${result})"></div>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-8">
<div class="mb-3 row">
<label class="col-2 col-form-label">基础设施</label>
<div class="col-6">
<label>
<select class="form-select" id="modelId" onchange="_tabChange(this.value)">
<option>--请选择基础设施--</option>
<option th:each="item ,stat: ${modelDefineList}" th:text="${item.getModelName()}"
th:value="${item.id}" th:selected="${item.checked}"></option>
</select>
</label>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
<div class="card-actions">
<a href="javascript:void(0)" class="btn btn-primary" onclick="addModeDataRecord()">
<!-- 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:each="item:${headerMap}" th:text="${item.value}"></th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${result?.getList()}">
<td th:text="${item['seq']}"></td>
<td th:each="entry : ${headerMap}" th:text="${item[entry.key]}"></td>
<td>
<a href="javascript:void(0)"
th:onclick="|editModelFieldRecord('${item['id']}')|">编辑</a>
<a href="javascript:void(0)"
th:onclick="|removeModelFieldRecord('${item['id']}')|">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<div th:if="${result!=null}" th:replace="fragments/dialog::paginationDialog(${result})"></div>
</div>
</div>
</div>
</div>
@ -91,12 +90,12 @@
</form>
<form id="dataExtendForm">
<input type="hidden" name="id" id="id">
<input type="hidden" name="modelDefineId" id="modelDefineId" th:value="${currentModelDefine.id}">
<input type="hidden" name="modelDefineId" id="modelDefineId" th:value="${currentModelDefine?.id}">
</form>
<script>
//增加模型数据的实际表单记录
function addModeDataRecord(modelId, data) {
function addModeDataRecord( data) {
let modelId = document.getElementById( "modelId" ).value;
let url = document.getElementById("_rootPath").value + "data/getForm/" + modelId;
let http = new HttpClient();
http.get(url, function (error, res, xhr) {

View File

@ -92,6 +92,29 @@
</div>
</div>
</div>
<div th:fragment="addFullScreenFormDialogNoBtn" id="addFormDialog-no-btn">
<div class="modal modal-blur fade" id="modal-full-width-no-btn" tabindex="-1" role="dialog"
aria-hidden="true" >
<div class="modal-dialog modal-full-width modal-dialog-centered modal-dialog-scrollable"
role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addFormDialog-model-title-no-btn"></h5>
<button type="button" class="btn-close"
onclick="closeDialog('modal-full-width-no-btn')"></button>
</div>
<div class="modal-body" id="add-full-screen-form-modal-body-no-btn" >
</div>
</div>
</div>
</div>
</div>
<!-- form add对话框 简单form操作居中对话框-->
<div th:fragment="addSimpleFormDialog" id="addSimpleFormDialog">
@ -113,12 +136,12 @@
</div>
</div>
<div th:fragment="paginationDialog(data)" id="paginationDialog">
<div class="card-footer d-flex align-items-center" th:if="${data.total>0}">
<div class="card-footer d-flex align-items-center" th:if="${data?.total>0}">
<p class="m-0 text-muted"><span
th:text="${data.total}"></span>
th:text="${data?.total}"></span>
记录</p>
<ul class="pagination m-0 ms-auto">
<li th:class="${data.isHasPrevious()}?'page-item':'page-item disabled'">
<li th:class="${data?.isHasPrevious()}?'page-item':'page-item disabled'">
<a class="page-link" href="#" tabindex="-1" aria-disabled="true"
onclick="_pre()">
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-left -->
@ -158,7 +181,7 @@
<div class="card-body border-bottom py-3">
<form id="searchForm">
<input type="hidden" id="_currentPage" name="currentPage"
th:value="${condition.currentPage}"/>
th:value="${condition?.currentPage}"/>
<div class="card-body border-bottom py-3">
<div class="d-flex">
@ -166,16 +189,16 @@
显示
<div class="mx-2 d-inline-block">
<input type="text" class="form-control form-control-sm"
th:value="${condition.pageSize}" size="3"
th:value="${condition?.pageSize}" size="3"
name="pageSize">
</div>
对象
记录/每页
</div>
<div class="ms-auto text-muted">
<div class="input-group mb-2">
<input type="text" class="form-control" placeholder="名称/编码" id="search"
name="search" th:value="${condition.search}">
name="search" th:value="${condition?.search}">
<button class="btn" type="button" onclick="_resetPageNoSearch()">查询</button>
</div>
</div>
@ -196,4 +219,63 @@
</ol>
</nav>
</div>
<div th:fragment="datasetForListMap(formId,headerMap,result,condition)" id="dataset_list_map_table_page">
<div class="card-body border-bottom py-3">
<div class="d-flex">
<div class="text-muted">
显示
<div class="mx-2 d-inline-block">
<input type="text" class="form-control form-control-sm" name="pageSize" th:value="${condition?.pageSize}" size="3" aria-label="Invoices count">
</div>
记录/每页
</div>
<div class="ms-auto text-muted">
<div class="input-group mb-2">
<label>
<input type="text" class="form-control" placeholder="名称/编码"
name="search" th:value="${condition?.search}">
</label>
<button class="btn" type="button" th:attr="data-form-id=${#strings.escapeJavaScript(formId)}" onclick="_dynamic_resetPageNoSearch(this.dataset.formId)">查询</button>
</div>
</div>
</div>
</div>
<div class="table-responsive" style="min-height: 20em;">
<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 d="M6 15l6 -6l6 6" /></svg>
</th>
<th th:each="item:${headerMap}" th:text="${item.value}"></th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${result?.getList()}">
<td th:text="${item['seq']}"></td>
<td th:each="entry : ${headerMap}" th:text="${item[entry.key]}"></td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer d-flex align-items-center">
<ul class="pagination m-0 ms-auto">
<li class="page-item disabled">
<a th:class="${result?.isHasPrevious()}?'page-item':'page-item disabled'" href="javascript:void(0)" tabindex="-1" aria-disabled="true" >
<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 d="M15 6l-6 6l6 6" /></svg>
</a>
</li>
<li class="page-item">
<a th:class="${result?.isHasNext()}?'page-item':'page-item disabled'" href="javascript:void(0)" >
<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 d="M9 6l6 6l-6 6" /></svg>
</a>
</li>
</ul>
</div>
</div>

View File

@ -258,7 +258,7 @@
<div id="alert-area"
style="position: fixed; left: 50%; top: 20px; transform: translateX(-50%); z-index: 9999; width: 400px; max-width: 90vw;">
</div>
<input type="hidden" name="_userId_" id="_userId_" th:value="${user?.id}">
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/tabler.min.js}"></script>
<script th:src="@{/js/htmx.min.js}"></script>
@ -267,6 +267,7 @@
<script th:src="@{/js/common/common.js}"></script>
<script th:src="@{/js/HttpClient.js}"></script>
<script th:src="@{/js/d3.v7.min.js}"></script>
<script>
let d3TreeData = null;
@ -335,8 +336,23 @@
}
}
if(evt.detail.target.querySelector('[data-page="evaluation_list"]')){
if(typeof initEvaluationList === 'function'){
initEvaluationList();
}
}
}
});
document.body.addEventListener("htmx:beforeCleanupElement", (evt) => {
console.log(1);
// during_log 所在的根容器被清理时,关掉 WS
// if (evt.detail.target&&evt.detail.target.querySelector('[data-page="project"]')) {
//
// if (evaluationWs && evaluationWs.readyState === WebSocket.OPEN) evaluationWs.close(1000, "htmx cleanup");
// evaluationWs = null;
// }
}, { once: true });
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",

View File

@ -6,12 +6,17 @@
<div class="mb-3">
<label class="form-label required">名称:</label>
<label class="form-label required" for="evaluationName">名称:</label>
<input type="text" class="form-control" name="evaluationName" id="evaluationName"
placeholder="名称">
<div class="invalid-feedback" id="evaluationName_error_tip"></div>
</div>
<div class="mb-3">
<label class="form-label required" for="grade">分值:</label>
<input type="text" class="form-control" name="grade" id="grade"
placeholder="对应分值(百分值)">
<div class="invalid-feedback" id="grade_error_tip"></div>
</div>
<div class="mb-3">
<label class="form-label required" for="minSymbol">符号:</label>
<select name="minSymbol" id="minSymbol" class="form-select">
@ -26,7 +31,7 @@
<div class="invalid-feedback" id="minSymbol_error_tip"></div>
</div>
<div class="mb-3">
<label class="form-label required">值(下限):</label>
<label class="form-label required" for="minValue">值(下限):</label>
<input type="text" class="form-control" name="minValue" id="minValue"
placeholder="开始值">
<div class="invalid-feedback" id="minValue_error_tip"></div>
@ -45,7 +50,7 @@
<div class="invalid-feedback" id="maxSymbol_error_tip"></div>
</div>
<div class="mb-3">
<label class="form-label required">值(上限):</label>
<label class="form-label required" for="maxValue">值(上限):</label>
<input type="text" class="form-control" name="maxValue" id="maxValue"
placeholder="开始值">
<div class="invalid-feedback" id="maxValue_error_tip"></div>

View File

@ -17,8 +17,9 @@
<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 class="form-select" name="indicationTopId" id="indicationTopId" onchange="changeIndicator()">
<option value="">--请选择指标--</option>
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.name}" th:selected="${item.checked}"></option>
</select>
</div>
<div class="col-auto text-muted small">选定后,下方“评价集设置”将随之变化</div>
@ -105,14 +106,16 @@
<script>
// 交互时给重点卡片一个轻微高亮
(function(){
function initEvaluationList() {
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";
@ -123,7 +126,7 @@
if (data) {
fillData(data);
} else {
let topId = $('input[name="radios-inline"]:checked').val();
let topId =document.getElementById("indicationTopId").value;
let indicationId = $('input[name="radios-inline-indicator-no-child"]:checked').val();
document.getElementById("evaluationForm")['indicatorTopId'].value = topId;
document.getElementById("evaluationForm")['indicatorId'].value = indicationId;
@ -142,7 +145,7 @@
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 topId = document.getElementById("indicationTopId").value;
let subId = $('input[name="radios-inline-indicator-no-child"]:checked').val();
document.getElementById("_evaluation_evaluationList")
.setAttribute("hx-vals", JSON.stringify({topIndicatorId: topId, indicatorId: subId}));
@ -169,10 +172,11 @@
}
function changeIndicator(){
let topId = $('input[name="radios-inline"]:checked').val();
let topId =document.getElementById("indicationTopId").value;
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>

View File

@ -42,7 +42,7 @@
<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">
<select id="indicationId" class="form-select" name="indicationId" onchange="indicatorTopIdChange()">
<option value="0">-选择根指标-</option>
<option th:each="item:${rootList}" th:value="${item.id}"
th:text="${item.getName()}" th:selected="${item.checked}"></option>
@ -116,9 +116,9 @@
</thead>
<tbody class="autonum">
<tr th:each="item:${childrenIndicator}">
<tr th:each="item,stat:${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-1 w-compact col-index" th:text="${stat.count}"></td>
<td class="sticky-body-col-2 col-name" th:text="${item.getName()}"></td>
<td>
@ -261,4 +261,10 @@
hero.classList.add('flash');
setTimeout(()=>hero.classList.remove('flash'), 900);
}
function indicatorTopIdChange() {
let topId = $('#indicationId').val();
document.getElementById("_indicator_mapper")
.setAttribute("hx-vals", JSON.stringify({indicatorTopId: topId,}));
document.getElementById("_indicator_mapper").click();
}
</script>

View File

@ -123,7 +123,17 @@
display: flex;
gap: 10px;
}
.card-body.search-bar {
border-radius: 0.5rem 0.5rem 0 0;
border-bottom: 1px solid #e9ecef;
background: #f6f8fb; /* 可选 */
padding-bottom: 0.5rem;
margin-bottom: 0;
}
.card-body.list-area {
border-radius: 0 0 0.5rem 0.5rem;
padding-top: 0.5rem;
}
</style>
<div class="page-body" data-page="metric">
@ -146,7 +156,22 @@
增加根指标
</a>
</div>
<div class="card-body" id="type_list">
<div class="card-body search-bar" >
<div class="input-icon ">
<input type="text" value="" class="form-control" placeholder="Search…">
<span class="input-icon-addon">
<!-- Download SVG icon from http://tabler-icons.io/i/search -->
<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="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path><path
d="M21 21l-6 -6"></path></svg>
</span>
</div>
</div>
<div class="card-body" id="type_list" style="max-height: 50em; overflow-y: scroll;">
<div class="d-flex align-items-center mb-2" th:each="item, iterStat : ${rootList}"
th:id="${item.id+'_indicator'}">
@ -190,11 +215,13 @@
</div>
</div>
</div>
<div class="col-md-9" id="tree-container" style="padding-left:0; min-height: 50em ;max-height: 60em ">
<div class="col-md-9" id="tree-container"
style="padding-left:0; min-height: 60em ;max-height: 60em ">
<div class="tree-toolbar" id="tree-toolbar">
<button id="zoom-in" title="放大" class="btn btn-sm btn-outline-primary">+</button>
<button id="zoom-out" title="缩小" class="btn btn-sm btn-outline-primary">-</button>
<button id="zoom-reset" title="重置" class="btn btn-sm btn-outline-secondary">重置</button>
<button id="zoom-reset" title="重置" class="btn btn-sm btn-outline-secondary">重置
</button>
</div>
</div>
</div>
@ -206,11 +233,11 @@
</form>
<script>
//编辑根指标
function editTopIndictorData(flag,id) {
function editTopIndictorData(flag, id) {
let url = document.getElementById("_rootPath").value + "indicator/" + id;
let http = new HttpClient();
http.get(url, function (err, res, xhr) {
addIndicator(flag,res.result);
addIndicator(flag, res.result);
}, 'indicatorForm')
}
@ -224,9 +251,10 @@
}
//导航到增加页面
function addIndicator(flag,data) {
function addIndicator(flag, data) {
let url = document.getElementById("_rootPath").value + "indicator/indicatorAdd?rootFlag="+flag;
let url = document.getElementById("_rootPath").value + "indicator/indicatorAdd?rootFlag="
+ flag;
let http = new HttpClient()
http.get(url, function (error, res, xhr) {
document.getElementById("simpleFormBody").innerHTML = res;
@ -264,16 +292,16 @@
}
function handleMenuAction(action, nodeData) {
console.log(nodeData)
if (action === "add") {
if (!nodeData.topId) {//根节点
nodeData.topId = nodeData.id;
}
addIndicator(false,{"parentId": nodeData.id, "topId": nodeData.topId});
addIndicator(false, {"parentId": nodeData.id, "topId": nodeData.topId});
} else if (action === "edit") {
editTopIndictorData(false,nodeData.id);
editTopIndictorData(false, nodeData.id);
} else if (action === "delete") {
removeTopIndictorData(nodeData.id);
@ -312,7 +340,7 @@
const nodeCount = root.descendants().length;
const width = Math.max(800, 120 * nodeCount);
const height = Math.max(400, 130 * (root.height + 1));
const height = Math.max(800, 130 * (root.height + 1));
const marginTop = 60;
const treeLayout = d3.tree()

View File

@ -4,7 +4,7 @@
<input type="hidden" name="dataModelId" id="dataModelId" value="">
<div class="col-md-6 ">
<div class="mb-3">
<label class="form-label required">标签</label>
<label class="form-label required">中文名称</label>
<input type="text" name="fieldLabel" id="fieldLabel" class="form-control" placeholder="标签"
autocomplete="off"
value="">
@ -13,7 +13,7 @@
</div>
<div class="col-md-6 ">
<div class="mb-3">
<label class="form-label required">name</label>
<label class="form-label required">英文名称</label>
<input type="text" name="fieldName" id="fieldName" class="form-control"
placeholder="name 必须是英文" autocomplete="off"
value="">
@ -29,31 +29,31 @@
</select>
</div>
</div>
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">默认值</label>-->
<!-- <input type="text" name="fieldDefaultValue" id="fieldDefaultValue" class="form-control"-->
<!-- placeholder="字段默认值" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldDefaultValue_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">输入提示</label>-->
<!-- <input type="text" name="fieldPlaceholder" id="fieldPlaceholder" class="form-control"-->
<!-- placeholder="输入提示" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldPlaceholder_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">默认值</label>-->
<!-- <input type="text" name="fieldDefaultValue" id="fieldDefaultValue" class="form-control"-->
<!-- placeholder="字段默认值" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldDefaultValue_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">输入提示</label>-->
<!-- <input type="text" name="fieldPlaceholder" id="fieldPlaceholder" class="form-control"-->
<!-- placeholder="输入提示" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldPlaceholder_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<div class="col-md-6 ">
<div class="mb-3">
<label class="form-label">单位</label>
<select name="fieldUnit" id="fieldUnit" class="form-control">
<option value=""></option>
<option th:each="item: ${items}" th:value="${item.nameCn}" th:text="${item.nameCn}">
<label class="form-label" for="fieldUnit">单位</label>
<input type="text" name="fieldUnit" id="fieldUnit" class="form-control"
placeholder="输入单位" autocomplete="off"
value="">
<div class="invalid-feedback" id="fieldUnit_error_tip"></div>
</option>
</select>
</div>
</div>
@ -62,139 +62,139 @@
<label class="form-label">类型</label>
<select name="fieldType" id="fieldType" class="form-control">
<option value="TEXT">单行文本输入</option>
<!-- <option value="TEXTAREA">多行文本输入</option>-->
<!-- <option value="PASSWORD">密码输入</option>-->
<!-- <option value="TEXTAREA">多行文本输入</option>-->
<!-- <option value="PASSWORD">密码输入</option>-->
<option value="NUMBER">数字输入</option>
<!-- <option value="EMAIL">邮箱输入</option>-->
<!-- <option value="SELECT">下拉选择</option>-->
<!-- <option value="RADIO">单选框</option>-->
<!-- <option value="CHECKBOX">多选框</option>-->
<!-- <option value="DATE">日期选择</option>-->
<!-- <option value="DATETIME">日期时间选择</option>-->
<!-- <option value="TIME">时间选择</option>-->
<!-- <option value="FILE">文件上传</option>-->
<!-- <option value="IMAGE">图片上传</option>-->
<!-- <option value="URL">链接输入</option>-->
<!-- <option value="COLOR">颜色选择器</option>-->
<!-- <option value="TEL">电话输入</option>-->
<!-- <option value="HIDDEN">隐藏字段</option>-->
<!-- <option value="EMAIL">邮箱输入</option>-->
<!-- <option value="SELECT">下拉选择</option>-->
<!-- <option value="RADIO">单选框</option>-->
<!-- <option value="CHECKBOX">多选框</option>-->
<!-- <option value="DATE">日期选择</option>-->
<!-- <option value="DATETIME">日期时间选择</option>-->
<!-- <option value="TIME">时间选择</option>-->
<!-- <option value="FILE">文件上传</option>-->
<!-- <option value="IMAGE">图片上传</option>-->
<!-- <option value="URL">链接输入</option>-->
<!-- <option value="COLOR">颜色选择器</option>-->
<!-- <option value="TEL">电话输入</option>-->
<!-- <option value="HIDDEN">隐藏字段</option>-->
</select>
</div>
</div>
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">码表选项</label>-->
<!-- <select name="fieldOptionsId" id="fieldOptionsId" class="form-control">-->
<!-- <option value="0">无</option>-->
<!-- <option th:each="type:${typeList}" th:text="${type.dictTypeName}"-->
<!-- th:value="${type.id}"></option>-->
<!-- </select>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">码表选项</label>-->
<!-- <select name="fieldOptionsId" id="fieldOptionsId" class="form-control">-->
<!-- <option value="0">无</option>-->
<!-- <option th:each="type:${typeList}" th:text="${type.dictTypeName}"-->
<!-- th:value="${type.id}"></option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">只读</label>-->
<!-- <select name="readonlyFlag" id="readonlyFlag" class="form-control">-->
<!-- <option value="0">非只读</option>-->
<!-- <option value="1">只读</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">只读</label>-->
<!-- <select name="readonlyFlag" id="readonlyFlag" class="form-control">-->
<!-- <option value="0">非只读</option>-->
<!-- <option value="1">只读</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">禁用</label>-->
<!-- <select name="disabledFlag" id="disabledFlag" class="form-control">-->
<!-- <option value="0">非禁用</option>-->
<!-- <option value="1">禁用</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">禁用</label>-->
<!-- <select name="disabledFlag" id="disabledFlag" class="form-control">-->
<!-- <option value="0">非禁用</option>-->
<!-- <option value="1">禁用</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">自动完成</label>-->
<!-- <select name="autocompleteFlag" id="autocompleteFlag" class="form-control">-->
<!-- <option value="0">非</option>-->
<!-- <option value="1">是</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">自动完成</label>-->
<!-- <select name="autocompleteFlag" id="autocompleteFlag" class="form-control">-->
<!-- <option value="0">非</option>-->
<!-- <option value="1">是</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">显示</label>-->
<!-- <select name="hiddenFlag" id="hiddenFlag" class="form-control">-->
<!-- <option value="0">显示</option>-->
<!-- <option value="1">不显示</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">显示</label>-->
<!-- <select name="hiddenFlag" id="hiddenFlag" class="form-control">-->
<!-- <option value="0">显示</option>-->
<!-- <option value="1">不显示</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">format函数</label>-->
<!-- <input type="text" name="formatFunc" id="formatFunc" class="form-control"-->
<!-- placeholder="format函数" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="formatFunc_error_tip"></div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">format函数</label>-->
<!-- <input type="text" name="formatFunc" id="formatFunc" class="form-control"-->
<!-- placeholder="format函数" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="formatFunc_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">正则表达式</label>-->
<!-- <input type="text" name="validateRule" id="validateRule" class="form-control"-->
<!-- placeholder="正则表达式" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="validateRule_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">正则表达提示</label>-->
<!-- <input type="text" name="validateRuleMessage" id="validateRuleMessage" class="form-control"-->
<!-- placeholder="正则表达提示" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="validateRuleMessage_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">长度最小值(文本型)</label>-->
<!-- <input type="number" name="fieldMinSize" id="fieldMinSize" class="form-control"-->
<!-- placeholder="字段长度最小值" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMinSize_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">长度最大值(文本型)</label>-->
<!-- <input type="number" name="fieldMaxSize" id="fieldMaxSize" class="form-control"-->
<!-- placeholder="字段长度最大值" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMaxSize_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">最小值(数值型)</label>-->
<!-- <input type="number" name="fieldMinVal" id="fieldMinVal" class="form-control"-->
<!-- placeholder="字段最小值(数值型)" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMinVal_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">最大值(数值型)</label>-->
<!-- <input type="number" name="fieldMaxVal" id="fieldMaxVal" class="form-control"-->
<!-- placeholder="字段最大值(数值型)" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMaxVal_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">正则表达式</label>-->
<!-- <input type="text" name="validateRule" id="validateRule" class="form-control"-->
<!-- placeholder="正则表达式" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="validateRule_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">正则表达提示</label>-->
<!-- <input type="text" name="validateRuleMessage" id="validateRuleMessage" class="form-control"-->
<!-- placeholder="正则表达提示" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="validateRuleMessage_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">长度最小值(文本型)</label>-->
<!-- <input type="number" name="fieldMinSize" id="fieldMinSize" class="form-control"-->
<!-- placeholder="字段长度最小值" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMinSize_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">长度最大值(文本型)</label>-->
<!-- <input type="number" name="fieldMaxSize" id="fieldMaxSize" class="form-control"-->
<!-- placeholder="字段长度最大值" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMaxSize_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">最小值(数值型)</label>-->
<!-- <input type="number" name="fieldMinVal" id="fieldMinVal" class="form-control"-->
<!-- placeholder="字段最小值(数值型)" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMinVal_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-md-6 ">-->
<!-- <div class="mb-3">-->
<!-- <label class="form-label">最大值(数值型)</label>-->
<!-- <input type="number" name="fieldMaxVal" id="fieldMaxVal" class="form-control"-->
<!-- placeholder="字段最大值(数值型)" value="" autocomplete="off">-->
<!-- <div class="invalid-feedback" id="fieldMaxVal_error_tip"></div>-->
<!-- </div>-->
<!-- </div>-->
<div class="col-md-6 ">
<div class="mb-3">
<label class="form-label">显示顺序</label>
<input type="number" name="sortOrder" id="sortOrder" class="form-control"
placeholder="显示顺序" autocomplete="off" value="1">
placeholder="显示顺序" autocomplete="off" value="1">
<div class="invalid-feedback" id="sortOrder_error_tip"></div>
</div>
</div>

View File

@ -1,4 +1,17 @@
<!-- Page body -->
<style>
.card-body.search-bar {
border-radius: 0.5rem 0.5rem 0 0;
border-bottom: 1px solid #e9ecef;
background: #f6f8fb; /* 可选 */
padding-bottom: 0.5rem;
margin-bottom: 0;
}
.card-body.list-area {
border-radius: 0 0 0.5rem 0.5rem;
padding-top: 0.5rem;
}
</style>
<div class="page-body">
<div class="container-xl">
<!-- 面包屑导航 -->
@ -21,7 +34,22 @@
增加设施
</a>
</div>
<div class="card-body" id="type_list">
<div class="card-body search-bar" >
<div class="input-icon ">
<input type="text" value="" class="form-control" placeholder="Search…">
<span class="input-icon-addon">
<!-- Download SVG icon from http://tabler-icons.io/i/search -->
<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="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path><path
d="M21 21l-6 -6"></path></svg>
</span>
</div>
</div>
<div class="card-body" id="type_list" style="max-height: 50em; overflow-y: scroll;">
<div class="d-flex align-items-center mb-2" th:each="item, iterStat : ${modelList}"

View File

@ -0,0 +1,92 @@
<input type="hidden" name="modelId" th:value="${modelId}" />
<input type="hidden" name="projectId" th:value="${projectId}" />
<input type="hidden" name="datasource" value="csv" />
<input type="hidden" name="search" th:value="${search}" />
<input type="hidden" name="randomKey" th:value="${randomKey}" />
<input type="hidden" name="method" th:value="${method}" />
<div class="row align-items-center mt-3 mb-3">
<div class="col-4">
<div class="progress">
<div class="progress-bar" style="width: 66%" role="progressbar" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100" aria-label="66% Complete">
<span class="visually-hidden">66% Complete</span>
</div>
</div>
</div>
<div class="col">
<div class="btn-list justify-content-end">
<a href="javascript:void(0)" class="btn btn-info" onclick="">
上一步
</a>
<a href="javascript:void(0)" class="btn btn-success" onclick="selectedDatasource()">
下一步
</a>
</div>
</div>
</div>
<div class="card" style="min-height: 20em;">
<div class="card-header">
<h4 class="card-title">CSV文件</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<div class="form-label">选择上传文件</div>
<input type="file" class="form-control">
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title"></h3>
</div>
<div class="card-body border-bottom py-3">
<div class="d-flex">
<div class="text-muted">
Show
<div class="mx-2 d-inline-block">
<input type="text" class="form-control form-control-sm" value="8" size="3" aria-label="Invoices count">
</div>
entries
</div>
<div class="ms-auto text-muted">
Search:
<div class="ms-2 d-inline-block">
<input type="text" class="form-control form-control-sm" aria-label="Search invoice">
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table card-table table-vcenter text-nowrap datatable">
<thead>
<tr>
<th class="w-1"><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select all invoices"></th>
<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 d="M6 15l6 -6l6 6" /></svg>
</th>
<th>Invoice Subject</th>
<th>Client</th>
<th>VAT No.</th>
<th>Created</th>
<th>Status</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="card-footer d-flex align-items-center">
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
<input type="hidden" name="modelId" th:value="${modelId}" />
<input type="hidden" name="projectId" th:value="${projectId}" />
<input type="hidden" name="templateId" th:value="${templateId}" />
<input type="hidden" name="datasource" value="database" />
<input type="hidden" name="search" th:value="${search}" />
<input type="hidden" name="randomKey" th:value="${randomKey}" />
<input type="hidden" name="method" th:value="${method}" />
<div class="row align-items-center mt-3 mb-3">
<div class="col-4">
<div class="progress">
<div class="progress-bar" style="width: 66%" role="progressbar" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100" aria-label="66% Complete">
<span class="visually-hidden">66% Complete</span>
</div>
</div>
</div>
<div class="col">
<div class="btn-list justify-content-end">
<a href="javascript:void(0)" class="btn btn-info" onclick="">
上一步
</a>
<a href="javascript:void(0)" class="btn btn-success" onclick="processEvaluation()">
下一步
</a>
</div>
</div>
</div>
<div class="card" style="min-height: 20em;">
<div class="card-header">
<h4 class="card-title">数据库记录</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title"></h3>
</div>
<div th:replace="fragments/dialog::datasetForListMap('stepForm',${headerMap},${result},${condition})"></div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,60 @@
<div class="row align-items-center mt-3 mb-3">
<div class="col-4">
<div class="progress">
<div class="progress-bar" style="width: 33%" role="progressbar" aria-valuenow="33" aria-valuemin="0" aria-valuemax="100" aria-label="33% Complete">
<span class="visually-hidden">33% Complete</span>
</div>
</div>
</div>
<div class="col">
<div class="btn-list justify-content-end">
<a href="javascript:void(0)" class="btn btn-primary" th:onclick="|selectedDatasource(${projectId})|">
下一步
</a>
</div>
</div>
</div>
<input type="hidden" name="projectId" id="projectId" th:value="${projectId}">
<input type="hidden" name="randomKey" id="randomKey" th:value="${randomKey}">
<div class="card" style="min-height: 20em;">
<div class="card-header">
<h4 class="card-title">选择数据源/评价方法</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="mb-3">
<div class="form-label">数据源</div>
<div>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="datasource_type" checked="" value="database">
<span class="form-check-label">数据库</span>
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="datasource_type" value="dataCsv">
<span class="form-check-label">CSV</span>
</label>
</div>
</div>
<div class="mb-3">
<div class="form-label">评价方法</div>
<div>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="aggregationMethod" checked="" value="WEIGHTED_AVERAGE">
<span class="form-check-label">加权平均(∑ wᵢ·sᵢ</span>
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="aggregationMethod" value="FUZZY_AVERAGE">
<span class="form-check-label">模糊平均/模糊综合</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<!-- Page body -->
<div class="page-body">
<div class="page-body" data-page="project">
<div class="container-xl">
<!-- 面包屑导航 -->
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
@ -83,8 +83,8 @@
<div th:replace="fragments/dialog::addSimpleFormDialog"></div>
</form>
<form id="stepForm">
<input type="hidden" id="projectId" name="projectId">
<div th:replace="fragments/dialog::addFullScreenFormDialog"></div>
<div th:replace="fragments/dialog::addFullScreenFormDialogNoBtn"></div>
</form>
<script>
@ -103,7 +103,6 @@
document.getElementById("simpleFormBody").innerHTML = res;
openDialog("simple-form-model");
document.getElementById("SimpleFormDialog_save").onclick = function () {
saveProject();
}
@ -120,8 +119,6 @@
}, "projectForm", "simple-form-model")
}
function _projectDelete(id) {
openDialog("modal-danger");
document.getElementById('delete-confirm-9999').onclick = function () {
@ -134,14 +131,133 @@
};
}
function _projectHistory(id) {
}
function _startEvaluation(id){
function _startEvaluation(id) {
let randomKey = generateDraftKey().replace("tpl:new:", "")
let url = document.getElementById("_rootPath").value
+ "evaluation/project/selectDatasourceType?projectId=" + id + "&randomKey=" + randomKey;
let http = new HttpClient();
http.get(url, function (error, response, xhr) {
openDialog("modal-full-width-no-btn");
document.getElementById("add-full-screen-form-modal-body-no-btn").innerHTML = response;
}, null,)
}
//选择了数据源后触发
function selectedDatasource(projectId) {
//获取选中的数据源
let value = $('input[name="datasource_type"]:checked').val();
let method = $('input[name="aggregationMethod"]:checked').val();
var url = "";
if (value === 'database') {
url = document.getElementById("_rootPath").value + "evaluation/project/database?id="
+ projectId + "&randomKey=" + document.getElementById("stepForm")['randomKey'].value+"&method=" + method;
}
if (value === 'dataCsv') {
url = document.getElementById("_rootPath").value + "evaluation/project/csv?id=" + projectId
+ "&randomKey=" + document.getElementById("stepForm")['randomKey'].value+"&method="+method;
}
let http = new HttpClient();
http.get(url, function (error, response, xhr) {
openDialog("modal-full-width-no-btn");
document.getElementById("add-full-screen-form-modal-body-no-btn").innerHTML = response;
}, null);
}
function processEvaluation() {
const url = document.getElementById("_rootPath").value + "evaluation/project/processEvaluation";
const http = new HttpClient();
const form = document.getElementById("stepForm");
const obj = {
datasourceType: form['datasource'].value,
modelId: form['modelId'].value,
templateId: form['templateId'].value,
projectId: form['projectId'].value,
search: form['search'].value,
randomKey: form['randomKey'].value,
method: form['method'].value,
};
http.post(url, obj, function (error, response, xhr) {
openDialog("modal-full-width-no-btn");
const modal = document.getElementById("modal-full-width-no-btn");
const modalBody = document.getElementById("add-full-screen-form-modal-body-no-btn");
modalBody.innerHTML = response;
// 只绑定一次modal 关闭就关 WS
if (!modal.dataset.wsCleanupBound) {
modal.addEventListener("hidden.bs.modal", () => {
if (window.evaluationWs && window.evaluationWs.readyState === WebSocket.OPEN) {
window.evaluationWs.close(1000, "modal hidden");
}
window.evaluationWs = null;
});
modal.dataset.wsCleanupBound = "true";
}
// 如果之前有连接,先关掉,避免泄漏
if (window.evaluationWs && window.evaluationWs.readyState === WebSocket.OPEN) {
window.evaluationWs.close(1000, "open new evaluation");
}
window.evaluationWs = null;
// 建立 ws 连接
window.evaluationWs = connectWs("/ws/evaluation/" + obj.randomKey, {
onOpen: () => {
let userId = document.getElementById("_userId_").value;
const cmdInfo = {type: "EVALUATION", cmd: obj, userId: userId};
window.evaluationWs.send(JSON.stringify(cmdInfo));
},
onClose: () => {
appendLog("WS closed");
},
onMessage: (ev) => {
// ev 可能是字符串或事件对象(取决于 connectWs 封装)
appendLog(ev);
},
onError: (err) => {
appendLog("WS error");
if (window.evaluationWs && window.evaluationWs.readyState === WebSocket.OPEN) {
window.evaluationWs.close(1002, "client error");
}
}
});
// 页面卸载时也关(防止刷新/跳转遗留)
if (!window._evaluationUnloadBound) {
window.addEventListener("beforeunload", () => {
if (window.evaluationWs && window.evaluationWs.readyState === WebSocket.OPEN) {
window.evaluationWs.close(1001, "page unload");
}
window.evaluationWs = null;
});
window._evaluationUnloadBound = true;
}
}, null, null);
}
// 简单的日志追加(每条独占一行 + 自动滚动)
function appendLog(text) {
const wrap = document.getElementById("during_log");
if (!wrap) {
return;
}
const code = document.createElement("code");
code.textContent = text;
code.style.display = "block"; // 一条一行
wrap.appendChild(code);
wrap.scrollTop = wrap.scrollHeight;
}
</script>

View File

@ -0,0 +1,50 @@
<div class="card">
<div class="card-header">
<div class="card-title">评估输入</div>
</div>
<div class="card-body">
<table class="table table-vcenter card-table">
<tbody>
<tr>
<th>数据来源:</th>
<td th:text="${datasourceName}">
</td>
</tr>
<tr>
<th>工程名称:</th>
<td th:text="${projectName}">
</td>
</tr>
<tr>
<th>指标名称:</th>
<td th:text="${indicatorName}">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">评估过程</div>
</div>
<div class="card-body" style="overflow-y: auto; max-height: 40em;"><pre id="during_log"></pre>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">评估结果</div>
</div>
</div>

View File

@ -165,9 +165,12 @@
// 根据指标ID获取指标树
function metricTree(id) {
let templateId = document.getElementById("templateForm")["id"].value;
if(!templateId||templateId===""){
templateId="0"
}
const url = document.getElementById("_rootPath").value
+ "evaluation/evaluationTemplate/metricTree/" + id;
+ "evaluation/evaluationTemplate/metricTree/" + id+"/" + templateId;
const http = new HttpClient();
http.get(url, function (error, res, xhr) {

View File

@ -0,0 +1,53 @@
package com.hshh;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.model.entity.ModelDefine;
import com.hshh.model.service.ModelDefineService;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
@Slf4j
public class ApplicationTest {
@Resource
private IndicatorService indicatorService;
@Resource
private ModelDefineService modelDefineService;
@Test
public void addIndicator() {
for (int i = 0; i < 100; i++) {
Indicator indicator = new Indicator();
indicator.setName("测试指标_" + i);
indicator.setCode("test_" + i);
indicatorService.save(indicator);
}
}
@Test
public void addModel() {
for (int i = 0; i < 100; i++) {
ModelDefine modelDefine = new ModelDefine();
modelDefine.setModelName("测试设施" + i);
modelDefine.setModelCode("test" + i);
modelDefine.setSortOrder(i);
modelDefineService.save(modelDefine);
}
}
}

View File

@ -75,5 +75,9 @@
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,18 @@
package com.hshh.system;
import com.hshh.system.common.cmd.CmdInfo;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 全局变量类 .
*
* @author LiDongYU
* @since 2025/7/22
*/
public class Global {
/**
* 全局待处理的命令容器.
*/
public static LinkedBlockingQueue<CmdInfo> cmdQueue = new LinkedBlockingQueue<>();
}

View File

@ -58,4 +58,5 @@ public class BaseController {
List<Menus> menuList = menuService.getMenus(href);
model.addAttribute("chainMenuList", menuList);
}
}

View File

@ -1,6 +1,7 @@
package com.hshh.system.common.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import lombok.Data;
/**
@ -10,7 +11,7 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class CheckedBean {
public class CheckedBean implements Serializable {
@TableField(exist = false)
private boolean checked;

View File

@ -2,6 +2,7 @@ package com.hshh.system.common.bean;
import com.hshh.system.reflect.GetterUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
@ -41,7 +42,8 @@ public class JsTree {
*/
private List<JsTree> children;
private Map<String, Object> data;
private Map<String, Object> data = new HashMap<>();
private double weight = 1;
/**
* 树的状态.

View File

@ -47,4 +47,12 @@ public class PaginationBean implements Serializable {
@Setter
private Integer id;
//以下为临时属性待重构 //todo
@Getter
@Setter
private String randomKey;
@Getter
@Setter
private String method;
}

View File

@ -0,0 +1,28 @@
package com.hshh.system.common.cmd;
import lombok.Getter;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public enum CmdEnum {
EVALUATION("evaluation", "评估");
@Getter
private final String desc;
@Getter
private final String code;
CmdEnum(String code, String desc) {
this.desc = desc;
this.code = code;
}
}

View File

@ -0,0 +1,18 @@
package com.hshh.system.common.cmd;
import lombok.Data;
/**
* 命令定义
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class CmdInfo {
private CmdEnum type;
private String cmd;
private Integer userId;
}

View File

@ -1,4 +1,4 @@
package com.hshh.data.controller;
package com.hshh.system.common.enums;
/**
* [类的简要说明]
@ -9,6 +9,7 @@ package com.hshh.data.controller;
* @author LiDongYU
* @since 2025/7/22
*/
public class TestController {
public enum AggregationMethod {
WEIGHTED_AVERAGE,
FUZZY_AVERAGE;
}

View File

@ -0,0 +1,36 @@
package com.hshh.system.common.threadpool;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public final class NamedThreadFactory implements ThreadFactory {
private final String poolName;
private final boolean daemon;
private final AtomicInteger seq = new AtomicInteger(1);
private final ThreadGroup group;
public NamedThreadFactory(String poolName, boolean daemon) {
this.poolName = poolName;
this.daemon = daemon;
SecurityManager s = System.getSecurityManager();
this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, poolName + "-" + seq.getAndIncrement(), 0);
t.setDaemon(daemon);
if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}

View File

@ -0,0 +1,63 @@
package com.hshh.system.common.threadpool;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public class ThreadPoolBuilder {
private final String poolName;
private int coreSize = Math.max(1, Runtime.getRuntime().availableProcessors());
private int maxSize = coreSize;
private int queueCapacity = 1024;
private long keepAliveSeconds = 30;
private boolean allowCoreTimeout = false;
private boolean daemon = false;
private RejectedExecutionHandler rejection = new ThreadPoolExecutor.AbortPolicy();
public ThreadPoolBuilder(String poolName) {
this.poolName = Objects.requireNonNull(poolName, "poolName");
}
public ThreadPoolBuilder coreSize(int n) { this.coreSize = n; return this; }
public ThreadPoolBuilder maxSize(int n) { this.maxSize = n; return this; }
public ThreadPoolBuilder queueCapacity(int cap) { this.queueCapacity = cap; return this; }
public ThreadPoolBuilder keepAliveSeconds(long sec) { this.keepAliveSeconds = sec; return this; }
public ThreadPoolBuilder allowCoreThreadTimeOut(boolean allow) { this.allowCoreTimeout = allow; return this; }
public ThreadPoolBuilder daemon(boolean d) { this.daemon = d; return this; }
public ThreadPoolBuilder rejection(RejectedExecutionHandler h) { this.rejection = h; return this; }
/** 构建 ThreadPoolExecutor */
public ThreadPoolExecutor build() {
if (maxSize < coreSize) maxSize = coreSize;
final BlockingQueue<Runnable> queue = (queueCapacity <= 0)
? new SynchronousQueue<>()
: new LinkedBlockingQueue<>(queueCapacity);
final NamedThreadFactory tf = new NamedThreadFactory(poolName, daemon);
ThreadPoolExecutor exec = new ThreadPoolExecutor(
coreSize,
maxSize,
keepAliveSeconds, TimeUnit.SECONDS,
queue,
tf,
rejection
);
exec.allowCoreThreadTimeOut(allowCoreTimeout);
return exec;
}
}

View File

@ -0,0 +1,85 @@
package com.hshh.system.common.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public final class ThreadPools {
private ThreadPools() {
}
/**
* 获取构建器入口
*/
public static ThreadPoolBuilder builder(String poolName) {
return new ThreadPoolBuilder(poolName);
}
/**
* CPU 密集线程数CPU核心数
*/
public static ExecutorService newCpuBoundPool(String poolName) {
int n = Math.max(1, Runtime.getRuntime().availableProcessors());
return builder(poolName)
.coreSize(n)
.maxSize(n)
.queueCapacity(1024)
.build();
}
/**
* IO 密集线程数扩大默认 2~4 且允许回收核心线程
*/
public static ExecutorService newIoBoundPool(String poolName) {
int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
return builder(poolName)
.coreSize(cores * 2)
.maxSize(cores * 4)
.queueCapacity(8192)
.keepAliveSeconds(60)
.allowCoreThreadTimeOut(true)
.build();
}
/**
* 计划任务线程池
*/
public static ScheduledExecutorService newScheduledPool(String poolName, int coreSize) {
return new ScheduledThreadPoolExecutor(
coreSize,
new NamedThreadFactory(poolName, false),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
/**
* 优雅关闭先等待 quietSeconds超时则 shutdownNow 再等 forceSeconds
*/
public static void gracefulShutdown(ExecutorService pool, long quietSeconds, long forceSeconds) {
if (pool == null) {
return;
}
pool.shutdown();
try {
if (!pool.awaitTermination(quietSeconds, TimeUnit.SECONDS)) {
pool.shutdownNow();
pool.awaitTermination(forceSeconds, TimeUnit.SECONDS);
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,20 @@
package com.hshh.system.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* ws配置.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Configuration
public class EndpointConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@ -37,7 +37,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http
.authorizeRequests()
.antMatchers("/login", "/css/**", "/js/**", "/img/**", "/libs/**", "/captcha", "/toAuth",
"/ws", "/swagger-ui.html",
"/ws/**", "/swagger-ui.html",
"/swagger-ui/**",
"/webfonts/**",
"/v3/api-docs",

View File

@ -0,0 +1,143 @@
package com.hshh.system.ws;
import com.alibaba.fastjson2.JSON;
import com.hshh.system.Global;
import com.hshh.system.common.cmd.CmdInfo;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 评估需求ws server.
*
* @author LiDongYU
* @since 2025/7/22
*/
@ServerEndpoint("/ws/evaluation/{key}")
@Component
@Slf4j
public class WsEvaluationServer {
// key -> 当前key下所有会话
private static final Map<String, Set<Session>> SESSION_MAP = new ConcurrentHashMap<>();
/**
* 打开事件.
*
* @param session session容器
* @param key key标识
*/
@OnOpen
public void onOpen(Session session, @PathParam("key") String key) {
log.info("session is open key: {}", key);
session.getUserProperties().put("key", key);
SESSION_MAP.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()).add(session);
}
/**
* 接收信息.
*
* @param message 信息
* @param session session容器
* @param key key标识
* @throws Exception 异常
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam("key") String key)
throws Exception {
log.info("receive message: {},key:{}", message, key);
try {
CmdInfo cmdInfo = JSON.parseObject(message, CmdInfo.class);
Global.cmdQueue.add(cmdInfo);
} catch (Exception e) {
log.error("error", e);
}
}
/**
* 关闭事件.
*
* @param session session容器
* @param key key标识
*/
@OnClose
public void onClose(Session session, @PathParam("key") String key) {
log.info("session is closed key: {},sessionId:{}", key, session.getId());
System.out.println("连接关闭: " + session.getId() + " 参数key=" + key);
removeSession(key, session);
}
/**
* 出错后的处理.
*
* @param session session容器
* @param error 异常
*/
@OnError
public void onError(Session session, Throwable error) {
String key = (String) session.getUserProperties().get("key");
removeSession(key, session);
log.error("session error ", error);
}
private void removeSession(String key, Session session) {
if (key == null) {
return;
}
Set<Session> set = SESSION_MAP.get(key);
if (set != null) {
set.remove(session);
if (set.isEmpty()) {
SESSION_MAP.remove(key);
}
}
}
// ========== 主动推送 API ==========
/**
* 给某个 key 的所有连接发消息.
*/
public static int sendToKey(String key, String msg) {
Set<Session> set = SESSION_MAP.get(key);
if (set == null) {
return 0;
}
int ok = 0;
for (Session s : set) {
if (s.isOpen()) {
try {
s.getBasicRemote().sendText(msg);
ok++;
} catch (Exception e) {
log.error("session error ", e);
}
}
}
return ok;
}
/**
* 给所有 key 的连接广播.
*/
public static int broadcast(String msg) {
int sum = 0;
for (String k : SESSION_MAP.keySet()) {
sum += sendToKey(k, msg);
}
return sum;
}
}

View File

@ -29,7 +29,7 @@ public class CodeGenerator {
basePath + "/src/main/resources/mapper")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("m_data_evaluation_template_weight") // 设置需要生成的表名多个用逗号分隔
builder.addInclude("m_data_evaluation_template_indicator_weight") // 设置需要生成的表名多个用逗号分隔
.addTablePrefix("m_data_"); // 设置过滤表前缀
})
.execute();