Преглед изворни кода

修复页面搜索功能 以及 导出功能

mqk пре 3 месеци
родитељ
комит
fd05980d0d

+ 33 - 4
ruoyi-ui/src/utils/request.js

@@ -132,11 +132,40 @@ service.interceptors.response.use(res => {
 // 通用下载方法
 export function download(url, params, filename) {
   downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
-  return service.post(url, params, {
-    transformRequest: [(params) => { return tansParams(params) }],
-    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+  // 使用axios直接调用,以便访问完整的响应对象(包括headers)
+  return axios.post(process.env.VUE_APP_BASE_API + url, tansParams(params), {
+    headers: { 
+      'Content-Type': 'application/x-www-form-urlencoded',
+      'Authorization': 'Bearer ' + getToken()
+    },
     responseType: 'blob'
-  }).then(async (data) => {
+  }).then(async (response) => {
+    const data = response.data;
+    // 检查响应头,如果有Content-Disposition且包含attachment,说明是文件下载
+    const contentDisposition = response.headers['content-disposition'] || response.headers['Content-Disposition'];
+    
+    // 如果是文件下载响应(有attachment头),直接下载
+    if (contentDisposition && contentDisposition.includes('attachment')) {
+      const blob = new Blob([data])
+      // 尝试从Content-Disposition头中提取文件名
+      const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
+      if (filenameMatch) {
+        let downloadFilename = filenameMatch[1].replace(/['"]/g, '');
+        // 处理URL编码的文件名
+        try {
+          downloadFilename = decodeURIComponent(downloadFilename);
+        } catch (e) {
+          // 如果解码失败,使用原始文件名
+        }
+        saveAs(blob, downloadFilename)
+      } else {
+        saveAs(blob, filename)
+      }
+      downloadLoadingInstance.close();
+      return;
+    }
+    
+    // 否则按原来的逻辑处理
     const isLogin = await blobValidate(data);
     if (isLogin) {
       const blob = new Blob([data])

+ 28 - 2
ruoyi-ui/src/views/system/moduleConf/compoConf/index.vue

@@ -1,6 +1,15 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="关键词" prop="keyword">
+        <el-input
+          v-model="queryParams.keyword"
+          placeholder="请输入关键词(支持搜索分类索引、子类路径、属性列表)"
+          clearable
+          @keyup.enter.native="handleQuery"
+          style="width: 300px;"
+        />
+      </el-form-item>
       <el-form-item label="分类索引" prop="moduleIndex">
         <el-input
           v-model="queryParams.moduleIndex"
@@ -291,6 +300,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         // confId: null,
+        keyword: null,
         moduleIndex: null,
         category: null,
         attributes: null
@@ -361,11 +371,27 @@ export default {
     },
     /** 搜索按钮操作 */
     handleQuery() {
+      // 将空字符串转换为null,确保空搜索时能返回所有数据
+      const params = { ...this.queryParams };
+      Object.keys(params).forEach(key => {
+        if (params[key] === '' || params[key] === undefined) {
+          params[key] = null;
+        }
+      });
+      this.queryParams = params;
       this.queryParams.pageNum = 1;
       this.getList();
     },
     /** 重置按钮操作 */
     resetQuery() {
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        keyword: null,
+        moduleIndex: null,
+        category: null,
+        attributes: null
+      };
       this.resetForm("queryForm");
       this.handleQuery();
     },
@@ -441,9 +467,9 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      this.download('test/sys/export', {
+      this.download('system/moduleConf/compoConf/export', {
         ...this.queryParams
-      }, `sys_${new Date().getTime()}.xlsx`)
+      }, `compoConf_${new Date().getTime()}.json`)
     },
     /** 导入按钮操作 */
     handleImport() {

+ 2 - 2
ruoyi-ui/src/views/system/moduleConf/priceConf/index.vue

@@ -405,9 +405,9 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      this.download('test/sys/export', {
+      this.download('system/moduleConf/priceConf/export', {
         ...this.queryParams
-      }, `sys_${new Date().getTime()}.xlsx`)
+      }, `priceConf_${new Date().getTime()}.json`)
     },
     /** 导入按钮操作 */
     handleImport() {

+ 28 - 2
ruoyi-ui/src/views/system/moduleConf/productConf/index.vue

@@ -1,6 +1,15 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="关键词" prop="keyword">
+        <el-input
+          v-model="queryParams.keyword"
+          placeholder="请输入关键词(支持搜索分类索引、子类路径、属性列表)"
+          clearable
+          @keyup.enter.native="handleQuery"
+          style="width: 300px;"
+        />
+      </el-form-item>
       <el-form-item label="分类索引" prop="moduleIndex">
         <el-input
           v-model="queryParams.moduleIndex"
@@ -291,6 +300,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         // confId: null,
+        keyword: null,
         moduleIndex: null,
         category: null,
         attributes: null
@@ -361,11 +371,27 @@ export default {
     },
     /** 搜索按钮操作 */
     handleQuery() {
+      // 将空字符串转换为null,确保空搜索时能返回所有数据
+      const params = { ...this.queryParams };
+      Object.keys(params).forEach(key => {
+        if (params[key] === '' || params[key] === undefined) {
+          params[key] = null;
+        }
+      });
+      this.queryParams = params;
       this.queryParams.pageNum = 1;
       this.getList();
     },
     /** 重置按钮操作 */
     resetQuery() {
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        keyword: null,
+        moduleIndex: null,
+        category: null,
+        attributes: null
+      };
       this.resetForm("queryForm");
       this.handleQuery();
     },
@@ -441,9 +467,9 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      this.download('test/sys/export', {
+      this.download('system/moduleConf/productConf/export', {
         ...this.queryParams
-      }, `sys_${new Date().getTime()}.xlsx`)
+      }, `productConf_${new Date().getTime()}.json`)
     },
     /** 导入按钮操作 */
     handleImport() {

+ 2 - 2
ruoyi-ui/src/views/system/moduleConf/stuffInfoConf/index.vue

@@ -405,9 +405,9 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      this.download('test/sys/export', {
+      this.download('system/moduleConf/stuffInfoConf/export', {
         ...this.queryParams
-      }, `sys_${new Date().getTime()}.xlsx`)
+      }, `stuffInfoConf_${new Date().getTime()}.json`)
     },
     /** 导入按钮操作 */
     handleImport() {

+ 55 - 2
ruoyi_admin/controller/system/sys_compo_conf.py

@@ -4,6 +4,10 @@
 from typing import List
 from typing_extensions import Annotated
 from pydantic import BeforeValidator
+import json
+from datetime import datetime
+
+from flask import Response
 
 from ruoyi_common.constant import UserConstants
 from ruoyi_common.base.model import AjaxResponse, TableResponse
@@ -11,8 +15,8 @@ from ruoyi_common.base.transformer import ids_to_str_list
 # from ruoyi_common.domain.entity import SysCompoConf
 from ruoyi_common.domain.entity import SysCompoConf
 from ruoyi_common.domain.enum import BusinessType
-from ruoyi_common.descriptor.serializer import JsonSerializer
-from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator,PathValidator
+from ruoyi_common.descriptor.serializer import JsonSerializer, BaseSerializer
+from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator, PathValidator, FileDownloadValidator
 from ruoyi_common.utils import security_util as SecurityUtil
 from ruoyi_system.service.module_conf.compo_info_conf import SysModuleCompoinfoConfService
 from ruoyi_framework.descriptor.log import Log
@@ -103,3 +107,52 @@ def system_compoinfo_conf_delete(
     flag = SysModuleCompoinfoConfService.delete_compoinfo_conf_by_id(ids)
     return AjaxResponse.from_success() if flag > 0 else AjaxResponse.from_error()
 
+
+@reg.api.route("/system/moduleConf/compoConf/export", methods=["POST"])
+@FileDownloadValidator()
+@PreAuthorize(HasPerm("moduleConf:compoConf:export"))
+@Log(title="套餐配置管理", business_type=BusinessType.EXPORT)
+@BaseSerializer()
+def system_compoinfo_conf_export(dto: SysCompoConf):
+    '''
+        导出套餐配置列表为JSON
+    '''
+    try:
+        rows = SysModuleCompoinfoConfService.select_compoinfo_conf_list(dto)
+        # 将Pydantic模型转换为字典
+        data = []
+        for row in rows:
+            try:
+                if hasattr(row, 'model_dump'):
+                    data.append(row.model_dump())
+                elif hasattr(row, 'dict'):
+                    data.append(row.dict())
+                else:
+                    # 如果是字典类型,直接使用
+                    data.append(dict(row) if hasattr(row, '__iter__') and not isinstance(row, str) else row)
+            except Exception as e:
+                # 如果序列化失败,尝试使用默认方法
+                data.append(str(row))
+        # 转换为JSON字符串
+        json_str = json.dumps(data, ensure_ascii=False, indent=2, default=str)
+        # 创建响应 - 使用application/octet-stream确保前端识别为文件下载
+        response = Response(
+            json_str.encode('utf-8'),
+            mimetype='application/octet-stream',
+            headers={
+                'Content-Disposition': f'attachment; filename=compoConf_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json',
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        )
+        return response
+    except Exception as e:
+        import traceback
+        error_msg = f"导出失败: {str(e)}"
+        error_json = json.dumps({"code": 500, "msg": error_msg}, ensure_ascii=False)
+        error_response = Response(
+            error_json,
+            mimetype='application/json',
+            status=500
+        )
+        return error_response
+

+ 55 - 2
ruoyi_admin/controller/system/sys_price_conf.py

@@ -4,6 +4,10 @@
 from typing import List
 from typing_extensions import Annotated
 from pydantic import BeforeValidator
+import json
+from datetime import datetime
+
+from flask import Response
 
 from ruoyi_common.constant import UserConstants
 from ruoyi_common.base.model import AjaxResponse, TableResponse
@@ -11,8 +15,8 @@ from ruoyi_common.base.transformer import ids_to_list
 # from ruoyi_common.domain.entity import SysPriceConf
 from ruoyi_common.domain.entity import SysPriceConf
 from ruoyi_common.domain.enum import BusinessType
-from ruoyi_common.descriptor.serializer import JsonSerializer
-from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator,PathValidator
+from ruoyi_common.descriptor.serializer import JsonSerializer, BaseSerializer
+from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator, PathValidator, FileDownloadValidator
 from ruoyi_common.utils import security_util as SecurityUtil
 from ruoyi_system.service.module_conf.price_info_conf import SysModulePriceinfoConfService
 from ruoyi_framework.descriptor.log import Log
@@ -103,3 +107,52 @@ def system_priceinfo_conf_delete(
     flag = SysModulePriceinfoConfService.delete_priceinfo_conf_by_id(ids)
     return AjaxResponse.from_success() if flag > 0 else AjaxResponse.from_error()
 
+
+@reg.api.route("/system/moduleConf/priceConf/export", methods=["POST"])
+@FileDownloadValidator()
+@PreAuthorize(HasPerm("moduleConf:priceConf:export"))
+@Log(title="价目配置管理", business_type=BusinessType.EXPORT)
+@BaseSerializer()
+def system_priceinfo_conf_export(dto: SysPriceConf):
+    '''
+        导出价目配置列表为JSON
+    '''
+    try:
+        rows = SysModulePriceinfoConfService.select_priceinfo_conf_list(dto)
+        # 将Pydantic模型转换为字典
+        data = []
+        for row in rows:
+            try:
+                if hasattr(row, 'model_dump'):
+                    data.append(row.model_dump())
+                elif hasattr(row, 'dict'):
+                    data.append(row.dict())
+                else:
+                    # 如果是字典类型,直接使用
+                    data.append(dict(row) if hasattr(row, '__iter__') and not isinstance(row, str) else row)
+            except Exception as e:
+                # 如果序列化失败,尝试使用默认方法
+                data.append(str(row))
+        # 转换为JSON字符串
+        json_str = json.dumps(data, ensure_ascii=False, indent=2, default=str)
+        # 创建响应 - 使用application/octet-stream确保前端识别为文件下载
+        response = Response(
+            json_str.encode('utf-8'),
+            mimetype='application/octet-stream',
+            headers={
+                'Content-Disposition': f'attachment; filename=priceConf_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json',
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        )
+        return response
+    except Exception as e:
+        import traceback
+        error_msg = f"导出失败: {str(e)}"
+        error_json = json.dumps({"code": 500, "msg": error_msg}, ensure_ascii=False)
+        error_response = Response(
+            error_json,
+            mimetype='application/json',
+            status=500
+        )
+        return error_response
+

+ 55 - 2
ruoyi_admin/controller/system/sys_product_conf.py

@@ -4,6 +4,10 @@
 from typing import List
 from typing_extensions import Annotated
 from pydantic import BeforeValidator
+import json
+from datetime import datetime
+
+from flask import Response
 
 from ruoyi_common.constant import UserConstants
 from ruoyi_common.base.model import AjaxResponse, TableResponse
@@ -11,8 +15,8 @@ from ruoyi_common.base.transformer import ids_to_str_list
 # from ruoyi_common.domain.entity import SysProductConf
 from ruoyi_common.domain.entity import SysProductConf
 from ruoyi_common.domain.enum import BusinessType
-from ruoyi_common.descriptor.serializer import JsonSerializer
-from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator,PathValidator
+from ruoyi_common.descriptor.serializer import JsonSerializer, BaseSerializer
+from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator, PathValidator, FileDownloadValidator
 from ruoyi_common.utils import security_util as SecurityUtil
 from ruoyi_system.service.module_conf.product_info_conf import SysModuleProductinfoConfService
 from ruoyi_framework.descriptor.log import Log
@@ -103,3 +107,52 @@ def system_productinfo_conf_delete(
     flag = SysModuleProductinfoConfService.delete_productinfo_conf_by_id(ids)
     return AjaxResponse.from_success() if flag > 0 else AjaxResponse.from_error()
 
+
+@reg.api.route("/system/moduleConf/productConf/export", methods=["POST"])
+@FileDownloadValidator()
+@PreAuthorize(HasPerm("moduleConf:productConf:export"))
+@Log(title="单品配置管理", business_type=BusinessType.EXPORT)
+@BaseSerializer()
+def system_productinfo_conf_export(dto: SysProductConf):
+    '''
+        导出单品配置列表为JSON
+    '''
+    try:
+        rows = SysModuleProductinfoConfService.select_productinfo_conf_list(dto)
+        # 将Pydantic模型转换为字典
+        data = []
+        for row in rows:
+            try:
+                if hasattr(row, 'model_dump'):
+                    data.append(row.model_dump())
+                elif hasattr(row, 'dict'):
+                    data.append(row.dict())
+                else:
+                    # 如果是字典类型,直接使用
+                    data.append(dict(row) if hasattr(row, '__iter__') and not isinstance(row, str) else row)
+            except Exception as e:
+                # 如果序列化失败,尝试使用默认方法
+                data.append(str(row))
+        # 转换为JSON字符串
+        json_str = json.dumps(data, ensure_ascii=False, indent=2, default=str)
+        # 创建响应 - 使用application/octet-stream确保前端识别为文件下载
+        response = Response(
+            json_str.encode('utf-8'),
+            mimetype='application/octet-stream',
+            headers={
+                'Content-Disposition': f'attachment; filename=productConf_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json',
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        )
+        return response
+    except Exception as e:
+        import traceback
+        error_msg = f"导出失败: {str(e)}"
+        error_json = json.dumps({"code": 500, "msg": error_msg}, ensure_ascii=False)
+        error_response = Response(
+            error_json,
+            mimetype='application/json',
+            status=500
+        )
+        return error_response
+

+ 55 - 2
ruoyi_admin/controller/system/sys_stuff_info_conf.py

@@ -4,6 +4,10 @@
 from typing import List
 from typing_extensions import Annotated
 from pydantic import BeforeValidator
+import json
+from datetime import datetime
+
+from flask import Response
 
 from ruoyi_common.constant import UserConstants
 from ruoyi_common.base.model import AjaxResponse, TableResponse
@@ -11,8 +15,8 @@ from ruoyi_common.base.transformer import ids_to_list
 # from ruoyi_common.domain.entity import SysStuffInfoConf
 from ruoyi_common.domain.entity import SysStuffInfoConf
 from ruoyi_common.domain.enum import BusinessType
-from ruoyi_common.descriptor.serializer import JsonSerializer
-from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator,PathValidator
+from ruoyi_common.descriptor.serializer import JsonSerializer, BaseSerializer
+from ruoyi_common.descriptor.validator import BodyValidator, QueryValidator, PathValidator, FileDownloadValidator
 from ruoyi_common.utils import security_util as SecurityUtil
 from ruoyi_system.service.module_conf.stuff_info_conf import SysModuleStuffinfoConfService
 from ruoyi_framework.descriptor.log import Log
@@ -101,3 +105,52 @@ def system_stuffinfo_conf_delete(
     # SysModuleStuffinfoConfService.check_stuffinfo_conf_data_scope(id)
     flag = SysModuleStuffinfoConfService.delete_stuffinfo_conf_by_id(ids)
     return AjaxResponse.from_success() if flag > 0 else AjaxResponse.from_error()
+
+
+@reg.api.route("/system/moduleConf/stuffInfoConf/export", methods=["POST"])
+@FileDownloadValidator()
+@PreAuthorize(HasPerm("moduleConf:stuffInfoConf:export"))
+@Log(title="员工配置管理", business_type=BusinessType.EXPORT)
+@BaseSerializer()
+def system_stuffinfo_conf_export(dto: SysStuffInfoConf):
+    '''
+        导出员工配置列表为JSON
+    '''
+    try:
+        rows = SysModuleStuffinfoConfService.select_stuffinfo_conf_list(dto)
+        # 将Pydantic模型转换为字典
+        data = []
+        for row in rows:
+            try:
+                if hasattr(row, 'model_dump'):
+                    data.append(row.model_dump())
+                elif hasattr(row, 'dict'):
+                    data.append(row.dict())
+                else:
+                    # 如果是字典类型,直接使用
+                    data.append(dict(row) if hasattr(row, '__iter__') and not isinstance(row, str) else row)
+            except Exception as e:
+                # 如果序列化失败,尝试使用默认方法
+                data.append(str(row))
+        # 转换为JSON字符串
+        json_str = json.dumps(data, ensure_ascii=False, indent=2, default=str)
+        # 创建响应 - 使用application/octet-stream确保前端识别为文件下载
+        response = Response(
+            json_str.encode('utf-8'),
+            mimetype='application/octet-stream',
+            headers={
+                'Content-Disposition': f'attachment; filename=stuffInfoConf_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json',
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        )
+        return response
+    except Exception as e:
+        import traceback
+        error_msg = f"导出失败: {str(e)}"
+        error_json = json.dumps({"code": 500, "msg": error_msg}, ensure_ascii=False)
+        error_response = Response(
+            error_json,
+            mimetype='application/json',
+            status=500
+        )
+        return error_response

+ 0 - 3
ruoyi_system/domain/po.py

@@ -25,7 +25,6 @@ class SysConfigPo(db.Model):
     update_time: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime, comment='更新时间')
     remark: Mapped[Optional[str]] = mapped_column(String(500), comment='备注')
 
-
 class SysDeptPo(db.Model):
     __tablename__ = 'sys_dept'
     __table_args__ = {'comment': '部门表'}
@@ -370,8 +369,6 @@ class SysProductConfPo(db.Model):
         nullable=True,
         comment='状态 0:禁用, 1:启用'
     )
-
-
 class SysCompoConfPo(db.Model):
     """
     套餐配置表PO对象

+ 33 - 14
ruoyi_system/mapper/sys_compo_conf_mapper.py

@@ -6,8 +6,8 @@
 from typing import List
 from datetime import datetime
 
-from flask import g
-from sqlalchemy import select, update, delete, func
+from flask import g, request
+from sqlalchemy import select, update, delete, func, or_
 
 from ruoyi_admin.ext import db
 from ruoyi_common.domain.entity import SysCompoConf
@@ -33,26 +33,45 @@ class SysCompoInfoConfMapper:
             # 构建查询条件
             stmt = select(SysCompoConfPo)
 
-
-            if sys.conf_id is not None:
+            # 获取关键词搜索参数(从请求参数中获取)
+            keyword = None
+            try:
+                if request and hasattr(request, 'args'):
+                    keyword = request.args.get('keyword', None)
+                    # 如果keyword是空字符串,转换为None
+                    if keyword == '':
+                        keyword = None
+            except:
+                pass
+            # 如果request中没有,尝试从g对象获取
+            if keyword is None and "criterian_meta" in g and hasattr(g.criterian_meta, 'keyword'):
+                keyword = getattr(g.criterian_meta, 'keyword', None)
+                if keyword == '':
+                    keyword = None
+
+            # 关键词模糊搜索(支持搜索分类索引、子类路径、属性列表)
+            if keyword is not None and str(keyword).strip() != '':
+                keyword_pattern = f"%{str(keyword).strip()}%"
+                stmt = stmt.where(
+                    or_(
+                        SysCompoConfPo.module_index.like(keyword_pattern),
+                        SysCompoConfPo.category.like(keyword_pattern),
+                        SysCompoConfPo.attributes.like(keyword_pattern)
+                    )
+                )
+
+            if sys.conf_id is not None and sys.conf_id != '':
                 stmt = stmt.where(SysCompoConfPo.conf_id == sys.conf_id)
 
-
-
-            if sys.module_index is not None:
+            if sys.module_index is not None and sys.module_index != '':
                 stmt = stmt.where(SysCompoConfPo.module_index == sys.module_index)
 
-
-
-            if sys.category is not None:
+            if sys.category is not None and sys.category != '':
                 stmt = stmt.where(SysCompoConfPo.category == sys.category)
 
-
-
-            if sys.attributes is not None:
+            if sys.attributes is not None and sys.attributes != '':
                 stmt = stmt.where(SysCompoConfPo.attributes == sys.attributes)
 
-
             if "criterian_meta" in g and g.criterian_meta.page:
                 g.criterian_meta.page.stmt = stmt
 

+ 33 - 21
ruoyi_system/mapper/sys_productinfo_conf_mapper.py

@@ -1,14 +1,7 @@
-# -*- coding: utf-8 -*-
-# @Author  : YY
-# @FileName: sys_product_info_conf_mapper.py
-# @Time    : 2025-12-22 04:49:51
-
 from typing import List
 from datetime import datetime
-
-from flask import g
-from sqlalchemy import select, update, delete, func
-
+from flask import g, request
+from sqlalchemy import select, update, delete, func, or_
 from ruoyi_admin.ext import db
 from ruoyi_common.domain.entity import SysProductConf
 from ruoyi_system.domain.po import SysProductConfPo
@@ -33,26 +26,45 @@ class SysProductInfoConfMapper:
             # 构建查询条件
             stmt = select(SysProductConfPo)
 
-
-            if sys.conf_id is not None:
+            # 获取关键词搜索参数(从请求参数中获取)
+            keyword = None
+            try:
+                if request and hasattr(request, 'args'):
+                    keyword = request.args.get('keyword', None)
+                    # 如果keyword是空字符串,转换为None
+                    if keyword == '':
+                        keyword = None
+            except:
+                pass
+            # 如果request中没有,尝试从g对象获取
+            if keyword is None and "criterian_meta" in g and hasattr(g.criterian_meta, 'keyword'):
+                keyword = getattr(g.criterian_meta, 'keyword', None)
+                if keyword == '':
+                    keyword = None
+
+            # 关键词模糊搜索(支持搜索分类索引、子类路径、属性列表)
+            if keyword is not None and str(keyword).strip() != '':
+                keyword_pattern = f"%{str(keyword).strip()}%"
+                stmt = stmt.where(
+                    or_(
+                        SysProductConfPo.module_index.like(keyword_pattern),
+                        SysProductConfPo.category.like(keyword_pattern),
+                        SysProductConfPo.attributes.like(keyword_pattern)
+                    )
+                )
+
+            if sys.conf_id is not None and sys.conf_id != '':
                 stmt = stmt.where(SysProductConfPo.conf_id == sys.conf_id)
 
-
-
-            if sys.module_index is not None:
+            if sys.module_index is not None and sys.module_index != '':
                 stmt = stmt.where(SysProductConfPo.module_index == sys.module_index)
 
-
-
-            if sys.category is not None:
+            if sys.category is not None and sys.category != '':
                 stmt = stmt.where(SysProductConfPo.category == sys.category)
 
-
-
-            if sys.attributes is not None:
+            if sys.attributes is not None and sys.attributes != '':
                 stmt = stmt.where(SysProductConfPo.attributes == sys.attributes)
 
-
             if "criterian_meta" in g and g.criterian_meta.page:
                 g.criterian_meta.page.stmt = stmt