学习进度条

今日所花时间:一小时
今日代码量: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 = '&nbsp;&nbsp;';
                }
                
                // 创建标签
                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">&laquo;</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">&raquo;</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>

按照上述代码能够实现测试要求,但仍然存在一些问题有待优化。

posted @ 2025-03-28 21:35  haoyinuo  阅读(22)  评论(0)    收藏  举报