From 78314c7574a880a1bccbdf9a8a531d3cf025a6b4 Mon Sep 17 00:00:00 2001
From: 康鲁杰 <60095866+KangLujie@users.noreply.github.com>
Date: 星期二, 24 六月 2025 10:13:28 +0800
Subject: [PATCH] 页面设计前后端

---
 ruoyi-modules/sc-page-designer/pom.xml                                                                        |    4 
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java                |   28 +
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java               |   29 +
 ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue                                           |  321 ++++++++++++++++++
 ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx                                                  |  113 ++++++
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java          |   29 +
 ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts                                                    |   52 +++
 ruoyi-modules/sc-page-designer/src/main/resources/mapper/SysUserMapper.xml                                    |   27 +
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java    |   72 ++++
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java |   97 +++++
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java                  |   38 ++
 ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts                                                  |   21 +
 ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java            |   20 +
 ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue                                                 |  151 ++++++++
 14 files changed, 997 insertions(+), 5 deletions(-)

diff --git a/ruoyi-modules/sc-page-designer/pom.xml b/ruoyi-modules/sc-page-designer/pom.xml
index c77f98f..400f53d 100644
--- a/ruoyi-modules/sc-page-designer/pom.xml
+++ b/ruoyi-modules/sc-page-designer/pom.xml
@@ -20,5 +20,9 @@
             <groupId>org.ruoyi</groupId>
             <artifactId>ruoyi-common-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-system-api</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java
