从0到1构建MES系统17-大屏设计器

122 阅读6分钟

一、概述

在现代制造企业的数字化转型中,生产过程数据、设备状态、质量监控等信息需要以直观的方式呈现给管理者和一线人员。数据可视化大屏因此成为 MES(Manufacturing Execution System)系统的标配模块之一。
本文结合我在 MES 行业的开发与业务经验,分享如何将开源 go-view 大屏设计器无缝集成到现有 MES 项目中,并实现与企业级登录权限、业务数据接口的打通。

二、背景

传统 MES 系统的数据展示多依赖报表或固定页面,迭代慢、交互差,难以满足生产现场对实时、灵活展示的需求。
而 go-view 作为一款基于 Vue3 + TypeScript 的开源大屏设计器,支持可视化拖拽、组件化管理、动态数据绑定等特性,可以帮助开发团队快速构建炫酷、实时的数据大屏。
优势总结

  • 开源免费,二次开发成本低
  • 丰富的图表与组件生态,支持实时数据刷新
  • 支持自定义接口与多种数据源,便于和 MES、WMS、QMS、EAM 等模块对接

三、功能介绍

在本次集成方案中,我们的目标包括:

  1. 可视化设计:运维或业务人员可在浏览器中拖拽组件,自由排版,配置数据源。
  2. 数据实时渲染:对接 MES 项目的接口,展示产线 OEE、设备稼动率、WMS 库存等关键指标。
  3. 统一权限管理:兼容原有的企业 SSO 或 Token 认证体系,保证数据安全。
  4. 附件与资源管理:支持从项目后台读取图片、视频等静态资源,满足丰富的展示需求。

以下是集成后界面展示:

Pasted image 20250912173437.png

Pasted image 20250912173515.png

以下是项目中案例展示

Pasted image 20250912174105.png

Pasted image 20250912174328.png

Pasted image 20250912174358.png

Pasted image 20250912174416.png

Pasted image 20250912174544.png

Pasted image 20250912174823.png

四、开发设计

1. 引入 go-view

  • go-view 作为独立子应用部署,建议使用 vite 打包产物。
  • 通过 iframe 方式挂载到 MES 前端框架中,确保与现有路由不冲突。
git clone https://gitee.com/dromara/go-view.git
cd go-view
pnpm install
pnpm build

注意: 这里使用的是go-view的1.x版本,在项目上出现了情况,由于有些电视的浏览器版本过低使用 2.x 版本的话可能会导致 go-view运行报错。

2. 改造数据接口

后端

/**  
 * 大屏项目Controller  
 * * @author fwj * @since 2025-08-08 */@Slf4j  
@Tag(name = "大屏项目控制器", description = "大屏项目控制器")  
@RestController  
@RequestMapping("/goview/project")  
public class GoviewProjectController {  
    @Autowired  
    private GoviewProjectService goviewProjectService;  
  
    @Autowired  
    private GoviewProjectDataService goviewProjectDataService;  
  
    @Autowired  
    private GoviewFileService goviewFileService;  
  
    @Autowired  
    private FileServiceContext fileServiceContext;  
  
    @Operation(summary = "查询大屏项目列表", description = "查询大屏项目列表")  
    @PostMapping("/pageList")  
    public Result<IPage<GoviewProject>> pageList(@Parameter(description = "查询参数") @RequestBody GoviewProjectParam goviewProjectParam) {  
        Page<GoviewProject> page = new Page<>(goviewProjectParam.getPage(), goviewProjectParam.getPageSize());  
        GoviewProject goviewProject = new GoviewProject();  
        BeanUtils.copyProperties(goviewProjectParam, goviewProject);  
        QueryWrapper<GoviewProject> wrapper = QueryWrapperUtil.build(goviewProject);  
        IPage<GoviewProject> list = goviewProjectService.page(page, wrapper);  
        return Result.success(list);  
    }  
  
