mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 15:35:55 +08:00
advfilter
This commit is contained in:
parent
8598ed0175
commit
3df5bd6d6a
|
@ -20,22 +20,23 @@ package com.rebuild.server.query;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.entityhub.DisplayType;
|
||||
import com.rebuild.server.entityhub.EasyMeta;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
|
||||
/**
|
||||
* 高级查询解析器
|
||||
|
@ -45,6 +46,8 @@ import cn.devezhao.persist4j.Field;
|
|||
*/
|
||||
public class AdvFilterParser {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(AdvFilterParser.class);
|
||||
|
||||
private Entity rootEntity;
|
||||
private JSONObject filterExp;
|
||||
|
||||
|
@ -72,27 +75,47 @@ public class AdvFilterParser {
|
|||
JSONObject values = filterExp.getJSONObject("values");
|
||||
String equation = StringUtils.defaultIfBlank(filterExp.getString("equation"), "OR");
|
||||
|
||||
List<String> itemsSql = new ArrayList<>();
|
||||
Map<Integer, String> indexItemSqls = new LinkedHashMap<>();
|
||||
int noIndex = 1;
|
||||
for (Object item : items) {
|
||||
String itemSql = parseItem((JSONObject) item, values);
|
||||
JSONObject jo = (JSONObject) item;
|
||||
Integer index = jo.getInteger("index");
|
||||
if (index == null) {
|
||||
index = noIndex++;
|
||||
}
|
||||
String itemSql = parseItem(jo, values);
|
||||
if (itemSql != null) {
|
||||
itemsSql.add(itemSql);
|
||||
indexItemSqls.put(index, itemSql);
|
||||
}
|
||||
}
|
||||
if (itemsSql.isEmpty()) {
|
||||
if (indexItemSqls.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("OR".equalsIgnoreCase(equation)) {
|
||||
return "( " + StringUtils.join(itemsSql, " or ") + " )";
|
||||
return "( " + StringUtils.join(indexItemSqls.values(), " or ") + " )";
|
||||
} else if ("AND".equalsIgnoreCase(equation)) {
|
||||
return "( " + StringUtils.join(itemsSql, " and ") + " )";
|
||||
return "( " + StringUtils.join(indexItemSqls.values(), " and ") + " )";
|
||||
} else {
|
||||
// 高级表达式 eg. (1 AND 2) or (3 AND 4)
|
||||
String tokens[] = equation.toLowerCase().split(" ");
|
||||
List<String> itemSqls = new ArrayList<>();
|
||||
for (int i = 0; i < tokens.length; i++) {
|
||||
String token = tokens[i];
|
||||
if (StringUtils.isBlank(token)) {
|
||||
continue;
|
||||
}
|
||||
if (NumberUtils.isDigits(token)) {
|
||||
String itemSql = StringUtils.defaultIfBlank(indexItemSqls.get(Integer.valueOf(token)), "(9=9)");
|
||||
itemSqls.add(itemSql);
|
||||
} else if (token.equals("(") || token.equals(")") || token.equals("or") || token.equals("and")) {
|
||||
itemSqls.add(token);
|
||||
} else {
|
||||
LOG.warn("Ignore equation token : " + token);
|
||||
}
|
||||
}
|
||||
return StringUtils.join(itemSqls, " ");
|
||||
}
|
||||
|
||||
// TODO 高级表达式 eg. (1 AND 2) or (3 AND 4)
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,68 +129,118 @@ public class AdvFilterParser {
|
|||
return null;
|
||||
}
|
||||
|
||||
Field metaField = rootEntity.getField(field);
|
||||
if (EasyMeta.getDisplayType(metaField) == DisplayType.PICKLIST) {
|
||||
field = '&' + field;
|
||||
}
|
||||
// Field metaField = rootEntity.getField(field);
|
||||
// if (EasyMeta.getDisplayType(metaField) == DisplayType.PICKLIST) {
|
||||
// field = '&' + field;
|
||||
// }
|
||||
|
||||
String op = convOp(item.getString("op"));
|
||||
|
||||
String value = item.getString("value");
|
||||
// 占位
|
||||
if (value.matches("\\{\\d+\\}")) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String valIndex = value.replaceAll("[\\{\\}]", "");
|
||||
Object valReady = values.get(valIndex);
|
||||
if (valReady == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// in
|
||||
if (valReady instanceof JSONArray) {
|
||||
Set<String> valArray = new HashSet<>();
|
||||
for (Object o : (JSONArray) valReady) {
|
||||
valArray.add(quote(o.toString()));
|
||||
}
|
||||
|
||||
if (valArray.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
value = "( " + StringUtils.join(valArray, ", ") + " )";
|
||||
}
|
||||
|
||||
} else {
|
||||
value = valReady.toString();
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (op.contains("like")) {
|
||||
value = '%' + value + '%';
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer(field)
|
||||
.append(' ')
|
||||
.append(op)
|
||||
.append(' ');
|
||||
// is null / is not null
|
||||
if (op.contains("null")) {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
if ("in".equals(op)) {
|
||||
String value = item.getString("value");
|
||||
if (StringUtils.isBlank(value)) {
|
||||
LOG.warn("Invalid item of advfilter : " + item.toJSONString());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 占位 {1}
|
||||
if (value.matches("\\{\\d+\\}")) {
|
||||
if (values == null) {
|
||||
LOG.warn("Invalid item of advfilter : " + item.toJSONString());
|
||||
return null;
|
||||
}
|
||||
|
||||
String valHold = value.replaceAll("[\\{\\}]", "");
|
||||
value = parseVal(values.get(valHold), op);
|
||||
} else {
|
||||
value = parseVal(value, op);
|
||||
}
|
||||
if (value == null) {
|
||||
LOG.warn("Invalid item of advfilter : " + item.toJSONString());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 区间
|
||||
boolean isBetween = op.equals("between");
|
||||
String value2 = isBetween ? parseVal(item.getString("value2"), op) : null;
|
||||
if (isBetween && value2 == null) {
|
||||
value2 = value;
|
||||
}
|
||||
|
||||
// like / not like
|
||||
if (op.contains("like")) {
|
||||
value = '%' + value + '%';
|
||||
}
|
||||
|
||||
if (op.equals("in") || op.equals("not in")) {
|
||||
sb.append(value);
|
||||
} else if (NumberUtils.isDigits(value)) {
|
||||
sb.append(value);
|
||||
} else if (StringUtils.isNotBlank(value)) {
|
||||
sb.append(quote(value));
|
||||
} else {
|
||||
sb.append(quoteVal(value));
|
||||
}
|
||||
|
||||
if (isBetween) {
|
||||
// sb.insert(0, "(").append(" and ").append(quoteVal(value2)).append(")");
|
||||
sb.append(" and ").append(quoteVal(value2));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String quote(String v) {
|
||||
return String.format("'%s'", StringEscapeUtils.escapeSql(v));
|
||||
/**
|
||||
* @param val
|
||||
* @param op
|
||||
* @return
|
||||
*/
|
||||
private String parseVal(Object val, String op) {
|
||||
String value = null;
|
||||
// IN
|
||||
if (val instanceof JSONArray) {
|
||||
Set<String> array = new HashSet<>();
|
||||
for (Object o : (JSONArray) val) {
|
||||
array.add(quoteVal(o.toString()));
|
||||
}
|
||||
|
||||
if (array.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
value = "( " + StringUtils.join(array, ",") + " )";
|
||||
}
|
||||
|
||||
} else {
|
||||
value = val.toString();
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 兼容 | 号分割
|
||||
if (op.equals("in") || op.equals("not in")) {
|
||||
Set<String> array = new HashSet<>();
|
||||
for (String v : value.split("\\|")) {
|
||||
array.add(quoteVal(v));
|
||||
}
|
||||
value = "( " + StringUtils.join(array, ",") + " )";
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param v
|
||||
* @return
|
||||
*/
|
||||
private String quoteVal(String v) {
|
||||
if (NumberUtils.isDigits(v)) {
|
||||
return v;
|
||||
} else if (StringUtils.isNotBlank(v)) {
|
||||
return String.format("'%s'", StringEscapeUtils.escapeSql(v));
|
||||
}
|
||||
return "''";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,9 +26,14 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.query.AdvFilterParser;
|
||||
import com.rebuild.web.BaseControll;
|
||||
import com.rebuild.web.LayoutConfig;
|
||||
|
||||
import cn.devezhao.commons.web.ServletUtils;
|
||||
|
||||
/**
|
||||
* 高级查询
|
||||
*
|
||||
|
@ -46,4 +51,13 @@ public class AdvFilterControll extends BaseControll implements LayoutConfig {
|
|||
@Override
|
||||
public void gets(String entity, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
}
|
||||
|
||||
@RequestMapping("test-advfilter")
|
||||
public void testAdvfilter(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
JSON advfilter = ServletUtils.getRequestJson(request);
|
||||
|
||||
AdvFilterParser filterParser = new AdvFilterParser((JSONObject) advfilter);
|
||||
String sql = filterParser.toSqlWhere();
|
||||
writeSuccess(response, sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18127,7 +18127,6 @@ form.dropzone .dz-preview .dz-error-message {
|
|||
outline: 0
|
||||
}
|
||||
.select2-container--default .select2-selection--single, .select2-container--default .select2-selection--multiple {
|
||||
height: 37px
|
||||
min-height: 37px;
|
||||
font-size: 0;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ class AdvFilter extends React.Component {
|
|||
// TODO parse exists items
|
||||
let items = []
|
||||
this.state = { ...props, items: items }
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.childrenRef = []
|
||||
}
|
||||
render() {
|
||||
|
@ -24,13 +25,13 @@ class AdvFilter extends React.Component {
|
|||
<div className="adv-filter">
|
||||
<div className="item">
|
||||
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline">
|
||||
<input className="custom-control-input" type="checkbox" onClick={()=>this.toggleAdvexp()} />
|
||||
<input className="custom-control-input" type="checkbox" onClick={()=>this.toggleEquation()} />
|
||||
<span className="custom-control-label"> 启用高级表达式</span>
|
||||
</label>
|
||||
</div>
|
||||
{this.state.enableAdvexp !== true ? null :
|
||||
{this.state.enableEquation !== true ? null :
|
||||
<div className="mb-3">
|
||||
<input className="form-control form-control-sm form-control-success" ref="adv-exp" value={this.state.advexp} onChange={()=>this.handleChange()} />
|
||||
<input className={'form-control form-control-sm' + (this.state.equationError ? ' is-invalid' : '')} value={this.state.equation || ''} onChange={this.handleChange} />
|
||||
</div>
|
||||
}
|
||||
<div className="item">
|
||||
|
@ -58,9 +59,22 @@ class AdvFilter extends React.Component {
|
|||
onRef = (child) => {
|
||||
this.childrenRef.push(child)
|
||||
}
|
||||
handleChange(event) {
|
||||
let v = event.target.value
|
||||
console.log(v)
|
||||
handleChange(e) {
|
||||
let val = e.target.value
|
||||
let that = this
|
||||
this.setState({ equation: val }, function(){
|
||||
let token = val.toLowerCase().split(' ')
|
||||
let hasError = false
|
||||
for (let i = 0; i < token.length; i++) {
|
||||
let t = $.trim(token[i])
|
||||
if (!!!t) continue
|
||||
if (!(t == '(' || t == ')' || t == 'or' || t == 'and' || (~~t > 0))) {
|
||||
hasError = true
|
||||
break
|
||||
}
|
||||
}
|
||||
that.setState({ equationError: hasError })
|
||||
})
|
||||
}
|
||||
|
||||
addItem(){
|
||||
|
@ -71,9 +85,9 @@ class AdvFilter extends React.Component {
|
|||
let id = 'item-' + $random()
|
||||
_items.push(<FilterItem index={_items.length + 1} fields={this.fields} $$$parent={this} key={id} id={id} onRef={this.onRef} />)
|
||||
|
||||
let advexp = []
|
||||
for (let i = 1; i <= _items.length; i++) advexp.push(i)
|
||||
this.setState({ items: _items, advexp: advexp.join(' OR ') })
|
||||
let equation = []
|
||||
for (let i = 1; i <= _items.length; i++) equation.push(i)
|
||||
this.setState({ items: _items, equation: equation.join(' OR ') })
|
||||
}
|
||||
removeItem(id){
|
||||
let _items = []
|
||||
|
@ -87,17 +101,17 @@ class AdvFilter extends React.Component {
|
|||
this.childrenRef = _children
|
||||
|
||||
let that = this
|
||||
let advexp = []
|
||||
for (let i = 1; i <= _items.length; i++) advexp.push(i)
|
||||
this.setState({ items: _items, advexp: advexp.join(' OR ') }, ()=>{
|
||||
let equation = []
|
||||
for (let i = 1; i <= _items.length; i++) equation.push(i)
|
||||
this.setState({ items: _items, equation: equation.join(' OR ') }, ()=>{
|
||||
that.childrenRef.forEach((child, idx)=>{
|
||||
child.setIndex(idx + 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
toggleAdvexp() {
|
||||
this.setState({ enableAdvexp: this.state.enableAdvexp !== true })
|
||||
toggleEquation() {
|
||||
this.setState({ enableEquation: this.state.enableEquation !== true })
|
||||
}
|
||||
|
||||
toFilterJson() {
|
||||
|
@ -111,8 +125,11 @@ class AdvFilter extends React.Component {
|
|||
if (hasError){ rb.notice('部分条件设置有误,请检查'); return }
|
||||
|
||||
let adv = { entity: this.props.entity, items: filters }
|
||||
if (this.state.enableAdvexp == true) adv.equation = this.state.advexp
|
||||
console.log(JSON.stringify(adv))
|
||||
if (this.state.enableEquation == true) adv.equation = this.state.equation
|
||||
|
||||
$.post(rb.baseUrl + '/app/entity/test-advfilter', JSON.stringify(adv), function(res){
|
||||
console.log(JSON.stringify(adv) + ' >> ' + res.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,6 +290,9 @@ class FilterItem extends React.Component {
|
|||
} else if (lastType == 'REFERENCE') {
|
||||
this.removeBizzSearch()
|
||||
}
|
||||
|
||||
if (this.state.value) this.valueCheck($(this.refs['filter-val']))
|
||||
if (this.state.value2 && this.refs['filter-val2']) this.valueCheck($(this.refs['filter-val2']))
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.__select2.forEach((item, index) => { item.select2('destroy') })
|
||||
|
@ -282,20 +302,23 @@ class FilterItem extends React.Component {
|
|||
this.removeBizzSearch()
|
||||
}
|
||||
|
||||
valueHandle(event) {
|
||||
valueHandle(e) {
|
||||
let that = this
|
||||
let val = event.target.value
|
||||
if (event.target.dataset.at == 2) this.setState({ value2: val })
|
||||
let val = e.target.value
|
||||
if (e.target.dataset.at == 2) this.setState({ value2: val })
|
||||
else this.setState({ value: val })
|
||||
}
|
||||
valueCheck(event){
|
||||
let el = $(event.target)
|
||||
let val = event.target.value
|
||||
// @e = el or event
|
||||
valueCheck(e){
|
||||
let el = e.target ? $(e.target) : e
|
||||
let val = e.target ? e.target.value : e.val()
|
||||
if (!!!val){
|
||||
el.addClass('is-invalid')
|
||||
} else {
|
||||
if (this.isNumberValue() && $regex.isDecimal(val) == false){
|
||||
el.addClass('is-invalid')
|
||||
} else if (this.state.type == 'DATE' && $regex.isUTCDate(val) == false) {
|
||||
el.addClass('is-invalid')
|
||||
} else {
|
||||
el.removeClass('is-invalid')
|
||||
}
|
||||
|
@ -330,8 +353,7 @@ class FilterItem extends React.Component {
|
|||
width: '100%',
|
||||
}).on('change.select2', function(e){
|
||||
let val = s2val.val()
|
||||
that.setState({ value: val.join(',') }, function(){
|
||||
})
|
||||
that.setState({ value: val.join('|') })
|
||||
})
|
||||
this.__select2_PickList = s2val
|
||||
}
|
||||
|
@ -340,49 +362,7 @@ class FilterItem extends React.Component {
|
|||
console.log('remove PickList ...')
|
||||
this.__select2_PickList.select2('destroy')
|
||||
this.__select2_PickList = null
|
||||
}
|
||||
}
|
||||
|
||||
renderDatepicker(){
|
||||
console.log('render Datepicker ...')
|
||||
let cfg = {
|
||||
componentIcon:'zmdi zmdi-calendar',
|
||||
navIcons: { rightIcon:'zmdi zmdi-chevron-right', leftIcon:'zmdi zmdi-chevron-left'},
|
||||
format: 'yyyy-mm-dd',
|
||||
minView: 2,
|
||||
startView: 'month',
|
||||
weekStart: 1,
|
||||
autoclose: true,
|
||||
language: 'zh',
|
||||
todayHighlight: true,
|
||||
showMeridian: false,
|
||||
keyboardNavigation: false,
|
||||
}
|
||||
|
||||
let that = this
|
||||
let dp1 = $(this.refs['filter-val']).datetimepicker(cfg)
|
||||
dp1.on('change.select2', function(e){
|
||||
that.setState({ value: e.target.value }, function(){
|
||||
})
|
||||
})
|
||||
this.__datepicker = [dp1]
|
||||
|
||||
if (this.refs['filter-val2']) {
|
||||
let dp2 = $(this.refs['filter-val2']).datetimepicker(cfg)
|
||||
dp2.on('change.select2', function(e){
|
||||
that.setState({ value2: e.target.value }, function(){
|
||||
})
|
||||
})
|
||||
this.__datepicker.push(dp2)
|
||||
}
|
||||
}
|
||||
removeDatepicker(){
|
||||
if (this.__datepicker) {
|
||||
console.log('remove Datepicker ...')
|
||||
this.__datepicker.forEach((item) => {
|
||||
item.datetimepicker('remove')
|
||||
})
|
||||
this.__datepicker = null
|
||||
this.setState({ value: null })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -411,7 +391,7 @@ class FilterItem extends React.Component {
|
|||
}
|
||||
}).on('change.select2', function(e){
|
||||
let val = s2val.val()
|
||||
that.setState({ value: val.join(',') })
|
||||
that.setState({ value: val.join('|') })
|
||||
})
|
||||
this.__select2_BizzSearch = s2val
|
||||
}
|
||||
|
@ -420,6 +400,52 @@ class FilterItem extends React.Component {
|
|||
console.log('remove BizzSearch ...')
|
||||
this.__select2_BizzSearch.select2('destroy')
|
||||
this.__select2_BizzSearch = null
|
||||
this.setState({ value: null })
|
||||
}
|
||||
}
|
||||
|
||||
renderDatepicker(){
|
||||
console.log('render Datepicker ...')
|
||||
let cfg = {
|
||||
componentIcon:'zmdi zmdi-calendar',
|
||||
navIcons: { rightIcon:'zmdi zmdi-chevron-right', leftIcon:'zmdi zmdi-chevron-left'},
|
||||
format: 'yyyy-mm-dd',
|
||||
minView: 2,
|
||||
startView: 'month',
|
||||
weekStart: 1,
|
||||
autoclose: true,
|
||||
language: 'zh',
|
||||
todayHighlight: true,
|
||||
showMeridian: false,
|
||||
keyboardNavigation: false,
|
||||
}
|
||||
|
||||
let that = this
|
||||
let dp1 = $(this.refs['filter-val']).datetimepicker(cfg)
|
||||
dp1.on('change.select2', function(e){
|
||||
that.setState({ value: e.target.value }, ()=>{
|
||||
that.valueCheck($(that.refs['filter-val']))
|
||||
})
|
||||
})
|
||||
this.__datepicker = [dp1]
|
||||
|
||||
if (this.refs['filter-val2']) {
|
||||
let dp2 = $(this.refs['filter-val2']).datetimepicker(cfg)
|
||||
dp2.on('change.select2', function(e){
|
||||
that.setState({ value2: e.target.value }, ()=>{
|
||||
that.valueCheck($(that.refs['filter-val2']))
|
||||
})
|
||||
})
|
||||
this.__datepicker.push(dp2)
|
||||
}
|
||||
}
|
||||
removeDatepicker(){
|
||||
if (this.__datepicker) {
|
||||
console.log('remove Datepicker ...')
|
||||
this.__datepicker.forEach((item) => {
|
||||
item.datetimepicker('remove')
|
||||
})
|
||||
this.__datepicker = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,7 @@ const $cleanMap = function(map) {
|
|||
// 常用正则
|
||||
const $regex = {
|
||||
_Date:/^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$/,
|
||||
_UTCDate:/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/, // 2010-01-01
|
||||
_Url:/^(http|https|ftp)\:\/\/[a-z0-9\-\.]+(:[0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&%\$#\=~!:])*$/i,
|
||||
_Mail:/^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i,
|
||||
_Number:/^[-+]?[0-9]+$/, // 数字
|
||||
|
@ -156,6 +157,9 @@ const $regex = {
|
|||
isDate:function(val){
|
||||
return this._Date.test(val);
|
||||
},
|
||||
isUTCDate:function(val){
|
||||
return this._UTCDate.test(val);
|
||||
},
|
||||
isUrl:function(val){
|
||||
return this._Url.test(val);
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue