JeeSite 4.0 说说前端的那些事
博客专区 > thinkgem 的博客 > 博客详情
JeeSite 4.0 说说前端的那些事
thinkgem 发表于2个月前
JeeSite 4.0 说说前端的那些事
  • 发表于 2个月前
  • 阅读 4734
  • 收藏 5
  • 点赞 13
  • 评论 34

标题:腾讯云 新注册用户域名抢购1元起>>>   

引言

一个不得不说的话题,经过近几年的发展,Web前端开发已经不是一个新有的岗位了,前端技术发展非常迅速,技术更新换代也很快,对于前端工程师来说是一个很大的挑战“挣扎期”。

从统计来看,中级前端的待遇是略高于中级后端的。这对于中小企业、创业公司来说组建一个专有的前端团队还是很一件很不容易的事情,无形中增加了人力成本, 话说招前端工程师简单,但能招聘到合适的前端工程师来说,是一件非常不容易的事情。

为了解决这个事情,中小企业、创业公司都在思考一个问题,做企业应用软件,如果不去组建专门的前端团队,能有一个很好的开发平台,很好的框架,让后端工程师具备一些基本的前端知识,就可以去做出很漂亮的界面就好了。

这个想法很好,但是你会说,可能吗,会一点基础前端就能做好吗?专业的事情还是有专业的人来做,前后端分离是趋势,表现逻辑分离意义很大。没有绝对完美的事情,尽管种种诱惑,唯心自问合适自己吗?我不否认,这还要针对产品、针对项目来选择解决方案。但JeeSite的中心思想是快速快发,快速交付,控制成本,对于一个想快速交付项目来说不见得是一件好事。

重口难调,也许你不赞同这些看法,作者欢迎提问,当然你也可以将JeeSite完全作为服务端代码,快速提供数据接口,自由实现或选型一套前端UI。

好了,既然是奔着快速交付,控制成本来的,我们就依这个角度去思考方案:

  • 第一,技术选型 Spring MVC + Beetl + jQuery + Bootstrap,优势很明显,不多说,下面章节简要说下;
  • 第二,开发一些常用函数库,如:字符串工具类,集合工具类,映射转换工具类,配置工具类,权限用户工具类等;
  • 第三,封装一些常用表单控件参考Spring MVC的form标签,实现更便捷的输入框、下拉框、单选、复选等,自动进行数据绑定;
  • 第四,封装一些常用表单组件,如:多级树结构选择组件,列表选择组件,文件上传组件,验证码生成组件等等;
  • 第五,封装一些常用JS类库,如:动态加载,对话框,消息框,加载框,JS模板,Ajax,格式化,动态Tab等待;
  • 第六,封装一个JS数据表格组件DataGrid,分页,排序,多表头,分组,子表,冻结,小计,合计,编辑行,树表表格等;
  • 第七,丰富例子,如:Box盒子、表单布局、栅格布局、图表等等

模板语言界定符选择

Beetl模板语言类似JS语言和习俗,只需要将Beetl语言放入定界符号里即可,如默认的是<% %>,那JeeSite是怎样选择的呢:

设想定义:<% %>

优点:jsp标准定界符,比较容易被理解,是后端运行的语法

缺点:html后缀的模板,不能和html标签混用,否则IDE会提示语法错误

设想定义:@ 回车符

优点:简单

缺点:多行beetl语法时,比较麻烦,并且会出现多余的很多空格空行

设想定义:[ ]、[@ ]、[@ ]

有点:可以和html混用

缺点:与js的[]冲突,必须使用\转义

