学习进度条
今日所花时间:一小时
今日代码量:100行
博客量:2篇
了解到的知识点: 深入学习androidStudio 完成课堂测试
在之前写的政策查询的基础上继续完成web端的开发
使用springboot实现
实体类代码
Policy
package com.example.search.pojo;
import jakarta.persistence.*;
import java.util.Date;
@Entity
@Table(name = "policy")
public class policy {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "type")
private String type;
@Column(name = "category")
private String category;
@Column(name = "range")
private String range;
@Column(name = "document")
private String document;
@Column(name = "form")
private String form;
@Column(name = "organ")
private String organ;
@Column(name = "viadata")
private Date viadata;
@Column(name = "pubdata")
private Date pubdata;
@Column(name = "perdata")
private Date perdata;
@Column(name = "field")
private String field;
@Column(name = "theme")
private String theme;
@Column(name = "keyword")
private String keyword;
@Column(name = "superior")
private String superior;
@Column(name = "precursor")
private String precursor;
@Column(name = "succeed")
private String succeed;
@Column(name = "state")
private String state;
@Column(name = "text", columnDefinition = "longtext")
private String text;
@Column(name = "pdf")
private String pdf;
@Column(name = "redundancy")
private String redundancy;
@Column(name = "rank")
private String rank;
@Column(name = "policykey", columnDefinition = "text")
private String policykey;
@Column(name = "newrank")
private String newrank;
@Column(name = "year")
private String year;
@Column(name = "newkey")
private String newkey;
@Column(name = "secondtheme")
private String secondtheme;
@Column(name = "allsum")
private Integer allsum;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getRange() {
return range;
}
public void setRange(String range) {
this.range = range;
}
public String getDocument() {
return document;
}
public void setDocument(String document) {
this.document = document;
}
public String getForm() {
return form;
}
public void setForm(String form) {
this.form = form;
}
public String getOrgan() {
return organ;
}
public void setOrgan(String organ) {
this.organ = organ;
}
public Date getViadata() {
return viadata;
}
public void setViadata(Date viadata) {
this.viadata = viadata;
}
public Date getPubdata() {
return pubdata;
}
public void setPubdata(Date pubdata) {
this.pubdata = pubdata;
}
public Date getPerdata() {
return perdata;
}
public void setPerdata(Date perdata) {
this.perdata = perdata;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public String getTheme() {
return theme;
}
public void setTheme(String theme) {
this.theme = theme;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public String getSuperior() {
return superior;
}
public void setSuperior(String superior) {
this.superior = superior;
}
public String getPrecursor() {
return precursor;
}
public void setPrecursor(String precursor) {
this.precursor = precursor;
}
public String getSucceed() {
return succeed;
}
public void setSucceed(String succeed) {
this.succeed = succeed;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getPdf() {
return pdf;
}
public void setPdf(String pdf) {
this.pdf = pdf;
}
public String getRedundancy() {
return redundancy;
}
public void setRedundancy(String redundancy) {
this.redundancy = redundancy;
}
public String getRank() {
return rank;
}
public void setRank(String rank) {
this.rank = rank;
}
public String getPolicykey() {
return policykey;
}
public void setPolicykey(String policykey) {
this.policykey = policykey;
}
public String getNewrank() {
return newrank;
}
public void setNewrank(String newrank) {
this.newrank = newrank;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public String getNewkey() {
return newkey;
}
public void setNewkey(String newkey) {
this.newkey = newkey;
}
public String getSecondtheme() {
return secondtheme;
}
public void setSecondtheme(String secondtheme) {
this.secondtheme = secondtheme;
}
public Integer getAllsum() {
return allsum;
}
public void setAllsum(Integer allsum) {
this.allsum = allsum;
}
}
PolicyType
package com.example.search.pojo;
import java.util.List;
public class PolicyType {
private String typeId;
private String typeName;
private String parentId;
private List<PolicyType> children;
// Getters and Setters
public String getTypeId() {
return typeId;
}
public void setTypeId(String typeId) {
this.typeId = typeId;
}
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public List<PolicyType> getChildren() {
return children;
}
public void setChildren(List<PolicyType> children) {
this.children = children;
}
}
controller层代码(SelectController)
package com.example.search.controller;
import com.example.search.mapper.SelectMapper;
import com.example.search.pojo.PolicyType;
import com.example.search.pojo.policy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class SelectController {
@Autowired
private SelectMapper selectMapper;
@GetMapping("/searchPolicies")
@Transactional(readOnly = true)
public ResponseEntity<List<policy>> searchPolicies(
@RequestParam String keyword,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String organ,
@RequestParam(required = false) String organNot) {
try {
List<policy> policies = selectMapper.findByNameContainingOrKeywordContaining(
keyword,
startDate,
organ,
organNot);
return ResponseEntity.ok(policies);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/policy/{id}")
@Transactional(readOnly = true)
public ResponseEntity<policy> showPolicyFullText(@PathVariable Long id) {
try {
policy policy = selectMapper.findById(id);
if (policy != null) {
return ResponseEntity.ok(policy);
}
return ResponseEntity.notFound().build();
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/policyTypes")
@Transactional(readOnly = true)
public ResponseEntity<List<PolicyType>> getAllPolicyTypes() {
try {
List<PolicyType> policyTypes = selectMapper.findAllPolicyTypes();
return ResponseEntity.ok(policyTypes);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/policiesByType")
@Transactional(readOnly = true)
public ResponseEntity<List<policy>> getPoliciesByType(
@RequestParam(required = false) String typeNames, // 参数名改为typeNames
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String organ,
@RequestParam(required = false) String organNot) {
try {
int offset = (pageNum - 1) * pageSize;
if (typeNames == null || typeNames.isEmpty()) {
List<policy> policies = selectMapper.findAllPolicies(
offset,
pageSize,
startDate,
organ,
organNot
);
return ResponseEntity.ok(policies);
} else {
// 处理分类名称列表
List<String> typeNameList = Arrays.stream(typeNames.split(","))
.map(String::trim)
.filter(name -> !name.isEmpty())
.collect(Collectors.toList());
List<policy> policies = selectMapper.findByTypeNames(
typeNameList,
offset,
pageSize,
startDate,
organ,
organNot
);
return ResponseEntity.ok(policies);
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/policyCountByType")
@Transactional(readOnly = true)
public ResponseEntity<Integer> getPolicyCountByType(
@RequestParam(required = false) String typeNames, // 参数名改为typeNames
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String organ,
@RequestParam(required = false) String organNot) {
try {
if (typeNames == null || typeNames.isEmpty()) {
int count = selectMapper.countAllPolicies(
startDate,
organ,
organNot
);
return ResponseEntity.ok(count);
} else {
// 处理分类名称列表
List<String> typeNameList = Arrays.stream(typeNames.split(","))
.map(String::trim)
.filter(name -> !name.isEmpty())
.collect(Collectors.toList());
int count = selectMapper.countByTypeNames(
typeNameList,
startDate,
organ,
organNot
);
return ResponseEntity.ok(count);
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
}
mapper层(selectMapper)
package com.example.search.mapper;
import com.example.search.pojo.PolicyType;
import com.example.search.pojo.policy;
import org.apache.ibatis.annotations.*;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Mapper
public interface SelectMapper {
@SelectProvider(type = PolicySqlProvider.class, method = "findByNameContainingOrKeywordContaining")
@Transactional(readOnly = true)
List<policy> findByNameContainingOrKeywordContaining(
@Param("keyword") String keyword,
@Param("startDate") String startDate,
@Param("organ") String organ,
@Param("organNot") String organNot);
@Select("SELECT * FROM policy WHERE id = #{id}")
@Transactional(readOnly = true)
policy findById(Long id);
@Select("SELECT type_id as typeId, type_name as typeName, parent_id as parentId FROM policy_type ORDER BY type_id")
@Transactional(readOnly = true)
List<PolicyType> findAllPolicyTypes();
@SelectProvider(type = PolicySqlProvider.class, method = "findByTypeNames")
@Transactional(readOnly = true)
List<policy> findByTypeNames(
@Param("typeNames") List<String> typeNames,
@Param("offset") int offset,
@Param("pageSize") int pageSize,
@Param("startDate") String startDate,
@Param("organ") String organ,
@Param("organNot") String organNot);
@SelectProvider(type = PolicySqlProvider.class, method = "countByTypeNames")
@Transactional(readOnly = true)
int countByTypeNames(
@Param("typeNames") List<String> typeNames,
@Param("startDate") String startDate,
@Param("organ") String organ,
@Param("organNot") String organNot);
@SelectProvider(type = PolicySqlProvider.class, method = "findAllPolicies")
@Transactional(readOnly = true)
List<policy> findAllPolicies(
@Param("offset") int offset,
@Param("pageSize") int pageSize,
@Param("startDate") String startDate,
@Param("organ") String organ,
@Param("organNot") String organNot);
@SelectProvider(type = PolicySqlProvider.class, method = "countAllPolicies")
@Transactional(readOnly = true)
int countAllPolicies(
@Param("startDate") String startDate,
@Param("organ") String organ,
@Param("organNot") String organNot);
}
使用映射文件(进行动态查询 PolicyMapper.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.search.mapper.SelectMapper">
<!-- 根据多个类型ID查询政策信息 -->
<select id="findPoliciesByTypeIds" resultType="com.example.search.pojo.policy">
SELECT name, pubdata FROM policy WHERE type IN
<foreach item="typeId" collection="typeIds" open="(" separator="," close=")">
#{typeId}
</foreach>
</select>
<!-- 根据多个类型ID、分页、时间和机构筛选查询政策信息 -->
<select id="findByTypeIds" resultType="com.example.search.pojo.policy">
SELECT * FROM policy
<where>
<if test="typeIds != null and typeIds.size() > 0">
type IN
<foreach item="typeId" collection="typeIds" open="(" separator="," close=")">
#{typeId}
</foreach>
</if>
<if test="startDate != null">
AND pubdata >= #{startDate}
</if>
<if test="organ != null">
AND organ LIKE CONCAT('%', #{organ}, '%')
</if>
<if test="organNot != null">
AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
</if>
</where>
LIMIT #{offset}, #{pageSize}
</select>
<!-- 根据多个类型ID、时间和机构筛选统计政策数量 -->
<select id="countByTypeIds" resultType="int">
SELECT COUNT(*) FROM policy
<where>
<if test="typeIds != null and typeIds.size() > 0">
type IN
<foreach item="typeId" collection="typeIds" open="(" separator="," close=")">
#{typeId}
</foreach>
</if>
<if test="startDate != null">
AND pubdata >= #{startDate}
</if>
<if test="organ != null">
AND organ LIKE CONCAT('%', #{organ}, '%')
</if>
<if test="organNot != null">
AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
</if>
</where>
</select>
<select id="findByTypeNames" resultType="com.example.search.pojo.policy">
SELECT * FROM policy
<where>
<if test="typeNames != null and typeNames.size() > 0">
type IN
<foreach item="typeName" collection="typeNames" open="(" separator="," close=")">
#{typeName}
</foreach>
</if>
<if test="startDate != null">
AND pubdata >= #{startDate}
</if>
<if test="organ != null">
AND organ LIKE CONCAT('%', #{organ}, '%')
</if>
<if test="organNot != null">
AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
</if>
</where>
ORDER BY pubdata DESC
LIMIT #{offset}, #{pageSize}
</select>
<!-- 根据分类名称统计政策数量 -->
<select id="countByTypeNames" resultType="int">
SELECT COUNT(*) FROM policy
<where>
<if test="typeNames != null and typeNames.size() > 0">
type IN
<foreach item="typeName" collection="typeNames" open="(" separator="," close=")">
#{typeName}
</foreach>
</if>
<if test="startDate != null">
AND pubdata >= #{startDate}
</if>
<if test="organ != null">
AND organ LIKE CONCAT('%', #{organ}, '%')
</if>
<if test="organNot != null">
AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
</if>
</where>
</select>
</mapper>
PolicySqlProvider.java(动态 SQL 提供类)
package com.example.search.mapper;
import org.apache.ibatis.jdbc.SQL;
import java.util.List;
public class PolicySqlProvider {
public String findByTypeNames(
List<String> typeNames,
int offset,
int pageSize,
String startDate,
String organ,
String organNot) {
return new SQL() {{
SELECT("*");
FROM("policy");
if (typeNames != null && !typeNames.isEmpty()) {
WHERE(buildTypeNameCondition(typeNames));
}
if (startDate != null && !startDate.trim().isEmpty()) {
WHERE("pubdata >= #{startDate}");
}
if (organ != null && !organ.trim().isEmpty()) {
WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
}
if (organNot != null && !organNot.trim().isEmpty()) {
WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
}
ORDER_BY("pubdata DESC");
}}.toString() + " LIMIT #{offset}, #{pageSize}";
}
public String countByTypeNames(
List<String> typeNames,
String startDate,
String organ,
String organNot) {
return new SQL() {{
SELECT("COUNT(*)");
FROM("policy");
if (typeNames != null && !typeNames.isEmpty()) {
WHERE(buildTypeNameCondition(typeNames));
}
if (startDate != null && !startDate.trim().isEmpty()) {
WHERE("pubdata >= #{startDate}");
}
if (organ != null && !organ.trim().isEmpty()) {
WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
}
if (organNot != null && !organNot.trim().isEmpty()) {
WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
}
}}.toString();
}
public String findAllPolicies(
int offset,
int pageSize,
String startDate,
String organ,
String organNot) {
return new SQL() {{
SELECT("*");
FROM("policy");
if (startDate != null && !startDate.trim().isEmpty()) {
WHERE("pubdata >= #{startDate}");
}
if (organ != null && !organ.trim().isEmpty()) {
WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
}
if (organNot != null && !organNot.trim().isEmpty()) {
WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
}
ORDER_BY("pubdata DESC");
}}.toString() + " LIMIT #{offset}, #{pageSize}";
}
public String countAllPolicies(
String startDate,
String organ,
String organNot) {
return new SQL() {{
SELECT("COUNT(*)");
FROM("policy");
if (startDate != null && !startDate.trim().isEmpty()) {
WHERE("pubdata >= #{startDate}");
}
if (organ != null && !organ.trim().isEmpty()) {
WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
}
if (organNot != null && !organNot.trim().isEmpty()) {
WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
}
}}.toString();
}
public String findByNameContainingOrKeywordContaining(
String keyword,
String startDate,
String organ,
String organNot) {
return new SQL() {{
SELECT("*");
FROM("policy");
WHERE("(name LIKE CONCAT('%', #{keyword}, '%') OR keyword LIKE CONCAT('%', #{keyword}, '%'))");
if (startDate != null && !startDate.trim().isEmpty()) {
WHERE("pubdata >= #{startDate}");
}
if (organ != null && !organ.trim().isEmpty()) {
WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
}
if (organNot != null && !organNot.trim().isEmpty()) {
WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
}
ORDER_BY("pubdata DESC");
}}.toString();
}
/**
* 构建LIKE查询条件,用于匹配分类ID及其子分类
* 例如:type LIKE '0100%' OR type LIKE '0200%'
*/
private String buildLikeClause(List<String> typeIds) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < typeIds.size(); i++) {
if (i > 0) {
sb.append(" OR ");
}
sb.append("type LIKE '").append(typeIds.get(i)).append("%'");
}
return sb.toString();
}
/**
* 原始方法,用于精确匹配分类ID
* 保留但不使用,作为参考
*/
private String buildInClause(List<String> typeIds) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < typeIds.size(); i++) {
if (i > 0) {
sb.append(",");
}
sb.append("'").append(typeIds.get(i)).append("'");
}
return sb.toString();
}
// 其他方法保持不变...
/**
* 构建分类名称查询条件
*/
private String buildTypeNameCondition(List<String> typeNames) {
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < typeNames.size(); i++) {
if (i > 0) {
sb.append(" OR ");
}
sb.append("type = #{typeNames[").append(i).append("]}");
}
sb.append(")");
return sb.toString();
}
}
前端页面代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>科技政策一点通</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
body {
font-family: "Arial", sans-serif;
background-color: #f4f4f9;
color: #333;
margin: 0;
padding: 0;
}
.header {
background-color: #4a90e2;
color: white;
padding: 20px 0;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 24px;
font-weight: bold;
margin: 0;
text-align: center;
}
.nav-tabs {
background-color: #ffffff;
padding: 10px 20px;
border-bottom: 1px solid #ddd;
}
.nav-tabs .nav-link {
color: #555;
border: none;
padding: 10px 15px;
font-weight: bold;
transition: color 0.3s ease;
}
.nav-tabs .nav-link.active {
color: #4a90e2;
border-bottom: 2px solid #4a90e2;
}
.sidebar {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
height: calc(100vh - 160px);
overflow-y: auto;
}
.main-content {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: calc(100vh - 160px);
}
.tree-item {
padding: 10px;
cursor: pointer;
display: flex;
align-items: center;
border-radius: 4px;
transition: background-color 0.3s ease;
}
.tree-item:hover {
background-color: #f0f7ff;
}
.tree-checkbox {
margin-right: 10px;
}
.tree-toggle {
display: inline-block;
width: 20px;
text-align: center;
cursor: pointer;
}
.tree-label {
flex: 1;
font-size: 14px;
color: #555;
}
.tree-children {
padding-left: 20px;
display: none;
}
.tree-item.expanded > .tree-children {
display: block;
}
.policy-item {
padding: 15px;
border-radius: 8px;
background-color: #f9f9f9;
margin-bottom: 15px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.policy-item:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.policy-title {
color: #4a90e2;
font-weight: bold;
margin-bottom: 5px;
font-size: 16px;
}
.policy-date {
color: #888;
font-size: 14px;
}
.search-box {
margin-bottom: 20px;
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.filter-box {
margin-bottom: 20px;
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.filter-group {
margin-bottom: 15px;
}
.filter-group-title {
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.pagination {
justify-content: center;
margin-top: 20px;
}
.no-results {
text-align: center;
padding: 50px;
color: #666;
font-size: 16px;
}
.loading-spinner {
display: none;
text-align: center;
padding: 20px;
}
.select-all-container {
font-weight: bold;
margin-bottom: 15px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.btn-primary {
background-color: #4a90e2;
border-color: #4a90e2;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.btn-primary:hover {
background-color: #357ab8;
border-color: #357ab8;
}
</style>
</head>
<body>
<!-- 页面结构保持不变 -->
<div class="header">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-md-8">
<h1><i class="bi bi-journal-bookmark-fill"></i> 科技政策一点通</h1>
</div>
<div class="col-md-4 text-end">
<div class="input-group" style="width: 300px;">
<input type="text" id="search-input" class="form-control form-control-sm" placeholder="请输入关键字">
<button class="btn btn-primary btn-sm" type="button" id="search-button"><i class="bi bi-search"></i> 搜索</button>
</div>
</div>
</div>
</div>
</div>
<div class="nav-tabs">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" href="#">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">政策动态</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">科技政策</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">政策解读</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">政策问答</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">政策培训</a>
</li>
</ul>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="sidebar">
<h5><i class="bi bi-list-ul"></i> 政策分类</h5>
<hr>
<div id="policy-tree"></div>
</div>
</div>
<div class="col-md-9">
<div class="main-content">
<div class="filter-box">
<div class="row">
<div class="col-md-6">
<div class="filter-group">
<div class="filter-group-title">时间:</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="timeFilter" id="timeAll" value="all" checked>
<label class="form-check-label" for="timeAll">全部时间</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="timeFilter" id="time1" value="1">
<label class="form-check-label" for="time1">近一月</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="timeFilter" id="time6" value="6">
<label class="form-check-label" for="time6">近半年</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="timeFilter" id="time12" value="12">
<label class="form-check-label" for="time12">近一年</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="filter-group">
<div class="filter-group-title">制主主体:</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sourceFilter" id="sourceAll" value="all" checked>
<label class="form-check-label" for="sourceAll">全部</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sourceFilter" id="sourceNational" value="national">
<label class="form-check-label" for="sourceNational">国家</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sourceFilter" id="sourceHebei" value="hebei">
<label class="form-check-label" for="sourceHebei">河北</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sourceFilter" id="sourceOther" value="other">
<label class="form-check-label" for="sourceOther">外省</label>
</div>
</div>
</div>
</div>
</div>
<div id="current-category" class="alert alert-info py-2" style="display: none;">
<strong>当前分类:</strong> <span id="category-name"></span>
</div>
<div id="loading-spinner" class="loading-spinner">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p>正在加载数据,请稍候...</p>
</div>
<div id="policy-list"></div>
<nav aria-label="Page navigation">
<ul class="pagination" id="pagination">
<!-- 分页按钮将通过JavaScript动态生成 -->
</ul>
</nav>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 加载政策分类树
loadPolicyTree();
// 默认加载所有政策
loadAllPolicies(1);
// 搜索按钮点击事件
document.getElementById('search-button').addEventListener('click', function() {
const keyword = document.getElementById('search-input').value.trim();
if (keyword) {
searchPolicies(keyword);
}
});
// 回车键搜索
document.getElementById('search-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const keyword = document.getElementById('search-input').value.trim();
if (keyword) {
searchPolicies(keyword);
}
}
});
// 时间筛选变化事件
document.querySelectorAll('input[name="timeFilter"]').forEach(radio => {
radio.addEventListener('change', function() {
updateSelectedCategories();
});
});
// 来源筛选变化事件
document.querySelectorAll('input[name="sourceFilter"]').forEach(radio => {
radio.addEventListener('change', function() {
updateSelectedCategories();
});
});
});
// 加载所有政策(不带分类筛选)
function loadAllPolicies(pageNum) {
document.getElementById('loading-spinner').style.display = 'block';
document.getElementById('policy-list').innerHTML = '';
document.getElementById('pagination').innerHTML = '';
document.getElementById('current-category').style.display = 'none';
// 获取时间筛选条件
const timeFilter = document.querySelector('input[name="timeFilter"]:checked').value;
// 获取来源筛选条件
const sourceFilter = document.querySelector('input[name="sourceFilter"]:checked').value;
// 构建查询参数
let url = `http://localhost:8080/api/policiesByType?pageNum=${pageNum}&pageSize=10`;
// 添加时间筛选条件
if (timeFilter !== 'all') {
const months = parseInt(timeFilter);
const date = new Date();
date.setMonth(date.getMonth() - months);
const dateStr = date.toISOString().split('T')[0];
url += `&startDate=${dateStr}`;
}
// 添加来源筛选条件
if (sourceFilter !== 'all') {
if (sourceFilter === 'hebei') {
url += `&organ=河北省`;
} else if (sourceFilter === 'national') {
url += `&organ=国务院`;
} else if (sourceFilter === 'other') {
url += `&organNot=河北省&organNot=国务院`;
}
}
// 构建数量查询URL
let countUrl = `http://localhost:8080/api/policyCountByType?`;
// 添加时间筛选条件到数量查询
if (timeFilter !== 'all') {
const months = parseInt(timeFilter);
const date = new Date();
date.setMonth(date.getMonth() - months);
const dateStr = date.toISOString().split('T')[0];
countUrl += `&startDate=${dateStr}`;
}
// 添加来源筛选条件到数量查询
if (sourceFilter !== 'all') {
if (sourceFilter === 'hebei') {
countUrl += `&organ=河北省`;
} else if (sourceFilter === 'national') {
countUrl += `&organ=国务院`;
} else if (sourceFilter === 'other') {
countUrl += `&organNot=河北省&organNot=国务院`;
}
}
// 获取政策数量
fetch(countUrl)
.then(response => response.json())
.then(count => {
const totalPages = Math.ceil(count / 10);
// 获取当前页的政策
fetch(url)
.then(response => response.json())
.then(policies => {
document.getElementById('loading-spinner').style.display = 'none';
renderPolicies(policies);
renderPagination('', pageNum, totalPages, true);
})
.catch(error => {
document.getElementById('loading-spinner').style.display = 'none';
console.error('加载政策失败:', error);
document.getElementById('policy-list').innerHTML =
'<div class="no-results"><i class="bi bi-exclamation-circle"></i> 加载政策失败,请稍后再试</div>';
});
})
.catch(error => {
document.getElementById('loading-spinner').style.display = 'none';
console.error('获取政策数量失败:', error);
});
}
// 加载政策分类树
function loadPolicyTree() {
fetch('http://localhost:8080/api/policyTypes')
.then(response => response.json())
.then(data => {
const treeContainer = document.getElementById('policy-tree');
// 添加全选/全不选控制
const selectAllDiv = document.createElement('div');
selectAllDiv.className = 'select-all-container';
const selectAllCheckbox = document.createElement('input');
selectAllCheckbox.type = 'checkbox';
selectAllCheckbox.className = 'form-check-input tree-checkbox';
selectAllCheckbox.id = 'tree-checkbox-all';
const selectAllLabel = document.createElement('label');
selectAllLabel.className = 'tree-label';
selectAllLabel.htmlFor = 'tree-checkbox-all';
selectAllLabel.textContent = '全部分类';
selectAllCheckbox.addEventListener('change', function() {
const allCheckboxes = treeContainer.querySelectorAll('.tree-checkbox');
allCheckboxes.forEach(checkbox => {
if (checkbox.id !== 'tree-checkbox-all') {
checkbox.checked = this.checked;
}
});
updateSelectedCategories();
});
selectAllDiv.appendChild(selectAllCheckbox);
selectAllDiv.appendChild(selectAllLabel);
treeContainer.appendChild(selectAllDiv);
// 渲染树形结构
const treeData = buildTree(data);
renderTree(treeData, treeContainer);
})
.catch(error => {
console.error('加载分类树失败:', error);
});
}
// 构建树形结构
function buildTree(items) {
const rootItems = [];
const itemMap = {};
// 首先将所有项存入映射表
items.forEach(item => {
itemMap[item.typeId] = {
...item,
children: []
};
});
// 构建树形结构
items.forEach(item => {
const mappedItem = itemMap[item.typeId];
if (item.parentId) {
if (itemMap[item.parentId]) {
itemMap[item.parentId].children.push(mappedItem);
}
} else {
rootItems.push(mappedItem);
}
});
return rootItems;
}
// 渲染树形结构(带复选框版本)
function renderTree(items, container) {
items.forEach(item => {
const hasChildren = item.children && item.children.length > 0;
const itemElement = document.createElement('div');
itemElement.className = 'tree-item';
itemElement.dataset.typeId = item.typeId;
// 创建复选框
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'form-check-input tree-checkbox';
checkbox.id = `tree-checkbox-${item.typeId}`;
checkbox.addEventListener('change', function() {
// 当父分类被选中/取消选中时,同步子分类的状态
if (hasChildren) {
const childrenContainer = itemElement.querySelector('.tree-children');
const childCheckboxes = childrenContainer.querySelectorAll('.tree-checkbox');
childCheckboxes.forEach(childCheckbox => {
childCheckbox.checked = this.checked;
});
}
updateSelectedCategories();
});
// 创建展开/折叠图标
const toggle = document.createElement('span');
toggle.className = 'tree-toggle';
if (hasChildren) {
toggle.innerHTML = '<i class="bi bi-plus-square"></i>';
toggle.addEventListener('click', function(e) {
e.stopPropagation();
itemElement.classList.toggle('expanded');
const icon = this.querySelector('i');
if (itemElement.classList.contains('expanded')) {
icon.classList.remove('bi-plus-square');
icon.classList.add('bi-dash-square');
} else {
icon.classList.remove('bi-dash-square');
icon.classList.add('bi-plus-square');
}
});
} else {
toggle.innerHTML = ' ';
}
// 创建标签
const label = document.createElement('label');
label.className = 'tree-label';
label.htmlFor = `tree-checkbox-${item.typeId}`;
label.textContent = item.typeName;
label.addEventListener('click', function(e) {
e.stopPropagation();
checkbox.checked = !checkbox.checked;
// 触发change事件
const event = new Event('change');
checkbox.dispatchEvent(event);
});
// 组装元素
itemElement.appendChild(checkbox);
itemElement.appendChild(toggle);
itemElement.appendChild(label);
// 如果有子节点,递归渲染
if (hasChildren) {
const childrenContainer = document.createElement('div');
childrenContainer.className = 'tree-children';
renderTree(item.children, childrenContainer);
itemElement.appendChild(childrenContainer);
}
container.appendChild(itemElement);
});
}
// 更新选中的分类
function updateSelectedCategories() {
const checkedBoxes = document.querySelectorAll('.tree-checkbox:checked');
// 排除全选复选框
const filteredCheckboxes = Array.from(checkedBoxes).filter(
checkbox => checkbox.id !== 'tree-checkbox-all'
);
if (filteredCheckboxes.length === 0) {
// 如果没有选中任何分类,显示全部政策
loadAllPolicies(1);
return;
}
// 获取所有选中的分类名称(而不是ID)
const allSelectedNames = [];
filteredCheckboxes.forEach(checkbox => {
const itemElement = checkbox.closest('.tree-item');
const typeName = itemElement.querySelector('.tree-label').textContent;
allSelectedNames.push(typeName);
// 检查是否有子分类被选中
const childrenContainer = itemElement.querySelector('.tree-children');
if (childrenContainer) {
const childCheckboxes = childrenContainer.querySelectorAll('.tree-checkbox:checked');
childCheckboxes.forEach(childCheckbox => {
const childItemElement = childCheckbox.closest('.tree-item');
const childTypeName = childItemElement.querySelector('.tree-label').textContent;
allSelectedNames.push(childTypeName);
});
}
});
// 去重
const uniqueNames = [...new Set(allSelectedNames)];
const typeNames = uniqueNames.join(',');
// 获取分类名称显示
const displayNames = filteredCheckboxes.map(checkbox => {
const itemElement = checkbox.closest('.tree-item');
return itemElement.querySelector('.tree-label').textContent;
}).join('、');
loadPoliciesByType(typeNames, displayNames, 1);
}
// 按分类加载政策(修改为使用typeNames)
function loadPoliciesByType(typeNames, displayName, pageNum) {
document.getElementById('loading-spinner').style.display = 'block';
document.getElementById('policy-list').innerHTML = '';
document.getElementById('pagination').innerHTML = '';
if (displayName && displayName !== '全部政策') {
document.getElementById('current-category').style.display = 'block';
document.getElementById('category-name').textContent = displayName;
} else {
document.getElementById('current-category').style.display = 'none';
}
// 获取时间筛选条件
const timeFilter = document.querySelector('input[name="timeFilter"]:checked').value;
// 获取来源筛选条件
const sourceFilter = document.querySelector('input[name="sourceFilter"]:checked').value;
// 构建查询参数(使用typeNames而不是typeIds)
let url = `http://localhost:8080/api/policiesByType?typeNames=${encodeURIComponent(typeNames)}&pageNum=${pageNum}&pageSize=10`;
// 添加时间筛选条件
if (timeFilter !== 'all') {
const months = parseInt(timeFilter);
const date = new Date();
date.setMonth(date.getMonth() - months);
const dateStr = date.toISOString().split('T')[0];
url += `&startDate=${dateStr}`;
}
// 添加来源筛选条件
if (sourceFilter !== 'all') {
if (sourceFilter === 'hebei') {
url += `&organ=河北省`;
} else if (sourceFilter === 'national') {
url += `&organ=国务院`;
} else if (sourceFilter === 'other') {
url += `&organNot=河北省&organNot=国务院`;
}
}
// 构建数量查询URL(使用typeNames而不是typeIds)
let countUrl = `http://localhost:8080/api/policyCountByType?typeNames=${encodeURIComponent(typeNames)}`;
// 添加时间筛选条件到数量查询
if (timeFilter !== 'all') {
const months = parseInt(timeFilter);
const date = new Date();
date.setMonth(date.getMonth() - months);
const dateStr = date.toISOString().split('T')[0];
countUrl += `&startDate=${dateStr}`;
}
// 添加来源筛选条件到数量查询
if (sourceFilter !== 'all') {
if (sourceFilter === 'hebei') {
countUrl += `&organ=河北省`;
} else if (sourceFilter === 'national') {
countUrl += `&organ=国务院`;
} else if (sourceFilter === 'other') {
countUrl += `&organNot=河北省&organNot=国务院`;
}
}
// 获取政策数量
fetch(countUrl)
.then(response => response.json())
.then(count => {
const totalPages = Math.ceil(count / 10);
// 获取当前页的政策
fetch(url)
.then(response => response.json())
.then(policies => {
document.getElementById('loading-spinner').style.display = 'none';
renderPolicies(policies);
renderPagination(typeNames, pageNum, totalPages, false);
})
.catch(error => {
document.getElementById('loading-spinner').style.display = 'none';
console.error('加载政策失败:', error);
document.getElementById('policy-list').innerHTML =
'<div class="no-results"><i class="bi bi-exclamation-circle"></i> 加载政策失败,请稍后再试</div>';
});
})
.catch(error => {
document.getElementById('loading-spinner').style.display = 'none';
console.error('获取政策数量失败:', error);
});
}
// 渲染政策列表
function renderPolicies(policies) {
const policyList = document.getElementById('policy-list');
if (policies.length === 0) {
policyList.innerHTML = '<div class="no-results"><i class="bi bi-info-circle"></i> 没有找到符合条件的政策</div>';
return;
}
policies.forEach(policy => {
const policyItem = document.createElement('div');
policyItem.className = 'policy-item';
const title = document.createElement('div');
title.className = 'policy-title';
title.textContent = policy.name;
const date = document.createElement('div');
date.className = 'policy-date';
date.textContent = formatDate(policy.pubdata);
policyItem.appendChild(title);
policyItem.appendChild(date);
policyList.appendChild(policyItem);
});
}
// 渲染分页控件
function renderPagination(typeNames, currentPage, totalPages, isAllPolicies) {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
if (totalPages <= 1) return;
// 上一页按钮
const prevLi = document.createElement('li');
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">«</span> 上一页</a>`;
prevLi.addEventListener('click', function(e) {
e.preventDefault();
if (currentPage > 1) {
if (isAllPolicies) {
loadAllPolicies(currentPage - 1);
} else {
loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, currentPage - 1);
}
}
});
pagination.appendChild(prevLi);
// 页码按钮
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
// 第一页
if (startPage > 1) {
const firstLi = document.createElement('li');
firstLi.className = 'page-item';
firstLi.innerHTML = `<a class="page-link" href="#">1</a>`;
firstLi.addEventListener('click', function(e) {
e.preventDefault();
if (isAllPolicies) {
loadAllPolicies(1);
} else {
loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, 1);
}
});
pagination.appendChild(firstLi);
if (startPage > 2) {
const ellipsisLi = document.createElement('li');
ellipsisLi.className = 'page-item disabled';
ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
pagination.appendChild(ellipsisLi);
}
}
for (let i = startPage; i <= endPage; i++) {
const pageLi = document.createElement('li');
pageLi.className = `page-item ${i === currentPage ? 'active' : ''}`;
pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
pageLi.addEventListener('click', function(e) {
e.preventDefault();
if (isAllPolicies) {
loadAllPolicies(i);
} else {
loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, i);
}
});
pagination.appendChild(pageLi);
}
// 最后一页
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
const ellipsisLi = document.createElement('li');
ellipsisLi.className = 'page-item disabled';
ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
pagination.appendChild(ellipsisLi);
}
const lastLi = document.createElement('li');
lastLi.className = 'page-item';
lastLi.innerHTML = `<a class="page-link" href="#">${totalPages}</a>`;
lastLi.addEventListener('click', function(e) {
e.preventDefault();
if (isAllPolicies) {
loadAllPolicies(totalPages);
} else {
loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, totalPages);
}
});
pagination.appendChild(lastLi);
}
// 下一页按钮
const nextLi = document.createElement('li');
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next">下一页 <span aria-hidden="true">»</span></a>`;
nextLi.addEventListener('click', function(e) {
e.preventDefault();
if (currentPage < totalPages) {
if (isAllPolicies) {
loadAllPolicies(currentPage + 1);
} else {
loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, currentPage + 1);
}
}
});
pagination.appendChild(nextLi);
}
// 搜索政策
function searchPolicies(keyword) {
document.getElementById('loading-spinner').style.display = 'block';
document.getElementById('policy-list').innerHTML = '';
document.getElementById('pagination').innerHTML = '';
document.getElementById('current-category').style.display = 'none';
// 清除所有复选框的选中状态(排除全选复选框)
document.querySelectorAll('.tree-checkbox').forEach(checkbox => {
if (checkbox.id !== 'tree-checkbox-all') {
checkbox.checked = false;
}
});
// 获取时间筛选条件
const timeFilter = document.querySelector('input[name="timeFilter"]:checked').value;
// 构建查询URL
let url = `http://localhost:8080/api/searchPolicies?keyword=${encodeURIComponent(keyword)}`;
// 添加时间筛选条件
if (timeFilter !== 'all') {
const months = parseInt(timeFilter);
const date = new Date();
date.setMonth(date.getMonth() - months);
const dateStr = date.toISOString().split('T')[0];
url += `&startDate=${dateStr}`;
}
fetch(url)
.then(response => response.json())
.then(policies => {
document.getElementById('loading-spinner').style.display = 'none';
if (policies.length === 0) {
document.getElementById('policy-list').innerHTML =
'<div class="no-results"><i class="bi bi-search"></i> 没有找到与 "' + keyword + '" 相关的政策</div>';
} else {
renderPolicies(policies);
}
})
.catch(error => {
document.getElementById('loading-spinner').style.display = 'none';
console.error('搜索政策失败:', error);
document.getElementById('policy-list').innerHTML =
'<div class="no-results"><i class="bi bi-exclamation-circle"></i> 搜索失败,请稍后再试</div>';
});
}
// 格式化日期
function formatDate(dateStr) {
if (!dateStr) return '未知日期';
const date = new Date(dateStr);
return isNaN(date.getTime()) ? '未知日期' : date.toLocaleDateString('zh-CN');
}
</script>
</body>
</html>
按照上述代码能够实现测试要求,但仍然存在一些问题有待优化。

浙公网安备 33010602011771号