若依(ruoyi)前后端分离项目集成积木报表

news/2025/4/1 21:14:59/文章来源:https://www.cnblogs.com/coderlucas/p/18796503

若依(ruoyi)前后端分离项目集成积木报表

致敬:

1:致敬若依开源项目(本文使用的是前后端分离版本)

        若依官网:https://www.ruoyi.vip/

2:致敬积木开源项目

        积木报表官网:https://www.jimureport.com/

3:致敬CSDN大神的文章(我是根据他的原文一步步配置的,如有版权问题,可随时联系本人删除)

        原文地址:https://blog.csdn.net/qq_55896432/article/details/145060090

背景:

写在前面:

搭建这个项目是因为自己想记录日常使用的一些数据(练琴统计,钢琴课程数量,花费等等这些),然后找了一些BI发现都不是很适合,目前积木是最适合的(集成程度高),所以记录了一下整体的操作步骤。
目前整体使用感觉还是可以的,数据源数据集设置都很方便,必要的控件也都够用。

项目运行的前提条件:

能正常运行若依前后端分离版本就OK.

效果(部分截图)

普通报表管理菜单

普通报表设计

数据源&数据集设置

大屏报表管理菜单

大屏设计

数据源&数据集设置

后端配置

ruoyi-common模块下pom.xml文件添加依赖

        <!-- 积木报表 --><dependency><groupId>org.jeecgframework.jimureport</groupId><artifactId>jimureport-spring-boot-starter</artifactId><version>1.9.2</version></dependency><!--积木BI大屏--><dependency><groupId>org.jeecgframework.jimureport</groupId><artifactId>jimubi-spring-boot-starter</artifactId><version>1.9.1</version></dependency>

添加完重新加载maven

ruoyi-admin模块修改application.yml文件,新增积木报表相关配置

jeecg :# 权限配置permission:# 报表权限配置report:# 查询权限符号,配置此权限代表只能查看报表query: jeecg:report:query# 修改权限符号,配置此权限代表拥有报表所有权限edit: jeecg:report:edit# 大屏权限配置drag:# 查询权限符号,配置此权限代表只能查看大屏query: jeecg:drag:query# 修改权限符号,配置此权限代表拥有大屏所有权限edit: jeecg:drag:editjmreport:#自定义项目前缀# 现在是开发环境下的前端访问前缀,部署项目时,需要切换到生成环境访问前缀customPrePath: /dev-api

注意:1.目前报表和大屏分别设置两个类型的权限,其中query权限表示只能查看,edit权限表示用于一切权限。2.customPrePath是用来配置前端访问后端的接口的前缀,当前是开发环境配置为/dev-api没问题。3.项目发布时,一定将这个配置改为生产环境下访问前缀,如:/prod-api。

允许匿名访问ruoyi-framework模块下修改SecurityConfig.java文件

增加积木报表和大屏匿名访问权限。

, "/jmreport/**", "/drag/**"

新建积木报表相关配置类

在com.ruoyi.framework包下增加包report,在report包下新增config包,在cofig包下新增ReportConfig.java类,并在此类中添加以下代码。

package com.ruoyi.framework.report.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class ReportConfig {// 报表查询权限@Value("${jeecg.permission.report.query}")private String reportQueryPermission;// 报表修改权限@Value("${jeecg.permission.report.edit}")private String reportEditPermission;// 大屏查看权限@Value("${jeecg.permission.drag.query}")private String dragQueryPermission;//大屏修改权限@Value("${jeecg.permission.drag.edit}")private String dragEditPermission;public String getReportQueryPermission() {return reportQueryPermission;}public void setReportQueryPermission(String reportQueryPermission) {this.reportQueryPermission = reportQueryPermission;}public String getReportEditPermission() {return reportEditPermission;}public void setReportEditPermission(String reportEditPermission) {this.reportEditPermission = reportEditPermission;}public String getDragQueryPermission() {return dragQueryPermission;}public void setDragQueryPermission(String dragQueryPermission) {this.dragQueryPermission = dragQueryPermission;}public String getDragEditPermission() {return dragEditPermission;}public void setDragEditPermission(String dragEditPermission) {this.dragEditPermission = dragEditPermission;}
}