**设想定义:[@ @]、[# #]、# #、@ @**

优点:基本没有标示符冲突

缺点:感官、阅读、写法稍微差点

设想定义:<!--# -->、<!--: -->

优点:使用html注释,没有标示符冲突

缺点:使用IDE高亮时,没有写代码的感觉,像是在写注释

最终选择:<% %>

经过上述分析,最终还是回归默认,使用jsp标准定界符<%%>来作为模板语言的定界符,一方同比较容易被理解,明确是后端运行的语法,另一方面冲突少,边界比较好界定

写一个通用布局的页面

<% layout('/layouts/default.html', {title: '菜单管理', libs: ['validate'], bodyClass: ''}){ %>
<div class="main-content">
	<div class="box box-main">
		<div class="box-header with-border">
			<div class="box-title">
				<i class="fa icon-book-open"></i> 菜单管理
			</div>
			<div class="box-tools pull-right">
				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
			</div>
		</div>
		<div class="box-body">
			
		</div>
		<div class="box-footer">
			
		</div>
	</div>
</div>
<% } %>

调用默认布局 /layouts/default.html,自动引入页面头部和尾部内容,通过参数设置要加载的css和js类库,参数如下:

title参数: 设置页面的标题名字

libs参数: 设置页面要引入的css和js类库,支持类库如下:

默认引入:layer、select2、WdatePicker

  • zTree:树结构控件
  • tabPage:动态页签插件
  • dataGrid:数据表格组件
  • validate:表单验证组件
  • inputmask:表单格式化工具
  • fileupload:文件上传插件
  • ueditor:富文本编辑器控件

bodyClass参数: 设置body的class属性值

定义常用函数库

常用工具类导入

以下工具类可通过@类型快速调用,如:${@Global.getConfig('key')}

  • Global:全局配置类,全局常量,读取属性文件参数值等
  • EncodeUtils:封装各种格式的编码解码工具类,HEX、Base64、HTML、URL、XSS过滤、SQL过滤等
  • ListUtils:List常用工具类,继承Apache的ListUtils,New工具、快速提取属性值、类型转换等
  • MapUtils:Map常用工具类,继承Apache的MapUtils,New工具、Map与Bean互转等
  • SetUtils:Set常用工具类,继承Apache的SetUtils,New工具等
  • IdGenerate:封装各种生成唯一性ID算法的工具类,生成LongUUID,StringUUID,Code生成等
  • ByteUtils:字节转换工具
  • DateUtils:日期工具类,继承Apache的DateUtils
  • NumberUtils:BigDecimal工具类,继承Apache的NumberUtils类
  • ObjectUtils:对象操作工具类,继承Apache的ObjectUtils类
  • StringUtils:字符串工具类,继承Apache的StringUtils类
  • TimeUtils:时间计算工具类,xx天xx时xx分xx秒,刚刚,xx秒,xx分钟,xx小时前、xx天前
  • WorkDayUtils:工作日计算工具类,计算日期直接的工作日等
  • BeanMapper:简单封装Dozer,对象数据映射
  • JaxbMapper:Jaxb实现XML与Java Object的转换
  • JsonMapper:简单封装Jackson,实现Json与Java Object的转换
  • ClassUtils:Class扫描工具类,根据接口查询类,根据继承查询类等
  • ReflectUtils:反射工具类,方便进行getter/setter方法, 访问私有变量, 调用私有方法
  • ModuleUtils:模块工具类,方便获取系统模块信息
  • UserUtils:用户工具类,方便获取进行用户及相关信息

以下是Beetl函数及扩展函数

  • date:返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期
  • print:打印一个对象 print(user.name);
  • println:打印一个对象以及回车换行符号,回车换号符号使用的是模板本身的,而不是本地系统的.如果仅仅打印一个换行符,则直接调用println() 即可
  • nvl:函数nvl,如果对象为null,则返回第二个参数,否则,返回自己 nvl(user,"不存在")
  • isEmpty:判断变量或者表达式是否为空,变量不存在,变量为null,变量是空字符串,变量是空集合,变量是空数组,此函数都将返回true
  • isNotEmpty:同上,判断对象是否不为空
  • has:变量名为参数,判断是否存在此全局变量,如 has(userList),类似于1.x版本的exist("userList"),但不需要输入引号了
  • assert:如果表达式为false,则抛出异常
  • trim:截取数字或者日期,返回字符,如trim(12.456,2)返回"12.45",trim(date,'yyyyy')返回"2017"
  • trunc:截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45.不推荐使用,因为处理float有问题,兼容原因保留了
  • decode:一个简化的if else 结构,如 decode(a,1,"a=1",2,"a=2","不知道了")},如果a是1,这decode输出"a=1",如果a是2,则输出"a==2", 如果是其他值,则输出"不知道了"
  • debug:在控制台输出debug指定的对象以及所在模板文件以及模板中的行数,如debug(1),则输出1 [在3行@/org/beetl/core/lab/hello.txt],也可以输出多个,如debug("hi",a),则输出hi,a=123,[在3行@/org/beetl/core/lab/hello.txt]
  • range:接收三个参数,初始值,结束值,还有步增(可以不需要,则默认为1),返回一个Iterator,常用于循环中,如for(var i in range(1,5)) {print(i)},将依次打印1234.
  • flush:强制io输出。
  • pageCtx:仅仅在web开发中,设置一个变量,然后可以在页面渲染过程中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,可以pageCtx("title") 获取该变量
  • cookie:返回指定的cookie对象 ,如var userCook = cookie("user"), allCookies = cookie();
  • isBlank:判断对象是否是一个空字符串,${isBlank('str')}
  • isNotBlank:判断对象是否不是一个空字符串,${isBlank('str')}
  • toJson:将对象转Json字符串,${toJson(Object)}
  • fromJson:将Json字符串转换为对象,${fromJson(Object)}
  • hasPermi:判断是否有改权限;单个权限验证:${hasPermi('sys:user:edit')};多个AND关系:${hasPermi('sys:user:view,sys:user:edit', 'and')}; 多个OR关系:${hasPermi('sys:user:view,sys:user:edit', 'or')}
  • cookie:获取cookie值,${cookie(name, isRemove)}

数据类型格式化

日期格式化:

Today is ${date,dateFormat="yyyy-MM-dd"}
Today is ${date,dateFormat}
如果date为日期类型可简写:
${date,“yyyy-MM-dd”}

数值格式化:

Salary is ${salary,numberFormat="##.##"}

基本控件封装(类似Spring MVC表单标签)

form 表单标签

生成一个form标签,支持指定model属性,类似SpringMVC的<form:form modelAttribute=""/>标签的属性,自动给表单内的控件绑定属性值
<#form:form id="inputForm" model="${user}" action="${ctx}/sys/user" method="POST" class="form-horizontal">
    表单内容
</#form:form>
支持上传文件:
<#form:form id="inputForm" model="${user}" action="${ctx}/sys/user" method="POST" enctype="multipart/form-data" class="form-horizontal">
    表单内容
</#form:form>

控件属性:

var p = {

	// 标签参数
	id: id!,					// 表单ID
	model: model!,				// 绑定Model对象,例如:${user!}
	action: action!,			// 表单请求地址
	method: method!,			// 请求方法,默认 post
	enctype: enctype!,			// 发送之前进行数据编码,上传文件时指定:multipart/form-data
	
};

input 输入框标签

自动绑定form:form上指定的model下的userName属性,类似SpringMVC的<form:input path=""/>标签的属性
<#form:input path="userName" maxlength="100" class="form-control required "/>
日期格式化:
<#form:input path="userName" maxlength="100" dataFormat="date" class="form-control required "/>
数值格式化:
<#form:input path="userName" maxlength="100" dataFormat="number" class="form-control required "/>
不自动绑定,把path改为name就可以:
<#form:input name="userName" value="${user.userName}" maxlength="100" class="form-control required "/>