    @Operation(summary = "获取大屏项目详细信息", description = "获取大屏项目详细信息")  
    @GetMapping(value = "/{id}")  
    public Result getInfo(@Parameter(description = "大屏项目ID") @PathVariable("id") Long id) {  
        return Result.success(goviewProjectService.getById(id));  
    }  
    @Operation(summary = "新增大屏项目", description = "新增大屏项目")  
    @PostMapping  
    public Result<?> save(@Parameter(description = "大屏项目") @RequestBody GoviewProjectVo goviewProject) {  
        goviewProjectService.saveProject(goviewProject);  
        return Result.success("保存成功", goviewProject.getId());  
    }  
  
    @Operation(summary = "修改大屏项目", description = "修改大屏项目")  
    @PutMapping  
    public Result<?> update(@Parameter(description = "大屏项目") @RequestBody GoviewProjectVo goviewProject) {  
        goviewProjectService.updateProject(goviewProject);  
        return Result.success("保存成功", goviewProject.getId());  
    }  
  
    @Operation(summary = "删除大屏项目", description = "删除大屏项目")  
    @DeleteMapping  
    public Result<?> remove(@Parameter(description = "删除ID") @RequestParam("ids") Long[] ids) {  
        return Result.success(goviewProjectService.removeByIds(Arrays.asList(ids)));  
    }  
  
    @Operation(summary = "项目重命名", description = "项目重命名")  
    @PostMapping("/rename")  
    public Result rename(@RequestBody GoviewProject goviewProject)  
    {  
        LambdaUpdateWrapper<GoviewProject> updateWrapper= new LambdaUpdateWrapper<>();  
        updateWrapper.eq(GoviewProject::getId, goviewProject.getId());  
        updateWrapper.set(GoviewProject::getProjectName, goviewProject.getProjectName());  
        goviewProjectService.update(updateWrapper);  
        return Result.success();  
    }  
  
  
    @Operation(summary="发布/取消项目状态", description="Customize Toolbar…")  
    @PutMapping("/publish")  
    public Result updateVisible(@RequestBody GoviewProject goviewProject){  
        if (goviewProject.getId() == null) {  
            throw new BizException("项目ID不能为空!");  
        }  
        if(goviewProject.getState()==1||goviewProject.getState()==0) {  
            LambdaUpdateWrapper<GoviewProject> updateWrapper=new LambdaUpdateWrapper<GoviewProject>();  
            updateWrapper.eq(GoviewProject::getId, goviewProject.getId());  
            updateWrapper.set(GoviewProject::getState, goviewProject.getState());  
            goviewProjectService.update(updateWrapper);  
            return Result.success();  
        }  
        return Result.error("警告非法字段");  
    }  
  
  
    @Operation(summary = "获取项目存储数据", description = "获取项目存储数据")  
    @GetMapping("/getData")  
    public Result getData(@RequestParam("projectId") Long projectId)  
    {  
        GoviewProject goviewProject= goviewProjectService.getById(projectId);  
        GoviewProjectVo goviewProjectVo=new GoviewProjectVo();  
        BeanUtils.copyProperties(goviewProject,goviewProjectVo);  
  
        GoviewProjectData blogText=goviewProjectDataService.getByProjectId(projectId);  
        if(blogText!=null) {  
            goviewProjectVo.setContent(blogText.getContent());  
        }  
        return Result.success(goviewProjectVo);  
  
    }  
  
    /**  
     * 上传文件  
     * @param file 文件流对象  
     * @return 文件信息  
     */  
    @Operation(summary="上传文件", description="上传文件")  
    @PostMapping("/upload")  
    public Result upload(@RequestParam("file") MultipartFile file) {  
        String fileName = file.getOriginalFilename();  
        //默认文件格式  
        String suffixName = ".png";  
        String mediaKey="";  
        Long filesize= file.getSize();  
        //文件名字  
        String fileSuffixName="";  
        if(fileName.lastIndexOf(".")!=-1) {//有后缀  
            suffixName = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();  
        }  
  
        String path = fileServiceContext.uploadFile(file, "goview");  
  
        GoviewFile goviewFile=new GoviewFile();  
        goviewFile.setFileName(fileName);  
        goviewFile.setFileSize(filesize);  
        goviewFile.setFileSuffix(fileSuffixName);  
        goviewFile.setFilePath(path);  
        goviewFileService.save(goviewFile);  
        return Result.success("上传成功", goviewFile.getId());  
    }  
  
