## VentAnaly_2.0_front 系统v2.0前端代码仓库 ### 前言 本项目以[jeecgboot](https://github.com/jeecgboot/jeecgboot-vue3)为模板,请先阅读此文档后继续! ### 开始 建议:安装 nvm 或其他 nodejs 版本管理器,使用 VSCode 作为 IDE,使用 Chrome/Edge 浏览器; 1. 在项目目录下执行 `pnpm install` 2. 在项目目录下执行 `git config commit.template .github/COMMIT_TEMPLATE` ### 开发 开发的基本流程,部分内容可忽略 `git pull` # 可以rebase `git checkout [branch]` # 可选 `nvm use 20` # 建议,高版本node自带pnpm包管理器 `pnpm dev` # 必选,dddd `git add .` `git commit` # 公用模板见.github/COMMIT_TEMPLATE `git push origin [branch]` # 目前master分支无保护,可直接推 ### 构建 `pnpm build` 各个系统的登录页面在src/router/routes/index.ts下进行配置 ### 主题及主题化 主题可以在 /views/vent/sys/setting/index.vue 中找到设置入口 常规的颜色、变量在 /design/color.less 或 /design/themify/ 下添加,图片资源应在 /assets/images/themify/ 下添加 一般来说,主题化开发可以参考以下代码 ```less // import theme variables @import '/@/design/theme.less'; // scope the selector @{theme-deepblue} { .your-component-root-class-name { --image-a: url('/@/assets/images/themify/deepblue/a.png'); --bg-a: #ffffff; } } // scope the selector @{theme-dark} { .your-component-root-class-name { --image-a: url('/@/assets/images/themify/dark/a.png'); --bg-a: #000000; } } // default css .your-component-root-class-name { --image-a: url('/@/assets/images/a.png'); --bg-a: #888888; .class-name-a { background-image: var(--image-a); background-color: var(--bg-a); } } ``` 更加详细的页面主题化标准模板可以参考登录页 /views/sys/login/Login.vue 如果已经写了css代码,想要支持主题化,则下面是配合主题化使用的工具,用于将已有样式代码输出为主题化代码。输入页面的 css 样式,即可输出标准模板所需的资源(返回的数据第一项为变量集合,第二项为css代码文本) ```javascript // Take note of requires below before using this function: // 1. replace all url patterns to patterns like `url(/@/xxx/xxx.xxx)`. // 2. remove all the in-line comments(//) or replace them to block comments(/** */). // 3. replace all rbg/rgba/hsa or any other css color functions to hex colors(#xxxxxx). function themifyScript( str, options = { color: false, gradient: false, url: true, } ) { const keySet = new Set(); let strcopy = str; let varstr = ''; // process url, extract all css url and replace them to css var and record them. { keySet.clear(); const regexp = /url\('?(\/[@|0-9|a-z|A-Z|\-|_]+)+.(png|svg)'?\)/g; let res = regexp.exec(str); while (res) { const [url, image] = res; const varname = `--image-${image.replace('/', '')}`; if (!keySet.has(image)) { keySet.add(image); varstr += `${varname}: ${url};`; } if (options.url) { strcopy = strcopy.replace(url, `var(${varname})`); } res = regexp.exec(str); } } // process gradient, extract all css gradient and replace them to css var and record them. { keySet.clear(); let key = 0; const regexp = /linear-gradient\([0-9|a-z|A-Z|#|,|\s|%|.]+\)/g; let res = regexp.exec(str); while (res) { const [gradient] = res; const varname = `--gradient-${key}`; if (!keySet.has(gradient)) { keySet.add(gradient); varstr += `${varname}: ${gradient};`; } if (options.gradient) { strcopy = strcopy.replace(gradient, `var(${varname})`); } res = regexp.exec(str); key += 1; } } { keySet.clear(); let key = 0; const regexp = /radial-gradient\([0-9|a-z|A-Z|#|,|\s|%|.]+\)/g; let res = regexp.exec(str); while (res) { const [gradient] = res; const varname = `--gradient-${key}`; if (!keySet.has(gradient)) { keySet.add(gradient); varstr += `${varname}: ${gradient};`; } if (options.gradient) { strcopy = strcopy.replace(gradient, `var(${varname})`); } res = regexp.exec(str); key += 1; } } // process color, extract all css colors and replace them to css var and record them. { keySet.clear(); let key = 0; const regexp = /#[0-9|a-z|A-Z]{3,8}/g; let res = regexp.exec(str); while (res) { const [color] = res; const varname = `--color-${key}`; if (!keySet.has(color)) { keySet.add(color); varstr += `${varname}: ${color};`; } if (options.color) { strcopy = strcopy.replace(color, `var(${varname})`); } res = regexp.exec(str); key += 1; } } return [varstr, strcopy]; } ``` ### SVG 模型动画开发 二维模型采用「下载装配」方案(2026-10 起):整幅 SVG 与动画数据作为静态资源下发, 运行时下载解析后装配进组件,组件只保留业务驱动。 - **模型资源**:`public/svg-models//model.svg`(整幅静态 SVG,`data-anim-id` 静态标注) + `model.json`(`viewBox`、`css`、`animations`、`elements`、`groups`、`maskVariants`), 同一模型并发请求只下载一次; - **运行时**:`src/utils/svg-model/`(`loader`/`namespace`/`css`/`engine`/`useSvgModel`/`useEngineAnim`) 与容器组件 `src/components/vent/SvgModelViewer.vue`; - **组件**:`` + `useEngineAnim(viewerRef)` 调用引擎; 页面注册表、props 与 `animate(...)` 入参签名与旧实现一致,**页面代码不需要改动**。 驱动组件写法: ```vue ``` 关键约定: - `identify`:同页多实例必须传不同值,运行时给内部 id 加 `svgm--` 前缀隔离; - 进度式动画(如风窗)用 `engine.animateElement(id, reverse, { progress })` 直接定帧; - 类驱动模型(如主扇)依赖 `model.json` 的 `css`,按状态类加/删即可; - 模型生成:设计侧用 `keyframes_generator`(mini_projects 仓库)输入帧 SVG 自动产出 `model.svg` / `model.json` / 驱动模板(`node index.js --id `),也可手工维护模型包文件。 开发调试: - 手动测试页 `public/svg2d-demo.html`:下拉选择模型、按状态按钮逐个触发动画并显示 DOM 概览; - 完整说明(包格式、迁移步骤、输出一致性对比、构建体积数据)见 [docs/svg-model.md](./docs/svg-model.md)。 ### 模型预览图配置 1、上传预览图至服务器 `/data/file/modelThumbnail/{devicekind}` 2、设备字段管理处修改对应设备的模型类型字段,将输入类型修改为 `selectmodel` 3、删除字典中对应的模型类型字典,否则系统将默认使用字典驱动 支持的设备有:`windows`、`gate`、`fanlocal`、`fanmain`、`windrect`、`door` 各设备类型对应的 dictCode 及 store key 对照: | 设备 (devicekind) | dictCode | | ----------------- | ----------------------- | | `windows` | `windowModalType` | | `gate` | `gateStyle` | | `fanlocal` | `fanlocal_install_kind` | | `fanmain` | `mainFanType` | | `windrect` | `windrecModel` | | `door` | `fireDoorStyle` | 4、常见问题 A:想用原本的文本类模型选择下拉框,只需要将把步骤 2 中的输入类型改回 `selectdict` 即可 B:如果使用模型预览图的下拉框但是没有图片出现,可以检查一下是否上传了对应设备的预览图,后端接口`/getAllModelFileJson`是否可用