控件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	type: type!'text',			// 元素的类型,默认text
	
	dataFormat: dataFormat!'',	// 数据格式化,支持如下值:
								// date: 日期;
								// datetime: 日期时间;
								// number: 数值类型,保留2位小数
	
};

select 下拉框标签

根据字典类型设置下拉数据:
<#form:select path="userType" dictType="sys_user_type" class="form-control required " />
增加一个空白选项:
<#form:select path="roleType" dictType="sys_role_type" blankOption="true" class="form-control " />
多选下拉列表:
<#form:select path="roleType" dictType="sys_role_type" multiple="true" class="form-control " />
手动设置下拉框值,类似SrpingMVC的<form:options items="" itemLabel="" itemValue=""/>标签的属性:
<#form:select path="moduleCodes" items="${moduleList}" itemLabel="moduleName" itemValue="moduleCode" class="form-control required" />

控件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	dictType: dictType!,		// 字典类型,从字典里获取,自动设置items、itemLabel、itemValue
	
	items: items![],			// 列表数据,可接受对象集合,如:List<DictData>
	itemLabel: itemLabel!'',	// 指定列表数据中的什么属性名作为option的标签名
	itemValue: itemValue!'',	// 指定列表数据中的什么属性名作为option的value值
	
	multiple: multiple!'false', // 是否为多选框
	
	blankOption: @ObjectUtils.toBoolean(blankOption!false), // 是否默认有个空白选择项目
	
};

radio 输入框标签

类似<#form:select/>标签的使用方法
<#form:radio path="menuType" dictType="sys_menu_type" class="form-control required" />

控件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	dictType: dictType!,		// 字典类型,从字典里获取,自动设置items、itemLabel、itemValue
	
	items: items!([]),			// 列表数据,可接受对象集合,如:List<DictData>
	itemLabel: itemLabel!'',	// 指定列表数据中的什么属性名作为option的标签名
	itemValue: itemValue!'',	// 指定列表数据中的什么属性名作为option的value值
	
};

checkbox 复选框标签

类似<#form:select/>标签的使用方法,后台接受moduleCodes为字符串,选择多个自动使用“,”分隔,相比SpringMVC必须是List方便的多
<#form:checkbox path="moduleCodes" items="${moduleList}" itemLabel="moduleName" itemValue="moduleCode" class="form-control required" />
生成一个复选框按钮,后台接受replaceFile为Global.YES或Global.NO值:
<#form:checkbox path="replaceFile" label="是否替换现有文件" class="form-control"/>

控件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	dictType: dictType!'',		// 字典类型,从字典里获取,自动设置items、itemLabel、itemValue
	
	items: items!([]),			// 列表数据,可接受对象集合,如:List<DictData>
	itemLabel: itemLabel!'',	// 指定列表数据中的什么属性名作为option的标签名
	itemValue: itemValue!'',	// 指定列表数据中的什么属性名作为option的value值
	
	label: label!,				// 只有一个复选按钮的情况下设置
	
};

textarea 文本域标签

<#form:textarea path="remarks" rows="3" maxlength="200" class="form-control"/>

控件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
};

hidden 隐藏域标签

<#form:hidden path="menuCode" />

控件属性:

var p = {
	
	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	type: type!'hidden',		// 元素的类型,默认hidden
	
};

表单组件封装

treeselect 树结构选择

封装layer+zTree实现树结构选择组件,使用场景如:部门选择,行政区划选择,栏目列表选择等

<#form:treeselect id="parent" title="上级菜单"
    	name="parent.id" value="${menu.parent.id!}"
    	labelName="parent.menuName" labelValue="${menu.parent.menuName!}"
    	url="${ctx}/sys/menu/treeData?excludeCode=${menu.menuCode}&sysCode=${menu.sysCode}&isShowCode=2"
    	class="" allowClear="true" canSelectRoot="true" canSelectParent="true"/>

组件属性:

var p = {
	
		// 标签参数
		id: id!,					// 元素ID
		
		name: name!,				// 隐藏域名称
		value: value!,				// 隐藏域值
		
		labelName: labelName!,		// 标签框名称
		labelValue: labelValue!,	// 标签框值
		
		class: class!'',			// 标签框的CSS类名
		placeholder: placeholder!,	// 标签框的预期值的提示信息
		dataMsgRequired: thisTag.attrs['data-msg-required'],	// 必填错误提示信息
		
		btnClass: btnClass!,		// 标签框后面的按钮CSS类名
		
		title: title!'选项',			// 对话框标题
		boxWidth: boxWidth!300, 		// 对话框宽度,默认300像素
		boxHeight: boxHeight!400,		// 对话框高度,默认400像素
		
		url: url!,					// 树结构,数据源地址 [{id, pid, name}]
		
		readonly: @ObjectUtils.toBoolean(readonly!false),		// 是否只读模式
		
		allowInput: @ObjectUtils.toBoolean(allowInput!false),	// 是否允许label框输入
		allowClear: @ObjectUtils.toBoolean(allowClear!true),	// 是否允许清空选择内容
		
		checkbox: @ObjectUtils.toBoolean(checkbox!false),	// 是否显示复选框,是否支持多选,如果设置canSelectParent=true则返回父节点数据
		showLevel: @ObjectUtils.toInteger(showLevel!(-1)),		// 默认展开层次级别(默认:如果有1个根节点,则展开一级节点,否则不展开)
		
		canSelectRoot: @ObjectUtils.toBoolean(canSelectRoot!false),		// 可以选择跟节点
		canSelectParent: @ObjectUtils.toBoolean(canSelectParent!false),	// 可以选择父级节点
		
		returnFullName: @ObjectUtils.toBoolean(returnFullName!false),	// 是否返回全路径,包含所有上级信息,以 returnFullNameSplit 参数分隔
		returnFullNameSplit: returnFullNameSplit!'/',					// 是否返回全路径,的分隔符,默认“/”
		
	};

