系统v2.0前端代码仓库
本项目以jeecgboot为模板,请先阅读此文档后继续!
建议:安装 nvm 或其他 nodejs 版本管理器,使用 VSCode 作为 IDE,使用 Chrome/Edge 浏览器;
在项目目录下执行 pnpm install
在项目目录下执行 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/ 下添加
一般来说,主题化开发可以参考以下代码
// 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代码文本)
// 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];
}
二维模型采用「下载装配」方案(2026-10 起):整幅 SVG 与动画数据作为静态资源下发, 运行时下载解析后装配进组件,组件只保留业务驱动。
public/svg-models/<modelId>/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;<SvgModelViewer model-id="xxx" /> + useEngineAnim(viewerRef) 调用引擎;
页面注册表、props 与 animate(...) 入参签名与旧实现一致,页面代码不需要改动。驱动组件写法:
<template>
<SvgModelViewer ref="viewerRef" model-id="gate-2d" />
</template>
<script setup lang="ts">
import SvgModelViewer from '/@/components/vent/SvgModelViewer.vue';
import { useEngineAnim } from '/@/utils/svg-model/useEngineAnim';
import { ref } from 'vue';
const viewerRef = ref<any>(null);
const { triggerAnimation } = useEngineAnim(viewerRef);
// 入参签名与旧组件保持一致
function animate(door1, door2, door3) {
const open = door1 || door2 || door3;
triggerAnimation(['FengMen1_L_0_Layer0_0_FILL', 'FengMen1_R_0_Layer0_0_FILL'], !open);
}
defineExpose({ animate });
</script>
关键约定:
identify:同页多实例必须传不同值,运行时给内部 id 加 svgm-<identify>- 前缀隔离;engine.animateElement(id, reverse, { progress }) 直接定帧;model.json 的 css,按状态类加/删即可;keyframes_generator(mini_projects 仓库)输入帧 SVG 自动产出
model.svg / model.json / 驱动模板(node index.js --id <modelId>),也可手工维护模型包文件。开发调试:
public/svg2d-demo.html:下拉选择模型、按状态按钮逐个触发动画并显示 DOM 概览;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是否可用