1. 指标子集映射

This commit is contained in:
李玉东 2025-09-19 20:26:28 +08:00
parent 3b33c3d95a
commit 1cae057f4b
104 changed files with 2201 additions and 1081 deletions

View File

@ -12,7 +12,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -8,7 +8,7 @@
<version>1.0</version>
</parent>
<artifactId>manager-admin</artifactId>
<artifactId>evaluation-indicator</artifactId>
<packaging>jar</packaging>
<name>manager-admin</name>
@ -39,15 +39,7 @@
</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,140 +0,0 @@
<!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>
<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

@ -0,0 +1,67 @@
package com.hshh.config;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.hshh.indicator.entity.IndicatorTopLevel;
import com.hshh.indicator.entity.IndicatorTopSet;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Component
@Slf4j
@Data
public class DefaultIndicatorSetConfig {
private static final String configPath = "./config/indicator_set.yaml";
private List<IndicatorTopLevel> levels;
private IndicatorTopSet function;
@PostConstruct
void load() {
try {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
Map root;
//首先查找配置
if (new File(configPath).exists()) {
root = mapper.readValue(new File(configPath), Map.class);
} else { //读取classpath
try (InputStream input = DefaultIndicatorSetConfig.class.getClassLoader()
.getResourceAsStream("indicator_set.yaml")) {
root = mapper.readValue(input, Map.class);
}
}
Map indicator = (Map) root.get("indicator");
Map def = (Map) indicator.get("default");
this.levels = mapper.convertValue(def.get("levels"),
new TypeReference<List<IndicatorTopLevel>>() {
});
this.function = mapper.convertValue(def.get("function"), IndicatorTopSet.class);
log.info(" this.function::{}", this.function);
} catch (Exception e) {
log.error("error:", e);
}
}
}

View File

@ -0,0 +1,29 @@
package com.hshh.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hshh.indicator.entity.IndicatorTopLevel;
import com.hshh.indicator.entity.IndicatorTopSet;
import java.util.List;
import java.util.Map;
import lombok.Data;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class IndicatorConfig {
@JsonCreator
public IndicatorConfig(Map<String, Object> root) {
}
}

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@ -74,7 +75,7 @@ public class DataController extends BaseController {
@LogOperation("进入数据管理")
@GetMapping("/list")
@Operation(summary = "导航到数据管理页面", description = "导航到数据管理页面")
public String index(PaginationBean request, Model model) {
public String index(PaginationBean request, Model model, HttpServletRequest httpServletRequest) {
setNavigateTitle(model, "/data/list");
List<ModelDefine> modelDefineList = modelDefineService.list(); //查询所有数据模型列表
modelDefineList.sort(Comparator.comparing(ModelDefine::getSortOrder)); //对模型数据列表排序
@ -154,7 +155,8 @@ public class DataController extends BaseController {
@GetMapping("/getForm/{id}")
@Operation(summary = "获取form输入窗体", description = "根据模型ID获取form窗体")
@ResponseBody
public OperateResult<String> getForm(@PathVariable("id") Integer id) {
public OperateResult<String> getForm(@PathVariable("id") Integer id,
HttpServletRequest httpServletRequest) {
return OperateResult.success(dataRecordService.html(id), ErrorMessage.SUCCESS.getMessage());
}
@ -169,7 +171,8 @@ public class DataController extends BaseController {
@PostMapping("/form/save")
@ResponseBody
@Operation(summary = "保存记录", description = "保存对应模型ID对应的form表单记录")
public OperateResult<Void> saveRecord(@RequestBody FormValue formValue) {
public OperateResult<Void> saveRecord(@RequestBody FormValue formValue,
HttpServletRequest httpServletRequest) {
//验证字段合法性
Integer modelId = formValue.getModelDefineId();
@ -193,7 +196,8 @@ public class DataController extends BaseController {
@ResponseBody
@GetMapping("/remove/{id}")
@Operation(summary = "删除记录", description = "根据ID删除记录")
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
public OperateResult<Void> remove(@PathVariable("id") Integer id
) {
formValueService.removeById(id);
return OperateResult.success();
}
@ -207,7 +211,8 @@ public class DataController extends BaseController {
@ResponseBody
@GetMapping("/{id}")
@Operation(summary = "删除记录", description = "根据ID删除记录")
public OperateResult<FormValue> view(@PathVariable("id") Integer id) {
public OperateResult<FormValue> view(@PathVariable("id") Integer id
) {
FormValue formValue = formValueService.getById(id);
if (formValue == null) {
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),

View File

@ -28,4 +28,5 @@ public interface DataRecordService {
* @return 错误信息
*/
List<ErrorField> getErrorField(List<FormFieldConfig> formConfigList, FormValue formValue);
void deleteFromByModelId(Integer id);
}

View File

@ -116,6 +116,22 @@ public class DataRecordServiceImpl implements DataRecordService {
.append(field.getFieldId())
.append("\" class=\"form-control\" autocomplete=\"off\" >");
break;
case DATE:
sb.append("<input type=\"date\" name=\"").append(field.getFieldName()).append("\" id=\"")
.append(field.getFieldId())
.append("\" class=\"form-control\" autocomplete=\"off\" >");
break;
case DATETIME:
sb.append("<input type=\"datetime\" name=\"").append(field.getFieldName())
.append("\" id=\"")
.append(field.getFieldId())
.append("\" class=\"form-control\" autocomplete=\"off\" >");
break;
case TIME:
sb.append("<input type=\"time\" name=\"").append(field.getFieldName()).append("\" id=\"")
.append(field.getFieldId())
.append("\" class=\"form-control\" autocomplete=\"off\" >");
break;
default:
break;
}
@ -134,4 +150,9 @@ public class DataRecordServiceImpl implements DataRecordService {
return errorFieldList;
}
@Override
public void deleteFromByModelId(Integer id) {
}
}

View File

@ -40,6 +40,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -73,11 +74,7 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
@Resource
private EvaluationTemplateService evaluationTemplateService;
/**
* 数据库引用关系记录服务类.
*/
@Resource
private TableRelationsService tableRelationsService;
/**
* 基础设施服务类.
@ -141,7 +138,13 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
* @return evaluation/add.html
*/
@GetMapping("/add")
public String add(Model model) {
public String add(Model model, HttpServletRequest request) {
int id =
request.getParameter("id") == null ? 0 : Integer.parseInt(request.getParameter("id"));
if (id != 0) {
EvaluationProject project = evaluationProjectService.getById(id);
model.addAttribute("project", project);
}
List<EvaluationTemplate> rootList = evaluationTemplateService.list();
model.addAttribute("rootList", rootList);
return "project/add";
@ -191,12 +194,8 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
@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()) {
return OperateResult.error(null, ErrorMessage.OBJ_ALREADY_TAKEN.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
evaluationProjectService.removeById(id);
evaluationProjectService.deleteTheWholeProject(id);
return OperateResult.success();
}
@ -274,9 +273,7 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
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"));
}
@ -356,7 +353,7 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
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());
}
@ -442,7 +439,7 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
reportBean.setBottomNum(bottomNum);
//设置所在评价集区间名称
List<IndicatorTopLevel> levelList = topLevelService.getTopLevel(rootResult.getIndicatorTopId());
List<IndicatorTopLevel> levelList = topLevelService.getTopLevel();
levelList.sort((a, b) -> Double.compare(Double.parseDouble(b.getGrade()),
Double.parseDouble(a.getGrade())));
setLevelName(levelList, rootResult);
@ -502,5 +499,11 @@ public class EvaluationProjectController extends AssistantEvaluationProjectContr
return "project/evaluation_history";
}
@GetMapping("/history/remove/{randomKey}")
@ResponseBody
public OperateResult<Void> deleteFromRandomKey(@PathVariable("randomKey") String randomKey) {
evaluationHistoryService.removeByRandomKey(randomKey);
return OperateResult.success();
}
}

View File

@ -3,19 +3,21 @@ package com.hshh.evaluation.controller;
import com.hshh.evaluation.bean.PageMetricComputeRequest;
import com.hshh.evaluation.bean.PageMetricComputerResponse;
import com.hshh.evaluation.bean.PageMetricMapperWeightBean;
import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.evaluation.entity.EvaluationTemplate;
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import com.hshh.evaluation.service.EvaluationProjectService;
import com.hshh.evaluation.service.EvaluationTemplateIndicatorWeightService;
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.algorithm.ahp.AhpNode;
import com.hshh.system.algorithm.ahp.AhpTreeCompute;
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.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;
@ -62,11 +64,7 @@ public class EvaluationTemplateController extends AssistantTemplateController {
@Resource
private IndicatorService indicatorService;
/**
* 数据库引用关系记录服务类.
*/
@Resource
private TableRelationsService tableRelationsService;
/**
* 权重服务详情类.
@ -78,7 +76,8 @@ public class EvaluationTemplateController extends AssistantTemplateController {
*/
@Resource
private EvaluationTemplateIndicatorWeightService evaluationTemplateIndicatorWeightService;
@Resource
private EvaluationProjectService evaluationProjectService;
/**
* 默认页.
@ -154,10 +153,10 @@ public class EvaluationTemplateController extends AssistantTemplateController {
@ResponseBody
@LogOperation("删除模板")
public OperateResult<Object> remove(@PathVariable("id") Integer id) {
List<TableRelations> reledList = tableRelationsService.queryRel(id,
"m_data_evaluation_template");
if (!reledList.isEmpty()) {
return OperateResult.error(null, ErrorMessage.OBJ_ALREADY_TAKEN.getMessage(),
List<EvaluationProject> projectList = evaluationProjectService.getByTemplateId(id);
if (!projectList.isEmpty()) {
return OperateResult.error(null, "有关联的工程占用",
ErrorCode.BUSINESS_ERROR.getCode());
}
evaluationTemplateService.deleteTemplate(evaluationTemplateService.getById(id));

View File

@ -47,4 +47,6 @@ public interface EvaluationCsvDataService extends IService<EvaluationCsvData> {
* @return 结果列表
*/
List<EvaluationCsvData> selectByIds(@Param("ids") List<Integer> ids);
void deleteFromRandomKey(String key);
void deleteByProjectId(Integer projectId);
}

View File

@ -35,4 +35,7 @@ public interface EvaluationHistoryService extends IService<EvaluationHistory> {
* @return 历史记录
*/
List<EvaluationHistory> queryListByProjectId(Integer projectId);
void removeByRandomKey(String randomKey);
List<String> queryRandomKeysByProjectId(Integer projectId);
}

View File

@ -69,4 +69,6 @@ public interface EvaluationProjectService extends IService<EvaluationProject> {
* @param uploadInfo csv文件信息
*/
void uploadCsv(CsvUploadBean uploadInfo) throws IOException;
List<EvaluationProject> getByTemplateId(Integer templateId);
void deleteTheWholeProject(Integer id);
}

View File

@ -2,6 +2,7 @@ package com.hshh.evaluation.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.hshh.config.DefaultIndicatorSetConfig;
import com.hshh.evaluation.bean.PageEvaluationRequest;
import com.hshh.evaluation.bean.SingleEvaluationData;
import com.hshh.evaluation.entity.EvaluationCsvData;
@ -83,7 +84,8 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
private EvaluationHistoryService evaluationHistoryService; //评估历史批次记录
@Resource
private EvaluationRootResultService evaluationRootResultService; //评估详情及概况记录
@Resource
private DefaultIndicatorSetConfig defaultIndicatorSetConfig;
// 定义分页大小常量便于管理
private static final int DEFAULT_PAGE_SIZE = 100;
@ -101,8 +103,10 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
Integer indicatorTopId = template.getIndicatorTopId();
request.setIndicatorTopId(indicatorTopId);
IndicatorTopSet membershipFuncSet = getMembershipFuncSetOrThrow(indicatorTopId); //隶属函数设置
List<IndicatorTopLevel> topLevelList = getTopLevelListOrThrow(indicatorTopId); //评价集区间
Indicator indicator = indicatorService.getIndicator(indicatorTopId); //获取指标
Indicator indicator = indicatorService.getRecursionIndicator(indicatorTopId); //获取指标
Map<Integer, String> indicatorWeightMap = evaluationTemplateIndicatorWeightService.getEvaluationTemplateIndicatorWeightMap(
indicatorTopId, template.getId()); //指标权重map
@ -167,10 +171,12 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
DataSourceStrategy<T> strategy, Map<Integer, String> indicatorWeightMap,
Map<Integer, List<IndicatorEvalItem>> indicatorEvalItemMap) {
//首先记录本次评估
//获取全局评估函数
request.setMembership(membershipFuncSet.getMembershipFunc());
//设置运算方式
request.setMethod(membershipFuncSet.getMethod());
evaluationHistoryService.saveWhole(request);
//查看是否指定某些记录进行评估
boolean hasSpecificIds = request.getIds() != null && request.getIds().length > 0;
if (hasSpecificIds) {
@ -324,17 +330,17 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
}
private IndicatorTopSet getMembershipFuncSetOrThrow(Integer indicatorTopId) {
IndicatorTopSet set = indicatorTopSetService.getIndicatorTopSet(indicatorTopId);
IndicatorTopSet set = indicatorTopSetService.getIndicatorTopSet();
if (set == null) {
throw new EvaluationException("没有找到对应的隶属度函数");
return defaultIndicatorSetConfig.getFunction();
}
return set;
}
private List<IndicatorTopLevel> getTopLevelListOrThrow(Integer indicatorTopId) {
List<IndicatorTopLevel> list = indicatorTopLevelService.getTopLevel(indicatorTopId);
List<IndicatorTopLevel> list = indicatorTopLevelService.getTopLevel();
if (list == null || list.isEmpty()) {
throw new EvaluationException("没有找到全局指标集区间和分值");
return defaultIndicatorSetConfig.getLevels();
}
return list;
}
@ -354,14 +360,15 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
topLevelList.forEach(topLevel -> {
EvaluationLevel evaluationLevel = new EvaluationLevel(topLevel.getLevelName(),
Double.parseDouble(topLevel.getGrade()));
Double.parseDouble(topLevel.getGrade()),
topLevel.getEqualValue() == null ? "" : topLevel.getEqualValue());
evaluationLevels.add(evaluationLevel);
});
return new GlobalEvaluationConfig(evaluationLevels);
}
/**
* 核心评估触发点此方法保持不变.
* 核心评估触发点.
*/
private void startEvaluation(PageEvaluationRequest request, Integer userId,
IndicatorTopSet membershipFuncSet, List<IndicatorTopLevel> topLevelList,
@ -409,7 +416,8 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
globalEvaluationConfig, membershipFuncSet, indicatorEvalItemMap);
FuzzyEvaluationService service = new FuzzyEvaluationService(globalEvaluationConfig);
service.performFuzzyEvaluation(root, FuzzyOperator.valueOf(request.getMethod()));
//核心评估行为
service.performFuzzyEvaluation(root, FuzzyOperator.valueOf(membershipFuncSet.getMethod()));
resultList.add(root);
}
saveResult(resultList);
@ -457,6 +465,7 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
Double.parseDouble(membershipFuncSet.getTrianglePeakRatio()),
Double.parseDouble(membershipFuncSet.getSoftEdgeS()),
globalEvaluationConfig));
//设置权重
childNode.setWeight(indicatorWeightMap.get(child.getId()) == null ? 1
: Double.parseDouble(indicatorWeightMap.get(child.getId())));
@ -473,7 +482,7 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
if (fieldName != null) {
Object currentValue = valueMap.get(fieldName);
if (currentValue != null) {
node.setActualValue(Double.parseDouble(currentValue.toString()));
node.setActualValue(currentValue);
}
}
@ -499,6 +508,7 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
}
}
//设置节点的评价集范围
private List<GradeRange> getGradeRange(List<IndicatorEvalItem> indicatorEvalItemList) {
List<GradeRange> list = new ArrayList<>();
indicatorEvalItemList.sort(
@ -506,7 +516,7 @@ public class CoreEvaluationServiceImpl implements CoreEvaluationService {
indicatorEvalItemList.forEach(indicatorEvalItem -> {
GradeRange gradeRange = new GradeRange(indicatorEvalItem.getEvaluationName(),
Double.parseDouble(indicatorEvalItem.getBottomValue()),
Double.parseDouble(indicatorEvalItem.getTopValue()));
Double.parseDouble(indicatorEvalItem.getTopValue()), indicatorEvalItem.getEqualValue());
list.add(gradeRange);
});
return list;

View File

@ -1,6 +1,7 @@
package com.hshh.evaluation.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.evaluation.bean.CsvUploadBean;
import com.hshh.evaluation.entity.EvaluationCsvData;
@ -58,4 +59,18 @@ public class EvaluationCsvDataServiceImpl extends
public List<EvaluationCsvData> selectByIds(List<Integer> ids) {
return this.baseMapper.selectByIds(ids);
}
@Override
public void deleteFromRandomKey(String key) {
QueryWrapper<EvaluationCsvData> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("random_key", key);
remove(queryWrapper);
}
@Override
public void deleteByProjectId(Integer projectId) {
QueryWrapper<EvaluationCsvData> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("project_id", projectId);
remove(queryWrapper);
}
}

View File

@ -5,9 +5,14 @@ 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.EvaluationCsvDataService;
import com.hshh.evaluation.service.EvaluationHistoryService;
import com.hshh.evaluation.service.EvaluationRootResultService;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,6 +29,11 @@ public class EvaluationHistoryServiceImpl extends
ServiceImpl<EvaluationHistoryMapper, EvaluationHistory> implements
EvaluationHistoryService {
@Resource
private EvaluationRootResultService evaluationRootResultService;
@Resource
private EvaluationCsvDataService evaluationCsvDataService;
@Transactional
@Override
public void saveWhole(PageEvaluationRequest request) {
@ -54,4 +64,22 @@ public class EvaluationHistoryServiceImpl extends
queryWrapper.orderByDesc("id");
return this.baseMapper.selectList(queryWrapper);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void removeByRandomKey(String randomKey) {
evaluationRootResultService.deleteByRandomKey(randomKey);
evaluationCsvDataService.deleteFromRandomKey(randomKey);
QueryWrapper<EvaluationHistory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("random_key", randomKey);
remove(queryWrapper);
}
@Override
public List<String> queryRandomKeysByProjectId(Integer projectId) {
QueryWrapper<EvaluationHistory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("project_id", projectId);
return this.list(queryWrapper).stream().map(EvaluationHistory::getRandomKey).collect(Collectors.toList());
}
}

View File

@ -8,24 +8,18 @@ import com.hshh.evaluation.entity.EvaluationProject;
import com.hshh.evaluation.mapper.EvaluationProjectMapper;
import com.hshh.evaluation.service.CoreEvaluationService;
import com.hshh.evaluation.service.EvaluationCsvDataService;
import com.hshh.evaluation.service.EvaluationHistoryService;
import com.hshh.evaluation.service.EvaluationProjectService;
import com.hshh.system.base.service.TableRelationsService;
import com.hshh.system.common.bean.PaginationBean;
import com.hshh.system.common.util.FileUtil;
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.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;
@ -58,8 +52,8 @@ public class EvaluationProjectServiceImpl extends
*/
@Resource
private CoreEvaluationService coreEvaluationService;
@Resource
private EvaluationHistoryService evaluationHistoryService;
@Override
public List<EvaluationProject> list(PaginationBean paginationBean) {
return this.baseMapper.list(paginationBean);
@ -110,39 +104,46 @@ public class EvaluationProjectServiceImpl extends
@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)) {
// 取有序表头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);
List<List<String>> dataList = FileUtil.parseCsvFromInputStream(uploadInfo.getFile());
if (dataList.isEmpty()) {
return;
}
List<String> headers = dataList.get(0);
for (int i = 1; i < dataList.size(); i++) {
Map<String, String> row = new LinkedHashMap<>();
List<String> data = dataList.get(i);
for (int j = 0; j < data.size(); j++) {
row.put(headers.get(j), data.get(j));
}
rows.add(row);
}
evaluationCsvDataService.saveWhole(rows, uploadInfo);
}
@Override
public List<EvaluationProject> getByTemplateId(Integer templateId) {
QueryWrapper<EvaluationProject> wrapper = new QueryWrapper<>();
wrapper.eq("template_id", templateId);
return this.list(wrapper);
}
@Override
@Transactional
public void deleteTheWholeProject(Integer id) {
//删除关联表csv_data
evaluationCsvDataService.deleteByProjectId(id);
//删除历史表
List<String> historyRandomList = evaluationHistoryService.queryRandomKeysByProjectId(id);
historyRandomList.forEach(historyRandom -> {
evaluationHistoryService.removeByRandomKey(historyRandom);
});
this.removeById(id);
}
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.evaluation.entity.EvaluationTemplateIndicatorWeight;
import com.hshh.evaluation.mapper.EvaluationTemplateIndicatorWeightMapper;
import com.hshh.evaluation.service.EvaluationTemplateIndicatorWeightService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -36,7 +37,7 @@ public class EvaluationTemplateIndicatorWeightServiceImpl extends
Collectors.toMap(EvaluationTemplateIndicatorWeight::getIndicatorId,
EvaluationTemplateIndicatorWeight::getWeight));
}
return Map.of();
return new HashMap<>();
}
@Transactional(rollbackFor = Exception.class)

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.evaluation.entity.EvaluationTemplateWeight;
import com.hshh.evaluation.mapper.EvaluationTemplateWeightMapper;
import com.hshh.evaluation.service.EvaluationTemplateWeightService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -43,7 +44,7 @@ public class EvaluationTemplateWeightServiceImpl extends
return list.stream()
.collect(Collectors.groupingBy(EvaluationTemplateWeight::getIndicatorParentId));
}
return Map.of();
return new HashMap<>();
}
@Override

View File

@ -3,10 +3,10 @@ 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.entity.IndicatorTopLevel;
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;
@ -46,6 +46,8 @@ public class EvaluationController extends BaseController {
*/
@Resource
private IndicatorEvalItemService indicatorEvalItemService;
@Resource
private IndicatorTopLevelService indicatorTopLevelService;
/**
* 导航到评价集设置页面.
@ -56,6 +58,9 @@ public class EvaluationController extends BaseController {
public String evaluationList(Model model) {
setNavigateTitle(model, "/evaluation/evaluationList");
List<Indicator> bottomList = indicatorService.queryRootList();
//查询全局评价级别
List<IndicatorTopLevel> levelList = indicatorTopLevelService.getTopLevel();
model.addAttribute("levelList", levelList);
model.addAttribute("rootList", bottomList);
return "indicator/evaluation_list";
}
@ -104,7 +109,7 @@ public class EvaluationController extends BaseController {
child.setText(a.getName());
if (evalMap.containsKey(a.getId())) {
List<IndicatorEvalItem> evalItemList = evalMap.get(a.getId());
evalItemList = evalItemList.stream().peek(aa -> {
evalItemList = evalItemList.stream().peek(aa -> {
if (aa.getEqualValue() == null) {
aa.setEqualValue("");
}

View File

@ -23,13 +23,17 @@ import com.hshh.system.common.bean.CheckedBean;
import com.hshh.system.common.bean.OperateResult;
import com.hshh.system.common.enums.ErrorCode;
import com.hshh.system.common.enums.ErrorMessage;
import com.hshh.system.common.util.StringTool;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@ -151,7 +155,7 @@ public class IndicatorController extends BaseController {
@ResponseBody
@GetMapping("/{id}")
public OperateResult<Object> view(@PathVariable("id") Integer id) {
Indicator indicator = indicatorService.getIndicator(id);
Indicator indicator = indicatorService.getRecursionIndicator(id);
if (indicator == null) {
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
@ -175,23 +179,28 @@ public class IndicatorController extends BaseController {
if (bindingResult.hasErrors()) {
return errorsInputHandle(bindingResult);
}
//查看名称或者编码是否重复
List<Indicator> exitList = indicatorService.queryList(indicator.getTopId(), indicator.getName(),
indicator.getCode());
if (indicator.getParentId() == null) {
indicator.setParentId(0);
}
//查看名称
List<Indicator> exitList = indicatorService.queryList(indicator.getParentId(),
indicator.getName()
);
if (indicator.getId() == null) {
if (!exitList.isEmpty()) {
return OperateResult.error(null, ErrorMessage.NAME_OR_CODE_EXIT.getMessage(),
return OperateResult.error(null, ErrorMessage.NAME_EXIT.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
indicatorService.save(indicator);
} else { //编辑
if (!exitList.isEmpty()) {
if (!exitList.get(0).getId().equals(indicator.getId())) {
return OperateResult.error(null, ErrorMessage.NAME_OR_CODE_EXIT.getMessage(),
return OperateResult.error(null, ErrorMessage.NAME_EXIT.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
indicatorService.updateById(indicator);
}
indicatorService.updateById(indicator);
}
return OperateResult.success(indicator, ErrorMessage.SUCCESS.getMessage());
}
@ -206,18 +215,13 @@ public class IndicatorController extends BaseController {
@ResponseBody
@LogOperation("删除指标")
@Operation(summary = "删除指标", description = "根据ID删除指定指标")
public OperateResult<Void> remove(@PathVariable("id") Integer id) {
public OperateResult<Void> remove(@PathVariable("id") Integer id) throws Exception {
if (id == null) {
return OperateResult.error(null, ErrorMessage.ID_NOT_EXIT.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
//查询此ID下是否有子指标
List<Indicator> children = indicatorService.queryChildren(id);
if (children != null && !children.isEmpty()) {
return OperateResult.error(null, ErrorMessage.OBJ_HAS_SUB.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
indicatorService.removeById(id);
indicatorService.deleteIndicator(id);
return OperateResult.success();
}
@ -245,8 +249,8 @@ public class IndicatorController extends BaseController {
model.addAttribute("rootList", rootList);
// 底层指标放入session容器
if (indicatorTopId != null) {
model.addAttribute("childrenIndicator", indicatorService.selectNoChildByTopId(
indicatorTopId));
model.addAttribute("childrenIndicator",
indicatorService.selectNoChildByTopId(indicatorTopId));
}
// form表单和顶级指标对应列表放入session容器
@ -262,8 +266,7 @@ public class IndicatorController extends BaseController {
indicatorTopId == null ? (rootList.isEmpty() ? 0 : rootList.get(0).getId())
: indicatorTopId);
Map<Integer, Integer> bottomFormMap = bottomList.stream()
.filter(a -> a.getFormFieldId() != null)
.collect(
.filter(a -> a.getFormFieldId() != null).collect(
Collectors.toMap(IndicatorBottomFormMapper::getIndicatorId,
IndicatorBottomFormMapper::getFormFieldId));
model.addAttribute("bottomFormMap", bottomFormMap);
@ -273,8 +276,7 @@ public class IndicatorController extends BaseController {
(indicatorTopId == null ? (rootList.isEmpty() ? 0 : rootList.get(0).getId())
: indicatorTopId));
Map<Integer, Integer> bottomCsvColumnMap = bottomCsvColumnList.stream()
.filter(a -> a.getCsvColumnId() != null)
.collect(
.filter(a -> a.getCsvColumnId() != null).collect(
Collectors.toMap(IndicatorBottomCsvMapper::getIndicatorId,
IndicatorBottomCsvMapper::getCsvColumnId));
model.addAttribute("bottomCsvColumnMap", bottomCsvColumnMap);
@ -322,7 +324,7 @@ public class IndicatorController extends BaseController {
response.setContentType("text/csv; charset=UTF-8");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + java.net.URLEncoder.encode(csvFile.getCsvName(),
StandardCharsets.UTF_8) + "\"");
"utf-8") + "\"");
// 写入文件流
try (OutputStream os = response.getOutputStream()) {
@ -423,4 +425,52 @@ public class IndicatorController extends BaseController {
indicatorService.saveBottomMapper(mapperBean);
return OperateResult.success();
}
/**
* 批量导入.
*
* @param file 原始文件
* @param parentId 父ID
* @param topId 根节点ID
* @return 操作结果
*/
@PostMapping("/import")
@ResponseBody
public OperateResult<Void> importChildIndicator(@RequestParam("file") MultipartFile file,
Integer parentId, Integer topId) throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
}
String content = sb.toString();
Set<String> duplicateSet = StringTool.checkDuplicates(content, "[,;\\n]");
//本次记录有没有重复名称
if (!duplicateSet.isEmpty()) {
return OperateResult.error(null, "存在重复的名称" + duplicateSet,
ErrorCode.BUSINESS_ERROR.getCode());
}
content = content.replaceAll("\\s+", "");
String[] childTextArray = content.split("[,;\\n]");
//数据库中是否存在重名
List<Indicator> exitList = indicatorService.selectByName(parentId, childTextArray);
if (!exitList.isEmpty()) {
StringBuffer tipsBuffer = new StringBuffer();
exitList.forEach(item -> {
tipsBuffer.append(item.getName()).append(";");
});
return OperateResult.error(null, "存在重复的名称" + tipsBuffer,
ErrorCode.BUSINESS_ERROR.getCode());
}
Long maxOrder = indicatorService.selectMaxOrderByParentId(parentId);
if (maxOrder == null) {
maxOrder = 9999L;
}
indicatorService.batchImport(parentId, topId, maxOrder, childTextArray);
return OperateResult.success();
}
}

View File

@ -1,9 +1,13 @@
package com.hshh.indicator.controller;
import com.hshh.indicator.bean.IndicatorSetBean;
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.OperateResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -11,6 +15,7 @@ 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;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 指标全局函数设置.
@ -23,20 +28,34 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Tag(name = "指标全局函数设置", description = "指标全局函数设置")
public class IndicatorSetController extends BaseController {
@Resource
private IndicatorTopLevelService topLevelService;
@Resource
private IndicatorTopSetService indicatorTopSetService;
/**
* 指标全局函数默认设置页面.
*
* @param model 数据容器
* @param request 页面请求
* @param model 数据容器
* @return indicator_set.html
*/
@GetMapping("/list")
public String list(Model model, HttpServletRequest request) {
@LogOperation("获取全局函数默认设置")
public String list(Model model) {
setNavigateTitle(model, "/indicatorSet/list");
IndicatorSetBean indicatorSetBean = new IndicatorSetBean();
indicatorSetBean.setLevels(topLevelService.getTopLevel());
indicatorSetBean.setTopSet(indicatorTopSetService.getIndicatorTopSet());
model.addAttribute("indicatorSetBean", indicatorSetBean);
return "system/indicator/indicator_set";
}
@PostMapping("/save")
public OperateResult<Void> save(@RequestBody IndicatorSetBean indicatorSetBean) {
@LogOperation("保存全局函数默认设置")
@PostMapping("/save")
@ResponseBody
public OperateResult<Void> save(@RequestBody IndicatorSetBean indicatorSetBean
) {
topLevelService.saveWhole(indicatorSetBean);
return OperateResult.success();
}
}

View File

@ -29,9 +29,8 @@ public class Indicator extends CheckedBean {
private Integer id;
private Integer parentId;
@NotBlank(message = "编码不能为空")
@Size(max = 30, message = "编码不能超过30字符")
private String code;
@NotBlank(message = "名称不能为空")
@Size(max = 30, message = "名称不能超过30字符")
private String name;

View File

@ -28,4 +28,15 @@ public interface IndicatorMapper extends BaseMapper<Indicator> {
* @return 指标列表
*/
List<Indicator> selectByTemplateId(@Param("templateId") Integer templateId);
/**
* 获取同一个父指标下最大顺序.
*
* @param parentId 父ID
* @return 当前最大顺序
*/
Long selectMaxOrderByParentId(Integer parentId);
List<Indicator> selectByName(@Param("parentId") Integer parentId,
@Param("nameList") String[] nameList);
}

View File

@ -41,4 +41,5 @@ public interface IndicatorBottomFormMapperService extends IService<IndicatorBott
* @return 对应关系列表
*/
List<IndicatorBottomFormMapper> selectIndicatorToFieldNameListByIndicatorTopId(Integer topId);
void deleteByFormFieldId(Integer formFieldId);
}

View File

@ -36,4 +36,5 @@ public interface IndicatorFromMapperService extends IService<IndicatorFormMapper
* @param indicatorTopId 指标顶级ID
*/
void deleteFormMapperByIndicatorId(Integer indicatorTopId);
void deleteByModelId(Integer modelId);
}

View File

@ -22,12 +22,12 @@ public interface IndicatorService extends IService<Indicator> {
/**
* 根据名称ID,编码获取此ID所有的顶级指标下包含的指标的名称和编码是否有一致.
*
* @param topId 指标ID(用于获取顶级父ID)
* @param parentId 父ID
* @param name 名称
* @param code 编码
* @return 结果列表
*/
List<Indicator> queryList(Integer topId, String name, String code);
List<Indicator> queryList(Integer parentId, String name);
/**
* 获取所有的根指标.
@ -42,7 +42,7 @@ public interface IndicatorService extends IService<Indicator> {
* @param id 指定ID
* @return 对应指标
*/
Indicator getIndicator(Integer id);
Indicator getRecursionIndicator(Integer id);
/**
* 根据父节点查询直接孩子.
@ -109,5 +109,33 @@ public interface IndicatorService extends IService<Indicator> {
*/
Indicator selectByTemplateId(@Param("templateId") Integer templateId);
/**
* 获取同一个父指标下最大顺序.
*
* @param parentId 父ID
* @return 当前最大顺序
*/
Long selectMaxOrderByParentId(Integer parentId);
/**
* 批量导入子指标.
*
* @param parentId 父ID
* @param topId 根ID
* @param maxOrder 最大顺序当前父指标下
* @param children 子指标名称
*/
void batchImport(Integer parentId, Integer topId, Long maxOrder, String[] children);
/**
* 查看同一个父ID下是否重名.
*
* @param parentId 父ID
* @param nameList 名称列表
* @return 存在记录
*/
List<Indicator> selectByName(@Param("parentId") Integer parentId,
@Param("nameList") String[] nameList);
void deleteIndicator(Integer id) throws Exception;
}

View File

@ -46,4 +46,11 @@ public class IndicatorBottomMapperServiceImpl extends
public List<IndicatorBottomFormMapper> selectIndicatorToFieldNameListByIndicatorTopId(Integer topId) {
return this.baseMapper.selectByIndicatorTopId(topId);
}
@Override
public void deleteByFormFieldId(Integer formFieldId) {
QueryWrapper<IndicatorBottomFormMapper> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("form_field_id", formFieldId);
remove(queryWrapper);
}
}

View File

@ -41,4 +41,11 @@ public class IndicatorFormMapperServiceImpl extends
this.baseMapper.delete(queryWrapper);
}
@Override
public void deleteByModelId(Integer modelId) {
QueryWrapper<IndicatorFormMapper> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("indicator_model_id", modelId);
remove(queryWrapper);
}
}

View File

@ -12,18 +12,21 @@ import com.hshh.indicator.service.IndicatorCsvColumnService;
import com.hshh.indicator.service.IndicatorCsvService;
import com.hshh.indicator.service.IndicatorService;
import com.hshh.system.common.bean.JsTree;
import com.hshh.system.common.util.FileUtil;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -40,6 +43,8 @@ import org.springframework.web.multipart.MultipartFile;
public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator> implements
IndicatorService {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
@Resource
private IndicatorCsvService csvService;
@Resource
@ -56,13 +61,13 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
private IndicatorBottomFormMapperService indicatorBottomFormMapperService;
@Override
public List<Indicator> queryList(@NotNull Integer topId, @NotNull String name,
@NotNull String code) {
public List<Indicator> queryList(Integer parentId, String name
) {
QueryWrapper<Indicator> queryWrapper = new QueryWrapper<>();
if (topId != null) {
queryWrapper.eq("top_id", topId);
if (parentId != null) {
queryWrapper.eq("parent_id", parentId);
}
queryWrapper.and(wrapper -> wrapper.eq("name", name).or().eq("code", code));
queryWrapper.and(wrapper -> wrapper.eq("name", name));
return this.list(queryWrapper);
}
@ -70,13 +75,14 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
@Override
public List<Indicator> queryRootList() {
QueryWrapper<Indicator> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("parent_id");
queryWrapper.eq("parent_id", 0);
queryWrapper.orderByAsc("sort_order");
return this.list(queryWrapper);
}
@Override
public Indicator getIndicator(Integer id) {
public Indicator getRecursionIndicator(Integer id) {
Indicator indicator = this.getById(id);
Map<Integer, List<Indicator>> map = this.list().stream()
.collect(Collectors.groupingBy(a -> a.getParentId() == null ? 0 : a.getParentId()));
@ -118,22 +124,23 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
@Override
public List<IndicatorCsvColumn> saveIndicatorTopCsvMapper(Integer topId, MultipartFile file)
throws IOException {
Reader reader = new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8);
CSVParser parser = CSVFormat.DEFAULT.builder().setHeader() // 表示第一行是表头
.setSkipHeaderRecord(true) // 解析时跳过表头行
.build().parse(reader);
ArrayList<String> headerList = new ArrayList<>(parser.getHeaderMap().keySet());
if (headerList.isEmpty()) {
return null;
List<List<String>> contentList = FileUtil.parseCsvFromInputStream(file);
if (!contentList.isEmpty()) {
List<String> headerList = contentList.get(0);
if (headerList.isEmpty()) {
return null;
}
//删除表中m_data_indicator_csv原来topID对应记录
csvService.removeByIndicatorId(topId);
//添加m_data_indicator_csv记录
Integer csvId = csvService.saveCsv(topId, file.getBytes(), file.getOriginalFilename());
//删除m_data_indicator_csv_column 原topID对应的记录
csvColumnService.deleteByTopId(topId);
//添加对应记录
return csvColumnService.save(headerList, topId, csvId);
}
//删除表中m_data_indicator_csv原来topID对应记录
csvService.removeByIndicatorId(topId);
//添加m_data_indicator_csv记录
Integer csvId = csvService.saveCsv(topId, file.getBytes(), file.getOriginalFilename());
//删除m_data_indicator_csv_column 原topID对应的记录
csvColumnService.deleteByTopId(topId);
//添加对应记录
return csvColumnService.save(headerList, topId, csvId);
return null;
}
/**
@ -159,7 +166,7 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
@Override
public List<JsTree> metricTree(Integer topId) {
Indicator top = getIndicator(topId);
Indicator top = getRecursionIndicator(topId);
List<Indicator> rootList = new ArrayList<>();
rootList.add(top);
@ -197,5 +204,92 @@ public class IndicatorServiceImpl extends ServiceImpl<IndicatorMapper, Indicator
return list.get(0);
}
@Override
public Long selectMaxOrderByParentId(Integer parentId) {
return this.baseMapper.selectMaxOrderByParentId(parentId);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void batchImport(Integer parentId, Integer topId, Long maxOrder, String[] children) {
for (String child : children) {
maxOrder = maxOrder + 1;
Indicator indicator = new Indicator();
indicator.setParentId(parentId);
indicator.setTopId(topId);
indicator.setSortOrder(maxOrder.intValue());
indicator.setName(child);
save(indicator);
}
}
@Override
public List<Indicator> selectByName(Integer parentId, String[] nameList) {
return this.baseMapper.selectByName(parentId, nameList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteIndicator(Integer id) throws SQLException {
Indicator indicator = getRecursionIndicator(id);
Set<Integer> idList = new HashSet<>();
fillIndicatorList(idList, indicator);
Connection connection = sqlSessionTemplate.getConnection();
List<String> sqlList = new ArrayList<>();
for (int i = 1; i < idList.size(); i++) {
}
idList.forEach(indicatorId -> {
if (!Objects.equals(indicatorId, id)) {
addSql(sqlList, indicatorId);
}
});
addSql(sqlList, id);
try (Statement ste = connection.createStatement()) {
for (String sql : sqlList) {
ste.addBatch(sql);
}
ste.executeBatch();
} catch (SQLException e) {
throw e; // 触发 Spring 事务自动回滚
}
}
private void addSql(List<String> sqlList, Integer id) {
sqlList.add(
"delete from m_data_evaluation_indicator_result where indicator_id = " + id);
sqlList.add("delete from m_data_evaluation_template_indicator_weight where indicator_id = "
+ id);
sqlList.add(
"delete from m_data_evaluation_template_weight where from_indicator_id = " + id
+ " or to_indicator_id=" + id);
sqlList.add("delete from m_data_evaluation_template_weight_detail where from_indicator_id="
+ id + " or to_indicator_id=" + id);
sqlList.add(
"delete from m_data_indicator_bottom_csv_mapper where indicator_id=" + id);
sqlList.add(
"delete from m_data_indicator_bottom_form_mapper where indicator_id=" + id);
sqlList.add("delete from m_data_indicator_eval_item where indicator_id=" + id);
sqlList.add("delete from m_data_evaluation_template where indicator_top_id=" + id);
sqlList.add(
"delete from m_data_evaluation_root_result where indicator_top_id=" + id);
sqlList.add("delete from m_data_indicator_form where indicator_top_id=" + id);
sqlList.add("delete from m_data_indicator_weight where indicator_id=" + id);
sqlList.add(
"delete from m_data_indicator_csv_column where indicator_top_id=" + id);
sqlList.add("delete from m_data_indicator_csv where indicator_top_id=" + id);
sqlList.add("delete from m_data_indicator where id=" + id);
}
private void fillIndicatorList(Set<Integer> idList, Indicator indicator) {
idList.add(indicator.getId());
if (indicator.getChildren() != null) {
indicator.getChildren().forEach(child -> {
fillIndicatorList(idList, child);
});
}
}
}

View File

@ -192,20 +192,8 @@ public class ModelDefineController extends BaseController {
@Operation(summary = "删除数据模型", description = "根据ID删除数据模型")
@ResponseBody
public OperateResult<Void> removeModelDefine(@PathVariable("id") Integer id) {
//查看是否有字段若有字段则无法删除
List<FormFieldConfig> fieldList = formFieldConfigService.getFormFieldConfigByModelId(id);
if (fieldList != null && !fieldList.isEmpty()) {
return OperateResult.error(null, ErrorMessage.OBJ_HAS_SUB.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
//查看数据关联表是否有记录
List<TableRelations> relList = tableRelationsService.queryRel(id,
"m_data_model_define");
if (relList != null && !relList.isEmpty()) {
return OperateResult.error(null, ErrorMessage.OBJ_ALREADY_TAKEN.getMessage(),
ErrorCode.BUSINESS_ERROR.getCode());
}
modelDefineService.removeById(id);
modelDefineService.deleteTheWholeModel(id);
return OperateResult.success();
}

View File

@ -41,4 +41,5 @@ public interface FormFieldConfigService extends IService<FormFieldConfig> {
* @return 字段map
*/
Map<String, String> getHeaderMap(Integer modelId);
void deleteByModelId(Integer modelId);
}

View File

@ -36,4 +36,5 @@ public interface FormValueService extends IService<FormValue> {
* @return 结果列表
*/
List<FormValue> selectByIds(List<Integer> ids);
void deleteFromByModelId(Integer id);
}

View File

@ -20,7 +20,7 @@ public interface ModelDefineService extends IService<ModelDefine> {
* @param modelCode 模型编码
* @return 模型
*/
public ModelDefine getModelDefineByModelCodeOrName(String modelName, String modelCode);
ModelDefine getModelDefineByModelCodeOrName(String modelName, String modelCode);
void deleteTheWholeModel(Integer id);
}

View File

@ -48,4 +48,11 @@ public class FormFieldConfigServiceImpl extends
});
return headerMap;
}
@Override
public void deleteByModelId(Integer modelId) {
QueryWrapper<FormFieldConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("data_model_id", modelId);
remove(queryWrapper);
}
}