iconselect 图标选择

<#form:iconselect path="menuIcon" class=""/>

组件属性:

var p = {
	
	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	class: class!'',			// 隐藏域和标签框的CSS类名
	
};

validcode 验证码

<#form:validcode name="validCode" isRequired="true" isRemote="true" />

组件属性:

var p = {

	id: id!name,				// 验证码输入框ID
	name: name!,				// 验证码输入框名称(必填)
	
	isRequired: @ObjectUtils.toBoolean(isRequired!true),	// 是否必填,默认必填
	dataMsgRequired: thisTag.attrs['data-msg-required'],	// 必填错误提示信息
	
	isRemote: @ObjectUtils.toBoolean(isRemote!true),		// 是否支持实时远程验证
	dataMsgRemote: thisTag.attrs['data-msg-remote'],		// 必填错误提示信息
	
	isLazy: @ObjectUtils.toBoolean(isLazy!false),			// 是否懒加载验证码图片,原noRefresh参数

};

listselect 列表选择

<#form:listselect id="userSelect" title="用户"
	url="${ctx}/sys/user/userSelect?userType=${role.userType}" allowClear="false" 
	checkbox="true" itemCode="userCode" itemName="userName"/>

组件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID
	
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 隐藏域名称
	value: value!,				// 隐藏域值
	
	labelPath: labelPath!,		// 绑定form上model中属性的值
	labelName: labelName!,		// 标签框名称
	labelValue: labelValue!,	// 标签框值
	
	class: class!'',			// 标签框的CSS类名
	placeholder: placeholder!,	// 标签框的预期值的提示信息
	dataMsgRequired: thisTag.attrs['data-msg-required'],	// 必填错误提示信息
	
	btnClass: btnClass!,		// 标签框后面的按钮CSS类名
	
	title: title!'选项',			// 对话框标题
	boxWidth: boxWidth!'$(top.window).width() - 100', 		// 对话框宽度
	boxHeight: boxHeight!'$(top.window).height() - 100',	// 对话框高度 
	
	url: url!,					// 树结构,数据源地址 [{id, pid, name}]
	
	readonly: @ObjectUtils.toBoolean(readonly!false),		// 是否只读模式
	
	allowInput: @ObjectUtils.toBoolean(allowInput!false),	// 是否允许label框输入
	allowClear: @ObjectUtils.toBoolean(allowClear!true),	// 是否允许清空选择内容
	
	checkbox: @ObjectUtils.toBoolean(checkbox!false),	// 是否显示复选框,是否支持多选,如果设置canSelectParent=true则返回父节点数据
	
	itemCode: itemCode!,	// 选择后结果集中的Code属性名,返回到隐藏域的值
	itemName: itemName!,	// 选择后结果集中的Name属性名,返回到输入框的值
	
};

fileupload 文件上传

1、文件上传:
<#form:fileupload id="upload1" bizKey="${user.id}" bizType="user_upload1"
	uploadType="all" class="required" readonly="false"/>
后台代码:FileUploadUtils.saveFileUpload(user.getId(), "user_upload1");

2、图片上传:
<#form:fileupload id="upload2" bizKey="${user.id}" bizType="user_upload2"
	uploadType="image" class="required" readonly="false"/>
后台代码:FileUploadUtils.saveFileUpload(user.getId(), "user_upload2");

3、返回路径:
<#form:fileupload id="upload3" returnPath="true"
	filePathInputId="upload3Path" fileNameInputId="upload3Name"
	uploadType="image" readonly="false" maxUploadNum="3" isMini="false"/>
<#form:input name="upload3Path" class="form-control"/>
<#form:input name="upload3Name" class="form-control"/>

组件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID
	
	bizKey: bizKey!,			// 业务表的主键值(与附件关联的业务数据)
	bizType: bizType!,			// 业务表的上传类型(全网唯一,推荐格式:实体名_上传类型,例如,文章图片:article_photo)
	
	returnPath: @ObjectUtils.toBoolean(returnPath!false), 	// 是否是返回文件路径到输入框(默认false),可将路径直接保存到某个字段里
	filePathInputId: filePathInputId!,	// 设置文件URL存放的输入框的ID,当returnPath为true的时候,返回文件URL到这个输入框
	fileNameInputId: fileNameInputId!,	// 设置文件名称存放的输入框的ID,当returnPath为true的时候,返回文件名称到这个输入框
	
	uploadType: uploadType!'',			// 上传文件类型:all、file、image、media,若不设置,则自动根据上传文件后缀获取
	class: class!'',					// 标签框的CSS类名,设置 required 加入必填验证
	readonly: @ObjectUtils.toBoolean(readonly!false),		// 是否只读模式,只读模式下为查看模式,只允许下载
	
	allowSuffixes: allowSuffixes!'', 	// 允许上传的后缀,前台的限制,不能超越file.*AllowSuffixes的设置,例如:.jpg,.png,
	maxUploadNum: @ObjectUtils.toInteger(maxUploadNum!300),		// 多文件下允许最多上传几个,默认300个,设置-1代表不限制
	
	imageMaxWidth: @ObjectUtils.toInteger(imageMaxWidth!1024),	// 图片压缩,最大宽度(uploadType为image生效),设置-1代表不做任何处理
	imageMaxHeight: @ObjectUtils.toInteger(imageMaxHeight!768),	// 图片压缩,最大宽度(uploadType为image生效),设置-1代表不做任何处理
	
	isLazy: @ObjectUtils.toBoolean(isLazy!false),				// 设置为ture需要点击上传按钮才上传文件,否则选择后就直接上传
	
	isMini: @ObjectUtils.toBoolean(isMini!false),				// 是否是精简上传窗口,无边距,无边框
	
	preview: preview!'',										// 是否显示预览按钮,接受参数:weboffice
	
};

