# data-browsing-components-public **Repository Path**: xtht/data-browsing-components-public ## Basic Information - **Project Name**: data-browsing-components-public - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2024-06-12 - **Last Updated**: 2025-03-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## DataBrowsingComponents 云道信息天气预报服务一体化业务平台数据浏览前端组件仓库,目的是以低代码的形式引入到“广西天气预报服务一体化业务平台”使用。采用 monorepos 管理思路,本项目采用 pnpm 的工作空间(workspace) 来管理本项目仓库。前端技术栈是 Vue3.0和 Typescript。 ### monorepos 技术介绍 Monorepo 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。简单理解:所有的项目在一个代码仓库中,但并不是说代码没有组织的都放在一个文件夹里面。Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用🧐,如 Google、Facebook 和 Microsoft 等,很多前端常用的开源库也在使用,如Vue、React、Element-plus pnpm workspace 是 pnpm 包管理工具的一个功能模块,专注于支持 Monorepo(单一仓库)的工作区管理。它通过高效的依赖共享机制,将多个相关的包集中管理,实现了更快速、更节省空间的依赖安装和执行。pnpm workspace 提供了命令行工具,支持多包管理、依赖共享、版本一致性维护、脚本运行等功能,使得在单一仓库中管理多个包变得更为便捷和高效。其在 Monorepo 场景下的优势在于提供了快速、节省空间、版本一致性等方面的综合解决方案。当使用 PNPM 来管理 monorepo 时,实际上在利用 PNPM 的特性来更高效地管理多个相关项目的依赖关系。 下面是一个关于使用 PNPM 管理 monorepo 的简要介绍: #### 什么是 monorepo Monorepo 指的是将多个相关的项目(通常是软件项目)放置在同一个版本控制仓库中的做法。这些项目可能共享某些代码、工具或者依赖。 #### 为什么选择 monorepo - 代码复用:因为多个项目共享一个代码库,所以避免了在不同项目中重复编写相同功能代码的问题,提高了开发效率。 - 提升协作效率:多个项目在同一个代码库中进行开发,可以方便地共享代码和文档,避免不同项目之间的沟通和协调成本。 - 集中管理:Monorepo 架构中,不同的应用程序都在同一个代码库中,方便管理和监控。这一点非常重要,特别是在需要同时对多个版本进行修改和维护的情况下。 - 统一构建:Monorepo 的一个重要特点是可以共用一套构建系统和工具链进行构建和部署,提升了构建的效率。 - 可以快速定位问题:由于所有的代码都在同一个代码库中进行开发,debugger 可以很快找出问题所在的代码文件和行数,便于开发人员调试问题。 - 一个版本:无需担心因为项目依赖于第三方库的冲突版本而导致的不兼容问题。 #### PNPM 的优势 npm/yarn 安装依赖时,存在依赖提升,某个项目使用的依赖,并没有在其 package.json 中声明,也可以直接使用,这种现象称之为 “幽灵依赖”;随着项目迭代,这个依赖不再被其他项目使用,不再被安装,使用幽灵依赖的项目,会因为无法找到依赖而报错。基于 npm/yarn 的 Monorepo 方案,依然存在 “幽灵依赖” 问题,我们可以通过 pnpm 彻底解决这个问题。 pnpm 是一款快速、高效使用磁盘空间的包管理器,它采用了符号链接的方式来共享依赖,从而节省了磁盘空间和下载时间。它具有以下优势: - 速度快:多数场景下,安装速度是 npm/yarn 的 2 - 3 倍。 - 基于内容寻址:硬链接节约磁盘空间,不会重复安装同一个包,对于同一个包的不同版本采取增量写入新文件的策略。 - 依赖访问安全性强:优化了 node_modules 的扁平结构,提供了限制依赖的非法访问(幽灵依赖) 的手段。 - 支持 Monorepo:自身能力就对 Monorepo 工程模式提供了有力的支持。在轻量场景下,无需集成 lerna Turborepo 等工具。 #### PNPM 与 monorepo 结合的使用方式 pnpm 支持 Monorepo 模式的工作机制叫做 workspace(工作空间)。它要求在代码仓的根目录下存有 pnpm-workspace.yaml 文件指定哪些目录作为独立的工作空间,这个工作空间可以理解为一个子模块或者 npm 包。 例如以下的 pnpm-workspace.yaml 文件定义:a 目录、b 目录、c 目录下的所有子目录,都会各自被视为独立的模块。 ```js packages: - a - b - c/* ``` 📦my-project ┣ 📂a ┃ ┗ 📜package.json ┣ 📂b ┃ ┗ 📜package.json ┣ 📂c ┃ ┣ 📂c-1 ┃ ┃ ┗ 📜package.json ┃ ┣ 📂c-2 ┃ ┃ ┗ 📜package.json ┃ ┗ 📂c-3 ┃ ┗ 📜package.json ┣ 📜package.json ┣ 📜pnpm-workspace.yaml 需要注意的是,pnpm 并不是通过目录名称,而是通过目录下 package.json 文件的 name 字段来识别仓库内的包与模块的。 在 monorepo 中,每个项目通常都有自己的 package.json 文件,用于定义项目的依赖关系和脚本命令。PNPM 支持使用 workspace 特性来管理 monorepo 中的多个项目。你可以在 monorepo 的根目录下创建一个统一的 package.json 文件,并在其中声明每个项目的路径以及共享的依赖。 通过配置 workspace,PNPM 可以自动地管理 monorepo 中各个项目的依赖,确保它们之间的依赖关系正确地解析和共享。 PNPM 还提供了其他一些功能,比如自动版本锁定、并行安装、缓存管理等,这些功能都有助于提高 monorepo 的开发效率和可维护性。 ### 项目结构 - /packages/components 组件文件夹 - /packages/composables 组合式函数文件夹 - /packages/directives 自定义指令文件夹 - /packages/locale 国际化逻辑文件夹 - /packages/composables 组合式函数文件夹 - /packages/data-browsing-components 项目打包入口,需要给外部使用的组件、类在这儿导出 - /packages/shared 扩展类的文件夹 - /packages/theme-default 组件样式文件夹 - /packages/utils 工具函数文件夹 特别需要规划好 components 中的目录结构,避免出现同名的组件。 ### 天气业务一体化平台组件开发指南 #### Vue3 组件化开发 - 采用vue3技术栈开发组件,参考 [vue3官方文档](https://cn.vuejs.org/guide/essentials/component-basics.html)。 ![Vue3官方文档](./snapshot/vue-cmp.png) #### 平台组件化要求 - 开发为[vue插件库](https://cn.vuejs.org/guide/reusability/plugins.html)(必须)。并以[库模式](https://cn.vitejs.dev/guide/build.html#library-mode)打包。在项目中通过packages.json在npm或者本地安装引入的。 ![Vue3官方文档](./snapshot/vue-plugin.png) - 依赖库 - 约定使用[Quasar](https://quasar.dev/)或者[ElementPlus](https://element-plus.org/zh-CN/)。 - 约定地图使用 maplibre-gl 或者 openlayers。 - 表格组件 vxe-table。 - 图表库 echarts。 - 其他 dayjs、turf、protobufjs 等。 - 组件参数暴露(可选) 需要开放到设计器配置的组件参数,按vue属性prop暴露方式编写,然后注册组件的时候提交暴露参数的类型、可选项等信息。 - 组件暴露属性: ![alt text](./snapshot/vue-prop.png) - 引入时设置这些属性,编辑类型等: ![alt text](./snapshot/vue-prop2.png) - 实际看到的效果: ![alt text](./snapshot/vue-prop3.png) - 插槽暴露(可选) 如果当前组件预留有插槽,需要暴露2个方法:(1)getSlotNames: () => string[] 当前组件所有插槽;(2)getActiveSlotName: () => string 当前激活的插槽(动态适用)。 - 组件命名(必须,根据国家局的文档《气象业务软件组件化开发指南(2023版).docx》) 气象业务组件命名遵循一定的规则,根据组件的分层、分类及分级标签进行分段编码命名。组件的名称由5部分代码组成,规则如下: - [组件分层]-[组件一级分类]-[组件二级分类]-[组件资料类型标签]-[扩展字段] - 组件命名时取各代码的英文缩写,各代码之间用中划线“-”连接。 例如:气象基础组件中的NetCDF格式处理的解码组件命名为: - mbc-mdfp-ncfp-mult-ncd - 其中,MBC为气象基础组件层标签,MDFP为一级分类气象数据格式处理类组件标签,NCFP为二级分类NetCDF格式处理组件标签,MULT为组件适用资料类型标签(多种资料),扩展字段NCD指NetCDF格式解码。 ![alt text](./snapshot/vue-namespace.png) 实际开发前应该会分配好各单位需要开发的组件。 #### 高级应用 - 事件总线mitt - 地图初始化完成消息。【系统约定】 - 特殊的事件消息。【系统约定、如:全局时间轴改变事件,广播消息,需要根据时间改变的部件接收消息并做出动作。】 - 组件内部相互通知。【组件约定,系统不干预。A组件发送消息,B组件接收对应消息。以下示例的消息字符串消息名称不推荐,生产实践建议推荐用Symbol对象,组织到composables中导出使用,避免消息重复。】 发送示例: ```ts import { useVueMaplibre } from '@meteosci/vue-maplibre' export function default () { const { map, vmMitt } = useVueMaplibre() const onRowClicked = row => { const { Lon, Lat } = row // addAndFlyToMarker([Lon, Lat]) vmMitt.emit('row-clicked', row) } } ``` 接收示例: ```ts import { useVueMaplibre } from '@meteosci/vue-maplibre' export function default () { const { map, vmMitt } = useVueMaplibre() onMounted(() => { map.on('click', onMapClicked) vmMitt.on('row-clicked', onRowClicked) }) onUnmounted(() => { map.off('click', onMapClicked) vmMitt.off('row-clicked', onRowClicked) }) const onMapClicked = (e: MapMouseEvent) => { lngLat.value = e.lngLat //getForecastData() } } ``` - 数据请求request 平台代码已注入 axios 实例 request,组件中可以通过组合式API方法拿到它。 ```ts import { useGlobalConfig } from '@data-browsing-components/composables' export default function () { const { request } = useGlobalConfig() const getLiveStationDataApi = (time: string) => { // 以下请求参数如果需要支持修改,可以通过props暴露 return request({ baseURL: '/', url: '/datas/liveStation.json', method: 'get', params: { dataCode: 'SURF_CHN_MUL_HOR', times: time, //20230423100000 adminCodes: '450000', elements: 'Station_Name,Station_Id_C,Lat,Lon,PRE_1h,PRE_3h,PRE_6h,PRE_12h', dataFormat: 'json' } }) } } ``` - 组件内部编辑配置 一些复杂的操作在平台编辑器中并不好交互,需要在组件内部实现。实际上就是在组件内部编辑props,为了保存编辑状态,内部的 prop 需要通过 update:modelValue 发送到外部。拿**CtwGxTab**组件演示说明。如图: ![alt text](./snapshot/vue-update-prop.png) - 组件导出与类型声明 - 组件导出(必须):参考提供的示范项目框架的结构导出即可。 (1)[widgets](packages/components/widgets/index.ts) (2)[all component](packages/data-browsing-components/component.ts) - 类型声明(可选):主要是为外部提示组件的props和暴露的方法以及插槽。参考[type](packages/components/widgets/examples/widget-chart/type.ts) - 本地调试 项目已经有配套的 demo 工程可以调试开发的组件,在本地调试正常的前提下,打包出来的库才能正确引入到“广西天气预报服务一体化业务平台”中使用。 ```bash cd /demo pnpm dev ``` #### **地图组件** 提供封装好的 maplibre-gl 和 openlayers 组件,推荐 Maplibre 地图组件。(1)地图实例采用 vue 依赖注入特性注入到子组件。(2)子组件监听地图初始化完成的消息,被动保存。参考[use-map](packages/composables/use-map/index.ts) - 使用示例 ```ts import { useGlobalConfig, useMap } from '@data-browsing-components/composables' export default function () { const { map } = useMap() const onTempChanged = e => { if (!tempGridData.value) { logger.warn('渲染数据失败,原因:格点数据为空!') return } if (!map.value) { logger.warn('渲染数据失败,原因:没有找到地图!') return } if (e) { const cmap = new ME.Renderer.Colomap.QualitativeColormap(Palette.temp, { clip: false }) if (map.value.getLayer(layerId)) { map.value.removeLayer(layerId) } const gridData: CTGridData = tempGridData.value const layer = new ME.Renderer.Layer.RadarRasterGridLayer({ id: layerId, bounds: [gridData.startLon, gridData.startLat, gridData.endLon, gridData.endLat], cmap: cmap, data: gridData.gridValues, opacity: 0.6, autoFitbounds: true, linear: true, resolution: [gridData.latGridCount, gridData.lonGridCount], showSymbolLayer: true }) map.value.addLayer(layer) let popup: Popup layer.on('click', e => { const coordinates = [e.lngLat.lng, e.lngLat.lat] if (e.value) { popup = new Popup() .setLngLat(e.lngLat) .setHTML(`经纬度: ${coordinates.map(v => (v = v.toFixed(6))).join(' ,')}
要素值: ${e.value.toFixed(2)}`) .addTo(map.value) } else { popup && popup.remove() } }) } else { if (map.value.getLayer(layerId)) { map.value.removeLayer(layerId) } } } return { onTempChanged } } ``` 详见:[useWidgetLayer](packages/components/widgets/examples/widget-layer-temp/useWidgetLayer.ts) #### 风格样式 各组件的样式统一放到[index.scss](packages/theme-default/src/index.scss)导出。 ![alt text](./snapshot/css-all.png) 可变的样式(为了支持多主题风格切换),确定用css变量来存储样式。为了避免 css 污染,全局 css 变量名称见 [CSS变量](demo/src/config/theme/light.ts),待丰富完善。 ![alt text](./snapshot/css-var.png) 静态资源推荐放到各自的后台程序的www静态资源服务中,组件中用的时候直接写 url。 对于图片类型的静态资源文件,也可以考虑放到**/packages/theme-default/src/images**文件夹中,然后引入,打包会过程中会把这些图片转成base64格式内置到index.css中。 大部分组件建议使用flex布局,宽高都设置为100%,以便在拖拽改变容器大小时,组件能自适应改变。[参考](packages/components/widgets/examples/widget-chart/Index.vue) #### 注册使用 - 将打包结果上传到广西组件仓库平台,并填写约定填写的信息,审核通过后,一体化平台通过 pnpm 本地安装使用。 ![alt text](./snapshot/register.png) #### 新建工程需要修改的内容 应局里要求,建议咱们各参与方的组件应该分类规划放到不同的工程,而不是一个公司一个工程,也没必要每个组件一个工程。此模板工程是云道信息“数据浏览前端组件仓库”分离出来的脚手架,项目名字(包名)叫做 “data-browsing-components”,各位不能直接在上此修改,而应该是基于此复制一份,来创建咱们自己的组件仓库,由于局里目前并没有统一项目命名,请大家自己拟。 1. /packages/data-browsing-components 文件夹重命名为您的项目名称的文件夹。 2. 各个模块中的 package.json 中的 data-browsing-components 改为您的项目名称。 3. [打包配置paths](build/utils/paths.ts) 中的 data-browsing-components 改为您的项目名称。 4. [打包配置constants](build/utils/constants.ts) 按您的项目名称修改。 5. [打包配置full-bundle](build/full-bundle.ts) 中的 data-browsing-components 改为您的项目名称。 6. [版本配置](scripts/gen-version.ts) 中的 data-browsing-components 改为您的项目名称。 7. [demo main配置](demo/src/main.ts) 中的 data-browsing-components 改为您的项目名称。 8. [项目ts配置](tsconfig.json) 中的 data-browsing-components 改为您的项目名称。 9. [demo](demo/src/pages/examples/Index.vue) 中的 data-browsing-components 改为您的项目名称。 10. [all.ts](packages/data-browsing-components/all.ts) 中的 data-browsing-components 改为您的项目名称。 11. [global.d.ts](typings/global.d.ts) 中的 data-browsing-components 改为您的项目名称。 12. [component.ts](packages/data-browsing-components/component.ts) 中的 data-browsing-components 改为您的项目名称。 ... 剩下的文件建议全局搜索 @data-browsing-components 来替换修改为您的项目名称。开启大小写匹配。 #### 快速上手 ##### 依赖安装 由于组件仓库不面向互联网,所以我们开发的组件库放在私服上,执行下面的命令,用提供的账户登录后即可安装。 ```bash npm login --registry=https://devapi.songluck.cn:6220/ ``` ##### 组件开发 建议看 **packages/components/widgets/examples** 中的例子。