Ver código fonte

提交代码

zlwu 1 ano atrás
pai
commit
ac1c2e69be

+ 34 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHolidayStayServiceImpl.java

@@ -113,9 +113,43 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService {
         lqw.eq(StringUtils.isNotBlank(bo.getExtField1()), XgHolidayStay::getExtField1, bo.getExtField1());
         lqw.eq(StringUtils.isNotBlank(bo.getExtField2()), XgHolidayStay::getExtField2, bo.getExtField2());
         lqw.eq(StringUtils.isNotBlank(bo.getExtField3()), XgHolidayStay::getExtField3, bo.getExtField3());
+
+        // 添加状态过滤逻辑
+        addStatusFilter(lqw, bo);
+
         return lqw;
     }
 
+    /**
+     * 添加状态过滤条件
+     * 
+     * @param lqw 查询条件构造器
+     * @param bo  查询参数对象
+     */
+    private void addStatusFilter(LambdaQueryWrapper<XgHolidayStay> lqw, XgHolidayStayBo bo) {
+        // 从参数中获取状态过滤条件
+        if (bo.getParams() != null) {
+            String statusType = (String) bo.getParams().get("statusType");
+            String currentTime = (String) bo.getParams().get("currentTime");
+
+            if (StringUtils.isNotBlank(statusType) && StringUtils.isNotBlank(currentTime)) {
+                if ("valid".equals(statusType)) {
+                    // 有效状态:结束时间 >= 当前时间 AND 结束时间不为空
+                    lqw.ge(XgHolidayStay::getApplyEndDate, currentTime)
+                            .isNotNull(XgHolidayStay::getApplyEndDate);
+                    log.debug("添加有效状态过滤条件: apply_end_date >= {} AND apply_end_date IS NOT NULL", currentTime);
+                } else if ("invalid".equals(statusType)) {
+                    // 失效状态:结束时间 < 当前时间 OR 结束时间为空
+                    lqw.and(wrapper -> wrapper
+                            .lt(XgHolidayStay::getApplyEndDate, currentTime)
+                            .or()
+                            .isNull(XgHolidayStay::getApplyEndDate));
+                    log.debug("添加失效状态过滤条件: apply_end_date < {} OR apply_end_date IS NULL", currentTime);
+                }
+            }
+        }
+    }
+
     /**
      * 新增假期留宿管理
      *

+ 238 - 0
docs/数据库优化/假期留宿状态过滤索引优化.sql

@@ -0,0 +1,238 @@
+-- 假期留宿状态过滤功能 - 数据库索引优化脚本
+-- 
+-- 目的:优化假期留宿管理表的查询性能,特别是状态过滤功能
+-- 创建时间:2025-01-28
+-- 适用表:xg_holiday_stay
+
+-- =============================================================================
+-- 1. 基础索引:结束日期字段索引
+-- =============================================================================
+
+-- 检查是否已存在索引
+SELECT 
+    INDEX_NAME, 
+    COLUMN_NAME, 
+    NON_UNIQUE
+FROM 
+    INFORMATION_SCHEMA.STATISTICS 
+WHERE 
+    TABLE_SCHEMA = DATABASE() 
+    AND TABLE_NAME = 'xg_holiday_stay' 
+    AND COLUMN_NAME = 'apply_end_date';
+
+-- 创建结束日期索引(如果不存在)
+-- 这个索引对状态过滤查询非常重要
+SET @sql = '
+CREATE INDEX idx_xg_holiday_stay_apply_end_date 
+ON xg_holiday_stay(apply_end_date)
+COMMENT ''假期留宿结束日期索引,用于状态过滤查询优化''';
+
+-- 检查索引是否存在,不存在则创建
+SET @index_exists = (
+    SELECT COUNT(*) 
+    FROM INFORMATION_SCHEMA.STATISTICS 
+    WHERE TABLE_SCHEMA = DATABASE() 
+      AND TABLE_NAME = 'xg_holiday_stay' 
+      AND INDEX_NAME = 'idx_xg_holiday_stay_apply_end_date'
+);
+
+SET @sql = IF(@index_exists = 0, @sql, 'SELECT ''索引 idx_xg_holiday_stay_apply_end_date 已存在'' AS message');
+
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- =============================================================================
+-- 2. 复合索引:状态查询优化
+-- =============================================================================
+
+-- 创建状态查询复合索引
+-- 包含:租户ID + 删除标志 + 结束日期
+-- 这个索引能显著提升多租户环境下的状态过滤查询性能
+SET @sql = '
+CREATE INDEX idx_xg_holiday_stay_status_query 
+ON xg_holiday_stay(tenant_id, deleted, apply_end_date)
+COMMENT ''假期留宿状态查询复合索引,优化多租户环境下的过滤查询''';
+
+-- 检查索引是否存在
+SET @index_exists = (
+    SELECT COUNT(*) 
+    FROM INFORMATION_SCHEMA.STATISTICS 
+    WHERE TABLE_SCHEMA = DATABASE() 
+      AND TABLE_NAME = 'xg_holiday_stay' 
+      AND INDEX_NAME = 'idx_xg_holiday_stay_status_query'
+);
+
+SET @sql = IF(@index_exists = 0, @sql, 'SELECT ''索引 idx_xg_holiday_stay_status_query 已存在'' AS message');
+
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- =============================================================================
+-- 3. 学生查询优化索引
+-- =============================================================================
+
+-- 创建学生相关查询的复合索引
+-- 包含:学生姓名 + 结束日期,便于按学生筛选时的状态过滤
+SET @sql = '
+CREATE INDEX idx_xg_holiday_stay_student_status 
+ON xg_holiday_stay(student_name, apply_end_date)
+COMMENT ''假期留宿学生状态查询索引,优化按学生筛选的状态过滤''';
+
+-- 检查索引是否存在
+SET @index_exists = (
+    SELECT COUNT(*) 
+    FROM INFORMATION_SCHEMA.STATISTICS 
+    WHERE TABLE_SCHEMA = DATABASE() 
+      AND TABLE_NAME = 'xg_holiday_stay' 
+      AND INDEX_NAME = 'idx_xg_holiday_stay_student_status'
+);
+
+SET @sql = IF(@index_exists = 0, @sql, 'SELECT ''索引 idx_xg_holiday_stay_student_status 已存在'' AS message');
+
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- =============================================================================
+-- 4. 时间范围查询优化索引
+-- =============================================================================
+
+-- 创建时间范围查询的复合索引
+-- 包含:开始日期 + 结束日期,便于时间范围查询
+SET @sql = '
+CREATE INDEX idx_xg_holiday_stay_date_range 
+ON xg_holiday_stay(apply_start_date, apply_end_date)
+COMMENT ''假期留宿时间范围查询索引,优化日期范围筛选''';
+
+-- 检查索引是否存在
+SET @index_exists = (
+    SELECT COUNT(*) 
+    FROM INFORMATION_SCHEMA.STATISTICS 
+    WHERE TABLE_SCHEMA = DATABASE() 
+      AND TABLE_NAME = 'xg_holiday_stay' 
+      AND INDEX_NAME = 'idx_xg_holiday_stay_date_range'
+);
+
+SET @sql = IF(@index_exists = 0, @sql, 'SELECT ''索引 idx_xg_holiday_stay_date_range 已存在'' AS message');
+
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- =============================================================================
+-- 5. 索引使用情况分析
+-- =============================================================================
+
+-- 查看表的所有索引
+SELECT 
+    INDEX_NAME AS '索引名称',
+    GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS '索引字段',
+    INDEX_TYPE AS '索引类型',
+    NON_UNIQUE AS '是否唯一',
+    INDEX_COMMENT AS '索引注释'
+FROM 
+    INFORMATION_SCHEMA.STATISTICS 
+WHERE 
+    TABLE_SCHEMA = DATABASE() 
+    AND TABLE_NAME = 'xg_holiday_stay'
+GROUP BY 
+    INDEX_NAME, INDEX_TYPE, NON_UNIQUE, INDEX_COMMENT
+ORDER BY 
+    INDEX_NAME;
+
+-- =============================================================================
+-- 6. 查询性能测试示例
+-- =============================================================================
+
+-- 设置查询性能测试变量
+SET @current_time = NOW();
+SET @test_tenant_id = 1; -- 替换为实际的租户ID
+
+-- 测试1:有效状态查询
+EXPLAIN 
+SELECT 
+    id, student_name, apply_end_date, tenant_id
+FROM 
+    xg_holiday_stay 
+WHERE 
+    tenant_id = @test_tenant_id 
+    AND deleted = 0 
+    AND apply_end_date >= @current_time 
+    AND apply_end_date IS NOT NULL
+LIMIT 10;
+
+-- 测试2:失效状态查询
+EXPLAIN 
+SELECT 
+    id, student_name, apply_end_date, tenant_id
+FROM 
+    xg_holiday_stay 
+WHERE 
+    tenant_id = @test_tenant_id 
+    AND deleted = 0 
+    AND (apply_end_date < @current_time OR apply_end_date IS NULL)
+LIMIT 10;
+
+-- 测试3:按学生名称 + 状态查询
+EXPLAIN 
+SELECT 
+    id, student_name, apply_end_date, tenant_id
+FROM 
+    xg_holiday_stay 
+WHERE 
+    student_name LIKE '%张%' 
+    AND apply_end_date >= @current_time 
+    AND apply_end_date IS NOT NULL
+LIMIT 10;
+
+-- =============================================================================
+-- 7. 索引维护建议
+-- =============================================================================
+
+/*
+索引维护建议:
+
+1. 监控索引使用情况
+   - 定期检查索引的使用频率
+   - 删除未使用的索引以减少存储开销
+
+2. 索引大小监控
+   - 监控索引占用的存储空间
+   - 当数据量增长时,考虑索引的分区策略
+
+3. 查询性能监控
+   - 定期分析慢查询日志
+   - 使用EXPLAIN分析查询执行计划
+
+4. 数据更新频率考量
+   - apply_end_date字段更新不频繁,适合建立索引
+   - 如果业务场景中该字段更新频繁,需要权衡索引维护成本
+
+5. 定期重建索引(可选)
+   - 对于频繁更新的表,可以定期重建索引以优化性能
+   - 命令:ALTER TABLE xg_holiday_stay ENGINE=InnoDB;
+*/
+
+-- =============================================================================
+-- 8. 清理脚本(如需删除索引)
+-- =============================================================================
+
+/*
+-- 如果需要删除创建的索引,可以使用以下命令:
+
+DROP INDEX idx_xg_holiday_stay_apply_end_date ON xg_holiday_stay;
+DROP INDEX idx_xg_holiday_stay_status_query ON xg_holiday_stay;
+DROP INDEX idx_xg_holiday_stay_student_status ON xg_holiday_stay;
+DROP INDEX idx_xg_holiday_stay_date_range ON xg_holiday_stay;
+*/
+
+-- =============================================================================
+-- 执行完成提示
+-- =============================================================================
+
+SELECT 
+    '✅ 假期留宿状态过滤索引优化脚本执行完成' AS message,
+    NOW() AS execution_time,
+    '请查看上述输出确认索引创建情况' AS note; 