imageclip 图片裁剪

<img id="avatarImg" class="profile-user-img img-responsive img-circle"
    src="${@user.getAvatarUrl().replaceFirst('/ctxPath', ctxPath)}">
<#form:imageclip name="imageBase64" btnText="修改头像" btnClass="btn-block"
	imageId="avatarImg" imageDefaultSrc="${ctxStatic+'/images/user1.jpg'}"
	circle="true"/>
后台代码:
// 如果设置了头像,则保存头像
if (StringUtils.isNotBlank(imageBase64)){
	if ("EMPTY".equals(imageBase64)){
		user.setAvatar(StringUtils.EMPTY);
	}else{
		String imageUrl = "avatar/"+user.getUserCode()
				+"."+FileUtils.getFileExtensionByImageBase64(imageBase64);
		String fileName = Global.getUserfilesBaseDir(imageUrl);
		FileUtils.writeToFileByImageBase64(fileName, imageBase64);
		user.setAvatar(Global.USERFILES_BASE_URL + imageUrl);
	}
}

组件属性:

var p = {

	// 标签参数
	id: id!,					// 元素ID,如果不填写,则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称,不填写
	value: value!,				// 元素值
	
	class: class!'',			// 隐藏域的CSS类名
	
	btnText: btnText!'选择图片',	// 按钮的名字
	btnClass: btnClass!'',		// 按钮的CSS类名
	
	imageId: imageId!'',					// 裁剪后base64返回到img的id
	imageDefaultSrc: imageDefaultSrc!'',	// 图片默认地址,清除后使用地址
	
	circle: circle!'false', 	// 是否圆形图片
	
};

ueditor 富文本在线编辑器

<#form:ueditor name="text" maxlength="10000" height="200" class="required"
	simpleToolbars="false" readonly="false" outline="false"/>

组件属性:

var p = {

	// 标签参数
	id: id!,				// 元素ID,如果不填写,则与name相同
	path: path!,			// 绑定form上model中属性的值
	name: name!,			// 元素名称,不填写
	value: value!,			// 元素值
	
	class: class!'',		// 标签框的CSS类名,设置 required 加入必填验证
	
	maxlength: maxlength!'',	// 编辑器最大输入字数,为空代表无限制
	height: height!'200',		// 编辑器的高度,默认200
	
	simpleToolbars: @ObjectUtils.toBoolean(simpleToolbars!false), // 是否是简单的工具条
	
	readonly: @ObjectUtils.toBoolean(readonly!false), 	// 是否只读模式
	
	outline: @ObjectUtils.toBoolean(outline!false), 	// 大纲视图
	
	options: options!'',	// UE附加选项,逗号隔开。
	
};

封装通用JavaScript方法

	
/**
 * 输出日志
 */
log(msg);

/**
 * 输出错误日志
 */
error(msg);

/**
 * URL 编码
 */
js.encodeUrl(url);

/**
 * URL 解码
 */
js.decodeUrl(url);

/**
 * 得到 IE 版本,如果是IE返回:IE版本号,否则返回:false
 * if (js.ie && js.ie <= 8){ alert('浏览器版本过低') }
 */
js.ie;

/**
 * 安全取值,复杂类型或嵌套类型时,取不到属性中的属性时不抛出异常
 * js.val(jsonObj, 'user.office.name');
 */
js.val(jsonObj, attrName);

/**
 * 返回HashCode唯一值(默认忽略大小写)
 * @param str 要获取的字符串HashCode值
 * @param caseSensitive 是否大小写敏感(默认false)
 * @usage js.hashCode(str);
 */
js.hashCode(str, caseSensitive);

/**
 * 异步加载文件,loadFile v1.0
 * js.loadFile(file文件路径, callback成功回调, error失败回调)
 * js.loadFile('js/test.js',function(){},function(data){});
 * js.loadFile(['js/test.js','css/test.css'],function(){},function(data){});
 */
js.loadFile(file, callback, error);

/**
 * 打开一个Window窗体
 */
js.windowOpen(url, name, width, height);

/**
 * 关闭当前Window窗体
 */
js.windowClose();

/**
 * 给URL地址添加参数,如果原来有参数则用&前缀,如果没有则用?前缀
 */
js.addParam(url, params);

/**
 * 获取URL地址的参数
 */
js.getParam(paramName, url);

/**
 * 查看Object的内容,手机调试用
 * @param obj
 */
js.alertObj(obj);

/**
 * 获取字典标签
 * js.getDictLabel(${@DictUtils.getDictListJson('sys_menu_type')}, val, '未知', true)
 */
js.getDictLabel(dictListJson, value, defaultValue, inCss);

/////////////////////////////////////// message dialog

/**
 * 显示加载框
 * @param message 加载框提示信息
 * @param ignoreMessageIfExists 如果已经有加载框现在,则忽略message信息的设置
 * @usage js.loading('正在保存...');
 */
js.loading(message, ignoreMessageIfExists);