new file mode 100644
index 0000000..16fad07
--- /dev/null
+++ b/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("鍒犻櫎鎴愬姛");
+    }
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java
new file mode 100644
index 0000000..0e2916b
--- /dev/null
+++ b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java
@@ -0,0 +1,38 @@
+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; // 鍏宠仈鐨勮彍鍗旾D
+    @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 瀛楃涓�
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java
new file mode 100644
index 0000000..977f6fb
--- /dev/null
+++ b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java
@@ -0,0 +1,29 @@
+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;
+import org.ruoyi.system.domain.SysUser;
+
+import java.util.List;
+
+/**
+ * @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;  // 鍚敤鐨勬搷浣滈」锛堝鍒犳敼鏌ワ級
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java
new file mode 100644
index 0000000..3b98968
--- /dev/null
+++ b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java
@@ -0,0 +1,28 @@
+package org.ruoyi.pageDesigner.domain;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.ruoyi.core.domain.BaseEntity;
+import org.ruoyi.system.domain.vo.SysMenuVo;
+
+import java.io.Serializable;
+
+/**
+ * @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 menuId;     // 鍏宠仈鑿滃崟 ID
+    private String parentId;     // 鍏宠仈鑿滃崟 ID
+    private String status;     // 鍚敤鐘舵��
+    private String remark;     // 澶囨敞
+    private String formJson;   // 琛ㄥ崟璁捐 JSON 瀛楃涓�
+    private String showColumn;   // 瀛楁鏄剧ず閰嶇疆
+    private String actionsFunc;  // 鍚敤鐨勬搷浣滈」锛堝鍒犳敼鏌ワ級
+    private SysMenuVo menu; // 鍒涘缓浜轰俊鎭�
+    private SysMenuVo parent; // 鍒涘缓浜轰俊鎭�
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java
new file mode 100644
index 0000000..f3757c7
--- /dev/null
+++ b/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);
+
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java
new file mode 100644
index 0000000..2289ae9
--- /dev/null
+++ b/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);
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java
new file mode 100644
index 0000000..b8e42d8
--- /dev/null
+++ b/ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java
@@ -0,0 +1,97 @@
+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.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.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 ObjectMapper objectMapper;
+
+    @Override
+    public void add(PageDesignerDTO dto) {
+        PageDesigner entity = convertToEntity(dto);
+        this.save(entity);
+    }
+
+    @Override
+    public void updatePage(PageDesignerDTO dto) {
+        PageDesigner entity = convertToEntity(dto);
+        this.updateById(entity);
+    }
+
+    @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) {
+        this.removeByIds(ids);
+    }
+
+    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());
+        return entity;
+    }
+}
diff --git a/ruoyi-modules/sc-page-designer/src/main/resources/mapper/SysUserMapper.xml b/ruoyi-modules/sc-page-designer/src/main/resources/mapper/SysUserMapper.xml
new file mode 100644
index 0000000..033657e
--- /dev/null
+++ b/ruoyi-modules/sc-page-designer/src/main/resources/mapper/SysUserMapper.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.PageDesignerMapper">
+
+    <!-- 澶氱粨鏋勫祵濂楄嚜鍔ㄦ槧灏勯渶甯︿笂姣忎釜瀹炰綋鐨勪富閿甶d 鍚﹀垯鏄犲皠浼氬け璐� -->
+    <resultMap type="org.ruoyi.pageDesigner.domain.PageDesignerVo" id="PageDesignerResult">
+        <id property="id" column="id"/>
+        <result property="name" column="name"/>
+        <result property="status" column="status"/>
+    </resultMap>
+
+
+    <select id="selectPagelistAll" resultMap="PageDesignerResult">
+        select
+            id,
+            name,
+            status
+        from page_designer
+        ${ew.getCustomSqlSegment}
+    </select>
+
+
+
+
+</mapper>
diff --git a/ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts b/ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts
new file mode 100644
index 0000000..79fa508
--- /dev/null
+++ b/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 });
+}
diff --git a/ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts b/ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts
new file mode 100644
index 0000000..88d885d
--- /dev/null
+++ b/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; // 涓婄骇鐩綍锛堣彍鍗旾D锛�
+  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;
+}
diff --git a/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx b/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx
new file mode 100644
index 0000000..eaf43de
--- /dev/null
+++ b/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx
@@ -0,0 +1,113 @@
+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: '姝e父', value: '1' },
+        { label: '鍋滅敤', value: '0' },
+      ],
+    },
+    fieldName: 'status',
+    label: '椤甸潰鐘舵��',
+  },
+];
+
+export const columns: VxeGridProps['columns'] = [
+  {
+    title: '椤甸潰鍚嶇О',
+    field: 'name',
+  },
+  {
+    title: '涓婄骇鐩綍',
+    field: 'parentName',
+    slots: {
+      default: ({ row }) => {
+        return row.parent?.name || row.menu?.menuName || '-';
+      },
+    },
+  },
+  {
+    title: '鐘舵��',
+    field: 'status',
+    slots: {
+      default: ({ row }) => {
+        return row.status === '1' ? '姝e父' : '鍋滅敤';
+      },
+    },
+  },
+  {
+    title: '鍒涘缓鏃堕棿',
+    field: 'createTime',
+  },
+  {
+    field: 'action',
+    fixed: 'right',
+    slots: { default: 'action' },
+    title: '鎿嶄綔',
+  },
+];
+
+export const drawerSchema = () => [
+  {
+    component: 'Input',
+    fieldName: 'name',
+    label: '椤甸潰鍚嶇О',
+    rules: 'required',
+  },
+  {
+    component: 'TreeSelect',
+    fieldName: 'menuId',
+    label: '涓婄骇鐩綍',
+    componentProps: {
+      allowClear: true,
+      showSearch: true,
+    },
+    rules: 'selectRequired',
+  },
+  {
+    component: 'Select',
+    fieldName: 'status',
+    label: '鐘舵��',
+    componentProps: {
+      options: [
+        { label: '姝e父', value: '1' },
+        { label: '鍋滅敤', value: '0' },
+      ],
+    },
+    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,
+    },
+  },
+]; 
\ No newline at end of file
diff --git a/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue b/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue
index d4fea39..af1bcf1 100644
--- a/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue
+++ b/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue
@@ -1,13 +1,154 @@
 <template>
-  <div>111</div>
+  <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>
+          <ghost-button class="btn-success" @click="handleSubAdd(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()" />
+  </Page>
+  <Fallback v-else description="鎮ㄦ病鏈夐〉闈㈢敓鎴愬櫒鐨勮闂潈闄�" status="403" />
 </template>
 
-<script>
-export default {
-  name: "index"
+<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';
+
+// 绉婚櫎mock鏁版嵁
+// const pageList = async (params: any) => { ... };
+// const pageRemove = async (ids: number[]) => {};
+
+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: VxeGridProps = {
+  columns,
+  height: 'auto',
+  keepSource: true,
+  pagerConfig: {
+    enabled: true,
+  },
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues = {}) => {
+        console.log('鏌ヨ鍙傛暟:', { page, formValues });
+        const resp = await pageList({
+          pageNum: page.currentPage,
+          pageSize: page.pageSize,
+          ...formValues,
+        });
+        console.log('鎺ュ彛杩斿洖鏁版嵁:', resp);
+        return {
+          rows: resp.rows,
+          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>
diff --git a/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue b/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue
new file mode 100644
index 0000000..a73c4ee
--- /dev/null
+++ b/ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue
@@ -0,0 +1,321 @@
+<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 { menuList } from '../../../api/system/menu';
+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([]); // 澶氶�夋閫変腑鐨勫瓧娈祂ey
+const fieldOptions = ref([]);   // 璁捐鍖烘墍鏈夊瓧娈�
+const modalVisible = ref(false);
+const modalLoading = ref(false);
+const currentEditId = ref<string | number>(''); // 褰撳墠缂栬緫鐨処D
+
+// 鎵撳紑寮圭獥
+const open = async (params: ModalProps = { update: false }) => {
+  try {
+    modalVisible.value = true;
+    modalLoading.value = true;
+    isUpdate.value = params.update;
+    currentEditId.value = params.id || ''; // 淇濆瓨褰撳墠缂栬緫鐨処D
+    await setupPageSelect();
+    
+    if (params.id) {
+      await formApi.setFieldValue('menuId', params.id);
+      if (params.update) {
+        // 鑾峰彇璇︽儏鏁版嵁
+        const record = await pageInfo(params.id);
+        console.log('缂栬緫鏁版嵁:', record);
+        
+        // 璁剧疆鍩虹琛ㄥ崟鏁版嵁
+        await formApi.setValues({
+          name: record.name,
+          menuId: record.menuId || record.parentId || params.id,  // 浼樺厛浣跨敤menuId锛屽叾娆arentId
+          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);
+              console.log('鎭㈠閫変腑瀛楁:', selectedFields.value);
+            }
+          } 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() {
+  // 鑾峰彇鑿滃崟鏁版嵁
+  const menuArray = await menuList();
+  // 杩囨护鎺夋寜閽被鍨�
+  const filteredList = 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' });
+  // 鍔犳牴鑺傜偣
+  const fullMenuTree = [
+    {
+      menuId: 0,
+      menuName: $t('menu.root'),
+      children: menuTree,
+    },
+  ];
+  // 鐢熸垚鍏ㄨ矾寰勫悕
+  addFullName(fullMenuTree, 'menuName', ' / ');
+
+  formApi.updateSchema([
+    {
+      componentProps: {
+        fieldNames: {
+          label: 'menuName',
+          value: 'menuId',
+          children: 'children'
+        },
+        getPopupContainer,
+        listHeight: 300,
+        showSearch: true,
+        treeData: fullMenuTree,
+        treeDefaultExpandAll: false,
+        treeDefaultExpandedKeys: [0],
+        treeLine: { showLeafIcon: false },
+        treeNodeFilterProp: 'menuName',
+        treeNodeLabelProp: 'fullName',
+      },
+      fieldName: 'menuId',
+    },
+  ]);
+}
+
+
+// 鍚屾鎵�鏈夊瓧娈靛埌閫変腑鐘舵��
+const syncAllFields = () => {
+  // 鑾峰彇琛ㄥ崟缁勪欢鐨勮鍒欐弿杩�
+  const formDesc = designer.value?.getFormDescription?.();
+  console.log('琛ㄥ崟缁勪欢鎻忚堪:', formDesc);
+  
+  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
+    }));
+  
+  console.log('鎻愬彇鐨勫瓧娈�:', allFields);
+  
+  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 = () => {
+  console.log('璁捐鍣ㄥ唴瀹瑰彉鍖�');
+  nextTick(() => {
+    updateFieldOptions();
+  });
+};
+
+// 褰撹璁″櫒鍐呭鍙樺寲鏃舵洿鏂板瓧娈甸�夐」
+const updateFieldOptions = () => {
+  console.log('updateFieldOptions');
+  console.log('designer.value', designer.value);
+  
+  // 鑾峰彇琛ㄥ崟缁勪欢鐨勮鍒欐弿杩�
+  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
+  }));
+  
+  console.log('鏇存柊鍚庣殑瀛楁閫夐」:', fieldOptions.value);
+};
+
+// 鐩戝惉璁捐鍣ㄥ唴瀹瑰彉鍖�
+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);
+    // 杞崲鍚敤鍔熻兘涓篔SON瀛楃涓�
+    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> 
\ No newline at end of file

--
Gitblit v1.9.3