SpringBoot整合Flowable【08】- 前后端如何交互

article/2025/7/1 6:10:22

引子

在第02篇中,我通过 Flowable-UI 绘制了一个简单的绩效流程,并在后续章节中基于这个流程演示了 Flowable 的各种API调用。然而,在实际业务场景中,如果要求前端将用户绘制的流程文件发送给后端再进行解析处理,这种方式显得繁琐且不够优雅。那么,有没有更简便的方法,让前端通过常规的参数传递方式就能实现流程创建呢?

答案是肯定的。Flowable 提供了强大的 BpmnModel API,它允许我们以编程方式动态构建流程定义,无需依赖XML文件。

什么是 BpmnModel ?

BpmnModelFlowable 提供的一个核心类,它允许开发者以编程方式构建完整的 BPMN 2.0 流程模型。与通过 Flowable-UI 设计流程并导出XML文件的方式不同,BpmnModel 让我们可以直接在代码中定义流程的各个元素。

所以,我们可以通过 BpmnModel 对象来动态创建流程定义,前端只需通过接口传递必要的流程参数即可,无需传递完整的 XML 文件。这里需要明确区分两个概念:一是后端如何接收参数并构建流程模型,二是前端如何提供流程设计的交互界面。对于后者,可以基于 bpmn.js 开发自定义设计器,也可以采用现有的开源方案,这些选项在第01篇中已有详细介绍。

代码编写

接下来,我将完全抛弃 Flowable UI 可视化建模方式,转而通过纯代码方式使用 BpmnModel 对象构建先前设计的绩效流程。让我们深入编码环节,一步步实现这一转换。

一、构建传参对象

1.流程定义实体

流程定义是创建流程的传参对象,包含流程的基本信息和节点结构。

import lombok.Data;
import java.io.Serializable;/*** 流程定义.*/
@Data
public class ProcessDef implements Serializable {/*** 流程定义id.*/private String processDefKey;/*** 流程定义名称.*/private String processName;/*** 流程定义描述.*/private String description;/*** 创建人id.*/private Long creatorId;/*** 流程定义节点.*/private ProcessNode processNode;}

2. 流程节点实体

流程节点定义了流程中的各个环节,包括节点类型、表单属性以及子节点关系等。

import lombok.Data;
import java.io.Serializable;
import java.util.List;/*** 流程节点.*/
@Data
public class ProcessNode implements Serializable {/*** 节点名称.*/private String name;/*** 节点类型.*/private NodeCategory category;/*** 是否启用 0否 1是.*/private Integer enable;/*** 办理人属性.*/private AssigneeProps assigneeProps;/*** 表单列表.*/private List<FormProp> formProps;/*** 子节点.*/private ProcessNode children;}

3. 相关枚举和属性类

为了更好地定义流程节点的各种属性,我们最好还是定义相关枚举和辅助类。(Ps:在实际开发中,个人建议如果类型比较多的情况下,尽量使用枚举,避免魔法字段。)

节点类型枚举
/*** 节点类型.*/
public enum NodeCategory {/*** 自评.*/SELF_EVALUATION,/*** 上级评.*/LEADER_EVALUATION,/*** 隔级评.*/AUDIT,/*** 绩效确认.*/CONFIRMATION;
}
办理人属性
import java.io.Serializable;
import lombok.Data;/*** 办理人Props.*/
@Data
public class AssigneeProps implements Serializable {/*** 办理人类型.*/private AssigneeType assigneeType;/*** 候选办理人类型.*/private AssigneeType candidateType;
}
办理人类型枚举
/*** 办理人类型.*/
public enum AssigneeType {/*** 节点处理人类型, 用户本人,直接上级,上级部门管理员,隔级上级,隔级部门管理员.*/USER_ASSESSED, LEADER, DEPT_MANAGER, SUPERIOR_LEADER, SUPERIOR_DEPT_MANAGER
}
表单属性
import java.io.Serializable;
import lombok.Data;/*** FormProps 表单属性.*/
@Data
public class FormProp implements Serializable {/*** id.*/private String id;/*** 属性名称.*/private String name;/*** 属性变量.*/private String variable;/*** 变量类型.*/private String type;/*** 值.*/private String value;/*** 表单配置.*/private FormConfig config;public FormConfig getConfig() {return config;}
}
表单配置
import java.io.Serializable;
import lombok.Data;/*** FormConfig 表单配置.*/
@Data
public class FormConfig implements Serializable {/*** 分组名称.*/private String group;/*** 分组权重.*/private Double weight;/*** 分类:评分SCORE、评语COMMENT.*/private FormCategory category;}
表单类别枚举
/*** 表单类别.*/
public enum FormCategory {/*** 评分.*/SCORE,/*** 评语.*/COMMENT;
}

二、创建控制器

import com.pitayafruit.base.BaseResponse;
import com.pitayafruit.rest.vo.ProcessDef;
import com.pitayafruit.service.ProcessDefService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 流程Controller.*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/process-def/v1")
public class ProcessDefControllerV1 {private final ProcessDefService processDefService;/*** 创建流程模型并部署.** @param processDef 流程定义* @return 流程key*/@PostMappingpublic ResponseEntity<Object> createProcessDef(@RequestBody ProcessDef processDef) {//1,创建并部署流程模型String processDefKey = processDefService.createProcessDefApi(processDef);//2.返回流程模型keyreturn new ResponseEntity<>(new BaseResponse<>(processDefKey), HttpStatus.OK);}}

三、实现服务层主方法

这个负责将前端传递的数据结构转换为 Flowable 的 BpmnModel 模型,并进行部署。

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.pitayafruit.rest.vo.AssigneeProps;
import com.pitayafruit.rest.vo.ProcessDef;
import com.pitayafruit.rest.vo.ProcessNode;
import com.pitayafruit.service.ProcessDefService;
import com.pitayafruit.utils.UUIDUtil;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.validation.ValidationError;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;/*** 流程定义ServiceImpl.*/
@Service
@RequiredArgsConstructor
public class ProcessDefServiceImpl implements ProcessDefService {private final RepositoryService repositoryService;/*** 创建流程模型并部署.** @param processDef 流程定义* @return 流程key*/@Overridepublic String createProcessDefApi(ProcessDef processDef) {//1.设置流程定义KeyprocessDef.setProcessDefKey("processDef" + UUIDUtil.getUUID());//2.设置流程定义创建人idprocessDef.setCreatorId(1L);//3.设置流程定义名称processDef.setProcessName("绩效流程");//4.创建流程模型BpmnModel bpmnModel = toBpmn(processDef);//5.部署流程模型repositoryService.createDeployment()//5-1.流程定义key.key(processDef.getProcessDefKey())//5-2.流程定义名称.name(processDef.getProcessName())//5-3.添加流程模型.addBpmnModel(processDef.getProcessDefKey() + ".bpmn", bpmnModel)//5-4.部署.deploy();//6.返回流程定义keyreturn processDef.getProcessDefKey();}

四、流程模型转换方法

类型转换涉及到的逻辑比较多,所以把这个方法单独抽取出来。首先创建一个流程对象,设置流程的基本属性,添加开始事件,然后通过调用 buildTask 方法递归构建流程中的各个节点,最后生成并验证 BpmnModel。

/*** 将流程定义转换为BpmnModel.** @param processDef 流程定义* @return Bpmn模型*/
private BpmnModel toBpmn(ProcessDef processDef) {//1.创建流程//1-1.声明流程对象Process process = new Process();//1-2.设置流程idprocess.setId(processDef.getProcessDefKey());//1-3.设置流程名称process.setName(processDef.getProcessName());//2.创建开始事件//2-1.声明开始事件对象StartEvent startEvent = new StartEvent();//2-2.设置开始事件idstartEvent.setId("startEvent" + UUIDUtil.getUUID());//2-3.设置开始事件名称startEvent.setName("开始");//2-4.将开始事件添加到流程中process.addFlowElement(startEvent);//创建用户节点ProcessNode processNode = processDef.getProcessNode();buildTask(startEvent, processNode, process);//创建流程模型BpmnModel bpmnModel = new BpmnModel();bpmnModel.addProcess(process);// 验证BPMN模型List<ValidationError> validationErrors = repositoryService.validateProcess(bpmnModel);if (ObjectUtil.isNotEmpty(validationErrors)) {//打印失败日志validationErrors.forEach(validationError -> System.out.println(validationError.toString()));throw new IllegalArgumentException("验证失败");}return bpmnModel;
}

五、任务节点构建方法

这个方法负责创建用户任务节点。根据传入的流程节点数据,创建一个用户任务对象,设置其属性,添加自定义属性和任务监听器,然后将任务添加到流程中,并构建与前一个节点的连线。如果当前节点未启用,则直接处理下一个节点。

/*** 创建用户任务节点.** @param parentTask  父节点* @param processNode 流程节点* @param process     流程定义*/
private void buildTask(FlowNode parentTask, ProcessNode processNode, Process process) {//如果节点启用,处理当前节点if (ObjectUtil.isNotNull(processNode.getEnable()) && processNode.getEnable() == 1) {UserTask userTask = new UserTask();userTask.setId("userTask" + UUIDUtil.getUUID());userTask.setName(processNode.getName());//设置节点类型userTask.setCategory(processNode.getCategory().toString());List<CustomProperty> customProperties = new ArrayList<>();//办理人属性AssigneeProps assigneeProps = processNode.getAssigneeProps();//设置办理人类型customProperties.add(buildCustomProperty("assigneeType", assigneeProps.getAssigneeType().toString()));//设置候选办理人类型if (ObjectUtil.isNotNull(assigneeProps.getCandidateType())) {customProperties.add(buildCustomProperty("candidateType", assigneeProps.getCandidateType().toString()));}//绑定表单userTask.setFormProperties(buildFormProperty(processNode));//表单列表添加到节点扩展属性customProperties.add(buildCustomProperty("formProps", JSON.toJSONString(processNode.getFormProps())));//设置自定义属性,包括办理人类型和表单分组和权重配置userTask.setCustomProperties(customProperties);//监听器列表List<FlowableListener> taskListeners = new ArrayList<>();//任务创建时添加监听器,用于动态指定任务的办理人和设置变量taskListeners.add(buildTaskListener(TaskListener.EVENTNAME_CREATE, "taskCreateListener"));//任务完成后添加监听器,用于计算分数和保存表单//如果是手动填写流程,添加手动填写的监听器taskListeners.add(buildTaskListener(TaskListener.EVENTNAME_COMPLETE, "taskCompleteListener"));//设置任务监听器userTask.setTaskListeners(taskListeners);// 将用户任务节点添加到流程定义process.addFlowElement(userTask);//添加流程连线process.addFlowElement(new SequenceFlow(parentTask.getId(), userTask.getId()));//解析下一个节点,参数为当前任务,当前流程节点,流程定义,流程类型buildChildren(userTask, processNode, process);} else {//当前节点关闭的情况下,直接解析下一个节点,参数为父任务,当前流程节点,流程定义,流程类型buildChildren(parentTask, processNode, process);}
}

六、子节点处理方法

这个方法用于处理流程节点的子节点。它检查当前节点是否有子节点,如果有则递归调用 buildTask 方法构建子节点,如果没有则创建结束事件,表示流程的结束。

/*** 解析子节点.** @param parentTask  下一个节点的父任务* @param processNode 流程节点* @param process     流程定义*/
private void buildChildren(FlowNode parentTask, ProcessNode processNode, Process process) {ProcessNode childrenNode = processNode.getChildren();if (ObjectUtil.isNotNull(childrenNode)) {//创建子节点buildTask(parentTask, childrenNode, process);} else {//创建结束事件EndEvent endEvent = new EndEvent();endEvent.setId("endEvent" + UUIDUtil.getUUID());endEvent.setName("结束");process.addFlowElement(endEvent);//添加流程连线process.addFlowElement(new SequenceFlow(parentTask.getId(), endEvent.getId()));}
}

七、任务监听器创建方法

这个方法用于创建任务监听器。在Flowable中,任务监听器可以在任务的不同生命周期阶段触发特定逻辑,例如在任务创建时动态分配任务办理人,或在任务完成时处理表单数据。这里使用了委托表达式方式,将监听器的实现委托给Spring容器中的Bean。

/*** 创建任务监听器,用于动态分配任务的办理人.** @param event    事件* @param beanName 类名* @return 任务监听器*/
private FlowableListener buildTaskListener(String event, String beanName) {FlowableListener flowableListener = new FlowableListener();flowableListener.setEvent(event);flowableListener.setImplementationType("delegateExpression");flowableListener.setImplementation("${" + beanName + "}");return flowableListener;
}

八、表单属性创建方法

这个方法用于创建表单属性。它遍历流程节点中的表单属性列表,为每个表单属性创建一个Flowable的FormProperty对象,设置其ID、名称、变量名等属性,并将原始表单属性的ID和变量名更新为实际使用的值,以便在后续处理中使用。

/*** 创建表单属性.** @param processNode 流程节点*/
private List<FormProperty> buildFormProperty(ProcessNode processNode) {List<FormProperty> formProperties = new ArrayList<>();if (ObjectUtil.isNull(processNode.getFormProps())) {return formProperties;}processNode.getFormProps().forEach(prop -> {//新建表单属性对象FormProperty formProperty = new FormProperty();//设置表单属性idString id = "formProperty" + UUIDUtil.getUUID();formProperty.setId(id);//设置表单名称formProperty.setName(prop.getName());//设置表单变量名为表单idformProperty.setVariable(id);//设置表单变量类型formProperty.setType(prop.getType());//设置表单是否必填formProperty.setRequired(true);formProperties.add(formProperty);//设置表单属性idprop.setId(id);prop.setVariable(id);});return formProperties;
}

九、自定义属性创建方法

这个方法用于创建自定义属性。在 Flowable 中,自定义属性可以用于存储不属于标准BPMN规范但业务上需要的信息,通常用来传递我们的业务数据。

/*** 创建自定义属性.** @param key  键* @param value 值* @return 自定义属性*/
private CustomProperty buildCustomProperty(String key, String value) {CustomProperty customProperty = new CustomProperty();//自定义属性名称customProperty.setName(key);//自定义属性值customProperty.setSimpleValue(value);return customProperty;
}

接口测试

我把请求转换成了curl命令,方便直接在命令行中测试。创建的这个流程与前面篇章中介绍的结构一致,包含三个核心节点:员工自评、上级评价和隔级评价,每个节点都包含分数、评语等表单项,并且每个节点都设置了不同的权重。

为什么要加权重?这是因为在实际业务场景中,不同评价环节的重要性往往不同。

curl -X POST 'http://localhost:8080/api/process-def/v1' \
-H 'Content-Type: application/json' \
-d '{"processName": "通过bpmn model创建的绩效流程","description": "用于员工季度绩效评估","processNode": {"name": "员工自评","category": "SELF_EVALUATION","enable": 1,"assigneeProps": {"assigneeType": "USER_ASSESSED"},"formProps": [{"name": "自评分数","type": "string","value": "0","config": {"group": "绩效评分","weight": 0.3,"category": "SCORE"}},{"name": "自我评价","type": "string","config": {"group": "评语","weight": 0.3,"category": "COMMENT"}}],"children": {"name": "上级评价","category": "LEADER_EVALUATION","enable": 1,"assigneeProps": {"assigneeType": "LEADER"},"formProps": [{"name": "上级评分","type": "string","value": "0","config": {"group": "绩效评分","weight": 0.5,"category": "SCORE"}},{"name": "上级评语","type": "string","config": {"group": "评语","weight": 0.5,"category": "COMMENT"}}],"children": {"name": "隔级评价","category": "AUDIT","enable": 1,"assigneeProps": {"assigneeType": "SUPERIOR_LEADER"},"formProps": [{"name": "隔级评分","type": "string","value": "0","config": {"group": "绩效评分","weight": 0.2,"category": "SCORE"}},{"name": "隔级评语","type": "string","config": {"group": "评语","weight": 0.2,"category": "COMMENT"}}],"children": null}}}
}'

按照接口的定义,部署成功后会返回流程模型key。
在这里插入图片描述

同时查看部署表和流程运行表,可以看到这个流程已经启动了。

在这里插入图片描述
在这里插入图片描述

小结

通过本章的实践,我们可以明显看到利用 BpmnModel API 构建流程虽然灵活强大,但即使是构建一个相对简单的线性流程,也需要编写大量代码来处理各种细节。这种方法的复杂性在面对更复杂的流程结构(如并行网关、排他网关或子流程等)时会进一步增加。

因此,在实际项目中,我建议大家可以花点时间封装,比如可以将创建开始事件、任务节点、结束事件、网关等常见元素的代码封装成独立方法。通过这些封装,在写业务的时候就能像搭积木一样轻松组合各种流程元素,提高开发效率和代码可维护性。


http://www.hkcw.cn/article/XOAxVhcfeo.shtml

相关文章

【GESP真题解析】第 18 集 GESP 二级 2024 年 12 月编程题 1:寻找数字

大家好,我是莫小特。 这篇文章给大家分享 GESP 二级 2024 年 12 月编程题第 1 题:寻找数字。 题目链接 洛谷链接:B4064 寻找数字 一、完成输入 根据输入格式的描述,输入有两个部分,第一部分是正整数 t,表示测试数据的组数,数据范围: 1 ≤ t ≤ 10 5 1\le t \le 10^5…

国际调解院的设立影响几何 填补全球治理赤字

5月30日上午,《关于建立国际调解院的公约》签署仪式在香港举行。外交部长王毅出席仪式,与来自亚洲、非洲、拉丁美洲和欧洲的近60个国家以及联合国等20个国际组织的高级别代表共同见证这一历史时刻。国际调解院作为全球首个专门通过调解解决国际争端的政府间法律组织,与荷兰海…

男子造成两尊铠甲武士俑损坏 警方发布警情通报

新京报讯 5月31日,西安市公安局临潼分局发布警情通报。编辑 毛天宇责任编辑:zhangxiaohua

我国造船新接订单量领跑全球 市场韧性强劲

在当前复杂的全球贸易形势下,中国造船产业仍表现出强劲的市场韧性和竞争力。今年1至4月,中国造船业新接订单量占世界市场份额继续保持全球第一。许多造船企业订单饱满,生产任务已经排到了几年之后。下午三点,在辽宁大连一家造船企业的码头上,一艘16000标箱的集装箱船完成交…

霍乱疫情严峻苏丹接收超290万剂疫苗 国际援助助力抗疫

5月30日,苏丹卫生部宣布,290.54万剂霍乱疫苗已抵达苏丹东部港口城市苏丹港。这批疫苗由世界卫生组织参与管理的疫苗供应国际协调小组捐赠,将被送往疫情严重的喀土穆州和北科尔多凡州。未来几天内,当地将为所有1岁以上的公民开展大规模疫苗接种活动。卫生部呼吁目标年龄段的…

造谣刘国梁试图出境被捕账号被关闭 微博严厉打击网络谣言

微博刚刚发布了一则谣言治理公告,强调了平台对于营造安全、文明、友善社区环境的承诺,并表示将对网络谣言进行坚决打击。为有效治理站内不实信息,微博持续开展专项治理工作。在近期(5.12-5.25)的治理中,依据《微博社区公约》等相关规定,平台对诸如“刘国梁在云南试图出境…

端午假期全国舒适度地图来了 南北天气差异明显

端午假期期间,我国北方天气以晴为主,适合出行;而南方正值主汛期,雨水较多,尤其是长江流域雨势较强,局地可能出现大暴雨并伴有强对流天气。北方地区如新疆、甘肃、内蒙古、陕西、山东等地天气晴朗舒适,最高气温在25℃左右,适宜外出。京津冀等地白天升温较快,午后气温可…

兵马俑受损等情况正在调查中 男子进入三号坑引发关注

今日下午,陕西西安秦始皇兵马俑博物馆内发生了一起事件,一名男子进入三号坑中。目前,博物馆相关工作人员表示正在调查具体情况。三号俑坑规模较小,平面呈“凹”字形,东边有斜坡门道。该坑东西长28.8米,南北宽24.57米,距现地表深5.2至5.4米。为了保护文物,三号坑游览区域…

哪吒汽车原CEO张勇79万元股权被冻结 再添冻结记录

天眼查法律诉讼信息显示,哪吒汽车原CEO张勇所持上海哲奥实业有限公司79万余元股权被冻结。执行法院为山东省青岛市市北区人民法院,冻结期限从2025年5月22日至2027年5月21日。此前,张勇已被冻结4050万元股权,涉及企业为桐乡合创德力叁科技咨询合伙企业(有限合伙)。责任编辑…

陕西普通高等学校招生实施办法公布 2025年高考政策明确

刚刚陕西省教育考试院印发了《2025年陕西省普通高等学校招生工作实施办法》明确了我省2025年高考各项政策一起来看↓↓↓责任编辑:zx0001

河北“狗王”在外网爆火 下司犬威震四方

近日,河北一只农村狗狗在社交媒体上走红,被外国网友戏称为“查尔斯国王”。它凭借威严的姿态和平静的目光就能轻松“统治”犬群。这只狗的品种为下司犬,是贵州黔东南州麻江县下司镇特有的一种世界级猎犬。下司犬的幼崽看起来像小萌猪,但其实拥有卓越的灵敏性和优秀的捕猎协…

解放军何时武统时机最佳?台媒:就在台风来临前,关键目标超40个

近日,台媒《中时电子报》发布文章指出,近年来解放军围绕“速决、低耗、体系崩溃”的作战设想,进行了大量兵棋推演,多个公开或半公开的资料都指向一个共同的结论,即大陆对台作战的战略不再侧重传统的装甲登陆或漫长的制空夺岛战斗,而是采取一种更为精准的“上兵伐谋”式体…

哈佛校长毕业致辞获3万人热烈鼓掌 “世界各地,这才对!”

当地时间5月29日,美国法官叫停了特朗普政府此前宣布的取消哈佛大学招收外国学生资质的政策,为哈佛大学带来了暂时的胜利。同一天,在哈佛大学的毕业典礼上,校长艾伦加伯巧妙地“暗讽”了特朗普政府对大学的打压,获得了全场30秒热烈的起立鼓掌。此前,美国国土安全部于5月22…

见证历史,美国4月商品进口额环比大降近20%,服装零售巨头跌超20% 美股三大指数集体低开

北京时间5月30日晚,美股开盘后三大指数集体低开。截至发稿前,道指下跌0.23%,纳指下跌0.67%,标普500指数下跌0.43%。大型科技股多数走低,英伟达跌超2%,谷歌跌超1%,亚马逊、苹果、Meta微跌,特斯拉和微软则小幅上涨。服装零售巨头GAP股价下跌超过20%,公司警告称关税将侵蚀…

阿维塔11香港上市 售价51.38万港币起

5月29日,阿维塔品牌及阿维塔11在香港正式发布。阿维塔11推出Premium版与Max版两款配置,官方指导价分别为51.38万港币和60.88万港币,折合人民币约为47.10万元和55.81万元。阿维塔11搭载宁德时代三元锂电池,配备座椅通风、按摩、“一键躺平”和英国之宝音响系统等配置。此外,…

法国为何掌握不到阵风战损信息 军售利益与官方否认交织

57印巴空战已过去20多天,关于号称最强4代半的法制“阵风”战机是否被击落,法国军方终于给出了官方回应。5月27日,法国军方发言人韦尔内上校表示:“很多消息还没有得到证实,如果相关消息属实,那么这是20年来阵风战斗机首次在实战中损毁。”这表明法国军方对这一消息持怀疑…

汽车工程师如何看待阿维塔风阻争议 官方直播回应质疑

沉寂了数天之后,阿维塔的风阻罗生门似乎终于要迎来定论。阿维塔官方全网直播了阿维塔12风洞测试,以最直接的方式回应近日风阻数据造假传闻。参与直播的嘉宾包括阿维塔科技副总裁雍军、重庆理工大学车辆工程学院院长赖晨光、长安汽车工程研究总院流体分析所经理严旭以及公证处…

全红婵多次现身馆内为广东队友加油 现场助威展现团队精神

5月27日下午,2025年全国跳水冠军赛女子3米跳板决赛中,广东选手陈艺文以392.70分的成绩夺得冠军。决赛最后一跳,陈艺文发挥稳定,进一步拉开了比分优势。最终,陈佳以380.55分获得亚军,山西选手李亚杰以360.85分获得季军。因伤病未能参赛的全红婵也来到现场观赛,并与广东选…

余承东称尊界可抑制冠状病毒 智能净化座舱显神威

在今日的尊界S800发布会上,华为常务董事、终端BG董事长余承东发表演讲。他表示,尊界S800全车空气质量远超国家标准,达到国家标准的55倍,甚至优于西双版纳雨林的空气质量。余承东介绍,尊界S800配备了自主智能净化座舱,使用可吸附降解材料,能在20分钟内使车内气味恢复如初…

郑钦文赛点发球遭情侣观众离场干扰 争议一幕引发网友热议

在北京时间5月30日晚结束的法网女单第三轮比赛中,中国选手郑钦文直落两盘击败18岁的资格赛黑马姆博科,时隔三年重返法网女单16强,追平了她参加法网的最佳战绩。比赛进入赛点时,现场出现了一幕争议。一对情侣在郑钦文准备发球时起身离开,男士穿着深色上衣,拉着穿长裙的女士…