MS平台
平台愿景是希望降低软件研发成本、加快软件研发进度、管控软件研发质量。平台目前只支持微信扫码注册且只能使用QQ号作为邮箱。
控制台

-
分享:分享所有模板和插件总数目
-
支出:在MStore下载插件和插件总数目消费金额
-
下载:在MStore下载模板和插件总数目
-
收益:分享后在MStore用户下载的收益,最终提现到个人账户
分享皮肤
价值源自分享!!! 通过分享给我们带来不断的社会价值,从现在开始打开您的电脑看看有没有之前或现在制作完成的皮肤,都可以分享到MStore。
Tip
如果上传已经存在的模板,封号处理
模板分享插件(分享插件可以在Store安装)
导出动图演示

安装好插件会新增 MStore分享 模块, 点击导出下载获得当前系统的模板与演示数据
导出的文档结构说明
--default.zip
|-- data 演示数据
| | |-- upload 导出上传资源文件
| | |-- APP.json 应用设置数据
| | |-- CMS_CATEGORY.json 栏目数据
| | |-- CMS_CONTENT.json 文章数据
| | |-- MDIY_MODEL_*.json 自定义模型数据
| | |-- MODEL_JSON_MDIY_MODEL_*.json 自定义模型配置数据
|-- default 模板文件夹(具体当前模板文件夹名称)
| |-- *.htm 模板文件
`-- html 静态化演示文件
模板定价参考
目前定价参考只针对CMS建站类模板,后续其他的皮肤
仿站 10元起
| 增加条件 | 价格(元) |
|---|---|
| 支持响应式 | +(10~50) |
| 内容栏目演示数据 | +(10~20) |
| 自定义业务 | +(10~20) |
| 自定义模型 | +(10~20) |
原创 100元起
| 增加条件 | 价格(元) |
|---|---|
| 支持响应式 | +(50~100) |
| 内容栏目演示数据 | +(10~20) |
| 自定义业务 | +(10~20) |
| 自定义模型 | +(10~20) |
Tip
价格变动会第一时间更新次文档,如果上架模板不符合定价标准,系统将审核失败进行退回处理。
分享收益
导出的模板可以快速分享到MStore

MStore中具有响应式效果的皮肤可以通过分享插件快速导入,导入的数据与演示系统一致

收益提现
收费的皮肤会给分享者带来实际的收益,通过会员中心的提现功能即可提取

分享插件导入导出常见问题
导入含有自定义业务的模板问题
新增自定义业务数据错误,异常信息id doesn't has default value,一般是由于自定义业务表的id是varchar类型导致的
上面情况的处理方法:
# 以mysql为例
ALTER TABLE 自定义表名 DROP PRIMARY KEY;
ALTER TABLE 自定义表名 MODIFY id INT AUTO_INCREMENT PRIMARY KEY;
规范问题
分享导出模板时,请遵守导出规范
-
对于所有使用在模板中的图片、视频等资源,一律存放在当前模板文件夹内的文件夹中,禁止放在upload文件夹下(除非这个资源有被文章、栏目引用),否则该资源会在导出时被删除

-
对于所有在文章、栏目、自定义模型中使用到的资源,一律存放在upload文件夹下
模板导出失败
导出时,应用设置未绑定模板

导出模板超过100M异常的解决方法
- 通过处理模板文件夹(推荐)
- 将当前模板文件夹(应用设置绑定的模板,默认是template/1/下,站群为template/appId下)下的大文件夹(如图片),手动从当前项目中移出后,再导出;

- 将导出的压缩包解压,然后把移出的文件夹,放入模板名称文件夹下

- 手动压缩文件,还原压缩包,注意压缩的层级要和导出的层级一致

Tip
不可以将模板文件移出
- 通过处理upload文件夹
- 将upload/appId(默认是1,站群为upload/当前站id)下的文件移出

- 将导出的压缩包解压,然后把移出的文件夹,放入data/upload下

- 手动压缩文件,还原压缩包,注意压缩的层级要和导出的层级一致
导入模板失败
根据系统异常日志信息,定位问题 以下列举一些情况
- 数据库配置忽略大小写
一般错误为xxx表doesn’t exist
如:

Tip
解决方案参考: 数据库大小写忽略
-
表已存在, 在自定义业务或自定义模型中,找到表名那条数据删除
-
导入文件过大,修改yml配置ms.upload.multipart.max-file-size
Tip
解决参考:文件过大
导入模板覆盖数据说明
1、非站群环境(默认):执行导入zip模板过程 首先删除旧文章、栏目数据和upload/1文件夹下的文件,再导入新的文章、栏目、upload/1下文件和自定义模型数据。
2、站群环境:执行导入zip模板过程 当前站点id说明 执行导入的模板只会删除当前站点id下旧文章、栏目数据和upload/当前站点id文件夹下的文件,再在当前站点下导入新的文章、栏目、upload/当前站点id下文件和自定义模型数据。
分享插件
价值源自分享!!! 一起来分享好用、实用的插件。
源码包

包含完整的业务代码,源码包需上传完整的src文件夹
如果已经发布中央库可以直接使用maven依赖坐标配置
升级包
ms-mpay 支付插件升级包结构参考

升级包需上传的包结构 如net.mingsoft.pay.upgrade.xxx,只需包含升级文件,其余方法不需要上传。
菜单 menu.json
在 业务包.upgrade,只能包含一个json菜单文件,可以通过代码生成器下载
Tip
菜单json只能导入顶级菜单,如果是子菜单需要编写业务代码导入,参考下方常见问题第一点
数据库 sql
在 业务包.upgrade,可以包含多个不同数据库 sql 脚本文件
可以存在多个sql文件
- ddl.sql 执行优先级最高,编写对表结构操作的语句;
- data.sql 初始化数据,编写对应插件的初始化数据;
Tip
也可以编写多个sql文件,与data.sql执行的效果一致,统一视为初始化数据sql
执行类 Upgrade.java
如果sql文件不能满足业务数据的初始化,可以通过升级类来进行业务数据的处理。
- 可以包含多个 Upgrade*.java 文件,只需要提供
public公共方法; - 类文件不能使用任何spring的注解;
- 通过 SpringUtil.getBean 来获取对应业务对象,可以处理复杂的业务逻辑
范例:
package net.mingsoft.mpay.upgrade;
/**
* 支付升级业务
* 这就是普通的类文件,唯一注意点是就是不能使用注解
*/
public class Upgrade {
private static String menuJson = "菜单json";
public void upgrade() {
//获取业务对象
IModelBiz modelBiz = SpringUtil.getBean(IModelBiz.class);
}
}
打包导出插件
分别需要导出插件 源码包 与 升级包
pom.xml 片段参考,注意注释部分,打包好会回分别生成 *-sources.zip与*-upgrade.zip
<?xml version="1.0" encoding="UTF-8"?>
...
<build>
...
<plugins>
<!--增加对应-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptors>
<!--打包源码-->
<descriptor>assembly-source.xml</descriptor>
<!--打包升级包-->
<!-- <descriptor>assembly-upgrade.xml</descriptor> -->
</descriptors>
</configuration>
</plugin>
</plugins>
...
</build>
</project>
- 打包源码脚本
assembly-source.xml
<!-- 源码包配置 -->
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>source</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>src</directory>
<outputDirectory>${file.separator}/src</outputDirectory>
<excludes>
<exclude>**/upgrade/**</exclude>
</excludes>
</fileSet>
</fileSets>
</assembly>
- 打包升级包
assembly-upgrade.xml
<!-- 升级包配置 -->
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>upgrade</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target${file.separator}classes</directory>
<outputDirectory>${file.separator}</outputDirectory>
<includes>
<include>**/upgrade/**</include>
</includes>
<excludes>
<exclude>**/upgrade/*.java</exclude>
</excludes>
</fileSet>
</fileSets>
</assembly>
分享
将打包出来的插件分享到MStore
定价参考
| 条件 | 价格(元) |
|---|---|
| 只是简单基于模板插件(不存在新的业务表) | +(10~50) |
| 基于自定义模块 | +(50~100) |
| 简单业务插件,存在3张表以内 | +(100~200) |
| 复杂业务插件,大于3张表 | +(200~5000) |
Tip
价格变动会第一时间更新次文档,如果上架模板不符合定价标准,系统将审核失败进行退回处理。
常见问题
二级菜单导入
// 资源引入
public class Upgrade {
// 菜单管理处 找到对应菜单 复制菜单json
private static String menuJson = "...";
public void upgrade() {
IModelBiz modelBiz = (IModelBiz) SpringUtil.getBean(IModelBiz.class);
LambdaQueryWrapper<ModelEntity> lqw = new LambdaQueryWrapper();
lqw.eq(ModelEntity::getModelTitle, "所属父菜单标题 如 权限管理");
ModelEntity model = (ModelEntity)modelBiz.getOne(lqw);
ModelEntity modelEntity = (ModelEntity) JSONUtil.toBean(menuJson, ModelEntity.class);
ManagerEntity manager = BasicUtil.getManager();
modelBiz.importModel(modelEntity, manager.getRoleId(), (String)null, Integer.parseInt(model.getId()));
}
}
代码生成器使用手册
降低软件研发成本,采用拖拽的方式设计表单生成SQL与Java代码。操作界面上大量采用?号帮助引导的信息,让用户快速上手使用。

特点
1、拖拽方式制作后台管理界面(列表、表单);
2、自动生成SQL DDL语句,暂时只支持MySQL;
3、自动生成规范的Java业务代码;
使用场景
1、快速创建项目:从零开始快速搭建项目;
2、自定义扩展:可用于生成MCms自定义模型、自定义表单的业务;
3、代码参考:可以快速生成Element控件、验证规则;
快速开发项目
在MCms的基础上,通过代码生成器快速开发新功能。
创建项目

这一步很重要,决定了生成Java包的结构、模块的名称与表名的生成规则
生成SQL中的表名称会拼接上设置好的表前缀
Note
注意:项目包+模块名必须在平台中唯一
创建业务表单

设计表单
根据实际业务通过拖拽控件的方式设计业务表单,每个控件都有对应的属性设置。
列表设置
根据实际业务通过拖拽字段控制列显示的先后顺序

- 默认值:在表单页会自动填写当前值
- 列表搜索:是否在列表页可搜索该字段
代码预览
保存之后,对应生成自定义模型与相关页面代码。
可导入到对应的自定义模块,也对源码进行下载。
导入到自定义模块更加简洁,无需调试代码。相关文档参考《自定义业务开发》。
点击此处预览源码
Tip
注意:预览代码前需要保存。
自定义模型
自定义模型复制即可导入到自定义模块。
导入之后自动解析生成后面的表单页面与SQL等等。无需进行后续的导入。
列表与表单
生成的表单与列表为ftl页面,需要添加页面则可在此路径src/main/webapp/WEB-INF/manager/cms(模块名)新增文件。
Tip
注意:此页面为后台界面文件,而非前台html文件,不可直接粘贴为前台页面
后台代码的生成
实体Entity、Action与Biz等。
此类业务代码,放置于此路径src/main/java/net/mingsoft(项目包)/cms(模块名)即可。
下载SQL
复制sql文件导入数据库中

源码的代码
若需要源码,在此处可下载。
1、目录层级自动生成,复制到项目工程即可使用(注意:合并复制)
2、修改net.mingsoft.MSApplication扫描包
@SpringBootApplication
@ComponentScan(basePackages = {"net.mingsoft","添加代码生成器项目包参数"})
@MapperScan(basePackages={"**.dao"})
@ServletComponentScan(basePackages = {"net.mingsoft"})
public class MSApplication {
public static void main(String[] args) {
SpringApplication.run(MSApplication.class, args);
}
}
3、重启系统
安装菜单

1、代码生成器项目列表初始化菜单数据
2、复制菜单数据
3、在系统菜单管理中导入菜单数据
定制页面情况
场景:增加一个菜单访问定制的页面
-
增加对应的接口,返回该页面名称,可以参考接口中的list和form接口;
-
定制页面放在WEB-INF下对应层级的位置,参考该接口list和form接口的页面;
-
增加菜单(子菜单),菜单地址为新增加接口的地址
ElementUI表单开发
常用的表单设计开发者不需要每次去阅读饿了么的手册,直接在代码生成器上预览并复制使用即可,提供表单业务开发效率。
ElementUI表单预览

自定义开发
自定义页面的开发
通过后台配置绑定对应模版方式实现。可以实现专题页、活动页的效果
- 以商城活动页为例
代码生成器的新增和导入同上。

新增一个活动页

标题为页面名称,不能重复。
路径关键字决定了访问地址的路径。后面会自动拼接.do

Tip
注意 自定义页面不需要静态化生成,即可预览最新效果。
需要登录的页面
如果此页面需要登录才能查看,路径需带有people/前缀。
Tip
注意 登录才能查看的页面,需要安装会员插件。
如:个人中心“people/center“,对应访问地址:“域名/people/center.do”
传参数
多个自定义页面之间,可以互相传递参数。
页面发起请求携带参数,模板之中可以使用${id}获取。
也可以通过 js 工具类 ms.util.getParameter("id")获取

自定义模型开发
可以快速扩展现有的业务表数据。 当现有字段不满足业务需求,不推荐直接在数据库中加字段。使用自定义模型会更好的管理。
- 以扩展文章字段为例
自定义模型的上传
从代码生成器中拖拽需要补充的字段。复制与上传同上图。
类型选择文章。

栏目绑定自定义模型字段
从内容管理中绑定自定义模型。

再从此栏目文章中进行编辑或者新增,都会出现自定义模型。
可参考文档:
自定义模型字段的前端获取。
前端中模型的显示是通过标签。 具体可以参考自定义模型标签使用
自定义配置的开发
快速实现基本配置管理业务,例如:系统设置、功能设置、上传设置等。
以文件上传配置为例
如图所示拉取组件,如上例保存与导入。
同理,可以进行表单预览。

相关代码可参考文档 插件手册
自定义配置的数据保存与获取
可通过数据预览,直接进行数据的保存。
可以把自定义的配置通过复制菜单JSON结合到菜单,参考上述自定义业务。
自定义字典
自定义字典是管理数据属性的一个通用管理工具,主要是管理数据的状态或属性
列如一个订单有很多状态:已下单、已付款、已发货、已收货、已签收、取消。就可以通过绑定字典来进行处理。

Tip
注意 系统中的下拉、单选、多选都可以通过字典来实现。
自定义字典添加
- 打开自定义模块,点击自定义字典
- 点击新增,类型可以选择现有类型,也可以输入新的类型

- 名称对应自定义字典展示的名称,数据值对应名称的值(如:男=man)

自定义字典使用
- 下拉选择框使用
在代码生成器拖一个下拉选择框控件,选择字典数据,输入对应的字典类型
可以组合使用,只需要输入对应的字典类型
效果图

自定义业务开发
这里的自定义指的是基于铭飞系统里面的自定义模块,如:自定义模型、自定义表单。直接通过代码生成器生成自定义模型代码,通过导入的方式就可以完成自定义业务的扩展。
- 以留言板为例
快速实现基础后台数据管理,也可以实现表单数据提交。例如:留言反馈。
留言板模型的拖拽
进入代码生成器,根据业务新建一个表单。

编辑表的组件与顺序

留言板的导入
开发者打开代码生成器,对于想要的组件进行拖拽

而后在后台的自定义模型中导入。对留言板的前端提交选择进行勾选。

此时可以通过表单预览,查看导入的表单是否和拖拽的一致。

如果需要更新,也可以通过更新模型按键。
实现留言板的前端提交
此时我们有了一个留言板数据模型,如果实现前台的提交,还需要有一个前台界面。
前台界面必须要绑定所提交的自定义业务名称。

模型的绑定和界面代码参考,可以参考插件手册 参考文档
留言数据的查看
前端提交的数据,可以通过数据预览去查看和管理。

菜单的管理和调整
此时开发者可以通过自定义业务中的留言模块去管理留言。
如果需要在主页面直接打开留言管理,可以通过复制菜单JSON,由菜单管理导入。

业务代码模版
列出一些常用的业务的视图代码,开发者可以直接复制使用。
权限设置
界面效果

代码模版
elementUI树形表格示例
<el-table v-loading="loading" height="calc(100vh - 148px)" border :data="modelChildList"
header-row-class-name='ms-table-head' row-class-name='ms-table-row' row-key="modelId"
default-expand-all :tree-props="{children: 'children'}">
<template slot="empty">
{{emptyText}}
</template>
<el-table-column label="模块标题" prop="modelTitle" width="300"></el-table-column>
<el-table-column label="功能权限">
<template slot-scope="scope" class="ms-row">
<div v-if= "scope.row.modelChildList.length>0">
<label class='ms-check'>
<el-checkbox-group v-model="roleIds" @change="handleCheckedIdsChange">
<el-checkbox v-for="model in scope.row.modelChildList" :label="model.modelId" :value='model.modelId' :key="model.modelId">{{model.modelTitle}}</el-checkbox>
</el-checkbox-group>
</label>
</div>
</template>
</el-table-column>
</el-table>
关键js代码
将后台返回的代码组织成树形数据,并且排除掉功能权限菜单
ms.http.get(ms.manager + "/model/modelList.do", {"roleId":id}).then(function (data) {
if (data.total > 0) {
//菜单modelIsMenu=1的为菜单数据,按钮为0
that.modelChildList = ms.util.treeData(data.rows.filter(f => f['modelIsMenu'] == 1),'modelId','modelModelId','children')
//循环数组
for(var i =0; i<data.rows.length;i++){
if(data.rows[i].modelChildList.length>0){
for(var j=0;j<data.rows[i].modelChildList.length;j++){
//判断是否选中
if(data.rows[i].modelChildList[j].chick ==1 ){
that.roleIds.push(data.rows[i].modelChildList[j].modelId);
}
}
}
}
that.emptyText='';
that.loading = false;
} else {
that.loading = false;
that.emptyText='暂无数据'
that.modelChildList =[];
}
}).catch(function (err) {
console.log(err);
});
注:具体代码详情可以在权限管理中查看
表单组件
新建业务表单
- 名称:业务表单名称,同项目内表单名称唯一
- 业务表名:对应创建的数据库表名称
其余配置留意页面配置项下方提示

基于https://element.eleme.cn/#/zh-CN(饿了么),同时也定制了一些特殊的控件,例如:地图、城市、百度编辑器等


组件属性
主要控制四个方面(页面显示控制、表字段控制、java代码控制、验证提示控制),注意:建议标题、字段名根据实际业务进行修改

基础组件
单行文本
-
标题:页面上显示的输入框名称
-
字段名:对应数据库表中的字段名
-
默认值:文本输入框的默认内容
-
操作属性
- 完全只读:只读,无法编辑
- 禁用:无法选中
- 显示清除按钮:鼠标移动到输入框,右边有按钮,点击清除所有文本输入内容
-
表单验证
- 必填:必须填写,在提交表单时会有校验
- 类型:限制文本输入的类型,类型在下方 类型下拉框中可以选择
- 长度:限制文本输入的长度,长度在下方 最大、最小长度输入框中填写
- 正则:限制文本的输入格式,正则在下方输入框中填写规则
- 不可重复:表单提交数据时唯一校验
-
占位内容:文本输入提示信息
-
宽度:组件页面宽度的占比
-
是否显示:设置组件是否显示在页面上
-
帮助信息:在组件下方设置文字,用于对组件输入内容的提示信息
-
字段长度:数据库字段长度

多行文本

密码框
- 操作属性
- 显示密码:在密码输入框右侧,点击按钮查看密码

金钱
- 默认显示2位小数

计数器
- 步长:点击“+“或”-“,一次变化的步幅

单选按钮
-
布局方式
- 行内:选项排列在一行
- 块级:选项排列在竖行
-
静态数据:直接在代码生成器中设置,按照 值 选项添加即可
-
远端数据
- 远端链接:请求选项的链接
- 值:远端链接的请求的值
- 标签:设置获取值的参数
字典数据:参考自定义开发中的新建自定义字典

多选按钮

时间选择器
- 是否为范围选择:开启后,设置时间范围且需要设置两个参数开始时间和结束时间,如 09:00:00 - 10:00:00,

日期选择器
-
显示类型
- year:年 格式YYYY
- month:年月 格式YYYY-MM
- date:年月日 格式YYYY-MM-dd
- dates:多个date类型
- datetime:年月日时分秒 格式YYYY-MM-dd HH:mm:ss
- datetimeRange:datetime范围,需要设置两个参数 开始datetime和结束datetime,如2023-01-01 09:00:00 -2023-02-02 10:00:00
- dateRange:date范围,需要设置两个参数,开始date和结束date,如2023-01-01 - 2023-02-02
-
是否获取时间戳:保存成时间戳

评分
- 允许半选:允许选中半颗星

颜色选择器
- 支持透明度选择:开启后,在颜色选择区域下方有调节透明度的滑块

下拉选择框
- 是否多选:能否多选下拉框中的选项
- 是否可搜索:在下拉框输入,可以显示匹配的选项
- 是否显示标签:开启后,直接显示 值 用于选项

多选列表搜索说明
场景说明:不同顺序选择相同值多个值时,保存的值不一致;导致搜索结果不符合预期;
eg: 选择 值2,值1进行保存,但是搜索时 选择值1,值2,搜索没有匹配结果
前提:使用源码进行开发
方案1: 在保存、更新和列表搜索时,按照既定选项顺序去处理保存和搜索,参考文章表单页中文章类型的处理

方案2:自定义列表list方法,通过xml处理搜索,参考下方截图

开关

滑动
- 显示输入框:显示输入框来设置值

高级组件
自定义

图片
- 宽度:设置图片的宽度,如100px
- 最大上传数:设置上传图片的数量

附件
- 可上传的格式:设置可上传文件的类型

编辑器

级联选择器

高德定位
备注:使用该组件请用户申请高德地图对应的秘钥。

城市选择器
- 类型:按需选择城市分级程度,如省市县、省市县镇等
备注:使用该组件请下载mstore插件中城市数据,获取最新的city表获取对应的城市数据源。

布局组件
栅格布局
- 栅格间隔:设置每个栅格放置组件的间隔
- 列配置项:总数为24,几个列配置就能放入几个组件
- 内容水平对齐:设置组件在栅格布局中水平摆放的位置
- 内容垂直布局:设置组件在栅格布局中垂直摆放的位置

模板的开发与制作
区别于mcms中生成htm的模板,代码生成器的模板更加强大,通过制作不同的模板可以生成任意类型任意内容的代码。
本文中所有模板均指代码生成器模板,和cms模板无关。
在代码生成器中一个项目中有多个表单(一个表单对应一张表),表单之中可以有多个组件,两两为一对多关系
项目图
表单图
新建模板组
模板组名称需要在0-30个字之间;描述不得超过120个字;价格如果选择为0则代表这套模板您设定为免费,任何人都可以使用你创建的这套模板,如果价格大于0那只有收费用户以及购买了这套模板的用户可以使用(商城暂未开放)
图1
Tip
请确保你创建的模板是原创的且合法合规不具有任何攻击性!
新建模板
同属于一套模板组的模板是一个整体,如果模板组被删除则从属模板也会被全部删除,在制作模板时建议时常备份避免误删
模板名称
模板名称长度需在0-30之间,名称可以是中文或者英文,同一套模板中不能存在相同的模板名称
生成根路径
在下载的代码压缩包中,以最外层目录为起点的目录层级,通常设置为项目的固定路径 如/src/main/java/net/mingsoft/cms
当然这样的写法不够灵活,我们提供了占位符的方式来自动获取项目配置的方式,如:/src/main/java/${projectPath}/${projectName},使用规则如下:
-
${projectPath} 项目包路径,如 net/mingsoft
-
${projectName} 项目模块名,如 cms
-
${beanNameUpperCase} 首字母大写的bean名称,如 PeopleLog,一般用于文件名字段
-
${beanNameLowerCase} 首字母小写单词之间用横岗隔开,如 people-log,一般用于前端模板的路径字段
注意!目前只有四种占位符可用,且只能用于 “生成根路径”、“路径”、“生成文件名” 这三个字段才有效,在模板代码中使用占位符是不会生效的!
路径
拼接在 “生成根路径” 之后的目录层级,一般可用来填写控制层的 controller或者action,也可以是 entity或dto 亦或者 biz/impl 等等,可用占位符
生成文件名
模板的生成文件名可以为空,但是不会生成文件也不会被预览看到,如果勾选了不生成文件但是想要能参与预览可以随意设置一个生成文件名;
一般填写 ${beanNameUpperCase}Action.java,文件后缀是必须的。
Tip
注意,文件名在固定项目风格后不应随意修改,否则会导致项目文件和文件内容的类名不一致编译失败的情况,请及时调整文件名或者模板代码
是否生成文件
未开启生成的模板在项目下载代码时不会生成实际文件,但是不会影响到预览功能
是否宏模板
宏模板不会生成文件也不会在预览代码中出现,它会在每个其他类型的模板渲染的时候自动注入,因此其他模板中可以使用宏模板中的自定义宏方法
自定义排序
在模板渲染时会按照模板顺序由大到小依次渲染读取(注意和展示顺序无关)
模板代码
模板代码遵循freemarker语法,如果熟悉freemarker语法可以跳过freemarker语法讲解的部分只阅读标签规则快速开发
freemarker语法快速解读
为了更好理解开发模板,这里需要对几个常用的freemarker语法进行简单讲解
Tip
注意,标签闭合需要带/,否则会渲染失败
assign标签
<#assign item = 1>
定义了一个item变量,值为:${item}
输出结果:定义了一个item变量,值为:1
assign是定义变量的标签,如上示例,item被定义后就可以在定义后的模板部分用freemarker语法进行获取了, ${} 是freemarker取值的方式 官方文档
if标签
<#assign i = 10>
<#if i gt 10>
定义的变量i大于10
<#elseif i gt 5>
定义的变量i介于5到10之间
<#else>
定义的变量i小于5
</#if>
输出结果:定义的变量i介于5到10之间
if else标签和其他语言的语法本质相同,需要搭配布尔类型,可以结合函数运算或者freemarker其他语法,如<#if i+1 gt 10></#if> 或者 <#if item?has_content></#if> 官方文档
list标签
[
<#list formItem.options.attachField as _formItem>
<#if _formItem?index gt 0>,</#if>{"name": "${_formItem.name}"}
</#list>
]
输出结果:[{“name”: “name1”},{“name”: “name2”},{“name”: “name3”}]
list标签和for循环相同,本质都是对一个集合进行遍历,list标签可以嵌套list实现嵌套循环,_formItem?index 作用是当前元素的下标 官方文档
示例
便于理解,下面展示五段模板作为示例内容
1.searchJson
<#--搜索json结构-->
[
<@widget field="getWidgetVue" key="condition"></@widget>
]
2.script
var custom_model = Vue.component("custom-model",{
el: '#custom-model',
data:function() {
return {
modelId:0,
modelName: "${project.projectModelObjects[0].pmoName}",
<@widget field="getWidgetVue" key="data"></@widget>
//表单数据
form: {
linkId:0,
<@widget field="getWidgetVue" key="form"></@widget>
},
rules:{
<@widget field="getWidgetVue" key="rule"></@widget>
},
}
},
watch:{
<@widget field="getWidgetVue" key="watch"></@widget>
},
components:{
<@widget field="getWidgetVue" key="component"></@widget>
},
computed:{
<@widget field="getWidgetVue" key="computed"></@widget>
},
methods: {
<@widget field="getWidgetVue" key="method"></@widget>
<#if table.pmoDialog>
open:function(id){
<@widget field="getWidgetVue" key="create"></@widget>
if (id) {
this.get(id);
}
<@widget field="getWidgetVue" key="get"></@widget>
this.$nextTick(function () {
this.dialogVisible = true;
})
},
</#if>
validate:function(){
var b = false
this.$refs.form.validate(function(valid){
b = valid;
});
return b;
},
save:function(callback) {
var that = this;
var url = this.formURL.save.url;
if (that.form.id > 0) {
url = this.formURL.update.url;
}
this.$refs.form.validate(function(valid) {
if (valid) {
<@widget field="getWidgetVue" key="saveValidate"></@widget>
<#--注意下面进行了数据转换-->
<@widget field="getWidgetVue" key="save"></@widget>
var form = JSON.parse(JSON.stringify(that.form));
form.modelId = that.modelId;
ms.http.post(url, form).then(function (res) {
if(callback) {
callback(res);
}
});
}
else{
callback({
result:false,msg:'请检查表单输入项'
});
}
})
},
//获取当前${table.remarks}
get:function(id) {
var that = this;
ms.http.get(this.formURL.get.url, Object.assign({"modelId":that.modelId},this.formURL.get.params)).then(function (res) {
<@widget field="getWidgetVue" key="get"></@widget>
if(res.result&&res.data){
that.form = res.data;
}
}).catch(function (err) {
console.log(err);
});
},
},
created:function() {
var that = this;
//渲染create
<@widget field="getWidgetVue" key="created"></@widget>
that.get(this.form.linkId);
}
});
Tip
数组值保存失败(如日期范围),一般都是模板中saveValidate的form指向错误; 如果需要对表单中的值做处理,需要在saveValidate中处理,避免修改双向绑定数据导致组件报错
3.html
<#assign beanName=table.beanName[0]?lower_case+table.beanName?substring(1)/>
<#-- 引入文件处理转换数据-->
<template id="custom-model">
<#assign formConfig=JSON.parse(table.attachJson)/>
<#--这里是整个form-->
<el-form ref="form" :model="form" :rules="rules" label-width="${formConfig.labelWidth}px" label-position="${formConfig.labelPosition}" size="${formConfig.size}">
<#list JSON.parse(table.json) as row>
<#--判断是否是栅格-->
<#if row.type == 'grid'>
<el-row
:gutter="${row.options.gutter}"
justify="${row.options.justify}" align="${row.options.align}">
<#-- 栅格就取列中的list进行遍历-->
<#list row.columns as column>
<el-col :span="${column.span}">
<#list column.list as formItem>
<#--这里循环渲染组件-->
<@widget field="getWidgetHtml" formItem=formItem></@widget>
</#list>
</el-col>
</#list>
</el-row>
<#else>
<#-- 不是就直接渲染,因为里面的模板都用了formItem作为参数所以需要赋值-->
<@widget field="getWidgetHtml" formItem=row></@widget>
</#if>
</#list>
</el-form>
</template>
4.field
[
<#list JSON.parse(table.json) as formItem>
<#if formItem.type !='primarykey' && (formItem.pmofType)?? && formItem.pmofAllowGenerater==1>
<@filedJson formItem=formItem index=formItem?index></@filedJson>
<#if formItem.options.attachField??>
<#list formItem.options.attachField as _formItem>
<#if _formItem?index == 0 >,</#if><@filedJson formItem=_formItem index=_formItem?index></@filedJson>
</#list>
</#if>
<#elseif formItem.type =='grid'>
<#list formItem.columns as col>
<#list col.list as __formItem>
<@filedJson formItem=__formItem index=__formItem?index></@filedJson>
</#list>
</#list>
</#if>
</#list>
]
<#macro filedJson formItem index>
<#if index gt 0 >,</#if>{
"model":"${JavaBeansUtil.getCamelCaseString(formItem.model,false)}",
"key":"${formItem.model}",
"field":"${formItem.model}",
"javaType":"${formItem.pmofJavaType}",
"jdbcType":"${formItem.pmofType}",
"name":"${formItem.name}",
"type":"${formItem.type}",
<#if formItem.type == "date">
"fmt":"${formItem.options.format}",
</#if>
<#if formItem.type == "date" && (formItem.options.type=="daterange" || formItem.options.type=="datetimerange") && formItem.options.attachField??>
"endModel":"${formItem.options.attachField[0].model?lower_case}",
</#if>
<#if formItem.type == "time" && formItem.options.isRange && formItem.options.attachField??>
"endModel":"${formItem.options.attachField[0].model?lower_case}",
</#if>
"length":"${formItem.pmofLength?default(0)?c}",
"isShow":"<#if formItem.options.table.show??>${formItem.options.table.show?c}<#else>false</#if>"
}
</#macro>
5.modelSql
<#list project.projectModelObjects as projectModelObject>
<#if projectModelObject?? && projectModelObject.pmoAllowGenerate==1>
<#--_prefix判断是否提前配置好了前缀,一般用于自定义模型 -->
<#assign _tableName= '{model}' + projectModelObject.pmoTableName?upper_case/>
-- ${projectModelObject.pmoTableName?upper_case}
CREATE TABLE `${_tableName}` (
`id` BIGINT(19) UNSIGNED NOT NULL AUTO_INCREMENT,
<#list projectModelObject.projectModelObjectFields as projectModelObjectField>
<#if !ignoreFields?seq_contains(projectModelObjectField.pmofName?lower_case) && projectModelObjectField.pmofAllowGenerater==1>
<#if projectModelObjectField.pmofType == "DATE" || projectModelObjectField.pmofType == "DATETIME" || projectModelObjectField.pmofType == "TIME" || projectModelObjectField.pmofType == "TEXT">
`${projectModelObjectField.pmofName}` ${projectModelObjectField.pmofType} DEFAULT NULL COMMENT '${projectModelObjectField.pmofDescription}',
<#elseif projectModelObjectField.pmofType == "DECIMAL">
`${projectModelObjectField.pmofName}` ${projectModelObjectField.pmofType?upper_case}(${projectModelObjectField.pmofLength?c},3) DEFAULT NULL COMMENT '${projectModelObjectField.pmofDescription}',
<#else>
`${projectModelObjectField.pmofName}` ${projectModelObjectField.pmofType?upper_case}(${projectModelObjectField.pmofLength?c}<#if projectModelObjectField.pmofType == "DOUBLE">,0</#if>) DEFAULT NULL COMMENT '${projectModelObjectField.pmofDescription}',
</#if>
</#if>
</#list>
`LINK_ID` BIGINT(20) DEFAULT NULL,
<#--判断是否是自定义表单-->
<#if projectModelObject.pmoTree>
`LEAF` INT(1) NULL DEFAULT NULL COMMENT '是否是叶子节点',
`TOP_ID` BIGINT(20) NULL DEFAULT NULL COMMENT '顶级ID',
`PARENT_ID` VARCHAR(255) CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI NULL DEFAULT NULL COMMENT '父节点',
`PARENT_IDS` VARCHAR(255) CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI NULL DEFAULT NULL COMMENT '父类型编号,多个ID逗号',
</#if>
`CREATE_DATE` DATETIME DEFAULT NULL COMMENT '创建时间',
`CREATE_BY` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
`UPDATE_DATE` DATETIME DEFAULT NULL COMMENT '修改时间',
`UPDATE_BY` VARCHAR(50) DEFAULT NULL COMMENT '修改人',
`DEL` INT(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='${projectModelObject.pmoDescription}';
ALTER TABLE `${_tableName}`
ADD UNIQUE INDEX `${_tableName}_UNIQUE_LINK_ID`(`LINK_ID`) USING BTREE COMMENT 'link_id唯一索引';
</#if>
</#list>
widget标签
widget标签用于获取一些组件的特殊代码,用法较为固定,实际开发模板过程中并不常用
如示例1的searchJson模板,widget标签用法为: <@widget field="getWidget*" * ></@widget>
其中field参数目前有五种写法:
1.getWidgetHtml,用于获取组件html代码,同样也常用于html模板中,固定写法 <@widget field="getWidgetHtml" formItem> (formItem是变量,具体根据实际模板中获取的值进行填写,所有模板都有html部分)
2.getWidgetHead,获取组件head代码,常用于html模板头部获取额外资源文件,固定写法 <@widget field="getWidgetHead"></@widget> 。(目前只有高德地图和mce编辑器两个组件有需要)
3.getWidgetOnload,获取组件预加载代码,常用于vue实例外在页面初始化时执行,固定写法 <@widget field="getWidgetOnload"></@widget> (目前只有高德地图组件有需要)
4.getWidgetCss,获取组件css代码,用于底部style代码中,固定写法 <@widget field="getWidgetCss"></@widget> (目前只有mce编辑器有需要)
5.getWidgetVue,获取组件在vue实例中特殊代码部分,常用于scrip代码中。格式 <@widget field="getWidgetVue" key="data"></@widget> (所有组件都有自己的vue对象)。
key值可以是 data、form、watch、method、created、get、rule、computed、component、save、saveValidate、condition,参考示例2。
Tip
虽然一个组件不会包含所有值,但是在编写模板的时候应尽量考虑通用性,如getWidgetCss,尽管可能不会用到mce编辑器,但模板中应使用widget标签取值确保模板通用性
可用参数
在模板中通过${}来获取项目或表单以及组件的参数值,除此之外还有一些其他的固定可用参数。
- beanName,首字母小写驼峰式的表名称 如peopleLog
- localEnable,是否国际化,布尔类型可以直接在if标签里当做条件判断
- title,表单名称(只有预览的时候有效)
- ignoreFields,排除字段,固定值 {“id”, “del”, “update_date”, “update_by”, “create_date”, “create_by”, “rand_code”}
- JSON,json工具类,可以在freemarker中通过 ${JSON.parse(str)} 的方式执行工具类方法输出,可用方法有:
parse(Object o)、toJSONString(String str) - UnicodeUtil,unicode工具类,同json工具类,可用方法参考hutool5.8.9版本cn.hutool.core.text.UnicodeUtil
- JavaBeansUtil,bean工具类,同json工具类,可用方法有:
getCamelCaseString(String str)转首字母小写驼峰式 - ConversionUtil,字符串转换工具类,同json工具类,可用方法有:
humpToLine(String str,String format)驼峰转成单词之间用format隔开、toJSONStr(String str)将字符串转换成json格式,就算不是json格式也会转义“、’并且用双引号包裹 - project,项目数据(下面详解)
- table,表单数据(下面详解)
project参数
常见问题
5.5.0以下代码生成器使用
Tip
注意 5.5.0以下的版本,如需要下载源码使用,还需将新版本swagger替换为老版本的swagger,替换可以参考:swagger替换
Tip
注意 开启正则规则进行替换,idea可以通过ctrl+shift+R快速替换如图
老版本swagger升级步骤
- 包名替换(换成v3的包)
import io.swagger.annotations.Api; 替换成 import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.annotations.ApiImplicitParam; 替换成 import io.swagger.v3.oas.annotations.Parameter;\n import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.annotations.ApiImplicitParams; 替换成 import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.annotations.ApiOperation; 替换成 import io.swagger.v3.oas.annotations.Operation;
import springfox.documentation.annotations.ApiIgnore; 替换成 import io.swagger.v3.oas.annotations.Hidden;

- @Api(tags=“…”)替换
可用规则
\@Api\(tags\s*=\s*?\s*"([^"]*)"\s*(?:,\s*"([^"]*)"\s*)*?\) 替换成 \@Tag\(name \= \"$1\"\)
\@Api\(value\s*=\s*?\s*"([^"]*)"\s*(?:,\s*"([^"]*)"\s*)*?\) 替换成 \@Tag\(name \= \"$1\"\)

- @ApiOperation(value = “…”) 替换
可用规则
\@ApiOperation\(\s*value\s*=\s*"([^"]*)"\s*\) 替换成 \@Operation\(summary \= \"$1\"\)

- @ApiImplicitParam(“….”) 及 @ApiImplicitParams(“….”) 替换
\@ApiImplicitParam\(\s*name\s*=\s*\"(.*?)\"\s*,\s*value\s*=\s*\"(.*?)\"\s*,\s*required\s*=\s*(.*?)\s*,\s*paramType\s*=\s*\"query\"\s*\)
替换成
\@Parameter\(name = \"$1\", description \= \"$2\", required \= $3, in \= ParameterIn.QUERY)
\@ApiImplicitParam\(\s*name\s*=\s*\"(.*?)\"\s*,\s*value\s*=\s*\"(.*?)\"\s*,\s*required\s*=\s*(.*?)\s*,\s*paramType\s*=\s*\"path\"\s*\)
替换成
\@Parameter\(name = \"$1\", description \= \"$2\", required \= $3, in \= ParameterIn.PATH)
\@ApiImplicitParam\(\s*name\s*=\s*\"(.*?)\"\s*,\s*value\s*=\s*\"(.*?)\"\s*,\s*required\s*=\s*(.*?)\s*,\s*paramType\s*=\s*\"form\"\s*\)
替换成
\@Parameter\(name = \"$1\", description \= \"$2\", required \= $3, style \= ParameterStyle.FORM)
\@ApiImplicitParam\(name \= \"(.*?)\"\, value \= \"(.*?)\"\, required \=(.*?)\,paramType\=\"form\"\,defaultValue\=\"(.*?)\"\)
替换成
\@Parameter\(name = \"$1\", description \= \"$2\", required \= $3, style \= ParameterStyle.FORM, schema = @Schema(defaultValue = \"$4\"\))
@ApiImplicitParams 替换成 @Parameters

- @ApiIgnore 替换
如果在类上或方法上
@ApiIgnore\n 替换成 @Hidden\n

如果作为方法参数
@ApiIgnore 替换成 @Parameter(hidden = true)

新版本swagger 换成 老版本swagger步骤
- 包名替换
将
import io.swagger.v3.oas.annotations.tags.Tag; 替换成 import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.enums.ParameterIn; 替换成 import io.swagger.annotations.ApiImplicitParam;
import io.swagger.v3.oas.annotations.Parameters; 替换成 import io.swagger.annotations.ApiImplicitParams;
import io.swagger.v3.oas.annotations.Operation; 替换成 import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Hidden; 替换成 import springfox.documentation.annotations.ApiIgnore;

- @Tag(name = “….”)替换
可用规则
@Tag\(name\s*=\s*\{?\s*"([^"]*)"\s*(?:,\s*"([^"]*)"\s*)*\}?\) 替换成 \@APi\(tags \= \"$1\"\)

- @Operation(summary = “…”) 替换
可用规则
\@Operation\(\s*summary\s*=\s*"([^"]*)"\s*\) 替换成 \@ApiOperation\(value \= \"$1\"\)

- @ApiImplicitParam(“….”) 及 @ApiImplicitParams(“….”) 替换
可用规则
@Parameters 替换成 @ApiImplicitParams
\@Parameter\(\s*name\s*=\s*\"(.*?)\"\s*,\s*description\s*=\s*\"(.*?)\"\s*,\s*required\s*=\s*(.*?)\s*,\s*in\s*=\s*ParameterIn.QUERY\s*\)
替换成
\@ApiImplicitParam\(name = \"$1\", value \= \"$2\", required \= $3, paramType \= "query")
\@Parameter\(\s*name\s*=\s*\"(.*?)\"\s*,\s*description\s*=\s*\"(.*?)\"\s*,\s*in\s*=\s*ParameterIn.QUERY\s*\)
替换成
\@ApiImplicitParam\(name = \"$1\", value \= \"$2\", paramType \= "query")

- @ApiIgnore 替换
如果在类上或方法上
@Hidden\n 替换成 @ApiIgnore\n

如果作为方法参数
@Parameter(hidden = true) 替换成 @ApiIgnore

tiny-mce编辑器使用步骤
Tip
使用前需先在Mstore下载新版本富文本编辑器插件,并导入项目。导入参考
-
先重写head-file文件,在basic下找到head-file.ftl文件

-
拷贝到mcms项目的WEB-INF/manager目录下

-
引入资源和key,挂载组件
<!--tinymce编辑器-->
<script src="${base}/static/plugins/tinymce/5.10.2/tinymce.min.js"></script>
<script src="${base}/static/plugins/tinymce-vue/5.1.2-rc/tinymce-vue.min.js"></script>
app.component("Editor",Editor);

高德地图使用步骤
Tip
高德地图需要提供第三方的key与密钥,故需要手动引入,步骤如下:
-
先重写head-file文件,在basic下找到head-file.ftl文件

-
拷贝到mcms项目的WEB-INF/manager目录下

-
引入资源和key,使用组件,高德地图key值获取
<!--高德地图-->
<script src="${base}/static/plugins/vue-amap/2.0.0/index.min.js"></script>
<link rel="stylesheet" href="${base}/static/plugins/vue-amap/2.0.0/style.min.css">
window.VueAMap.initAMapApiLoader({
key: 'ea56505722bb1717xxxxx', 填写高德地图key
securityJsCode:'070253fc3b619e83xxxxx' 密钥
});
app.use(VueAMap);

- 正常使用代码生成器托高德地图即可
老版本(5.4.3以下)使用附件、图片组件,通过{@ms:file 图片或文件/}获取不到值
修改web-inf下的macro.ftl文件,或直接在mcms的web-inf下创建macro.ftl,效果一样
<#macro ms_file jsonString>
<#if jsonString?? && jsonString != '' && jsonString != '[]'>
<@compress>
<#assign jsonObject = jsonString?eval[0]>
${jsonObject.url!'${jsonObject.path}'}
</@compress>
</#if>
</#macro>
<#macro ms_len text len>
<#if text?length lt (len+1)?number>
${text}
<#else>
${text[0..len?number]}...
</#if>
</#macro>
老版本(5.4.0以下)MCms使用新版本代码生成器方法
准备head-file
<meta charset="utf-8">
<!--网络请求框架-->
<script src="${base}/static/plugins/axios/0.18.0/axios.min.js"></script>
<script src="${base}/static/plugins/qs/6.6.0/qs.min.js"></script>
<script type="text/javascript" src="${base}/static/plugins/vue/3.4.25/vue.global.js"></script>
<script src="${base}/static/plugins/vue-i18n/8.18.2/vue-i18n.js"></script>
<!-- 图标 -->
<link rel="stylesheet" type="text/css" href="${base}/static/plugins/iconfont/1.0.0/iconfont.css" />
<script src="${base}/static/plugins/iconfont/1.0.0/iconfont.js"></script>
<!-- element-plus引入 -->
<link rel="stylesheet" type="text/css" href="${base}/static/plugins/element-plus/2.7.1/index.css" />
<script src="${base}/static/plugins/element-plus/2.7.1/index.full.js"></script>
<script src="${base}/static/plugins/element-plus/2.7.1/zh-cn.js"></script>
<link rel="stylesheet" type="text/css" href="${base}/static/plugins/element-icons/icon.css" />
<!--金额转换-->
<script src="${base}/static/plugins/accounting/0.4.1/accounting.js"></script>
<!--时间转换-->
<script src="${base}/static/plugins/moment/2.24.0/moment.min.js"></script>
<!--百度编辑器-->
<script src="${base}/static/plugins/vue-ueditor-wrap/3.0/vue-ueditor-wrap.umd.js"></script>
<!--复制-->
<script src="${base}/static/plugins/clipboard/clipboard.js"></script>
<!--通用样式-->
<link rel="stylesheet" href="${base}/static/css/app.css"/>
<!--主题-->
<link rel="stylesheet" href="${base}/static/css/theme.css">
<!--代码编辑-->
<script src="${base}/static/plugins/codemirror/5.48.4/codemirror.js"></script>
<link href="${base}/static/plugins/codemirror/5.48.4/codemirror.css" rel="stylesheet">
<script src="${base}/static/plugins/codemirror/5.48.4/mode/css/css.js"></script>
<script src="${base}/static/plugins/codemirror/5.48.4/addon/scroll/annotatescrollbar.js"></script>
<script src="${base}/static/plugins/codemirror/5.48.4/mode/xml/xml.js"></script>
<script src="${base}/static/plugins/codemirror/5.48.4/mode/javascript/javascript.js"></script>
<script src="${base}/static/plugins/codemirror-editor-vue3/2.5.8/codemirror-editor-vue3.umd.js"></script>
<!--自定义-->
<script src="${base}/static/plugins/vue3-sfc-loader/0.9.5/vue3-sfc-loader.js"></script>
<script>
//必须先设置对象,否则自定义无法渲染
window.vue3SfcLoader = window["vue3-sfc-loader"];
</script>
<!--铭飞-->
<script src="${base}/static/plugins/ms/3.0/ms.umd.js"></script>
<script src="${base}/static/plugins/ms/3.0/ms-el-form.umd.js"></script>
<link rel="stylesheet" type="text/css" href="${base}/static/plugins/ms/3.0/ms-el-form.css" />
<script src="${base}/static/mdiy/index.js"></script>
<!-- 此部分是铭飞平台MStroe的客户端(MStore不在铭飞开源产品范围),如果不需要使用MStore可以删除掉 -->
<script src="${base}/static/plugins/ms/3.0/ms-store.umd.js"></script>
<link rel="stylesheet" type="text/css" href="${base}/static/plugins/ms/3.0/ms-store.css" />
<!--高德地图-->
<script src="${base}/static/plugins/vue-amap/2.0.0/index.min.js"></script>
<link rel="stylesheet" href="${base}/static/plugins/vue-amap/2.0.0/style.min.css">
<script >
window.VueAMap.initAMapApiLoader({
key: '33307e966f7ce225e2073fcea538553e',
securityJsCode:'701ae19d1eabdd830a68129d7415e4d0'
});
ms.base = "${base}";
ms.login = "${managerPath}/login.do";
ms.manager = "${managerPath}";
ms.web = ms.base;
ms.editorConfig= {
imageScaleEnabled :true,
autoHeightEnabled: true,
autoFloatEnabled: false,
scaleEnabled: true,
compressSide:0,
maxImageSideLength:1000,
maximumWords: 2000,
initialFrameWidth: '100%',
initialFrameHeight: 400,
serverUrl: ms.base + "/static/plugins/ueditor/1.4.3.3/jsp/editor.do?jsonConfig=%7BvideoUrlPrefix:\'\',fileManagerListPath:\'\',imageMaxSize:204800000,videoMaxSize:204800000,fileMaxSize:204800000,fileUrlPrefix:\'\',imageUrlPrefix:\'\',imagePathFormat:\'/${app.id}/editor/%7Btime%7D\',filePathFormat:\'/${app.id}/editor/%7Btime%7D\',videoPathFormat:\'/${app.id}/editor/%7Btime%7D\'%7D",
UEDITOR_HOME_URL: ms.base + '/static/plugins/ueditor/1.4.3.3/'
}
</script>
<script >
/**
* 封装vue创建过程,方便初始化组件
* @param obj
* @returns vue实例
* @private
*/
function _Vue(obj) {
var app = Vue.createApp(obj);
app.config.globalProperties.ms = ms;
app.use(ElementPlus,{
locale: ElementPlusLocaleZhCn
});
app.use(MsElForm);
app.use(MsStore);
app.use(VueAMap);
app.use(VueUeditorWrap);
app.use(InstallCodemirro, { componentName: "codemirror" });
app.config.globalProperties.moment = window.moment;
app.config.globalProperties.moneyFormatter = function (row, column, cellValue, index) {
if (cellValue != undefined) {
return accounting.formatMoney(cellValue, '¥')
} else {
return ''
}
}
app.component(obj.components);
return app.mount(obj.el);
}
</script>
<style>
.ms-admin-menu .is-active {
border: 0px !important;
}
</style>
其他资源文件
// 从开源最新版本获取下面的资源,并覆盖更新到当前版本src/main/webapp/static/plugins/目录下
src/main/webapp/static/plugins/element-icons
src/main/webapp/static/plugins/element-plus
src/main/webapp/static/plugins/vue-amap
src/main/webapp/static/plugins/ms
src/main/webapp/static/plugins/vue
src/main/webapp/static/plugins/vue3-sfc-loader
src/main/webapp/static/plugins/codemirror-editor-vue3
src/main/webapp/static/plugins/vue-ueditor-wrap
下载源码使用
对于源代码的ftl文件,需要将include的路径换成上面head-file的路径

自定义模型使用
推荐更新到最新版本
后台页面国际化设置
可以通过mysql-国际化模板组实现,以下是效果图

Tip
注意 已有页面不会直接设置成国际化,可以通过国际化模板下载代码后,参考添加
代码生成器生成脚手架页面
使用mysql-vue模板组,拖拽组件后,下载代码的结构如下

- 下载后,将src文件夹覆盖到后端,views文件夹覆盖到脚手架;
- 通过代码生成器编辑菜单->初始化菜单->复制菜单,将菜单导入到系统中(权限管理->菜单管理处导入),完成脚手架路由的配置
组件设置不可重复属性
- 设置不可重复表示该组件的值唯一
- 后台新增和更新都对组件值做了唯一校验
- 前端校验需要手动添加,下面给一个示例

// form.ftl 表单页
...
data:function() {
var checkRepeat = function (rule, value, callback){
ms.http.get(ms.manager + "/norepeat/norepeatInput/verify.do",{
fieldName: "input_no_repeat",
fieldValue: value,
id: ms.util.getParameter("id"),
idName: "id"
}).then(function (res){
if (res.result) {
if (!res.data) {
callback("名称值已存在!");
} else {
callback();
}
}
})
}
return {
//表单数据
form: {
// 名称
inputA:'',
},
rules:{
// 名称
inputA: [{validator:checkRepeat,trigger: ['change']}],
}
...
下载的代码放怎么使用?
代码是基于开源项目MCms,方便用户快速二次开发,使用的引用都在开源项目中,直接放在开源项目,导入菜单数据即可。
生成的代码怎么看界面?
- 先通过项目拖动好您需要的业务表单页,并保存
- 在代码生成器项目列表中获取菜单数据,菜单数据可以初始化,开发者也可以加所需要的菜单。
- 点击复制菜单,在后台菜单管理中点击导入菜单。

导入菜单数据后图标显示不全?
确保开源项目代码是跟master分支同步,所引用的图标都在static目录中。
导入菜单的入口在哪,菜单管理中没有这个功能?
导入菜单功能是基于basic1.0.17以上版本,在项目中使用最新的依赖即可。
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>ms-basic</artifactId>
<version>1.0.17</version>
</dependency>
生成的代码城市数据没有怎么办?
使用该组件请下载mstore插件中城市数据,获取最新的city表获取对应的城市数据源。
代码生成器数据库字段名为保留关键词怎么办?
- 先试用其他字段名保存业务表单
- 下载源码后,通过批量替换一下对应的sql和xml等相关涉及的地方
通过分享插件导入的模板,自定义业务筛选功能不可用
- 在自定义业务找到这个模型,复制,业务名称,表名

- 在平台代码生成器 ,新建表单,表名去除前面的MDIY_FORM_前缀

- 在根据字段信息,托组件,注意字段名称需与原模型一致

- 表单托好后复制代码,再更新模型即可正常恢复筛选

通过分享插件导入模板,报时间戳新增异常
原因:旧分享插件导出的自定义业务数据中时间为时间戳格式,mysql不支持直接新增时间戳
旧的模板无法导入解决方式:
找到下载的模板,在/data目录下,删除数据文件DATA_MODEL_JSON_MDIY_FORM_xx

然后在根目录下将data、模板、html打包成压缩包,
Tip
注意打包的压缩包,解压后的目录结构要与下载的模板目录结构一致。

最后使用新打包的压缩包导入。
Tip
在最新的分享插件已经规范导入导出的时间,不会出现时间戳问题。
介绍
设计器可以规避一切前端无意义代码,具备高效、简便的功能。
基本功能!
设计规范
注意事项:
1.使用设计器去拖拽容器的时候,一定要记得为其添加上类名
2.类名要求见名知意,容器的名称也要调整到一眼看懂。
关于布局
每一块区域都按照层级去制作
描述
1.组件
2.图层
3.样式
4.标签
组件概念
组件可以让我们使用设计器的时候避免 “重复造轮子” ,例如制作好一个导航栏,可以将其转为组件,下次使用直接在组件区域拖拽即可。
在这分有两个知识点:
- 普通组件:组件化后相当于规避了重复造轮子,可以进行编辑图层、样式
- 高级组件:组件化后无法编辑图层、样式,只能通过该组件提供的配置去进行设置
1.普通组件
将容器转组件后,可以在红色框选里面查看到已组件化的所有容器
第一个(演示组件)就是刚刚组件化的容器,这里方便展示没有框选。
图层概念
这里的图层与PS中的图层是一个意思,在图层控制区可以对每一个图层进行编辑。
图层核心功能
从上到下依次表示为:
- 快速定位选中图层位置
- 删除选中图层
- 显示\隐藏图层
图层切换
如需进行图层之间的层级切换,只需在图层列表中上下拖拽即可
图层布局
推荐横向划分布局,将页面成一个个区域(每个区域为一个大容器),再到每个横向区域中细分其中的元素(大容器中的子容器)进行布局设置,推荐每个元素放置于容器的中间位置;这里以官网头部为例,每个红框可视为一个容器;

皮肤设计器中图层结构示例

Tip
可以双击容器,给容器重命名,并设置容器的class名称
预览代码可以看到会生成对应注释
Tip
在父容器中可以设置子元素的布局方式
相同的布局(如导航条中每个导航项),直接复制即可,无需多次拖拽

常见问题
预览效果和设计器显示效果不一致
有容器没有设置class名称,导致样式异常;规范设计,每新增容器都要规范命名;
拖拽定位容器问题
拖拽

定位

响应式样式问题
切换移动端,再设计样式即可(图标没有显示,按住shift刷新页面)

编辑样式
在图层列表中选中需编辑的图层,右侧就会自动展示出该图层的样式 直接在样式设置去对其编辑即可

标签
设计器中的标签指的就是我们mcms使用手册中的标签,使用设计器添加标签后即可无需再手动去改动模板中的标签

在图层列表中选中已有标签的图层,点击上方的 <预览>按钮,可以快速的核对标签是否使用正确
SaaS建站介绍
满足更多普通客户的需要,低成本的快速搭建网站
基本使用
创建站点
通过平台创建站点 ,支付成功后会自动生成一个临时的访问域名地址可以管理站点。

站点管理
登录平台后,再点击站点管理 ,可以看到已购买的站点列表

域名绑定
域名需要先解析才能使用。具体参考 域名解析方法
推荐推荐域名填写http://www.xxx.com/,附属域名填写http://xxx.com/

管理员密码
点击修改密码,可修改当前站点的超级管理员密码

续费
网站到期前会通过公众号的方式通知,收到通知直接再公众号完成续费操作就可以。
管理网站
基本操作与开源系统一致,可以参考 开源系统后台使用
导入模板
导入MStore 选择的模板压缩包,导入完成后 点击右上角的“发布网站“按钮进行网站发布


预览代码可以看到会生成对应注释