/**
 * 关闭加载框
 * @param timeout 关闭延迟时间
 * @param forceClose 是否强制关闭
 * @usage js.closeLoading(1000, true);
 */
js.closeLoading(timeout, forceClose);

/**
 * 得到layer对话框对象
 * js.layer.msg();
 */
js.layer;

/**
 * 显示提示框
 * @param message 提示消息
 * @param title 提示标题
 * @param type 提示类型(success、error、warning、info)
 * @param timeout 自动关闭毫秒(默认4000毫秒)
 */
js.showMessage(message, title, type, timeout);

/**
 * 显示错误提示框
 */
js.showErrorMessage(responseText);

/**
 * 关闭提示框
 */
js.closeMessage();

/**
 * 提示对话框
 * @param message 提示消息
 * @param options 对话框选项
 * @param closed 对话框关闭回调方法
 * @usage js.alert('你好!', function(){})
 * @usage js.alert('你好!', {icon: 2}, function(){})
 */
js.alert(message, options, closed);

/**
 * 确认对话框
 * @param message 确认信息
 * @param urlOrFun 确认后的跳转的地址,或调用的方法
 * @param data 如果urlOrFun是地址,该参数是调用地址的参数信息
 * @param callback 执行ajax的回调方法,如果为空,则直接通过location=urlOrFun跳转。
 * @param dataType 返回数据类型(默认json)
 * @param async 是否异步(默认true)
 * @param loadingMessage 调用loading(loadingMessage)的提示信息。
 * @usage js.confirm('确认删除吗?', '$ctx/biz/delete?id=123', function(data){alert('删除成功')}, 'json', true, '正在删除...');
 * @usage js.confirm('确认删除吗?', '$ctx/biz/delete', {id: '123'}, function(data){alert('删除成功')}, 'json', true, '正在删除...');
 * @usage js.confirm('确认删除吗?', function(data){alert('删除成功')});
 */
js.confirm(message, urlOrFun, data, callback, dataType, async, loadingMessage);

/////////////////////////////////////// js template

/**
 * 根据js模板生成代码,使用laytpl引擎
 * @param id 模板ID
 * @param data 模板数据(可选)
 * @param callback 如果填写,则为异步渲染
 * @usage 
 * 		模板格式: <sc ript id="dempTpl" type="text/template">//<!--
 * 					 这里写模块内容...
 * 				//--></sc ript>
 * 		调用方法: js.template('dempTpl', data);
 * 		模版语法:
 * 			输出一个普通字段,不转义html:   {{ d.field }}
 * 			输出一个普通字段,并转义html:   {{= d.field }}
 * 			JavaScript脚本: {{# JavaScript statement }}
 */
js.template(id, data, callback);

/////////////////////////////////////// ajax form

/**
 * AJAX 提交
 * js.ajaxSubmit('/sys/user/save', {param: 1}, function(data){})
 */
js.ajaxSubmit(url, data, callback, dataType, async, message);

/**
 * AJAX 提交表单(支持文件上传)
 * js.ajaxSubmitForm($(form), function(data){})
 */
js.ajaxSubmitForm(formJqueryObj, callback, dataType, async, message);

/////////////////////////////////////// string

/**
 * String两边去空格
 */
js.trim(str);

/**
 * String的startWith
 */
js.startWith(str, start);

/**
 * String的endWith
 */
js.endWith(str, end);

/**
 * 截取字符串,区别汉字和英文
 */
js.abbr(name, maxLength);

/////////////////////////////////////// number

/**
 * 格式化数值
 * @param num 待格式化的树
 * @param cent 保留小数位数
 * @param isThousand 是否进行千分位格式化
 */
js.formatNumber(num, cent, isThousand);

/**
 * 金额格式化(千位符,小数四舍五入)金额每隔三位加一个逗号
 * @param s 要格式化的数值
 * @param n 小数位数
 */
js.formatMoney(s, n);

/**
 * 数值前补零
 */
js.numberPad(num, n);

/////////////////////////////////////// date

/**
 * 日期格式化
 * @param date 日期 new Date()
 * @param f 格式化字符串 yyyy-MM-dd HH:mm:ss
 */
js.formatDate(date, f);

/**
 * 字符串转为日期
 * @param date
 */
js.parseDate(date);

/**
 * 日期加减
 * @param date
 * @param dadd 天数
 */
js.addDate(date, dadd);

/**
 * 快速选择日期方法
 * @param type 1今日,2本周,3本月,4本季度,5上月
 * @param beginDateId 开始日期控件的ID
 * @param endDateId	 结束日期控件的ID
 */
js.quickSelectDate(type, beginDateId, endDateId);

/////////////////////////////////////// cookie

/**
 * cookie 操作
 * @param name Cookie名称
 * @param value Cookie值,填写表示设置,不填写表示获取
 * @parma options:{expires:7} 如果是数字,则expires单位为天。
 */
js.cookie(name, value, options);

/////////////////////////////////////// tabPage

/**
 * 得到TabPage对象
 */
js.tabPage;

/**
 * 初始化TAB页面
 * @param id
 */
js.initTabPage(id, options);

/**
 * 添加TAB页面
 * @param $this 		点击的对象
 * @param title 		提示标题
 * @param url	 		访问的路径
 * @param closeable	 	是否有关闭按钮
 * @param refresh 		打开后是否刷新重新加载
 */
js.addTabPage($this, title, url, closeable, refresh);

/**
 * 获取当前TAB页面
 * @param currentTabCallback(contents, contentWindow) 当前页面回调方法,传入当前tab页面的contents和contentWindow
 */
js.getCurrentTabPage(currentTabCallback);