View File

@ -1,5 +1,6 @@
package com.hshh.model.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.model.entity.FormValue;
import com.hshh.model.mapper.FormValueMapper;
@ -32,4 +33,11 @@ public class FormValueServiceImpl extends ServiceImpl<FormValueMapper, FormValue
public List<FormValue> selectByIds(List<Integer> ids) {
return this.baseMapper.selectByIds(ids);
}
@Override
public void deleteFromByModelId(Integer id) {
QueryWrapper<FormValue> queryWrapper = new QueryWrapper<FormValue>();
queryWrapper.eq("model_define_id",id);
remove(queryWrapper);
}
}

View File

@ -2,12 +2,18 @@ package com.hshh.model.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hshh.indicator.service.IndicatorBottomFormMapperService;
import com.hshh.indicator.service.IndicatorFromMapperService;
import com.hshh.model.entity.FormFieldConfig;
import com.hshh.model.entity.ModelDefine;
import com.hshh.model.mapper.ModelDefineMapper;
import com.hshh.model.service.FormFieldConfigService;
import com.hshh.model.service.FormValueService;
import com.hshh.model.service.ModelDefineService;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 服务实现类.
@ -19,6 +25,14 @@ import org.springframework.stereotype.Service;
public class ModelDefineServiceImpl extends ServiceImpl<ModelDefineMapper, ModelDefine> implements
ModelDefineService {
@Resource
private FormFieldConfigService formFieldConfigService;
@Resource
private FormValueService formValueService;
@Resource
private IndicatorBottomFormMapperService indicatorBottomFormMapperService;
@Resource
private IndicatorFromMapperService indicatorFromMapperService;
@Override
public ModelDefine getModelDefineByModelCodeOrName(String modelName, String modelCode) {
@ -28,5 +42,18 @@ public class ModelDefineServiceImpl extends ServiceImpl<ModelDefineMapper, Model
return this.list(queryWrapper).stream().findFirst().orElse(null);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteTheWholeModel(Integer id) {
//查询model对应的字段
List<FormFieldConfig> fieldList = formFieldConfigService.getFormFieldConfigByModelId(id);
fieldList.forEach(field -> {
indicatorBottomFormMapperService.deleteByFormFieldId(field.getId());
});
formValueService.deleteFromByModelId(id);
formFieldConfigService.deleteByModelId(id);
indicatorFromMapperService.deleteByModelId(id);
this.removeById(id);
}
}

View File

@ -1,9 +1,22 @@
server:
port: 8088
port: 8188
# SSL:
# key-store: classpath:keystore.p12
# key-store-password: 123456
# key-store-type: PKCS12
# servlet:
# session:
# cookie:
# same-site: none
# secure: true
spring:
thymeleaf:
cache: false
datasource:
# url: jdbc:dm://192.168.0.53:5236/MANAGER
# username: sysdba
# password: Admin123
# driver-class-name: dm.jdbc.driver.DmDriver
url: jdbc:mysql://localhost:3306/manager?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: 123456
@ -27,7 +40,7 @@ mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
database-id: mysql
database-id: dm
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

View File

@ -0,0 +1,26 @@
indicator:
default:
levels:
- levelName:
grade: 90
levelOrder: 1
equalValue:
- levelName:
grade: 80
levelOrder: 2
equalValue:
- levelName:
grade: 70
levelOrder: 3
equalValue:
- levelName:
grade: 60
levelOrder: 4
equalValue:
function:
membershipFunc: TRAP_TRI
softEdgeS: 0.10
triangleOverlapRatio: 0.01
trianglePeakRatio: 0.01
method: PRODUCT_BOUNDED_SUM

View File

@ -17,15 +17,16 @@
#{start},#{pageSize}
</select>
<select id="list" resultType="com.hshh.evaluation.entity.EvaluationProject"
parameterType="com.hshh.system.common.bean.PaginationBean" databaseId="dm">SELECT
parameterType="com.hshh.system.common.bean.PaginationBean" databaseId="dm">
SELECT
t.seq,
t.*
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS seq,
ROW_NUMBER() OVER (ORDER BY a.id ASC) AS seq,
a.*,
a1.template_name as templateName
FROM m_data_evaluation_project a left join m_data_evaluation_template a1 on a.template_id=a1.name
FROM m_data_evaluation_project a left join m_data_evaluation_template a1 on a.template_id=a1.id
<where>
<if test="search != null and search !='' ">
a.project_name LIKE '%'||#{search}||'%'

View File

@ -17,15 +17,16 @@
#{start},#{pageSize}
</select>
<select id="list" resultType="com.hshh.evaluation.entity.EvaluationTemplate"
parameterType="com.hshh.system.common.bean.PaginationBean" databaseId="dm">SELECT
parameterType="com.hshh.system.common.bean.PaginationBean" databaseId="dm">
SELECT
t.seq,
t.*
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id desc) AS seq,
ROW_NUMBER() OVER (ORDER BY a.id desc) AS seq,
a.*,
a1.name as indicatorTopName
FROM m_data_evaluation_template a left join m_data_indicator a1 on a.indicator_top_id=a1.name
FROM m_data_evaluation_template a left join m_data_indicator a1 on a.indicator_top_id=a1.id
<where>
<if test="search != null and search !='' ">
a.template_name LIKE '%'||#{search}||'%'

View File

@ -8,7 +8,7 @@
and t1.id NOT IN (SELECT t2.parent_id
FROM m_data_indicator t2
WHERE t2.top_id = #{topId} and t2.parent_id IS NOT NULL)
order by sort_order
order by t1.parent_id asc,t1.id asc
</select>
<select id="selectByTemplateId" resultType="com.hshh.indicator.entity.Indicator">
SELECT
@ -18,4 +18,13 @@
inner join m_data_evaluation_template t2 ON t1.id = t2.indicator_top_id
where t2.id=#{templateId}
</select>
<select id="selectMaxOrderByParentId" resultType="java.lang.Long">
SELECT max(sort_order) FROM m_data_indicator where parent_id=#{parentId}
</select>
<select id="selectByName" resultType="com.hshh.indicator.entity.Indicator">
SELECT * FROM m_data_indicator where parent_id=#{parentId} and name in
<foreach collection="nameList" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</select>
</mapper>

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,85 +0,0 @@
<svg viewBox="0 0 1200 600" width="100%" height="600" xmlns="http://www.w3.org/2000/svg" fill="none">
<!-- 背景 -->
<rect width="1200" height="600" fill="#F7FAFC"/>
<!-- 港口起重机 -->
<rect x="120" y="260" width="20" height="100" rx="8" fill="#467FCF"/>
<rect x="110" y="250" width="40" height="18" rx="4" fill="#A3C0F7"/>
<rect x="127" y="210" width="6" height="40" rx="3" fill="#A3C0F7"/>
<rect x="137" y="250" width="6" height="30" fill="#467FCF"/>
<!-- 港口集装箱 -->
<rect x="170" y="340" width="48" height="32" rx="5" fill="#F4C890"/>
<rect x="222" y="348" width="36" height="24" rx="4" fill="#A3C0F7"/>
<rect x="165" y="370" width="38" height="18" rx="3" fill="#A3C0F7"/>
<!-- 港口水面 -->
<ellipse cx="300" cy="410" rx="120" ry="18" fill="#D9E7F6"/>
<!-- 轮船主体 -->
<rect x="250" y="370" width="100" height="28" rx="10" fill="#467FCF"/>
<rect x="290" y="350" width="30" height="22" rx="4" fill="#A3C0F7"/>
<rect x="320" y="356" width="14" height="16" rx="3" fill="#C9D8F3"/>
<!-- 船头 -->
<polygon points="250,398 240,383 250,370" fill="#A3C0F7"/>
<!-- 船窗 -->
<circle cx="263" cy="384" r="3" fill="#F7FAFC"/>
<circle cx="273" cy="384" r="3" fill="#F7FAFC"/>
<circle cx="283" cy="384" r="3" fill="#F7FAFC"/>
<!-- 卡车 -->
<rect x="480" y="370" width="120" height="38" rx="8" fill="#467FCF"/>
<rect x="590" y="382" width="38" height="26" rx="6" fill="#A3C0F7"/>
<rect x="468" y="380" width="24" height="16" rx="4" fill="#F4C890"/>
<circle cx="500" cy="410" r="13" fill="#333"/>
<circle cx="610" cy="410" r="13" fill="#333"/>
<!-- 飞机场跑道 -->
<rect x="800" y="430" width="290" height="18" rx="6" fill="#E3ECF8"/>
<!-- 跑道中心线 -->
<rect x="830" y="438" width="30" height="2.5" rx="1" fill="#fff"/>
<rect x="880" y="438" width="30" height="2.5" rx="1" fill="#fff"/>
<rect x="930" y="438" width="30" height="2.5" rx="1" fill="#fff"/>
<rect x="980" y="438" width="30" height="2.5" rx="1" fill="#fff"/>
<rect x="1030" y="438" width="30" height="2.5" rx="1" fill="#fff"/>
<!-- 机场航站楼 -->
<rect x="950" y="370" width="100" height="50" rx="10" fill="#A3C0F7"/>
<rect x="960" y="390" width="28" height="16" rx="3" fill="#C9D8F3"/>
<rect x="1000" y="390" width="28" height="16" rx="3" fill="#C9D8F3"/>
<!-- 塔台 -->
<rect x="1070" y="330" width="18" height="45" rx="6" fill="#467FCF"/>
<ellipse cx="1079" cy="325" rx="24" ry="10" fill="#A3C0F7"/>
<rect x="1066" y="317" width="26" height="10" rx="4" fill="#C9D8F3"/>
<!-- 飞机 -->
<g>
<!-- 机身 -->
<rect x="850" y="285" width="140" height="18" rx="9" fill="#467FCF"/>
<!-- 机头 -->
<ellipse cx="990" cy="294" rx="13" ry="9" fill="#467FCF"/>
<!-- 机翼 -->
<rect x="900" y="295" width="90" height="8" rx="4" fill="#A3C0F7" transform="rotate(-10 900 295)"/>
<rect x="880" y="297" width="60" height="6" rx="3" fill="#A3C0F7" transform="rotate(12 880 297)"/>
<!-- 垂直尾翼 -->
<rect x="850" y="282" width="10" height="13" rx="3" fill="#A3C0F7"/>
<!-- 舱窗 -->
<circle cx="870" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="880" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="890" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="900" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="910" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="920" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="930" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="940" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="950" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="960" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="970" cy="294" r="2" fill="#F7FAFC"/>
<circle cx="980" cy="294" r="2" fill="#F7FAFC"/>
</g>
<!-- 装饰云朵 -->
<ellipse cx="300" cy="120" rx="50" ry="16" fill="#E3ECF8"/>
<ellipse cx="380" cy="90" rx="28" ry="10" fill="#E3ECF8"/>
<ellipse cx="950" cy="120" rx="38" ry="12" fill="#E3ECF8"/>
<ellipse cx="1100" cy="70" rx="24" ry="8" fill="#E3ECF8"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" width="988.5" height="774.981" viewBox="0 0 988.5 774.981" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/">
<g id="Group_12" data-name="Group 12" transform="translate(-331.843 -152.51)">
<path id="Path_305-421" data-name="Path 305" d="M1140.171,623.562c-1.048,2.015-2.141,3.995-3.269,5.963-43.26,75.366-143.894,122.172-313.609,97.4-96.363-14.735-137.4,39.312-177.139,94.671-.68.955-1.359,1.911-2.049,2.866-41.614,58.053-82.814,116.266-185.83,96.916-109.21-17.267-176.61-105.941-187.039-200.612-.127-1.151-.242-2.314-.346-3.465-5.859-61.379,12.248-124.935,58.444-172.915,27.9-28.974,67.55-44.365,108.473-43.882a199.385,199.385,0,0,0,36.791-2.728c2.682-.472,5.284-.99,7.816-1.577,52.136-11.914,76.16-47.335,90.895-90.561.346-1.025.691-2.049,1.025-3.085,33.833-103.086,18.188-247.774,202.12-226.489,164.592,12.133,285.383,103.4,344.632,208.693.587,1.059,1.174,2.118,1.75,3.177a321.16,321.16,0,0,1,34.442,93.6c.369,1.842.7,3.684,1.013,5.525C1166.67,535.959,1161.179,583.49,1140.171,623.562Z" transform="translate(137.976 2.326)" fill="#f2f2f2"/>
<path id="Path_306-422" data-name="Path 306" d="M1049.679,337.12A252.041,252.041,0,0,1,1010,375c-56.74,43.514-128.607,67.112-199.506,72.062a396.383,396.383,0,0,1-108.807-7.391c-36.377-7.621-71.072-21.043-104.674-36.722-32.681-15.253-64.4-32.451-96.858-48.13.346-1.025.691-2.049,1.025-3.085,35.4,17.037,70.071,35.812,105.883,52.044,33.74,15.3,68.666,27.777,105.192,34.454a394.994,394.994,0,0,0,110.488,4.328c70.7-7.114,142.628-33.2,196.985-79.867a235.212,235.212,0,0,0,28.2-28.756C1048.516,335,1049.1,336.061,1049.679,337.12Z" transform="translate(211.131 53.139)" fill="#fff"/>
<path id="Path_307-423" data-name="Path 307" d="M768.339,156.736q-.124,0-.247.008C680.844,162.21,612.5,235.035,612.5,322.539s68.344,160.33,155.591,165.794a3.79,3.79,0,0,0,2.857-1.026,3.75,3.75,0,0,0,1.194-2.76V160.53A3.8,3.8,0,0,0,768.339,156.736Z" transform="translate(246.814 -3.144)" fill="#6c63ff"/>
<path id="Path_308-424" data-name="Path 308" d="M860.383,433.517a4.892,4.892,0,0,1-3.45-1.428l-112.81-112.81a4.839,4.839,0,0,1-1.427-3.444V160.791a4.822,4.822,0,0,1,1.536-3.548,4.9,4.9,0,0,1,3.665-1.319c87.816,5.5,156.606,78.8,156.606,166.875a167.2,167.2,0,0,1-40.43,109.029,4.9,4.9,0,0,1-3.509,1.685Q860.474,433.517,860.383,433.517Z" transform="translate(288.166 -3.405)" fill="#ccc"/>
<path id="Path_309-425" data-name="Path 309" d="M747.587,445.3a4.911,4.911,0,0,1-3.353-1.327,4.826,4.826,0,0,1-1.538-3.551V300.532a4.871,4.871,0,0,1,8.314-3.444l98.441,98.441a4.881,4.881,0,0,1-.183,7.072A167.567,167.567,0,0,1,747.889,445.29C747.789,445.3,747.687,445.3,747.587,445.3Z" transform="translate(288.166 40.98)" fill="#6c63ff"/>
<path id="Path_310-426" data-name="Path 310" d="M1108.929,412.917c-15.794,28.008-38.092,52.711-63.624,72.292C988.564,528.723,916.7,552.322,845.8,557.272a396.386,396.386,0,0,1-108.807-7.391c-36.376-7.621-71.072-21.043-104.674-36.722-34.212-15.966-67.377-34.074-101.44-50.34-34.028-16.254-69-30.114-105.641-39.185,2.682-.472,5.284-.99,7.816-1.577q3.592.932,7.16,1.934c71.648,20.03,134.881,59.5,202.154,90,33.74,15.3,68.666,27.777,105.192,34.454a394.992,394.992,0,0,0,110.488,4.328c70.7-7.114,142.628-33.2,196.985-79.867a229.605,229.605,0,0,0,52.884-65.512C1108.285,409.233,1108.618,411.075,1108.929,412.917Z" transform="translate(187.337 76.467)" fill="#fff"/>
<path id="Path_311-427" data-name="Path 311" d="M446.724,602.513c70.589,19.731,133,58.34,199.173,88.627-.68.955-1.359,1.911-2.049,2.866-1.681-.771-3.349-1.542-5.019-2.325-34.212-15.966-67.377-34.074-101.44-50.34-34.753-16.6-70.485-30.7-107.978-39.761a482.479,482.479,0,0,0-158.433-11.27c-.127-1.151-.242-2.314-.346-3.465A486.114,486.114,0,0,1,446.724,602.513Z" transform="translate(138.233 132.78)" fill="#fff"/>
<path id="Path_312-428" data-name="Path 312" d="M740.929,683.151H238.919a1.318,1.318,0,1,1,0-2.635h502.01a1.318,1.318,0,0,1,0,2.635Z" transform="translate(127.742 163.214)" fill="#ccc"/>
<path id="Path_318-429" data-name="Path 318" d="M388.751,622.3l-31.819-3.878a6.347,6.347,0,0,1-5.352-7.983l8.473-30.814a17.626,17.626,0,1,1,34.981,4.367l.83,31.843a6.347,6.347,0,0,1-7.112,6.463Z" transform="translate(163.871 126.266)" fill="#6c63ff"/>
<path id="Path_323-430" data-name="Path 323" d="M745.633,544.9H638.814a5.805,5.805,0,0,1,0-11.611H745.633a5.805,5.805,0,0,1,0,11.611Z" transform="translate(253.328 116.453)" fill="#ccc"/>
<path id="Path_324-431" data-name="Path 324" d="M616.12,552.385a12.191,12.191,0,1,1,12.191-12.191A12.191,12.191,0,0,1,616.12,552.385Z" transform="translate(244.092 114.774)" fill="#6c63ff"/>
<path id="Path_325-432" data-name="Path 325" d="M745.633,571.336H638.814a5.805,5.805,0,0,1,0-11.611H745.633a5.805,5.805,0,0,1,0,11.611Z" transform="translate(253.328 124.849)" fill="#ccc"/>
<path id="Path_326-433" data-name="Path 326" d="M616.12,578.821a12.191,12.191,0,1,1,12.191-12.191A12.191,12.191,0,0,1,616.12,578.821Z" transform="translate(244.092 123.17)" fill="#6c63ff"/>
<path id="Path_327-434" data-name="Path 327" d="M745.633,597.772H638.814a5.805,5.805,0,1,1,0-11.611H745.633a5.805,5.805,0,1,1,0,11.611Z" transform="translate(253.328 133.246)" fill="#ccc"/>
<path id="Path_328-435" data-name="Path 328" d="M616.12,605.257a12.191,12.191,0,1,1,12.191-12.191A12.191,12.191,0,0,1,616.12,605.257Z" transform="translate(244.092 131.567)" fill="#6c63ff"/>
<path id="Path_329-436" data-name="Path 329" d="M742.057,630.321h44.121a1.161,1.161,0,0,0,1.161-1.161V356.4a1.161,1.161,0,0,0-2.322,0V628h-42.96a1.161,1.161,0,0,0,0,2.322Z" transform="translate(287.594 59.902)" fill="#3f3d56"/>
<path id="Path_330-437" data-name="Path 330" d="M889.16,228.023h0A14.965,14.965,0,1,1,902.875,211.9,14.965,14.965,0,0,1,889.16,228.023Zm-.185-2.295h0a12.663,12.663,0,1,0-13.638-11.605,12.663,12.663,0,0,0,13.638,11.605Z" transform="translate(329.549 10.006)" fill="#ccc"/>
<circle id="Ellipse_66" data-name="Ellipse 66" cx="9.209" cy="9.209" r="9.209" transform="translate(1261.03 181.212)" fill="#ccc"/>
<circle id="Ellipse_67" data-name="Ellipse 67" cx="4.838" cy="4.838" r="4.838" transform="translate(1310.668 156.651)" fill="#ccc"/>
<path id="Path_331-438" data-name="Path 331" d="M838.513,449.971a14.965,14.965,0,1,1-4.236-20.736h0a14.982,14.982,0,0,1,4.236,20.735Zm-23.05-15.231a12.663,12.663,0,1,0,17.546-3.584h0a12.677,12.677,0,0,0-17.545,3.584Z" transform="translate(309.879 82.616)" fill="#ccc"/>
<circle id="Ellipse_68" data-name="Ellipse 68" cx="9.209" cy="9.209" r="9.209" transform="translate(1157.111 569.206)" fill="#ccc"/>
<circle id="Ellipse_69" data-name="Ellipse 69" cx="4.838" cy="4.838" r="4.838" transform="translate(1187.171 620.761)" fill="#ccc"/>
<path id="Path_332-439" data-name="Path 332" d="M550.257,258.834a14.965,14.965,0,1,1,20.061,6.742h0a14.982,14.982,0,0,1-20.061-6.742ZM575,246.539a12.663,12.663,0,1,0-5.7,16.975h0a12.677,12.677,0,0,0,5.7-16.975Z" transform="translate(226.547 22.414)" fill="#ccc"/>
<circle id="Ellipse_70" data-name="Ellipse 70" cx="9.209" cy="9.209" r="9.209" transform="translate(718.958 264.548)" fill="#ccc"/>
<circle id="Ellipse_71" data-name="Ellipse 71" cx="4.838" cy="4.838" r="4.838" transform="translate(669.627 267.462)" fill="#ccc"/>
<g id="Group_11" data-name="Group 11" transform="translate(302 174)">
<path id="Path_289-440" data-name="Path 289" d="M510.8,643.322,519.94,671.6s-8.267,26.541-4.351,56.128,1.74,30.457,1.74,30.457-39.126-2.252-35.21,9.5,42.172,4.863,42.172,4.863,7.347-1.978,10.828-22.863,12.232-61.112,12.232-61.112L539.122,650.6Z" transform="translate(-452 -152.441)" fill="#ffb9b9"/>
<path id="Path_291-441" data-name="Path 291" d="M553.211,742.808l34.808-1.3,26.541-3.916-7.832,26.106-64.4,1.74Z" transform="translate(-353 -114)" fill="#ffb9b9"/>
<path id="Path_292-442" data-name="Path 292" d="M702.247,784.73s-.435-9.572-4.351-9.137-1.305-3.481,2.175-4.351,13.488-5.221,13.488-5.221l39.594,2.611s25.671,22.625,15.229,34.808-37.419,1.305-37.419,1.305l-29.152.435S692.24,789.516,702.247,784.73Z" transform="translate(-452 -152.441)" fill="#090814"/>
<path id="Path_293-443" data-name="Path 293" d="M618.911,712.786l12.618,59.174,26.106-5.221-14.793-73.967-15.664-8.267Z" transform="translate(-353 -114)" fill="#ffb9b9"/>
<path id="Path_294-444" data-name="Path 294" d="M567.8,743.83S494.7,776.9,525.6,810.4c0,0,0,15.664,32.633,12.618s95.722-8.7,98.768-6.962,12.618-34.373,4.351-36.113-18.274-3.916-18.274-3.916,8.7-1.305,8.7-8.7c0,0,24.8-1.74,32.2-17.4s14.358-27.411,14.358-27.411L715.3,766.02s19.58-36.113,34.373-20.015c0,0-16.534-103.989-29.152-113.561s-21.32-12.618-33.068-3.916-22.625,30.892-22.625,30.892Z" transform="translate(-452 -152.441)" fill="#090814"/>
<path id="Path_295-445" data-name="Path 295" d="M742.277,792.562a98.038,98.038,0,0,0-8.7,7.4c-3.481,3.481-8.267,1.74-8.7,0s-8.267,1.305-9.137,13.923-6.091,23.931,7.832,27.846,13.488,15.229,13.488,15.229a46.934,46.934,0,0,0,35.243,2.176c19.58-6.526,12.618-13.488,12.618-13.488l-26.106-43.075S751.849,789.516,742.277,792.562Z" transform="translate(-452 -152.441)" fill="#090814"/>
<path id="Path_296-446" data-name="Path 296" d="M670.05,576.751s41.77,14.358,46.121,17.4,23.931,17.4,14.358,24.8-17.4,12.183-36.113,6.527-39.594-23.06-39.594-23.06Z" transform="translate(-452 -152.441)" fill="#ffb9b9"/>
<path id="Path_298-447" data-name="Path 298" d="M552.137,544.989s.435,16.969-1.305,18.709,20.885,53.082,45.686,30.457,5.656-31.762,5.656-31.762-9.137-16.969-8.7-18.709-40.464,0-40.464,0Z" transform="translate(-452 -152.441)" fill="#ffb9b9"/>
<g id="Group_10" data-name="Group 10">
<path id="Path_290-448" data-name="Path 290" d="M513.849,585.453s-16.534,6.091-16.534,22.625,6.091,39.159,10.007,40.464,30.022,19.144,34.808,6.526S513.849,585.453,513.849,585.453Z" transform="translate(-452 -152.441)" fill="#6c63ff"/>
<path id="Path_297-449" data-name="Path 297" d="M628.28,557.607s16.534-3.046,28.282,3.481,21.755,19.58,21.755,19.58-10.878,23.06-18.709,26.541-22.19-13.923-22.19-13.923Z" transform="translate(-452 -152.441)" fill="#6c63ff"/>
<path id="Path_300-450" data-name="Path 300" d="M559.1,577.622s16.534,11.313,23.5,8.7,15.664-13.923,16.534-14.793,45.251,88.326,45.251,88.326-6.527,32.633-23.931,42.2-17.187,17.187-19.362,23.713-30.24-20.667-30.24-20.667L553.008,629.4V583.278Z" transform="translate(-452 -152.441)" fill="#6c63ff"/>
<path id="Path_301-451" data-name="Path 301" d="M551.92,557.389s-5.439-1.088-11.1,3.7-32.2,22.625-30.892,24.366S522.986,611.995,525.6,622s6.526,11.313,6.526,11.313,9.137,6.526,7.832,11.313-6.962,70.051.87,84.41,6.526,26.976,4.351,30.457,20.885-10.007,20.885-10.007,34.373-27.846,36.984-36.113a97.269,97.269,0,0,0,3.481-17.4s-22.19-87.891-29.152-92.677S551.92,557.389,551.92,557.389Z" transform="translate(-452 -149.441)" fill="#6c63ff"/>
<path id="Path_302-452" data-name="Path 302" d="M599.72,557.733s10.721-6.653,12.9-4.913,22.625,1.74,23.06,3.481,1.305,43.51,16.1,54.388S657,643.322,657,643.322l10.878,17.4s-59.173,56.994-55.258,37.419c.218-1.088,0-1.305,0-1.305S589.556,598.071,592.6,592.85,601.182,563.951,599.72,557.733Z" transform="translate(-452 -150.441)" fill="#6c63ff"/>
</g>
<circle id="Ellipse_72" data-name="Ellipse 72" cx="32.197" cy="32.197" r="32.197" transform="translate(91.435 344.769)" opacity="0.1"/>
<path id="Path_303-453" data-name="Path 303" d="M690.935,597.636l-59.174,20.45s-58.739,13.923-49.6,30.022S630.021,636.8,630.021,636.8l78.8-8.829S740.1,601.552,690.935,597.636Z" transform="translate(-452 -152.441)" fill="#ffb9b9"/>
<circle id="Ellipse_64" data-name="Ellipse 64" cx="32.197" cy="32.197" r="32.197" transform="translate(91.435 344.252)" fill="#ffb9b9"/>
<path id="Path_304-454" data-name="Path 304" d="M549.992,496.891l-5.956-2.166s12.453-12.453,29.779-11.37l-4.873-4.873s11.912-4.331,22.741,7.039c5.693,5.977,12.279,13,16.385,20.917h6.378l-2.662,5.324,9.317,5.324-9.563-.956a26.866,26.866,0,0,1-.9,13.789l-2.166,5.956s-8.663-17.326-8.663-19.492V521.8s-5.956-4.873-5.956-8.122l-3.249,3.79-1.624-5.956-20.033,5.956,3.249-4.873-12.453,1.624,4.873-5.956s-14.077,7.039-14.619,12.995-7.58,13.536-7.58,13.536l-3.249-5.414S534.29,505.013,549.992,496.891Z" transform="translate(-452 -152.441)" fill="#090814"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,91 @@
<!doctype html>
<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></title>
<!-- CSS files -->
<link th:href="@{/css/tabler.min.css}" rel="stylesheet"/>
<link th:href="@{/css/tabler-flags.min.css}" rel="stylesheet"/>
<link th:href="@{/css/tabler-payments.min.css}" rel="stylesheet"/>
<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"/>
<style>
</style>
</head>
<body>
<div class="page">
<div class="page-wrapper">
<div class="page-body">
<div class="container-xl">
<div class="card card-lg">
<div class="card-body">
<div class="space-y-4">
<div><h2 style="text-align: center;">使用帮助</h2></div>
<div>
<h2 class="mb-3">1. 如何建立指标?</h2>
<div id="faq-1" class="accordion" role="tablist" aria-multiselectable="true">
<div class="accordion-item">
<div class="accordion-header" role="tab">
<button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#faq-1-1">建立根指标</button>
</div>
<div id="faq-1-1" class="accordion-collapse collapse show" role="tabpanel" data-bs-parent="#faq-1">
<div class="accordion-body pt-0">
<div>
<p>点击菜单[评估指标管理/指标体系],建立根指标,如图 </p>
<img th:src="@{/img/help/root_add.png}" alt="评估指标管理菜单截图" style="max-width:80%;box-shadow:0 2px 8px #eee;">
<p></p>
<p>点击新增后如图:</p>
<img th:src="@{/img/help/root_add_1.png}" alt="评估指标管理菜单截图" style="max-width:80%;box-shadow:0 2px 8px #eee;">
<p></p>
<p>成功添加后显示</p>
<img th:src="@{/img/help/root_add_2.png}" alt="评估指标管理菜单截图" style="max-width:80%;box-shadow:0 2px 8px #eee;">
<p></p>
<p>根指标建立完成,接着可以对根指标建立下级指标</p>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" role="tab">
<button class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#faq-1-2">建立子指标</button>
</div>
<div id="faq-1-2" class="accordion-collapse collapse" role="tabpanel" data-bs-parent="#faq-1">
<div class="accordion-body pt-0">
<div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium alias dignissimos dolorum ea est eveniet, excepturi illum in iste iure maiores nemo recusandae rerum saepe sed, sunt totam! Explicabo, ipsa?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium alias dignissimos dolorum ea est eveniet, excepturi illum in iste iure maiores nemo recusandae rerum saepe sed, sunt totam! Explicabo, ipsa?</p>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" role="tab">
<button class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#faq-1-3">批量导入指标</button>
</div>
<div id="faq-1-3" class="accordion-collapse collapse" role="tabpanel" data-bs-parent="#faq-1">
<div class="accordion-body pt-0">
<div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium alias dignissimos dolorum ea est eveniet, excepturi illum in iste iure maiores nemo recusandae rerum saepe sed, sunt totam! Explicabo, ipsa?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium alias dignissimos dolorum ea est eveniet, excepturi illum in iste iure maiores nemo recusandae rerum saepe sed, sunt totam! Explicabo, ipsa?</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/tabler.min.js}"></script>
</body>

