1. 指标子集映射

This commit is contained in:
李玉东 2025-09-05 15:16:19 +08:00
parent e0bc36ae0e
commit 92f28962ef
162 changed files with 7105 additions and 2002 deletions

View File

@ -1,14 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$/manager-admin/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-admin/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-common/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-common/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-system/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-system/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-z-generation/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/manager-z-generation/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>

View File

@ -39,6 +39,15 @@
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>

View File

@ -1,670 +1,140 @@
<!doctype html>
<!--
* 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, 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>
@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;
}
body {
font-feature-settings: "cv03", "cv04", "cv11";
}
</style>
</head>
<body >
<script src="./dist/js/demo-theme.min.js?1684106062"></script>
<div class="page">
<!-- Navbar -->
<header class="navbar navbar-expand-md d-print-none" >
<div class="container-xl">
<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>
<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>
</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>
<!DOCTYPE html>
<meta charset="utf-8" />
<title>替换雷达为横向条形图 · 最小示例</title>
<style>
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial; background:#f8fafc; }
.chart-card { width: 900px; margin: 40px auto; background: #f3f6fb; border-radius: 12px; padding: 24px; }
.chart-title { margin: 0 0 8px; text-align: center; color:#0f172a; }
.chart-container {
position: relative;
height: 300px; /* 你提供的高度 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
<div class="chart-card">
<h3 class="chart-title">一级维度得分对比</h3>
<div class="chart-container">
<svg id="radarChart"></svg>
</div>
</div>
<!-- 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>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script>
/**
* 横向条形图(替换 #radarChart
* @param {string|SVGElement} svgSel - 目标 SVG 选择器/元素
* @param {{name:string,score:number}[]} data - 数据score 建议 0~100
* @param {{domainMax?:number, sortAsc?:boolean, target?:number, margin?:object}} opts
*/
function renderBarChart(svgSel, data, opts = {}) {
const cfg = Object.assign({
sortAsc: true,
domainMax: 100, // 轴与条统一最大值,确保与刻度对齐
target: 80,
margin: { top: 24, right: 120, bottom: 28, left: 200 }
}, opts);
const src = (data||[]).map(d=>({...d}));
if (cfg.sortAsc) src.sort((a,b)=>d3.ascending(a.score,b.score));
const svg = d3.select(svgSel);
const parent = svg.node().parentNode;
const W = parent.clientWidth || 800;
const H = parent.clientHeight || Math.max(240, src.length*40 + cfg.margin.top + cfg.margin.bottom);
svg.attr("width", W).attr("height", H)
.attr("viewBox", `0 0 ${W} ${H}`)
.attr("preserveAspectRatio","xMidYMid meet")
.selectAll("*").remove();
const {top,right,bottom,left} = cfg.margin;
const width = Math.max(50, W - left - right);
const innerH = Math.max(20, H - top - bottom);
const gap = 10;
const barH = Math.max(16, Math.min(32, (innerH - (src.length-1)*gap) / Math.max(1,src.length)));
const g = svg.append("g").attr("transform", `translate(${left},${top})`);
// ★ 唯一的 x 比例尺(条/轴/目标线共用)
const x = d3.scaleLinear()
.domain([0, cfg.domainMax])
.nice()
.range([0, width]);
const y = d3.scaleBand()
.domain(src.map(d=>d.name))
.range([0, src.length*(barH+gap)-gap])
.paddingInner(gap/(barH+gap));
const color = d3.scaleLinear()
.domain([0.5*cfg.domainMax, 0.75*cfg.domainMax, 0.9*cfg.domainMax])
.range (["#ef4444", "#f59e0b", "#10b981"])
.clamp(true);
// X 轴
g.append("g")
.attr("transform", `translate(0,${y.range()[1]})`)
.call(d3.axisBottom(x).ticks(6).tickSizeOuter(0))
.selectAll("path,line").attr("stroke", "#e5e7eb");
// 行
const row = g.selectAll(".row").data(src).enter().append("g")
.attr("class","row")
.attr("transform", d=>`translate(0,${y(d.name)})`);
// 条形
row.append("rect")
.attr("height", barH)
.attr("width", d=>x(d.score)) // 与轴同一比例尺
.attr("fill", d=>color(d.score))
.attr("rx", 8).attr("ry", 8);
// 左侧分类名
row.append("text")
.attr("x", -10).attr("y", barH/2).attr("dy",".35em")
.attr("text-anchor","end").attr("fill","#0f172a").attr("font-weight",700)
.text(d=>d.name);
// 右侧分数
row.append("text")
.attr("x", d=>x(d.score)+8).attr("y", barH/2).attr("dy",".35em")
.attr("fill","#0f172a").attr("font-weight",800).attr("font-size",18)
.text(d=>d.score.toFixed(1));
// 最低分标注
const minScore = d3.min(src, d=>d.score);
row.filter(d=>d.score===minScore).append("text")
.attr("x", d=>x(d.score)+8).attr("y", barH/2+16)
.attr("fill","#ef4444").attr("font-weight",800).attr("font-size",12)
.text("LOWEST");
// 目标线(可选)
if (typeof cfg.target === "number") {
g.append("line")
.attr("x1", x(cfg.target)).attr("x2", x(cfg.target))
.attr("y1", 0).attr("y2", y.range()[1])
.attr("stroke", "#94a3b8").attr("stroke-dasharray", "4,4");
g.append("text")
.attr("x", x(cfg.target)+6).attr("y", -6)
.attr("fill","#64748b").attr("font-size",12)
.text(`目标 ${cfg.target}`);
}
}
// ====== 最小示例数据(可替换为你的实时数据)======
const level1 = [
{ name:"港口安全管理", score: 90 },
{ name:"港口环境与资源利用", score: 90 },
{ name:"港口运行效率", score:90 }
];
// 渲染 & 自适应
renderBarChart('#radarChart', level1, { domainMax: 100, target: 80 });
window.addEventListener('resize', () =>
renderBarChart('#radarChart', level1, { domainMax: 100, target: 80 })
);
</script>

View File

@ -1,9 +1,12 @@
package com.hshh;
import com.hshh.system.base.service.LogsService;
import com.hshh.system.common.threadpool.ThreadPools;
import com.hshh.thread.HandleCmdThread;
import com.hshh.system.logs.LogRecorderThread;
import com.hshh.thread.HandleEvaluationCmdThread;
import java.util.concurrent.ExecutorService;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -15,8 +18,10 @@ import org.springframework.stereotype.Component;
*/
@Component
@Slf4j
public class Init {
public class AdminInit {
@Resource
private LogsService logsService;
private static final ExecutorService cpu = ThreadPools.newCpuBoundPool("cpu-pool");
private static final int evaluationThreadNum = 5;
@ -25,16 +30,23 @@ public class Init {
*/
@PostConstruct
public void init() {
//评估线程
startEvaluationThread();
//日志记录线程
startRecordLogs();
}
private void startEvaluationThread() {
try {
for (int i = 0; i < evaluationThreadNum; i++) {
cpu.execute(new Thread(new HandleCmdThread()));
cpu.execute(new Thread(new HandleEvaluationCmdThread()));
}
} catch (Exception e) {
log.error("error::", e);
}
}
private void startRecordLogs() {
new Thread(new LogRecorderThread(logsService)).start();
}
}

View File

@ -7,6 +7,7 @@ import com.hshh.model.entity.ModelDefine;
import com.hshh.model.service.FormFieldConfigService;
import com.hshh.model.service.FormValueService;
import com.hshh.model.service.ModelDefineService;
import com.hshh.system.annotation.LogOperation;
import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.bean.ErrorField;
import com.hshh.system.common.bean.OperateResult;
@ -70,6 +71,7 @@ public class DataController extends BaseController {
* @param model session容器
* @return /data/list.html
*/
@LogOperation("进入数据管理")
@GetMapping("/list")
@Operation(summary = "导航到数据管理页面", description = "导航到数据管理页面")
public String index(PaginationBean request, Model model) {
@ -81,12 +83,12 @@ public class DataController extends BaseController {
model.addAttribute("modelDefineList", modelDefineList); //设置到session中
if (!modelDefineList.isEmpty()) {
if (!modelDefineList.isEmpty() && request.getBusinessKey() != null) {
List<FormFieldConfig> formConfigList = formFieldConfigService.getFormFieldConfigByModelId(
(request.getId() == null ? modelDefineList.get(0).getId() : request.getId())); //查询模型表头
request.getBusinessKey()); //查询模型表头
sortFieldList(formConfigList); //排序
Map<String, String> headerMap = new LinkedHashMap<>(); //定义表头兑现
Map<String, String> headerMap = new LinkedHashMap<>(); //定义表头
formConfigList.forEach(formFieldConfig -> {
headerMap.put(formFieldConfig.getFieldName(), formFieldConfig.getFieldLabel());
});
@ -114,10 +116,10 @@ public class DataController extends BaseController {
*/
private void setCurrentActiveModelDefine(Model model, PaginationBean request,
List<ModelDefine> modelDefineList) {
AtomicReference<ModelDefine> activeModelDefine = new AtomicReference<>(); //当前激活的数据模型tab页
AtomicReference<ModelDefine> activeModelDefine = new AtomicReference<>();
modelDefineList.forEach(a -> {
if (request.getId() != null && request.getId().equals(a.getId())) {
if (request.getBusinessKey() != null && request.getBusinessKey().equals(a.getId())) {
a.setChecked(true);
activeModelDefine.set(a);
@ -129,15 +131,6 @@ public class DataController extends BaseController {
});
if (activeModelDefine.get() != null) {
model.addAttribute("currentModelDefine", activeModelDefine.get());
} else {
//设置默认第一个选中
if (!modelDefineList.isEmpty()) {
modelDefineList.get(0).setChecked(true);
//设置查询参数为第一个
request.setId(modelDefineList.get(0).getId());
model.addAttribute("currentModelDefine", modelDefineList.get(0));
}
}
}
@ -157,6 +150,7 @@ public class DataController extends BaseController {
* @param id 模型id
* @return form表单内容 div...
*/
@LogOperation("获取form输入窗体")
@GetMapping("/getForm/{id}")
@Operation(summary = "获取form输入窗体", description = "根据模型ID获取form窗体")
@ResponseBody
@ -171,6 +165,7 @@ public class DataController extends BaseController {
* @param formValue 表单值
* @return 操作结果
*/
@LogOperation("保存对应模型ID对应的form表单记录")
@PostMapping("/form/save")
@ResponseBody
@Operation(summary = "保存记录", description = "保存对应模型ID对应的form表单记录")
@ -194,6 +189,7 @@ public class DataController extends BaseController {
* @param id 记录ID
* @return 操作结果
*/
@LogOperation("根据ID删除记录")
@ResponseBody
@GetMapping("/remove/{id}")
@Operation(summary = "删除记录", description = "根据ID删除记录")

View File

@ -0,0 +1,19 @@
package com.hshh.evaluation.bean;
import lombok.Data;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class BarData {
private String name;
private double score;
}

View File

@ -0,0 +1,19 @@
package com.hshh.evaluation.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.web.multipart.MultipartFile;
/**
* csv上传文件描述类.
*
* @author LiDongYU
* @since 2025/7/22
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CsvUploadBean extends PageEvaluationRequest {
private MultipartFile file;
}

View File

@ -10,7 +10,7 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class DatasourceRequest implements Serializable {
public class PageDatasourceRequest implements Serializable {
private static final long serialVersionUID = 1L;
//数据源类型 数据库||Csv文件

View File

@ -9,7 +9,7 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class EvaluationRequest {
public class PageEvaluationRequest {
/**
* database | csv.
@ -36,7 +36,16 @@ public class EvaluationRequest {
*/
private String randomKey;
/**
* 计算方式
* 计算方式.
*/
private String method;
private Integer indicatorTopId;
/**
* 用户ID.
*/
private Integer userId;
private String membership;
private int[] ids;
//csv数据标识字段
private String identifier;
}

View File

@ -0,0 +1,22 @@
package com.hshh.evaluation.bean;
import lombok.Data;
/**
* 页面指标权重.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class PageIndicatorWeight {
/**
* 指标ID.
*/
private int indicatorId;
/**
* 权重.
*/
private String weight;
}

View File

@ -10,9 +10,9 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class MetricComputeRequest {
public class PageMetricComputeRequest {
private List<String> metric;
private List<List<MetricMapperWeightBean>> weightList;
private List<List<PageMetricMapperWeightBean>> weightList;
}

View File

@ -9,7 +9,7 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class MetricComputerResponse {
public class PageMetricComputerResponse {
private String id;
private String weight;

View File

@ -9,7 +9,7 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class MetricMapperWeightBean {
public class PageMetricMapperWeightBean {
/**
* 从那个指标开始.
@ -31,4 +31,5 @@ public class MetricMapperWeightBean {
* 行号.
*/
private int rowNum;
}

View File

@ -9,7 +9,7 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class MetricTableHeaderBean {
public class PageMetricTableHeaderBean {
private int id;
private String name;

View File

@ -0,0 +1,22 @@
package com.hshh.evaluation.bean;
import java.io.Serializable;
import lombok.Data;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class PieData implements Serializable {
private static final long serialVersionUID = 1L;
private String label;
private double value;
private String color;
}

View File

@ -0,0 +1,28 @@
package com.hshh.evaluation.bean;
import com.hshh.evaluation.entity.EvaluationRootResult;
import com.hshh.indicator.entity.IndicatorTopLevel;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
/**
* 报告模板类.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class ReportBean implements Serializable {
private EvaluationRootResult result;
private List<IndicatorTopLevel> levelList;
private String highRate;
private Integer bottomNum;
private List<PieData> pieDataList;
private List<ReportIndicatorNodeData> dataNodeList;
private List<BarData> barDataList;
}

View File

@ -0,0 +1,28 @@
package com.hshh.evaluation.bean;
import java.io.Serializable;
import java.util.Map;
import lombok.Data;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class ReportIndicatorNodeData implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private Integer level;
private double score;
private Map<String, Double> membershipDist;
private double weight;
private String parent;
private String evaluation;
}

View File

@ -0,0 +1,19 @@
package com.hshh.evaluation.bean;
import lombok.Data;
/**
* 评估数据描述.原始数据id,实际数据datatype=csv or database.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class SingleEvaluationData {
private int id;
private String data;
private String type;
}

View File

@ -11,14 +11,14 @@ import lombok.Data;
* @since 2025/7/22
*/
@Data
public class DynamicTable {
public class pageDynamicTable {
/**
* 表头map linkedMap.
*/
private Map<Integer, MetricTableHeaderBean> headerMap;
private Map<Integer, PageMetricTableHeaderBean> headerMap;
private List<List<MetricMapperWeightBean>> weight;
private List<List<PageMetricMapperWeightBean>> weight;
}

View File

@ -0,0 +1,145 @@
package com.hshh.evaluation.controller;
import com.hshh.evaluation.bean.BarData;
import com.hshh.evaluation.bean.PieData;
import com.hshh.evaluation.bean.ReportIndicatorNodeData;
import com.hshh.evaluation.entity.EvaluationIndicatorResult;
import com.hshh.evaluation.entity.EvaluationRootResult;
import com.hshh.indicator.entity.IndicatorTopLevel;
import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.enums.LevelEnum;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public class AssistantEvaluationProjectController extends BaseController {
protected Map<String, Double> membershipScoreMap(String input) {
Map<String, Double> map = new LinkedHashMap<>(); // 保证顺序和输入一致
for (String pair : input.split(";")) {
if (pair.trim().isEmpty()) {
continue;
}
String[] kv = pair.split(":");
if (kv.length == 2) {
String key = kv[0].trim();
double value = Double.parseDouble(kv[1].trim());
map.put(key, value);
}
}
return map;
}
protected String getClosestLevel(List<IndicatorTopLevel> levelList, int targetScore) {
IndicatorTopLevel closest = null;
int minDiff = Integer.MAX_VALUE;
for (IndicatorTopLevel item : levelList) {
int diff = Math.abs(targetScore - (int) Double.parseDouble(item.getGrade()));
if (diff < minDiff) {
minDiff = diff;
closest = item;
}
}
return closest != null ? closest.getLevelName() : null;
}
//获取优良率
protected String highRate(Map<String, Double> membershipMap) {
AtomicReference<Double> highRate = new AtomicReference<>((double) 0);
membershipMap.forEach((k, v) -> {
if (k.equals(LevelEnum.Excellent.getLevel())) {
highRate.set(highRate.get() + v);
}
if (k.equals(LevelEnum.Good.getLevel())) {
highRate.set(highRate.get() + v);
}
});
return String.format("%.2f", highRate.get() * 100);
}
protected void setLevelName(List<IndicatorTopLevel> levelList, EvaluationRootResult rootResult) {
if (levelList.isEmpty()) {
return;
}
rootResult.setLevelName(levelList.get(levelList.size() - 1).getLevelName());
for (IndicatorTopLevel indicatorTopLevel : levelList) {
if (Double.parseDouble(rootResult.getFinalScore()) >= Double.parseDouble(
indicatorTopLevel.getGrade())) {
rootResult.setLevelName(indicatorTopLevel.getLevelName());
break;
}
}
}
protected List<PieData> pieDataList(Map<String, Double> membershipMap) {
List<PieData> list = new ArrayList<>();
membershipMap.forEach((k, v) -> {
PieData pieData = new PieData();
pieData.setLabel(k);
pieData.setValue(v * 100);
pieData.setColor(LevelEnum.getColor(k));
list.add(pieData);
});
return list;
}
protected List<ReportIndicatorNodeData> covertIndicatorResultToReportIndicatorNodeData(
List<EvaluationIndicatorResult> list, List<IndicatorTopLevel> levelList) {
List<ReportIndicatorNodeData> resultList = new ArrayList<>();
list.forEach(indicatorResult -> {
ReportIndicatorNodeData reportIndicatorNodeData = new ReportIndicatorNodeData();
resultList.add(reportIndicatorNodeData);
reportIndicatorNodeData.setId("node_" + indicatorResult.getIndicatorId());
reportIndicatorNodeData.setName(indicatorResult.getIndicatorName());
reportIndicatorNodeData.setLevel(indicatorResult.getLevel());
reportIndicatorNodeData.setScore(
new BigDecimal(indicatorResult.getFinalScore()).setScale(2, RoundingMode.HALF_UP)
.doubleValue());
Map<String, Double> membershipScoreMap = new HashMap<>();
Map<String, Double> originalMap = membershipScoreMap(indicatorResult.getMemberShipScore());
originalMap.forEach((k, v) -> {
membershipScoreMap.put(LevelEnum.getCode(k),
BigDecimal.valueOf(v * 100).setScale(2, RoundingMode.HALF_UP).doubleValue());
});
reportIndicatorNodeData.setMembershipDist(membershipScoreMap);
reportIndicatorNodeData.setEvaluation(
getClosestLevel(levelList, (int) reportIndicatorNodeData.getScore()));
reportIndicatorNodeData.setWeight(indicatorResult.getWeight());
if (indicatorResult.getIndicatorParentId() != null) {
reportIndicatorNodeData.setParent("node_" + indicatorResult.getIndicatorParentId());
}
});
return resultList;
}
protected List<BarData> covertIndicatorResultToBarData(List<EvaluationIndicatorResult> list) {
List<BarData> resultList = new ArrayList<>();
list.forEach(indicatorResult -> {
BarData barData = new BarData();
barData.setName(indicatorResult.getIndicatorName());
barData.setScore(
new BigDecimal(indicatorResult.getFinalScore()).setScale(2, RoundingMode.HALF_UP)
.doubleValue());
resultList.add(barData);
});
return resultList;
}
}

View File

@ -1,8 +1,8 @@
package com.hshh.evaluation.controller;
import com.hshh.evaluation.bean.DynamicTable;
import com.hshh.evaluation.bean.MetricMapperWeightBean;
import com.hshh.evaluation.bean.MetricTableHeaderBean;
import com.hshh.evaluation.bean.PageMetricMapperWeightBean;
import com.hshh.evaluation.bean.PageMetricTableHeaderBean;
import com.hshh.evaluation.bean.pageDynamicTable;
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.service.IndicatorService;
@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 评估模板类辅助方法.
@ -29,7 +30,7 @@ public class AssistantTemplateController extends BaseController {
* @return list string数组
*/
protected List<String[]> convertMetricMapperWeightBeanListToStrArray(
List<List<MetricMapperWeightBean>> weightList) {
List<List<PageMetricMapperWeightBean>> weightList) {
List<String[]> data = new ArrayList<>();
weightList.forEach(weight -> {
@ -48,19 +49,27 @@ public class AssistantTemplateController extends BaseController {
* @param detailList 数据库数据
* @return 页面约定的权重列表
*/
protected List<MetricMapperWeightBean> convertEvaluationTemplateWeightList(
protected List<List<PageMetricMapperWeightBean>> convertEvaluationTemplateWeightList(
List<EvaluationTemplateWeight> detailList) {
List<MetricMapperWeightBean> list = new ArrayList<>();
detailList.forEach(detail -> {
MetricMapperWeightBean weightBean = new MetricMapperWeightBean();
weightBean.setRowNum(detail.getRowNum());
weightBean.setValue(detail.getWeight());
weightBean.setRowId(detail.getFromIndicatorId());
weightBean.setColId(detail.getToIndicatorId());
weightBean.setParentId(detail.getIndicatorParentId());
list.add(weightBean);
List<List<PageMetricMapperWeightBean>> finalList = new ArrayList<>();
Map<Integer, List<EvaluationTemplateWeight>> groupByFromIndicatorMap = detailList.stream()
.collect(Collectors.groupingBy(EvaluationTemplateWeight::getFromIndicatorId));
groupByFromIndicatorMap.forEach((key, value) -> {
List<PageMetricMapperWeightBean> list = new ArrayList<>();
finalList.add(list);
value.forEach(detail -> {
PageMetricMapperWeightBean weightBean = new PageMetricMapperWeightBean();
weightBean.setRowNum(detail.getRowNum());
weightBean.setValue(detail.getWeight());
weightBean.setRowId(detail.getFromIndicatorId());
weightBean.setColId(detail.getToIndicatorId());
weightBean.setParentId(detail.getIndicatorParentId());
list.add(weightBean);
});
});
return list;
return finalList;
}
/**
@ -69,15 +78,15 @@ public class AssistantTemplateController extends BaseController {
* @param children 子指标
* @return 表格头信息
*/
protected Map<Integer, MetricTableHeaderBean> createTableHeaderMap(List<Indicator> children) {
List<MetricTableHeaderBean> headerBeans = new ArrayList<>();
protected Map<Integer, PageMetricTableHeaderBean> createTableHeaderMap(List<Indicator> children) {
List<PageMetricTableHeaderBean> headerBeans = new ArrayList<>();
children.forEach(a -> {
MetricTableHeaderBean bean = new MetricTableHeaderBean();
PageMetricTableHeaderBean bean = new PageMetricTableHeaderBean();
bean.setId(a.getId());
bean.setName(a.getName());
headerBeans.add(bean);
});
Map<Integer, MetricTableHeaderBean> headerMap = new LinkedHashMap<>();
Map<Integer, PageMetricTableHeaderBean> headerMap = new LinkedHashMap<>();
headerBeans.forEach(header -> {
headerMap.put(header.getId(), header);
});
@ -92,11 +101,11 @@ public class AssistantTemplateController extends BaseController {
* @param i 行号
* @return 默认映射列表
*/
protected List<MetricMapperWeightBean> createEmptyMapperList(Integer parentIndicatorId,
protected List<PageMetricMapperWeightBean> createEmptyMapperList(Integer parentIndicatorId,
List<Indicator> children, int i) {
List<MetricMapperWeightBean> innerList = new ArrayList<>();
List<PageMetricMapperWeightBean> innerList = new ArrayList<>();
for (Indicator child : children) {
MetricMapperWeightBean weightBean = new MetricMapperWeightBean();
PageMetricMapperWeightBean weightBean = new PageMetricMapperWeightBean();
//行ID
weightBean.setRowId(children.get(i).getId());
//列Id
@ -119,8 +128,8 @@ public class AssistantTemplateController extends BaseController {
* @param indicatePid 父节点指标
* @return 动态表
*/
protected DynamicTable createNewDynamicFromDatabase(Integer indicatePid) {
DynamicTable dynamicTable = new DynamicTable();
protected pageDynamicTable createNewDynamicFromDatabase(Integer indicatePid) {
pageDynamicTable dynamicTable = new pageDynamicTable();
//查询当前指标的子指标
IndicatorService indicatorService = SpringContextHolder.getBean(IndicatorServiceImpl.class);
List<Indicator> children = indicatorService.queryChildren(indicatePid);
@ -129,9 +138,9 @@ public class AssistantTemplateController extends BaseController {
dynamicTable.setHeaderMap(createTableHeaderMap(children));
//设置权重信息
List<List<MetricMapperWeightBean>> weight = new ArrayList<>();
List<List<PageMetricMapperWeightBean>> weight = new ArrayList<>();
for (int i = 0; i < children.size(); i++) {
List<MetricMapperWeightBean> innerList = createEmptyMapperList(indicatePid, children, i
List<PageMetricMapperWeightBean> innerList = createEmptyMapperList(indicatePid, children, i
);
weight.add(innerList);
}

View File

@ -1,18 +0,0 @@
package com.hshh.evaluation.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
/**
* <p>
* 前端控制器
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
@Controller
@RequestMapping("/evaluation/evaluationCsvData")
public class EvaluationCsvDataController {
}

View File

@ -1,18 +0,0 @@
package com.hshh.evaluation.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
/**
* <p>
* 评估记录表 前端控制器
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
@Controller
@RequestMapping("/evaluation/evaluationHistory")
public class EvaluationHistoryController {
}

View File

@ -1,25 +1,42 @@
package com.hshh.evaluation.controller;
import com.hshh.evaluation.bean.EvaluationRequest;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.hshh.evaluation.bean.CsvUploadBean;
import com.hshh.evaluation.bean.PageEvaluationRequest;
import com.hshh.evaluation.bean.ReportBean;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.hshh.evaluation.entity.EvaluationHistory;
import com.hshh.evaluation.entity.EvaluationIndicatorResult;
import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.evaluation.entity.EvaluationRootResult;
import com.hshh.evaluation.entity.EvaluationTemplate;
import com.hshh.evaluation.service.EvaluationCsvDataService;
import com.hshh.evaluation.service.EvaluationHistoryService;
import com.hshh.evaluation.service.EvaluationIndicatorResultService;
import com.hshh.evaluation.service.EvaluationProjectService;
import com.hshh.evaluation.service.EvaluationRootResultService;
import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.entity.IndicatorTopLevel;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.indicator.service.IndicatorTopLevelService;
import com.hshh.model.entity.FormValue;
import com.hshh.model.service.FormFieldConfigService;
import com.hshh.model.service.FormValueService;
import com.hshh.system.annotation.LogOperation;
import com.hshh.system.base.entity.TableRelations;
import com.hshh.system.base.service.TableRelationsService;
import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.bean.OperateResult;
import com.hshh.system.common.bean.PaginationBean;
import com.hshh.system.common.enums.ErrorCode;
import com.hshh.system.common.enums.ErrorMessage;
import io.swagger.v3.oas.annotations.Operation;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
@ -28,11 +45,14 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* 评估工程.
@ -42,7 +62,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
*/
@Controller
@RequestMapping("/evaluation/project")
public class EvaluationProjectController extends BaseController {
public class EvaluationProjectController extends AssistantEvaluationProjectController {
@Resource
private EvaluationProjectService evaluationProjectService;
@ -76,6 +96,28 @@ public class EvaluationProjectController extends BaseController {
private IndicatorService indicatorService;
/**
* 上传的csv文件数据记录服务类.
*/
@Resource
private EvaluationCsvDataService evaluationCsvDataService;
/**
* 评估历史服务类.
*/
@Resource
private EvaluationHistoryService evaluationHistoryService;
/**
* 根指标评估历史记录服务类.
*/
@Resource
private EvaluationRootResultService rootResultService;
@Resource
private IndicatorTopLevelService topLevelService;
@Resource
private EvaluationIndicatorResultService evaluationIndicatorResultService;
/**
* 默认页.
*
@ -114,6 +156,7 @@ public class EvaluationProjectController extends BaseController {
*/
@ResponseBody
@PostMapping("/save")
@LogOperation("保存评估工程")
public OperateResult<Object> save(@Valid @RequestBody EvaluationProject project,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
@ -146,6 +189,7 @@ public class EvaluationProjectController extends BaseController {
*/
@ResponseBody
@GetMapping("/remove/{id}")
@LogOperation("删除工程")
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
List<TableRelations> reList = tableRelationsService.queryRel(id, "m_data_evaluation_project");
if (!reList.isEmpty()) {
@ -159,14 +203,14 @@ public class EvaluationProjectController extends BaseController {
/**
* 选择数据源类型.
*
* @param projectId 工程ID
* @param model session容器
* @param params 请求参数
* @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);
public String selectDatasourceType(@RequestParam Map<String, String> params, Model model) {
setAttribute(model, params);
return "project/datasource_select";
}
@ -178,19 +222,27 @@ public class EvaluationProjectController extends BaseController {
* @return project/datasource_db.html
*/
@GetMapping("/database")
public String databasePage(PaginationBean request, Model model) {
//获取工程
public String databasePage(PaginationBean request, Model model,
@RequestParam Map<String, String> params) {
//获取基础设施ID
Long modelId = evaluationProjectService.selectModelIdByProjectId(request.getId());
Long modelId = evaluationProjectService.selectModelIdByProjectId(
params.get("projectId") == null ? 0 : Integer.parseInt(params.get("projectId")));
if (modelId == null) {
throw new RuntimeException("没有设置基础设施");
}
params.put("modelId", modelId.toString());
//设置数据源页面中的隐藏属性
addDatasourceModelAttribute(modelId, request, model);
addDatasourceModelAttribute(request, model, params);
//获取表头
Map<String, String> headerMap = formFieldConfigService.getHeaderMap(modelId.intValue());
model.addAttribute("headerMap", headerMap);
//查询数据
//替换为modelId查询
request.setId(modelId.intValue());
request.setBusinessKey(modelId.intValue());
List<FormValue> formDataList = formValueService.list(request);
List<Map<String, Object>> listMap = new ArrayList<>();
formDataList.forEach(formData -> {
@ -203,17 +255,41 @@ public class EvaluationProjectController extends BaseController {
return "project/datasource_db";
}
private void addDatasourceModelAttribute(Long modelId, PaginationBean request, Model model) {
EvaluationProject project = evaluationProjectService.getById(request.getId());
private void addDatasourceModelAttribute(PaginationBean request, Model model,
@RequestParam Map<String, String> params) {
EvaluationProject project = evaluationProjectService.getById(
params.get("projectId") == null ? 0 : Integer.parseInt(params.get("projectId")));
if (project != null) {
model.addAttribute("templateId", project.getTemplateId());
params.put("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());
setAttribute(model, params);
}
private void setAttribute(Model model, @RequestParam Map<String, String> params) {
if (params.get("projectId") != null) {
model.addAttribute("projectId", params.get("projectId"));
}
if (params.get("templateId") != null) {
model.addAttribute("templateId", params.get("templateId"));
}
if (params.get("method") != null) {
model.addAttribute("method", params.get("method"));
}
if (params.get("search") != null) {
model.addAttribute("search", params.get("search"));
}
if (params.get("randomKey") != null) {
model.addAttribute("randomKey", params.get("randomKey"));
}
if (params.get("modelId") != null) {
model.addAttribute("modelId", params.get("modelId"));
}
if (params.get("membership") != null) {
model.addAttribute("membership", params.get("membership"));
}
}
/**
@ -223,11 +299,70 @@ public class EvaluationProjectController extends BaseController {
* @param model session容器
* @return project/datasource_csv.html
*/
@GetMapping("/csv")
public String csvPage(PaginationBean request, Model model) {
public String csvPage(PaginationBean request, Model model,
@RequestParam Map<String, String> params) {
if (params.get("projectId") != null && params.get("templateId") == null) {
EvaluationProject project = evaluationProjectService.getById(
Integer.parseInt(params.get("projectId")));
if (project != null) {
model.addAttribute("templateId", project.getTemplateId());
}
}
setAttribute(model, params);
//分页查询randomKey
List<EvaluationCsvData> list = evaluationCsvDataService.list(request);
if (!list.isEmpty()) {
//取出csvHeader
JSONArray first = JSON.parseArray(list.get(0).getCsvHeader());
Map<String, String> headerMap = new LinkedHashMap<>();
first.forEach(item -> {
headerMap.put(item.toString(), item.toString());
});
model.addAttribute("headerMap", headerMap);
List<Map<String, Object>> listMap = new ArrayList<>();
list.forEach(formData -> {
listMap.add(formData.toMap()); //转为map对象放入listMap中
});
Long total = evaluationCsvDataService.count(request);
setPaginationInfo(request, listMap, total, model);
}
return "project/datasource_csv";
}
/**
* 上传csv文件.
*
* @param uploadInfo 上传的信息
* @return project/datasource_csv.html
*/
@PostMapping("/uploadCsv")
public String uploadCsv(@ModelAttribute CsvUploadBean uploadInfo,
RedirectAttributes ra, @RequestParam Map<String, String> params) throws IOException {
//上传文件信息放入数据库
evaluationProjectService.uploadCsv(uploadInfo);
//设置工程ID
ra.addAttribute("id", uploadInfo.getProjectId());
ra.addAttribute("search", uploadInfo.getSearch());
ra.addAttribute("randomKey", uploadInfo.getRandomKey());
ra.addAttribute("method", uploadInfo.getMethod());
for (Map.Entry<String, String> entry : params.entrySet()) {
ra.addAttribute(entry.getKey(), entry.getValue());
}
return "redirect:/evaluation/project/csv";
}
/**
* 导航的数据评估执行页面.
*
@ -235,21 +370,21 @@ public class EvaluationProjectController extends BaseController {
* @param model session容器
* @return 评估处理页面
*/
@LogOperation("评估执行页面")
@PostMapping("/processEvaluation")
public String processEvaluationPage(@RequestBody EvaluationRequest request, Model model) {
public String processEvaluationPage(@RequestBody PageEvaluationRequest request, Model model) {
addProcessEvaluationModelAttribute(request, model);
return "project/project_evaluation_process";
}
private void addProcessEvaluationModelAttribute(EvaluationRequest request, Model model) {
private void addProcessEvaluationModelAttribute(PageEvaluationRequest request, Model model) {
//获取工程名称
EvaluationProject project = evaluationProjectService.getById(request.getProjectId());
if (project != null) {
model.addAttribute("projectName", project.getProjectName());
}
Indicator indicator = indicatorService.selectByTemplateId(
request.getTemplateId());
Indicator indicator = indicatorService.selectByTemplateId(request.getTemplateId());
//获取指标名称
if (indicator != null) {
model.addAttribute("indicatorName", indicator.getName());
@ -258,4 +393,114 @@ public class EvaluationProjectController extends BaseController {
model.addAttribute("datasourceName",
(request.getDatasourceType().equals("database") ? "数据库" : "CSV文件"));
}
/**
* 查看根指标评估历史.
*
* @param projectId 工程ID
* @return project/evaluation_root_history.html
*/
@GetMapping("/root_history")
public String rootHistory(Integer projectId) {
EvaluationHistory recentHistory = evaluationHistoryService.queryRecentByProjectId(projectId);
if (recentHistory == null) {
throw new RuntimeException("请先开始评估");
}
String randomKey = recentHistory.getRandomKey();
// 重定向到另一个接口带上参数
return "redirect:root_history_by_randomKey?randomKey=" + randomKey + "&projectId=" + projectId;
}
@GetMapping("/root_history_by_randomKey")
public String rootHistoryByRandomKey(String randomKey, Integer projectId, Model model) {
List<EvaluationRootResult> rootList = rootResultService.queryListByRandomKey(
randomKey);
model.addAttribute("projectId", projectId);
model.addAttribute("rootList", rootList);
return "project/evaluation_root_history";
}
/**
* 查看报告.
*
* @param id 根历史记录ID
*/
@GetMapping("/reportData")
@ResponseBody
@LogOperation("查看报告")
public OperateResult<ReportBean> report(Integer id) {
final ReportBean reportBean = new ReportBean();
//设置摘要信息
EvaluationRootResult rootResult = rootResultService.getById(id);
reportBean.setResult(rootResult);
//规范化时间
rootResult.setCreateTimeStr(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(rootResult.getCreateTime()));
//获取底层指标数量
int bottomNum = indicatorService.selectNoChildByTopId(rootResult.getIndicatorTopId()).size();
reportBean.setBottomNum(bottomNum);
//设置所在评价集区间名称
List<IndicatorTopLevel> levelList = topLevelService.getTopLevel(rootResult.getIndicatorTopId());
levelList.sort((a, b) -> Double.compare(Double.parseDouble(b.getGrade()),
Double.parseDouble(a.getGrade())));
setLevelName(levelList, rootResult);
reportBean.setLevelList(levelList);
//各维度得分对比
String membership = rootResult.getMembershipScore();
Map<String, Double> membershipMap = membershipScoreMap(membership);
StringBuilder replaceMemberShipBuilder = new StringBuilder();
levelList.forEach(level -> {
if (membershipMap.get(level.getLevelName()) != null) {
replaceMemberShipBuilder.append(level.getLevelName()).append(":")
.append(membershipMap.get(level.getLevelName())).append(";");
level.setActualValue(
Double.parseDouble(level.getGrade()) * membershipMap.get(level.getLevelName()));
}
});
//按照优先级重新覆盖隶属函数对应的值
rootResult.setMembershipScore(replaceMemberShipBuilder.toString());
//设置指标表格
List<EvaluationIndicatorResult> indicatorResultList = evaluationIndicatorResultService.selectByRootId(
id);
reportBean.setDataNodeList(
covertIndicatorResultToReportIndicatorNodeData(indicatorResultList, levelList));
//设置优和良综合
reportBean.setHighRate(highRate(membershipMap));
//设置隶属度pie数据
reportBean.setPieDataList(pieDataList(membershipMap));
//查询一级指标的名称和得分
List<EvaluationIndicatorResult> firstLevelList = evaluationIndicatorResultService.queryListByParentIdAndRandomKey(
rootResult.getIndicatorTopId(),
rootResult.getRandomKey(), rootResult.getId());
//设置柱状图数据
reportBean.setBarDataList(covertIndicatorResultToBarData(firstLevelList));
return OperateResult.success(reportBean);
}
/**
* 导航到报告页面.
*/
@GetMapping("/report")
public String reportPage() {
return "project/evaluation_report";
}
/**
* 导航到历史页面.
*/
@GetMapping("/history")
public String history(Integer projectId, Model model) {
List<EvaluationHistory> list = evaluationHistoryService.queryListByProjectId(
projectId);
model.addAttribute("list", list);
return "project/evaluation_history";
}
}

View File

@ -1,18 +0,0 @@
package com.hshh.evaluation.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
/**
* <p>
* 评估结果表 前端控制器
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
@Controller
@RequestMapping("/evaluation/evaluationResult")
public class EvaluationResultController {
}

View File

@ -1,10 +1,8 @@
package com.hshh.evaluation.controller;
import com.hshh.evaluation.bean.DynamicTable;
import com.hshh.evaluation.bean.MetricComputeRequest;
import com.hshh.evaluation.bean.MetricComputerResponse;
import com.hshh.evaluation.bean.MetricMapperWeightBean;
import com.hshh.evaluation.bean.MetricTableHeaderBean;
import com.hshh.evaluation.bean.PageMetricComputeRequest;
import com.hshh.evaluation.bean.PageMetricComputerResponse;
import com.hshh.evaluation.bean.PageMetricMapperWeightBean;
import com.hshh.evaluation.entity.EvaluationTemplate;
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import com.hshh.evaluation.service.EvaluationTemplateIndicatorWeightService;
@ -12,11 +10,12 @@ import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.system.annotation.LogOperation;
import com.hshh.system.base.entity.TableRelations;
import com.hshh.system.base.service.TableRelationsService;
import com.hshh.system.common.Strings.StringOperationUtil;
import com.hshh.system.common.algorithm.AhpNode;
import com.hshh.system.common.algorithm.AhpTreeCompute;
import com.hshh.system.common.Strings.StringUtil;
import com.hshh.system.algorithm.ahp.AhpNode;
import com.hshh.system.algorithm.ahp.AhpTreeCompute;
import com.hshh.system.common.bean.JsTree;
import com.hshh.system.common.bean.OperateResult;
import com.hshh.system.common.bean.PaginationBean;
@ -25,7 +24,6 @@ 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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
@ -122,6 +120,7 @@ public class EvaluationTemplateController extends AssistantTemplateController {
*/
@ResponseBody
@PostMapping("/save")
@LogOperation("保存模板")
public OperateResult<Object> save(@Valid @RequestBody EvaluationTemplate evaluationTemplate,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
@ -141,29 +140,19 @@ public class EvaluationTemplateController extends AssistantTemplateController {
evaluationTemplate.setCreateTime(LocalDateTime.now());
}
// //1.合并权重数据
// Map<Integer, DraftWeightData> unitWeightDataMap = unitWeightMap(evaluationTemplate);
//
// //2.填充数据;如果一个指标下面只有一个孩子则默认填充为1
// List<Indicator> lonelyChild = indicatorService.queryLonelyChild(
// evaluationTemplate.getIndicatorTopId());
//
// paddingDefaultValueWhenParentHasOnlyLonelyChild(lonelyChild, unitWeightDataMap);
// //4. 提交
// evaluationTemplateService.saveWhole(evaluationTemplate, unitWeightDataMap);
evaluationTemplateService.saveWhole(evaluationTemplate);
return OperateResult.success();
}
/**
* 根据id删除指标.
* 根据id删除模板.
*
* @param id 要删除的ID
* @return 操作结果
*/
@GetMapping("/remove/{id}")
@ResponseBody
@LogOperation("删除模板")
public OperateResult<Object> remove(@PathVariable("id") Integer id) {
List<TableRelations> reledList = tableRelationsService.queryRel(id,
"m_data_evaluation_template");
@ -206,8 +195,9 @@ public class EvaluationTemplateController extends AssistantTemplateController {
return OperateResult.success(new ArrayList<>());
}
//查询指标权重树
Map<Integer, Double> weightMap = evaluationTemplateIndicatorWeightService.getEvaluationTemplateIndicatorWeightMap(
Map<Integer, String> weightMap = evaluationTemplateIndicatorWeightService.getEvaluationTemplateIndicatorWeightMap(
id, templateId);
//获取互比信息
Map<Integer, List<EvaluationTemplateWeight>> weightDetailMap = evaluationTemplateDetailWeightService.groupByParentIndicatorId(
id, templateId);
List<JsTree> jsTreeList = indicatorService.metricTree(id);
@ -216,20 +206,22 @@ public class EvaluationTemplateController extends AssistantTemplateController {
return OperateResult.success(jsTreeList);
}
private void setWeightToTree(List<JsTree> jsTreeList, Map<Integer, Double> weightMap,
private void setWeightToTree(List<JsTree> jsTreeList, Map<Integer, String> weightMap,
Map<Integer, List<EvaluationTemplateWeight>> weightDetailMap) {
if (jsTreeList == null) {
return;
}
jsTreeList.forEach(tree -> {
if (weightMap.containsKey((int) tree.getOriginalId())) {
tree.getData().put("weight", weightMap.get((int) tree.getOriginalId()));
tree.getData().put("weight", weightMap.get((int) tree.getOriginalId()));
tree.setText(tree.getText() + "(" + weightMap.get((int) tree.getOriginalId()) + ")");
} else {
tree.setText(tree.getText() + "(1)");
tree.getData().put("weight", 1);
}
if (weightDetailMap.containsKey((int) tree.getOriginalId())) {
tree.getData().put("weightDetail",
tree.getData().put("children",
convertEvaluationTemplateWeightList(weightDetailMap.get((int) tree.getOriginalId())));
}
setWeightToTree(tree.getChildren(), weightMap, weightDetailMap);
@ -237,8 +229,6 @@ public class EvaluationTemplateController extends AssistantTemplateController {
}
/**
* 获取指标编辑/新增时的table表格.
*
@ -262,13 +252,13 @@ public class EvaluationTemplateController extends AssistantTemplateController {
*/
@PostMapping("/computer")
@ResponseBody
public synchronized OperateResult<List<MetricComputerResponse>> computerWeight(
@RequestBody MetricComputeRequest requests) {
public synchronized OperateResult<List<PageMetricComputerResponse>> computerWeight(
@RequestBody PageMetricComputeRequest requests) {
//如果没有值直接静默返回
if (requests.getWeightList() == null || requests.getWeightList().isEmpty()) {
return OperateResult.success();
}
final List<MetricComputerResponse> responseList = new ArrayList<>();
final List<PageMetricComputerResponse> responseList = new ArrayList<>();
List<String> metricList = requests.getMetric();
final AhpNode H = new AhpNode("H");
@ -276,15 +266,15 @@ public class EvaluationTemplateController extends AssistantTemplateController {
AhpNode m = new AhpNode(metric);
H.add(m);
});
List<List<MetricMapperWeightBean>> weightList = requests.getWeightList();
List<List<PageMetricMapperWeightBean>> weightList = requests.getWeightList();
double[][] data = StringOperationUtil.toDoubleArray(
double[][] data = StringUtil.toDoubleArray(
convertMetricMapperWeightBeanListToStrArray(weightList));
H.setLocalMatrix(data);
H.globalWeight = 1; // 根节点全局权重=1
AhpTreeCompute.computeTree(H);
H.children.forEach(child -> {
MetricComputerResponse response = new MetricComputerResponse();
PageMetricComputerResponse response = new PageMetricComputerResponse();
response.setId(child.name);
response.setWeight(String.format("%.4f", child.globalWeight));
responseList.add(response);

View File

@ -1,18 +0,0 @@
package com.hshh.evaluation.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
/**
* <p>
* 前端控制器
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
@Controller
@RequestMapping("/evaluation/evaluationTemplateIndicatorWeight")
public class EvaluationTemplateIndicatorWeightController {
}

View File

@ -1,10 +1,14 @@
package com.hshh.evaluation.entity;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import com.hshh.system.common.bean.BaseBean;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
/**
@ -15,7 +19,7 @@ import lombok.Data;
*/
@TableName("m_data_evaluation_csv_data")
@Data
public class EvaluationCsvData implements Serializable {
public class EvaluationCsvData extends BaseBean {
private static final long serialVersionUID = 1L;
@ -36,5 +40,15 @@ public class EvaluationCsvData implements Serializable {
private Integer userId;
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("seq", getSeq());
JSONObject jsonObj = JSON.parseObject(rawData, JSONObject.class);
map.putAll(jsonObj);
return map;
}
private String csvHeader;
}

View File

@ -32,4 +32,8 @@ public class EvaluationHistory implements Serializable {
private Integer userId;
private String randomKey;
private String datasourceType;
private String membershipFunc;
private String fuzzyOperator;
}

View File

@ -1,45 +0,0 @@
package com.hshh.evaluation.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 评估结果表.
*
* @author liDongYu
* @since 2025-08-21
*/
@TableName("m_data_evaluation_result")
@Data
public class EvaluationResult implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer historyId;
private Integer projectId;
private String rawData;
private String resultData;
private LocalDateTime createTime;
private String randomKey;
/**
* 得分.
*/
private Double finalScore;
}

View File

@ -4,7 +4,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.hshh.evaluation.bean.MetricMapperWeightBean;
import com.hshh.evaluation.bean.PageIndicatorWeight;
import com.hshh.evaluation.bean.PageMetricMapperWeightBean;
import com.hshh.system.common.bean.BaseBean;
import java.time.LocalDateTime;
import java.util.List;
@ -47,12 +48,16 @@ public class EvaluationTemplate extends BaseBean {
private String indicatorTopName;
@TableField(exist = false)
private String templateStatusName;
//提交时当前页面对应的指标id,其余指标从缓存取
@TableField(exist = false)
private List<List<MetricMapperWeightBean>> currentPagePartData;
//暂存指标权重的key
@TableField(exist = false)
private String draftKey;
/**
* 前端传递的各个指标的权重.
*/
@TableField(exist = false)
private List<PageIndicatorWeight> indicatorWeightList;
/**
* 前端传递的各个指标之间对比权重信息.
*/
@TableField(exist = false)
private List<PageMetricMapperWeightBean> templateDetailWeightList;
}

View File

@ -1,16 +1,40 @@
package com.hshh.evaluation.mapper;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.hshh.system.common.bean.PaginationBean;
import java.util.List;
import org.apache.ibatis.annotations.Param;
/**
* <p>
* Mapper 接口
* </p>
* 上传的评估数据csv原始数据记录.
*
* @author liDongYu
* @since 2025-08-21
*/
public interface EvaluationCsvDataMapper extends BaseMapper<EvaluationCsvData> {
/**
* 列表信息.
*
* @param paginationBean 请求参数
* @return 结果列表
*/
List<EvaluationCsvData> list(PaginationBean paginationBean);
/**
* 总条数.
*
* @param paginationBean 请求参数
* @return 总条目
*/
Long count(PaginationBean paginationBean);
/**
* 根据id集合查找记录.
*
* @param ids id集合
* @return 结果列表
*/
List<EvaluationCsvData> selectByIds(@Param("ids") List<Integer> ids);
}

View File

@ -1,7 +1,8 @@
package com.hshh.evaluation.mapper;
import com.hshh.evaluation.entity.EvaluationHistory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hshh.evaluation.entity.EvaluationHistory;
import java.util.List;
/**
* <p>
@ -13,4 +14,11 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface EvaluationHistoryMapper extends BaseMapper<EvaluationHistory> {
/**
* 根据项目查询最新的历史记录.
*
* @param projectId 工程ID
* @return 最新的历史记录
*/
List<EvaluationHistory> queryRecentByProjectId(Integer projectId);
}

View File

@ -1,16 +0,0 @@
package com.hshh.evaluation.mapper;
import com.hshh.evaluation.entity.EvaluationResult;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 评估结果表 Mapper 接口
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
public interface EvaluationResultMapper extends BaseMapper<EvaluationResult> {
}

View File

@ -0,0 +1,21 @@
package com.hshh.evaluation.service;
import com.hshh.evaluation.bean.PageEvaluationRequest;
/**
* 核心评估服务.
*
* @author LiDongYU
* @since 2025/7/22
*/
public interface CoreEvaluationService {
/**
* 核心评估算法.
*
* @param request 评估请求
* @param userId 用户 ID
*/
void evaluation(PageEvaluationRequest request, Integer userId);
}

View File

@ -0,0 +1,50 @@
package com.hshh.evaluation.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.bean.CsvUploadBean;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.hshh.system.common.bean.PaginationBean;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
/**
* 保存每次评估时上传的csv数据.
*
* @author liDongYu
* @since 2025-08-21
*/
public interface EvaluationCsvDataService extends IService<EvaluationCsvData> {
/**
* 保存用户上传的csv文件记录.
*
* @param dataList csv解析后的数据
* @param uploadInfo 附加表单信息
*/
void saveWhole(List<Map<String, String>> dataList, CsvUploadBean uploadInfo);
/**
* 列表信息.
*
* @param paginationBean 请求参数
* @return 结果列表
*/
List<EvaluationCsvData> list(PaginationBean paginationBean);
/**
* 总条数.
*
* @param paginationBean 请求参数
* @return 总条目
*/
Long count(PaginationBean paginationBean);
/**
* 根据id集合查找记录.
*
* @param ids id集合
* @return 结果列表
*/
List<EvaluationCsvData> selectByIds(@Param("ids") List<Integer> ids);
}

View File

@ -1,7 +1,9 @@
package com.hshh.evaluation.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.bean.PageEvaluationRequest;
import com.hshh.evaluation.entity.EvaluationHistory;
import java.util.List;
/**
* 评估记录表 服务类.
@ -11,4 +13,26 @@ import com.hshh.evaluation.entity.EvaluationHistory;
*/
public interface EvaluationHistoryService extends IService<EvaluationHistory> {
/**
* 保存评估历史.
*
* @param request 评估请求
*/
void saveWhole(PageEvaluationRequest request);
/**
* 根据项目查询最新的历史记录.
*
* @param projectId 工程ID
* @return 最新的历史记录
*/
EvaluationHistory queryRecentByProjectId(Integer projectId);
/**
* 查询工程评估的历史记录.
*
* @param projectId 工程ID
* @return 历史记录
*/
List<EvaluationHistory> queryListByProjectId(Integer projectId);
}

View File

@ -1,9 +1,11 @@
package com.hshh.evaluation.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.bean.EvaluationRequest;
import com.hshh.evaluation.bean.CsvUploadBean;
import com.hshh.evaluation.bean.PageEvaluationRequest;
import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.system.common.bean.PaginationBean;
import java.io.IOException;
import java.util.List;
/**
@ -59,5 +61,12 @@ public interface EvaluationProjectService extends IService<EvaluationProject> {
* @param request 评估请求
* @param userId 用户ID
*/
void evaluate(EvaluationRequest request, Integer userId);
void evaluate(PageEvaluationRequest request, Integer userId);
/**
* 上传csv文件.
*
* @param uploadInfo csv文件信息
*/
void uploadCsv(CsvUploadBean uploadInfo) throws IOException;
}

View File

@ -1,16 +0,0 @@
package com.hshh.evaluation.service;
import com.hshh.evaluation.entity.EvaluationResult;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 评估结果表 服务类
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
public interface EvaluationResultService extends IService<EvaluationResult> {
}

View File

@ -2,6 +2,7 @@ package com.hshh.evaluation.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.entity.EvaluationTemplateIndicatorWeight;
import java.util.List;
import java.util.Map;
/**
@ -17,9 +18,27 @@ public interface EvaluationTemplateIndicatorWeightService extends
* 获取指标对应的权重.
*
* @param indicatorTopId 指标ID
* @param templateId 模板 ID
* @param templateId 模板 ID
* @return 权重信息
*/
public Map<Integer, Double> getEvaluationTemplateIndicatorWeightMap(Integer indicatorTopId,
Map<Integer, String> getEvaluationTemplateIndicatorWeightMap(Integer indicatorTopId,
Integer templateId);
/**
* 保存指标权重信息.
*
* @param templateId 模板ID
* @param indicatorTopId 顶级指标ID
* @param list 指标权重集合
*/
void saveEvaluationTemplateIndicatorWeight(Integer templateId, Integer indicatorTopId,
List<EvaluationTemplateIndicatorWeight> list);
/**
* 删除指标权重信息.
*
* @param templateId 模板 ID
* @param indicatorTopId 顶级指标ID
*/
void deleteByTemplateIdAndIndicatorTopId(Integer templateId, Integer indicatorTopId);
}

View File

@ -34,10 +34,10 @@ public interface EvaluationTemplateService extends IService<EvaluationTemplate>
* 保存评估模板数据.
*
* @param evaluationTemplate 评估模板
* @param weightData 权重
*/
void saveWhole(EvaluationTemplate evaluationTemplate,
Map<Integer, DraftWeightData> weightData);
void saveWhole(EvaluationTemplate evaluationTemplate
);
/**
* 根据名称查询列表.

View File

@ -14,24 +14,6 @@ import java.util.Map;
public interface EvaluationTemplateWeightService extends IService<EvaluationTemplateWeight> {
/**
* 根据模板id+指标id删除.
*
* @param templateId 模板ID
* @param indicatorTopId 顶级指标ID
* @param parentId 上级指标ID
*/
void deleteEvaluationTemplateWeightWithTemplateIdAndIndicatorId(Integer templateId,
Integer indicatorTopId,
Integer parentId);
/**
* 根据模板ID删除权重信息.
*
* @param templateId 模板ID
*/
void deleteEvaluationTemplateWeightWithTemplateId(Integer templateId);
/**
* 根据指标父ID和模板id获取权重列表.
*
@ -42,6 +24,16 @@ public interface EvaluationTemplateWeightService extends IService<EvaluationTemp
List<EvaluationTemplateWeight> queryListByIndicatorParentIdAndTemplateId(
Integer indicatorParentId, Integer templateId);
/**
* 根据顶级ID和模板id获取权重列表.
*
* @param indicatorTopId 指标顶级ID
* @param templateId 模板ID
* @return 权重列表
*/
List<EvaluationTemplateWeight> queryListByIndicatorTopIdAndTemplateId(
Integer indicatorTopId, Integer templateId);
/**
* 指标对应的权重详情按照父指标ID分类.
*
@ -51,4 +43,23 @@ public interface EvaluationTemplateWeightService extends IService<EvaluationTemp
*/
Map<Integer, List<EvaluationTemplateWeight>> groupByParentIndicatorId(Integer indicatorParentId,
Integer templateId);
/**
* 保存指标和指标之间的矩阵信息.
*
* @param templateId 模板ID
* @param indicatorTopId 指标ID
* @param weightList 互比矩阵列表
*/
void saveIndicatorMatrix(Integer templateId, Integer indicatorTopId,
List<EvaluationTemplateWeight> weightList);
/**
* 删除互比信息.
*
* @param templateId 模板ID
* @param indicatorTopId 顶级指标ID
*/
void deleteByTemplateIdAndIndicatorTopId(Integer templateId, Integer indicatorTopId);
}

View File

@ -1,16 +0,0 @@
package com.hshh.evaluation.service;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
public interface IEvaluationCsvDataService extends IService<EvaluationCsvData> {
}

View File

@ -0,0 +1,514 @@
package com.hshh.evaluation.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.hshh.evaluation.bean.PageEvaluationRequest;
import com.hshh.evaluation.bean.SingleEvaluationData;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.hshh.evaluation.entity.EvaluationTemplate;
import com.hshh.evaluation.service.CoreEvaluationService;
import com.hshh.evaluation.service.EvaluationCsvDataService;
import com.hshh.evaluation.service.EvaluationHistoryService;
import com.hshh.evaluation.service.EvaluationRootResultService;
import com.hshh.evaluation.service.EvaluationTemplateIndicatorWeightService;
import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.entity.IndicatorBottomCsvMapper;
import com.hshh.indicator.entity.IndicatorBottomFormMapper;
import com.hshh.indicator.entity.IndicatorEvalItem;
import com.hshh.indicator.entity.IndicatorTopLevel;
import com.hshh.indicator.entity.IndicatorTopSet;
import com.hshh.indicator.service.IndicatorBottomCsvMapperService;
import com.hshh.indicator.service.IndicatorBottomFormMapperService;
import com.hshh.indicator.service.IndicatorEvalItemService;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.indicator.service.IndicatorTopLevelService;
import com.hshh.indicator.service.IndicatorTopSetService;
import com.hshh.model.entity.FormValue;
import com.hshh.model.service.FormValueService;
import com.hshh.system.algorithm.fuzzy.EvaluationLevel;
import com.hshh.system.algorithm.fuzzy.FuzzyEvaluationService;
import com.hshh.system.algorithm.fuzzy.FuzzyOperator;
import com.hshh.system.algorithm.fuzzy.GlobalEvaluationConfig;
import com.hshh.system.algorithm.fuzzy.GlobalMembershipFunction;
import com.hshh.system.algorithm.fuzzy.GradeRange;
import com.hshh.system.algorithm.fuzzy.IndicatorNode;
import com.hshh.system.algorithm.fuzzy.LeafFullTriangularMembershipFunction;
import com.hshh.system.algorithm.fuzzy.LeafHybridMembershipFunction;
import com.hshh.system.common.bean.PaginationBean;
import com.hshh.system.ws.WsEvaluationServer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 模糊综合评估服务类 (重构后).
*
* @author LiDongYU
* @since 2025/7/22
*/
@Service
public class CoreEvaluationServiceImpl implements CoreEvaluationService {
@Resource
private EvaluationTemplateService evaluationTemplateService;
@Resource
private IndicatorTopSetService indicatorTopSetService;
@Resource
private IndicatorService indicatorService;
@Resource
private IndicatorTopLevelService indicatorTopLevelService;
@Resource
private IndicatorBottomFormMapperService indicatorBottomFormMapperService;
@Resource
private IndicatorBottomCsvMapperService indicatorBottomCsvMapperService;
@Resource
private FormValueService formValueService;
@Resource
private EvaluationCsvDataService evaluationCsvDataService;
@Resource
private EvaluationTemplateIndicatorWeightService evaluationTemplateIndicatorWeightService;
@Resource
private IndicatorEvalItemService indicatorEvalItemService; //底部指标评价集区间
@Resource
private EvaluationHistoryService evaluationHistoryService; //评估历史批次记录
@Resource
private EvaluationRootResultService evaluationRootResultService; //评估详情及概况记录
// 定义分页大小常量便于管理
private static final int DEFAULT_PAGE_SIZE = 100;
/**
* 评估主入口方法 通过策略模式分发到不同的数据源处理器.
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void evaluation(PageEvaluationRequest request, Integer userId) {
long now = System.currentTimeMillis();
try {
// 1. 获取评估所需的通用前置数据模板隶属度评价集指标树
EvaluationTemplate template = getTemplateOrThrow(request.getTemplateId());
Integer indicatorTopId = template.getIndicatorTopId();
request.setIndicatorTopId(indicatorTopId);
IndicatorTopSet membershipFuncSet = getMembershipFuncSetOrThrow(indicatorTopId); //隶属函数设置
List<IndicatorTopLevel> topLevelList = getTopLevelListOrThrow(indicatorTopId); //评价集区间
Indicator indicator = indicatorService.getIndicator(indicatorTopId); //获取指标
Map<Integer, String> indicatorWeightMap = evaluationTemplateIndicatorWeightService.getEvaluationTemplateIndicatorWeightMap(
indicatorTopId, template.getId()); //指标权重map
Map<Integer, List<IndicatorEvalItem>> indicatorEvalItemMap = indicatorEvalItemService.getIndicatorEvalItemMapByIndicatorTopId(
indicatorTopId); //底部指标评价范围
// 2. 根据数据源类型选择并执行相应的策略
String dataSourceType = request.getDatasourceType();
if ("database".equals(dataSourceType)) {
// 获取数据库数据源的特定映射关系
List<IndicatorBottomFormMapper> mapperList = indicatorBottomFormMapperService.selectIndicatorToFieldNameListByIndicatorTopId(
indicatorTopId);
if (mapperList == null || mapperList.isEmpty()) {
throw new EvaluationException("设施字段和指标没有设置映射关系");
}
Map<Integer, String> mappingMap = mapperList.stream().collect(
Collectors.toMap(IndicatorBottomFormMapper::getIndicatorId,
IndicatorBottomFormMapper::getFiledName));
// 创建数据库策略并执行
DataSourceStrategy<FormValue> dbStrategy = new DatabaseSourceStrategy(formValueService,
FormValue::getModelData);
processEvaluation(request, userId, membershipFuncSet, topLevelList, indicator, mappingMap,
dbStrategy, indicatorWeightMap, indicatorEvalItemMap);
} else if ("csv".equals(dataSourceType)) {
// 获取CSV数据源的特定映射关系
List<IndicatorBottomCsvMapper> mapperList = indicatorBottomCsvMapperService.selectIndicatorToFieldNameListByIndicatorTopId(
indicatorTopId);
if (mapperList == null || mapperList.isEmpty()) {
throw new EvaluationException("CSV列和指标没有设置映射关系");
}
Map<Integer, String> mappingMap = mapperList.stream().collect(
Collectors.toMap(IndicatorBottomCsvMapper::getIndicatorId,
IndicatorBottomCsvMapper::getCsvColumnName));
// 创建CSV策略并执行
DataSourceStrategy<EvaluationCsvData> csvStrategy = new CsvSourceStrategy(
evaluationCsvDataService, EvaluationCsvData::getRawData);
processEvaluation(request, userId, membershipFuncSet, topLevelList, indicator, mappingMap,
csvStrategy, indicatorWeightMap, indicatorEvalItemMap);
} else {
throw new EvaluationException("不支持的数据源类型: " + dataSourceType);
}
} catch (EvaluationException e) {
WsEvaluationServer.sendTips(e.getMessage(), request.getRandomKey());
}
long end = System.currentTimeMillis();
long diff = (end - now) / 1000;
WsEvaluationServer.sendTips("整体评估完成,总耗时" + diff + "", request.getRandomKey());
}
/**
* 统一的评估处理流程与具体数据源解耦.
*
* @param strategy 数据源策略封装了如何获取数据的具体实现
*/
private <T> void processEvaluation(PageEvaluationRequest request, Integer userId,
IndicatorTopSet membershipFuncSet, List<IndicatorTopLevel> topLevelList,
Indicator indicator, Map<Integer, String> mappingMap,
DataSourceStrategy<T> strategy, Map<Integer, String> indicatorWeightMap,
Map<Integer, List<IndicatorEvalItem>> indicatorEvalItemMap) {
//首先记录本次评估
request.setMembership(membershipFuncSet.getMembershipFunc());
evaluationHistoryService.saveWhole(request);
boolean hasSpecificIds = request.getIds() != null && request.getIds().length > 0;
if (hasSpecificIds) {
// --- 按指定ID列表进行评估 ---
List<Integer> idList = Arrays.stream(request.getIds()).boxed().collect(Collectors.toList());
List<T> dataList = strategy.fetchByIds(idList);
if (dataList != null && !dataList.isEmpty()) {
List<SingleEvaluationData> evaluationData = strategy.extractData(dataList);
startEvaluation(request, userId, membershipFuncSet, topLevelList, indicator, evaluationData,
mappingMap, indicatorWeightMap, indicatorEvalItemMap);
}
} else {
// --- 分页处理所有数据进行评估 ---
PaginationBean paginationBean = new PaginationBean();
paginationBean.setSearch(request.getSearch());
paginationBean.setPageSize(DEFAULT_PAGE_SIZE);
paginationBean.setRandomKey(request.getRandomKey());
paginationBean.setBusinessKey(request.getModelId());
Long totalRecords = strategy.count(paginationBean);
if (totalRecords == null || totalRecords == 0) {
return; // 没有数据直接返回
}
int totalPages = (int) ((totalRecords + DEFAULT_PAGE_SIZE - 1) / DEFAULT_PAGE_SIZE);
for (int currentPage = 1; currentPage <= totalPages; currentPage++) {
paginationBean.setCurrentPage(currentPage);
List<T> currentPageData = strategy.fetchByPage(paginationBean);
if (currentPageData != null && !currentPageData.isEmpty()) {
List<SingleEvaluationData> evaluationData = strategy.extractData(currentPageData);
startEvaluation(request, userId, membershipFuncSet, topLevelList, indicator,
evaluationData, mappingMap, indicatorWeightMap, indicatorEvalItemMap);
}
}
}
}
/**
* 数据源策略接口 定义了数据访问的统一契约将数据获取的算法从主流程中分离出来.
*
* @param <T> 数据源对应的实体类型 ( FormValue, EvaluationCsvData)
*/
private interface DataSourceStrategy<T> {
List<T> fetchByIds(List<Integer> ids);
List<T> fetchByPage(PaginationBean paginationBean);
Long count(PaginationBean paginationBean);
List<SingleEvaluationData> extractData(List<T> dataList);
}
/**
* 数据库数据源的具体实现策略.
*/
private static class DatabaseSourceStrategy implements DataSourceStrategy<FormValue> {
private final FormValueService service;
private final Function<FormValue, String> dataExtractor;
public DatabaseSourceStrategy(FormValueService service,
Function<FormValue, String> dataExtractor) {
this.service = service;
this.dataExtractor = dataExtractor;
}
@Override
public List<FormValue> fetchByIds(List<Integer> ids) {
return service.selectByIds(ids);
}
@Override
public List<FormValue> fetchByPage(PaginationBean paginationBean) {
return service.list(paginationBean);
}
@Override
public Long count(PaginationBean paginationBean) {
return service.count(paginationBean);
}
@Override
public List<SingleEvaluationData> extractData(List<FormValue> dataList) {
List<SingleEvaluationData> list = new ArrayList<>();
dataList.forEach(value -> {
SingleEvaluationData singleEvaluationData = new SingleEvaluationData();
singleEvaluationData.setData(value.getModelData());
singleEvaluationData.setId(value.getId());
singleEvaluationData.setType("database");
list.add(singleEvaluationData);
});
return list;
}
}
/**
* CSV文件数据源的具体实现策略.
*/
private static class CsvSourceStrategy implements DataSourceStrategy<EvaluationCsvData> {
private final EvaluationCsvDataService service;
private final Function<EvaluationCsvData, String> dataExtractor;
public CsvSourceStrategy(EvaluationCsvDataService service,
Function<EvaluationCsvData, String> dataExtractor) {
this.service = service;
this.dataExtractor = dataExtractor;
}
@Override
public List<EvaluationCsvData> fetchByIds(List<Integer> ids) {
return service.selectByIds(ids);
}
@Override
public List<EvaluationCsvData> fetchByPage(PaginationBean paginationBean) {
return service.list(paginationBean);
}
@Override
public Long count(PaginationBean paginationBean) {
return service.count(paginationBean);
}
@Override
public List<SingleEvaluationData> extractData(List<EvaluationCsvData> dataList) {
List<SingleEvaluationData> list = new ArrayList<>();
dataList.forEach(value -> {
SingleEvaluationData singleEvaluationData = new SingleEvaluationData();
singleEvaluationData.setId(value.getId());
singleEvaluationData.setType("csv");
singleEvaluationData.setData(value.getRawData());
list.add(singleEvaluationData);
});
return list;
}
}
private EvaluationTemplate getTemplateOrThrow(Integer templateId) {
EvaluationTemplate template = evaluationTemplateService.getById(templateId);
if (template == null) {
throw new EvaluationException("没有找到对应的指标信息");
}
return template;
}
private IndicatorTopSet getMembershipFuncSetOrThrow(Integer indicatorTopId) {
IndicatorTopSet set = indicatorTopSetService.getIndicatorTopSet(indicatorTopId);
if (set == null) {
throw new EvaluationException("没有找到对应的隶属度函数");
}
return set;
}
private List<IndicatorTopLevel> getTopLevelListOrThrow(Integer indicatorTopId) {
List<IndicatorTopLevel> list = indicatorTopLevelService.getTopLevel(indicatorTopId);
if (list == null || list.isEmpty()) {
throw new EvaluationException("没有找到全局指标集区间和分值");
}
return list;
}
private static class EvaluationException extends RuntimeException {
public EvaluationException(String message) {
super(message);
}
}
private GlobalEvaluationConfig globalEvaluationConfig(List<IndicatorTopLevel> topLevelList) {
topLevelList.sort((a, b) -> Integer.compare(b.getLevelOrder(), a.getLevelOrder()));
List<EvaluationLevel> evaluationLevels = new ArrayList<>();
topLevelList.forEach(topLevel -> {
EvaluationLevel evaluationLevel = new EvaluationLevel(topLevel.getLevelName(),
Double.parseDouble(topLevel.getGrade()));
evaluationLevels.add(evaluationLevel);
});
return new GlobalEvaluationConfig(evaluationLevels);
}
/**
* 核心评估触发点此方法保持不变.
*/
private void startEvaluation(PageEvaluationRequest request, Integer userId,
IndicatorTopSet membershipFuncSet, List<IndicatorTopLevel> topLevelList,
Indicator indicator, List<SingleEvaluationData> originalList, Map<Integer, String> mappingMap,
Map<Integer, String> indicatorWeightMap,
Map<Integer, List<IndicatorEvalItem>> indicatorEvalItemMap) {
long now = System.currentTimeMillis();
WsEvaluationServer.sendTips("分批次评估" + originalList.size() + "条记录",
request.getRandomKey());
List<IndicatorNode> resultList = new ArrayList<>();
//全局level等级及代表分
GlobalEvaluationConfig globalEvaluationConfig = globalEvaluationConfig(topLevelList);
//遍历数据
for (SingleEvaluationData singleEvaluationData : originalList) {
JSONObject jsonObject = JSON.parseObject(singleEvaluationData.getData());
Map<String, Object> valueMap = new HashMap<>(jsonObject);
IndicatorNode root = new IndicatorNode(indicator.getId() + "", indicator.getName());
//工程ID
root.setProjectId(request.getProjectId());
//记录原始数据ID
root.setOriginalId(indicator.getId());
//记录数据来源
root.setType(request.getDatasourceType());
//本批次唯一标识
root.setRandomKey(request.getRandomKey());
//用户id
root.setUserId(userId);
//设置标识
root.setLogicName(jsonObject.getString(request.getIdentifier()) == null ? ""
: jsonObject.getString(request.getIdentifier()));
root.setLevel(0);
//设置隶属函数
root.setMembershipFunction(new GlobalMembershipFunction(membershipFuncSet.getMembershipFunc(),
Double.parseDouble(membershipFuncSet.getTriangleOverlapRatio()),
Double.parseDouble(membershipFuncSet.getTrianglePeakRatio()),
Double.parseDouble(membershipFuncSet.getSoftEdgeS()),
globalEvaluationConfig));
//设置权重
root.setWeight(indicatorWeightMap.get(indicator.getId()) == null ? 1
: Double.parseDouble(indicatorWeightMap.get(indicator.getId())));
recursionBuild(valueMap, indicatorWeightMap, indicator, root, mappingMap,
globalEvaluationConfig, membershipFuncSet, indicatorEvalItemMap);
FuzzyEvaluationService service = new FuzzyEvaluationService(globalEvaluationConfig);
service.performFuzzyEvaluation(root, FuzzyOperator.valueOf(request.getMethod()));
resultList.add(root);
}
saveResult(resultList);
long end = System.currentTimeMillis();
long diff = (end - now) / 1000;
WsEvaluationServer.sendTips(
"分批次结束评估" + originalList.size() + "条记录,耗时:" + diff + "",
request.getRandomKey());
}
private void saveResult(List<IndicatorNode> resultList) {
resultList.forEach(indicatorNode -> {
evaluationRootResultService.saveWhole(indicatorNode);
});
}
/**
* 递归一个根指标设置实际值隶属度权重.
*
* @param valueMap 实际值map key=字段名称
* @param indicatorWeightMap 权重map key=指标ID
* @param indicator 当前指标
* @param node 当前节点
* @param mappingMap 指标id对应字段名称
* @param globalEvaluationConfig 全局评价集集合
* @param membershipFuncSet 全局隶属函数设置告知采用那个函数
* @param indicatorEvalItemMap 底部指标评价集区间
*/
private void recursionBuild(Map<String, Object> valueMap,
Map<Integer, String> indicatorWeightMap,
Indicator indicator, IndicatorNode node, Map<Integer, String> mappingMap,
GlobalEvaluationConfig globalEvaluationConfig, IndicatorTopSet membershipFuncSet,
Map<Integer, List<IndicatorEvalItem>> indicatorEvalItemMap) {
if (indicator.getChildren() != null && !indicator.getChildren().isEmpty()) {
node.setChildren(new ArrayList<>());
indicator.getChildren().forEach(child -> {
IndicatorNode childNode = new IndicatorNode(child.getId() + "", child.getName());
childNode.setLevel(node.getLevel() + 1);
//设置隶属函数
childNode.setMembershipFunction(
new GlobalMembershipFunction(membershipFuncSet.getMembershipFunc(),
Double.parseDouble(membershipFuncSet.getTriangleOverlapRatio()),
Double.parseDouble(membershipFuncSet.getTrianglePeakRatio()),
Double.parseDouble(membershipFuncSet.getSoftEdgeS()),
globalEvaluationConfig));
//设置权重
childNode.setWeight(indicatorWeightMap.get(child.getId()) == null ? 1
: Double.parseDouble(indicatorWeightMap.get(child.getId())));
childNode.setOriginalId(child.getId());
childNode.setRandomKey(node.getRandomKey());
node.getChildren().add(childNode);
recursionBuild(valueMap, indicatorWeightMap, child, childNode, mappingMap,
globalEvaluationConfig, membershipFuncSet, indicatorEvalItemMap);
});
} else { //是叶子节点设置值设置
//根据mappingMap获取这个指标对应的输入字段
String fieldName = mappingMap.get(indicator.getId());
if (fieldName != null) {
Object currentValue = valueMap.get(fieldName);
if (currentValue != null) {
node.setActualValue(Double.parseDouble(currentValue.toString()));
}
}
if (indicatorEvalItemMap.get(indicator.getId()) != null) {
List<IndicatorEvalItem> indicatorEvalItemList = indicatorEvalItemMap.get(indicator.getId());
List<GradeRange> gradeRanges = getGradeRange(indicatorEvalItemList);
if (membershipFuncSet.getMembershipFunc().equals("TRAP_TRI")) {
node.setMembershipFunction(new LeafHybridMembershipFunction(gradeRanges,
Double.parseDouble(membershipFuncSet.getTriangleOverlapRatio()),
Double.parseDouble(membershipFuncSet.getTrianglePeakRatio()),
Double.parseDouble(membershipFuncSet.getSoftEdgeS()),
globalEvaluationConfig));
} else {
node.setMembershipFunction(new LeafFullTriangularMembershipFunction(gradeRanges,
Double.parseDouble(membershipFuncSet.getTriangleOverlapRatio()),
Double.parseDouble(membershipFuncSet.getTrianglePeakRatio()),
Double.parseDouble(membershipFuncSet.getSoftEdgeS()),
globalEvaluationConfig));
}
}
}
}
private List<GradeRange> getGradeRange(List<IndicatorEvalItem> indicatorEvalItemList) {
List<GradeRange> list = new ArrayList<>();
indicatorEvalItemList.sort(
Comparator.comparingDouble(a -> Double.parseDouble(a.getBottomValue())));
indicatorEvalItemList.forEach(indicatorEvalItem -> {
GradeRange gradeRange = new GradeRange(indicatorEvalItem.getEvaluationName(),
Double.parseDouble(indicatorEvalItem.getBottomValue()),
Double.parseDouble(indicatorEvalItem.getTopValue()));
list.add(gradeRange);
});
return list;
}
}

View File

@ -1,20 +1,61 @@
package com.hshh.evaluation.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.evaluation.bean.CsvUploadBean;
import com.hshh.evaluation.entity.EvaluationCsvData;
import com.hshh.evaluation.mapper.EvaluationCsvDataMapper;
import com.hshh.evaluation.service.IEvaluationCsvDataService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.evaluation.service.EvaluationCsvDataService;
import com.hshh.system.common.bean.PaginationBean;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* 服务实现类
* </p>
* 服务实现类.
*
* @author liDongYu
* @since 2025-08-21
*/
@Service
public class EvaluationCsvDataServiceImpl extends ServiceImpl<EvaluationCsvDataMapper, EvaluationCsvData> implements IEvaluationCsvDataService {
public class EvaluationCsvDataServiceImpl extends
ServiceImpl<EvaluationCsvDataMapper, EvaluationCsvData> implements
EvaluationCsvDataService {
@Transactional(rollbackFor = Exception.class)
@Override
public void saveWhole(List<Map<String, String>> dataList, CsvUploadBean uploadInfo) {
dataList.forEach(map -> {
StringBuilder header = new StringBuilder();
EvaluationCsvData evaluationCsvData = new EvaluationCsvData();
//设置原始数据
evaluationCsvData.setRawData(JSON.toJSONString(map));
//设置随机key
evaluationCsvData.setRandomKey(uploadInfo.getRandomKey());
//时间
evaluationCsvData.setCreateTime(LocalDateTime.now());
//工程
evaluationCsvData.setProjectId(uploadInfo.getProjectId());
//设置表头
evaluationCsvData.setCsvHeader(JSON.toJSONString(map.keySet()));
save(evaluationCsvData);
});
}
@Override
public List<EvaluationCsvData> list(PaginationBean paginationBean) {
return this.baseMapper.list(paginationBean);
}
@Override
public Long count(PaginationBean paginationBean) {
return this.baseMapper.count(paginationBean);
}
@Override
public List<EvaluationCsvData> selectByIds(List<Integer> ids) {
return this.baseMapper.selectByIds(ids);
}
}

View File

@ -1,10 +1,15 @@
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.PageEvaluationRequest;
import com.hshh.evaluation.entity.EvaluationHistory;
import com.hshh.evaluation.mapper.EvaluationHistoryMapper;
import com.hshh.evaluation.service.EvaluationHistoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
@ -15,7 +20,38 @@ import org.springframework.stereotype.Service;
* @since 2025-08-21
*/
@Service
public class EvaluationHistoryServiceImpl extends ServiceImpl<EvaluationHistoryMapper, EvaluationHistory> implements
public class EvaluationHistoryServiceImpl extends
ServiceImpl<EvaluationHistoryMapper, EvaluationHistory> implements
EvaluationHistoryService {
@Transactional
@Override
public void saveWhole(PageEvaluationRequest request) {
EvaluationHistory history = new EvaluationHistory();
history.setCreateTime(LocalDateTime.now());
history.setUserId(request.getUserId());
history.setRandomKey(request.getRandomKey());
history.setProjectId(request.getProjectId());
history.setDatasourceType(request.getDatasourceType());
history.setMembershipFunc(request.getMembership());
history.setFuzzyOperator(request.getMethod());
save(history);
}
@Override
public EvaluationHistory queryRecentByProjectId(Integer projectId) {
List<EvaluationHistory> list = this.baseMapper.queryRecentByProjectId(projectId);
if (!list.isEmpty()) {
return list.get(0);
}
return null;
}
@Override
public List<EvaluationHistory> queryListByProjectId(Integer projectId) {
QueryWrapper<EvaluationHistory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("project_id", projectId);
queryWrapper.orderByDesc("id");
return this.baseMapper.selectList(queryWrapper);
}
}

View File

@ -2,27 +2,30 @@ 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.bean.CsvUploadBean;
import com.hshh.evaluation.bean.PageEvaluationRequest;
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.CoreEvaluationService;
import com.hshh.evaluation.service.EvaluationCsvDataService;
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.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -37,29 +40,25 @@ import org.springframework.transaction.annotation.Transactional;
public class EvaluationProjectServiceImpl extends
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;
/**
* 评估历史服务类.
* 上传的csv文件数据记录服务类.
*/
@Resource
private EvaluationHistoryService evaluationHistoryService;
private EvaluationCsvDataService evaluationCsvDataService;
/**
* 评估模板服务.
* 数据库记录评估服务.
*/
@Resource
private EvaluationTemplateService evaluationTemplateService;
/**
* 数据查询服务类.
*/
@Resource
private FormValueService formValueService;
private CoreEvaluationService coreEvaluationService;
@Override
public List<EvaluationProject> list(PaginationBean paginationBean) {
@ -103,125 +102,47 @@ public class EvaluationProjectServiceImpl extends
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void evaluate(EvaluationRequest request, Integer userId) {
public void evaluate(PageEvaluationRequest request, Integer userId) {
//评估接口单列
coreEvaluationService.evaluation(request, userId);
//查询模板然后获取指标ID
EvaluationTemplate template = evaluationTemplateService.getById(request.getTemplateId());
}
//发送开始处理消息
sendTips("开始处理", request.getRandomKey());
@Override
public void uploadCsv(CsvUploadBean uploadInfo) throws IOException {
CSVFormat format = CSVFormat.DEFAULT
.builder()
.setHeader() // 表示第一行就是表头
.setSkipHeaderRecord(true) // 跳过第一行数据不把它算到记录里
.setIgnoreEmptyLines(true)
.setTrim(true)
.build();
//获取表头信息
List<Map<String, String>> rows = new ArrayList<>();
try (Reader reader = new InputStreamReader(uploadInfo.getFile().getInputStream(),
StandardCharsets.UTF_8);
CSVParser parser = new CSVParser(reader, format)) {
//插入评估历史表
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);
// 取有序表头Apache 返回 Map<列名, 索引>按索引排序得到原始顺序
List<String> headers = parser.getHeaderMap()
.entrySet()
.stream()
.sorted(Comparator.comparingInt(Map.Entry::getValue))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
for (CSVRecord rec : parser) {
Map<String, String> obj = new LinkedHashMap<>();
for (String h : headers) {
// get(h)按列名取值可能为 null
obj.put(h, rec.isMapped(h) ? rec.get(h) : null);
}
rows.add(obj);
}
evaluationCsvDataService.saveWhole(rows, uploadInfo);
}
}
//根据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

@ -1,21 +0,0 @@
package com.hshh.evaluation.service.impl;
import com.hshh.evaluation.entity.EvaluationResult;
import com.hshh.evaluation.mapper.EvaluationResultMapper;
import com.hshh.evaluation.service.EvaluationResultService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 评估结果表 服务实现类
* </p>
*
* @author liDongYu
* @since 2025-08-21
*/
@Service
public class EvaluationResultServiceImpl extends ServiceImpl<EvaluationResultMapper, EvaluationResult> implements
EvaluationResultService {
}

View File

@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
@ -24,7 +25,7 @@ public class EvaluationTemplateIndicatorWeightServiceImpl extends
EvaluationTemplateIndicatorWeightService {
@Override
public Map<Integer, Double> getEvaluationTemplateIndicatorWeightMap(Integer indicatorTopId,
public Map<Integer, String> getEvaluationTemplateIndicatorWeightMap(Integer indicatorTopId,
Integer templateId) {
QueryWrapper<EvaluationTemplateIndicatorWeight> wrapper = new QueryWrapper<>();
wrapper.eq("indicator_top_id", indicatorTopId);
@ -37,4 +38,26 @@ public class EvaluationTemplateIndicatorWeightServiceImpl extends
}
return Map.of();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveEvaluationTemplateIndicatorWeight(Integer templateId, Integer indicatorTopId,
List<EvaluationTemplateIndicatorWeight> list) {
//先删除旧信息
QueryWrapper<EvaluationTemplateIndicatorWeight> wrapper = new QueryWrapper<>();
wrapper.eq("template_id", templateId);
wrapper.eq("indicator_top_id", indicatorTopId);
this.remove(wrapper);
if (list != null && !list.isEmpty()) {
list.forEach(this::save);
}
}
@Override
public void deleteByTemplateIdAndIndicatorTopId(Integer templateId, Integer indicatorTopId) {
QueryWrapper<EvaluationTemplateIndicatorWeight> wrapper = new QueryWrapper<>();
wrapper.eq("template_id", templateId);
wrapper.eq("indicator_top_id", indicatorTopId);
this.remove(wrapper);
}
}

View File

@ -3,13 +3,15 @@ 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.entity.EvaluationTemplate;
import com.hshh.evaluation.entity.EvaluationTemplateIndicatorWeight;
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import com.hshh.evaluation.mapper.EvaluationTemplateMapper;
import com.hshh.evaluation.service.EvaluationTemplateIndicatorWeightService;
import com.hshh.evaluation.service.EvaluationTemplateService;
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
import com.hshh.system.common.bean.PaginationBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,8 +26,16 @@ import org.springframework.transaction.annotation.Transactional;
public class EvaluationTemplateServiceImpl extends
ServiceImpl<EvaluationTemplateMapper, EvaluationTemplate> implements EvaluationTemplateService {
/**
* 指标互比详情服务类.
*/
@Resource
private EvaluationTemplateWeightService evaluationTemplateWeightService;
private EvaluationTemplateWeightService evaluationTemplateDetailWeightService;
/**
* 指标权重服务类.
*/
@Resource
private EvaluationTemplateIndicatorWeightService evaluationTemplateIndicatorWeightService;
@Override
public List<EvaluationTemplate> list(PaginationBean search) {
@ -39,42 +49,52 @@ public class EvaluationTemplateServiceImpl extends
@Transactional(rollbackFor = Exception.class)
@Override
public void saveWhole(EvaluationTemplate evaluationTemplate,
Map<Integer, DraftWeightData> weightDataMap) {
public void saveWhole(EvaluationTemplate evaluationTemplate
) {
//保存或者更新模板信息
saveOrUpdate(evaluationTemplate);
if (weightDataMap != null && !weightDataMap.isEmpty()) {
weightDataMap.forEach((k, v) -> {
//先按模板ID+上级指标ID
evaluationTemplateWeightService.deleteEvaluationTemplateWeightWithTemplateIdAndIndicatorId(
evaluationTemplate.getId(), evaluationTemplate.getIndicatorTopId(), k);
v.getWeight().forEach(weightBeanList -> {
weightBeanList.forEach(weightBean -> {
EvaluationTemplateWeight evaluationTemplateWeight = new EvaluationTemplateWeight();
//设置模板ID
evaluationTemplateWeight.setTemplateId(evaluationTemplate.getId());
//设置权重
evaluationTemplateWeight.setWeight(Double.valueOf(weightBean.getValue()));
//设置顶级指标ID
evaluationTemplateWeight.setIndicatorTopId(evaluationTemplate.getIndicatorTopId());
//fromIndicatorId
evaluationTemplateWeight.setFromIndicatorId(weightBean.getRowId());
//toIndicatorId
evaluationTemplateWeight.setToIndicatorId(weightBean.getColId());
//设置上级指标ID
evaluationTemplateWeight.setIndicatorParentId(weightBean.getParentId());
//设置行号
evaluationTemplateWeight.setRowNum(weightBean.getRowNum());
//保存
evaluationTemplateWeightService.save(evaluationTemplateWeight);
});
});
//保存指标权重信息
List<EvaluationTemplateIndicatorWeight> indicatorWeightList = new ArrayList<>();
if (evaluationTemplate.getIndicatorWeightList() != null
&& !evaluationTemplate.getIndicatorWeightList()
.isEmpty()) {
evaluationTemplate.getIndicatorWeightList().forEach(a -> {
EvaluationTemplateIndicatorWeight indicatorWeight = new EvaluationTemplateIndicatorWeight();
indicatorWeight.setWeight(a.getWeight());
//设置模板ID
indicatorWeight.setTemplateId(evaluationTemplate.getId());
//设置顶级指标ID
indicatorWeight.setIndicatorTopId(evaluationTemplate.getIndicatorTopId());
//设置指标ID
indicatorWeight.setIndicatorId(a.getIndicatorId());
indicatorWeightList.add(indicatorWeight);
});
evaluationTemplateIndicatorWeightService.saveEvaluationTemplateIndicatorWeight(
evaluationTemplate.getId(), evaluationTemplate.getIndicatorTopId(), indicatorWeightList);
}
//保存各个指标之间互相比较的权重信息
if (evaluationTemplate.getTemplateDetailWeightList() != null
&& !evaluationTemplate.getTemplateDetailWeightList()
.isEmpty()) {
List<EvaluationTemplateWeight> detailList = new ArrayList<>();
evaluationTemplate.getTemplateDetailWeightList().forEach(a -> {
EvaluationTemplateWeight detailWeight = new EvaluationTemplateWeight();
//设置模板ID
detailWeight.setTemplateId(evaluationTemplate.getId());
//设置顶级指标ID
detailWeight.setIndicatorTopId(evaluationTemplate.getIndicatorTopId());
detailWeight.setFromIndicatorId(a.getRowId());
detailWeight.setToIndicatorId(a.getColId());
detailWeight.setWeight(a.getValue());
detailWeight.setIndicatorParentId(a.getParentId());
detailWeight.setRowNum(a.getRowNum());
detailList.add(detailWeight);
});
evaluationTemplateDetailWeightService.saveIndicatorMatrix(evaluationTemplate.getId(),
evaluationTemplate.getIndicatorTopId(), detailList);
}
}
@Override
@ -87,8 +107,14 @@ public class EvaluationTemplateServiceImpl extends
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteTemplate(EvaluationTemplate evaluationTemplate) {
evaluationTemplateWeightService.deleteEvaluationTemplateWeightWithTemplateId(
evaluationTemplate.getId());
//删除互比信息
evaluationTemplateDetailWeightService.deleteByTemplateIdAndIndicatorTopId(
evaluationTemplate.getId(), evaluationTemplate.getIndicatorTopId());
//删除指标权重信息
evaluationTemplateIndicatorWeightService.deleteByTemplateIdAndIndicatorTopId(
evaluationTemplate.getId(), evaluationTemplate.getIndicatorTopId());
//删除模板
this.removeById(evaluationTemplate.getId());
}

View File

@ -9,7 +9,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
@ -25,25 +24,6 @@ public class EvaluationTemplateWeightServiceImpl extends
EvaluationTemplateWeightService {
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteEvaluationTemplateWeightWithTemplateIdAndIndicatorId(Integer templateId,
Integer indicatorTopId, Integer parentId) {
QueryWrapper<EvaluationTemplateWeight> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("template_id", templateId);
queryWrapper.eq("indicator_top_id", indicatorTopId);
queryWrapper.eq("indicator_parent_id", parentId);
this.remove(queryWrapper);
}
@Override
public void deleteEvaluationTemplateWeightWithTemplateId(Integer templateId) {
QueryWrapper<EvaluationTemplateWeight> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("template_id", templateId);
this.remove(queryWrapper);
}
@Override
public List<EvaluationTemplateWeight> queryListByIndicatorParentIdAndTemplateId(
Integer indicatorParentId, Integer templateId) {
@ -57,7 +37,7 @@ public class EvaluationTemplateWeightServiceImpl extends
@Override
public Map<Integer, List<EvaluationTemplateWeight>> groupByParentIndicatorId(
Integer indicatorParentId, Integer templateId) {
List<EvaluationTemplateWeight> list = this.queryListByIndicatorParentIdAndTemplateId(
List<EvaluationTemplateWeight> list = this.queryListByIndicatorTopIdAndTemplateId(
indicatorParentId, templateId);
if (!list.isEmpty()) {
return list.stream()
@ -65,4 +45,33 @@ public class EvaluationTemplateWeightServiceImpl extends
}
return Map.of();
}
@Override
public void saveIndicatorMatrix(Integer templateId, Integer indicatorTopId,
List<EvaluationTemplateWeight> weightList) {
QueryWrapper<EvaluationTemplateWeight> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("template_id", templateId);
queryWrapper.eq("indicator_top_id", indicatorTopId);
this.remove(queryWrapper);
if (weightList != null && !weightList.isEmpty()) {
weightList.forEach(this::save);
}
}
@Override
public void deleteByTemplateIdAndIndicatorTopId(Integer templateId, Integer indicatorTopId) {
QueryWrapper<EvaluationTemplateWeight> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("template_id", templateId);
queryWrapper.eq("indicator_top_id", indicatorTopId);
this.remove(queryWrapper);
}
@Override
public List<EvaluationTemplateWeight> queryListByIndicatorTopIdAndTemplateId(
Integer indicatorTopId, Integer templateId) {
QueryWrapper<EvaluationTemplateWeight> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("template_id", templateId);
queryWrapper.eq("indicator_top_id", indicatorTopId);
return this.list(queryWrapper);
}
}

View File

@ -0,0 +1,26 @@
package com.hshh.indicator.bean;
import com.hshh.indicator.entity.IndicatorEvalItem;
import com.hshh.system.common.bean.JsTree;
import java.util.List;
import lombok.Data;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class IndicatorEvalBean {
private List<JsTree> jsTreeList;
//评价集范围
private List<IndicatorEvalItem> evalItemList;
//顶级指标ID
private Integer indicatorTopId;
}

View File

@ -0,0 +1,19 @@
package com.hshh.indicator.bean;
import com.hshh.indicator.entity.IndicatorTopLevel;
import com.hshh.indicator.entity.IndicatorTopSet;
import java.util.List;
import lombok.Data;
/**
* 接收全局指标level隶属函数bean.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class IndicatorSetBean {
private List<IndicatorTopLevel> levels;
private IndicatorTopSet topSet;
}

View File

@ -1,20 +1,23 @@
package com.hshh.indicator.controller;
import com.hshh.indicator.bean.IndicatorEvalBean;
import com.hshh.indicator.entity.Indicator;
import com.hshh.indicator.entity.IndicatorEvalItem;
import com.hshh.indicator.service.IndicatorEvalItemService;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.indicator.service.IndicatorTopLevelService;
import com.hshh.indicator.service.IndicatorTopSetService;
import com.hshh.system.annotation.LogOperation;
import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.bean.JsTree;
import com.hshh.system.common.bean.OperateResult;
import com.hshh.system.common.enums.ErrorCode;
import com.hshh.system.common.enums.ErrorMessage;
import io.swagger.v3.oas.annotations.Operation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -32,13 +35,14 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/evaluation")
public class EvaluationController extends BaseController {
/**
* 指标服务类. 查询所有指标列表
* 指标服务类.
*/
@Resource
private IndicatorService indicatorService;
/**
* 评价服务类.
* 指标评价集范围服务类.
*/
@Resource
private IndicatorEvalItemService indicatorEvalItemService;
@ -49,107 +53,87 @@ public class EvaluationController extends BaseController {
* @return indicator/evaluation_list.html
*/
@GetMapping("/evaluationList")
public String evaluationList(Integer topIndicatorId, Integer indicatorId, Model model) {
public String evaluationList(Model model) {
setNavigateTitle(model, "/evaluation/evaluationList");
List<Indicator> rootList = indicatorService.queryRootList();
if (rootList != null && !rootList.isEmpty()) {
if (topIndicatorId != null) {
for (Indicator indicator : rootList) {
if (indicator.getId().equals(topIndicatorId)) {
//回显上次选择的
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);
}
}
}
//查询子指标的评价集
List<IndicatorEvalItem> evaluationList = indicatorEvalItemService.queryListByIndicatorId(
(indicatorId == null ? indicatorListWithoutChildren.get(0).getId() : indicatorId)
);
model.addAttribute("evaluationList", evaluationList);
model.addAttribute("indicatorListWithoutChildren", indicatorListWithoutChildren);
}
}
model.addAttribute("rootList", rootList);
List<Indicator> bottomList = indicatorService.queryRootList();
model.addAttribute("rootList", bottomList);
return "indicator/evaluation_list";
}
/**
* 导航到评价集增加页面.
* 获取当前指标的评价信息.
*
* @return indicator/add_evaluation.html
* @param id 指标ID
* @return 评价详细信息
*/
@RequestMapping("/evaluationAdd")
@Operation(summary = "导航到评价集增加页面", description = "导航到评价集增加页面")
public String evaluationAdd() {
return "indicator/add_evaluation";
@GetMapping("/{id}")
@ResponseBody
public OperateResult<IndicatorEvalBean> metricIdChange(@PathVariable("id") Integer id) {
IndicatorEvalBean indicatorEvalBean = new IndicatorEvalBean();
//查询底部评价集
indicatorEvalBean.setJsTreeList(evalItemTree(id));
return OperateResult.success(indicatorEvalBean);
}
/**
* 保存评价集.
* 建立底部指标树.
*
* @param item 评价集数据
* @param bindingResult 字段错误信息
* @param id 根指标
* @return 包含底部指标的树
*/
private List<JsTree> evalItemTree(Integer id) {
List<JsTree> jsTreeList = new ArrayList<>();
JsTree jsTree = new JsTree();
jsTree.setText("根指标");
jsTree.setId("0");
jsTreeList.add(jsTree);
jsTree.setChildren(new ArrayList<>());
//查询根部指标所有底部指标
List<Indicator> bottomList = indicatorService.selectNoChildByTopId(
id);
//查询底部指标的评价集范围
Map<Integer, List<IndicatorEvalItem>> evalMap = indicatorEvalItemService.getIndicatorEvalItemMapByIndicatorTopId(
id);
bottomList.forEach(a -> {
JsTree child = new JsTree();
jsTree.getChildren().add(child);
child.setId("tree_" + a.getId());
child.setOriginalId(a.getId());
child.setText(a.getName());
if (evalMap.containsKey(a.getId())) {
List<IndicatorEvalItem> evalItemList = evalMap.get(a.getId());
evalItemList = evalItemList.stream().peek(aa -> {
if (aa.getEqualValue() == null) {
aa.setEqualValue("");
}
}).collect(Collectors.toList());
StringBuilder tipBuilder = new StringBuilder();
evalItemList.forEach(evalItem -> {
tipBuilder.append(evalItem.getEvaluationName()).append(":")
.append(evalItem.getBottomValue())
.append(",").append(evalItem.getTopValue());
child.setText(a.getName() + "(" + tipBuilder + ")");
});
child.getData().put("range", evalItemList);
}
});
return jsTreeList;
}
/**
* 保存评价集隶属函数设置评级集枚举和分值.
*
* @param data 前端数据
* @return 操作结果
*/
@PostMapping("/save")
@ResponseBody
public OperateResult<Object> save(@Valid @RequestBody IndicatorEvalItem item,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return errorsInputHandle(bindingResult);
}
indicatorEvalItemService.saveOrUpdate(item);
@LogOperation("保存评价集")
public OperateResult<Void> save(@RequestBody IndicatorEvalBean data) {
indicatorEvalItemService.saveWhole(data);
return OperateResult.success();
}
/**
* 根据id删除评价.
*
* @param id 要删除的id
* @return 操作结果
*/
@GetMapping("/remove/{id}")
@ResponseBody
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
IndicatorEvalItem evaluation = indicatorEvalItemService.getById(id);
if (evaluation == null) {
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
indicatorEvalItemService.removeById(id);
return OperateResult.success();
}
/**
* 根据ID查询评价信息.
*
* @param id 评价ID
* @return id对应的评价
*/
@GetMapping("/{id}")
@ResponseBody
public OperateResult<IndicatorEvalItem> view(@PathVariable("id") Integer id) {
IndicatorEvalItem evaluation = indicatorEvalItemService.getById(id);
if (evaluation == null) {
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
return OperateResult.success(evaluation, ErrorMessage.SUCCESS.getMessage());
}
}

View File

@ -17,6 +17,7 @@ import com.hshh.model.entity.FormFieldConfig;
import com.hshh.model.entity.ModelDefine;
import com.hshh.model.service.FormFieldConfigService;
import com.hshh.model.service.ModelDefineService;
import com.hshh.system.annotation.LogOperation;
import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.bean.CheckedBean;
import com.hshh.system.common.bean.OperateResult;
@ -138,7 +139,7 @@ public class IndicatorController extends BaseController {
List<ModelDefine> modelDefineList = modelDefineService.list();
model.addAttribute("modelDefineList", modelDefineList);
}
return "/indicator/add_indicator";
return "indicator/add_indicator";
}
/**
@ -165,6 +166,7 @@ public class IndicatorController extends BaseController {
* @param bindingResult 字段验证信息
* @return 保存结果
*/
@LogOperation("保存指标")
@PostMapping("/save")
@ResponseBody
@Operation(summary = "保存指标", description = "验证后保存指标")
@ -202,6 +204,7 @@ public class IndicatorController extends BaseController {
*/
@GetMapping("/remove/{id}")
@ResponseBody
@LogOperation("删除指标")
@Operation(summary = "删除指标", description = "根据ID删除指定指标")
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
if (id == null) {
@ -337,6 +340,7 @@ public class IndicatorController extends BaseController {
*/
@PostMapping("/uploadCsv")
@ResponseBody
@LogOperation("上传csv文件")
public OperateResult<List<IndicatorCsvColumn>> uploadCsv(@RequestParam("file") MultipartFile file,
Integer indicatorTopId) throws IOException {
List<IndicatorCsvColumn> list = indicatorService.saveIndicatorTopCsvMapper(indicatorTopId,
@ -356,6 +360,7 @@ public class IndicatorController extends BaseController {
*/
@PostMapping("/indicatorFormMapper")
@ResponseBody
@LogOperation("保存指标和form表单ID的映射关系")
public OperateResult<List<FormFieldConfig>> saveIndicatorFormMapper(
@RequestBody IndicatorFormMapper formMapper) {
indicatorTopMapperService.saveFormMapper(formMapper);

View File

@ -0,0 +1,42 @@
package com.hshh.indicator.controller;
import com.hshh.indicator.bean.IndicatorSetBean;
import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.bean.OperateResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 指标全局函数设置.
*
* @author LiDongYU
* @since 2025/7/22
*/
@Controller
@RequestMapping("/indicatorSet")
@Tag(name = "指标全局函数设置", description = "指标全局函数设置")
public class IndicatorSetController extends BaseController {
/**
* 指标全局函数默认设置页面.
*
* @param model 数据容器
* @param request 页面请求
* @return indicator_set.html
*/
@GetMapping("/list")
public String list(Model model, HttpServletRequest request) {
setNavigateTitle(model, "/indicatorSet/list");
return "system/indicator/indicator_set";
}
@PostMapping("/save")
public OperateResult<Void> save(@RequestBody IndicatorSetBean indicatorSetBean) {
}
}

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.hshh.system.common.bean.CheckedBean;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
@ -47,5 +48,15 @@ public class Indicator extends CheckedBean {
@TableField(exist = false)
private double weight;
//当前值
@TableField(exist = false)
private String value;
//隶属度描述
@TableField(exist = false)
private Map<String, String> membership;
//得分
@TableField(exist = false)
private String score;
}

View File

@ -1,6 +1,7 @@
package com.hshh.indicator.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
@ -17,58 +18,69 @@ import java.io.Serializable;
public class IndicatorBottomCsvMapper implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer indicatorTopId;
private Integer indicatorTopId;
private Integer indicatorId;
private Integer indicatorId;
private Integer csvColumnId;
private Integer csvColumnId;
public Integer getId() {
return id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getIndicatorTopId() {
return indicatorTopId;
}
public Integer getIndicatorTopId() {
return indicatorTopId;
}
public void setIndicatorTopId(Integer indicatorTopId) {
this.indicatorTopId = indicatorTopId;
}
public void setIndicatorTopId(Integer indicatorTopId) {
this.indicatorTopId = indicatorTopId;
}
public Integer getIndicatorId() {
return indicatorId;
}
public Integer getIndicatorId() {
return indicatorId;
}
public void setIndicatorId(Integer indicatorId) {
this.indicatorId = indicatorId;
}
public void setIndicatorId(Integer indicatorId) {
this.indicatorId = indicatorId;
}
public Integer getCsvColumnId() {
return csvColumnId;
}
public Integer getCsvColumnId() {
return csvColumnId;
}
public void setCsvColumnId(Integer csvColumnId) {
this.csvColumnId = csvColumnId;
}
public void setCsvColumnId(Integer csvColumnId) {
this.csvColumnId = csvColumnId;
}
@Override
public String toString() {
return "IndicatorBottomCsvMapper{" +
@Override
public String toString() {
return "IndicatorBottomCsvMapper{" +
"id = " + id +
", indicatorTopId = " + indicatorTopId +
", indicatorId = " + indicatorId +
", csvColumnId = " + csvColumnId +
"}";
}
}
@TableField(exist = false)
private String csvColumnName;
public String getCsvColumnName() {
return csvColumnName;
}
public void setCsvColumnName(String csvColumnName) {
this.csvColumnName = csvColumnName;
}
}

View File

@ -1,6 +1,7 @@
package com.hshh.indicator.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
@ -28,10 +29,7 @@ public class IndicatorBottomFormMapper implements Serializable {
private Integer formFieldId;
private Integer indicatorTopId;
@TableField(exist = false)
private String filedName;
}

View File

@ -1,6 +1,7 @@
package com.hshh.indicator.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
@ -32,27 +33,13 @@ public class IndicatorEvalItem implements Serializable {
private Integer indicatorId;
private Integer sortOrder;
@NotNull(message = "名称不能为空")
@Size(max = 50, message = "名称不能超过50字符")
private String evaluationName;
private String topValue;
@NotNull(message = "符号不能为空")
@Size(max = 5, message = "名称不能超过5字符")
private String minSymbol;
@NotNull(message = "值不能为空")
@Size(max = 10, message = "值不能超过10字符")
private String minValue;
@NotNull(message = "符号不能为空")
@Size(max = 5, message = "名称不能超过5字符")
private String maxSymbol;
@NotNull(message = "值不能为空")
@Size(max = 10, message = "值不能超过10字符")
private String maxValue;
@NotNull(message = "值不能为空")
private Double grade;
private String bottomValue;
//除去范围外等于某个字符串值
private String equalValue;
}

View File

@ -1,6 +1,7 @@
package com.hshh.indicator.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
@ -27,4 +28,5 @@ public class IndicatorFormMapper implements Serializable {
}

View File

@ -1,7 +1,8 @@
package com.hshh.indicator.mapper;
import com.hshh.indicator.entity.IndicatorBottomCsvMapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hshh.indicator.entity.IndicatorBottomCsvMapper;
import java.util.List;
/**
* <p>
@ -13,4 +14,11 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface IndicatorBottomCsvMapperMapper extends BaseMapper<IndicatorBottomCsvMapper> {
/**
* 根据根指标查找对应关系.
*
* @param indicatorTopId 根指标
* @return 对应关系列表
*/
List<IndicatorBottomCsvMapper> selectByIndicatorTopId(Integer indicatorTopId);
}

View File

@ -1,11 +1,12 @@
package com.hshh.indicator.mapper;
import com.hshh.indicator.entity.IndicatorBottomFormMapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hshh.indicator.entity.IndicatorBottomFormMapper;
import java.util.List;
/**
* <p>
* Mapper 接口
* Mapper 接口
* </p>
*
* @author liDongYu
@ -13,5 +14,12 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface IndicatorBottomMapperMapper extends BaseMapper<IndicatorBottomFormMapper> {
/**
* 查找指标ID和字段名称的对应关系.
*
* @param topId 根指标ID
* @return 对应关系列表
*/
List<IndicatorBottomFormMapper> selectByIndicatorTopId(Integer topId);
}

View File

@ -27,4 +27,12 @@ public interface IndicatorBottomCsvMapperService extends IService<IndicatorBotto
* @param indicatorTopId 顶部指标ID
*/
void deleteByIndicatorTopId(Integer indicatorTopId);
/**
* 根据指标ID获取对应关系.
*
* @param topId 根指标ID
* @return 对应关系列表
*/
List<IndicatorBottomCsvMapper> selectIndicatorToFieldNameListByIndicatorTopId(Integer topId);
}

View File

@ -34,4 +34,11 @@ public interface IndicatorBottomFormMapperService extends IService<IndicatorBott
*/
void saveMapper(List<IndicatorBottomFormMapper> indicatorBottomFormMapperList);
/**
* 查找指标ID和字段名称的对应关系.
*
* @param topId 根指标ID
* @return 对应关系列表
*/
List<IndicatorBottomFormMapper> selectIndicatorToFieldNameListByIndicatorTopId(Integer topId);
}

View File

@ -1,7 +1,7 @@
package com.hshh.indicator.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hshh.evaluation.bean.EvalItemToFieldInfo;
import com.hshh.indicator.bean.IndicatorEvalBean;
import com.hshh.indicator.entity.IndicatorEvalItem;
import java.util.List;
import java.util.Map;
@ -15,35 +15,18 @@ import java.util.Map;
public interface IndicatorEvalItemService extends IService<IndicatorEvalItem> {
/**
* 根据指标ID和名称查询记录.
* 根据顶级指标查询返回以子指标为key,value为子指标评价范围集合.
*
* @param indicatorId 指标ID
* @param name 名称
* @return 记录
* @param indicatorTopId 顶级指标
* @return 以子指标分类的评价集范围
*/
List<IndicatorEvalItem> queryListByNameAndIndicatorId(Integer indicatorId, String name);
Map<Integer, List<IndicatorEvalItem>> getIndicatorEvalItemMapByIndicatorTopId(
Integer indicatorTopId);
/**
* 根据子指标查询他的评价集.
* 保存评价集
*
* @param indicatorId 子指标
* @return 评价集
* @param data 前端数据
*/
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);
void saveWhole(IndicatorEvalBean data);
}

View File

@ -39,4 +39,10 @@ public class IndicatorBottomCsvMapperServiceImpl extends
queryWrapper.eq("indicator_top_id", indicatorTopId);
remove(queryWrapper);
}
@Override
public List<IndicatorBottomCsvMapper> selectIndicatorToFieldNameListByIndicatorTopId(
Integer topId) {
return this.baseMapper.selectByIndicatorTopId(topId);
}
}

View File

@ -41,4 +41,9 @@ public class IndicatorBottomMapperServiceImpl extends
public void saveMapper(List<IndicatorBottomFormMapper> indicatorBottomFormMapperList) {
indicatorBottomFormMapperList.forEach(this::save);
}
@Override
public List<IndicatorBottomFormMapper> selectIndicatorToFieldNameListByIndicatorTopId(Integer topId) {
return this.baseMapper.selectByIndicatorTopId(topId);
}
}

View File

@ -2,13 +2,18 @@ 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.bean.IndicatorEvalBean;
import com.hshh.indicator.entity.IndicatorEvalItem;
import com.hshh.indicator.mapper.IndicatorEvalItemMapper;
import com.hshh.indicator.service.IndicatorEvalItemService;
import com.hshh.indicator.service.IndicatorTopLevelService;
import com.hshh.indicator.service.IndicatorTopSetService;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 指标规则表 服务实现类.
@ -20,36 +25,31 @@ import org.springframework.stereotype.Service;
public class IndicatorEvalItemServiceImpl extends
ServiceImpl<IndicatorEvalItemMapper, IndicatorEvalItem> implements IndicatorEvalItemService {
@Override
public List<IndicatorEvalItem> queryListByNameAndIndicatorId(Integer indicatorId, String name) {
public Map<Integer, List<IndicatorEvalItem>> getIndicatorEvalItemMapByIndicatorTopId(
Integer indicatorTopId) {
QueryWrapper<IndicatorEvalItem> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("indicator_id", indicatorId);
queryWrapper.eq("evaluation_name", name);
queryWrapper.eq("indicator_top_id", indicatorTopId);
queryWrapper.orderByAsc("id");
List<IndicatorEvalItem> indicatorEvalItemList = this.list(queryWrapper);
return this.list(queryWrapper);
return indicatorEvalItemList.stream()
.collect(Collectors.groupingBy(IndicatorEvalItem::getIndicatorId));
}
@Transactional(rollbackFor = Exception.class)
@Override
public List<IndicatorEvalItem> queryListByIndicatorId(Integer indicatorId) {
public void saveWhole(IndicatorEvalBean data) {
//保存评价集
QueryWrapper<IndicatorEvalItem> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("indicator_id", indicatorId);
queryWrapper.orderByAsc("sort_order", "id");
queryWrapper.eq("indicator_top_id", data.getIndicatorTopId());
remove(queryWrapper);
data.getEvalItemList().forEach(item -> {
item.setIndicatorTopId(data.getIndicatorTopId());
save(item);
});
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

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hshh.model.entity.FormValue;
import com.hshh.system.common.bean.PaginationBean;
import java.util.List;
import org.apache.ibatis.annotations.Param;
/**
* 动态表单实际对象记录表 Mapper 接口.
@ -28,4 +29,12 @@ public interface FormValueMapper extends BaseMapper<FormValue> {
* @return 查询结果
*/
Long count(PaginationBean request);
/**
* 查询指定id的记录.
*
* @param ids id集合
* @return 结果列表
*/
List<FormValue> selectByIds(@Param("ids") List<Integer> ids);
}

View File

@ -12,6 +12,7 @@ import java.util.List;
* @since 2025-08-01
*/
public interface FormValueService extends IService<FormValue> {
/**
* 查询列表.
*
@ -27,4 +28,12 @@ public interface FormValueService extends IService<FormValue> {
* @return 查询结果
*/
Long count(PaginationBean request);
/**
* 查询指定id的记录.
*
* @param ids id集合
* @return 结果列表
*/
List<FormValue> selectByIds(List<Integer> ids);
}

View File

@ -27,4 +27,9 @@ public class FormValueServiceImpl extends ServiceImpl<FormValueMapper, FormValue
public Long count(PaginationBean request) {
return this.baseMapper.count(request);
}
@Override
public List<FormValue> selectByIds(List<Integer> ids) {
return this.baseMapper.selectByIds(ids);
}
}

View File

@ -1,7 +1,7 @@
package com.hshh.thread;
import com.alibaba.fastjson2.JSON;
import com.hshh.evaluation.bean.EvaluationRequest;
import com.hshh.evaluation.bean.PageEvaluationRequest;
import com.hshh.evaluation.service.impl.EvaluationProjectServiceImpl;
import com.hshh.system.Global;
import com.hshh.system.common.bean.SpringContextHolder;
@ -17,7 +17,7 @@ import lombok.extern.slf4j.Slf4j;
* @since 2025/7/22
*/
@Slf4j
public class HandleCmdThread implements Runnable {
public class HandleEvaluationCmdThread implements Runnable {
@Override
public void run() {
@ -39,6 +39,6 @@ public class HandleCmdThread implements Runnable {
SpringContextHolder.getBean(EvaluationProjectServiceImpl.class)
.evaluate(JSON.parseObject(cmd.getCmd(),
EvaluationRequest.class), cmd.getUserId());
PageEvaluationRequest.class), cmd.getUserId());
}
}

View File

@ -22,15 +22,17 @@ spring:
max-request-size: 20MB
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
database-id: mysql
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@ -1,5 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hshh.evaluation.mapper.EvaluationCsvDataMapper">
<select id="list" resultType="com.hshh.evaluation.entity.EvaluationCsvData"
parameterType="com.hshh.system.common.bean.PaginationBean" databaseId="mysql">
SELECT
@rownum := @rownum + 1 AS seq,
t.*
FROM (
SELECT * FROM m_data_evaluation_csv_data
<where>
<if test="randomKey!=null">
and random_key = #{randomKey}
</if>
<if test="search != null and search !='' ">
and raw_data LIKE CONCAT('%',#{search},'%')
</if>
</where>
order by id asc ) t, ( SELECT @rownum := #{start} ) r limit
#{start},#{pageSize}
</select>
<select id="list" resultType="com.hshh.evaluation.entity.EvaluationCsvData"
parameterType="com.hshh.system.common.bean.PaginationBean" databaseId="dm">SELECT
t.seq,
t.*
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS seq,
a.*
FROM m_data_evaluation_csv_data a
<where>
<if test="randomKey!=null">
and random_key = #{randomKey}
</if>
<if test="search != null and search !='' ">
and raw_data LIKE '%'||#{search}||'%'
</if>
</where>
) t
WHERE t.seq > #{start} AND t.seq &lt;= (#{start} + #{pageSize})
</select>
<select id="count" resultType="java.lang.Long" databaseId="dm">
select count(id) from m_data_evaluation_csv_data
<where>
<if test="randomKey!=null">
and random_key = #{randomKey}
</if>
<if test="search != null and search !=''">
and (raw_data LIKE '%'||#{search}||'%')
</if>
</where>
</select>
<select id="count" resultType="java.lang.Long" databaseId="mysql">
select count(id) from m_data_evaluation_csv_data
<where>
<if test="randomKey!=null">
and random_key = #{randomKey}
</if>
<if test="search != null and search !=''">
and (raw_data LIKE CONCAT('%',#{search},'%'))
</if>
</where>
</select>
<select id="selectByIds" resultType="com.hshh.evaluation.entity.EvaluationCsvData">
SELECT * FROM m_data_evaluation_csv_data
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@ -1,5 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hshh.evaluation.mapper.EvaluationHistoryMapper">
<select id="queryRecentByProjectId" resultType="com.hshh.evaluation.entity.EvaluationHistory">
SELECT *
FROM m_data_evaluation_history
where project_id = #{projectId}
ORDER BY id DESC
LIMIT 1;
</select>
</mapper>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hshh.evaluation.mapper.EvaluationResultMapper">
</mapper>

View File

@ -9,8 +9,8 @@
FROM (
SELECT * FROM m_data_model_field_value
<where>
<if test="id!=null">
and model_define_id = #{id}
<if test="businessKey!=null">
and model_define_id = #{businessKey}
</if>
<if test="search != null and search !='' ">
and model_data LIKE CONCAT('%',#{search},'%')
@ -29,8 +29,8 @@
a.*
FROM m_data_model_field_value a
<where>
<if test="id!=null">
and model_define_id = #{id}
<if test="businessKey!=null">
and model_define_id = #{businessKey}
</if>
<if test="search != null and search !='' ">
and model_data LIKE '%'||#{search}||'%'
@ -42,8 +42,8 @@
<select id="count" resultType="java.lang.Long" databaseId="dm">
select count(id) from m_data_model_field_value
<where>
<if test="id!=null">
and model_define_id = #{id}
<if test="businessKey!=null">
and model_define_id = #{businessKey}
</if>
<if test="search != null and search !=''">
and (model_data LIKE '%'||#{search}||'%')
@ -53,12 +53,19 @@
<select id="count" resultType="java.lang.Long" databaseId="mysql">
select count(id) from m_data_model_field_value
<where>
<if test="id!=null">
and model_define_id = #{id}
<if test="businessKey!=null">
and model_define_id = #{businessKey}
</if>
<if test="search != null and search !=''">
and (model_data LIKE CONCAT('%',#{search},'%'))
</if>
</where>
</select>
<select id="selectByIds" resultType="com.hshh.model.entity.FormValue">
SELECT * FROM m_data_model_field_value
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@ -1,5 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hshh.indicator.mapper.IndicatorBottomCsvMapperMapper">
<select id="selectByIndicatorTopId" resultType="com.hshh.indicator.entity.IndicatorBottomCsvMapper">
SELECT
t1.indicator_id,
t2.csv_column_name
FROM
m_data_indicator_bottom_csv_mapper t1
LEFT JOIN m_data_indicator_csv_column t2 on t1.csv_column_id=t2.id
where t1.indicator_top_id=#{id}
</select>
</mapper>

View File

@ -2,5 +2,13 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hshh.indicator.mapper.IndicatorBottomMapperMapper">
<select id="selectByIndicatorTopId" resultType="com.hshh.indicator.entity.IndicatorBottomFormMapper">
SELECT
t1.indicator_id,
t2.field_id as filedName
FROM
m_data_indicator_bottom_form_mapper t1
LEFT JOIN m_data_model_field_config t2 on t1.form_field_id=t2.id
where t1.indicator_top_id=#{indicatorTopId}
</select>
</mapper>

View File

@ -5,4 +5,5 @@
select t.indicator_model_id
from m_data_indicator_form t where t.indicator_top_id=#{topId}
</select>
</mapper>

View File

@ -246,12 +246,18 @@ function _resetPageNoSearch() {
//动态数据,动态表头,动态表格 header=map record=list(map)
function _dynamic_next(form) {
let pageNum = document.forms[form]["currentPage"].value;
document.forms[form]["currentPage"].value = parseInt(pageNum)+1;
dynamic_search();
}
function _dynamic_pre(form) {
let pageNum = document.forms[form]["currentPage"].value;
document.forms[form]["currentPage"].value = parseInt(pageNum)-1;
dynamic_search();
}
function _dynamic_resetPageNoSearch(form){
document.forms[form]["currentPage"].value = 1;
dynamic_search();
}
@ -408,4 +414,25 @@ function wsUrl(path) {
const loc = window.location; // 当前页面的地址
const scheme = loc.protocol === "https:" ? "wss:" : "ws:";
return scheme + "//" + loc.host + path;
}
function formToQueryString(formId) {
const form = document.getElementById(formId);
const formData = new FormData(form);
const params = new URLSearchParams();
for (let [key, value] of formData.entries()) {
// 排除文件输入
const field = form.elements[key];
if (field && field.type === "file") continue;
params.append(key, value);
}
return params.toString(); // "key1=val1&key2=val2"
}
function encryptPassword(password, aesKeyFromBackend,aesIVFromBackend) {
var key = CryptoJS.enc.Hex.parse(aesKeyFromBackend); // 32字节
var iv = CryptoJS.enc.Hex.parse(aesIVFromBackend); // 16字节
return CryptoJS.AES.encrypt(password, key, { iv: iv, mode: CryptoJS.mode.CBC }).toString();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@
<label class="col-2 col-form-label">基础设施</label>
<div class="col-6">
<label>
<select class="form-select" id="modelId" onchange="_tabChange(this.value)">
<select class="form-select" id="modelId" onchange="_tabChange(this)">
<option>--请选择基础设施--</option>
<option th:each="item ,stat: ${modelDefineList}" th:text="${item.getModelName()}"
th:value="${item.id}" th:selected="${item.checked}"></option>
@ -129,14 +129,15 @@
http.post(url, postObj, function (error, res, xhr) {
closeDialog("modal-full-width");
document.getElementById("_data_list").setAttribute("hx-vals",
JSON.stringify({id: postObj.modelDefineId}))
JSON.stringify({businessKey: postObj.modelDefineId}))
document.getElementById("_data_list").click();
}, "dataRecordForm", "modal-full-width")
}
function _tabChange(id) {
function _tabChange(obj) {
document.getElementById("_data_list").setAttribute("hx-vals",
JSON.stringify({id: id}));
JSON.stringify({businessKey: obj.value}));
document.getElementById("_data_list").click();
}
@ -173,7 +174,7 @@
if (!error) {
document.getElementById("dataExtendForm")['id'].value = res.result.id;
addModeDataRecord(res.result.modelDefineId, res.result);
addModeDataRecord( res.result);
}
}, "dataRecordForm")
}

View File

@ -0,0 +1,12 @@
<div class="page page-center">
<div class="container-tight py-4">
<div class="empty">
<div class="empty-header">404</div>
<p class="empty-title">页面不存在</p>
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="page page-center">
<div class="container-tight py-4">
<div class="empty">
<div class="empty-header">500</div>
<p class="empty-title" th:text="${errorMessage}"></p>
</div>
</div>
</div>

View File

@ -118,14 +118,14 @@
</div>
<!-- form add对话框 简单form操作居中对话框-->
<div th:fragment="addSimpleFormDialog" id="addSimpleFormDialog">
<div class="modal modal-blur fade" id="simple-form-model" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal modal-blur fade" id="simple-form-model" tabindex="-1" role="dialog" aria-hidden="true" >
<div class="modal-dialog modal-dialog-centered" role="document" style="min-width: 50em;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="SimpleFormDialog_title"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeDialog('simple-form-model')"></button>
</div>
<div class="modal-body" id="simpleFormBody">
<div class="modal-body" id="simpleFormBody" >
</div>
<div class="modal-footer">
@ -219,7 +219,10 @@
</ol>
</nav>
</div>
<div th:fragment="datasetForListMap(formId,headerMap,result,condition)" id="dataset_list_map_table_page">
<div th:fragment="datasetForListMap(formId,headerMap,result,condition)" id="dataset_list_map_table_page" style="min-height: 50em;max-height: 50em;overflow-y: scroll;">
<input type="hidden" name="currentPage"
th:value="${condition?.currentPage}"/>
<div class="card-body border-bottom py-3">
<div class="d-flex">
<div class="text-muted">
@ -245,15 +248,17 @@
<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>
<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><label>
<input type="checkbox" th:value="${item['id']}" name="list_map_table_checkbox">
</label></td>
<td th:each="entry : ${headerMap}" th:text="${item[entry.key]}"></td>
</tr>
</tbody>
@ -262,17 +267,33 @@
<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" >
<li th:class="${result?.isHasPrevious()}?'page-item':'page-item disabled'">
<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 class="page-link" href="#" tabindex="-1" aria-disabled="true" th:attr="data-form-id=${#strings.escapeJavaScript(formId)}"
onclick="_dynamic_pre(this.dataset.formId)">
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-left -->
<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)" >
<li th:class="${result?.isHasNext()}?'page-item':'page-item disabled'" >
<a class="page-link" href="#" onclick="_dynamic_next(this.dataset.formId)" th:attr="data-form-id=${#strings.escapeJavaScript(formId)}">
<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>
<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>

View File

@ -20,7 +20,7 @@
<link th:href="@{/css/tabler-vendors.min.css}" rel="stylesheet"/>
<link th:href="@{/js/jstree/themes/default/style.min.css}" rel="stylesheet"/>
<link th:href="@{/css/fontawesome-all.min.css}" rel="stylesheet"/>
<link th:href="@{/css/jsmind.css}" rel="stylesheet"/>
<style>
@ -267,9 +267,21 @@
<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 th:src="@{/js/html2canvas.min.js}"></script>
<script th:src="@{/js/jspdf.umd.min.js}"></script>
<script>
const categoryColors = [
"#00bcd4", // 卓越
"#2196f3", // 优秀
"#42a5f5", // 优
"#66bb6a", // 良
"#ffd600", // 中
"#ffa726", // 及格
"#ff7043", // 及格下
"#ef5350", // 差
"#8d6e63" // 极差
];
let d3TreeData = null;
document.addEventListener('DOMContentLoaded', function () {
// 1. 支持多种菜单项,逗号分隔多个选择器
@ -306,12 +318,14 @@
}
// htmx等局部刷新后确保DOM插入再init
document.body.addEventListener('htmx:afterSwap', function (evt) {
document.body.addEventListener('htmx:afterSettle', function (evt) {
if (evt.detail.target.id === 'main-content') {
//菜单
if (evt.detail.target.querySelector('[data-page="menu-list"]')) {
setTimeout(initMenu, 0); // 保证DOM已插入
setTimeout(initMenu, 1); // 保证DOM已插入
}
//指标
if(evt.detail.target.querySelector('[data-page="metric"]')){
if (typeof topIndictorTree === 'function') {
topIndictorTree();
@ -336,6 +350,7 @@
}
}
//评估
if(evt.detail.target.querySelector('[data-page="evaluation_list"]')){
if(typeof initEvaluationList === 'function'){
@ -344,20 +359,20 @@
}
}
});
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 });
document.body.addEventListener('htmx:responseError', function (e) {
const status = e.detail.xhr.status;
if (status === 404 || status === 500) {
const html = e.detail.xhr.responseText; // 服务器返回的错误页 HTML
// 把错误页塞进主容器(按你的布局替换 id
document.getElementById('main-content').innerHTML = html;
}
});
const RECIP = {
"9":"0.11111","8":"0.125","7":"0.14286","6":"0.16667","5":"0.2","4":"0.25","3":"0.3333","2":"0.5","1":"1",
"0.5":"2","0.3333":"3","0.25":"4","0.2":"5","0.16667":"6","0.14286":"7","0.125":"8","0.11111":"9"
};
</script>
</body>

View File

@ -1,60 +0,0 @@
<div class="row">
<div class="col-xl-12">
<input type="hidden" name="id" id="id">
<input type="hidden" name="indicatorTopId" id="indicatorTopId">
<input type="hidden" name="indicatorId" id="indicatorId">
<div class="mb-3">
<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">
<option value="">默认空</option>
<option value="==">等于</option>
<option value=">=">大于等于</option>
<option value=">">大于</option>
<option value="<">小于</option>
<option value="<=">小于等于</option>
</select>
<div class="invalid-feedback" id="minSymbol_error_tip"></div>
</div>
<div class="mb-3">
<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>
</div>
<div class="mb-3">
<label class="form-label required" for="maxSymbol">符号:</label>
<select name="maxSymbol" id="maxSymbol" class="form-select">
<option value="">默认空</option>
<option value="==">等于</option>
<option value=">=">大于等于</option>
<option value=">">大于</option>
<option value="<">小于</option>
<option value="<=">小于等于</option>
</select>
<div class="invalid-feedback" id="maxSymbol_error_tip"></div>
</div>
<div class="mb-3">
<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>
</div>
</div>
</div>

View File

@ -1,182 +1,283 @@
<style>
</style>
</head>
<div class="page-body" data-page="evaluation_list">
<div class="container-xl">
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
<div class="page">
<div class="page-body" data-page="indicator_list">
<div class="container-xl">
<div class="row row-cards">
<!-- 面包屑(静态示例/Thymeleaf -->
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
<!-- 顶部重点卡片 -->
<div class="card card-priority">
<div class="card-header">
<h3 class="card-title title-accent">选择评价指标
<span class="badge bg-primary ms-2">必填</span>
</h3>
</div>
<div class="card-body">
<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" 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 class="row row-cards">
<!-- 顶层主卡片:加上 card-hero -->
<div class="card card-hero">
<div class="card-header">
<div class="card-title">指标数据评价集</div>
<div class="card-actions">
<a href="javascript:void(0)" class="btn btn-primary" onclick="evaluationSave()">
<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 class="col-auto text-muted small">选定后,下方“评价集设置”将随之变化</div>
</div>
</div>
</div>
<!-- 次级卡片 -->
<div class="card">
<div class="card-header">
<div class="card-title fw-bold">评价集设置</div>
<div class="ms-auto">
<a href="#" class="btn btn-primary" onclick="evaluation_add()">增加评价</a>
</div>
</div>
<div class="card-body">
<div class="card-body">
<div class="row g-3">
<!-- 左:子集列表 -->
<div class="col-md-3">
<div class="subset-panel">
<div class="subset-scroll list-group list-group-flush">
<div class="list-group-item bg-transparent border-0 py-2"
th:each="item:${indicatorListWithoutChildren}">
<label class="form-check form-check-inline m-0">
<input class="form-check-input" type="radio"
name="radios-inline-indicator-no-child" th:value="${item.getId()}"
th:checked="${item.checked}" onclick="changeIndicator()">
<span class="form-check-label ms-2" th:text="${item.getName()}"></span>
</label>
<!-- 1. 根指标 -->
<div class="card section mb-3">
<div class="card-header">
<div class="card-title">1. 选择指标(根指标)</div>
</div>
<div class="card-body">
<div class="row g-2 align-items-center">
<div class="col-auto">
<label for="indicationId" class="form-label m-0">根指标</label>
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<select id="indicationId" class="form-select" name="indicationId"
onchange="indicatorChange(this.value)">
<option value="0">-选择根指标-</option>
<option th:each="item:${rootList}" th:value="${item.id}"
th:text="${item.getName()}" th:selected="${item.checked}"></option>
</select>
</div>
<div class="col-auto text-muted small">选择根指标后,再选择设施与 CSV 以建立映射。
</div>
</div>
</div>
</div>
<!-- 右:表格 -->
<div class="col-md-9">
<div class="table-responsive">
<table class="table table-vcenter card-table">
<thead>
<tr>
<th class="w-1">序号</th>
<th>名称</th>
<th class="w-1">符号</th>
<th class="w-1">下限值</th>
<th class="w-1">符号</th>
<th class="w-1">上限值</th>
<th class="text-end w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="item, stats :${evaluationList}">
<td th:text="${stats.count}"></td>
<td th:text="${item.getEvaluationName()}"></td>
<td th:text="${item.getMinSymbol()}"></td>
<td th:text="${item.getMinValue()}"></td>
<td th:text="${item.getMaxSymbol()}"></td>
<td th:text="${item.getMaxValue()}"></td>
<td class="text-end">
<a href="javascript:void(0)" class="me-2"
th:onclick="|editEvaluation('${item.id}')|">编辑</a>
<a href="javascript:void(0)"
th:onclick="|removeEvaluation('${item.id}')|">删除</a>
</td>
</tr>
</tbody>
</table>
<!-- 4. 子指标映射 -->
<div class="card section">
<div class="card-header">
<div class="card-title">2. 底部指标区间设置</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-5" id="evaluation_tree"></div>
<div class="col-7">
<div class="card">
<div class="card-header">
<div class="card-title" id="level_range_title"></div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-vcenter card-table">
<thead>
<tr>
<th>等级名</th>
<th>上限</th>
<th>下限</th>
<th>等于</th>
</tr>
</thead>
<tbody id="level_range_body">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> <!-- row -->
</div>
</div>
</div>
</div>
</div>
</div>
<form id="evaluationForm">
<div th:replace="fragments/dialog::addSimpleFormDialog"></div>
</form>
<div th:replace="fragments/dialog::confirmationDialog"></div>
<script>
// 交互时给重点卡片一个轻微高亮
function initEvaluationList() {
function indicatorChange(id) {
if (id === '0') {
return;
}
const card = document.querySelector('.card-priority');
const topSel = document.getElementById('indicationTopId');
topSel?.addEventListener('change', ()=>{
card.classList.add('flash');
setTimeout(()=>card.classList.remove('flash'), 900);
});
}
function evaluation_add(data) {
let url = document.getElementById("_rootPath").value + "evaluation/evaluationAdd";
let http = new HttpClient();
http.get(url, function (err, res, xhr) {
document.getElementById("simpleFormBody").innerHTML = res;
openDialog("simple-form-model");
if (data) {
fillData(data);
} else {
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;
}
document.getElementById("SimpleFormDialog_save").onclick = function () {
evaluation_save();
}
}, "evaluationForm")
}
function fillData(data) { fillForm('evaluationForm', data); }
function evaluation_save() {
let url = document.getElementById("_rootPath").value + "evaluation/save";
let http = new HttpClient();
let form = document.getElementById("evaluationForm");
http.post(url, formObjToObject(form), function (err, res, xhr) {
closeDialog("simple-form-model");
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();
}, "evaluationForm", "simple-form-model");
}
function editEvaluation(id) {
let url = document.getElementById("_rootPath").value + "evaluation/" + id;
let http = new HttpClient();
http.get(url, function (error, res, xhr) { evaluation_add(res.result); },
"evaluationForm", "simple-form-model");
http.get(url, function (error, res, xhr) {
//处理树
if (res.result.jsTreeList) {
let $tree = $('#evaluation_tree');
// 如果已经有树,先销毁
if ($.jstree.reference($tree)) {
$tree.jstree("destroy").empty();
}
$('#evaluation_tree').jstree({
'core': {
'data': res.result.jsTreeList,
check_callback: true // ← 关键
}
}).on("select_node.jstree", function (e, data) {
// 这里 data.node 就是被选中的节点对象
buildLevelRangeBody(data.node);
});
}
}, "")
}
function removeEvaluation(id) {
openDialog("modal-danger");
document.getElementById('delete-confirm-9999').onclick = function () {
let url = document.getElementById("_rootPath").value + "evaluation/remove/" + id;
let http = new HttpClient();
http.get(url, (err, res, xhr) => {
if (!err) document.getElementById("_evaluation_evaluationList").click();
function inputChange() {
//获取选中的树节点
const $tree = $('#evaluation_tree');
try {
const inst = $tree.jstree(true);
let selectedNodes = inst.get_selected(true);
if (selectedNodes && selectedNodes.length) {
let currentNode = selectedNodes[0];
//获取右边的表格值放入data.range中
let id = currentNode.id.replace("tree_", "");
let data = getTableData(id);
//修改节点名称,提示用户
let nodeName = currentNode.text;
nodeName = nodeName.replace(/\([^)]*\)/g, "");
let str = "";
data.forEach(function (element) {
str = str + element.evaluationName + ":" + element.bottomValue + "," + element.topValue
+","+ element.equalValue+";"
})
nodeName = nodeName + "(" + str + ")";
currentNode.data.range = data;
inst.rename_node(currentNode, nodeName);
}
} catch (e) {
console.warn('destroy skipped:', e);
}
}
function buildLevelRangeBody(node) {
document.getElementById("level_range_title").textContent = node.text;
document.getElementById("level_range_body").innerHTML = "";
let levelNames = document.getElementsByName("_level");
let historyMap = new Map();
if (node.data.range) {
node.data.range.forEach((element) => {
historyMap.set(element.evaluationName, element);
})
};
}
for (let i = 0; i < levelNames.length; i++) {
let tr = document.createElement("tr");
let td = document.createElement("td");
td.textContent = levelNames[i].value;
tr.appendChild(td);
//底部指标上限输入
let topTd = document.createElement("td");
let topInput = document.createElement("input");
topInput.type = "number";
topInput.style.width = "8ch";
topInput.addEventListener("change", function () {
inputChange();
});
if (historyMap.get(td.textContent)) {
topInput.value = historyMap.get(td.textContent).topValue;
}
topTd.appendChild(topInput);
//底部指标下限输入
let bottomTd = document.createElement("td");
let bottomInput = document.createElement("input");
bottomInput.type = "number";
bottomInput.style.width = "8ch";
bottomInput.addEventListener("change", function () {
inputChange()
})
if (historyMap.get(td.textContent)) {
bottomInput.value = historyMap.get(td.textContent).bottomValue;
}
bottomTd.appendChild(bottomInput);
//相等值输入
let equalValueTd = document.createElement("td");
let equalValueInput = document.createElement("input");
equalValueInput.type = "text";
equalValueInput.style.width = "8ch";
equalValueInput.addEventListener("change", function () {
inputChange();
});
if (historyMap.get(equalValueTd.textContent)) {
equalValueInput.value = historyMap.get(td.textContent).equalValue;
}
equalValueTd.appendChild(equalValueInput);
tr.appendChild(topTd);
tr.appendChild(bottomTd);
tr.appendChild(equalValueTd);
document.getElementById("level_range_body").appendChild(tr);
}
}
function changeIndicator(){
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();
function getTableData(indicatorId) {
let trs = document.getElementById("level_range_body").getElementsByTagName("tr");
let data = [];
for (let i = 0; i < trs.length; i++) {
let obj = {};
let tr = trs[i];
let tds = tr.getElementsByTagName("td")
obj.indicatorId = parseInt(indicatorId);
obj.evaluationName = tds[0].textContent;
obj.topValue = parseFloat(tds[1].getElementsByTagName("input")[0].value);
obj.bottomValue = parseFloat(tds[2].getElementsByTagName("input")[0].value);
obj.equalValue=tds[3].getElementsByTagName("input")[0].value
data.push(obj);
}
return data;
}
//保存评估评价集
function evaluationSave() {
let obj = {};
//顶级指标ID
obj.indicatorTopId = parseInt(document.getElementById("indicationId").value);
// 设置各个指标的评价集
const inst = $('#evaluation_tree').jstree(true);
const flat = inst.get_json('#', {flat: true, no_data: false});
let evalItemList = [];
flat.forEach(n => {
if (n.data) {
if (n.data.range) {
evalItemList = evalItemList.concat(n.data.range);
}
}
});
obj.evalItemList = evalItemList;
let url = document.getElementById("_rootPath").value + "evaluation/save";
let http = new HttpClient();
http.post(url, obj, function (error, response, xhr) {
showAlert("success", "保存成功");
}, "", "")
}
</script>
</script>

View File

@ -81,8 +81,10 @@
<div class="card-title">3. 上传 CSV 文件</div>
</div>
<div class="card-body">
<input type="file" id="csvFile" class="form-control mb-3" accept=".csv">
<label for="csvFile" class="btn btn-primary">选择文件</label>
<span id="file-name">未选择文件</span>
<input type="file" id="csvFile" accept=".csv" style="display:none" />
<!-- 上次上传文件 -->
<div class="mb-2" th:if="${csvMapper!=null}">
<span class="fw-bold">上次上传文件:</span>
@ -158,7 +160,7 @@
</div>
<!-- ====== JS保持你的原有逻辑 ====== -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script>
function formModelChange(object) {
let postUrl = document.getElementById('_rootPath').value + "indicator/indicatorFormMapper";
@ -179,6 +181,16 @@
}
function csvListen() {
const input = document.getElementById("csvFile");
const fileName = document.getElementById("file-name");
input.addEventListener("change", function() {
if (this.files.length > 0) {
fileName.textContent = this.files[0].name;
} else {
fileName.textContent = "未选择文件";
}
});
$('#csvFile').on('change', function () {
const f = this.files[0];
if (!f) return;
@ -251,8 +263,7 @@
// 根指标变化时也高亮主卡
$('#indicationId').on('change', flashHero);
// 初始化 CSV 监听
csvListen();
// 轻微高亮动画(与 CSS 中 .flash 对应)
function flashHero(){

View File

@ -74,7 +74,8 @@
<div class="form-footer">
<button type="submit" class="btn btn-primary w-100">登录</button>
</div>
<input type="hidden" name="aesKey" id="aesKey" th:value="${aesKey}"/>
<input type="hidden" name="aesIv" id="aesIv" th:value="${aesIv}"/>
</form>
@ -92,6 +93,7 @@
<!-- Tabler Core -->
<script th:src="@{/js/tabler.min.js}" defer></script>
<script th:src="@{/js/common/common.js}" ></script>
<script th:src="@{/js/crypto-js.min.js}"></script>
</body>
@ -108,8 +110,9 @@
formData.forEach((value, key) => {
object[key] = value;
});
object.password = encryptPassword( object.password,object.aesKey,object.aesIv);
var jsonData = JSON.stringify(object);
console.log("转换得到的 JSON 数据:", jsonData);
// 从 form 的 action 属性获取 URLThymeleaf 渲染后会转换为实际地址)
var formAction = this.getAttribute('action');
@ -145,7 +148,7 @@
document.getElementById(item.elementId + "_error_tip").innerHTML = item.message;
}
}
if(data.code===500){//提交错误
if(data.code===10001){//提交错误
document.getElementById("error_message").innerHTML = data.message;
document.getElementById("error_message").style.display = "block";
}

View File

@ -1,9 +1,11 @@
<input type="hidden" name="modelId" th:value="${modelId}" />
<input type="hidden" name="modelId" value="" />
<input type="hidden" name="projectId" th:value="${projectId}" />
<input type="hidden" name="templateId" th:value="${templateId}" />
<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}" />
<input type="hidden" name="membership" th:value="${membership}" />
<div class="row align-items-center mt-3 mb-3">
<div class="col-4">
<div class="progress">
@ -14,10 +16,10 @@
</div>
<div class="col">
<div class="btn-list justify-content-end">
<a href="javascript:void(0)" class="btn btn-info" onclick="">
<a href="javascript:void(0)" class="btn btn-info" th:attr="data-project-id=${projectId}" onclick="_startEvaluation(this.dataset.projectId)">
上一步
</a>
<a href="javascript:void(0)" class="btn btn-success" onclick="selectedDatasource()">
<a href="javascript:void(0)" class="btn btn-success" onclick="processEvaluation()">
下一步
</a>
</div>
@ -32,59 +34,27 @@
<div class="col-md-6">
<div class="mb-3">
<div class="form-label">选择上传文件</div>
<input type="file" class="form-control">
<input type="file" class="form-control" name="file" accept=".csv" id="csv_file" onchange="readCsv()" />
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label" FOR="csv_identifier">标识字段</label>
<select class="form-select" id="csv_identifier">
<option th:each="entry : ${headerMap}" th:value="${entry.key}" th:text="${entry.value}"></option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="row" >
<div class="col-md-12">
<div class="card">
<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 th:replace="fragments/dialog::datasetForListMap('stepForm',${headerMap},${result},${condition})"></div>
</div>
</div>
</div>

View File

@ -2,9 +2,9 @@
<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}" />
<input type="hidden" name="membership" th:value="${membership}" />
<div class="row align-items-center mt-3 mb-3">
<div class="col-4">
<div class="progress">
@ -15,7 +15,7 @@
</div>
<div class="col">
<div class="btn-list justify-content-end">
<a href="javascript:void(0)" class="btn btn-info" onclick="">
<a href="javascript:void(0)" class="btn btn-info" th:attr="data-project-id=${projectId}" onclick="_startEvaluation(this.dataset.projectId)">
上一步
</a>
<a href="javascript:void(0)" class="btn btn-success" onclick="processEvaluation()">
@ -24,7 +24,9 @@
</div>
</div>
</div>
<div class="card" style="min-height: 20em;">
<div class="card" style="min-height: 50em;max-height: 50em;overflow-y: scroll;">
<div class="card-header">
<h4 class="card-title">数据库记录</h4>
</div>

View File

@ -1,7 +1,8 @@
<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">
<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>
@ -9,7 +10,9 @@
<div class="col">
<div class="btn-list justify-content-end">
<a href="javascript:void(0)" class="btn btn-primary" th:onclick="|selectedDatasource(${projectId})|">
<a href="javascript:void(0)" class="btn btn-primary"
th:attr="data-project-id=${projectId}"
onclick="selectedDatasource(this.dataset.projectId)">
下一步
</a>
</div>
@ -18,43 +21,62 @@
<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-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 class="row">
<div class="col-md-12">
<div class="mb-3">
</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 class="form-label">1.选择数据源</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">2.模糊运算方式</div>
<div>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="aggregationMethod" checked="checked"
value="PRODUCT_BOUNDED_SUM">
<span class="form-check-label">乘积有界和算子M(·,⊕)</span>
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="aggregationMethod"
value="MIN_MAX">
<span class="form-check-label">取小取大算子 M(∧,)</span>
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="aggregationMethod"
value="PRODUCT_MAX">
<span class="form-check-label">乘积取大算子 M(·,)</span>
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="aggregationMethod"
value="MIN_BOUNDED_SUM">
<span class="form-check-label">取小有界和算子M(∧,⊕)</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,42 @@
<div class="col-12">
<div class="card">
<div class="card-header">
<div class="card-title">工程评估历史</div>
</div>
<div class="card-body" style="min-height:40em;">
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th></th>
<th>创建时间</th>
<th>数据来源</th>
<th>隶属函数</th>
<th>模糊运算算子</th>
<th></th>
</tr>
</thead>
<tbody >
<tr th:each="evaluationHistory,stat :${list}">
<td th:text="${stat.count}"></td>
<td th:text="${#temporals.format(evaluationHistory.createTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${evaluationHistory.datasourceType}"></td>
<td th:text="${evaluationHistory.membershipFunc}">隶属函数</td>
<td th:text="${evaluationHistory.fuzzyOperator}">模糊运算算子</td>
<td>
<div class="btn-list flex-nowrap"><a href="#" class="btn"
th:data-result-id="${evaluationHistory.randomKey}"
th:data-project-id="${evaluationHistory.getProjectId()}"
onclick="viewRootList(this)">
查看详情
</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,748 @@
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'Segoe UI', Arial, sans-serif;
background-color: #f8fafc;
color: #1e293b;
line-height: 1.6;
}
.report-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.report-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
border-radius: 16px;
margin-bottom: 30px;
text-align: center;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
position: relative;
}
/* PDF导出按钮 - 嵌入在报告头部 */
.export-btn {
position: absolute;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
font-size: 0.85rem;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.export-btn:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.export-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid transparent;
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.report-title {
font-size: 2.8rem;
font-weight: 700;
margin-bottom: 10px;
}
.report-subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 20px;
}
.report-meta {
display: flex;
justify-content: space-between;
font-size: 0.95rem;
opacity: 0.8;
}
.executive-summary {
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
border-left: 6px solid #3b82f6;
}
.section-title {
font-size: 1.8rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 25px;
}
.summary-card {
background: #f8fafc;
padding: 20px;
border-radius: 12px;
text-align: center;
border: 1px solid #e2e8f0;
}
.summary-value {
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
}
.summary-label {
color: #64748b;
font-size: 0.9rem;
font-weight: 500;
}
.summary-text {
font-size: 1rem;
color: #475569;
line-height: 1.7;
background: #f1f5f9;
padding: 20px;
border-radius: 12px;
border-left: 4px solid #3b82f6;
}
.methodology {
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.method-steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.method-step {
background: #f8fafc;
padding: 20px;
border-radius: 12px;
border-left: 4px solid #10b981;
}
.step-number {
width: 30px;
height: 30px;
background: #10b981;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
margin-bottom: 10px;
}
.step-title {
font-weight: 600;
margin-bottom: 8px;
color: #1e293b;
}
.step-desc {
font-size: 0.9rem;
color: #64748b;
}
.results-section {
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.charts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.chart-card {
background: #f8fafc;
padding: 25px;
border-radius: 12px;
border: 1px solid #e2e8f0;
}
.chart-title {
font-size: 1.3rem;
font-weight: 600;
color: #1e293b;
margin-bottom: 20px;
text-align: center;
}
.chart-container {
position: relative;
height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* 饼图容器调整 */
.pie-chart-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
}
.pie-chart-svg {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.pie-legend {
margin-top: 15px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: #475569;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
/* D3.js 图表样式 */
.pie-chart .arc text {
font-size: 12px;
font-weight: 600;
fill: white;
text-anchor: middle;
}
.radar-chart .grid-line {
stroke: #e2e8f0;
stroke-width: 1;
fill: none;
}
.radar-chart .axis-line {
stroke: #94a3b8;
stroke-width: 1;
}
.radar-chart .axis-label {
font-size: 12px;
fill: #475569;
text-anchor: middle;
}
.radar-chart .data-area {
fill: rgba(59, 130, 246, 0.3);
stroke: #3b82f6;
stroke-width: 2;
}
.radar-chart .data-point {
fill: #3b82f6;
stroke: white;
stroke-width: 2;
}
.tooltip {
position: absolute;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.8);
color: white;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
}
.data-table-section {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
margin-bottom: 30px;
}
.table-header {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
padding: 25px;
border-bottom: 2px solid #e2e8f0;
}
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
th {
background: #f8fafc;
padding: 18px 20px;
text-align: left;
font-weight: 600;
color: #374151;
border-bottom: 2px solid #e2e8f0;
}
td {
padding: 16px 20px;
border-bottom: 1px solid #f1f5f9;
vertical-align: middle;
}
tr:hover {
background-color: #f8fafc;
}
.indicator-name {
display: flex;
align-items: flex-start;
gap: 12px;
}
.level-marker {
width: 4px;
height: 40px;
border-radius: 2px;
flex-shrink: 0;
margin-top: 2px;
}
.level-0 { background-color: #3b82f6; }
.level-1 { background-color: #10b981; }
.level-2 { background-color: #f59e0b; }
.level-3 { background-color: #8b5cf6; }
.level-content {
flex: 1;
}
.indicator-text {
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-size: 1rem;
}
.indicator-path {
font-size: 0.8rem;
color: #64748b;
margin-bottom: 2px;
}
.path-separator {
color: #94a3b8;
margin: 0 4px;
}
/* 隶属度分布显示样式 */
.membership-display {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 280px;
}
.membership-bar {
width: 100%;
height: 28px;
border-radius: 14px;
position: relative;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
border: 1px solid #e2e8f0;
overflow: hidden;
display: flex;
}
.membership-segment {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.segment-excellent { background-color: #10b981; } /* 优 - 绿色 */
.segment-good { background-color: #3b82f6; } /* 良 - 蓝色 */
.segment-fair { background-color: #f59e0b; } /* 可 - 黄色 */
.segment-poor { background-color: #ef4444; } /* 差 - 红色 */
.membership-values {
display: flex;
justify-content: space-between;
font-size: 0.7rem;
color: #374151;
font-weight: 600;
margin-bottom: 4px;
}
.analysis-section {
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.analysis-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-top: 20px;
}
.analysis-card {
background: #f8fafc;
padding: 25px;
border-radius: 12px;
border-left: 4px solid #f59e0b;
}
.analysis-title {
font-size: 1.2rem;
font-weight: 600;
color: #1e293b;
margin-bottom: 15px;
}
.analysis-list {
list-style: none;
}
.analysis-list li {
padding: 8px 0;
border-bottom: 1px solid #e2e8f0;
font-size: 0.95rem;
color: #475569;
}
.analysis-list li:last-child {
border-bottom: none;
}
.analysis-list li:before {
content: "▶";
color: #f59e0b;
font-weight: 700;
margin-right: 8px;
}
.level-1 .indicator-name { margin-left: 20px; }
.level-2 .indicator-name { margin-left: 40px; }
.level-3 .indicator-name { margin-left: 60px; }
.weight-display {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.weight-value {
font-weight: 700;
font-size: 1rem;
color: #1e293b;
}
.weight-bar {
width: 80px;
height: 16px;
background-color: #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.weight-fill {
height: 100%;
background: linear-gradient(90deg, #3b82f6, #1e40af);
border-radius: 8px;
}
.score-badge {
display: inline-block;
padding: 6px 14px;
border-radius: 20px;
font-weight: 600;
font-size: 0.85rem;
margin-top: 4px;
}
.floating-export {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
}
.export-btn-float {
width: 56px;
height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 50%;
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
}
.export-btn-float:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}
@media print {
.floating-export {
display: none !important;
}
}
/* 四级评价等级颜色 */
.score-excellent { background-color: #dcfce7; color: #166534; } /* 优 - 绿色 */
.score-good { background-color: #dbeafe; color: #1e40af; } /* 良 - 蓝色 */
.score-fair { background-color: #fef3c7; color: #92400e; } /* 可 - 黄色 */
.score-poor { background-color: #fee2e2; color: #dc2626; } /* 差 - 红色 */
@media (max-width: 1200px) {
.charts-grid, .analysis-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.export-btn {
position: relative;
top: auto;
right: auto;
margin-bottom: 20px;
align-self: center;
}
.report-header {
text-align: left;
display: flex;
flex-direction: column;
}
.report-title { font-size: 2rem; }
.summary-grid { grid-template-columns: repeat(2, 1fr); }
.method-steps { grid-template-columns: 1fr; }
.pie-legend {
flex-direction: column;
gap: 8px;
}
}
@media print {
.export-btn {
display: none !important;
}
body {
background-color: white !important;
}
.report-container {
max-width: none !important;
padding: 0 !important;
}
}
</style>
<div class="floating-export">
<button class="export-btn-float" onclick="downloadPDF()" id="pdfBtn">
<span id="btnText">📄</span>
<div id="btnSpinner" class="loading-spinner" style="display: none;"></div>
</button>
</div>
<div class="report-container" id="reportContent">
<!-- 报告头部 -->
<div class="report-header">
<!-- PDF导出按钮 - 嵌入在头部右上角 -->
<h1 class="report-title" id="report-title"></h1>
<p class="report-subtitle">基于层次分析法的多维度综合评估</p>
<div class="report-meta">
<span id="report-meta-object"></span>
<span id="report-meta-date"></span>
</span>
<span>评价方法:模糊综合评价法</span>
</div>
</div>
<!-- 执行摘要 -->
<div class="executive-summary">
<h2 class="section-title">📊 摘要信息</h2>
<div class="summary-grid">
<div class="summary-card">
<div class="summary-value" style="color: #3b82f6;" id="object_score"></div>
<div class="summary-label">综合得分</div>
</div>
<div class="summary-card">
<div class="summary-value" style="color: #10b981;" id="object_level"></div>
<div class="summary-label" >评价等级</div>
</div>
<div class="summary-card">
<div class="summary-value" style="color: #f59e0b;" id="highRate"> 70.0%</div>
<div class="summary-label" >优良率</div>
</div>
<div class="summary-card">
<div class="summary-value" style="color: #8b5cf6;" id="bottomNum">10</div>
<div class="summary-label">底层指标数</div>
</div>
</div>
</div>
<!-- 方法论说明 -->
<div class="methodology">
<h2 class="section-title">🔬 评价方法论</h2>
<div class="method-steps">
<div class="method-step">
<div class="step-number">1</div>
<div class="step-title">构建指标体系</div>
<div class="step-desc">建立3级指标体系包含学术能力、社会影响、个人发展等维度</div>
</div>
<div class="method-step">
<div class="step-number">2</div>
<div class="step-title">确定权重分配</div>
<div class="step-desc">运用层次分析法确定各级指标权重,确保评价体系的科学性</div>
</div>
<div class="method-step">
<div class="step-number">3</div>
<div class="step-title">隶属度计算</div>
<div class="step-desc">建立模糊评价矩阵,计算各指标对不同等级的隶属度</div>
</div>
<div class="method-step">
<div class="step-number">4</div>
<div class="step-title">综合评价</div>
<div class="step-desc">运用模糊综合评价算法,得出最终评价结果和等级分布</div>
</div>
</div>
</div>
<!-- 结果展示 -->
<div class="results-section">
<h2 class="section-title">📈 评价结果分析</h2>
<div class="charts-grid">
<div class="chart-card">
<h3 class="chart-title">隶属度等级分布</h3>
<div class="chart-container">
<div class="pie-chart-wrapper" id="pieChartWrapper"></div>
</div>
</div>
<div class="chart-card">
<h3 class="chart-title">一级维度得分对比</h3>
<div class="chart-container">
<svg id="radarChart"></svg>
</div>
</div>
</div>
</div>
<!-- 详细数据表格 -->
<div class="data-table-section">
<div class="table-header">
<h2 class="section-title">📋 详细评价数据</h2>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th style="width: 35%;">指标名称</th>
<th style="width: 15%;">权重</th>
<th style="width: 30%;">隶属度分布</th>
<th style="width: 15%;">综合得分</th>
<th style="width: 5%;">等级</th>
</tr>
</thead>
<tbody id="indicatorTable">
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
<div class="col-12">
<div class="card">
<div class="card-header">
<div class="card-title">指标评估记录</div>
<div class="card-actions">
<a href="#" class="btn btn-info w-100" th:data-result-id="${projectId}" onclick="goToHistory(this)">
评估历史
</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive" style="min-height:30em;overflow-x: auto;">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th>NO.</th>
<th>名称</th>
<th>最终分数</th>
<th>隶属度</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="history,stat : ${rootList}">
<td th:text="${stat.count}">
</td>
<td th:text="${history.identifier}">
</td>
<td th:text="${history.finalScore}">
</td>
<td th:text="${history.membershipScore}">
</td>
<td>
<div class="btn-list flex-nowrap"><a href="#" class="btn"
th:data-result-id="${history.id}"
onclick="viewResult(this)">
查看报告
</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More