重写若依框架getLoginUser()方法

打开web.service包下的TokenService.java文件,将自带的getLoginUser方法注释掉

新增代码

    /***************************积木报表修改,注释掉了原来的getLoginUser*******************************/// 修改public LoginUser getLoginUser(HttpServletRequest request) {// 获取请求携带的令牌String token = getToken(request);return getLoginUser(token);}public LoginUser getLoginUser(String token) {if (StringUtils.isNotEmpty(token)) {try {Claims claims = parseToken(token);// 解析对应的权限以及用户信息String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);LoginUser user = redisCache.getCacheObject(userKey);return user;} catch (Exception e) {log.error("获取用户信息异常'{}'", e.getMessage());}}return null;}/***************************积木报表修改,注释掉了原来的getLoginUser*******************************/

在report包下新增service包下,在service包下新增ReportTokenService.java类,并且在此类中添加以下代码

package com.ruoyi.framework.report.service;import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.report.config.ReportConfig;
import com.ruoyi.framework.web.service.TokenService;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;@Component
public class ReportTokenService implements JmReportTokenServiceI {// 若依框架token@Value("${token.header}")private String ryHeader;// 积木报表tokenprivate String jmHeader = "X-Access-Token";@Autowiredprivate ReportConfig reportConfig;@Autowiredprivate TokenService tokenService;@Overridepublic String getUsername(String s) {LoginUser loginUser = tokenService.getLoginUser(s);return loginUser.getUsername();}@Overridepublic String[] getRoles(String s) {LoginUser loginUser = tokenService.getLoginUser(s);SysUser user = loginUser.getUser();List<SysRole> roles = user.getRoles();String[] roleNameArray = roles.stream().map(SysRole::getRoleName).toArray(String[]::new);return roleNameArray;}@Overridepublic Boolean verifyToken(String s) {LoginUser loginUser = tokenService.getLoginUser(s);if (StringUtils.isNotNull(loginUser)){tokenService.refreshToken(loginUser);SysUser user = loginUser.getUser();// 超级管理员放权if (StringUtils.isNotNull(user) && user.isAdmin()) {return true;} else {Set<String> permissions = loginUser.getPermissions();if (StringUtils.isNotNull(permissions) && (permissions.contains(reportConfig.getReportQueryPermission()) || permissions.contains(reportConfig.getReportEditPermission()) || permissions.contains(reportConfig.getDragQueryPermission()) || permissions.contains(reportConfig.getDragEditPermission()))) {return true;}}}return false;}@Overridepublic String getToken(HttpServletRequest request) {String token = request.getParameter("token");if (StringUtils.isNull(token)) {token = request.getHeader(jmHeader);}if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)){token = token.replace(Constants.TOKEN_PREFIX, "");}return token;}@Overridepublic Map<String, Object> getUserInfo(String token) {token = token.replace(Constants.TOKEN_PREFIX, "");LoginUser loginUser = tokenService.getLoginUser(token);Map<String, Object> map = new HashMap<>();map.put(SYS_USER_CODE, loginUser.getUserId());map.put(SYS_ORG_CODE, loginUser.getDeptId());return map;}@Overridepublic HttpHeaders customApiHeader() {HttpHeaders headers = new HttpHeaders();headers.add(ryHeader, Constants.TOKEN_PREFIX + getToken());headers.add(jmHeader, getToken());return headers;}
}

贴一下原作者的注意事项:

配置拦截器

在report包下新增interceptor包,在interceptor包下新增ReportInterceptor.java类,并且在此类中添加以下代码

