chengf
2025-12-05 ec361f081230e73de3510718cf055d4e22499772
添加排序1205-cgf
2个文件已修改
19个文件已添加
1455 ■■■■■ 已修改文件
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/customer/controller/CustomerController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/mediaOrderRecords/vue3/V20251112_1__menu_insert_MediaOrderRecords.sql 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/controller/QaInfoController.java 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/entity/QaInfo.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/entity/RelatedArticle.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/QaInfoMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/RelatedArticleMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/xml/QaInfoMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/xml/RelatedArticleMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/IQaInfoService.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/IRelatedArticleService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/impl/QaInfoServiceImpl.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/impl/RelatedArticleServiceImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vo/QaInfoPage.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/QaInfo.api.ts 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/QaInfo.data.ts 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/QaInfoList.vue 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/V20251205_1__menu_insert_QaInfo.sql 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/components/QaInfoForm.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/components/QaInfoModal.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
@@ -90,6 +90,7 @@
        filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
        filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登录接口排除
        filterChainDefinitionMap.put("/api/robin/**", "anon"); //登录接口排除
        filterChainDefinitionMap.put("/qaInfo/qaInfo/**", "anon"); //存储网址文章排除
        filterChainDefinitionMap.put("/mediaOrderRecords/mediaOrderRecords/list", "anon"); //登录接口排除
        filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
        filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登录
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/customer/controller/CustomerController.java
@@ -90,6 +90,9 @@
            queryWrapper.eq(Customer::getAgentsName,customer.getAgentsNameToCustomer());
            queryWrapper.or();
            queryWrapper.eq(Customer::getCreateBy,customer.getAgentsNameToCustomer());
            queryWrapper.or();
            queryWrapper.exists("SELECT 1 FROM contract WHERE contract.agents_name = {0} AND contract.customer_name = customer.enterprise_name",
                    customer.getAgentsNameToCustomer());
            // 2. 查询字段值,返回Object列表
            List<Object> objList = customerService.listObjs(queryWrapper);
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/mediaOrderRecords/vue3/V20251112_1__menu_insert_MediaOrderRecords.sql
New file
@@ -0,0 +1,40 @@
-- 注意:该页面对应的前台目录为views/mediaOrderRecords文件夹下
-- 如果你想更改到其他目录,请修改sql中component字段对应的值
-- 主菜单
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('176291663443701', NULL, '媒体订单记录', '/mediaOrderRecords/mediaOrderRecordsList', 'mediaOrderRecords/MediaOrderRecordsList', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 1, 0, 0, 0, 0, NULL, '1', 0, 0, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0);
-- 新增
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176291663443802', '176291663443701', '添加媒体订单记录', NULL, NULL, 0, NULL, NULL, 2, 'mediaOrderRecords:media_order_records:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0, 0, '1', 0);
-- 编辑
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176291663443803', '176291663443701', '编辑媒体订单记录', NULL, NULL, 0, NULL, NULL, 2, 'mediaOrderRecords:media_order_records:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0, 0, '1', 0);
-- 删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176291663443804', '176291663443701', '删除媒体订单记录', NULL, NULL, 0, NULL, NULL, 2, 'mediaOrderRecords:media_order_records:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0, 0, '1', 0);
-- 批量删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176291663443805', '176291663443701', '批量删除媒体订单记录', NULL, NULL, 0, NULL, NULL, 2, 'mediaOrderRecords:media_order_records:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0, 0, '1', 0);
-- 导出excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176291663443806', '176291663443701', '导出excel_媒体订单记录', NULL, NULL, 0, NULL, NULL, 2, 'mediaOrderRecords:media_order_records:exportXls', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0, 0, '1', 0);
-- 导入excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176291663443807', '176291663443701', '导入excel_媒体订单记录', NULL, NULL, 0, NULL, NULL, 2, 'mediaOrderRecords:media_order_records:importExcel', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-11-12 11:03:54', NULL, NULL, 0, 0, '1', 0);
-- 角色授权(以 admin 角色为例,role_id 可替换)
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445508', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443701', NULL, '2025-11-12 11:03:54', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445509', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443802', NULL, '2025-11-12 11:03:54', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445510', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443803', NULL, '2025-11-12 11:03:54', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445511', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443804', NULL, '2025-11-12 11:03:54', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445512', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443805', NULL, '2025-11-12 11:03:54', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445513', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443806', NULL, '2025-11-12 11:03:54', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176291663445514', 'f6817f48af4fb3af11b9e8bf182f618b', '176291663443807', NULL, '2025-11-12 11:03:54', '127.0.0.1');
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/controller/QaInfoController.java
New file
@@ -0,0 +1,270 @@
package org.jeecg.modules.demo.qaInfo.controller;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.HashMap;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecg.common.system.vo.LoginUser;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import org.jeecg.modules.demo.qaInfo.entity.QaInfo;
import org.jeecg.modules.demo.qaInfo.vo.QaInfoPage;
import org.jeecg.modules.demo.qaInfo.service.IQaInfoService;
import org.jeecg.modules.demo.qaInfo.service.IRelatedArticleService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;
 /**
 * @Description: 提问回答表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
@Tag(name="提问回答表")
@RestController
@RequestMapping("/qaInfo/qaInfo")
@Slf4j
public class QaInfoController {
    @Autowired
    private IQaInfoService qaInfoService;
    @Autowired
    private IRelatedArticleService relatedArticleService;
    /**
     * 分页列表查询
     *
     * @param qaInfo
     * @param pageNo
     * @param pageSize
     * @param req
     * @return
     */
    //@AutoLog(value = "提问回答表-分页列表查询")
    @Operation(summary="提问回答表-分页列表查询")
    @GetMapping(value = "/list")
    public Result<IPage<QaInfo>> queryPageList(QaInfo qaInfo,
                                   @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
                                   @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
                                   HttpServletRequest req) {
        QueryWrapper<QaInfo> queryWrapper = QueryGenerator.initQueryWrapper(qaInfo, req.getParameterMap());
        Page<QaInfo> page = new Page<QaInfo>(pageNo, pageSize);
        IPage<QaInfo> pageList = qaInfoService.page(page, queryWrapper);
        return Result.OK(pageList);
    }
    /**
     *   添加
     *
     * @param qaInfoPage
     * @return
     */
    @AutoLog(value = "提问回答表-添加")
    @Operation(summary="提问回答表-添加")
    @RequiresPermissions("qaInfo:qa_info:add")
    @PostMapping(value = "/add")
    public Result<String> add(@RequestBody QaInfoPage qaInfoPage) {
        QaInfo qaInfo = new QaInfo();
        BeanUtils.copyProperties(qaInfoPage, qaInfo);
        qaInfoService.saveMain(qaInfo, qaInfoPage.getRelatedArticleList());
        return Result.OK("添加成功!");
    }
    /**
     *  编辑
     *
     * @param qaInfoPage
     * @return
     */
    @AutoLog(value = "提问回答表-编辑")
    @Operation(summary="提问回答表-编辑")
    @RequiresPermissions("qaInfo:qa_info:edit")
    @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
    public Result<String> edit(@RequestBody QaInfoPage qaInfoPage) {
        QaInfo qaInfo = new QaInfo();
        BeanUtils.copyProperties(qaInfoPage, qaInfo);
        QaInfo qaInfoEntity = qaInfoService.getById(qaInfo.getId());
        if(qaInfoEntity==null) {
            return Result.error("未找到对应数据");
        }
        qaInfoService.updateMain(qaInfo, qaInfoPage.getRelatedArticleList());
        return Result.OK("编辑成功!");
    }
    /**
     *   通过id删除
     *
     * @param id
     * @return
     */
    @AutoLog(value = "提问回答表-通过id删除")
    @Operation(summary="提问回答表-通过id删除")
    @RequiresPermissions("qaInfo:qa_info:delete")
    @DeleteMapping(value = "/delete")
    public Result<String> delete(@RequestParam(name="id",required=true) String id) {
        qaInfoService.delMain(id);
        return Result.OK("删除成功!");
    }
    /**
     *  批量删除
     *
     * @param ids
     * @return
     */
    @AutoLog(value = "提问回答表-批量删除")
    @Operation(summary="提问回答表-批量删除")
    @RequiresPermissions("qaInfo:qa_info:deleteBatch")
    @DeleteMapping(value = "/deleteBatch")
    public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
        this.qaInfoService.delBatchMain(Arrays.asList(ids.split(",")));
        return Result.OK("批量删除成功!");
    }
    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    //@AutoLog(value = "提问回答表-通过id查询")
    @Operation(summary="提问回答表-通过id查询")
    @GetMapping(value = "/queryById")
    public Result<QaInfo> queryById(@RequestParam(name="id",required=true) String id) {
        QaInfo qaInfo = qaInfoService.getById(id);
        if(qaInfo==null) {
            return Result.error("未找到对应数据");
        }
        return Result.OK(qaInfo);
    }
    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    //@AutoLog(value = "关联文件表通过主表ID查询")
    @Operation(summary="关联文件表主表ID查询")
    @GetMapping(value = "/queryRelatedArticleByMainId")
    public Result<List<RelatedArticle>> queryRelatedArticleListByMainId(@RequestParam(name="id",required=true) String id) {
        List<RelatedArticle> relatedArticleList = relatedArticleService.selectByMainId(id);
        return Result.OK(relatedArticleList);
    }
    /**
    * 导出excel
    *
    * @param request
    * @param qaInfo
    */
    @RequiresPermissions("qaInfo:qa_info:exportXls")
    @RequestMapping(value = "/exportXls")
    public ModelAndView exportXls(HttpServletRequest request, QaInfo qaInfo) {
      // Step.1 组装查询条件查询数据
      QueryWrapper<QaInfo> queryWrapper = QueryGenerator.initQueryWrapper(qaInfo, request.getParameterMap());
      LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
      //配置选中数据查询条件
      String selections = request.getParameter("selections");
      if(oConvertUtils.isNotEmpty(selections)) {
         List<String> selectionList = Arrays.asList(selections.split(","));
         queryWrapper.in("id",selectionList);
      }
      //Step.2 获取导出数据
      List<QaInfo> qaInfoList = qaInfoService.list(queryWrapper);
      // Step.3 组装pageList
      List<QaInfoPage> pageList = new ArrayList<QaInfoPage>();
      for (QaInfo main : qaInfoList) {
          QaInfoPage vo = new QaInfoPage();
          BeanUtils.copyProperties(main, vo);
          List<RelatedArticle> relatedArticleList = relatedArticleService.selectByMainId(main.getId());
          vo.setRelatedArticleList(relatedArticleList);
          pageList.add(vo);
      }
      // Step.4 AutoPoi 导出Excel
      ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
      mv.addObject(NormalExcelConstants.FILE_NAME, "提问回答表列表");
      mv.addObject(NormalExcelConstants.CLASS, QaInfoPage.class);
      mv.addObject(NormalExcelConstants.PARAMS, new ExportParams("提问回答表数据", "导出人:"+sysUser.getRealname(), "提问回答表"));
      mv.addObject(NormalExcelConstants.DATA_LIST, pageList);
      return mv;
    }
    /**
    * 通过excel导入数据
    *
    * @param request
    * @param response
    * @return
    */
    @RequiresPermissions("qaInfo:qa_info:importExcel")
    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
      MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
      Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
      for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
          // 获取上传文件对象
          MultipartFile file = entity.getValue();
          ImportParams params = new ImportParams();
          params.setTitleRows(2);
          params.setHeadRows(1);
          params.setNeedSave(true);
          try {
              List<QaInfoPage> list = ExcelImportUtil.importExcel(file.getInputStream(), QaInfoPage.class, params);
              for (QaInfoPage page : list) {
                  QaInfo po = new QaInfo();
                  BeanUtils.copyProperties(page, po);
                  qaInfoService.saveMain(po, page.getRelatedArticleList());
              }
              return Result.OK("文件导入成功!数据行数:" + list.size());
          } catch (Exception e) {
              log.error(e.getMessage(),e);
              return Result.error("文件导入失败:"+e.getMessage());
          } finally {
              try {
                  file.getInputStream().close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      return Result.OK("文件导入失败!");
    }
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/entity/QaInfo.java
New file
@@ -0,0 +1,63 @@
package org.jeecg.modules.demo.qaInfo.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
/**
 * @Description: 提问回答表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
@Schema(description="提问回答表")
@Data
@TableName("qa_info")
public class QaInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**主键*/
    @TableId(type = IdType.ASSIGN_ID)
    @Schema(description = "主键")
    private String id;
    /**创建人*/
    @Schema(description = "创建人")
    private String createBy;
    /**创建日期*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @Schema(description = "创建日期")
    private Date createTime;
    /**更新人*/
    @Schema(description = "更新人")
    private String updateBy;
    /**更新日期*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @Schema(description = "更新日期")
    private Date updateTime;
    /**所属部门*/
    @Schema(description = "所属部门")
    private String sysOrgCode;
    /**问题*/
    @Excel(name = "问题", width = 15)
    @Schema(description = "问题")
    private String question;
    /**回答*/
    @Excel(name = "回答", width = 15)
    @Schema(description = "回答")
    private String answer;
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/entity/RelatedArticle.java
New file
@@ -0,0 +1,74 @@
package org.jeecg.modules.demo.qaInfo.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.UnsupportedEncodingException;
/**
 * @Description: 关联文件表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
@Schema(description="关联文件表")
@Data
@TableName("related_article")
public class RelatedArticle implements Serializable {
    private static final long serialVersionUID = 1L;
    /**主键*/
    @TableId(type = IdType.ASSIGN_ID)
    @Schema(description = "主键")
    private String id;
    /**创建人*/
    @Schema(description = "创建人")
    private String createBy;
    /**创建日期*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @Schema(description = "创建日期")
    private Date createTime;
    /**更新人*/
    @Schema(description = "更新人")
    private String updateBy;
    /**更新日期*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @Schema(description = "更新日期")
    private Date updateTime;
    /**所属部门*/
    @Schema(description = "所属部门")
    private String sysOrgCode;
    /**关联的提问回答 ID*/
    @Schema(description = "关联的提问回答 ID")
    private String qaId;
    /**关联文件标题*/
    @Excel(name = "关联文件标题", width = 15)
    @Schema(description = "关联文件标题")
    private String fileTitle;
    /**文件发布时间*/
    @Excel(name = "文件发布时间", width = 15, format = "yyyy-MM-dd")
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern="yyyy-MM-dd")
    @Schema(description = "文件发布时间")
    private Date publishTime;
    /**文件来源链接*/
    @Excel(name = "文件来源链接", width = 15)
    @Schema(description = "文件来源链接")
    private String sourceUrl;
    /**文章正文内容*/
    @Excel(name = "文章正文内容", width = 15)
    @Schema(description = "文章正文内容")
    private String content;
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/QaInfoMapper.java
New file
@@ -0,0 +1,17 @@
package org.jeecg.modules.demo.qaInfo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.demo.qaInfo.entity.QaInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * @Description: 提问回答表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
public interface QaInfoMapper extends BaseMapper<QaInfo> {
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/RelatedArticleMapper.java
New file
@@ -0,0 +1,31 @@
package org.jeecg.modules.demo.qaInfo.mapper;
import java.util.List;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
 * @Description: 关联文件表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
public interface RelatedArticleMapper extends BaseMapper<RelatedArticle> {
    /**
     * 通过主表id删除子表数据
     *
     * @param mainId 主表id
     * @return boolean
     */
    public boolean deleteByMainId(@Param("mainId") String mainId);
  /**
   * 通过主表id查询子表数据
   *
   * @param mainId 主表id
   * @return List<RelatedArticle>
   */
    public List<RelatedArticle> selectByMainId(@Param("mainId") String mainId);
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/xml/QaInfoMapper.xml
New file
@@ -0,0 +1,5 @@
<?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="org.jeecg.modules.demo.qaInfo.mapper.QaInfoMapper">
</mapper>
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/mapper/xml/RelatedArticleMapper.xml
New file
@@ -0,0 +1,16 @@
<?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="org.jeecg.modules.demo.qaInfo.mapper.RelatedArticleMapper">
    <delete id="deleteByMainId" parameterType="java.lang.String">
        DELETE
        FROM  related_article
        WHERE
             qa_id = #{mainId}     </delete>
    <select id="selectByMainId" parameterType="java.lang.String" resultType="org.jeecg.modules.demo.qaInfo.entity.RelatedArticle">
        SELECT *
        FROM  related_article
        WHERE
             qa_id = #{mainId}     </select>
</mapper>
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/IQaInfoService.java
New file
@@ -0,0 +1,48 @@
package org.jeecg.modules.demo.qaInfo.service;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import org.jeecg.modules.demo.qaInfo.entity.QaInfo;
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
 * @Description: 提问回答表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
public interface IQaInfoService extends IService<QaInfo> {
    /**
     * 添加一对多
     *
     * @param qaInfo
     * @param relatedArticleList
     */
    public void saveMain(QaInfo qaInfo,List<RelatedArticle> relatedArticleList) ;
    /**
     * 修改一对多
     *
   * @param qaInfo
   * @param relatedArticleList
     */
    public void updateMain(QaInfo qaInfo,List<RelatedArticle> relatedArticleList);
    /**
     * 删除一对多
     *
     * @param id
     */
    public void delMain (String id);
    /**
     * 批量删除一对多
     *
     * @param idList
     */
    public void delBatchMain (Collection<? extends Serializable> idList);
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/IRelatedArticleService.java
New file
@@ -0,0 +1,22 @@
package org.jeecg.modules.demo.qaInfo.service;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * @Description: 关联文件表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
public interface IRelatedArticleService extends IService<RelatedArticle> {
    /**
     * 通过主表id查询子表数据
     *
     * @param mainId 主表id
     * @return List<RelatedArticle>
     */
    public List<RelatedArticle> selectByMainId(String mainId);
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/impl/QaInfoServiceImpl.java
New file
@@ -0,0 +1,77 @@
package org.jeecg.modules.demo.qaInfo.service.impl;
import org.jeecg.modules.demo.qaInfo.entity.QaInfo;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import org.jeecg.modules.demo.qaInfo.mapper.RelatedArticleMapper;
import org.jeecg.modules.demo.qaInfo.mapper.QaInfoMapper;
import org.jeecg.modules.demo.qaInfo.service.IQaInfoService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.List;
import java.util.Collection;
/**
 * @Description: 提问回答表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
@Service
public class QaInfoServiceImpl extends ServiceImpl<QaInfoMapper, QaInfo> implements IQaInfoService {
    @Autowired
    private QaInfoMapper qaInfoMapper;
    @Autowired
    private RelatedArticleMapper relatedArticleMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveMain(QaInfo qaInfo, List<RelatedArticle> relatedArticleList) {
        qaInfoMapper.insert(qaInfo);
        if(relatedArticleList!=null && relatedArticleList.size()>0) {
            for(RelatedArticle entity:relatedArticleList) {
                //外键设置
                entity.setQaId(qaInfo.getId());
                relatedArticleMapper.insert(entity);
            }
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateMain(QaInfo qaInfo,List<RelatedArticle> relatedArticleList) {
        qaInfoMapper.updateById(qaInfo);
        //1.先删除子表数据
        relatedArticleMapper.deleteByMainId(qaInfo.getId());
        //2.子表数据重新插入
        if(relatedArticleList!=null && relatedArticleList.size()>0) {
            for(RelatedArticle entity:relatedArticleList) {
                //外键设置
                entity.setQaId(qaInfo.getId());
                relatedArticleMapper.insert(entity);
            }
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delMain(String id) {
        relatedArticleMapper.deleteByMainId(id);
        qaInfoMapper.deleteById(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delBatchMain(Collection<? extends Serializable> idList) {
        for(Serializable id:idList) {
            relatedArticleMapper.deleteByMainId(id.toString());
            qaInfoMapper.deleteById(id);
        }
    }
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/service/impl/RelatedArticleServiceImpl.java
New file
@@ -0,0 +1,27 @@
package org.jeecg.modules.demo.qaInfo.service.impl;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import org.jeecg.modules.demo.qaInfo.mapper.RelatedArticleMapper;
import org.jeecg.modules.demo.qaInfo.service.IRelatedArticleService;
import org.springframework.stereotype.Service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
/**
 * @Description: 关联文件表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
@Service
public class RelatedArticleServiceImpl extends ServiceImpl<RelatedArticleMapper, RelatedArticle> implements IRelatedArticleService {
    @Autowired
    private RelatedArticleMapper relatedArticleMapper;
    @Override
    public List<RelatedArticle> selectByMainId(String mainId) {
        return relatedArticleMapper.selectByMainId(mainId);
    }
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vo/QaInfoPage.java
New file
@@ -0,0 +1,64 @@
package org.jeecg.modules.demo.qaInfo.vo;
import java.util.List;
import org.jeecg.modules.demo.qaInfo.entity.QaInfo;
import org.jeecg.modules.demo.qaInfo.entity.RelatedArticle;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecgframework.poi.excel.annotation.ExcelEntity;
import org.jeecgframework.poi.excel.annotation.ExcelCollection;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import io.swagger.v3.oas.annotations.media.Schema;
/**
 * @Description: 提问回答表
 * @Author: jeecg-boot
 * @Date:   2025-12-05
 * @Version: V1.0
 */
@Data
@Schema(description="提问回答表")
public class QaInfoPage {
    /**主键*/
    @Schema(description = "主键")
    private String id;
    /**创建人*/
    @Schema(description = "创建人")
    private String createBy;
    /**创建日期*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @Schema(description = "创建日期")
    private Date createTime;
    /**更新人*/
    @Schema(description = "更新人")
    private String updateBy;
    /**更新日期*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @Schema(description = "更新日期")
    private Date updateTime;
    /**所属部门*/
    @Schema(description = "所属部门")
    private String sysOrgCode;
    /**问题*/
    @Excel(name = "问题", width = 15)
    @Schema(description = "问题")
    private String question;
    /**回答*/
    @Excel(name = "回答", width = 15)
    @Schema(description = "回答")
    private String answer;
    @ExcelCollection(name="关联文件表")
    @Schema(description = "关联文件表")
    private List<RelatedArticle> relatedArticleList;
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/QaInfo.api.ts
New file
@@ -0,0 +1,71 @@
import {defHttp} from '/@/utils/http/axios';
import { useMessage } from "/@/hooks/web/useMessage";
const { createConfirm } = useMessage();
enum Api {
  list = '/qaInfo/qaInfo/list',
  save='/qaInfo/qaInfo/add',
  edit='/qaInfo/qaInfo/edit',
  deleteOne = '/qaInfo/qaInfo/delete',
  deleteBatch = '/qaInfo/qaInfo/deleteBatch',
  importExcel = '/qaInfo/qaInfo/importExcel',
  exportXls = '/qaInfo/qaInfo/exportXls',
  relatedArticleList = '/qaInfo/qaInfo/queryRelatedArticleByMainId',
}
/**
 * 导出api
 * @param params
 */
export const getExportUrl = Api.exportXls;
/**
 * 导入api
 */
export const getImportUrl = Api.importExcel;
/**
 * 查询子表数据
 * @param params
 */
export const relatedArticleList = Api.relatedArticleList;
/**
 * 列表接口
 * @param params
 */
export const list = (params) =>
  defHttp.get({url: Api.list, params});
/**
 * 删除单个
 */
export const deleteOne = (params,handleSuccess) => {
  return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
    handleSuccess();
  });
}
/**
 * 批量删除
 * @param params
 */
export const batchDelete = (params, handleSuccess) => {
  createConfirm({
    iconType: 'warning',
    title: '确认删除',
    content: '是否删除选中数据',
    okText: '确认',
    cancelText: '取消',
    onOk: () => {
      return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
        handleSuccess();
      });
    }
  });
}
/**
 * 保存或者更新
 * @param params
 */
export const saveOrUpdate = (params, isUpdate) => {
  let url = isUpdate ? Api.edit : Api.save;
  return defHttp.post({url: url, params});
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/QaInfo.data.ts
New file
@@ -0,0 +1,114 @@
import {BasicColumn} from '/@/components/Table';
import {FormSchema} from '/@/components/Table';
import { rules} from '/@/utils/helper/validator';
import { render } from '/@/utils/common/renderUtils';
import {JVxeTypes,JVxeColumn} from '/@/components/jeecg/JVxeTable/types'
import { getWeekMonthQuarterYear } from '/@/utils';
//列表数据
export const columns: BasicColumn[] = [
   {
    title: '问题',
    align:"center",
    dataIndex: 'question'
   },
   {
    title: '回答',
    align:"center",
    dataIndex: 'answer'
   },
];
//查询数据
export const searchFormSchema: FormSchema[] = [
];
//表单数据
export const formSchema: FormSchema[] = [
  {
    label: '问题',
    field: 'question',
    component: 'Input',
  },
  {
    label: '回答',
    field: 'answer',
    component: 'Input',
  },
    // TODO 主键隐藏字段,目前写死为ID
    {
      label: '',
      field: 'id',
      component: 'Input',
      show: false
    },
];
//子表单数据
//子表表格配置
export const relatedArticleColumns: JVxeColumn[] = [
    {
      title: '关联的提问回答 ID',
      key: 'qaId',
      type: JVxeTypes.input,
      width:"200px",
      placeholder: '请输入${title}',
      defaultValue:'',
    },
    {
      title: '关联文件标题',
      key: 'fileTitle',
      type: JVxeTypes.input,
      width:"200px",
      placeholder: '请输入${title}',
      defaultValue:'',
    },
    {
      title: '文件发布时间',
      key: 'publishTime',
      type: JVxeTypes.date,
      width:"200px",
      placeholder: '请输入${title}',
      defaultValue:'',
    },
    {
      title: '文件来源链接',
      key: 'sourceUrl',
      type: JVxeTypes.input,
      width:"200px",
      placeholder: '请输入${title}',
      defaultValue:'',
    },
    {
      title: '文章正文内容',
      key: 'content',
      type: JVxeTypes.input,
      width:"200px",
      placeholder: '请输入${title}',
      defaultValue:'',
    },
  ]
// 高级查询数据
export const superQuerySchema = {
  question: {title: '问题',order: 0,view: 'text', type: 'string',},
  answer: {title: '回答',order: 1,view: 'text', type: 'string',},
  //子表高级查询
  relatedArticle: {
    title: '关联文件表',
    view: 'table',
    fields: {
        qaId: {title: '关联的提问回答 ID',order: 0,view: 'text', type: 'string',},
        fileTitle: {title: '关联文件标题',order: 1,view: 'text', type: 'string',},
        publishTime: {title: '文件发布时间',order: 2,view: 'date', type: 'string',},
        sourceUrl: {title: '文件来源链接',order: 3,view: 'text', type: 'string',},
        content: {title: '文章正文内容',order: 4,view: 'text', type: 'string',},
    }
  },
};
/**
* 流程表单调用这个方法获取formSchema
* @param param
*/
export function getBpmFormSchema(_formData): FormSchema[]{
// 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
  return formSchema;
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/QaInfoList.vue
New file
@@ -0,0 +1,207 @@
<template>
  <div>
    <!--引用表格-->
   <BasicTable @register="registerTable" :rowSelection="rowSelection">
     <!--插槽:table标题-->
      <template #tableTitle>
          <a-button type="primary" v-auth="'qaInfo:qa_info:add'"  @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
          <a-button  type="primary" v-auth="'qaInfo:qa_info:exportXls'"  preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
          <j-upload-button  type="primary" v-auth="'qaInfo:qa_info:importExcel'"  preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
          <a-dropdown v-if="selectedRowKeys.length > 0">
              <template #overlay>
                <a-menu>
                  <a-menu-item key="1" @click="batchHandleDelete">
                    <Icon icon="ant-design:delete-outlined"></Icon>
                    删除
                  </a-menu-item>
                </a-menu>
              </template>
              <a-button v-auth="'qaInfo:qa_info:deleteBatch'">批量操作
                <Icon icon="mdi:chevron-down"></Icon>
              </a-button>
        </a-dropdown>
        <!-- 高级查询 -->
        <super-query :config="superQueryConfig" @search="handleSuperQuery" />
      </template>
       <!--操作栏-->
      <template #action="{ record }">
        <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
      </template>
      <!--字段回显插槽-->
      <template v-slot:bodyCell="{ column, record, index, text }">
      </template>
    </BasicTable>
    <!-- 表单区域 -->
    <QaInfoModal @register="registerModal" @success="handleSuccess"></QaInfoModal>
  </div>
</template>
<script lang="ts" name="qaInfo-qaInfo" setup>
  import {ref, reactive, computed, unref} from 'vue';
  import {BasicTable, useTable, TableAction} from '/@/components/Table';
  import { useListPage } from '/@/hooks/system/useListPage'
  import {useModal} from '/@/components/Modal';
  import QaInfoModal from './components/QaInfoModal.vue'
  import {columns, searchFormSchema, superQuerySchema} from './QaInfo.data';
  import {list, deleteOne, batchDelete, getImportUrl,getExportUrl} from './QaInfo.api';
  import {downloadFile} from '/@/utils/common/renderUtils';
  import { useUserStore } from '/@/store/modules/user';
  import { useMessage } from '/@/hooks/web/useMessage';
  import { getDateByPicker } from '/@/utils';
  //日期个性化选择
  const fieldPickers = reactive({
  });
  const queryParam = reactive<any>({});
  const checkedKeys = ref<Array<string | number>>([]);
  const userStore = useUserStore();
  const { createMessage } = useMessage();
  //注册model
  const [registerModal, {openModal}] = useModal();
   //注册table数据
  const { prefixCls,tableContext,onExportXls,onImportXls } = useListPage({
      tableProps:{
           title: '提问回答表',
           api: list,
           columns,
           canResize:true,
           formConfig: {
                //labelWidth: 120,
                schemas: searchFormSchema,
                autoSubmitOnEnter:true,
                showAdvancedButton:true,
                fieldMapToNumber: [
                ],
                fieldMapToTime: [
                ],
            },
           actionColumn: {
               width: 120,
               fixed:'right'
           },
           beforeFetch: (params) => {
              if (params && fieldPickers) {
                for (let key in fieldPickers) {
                  if (params[key]) {
                    params[key] = getDateByPicker(params[key], fieldPickers[key]);
                  }
                }
              }
             return Object.assign(params, queryParam);
           },
        },
        exportConfig: {
            name:"提问回答表",
            url: getExportUrl,
            params: queryParam,
        },
        importConfig: {
            url: getImportUrl,
            success: handleSuccess
        },
    })
  const [registerTable, {reload},{ rowSelection, selectedRowKeys }] = tableContext
  // 高级查询配置
  const superQueryConfig = reactive(superQuerySchema);
  /**
   * 高级查询事件
   */
  function handleSuperQuery(params) {
    Object.keys(params).map((k) => {
      queryParam[k] = params[k];
    });
    reload();
  }
   /**
    * 新增事件
    */
  function handleAdd() {
     openModal(true, {
       isUpdate: false,
       showFooter: true,
     });
  }
   /**
    * 编辑事件
    */
  function handleEdit(record: Recordable) {
     openModal(true, {
       record,
       isUpdate: true,
       showFooter: true,
     });
   }
   /**
    * 详情
   */
  function handleDetail(record: Recordable) {
     openModal(true, {
       record,
       isUpdate: true,
       showFooter: false,
     });
   }
   /**
    * 删除事件
    */
  async function handleDelete(record) {
     await deleteOne({id: record.id}, handleSuccess);
   }
   /**
    * 批量删除事件
    */
  async function batchHandleDelete() {
     await batchDelete({ids: selectedRowKeys.value},handleSuccess);
   }
   /**
    * 成功回调
    */
  function handleSuccess() {
      (selectedRowKeys.value = []) && reload();
   }
   /**
      * 操作栏
      */
  function getTableAction(record){
       return [
         {
           label: '编辑',
           onClick: handleEdit.bind(null, record),
           auth: 'qaInfo:qa_info:edit'
         }
       ]
   }
  /**
   * 下拉操作栏
   */
  function getDropDownAction(record){
    return [
      {
        label: '详情',
        onClick: handleDetail.bind(null, record),
      }, {
        label: '删除',
        popConfirm: {
          title: '是否确认删除',
          confirm: handleDelete.bind(null, record),
          placement: 'topLeft'
        },
        auth: 'qaInfo:qa_info:delete'
      }
    ]
  }
</script>
<style lang="less" scoped>
  :deep(.ant-picker),:deep(.ant-input-number){
    width: 100%;
  }
</style>
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/V20251205_1__menu_insert_QaInfo.sql
New file
@@ -0,0 +1,40 @@
-- 注意:该页面对应的前台目录为views/qaInfo文件夹下
-- 如果你想更改到其他目录,请修改sql中component字段对应的值
-- 主菜单
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('176490565821901', NULL, '提问回答表', '/qaInfo/qaInfoList', 'qaInfo/QaInfoList', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 1, 0, 0, 0, 0, NULL, '1', 0, 0, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0);
-- 新增
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176490565821902', '176490565821901', '添加提问回答表', NULL, NULL, 0, NULL, NULL, 2, 'qaInfo:qa_info:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0, 0, '1', 0);
-- 编辑
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176490565821903', '176490565821901', '编辑提问回答表', NULL, NULL, 0, NULL, NULL, 2, 'qaInfo:qa_info:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0, 0, '1', 0);
-- 删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176490565821904', '176490565821901', '删除提问回答表', NULL, NULL, 0, NULL, NULL, 2, 'qaInfo:qa_info:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0, 0, '1', 0);
-- 批量删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176490565821905', '176490565821901', '批量删除提问回答表', NULL, NULL, 0, NULL, NULL, 2, 'qaInfo:qa_info:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0, 0, '1', 0);
-- 导出excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176490565821906', '176490565821901', '导出excel_提问回答表', NULL, NULL, 0, NULL, NULL, 2, 'qaInfo:qa_info:exportXls', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0, 0, '1', 0);
-- 导入excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('176490565821907', '176490565821901', '导入excel_提问回答表', NULL, NULL, 0, NULL, NULL, 2, 'qaInfo:qa_info:importExcel', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-12-05 11:34:18', NULL, NULL, 0, 0, '1', 0);
-- 角色授权(以 admin 角色为例,role_id 可替换)
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822008', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821901', NULL, '2025-12-05 11:34:18', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822009', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821902', NULL, '2025-12-05 11:34:18', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822010', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821903', NULL, '2025-12-05 11:34:18', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822011', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821904', NULL, '2025-12-05 11:34:18', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822012', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821905', NULL, '2025-12-05 11:34:18', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822013', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821906', NULL, '2025-12-05 11:34:18', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('176490565822014', 'f6817f48af4fb3af11b9e8bf182f618b', '176490565821907', NULL, '2025-12-05 11:34:18', '127.0.0.1');
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/components/QaInfoForm.vue
New file
@@ -0,0 +1,118 @@
<template>
  <div>
    <BasicForm @register="registerForm" ref="formRef"/>
    <!-- 子表单区域 -->
    <a-tabs v-model:activeKey="activeKey" animated  @change="handleChangeTabs">
      <a-tab-pane tab="关联文件表" key="relatedArticle" :forceRender="true">
        <JVxeTable
          keep-source
          resizable
          ref="relatedArticle"
          v-if="relatedArticleTable.show"
          :loading="relatedArticleTable.loading"
          :columns="relatedArticleTable.columns"
          :dataSource="relatedArticleTable.dataSource"
          :height="340"
          :rowNumber="true"
          :rowSelection="true"
          :disabled="formDisabled"
          :toolbar="true"
        />
      </a-tab-pane>
    </a-tabs>
    <div style="width: 100%;text-align: center" v-if="!formDisabled">
      <a-button @click="handleSubmit" pre-icon="ant-design:check" type="primary">提 交</a-button>
    </div>
  </div>
</template>
<script lang="ts">
  import {BasicForm, useForm} from '/@/components/Form/index';
  import { computed, defineComponent, reactive, ref, unref } from 'vue';
  import {defHttp} from '/@/utils/http/axios';
  import { propTypes } from '/@/utils/propTypes';
  import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods';
  import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils';
  import {getBpmFormSchema,relatedArticleColumns} from '../QaInfo.data';
  import {saveOrUpdate,relatedArticleList} from '../QaInfo.api';
  export default defineComponent({
    name: "QaInfoForm",
    components:{
      BasicForm,
    },
    props:{
      formData: propTypes.object.def({}),
      formBpm: propTypes.bool.def(true),
    },
    setup(props){
      const [registerForm, { setFieldsValue, setProps }] = useForm({
        labelWidth: 150,
        schemas: getBpmFormSchema(props.formData),
        showActionButtonGroup: false,
        baseColProps: {span: 24}
      });
      const formDisabled = computed(()=>{
        if(props.formData.disabled === false){
          return false;
        }
        return true;
      });
      const refKeys = ref(['relatedArticle', ]);
      const activeKey = ref('relatedArticle');
      const relatedArticle = ref();
      const tableRefs = {relatedArticle, };
      const relatedArticleTable = reactive({
        loading: false,
        dataSource: [],
        columns:relatedArticleColumns,
        show: false
      })
      const [handleChangeTabs,handleSubmit,requestSubTableData,formRef] = useJvxeMethod(requestAddOrEdit,classifyIntoFormData,tableRefs,activeKey,refKeys,validateSubForm);
      function classifyIntoFormData(allValues) {
        let main = Object.assign({}, allValues.formValue)
        return {
          ...main, // 展开
          relatedArticleList: allValues.tablesValue[0].tableData,
        }
      }
      //表单提交事件
      async function requestAddOrEdit(values) {
        await saveOrUpdate(values, true);
      }
      const queryByIdUrl = '/qaInfo/qaInfo/queryById';
      async function initFormData(){
        let params = {id: props.formData.dataId};
        const data = await defHttp.get({url: queryByIdUrl, params});
        //设置表单的值
        await setFieldsValue({...data});
        requestSubTableData(relatedArticleList, {id: data.id}, relatedArticleTable, ()=>{
          relatedArticleTable.show = true;
        });
        //默认是禁用
        await setProps({disabled: formDisabled.value})
      }
      initFormData();
      return {
        registerForm,
        formDisabled,
        formRef,
        handleSubmit,
        activeKey,
        handleChangeTabs,
        relatedArticle,
        relatedArticleTable,
      }
    }
  });
</script>
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/qaInfo/vue3/components/QaInfoModal.vue
New file
@@ -0,0 +1,147 @@
<template>
  <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit">
    <BasicForm @register="registerForm" ref="formRef" name="QaInfoForm"/>
    <!-- 子表单区域 -->
    <a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
      <a-tab-pane tab="关联文件表" key="relatedArticle" :forceRender="true">
        <JVxeTable
          keep-source
          resizable
          ref="relatedArticle"
          :loading="relatedArticleTable.loading"
          :columns="relatedArticleTable.columns"
          :dataSource="relatedArticleTable.dataSource"
          :height="340"
          :rowNumber="true"
          :rowSelection="true"
          :disabled="formDisabled"
          :toolbar="true"
          />
      </a-tab-pane>
    </a-tabs>
  </BasicModal>
</template>
<script lang="ts" setup>
    import {ref, computed, unref,reactive} from 'vue';
    import {BasicModal, useModalInner} from '/@/components/Modal';
    import {BasicForm, useForm} from '/@/components/Form/index';
    import { JVxeTable } from '/@/components/jeecg/JVxeTable'
    import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods.ts'
    import {formSchema,relatedArticleColumns} from '../QaInfo.data';
    import {saveOrUpdate,relatedArticleList} from '../QaInfo.api';
    import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils'
    import { useMessage } from '/@/hooks/web/useMessage';
    import { getDateByPicker } from '/@/utils';
    //日期个性化选择
    const fieldPickers = reactive({
    });
      const relatedArticleFieldPickers = reactive({
      });
    const { createMessage } = useMessage();
    // Emits声明
    const emit = defineEmits(['register','success']);
    const isUpdate = ref(true);
    const formDisabled = ref(false);
    const refKeys = ref(['relatedArticle', ]);
    const activeKey = ref('relatedArticle');
    const relatedArticle = ref();
    const tableRefs = {relatedArticle, };
    const relatedArticleTable = reactive({
          loading: false,
          dataSource: [],
          columns:relatedArticleColumns
    })
    //表单配置
    const [registerForm, {setProps,resetFields, setFieldsValue, validate}] = useForm({
        labelWidth: 150,
        schemas: formSchema,
        showActionButtonGroup: false,
        baseColProps: {span: 24}
    });
     //表单赋值
    const [registerModal, {setModalProps, closeModal}] = useModalInner(async (data) => {
        //重置表单
        await reset();
        setModalProps({confirmLoading: false,showCancelBtn:data?.showFooter,showOkBtn:data?.showFooter});
        isUpdate.value = !!data?.isUpdate;
        formDisabled.value = !data?.showFooter;
        if (unref(isUpdate)) {
            //表单赋值
            await setFieldsValue({
                ...data.record,
            });
             requestSubTableData(relatedArticleList, {id:data?.record?.id}, relatedArticleTable)
        }
        // 隐藏底部时禁用整个表单
       setProps({ disabled: !data?.showFooter })
    });
    //方法配置
    const [handleChangeTabs,handleSubmit,requestSubTableData,formRef] = useJvxeMethod(requestAddOrEdit,classifyIntoFormData,tableRefs,activeKey,refKeys);
    //设置标题
    const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(formDisabled) ? '编辑' : '详情'));
    async function reset(){
      await resetFields();
      activeKey.value = 'relatedArticle';
      relatedArticleTable.dataSource = [];
    }
    function classifyIntoFormData(allValues) {
         let main = Object.assign({}, allValues.formValue)
         return {
           ...main, // 展开
           relatedArticleList: allValues.tablesValue[0].tableData,
         }
       }
    //表单提交事件
    async function requestAddOrEdit(values) {
        try {
            // 预处理日期数据
            changeDateValue(values);
            setModalProps({confirmLoading: true});
            //提交表单
            await saveOrUpdate(values, isUpdate.value);
            //关闭弹窗
            closeModal();
            //刷新列表
            emit('success');
        } finally {
            setModalProps({confirmLoading: false});
        }
    }
    /**
     * 处理日期值
     * @param formData 表单数据
     */
    const changeDateValue = (formData) => {
      if (formData && fieldPickers) {
          for (let key in fieldPickers) {
              if (formData[key]) {
                  formData[key] = getDateByPicker(formData[key], fieldPickers[key]);
              }
          }
      }
      if(formData && formData.relatedArticleList && formData.relatedArticleList.length > 0){
          formData.relatedArticleList.forEach(subFormData=>{
              for (let key in relatedArticleFieldPickers) {
                  if (subFormData[key]) {
                      subFormData[key] = getDateByPicker(subFormData[key], relatedArticleFieldPickers[key]);
                  }
              }
          })
      }
    };
</script>
<style lang="less" scoped>
    /** 时间和数字输入框样式 */
  :deep(.ant-input-number) {
    width: 100%;
  }
  :deep(.ant-calendar-picker) {
    width: 100%;
  }
</style>