最新工作流引擎之activiti6.x、activiti5.x实时流程图追踪与高亮显示两种方式实现

本文介绍了在Activiti6.x中如何实现流程图的实时追踪和高亮显示,由于Activiti6.x删除了pvm包,无法再使用ActivityImpl类。文章详细讲解了两种实现方式,包括关注bpmnmodel、highLightedActivities和highLightedFlows这三个关键字段,并提供了代码片段。同时,文章还探讨了如何自定义样式以改变流程图的美观度,以及在Activiti5.x中的生成流程图方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在activiti5.x的追踪流程节点查找,可以用ActivityImpl这个类来实现,但是在activiti6版本,pvm包整个类包都被删除,再也没有ActivityImpl这个类。本章节就是教大家如何用activiti6来生成2种方式实时流程图追踪

第一种方式:

1.1 生成流程图核心类

主要是利用ProcessDiagramGenerator这个接口实现类,去实现generateDiagram这个方法。默认可以采用ProcessDiagramGenerator的子类

DefaultProcessDiagramGenerator调用generateDiagram实现流程图。当然也可以自定义相应的子类CustomProcessDiagramGenerator去定制化实现

1.2 生成流程图核心方法

public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, 
      String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor;

1.3  关注的是bpmnmodel,highLightedActivities,highLightedFlows

1.3 关注 bpmnmodel,highLightedActivities,highLightedFlows, 这三个字段值 bpmnmodel:bpmn模型,这样调用

BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());

调用默认api就可以获取 highLightedActivities(需要高亮的执行流程节点集合的获取)、highLightedFlows(需要高亮流程连接线集合的获取)

activiti6如何获取这两个字段 activiti6获取highLightedActivities核心代码片段

// 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            // 获取流程中已经执行的节点,按照执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceId().asc().list();
            // 高亮已经执行流程节点ID集合
            List<String> highLightedActivitiIds = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                highLightedActivitiIds.add(historicActivityInstance.getActivityId());
            }

 

 

activiti6获取highLightedFlows核心代码片段

/**
     * 获取已经流转的线
     * 
     * @param bpmnModel
     * @param historicActivityInstances
     * @return
     */
    private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 高亮流程已发生流转的线id集合
        List<String> highLightedFlowIds = new ArrayList<>();
        // 全部活动节点
        List<FlowNode> historicActivityNodes = new ArrayList<>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            historicActivityNodes.add(flowNode);
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstances.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode = null;
        FlowNode targetFlowNode = null;
        // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();

            /**
             * 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点,找到匹配流程目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicActivityNodes.contains(targetFlowNode)) {
                        highLightedFlowIds.add(targetFlowNode.getId());
                    }
                }
            } else {
                List<Map<String, Object>> tempMapList = new ArrayList<>();
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            Map<String, Object> map = new HashMap<>();
                            map.put("highLightedFlowId", sequenceFlow.getId());
                            map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
                            tempMapList.add(map);
                        }
                    }
                }

                if (!CollectionUtils.isEmpty(tempMapList)) {
                    // 遍历匹配的集合,取得开始时间最早的一个
                    long earliestStamp = 0L;
                    String highLightedFlowId = null;
                    for (Map<String, Object> map : tempMapList) {
                        long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
                        if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
                            highLightedFlowId = map.get("highLightedFlowId").toString();
                            earliestStamp = highLightedFlowStartTime;
                        }
                    }

                    highLightedFlowIds.add(highLightedFlowId);
                }

            }

        }
        return highLightedFlowIds;
    }

 

activiti6生成流程图代码

/**
     * 根据流程实例Id,获取实时流程图片
     * 
     * @param processInstanceId
     * @param outputStream
     * @return
     */
    public static void getFlowImgByInstanceId(String processInstanceId, OutputStream outputStream) {
        try {
            if (StringUtils.isEmpty(processInstanceId)) {
                logger.error("processInstanceId is null");
                return;
            }
            // 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            // 获取流程中已经执行的节点,按照执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceId().asc().list();
            // 高亮已经执行流程节点ID集合
            List<String> highLightedActivitiIds = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                highLightedActivitiIds.add(historicActivityInstance.getActivityId());
            }

            List<HistoricProcessInstance> historicFinishedProcessInstances = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).finished()
                    .list();
            ProcessDiagramGenerator processDiagramGenerator = null;
            // 如果还没完成,流程图高亮颜色为绿色,如果已经完成为红色
            if (!CollectionUtils.isEmpty(historicFinishedProcessInstances)) {
                // 如果不为空,说明已经完成
                processDiagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            } else {
                processDiagramGenerator = new CustomProcessDiagramGenerator();
            }

            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 高亮流程已发生流转的线id集合
            List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);

            // 使用默认配置获得流程图表生成器,并生成追踪图片字符流
            InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitiIds, highLightedFlowIds, "宋体", "微软雅黑", "黑体", null, 2.0);

            // 输出图片内容
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
                outputStream.write(b, 0, len);
            }
        } catch (Exception e) {
            logger.error("processInstanceId" + processInstanceId + "生成流程图失败,原因:" + e.getMessage(), e);
        }

    }

activiti5.x生成流程图代码

/**
     * 根据流程实例Id,获取实时流程图片
     * 
     * @param processInstanceId
     * @return
     */
    public static InputStream getFlowImgByInstantId(String processInstanceId) {
        if (StringUtils.isEmpty(processInstanceId)) {
            return null;
        }
        // 获取流程图输入流
        InputStream inputStream = null;
        // 查询历史
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (historicProcessInstance.getEndTime() != null) { // 该流程已经结束
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(historicProcessInstance.getProcessDefinitionId())
                    .singleResult();
            inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getDiagramResourceName());
        } else {
            // 查询当前的流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
            ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(processInstance.getProcessDefinitionId()).singleResult();
            List<String> highLightedFlows = new ArrayList<String>();
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceStartTime().asc().list();
            List<String> historicActivityInstanceList = new ArrayList<String>();
            for (HistoricActivityInstance hai : historicActivityInstances) {
                historicActivityInstanceList.add(hai.getActivityId());
            }
            List<String> highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId);
            historicActivityInstanceList.addAll(highLightedActivities);
            for (ActivityImpl activity : processDefinitionEntity.getActivities()) {
                int index = historicActivityInstanceList.indexOf(activity.getId());
                if (index >= 0 && index + 1 < historicActivityInstanceList.size()) {
                    List<PvmTransition> pvmTransitionList = activity.getOutgoingTransitions();
                    for (PvmTransition pvmTransition : pvmTransitionList) {
                        String destinationFlowId = pvmTransition.getDestination().getId();
                        if (destinationFlowId.equals(historicActivityInstanceList.get(index + 1))) {
                            highLightedFlows.add(pvmTransition.getId());
                        }
                    }
                }
            }
            ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            List<String> activeActivityIds = new ArrayList<String>();
            List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
            for (org.activiti.engine.task.Task task : tasks) {
                activeActivityIds.add(task.getTaskDefinitionKey());
            }
            inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds, highLightedFlows, "宋体", "宋体", null, null, 1.0);
        }
        return inputStream;
    }

自定义样式生成流程图思路

activiti提供的样式可能不是特别美观,它的api目前只能改字体大小。如果遇到改颜色等,可以通过重写ProcessDiagramCanvas这个类,并实现ProcessDiagramGenerator这个类的接口

样图:

 

 

第2种方式生成:

2.1 样式类一:class CustomProcessDiagramCanvas

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.engine.ActivitiException;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.util.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents a canvas on which BPMN 2.0 constructs can be drawn. Some of the icons used are licensed under a Creative Commons Attribution 2.5 License, see
 * http://www.famfamfam.com/lab/icons/silk/
 * 
 * @see org.activiti.engine.impl.bpmn.diagram.DefaultProcessDiagramGenerator
 * @author Joram Barrez
 */
public class CustomProcessDiagramCanvas {
	
	protected static final Logger LOGGER = LoggerFactory.getLogger(CustomProcessDiagramCanvas.class);
	
	public enum SHAPE_TYPE {
		Rectangle, Rhombus, Ellipse
	}
	
	// Predefined sized
	protected static final int		ARROW_WIDTH						= 5;
	protected static final int		CONDITIONAL_INDICATOR_WIDTH		= 16;
	protected static final int		DEFAULT_INDICATOR_WIDTH			= 10;
	protected static final int		MARKER_WIDTH					= 12;
	protected static final int		FONT_SIZE						= 11;
	protected static final int		FONT_SPACING					= 2;
	protected static final int		TEXT_PADDING					= 3;
	protected static final int		ANNOTATION_TEXT_PADDING			= 7;
	protected static final int		LINE_HEIGHT						= FONT_SIZE + FONT_SPACING;
	
	// Colors
	protected static Color			TASK_BOX_COLOR					= new Color(249, 249, 249);
	protected static Color			SUBPROCESS_BOX_COLOR			= new Color(255, 255, 255);
	protected static Color			EVENT_COLOR						= new Color(255, 255, 255);
	protected static Color			CONNECTION_COLOR				= new Color(88, 88, 88);
	protected static Color			CONDITIONAL_INDICATOR_COLOR		= new Color(255, 255, 255);
	// protected static Color HIGHLIGHT_COLOR = Color.RED;
	protected static Color			HIGHLIGHT_COLOR					= Color.GREEN;
	protected static Color			LABEL_COLOR						= new Color(112, 146, 190);
	protected static Color			TASK_BORDER_COLOR				= new Color(187, 187, 187);
	protected static Color			EVENT_BORDER_COLOR				= new Color(88, 88, 88);
	protected static Color			SUBPROCESS_BORDER_COLOR			= new Color(0, 0, 0);
	
	// Fonts
	protected static Font			LABEL_FONT						= null;
	protected static Font			ANNOTATION_FONT					= new Font("Arial", Font.PLAIN, FONT_SIZE);
	protected static Font			TASK_FONT						= new Font("Arial", Font.PLAIN, FONT_SIZE);
	
	// Strokes
	protected static Stroke			THICK_TASK_BORDER_STROKE		= new BasicStroke(3.0f);
	protected static Stroke			GATEWAY_TYPE_STROKE				= new BasicStroke(3.0f);
	protected static Stroke			END_EVENT_STROKE				= new BasicStroke(3.0f);
	protected static Stroke			MULTI_INSTANCE_STROKE			= new BasicStroke(1.3f);
	protected static Stroke			EVENT_SUBPROCESS_STROKE			= new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[] { 1.0f }, 0.0f);
	protected static Stroke			NON_INTERRUPTING_EVENT_STROKE	= new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[] { 4.0f, 3.0f }, 0.0f);
	protected static Stroke			HIGHLIGHT_FLOW_STROKE			= new BasicStroke(1.3f);
	protected static Stroke			ANNOTATION_STROKE				= new BasicStroke(2.0f);
	protected static Stroke			ASSOCIATION_STROKE				= new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[] { 2.0f, 2.0f }, 0.0f);
	
	// icons
	protected static int			ICON_PADDING					= 5;
	protected static BufferedImage	USERTASK_IMAGE;
	protected static BufferedImage	SCRIPTTASK_IMAGE;
	protected static BufferedImage	SERVICETASK_IMAGE;
	protected static BufferedImage	RECEIVETASK_IMAGE;
	protected static BufferedImage	SENDTASK_IMAGE;
	protected static BufferedImage	MANUALTASK_IMAGE;
	protected static BufferedImage	BUSINESS_RULE_TASK_IMAGE;
	protected static BufferedImage	SHELL_TASK_IMAGE;
	protected static BufferedImage	MULE_TASK_IMAGE;
	protected static BufferedImage	CAMEL_TASK_IMAGE;
	
	protected static BufferedImage	TIMER_IMAGE;
	protected static BufferedImage	COMPENSATE_THROW_IMAGE;
	protected static BufferedImage	COMPENSATE_CATCH_IMAGE;
	protected static BufferedImage	ERROR_THROW_IMAGE;
	protected static BufferedImage	ERROR_CATCH_IMAGE;
	protected static BufferedImage	MESSAGE_THROW_IMAGE;
	protected static BufferedImage	MESSAGE_CATCH_IMAGE;
	protected static BufferedImage	SIGNAL_CATCH_IMAGE;
	protected static BufferedImage	SIGNAL_THROW_IMAGE;
	
	protected int					canvasWidth						= -1;
	protected int					canvasHeight					= -1;
	protected int					minX							= -1;
	protected int					minY							= -1;
	protected BufferedImage			processDiagram;
	protected Graphics2D			g;
	protected FontMetrics			fontMetrics;
	protected boolean				closed;
	protected ClassLoader			customClassLoader;
	protected String				activityFontName				= "Arial";
	protected String				labelFontName					= "Arial";
	
	/**
	 * Creates an empty canvas with given width and height. Allows to specify minimal boundaries on the left and upper side of the canvas. This is useful for diagrams that have
	 * white space there. Everything beneath these minimum values will be cropped. It's also possible to pass a specific font name and a class loader for the icon images.
	 */
	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, ClassLoader customClassLoader) {
		
		this.canvasWidth = width;
		this.canvasHeight = height;
		this.minX = minX;
		this.minY = minY;
		if (activityFontName != null) {
			this.activityFontName = activityFontName;
		}
		if (labelFontName != null) {
			this.labelFontName = labelFontName;
		}
		this.customClassLoader = customClassLoader;
		
		initialize(imageType);
	}
	
	/**
	 * Creates an empty canvas with given width and height. Allows to specify minimal boundaries on the left and upper side of the canvas. This is useful for diagrams that have
	 * white space there (eg Signavio). Everything beneath these minimum values will be cropped.
	 * 
	 * @param minX
	 *            Hint that will be used when generating the image. Parts that fall below minX on the horizontal scale will be cropped.
	 * @param minY
	 *            Hint that will be used when generating the image. Parts that fall below minX on the horizontal scale will be cropped.
	 */
	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
		this.canvasWidth = width;
		this.canvasHeight = height;
		this.minX = minX;
		this.minY = minY;
		
		initialize(imageType);
	}
	
	public void initialize(String imageType) {
		if ("png".equalsIgnoreCase(imageType)) {
			this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
		} else {
			this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
		}
		
		this.g = processDiagram.createGraphics();
		if ("png".equalsIgnoreCase(imageType) == false) {
			this.g.setBackground(new Color(255, 255, 255, 0));
			this.g.clearRect(0, 0, canvasWidth, canvasHeight);
		}
		
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setPaint(Color.black);
		
		Font font = new Font(activityFontName, Font.BOLD, FONT_SIZE);
		g.setFont(font);
		this.fontMetrics = g.getFontMetrics();
		
		LABEL_FONT = new Font(labelFontName, Font.ITALIC, 10);
		
		try {
			USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/userTask.png", customClassLoader));
			SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/scriptTask.png", customClassLoader));
			SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/serviceTask.png", customClassLoader));
			RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/receiveTask.png", customClassLoader));
			SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/sendTask.png", customClassLoader));
			MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/manualTask.png", cu
电动汽车数据集:2025年3K+记录 真实电动汽车数据:特斯拉、宝马、日产车型,含2025年电池规格和销售数据 关于数据集 电动汽车数据集 这个合成数据集包含许多品牌和年份的电动汽车和插电式车型的记录,捕捉技术规格、性能、定价、制造来源、销售和安全相关属性。每一行代表由vehicle_ID标识的唯一车辆列表。 关键特性 覆盖范围:全球制造商和车型组合,包括纯电动汽车和插电式混合动力汽车。 范围:电池化学成分、容量、续航里程、充电标准和速度、价格、产地、自主水平、排放、安全等级、销售和保修。 时间跨度:模型跨度多年(包括传统和即将推出的)。 数据质量说明: 某些行可能缺少某些字段(空白)。 几个分类字段包含不同的、特定于供应商的值(例如,Charging_Type、Battery_Type)。 各列中的单位混合在一起;注意kWh、km、hr、USD、g/km和额定值。 列 列类型描述示例 Vehicle_ID整数每个车辆记录的唯一标识符。1 制造商分类汽车品牌或OEM。特斯拉 型号类别特定型号名称/变体。型号Y 记录关联的年份整数模型。2024 电池_类型分类使用的电池化学/技术。磷酸铁锂 Battery_Capacity_kWh浮充电池标称容量,单位为千瓦时。75.0 Range_km整数表示充满电后的行驶里程(公里)。505 充电类型主要充电接口或功能。CCS、NACS、CHAdeMO、DCFC、V2G、V2H、V2L Charge_Time_hr浮动充电的大致时间(小时),上下文因充电方法而异。7.5 价格_USD浮动参考车辆价格(美元).85000.00 颜色类别主要外观颜色或饰面。午夜黑 制造国_制造类别车辆制造/组装的国家。美国 Autonomous_Level浮点自动化能力级别(例如0-5),可能包括子级别的小
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值