/**
 * 获取当前页面的上一个TAB页面,并激活上级页面
 * @param preTabCallback(contents, contentWindow) 传入上一个tab页面的contents和contentWindow
 * @param isCloseCurrentTab 是否关闭当前页签
 */
js.getPrevTabPage(preTabCallback, isCloseCurrentTab);

/**
 * 关闭当前TAB页面,并激活上级页面
 * @param preTabCallback(contents, contentWindow) 关闭前的回调方法,传入上一个tab页面的contents和contentWindow
 */
js.closeCurrentTabPage(preTabCallback);

封装数据表格组件DataGrid

数据表格是一个必不可少的元素,在选择这个选型的时候尝试了很多开源组件,最终选择jqGrid,只是因为它接近经典思维,用着还算顺手,最主要的是遇见什么问题都可以自行解决和修复问题,有人说jqGrid不好看,这没关系这完全而已自行编写CSS改造它,下面看看一个简单的例子:

<#form:form id="searchForm" model="${config}" action="${ctx}/sys/config/listData" method="post" class="form-inline "
		data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
	参数名称:<#form:input path="configName" maxlength="100" class="form-control" />
	参数键名:<#form:input path="configKey_like" maxlength="100" class="form-control" />
	<button type="submit" class="btn btn-primary btn-sm">查询</button>
	<button type="reset" class="btn btn-default btn-sm">重置</button>
</#form:form>
<table id="dataGrid"></table>
<div id="dataGridPage"></div>
// 初始化DataGrid对象
$('#dataGrid').dataGrid({
    // 查询数据表单
    searchForm: $('#searchForm'),
	// 设置数据表格列
	columnModel: [ 
		{header:'参数名称', name:'configName', index:'a.config_name', width:200, formatter: function(val, obj, row, act){
			return '<a href="${ctx}/sys/config/form?id='+row.id+'" class="btnList" data-title="编辑参数">'+val+'</a>';
		}},
		{header:'参数键名', name:'configKey', index:'a.config_key', width:200},
		{header:'参数键值', name:'configValue', sortable:false, width:260, classes:"nowrap"},
		{header:'操作', name:'actions', width:100, sortable:false, title:false, formatter: function(val, obj, row, act){
			var actions = [];
			<% if(hasPermi('sys:config:edit')){ %>
				actions.push('<a href="${ctx}/sys/config/form?id='+row.id+'" class="btnList" title="编辑参数"><i class="fa fa-pencil"></i></a>&nbsp;');
			<% } %>
			<% if(hasPermi('sys:config:delete')){ %>
				actions.push('<a href="${ctx}/sys/config/delete?id='+row.id+'" class="btnList" title="删除参数" data-confirm="确认要删除该参数吗?"><i class="fa fa-trash-o"></i></a>&nbsp;');
			<% } %>
			return actions.join('');
		}}
	],
	// 加载成功后执行事件
	ajaxSuccess: function(data){
		
	}
});

是不是比你使用foreach方便的多,封装后名字叫dataGrid,这只是展示了冰山一角,它支持所有jqGrid参数,即简化了代码编写,又不失功能

提供丰富的演示例子

  • AdminLTE 2.4
  • Bootstrap 3.3
  • Layer 3.0
  • My97DatePicker 4.8
  • jQurey Select2 4.0
  • jQurey Validation 1.16
  • jQurey zTree API 3.5
  • jQurey zTree Demo 3.5
  • jQuery jqGrid 4.7
共有 人打赏支持
thinkgem
粉丝 562
博文 131
码字总数 14892
作品 1
评论 (34)
董永江
沙发,关注jeesite好久了啊,一直在等着4.0发布呢,thinkgem哥,预计啥时候发布呀,特别的期待哦
kami0314
文件上传使用百度的Web Uploader怎么样,支持h5上传
thinkgem

引用来自“kami0314”的评论

文件上传使用百度的Web Uploader怎么样,支持h5上传
对,用的是这个插件
rishi
4.0啥时候能出来呀?
冰舞世界
本人不建议对前端框架进行封装,能做到前后端完全分离最好,前端关系到用户体验,不同的用户体验也不一样,我们在给政府做项目时,要求页面和他们的网站界面风格一致,我们这样考虑的:一,在jeesite的基础上进行修改,把jeesite的样式风格改成他们要求的,二,前端人员重新设计。第一种方案我都没有同意,原因是:后台人员去把jeesite的样式风格修改成客户要求的,对于后台人员来说,技术不是问题,但是做出来的效果远远达不到客户的要求,而且特别浪费时间。方案2,老板没有同意,因为增加成本。最后老板让客户把他们的网站风格拿过来我们改。
冰舞世界
接着上面的说:客户发过来的前台html,我一看头就大了,页面使用的js,css等都是在IE7,8环境下的,js是基于jquery1.4的,和jeesite前台完全不兼容,把它和jeesite弄到一块,出现的样式问题,js不兼容问题,我勒个去,解决起来比我开发几个项目的时间都长。。。。 基于这种经验,我不建议对前端进行太多的封装,做到前后台完全分离最好
我的建议:可以参考rest风格,现在rest不是很流行吗,后台直接返回前台json(或者xml等),这样前台可以交个不同的开发团队去完成,前台和后台没有交集,有交集的只是数据
冰舞世界
关于技术选型(包括前台js控件),要慎重,包括前台和后台,特别是一些国产的开源技术(本人并没有贬低国产的意思),过一段时间不更新了,出现bug问题,不兼容问题不好修改。
全村人的骄傲

引用来自“kami0314”的评论

文件上传使用百度的Web Uploader怎么样,支持h5上传

