高级检索

PolicyList.vue

<template>
  <div class="policy-container">
    <el-container>
      <el-aside width="250px">
        <el-tree
          ref="treeRef"
          :data="typeTree"
          :props="defaultProps"
          @check="handleCheck"
          show-checkbox
          node-key="id"
          default-expand-all
        />
      </el-aside>
      
      <el-main>
        <div class="search-bar">
          <el-input
            v-model="searchKeyword"
            placeholder="请输入关键词搜索"
            class="search-input"
            @keyup.enter="handleSearch"
          >
            <template #append>
              <el-button @click="handleSearch">搜索</el-button>
            </template>
          </el-input>
          <el-button type="primary" @click="handleTypeSearch" style="margin-left: 10px">按分类筛选</el-button>
          <el-button type="primary" @click="showAdvancedSearch" style="margin-left: 10px">高级检索</el-button>
        </div>

        <!-- 添加高级检索对话框 -->
        <el-dialog
          v-model="advancedSearchVisible"
          title="高级检索"
          width="70%"
        >
          <advanced-search 
            :type-tree="typeTree" 
            @search-complete="handleAdvancedSearchComplete" 
          />
        </el-dialog>

        <el-table :data="policies" border style="width: 100%">
          <el-table-column prop="id" label="ID" width="80" />
          <el-table-column prop="name" label="政策名称" />
          <el-table-column prop="type" label="类型" width="120" />
          <el-table-column prop="organ" label="发布机构" width="200" />
          <el-table-column prop="pubdata" label="发布日期" width="120" />
          <el-table-column fixed="right" label="操作" width="120">
            <template #default="scope">
              <el-button link type="primary" @click="viewDetails(scope.row)">查看详情</el-button>
            </template>
          </el-table-column>
        </el-table>

        <div class="pagination">
          <el-pagination
            v-model:current-page="currentPage"
            v-model:page-size="pageSize"
            :page-sizes="[10, 20, 50, 100]"
            layout="total, sizes, prev, pager, next"
            :total="total"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </el-main>
    </el-container>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import axios from 'axios'
import AdvancedSearch from '../components/AdvancedSearch.vue'
import { ElMessage } from 'element-plus'

const router = useRouter()
const policies = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const searchKeyword = ref('')

const fetchPolicies = async () => {
  try {
    const response = await axios.get('http://localhost:8090/policy/search', {
      params: {
        keyword: searchKeyword.value,
        current: currentPage.value,
        size: pageSize.value
      }
    })
    policies.value = response.data.records
    total.value = response.data.total
  } catch (error) {
    console.error('获取政策列表失败:', error)
  }
}

const handleSearch = () => {
  currentPage.value = 1
  fetchPolicies()
}

const handleSizeChange = (val) => {
  pageSize.value = val
  fetchPolicies()
}

const handleCurrentChange = (val) => {
  currentPage.value = val
  fetchPolicies()
}

const viewDetails = (row) => {
  router.push(`/policy/${row.id}`)
}

const typeTree = ref([])
const defaultProps = {
  children: 'children',
  label: 'label'
}

const fetchTypeTree = async () => {
  try {
    const response = await axios.get('http://localhost:8090/policy/types')
    typeTree.value = response.data
  } catch (error) {
    console.error('获取分类树失败:', error)
  }
}

const handleNodeClick = async (data) => {
  try {
    currentPage.value = 1;
    console.log('点击的节点数据:', data);
    console.log('传递的分类名称:', data.label); // 使用label获取type值
    
    const response = await axios.get('http://localhost:8090/policy/search/byType', {
      params: {
        type: data.label, // 改用type字段进行查询
        current: currentPage.value,
        size: pageSize.value
      }
    })
    if (response.data) {
      console.log('接收到的响应数据:', response.data);
      policies.value = response.data.records || []
      total.value = response.data.total || 0
    }
  } catch (error) {
    console.error('按分类获取政策列表失败:', error)
    policies.value = []
    total.value = 0
  }
}

onMounted(() => {
  fetchTypeTree()
  fetchPolicies()
})

const treeRef = ref(null)
const selectedTypes = ref([])

// 替换原来的handleNodeClick为handleCheck
const handleCheck = (data, checked) => {
  console.log('选中的节点:', checked.checkedNodes)
  selectedTypes.value = checked.checkedNodes.map(node => node.label)
}