package com.ruoyi.framework.report.interceptor;import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.report.config.ReportConfig;
import com.ruoyi.framework.report.service.ReportTokenService;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;@Component
public class ReportInterceptor implements HandlerInterceptor {@Autowiredprivate TokenService tokenService;@Autowiredprivate ReportConfig reportConfig;@Autowiredprivate ReportTokenService reportTokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = reportTokenService.getToken(request);LoginUser loginUser = tokenService.getLoginUser(token);
//        String uri = request.getRequestURI();
//        System.out.println(uri);if (StringUtils.isNotNull(loginUser)) {SysUser user = loginUser.getUser();// 超级管理员放权if (StringUtils.isNotNull(user) && user.isAdmin()) {return true;} else {//获取权限集合Set<String> permissions = loginUser.getPermissions();//如果拥有设计器的权限,则无需view权限,也可以通过校验if (StringUtils.isNotNull(permissions)) {String uri = request.getRequestURI();// 如果访问报表if (uri.contains("/jmreport/")) {// 如果有操作权限,直接放行if (permissions.contains(reportConfig.getReportEditPermission())) {return true;} else {// 设置查询报表的路径,没有带报表编码的路径Set<String> queryReportSet = new HashSet<>();queryReportSet.add("/jmreport/getQueryInfo");queryReportSet.add("/jmreport/show");// 设置查询报表的路径,带报表编码的路径ArrayList<String> queryReportList = new ArrayList<>();queryReportList.add("/jmreport/view/");queryReportList.add("/jmreport/addViewCount/");queryReportList.add("/jmreport/checkParam/");// 如果有查询权限if (permissions.contains(reportConfig.getReportQueryPermission())) {// 如果是没有报表编码的路径,放行if (queryReportSet.contains(uri)) {return true;} else {// 如果是带报表编码的路径,放行for (int i = 0; i < queryReportList.size(); i++) {String s = queryReportList.get(i);if (uri.contains(s)) {return true;}}}}}//如果访问大屏} else if (uri.contains("/drag/")) {// 如果有操作权限,直接放行if (permissions.contains(reportConfig.getDragEditPermission())) {return true;} else {// 设置查询大屏的路径,完全路径Set<String> queryDragSet = new HashSet<>();queryDragSet.add("/drag/page/queryById");queryDragSet.add("/drag/page/addVisitsNumber");// 设置查询大屏的路径,带有包含部分路径ArrayList<String> queryDragList = new ArrayList<>();queryDragList.add("/drag/share/view/");queryDragList.add("/drag/mock/json/");// 如果有查询权限,并且访问的查询接口,放行if (permissions.contains(reportConfig.getDragQueryPermission()) && queryDragSet.contains(uri)) {return true;}}// 拦截非报表和大屏路径处理,正确配置,不会出现此情况。} else {return true;}}}}AjaxResult ajaxResult = AjaxResult.error(403, "当前操作没有权限");ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));return false;}
}

原作者说的注意事项:

打开config包下的ResourcesConfig.java文件,注入ReportInterceptor拦截器,并且注册拦截器和设置拦截规则

修改addInterceptors()方法

  /*** 自定义拦截规则*/@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**")// 积木报表新增:不拦截静态资源.excludePathPatterns("/*.**, /**/*.html, /**/*.css, /**/*.js, /**/*.png, /**/*.jpg, /**/*.woff, /**/*.woff2, /**/*.ttf,  /**/*.svg, /**/*.ico, /**/*.map" );}

如果出现未设置的静态资源,需要自己进行设置。

实现数据格式转换器

在report包下新增adapter包,在adapter包下,新增ReportDataConvertAdapter.java文件,并且在此类中添加以下代码

package com.ruoyi.framework.report.adapter;import com.alibaba.fastjson.JSONObject;
import org.jeecg.modules.jmreport.desreport.render.handler.convert.ApiDataConvertAdapter;
import org.springframework.stereotype.Component;@Component("reportDataConvertAdapter")
public class ReportDataConvertAdapter implements ApiDataConvertAdapter {@Overridepublic String getData(JSONObject jsonObject) {if(jsonObject.containsKey("data")){String data = jsonObject.getString("data");return data;}else if(jsonObject.containsKey("rows")){return jsonObject.getString("rows");}else {return jsonObject.toJSONString();}}
}