+ 5 - 0
web/src/api/xg/xgss/holidayStay/holidayStay/types.ts

@@ -521,6 +521,11 @@ export interface HolidayStayQuery extends PageQuery {
    */
   extField3?: string;
 
+  /**
+   * 状态过滤(valid-有效,invalid-失效)
+   */
+  status?: string;
+
     /**
      * 日期范围参数
      */

+ 72 - 12
web/src/views/xg/xgss/holidayStay/holidayStay/index.vue

@@ -13,6 +13,12 @@
             <el-form-item label="学生姓名" prop="studentName">
               <el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
             </el-form-item>
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                <el-option label="有效" value="valid" />
+                <el-option label="失效" value="invalid" />
+              </el-select>
+            </el-form-item>
             <!-- <el-form-item label="学号" prop="studentNo">
               <el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
             </el-form-item>
@@ -215,7 +221,14 @@
         <el-table-column label="扩展字段2" align="center" prop="extField2" v-if="false" />
         <el-table-column label="扩展字段3" align="center" prop="extField3" v-if="false" />
         <el-table-column label="备注信息" align="center" prop="remark" />
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <el-table-column label="状态" align="center" prop="status" width="90">
+          <template #default="scope">
+            <el-tag :type="getStatusType(scope.row)" size="small">
+              {{ getStatusText(scope.row) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="100">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['holidayStay:holidayStay:edit']"></el-button>
@@ -223,7 +236,7 @@
             <el-tooltip content="删除" placement="top">
               <el-button
                 link
-                type="primary"
+                type="danger"
                 icon="Delete"
                 @click="handleDelete(scope.row)"
                 v-hasPermi="['holidayStay:holidayStay:remove']"
@@ -706,21 +719,16 @@ const data = reactive<PageData<HolidayStayForm, HolidayStayQuery>>({
     extField1: undefined,
     extField2: undefined,
     extField3: undefined,
+    status: undefined,
     params: {}
   },
   rules: {
     id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
-    studentId: [{ required: true, message: '学生ID,关联xg_student_msg.id不能为空', trigger: 'blur' }],
+    studentId: [{ required: true, message: '学生ID', trigger: 'blur' }],
     studentName: [{ required: true, message: '学生姓名不能为空', trigger: 'blur' }],
     studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
-    holidayType: [{ required: true, message: '假期类型不能为空', trigger: 'change' }],
-    applyStartDate: [{ required: true, message: '申请留宿开始日期不能为空', trigger: 'blur' }],
-    applyEndDate: [{ required: true, message: '申请留宿结束日期不能为空', trigger: 'blur' }],
-    stayReason: [{ required: true, message: '留宿原因不能为空', trigger: 'blur' }],
-    studentPhone: [{ required: true, message: '学生联系电话不能为空', trigger: 'blur' }],
-    emergencyContact: [{ required: true, message: '紧急联系人姓名不能为空', trigger: 'blur' }],
-    emergencyPhone: [{ required: true, message: '紧急联系人电话不能为空', trigger: 'blur' }],
-    parentConsent: [{ required: true, message: '家长同意不能为空', trigger: 'change' }]
+    applyStartDate: [{ required: true, message: '留宿开始日期不能为空', trigger: 'blur' }],
+    applyEndDate: [{ required: true, message: '留宿结束日期不能为空', trigger: 'blur' }]
   }
 });
 
@@ -747,7 +755,35 @@ const uploadData = computed(() => {
 /** 查询假期留宿管理列表 */
 const getList = async () => {
   loading.value = true;
-  const res = await listHolidayStay(queryParams.value);
+
+  // 处理状态过滤逻辑
+  const requestParams = { ...queryParams.value };
+
+  // 根据状态设置时间过滤条件
+  if (requestParams.status) {
+    const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
+
+    if (requestParams.status === 'valid') {
+      // 查询有效状态:结束时间大于等于当前时间
+      requestParams.params = {
+        ...requestParams.params,
+        statusType: 'valid',
+        currentTime: currentTime
+      };
+    } else if (requestParams.status === 'invalid') {
+      // 查询失效状态:结束时间小于当前时间
+      requestParams.params = {
+        ...requestParams.params,
+        statusType: 'invalid',
+        currentTime: currentTime
+      };
+    }
+
+    // 移除status参数,因为后端不需要这个字段
+    delete requestParams.status;
+  }
+
+  const res = await listHolidayStay(requestParams);
   holidayStayList.value = res.rows;
   total.value = res.total;
   loading.value = false;
@@ -1265,6 +1301,30 @@ const loadCascadeOptionsForUpdate = async (formData: HolidayStayForm) => {
   }
 };
 
+/** 获取状态文本 */
+const getStatusText = (row: HolidayStayVO) => {
+  if (!row.applyEndDate) {
+    return '未知';
+  }
+
+  const currentTime = new Date();
+  const endTime = new Date(row.applyEndDate);
+
+  return currentTime > endTime ? '失效' : '有效';
+};
+
+/** 获取状态标签类型 */
+const getStatusType = (row: HolidayStayVO) => {
+  if (!row.applyEndDate) {
+    return 'info';
+  }
+
+  const currentTime = new Date();
+  const endTime = new Date(row.applyEndDate);
+
+  return currentTime > endTime ? 'danger' : 'success';
+};
+
 // 文件类型判断
 const isImageFile = (file: any) => {
   const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];