办学质量监测教学评价系统
康鲁杰
昨天 78314c7574a880a1bccbdf9a8a531d3cf025a6b4
页面设计前后端
已修改2个文件
已添加12个文件
1002 ■■■■■ 文件已修改
ruoyi-modules/sc-page-designer/pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | 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/domain/PageDesigner.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java 28 ●●●●● 补丁 | 查看 | 原始文档 | 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/service/PageDesignerService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/resources/mapper/SysUserMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | 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/views/tool/page-designer/data.tsx 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue 321 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>
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/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; // å…³è”的菜单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,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;  // å¯ç”¨çš„æ“ä½œé¡¹ï¼ˆå¢žåˆ æ”¹æŸ¥ï¼‰
}
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; // åˆ›å»ºäººä¿¡æ¯
}
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/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/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;
    }
}
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">
    <!-- å¤šç»“构嵌套自动映射需带上每个实体的主键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"/>
    </resultMap>
    <select id="selectPagelistAll" resultMap="PageDesignerResult">
        select
            id,
            name,
            status
        from page_designer
        ${ew.getCustomSqlSegment}
    </select>
</mapper>
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/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: '正常', 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' ? '正常' : '停用';
      },
    },
  },
  {
    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: '正常', 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,
    },
  },
];
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>
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([]); // å¤šé€‰æ¡†é€‰ä¸­çš„字段key
const fieldOptions = ref([]);   // è®¾è®¡åŒºæ‰€æœ‰å­—段
const modalVisible = ref(false);
const modalLoading = ref(false);
const currentEditId = ref<string | number>(''); // å½“前编辑的ID
// æ‰“开弹窗
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('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,其次parentId
          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);
    // è½¬æ¢å¯ç”¨åŠŸèƒ½ä¸º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>