去官网拿积木的数据结构,在数据库执行

官网地址:

https://github.com/jeecgboot/jimureport/blob/master/db/jimureport.mysql5.7.create.sql

前端配置

新建jeecg文件夹,并且jeecg文件夹下,新增request.js文件,并且在此文件中添加以下代码

import axios from 'axios'
import { Notification, MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams } from "@/utils/ruoyi";
import cache from '@/plugins/cache'// 是否显示重新登录
export let isRelogin = { show: false };axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: process.env.VUE_APP_BASE_API,// 超时timeout: 10000
})// request拦截器
service.interceptors.request.use(config => {// 是否需要设置 tokenconst isToken = (config.headers || {}).isToken === false// 是否需要防止数据重复提交const isRepeatSubmit = (config.headers || {}).repeatSubmit === falseif (getToken() && !isToken) {config.headers['X-Access-Token'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改}// get请求映射params参数if (config.method === 'get' && config.params) {let url = config.url + '?' + tansParams(config.params);url = url.slice(0, -1);config.params = {};config.url = url;}if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {const requestObj = {url: config.url,data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,time: new Date().getTime()}const sessionObj = cache.session.getJSON('sessionObj')if (sessionObj === undefined || sessionObj === null || sessionObj === '') {cache.session.setJSON('sessionObj', requestObj)} else {const s_url = sessionObj.url;                  // 请求地址const s_data = sessionObj.data;                // 请求数据const s_time = sessionObj.time;                // 请求时间const interval = 1000;                         // 间隔时间(ms),小于此时间视为重复提交if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {const message = '数据正在处理,请勿重复提交';console.warn(`[${s_url}]: ` + message)return Promise.reject(new Error(message))} else {cache.session.setJSON('sessionObj', requestObj)}}}return config
}, error => {console.log(error)Promise.reject(error)
})// 响应拦截器
service.interceptors.response.use(res => {// 未设置状态码则默认成功状态const code = res.data.code || 200;// 获取错误信息const msg = errorCode[code] || res.data.msg || res.data.message || errorCode['default']// 二进制数据则直接返回if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {return res}if (code === 401) {if (!isRelogin.show) {isRelogin.show = true;MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {isRelogin.show = false;store.dispatch('LogOut').then(() => {location.href = '/index';})}).catch(() => {isRelogin.show = false;});}return Promise.reject('无效的会话,或者会话已过期,请重新登录。')} else if (code === 500) {Message({ message: msg, type: 'error' })return Promise.reject(new Error(msg))} else if (code === 601) {Message({ message: msg, type: 'warning' })return Promise.reject('error')} else if (code !== 200) {Notification.error({ title: msg })return Promise.reject('error')} else {return res.data}},error => {console.log('err' + error)let { message } = error;if (message == "Network Error") {message = "后端接口连接异常";} else if (message.includes("timeout")) {message = "系统接口请求超时";} else if (message.includes("Request failed with status code")) {message = "系统接口" + message.substr(message.length - 3) + "异常";}Message({ message: message, type: 'error', duration: 5 * 1000 })return Promise.reject(error)}
)export default service

在jeecg文件夹下,新增report.js文件,并且在此文件中添加以下代码

import request from "@/api/jeecg/request";
let jmreportUrl = "/jmreport";
import { getToken } from "@/utils/auth";
let paramObj = {token: "Bearer " + getToken()
}
// 获取填报报表
export function listReport(queryParams) {let params = {...paramObj,...queryParams}return request({url: jmreportUrl + "/excelQuery",method: 'get',params: params})
}// 获取填报报表列表
export function listFillReport(queryParams) {paramObj["reportType"] = "1011126161407836160";let params = {...paramObj,...queryParams}return request({url: jmreportUrl + "/excelQuery",method: 'get',params: params})
}

在src/view文件夹下新增jeecg文件夹,在jeecg文件夹下新增report文件夹,在report文件夹下新增index.vue文件,并且在此文件中添加以下代码

<template><div><i-frame :src="reportUrl"></i-frame></div>
</template>
<script>
import { getToken } from "@/utils/auth";
import iFrame from "@/components/iFrame/index";
export default {name: "PyJeecgReport",components: { iFrame },data() {return {reportUrl: process.env.VUE_APP_BASE_API + "/jmreport/list?token=Bearer " + getToken()}}
}
</script>

在report文件夹下新增view文件夹,在view文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template><div><i-frame :src="reportUrl"></i-frame></div>
</template>
<script>
import { getToken } from "@/utils/auth";
import iFrame from "@/components/iFrame/index";
export default {name: "PyJeecgReportView",components: { iFrame },data() {return {reportUrl: ""}},created() {let query = this.$route.query;let code = undefined;let paramsString = "";if (query) {for (const key in query) {if (Object.hasOwnProperty.call(query, key)) {const param = query[key];if (key == "code") {code = param;} else {if (paramsString != "") {paramsString += paramsString + "&" + key + "=" + param;} else {paramsString = "&" + key + "=" + param;}}}}}if (code) {this.reportUrl = process.env.VUE_APP_BASE_API + "/jmreport/view/" + code + "?token=Bearer " + getToken() + paramsString;} else {this.$modal.msgError("报表编码(code)为空,无法打开报表");}}
}
</script>

在jeecg文件夹下新增drag文件夹,在drag文件夹下新增index.vue文件,并且在此文件中添加以下代码

<template><div><i-frame :src="dragUrl"></i-frame></div>
</template>
<script>
import { getToken } from "@/utils/auth";
import iFrame from "@/components/iFrame/index";
export default {name: "PyJeecgDrag",components: { iFrame },data() {return {dragUrl: process.env.VUE_APP_BASE_API + "/drag/list?token=Bearer " + getToken()}}
}
</script>

在drag文件夹下新增view文件夹,在view文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template><div><i-frame :src="dragUrl"></i-frame></div>
</template>
<script>
import { getToken } from "@/utils/auth";
import iFrame from "@/components/iFrame/index";
export default {name: "PyJeecgDragView",components: { iFrame },data() {return {dragUrl: ""}},created() {let query = this.$route.query;let code = undefined;let paramsString = "";if (query) {for (const key in query) {if (Object.hasOwnProperty.call(query, key)) {const param = query[key];if (key == "code") {code = param;} else {if (paramsString != "") {paramsString += paramsString + "&" + key + "=" + param;} else {paramsString = "&" + key + "=" + param;}}}}}if (code) {this.dragUrl = process.env.VUE_APP_BASE_API + "/drag/share/view/" + code + "?token=Bearer " + getToken() + paramsString;} else {this.$modal.msgError("大屏编码(code)为空,无法打开大屏");}}
}
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/906760.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s部署HA高可用集群

1.初始化系统 2.内存升级 3.安装k8s组件 4.编译kubeadm,修改永久证书授权 5.将kubeadm拷贝到两台master上,然后/usr/bin/kubeadm备份,将新编译的kubeadm 6.部署keepalived 7.初始化k8s 【master1】 7.1 编写kubeadm-config.yaml cat kubeadm-config.yaml apiVersion: kubead…

engine2x_低代码系统快速配置表格的xx列固定并且不随着x轴的滚动而滚动

代码:var is_long_width = false;//定义标识//数据列表的回调 function get_portal_data_list_done_back() {if (is_long_width === true) {find_element_by_class("table-set").css("position", "sticky").css("right", 0).css(&quo…

上下界网络流

上下界网络流 上下界流就是在普通的网络中加了下界。却还是要对于非源汇点,满足入流=出流,于是可能存在不合法的情况。 基础款 无源汇可行流 就是:对于每个点,入流 = 出流对于每条边,流量 $\in $ 合法区间我们先满足其下界,但这样可能出现入流 \(\ne\) 出流的情况,于是在…

NPIO-导出数据到xls中设置时间格式的值可筛选

//定义时间格式: var xSSFCellStyle1 = workbook.CreateCellStyle();var format = workbook.CreateDataFormat();xSSFCellStyle1.DataFormat = format.GetFormat("yyyy-MM-dd HH:mm:ss"); // 循环中 判断是否是时间类型 yes if (val.is_datetime()){ dataRow.Cre…

虚幻5入门,MashRunner项目1

继续之前油管上Cobra Code的The Ultimate Unreal Engine 2D Game Development Course课程的学习,这是第二个项目MashRunner 一.创建项目 启动虚幻引擎,创建一个空白项目,命名为MashRunner。之后打开VS,在MashRunner.Build.cs中添加一下插件包 using UnrealBuildTool;public…

C# 通过EtherCAT 控制伺服电机(无需板卡或PLC)

前言 无需使用板卡或PLC 用C#配合普通电脑 直接控制伺服电机 环境:vs2022 .Net9 WinPcap_4_1_3.exe Win11 一、引用库二、控制伺服 1.添加EtherCAT主站及从站 EtherCATMaster _etherCATMaster; EtherCATSlave_CiA402 _axis; private void Form1_Load(object sender, EventArgs …

【入门】Python类方法有几种?常用的有哪种?怎么用呢?

实例方法 (Instance Method) 最常用,必须通过类的实例调用,第一个参数是 self(指向实例本身)。 class MyClass:def instance_method(self, arg1, arg2):print(f"实例方法被调用,self={self}, args={arg1}, {arg2}")# 使用 obj = MyClass() obj.instance_method(…

Pydantic Schema生成指南:自定义JSON Schema

title: Pydantic Schema生成指南:自定义JSON Schema date: 2025/3/27 updated: 2025/3/27 author: cmdragon excerpt: Pydantic的Schema生成机制支持从基础定义到企业级应用的完整解决方案。默认流程包含字段定义、元数据收集、类型映射和Schema组装四个步骤。通过Field的js…

Mybatis三大执行器

目录 1、执行器介绍执行器的选择入口设置执行器两种方式全局配置(不建议)局部设置(建议)2、三个执行器区别SimpleExecutorReuseExecutorBatchExecutor总结3、效率测试 4、平时开发使用 一、执行器介绍Mybatis中执行器关系如上图所示,真正生效并在最后执行中有着不同效果的…

顶灯控制器OHC

汽车顶灯控制器OHC(Over Head Console)顶部控制终端系统,主要实现对车内饰灯以及天窗的控制功能。OHC产品采用平台化设计,并已通过多家整车厂的设计评审和试验验证,为特斯拉、福特、林肯、捷豹、路虎等众多车型配套。 汽车顶灯控制器OHC(Over Head Console)顶部控制终…

MES系统选择哪家好?珠海盈致科技

在制造业数字化转型的浪潮中,制造执行系统(MES)已成为连接企业计划层与生产现场的核心枢纽。面对市场上众多MES服务商,珠海盈致科技凭借其独特的技术架构、行业深耕能力与创新服务模式,成为企业实现精益生产与智能决策的可靠选择。 一、技术架构:从底层兼容到顶层智能的…

新一代ITSM:燕千云重构企业智慧服务生态体系

随着企业数字化建设不断深入,IT服务请求量也呈现爆发式增长态势,多数企业领导者已将员工体验列为战略优先级,企业服务管理已从后台支持走向战略核心。现代企业的服务管理已形成两大核心战场——技术支撑的IT服务与以人为本的员工服务。前者涵盖从基础设施运维到新兴技术融合…