View File

@ -6,11 +6,13 @@
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown"
aria-label="Open user menu">
<div class="d-none d-xl-block ps-2">
<div><span th:text="${user.nickName}"></span></div>
<div><span th:text="${user?.nickName}"></span></div>
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
<a th:href="@{/logout}" class="dropdown-item">退出登录</a>
</div>
</div>
</div>
</div>

View File

@ -12,94 +12,117 @@
<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></title>
<!-- CSS files -->
<link th:href="@{/css/tabler.min.css}" rel="stylesheet"/>
<link th:href="@{/css/tabler-flags.min.css}" rel="stylesheet"/>
<link th:href="@{/css/tabler-payments.min.css}" rel="stylesheet"/>
<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="@{/js/jstree/themes/default/style.min.css}" rel="stylesheet"/>
<link th:href="@{/css/fontawesome-all.min.css}" rel="stylesheet"/>
<style>
.float-help {
position: fixed;
right: 32px;
bottom: 32px;
width: 48px;
height: 48px;
border-radius: 50%;
background: #2196f3;
color: #fff;
border: none;
box-shadow: 0 2px 8px #bbb;
cursor: pointer;
font-size: 22px;
z-index: 1000;
transition: background 0.2s;
}
.float-help:hover { background: #1976d2; }
/* =========================
基础变量 & 全局
========================= */
:root{
--accent: #206bc4; /* 主题主色 */
--border: #e0e6ed;
--muted: #f6f8fb;
--card-bg: #fff;
--head-bg: #f3f6fa;
--sticky-bg:#ffffff; /* 粘列背景,防止覆盖时变暗 */
--col-index-w: 48px; /* 表格首列粘列1宽度 */
:root {
--accent: #206bc4; /* 主题主色 */
--border: #e0e6ed;
--muted: #f6f8fb;
--card-bg: #fff;
--head-bg: #f3f6fa;
--sticky-bg: #ffffff; /* 粘列背景,防止覆盖时变暗 */
--col-index-w: 48px; /* 表格首列粘列1宽度 */
}
body, .page-body{
body, .page-body {
background: #f5f7fb;
}
/* =========================
卡片hero / section
========================= */
.card.card-hero{
.card.card-hero {
border: 1px solid var(--border);
border-radius: 14px;
background: var(--card-bg);
box-shadow: 0 14px 36px rgba(0,0,0,.08);
box-shadow: 0 14px 36px rgba(0, 0, 0, .08);
}
.card.card-hero > .card-header{
background: linear-gradient(90deg, rgba(32,107,196,.10), rgba(32,107,196,0));
border-bottom: 1px solid rgba(32,107,196,.25);
.card.card-hero > .card-header {
background: linear-gradient(90deg, rgba(32, 107, 196, .10), rgba(32, 107, 196, 0));
border-bottom: 1px solid rgba(32, 107, 196, .25);
border-top-left-radius: 14px;
border-top-right-radius: 14px;
}
.card.card-hero .card-title{
.card.card-hero .card-title {
font-weight: 800;
border-left: 6px solid var(--accent);
padding-left: .5rem;
}
.card.section{
.card.section {
border: 1px solid var(--border);
border-radius: 12px;
background: var(--card-bg);
box-shadow: 0 6px 18px rgba(0,0,0,.06);
box-shadow: 0 6px 18px rgba(0, 0, 0, .06);
margin-bottom: 1rem;
}
.card.section .card-header{
.card.section .card-header {
background: var(--head-bg);
border-bottom: 1px solid #e9edf3;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.card.section .card-title{
.card.section .card-title {
font-weight: 700;
}
/* =========================
表单(输入/下拉/勾选)
========================= */
.form-select:focus, .form-control:focus{
.form-select:focus, .form-control:focus {
border-color: var(--accent);
box-shadow: 0 0 0 .2rem rgba(32,107,196,.15);
box-shadow: 0 0 0 .2rem rgba(32, 107, 196, .15);
}
.form-check-input[type="radio"],
.form-check-input[type="checkbox"]{
.form-check-input[type="checkbox"] {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent) inset;
background-color: #fff;
transition: all .15s ease-in-out;
}
.form-check-input[type="radio"]:hover,
.form-check-input[type="checkbox"]:hover{
background-color: rgba(32,107,196,.10);
box-shadow: 0 0 0 2px rgba(32,107,196,.25);
.form-check-input[type="checkbox"]:hover {
background-color: rgba(32, 107, 196, .10);
box-shadow: 0 0 0 2px rgba(32, 107, 196, .25);
}
.form-check-input:checked{
.form-check-input:checked {
background-color: var(--accent);
border-color: var(--accent);
box-shadow: none;
@ -108,28 +131,28 @@
/* =========================
CSV 列表
========================= */
.csv-heads .list-group-item{
.csv-heads .list-group-item {
background: #fff;
border: 1px solid #eef2f7;
margin-bottom: .25rem;
border-radius: 8px;
padding: .5rem .75rem;
box-shadow: 0 1px 3px rgba(0,0,0,.04);
box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
}
/* =========================
AHP 表:容器与表格
========================= */
.map-scroller{
.map-scroller {
overflow: auto;
border: 1px solid #e9edf3;
border-radius: 10px;
box-shadow: inset 0 1px 0 rgba(0,0,0,.02);
box-shadow: inset 0 1px 0 rgba(0, 0, 0, .02);
background: #fff;
}
.map-table{
min-width: 980px; /* 横向滚动更友好 */
.map-table {
min-width: 980px; /* 横向滚动更友好 */
border-collapse: separate;
border-spacing: 0;
/* 确保粗体切片可用(有些字体 700 看不粗) */
@ -137,49 +160,64 @@
}
/* ---- 表头吸顶 ---- */
.map-table thead th.sticky-header{
position: sticky; top: 0; z-index: 4;
.map-table thead th.sticky-header {
position: sticky;
top: 0;
z-index: 4;
background: var(--head-bg);
border-bottom: 1px solid #dee2e6;
}
/* ---- 粘左两列(表头 + 表体) ---- */
.map-table thead th.sticky-col-1,
.map-table tbody td.sticky-body-col-1{
position: sticky; left: 0; z-index: 3;
.map-table tbody td.sticky-body-col-1 {
position: sticky;
left: 0;
z-index: 3;
background: var(--sticky-bg);
box-shadow: 1px 0 0 #e9edf3;
}
.map-table thead th.sticky-col-2,
.map-table tbody td.sticky-body-col-2{
position: sticky; left: var(--col-index-w); z-index: 3; /* 用变量替代 48px */
.map-table tbody td.sticky-body-col-2 {
position: sticky;
left: var(--col-index-w);
z-index: 3; /* 用变量替代 48px */
background: var(--sticky-bg);
box-shadow: 1px 0 0 #e9edf3;
}
/* 粘列在表头处层级更高,避免被后列覆盖 */
.map-table thead th.sticky-col-1,
.map-table thead th.sticky-col-2{
.map-table thead th.sticky-col-2 {
z-index: 5;
}
/* ---- 条纹 & 悬停 ---- */
.map-table tbody tr:nth-child(odd){ background: #fcfdfe; }
.map-table tbody tr:hover{ background: #f3f8ff; }
.map-table tbody tr:nth-child(odd) {
background: #fcfdfe;
}
.map-table tbody tr:hover {
background: #f3f8ff;
}
/* ---- 紧凑列 ---- */
.map-table th.w-compact,
.map-table td.w-compact{
width: 1%; white-space: nowrap;
.map-table td.w-compact {
width: 1%;
white-space: nowrap;
}
/* ---- 加粗规则(表头 + 第一列标签)---- */
/* Tabler/Bootstrap 有时会把表头设为 400这里强制 700 */
.map-table thead th{
.map-table thead th {
font-weight: 700 !important;
font-size: 16px; /* 可按需调整 */
color: inherit !important; /* 继承父级颜色 */
}
.map-table tbody td:first-child{
.map-table tbody td:first-child {
font-weight: 700 !important;
font-size: 16px; /* 可选,同步视觉 */
}
@ -187,39 +225,151 @@
/* =========================
动画/菜单/通用
========================= */
@keyframes flashHighlight{
0%{ box-shadow:0 0 0 0 rgba(32,107,196,0); }
20%{ box-shadow:0 0 0 8px rgba(32,107,196,.22); }
100%{ box-shadow:0 14px 36px rgba(0,0,0,.08); }
}
.flash{ animation: flashHighlight .9s ease-out; }
@keyframes pop-in{
0%{ transform: scale(0.8); opacity: .4; }
100%{ transform: scale(1); opacity: 1; }
@keyframes flashHighlight {
0% {
box-shadow: 0 0 0 0 rgba(32, 107, 196, 0);
}
20% {
box-shadow: 0 0 0 8px rgba(32, 107, 196, .22);
}
100% {
box-shadow: 0 14px 36px rgba(0, 0, 0, .08);
}
}
#contextMenu ul{ list-style:none; margin:0; padding:0; }
#contextMenu li{
.flash {
animation: flashHighlight .9s ease-out;
}
@keyframes pop-in {
0% {
transform: scale(0.8);
opacity: .4;
}
100% {
transform: scale(1);
opacity: 1;
}
}
#contextMenu ul {
list-style: none;
margin: 0;
padding: 0;
}
#contextMenu li {
padding: 9px 18px;
cursor: pointer;
border-bottom: 1px solid #eee;
transition: background .18s, color .18s;
display: flex; align-items: center;
display: flex;
align-items: center;
font-size: 15px;
}
#contextMenu li:last-child{ border-bottom: none; }
#contextMenu li:hover{ background: #ffcd38; color: #fff; }
#contextMenu li i{ margin-right: 8px; font-size: 15px; opacity: .85; }
.dropdown-item.active{
#contextMenu li:last-child {
border-bottom: none;
}
#contextMenu li:hover {
background: #ffcd38;
color: #fff;
}
#contextMenu li i {
margin-right: 8px;
font-size: 15px;
opacity: .85;
}
.dropdown-item.active {
background-color: var(--accent) !important;
color: #fff !important;
}
.alert{ margin-bottom: 1rem; }
.alert {
margin-bottom: 1rem;
}
.execution-results {
max-width: 100%;
overflow-x: auto;
}
.result-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
table-layout: fixed;
}
.result-table th {
background-color: #f5f5f5;
font-weight: bold;
padding: 12px 8px;
border: 1px solid #ddd;
}
.result-table td {
padding: 8px;
border: 1px solid #ddd;
vertical-align: top;
}
.col-no {
width: 80px;
text-align: center;
font-weight: bold;
}
.col-status {
width: calc(100% - 80px);
}
.status-success {
color: #52c41a;
font-weight: bold;
}
.status-error {
color: #ff4d4f;
font-size: 12px;
line-height: 1.4;
max-width: 100%;
word-wrap: break-word;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 3; /* 最多显示3行 */
-webkit-box-orient: vertical;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
}
.status-error:hover {
-webkit-line-clamp: none;
max-height: none;
background-color: #fff2f0;
border-radius: 4px;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 响应式设计 */
@media (max-width: 768px) {
.result-table {
font-size: 12px;
}
.col-no {
width: 50px;
}
.col-status {
width: calc(100% - 50px);
}
}
</style>
</head>
@ -245,9 +395,14 @@
</header>
<div class="page-wrapper">
<div id="main-content">
<div style="text-align:center;margin-top:120px;">
<img th:src="@{/img/undraw_personal-goals_f9bb.svg}" alt="empty" style="width:180px;opacity:0.85;">
<h2 style="color:#2196f3;margin-top:18px;" th:text="${'欢迎使用'+systemTitle}"></h2>
</div>
</div>
<footer class="footer footer-transparent d-print-none">
<div class="container-xl" th:insert="fragments/footer">
</div>
@ -258,6 +413,11 @@
<div id="alert-area"
style="position: fixed; left: 50%; top: 20px; transform: translateX(-50%); z-index: 9999; width: 400px; max-width: 90vw;">
</div>
<!-- <button class="float-help" title="帮助" onclick="openFaq()">-->
<!-- <span style="font-size: 22px;">?</span>-->
<!-- </button>-->
<input type="hidden" name="_userId_" id="_userId_" th:value="${user?.id}">
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/tabler.min.js}"></script>
@ -271,17 +431,25 @@
<script th:src="@{/js/jspdf.umd.min.js}"></script>
<script>
const categoryColors = [
"#00bcd4", // 卓越
"#2196f3", // 优秀
"#42a5f5", // 优
"#66bb6a", // 良
"#ffd600", // 中
"#ffa726", // 及格
"#ff7043", // 及格下
"#ef5350", // 差
"#8d6e63" // 极差
];
function openFaq(){
let url = document.getElementById("_rootPath").value+"faq";
window.open(
url,
'FAQ',
'width=900,height=600,resizable=yes,scrollbars=yes,toolbar=no,menubar=no,location=no'
);
}
// const categoryColors = [
// "#00bcd4", // 卓越
// "#2196f3", // 优秀
// "#42a5f5", // 优
// "#66bb6a", // 良
// "#ffd600", // 中
// "#ffa726", // 及格
// "#ff7043", // 及格下
// "#ef5350", // 差
// "#8d6e63" // 极差
// ];
let d3TreeData = null;
document.addEventListener('DOMContentLoaded', function () {
// 1. 支持多种菜单项,逗号分隔多个选择器
@ -326,36 +494,37 @@
setTimeout(initMenu, 1); // 保证DOM已插入
}
//指标
if(evt.detail.target.querySelector('[data-page="metric"]')){
if (evt.detail.target.querySelector('[data-page="metric"]')) {
if (typeof topIndictorTree === 'function') {
topIndictorTree();
}
if(topIndictorTree)
// 绑定全局菜单关闭事件(如未绑定过)
window.removeEventListener("click", hideContextMenu);
if (topIndictorTree)
// 绑定全局菜单关闭事件(如未绑定过)
{
window.removeEventListener("click", hideContextMenu);
}
window.removeEventListener("scroll", hideContextMenu);
window.addEventListener("click", hideContextMenu);
window.addEventListener("scroll", hideContextMenu);
window.addEventListener("resize", function() {
topIndictorTree&&topIndictorTree();
window.addEventListener("resize", function () {
topIndictorTree && topIndictorTree();
hideContextMenu && hideContextMenu();
});
}
//指标映射
if(evt.detail.target.querySelector('[data-page="indicator_mapper"]')){
if (evt.detail.target.querySelector('[data-page="indicator_mapper"]')) {
if(typeof csvListen === 'function'){
if (typeof csvListen === 'function') {
csvListen();
}
}
//评估
if(evt.detail.target.querySelector('[data-page="evaluation_list"]')){
if(typeof initEvaluationList === 'function'){
initEvaluationList();
}
if (evt.detail.target.querySelector('[data-page="project"]')) {
}
}
});
@ -369,9 +538,33 @@
});
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"
"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"
};
// 流程节点数据
const steps = [
"建立指标",
"设置权重",
"指标与实体/CSV映射",
"选择数据",
"计算指标隶属度",
"出报告"
];
</script>
</body>

View File

@ -12,12 +12,7 @@
<div class="invalid-feedback" id="name_error_tip"></div>
</div>
<div class="mb-3">
<label class="form-label required">指标编码:</label>
<input type="text" class="form-control" name="code" id="code"
placeholder="指标编码" >
<div class="invalid-feedback" id="code_error_tip"></div>
</div>
</div>

View File

@ -6,7 +6,7 @@
<div class="page">
<div class="page-body" data-page="indicator_list">
<div class="container-xl">
<input type="hidden" name="_level" th:each="level : ${levelList}" th:value="${level.levelName}"/>
<!-- 面包屑(静态示例/Thymeleaf -->
<div th:replace="fragments/dialog::navigateDialog(${chainMenuList})"></div>
@ -76,8 +76,8 @@
<thead>
<tr>
<th>等级名</th>
<th>上限</th>
<th>下限</th>
<th>上限(包含)</th>
<th>下限(不包含)</th>
<th>等于</th>
</tr>
</thead>
@ -195,6 +195,7 @@
let topTd = document.createElement("td");
let topInput = document.createElement("input");
topInput.type = "number";
topInput.value=0
topInput.style.width = "8ch";
topInput.addEventListener("change", function () {
inputChange();
@ -207,6 +208,7 @@
let bottomTd = document.createElement("td");
let bottomInput = document.createElement("input");
bottomInput.type = "number";
bottomInput.value = 0;
bottomInput.style.width = "8ch";
bottomInput.addEventListener("change", function () {
inputChange()
@ -224,7 +226,7 @@
equalValueInput.addEventListener("change", function () {
inputChange();
});
if (historyMap.get(equalValueTd.textContent)) {
if (historyMap.get(td.textContent)) {
equalValueInput.value = historyMap.get(td.textContent).equalValue;
}
equalValueTd.appendChild(equalValueInput);

View File

@ -37,18 +37,22 @@
<div class="card-title">1. 选择指标(根指标)</div>
</div>
<div class="card-body">
<div class="col-auto text-muted small mb-3">选择根指标后,再选择设施与 CSV
以建立映射。
</div>
<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="indicatorTopIdChange()">
<select id="indicationId" class="form-select" name="indicationId"
onchange="indicatorTopIdChange()">
<option value="0">-选择根指标-</option>
<option th:each="item:${rootList}" th:value="${item.id}"
th:text="${item.getName()}" th:selected="${item.checked}"></option>
</select>
</div>
<div class="col-auto text-muted small">选择根指标后,再选择设施与 CSV 以建立映射。</div>
</div>
</div>
</div>
@ -81,10 +85,10 @@
<div class="card-title">3. 上传 CSV 文件</div>
</div>
<div class="card-body">
<div class="col-auto text-muted small mb-3">上传文件,根据表头对应指标</div>
<label for="csvFile" class="btn btn-primary">选择文件</label>
<span id="file-name">未选择文件</span>
<input type="file" id="csvFile" accept=".csv" style="display:none" />
<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>
@ -104,10 +108,21 @@
<div class="card section">
<div class="card-header">
<div class="card-title">4. 子指标映射设置</div>
<div class="card-actions">
<a href="javascript:void(0)" class="btn btn-success" onclick="quickMapper()"
title="系统会以设施字段或者csv表头字段第一行为基准按顺序对应">
快捷对应
</a>
</div>
</div>
<div class="card-body">
<div class="text-muted small mt-2 mb-3">
说明:为每个子指标选择表单字段或 CSV 列,系统会保存映射关系用于后续处理。
</div>
<div class="map-scroller">
<table class="table table-vcenter table-striped table-hover align-middle map-table">
<table
class="table table-vcenter table-striped table-hover align-middle map-table">
<thead>
<tr>
<th class="sticky-header sticky-col-1 w-compact" style="width:48px;">#</th>
@ -117,14 +132,16 @@
</tr>
</thead>
<tbody class="autonum">
<tbody class="autonum" id="tbody-mapper">
<tr th:each="item,stat:${childrenIndicator}">
<input type="hidden" name="indicatorId" th:value="${item.getId()}">
<td class="sticky-body-col-1 w-compact col-index" th:text="${stat.count}"></td>
<td class="sticky-body-col-1 w-compact col-index"
th:text="${stat.count}"></td>
<td class="sticky-body-col-2 col-name" th:text="${item.getName()}"></td>
<td>
<select class="form-select form-select-sm" name="formField">
<option th:if="${formFieldList.size()>0}"
th:each="field:${formFieldList}" th:value="${field.id}"
th:text="${field.fieldLabel}"
@ -135,6 +152,7 @@
<td>
<select class="form-select form-select-sm" name="csvField">
<option th:if="${csvColumns.size()>0}" th:each="column:${csvColumns}"
th:value="${column.id}"
th:text="${column.getCsvColumnName()}"
@ -146,9 +164,7 @@
</tbody>
</table>
</div>
<div class="text-muted small mt-2">
说明:为每个子指标选择表单字段或 CSV 列,系统会保存映射关系用于后续处理。
</div>
</div>
</div>
@ -162,6 +178,33 @@
<!-- ====== JS保持你的原有逻辑 ====== -->
<script>
function quickMapper() {
//设施快捷对应
let formSelectList = document.getElementsByName("formField");
quick(formSelectList);
//对应csv
let csvSelectList = document.getElementsByName("csvField");
quick(csvSelectList);
}
function quick(formSelectList) {
if (formSelectList && formSelectList.length > 0) {
//获取第一个的下拉选项
let options = formSelectList[0].options;
//获取第一个选中的序号
const index = formSelectList[0].selectedIndex;
for (let i = 1; i < formSelectList.length; i++) {
if (index + i - 1 < options.length) {
formSelectList[i].selectedIndex = index + i;
}
}
}
}
function formModelChange(object) {
let postUrl = document.getElementById('_rootPath').value + "indicator/indicatorFormMapper";
let obj = {};
@ -182,49 +225,58 @@
function csvListen() {
const input = document.getElementById("csvFile");
const fileName = document.getElementById("file-name");
input.addEventListener("change", function() {
input.addEventListener("change", function () {
if (this.files.length > 0) {
const fileName = document.getElementById("file-name");
fileName.textContent = this.files[0].name;
let indicatorTopId = $('#indicationId').val();
if (indicatorTopId === '0') {
showAlert("danger", "必须选择指标")
return;
}
const f = this.files[0];
if (!f) {
return;
}
const fd = new FormData();
fd.append('file', f);
fd.append('indicatorTopId', indicatorTopId);
let postUrl = document.getElementById('_rootPath').value + "indicator/uploadCsv";
let http = new HttpClient();
http.postFormData(postUrl, fd, function (error, response, xhr) {
let data = response.result || [];
let len = data.length;
document.getElementById("csvHeader-total-num").innerHTML = "已解析 CSV 文件:" + len
+ " 列";
const list = document.getElementById("csvHeaders");
list.innerHTML = "";
if (len > 0) {
const frag = document.createDocumentFragment();
data.forEach(item => {
const div = document.createElement("div");
div.className = "list-group-item";
div.textContent = item.csvColumnName;
frag.appendChild(div);
});
list.appendChild(frag);
// 填充右侧下拉
let $sel = $('select[name="csvField"]');
$sel.empty();
$.each(data, function (_, opt) {
$sel.append('<option value="' + opt.id + '">' + opt.csvColumnName + '</option>');
});
}
flashHero();
}, null, null)
} else {
fileName.textContent = "未选择文件";
}
});
$('#csvFile').on('change', function () {
const f = this.files[0];
if (!f) return;
let indicatorTopId = $('#indicationId').val();
const fd = new FormData();
fd.append('file', f);
fd.append('indicatorTopId', indicatorTopId);
let postUrl = document.getElementById('_rootPath').value + "indicator/uploadCsv";
let http = new HttpClient();
http.postFormData(postUrl, fd, function (error, response, xhr) {
let data = response.result || [];
let len = data.length;
document.getElementById("csvHeader-total-num").innerHTML = "已解析 CSV 文件:" + len + " 列";
const list = document.getElementById("csvHeaders");
list.innerHTML = "";
if (len > 0) {
const frag = document.createDocumentFragment();
data.forEach(item => {
const div = document.createElement("div");
div.className = "list-group-item";
div.textContent = item.csvColumnName;
frag.appendChild(div);
});
list.appendChild(frag);
// 填充右侧下拉
let $sel = $('select[name="csvField"]');
$sel.empty();
$.each(data, function (_, opt) {
$sel.append('<option value="' + opt.id + '">' + opt.csvColumnName + '</option>');
});
}
flashHero();
}, null, null)
});
}
function mapperSave() {
@ -255,7 +307,7 @@
let http = new HttpClient();
let url = document.getElementById('_rootPath').value + "indicator/saveMapper";
http.post(url, obj, function (error, response, xhr) {
showAlert("success","保存成功");
showAlert("success", "保存成功");
flashHero();
}, null, null);
}
@ -263,17 +315,18 @@
// 根指标变化时也高亮主卡
$('#indicationId').on('change', flashHero);
// 轻微高亮动画(与 CSS 中 .flash 对应)
function flashHero(){
function flashHero() {
const hero = document.querySelector('.card-hero');
if(!hero) return;
if (!hero) {
return;
}
hero.classList.add('flash');
setTimeout(()=>hero.classList.remove('flash'), 900);
setTimeout(() => hero.classList.remove('flash'), 900);
}
function indicatorTopIdChange() {
let topId = $('#indicationId').val();
function indicatorTopIdChange() {
let topId = $('#indicationId').val();
document.getElementById("_indicator_mapper")
.setAttribute("hx-vals", JSON.stringify({indicatorTopId: topId,}));
document.getElementById("_indicator_mapper").click();

View File

@ -123,6 +123,7 @@
display: flex;
gap: 10px;
}
.card-body.search-bar {
border-radius: 0.5rem 0.5rem 0 0;
border-bottom: 1px solid #e9ecef;
@ -130,6 +131,7 @@
padding-bottom: 0.5rem;
margin-bottom: 0;
}
.card-body.list-area {
border-radius: 0 0 0.5rem 0.5rem;
padding-top: 0.5rem;
@ -156,7 +158,7 @@
增加根指标
</a>
</div>
<div class="card-body search-bar" >
<div class="card-body search-bar">
<div class="input-icon ">
<input type="text" value="" class="form-control" placeholder="Search…">
<span class="input-icon-addon">
@ -228,6 +230,7 @@
</div>
</div>
<div id="context-menu" class="context-menu"></div>
<input id="fileInput" type="file" style="display: none"/>
<form id="indicatorForm">
<div th:replace="fragments/dialog::addSimpleFormDialog"></div>
</form>
@ -305,16 +308,53 @@
} else if (action === "delete") {
removeTopIndictorData(nodeData.id);
} else if (action === 'weight') {
} else if (action === 'import') {
if (!nodeData.topId) {//根节点
nodeData.topId = nodeData.id;
}
importChild(nodeData.id, nodeData.topId)
}
}
function importChild(pid, topId) {
const fileInput = document.getElementById('fileInput');
fileInput.click();
fileInput.addEventListener('change', function () {
if (fileInput.files.length > 0) {
const file = fileInput.files[0];
// 构造 FormData 并上传
const formData = new FormData();
formData.append('file', file);
formData.append('topId', topId);
formData.append('parentId', pid);
// 假设你的上传接口是 /upload
let url = document.getElementById("_rootPath").value + "indicator/import";
fetch(url, {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if(data.code===0){
refresh();
}else{
showAlert("danger",data.message);
}
})
.catch(err => {
showAlert("danger",err);
});
}
});
}
function topIndictorTree() {
//获取选中的根节点
let id = $(
'input[name="radio_names"]:checked').val();
if(id){
if (id) {
let url = document.getElementById("_rootPath").value + "indicator/" + id;
let http = new HttpClient();
http.get(url, function (error, res, xhr) {
@ -341,47 +381,61 @@
setParentRefs(d3TreeData, null);
const root = d3.hierarchy(d3TreeData);
const nodeCount = root.descendants().length;
const width = Math.max(800, 120 * nodeCount);
const height = Math.max(800, 130 * (root.height + 1));
const marginTop = 60;
// 测量节点最大宽高 - 为垂直文字和竖框调整
let maxBoxWidth = 0, maxBoxHeight = 0;
root.descendants().forEach(d => {
const textLength = d.data.name.length;
const charHeight = 20; // 每个字符的高度
const charWidth = 16; // 每个字符的宽度
const paddingX = 15, paddingY = 20;
// 竖框:窄而高
d.boxWidth = charWidth + 2 * paddingX; // 固定宽度,刚好容纳一个字符
d.boxHeight = textLength * charHeight + 2 * paddingY; // 高度根据字符数量
maxBoxWidth = Math.max(maxBoxWidth, d.boxWidth);
maxBoxHeight = Math.max(maxBoxHeight, d.boxHeight);
});
// 竖直树 separation 让兄弟节点间距和宽度动态相关
const verticalSpacing = 60;
const width = Math.max(800, 100 * root.leaves().length); // 减少横向间距,因为框变窄了
const height = (maxBoxHeight + verticalSpacing) * (root.height + 1);
const maxNodeWidth = Math.max(...root.descendants().map(d => d.boxWidth || 50));
const treeLayout = d3.tree()
.size([width - 120, height - 100]);
.size([width, height])
.separation((a, b) => {
// 框变窄了,可以减少间距
const baseSpacing = maxNodeWidth / 80 + 1.2;
return a.parent === b.parent ? baseSpacing : baseSpacing * 1.1;
});
treeLayout(root);
// 1. 创建SVG和主g
// 计算所有节点的x/y最大最小值自动撑开画布
const xVals = root.descendants().map(d => d.x);
const yVals = root.descendants().map(d => d.y);
const minX = Math.min(...xVals);
const maxX = Math.max(...xVals);
const minY = Math.min(...yVals);
const maxY = Math.max(...yVals);
const svgPadding = 80;
const svgWidth = (maxX - minX) + svgPadding * 2;
const svgHeight = (maxY - minY) + svgPadding * 2;
const svg = container.append("svg")
.attr("width", width)
.attr("height", height)
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("display", "block")
.style("margin", "0 auto");
// 2. 创建主g用于缩放
const mainG = svg.append("g").attr("class", "main-g");
const mainG = svg.append("g")
.attr("class", "main-g")
.attr("transform", `translate(${svgPadding - minX},${svgPadding - minY})`);
// 3. 缩放行为
const zoom = d3.zoom()
.scaleExtent([0.2, 3])
.on("zoom", (event) => {
mainG.attr("transform", event.transform);
});
svg.call(zoom);
// 4. 绑定按钮事件
document.getElementById("zoom-in").onclick = function () {
svg.transition().call(zoom.scaleBy, 1.2);
};
document.getElementById("zoom-out").onclick = function () {
svg.transition().call(zoom.scaleBy, 0.8);
};
document.getElementById("zoom-reset").onclick = function () {
svg.transition().call(zoom.scaleTo, 1);
// svg.transition().call(zoom.translateTo, width / 2, marginTop); // 如需重置到居中
};
// 5. 后续所有g内容都加到mainG上
// 绘制连线
// 画连线
mainG.append("g")
.selectAll("path")
.data(root.links())
@ -389,51 +443,69 @@
.attr("class", "link")
.attr("d", d3.linkVertical()
.x(d => d.x)
.y(d => d.y + marginTop)
.y(d => d.y)
);
// 绘制节点
// 节点
const nodeG = mainG.append("g")
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("class", "node-group")
.attr("transform", d => `translate(${d.x},${d.y + marginTop})`);
.attr("transform", d => `translate(${d.x},${d.y})`);
nodeG.append("text")
.attr("class", "node-text")
.attr("x", 0)
.attr("y", 0)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.text(d => d.data.name);
// 竖直的矩形框
nodeG.append("rect")
.attr("class", "node-rect")
.attr("x", d => -d.boxWidth / 2)
.attr("y", d => -d.boxHeight / 2)
.attr("width", d => d.boxWidth)
.attr("height", d => d.boxHeight)
.attr("rx", 8)
.attr("ry", 8)
.on("contextmenu", function (event, nd) {
event.preventDefault();
showContextMenu(event.clientX, event.clientY, nd.data);
});
// 自适应rect
nodeG.each(function (d) {
const text = d3.select(this).select("text");
const bbox = text.node().getBBox();
const paddingX = 18, paddingY = 10;
d.boxWidth = bbox.width + 2 * paddingX;
d.boxHeight = bbox.height + 2 * paddingY;
d3.select(this)
.insert("rect", "text")
.attr("class", "node-rect")
.attr("x", -d.boxWidth / 2)
.attr("y", -d.boxHeight / 2)
.attr("width", d.boxWidth)
.attr("height", d.boxHeight)
.attr("rx", 12)
.attr("ry", 12)
.on("contextmenu", function (event, nd) {
event.preventDefault();
showContextMenu(event.clientX, event.clientY, nd.data);
// 竖直排列的文字,每个字符一行
nodeG.each(function(d) {
const group = d3.select(this);
const text = d.data.name;
const lineHeight = 20;
text.split('').forEach((char, i) => {
group.append("text")
.attr("class", "node-text-char")
.attr("x", 0)
.attr("y", (i - (text.length - 1) / 2) * lineHeight)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("font-size", "14px")
.text(char);
});
});
// 缩放
const zoom = d3.zoom()
.scaleExtent([0.2, 3])
.on("zoom", (event) => {
mainG.attr("transform", `translate(${svgPadding - minX},${svgPadding - minY})` + event.transform);
});
svg.call(zoom);
document.getElementById("zoom-in").onclick = function () {
svg.transition().call(zoom.scaleBy, 1.2);
};
document.getElementById("zoom-out").onclick = function () {
svg.transition().call(zoom.scaleBy, 0.8);
};
}
function showContextMenu(x, y, nodeData) {
const menuItems = [
{text: "添加子节点", action: "add", icon: "fa-plus"}
{text: "添加子节点", action: "add", icon: "fa-plus"},
{text: "导入子节点", action: "import", icon: "fa-upload"}
];
//有父节点,可以编辑删除,非根节点
if (nodeData.parentId) {

View File

@ -69,9 +69,9 @@
<!-- <option value="SELECT">下拉选择</option>-->
<!-- <option value="RADIO">单选框</option>-->
<!-- <option value="CHECKBOX">多选框</option>-->
<!-- <option value="DATE">日期选择</option>-->
<!-- <option value="DATETIME">日期时间选择</option>-->
<!-- <option value="TIME">时间选择</option>-->
<option value="DATE">日期选择</option>
<option value="DATETIME">日期时间选择</option>
<option value="TIME">时间选择</option>
<!-- <option value="FILE">文件上传</option>-->
<!-- <option value="IMAGE">图片上传</option>-->
<!-- <option value="URL">链接输入</option>-->

View File

@ -133,16 +133,16 @@
<th>可选项</th>
<th>输入提示</th>
<th>必填标志</th>
<th>只读标志</th>
<th>禁用标志</th>
<th>自动完成标志</th>
<th>最大长度</th>
<th>最小长度</th>
<th>最大值</th>
<th>最小值</th>
<th>正则表达式</th>
<th>正则验证消息</th>
<th>format函数</th>
<!-- <th>只读标志</th>-->
<!-- <th>禁用标志</th>-->
<!-- <th>自动完成标志</th>-->
<!-- <th>最大长度</th>-->
<!-- <th>最小长度</th>-->
<!-- <th>最大值</th>-->
<!-- <th>最小值</th>-->
<!-- <th>正则表达式</th>-->
<!-- <th>正则验证消息</th>-->
<!-- <th>format函数</th>-->
<th></th>
</tr>
</thead>
@ -157,16 +157,16 @@
<td th:text="${field.getFieldOptionsId()}"></td>
<td th:text="${field.getFieldPlaceholder()}"></td>
<td th:text="${field.getIsRequired() == 1 ? 'Y' : 'N'}"></td>
<td th:text="${field.getReadonlyFlag()==1?'Y':'N'}"></td>
<td th:text="${field.getDisabledFlag()==1?'Y':'N'}"></td>
<td th:text="${field.getAutocompleteFlag()==1?'Y':'N'}"></td>
<td th:text="${field.getFieldMaxSize()}"></td>
<td th:text="${field.getFieldMinSize()}"></td>
<td th:text="${field.getFieldMaxVal()}"></td>
<td th:text="${field.getFieldMinVal()}"></td>
<td th:text="${field.getValidateRule()}"></td>
<td th:text="${field.getValidateRuleMessage()}"></td>
<td th:text="${field.getFormatFunc()}"></td>
<!-- <td th:text="${field.getReadonlyFlag()==1?'Y':'N'}"></td>-->
<!-- <td th:text="${field.getDisabledFlag()==1?'Y':'N'}"></td>-->
<!-- <td th:text="${field.getAutocompleteFlag()==1?'Y':'N'}"></td>-->
<!-- <td th:text="${field.getFieldMaxSize()}"></td>-->
<!-- <td th:text="${field.getFieldMinSize()}"></td>-->
<!-- <td th:text="${field.getFieldMaxVal()}"></td>-->
<!-- <td th:text="${field.getFieldMinVal()}"></td>-->
<!-- <td th:text="${field.getValidateRule()}"></td>-->
<!-- <td th:text="${field.getValidateRuleMessage()}"></td>-->
<!-- <td th:text="${field.getFormatFunc()}"></td>-->
<td>
<a href="javascript:void(0)"

View File

@ -1,23 +1,23 @@
<div class="row">
<div class="col-xl-12">
<input type="hidden" name="id" id="id">
<input type="hidden" name="id" id="id" th:value="${project?.id}">
<div class="mb-3">
<label class="form-label required" for="projectName">工程名称:</label>
<input type="text" class="form-control" name="projectName" id="projectName"
placeholder="工程名称">
placeholder="工程名称" th:value="${project?.projectName}">
<div class="invalid-feedback" id="projectName_error_tip"></div>
</div>
<div class="mb-3">
<div class="mb-3" >
<label class="form-label required" for="templateId">评估模板:</label>
<select name="templateId" id="templateId" class="form-control">
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.templateName}"></option>
<option th:each="item:${rootList}" th:value="${item.id}" th:text="${item.templateName}" th:selected="${project?.getTemplateId()==item.id}"></option>
</select>
</div>
<div class="mb-3">
<label class="form-label " for="projectMemo">描述:</label>
<textarea class="form-control" name="projectMemo" id="projectMemo"
placeholder="描述信息"></textarea>
placeholder="描述信息" th:text="${project?.getProjectMemo()}"></textarea>
<div class="invalid-feedback" id="projectMemo_error_tip"></div>
</div>
</div>

View File

@ -4,7 +4,7 @@
<input type="hidden" name="datasource" value="csv" />
<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">
@ -33,8 +33,10 @@
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<div class="form-label">选择上传文件</div>
<input type="file" class="form-control" name="file" accept=".csv" id="csv_file" onchange="readCsv()" />
<label for="csvFile" class="btn btn-primary">选择文件</label>
<span id="file-name"></span>
<input type="file" id="csvFile" accept=".csv" style="display:none" onchange="readCsv()" />
</div>
</div>
<div class="col-md-4">

View File

@ -3,7 +3,7 @@
<input type="hidden" name="templateId" th:value="${templateId}" />
<input type="hidden" name="datasource" value="database" />
<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">

View File

@ -33,7 +33,7 @@
<div class="form-label">1.选择数据源</div>
<div class="form-label">选择数据源</div>
<div>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="datasource_type" checked=""
@ -42,40 +42,13 @@
</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>
<span class="form-check-label">文件</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>

View File

@ -24,12 +24,19 @@
<td th:text="${evaluationHistory.membershipFunc}">隶属函数</td>
<td th:text="${evaluationHistory.fuzzyOperator}">模糊运算算子</td>
<td>
<div class="btn-list flex-nowrap"><a href="#" class="btn"
<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>
<a href="#" class="btn"
th:data-result-id="${evaluationHistory.randomKey}"
th:data-project-id="${evaluationHistory.getProjectId()}"
onclick="removeHistory(this)">
删除
</a>
</div>
</td>
</tr>

View File

@ -544,36 +544,68 @@
}
.floating-export {
position: fixed;
top: 20px;
left: 20px;
top: 30px;
left: 30px;
z-index: 1000;
}
.export-btn-float {
width: 56px;
height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
width: 70px;
height: 70px;
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
color: white;
border: none;
border-radius: 50%;
font-size: 1.2rem;
font-size: 1.3rem;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
box-shadow: 0 8px 24px rgba(255, 126, 95, 0.25), 0 1.5px 4px rgba(0,0,0,0.12);
transition: transform 0.15s, box-shadow 0.15s;
animation: floatBtnPulse 1.6s infinite alternate;
border: 3px solid #fff;
}
.export-btn-float:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
transform: scale(1.12);
box-shadow: 0 10px 32px rgba(255, 126, 95, 0.35), 0 2px 8px rgba(0,0,0,0.15);
}
@media print {
.floating-export {
display: none !important;
}
@keyframes floatBtnPulse {
0% { box-shadow: 0 8px 24px rgba(255, 126, 95, 0.25), 0 1.5px 4px rgba(0,0,0,0.12);}
100% { box-shadow: 0 12px 40px 6px rgba(255, 126, 95, 0.35), 0 2.5px 10px rgba(0,0,0,0.18);}
}
.export-icon {
width: 28px;
height: 28px;
margin-bottom: 2px;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.16));
}
#btnText {
font-weight: bold;
font-size: 1.1rem;
letter-spacing: 2px;
text-shadow: 0 1px 6px rgba(0,0,0,0.08);
}
.loading-spinner {
margin-top: 4px;
border: 3px solid #fff;
border-top: 3px solid #feb47b;
border-radius: 50%;
width: 18px;
height: 18px;
animation: spin 0.6s linear infinite;
display: block;
}
@keyframes spin {
0% { transform: rotate(0deg);}
100% { transform: rotate(360deg);}
}
/* 四级评价等级颜色 */
.score-excellent { background-color: #dcfce7; color: #166534; } /* 优 - 绿色 */
@ -626,7 +658,10 @@
<div class="floating-export">
<button class="export-btn-float" onclick="downloadPDF()" id="pdfBtn">
<span id="btnText">📄</span>
<svg class="export-icon" viewBox="0 0 24 24">
<path fill="white" d="M5 20h14v-2H5v2zm7-18C6.48 2 2 6.48 2 12c0 5.52 4.48 10 10 10s10-4.48 10-10C22 6.48 17.52 2 12 2zm1 15h-2v-6H8l4-4 4 4h-3v6z"/>
</svg>
<span id="btnText">导出</span>
<div id="btnSpinner" class="loading-spinner" style="display: none;"></div>
</button>
</div>
@ -643,7 +678,6 @@
<span id="report-meta-object"></span>
<span id="report-meta-date"></span>
</span>
<span>评价方法:模糊综合评价法</span>
</div>
</div>

View File

@ -10,7 +10,7 @@
<div class="card-header">
<h3 class="card-title">评估工程列表</h3>
<div class="card-actions">
<a href="javascript:void(0)" class="btn btn-primary" onclick="_toAdd()">
<a href="javascript:void(0)" class="btn btn-primary" onclick="_toAdd(0)">
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
@ -66,6 +66,10 @@
th:onclick="|_projectDelete('${project.id}')|">
删除
</a>
<a href="javascript:void(0)" class=" btn btn-warning"
th:onclick="|_editProject('${project.id}')|">
编辑
</a>
</div>
</td>
</tr>
@ -96,8 +100,8 @@
document.getElementById("_evaluation_project_").click();
}
function _toAdd() {
let url = document.getElementById("_rootPath").value + "evaluation/project/add";
function _toAdd(id) {
let url = document.getElementById("_rootPath").value + "evaluation/project/add?id="+id;
let http = new HttpClient();
http.get(url, function (error, res, xhr) {
document.getElementById("simpleFormBody").innerHTML = res;
@ -157,23 +161,22 @@
}
//选择了数据源后触发
//缓存projectId+randomId+method
//缓存projectId
function selectedDatasource(projectId) {
//获取选中的数据源
let value = $('input[name="datasource_type"]:checked').val();
let method = $('input[name="aggregationMethod"]:checked').val();
var url = "";
if (value === 'database') {
url = document.getElementById("_rootPath").value + "evaluation/project/database?projectId="
+ projectId + "&randomKey=" + document.getElementById("stepForm")['randomKey'].value
+ "&method=" + method;
}
if (value === 'dataCsv') {
url = document.getElementById("_rootPath").value + "evaluation/project/csv?projectId="
+ projectId
+ "&randomKey=" + document.getElementById("stepForm")['randomKey'].value + "&method="
+ method;
+ "&randomKey=" + document.getElementById("stepForm")['randomKey'].value
}
let http = new HttpClient();
http.get(url, function (error, response, xhr) {
@ -211,7 +214,7 @@
projectId: form['projectId'].value,
search: form['search'].value,
randomKey: form['randomKey'].value,
method: form['method'].value,
ids: ids,
identifier: identifier,
@ -292,11 +295,16 @@
}
function readCsv() {
const $files = document.getElementById("csv_file");
const $files = document.getElementById("csvFile");
const fs = $files.files;
if (fs.length === 0) {
return;
}
const fileName = document.getElementById("file-name");
fileName.textContent = fs[0].name;
const MAX = 10 * 1024 * 1024; // 10MB
const $form = document.getElementById("stepForm");
if (fs[0].size > MAX) {
@ -305,13 +313,14 @@
$files.value = ""; // 清空选择
return;
}
const formData = new FormData();
formData.append("modelId", $form['modelId'].value);
formData.append("projectId", $form['projectId'].value);
formData.append("datasource", "csv");
formData.append("search", $form['search'].value);
formData.append("randomKey", $form['randomKey'].value);
formData.append("method", $form['method'].value);
formData.append("file", fs[0]);
let url = document.getElementById("_rootPath").value + "evaluation/project/uploadCsv";
fetch(url, {
@ -442,8 +451,6 @@
});
}
function populateTable(reportData) {
const tbody = document.getElementById('indicatorTable');
tbody.innerHTML = '';
@ -775,17 +782,18 @@
.text(`目标 ${cfg.target}`);
}
}
function dynamic_search(){
function dynamic_search() {
let value = $('input[name="datasource_type"]:checked').val();
let url = document.getElementById("_rootPath").value;
if(value==='database'){
url = url+"evaluation/project/database";
}else{
url = url+"evaluation/project/csv";
if (value === 'database') {
url = url + "evaluation/project/database";
} else {
url = url + "evaluation/project/csv";
}
//组装参数
let qs = formToQueryString('stepForm');
url = url+"?"+qs;
url = url + "?" + qs;
let http = new HttpClient();
http.get(url, function (error, response, xhr) {
@ -793,4 +801,20 @@
document.getElementById("add-full-screen-form-modal-body-no-btn").innerHTML = response;
}, null);
}
function removeHistory(obj) {
let url = document.getElementById("_rootPath").value + "evaluation/project/history/remove/" + obj.dataset.resultId;
let http = new HttpClient();
http.get(url, function (error, response, xhr) {
var tr = obj.closest('tr');
if (tr) {
tr.remove();
}
return false; // 阻止<a>的默认跳转
}, null)
}
function _editProject(id){
_toAdd(id)
}
</script>

View File

@ -30,66 +30,102 @@
<th>等级名</th>
<th>代表分</th>
<th>代表值</th>
</tr>
</thead>
<tbody id="level_list_table">
<tr>
<th:block th:if="${indicatorSetBean.getLevels()!=null and indicatorSetBean.getLevels().size()>0 }">
<tr th:each="level : ${indicatorSetBean.getLevels()}">
<td>
<label>
<input type="text" value="优" style="width:8ch" name="_level"/>
<input type="text" th:value="${level.getLevelName()}" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="90" style="width:8ch" name="_score"/>
</label>
</td>
</tr>
<tr>
<td>
<label>
<input type="text" value="良" style="width:8ch" name="_level"/>
<input type="number" th:value="${level.getGrade()}" style="width:8ch" name="_score"/>
</label>
</td>
<td>
<label>
<input type="number" value="80" style="width:8ch" name="_score"/>
<input type="text" th:value="${level.getEqualValue()}" style="width:8ch" name="_equalValue"/>
</label>
</td>
</tr>
<tr>
<td>
<label>
<input type="text" value="可" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="70" style="width:8ch" name="_score"/>
</label>
</td>
</tr>
<tr>
<td>
<label>
<input type="text" value="差" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="60" style="width:8ch" name="_score"/>
</label>
</td>
</tr>
</th:block>
<th:block th:if="${indicatorSetBean.getLevels()==null or indicatorSetBean.getLevels().size()==0 }">
<tr>
<td>
<label>
<input type="text" value="优" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="90" style="width:8ch" name="_score"/>
</label>
</td>
<td>
<label>
<input type="text" value="优" style="width:8ch" name="_equalValue"/>
</label>
</td>
</tr>
<tr>
<td>
<label>
<input type="text" value="良" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="80" style="width:8ch" name="_score"/>
</label>
</td>
<td>
<label>
<input type="text" value="良" style="width:8ch" name="_equalValue"/>
</label>
</td>
</tr>
<tr>
<td>
<label>
<input type="text" value="可" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="70" style="width:8ch" name="_score"/>
</label>
</td>
<td>
<label>
<input type="text" value="可" style="width:8ch" name="_equalValue"/>
</label>
</td>
</tr>
<tr>
<td>
<label>
<input type="text" value="差" style="width:8ch" name="_level"/>
</label>
</td>
<td>
<label>
<input type="number" value="60" style="width:8ch" name="_score"/>
</label>
</td>
<td>
<label>
<input type="text" value="差" style="width:8ch" name="_equalValue"/>
</label>
</td>
</tr>
</th:block>
</tbody>
</table>
</form>
@ -99,7 +135,7 @@
</div>
<div class="card section">
<div class="card section mb-3">
<div class="card-header">
<div class="card-title">2.全局默认隶属函数设置</div>
</div>
@ -110,9 +146,9 @@
<label class="col-3 col-form-label">全局隶属函数</label>
<div class="col">
<label>
<select class="form-select" name="_membershipFunc" id="_membershipFunc">
<option value="TRAP_TRI">两端梯形·中间三角</option>
<option value="ALL_TRI">全三角</option>
<select class="form-select" name="_membershipFunc" id="_membershipFunc" >
<option value="TRAP_TRI" th:selected="${indicatorSetBean.getTopSet()?.getMembershipFunc()=='TRAP_TRI'}">两端梯形·中间三角</option>
<option value="ALL_TRI" th:selected="${indicatorSetBean.getTopSet()?.getMembershipFunc()=='ALL_TRI'}">全三角</option>
</select>
</label>
@ -122,7 +158,7 @@
<label class="col-3 col-form-label ">软边S</label>
<div class="col">
<label>
<input type="text" class="form-control" value="0.10" name="softEdgeS"
<input type="text" class="form-control" th:value="${indicatorSetBean.getTopSet()?.getSoftEdgeS()}" name="softEdgeS"
id="softEdgeS"/>
</label>
@ -133,7 +169,7 @@
<label class="col-3 col-form-label ">三角重叠比</label>
<div class="col">
<label>
<input type="text" class="form-control" value="0.30"
<input type="text" class="form-control" th:value="${indicatorSetBean.getTopSet()?.getTriangleOverlapRatio()}"
name="triangleOverlapRatio" id="triangleOverlapRatio"/>
</label>
@ -143,7 +179,7 @@
<label class="col-3 col-form-label ">三角峰位比</label>
<div class="col">
<label>
<input type="text" class="form-control" value="0.50"
<input type="text" class="form-control" th:value="${indicatorSetBean.getTopSet()?.getTrianglePeakRatio()}"
name="trianglePeakRatio" id="trianglePeakRatio"/>
</label>
</div>
@ -152,6 +188,31 @@
</div>
</div>
</div>
<div class="card section mb-3">
<div class="card-header">
<div class="card-title">3.模糊运算方式</div>
</div>
<div class="card-body">
<div class="card-body">
<form id="methodForm">
<div class="mb-3 row">
<label class="col-3 col-form-label">全局模糊运算方式</label>
<div class="col">
<label>
<select class="form-select" name="_method" id="_method" >
<option value="PRODUCT_BOUNDED_SUM" th:selected="${indicatorSetBean.getTopSet()?.getMethod()=='PRODUCT_BOUNDED_SUM'}">乘积有界和算子M(·,⊕)</option>
<option value="MIN_MAX" th:selected="${indicatorSetBean.getTopSet()?.getMethod()=='MIN_MAX'}">取小取大算子 M(∧,)</option>
<option value="PRODUCT_MAX" th:selected="${indicatorSetBean.getTopSet()?.getMethod()=='PRODUCT_MAX'}">乘积取大算子 M(·,)</option>
<option value="MIN_BOUNDED_SUM" th:selected="${indicatorSetBean.getTopSet()?.getMethod()=='MIN_BOUNDED_SUM'}">取小有界和算子M(∧,⊕)</option>
</select>
</label>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@ -168,23 +229,31 @@ function saveIndicatorGlobalSet(){
let levelGradeList = [];
let levelNames = document.getElementsByName("_level");
let grades = document.getElementsByName("_score");
let equalValues = document.getElementsByName("_equalValue");
for (let i = 0; i < levelNames.length; i++) {
let level = {};
level.levelName = levelNames[i].value;
level.indicatorTopId = parseInt(obj.indicatorTopId);
level.grade = parseInt(grades[i].value);
level.equalValue = (equalValues[i].value);
levelGradeList.push(level);
}
obj.topLevel = levelGradeList;
//获取顶级指标隶属函数设置
//评价集设置
obj.levels = levelGradeList;
let membership = {};
membership.indicatorTopId = obj.indicatorTopId;
membership.membershipFunc = document.getElementById("_membershipFunc").value;
membership.softEdgeS = document.getElementById("softEdgeS").value;
membership.triangleOverlapRatio = document.getElementById("triangleOverlapRatio").value;
membership.trianglePeakRatio = document.getElementById("trianglePeakRatio").value;
membership.method = document.getElementById("_method").value;
//级指标隶属函数设置
obj.topSet = membership;
let url = document.getElementById("_rootPath").value+"indicatorSet/save"
let http = new HttpClient();
http.post(url, obj,function (error,res,xhr) {
showAlert("success","保存成功")
},null,null)
}
</script>

View File

@ -58,7 +58,7 @@
<div class="table-responsive">
<table class="table card-table table-vcenter text-nowrap datatable">
<table class="table card-table table-vcenter text-nowrap datatable ">
<thead>
<tr>
@ -82,7 +82,7 @@
<tbody>
<tr th:if="${result.list!=null}" th:each="log : ${result.list}">
<td th:text="${log.seq}"></td>
<td th:text="${log.msg}"></td>
<td th:text="${#strings.abbreviate(log.msg, 10)}" th:title="${log.msg}"></td>
<td th:text="${#temporals.format(log.createTime, 'yyyy-MM-dd HH:mm:ss')}" ></td>
<td th:text="${log.operator}"></td>
<td th:text="${log.ip}"></td>

View File

@ -48,7 +48,7 @@
<div class="invalid-feedback" id="confirmHashedPassword_error_tip"></div>
</div>
<div class="mb-3">
<div class="form-label">昵称</div>
<div class="form-label required">昵称</div>
<input type="text" name="nickName" class="form-control" id="nickName"
placeholder="昵称" autocomplete="off">
<div class="invalid-feedback" id="nickName_error_tip"></div>

View File

@ -79,16 +79,21 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Spring Boot Starter AOP (推荐) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring Boot 项目通常无需手动添加 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>com</groupId>
<artifactId>dmjdbc</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -52,16 +52,7 @@ public class AhpTreeCompute {
* 打印树缩进展示显示局部/全局权重与一致性
*/
static void printTree(AhpNode node, int depth) {
String indent = " ".repeat(depth);
String info = String.format("%s- %s (global=%.4f)", indent, node.name,
Double.isNaN(node.globalWeight) ? 0.0 : node.globalWeight);
for (AhpNode c : node.children) {
printTree(c, depth + 1);
}
}
// ---------------- 完整示例 ----------------

View File

@ -12,11 +12,15 @@ package com.hshh.system.algorithm.fuzzy;
// 评价等级定义类
public class EvaluationLevel {
private String levelName;
//分值
private double score;
//相等值
private String equalValue;
public EvaluationLevel(String levelName, double score) {
public EvaluationLevel(String levelName, double score, String equalValue) {
this.levelName = levelName;
this.score = score;
this.equalValue = equalValue;
}
public String getLevelName() { return levelName; }

View File

@ -15,6 +15,7 @@ import java.util.Map;
* @since 2025/7/22
*/
public class FuzzyEvaluationService {
private GlobalEvaluationConfig globalConfig;
public FuzzyEvaluationService(GlobalEvaluationConfig globalConfig) {
@ -37,8 +38,15 @@ public class FuzzyEvaluationService {
calculateScore(node);
}
/**
* 计算叶子界的点的隶属度.
*
* @param node 叶子节点
*/
private void calculateLeafNodeMembership(IndicatorNode node) {
if (node.getActualValue() != null && node.getMembershipFunction() != null) {
Map<String, Double> membership = node.getMembershipFunction()
.calculateMembership(node.getActualValue());
node.setMembershipDegrees(membership);
@ -69,7 +77,8 @@ public class FuzzyEvaluationService {
}
// 修正添加归一化处理
if (operator == FuzzyOperator.MIN_BOUNDED_SUM || operator == FuzzyOperator.PRODUCT_BOUNDED_SUM) {
if (operator == FuzzyOperator.MIN_BOUNDED_SUM
|| operator == FuzzyOperator.PRODUCT_BOUNDED_SUM) {
normalizeMembership(combinedMembership);
}
@ -146,47 +155,9 @@ public class FuzzyEvaluationService {
}
public void printEvaluationResult(IndicatorNode root) {
System.out.println("=== 模糊综合评价结果 ===");
System.out.println("全局评价等级和代表分:");
for (EvaluationLevel level : globalConfig.getEvaluationLevels()) {
System.out.println(" " + level.getLevelName() + ": " + level.getScore());
}
System.out.println();
printNodeResult(root, 0);
}
private void printNodeResult(IndicatorNode node, int level) {
String indent = " ".repeat(level);
System.out.println(indent + "节点: " + node.getName() + " (ID: " + node.getId() + ")");
if (node.getWeight() != null) {
System.out.println(indent + "权重: " + String.format("%.4f", node.getWeight()));
}
if (node.getActualValue() != null) {
System.out.println(indent + "实际值: " + node.getActualValue());
}
if (node.getMembershipFunction() != null) {
System.out.println(indent + "隶属函数类型: " + node.getMembershipFunction().getType());
}
System.out.println(indent + "隶属度: ");
// 修正显示隶属度总和用于调试
double totalMembership = 0.0;
for (String levelName : globalConfig.getAllLevelNames()) {
double degree = node.getMembershipDegrees().getOrDefault(levelName, 0.0);
totalMembership += degree;
System.out.println(indent + " " + levelName + ": " + String.format("%.4f", degree));
}
System.out.println(indent + " 总和: " + String.format("%.4f", totalMembership));
if (node.getScore() != null) {
System.out.println(indent + "综合分值: " + String.format("%.2f", node.getScore()));
}
System.out.println(indent + "---");
for (IndicatorNode child : node.getChildren()) {
printNodeResult(child, level + 1);
}
}
}

View File

@ -14,11 +14,11 @@ public class FuzzyEvaluationTest {
public static void main(String[] args) {
// 创建全局评价配置
List<EvaluationLevel> evaluationLevels = Arrays.asList(
new EvaluationLevel("", 40.0),
new EvaluationLevel("及格", 60.0),
new EvaluationLevel("", 70.0),
new EvaluationLevel("", 80.0),
new EvaluationLevel("", 90.0)
new EvaluationLevel("", 40.0,""),
new EvaluationLevel("及格", 60.0,""),
new EvaluationLevel("", 70.0,""),
new EvaluationLevel("", 80.0,""),
new EvaluationLevel("", 90.0,"")
);
GlobalEvaluationConfig globalConfig = new GlobalEvaluationConfig(evaluationLevels);
@ -75,19 +75,19 @@ public class FuzzyEvaluationTest {
// 修正调整底部指标的评价区间使其更合理
List<GradeRange> chineseRanges = Arrays.asList(
new GradeRange("", 0, 60),
new GradeRange("及格", 60, 70),
new GradeRange("", 70, 80), // 调整回原来的区间
new GradeRange("", 80, 90), // 88分应该在这个区间有较高隶属度
new GradeRange("", 90, 100)
new GradeRange("", 0, 60,""),
new GradeRange("及格", 60, 70,""),
new GradeRange("", 70, 80,""), // 调整回原来的区间
new GradeRange("", 80, 90,""), // 88分应该在这个区间有较高隶属度
new GradeRange("", 90, 100,"")
);
List<GradeRange> englishRanges = Arrays.asList(
new GradeRange("", 0, 60),
new GradeRange("及格", 60, 70),
new GradeRange("", 70, 80), // 调整回原来的区间
new GradeRange("", 80, 90), // 88分应该在这个区间有较高隶属度
new GradeRange("", 90, 100)
new GradeRange("", 0, 60,""),
new GradeRange("及格", 60, 70,""),
new GradeRange("", 70, 80,""), // 调整回原来的区间
new GradeRange("", 80, 90,""), // 88分应该在这个区间有较高隶属度
new GradeRange("", 90, 100,"")
);

View File

@ -14,6 +14,7 @@ import java.util.Map;
* @since 2025/7/22
*/
public class GlobalMembershipFunction implements MembershipFunction {
private String functionType;
private double overlapRatio;
private double peakRatio;
@ -31,7 +32,7 @@ public class GlobalMembershipFunction implements MembershipFunction {
}
@Override
public Map<String, Double> calculateMembership(double value) {
public Map<String, Double> calculateMembership(Object value) {
Map<String, Double> membership = new HashMap<>();
List<String> levelNames = globalConfig.getAllLevelNames();
@ -42,14 +43,14 @@ public class GlobalMembershipFunction implements MembershipFunction {
if ("TRAP_TRI".equals(functionType)) {
if (i == 0) {
degree = calculateLeftTrapezoid(value, scores, i);
degree = calculateLeftTrapezoid((Double) value, scores, i);
} else if (i == levelNames.size() - 1) {
degree = calculateRightTrapezoid(value, scores, i);
degree = calculateRightTrapezoid((Double) value, scores, i);
} else {
degree = calculateTriangle(value, scores, i);
degree = calculateTriangle((Double) value, scores, i);
}
} else {
degree = calculateTriangle(value, scores, i);
degree = calculateTriangle((Double) value, scores, i);
}
membership.put(levelNames.get(i), Math.max(0.0, Math.min(1.0, degree)));

View File

@ -1,5 +1,7 @@
package com.hshh.system.algorithm.fuzzy;
import lombok.Data;
/**
* [类的简要说明]
* <p>
@ -9,21 +11,18 @@ package com.hshh.system.algorithm.fuzzy;
* @author LiDongYU
* @since 2025/7/22
*/
@Data
public class GradeRange {
private String gradeName;
private double lowerBound;
private double upperBound;
public GradeRange(String gradeName, double lowerBound, double upperBound) {
private String equalVal;
public GradeRange(String gradeName, double lowerBound, double upperBound, String equalVal) {
this.gradeName = gradeName;
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.equalVal = equalVal;
}
public String getGradeName() { return gradeName; }
public double getLowerBound() { return lowerBound; }
public double getUpperBound() { return upperBound; }
public void setGradeName(String gradeName) { this.gradeName = gradeName; }
public void setLowerBound(double lowerBound) { this.lowerBound = lowerBound; }
public void setUpperBound(double upperBound) { this.upperBound = upperBound; }
}

View File

@ -21,7 +21,7 @@ public class IndicatorNode {
private String id;
private String name;
private Double weight;
private Double actualValue;
private Object actualValue;
private MembershipFunction membershipFunction;
private List<IndicatorNode> children;
private IndicatorNode parent;
@ -34,11 +34,14 @@ public class IndicatorNode {
private String logicName;
private int projectId;
public IndicatorNode(String id, String name) {
this.id = id;
this.name = name;
this.children = new ArrayList<>();
this.membershipDegrees = new HashMap<>();
}

View File

@ -14,6 +14,7 @@ import java.util.Map;
* @since 2025/7/22
*/
public class LeafFullTriangularMembershipFunction implements MembershipFunction {
private List<GradeRange> gradeRanges;
private double overlapRatio;
private double peakRatio;
@ -36,76 +37,63 @@ public class LeafFullTriangularMembershipFunction implements MembershipFunction
List<String> globalLevels = globalConfig.getAllLevelNames();
for (GradeRange range : gradeRanges) {
if (!globalLevels.contains(range.getGradeName())) {
throw new IllegalArgumentException("评价等级名称 '" + range.getGradeName() + "' 不在全局定义中");
throw new IllegalArgumentException(
"评价等级名称 '" + range.getGradeName() + "' 不在全局定义中");
}
}
}
@Override
public Map<String, Double> calculateMembership(double value) {
System.out.println("\n🔍 ===== ALL_TRI算法开始计算隶属度 =====");
System.out.println("输入值: " + value);
System.out.println("重叠比例: " + overlapRatio);
System.out.println("峰值比例: " + peakRatio);
System.out.println("软边界: " + softEdge);
System.out.println("等级数量: " + gradeRanges.size());
public Map<String, Double> calculateMembership(Object value) {
Map<String, Double> membership = new HashMap<>();
// 初始化所有全局等级的隶属度为0
for (String levelName : globalConfig.getAllLevelNames()) {
membership.put(levelName, 0.0);
}
System.out.println("\n📊 开始逐个计算等级隶属度:");
for (int i = 0; i < gradeRanges.size(); i++) {
GradeRange range = gradeRanges.get(i);
System.out.println("\n--- 第" + (i+1) + "个等级: " + range.getGradeName() + " ---");
System.out.println("原始区间: [" + range.getLowerBound() + ", " + range.getUpperBound() + "]");
double degree = calculateTriangle(value, range, i);
degree = Math.max(0.0, Math.min(1.0, degree));
membership.put(range.getGradeName(), degree);
System.out.println("最终隶属度: " + degree);
}
System.out.println("\n📈 归一化前的隶属度:");
double totalBefore = 0.0;
for (Map.Entry<String, Double> entry : membership.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + String.format("%.6f", entry.getValue()));
totalBefore += entry.getValue();
}
System.out.println("归一化前总和: " + String.format("%.6f", totalBefore));
// 🔥 关键修复添加归一化处理
if (totalBefore > 0 && Math.abs(totalBefore - 1.0) > 0.001) {
System.out.println("\n🔄 开始归一化处理:");
for (String key : membership.keySet()) {
if (membership.get(key) > 0) {
double originalValue = membership.get(key);
double normalizedValue = originalValue / totalBefore;
membership.put(key, normalizedValue);
System.out.println(" " + key + ": " + String.format("%.6f", originalValue) + "" + String.format("%.6f", normalizedValue));
if (!isNumber(value.toString())) {
for (GradeRange range : gradeRanges) {
if (range.getEqualVal().equals(value)) {
if (membership.get(range.getGradeName()) != null) {
membership.put(range.getGradeName(), 1.0);
break;
}
}
}
double totalAfter = membership.values().stream().mapToDouble(Double::doubleValue).sum();
System.out.println("归一化后总和: " + String.format("%.6f", totalAfter));
} else {
System.out.println("\n✅ 无需归一化 (总和已接近1.0)");
}
System.out.println("\n📋 最终隶属度结果:");
for (Map.Entry<String, Double> entry : membership.entrySet()) {
if (entry.getValue() > 0) {
System.out.println(" " + entry.getKey() + ": " + String.format("%.6f", entry.getValue()));
if (isNumber(value.toString())) {
for (int i = 0; i < gradeRanges.size(); i++) {
GradeRange range = gradeRanges.get(i);
double degree = calculateTriangle(Double.parseDouble(value.toString()) , range, i);
degree = Math.max(0.0, Math.min(1.0, degree));
membership.put(range.getGradeName(), degree);
}
double totalBefore = 0.0;
for (Map.Entry<String, Double> entry : membership.entrySet()) {
totalBefore += entry.getValue();
}
// 🔥 关键修复添加归一化处理
if (totalBefore > 0 && Math.abs(totalBefore - 1.0) > 0.001) {
for (String key : membership.keySet()) {
if (membership.get(key) > 0) {
double originalValue = membership.get(key);
double normalizedValue = originalValue / totalBefore;
membership.put(key, normalizedValue);
}
}
}
}
System.out.println("===== ALL_TRI算法计算完成 =====\n");
return membership;
}
@ -142,37 +130,27 @@ public class LeafFullTriangularMembershipFunction implements MembershipFunction
System.out.println(" 最后一个等级,右边界大幅延伸: " + rightBoundary);
}
System.out.println(" 区间宽度: " + rangeWidth);
System.out.println(" 重叠距离: " + overlap);
System.out.println(" 扩展后区间: [" + leftBoundary + ", " + rightBoundary + "]");
double result;
if (value <= leftBoundary || value >= rightBoundary) {
System.out.println(" 判断: value 在扩展区间外 → 返回 0.0");
result = 0.0;
} else if (Math.abs(value - peakPoint) < 0.0001) { // 处理浮点数比较
System.out.println(" 判断: value 等于峰值点 → 返回 1.0");
result = 1.0;
} else if (value <= peakPoint) {
// 上升边
double slope = 1.0 / (peakPoint - leftBoundary);
double basicDegree = (value - leftBoundary) * slope;
System.out.println(" 判断: 上升边计算");
System.out.println(" 上升边区间: [" + leftBoundary + ", " + peakPoint + "]");
System.out.println(" 斜率: " + slope);
System.out.println(" 基础隶属度: " + basicDegree);
result = applySoftEdge(basicDegree);
System.out.println(" 软边界处理后: " + result);
} else {
// 下降边
double slope = 1.0 / (rightBoundary - peakPoint);
double basicDegree = (rightBoundary - value) * slope;
System.out.println(" 判断: 下降边计算");
System.out.println(" 下降边区间: [" + peakPoint + ", " + rightBoundary + "]");
System.out.println(" 斜率: " + slope);
System.out.println(" 基础隶属度: " + basicDegree);
result = applySoftEdge(basicDegree);
System.out.println(" 软边界处理后: " + result);
}
return result;
@ -180,11 +158,11 @@ public class LeafFullTriangularMembershipFunction implements MembershipFunction
private double applySoftEdge(double degree) {
if (softEdge <= 0) {
System.out.println(" 软边界处理: softEdge <= 0直接返回原值");
return degree;
} else {
double result = 1.0 / (1.0 + Math.exp(-softEdge * (degree - 0.5)));
System.out.println(" 软边界处理: sigmoid函数" + degree + "" + result);
return result;
}
}

View File

@ -14,6 +14,7 @@ import java.util.Map;
* @since 2025/7/22
*/
public class LeafHybridMembershipFunction implements MembershipFunction {
private List<GradeRange> gradeRanges;
private double overlapRatio;
private double peakRatio;
@ -36,14 +37,14 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
List<String> globalLevels = globalConfig.getAllLevelNames();
for (GradeRange range : gradeRanges) {
if (!globalLevels.contains(range.getGradeName())) {
throw new IllegalArgumentException("评价等级名称 '" + range.getGradeName() + "' 不在全局定义中");
throw new IllegalArgumentException(
"评价等级名称 '" + range.getGradeName() + "' 不在全局定义中");
}
}
}
@Override
public Map<String, Double> calculateMembership(double value) {
public Map<String, Double> calculateMembership(Object value) {
Map<String, Double> membership = new HashMap<>();
@ -52,68 +53,63 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
membership.put(levelName, 0.0);
}
System.out.println("\n📊 开始逐个计算等级隶属度:");
// 计算每个自定义区间的隶属度
for (int i = 0; i < gradeRanges.size(); i++) {
GradeRange range = gradeRanges.get(i);
System.out.println("\n--- 第" + (i+1) + "个等级: " + range.getGradeName() + " ---");
System.out.println("原始区间: [" + range.getLowerBound() + ", " + range.getUpperBound() + "]");
double degree = 0.0;
if (i == 0) {
System.out.println("类型: 左梯形 (第一个等级)");
degree = calculateLeftTrapezoid(value, range, i);
} else if (i == gradeRanges.size() - 1) {
System.out.println("类型: 右梯形 (最后一个等级)");
degree = calculateRightTrapezoid(value, range, i);
} else {
System.out.println("类型: 三角形 (中间等级)");
degree = calculateTriangle(value, range, i);
}
degree = Math.max(0.0, Math.min(1.0, degree));
membership.put(range.getGradeName(), degree);
System.out.println("最终隶属度: " + degree);
}
System.out.println("\n📈 归一化前的隶属度:");
double totalBefore = 0.0;
for (Map.Entry<String, Double> entry : membership.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + String.format("%.6f", entry.getValue()));
totalBefore += entry.getValue();
}
System.out.println("归一化前总和: " + String.format("%.6f", totalBefore));
// 归一化处理修正版
if (totalBefore > 0 && Math.abs(totalBefore - 1.0) > 0.001) {
System.out.println("\n🔄 开始归一化处理:");
for (String key : membership.keySet()) {
if (membership.get(key) > 0) {
double originalValue = membership.get(key);
double normalizedValue = originalValue / totalBefore;
membership.put(key, normalizedValue);
System.out.println(" " + key + ": " + String.format("%.6f", originalValue) + "" + String.format("%.6f", normalizedValue));
if (!isNumber(value.toString())) {
for (GradeRange range : gradeRanges) {
if (range.getEqualVal().equals(value)) {
if (membership.get(range.getGradeName()) != null) {
membership.put(range.getGradeName(), 1.0);
break;
}
}
}
double totalAfter = membership.values().stream().mapToDouble(Double::doubleValue).sum();
System.out.println("归一化后总和: " + String.format("%.6f", totalAfter));
} else {
System.out.println("\n✅ 无需归一化 (总和已接近1.0)");
}
System.out.println("\n📋 最终隶属度结果:");
for (Map.Entry<String, Double> entry : membership.entrySet()) {
if (entry.getValue() > 0) {
System.out.println(" " + entry.getKey() + ": " + String.format("%.6f", entry.getValue()));
if (isNumber(value.toString())) {
// 计算每个自定义区间的隶属度
for (int i = 0; i < gradeRanges.size(); i++) {
GradeRange range = gradeRanges.get(i);
double degree = 0.0;
if (i == 0) {
degree = calculateLeftTrapezoid(Double.parseDouble(value.toString()), range, i);
} else if (i == gradeRanges.size() - 1) {
degree = calculateRightTrapezoid(Double.parseDouble(value.toString()), range, i);
} else {
degree = calculateTriangle(Double.parseDouble(value.toString()), range, i);
}
degree = Math.max(0.0, Math.min(1.0, degree));
membership.put(range.getGradeName(), degree);
}
double totalBefore = 0.0;
for (Map.Entry<String, Double> entry : membership.entrySet()) {
totalBefore += entry.getValue();
}
// 归一化处理修正版
if (totalBefore > 0 && Math.abs(totalBefore - 1.0) > 0.001) {
for (String key : membership.keySet()) {
if (membership.get(key) > 0) {
double originalValue = membership.get(key);
double normalizedValue = originalValue / totalBefore;
membership.put(key, normalizedValue);
}
}
}
}
System.out.println("===== 隶属度计算完成 =====\n");
return membership;
}
@ -125,27 +121,22 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
double nextLowerBound = index + 1 < gradeRanges.size() ?
gradeRanges.get(index + 1).getLowerBound() : plateauEnd;
System.out.println(" 平台区间: [" + plateauStart + ", " + plateauEnd + "]");
System.out.println(" 下一等级下界: " + nextLowerBound);
double result;
if (value <= plateauStart) {
System.out.println(" 判断: value <= plateauStart → 返回 1.0");
result = 1.0;
} else if (value <= plateauEnd) {
System.out.println(" 判断: plateauStart < value <= plateauEnd → 返回 1.0");
result = 1.0;
} else if (value >= nextLowerBound) {
System.out.println(" 判断: value >= nextLowerBound → 返回 0.0");
result = 0.0;
} else {
double slope = 1.0 / (nextLowerBound - plateauEnd);
double basicDegree = (nextLowerBound - value) * slope;
System.out.println(" 判断: 下降边计算");
System.out.println(" 斜率: " + slope);
System.out.println(" 基础隶属度: " + basicDegree);
result = applySoftEdge(basicDegree);
System.out.println(" 软边界处理后: " + result);
}
return result;
@ -160,11 +151,6 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
double overlap = rangeWidth * overlapRatio;
double leftExtend = plateauStart - overlap;
System.out.println(" 平台区间: [" + plateauStart + ", " + plateauEnd + "]");
System.out.println(" 区间宽度: " + rangeWidth);
System.out.println(" 重叠距离: " + overlap);
System.out.println(" 左扩展点: " + leftExtend);
double result;
if (value <= leftExtend) {
System.out.println(" 判断: value <= leftExtend → 返回 0.0");
@ -172,13 +158,11 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
} else if (value <= plateauStart) {
double slope = 1.0 / (plateauStart - leftExtend);
double basicDegree = (value - leftExtend) * slope;
System.out.println(" 判断: 上升边计算");
System.out.println(" 斜率: " + slope);
System.out.println(" 基础隶属度: " + basicDegree);
result = applySoftEdge(basicDegree);
System.out.println(" 软边界处理后: " + result);
} else {
System.out.println(" 判断: value > plateauStart → 返回 1.0");
result = 1.0;
}
@ -196,36 +180,25 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
double leftBoundary = lowerBound - overlap / 2;
double rightBoundary = upperBound + overlap / 2;
System.out.println(" 区间宽度: " + rangeWidth);
System.out.println(" 重叠距离: " + overlap);
System.out.println(" 峰值点: " + peakPoint);
System.out.println(" 扩展后区间: [" + leftBoundary + ", " + rightBoundary + "]");
double result;
if (value <= leftBoundary || value >= rightBoundary) {
System.out.println(" 判断: value 在扩展区间外 → 返回 0.0");
result = 0.0;
} else if (Math.abs(value - peakPoint) < 0.0001) { // 处理浮点数比较
System.out.println(" 判断: value 等于峰值点 → 返回 1.0");
result = 1.0;
} else if (value < peakPoint) {
double slope = 1.0 / (peakPoint - leftBoundary);
double basicDegree = (value - leftBoundary) * slope;
System.out.println(" 判断: 上升边计算");
System.out.println(" 上升边区间: [" + leftBoundary + ", " + peakPoint + "]");
System.out.println(" 斜率: " + slope);
System.out.println(" 基础隶属度: " + basicDegree);
result = applySoftEdge(basicDegree);
System.out.println(" 软边界处理后: " + result);
} else {
double slope = 1.0 / (rightBoundary - peakPoint);
double basicDegree = (rightBoundary - value) * slope;
System.out.println(" 判断: 下降边计算");
System.out.println(" 下降边区间: [" + peakPoint + ", " + rightBoundary + "]");
System.out.println(" 斜率: " + slope);
System.out.println(" 基础隶属度: " + basicDegree);
result = applySoftEdge(basicDegree);
System.out.println(" 软边界处理后: " + result);
}
return result;
@ -233,11 +206,11 @@ public class LeafHybridMembershipFunction implements MembershipFunction {
private double applySoftEdge(double degree) {
if (softEdge <= 0) {
System.out.println(" 软边界处理: softEdge <= 0直接返回原值");
return degree;
} else {
double result = 1.0 / (1.0 + Math.exp(-softEdge * (degree - 0.5)));
System.out.println(" 软边界处理: sigmoid函数" + degree + "" + result);
return result;
}
}

View File

@ -12,6 +12,12 @@ import java.util.Map;
* @since 2025/7/22
*/
public interface MembershipFunction {
Map<String, Double> calculateMembership(double value);
Map<String, Double> calculateMembership(Object value);
String getType();
default boolean isNumber(String str) {
return str != null && str.matches("^\\d+(\\.\\d+)?$");
}
}

View File

@ -3,6 +3,7 @@ package com.hshh.system.aspect;
import com.hshh.system.Global;
import com.hshh.system.annotation.LogOperation;
import com.hshh.system.base.entity.Logs;
import com.hshh.system.common.Strings.StringUtil;
import com.hshh.system.common.jwt.JwtUtil;
import com.hshh.system.common.util.IpUtil;
import java.time.LocalDateTime;
@ -37,23 +38,48 @@ public class LogOperationAspect {
public void logPointcut() {
}
@SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
@Before("logPointcut()")
public void before(JoinPoint joinPoint) {
try {
HttpServletRequest request = getCurrentRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
LogOperation logOperation = signature.getMethod().getAnnotation(LogOperation.class);
Object[] args = joinPoint.getArgs();
String ip = getCurrentUserIp();
String userName = JwtUtil.getCurrentUsername();
Logs logs = new Logs();
logs.setIp(ip);
logs.setOperator(userName);
logs.setType("BIZ");
if (request != null) {
logs.setUrl(request.getRequestURL().toString());
logs.setParams(StringUtil.getParamsAsJson(request));
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
LogOperation logOperation = signature.getMethod().getAnnotation(LogOperation.class);
String ip = getCurrentUserIp();
String userName = JwtUtil.getCurrentUsername();
Logs logs = new Logs();
logs.setIp(ip);
logs.setOperator(userName);
logs.setType("BIZ");
logs.setCreateTime(LocalDateTime.now());
if (logOperation != null) {
logs.setMsg(logOperation.value());
logs.setCreateTime(LocalDateTime.now());
if (logOperation != null) {
logs.setMsg(logOperation.value());
}
Global.logQueue.offer(logs);
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 获取当前请求的HttpServletRequest.
*/
private HttpServletRequest getCurrentRequest() {
try {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attributes.getRequest();
} catch (IllegalStateException e) {
log.warn("无法获取当前请求上下文");
return null;
}
Global.logQueue.offer(logs);
}
/**

View File

@ -169,6 +169,8 @@ public class LoginController {
if (extendUserDetails.isAdmin()) {
request.setAttribute("menus", menusService.getRootMenus());
} else {
request.setAttribute("menus", menusService.getUserMenus(extendUserDetails.getId()));
}
return "home";
}

View File

@ -0,0 +1,104 @@
package com.hshh.system.auth.controller;
import com.hshh.system.auth.bean.ExtendUserDetails;
import com.hshh.system.base.entity.Users;
import com.hshh.system.base.service.ConfigSetService;
import com.hshh.system.base.service.MenusService;
import com.hshh.system.base.service.UsersService;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Controller
@RequestMapping("/thirdLogin")
@Slf4j
public class ThirdLoginController {
private final AuthenticationManager authenticationManager;
//系统配置参数服务类
private final ConfigSetService configSetService;
private final MenusService menusService;
private final UsersService usersService;
//第三方页面登录时统一分配简单密码用于访问功能
private static final String defaultPassword = "123456";
public ThirdLoginController(AuthenticationManager authenticationManager,
ConfigSetService configSetService, MenusService menusService, UsersService usersService) {
this.authenticationManager = authenticationManager;
this.configSetService = configSetService;
this.menusService = menusService;
this.usersService = usersService;
}
/**
* 外部调用直接进入评估系统页面.
*
* @param username 用户名
* @param model 容器
* @param request 请求
* @return home.html
*/
@GetMapping("/thirdLoginPage")
public String thirdLoginPage(String username, Model model,
HttpServletRequest request) {
try {
Users exitUser = usersService.getUserByUsername(username);
if (exitUser == null) {
//建立新用户
Users user = new Users();
user.setUsername(username);
user.setNickName(username);
user.setAdmin(true);
user.setHashedPassword(new BCryptPasswordEncoder().encode(defaultPassword));
usersService.saveUsers(user);
}
//查询用户名是否存在如果存在直接登录如果不存在新建用户
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
username, defaultPassword);
//验证
Authentication authentication = authenticationManager.authenticate(token);
//结果放入
SecurityContextHolder.getContext().setAuthentication(authentication);
//放入session中
request.getSession()
.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
ExtendUserDetails extendUserDetails = (ExtendUserDetails) authentication.getPrincipal();
//用户信息
request.setAttribute("user", extendUserDetails);
//获取菜单
if (extendUserDetails.isAdmin()) {
request.setAttribute("menus", menusService.getRootMenus());
}
model.addAttribute("systemTitle", configSetService.getTitle());
} catch (Exception e) {
log.error(e.getMessage());
}
return "home";
}
}

View File

@ -0,0 +1,22 @@
package com.hshh.system.base.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Controller
public class FaqController {
@GetMapping("faq")
public String faq() {
return "faq";
}
}

View File

@ -13,11 +13,11 @@ import com.hshh.system.common.bean.BaseController;
import com.hshh.system.common.bean.ErrorField;
import com.hshh.system.common.bean.OperateResult;
import com.hshh.system.common.bean.PaginationBean;
import com.hshh.system.common.bean.TableRowsResult;
import com.hshh.system.common.enums.ErrorCode;
import com.hshh.system.common.enums.ErrorMessage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Collections;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -125,9 +125,10 @@ public class UserController extends BaseController {
private OperateResult<Object> handleUserSave(Users user) {
//检查确认密码和密码是否一致
if (!user.getHashedPassword().equalsIgnoreCase(user.getConfirmHashedPassword())) {
if (user.getHashedPassword() != null && user.getConfirmHashedPassword() != null
&& !user.getHashedPassword().equalsIgnoreCase(user.getConfirmHashedPassword())) {
ErrorField errorField = new ErrorField("confirmHashedPassword", "确认密码和密码不一致");
List<ErrorField> errorFieldList = List.of(errorField);
List<ErrorField> errorFieldList = Collections.singletonList(errorField);
return OperateResult.error(null, "", ErrorCode.PARAM_FORMAT.getCode(), errorFieldList);
}
if (user.getHashedPassword() != null && !user.getHashedPassword().equalsIgnoreCase("")) {

View File

@ -31,8 +31,8 @@ public class Users extends BaseBean {
private String username;
@NotBlank(message = "密码不能为空", groups = {CreateUser.class})
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_]).{6,}$",
message = "密码不少于6位且至少包含大写字母、小写字母、数字和特殊符号",
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,}$",
message = "密码不少于6位且至少包含大写字母、小写字母、数字",
groups = {CreateUser.class, UpdateUser.class}
)
@ -42,8 +42,8 @@ public class Users extends BaseBean {
@Setter
@NotBlank(message = "确认密码不能为空", groups = {CreateUser.class})
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_]).{6,}$",
message = "确认密码密码不少于6位且至少包含大写字母、小写字母、数字和特殊符号",
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,}$",
message = "确认密码密码不少于6位且至少包含大写字母、小写字母、数字",
groups = {CreateUser.class, UpdateUser.class}
)
private String confirmHashedPassword;
@ -90,6 +90,7 @@ public class Users extends BaseBean {
/**
* 昵称.
*/
@NotBlank(message = "昵称不能为空")
@Size(max = 50, message = "昵称不能超过50字符")
private String nickName;

View File

@ -10,6 +10,7 @@ import java.util.List;
* @author liDongYu
* @since 2025-07-29
*/
@Deprecated
public interface TableRelationsService extends IService<TableRelations> {
/**
@ -19,6 +20,7 @@ public interface TableRelationsService extends IService<TableRelations> {
* @param dstTable 引用表
* @return 查询结果
*/
@Deprecated
List<TableRelations> queryRel(Integer id, String dstTable);
/**
@ -28,6 +30,7 @@ public interface TableRelationsService extends IService<TableRelations> {
* @param srcTable 原表名称
* @param dstTable 目标表名称
*/
@Deprecated
void removeRel(Integer srcId, String srcTable, String dstTable);
/**
@ -38,6 +41,7 @@ public interface TableRelationsService extends IService<TableRelations> {
* @param dstId 目标表ID
* @param dstTable 目标表名称
*/
@Deprecated
void removeRel(Integer srcId, String srcTable, Integer dstId, String dstTable);
/**
@ -48,6 +52,7 @@ public interface TableRelationsService extends IService<TableRelations> {
* @param dstIdList 引用表ID集合
* @param dstTable 引用表
*/
@Deprecated
void addRel(Integer srcId, String srcTable, List<Integer> dstIdList, String dstTable);
}

View File

@ -11,7 +11,7 @@ import lombok.Getter;
@Getter
public enum ErrorMessage {
NAME_OR_CODE_EXIT("名称或者编码已经存在"),
NAME_EXIT("名称或者编码已经存在"),
ID_NOT_EXIT("记录不存在"),
SUCCESS("操作成功"),

View File

@ -0,0 +1,68 @@
package com.hshh.system.common.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public class FileUtil {
// 从InputStream解析CSV
public static List<List<String>> parseCsvFromInputStream(MultipartFile file) throws IOException {
List<List<String>> result = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
result.add(parseCsvLine(line));
}
}
}
return result;
}
// 解析单行CSV
private static List<String> parseCsvLine(String line) {
List<String> fields = new ArrayList<>();
StringBuilder currentField = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char currentChar = line.charAt(i);
char nextChar = (i + 1 < line.length()) ? line.charAt(i + 1) : '\0';
if (currentChar == '"') {
if (inQuotes && nextChar == '"') {
currentField.append('"');
i++;
} else {
inQuotes = !inQuotes;
}
} else if (currentChar == ',' && !inQuotes) {
fields.add(currentField.toString());
currentField.setLength(0);
} else {
currentField.append(currentChar);
}
}
fields.add(currentField.toString());
return fields;
}
}

View File

@ -0,0 +1,90 @@
package com.hshh.system.common.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* [类的简要说明]
* <p>
* [详细描述可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
public class StringTool {
public static Set<String> checkDuplicates(String content, String split) {
// , ; 换行 分割
String[] childTextArray = content.split(split);
// 用于去重和判断重复
Set<String> set = new HashSet<>();
Set<String> duplicates = new HashSet<>();
for (String name : childTextArray) {
name = name.trim();
if (name.isEmpty()) {
continue;
}
if (!set.add(name)) {
duplicates.add(name);
}
}
return duplicates;
}
public static List<List<String>> parseCSV(String csvText) {
List<List<String>> rows = new ArrayList<>();
List<String> currentRow = new ArrayList<>();
StringBuilder currentField = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < csvText.length(); i++) {
char currentChar = csvText.charAt(i);
char nextChar = (i + 1 < csvText.length()) ? csvText.charAt(i + 1) : '\0';
if (currentChar == '"') {
if (inQuotes && nextChar == '"') {
// 处理双引号转义
currentField.append('"');
i++; // 跳过下一个引号
} else {
// 切换引号状态
inQuotes = !inQuotes;
}
} else if (currentChar == ',' && !inQuotes) {
// 字段分隔符
currentRow.add(currentField.toString());
currentField.setLength(0);
} else if ((currentChar == '\n' || currentChar == '\r') && !inQuotes) {
// 行结束
if (currentField.length() > 0 || !currentRow.isEmpty()) {
currentRow.add(currentField.toString());
rows.add(new ArrayList<>(currentRow));
currentRow.clear();
currentField.setLength(0);
}
// 跳过 \r\n 中的 \n
if (currentChar == '\r' && nextChar == '\n') {
i++;
}
} else {
// 普通字符
currentField.append(currentChar);
}
}
// 处理最后一行
if (currentField.length() > 0 || !currentRow.isEmpty()) {
currentRow.add(currentField.toString());
rows.add(currentRow);
}
return rows;
}
}

View File

@ -19,7 +19,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource (name="userAuthService")
@Resource(name = "userAuthService")
private UserDetailsService userAuthService;
@Bean
@ -34,9 +34,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//允许iframe
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/login", "/css/**", "/js/**", "/img/**", "/libs/**", "/captcha", "/toAuth",
"/thirdLogin/**",
"/ws/**", "/swagger-ui.html",
"/swagger-ui/**",
"/webfonts/**",
@ -77,6 +79,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
public static void main(String[] args) {
System.out.println( new BCryptPasswordEncoder().encode("123456")); ;
System.out.println(new BCryptPasswordEncoder().encode("123456"));
;
}
}

View File

@ -11,7 +11,7 @@ import org.springframework.web.socket.server.standard.ServerEndpointExporter;
* @since 2025/7/22
*/
@Configuration
public class EndpointConfig {
public class WsEndpointConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {

View File

@ -1,5 +1,6 @@
package com.hshh.system.exception;
import com.hshh.system.Global;
import com.hshh.system.base.entity.Logs;
import com.hshh.system.common.Strings.StringUtil;
import com.hshh.system.common.bean.OperateResult;
@ -33,7 +34,7 @@ public class ApiGlobalExceptionHandler {
*/
@ExceptionHandler(Exception.class)
public OperateResult<Void> handleAll(Exception ex, HttpServletRequest req) {
log.error("error happened", ex);
recordLog(ex, req);
String msg = ex.getMessage();
return OperateResult.error(null, msg, ErrorCode.BUSINESS_ERROR.getCode());
@ -42,7 +43,7 @@ public class ApiGlobalExceptionHandler {
private void recordLog(Exception ex, HttpServletRequest req) {
try {
Logs logs = new Logs();
logs.setMsg(ex.getMessage());
logs.setMsg(ex.getLocalizedMessage());
logs.setType("ERROR");
//来源IP
logs.setIp(IpUtil.getClientIpAddress(req));
@ -58,6 +59,7 @@ public class ApiGlobalExceptionHandler {
logs.setHttpMethod(req.getMethod());
//请求的参数
logs.setParams(StringUtil.getParamsAsJson(req));
Global.logQueue.add(logs);
log.error("error::", ex);
} catch (Exception e) {
log.error("error::", e);

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