办学质量监测教学评价系统
shenrongliang
14 小时以前 fa1f70a074ab87d1dc9876bb77ad841e391564c9
Merge remote-tracking branch 'origin/main'

# Conflicts:
# ruoyi-modules/pom.xml
已修改10个文件
已添加36个文件
3018 ■■■■■ 文件已修改
pom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatSession.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/pom.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/pom.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerTemplateController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplate.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerTemplateMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerTemplateService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerTemplateServiceImpl.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerMapper.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerTemplateMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-services/pom.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/package.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/template/index.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/template/model.d.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/bootstrap.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/services/flowableService.js 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/data.ts 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/index.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/serivceDialog.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/system/process/index.vue 472 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/dynamicForm/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue 337 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/template/data.tsx 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/template/index.vue 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/template/template-drawer.vue 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/work/statistics/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -308,7 +308,16 @@
                <artifactId>ruoyi-chat</artifactId>
                <version>${revision}</version>
            </dependency>
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>sc-services</artifactId>
                <version>${revision}</version>
            </dependency>
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>sc-page-designer</artifactId>
                <version>${revision}</version>
            </dependency>
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>ruoyi-knowledge-api</artifactId>
ruoyi-admin/pom.xml
@@ -61,6 +61,16 @@
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>sc-services</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>sc-page-designer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-generator</artifactId>
        </dependency>
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatSession.java
@@ -1,5 +1,6 @@
package org.ruoyi.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -16,7 +17,7 @@
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_session")
@TableName("CHAT_SESSION")
public class ChatSession extends BaseEntity {
    @Serial
@@ -25,28 +26,30 @@
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    @TableId(value = "ID")
    private Long id;
    /**
     * ç”¨æˆ·id
     * ç”¨æˆ·ID
     */
    @TableField("USER_ID")
    private Long userId;
    /**
     * ä¼šè¯æ ‡é¢˜
     */
    @TableField("SESSION_TITLE")
    private String sessionTitle;
    /**
     * ä¼šè¯å†…容
     */
    @TableField("SESSION_CONTENT")
    private String sessionContent;
    /**
     * å¤‡æ³¨
     */
    @TableField("REMARK")
    private String remark;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java
@@ -17,7 +17,7 @@
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_info")
@TableName("KNOWLEDGE_INFO")
public class KnowledgeInfo extends BaseEntity {
    @Serial
@@ -27,6 +27,7 @@
     * ä¸»é”®ID
     */
    @TableId(value = "ID")
    @TableField("ID")
    private Long id;
    /**
ruoyi-modules/pom.xml
@@ -22,6 +22,8 @@
        <module>ruoyi-system</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-flowable</module>
        <module>sc-services</module>
        <module>sc-page-designer</module>
    </modules>
    <properties>
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
@@ -177,7 +177,8 @@
  @Transactional(rollbackFor = Exception.class)
  public void removeKnowledge(String id) {
    Map<String,Object> map = new HashMap<>();
    KnowledgeInfo knowledgeInfo = baseMapper.selectById(id);
    KnowledgeInfo knowledgeInfo = baseMapper.selectOne(new LambdaQueryWrapper<KnowledgeInfo>()
            .eq(KnowledgeInfo::getKid, id));
    check(knowledgeInfo);
    map.put("kid",knowledgeInfo.getKid());
    // åˆ é™¤å‘量数据
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java
@@ -322,4 +322,15 @@
        return R.ok(trees);
    }
    /**
     * èŽ·å–éƒ¨é—¨ä¸‹çš„æ‰€æœ‰ç”¨æˆ·ä¿¡æ¯
     */
    @SaCheckPermission("system:user:list")
    @GetMapping("/list/dept/{deptId}")
    public R<List<SysUserVo>> listByDeptId(@PathVariable Long deptId) {
        List<SysUserVo> sysUserVos = userService.selectUserList(new SysUserBo());
        return R.ok(sysUserVos.stream()
                .filter(user -> deptId.equals(user.getDeptId()))
                .collect(Collectors.toList()));
    }
}
ruoyi-modules/sc-page-designer/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>sc-page-designer</artifactId>
    <description>
        school-ai页面设计器模块
    </description>
    <dependencies>
        <!-- é€šç”¨å·¥å…·-->
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-common-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-system-api</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package org.ruoyi.pageDesigner.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerDTO;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
import org.ruoyi.pageDesigner.service.PageDesignerService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 14:41:24
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/page-designer")
public class PageDesignerController extends BaseController {
    private final PageDesignerService pageDesignerService;
    /**
     * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
     */
    @GetMapping("/list")
    public TableDataInfo<PageDesignerVo> list(PageDesignerDTO pageDesignerDTO, PageQuery pageQuery) {
        return pageDesignerService.selectPagelistAll(pageDesignerDTO, pageQuery);
    }
    /**
     * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
     */
    @GetMapping("/{id}")
    public R<PageDesigner> getInfo(@PathVariable Long id) {
        return R.ok(pageDesignerService.getDetail(id));
    }
    /**
     * æ–°å¢žé¡µé¢è®¾è®¡
     */
    @PostMapping
    public R<Void> add(@Valid @RequestBody PageDesignerDTO dto) {
        pageDesignerService.add(dto);
        return R.ok("新增成功");
    }
    /**
     * ä¿®æ”¹é¡µé¢è®¾è®¡
     */
    @PutMapping
    public R<Void> update(@Valid @RequestBody PageDesignerDTO dto) {
        pageDesignerService.updatePage(dto);
        return R.ok("修改成功");
    }
    /**
     * åˆ é™¤é¡µé¢è®¾è®¡
     */
    @DeleteMapping
    public R<Void> remove(@RequestBody List<Long> ids) {
        pageDesignerService.deleteByIds(ids);
        return R.ok("删除成功");
    }
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerTemplateController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package org.ruoyi.pageDesigner.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.*;
import org.ruoyi.pageDesigner.service.PageDesignerTemplateService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 14:41:24
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/page-designer-template")
public class PageDesignerTemplateController extends BaseController {
    private final PageDesignerTemplateService service;
    /**
     * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
     */
    @GetMapping("/list")
    public TableDataInfo<PageDesignerTemplateVo> list(PageDesignerTemplateDTO dto, PageQuery pageQuery) {
        return service.selectPagelistAll(dto, pageQuery);
    }
    /**
     * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
     */
    @GetMapping("/{id}")
    public R<PageDesignerTemplate> getInfo(@PathVariable Long id) {
        return R.ok(service.getDetail(id));
    }
    /**
     * æ–°å¢žé¡µé¢è®¾è®¡
     */
    @PostMapping
    public R<Void> add(@Valid @RequestBody PageDesignerTemplateDTO dto) {
        service.add(dto);
        return R.ok("新增成功");
    }
    /**
     * ä¿®æ”¹é¡µé¢è®¾è®¡
     */
    @PutMapping
    public R<Void> update(@Valid @RequestBody PageDesignerTemplateDTO dto) {
        service.updatePage(dto);
        return R.ok("修改成功");
    }
    /**
     * åˆ é™¤é¡µé¢è®¾è®¡
     */
    @DeleteMapping
    public R<Void> remove(@RequestBody List<Long> ids) {
        service.deleteByIds(ids);
        return R.ok("删除成功");
    }
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package org.ruoyi.pageDesigner.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:24:05
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("PAGE_DESIGNER")
public class PageDesigner extends BaseEntity {
    @TableId(value = "ID")
    private Long id;
    @TableField("NAME")
    private String name;
    @TableField("MENU_ID")
    private String menuId; // å…³è”的菜单ID
    @TableField("MENU_PARENT_ID")
    private String menuParentId; // å…³è”的菜单父ID
    @TableField("STATUS")
    private String status;
    @TableField(value = "DEL_FLAG")
    @TableLogic
    private String delFlag;
    @TableField("REMARK")
    private String remark;
    @TableField("FORM_JSON")
    private String formJson;
    @TableField("SHOW_COLUMN")
    private String showColumn; // å¯ä»¥å°† selectedFields å­˜å‚¨ä¸º JSON å­—符串
    @TableField("ACTIONS_FUNC")
    private String actionsFunc;  // å¯ä»¥å°† enableActions å­˜å‚¨ä¸º JSON å­—符串
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = PageDesigner.class, reverseConvertGenerate = false)
public class PageDesignerDTO extends BaseEntity {
    private Long id; // ä¿®æ”¹æ—¶ä¼ å…¥
    private String name;       // é¡µé¢åç§°
    private String menuId;     // å…³è”菜单 ID
    private String status;     // å¯ç”¨çŠ¶æ€
    private String remark;     // å¤‡æ³¨
    private String formJson;   // è¡¨å•设计 JSON å­—符串
    private String showColumn;   // å­—段显示配置
    private String actionsFunc;  // å¯ç”¨çš„æ“ä½œé¡¹ï¼ˆå¢žåˆ æ”¹æŸ¥ï¼‰
    private String menuParentId;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplate.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.ruoyi.pageDesigner.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:24:05
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("PAGE_DESIGNER_TEMPLATE")
public class PageDesignerTemplate extends BaseEntity {
    @TableId(value = "ID")
    private Long id;
    @TableField(value = "PAGE_ID")
    private Long pageId;
    @TableField("FORM_DATA")
    private String formData;
    @TableField(value = "DEL_FLAG")
    @TableLogic
    private String delFlag;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = PageDesignerTemplate.class, reverseConvertGenerate = false)
public class PageDesignerTemplateDTO extends BaseEntity {
    private Long id;
    private Long pageId;
    private String formData;
    private String delFlag;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serializable;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@AutoMapper(target = PageDesignerTemplate.class)
public class PageDesignerTemplateVo implements Serializable {
    private Long id;
    private Long pageId;
    private String formData;
    private String delFlag;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.system.domain.vo.SysMenuVo;
import java.io.Serializable;
import java.util.Date;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@AutoMapper(target = PageDesigner.class)
public class PageDesignerVo implements Serializable {
    private Long id; // ä¿®æ”¹æ—¶ä¼ å…¥
    private String name;       // é¡µé¢åç§°
    private String status;     // å¯ç”¨çŠ¶æ€
    private String remark;     // å¤‡æ³¨
    private String menuParentId; // èœå•父ID
    private Date createTime; // åˆ›å»ºæ—¶é—´
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package org.ruoyi.pageDesigner.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
/**
 * @author kanglujie
 * @date 2025-06-23 17:34:48
 */
@Mapper
public interface PageDesignerMapper extends BaseMapper<PageDesigner> {
    Page<PageDesignerVo> selectPagelistAll(@Param("page") Page<PageDesigner> page, @Param(Constants.WRAPPER) Wrapper<PageDesigner> queryWrapper);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerTemplateMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package org.ruoyi.pageDesigner.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.ruoyi.pageDesigner.domain.PageDesignerTemplate;
import org.ruoyi.pageDesigner.domain.PageDesignerTemplateVo;
/**
 * @author kanglujie
 * @date 2025-06-23 17:34:48
 */
@Mapper
public interface PageDesignerTemplateMapper extends BaseMapper<PageDesignerTemplate> {
    Page<PageDesignerTemplateVo> selectPagelistAll(@Param("page") Page<PageDesignerTemplate> page, @Param(Constants.WRAPPER) Wrapper<PageDesignerTemplate> queryWrapper);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package org.ruoyi.pageDesigner.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerDTO;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 17:35:56
 */
public interface PageDesignerService extends IService<PageDesigner> {
    void add(PageDesignerDTO dto);
    void updatePage(PageDesignerDTO dto);
    List<PageDesigner> listAll(String keyword);
    PageDesigner getDetail(Long id);
    void deleteByIds(List<Long> ids);
    TableDataInfo<PageDesignerVo> selectPagelistAll(PageDesignerDTO pageDesignerDTO, PageQuery pageQuery);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerTemplateService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.ruoyi.pageDesigner.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.*;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 17:35:56
 */
public interface PageDesignerTemplateService extends IService<PageDesignerTemplate> {
    void add(PageDesignerTemplateDTO dto);
    void updatePage(PageDesignerTemplateDTO dto);
    List<PageDesignerTemplate> listAll(String keyword);
    PageDesignerTemplate getDetail(Long id);
    void deleteByIds(List<Long> ids);
    TableDataInfo<PageDesignerTemplateVo> selectPagelistAll(PageDesignerTemplateDTO dto, PageQuery pageQuery);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package org.ruoyi.pageDesigner.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.amazonaws.util.json.Jackson;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.UserConstants;
import org.ruoyi.common.core.utils.StreamUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.helper.DataBaseHelper;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerDTO;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
import org.ruoyi.pageDesigner.mapper.PageDesignerMapper;
import org.ruoyi.pageDesigner.service.PageDesignerService;
import org.ruoyi.system.domain.SysMenu;
import org.ruoyi.system.mapper.SysMenuMapper;
import org.ruoyi.system.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
 * @author kanglujie
 * @date 2025-06-23 17:36:29
 */
@Service
@RequiredArgsConstructor
public class PageDesignerServiceImpl extends ServiceImpl<PageDesignerMapper, PageDesigner>
        implements PageDesignerService {
    private final SysMenuMapper menuMapper;
    @Override
    public void add(PageDesignerDTO dto) {
        SysMenu menu = new SysMenu();
        menu.setMenuName(dto.getName());
        menu.setParentId(Long.valueOf(dto.getMenuParentId()));
        menu.setOrderNum(999);
        menu.setComponent("tool/template/index");
        menu.setIsFrame("1");
        menu.setIsCache("0");
        menu.setMenuType("C"); // C:目录, M:菜单, F:按钮
        menu.setVisible("0");
        menu.setStatus(dto.getStatus());
        menuMapper.insert(menu);
        PageDesigner entity = convertToEntity(dto);
        entity.setMenuId(menu.getMenuId().toString());
        this.save(entity);
        menu.setPath(String.valueOf(entity.getId()));
        menuMapper.updateById(menu);
    }
    @Override
    public void updatePage(PageDesignerDTO dto) {
        PageDesigner entity = convertToEntity(dto);
        this.updateById(entity);
        PageDesigner byId = this.getById(entity.getId());
        SysMenu menu = new SysMenu();
        menu.setMenuId(Long.valueOf(byId.getMenuId()));
        menu.setStatus(entity.getStatus());
        menu.setParentId(Long.valueOf(entity.getMenuParentId()));
        menuMapper.updateById(menu);
    }
    @Override
    public TableDataInfo<PageDesignerVo> selectPagelistAll(PageDesignerDTO pageDesignerDTO, PageQuery pageQuery) {
        Page<PageDesignerVo> page = baseMapper.selectPagelistAll(pageQuery.build(), this.buildQueryWrapper(pageDesignerDTO));
        return TableDataInfo.build(page);
    }
    private Wrapper<PageDesigner> buildQueryWrapper(PageDesignerDTO pageDesignerDTO) {
        Map<String, Object> params = pageDesignerDTO.getParams();
        QueryWrapper<PageDesigner> wrapper = Wrappers.query();
        wrapper.eq("del_flag", UserConstants.USER_NORMAL)
                .eq(ObjectUtil.isNotNull(pageDesignerDTO.getId()), "id", pageDesignerDTO.getId())
                .like(StringUtils.isNotBlank(pageDesignerDTO.getName()), "name", pageDesignerDTO.getName())
                .eq(StringUtils.isNotBlank(pageDesignerDTO.getStatus()), "status", pageDesignerDTO.getStatus())
                .between(params.get("beginTime") != null && params.get("endTime") != null,
                        "create_time", params.get("beginTime"), params.get("endTime"))
        ;
        return wrapper;
    }
    @Override
    public List<PageDesigner> listAll(String keyword) {
        LambdaQueryWrapper<PageDesigner> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(keyword != null, PageDesigner::getName, keyword);
        return this.list(wrapper);
    }
    @Override
    public PageDesigner getDetail(Long id) {
        return this.getById(id);
    }
    @Override
    public void deleteByIds(List<Long> ids) {
        List<PageDesigner> designers = this.listByIds(ids);
        for (PageDesigner designer : designers) {
            Long id = designer.getId();
            this.removeById(id);
            Long menuId = Long.valueOf(designer.getMenuId());
            if (menuId != null) {
                menuMapper.deleteById(menuId);
            }
        }
    }
    private PageDesigner convertToEntity(PageDesignerDTO dto) {
        PageDesigner entity = new PageDesigner();
        entity.setId(dto.getId());
        entity.setName(dto.getName());
        entity.setMenuId(dto.getMenuId());
        entity.setStatus(dto.getStatus());
        entity.setRemark(dto.getRemark());
        entity.setFormJson(dto.getFormJson());
        entity.setShowColumn(dto.getShowColumn());
        entity.setActionsFunc(dto.getActionsFunc());
        entity.setMenuParentId(dto.getMenuParentId());
        return entity;
    }
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerTemplateServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
package org.ruoyi.pageDesigner.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.UserConstants;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.*;
import org.ruoyi.pageDesigner.mapper.PageDesignerMapper;
import org.ruoyi.pageDesigner.mapper.PageDesignerTemplateMapper;
import org.ruoyi.pageDesigner.service.PageDesignerService;
import org.ruoyi.pageDesigner.service.PageDesignerTemplateService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 17:36:29
 */
@Service
@RequiredArgsConstructor
public class PageDesignerTemplateServiceImpl extends ServiceImpl<PageDesignerTemplateMapper, PageDesignerTemplate>
        implements PageDesignerTemplateService {
    private final ObjectMapper objectMapper;
    @Override
    public void add(PageDesignerTemplateDTO dto) {
        PageDesignerTemplate entity = convertToEntity(dto);
        this.save(entity);
    }
    @Override
    public void updatePage(PageDesignerTemplateDTO dto) {
        PageDesignerTemplate entity = convertToEntity(dto);
        this.updateById(entity);
    }
    @Override
    public TableDataInfo<PageDesignerTemplateVo> selectPagelistAll(PageDesignerTemplateDTO dto, PageQuery pageQuery) {
        Page<PageDesignerTemplateVo> page = baseMapper.selectPagelistAll(pageQuery.build(), this.buildQueryWrapper(dto));
        return TableDataInfo.build(page);
    }
    private Wrapper<PageDesignerTemplate> buildQueryWrapper(PageDesignerTemplateDTO dto) {
        //Map<String, Object> params = pageDesignerDTO.getParams();
        QueryWrapper<PageDesignerTemplate> wrapper = Wrappers.query();
        wrapper.eq("del_flag", UserConstants.USER_NORMAL)
                .eq(ObjectUtil.isNotNull(dto.getPageId()), "page_id", dto.getPageId())
        //        .like(StringUtils.isNotBlank(pageDesignerDTO.getName()), "name", pageDesignerDTO.getName())
        //        .eq(StringUtils.isNotBlank(pageDesignerDTO.getStatus()), "status", pageDesignerDTO.getStatus())
        //        .between(params.get("beginTime") != null && params.get("endTime") != null,
        //                "create_time", params.get("beginTime"), params.get("endTime"))
        ;
        return wrapper;
    }
    @Override
    public List<PageDesignerTemplate> listAll(String keyword) {
        LambdaQueryWrapper<PageDesignerTemplate> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(keyword != null, PageDesignerTemplate::getPageId, keyword);
        return this.list(wrapper);
    }
    @Override
    public PageDesignerTemplate getDetail(Long id) {
        return this.getById(id);
    }
    @Override
    public void deleteByIds(List<Long> ids) {
        this.removeByIds(ids);
    }
    private PageDesignerTemplate convertToEntity(PageDesignerTemplateDTO dto) {
        PageDesignerTemplate entity = new PageDesignerTemplate();
        entity.setId(dto.getId());
        entity.setFormData(dto.getFormData());
        entity.setDelFlag(dto.getDelFlag());
        entity.setPageId(dto.getPageId());
        return entity;
    }
}
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
<?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.ruoyi.pageDesigner.mapper.PageDesignerMapper">
    <!-- å¤šç»“构嵌套自动映射需带上每个实体的主键id å¦åˆ™æ˜ å°„会失败 -->
    <resultMap type="org.ruoyi.pageDesigner.domain.PageDesignerVo" id="PageDesignerResult">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="status" column="status"/>
        <result property="createTime" column="create_time"/>
        <result property="menuParentId" column="menu_parent_id"/>
        <result property="remark" column="remark"/>
    </resultMap>
    <select id="selectPagelistAll" resultMap="PageDesignerResult">
        select
            id,
            name,
            status,
            create_time,
            menu_parent_id,
            remark
        from page_designer
        ${ew.getCustomSqlSegment}
    </select>
</mapper>
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerTemplateMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
<?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.ruoyi.pageDesigner.mapper.PageDesignerTemplateMapper">
    <!-- ç»“果映射 -->
    <resultMap type="org.ruoyi.pageDesigner.domain.PageDesignerTemplateVo" id="PageDesignerTemplateResult">
        <id property="id" column="id"/>
        <result property="pageId" column="page_id"/>
        <result property="formData" column="form_data"/>
        <result property="delFlag" column="del_flag"/>
    </resultMap>
    <select id="selectPagelistAll" resultMap="PageDesignerTemplateResult">
        SELECT
        id,
        page_id,
        form_data,
        del_flag
        FROM page_designer_template
        <where>
            ${ew.sqlSegment}
        </where>
    </select>
</mapper>
ruoyi-modules/sc-services/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>sc-services</artifactId>
    <description>
        school-ai业务模块
    </description>
    <dependencies>
    <!-- é€šç”¨å·¥å…·-->
    <dependency>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-common-core</artifactId>
    </dependency>
    </dependencies>
</project>
ruoyi-ui/apps/web-antd/package.json
@@ -46,14 +46,18 @@
    "@vben/utils": "workspace:*",
    "@vueuse/core": "catalog:",
    "ant-design-vue": "catalog:",
    "axios": "^1.10.0",
    "bpmn-js": "^18.6.2",
    "cropperjs": "^1.6.2",
    "crypto-js": "^4.2.0",
    "dayjs": "catalog:",
    "diagram-js": "^15.3.0",
    "echarts": "^5.5.1",
    "element-plus": "^2.10.2",
    "jsencrypt": "^3.3.2",
    "lodash-es": "^4.17.21",
    "pinia": "catalog:",
    "qs": "^6.13.1",
    "tinymce": "^7.3.0",
    "unplugin-vue-components": "^0.27.3",
    "vue": "catalog:",
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import { requestClient } from '#/api/request';
import type {
  PageDesigner
} from './model';
enum Api {
  pageAdd = '/page-designer',
  pageUpdate = '/page-designer',
  pageList = '/page-designer/list',
  pageInfo = '/page-designer',
  pageRemove = '/page-designer',
}
/**
 * æ–°å¢žé¡µé¢è®¾è®¡
 */
export function pageAdd(data: any) {
  return requestClient.post(Api.pageAdd, data);
}
/**
 * ä¿®æ”¹é¡µé¢è®¾è®¡
 */
export function pageUpdate(data: any) {
  return requestClient.put(Api.pageUpdate, data);
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
 */
export function pageList(params?: any) {
    return requestClient.get<{
      total: number;
      rows: PageDesigner[];
      code: number;
      msg: string;
    }>(Api.pageList, { params });
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
 */
export function pageInfo(id: string | number) {
  return requestClient.get<PageDesigner>(`${Api.pageInfo}/${id}`);
}
/**
 * åˆ é™¤é¡µé¢è®¾è®¡
 */
export function pageRemove(ids: string[] | number[]) {
  return requestClient.delete(Api.pageRemove, { data: ids });
}
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
export interface PageDesigner {
  id?: string | number;          // ä¸»é”®ï¼Œç¼–辑时用
  name: string;                  // é¡µé¢åç§°
  menuId: string | number | null; // ä¸Šçº§ç›®å½•(菜单ID)
  parentId: string | number | null; // çˆ¶çº§ID
  status: string;                // çŠ¶æ€
  remark?: string | null;        // å¤‡æ³¨
  formJson?: string | null;      // è¡¨å•设计JSON
  showColumn?: string | null;    // æ˜¾ç¤ºå­—段(JSON字符串)
  actionsFunc?: string | null;   // å¯ç”¨åŠŸèƒ½(JSON字符串)
  createTime?: string;           // åˆ›å»ºæ—¶é—´
  updateTime?: string;           // æ›´æ–°æ—¶é—´
  menu?: {                       // å…³è”的菜单信息
    menuId: number;
    menuName: string;
  } | null;
  parent?: {                     // å…³è”的父级信息
    id: string | number;
    name: string;
  } | null;
}
ruoyi-ui/apps/web-antd/src/api/tool/template/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
import { requestClient } from '#/api/request';
import type { Template } from './model';
enum Api {
  templateAdd = '/page-designer-template',
  templateUpdate = '/page-designer-template',
  templateList = '/page-designer-template/list',
  templateInfo = '/page-designer-template',
  templateRemove = '/page-designer-template',
}
/**
 * æ–°å¢žé¡µé¢è®¾è®¡
 */
export function templateAdd(data: any) {
  return requestClient.post(Api.templateAdd, data);
}
/**
 * ä¿®æ”¹é¡µé¢è®¾è®¡
 */
export function templateUpdate(data: any) {
  return requestClient.put(Api.templateUpdate, data);
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
 */
export function templateList(params?: any) {
  return requestClient.get(Api.templateList, { params });
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
 */
export function templateInfo(id: string | number) {
  return requestClient.get(`${Api.templateInfo}/${id}`);
}
/**
 * åˆ é™¤é¡µé¢è®¾è®¡
 */
export function templateRemove(ids: string[] | number[]) {
  return requestClient.delete(Api.templateRemove, { data: ids });
}
ruoyi-ui/apps/web-antd/src/api/tool/template/model.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
export interface Template {
  id?: string | number;          // ä¸»é”®
  formData: string;                  // æ¨¡ç‰ˆåç§°
  pageDesignId: string | number; // å…³è”的页面设计ID
  createTime?: string;           // åˆ›å»ºæ—¶é—´
  updateTime?: string;           // æ›´æ–°æ—¶é—´
  pageDesign?: {                 // å…³è”的页面设计信息
    id: string | number;
    name: string;
    formJson?: string;
    showColumn?: string;
    actionsFunc?: string;
  } | null;
}
ruoyi-ui/apps/web-antd/src/bootstrap.ts
@@ -19,7 +19,10 @@
import { router } from './router';
import formCreate from '@form-create/element-ui';
import FcDesigner from '@form-create/designer';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
async function bootstrap(namespace: string) {
  // åˆå§‹åŒ–组件适配器
  await initComponentAdapter();
@@ -62,6 +65,7 @@
  app.use(ElementPlus);
  app.use(formCreate);
  app.use(FcDesigner);
  app.use(Antd);
  // åŠ¨æ€æ›´æ–°æ ‡é¢˜
  watchEffect(() => {
    if (preferences.app.dynamicTitle) {
ruoyi-ui/apps/web-antd/src/services/flowableService.js
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import type { VxeGridProps } from 'src/adapter/vxe-table';
import type { FormSchemaGetter } from "src/adapter/form";
export const querySchema: FormSchemaGetter = () => [
  {
    component: 'Input',
    fieldName: 'roleName',
    label: '名称',
  },
  {
    component: 'Select',
    fieldName: 'roleSort',
    label: '创建人',
  },
  {
    component: 'RangePicker',
    fieldName: 'createTime',
    label: '创建时间',
  },
];
export const columns: VxeGridProps['columns'] = [
  { type: 'checkbox', width: 60 },
  {
    title: '名称',
    field: 'roleName',
  },
  {
    title: '创建人',
    field: 'roleSort',
  },
  {
    title: '创建时间',
    field: 'createTime',
  },
  {
    field: 'action',
    fixed: 'right',
    slots: { default: 'action' },
    title: '操作',
    width: 180,
  },
];
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<template>
  <Page :auto-content-height="true">
    <BasicTable table-title="工作考核指标列表">
      <template #toolbar-tools>
        <Space>
          <a-button
            v-access:code="['system:role:export']"
            @click="handleDownloadExcel"
            style="margin-right: 10px"
          >
            {{ $t('pages.common.export') }}
          </a-button>
          <a-button
            type="primary"
            v-access:code="['system:role:add']"
            @click="handleAdd"
          >
            {{ $t('pages.common.add') }}
          </a-button>
        </Space>
      </template>
    </BasicTable>
  </Page>
</template>
<script setup lang="ts">
import type { VxeGridProps } from "#/adapter/vxe-table";
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import type { VbenFormProps } from '@vben/common-ui';
import type { roleList} from "#/api/system/role";
import { columns, querySchema } from './data';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
const formOptions: VbenFormProps = {
  commonConfig: {
    labelWidth: 80,
    componentProps: {
      allowClear: true,
    },
  },
  schema: querySchema(),
  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
  // æ—¥æœŸé€‰æ‹©æ ¼å¼åŒ–
  fieldMappingTime: [
    [
      'createTime',
      ['params[beginTime]', 'params[endTime]'],
      ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
    ],
  ],
};
const gridOptions: VxeGridProps = {
  checkboxConfig: {
    // é«˜äº®
    highlight: true,
    // ç¿»é¡µæ—¶ä¿ç•™é€‰ä¸­çŠ¶æ€
    reserve: true,
    // ç‚¹å‡»è¡Œé€‰ä¸­
    // trigger: 'row',
    checkMethod: ({ row }) => row.roleId !== 1,
  },
  columns,
  height: 'auto',
  keepSource: true,
  pagerConfig: {},
  proxyConfig: {
    ajax: {
      query: async ({ page }, formValues = {}) => {
        return await roleList({
          pageNum: page.currentPage,
          pageSize: page.pageSize,
          ...formValues,
        });
      },
    },
  },
  rowConfig: {
    keyField: 'roleId',
  },
  id: 'system-role-index',
};
const [BasicTable,tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
});
const handleAdd = () => {
  console.log('新增')
}
const handleDownloadExcel = () => {
  console.log('导出')
}
</script>
<style scoped>
</style>
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/serivceDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
<script>
import {modelInfo} from "#/api/system/model/index.js";
import {pick} from "lodash-es";
import {useVbenModal} from "@vben/common-ui";
import {ref, computed} from "vue";
import { $t } from '@vben/locales';
const defaultValues = {
  id: undefined,
  name: undefined,
}
const isUpdate = ref(false);
const title = computed(() => {
  return isUpdate.value ? $t('编辑') : $t('新增');
});
const { validate, validateInfos, resetFields } = Form.useForm(
  formData,
  formRules,
);
const [BasicModal, modalApi] = useVbenModal({
  class: 'w-[550px]',
  fullscreenButton: false,
  closeOnClickModal: false,
  onClosed: handleCancel,
  onConfirm: handleConfirm,
  onOpenChange: async (isOpen) => {
    if (!isOpen) {
      return null;
    }
    modalApi.modalLoading(true);
    const { id } = modalApi.getData() as { id?: number | string };
    isUpdate.value = !!id;
    if (isUpdate.value && id) {
      const record = await modelInfo(id);
      // åªèµ‹å€¼å­˜åœ¨çš„字段
      const filterRecord = pick(record, Object.keys(defaultValues));
      formData.value = filterRecord;
    }
    modalApi.modalLoading(false);
  },
});
</script>
<template>
  <BasicModal :title="title">
    <Form :label-col="{ span: 4 }">
      <FormItem label="名称" v-bind="validateInfos.name">
        <Input
          v-model:value="formData.name"
          :placeholder="$t('ui.formRules.required')"
        />
      </FormItem>
    </Form>
  </BasicModal>
</template>
<style scoped>
</style>
ruoyi-ui/apps/web-antd/src/views/system/process/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,472 @@
<template>
  <div class="process-definition-container">
    <a-card title="流程定义列表" :bordered="false">
      <div class="table-actions">
        <a-button type="primary" @click="showCreateModal">新增流程图</a-button>
      </div>
      <a-table
        :columns="columns"
        :data-source="definitions"
        :row-key="record => record.id"
        :pagination="pagination"
        :loading="loading"
        @change="handleTableChange"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'action'">
            <a-space>
              <a-button type="link" @click="showDiagram(record)">查看</a-button>
              <a-button type="link" @click="editDiagram(record)">修改</a-button>
              <a-button type="link" danger @click="deleteDefinition(record)">删除</a-button>
            </a-space>
          </template>
        </template>
      </a-table>
    </a-card>
    <!-- æµç¨‹å›¾æŸ¥çœ‹æ¨¡æ€æ¡† -->
    <a-modal
      v-model:visible="diagramVisible"
      title="流程图查看"
      width="80%"
      :footer="null"
      @cancel="handleDiagramCancel"
    >
      <div style="text-align: center">
        <img
          v-if="currentDiagramUrl"
          :src="currentDiagramUrl"
          alt="流程图"
          style="max-width: 100%"
        />
        <a-skeleton v-else active />
      </div>
    </a-modal>
    <!-- æµç¨‹å›¾ç¼–辑模态框 -->
    <a-modal
      v-model:visible="editorVisible"
      :title="editorTitle"
      width="90%"
      :maskClosable="false"
      :okText="'保存'"
      :cancelText="'取消'"
      :confirmLoading="editorSaving"
      @ok="handleEditorOk"
      @cancel="handleEditorCancel"
      :destroyOnClose="true"
      :afterClose="handleEditorAfterClose"
      :style="{ top: '20px' }"
      :bodyStyle="{
      padding: '0',
      height: 'calc(100vh - 100px)',
      overflow: 'hidden',
      display: 'flex',
      flexDirection: 'column'
    }"
    >
      <div class="editor-container">
        <div v-if="isCreateMode" class="create-form">
          <a-form layout="vertical">
            <a-form-item label="流程名称" required>
              <a-input v-model:value="newProcess.name" placeholder="请输入流程名称" />
            </a-form-item>
            <a-form-item label="流程Key" required>
              <a-input v-model:value="newProcess.key" placeholder="请输入流程Key" />
            </a-form-item>
            <a-form-item label="流程描述">
              <a-textarea v-model:value="newProcess.description" placeholder="请输入流程描述" />
            </a-form-item>
          </a-form>
        </div>
        <div class="bpmn-editor" ref="bpmnEditor"></div>
      </div>
    </a-modal>
  </div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { message, Modal } from 'ant-design-vue'
import BpmnModeler from 'bpmn-js/lib/Modeler'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-js.css'
// è¡¨æ ¼åˆ—定义
const columns = [
  {
    title: 'ID',
    dataIndex: 'id',
    key: 'id',
    ellipsis: true
  },
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name'
  },
  {
    title: 'Key',
    dataIndex: 'key',
    key: 'key'
  },
  {
    title: '操作',
    key: 'action',
  }
]
// æ•°æ®çŠ¶æ€
const definitions = ref([])
const loading = ref(false)
const pagination = ref({
  current: 1,
  pageSize: 10,
  total: 0,
  showSizeChanger: true,
  pageSizeOptions: ['10', '20', '50']
})
// æµç¨‹å›¾æŸ¥çœ‹ç›¸å…³çŠ¶æ€
const diagramVisible = ref(false)
const currentDiagramUrl = ref('')
const currentProcessDefinition = ref(null)
// æµç¨‹å›¾ç¼–辑相关状态
const editorVisible = ref(false)
const editorSaving = ref(false)
const editorTitle = ref('流程图编辑')
const bpmnModeler = ref(null)
const bpmnEditor = ref(null)
const isCreateMode = ref(false)
const newProcess = ref({
  name: '',
  key: '',
  description: ''
})
// èŽ·å–æµç¨‹å®šä¹‰åˆ—è¡¨
const fetchProcessDefinitions = async (params = {}) => {
  loading.value = true
  try {
    // æ¨¡æ‹Ÿæ•°æ®
    const mockData = [
      { id: '1', name: '请假流程', key: 'leaveProcess'},
      { id: '2', name: '报销流程', key: 'expenseProcess'},
      { id: '3', name: '采购流程', key: 'purchaseProcess' }
    ]
    // æ¨¡æ‹Ÿåˆ†é¡µ
    const start = (params.page - 1) * params.size
    const end = start + params.size
    definitions.value = mockData.slice(start, end)
    pagination.value.total = mockData.length
  } catch (error) {
    message.error('加载流程定义失败:'+ error.message)
  } finally {
    loading.value = false
  }
}
// è¡¨æ ¼åˆ†é¡µ/排序变化处理
const handleTableChange = (pag, filters, sorter) => {
  const params = {
    page: pag.current,
    size: pag.pageSize
  }
  if (sorter.field) {
    params.sort = sorter.field
    params.order = sorter.order === 'ascend' ? 'asc' : 'desc'
  }
  fetchProcessDefinitions(params)
}
// æ˜¾ç¤ºæµç¨‹å›¾
const showDiagram = (record) => {
  currentProcessDefinition.value = record
  currentDiagramUrl.value = `https://via.placeholder.com/800x600?text=流程图+${record.id}`
  diagramVisible.value = true
}
// é”€æ¯BPMN编辑器
const destroyBpmnEditor = () => {
  if (bpmnModeler.value) {
    bpmnModeler.value.destroy()
    bpmnModeler.value = null
  }
}
// åˆå§‹åŒ–BPMN编辑器
const initBpmnEditor = async (xml) => {
  await nextTick()
  // å…ˆé”€æ¯æ—§çš„编辑器
  destroyBpmnEditor()
  try {
    // åˆ›å»ºæ–°çš„编辑器实例
    bpmnModeler.value = new BpmnModeler({
      container: bpmnEditor.value,
    })
    // åŠ è½½XML或默认流程图
    const diagram = xml || `
      <?xml version="1.0" encoding="UTF-8"?>
      <bpmn:definitions
        xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
        xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
        xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
        xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
        id="Definitions_1"
        targetNamespace="http://bpmn.io/schema/bpmn">
        <bpmn:process id="Process_1" isExecutable="false">
          <bpmn:startEvent id="StartEvent_1" />
        </bpmn:process>
        <bpmndi:BPMNDiagram id="BPMNDiagram_1">
          <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
            <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
              <dc:Bounds x="173" y="102" width="36" height="36" />
            </bpmndi:BPMNShape>
          </bpmndi:BPMNPlane>
        </bpmndi:BPMNDiagram>
      </bpmn:definitions>
    `
    await bpmnModeler.value.importXML(diagram)
  } catch (err) {
    console.error('Error rendering diagram', err)
    message.error('初始化流程图编辑器失败: ' + err.message)
  }
}
// æ˜¾ç¤ºåˆ›å»ºæ–°æµç¨‹æ¨¡æ€æ¡†
const showCreateModal = () => {
  isCreateMode.value = true
  editorTitle.value = '创建新流程图'
  editorVisible.value = true
  // é‡ç½®è¡¨å•
  newProcess.value = {
    name: '',
    key: '',
    description: ''
  }
  // åˆå§‹åŒ–编辑器
  initBpmnEditor()
}
// ç¼–辑流程图
const editDiagram = async (record) => {
  isCreateMode.value = false
  currentProcessDefinition.value = record
  editorTitle.value = `编辑流程 - ${record.name}`
  editorVisible.value = true
  // æ¨¡æ‹ŸåŠ è½½æµç¨‹å®šä¹‰XML
  const mockXml = `
    <?xml version="1.0" encoding="UTF-8"?>
    <bpmn:definitions
      xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
      xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
      xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
      xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
      id="Definitions_1"
      targetNamespace="http://bpmn.io/schema/bpmn">
      <bpmn:process id="${record.id}" name="${record.name}" isExecutable="true">
        <bpmn:startEvent id="StartEvent_1" name="开始" />
        <bpmn:userTask id="UserTask_1" name="提交申请" />
        <bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="UserTask_1" />
        <bpmn:endEvent id="EndEvent_1" name="结束" />
        <bpmn:sequenceFlow id="Flow_2" sourceRef="UserTask_1" targetRef="EndEvent_1" />
      </bpmn:process>
      <bpmndi:BPMNDiagram id="BPMNDiagram_1">
        <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${record.id}">
          <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
            <dc:Bounds x="173" y="102" width="36" height="36" />
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="UserTask_1_di" bpmnElement="UserTask_1">
            <dc:Bounds x="280" y="80" width="100" height="80" />
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
            <dc:Bounds x="450" y="102" width="36" height="36" />
          </bpmndi:BPMNShape>
          <bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
            <di:waypoint x="209" y="120" />
            <di:waypoint x="280" y="120" />
          </bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
            <di:waypoint x="380" y="120" />
            <di:waypoint x="450" y="120" />
          </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
      </bpmndi:BPMNDiagram>
    </bpmn:definitions>
  `
  // åˆå§‹åŒ–编辑器并加载XML
  initBpmnEditor(mockXml)
}
// ä¿å­˜æµç¨‹å›¾ä¿®æ”¹
const handleEditorOk = async () => {
  editorSaving.value = true
  try {
    if (isCreateMode.value) {
      // éªŒè¯è¡¨å•
      if (!newProcess.value.name || !newProcess.value.key) {
        message.error('请填写流程名称和Key')
        return
      }
      // èŽ·å–XML
      const { xml } = await bpmnModeler.value.saveXML({ format: true })
      console.log('新流程XML:', xml)
      // æ¨¡æ‹Ÿåˆ›å»ºæ–°æµç¨‹
      await new Promise(resolve => setTimeout(resolve, 1000))
      // æ·»åŠ åˆ°åˆ—è¡¨
      const newId = Math.max(...definitions.value.map(d => parseInt(d.id))) + 1
      definitions.value.unshift({
        id: newId.toString(),
        name: newProcess.value.name,
        key: newProcess.value.key,
        version: 1
      })
      message.success('新流程创建成功')
    } else {
      // èŽ·å–ä¿®æ”¹åŽçš„XML
      const { xml } = await bpmnModeler.value.saveXML({ format: true })
      console.log('修改后的XML:', xml)
      // æ¨¡æ‹Ÿä¿å­˜
      await new Promise(resolve => setTimeout(resolve, 1000))
      message.success('流程图保存成功')
    }
    editorVisible.value = false
  } catch (error) {
    console.error('Error saving BPMN diagram', error)
    message.error('操作失败: ' + error.message)
  } finally {
    editorSaving.value = false
  }
}
// åˆ é™¤æµç¨‹å®šä¹‰
const deleteDefinition = (record) => {
  Modal.confirm({
    title: '确认删除流程?',
    content: `确定要删除流程 "${record.name}" å—?此操作不可恢复。`,
    okText: '确认',
    okType: 'danger',
    cancelText: '取消',
    onOk() {
      // æ¨¡æ‹Ÿåˆ é™¤
      definitions.value = definitions.value.filter(item => item.id !== record.id)
      message.success('流程删除成功')
    }
  })
}
// æ¨¡æ€æ¡†å…³é—­åŽæ¸…理
const handleEditorAfterClose = () => {
  destroyBpmnEditor()
}
const handleEditorCancel = () => {
  editorVisible.value = false
}
const handleDiagramCancel = () => {
  diagramVisible.value = false
}
// åˆå§‹åŒ–加载数据
onMounted(() => {
  const params = {
    page: 1,
    size: 10
  }
  fetchProcessDefinitions(params)
})
</script>
<style scoped>
.process-definition-container {
  padding: 20px;
  background: #fff;
}
.table-actions {
  margin-bottom: 16px;
  display: flex;
  justify-content: flex-end;
}
.editor-container {
  height: 600px;
  display: flex;
  flex-direction: column;
}
.bpmn-editor {
  flex: 1;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  margin-top: 16px;
  min-height: 500px; /* ç¡®ä¿ç¼–辑器有足够高度 */
}
.create-form {
  padding: 16px;
  background: #fafafa;
  border-radius: 2px;
  border: 1px solid #d9d9d9;
  margin-bottom: 16px;
}
/* éšè—BPMN水印 */
.bpmn-editor :deep(.bjs-powered-by) {
  display: none !important;
}
.editor-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}
.bpmn-editor {
  flex: 1;
  min-height: 0; /* é‡è¦ï¼šå…è®¸flex容器收缩 */
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  margin-top: 16px;
}
.create-form {
  padding: 16px;
  background: #fafafa;
  border-radius: 2px;
  border: 1px solid #d9d9d9;
  margin-bottom: 16px;
}
/* éšè—BPMN水印 */
.bpmn-editor :deep(.bjs-powered-by) {
  display: none !important;
}
/* ç¡®ä¿BPMN工具栏可见 */
.bpmn-editor :deep(.djs-palette) {
  top: 20px;
  left: 20px;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/dynamicForm/index.vue
@@ -1,5 +1,5 @@
<template>
  <fc-designer ref="designer" />
  <fc-designer ref="designer" :config="config"  @save="handleSave" />
</template>
<script setup>
@@ -16,4 +16,12 @@
const loadJson = (json) => {
  designer.value.setJson(json);
};
const config = ref({
  showSaveBtn: true,
  showPreviewBtn: true,
});
function handleSave(data) {
  //保存设计规则
  console.log(data);
}
</script>
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { h } from 'vue';
import { FolderIcon, VbenIcon } from '@vben/icons';
export const querySchema: FormSchemaGetter = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '页面名称',
  },
  {
    component: 'Select',
    componentProps: {
      options: [
        { label: '正常', value: '0' },
        { label: '停用', value: '1' },
      ],
    },
    fieldName: 'status',
    label: '页面状态',
  },
];
export const columns: VxeGridProps['columns'] = [
  {
    title: '页面名称',
    field: 'name',
  },
  {
    title: '上级目录',
    field: 'menuParentName',
  },
  {
    title: '状态',
    field: 'status',
    slots: {
      default: ({ row }) => {
        return row.status === '0' ? '正常' : '停用';
      },
    },
  },
  {
    title: '创建时间',
    field: 'createTime',
  },
  {
    field: 'action',
    fixed: 'right',
    slots: { default: 'action' },
    title: '操作',
  },
];
export const drawerSchema = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '页面名称',
    rules: 'required',
  },
  {
    component: 'TreeSelect',
    fieldName: 'menuParentId',
    label: '上级目录',
    componentProps: {
      allowClear: true,
      showSearch: true,
    },
    rules: 'selectRequired',
  },
  {
    component: 'Select',
    fieldName: 'status',
    label: '状态',
    componentProps: {
      options: [
        { label: '正常', value: '0' },
        { label: '停用', value: '1' },
      ],
    },
    rules: 'selectRequired',
  },
  {
    component: 'CheckboxGroup',
    fieldName: 'actionsFunc',
    label: '启用功能',
    defaultValue: ['add', 'edit', 'delete', 'query'],
    componentProps: {
      options: [
        { label: '新增', value: 'add' },
        { label: '编辑', value: 'edit' },
        { label: '删除', value: 'delete' },
        { label: '查询', value: 'query' },
      ],
    },
    rules: 'required',
  },
  {
    component: 'Input',
    fieldName: 'remark',
    label: '备注',
    componentProps: {
      type: 'textarea',
      rows: 2,
    },
  },
];
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,217 @@
<template>
  <Page v-if="isAdmin" :auto-content-height="true">
    <BasicTable table-title="页面设计器" >
      <template #toolbar-tools>
        <Space>
          <a-button type="primary" @click="handleAdd">新增</a-button>
        </Space>
      </template>
      <template #action="{ row }">
        <Space>
          <ghost-button @click="handleEdit(row)">编辑</ghost-button>
          <Popconfirm :get-popup-container="getVxePopupContainer" placement="left" title="确认删除?" @confirm="handleDelete(row)">
            <ghost-button danger @click.stop="">删除</ghost-button>
          </Popconfirm>
        </Space>
      </template>
    </BasicTable>
    <PageDrawer ref="pageModalRef" @reload="tableApi.query()" :menu-array="menuArray" />
  </Page>
  <Fallback v-else description="您没有页面生成器的访问权限" status="403" />
</template>
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, ref, onMounted } from 'vue';
import { useAccess } from '@vben/access';
import { Fallback, Page, useVbenDrawer } from '@vben/common-ui';
import { eachTree, getVxePopupContainer } from '@vben/utils';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { columns, querySchema } from './data';
import PageDrawer from './page-drawer.vue';
import FcDesigner from '@form-create/designer';
import { pageList, pageRemove } from '#/api/tool/page-designer';
import { menuList } from '../../../api/system/menu';
import { listToTree} from '@vben/utils';
// ç§»é™¤mock数据
// const pageList = async (params: any) => { ... };
// const pageRemove = async (ids: number[]) => {};
const menuArray = ref([]);
const processedMenuTree = ref([]);
onMounted(async () => {
  try {
    // èŽ·å–åŽŸå§‹èœå•æ•°æ®
    const rawMenuData = await menuList();
    menuArray.value = rawMenuData;
    // å¤„理菜单数据
    processMenuData();
  } catch (error) {
    console.error('获取菜单数据失败:', error);
  }
});
// å¤„理菜单数据的函数
const processMenuData = () => {
  if (!menuArray.value || menuArray.value.length === 0) return;
  // 1. è¿‡æ»¤æŽ‰æŒ‰é’®ç±»åž‹(F)和菜单类型(C)
  const filteredList = menuArray.value.filter(item =>
    item.menuType !== 'F' && item.menuType !== 'C'
  );
  // 2. è½¬æ¢ä¸ºæ ‘形结构
  const treeData = listToTree(filteredList, {
    id: 'menuId',
    pid: 'parentId'
  });
  // 3. æ·»åŠ æ ¹èŠ‚ç‚¹
  processedMenuTree.value = [
    {
      menuId: 0,
      parentId: 0,
      menuName: '根目录',
      children: treeData
    }
  ];
};
const formOptions: VbenFormProps = {
  commonConfig: {
    labelWidth: 80,
    componentProps: {
      allowClear: true,
    },
  },
  schema: querySchema(),
  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const getFullMenuPath = (id: number) => {
  if (!processedMenuTree.value || processedMenuTree.value.length === 0) return '';
  // é€’归查找菜单路径
  const findPath = (tree, currentId, path = []): string[] | null => {
    for (const item of tree) {
      if (item.menuId === currentId) {
        return [...path, item.menuName];
      }
      if (item.children && item.children.length > 0) {
        const found = findPath(item.children, currentId, [...path, item.menuName]);
        if (found) return found;
      }
    }
    return null;
  };
  const path = findPath(processedMenuTree.value, id);
  return path ? path.join(' / ') : '根目录';
};
const gridOptions: VxeGridProps = {
  columns,
  height: 'auto',
  keepSource: true,
  pagerConfig: {
    enabled: true,
  },
  proxyConfig: {
    ajax: {
      query: async ({ page }, formValues = {}) => {
        const resp = await pageList({
          pageNum: page.currentPage,
          pageSize: page.pageSize,
          ...formValues,
        });
        // å¤„理返回数据,添加menuParentName
        const processedRows = resp.rows.map(row => {
          return {
            ...row,
            menuParentName: getFullMenuPath(row.menuParentId) || '根目录'
          };
        });
        return {
          rows: processedRows,  // ä½¿ç”¨å¤„理后的数据
          total: resp.total,
        };
      },
    },
  },
  rowConfig: {
    keyField: 'id',
  },
  id: 'tool-page-designer-index',
  columnConfig: { resizable: true },
};
const [BasicTable, tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
});
const designer = ref();
const pageModalRef = ref();
function getFormJson() {
  // èŽ·å–è®¾è®¡ç»“æžœ
  const json = designer.value.getRule();
  // ä½ å¯ä»¥å°† json å­˜åˆ°åŽç«¯
}
function setFormJson(json) {
  // åŠ è½½å·²æœ‰è®¾è®¡
  designer.value.setRule(json);
}
function handleAdd() {
  pageModalRef.value.open({ update: false });
}
function handleEdit(record) {
  pageModalRef.value.open({ id: record.id, update: true });
}
async function handleDelete(row: any) {
  await pageRemove([row.id]);
  await tableApi.query();
}
function handleSubAdd(row) {
  pageModalRef.value.open({ id: row.id, update: false });
}
const { hasAccessByRoles } = useAccess();
const isAdmin = computed(() => {
  return hasAccessByRoles(['admin', 'superadmin']);
});
</script>
<style scoped>
.designer-page {
  background: #f5f6fa;
  padding: 16px;
  min-height: 100vh;
}
.designer-query-form {
  background: #fff;
  padding: 16px 16px 0 16px;
  border-radius: 6px;
  margin-bottom: 12px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
.designer-toolbar {
  background: #fff;
  padding: 12px 16px;
  border-radius: 6px;
  margin-bottom: 12px;
  display: flex;
  gap: 8px;
}
.designer-table {
  background: #fff;
  border-radius: 6px;
  padding: 0 0 16px 0;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,337 @@
<script setup lang="ts">
import { computed, ref, watch, nextTick, onMounted } from 'vue';
import { Modal, message, FormItem } from 'ant-design-vue';
import { $t } from '@vben/locales';
import { useVbenForm } from '#/adapter/form';
import { drawerSchema } from './data';
import FcDesigner from '@form-create/designer';
import { listToTree, addFullName, getPopupContainer } from '@vben/utils';
import { pageAdd, pageUpdate, pageInfo } from '#/api/tool/page-designer';
interface ModalProps {
  id?: number | string;
  update: boolean;
}
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
  return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  schema: drawerSchema(),
  showDefaultActions: false,
  wrapperClass: 'grid-cols-5',
});
const designer = ref();
const selectedFields = ref([]); // å¤šé€‰æ¡†é€‰ä¸­çš„字段key
const fieldOptions = ref([]);   // è®¾è®¡åŒºæ‰€æœ‰å­—段
const modalVisible = ref(false);
const modalLoading = ref(false);
const currentEditId = ref<string | number>(''); // å½“前编辑的ID
const fullMenuTree = ref([]);
const props=defineProps({
  menuArray: {
    type: Array,
    required: true,
    default: () => []
  }
});
// æ‰“开弹窗
const open = async (params: ModalProps = { update: false }) => {
  try {
    modalVisible.value = true;
    modalLoading.value = true;
    isUpdate.value = params.update;
    currentEditId.value = params.id || ''; // ä¿å­˜å½“前编辑的ID
    await setupPageSelect();
    if (params.id) {
      await formApi.setFieldValue('menuParentId', params.id);
      if (params.update) {
        // èŽ·å–è¯¦æƒ…æ•°æ®
        const record = await pageInfo(params.id);
        // è®¾ç½®åŸºç¡€è¡¨å•数据
        const menuParentId = String(record.menuParentId || record.parentId || '');
        await formApi.setValues({
          name: record.name,
          menuParentId: menuParentId,  // ç”¨ menuParentId å­—段
          status: record.status,
          remark: record.remark,
          actionsFunc: record.actionsFunc ? JSON.parse(record.actionsFunc) : ['add', 'edit', 'delete', 'query']
        });
        // åŠ è½½è¡¨å•è®¾è®¡æ•°æ®
        if (record.formJson) {
          try {
            const formRule = JSON.parse(record.formJson);
            console.log('设计器规则:', formRule);
            designer.value.setRule(formRule);
            // æ›´æ–°å­—段选项
            await nextTick();
            updateFieldOptions();
            // æ¢å¤é€‰ä¸­çš„字段
            if (record.showColumn) {
              selectedFields.value = JSON.parse(record.showColumn);
            }
          } catch (e) {
            console.error('加载表单设计数据失败:', e);
            message.error('加载表单设计数据失败');
          }
        }
      }
    } else {
      // æ–°å¢žæ—¶é‡ç½®æ•°æ®
      designer.value?.setRule([]);
      selectedFields.value = [];
      await formApi.resetForm();
    }
  } catch (error) {
    console.error('打开弹窗失败:', error);
    message.error('加载数据失败');
  } finally {
    modalLoading.value = false;
  }
};
const close = () => {
  modalVisible.value = false;
};
defineExpose({ open, close });
async function setupPageSelect() {
  // èŽ·å–èœå•æ•°æ®
  if (!props.menuArray || props.menuArray.length === 0) {
    await nextTick(); // ç­‰å¾…可能的异步加载
    if (!props.menuArray || props.menuArray.length === 0) {
      console.warn('menuArray is empty');
      return;
    }
  }
  // è¿‡æ»¤æŽ‰æŒ‰é’®ç±»åž‹
  const filteredList = props.menuArray.filter(item => item.menuType !== 'F' && item.menuType !== 'C');
  // æ”¯æŒi18n
  filteredList.forEach(item => { item.menuName = $t(item.menuName); });
  // è½¬ä¸ºæ ‘结构
  const menuTree = listToTree(filteredList, { id: 'menuId', pid: 'parentId' });
  // é€’归映射 menuId -> menuParentId
  function mapMenuIdToParentId(list) {
    return list.map(item => {
      const newItem = { ...item, menuParentId: item.menuId };
      if (item.children) {
        newItem.children = mapMenuIdToParentId(item.children);
      }
      return newItem;
    });
  }
  fullMenuTree.value = [
    {
      menuId: 0,
      menuParentId: 0,
      menuName: $t('menu.root'),
      children: mapMenuIdToParentId(menuTree),
    },
  ];
  // ç”Ÿæˆå…¨è·¯å¾„名
  addFullName(fullMenuTree.value, 'menuName', ' / ');
  formApi.updateSchema([
    {
      componentProps: {
        fieldNames: {
          label: 'menuName',
          value: 'menuId', // ç”¨ menuId
          children: 'children'
        },
        getPopupContainer,
        listHeight: 300,
        showSearch: true,
        treeData: fullMenuTree.value,
        treeDefaultExpandAll: false,
        treeDefaultExpandedKeys: [0],
        treeLine: { showLeafIcon: false },
        treeNodeFilterProp: 'menuName',
        treeNodeLabelProp: 'fullName',
      },
      fieldName: 'menuParentId', // ç”¨ menuParentId
    },
  ]);
}
// åŒæ­¥æ‰€æœ‰å­—段到选中状态
const syncAllFields = () => {
  // èŽ·å–è¡¨å•ç»„ä»¶çš„è§„åˆ™æè¿°
  const formDesc = designer.value?.getFormDescription?.();
  if (!formDesc || !Array.isArray(formDesc)) {
    message.warning('暂无设计数据');
    return;
  }
  // æå–字段信息
  const allFields = formDesc
    .filter(item => item && item.field && item.title)
    .map(item => ({
      title: item.title,
      field: item.field
    }));
  if (allFields.length === 0) {
    message.warning('未找到可用字段');
    return;
  }
  // æ›´æ–°å­—段选项
  fieldOptions.value = allFields.map(item => ({
    label: item.title,
    value: item.field
  }));
  // é€‰ä¸­æ‰€æœ‰å­—段
  selectedFields.value = allFields.map(item => item.field);
  message.success(`已同步 ${allFields.length} ä¸ªå­—段`);
};
// å¤„理设计器变化
const handleDesignerChange = () => {
  nextTick(() => {
    updateFieldOptions();
  });
};
// å½“设计器内容变化时更新字段选项
const updateFieldOptions = () => {
  // èŽ·å–è¡¨å•ç»„ä»¶çš„è§„åˆ™æè¿°
  const formDesc = designer.value?.getFormDescription?.();
  if (!formDesc || !Array.isArray(formDesc)) return;
  const fields = formDesc
    .filter(item => item && item.field && item.title)
    .map(item => ({
      title: item.title,
      field: item.field
    }));
  fieldOptions.value = fields.map(item => ({
    label: item.title,
    value: item.field
  }));
};
// ç›‘听设计器内容变化
watch(() => modalVisible.value, (val) => {
  if (val) {
    nextTick(() => updateFieldOptions());
  }
});
async function handleOk() {
  try {
    modalLoading.value = true;
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    const data = await formApi.getValues();
    // å¦‚果是编辑模式,添加id字段
    if (isUpdate.value) {
      data.id = currentEditId.value;
    }
    // èŽ·å–è¡¨å•è®¾è®¡ JSON
    data.formJson = designer.value.getJson();
    // æ·»åŠ é€‰ä¸­çš„å­—æ®µ
    data.showColumn = JSON.stringify(selectedFields.value);
    // è½¬æ¢å¯ç”¨åŠŸèƒ½ä¸ºJSON字符串
    data.actionsFunc = JSON.stringify(data.actionsFunc);
    // åŒæ­¥ä¸€æ¬¡å­—段多选
    updateFieldOptions();
    await (isUpdate.value ? pageUpdate(data) : pageAdd(data));
    emit('reload');
    close();
    message.success('保存成功');
  } catch (error) {
    console.error(error);
  } finally {
    modalLoading.value = false;
  }
}
function handleCancel() {
  close();
}
</script>
<template>
  <a-modal
    v-model:open="modalVisible"
    :title="title"
    :width="'80vw'"
    :confirm-loading="modalLoading"
    @ok="handleOk"
    @cancel="handleCancel"
    :bodyStyle="{ padding: '24px', minHeight: '60vh' }"
    destroyOnClose
    wrapClassName="page-designer-modal"
  >
    <template #closeIcon>
      <span></span>
    </template>
    <BasicForm />
    <div style="margin-top: 16px;">
      <FcDesigner
        ref="designer"
        @update="handleDesignerChange"
        @change="handleDesignerChange"
        @add-rule="handleDesignerChange"
        @remove-rule="handleDesignerChange"
      />
      <div style="margin-top: 8px; display: flex; justify-content: flex-end;">
        <a-button type="primary" ghost @click="syncAllFields">
          åŒæ­¥è®¾è®¡å­—段到表格
        </a-button>
      </div>
    </div>
    <FormItem label="表格字段" style="margin-top: 24px;">
      <a-checkbox-group
        v-model:value="selectedFields"
        :options="fieldOptions"
        style="width:100%;display:flex;flex-wrap:wrap;gap:8px"
      />
    </FormItem>
    <template #empty>
      <div style="padding: 32px 0; color: #999; text-align: center;">
        æš‚无数据
      </div>
    </template>
  </a-modal>
</template>
<style scoped>
.page-designer-modal .ant-modal {
  max-width: 1200px;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/template/data.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '模版名称',
  },
  {
    component: 'Select',
    componentProps: {
      options: [
        { label: '正常', value: '1' },
        { label: '停用', value: '0' },
      ],
    },
    fieldName: 'status',
    label: '模版状态',
  },
];
export const columns = [
  {
    title: '姓名',
    dataIndex: 'name',
    auth: 'test', // æ ¹æ®æƒé™æŽ§åˆ¶æ˜¯å¦æ˜¾ç¤º: æ— æƒé™ï¼Œä¸æ˜¾ç¤º
  }
];
export const drawerSchema = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '模版名称',
    rules: 'required',
  },
  {
    component: 'Select',
    fieldName: 'pageDesignId',
    label: '页面设计',
    componentProps: {
      allowClear: true,
      showSearch: true,
      filterOption: (input: string, option: any) => {
        return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
      },
    },
    rules: 'selectRequired',
  },
  {
    component: 'Select',
    fieldName: 'templateType',
    label: '模版类型',
    componentProps: {
      options: [
        { label: '列表页', value: 'list' },
        { label: '表单页', value: 'form' },
        { label: '详情页', value: 'detail' },
        { label: '仪表板', value: 'dashboard' },
      ],
    },
    rules: 'selectRequired',
  },
  {
    component: 'Select',
    fieldName: 'status',
    label: '状态',
    componentProps: {
      options: [
        { label: '正常', value: '1' },
        { label: '停用', value: '0' },
      ],
    },
    rules: 'selectRequired',
  },
  {
    component: 'Input',
    fieldName: 'remark',
    label: '备注',
    componentProps: {
      type: 'textarea',
      rows: 2,
    },
  },
];
ruoyi-ui/apps/web-antd/src/views/tool/template/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,397 @@
<template>
  <Page v-if="isAdmin && pageId" :auto-content-height="true">
    <BasicTable
      :key="tableKey"
      :table-title="pageDesignDetail?.name || '模板列表'"
      :grid-options="gridOptions"
    >
      <template #toolbar-tools>
        <Space>
          <a-button v-if="showAction('add')" type="primary" @click="handleAdd">新增</a-button>
        </Space>
      </template>
      <template #action="{ row }">
        <Space>
          <ghost-button v-if="showAction('edit')" @click="handleEdit(row)">编辑</ghost-button>
          <Popconfirm v-if="showAction('delete')" :get-popup-container="getVxePopupContainer" placement="left" title="确认删除?" @confirm="handleDelete(row)">
            <ghost-button danger @click.stop="">删除</ghost-button>
          </Popconfirm>
        </Space>
      </template>
    </BasicTable>
    <TemplateDrawer ref="templateModalRef" @reload="tableApi.query()" />
  </Page>
  <Fallback v-else description="未指定 pageId,无法访问此页面" status="403" />
</template>
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, ref, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAccess } from '@vben/access';
import { Fallback, Page } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Popconfirm, Space, Spin as ASpin } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { columns as baseColumns, querySchema } from './data';
import TemplateDrawer from './template-drawer.vue';
import { templateList, templateRemove } from '#/api/tool/template';
import { pageInfo } from '#/api/tool/page-designer';
const route = useRoute();
const router = useRouter();
const pageId = ref<string | number>('');
const pageDesignDetail = ref<any>(null); // é¡µé¢è®¾è®¡è¯¦æƒ…
const loading = ref(true); // åŠ è½½ä¸­
// åŠ¨æ€columns
const dynamicColumns = ref([
  { field: 'id', title: 'ID', width: 100 },
  { field: 'formData', title: '表单数据', minWidth: 160 },
  { field: 'action', title: '操作', width: 160, slots: { default: 'action' } }
]);
// ç”¨äºŽè¡¨æ ¼é‡æ–°æ¸²æŸ“çš„ key
const tableKey = ref(0);
// æ›´æ–°åŠ¨æ€åˆ—çš„å‡½æ•°
function updateDynamicColumns() {
  if (!pageDesignDetail.value || !pageDesignDetail.value.showColumn || !pageDesignDetail.value.formJson) {
    console.log('使用默认列');
    return;
  }
  try {
    const showFields = JSON.parse(pageDesignDetail.value.showColumn);
    const formFields = JSON.parse(pageDesignDetail.value.formJson);
    const cols = showFields.map(field => {
      const fieldDef = formFields.find(f => f.field === field);
      return {
        field: field,
        title: fieldDef ? fieldDef.title : field,
        minWidth: 120,
        align: 'center',
      };
    });
    cols.push({
      field: 'action',
      title: '操作',
      width: 160,
      slots: { default: 'action' },
    });
    dynamicColumns.value = cols;
    gridOptions.value = { ...gridOptions.value, columns: cols };
    tableKey.value++;
  } catch (error) {
    const fallbackCols = [
      { field: 'id', title: 'ID', width: 100 },
      { field: 'formData', title: '表单数据', minWidth: 160 },
      { field: 'action', title: '操作', width: 160, slots: { default: 'action' } }
    ];
    dynamicColumns.value = fallbackCols;
    gridOptions.value = { ...gridOptions.value, columns: fallbackCols };
    tableKey.value++;
  }
}
// åŠ¨æ€æŒ‰é’®
function showAction(action: string) {
  if (!pageDesignDetail.value || !pageDesignDetail.value.actionsFunc) {
    return true;
  }
  try {
    const actions = JSON.parse(pageDesignDetail.value.actionsFunc);
    if (!Array.isArray(actions)) {
      console.warn('actionsFunc ä¸æ˜¯æ•°ç»„格式:', pageDesignDetail.value.actionsFunc);
      return true;
    }
    return actions.includes(action);
  } catch (error) {
    console.error('解析 actionsFunc å¤±è´¥:', error);
    return true;
  }
}
// èŽ·å– pageId,只用 params
function getPageId() {
  const segments = window.location.pathname.split('/');
  return segments[segments.length - 1] || '';
}
onMounted(() => {
  const initialPageId = getPageId();
  console.log('获取到的 pageId:', initialPageId);
  if (initialPageId) {
    pageId.value = initialPageId;
    handlePageIdChange();
  } else {
    loading.value = false;
  }
});
// ç›‘听路由变化,自动更新 pageId
watch(
  () => [route.meta.pageId, route.params.pageId, route.query.pageId],
  (newValues, oldValues) => {
    // åªæœ‰å½“值真正变化时才处理
    if (JSON.stringify(newValues) !== JSON.stringify(oldValues)) {
      const newPageId = getPageId();
      console.log('路由变化后 pageId:', newPageId);
      // åªæœ‰å½“ pageId çœŸæ­£å˜åŒ–时才更新
      if (newPageId !== pageId.value) {
        pageId.value = newPageId;
        handlePageIdChange();
        tableApi.query();
      }
    }
  },
  { deep: true }
);
// pageId变化时自动获取页面设计详情
async function handlePageIdChange() {
  loading.value = true;
  console.log(`[handlePageIdChange] å¼€å§‹å¤„理 pageId: ${pageId.value}`);
  try {
    if (pageId.value) {
      const detail = await pageInfo(pageId.value);
      console.log('[handlePageIdChange] èŽ·å–åˆ°çš„é¡µé¢è®¾è®¡è¯¦æƒ… (detail):', JSON.parse(JSON.stringify(detail)));
      // å¤„理数据,避免循环引用
      const safeDetail = {
        id: detail.id,
        name: detail.name,
        menuParentId: detail.menuId,
        status: detail.status,
        remark: detail.remark,
        formJson: detail.formJson,
        showColumn: detail.showColumn,
        actionsFunc: detail.actionsFunc,
        createTime: detail.createTime,
        updateTime: detail.updateTime,
        createBy: detail.createBy,
        updateBy: detail.updateBy,
        createDept: detail.createDept
      };
      pageDesignDetail.value = safeDetail;
      console.log('[handlePageIdChange] è®¾ç½®çš„ pageDesignDetail.value:', JSON.parse(JSON.stringify(pageDesignDetail.value)));
      updateDynamicColumns();
    } else {
      pageDesignDetail.value = null;
      updateDynamicColumns();
    }
  } catch (error) {
    console.error('[handlePageIdChange] èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…å¤±è´¥:', error);
    pageDesignDetail.value = null;
    updateDynamicColumns();
  } finally {
    loading.value = false;
  }
}
const formOptions: VbenFormProps = {
  commonConfig: {
    labelWidth: 80,
    componentProps: {
      allowClear: true,
    },
  },
  schema: querySchema(),
  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const gridOptions = ref<VxeGridProps>({
  columns: dynamicColumns.value,
  height: 'auto',
  keepSource: true,
  pagerConfig: {
    enabled: true,
  },
  proxyConfig: {
    ajax: {
      query: async ({ page }, formValues = {}) => {
        try {
          console.log('查询参数:', { page, formValues });
          const queryParams = {
            pageNum: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
          };
          if (pageId.value) {
            queryParams.pageId = pageId.value;
          }
          const resp = await templateList(queryParams);
          // å¤„理每条 row çš„ formData
          const rows = (resp.rows || []).map(row => {
            let formData = {};
            try {
              if (row.formData) {
                formData = JSON.parse(row.formData);
                console.log('解析后的 formData:', formData);
                // åˆ é™¤åŽŸå§‹çš„ formData å­—段,因为我们已经展开它的内容
                const { formData: _, ...restRow } = row;
                // è¿”回展开后的数据
                return {
                  ...restRow,
                  ...formData
                };
              }
            } catch (e) {
              console.error('解析 formData å¤±è´¥:', e);
            }
            return row;
          });
          console.log('处理后的 rows:', rows);
          return {
            rows,
            total: resp.total || 0,
          };
        } catch (error) {
          console.error('查询模板列表失败:', error);
          return {
            rows: [],
            total: 0,
          };
        }
      },
    },
  },
  rowConfig: {
    keyField: 'id',
  },
  id: 'tool-template-index',
  columnConfig: { resizable: true },
});
const [BasicTable, tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions: computed(() => ({
    ...gridOptions.value,
    columns: dynamicColumns.value
  })),
});
const templateModalRef = ref();
const generateModalRef = ref();
function handleAdd() {
  // å¦‚果有 pageId,传递给新增
  const params: any = { update: false };
  if (pageId.value) {
    params.pageId = pageId.value;
  }
  // åŠ¨æ€ä¼ é€’formJson,先JSON.parse,保证是纯对象
  if (pageDesignDetail.value && pageDesignDetail.value.formJson) {
    try {
      const formJson = JSON.parse(pageDesignDetail.value.formJson);
      params.formJson = formJson;
      console.log('传递动态表单字段:', formJson);
    } catch (error) {
      console.error('解析 formJson å¤±è´¥:', error);
      params.formJson = undefined;
    }
  }
  templateModalRef.value.open(params);
}
function handleEdit(record) {
  // ç¼–辑时也传递formJson,先JSON.parse,保证是纯对象
  const params: any = {
    id: record.id,
    update: true,
    pageId: pageId.value,  // ä¼ é€’页面设计ID
    record: record  // ä¼ é€’完整的记录数据
  };
  if (pageDesignDetail.value && pageDesignDetail.value.formJson) {
    try {
      const formJson = JSON.parse(pageDesignDetail.value.formJson);
      params.formJson = formJson;
      console.log('编辑时传递数据:', { record, formJson, pageId: pageId.value });
    } catch (error) {
      console.error('解析 formJson å¤±è´¥:', error);
      params.formJson = undefined;
    }
  }
  templateModalRef.value.open(params);
}
async function handleDelete(row: any) {
  try {
    await templateRemove([row.id]);
    await tableApi.query();
  } catch (error) {
    console.error('删除模板失败:', error);
  }
}
function handleGenerate(row) {
  try {
    generateModalRef.value.open(row);
  } catch (error) {
    console.error('打开生成页面失败:', error);
  }
}
function handlePreview(row) {
  try {
    // æ‰“开预览窗口
    const url = `/tool/template/preview/${row.id}`;
    window.open(url, '_blank');
  } catch (error) {
    console.error('打开预览失败:', error);
  }
}
const { hasAccessByRoles } = useAccess();
const isAdmin = computed(() => {
  try {
    return hasAccessByRoles(['admin', 'superadmin']);
  } catch (error) {
    console.error('检查权限失败:', error);
    return false;
  }
});
const isReady = computed(() => {
  try {
    // ç®€åŒ–判断逻辑,减少不必要的计算
    return !!(pageDesignDetail.value && !loading.value);
  } catch (error) {
    console.error('检查页面准备状态失败:', error);
    return false;
  }
});
</script>
<style scoped>
.template-page {
  background: #f5f6fa;
  padding: 16px;
  min-height: 100vh;
  height: 100vh;
  overflow: hidden;
}
/* ç¡®ä¿è¡¨æ ¼å®¹å™¨é«˜åº¦ç¨³å®š */
:deep(.vxe-table--main-wrapper) {
  height: 600px !important;
}
/* ç¡®ä¿åˆ†é¡µå™¨ä½ç½®å›ºå®š */
:deep(.vxe-pager) {
  position: sticky;
  bottom: 0;
  background: white;
  z-index: 10;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/template/template-drawer.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,224 @@
<script setup lang="ts">
import { computed, ref, watch, nextTick } from 'vue';
import { Modal, message, FormItem } from 'ant-design-vue';
import { $t } from '@vben/locales';
import { templateAdd, templateUpdate, templateInfo } from '#/api/tool/template';
import { pageList } from '#/api/tool/page-designer';
import formCreate from '@form-create/element-ui';
interface ModalProps {
  id?: number | string;
  update: boolean;
  pageId?: string | number;
  formJson?: any;
}
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
  return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const modalVisible = ref(false);
const modalLoading = ref(false);
const currentEditId = ref<string | number>('');
const dynamicFormRef = ref();
const dynamicFormRule = ref([]);
const dynamicFormOption = ref({
  submitBtn: false,
  resetBtn: false
});
const showOkBtn = ref(true);
const currentPageId = ref('');
const formApi = ref();
// åˆ¤æ–­æ˜¯å¦æœ‰æäº¤æŒ‰é’®
type RuleItem = { type?: string };
const hasSubmitBtn = (rules: any[] = []) => {
  return rules.some((item: RuleItem) => item.type === 'submit');
};
// æ›´å½»åº•的递归移除所有 submit æŒ‰é’®ï¼Œæ”¯æŒ children、body、columns、list、options、tabs ç­‰å®¹å™¨
function removeSubmitBtn(rules = []) {
  return rules
    .filter(item => item.type !== 'submit')
    .map(item => {
      if (item.children) item.children = removeSubmitBtn(item.children);
      if (item.body) item.body = removeSubmitBtn(item.body);
      if (item.columns) item.columns = item.columns.map(col => ({
        ...col,
        list: removeSubmitBtn(col.list || [])
      }));
      if (item.list) item.list = removeSubmitBtn(item.list);
      if (item.options && Array.isArray(item.options)) item.options = removeSubmitBtn(item.options);
      if (item.tabs) item.tabs = item.tabs.map(tab => ({
        ...tab,
        list: removeSubmitBtn(tab.list || [])
      }));
      return item;
    });
}
// æ‰“开弹窗
const open = async (params: ModalProps = { update: false }) => {
  try {
    console.log('打开弹窗,参数:', params);
    modalVisible.value = true;
    modalLoading.value = true;
    isUpdate.value = params.update;
    currentEditId.value = params.id || '';
    currentPageId.value = params.pageId || '';
    // è®¾ç½®åŠ¨æ€è¡¨å•å­—æ®µï¼Œå§‹ç»ˆç§»é™¤æ‰€æœ‰submit按钮
    if (params.formJson) {
      console.log('设置动态表单规则:', params.formJson);
      dynamicFormRule.value = removeSubmitBtn(params.formJson);
      showOkBtn.value = true; // å§‹ç»ˆæ˜¾ç¤ºç¡®å®šæŒ‰é’®
    } else {
      dynamicFormRule.value = [];
      showOkBtn.value = true;
    }
    // ç¼–辑模式
    if (params.id && params.update) {
      console.log('编辑模式,获取详情数据');
      try {
        // èŽ·å–è¯¦æƒ…æ•°æ®
        const record = await templateInfo(params.id);
        console.log('获取到的记录数据:', record);
        // è®¾ç½®åŠ¨æ€è¡¨å•æ•°æ®
        if (record.formData) {
          try {
            const dynamicData = JSON.parse(record.formData);
            console.log('解析后的表单数据:', dynamicData);
            await nextTick();
            if (formApi.value) {
              console.log('准备设置表单数据到组件');
              // ä½¿ç”¨ form-create çš„ API è®¾ç½®å€¼
              Object.keys(dynamicData).forEach(key => {
                formApi.value.setValue(key, dynamicData[key]);
              });
              console.log('表单数据设置完成');
            } else {
              console.warn('表单API未就绪');
              message.error('表单未就绪,请重试');
            }
          } catch (error) {
            console.error('解析动态字段数据失败:', error);
            message.error('加载表单数据失败');
          }
        } else {
          console.log('记录中没有 formData æ•°æ®');
        }
      } catch (error) {
        console.error('处理表单数据失败:', error);
        message.error('加载表单数据失败,请重试');
      }
    } else {
      // æ–°å¢žæ—¶é‡ç½®æ•°æ®
      console.log('新增模式,重置表单');
      await nextTick();
      if (formApi.value) {
        formApi.value.resetFields && formApi.value.resetFields();
      }
    }
  } catch (error) {
    console.error('打开弹窗失败:', error);
    message.error('加载数据失败');
  } finally {
    modalLoading.value = false;
  }
};
const close = () => {
  modalVisible.value = false;
};
defineExpose({ open, close });
function onFormMounted(api) {
  formApi.value = api;
}
// handleOk åªè´Ÿè´£è§¦å‘表单 submit
async function handleOk() {
  await nextTick();
  console.log('handleOk formApi:', formApi.value);
  if (!dynamicFormRule.value || dynamicFormRule.value.length === 0) {
    message.error('表单规则未加载,无法提交');
    return;
  }
  if (!formApi.value) {
    message.error('表单未渲染完成,请稍后重试');
    return;
  }
  try {
    modalLoading.value = true;
    const valid = await formApi.value.validate();
    if (!valid) {
      modalLoading.value = false;
      return;
    }
    const formData = formApi.value.formData();
    await onFormSubmit(formData);
  } catch (error) {
    console.error('保存失败:', error);
    message.error('保存失败');
    modalLoading.value = false;
  }
}
// form-create çš„ submit äº‹ä»¶å¤„理
async function onFormSubmit(dynamicData: any) {
  try {
    modalLoading.value = true;
    // æž„建保存数据
    const finalData = {
      pageId: currentPageId.value,
      formData: Object.keys(dynamicData).length > 0 ? JSON.stringify(dynamicData) : undefined
    };
    if (isUpdate.value) {
      finalData.id = currentEditId.value;
    }
    console.log(finalData)
    await (isUpdate.value ? templateUpdate(finalData) : templateAdd(finalData));
    emit('reload');
    close();
    message.success('保存成功');
  } catch (error) {
    console.error('保存失败:', error);
    message.error('保存失败');
  } finally {
    modalLoading.value = false;
  }
}
function handleCancel() {
  close();
}
</script>
<template>
  <a-modal
    v-model:open="modalVisible"
    :title="title"
    :width="'80vw'"
    :confirm-loading="modalLoading"
    @ok="handleOk"
    @cancel="handleCancel"
    :bodyStyle="{ padding: '24px', minHeight: '60vh' }"
    destroyOnClose
    :ok-button-props="{ style: showOkBtn ? {} : { display: 'none' } }"
  >
    <!-- åŠ¨æ€è¡¨å•åŒºåŸŸ -->
    <form-create
      ref="dynamicFormRef"
      :rule="dynamicFormRule"
      :option="dynamicFormOption"
      @submit="onFormSubmit"
      @mounted="onFormMounted"
    />
  </a-modal>
</template>
ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<template>
  <div>工作下发</div>
</template>
<script>
export default {
  name: "index"
}
</script>
<style scoped>
</style>
ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<template>
  <div>我的工作</div>
</template>
<script>
export default {
  name: "index"
}
</script>
<style scoped>
</style>
ruoyi-ui/apps/web-antd/src/views/work/statistics/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<template>
<div>工作统计</div>
</template>
<script>
export default {
  name: "index"
}
</script>
<style scoped>
</style>