// 添加新的分类搜索方法
const handleTypeSearch = async () => {
  if (selectedTypes.value.length === 0) {
    ElMessage.warning('请至少选择一个分类')
    return
  }

  try {
    currentPage.value = 1
    console.log('选中的分类:', selectedTypes.value)
    
    const response = await axios.get('http://localhost:8090/policy/search/byTypes', {
      params: {
        types: selectedTypes.value.join(','),
        current: currentPage.value,
        size: pageSize.value
      }
    })
    if (response.data) {
      console.log('接收到的响应数据:', response.data)
      policies.value = response.data.records || []
      total.value = response.data.total || 0
    }
  } catch (error) {
    console.error('按分类获取政策列表失败:', error)
    policies.value = []
    total.value = 0
  }
}

// 添加高级检索相关变量和方法
const advancedSearchVisible = ref(false)

const showAdvancedSearch = () => {
  advancedSearchVisible.value = true
}

const handleAdvancedSearchComplete = (result) => {
  policies.value = result.records
  total.value = result.total
  advancedSearchVisible.value = false
}
</script>

<style scoped>
.policy-container {
  height: 100vh;
  background-color: #f0f2f5;
}

.el-aside {
  background: linear-gradient(135deg, #1e2f97 0%, #1b4db2 100%);
  padding: 20px;
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}

/* 树形控件样式 */
:deep(.el-tree) {
  background: transparent;
}

:deep(.el-tree-node__content) {
  color: #fff;
}

:deep(.el-tree-node:focus > .el-tree-node__content) {
  background-color: rgba(255, 255, 255, 0.1);
}

.el-main {
  padding: 25px;
  background-color: #fff;
  border-radius: 8px;
  margin: 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.search-bar {
  margin-bottom: 25px;
  display: flex;
  align-items: center;
  gap: 10px;
}

.search-input {
  width: 300px;
}

:deep(.el-input__wrapper) {
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}

:deep(.el-button--primary) {
  background: linear-gradient(135deg, #1890ff 0%, #1677ff 100%);
  border: none;
  transition: all 0.3s;
}

:deep(.el-button--primary:hover) {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}

/* 表格样式 */
:deep(.el-table) {
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}

:deep(.el-table th) {
  background-color: #f5f7fa;
  color: #1e2f97;
  font-weight: 600;
}

:deep(.el-table tr:hover) {
  background-color: #f0f7ff !important;
}

:deep(.el-table td) {
  padding: 12px 0;
}

/* 分页控件样式 */
.pagination {
  margin-top: 30px;
  display: flex;
  justify-content: center;
  padding: 20px 0;
}

:deep(.el-pagination.is-background .el-pager li:not(.is-disabled).is-active) {
  background: linear-gradient(135deg, #1890ff 0%, #1677ff 100%);
}

/* 高级检索对话框样式 */
:deep(.el-dialog) {
  border-radius: 12px;
  overflow: hidden;
}

:deep(.el-dialog__header) {
  background: linear-gradient(135deg, #1e2f97 0%, #1b4db2 100%);
  padding: 20px;
  margin: 0;
}

:deep(.el-dialog__title) {
  color: #fff;
  font-size: 18px;
  font-weight: 500;
}

:deep(.el-dialog__body) {
  padding: 30px;
}

/* 响应式布局 */
@media screen and (max-width: 768px) {
  .search-bar {
    flex-direction: column;
    align-items: stretch;
  }

  .search-input {
    width: 100%;
  }
}
</style>

AdvancedSearch.vue

<template>
  <div class="advanced-search">
    <el-form :model="searchForm" label-width="100px">
      <el-form-item label="政策标题">
        <el-input v-model="searchForm.title" placeholder="请输入政策标题"></el-input>
      </el-form-item>
      
      <el-form-item label="政策内容">
        <el-input v-model="searchForm.content" placeholder="请输入政策内容"></el-input>
      </el-form-item>
      
      <el-form-item label="发文机构">
        <el-input v-model="searchForm.organ" placeholder="请输入发文机构"></el-input>
      </el-form-item>
      
      <el-form-item label="政策分类">
        <el-cascader
          v-model="searchForm.type"
          :options="typeOptions"
          :props="cascaderProps"
          clearable
          placeholder="请选择政策分类"
        />
      </el-form-item>
      
      <el-form-item label="政策文号">
        <el-input v-model="searchForm.document" placeholder="请输入政策文号"></el-input>
      </el-form-item>
      
      <el-form-item>
        <el-button type="primary" @click="handleSearch">检索</el-button>
        <el-button @click="resetForm">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 搜索结果列表 -->
    <el-table v-loading="loading" :data="policyList" style="width: 100%">
      <el-table-column prop="name" label="政策标题"></el-table-column>
      <el-table-column prop="organ" label="发文机构"></el-table-column>
      <el-table-column prop="type" label="政策分类"></el-table-column>
      <el-table-column prop="document" label="政策文号"></el-table-column>
      <el-table-column prop="pubdata" label="发布日期">
        <template #default="scope">
          {{ formatDate(scope.row.pubdata) }}
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div class="pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :total="total"
        :page-sizes="[10, 20, 30, 50]"
        layout="total, sizes, prev, pager, next"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange">
      </el-pagination>
    </div>
  </div>
</template>

<script>
import { ref, reactive, onMounted } from 'vue'
import axios from 'axios'

export default {
  name: 'AdvancedSearch',
  emits: ['search-complete'],
  props: {
    typeTree: {
      type: Array,
      default: () => []
    }
  },
  setup(props, { emit }) {
    const searchForm = reactive({
      title: '',
      content: '',
      organ: '',
      type: '',
      document: ''
    })

    const loading = ref(false)
    const policyList = ref([])
    const currentPage = ref(1)
    const pageSize = ref(10)
    const total = ref(0)
    const typeOptions = ref([])

    const cascaderProps = {
      value: 'value',      // 修改为与后端数据结构匹配的字段
      label: 'label',
      children: 'children',
      checkStrictly: true, // 允许选择任意层级
      emitPath: true       // 返回完整路径
    }

    //const typeOptions = computed(() => props.typeTree)

    // 获取政策分类选项
    const fetchTypeOptions = async () => {
      try {
        const response = await axios.get('http://localhost:8090/policy/types')
        typeOptions.value = response.data.map(type => ({
          value: type.label || type.name,  // 使用分类名称作为值
          label: type.label || type.name,
          children: type.children ? type.children.map(child => ({
            value: child.label || child.name,  // 使用分类名称作为值
            label: child.label || child.name,
            children: child.children || []
          })) : []
        }))
      } catch (error) {
        console.error('获取政策分类失败:', error)
      }
    }

    // 修改 handleSearch 方法
    const handleSearch = async () => {
      loading.value = true
      try {
        const params = {
          ...searchForm,
          // 直接使用选中的分类名称
          type: Array.isArray(searchForm.type) ? searchForm.type[searchForm.type.length - 1] : searchForm.type,
          current: currentPage.value,
          size: pageSize.value
        }
        const response = await axios.get('http://localhost:8090/policy/advanced-search', { params })
        policyList.value = response.data.records
        total.value = response.data.total
        emit('search-complete', response.data)
      } catch (error) {
        console.error('搜索失败:', error)
      } finally {
        loading.value = false
      }
    }

    // 重置表单
    const resetForm = () => {
      Object.keys(searchForm).forEach(key => {
        searchForm[key] = ''
      })
      currentPage.value = 1
      handleSearch()
    }

    // 处理页码变化
    const handleCurrentChange = (val) => {
      currentPage.value = val
      handleSearch()
    }

    // 处理每页条数变化
    const handleSizeChange = (val) => {
      pageSize.value = val
      currentPage.value = 1
      handleSearch()
    }

    // 格式化日期
    const formatDate = (date) => {
      if (!date) return ''
      return new Date(date).toLocaleDateString()
    }

    onMounted(() => {
      fetchTypeOptions()
      handleSearch()
    })

    return {
      searchForm,
      loading,
      policyList,
      currentPage,
      pageSize,
      total,
      typeOptions,
      handleSearch,
      resetForm,
      handleCurrentChange,
      handleSizeChange,
      formatDate
    }
  }
}
</script>

<style scoped>
.advanced-search {
  max-width: 1200px;
  margin: 20px auto;
  padding: 30px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.el-form {
  margin-bottom: 30px;
}

.el-form-item {
  margin-bottom: 25px;
}

.el-input {
  width: 100%;
  max-width: 500px;
}

.el-cascader {
  width: 100%;
  max-width: 500px;
}

.el-button {
  padding: 12px 25px;
  font-size: 14px;
}

.el-button + .el-button {
  margin-left: 15px;
}

.el-table {
  margin-top: 20px;
  border-radius: 4px;
  overflow: hidden;
}

.el-table th {
  background-color: #f5f7fa;
  color: #606266;
  font-weight: 500;
  padding: 12px 0;
}

.el-table td {
  padding: 12px 0;
}

.pagination {
  margin-top: 30px;
  padding: 20px 0;
  text-align: right;
  background-color: #fff;
  border-radius: 4px;
}

/* 响应式布局 */
@media screen and (max-width: 768px) {
  .advanced-search {
    padding: 15px;
    margin: 10px;
  }

  .el-input,
  .el-cascader {
    max-width: 100%;
  }

  .el-button {
    width: 100%;
    margin: 5px 0;
  }

  .el-button + .el-button {
    margin-left: 0;
  }
}
</style>

PolicyController

package com.example.demo.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Policy;
import com.example.demo.service.PolicyService;
import com.example.demo.service.PolicyTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/policy")
public class PolicyController {
    @Autowired
    private PolicyService policyService;
    
    @Autowired
    private PolicyTypeService policyTypeService;

    @GetMapping("/search")
    public Page<Policy> search(
            @RequestParam(defaultValue = "") String keyword,
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "10") Integer size) {
        
        Page<Policy> page = new Page<>(current, size);
        LambdaQueryWrapper<Policy> wrapper = new LambdaQueryWrapper<>();
        
        // 模糊匹配名称、关键词或政策文本
        wrapper.like(Policy::getName, keyword)
                .or()
                .like(Policy::getKeyword, keyword)
                .or()
                .like(Policy::getText, keyword);
        
        return policyService.page(page, wrapper);
    }

    @GetMapping("/{id}")
    public ResponseEntity<?> getById(@PathVariable Long id) {
        Policy policy = policyService.getById(id);
        if (policy == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(policy);
    }

    @GetMapping("/types")
    public ResponseEntity<?> getTypeTree() {
        return ResponseEntity.ok(policyTypeService.getTypeTree());
    }

    @GetMapping("/search/byType")
    public Page<Policy> searchByType(
            @RequestParam String type,
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "10") Integer size) {
        
        Page<Policy> page = new Page<>(current, size);
        LambdaQueryWrapper<Policy> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Policy::getType, type);
        
        return policyService.page(page, wrapper);
    }

    @GetMapping("/search/byTypes")
    public Page<Policy> searchByTypes(
            @RequestParam String types,
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "10") Integer size) {
        
        List<String> typeList = Arrays.asList(types.split(","));
        
        Page<Policy> page = new Page<>(current, size);
        LambdaQueryWrapper<Policy> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(Policy::getType, typeList);
        
        return policyService.page(page, wrapper);
    }

    @GetMapping("/advanced-search")
    public Page<Policy> advancedSearch(
            @RequestParam(required = false) String title,
            @RequestParam(required = false) String content,
            @RequestParam(required = false) String organ,
            @RequestParam(required = false) String type,
            @RequestParam(required = false) String document,
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "10") Integer size) {
        
        Page<Policy> page = new Page<>(current, size);
        LambdaQueryWrapper<Policy> wrapper = new LambdaQueryWrapper<>();
        
        // 构建多条件查询
        wrapper.like(title != null && !title.isEmpty(), Policy::getName, title)
                .and(content != null && !content.isEmpty(), 
                    w -> w.like(Policy::getText, content))
                .and(organ != null && !organ.isEmpty(), 
                    w -> w.like(Policy::getOrgan, organ))
                .and(type != null && !type.isEmpty(), 
                    w -> w.eq(Policy::getType, type))
                .and(document != null && !document.isEmpty(), 
                    w -> w.like(Policy::getDocument, document));
        
        return policyService.page(page, wrapper);
    }
}
posted @ 2025-04-02 21:38  QixunQiu  阅读(23)  评论(0)    收藏  举报