引用来自“thinkgem”的评论

对,用的是这个插件
有QQ群或者微信群吗,想看看开发进度,也想参与进来。
thinkgem

引用来自“冰舞世界”的评论

接着上面的说:客户发过来的前台html,我一看头就大了,页面使用的js,css等都是在IE7,8环境下的,js是基于jquery1.4的,和jeesite前台完全不兼容,把它和jeesite弄到一块,出现的样式问题,js不兼容问题,我勒个去,解决起来比我开发几个项目的时间都长。。。。 基于这种经验,我不建议对前端进行太多的封装,做到前后台完全分离最好
我的建议:可以参考rest风格,现在rest不是很流行吗,后台直接返回前台json(或者xml等),这样前台可以交个不同的开发团队去完成,前台和后台没有交集,有交集的只是数据

做这些封装可以满足90%的情况,甚至更多,何乐而不为呢,当然如果遇见你这种情况也是可以支持的,封装不是限制你必须这样去做,这个设计思路可以完全支持,你可以去自定义主题,可以前后分离,手机端就是个很典型的例子
thinkgem

引用来自“冰舞世界”的评论

关于技术选型(包括前台js控件),要慎重,包括前台和后台,特别是一些国产的开源技术(本人并没有贬低国产的意思),过一段时间不更新了,出现bug问题,不兼容问题不好修改。

作为开源软件作者我深有体会,也可能是习惯问题,比如咱们可以拿来jeesite去改,可有几位能吧修改后的东西通用化并分享出来的?分享精神可佳,但由于个人种种原因的变故,都能影响开源软件的发展,如果能持续维护真的不容易,必须给他们点个赞,或者那个啥,也是对作者的一种鼓励
thinkgem

引用来自“nullpionter”的评论

引用来自“kami0314”的评论

文件上传使用百度的Web Uploader怎么样,支持h5上传

引用来自“thinkgem”的评论

对,用的是这个插件
有QQ群或者微信群吗,想看看开发进度,也想参与进来。

目前有Q群,在官网上可看到,微信还没有,关注我可了解最新动态
wolma

引用来自“thinkgem”的评论

引用来自“冰舞世界”的评论

关于技术选型(包括前台js控件),要慎重,包括前台和后台,特别是一些国产的开源技术(本人并没有贬低国产的意思),过一段时间不更新了,出现bug问题,不兼容问题不好修改。

作为开源软件作者我深有体会,也可能是习惯问题,比如咱们可以拿来jeesite去改,可有几位能吧修改后的东西通用化并分享出来的?分享精神可佳,但由于个人种种原因的变故,都能影响开源软件的发展,如果能持续维护真的不容易,必须给他们点个赞,或者那个啥,也是对作者的一种鼓励
开源是很难得一件事,不像国外人家不愁吃穿住行
wolma

引用来自“thinkgem”的评论

引用来自“nullpionter”的评论

引用来自“kami0314”的评论

文件上传使用百度的Web Uploader怎么样,支持h5上传

引用来自“thinkgem”的评论

对,用的是这个插件
有QQ群或者微信群吗,想看看开发进度,也想参与进来。

目前有Q群,在官网上可看到,微信还没有,关注我可了解最新动态
官网是哪个地方,没看到地址啊
thinkgem

引用来自“thinkgem”的评论

引用来自“nullpionter”的评论

引用来自“kami0314”的评论

文件上传使用百度的Web Uploader怎么样,支持h5上传

引用来自“thinkgem”的评论

对,用的是这个插件
有QQ群或者微信群吗,想看看开发进度,也想参与进来。

目前有Q群,在官网上可看到,微信还没有,关注我可了解最新动态

引用来自“wolma”的评论

官网是哪个地方,没看到地址啊
http://jeesite.com
马熊二
如何加入你的开发团队中去呢
影子里的句号
thinkgem大神,现在使用您之前发布的版本,现在有一个注册页面和发布webserver接口,注册页面的访问URL在白名单中配置了是可以不用登陆能访问到的,但是在注册页面中会有几处调用controller中的方面来取数据的,都会报"未登录,表登录后再试"想请教shiro的这个controller级的方法白名单在那里配置呀???谢谢谢谢您了
thinkgem

引用来自“影子里的句号”的评论

thinkgem大神,现在使用您之前发布的版本,现在有一个注册页面和发布webserver接口,注册页面的访问URL在白名单中配置了是可以不用登陆能访问到的,但是在注册页面中会有几处调用controller中的方面来取数据的,都会报"未登录,表登录后再试"想请教shiro的这个controller级的方法白名单在那里配置呀???谢谢谢谢您了
在spring-context-shiro.xm里可配权限过滤 http://shiro.apache.org/web.html#Web-webini
赵二伟24740
是否考虑用multipleselect 来做左右选择框呢 因为默认的select2 单个属性选择 不管是对选还是单选 单个属性无法满足正常业务需求了
kami0314
能先把前台的beetl模版自定义函数功能贴出来看一下吗?(例如截取、取值、比较格式化等)。
看你下版本使用beetl模版,我自己也尝试使用,但是总感觉好多功能都得自己封装。
thinkgem

引用来自“kami0314”的评论

能先把前台的beetl模版自定义函数功能贴出来看一下吗?(例如截取、取值、比较格式化等)。
看你下版本使用beetl模版,我自己也尝试使用,但是总感觉好多功能都得自己封装。
beetl自带常用函数,自己可再扩展一部分,实现方法ibeetl.com官方文档上有说明,有些类库为了方便调用,可以使用IMPORT_PACKAGE方式导入java静态工具类库。
×
thinkgem
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: