boot-admin整合flowable官方editor-app进行BPMN2.0建模
正所谓百家争鸣、见仁见智、众说纷纭、各有千秋!在工作流bpmn2.0可视化建模工具实现的细分领域,网上扑面而来的是 bpmn.js这个渲染工具包和web建模器,而笔者却认为使用flowable官方开源 editor-app才是王道。
Flowable 开源版本中的 web 版流程设计器editor-app,展示风格和功能基本跟 activiti-modeler 一样,集成简单,开发工作量小,界面美观大方,功能强大,用户体验友好。
(相关资料图)
通过以下两张Gif动图来个PK,您的直观感受如何呢?bpmn.js运行效果图(gif动图取自互联网)
Flowable editor-app运行效果:
boot-admin是一款采用前后端分离模式、基于SpringCloud微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功能模块,集成分布式事务Seata、工作流引擎Flowable、业务规则引擎Drools、后台作业调度框架Quartz等,技术栈包括Mybatis-plus、Redis、Nacos、Seata、Flowable、Drools、Quartz、SpringCloud、Springboot Admin Gateway、Liquibase、jwt、Openfeign、I18n等。gitee源码地址github源码地址
下面介绍 boot-admin对flowable官方bpmn2.0可视化建模工具 editor-app的集成改造步骤:
获取前端源码下载官方数据包flowable-6.4.1.zip从压缩包中解压出flowable-6.4.1\wars下面的flowable-modeler.war从flowable-modeler.war中解压出 WEB-INF\classes\static\editor-app 文件夹将数据包中 editor-app 文件夹复制到 boot-admin项目 前端工程的 public 文件夹下面在 boot-admin项目 前端工程 public 文件夹下面创建 modeler.html 作为编辑器入口modeler.html内容:
Activiti Editor {{alerts.current.message}} {{alerts.queue.length + 1}} <script src="/editor-app/libs/jquery_1.11.0/jquery.min.js"></script><script src="/editor-app/libs/jquery-ui-1.10.3.custom.min.js"></script><script src="/editor-app/libs/angular_1.2.13/angular.min.js"></script><script src="/editor-app/libs/angular_1.2.13/angular-animate.min.js"></script><script src="/editor-app/libs/bootstrap_3.1.1/js/bootstrap.min.js"></script><script src="/editor-app/libs/angular-resource_1.2.13/angular-resource.min.js"></script><script src="/editor-app/libs/angular-cookies_1.2.13/angular-cookies.min.js"></script><script src="/editor-app/libs/angular-sanitize_1.2.13/angular-sanitize.min.js"></script><script src="/editor-app/libs/angular-route_1.2.13/angular-route.min.js"></script><script src="/editor-app/libs/angular-translate_2.4.2/angular-translate.min.js"></script><script src="/editor-app/libs/angular-translate-storage-cookie/angular-translate-storage-cookie.js"></script><script src="/editor-app/libs/angular-translate-loader-static-files/angular-translate-loader-static-files.js"></script><script src="/editor-app/libs/angular-strap_2.0.5/angular-strap.min.js"></script><script src="/editor-app/libs/angular-strap_2.0.5/angular-strap.tpl.min.js"></script><script src="/editor-app/libs/momentjs_2.5.1/momentjs.min.js"></script><script src="/editor-app/libs/ui-utils.min-0.0.4.js" type="text/javascript"></script><script src="/editor-app/libs/ng-grid-2.0.7-min.js" type="text/javascript"></script><script src="/editor-app/libs/angular-dragdrop.min-1.0.3.js" type="text/javascript"></script><script src="/editor-app/libs/mousetrap-1.4.5.min.js" type="text/javascript"></script><script src="/editor-app/libs/jquery.autogrow-textarea.js" type="text/javascript"></script><script src="/editor-app/libs/prototype-1.5.1.js" type="text/javascript"></script><script src="/editor-app/libs/path_parser.js" type="text/javascript"></script><script src="/editor-app/libs/angular-scroll_0.5.7/angular-scroll.min.js" type="text/javascript"></script><script src="/editor-app/app-cfg.js?v=1"></script><script src="/editor-app/editor-config.js" type="text/javascript"></script><script src="/editor-app/configuration/url-config.js" type="text/javascript"></script><script src="/editor-app/editor/i18n/translation_en_us.js" type="text/javascript"></script><script src="/editor-app/editor/i18n/translation_signavio_en_us.js" type="text/javascript"></script><script src="/editor-app/editor/oryx.debug.js" type="text/javascript"></script><script src="/editor-app/app.js"></script><script src="/editor-app/eventbus.js" type="text/javascript"></script><script src="/editor-app/editor-controller.js" type="text/javascript"></script><script src="/editor-app/stencil-controller.js" type="text/javascript"></script><script src="/editor-app/toolbar-controller.js" type="text/javascript"></script><script src="/editor-app/header-controller.js" type="text/javascript"></script><script src="/editor-app/select-shape-controller.js" type="text/javascript"></script><script src="/editor-app/editor-utils.js" type="text/javascript"></script><script src="/editor-app/configuration/toolbar-default-actions.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-default-controllers.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-execution-listeners-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-event-listeners-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-assignment-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-fields-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-form-properties-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-in-parameters-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-multiinstance-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-out-parameters-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-task-listeners-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-sequenceflow-order-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-condition-expression-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-signal-definitions-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-signal-scope-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-message-definitions-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-message-scope-controller.js" type="text/javascript"></script><script src="/editor-app/configuration/toolbar.js" type="text/javascript"></script><script src="/editor-app/configuration/toolbar-custom-actions.js" type="text/javascript"></script><script src="/editor-app/configuration/properties.js" type="text/javascript"></script><script src="/editor-app/configuration/properties-custom-controllers.js" type="text/javascript"></script>
整合改造前端源码修改 ACTIVITI.CONFIG ,设置网关 URLvar ACTIVITI = ACTIVITI || {};ACTIVITI.CONFIG = {"contextRoot" : "http://网关IP:网关端口号/api/workflow/auth/activiti",};
修改 configuration\url-config.js,设置各具体访问点URLvar KISBPM = KISBPM || {};KISBPM.URL = { //通过modelId,获取已保存模型的json数据 getModel: function(modelId) { return ACTIVITI.CONFIG.contextRoot + "/model/json?modelId=" + modelId; }, //获取汉化资源json数据 getStencilSet: function() { return ACTIVITI.CONFIG.contextRoot + "/editor/stencilset?version=" + Date.now(); }, //保存模型数据 putModel: function(modelId) { return ACTIVITI.CONFIG.contextRoot + "/model/save?modelId=" + modelId; }, //从cookie中读取令牌 getToken: function() { var cookies = document.cookie; var list = cookies.split("; "); // 解析出名/值对列表 for (var i = 0; i < list.length; i++) { var arr = list[i].split("="); // 解析出名和值 if (arr[0] == "Admin-Token") { var cookieVal = decodeURIComponent(arr[1]); // 对cookie值解码 break; } } return "Bearer" + cookieVal; }};
修改 /public/editor-app/stencil-controller.js 中获取汉化包的方法,由源码中自由访问修改为携带令牌访问后台资源$http({method: "GET", headers: { "X-Token": KISBPM.URL.getToken() }, url: KISBPM.URL.getStencilSet()}) .success(function (data, status, headers, config) { var quickMenuDefinition = ["UserTask", "EndNoneEvent", "ExclusiveGateway", "CatchTimerEvent", "ThrowNoneEvent", "TextAnnotation", "SequenceFlow", "Association"]; var ignoreForPaletteDefinition = ["SequenceFlow", "MessageFlow", "Association", "DataAssociation", "DataStore", "SendTask"]; var quickMenuItems = []; var morphRoles = []; for (var i = 0; i < data.rules.morphingRules.length; i++) { var role = data.rules.morphingRules[i].role; var roleItem = {"role": role, "morphOptions": []}; morphRoles.push(roleItem); } // Check all received items for (var stencilIndex = 0; stencilIndex < data.stencils.length; stencilIndex++) { // Check if the root group is the "diagram" group. If so, this item should not be shown. var currentGroupName = data.stencils[stencilIndex].groups[0]; if (currentGroupName === "Diagram" || currentGroupName === "Form") { continue; // go to next item } var removed = false; if (data.stencils[stencilIndex].removed) { removed = true; } var currentGroup = undefined; if (!removed) { // Check if this group already exists. If not, we create a new one if (currentGroupName !== null && currentGroupName !== undefined && currentGroupName.length > 0) { currentGroup = findGroup(currentGroupName, stencilItemGroups); // Find group in root groups array if (currentGroup === null) { currentGroup = addGroup(currentGroupName, stencilItemGroups); } // Add all child groups (if any) for (var groupIndex = 1; groupIndex < data.stencils[stencilIndex].groups.length; groupIndex++) { var childGroupName = data.stencils[stencilIndex].groups[groupIndex]; var childGroup = findGroup(childGroupName, currentGroup.groups); if (childGroup === null) { childGroup = addGroup(childGroupName, currentGroup.groups); } // The current group variable holds the parent of the next group (if any), // and is basically the last element in the array of groups defined in the stencil item currentGroup = childGroup; } } } // Construct the stencil item var stencilItem = {"id": data.stencils[stencilIndex].id, "name": data.stencils[stencilIndex].title, "description": data.stencils[stencilIndex].description, "icon": data.stencils[stencilIndex].icon, "type": data.stencils[stencilIndex].type, "roles": data.stencils[stencilIndex].roles, "removed": removed, "customIcon": false, "canConnect": false, "canConnectTo": false, "canConnectAssociation": false}; if (data.stencils[stencilIndex].customIconId && data.stencils[stencilIndex].customIconId > 0) { stencilItem.customIcon = true; stencilItem.icon = data.stencils[stencilIndex].customIconId; } if (!removed) { if (quickMenuDefinition.indexOf(stencilItem.id) >= 0) { quickMenuItems[quickMenuDefinition.indexOf(stencilItem.id)] = stencilItem; } } if (stencilItem.id === "TextAnnotation" || stencilItem.id === "BoundaryCompensationEvent") { stencilItem.canConnectAssociation = true; } for (var i = 0; i < data.stencils[stencilIndex].roles.length; i++) { var stencilRole = data.stencils[stencilIndex].roles[i]; if (stencilRole === "sequence_start") { stencilItem.canConnect = true; } else if (stencilRole === "sequence_end") { stencilItem.canConnectTo = true; } for (var j = 0; j < morphRoles.length; j++) { if (stencilRole === morphRoles[j].role) { if (!removed) { morphRoles[j].morphOptions.push(stencilItem); } stencilItem.morphRole = morphRoles[j].role; break; } } } if (currentGroup) { // Add the stencil item to the correct group currentGroup.items.push(stencilItem); if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) { currentGroup.paletteItems.push(stencilItem); } } else { // It"s a root stencil element if (!removed) { stencilItemGroups.push(stencilItem); } } } for (var i = 0; i < stencilItemGroups.length; i++) { if (stencilItemGroups[i].paletteItems && stencilItemGroups[i].paletteItems.length == 0) { stencilItemGroups[i].visible = false; } } $scope.stencilItemGroups = stencilItemGroups; var containmentRules = []; for (var i = 0; i < data.rules.containmentRules.length; i++) { var rule = data.rules.containmentRules[i]; containmentRules.push(rule); } $scope.containmentRules = containmentRules; // remove quick menu items which are not available anymore due to custom pallette var availableQuickMenuItems = []; for (var i = 0; i < quickMenuItems.length; i++) { if (quickMenuItems[i]) { availableQuickMenuItems[availableQuickMenuItems.length] = quickMenuItems[i]; } } $scope.quickMenuItems = availableQuickMenuItems; $scope.morphRoles = morphRoles; }). error(function (data, status, headers, config) { console.log("Something went wrong when fetching stencil items:" + JSON.stringify(data)); });
修改 /public/editor-app/app.js 中获取模型数据的方法,由源码中自由访问修改为携带令牌访问后台资源function fetchModel(modelId) { var modelUrl = KISBPM.URL.getModel(modelId); $http({method: "GET", headers: {"X-Token": KISBPM.URL.getToken()}, url: modelUrl}). success(function (data, status, headers, config) { $rootScope.editor = new ORYX.Editor(data); $rootScope.modelData = angular.fromJson(data); $rootScope.editorFactory.resolve(); }). error(function (data, status, headers, config) { console.log("Error loading model with id " + modelId + " " + data); }); }
修改 /public/editor-app/configuration/toolbar-default-actions.js 中保存模型的方法,由源码中自由访问修改为携带令牌访问后台资源$http({ method: "PUT", data: params, ignoreErrors: true, headers: {"Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Token": KISBPM.URL.getToken()}, transformRequest: function (obj) { var str = []; for (var p in obj) { str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); } return str.join("&"); }, url: KISBPM.URL.putModel(modelMetaData.modelId)}) .success(function (data, status, headers, config) { $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_SAVED }); $scope.modelData.name = $scope.saveDialog.name; $scope.modelData.lastUpdated = data.lastUpdated; $scope.status.loading = false; $scope.$hide(); // Fire event to all who is listening var saveEvent = { type: KISBPM.eventBus.EVENT_TYPE_MODEL_SAVED, model: params, modelId: modelMetaData.modelId, eventType: "update-model" }; KISBPM.eventBus.dispatch(KISBPM.eventBus.EVENT_TYPE_MODEL_SAVED, saveEvent); // Reset state $scope.error = undefined; $scope.status.loading = false; // Execute any callback if (successCallback) { successCallback(); } }) .error(function (data, status, headers, config) { $scope.error = {}; console.log("Something went wrong when updating the process model:" + JSON.stringify(data)); $scope.status.loading = false; });
创建 Modeler.vue 组件,以 iframe 形式将 editor-app 嵌入 vue-element-ui的弹窗 el-dialog 中 <script> export default { name: "Modeler", data() { return { dialogVisible: false, contents: "/modeler.html?modelId=0" } }, mounted() { }, methods: { setSrc(src){ this.contents="/modeler.html?modelId="+src }, showDialog() { this.dialogVisible = true }, closeDialog(){ this.$emit("refreshTable",true) } } }</script>
模型管理VUE文件 查询 重置 关闭 刷新 新建 查询 {{ props.row.id }} {{ props.row.key }} {{ props.row.name }} {{ props.row.version }} {{ $commonUtils.dateTimeFormat(props.row.createTime) }} {{ $commonUtils.dateTimeFormat(props.row.lastUpdateTime) }} 修改 删除 部署 XML <script> import Modeler from "./components/Modeler" import { fetchModelPage, saveNewModel, delModel, deployModel, fetchXml } from "@/api/workflow-model" import { getDictionaryOptionsByItemType, lazyFetchDictionaryNode } from "@/api/dictionary" export default { name: "model", computed: {}, components: { Modeler }, data() { const that = this; return { loading: true, mainTableData: [], mainDataForm: { editingRecord: { key: "", name: "", version: "", enabled: "1", deleted: "1", description: "无", }, mainDataFormDialogVisible: false, mainDataFormDialogTitle: "连续新增" }, sourceCodeForm: { editingRecord: { sourceCode: "" }, dialogVisible: false, }, filterDrawer: { dialogVisible: false, formLabelWidth: "100px", formData: { id: "", key: "", name: "", version: null, createTime: null, lastUpdateTime: null, datestamp: null, enabled: "", deleted: "", description: "", currentPage: 1, pageSize: 10, total: 0, }, }, optionMap: new Map(), //本页需要加载的option数据类型罗列在下面的数组中 optionKey: [ this.$commonDicType.ENABLED(), this.$commonDicType.DELETED(), ], cascaderValue: {}, rules: { id: [{ required: true, message: "请输入主键", trigger: "blur" }], key: [{ required: true, message: "请输入模型标识", trigger: "blur" }], name: [{ required: true, message: "请输入模型名称", trigger: "blur" }], version: [{ required: true, message: "请输入版本号", trigger: "blur" }], createTime: [{ required: true, message: "请输入记录创建时间", trigger: "blur" }], lastUpdateTime: [{ required: true, message: "请输入记录最后修改时间", trigger: "blur" }], } } }, created() {}, mounted() { this.loadAllOptions() this.getMainTableData() }, watch: {}, inject: ["reload"], methods: { refresh() { this.reload() }, loadAllOptions() { for (var i = 0; i < this.optionKey.length; i++) { this.loadDictionaryOptions(this.optionKey[i], false) } }, colFormatter(row, column, cellValue, key) { return this.$commonUtils.optoinValue2Lable(this.optionMap.get(key), cellValue) }, dateTimeColFormatter(row, column, cellValue) { return this.$commonUtils.dateTimeFormat(cellValue) }, dateColFormatter(row, column, cellValue) { return this.$commonUtils.dateFormat(cellValue) }, async loadDictionaryOptions(itemType, includeAllOptions) { this.listLoading = true const response = await getDictionaryOptionsByItemType(itemType, includeAllOptions) this.listLoading = false if (response.code !== 100) { this.$message({ message: response.message, type: "warning" }) return } const { data } = response this.optionMap.set(itemType, data) }, handlePageSizeChange(val) { if (val != this.filterDrawer.formData.pageSize) { this.filterDrawer.formData.pageSize = val; this.getMainTableData() } }, handlePageCurrentChange(val) { if (val != this.filterDrawer.formData.currentPage) { this.filterDrawer.formData.currentPage = val; this.getMainTableData() } }, indexMethod(index) { return this.filterDrawer.formData.pageSize * (this.filterDrawer.formData.currentPage - 1) + index + 1; }, resetForm(formName) { this.$refs[formName].resetFields(); }, showDrawer() { this.filterDrawer.dialogVisible = true }, hideDrawer() { this.filterDrawer.dialogVisible = false }, handleQueryButton() { this.filterDrawer.formData.currentPage = 1 this.getMainTableData() }, async getMainTableData() { this.loading = false const response = await fetchModelPage(this.filterDrawer.formData) this.loading = false if (100 !== response.code) { this.$message({ message: response.message, type: "warning" }) return } const { data } = response this.mainTableData = data.records this.filterDrawer.formData.total = data.total }, handleEditRow(row) { this.$nextTick(() => { this.$refs.modelerComponent.setSrc(row.id) this.$refs.modelerComponent.showDialog() }) }, handleDeleteRow(row) { this.$confirm("此操作将删除选中的数据, 是否继续?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(() => { this.awaitDelModel(row.id) }).catch(() => { this.$message({ type: "info", message: "已取消删除" }); }); }, handleDeployModel(row) { this.$confirm("此操作将部署选中的模型, 是否继续?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(() => { this.awaitDeployModel(row.id) }).catch(() => { this.$message({ type: "info", message: "已取消部署" }); }); }, async handleFetchXml(row){ const guidVO = { guid: row.id } const result = await fetchXml(guidVO) if (this.$commonResultCode.SUCCESS() == result.code) { this.sourceCodeForm.editingRecord.sourceCode = result.data this.sourceCodeForm.dialogVisible = true } this.$message({ message: result.message, type: "warning" }) }, async awaitDelModel(guid) { const guidVO = { guid } const result = await delModel(guidVO) if (this.$commonResultCode.SUCCESS() == result.code) { this.getMainTableData() } this.$message({ message: result.message, type: "warning" }) }, async awaitDeployModel(guid) { const guidVO = { guid } const result = await deployModel(guidVO) this.$message({ message: result.message, type: "warning" }) }, handleClickAddButton() { this.mainDataForm.mainDataFormDialogTitle = "创建新的模型" this.initmainDataForm() this.mainDataForm.mainDataFormDialogVisible = true }, initmainDataForm() { this.mainDataForm.editingRecord.id = "" this.mainDataForm.editingRecord.key = "" this.mainDataForm.editingRecord.name = "" this.mainDataForm.editingRecord.description = "" }, handleSubmitMainDataForm() { this.$refs["mainEditForm"].validate((valid) => { if (valid) { this.submitMainDataForm() } else { console.log("未通过表单校验!!"); return false; } }); }, async submitMainDataForm() { const response = await saveNewModel(this.mainDataForm.editingRecord) if (response.code !== 100) { this.$message({ message: response.message, type: "warning" }) return } const { data } = response this.mainDataForm.mainDataFormDialogVisible = false this.$nextTick(() => { this.$refs.modelerComponent.setSrc(data) this.$refs.modelerComponent.showDialog() }) }, handleCloseMainDataFormDialog() { this.getMainTableData() this.mainDataForm.mainDataFormDialogVisible = false }, async loadLazyCodeNode(dicType, code, resolve) { this.listLoading = true const response = await lazyFetchDictionaryNode(dicType, code) this.listLoading = false if (response.code !== 100) { this.$message({ message: response.message, type: "warning" }) return } const { data } = response // 通过调用resolve将子节点数据返回,通知组件数据加载完成 resolve(data); }, handleCloseSourceCodeDialog(){ this.sourceCodeForm.dialogVisible = false } } }</script>
workflow-model.js
import request from "@/utils/request"//分页获取模型数据export function fetchModelPage(data) { return request({ url: "/api/workflow/auth/activiti/model/page", method: "post", data })}//保存模型export function saveNewModel(data) { return request({ url: "/api/workflow/auth/activiti/model/add", method: "post", data })}//删除模型数据export function delModel(data) { return request({ url: "/api/workflow/auth/activiti/model/del", method: "post", data })}//部署模型export function deployModel(data) { return request({ url: "/api/workflow/auth/activiti/model/deploy", method: "post", data })}//获取模型XMLexport function fetchXml(data) { return request({ url: "/api/workflow/auth/activiti/model/xml", method: "post", data })}
后端功能实现对应前端需求,后端主要实现使用flowable引擎,获取汉化资源、读取模型数据、保存模型数据三个功能。
具体内容参见下一篇博文
项目源码仓库github项目源码仓库gitee
标签:
加快场景创新 科技部首批支持建设十个人工智能示范应用场景
2022-08-16
科技部公布《企业技术创新能力提升行动方案》 亮出10项行动内容
2022-08-16
进入了发展快车道 冷链行业市场规模正在快速膨胀
2022-03-21
行业正站在风口 数字化时代在为传统的自行车产业赋能
2022-03-21
以做强实体经济支撑为重点 成都单个项目年度计划投资同比提升
2022-03-21
拥有多个国际赛事的直播版权 广州游戏电竞企业业绩向好
2022-03-21
投诉量激增 直播带货存在这么多问题的主要原因是什么?
2022-03-21
工作专班深入到各企业 春寒料峭挡不住松原市施工热情
2022-03-21
引导企业向提供“产品+服务”转变 湖南加快智能农机服务化转型
2022-03-21
创新平台建设和科技成果转化 德州加大力度重奖创新
2022-03-21
科技部公布《企业技术创新能力提升行动方案》 亮出10项行动内容
进入了发展快车道 冷链行业市场规模正在快速膨胀
行业正站在风口 数字化时代在为传统的自行车产业赋能
以做强实体经济支撑为重点 成都单个项目年度计划投资同比提升
拥有多个国际赛事的直播版权 广州游戏电竞企业业绩向好
投诉量激增 直播带货存在这么多问题的主要原因是什么?
工作专班深入到各企业 春寒料峭挡不住松原市施工热情
引导企业向提供“产品+服务”转变 湖南加快智能农机服务化转型
创新平台建设和科技成果转化 德州加大力度重奖创新
潜在风险进一步放大 商品房现房销售已是大势所趋
有序复工复产 1—2月份工业经济发展新动能持续增强
多层次高频调度 1至2月河北省工业运行先行指标稳中有增
以车路协同为基础 智能交通推动城市交通绿色高质量发展
人才短板成为制约产业链高质量发展的关键节点
通过技术手段整合调配供给资源 家政行业不断提质扩容
强化产业链深层次合作 加强重大装备国产化“一条龙”模式构建
如何进一步提升纳税人缴费人的减税降费获得感?
探索建设大数据及网络安全示范试点城市有哪些积极意义?
对制造业中小微企业实施缓缴税费政策有哪些积极意义?
进一步增强自我保护意识 消费者需注意辨别谨慎消费
将“走出去”变“请进来” 西安贸易产业转移承接作用不断得到增强
厦门应如何融入“数字中国”的重大战略发展大局?
江苏省如何不断满足老人日益增长的养老服务需求?
建设一体化的职业健康信息管理平台 天津职业人群保障加强
潜力持续释放 1—2月乡村消费品市场恢复略好于城镇
直接对接社会化服务 楼宇调解室将整体提升青岛劳动争议水平
成功化解纠纷11.47万件 银保监会服务质量日趋提高
春雷响百虫出 惊蛰文化在其他方面有了进一步发展
青绿山水画在古代山水画发展史上有着怎样的影响与地位?
- 开播即爆款 “文化类节目收视率低”这一固有印象被推翻
- 涵盖了109件真迹作品 凯斯·哈林展览将持续至6月13日
- 带有一点自信的自嘲 “隔路”是另一种味道的“凡尔赛”
- 与文渊阁前后呼应 “何以中国”特展隆重致敬文化大成
- 严重者可造成暂时性失明 享受冰雪运动要注意眼睛的健康防护
- 种类繁多让人眼花缭乱 选购牛奶时需要重点关注什么?
- 网课让孩子感到不安焦虑怎么办?八问八答回应广大家长关切
- 循环系统很容易受到刺激 “倒春寒”期间老人该如何做?
- 青少年患者睡眠问题日趋增加 9条建议为孩子助眠
- 我国肥胖人群正逐年递增 不良饮食习惯是重要诱因
- 如何减少噪声对听力的损伤?这份耳部和听力保健小贴士请收好
- 强化住房限购措施 西安限购限售范围进一步扩大
- 多种方式增加供给 进一步降低新市民和青年人的居住成本
- 预计9月下旬海口可实现安居房申请网上办理
- 政策调控力度持续升级 8月百城二手房市场均价止涨转跌
- 8月中国新房找房热度依然保持平稳 环比微涨0.2%
- 进一步加强商品房销售价格备案管理 今年全国楼市调控刷新历史纪录
- 西安第二批集中供地中28宗为现场拍卖方式出让
- 细分化需求得到释放 房屋居住的属性越发凸显
- 佛山顺德龙江近日挂牌商住地起拍价约19.88亿元
- 青岛市4宗地竞品质抽签结果出炉 地溢价均约15%
- 坚持政策支持、多方参与 浙江版保障性租赁住房明确新增比例目标
- 简化审批流程 武汉将实现房源申请配租全程网上办
- 哈尔滨新增本土确诊病例3例 活动轨迹公布
- 哈尔滨市公布3例新增本土新冠肺炎确诊病例活动轨迹
- 山东深耕文化资源 推动旅游业高质量发展
- 今年新增952件(套)!南京大屠杀再添新证
- 四川非遗传承人张雄志:巧手捏面塑 指尖传非遗
- 10月以来我国寒潮为何如此频繁?中国气象局回应
- 56位残疾人士登上黄山 互利互勉共建生活希望
- 安徽潜山两车相撞 已致8人死亡3人受伤
- 上海洋山海关首次在出口货运渠道查获夹带卷烟
- 山西忻州古城:一城风华延续千年历史文脉
- 呼伦贝尔新巴尔虎右旗公布1例无症状感染者行动轨迹
- 新增“53+1” 内蒙古累计本土确诊病例增至185例
- 昆明公安打击破坏生物多样性犯罪 抓获130名涉案嫌疑人
- 山西朔州“11·11”较大透水事故调查报告发布 对38人问责处理
- “海关国门小卫士”竞争上岗 淘汰率接近一半
- 深圳摧毁特大品牌化妆品走私网
- 28人被问责!山西石港煤业“3·25”事故调查报告公布
- 湖南韶山以河长制带动全民治水 让每一处水面“长治久清”
- 上海市奉贤区人大常委会原党组书记袁晓林被“双开”
- 民进会员谈反映社情民意信息工作:心怀大我 敢讲实情
- 80岁“留守”奶奶短视频诉孤独 千万网友心疼:我们陪您唠嗑
- 40年来为子弟兵送出1.3万余双布鞋和鞋垫的“布鞋奶奶”走了
- 当男幼师是什么体验?他们说:有委屈尴尬 但大部分是幸福
- 庐阳警方通报幼童坠亡事件:嫌疑人已被刑拘
- 内蒙古新增本土确诊病例53例、本土无症状感染者1例
- 哈尔滨市启动部分地区第一轮全员核酸检测
- 四川通江发生两车相撞事故 致3人死亡