    /**  
     * 下载文件  
     *  
     * @param attachmentId 附件ID  
     */    @GetMapping("/download/{fileId}")  
    public void fileDownload(@PathVariable("fileId") Long fileId, HttpServletResponse response, HttpServletRequest request)  
    {  
        try  
        {  
            GoviewFile goviewFile = goviewFileService.getById(fileId);  
            if (goviewFile == null) {  
                throw new FileException("获取文件失败!");  
            }  
  
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);  
            FileUtils.setAttachmentResponseHeader(response, goviewFile.getFileName());  
  
            byte[] bytes = fileServiceContext.downloadFile(goviewFile.getFilePath());  
  
            FileUtils.writeBytes(bytes, response.getOutputStream());  
  
        }catch (Exception e){  
            log.error("下载文件失败", e);  
            throw new FileException("下载文件失败!", e);  
        }  
    }  
}

数据库设计

大屏项目 (data_goview_project)

字段名数据类型允许空值主键注释
idbigint主键
project_namevarchar(256)项目名
stateint状态
is_deleteint是否删除
index_imagevarchar(1024)封面
remarkvarchar(1024)备注
create_bybigint
create_timedatetime创建时间
update_bybigint更新人
update_timedatetime更新时间
**大屏项目数据 (data_goview_project_data) **
字段名数据类型允许空值主键注释
idbigint主键
project_idbigint项目ID
contenttext项目数据
create_timedatetime创建时间
create_bybigint创建人
大屏图片 (data_goview_file)
字段名数据类型允许空值主键注释
idbigint主键
file_namevarchar(128)文件名
file_sizeint文件大小
file_suffixvarchar(128)文件后缀
file_pathvarchar(1024)文件路径
create_bybigint创建人
create_timedatetime创建时间

前端改造

  • 修改/src/views/project/items/components/ProjectItemsList/index.vue获取列表数据、发布、编辑、删除的方法,调用新的API和项目后端做交互。
  • 修改 /src/views/project/items/components/ProjectItemsCard/index.vue 卡片图片显示的 src ,修改成后后端获取到图片的接口。
  • 修改 /src/views/chart/ContentEdit/components/EditTools/index.vue 保存、更新、文件上传、获取项目接口。

3. 统一登录与权限

为保证安全与一致的用户体验,需要解决 go-view 与原系统登录体系不一致 的问题。 方案:使用后端签发的 秘钥 获取 Token,再由大屏设计器在请求头中携带。 流程如下:

  1. 首先在系统中为数据大屏新建一个web令牌 ![[Pasted image 20250912172316.png]]
  2. MES 后端提供 /system/token/login 接口,使用sa-token工具,登录之后会自动把token写入到请求头中。
  3. go-view 在初始化时调用该接口
  4. 在axios拦截器中先判断时候有token,如果没有先请求获取token的方法。
// 通过秘钥获取token
export const getJwtToken = async (data: { appCode: string; secret: string }) => {
	return await http(RequestHttpEnum.POST)('/system/token/login', data);
};

4. 部署与运维

  • 前端:大屏设计器前端可独立部署在 Nginx 上,通过子路径(如 /bigscreen/)访问。
  • 后端:接口与 MES 后端共用一套微服务架构,复用现有日志、监控、权限系统。

五、总结

在 MES 项目中集成 go-view 大屏设计器,不仅能快速构建高可视化、可拖拽的生产数据大屏,还能通过秘钥登录、接口改造实现与现有权限、数据体系的无缝衔接。
这一方案极大提升了生产现场的可视化水平运维灵活性,对于需要统一管理 WMS、QMS、EAM、CRM、SCM 等模块的制造企业具有很高的实践价值。

本文源码已上传Gitee 开源项目地址

欢迎在评论区分享你的技术选型经验,或对本文方案的改进建议!

关注公众号「慧工云创」

扫码_搜索联合传播样式-标准色版.png