更新 openhis-ui

This commit is contained in:
郭睿
2025-03-04 10:16:02 +08:00
parent 323d6ffd51
commit 98ecb5f3b0
420 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,553 @@
/**
* 字符串工具类
**/
export const StrUtil = {
/**
* 字符串是否为空白 空白的定义如下: <br>
* 1、为null <br>
* 2、为不可见字符如空格<br>
* 3、""<br>
*
* @param str 被检测的字符串
* @return boolean 是否为空
*/
isBlank: function (str) {
return str === undefined || str == null || this.trim(str) === "";
},
/**
* 字符串是否为非空白 空白的定义如下: <br>
* 1、不为null <br>
* 2、不为不可见字符如空格<br>
* 3、不为""<br>
*
* @param str 被检测的字符串
* @return boolean 是否为非空
*/
isNotBlank: function (str) {
// == 代表相同,=== 代表严格相同
return false === StrUtil.isBlank(str);
},
/**
* 字符串是否为空,空的定义如下:<br>
* 1、为null <br>
* 2、为""<br>
*
* @param str 被检测的字符串
* @return boolean 是否为空
*/
isEmpty: function (str) {
return str == null || str === "";
},
/**
* 字符串是否为非空白 空白的定义如下: <br>
* 1、不为null <br>
* 2、不为""<br>
*
* @param str 被检测的字符串
* @return boolean 是否为非空
*/
isNotEmpty: function (str) {
return !StrUtil.isEmpty(str);
},
/**
* 空对象转字符串
*
* @param str 被检查的字符串
* @return string 原字符串或者空串
*/
nullToStr: function (str) {
if (StrUtil.isEmpty(str)) {
return "";
}
return str;
},
/**
* 空格截取
*
* @param str 截取的字符串
* @return string
*/
trim: function (str) {
if (str == null) {
return "";
}
return str.toString().replace(/(^\s*)|(\s*$)|\r|\n/g, "");
},
/**
* 比较两个字符串(大小写敏感)
*
* @param str 字符串
* @param that 比较的字符串
* @return boolean
*/
equals: function (str, that) {
return str === that;
},
/**
* 比较两个字符串(大小写不敏感)
*
* @param str 字符串
* @param that 比较的字符串
* @return boolean
*/
equalsIgnoreCase: function (str, that) {
return String(str).toUpperCase() === String(that).toUpperCase();
},
/**
* 将字符串按指定字符分割
*
* @param str 字符串
* @param sep 比较的字符串
* @param maxLen 最大长度
* @return string[] 分割后的数组
*/
split: function (str, sep, maxLen) {
if (StrUtil.isEmpty(str)) {
return null;
}
const value = String(str).split(sep);
return maxLen ? value.slice(0, maxLen - 1) : value;
},
/**
* 字符串格式化(%s )
*
* @param str 字符串
* @return 格式化后的字符串
*/
sprintf: function (str) {
let args = arguments, flag = true, i = 1;
str = str.replace(/%s/g, function () {
const arg = args[i++];
if (typeof arg === 'undefined') {
flag = false;
return '';
}
return arg;
});
return flag ? str : '';
},
/**
* 判断字符串是否是以start开头
*
* @param str 字符串
* @param start 开始的字符串
* @return boolean
*/
startWith: function (str, start) {
const reg = new RegExp("^" + start);
return reg.test(str);
},
/**
* 判断字符串是否是以end结尾
*
* @param str 字符串
* @param end 结尾的字符串
* @return boolean
*/
endWith: function (str, end) {
const reg = new RegExp(end + "$");
return reg.test(str);
},
containsWhitespace: function (input) {
return this.contains(input, ' ');
},
//生成指定个数的字符
repeat: function (ch, repeatTimes) {
let result = "";
for (let i = 0; i < repeatTimes; i++) {
result += ch;
}
return result;
},
deleteWhitespace: function (input) {
return input.replace(/\s+/g, '');
},
rightPad: function (input, size, padStr) {
return input + this.repeat(padStr, size);
},
leftPad: function (input, size, padStr) {
return this.repeat(padStr, size) + input;
},
//首小写字母转大写
capitalize: function (input) {
let strLen = 0;
if (input == null || (strLen = input.length) === 0) {
return input;
}
return input.replace(/^[a-z]/, function (matchStr) {
return matchStr.toLocaleUpperCase();
});
},
//首大写字母转小写
uncapitalize: function (input) {
let strLen = 0;
if (input == null || (strLen = input.length) === 0) {
return input;
}
return input.replace(/^[A-Z]/, function (matchStr) {
return matchStr.toLocaleLowerCase();
});
},
//大写转小写,小写转大写
swapCase: function (input) {
return input.replace(/[a-z]/ig, function (matchStr) {
if (matchStr >= 'A' && matchStr <= 'Z') {
return matchStr.toLocaleLowerCase();
} else if (matchStr >= 'a' && matchStr <= 'z') {
return matchStr.toLocaleUpperCase();
}
});
},
//统计含有的子字符串的个数
countMatches: function (input, sub) {
if (this.isEmpty(input) || this.isEmpty(sub)) {
return 0;
}
let count = 0;
let index = 0;
while ((index = input.indexOf(sub, index)) !== -1) {
index += sub.length;
count++;
}
return count;
},
//只包含字母
isAlpha: function (input) {
return /^[a-z]+$/i.test(input);
},
//只包含字母、空格
isAlphaSpace: function (input) {
return /^[a-z\s]*$/i.test(input);
},
//只包含字母、数字
isAlphanumeric: function (input) {
return /^[a-z0-9]+$/i.test(input);
},
//只包含字母、数字和空格
isAlphanumericSpace: function (input) {
return /^[a-z0-9\s]*$/i.test(input);
},
//数字
isNumeric: function (input) {
return /^(?:[1-9]\d*|0)(?:\.\d+)?$/.test(input);
},
//小数
isDecimal: function (input) {
return /^[-+]?(?:0|[1-9]\d*)\.\d+$/.test(input);
},
//负小数
isNegativeDecimal: function (input) {
return /^\-?(?:0|[1-9]\d*)\.\d+$/.test(input);
},
//正小数
isPositiveDecimal: function (input) {
return /^\+?(?:0|[1-9]\d*)\.\d+$/.test(input);
},
//整数
isInteger: function (input) {
return /^[-+]?(?:0|[1-9]\d*)$/.test(input);
},
//正整数
isPositiveInteger: function (input) {
return /^\+?(?:0|[1-9]\d*)$/.test(input);
},
//负整数
isNegativeInteger: function (input) {
return /^\-?(?:0|[1-9]\d*)$/.test(input);
},
//只包含数字和空格
isNumericSpace: function (input) {
return /^[\d\s]*$/.test(input);
},
isWhitespace: function (input) {
return /^\s*$/.test(input);
},
isAllLowerCase: function (input) {
return /^[a-z]+$/.test(input);
},
isAllUpperCase: function (input) {
return /^[A-Z]+$/.test(input);
},
defaultString: function (input, defaultStr) {
return input == null ? defaultStr : input;
},
defaultIfBlank: function (input, defaultStr) {
return this.isBlank(input) ? defaultStr : input;
},
defaultIfEmpty: function (input, defaultStr) {
return this.isEmpty(input) ? defaultStr : input;
},
//字符串反转
reverse: function (input) {
if (this.isBlank(input)) {
input;
}
return input.split("").reverse().join("");
},
//删掉特殊字符(英文状态下)
removeSpecialCharacter: function (input) {
return input.replace(/[!-/:-@\[-`{-~]/g, "");
},
//只包含特殊字符、数字和字母(不包括空格,若想包括空格,改为[ -~]
isSpecialCharacterAlphanumeric: function (input) {
return /^[!-~]+$/.test(input);
},
/**
* 校验时排除某些字符串,即不能包含某些字符串
* @param {Object} conditions:里面有多个属性,如下:
*
* @param {String} matcherFlag 匹配标识
* 0:数字1字母2小写字母3:大写字母4特殊字符,指英文状态下的标点符号及括号等5:中文;
* 6:数字和字母7数字和小写字母8数字和大写字母9数字、字母和特殊字符10数字和中文
* 11小写字母和特殊字符12大写字母和特殊字符13字母和特殊字符14小写字母和中文15大写字母和中文
* 16字母和中文17特殊字符、和中文18特殊字符、字母和中文19特殊字符、小写字母和中文20特殊字符、大写字母和中文
* 100所有字符;
* @param {Array} excludeStrArr 排除的字符串,数组格式
* @param {String} length 长度可为空。1,2表示长度1到2之间10表示10个以上字符5表示长度为5
* @param {Boolean} ignoreCase 是否忽略大小写
* conditions={matcherFlag:"0",excludeStrArr:[],length:"",ignoreCase:true}
*/
isPatternMustExcludeSomeStr: function (input, conditions) {
//参数
const matcherFlag = conditions.matcherFlag;
const excludeStrArr = conditions.excludeStrArr;
const length = conditions.length;
const ignoreCase = conditions.ignoreCase;
//拼正则
const size = excludeStrArr.length;
let regex = (size === 0) ? "^" : "^(?!.*(?:{0}))";
let subPattern = "";
for (let i = 0; i < size; i++) {
excludeStrArr[i] = Bee.StringUtils.escapeMetacharacterOfStr(excludeStrArr[i]);
subPattern += excludeStrArr[i];
if (i !== size - 1) {
subPattern += "|";
}
}
regex = this.format(regex, [subPattern]);
switch (matcherFlag) {
case '0':
regex += "\\d";
break;
case '1':
regex += "[a-zA-Z]";
break;
case '2':
regex += "[a-z]";
break;
case '3':
regex += "[A-Z]";
break;
case '4':
regex += "[!-/:-@\[-`{-~]";
break;
case '5':
regex += "[\u4E00-\u9FA5]";
break;
case '6':
regex += "[a-zA-Z0-9]";
break;
case '7':
regex += "[a-z0-9]";
break;
case '8':
regex += "[A-Z0-9]";
break;
case '9':
regex += "[!-~]";
break;
case '10':
regex += "[0-9\u4E00-\u9FA5]";
break;
case '11':
regex += "[a-z!-/:-@\[-`{-~]";
break;
case '12':
regex += "[A-Z!-/:-@\[-`{-~]";
break;
case '13':
regex += "[a-zA-Z!-/:-@\[-`{-~]";
break;
case '14':
regex += "[a-z\u4E00-\u9FA5]";
break;
case '15':
regex += "[A-Z\u4E00-\u9FA5]";
break;
case '16':
regex += "[a-zA-Z\u4E00-\u9FA5]";
break;
case '17':
regex += "[\u4E00-\u9FA5!-/:-@\[-`{-~]";
break;
case '18':
regex += "[\u4E00-\u9FA5!-~]";
break;
case '19':
regex += "[a-z\u4E00-\u9FA5!-/:-@\[-`{-~]";
break;
case '20':
regex += "[A-Z\u4E00-\u9FA5!-/:-@\[-`{-~]";
break;
case '100':
regex += "[\s\S]";
break;
default:
alert(matcherFlag + ":This type is not supported!");
}
regex += this.isNotBlank(length) ? "{" + length + "}" : "+";
regex += "$";
const pattern = new RegExp(regex, ignoreCase ? "i" : "");
return pattern.test(input);
},
/**
* @param {String} message
* @param {Array} arr
* 消息格式化
*/
format: function (message, arr) {
return message.replace(/{(\d+)}/g, function (matchStr, group1) {
return arr[group1];
});
},
/**
* 把连续出现多次的字母字符串进行压缩。如输入:aaabbbbcccccd 输出:3a4b5cd
* @param {String} input
* @param {Boolean} ignoreCase : true or false
*/
compressRepeatedStr: function (input, ignoreCase) {
const pattern = new RegExp("([a-z])\\1+", ignoreCase ? "ig" : "g");
return input.replace(pattern, function (matchStr, group1) {
return matchStr.length + group1;
});
},
/**
* 校验必须同时包含某些字符串
* @param {String} input
* @param {Object} conditions:里面有多个属性,如下:
*
* @param {String} matcherFlag 匹配标识
* 0:数字1字母2小写字母3:大写字母4特殊字符,指英文状态下的标点符号及括号等5:中文;
* 6:数字和字母7数字和小写字母8数字和大写字母9数字、字母和特殊字符10数字和中文
* 11小写字母和特殊字符12大写字母和特殊字符13字母和特殊字符14小写字母和中文15大写字母和中文
* 16字母和中文17特殊字符、和中文18特殊字符、字母和中文19特殊字符、小写字母和中文20特殊字符、大写字母和中文
* 100所有字符;
* @param {Array} excludeStrArr 排除的字符串,数组格式
* @param {String} length 长度可为空。1,2表示长度1到2之间10表示10个以上字符5表示长度为5
* @param {Boolean} ignoreCase 是否忽略大小写
* conditions={matcherFlag:"0",containStrArr:[],length:"",ignoreCase:true}
*
*/
isPatternMustContainSomeStr: function (input, conditions) {
//参数
const matcherFlag = conditions.matcherFlag;
const containStrArr = conditions.containStrArr;
const length = conditions.length;
const ignoreCase = conditions.ignoreCase;
//创建正则
const size = containStrArr.length;
let regex = "^";
let subPattern = "";
for (let i = 0; i < size; i++) {
containStrArr[i] = Bee.StringUtils.escapeMetacharacterOfStr(containStrArr[i]);
subPattern += "(?=.*" + containStrArr[i] + ")";
}
regex += subPattern;
switch (matcherFlag) {
case '0':
regex += "\\d";
break;
case '1':
regex += "[a-zA-Z]";
break;
case '2':
regex += "[a-z]";
break;
case '3':
regex += "[A-Z]";
break;
case '4':
regex += "[!-/:-@\[-`{-~]";
break;
case '5':
regex += "[\u4E00-\u9FA5]";
break;
case '6':
regex += "[a-zA-Z0-9]";
break;
case '7':
regex += "[a-z0-9]";
break;
case '8':
regex += "[A-Z0-9]";
break;
case '9':
regex += "[!-~]";
break;
case '10':
regex += "[0-9\u4E00-\u9FA5]";
break;
case '11':
regex += "[a-z!-/:-@\[-`{-~]";
break;
case '12':
regex += "[A-Z!-/:-@\[-`{-~]";
break;
case '13':
regex += "[a-zA-Z!-/:-@\[-`{-~]";
break;
case '14':
regex += "[a-z\u4E00-\u9FA5]";
break;
case '15':
regex += "[A-Z\u4E00-\u9FA5]";
break;
case '16':
regex += "[a-zA-Z\u4E00-\u9FA5]";
break;
case '17':
regex += "[\u4E00-\u9FA5!-/:-@\[-`{-~]";
break;
case '18':
regex += "[\u4E00-\u9FA5!-~]";
break;
case '19':
regex += "[a-z\u4E00-\u9FA5!-/:-@\[-`{-~]";
break;
case '20':
regex += "[A-Z\u4E00-\u9FA5!-/:-@\[-`{-~]";
break;
case '100':
regex += "[\s\S]";
break;
default:
alert(matcherFlag + ":This type is not supported!");
}
regex += this.isNotBlank(length) ? "{" + length + "}" : "+";
regex += "$";
const pattern = new RegExp(regex, ignoreCase ? "i" : "");
return pattern.test(input);
},
//中文校验
isChinese: function (input) {
return /^[\u4E00-\u9FA5]+$/.test(input);
},
//去掉中文字符
removeChinese: function (input) {
return input.replace(/[\u4E00-\u9FA5]+/gm, "");
},
//转义元字符
escapeMetacharacter: function (input) {
const metacharacter = "^$()*+.[]|\\-?{}|";
if (metacharacter.indexOf(input) >= 0) {
input = "\\" + input;
}
return input;
},
//转义字符串中的元字符
escapeMetacharacterOfStr: function (input) {
return input.replace(/[\^\$\*\+\.\|\\\-\?\{\}\|]/gm, "\\$&");
}
};

View File

@@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

View File

@@ -0,0 +1,58 @@
const DRAWING_ITEMS = 'drawingItems'
const DRAWING_ITEMS_VERSION = '1.2'
const DRAWING_ITEMS_VERSION_KEY = 'DRAWING_ITEMS_VERSION'
const DRAWING_ID = 'idGlobal'
const TREE_NODE_ID = 'treeNodeId'
const FORM_CONF = 'formConf'
export function getDrawingList() {
// 加入缓存版本的概念,保证缓存数据与程序匹配
const version = localStorage.getItem(DRAWING_ITEMS_VERSION_KEY)
if (version !== DRAWING_ITEMS_VERSION) {
localStorage.setItem(DRAWING_ITEMS_VERSION_KEY, DRAWING_ITEMS_VERSION)
saveDrawingList([])
return null
}
const str = localStorage.getItem(DRAWING_ITEMS)
if (str) return JSON.parse(str)
return null
}
export function saveDrawingList(list) {
if (JSON.stringify(list)){
localStorage.setItem(DRAWING_ITEMS, JSON.stringify(list))
}else {
localStorage.setItem(DRAWING_ITEMS, '[]')
}
}
export function getIdGlobal() {
const str = localStorage.getItem(DRAWING_ID)
if (str) return parseInt(str, 10)
return 100
}
export function saveIdGlobal(id) {
localStorage.setItem(DRAWING_ID, `${id}`)
}
export function getTreeNodeId() {
const str = localStorage.getItem(TREE_NODE_ID)
if (str) return parseInt(str, 10)
return 100
}
export function saveTreeNodeId(id) {
localStorage.setItem(TREE_NODE_ID, `${id}`)
}
export function getFormConf() {
const str = localStorage.getItem(FORM_CONF)
if (str) return JSON.parse(str)
return null
}
export function saveFormConf(obj) {
localStorage.setItem(FORM_CONF, JSON.stringify(obj))
}

View File

@@ -0,0 +1,82 @@
import Vue from 'vue'
import { mergeRecursive } from "@/utils/ruoyi";
import DictMeta from './DictMeta'
import DictData from './DictData'
const DEFAULT_DICT_OPTIONS = {
types: [],
}
/**
* @classdesc 字典
* @property {Object} label 标签对象,内部属性名为字典类型名称
* @property {Object} dict 字段数组,内部属性名为字典类型名称
* @property {Array.<DictMeta>} _dictMetas 字典元数据数组
*/
export default class Dict {
constructor() {
this.owner = null
this.label = {}
this.type = {}
}
init(options) {
if (options instanceof Array) {
options = { types: options }
}
const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
if (opts.types === undefined) {
throw new Error('need dict types')
}
const ps = []
this._dictMetas = opts.types.map(t => DictMeta.parse(t))
this._dictMetas.forEach(dictMeta => {
const type = dictMeta.type
Vue.set(this.label, type, {})
Vue.set(this.type, type, [])
if (dictMeta.lazy) {
return
}
ps.push(loadDict(this, dictMeta))
})
return Promise.all(ps)
}
/**
* 重新加载字典
* @param {String} type 字典类型
*/
reloadDict(type) {
const dictMeta = this._dictMetas.find(e => e.type === type)
if (dictMeta === undefined) {
return Promise.reject(`the dict meta of ${type} was not found`)
}
return loadDict(this, dictMeta)
}
}
/**
* 加载字典
* @param {Dict} dict 字典
* @param {DictMeta} dictMeta 字典元数据
* @returns {Promise}
*/
function loadDict(dict, dictMeta) {
return dictMeta.request(dictMeta)
.then(response => {
const type = dictMeta.type
let dicts = dictMeta.responseConverter(response, dictMeta)
if (!(dicts instanceof Array)) {
console.error('the return of responseConverter must be Array.<DictData>')
dicts = []
} else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
console.error('the type of elements in dicts must be DictData')
dicts = []
}
dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
dicts.forEach(d => {
Vue.set(dict.label[type], d.value, d.label)
})
return dicts
})
}

View File

@@ -0,0 +1,17 @@
import DictOptions from './DictOptions'
import DictData from './DictData'
export default function(dict, dictMeta) {
const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS)
const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS)
return new DictData(dict[label], dict[value], dict)
}
/**
* 确定字典字段
* @param {DictData} dict
* @param {...String} fields
*/
function determineDictField(dict, ...fields) {
return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f))
}

View File

@@ -0,0 +1,13 @@
/**
* @classdesc 字典数据
* @property {String} label 标签
* @property {*} value 标签
* @property {Object} raw 原始数据
*/
export default class DictData {
constructor(label, value, raw) {
this.label = label
this.value = value
this.raw = raw
}
}

View File

@@ -0,0 +1,38 @@
import { mergeRecursive } from "@/utils/ruoyi";
import DictOptions from './DictOptions'
/**
* @classdesc 字典元数据
* @property {String} type 类型
* @property {Function} request 请求
* @property {String} label 标签字段
* @property {String} value 值字段
*/
export default class DictMeta {
constructor(options) {
this.type = options.type
this.request = options.request
this.responseConverter = options.responseConverter
this.labelField = options.labelField
this.valueField = options.valueField
this.lazy = options.lazy === true
}
}
/**
* 解析字典元数据
* @param {Object} options
* @returns {DictMeta}
*/
DictMeta.parse= function(options) {
let opts = null
if (typeof options === 'string') {
opts = DictOptions.metas[options] || {}
opts.type = options
} else if (typeof options === 'object') {
opts = options
}
opts = mergeRecursive(DictOptions.metas['*'], opts)
return new DictMeta(opts)
}

View File

@@ -0,0 +1,51 @@
import { mergeRecursive } from "@/utils/ruoyi";
import dictConverter from './DictConverter'
export const options = {
metas: {
'*': {
/**
* 字典请求方法签名为function(dictMeta: DictMeta): Promise
*/
request: (dictMeta) => {
console.log(`load dict ${dictMeta.type}`)
return Promise.resolve([])
},
/**
* 字典响应数据转换器方法签名为function(response: Object, dictMeta: DictMeta): DictData
*/
responseConverter,
labelField: 'label',
valueField: 'value',
},
},
/**
* 默认标签字段
*/
DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],
/**
* 默认值字段
*/
DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],
}
/**
* 映射字典
* @param {Object} response 字典数据
* @param {DictMeta} dictMeta 字典元数据
* @returns {DictData}
*/
function responseConverter(response, dictMeta) {
const dicts = response.content instanceof Array ? response.content : response
if (dicts === undefined) {
console.warn(`no dict data of "${dictMeta.type}" found in the response`)
return []
}
return dicts.map(d => dictConverter(d, dictMeta))
}
export function mergeOptions(src) {
mergeRecursive(options, src)
}
export default options

View File

@@ -0,0 +1,33 @@
import Dict from './Dict'
import { mergeOptions } from './DictOptions'
export default function(Vue, options) {
mergeOptions(options)
Vue.mixin({
data() {
if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {
return {}
}
const dict = new Dict()
dict.owner = this
return {
dict
}
},
created() {
if (!(this.dict instanceof Dict)) {
return
}
options.onCreated && options.onCreated(this.dict)
this.dict.init(this.$options.dicts).then(() => {
options.onReady && options.onReady(this.dict)
this.$nextTick(() => {
this.$emit('dictReady', this.dict)
if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {
this.$options.methods.onDictReady.call(this, this.dict)
}
})
})
},
})
}

View File

@@ -0,0 +1,6 @@
export default {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}

View File

@@ -0,0 +1,629 @@
// 表单属性【右面板】
export const formConf = {
formRef: 'elForm',
formModel: 'formData',
size: 'medium',
labelPosition: 'right',
labelWidth: 100,
formRules: 'rules',
gutter: 15,
disabled: false,
span: 24,
formBtns: true
}
// 输入型组件 【左面板】
export const inputComponents = [
{
// 组件的自定义配置
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
// 正则校验规则
regList: []
},
// 组件的插槽属性
__slot__: {
prepend: '',
append: ''
},
// 其余的为可直接写在组件标签上的属性
placeholder: '请输入',
style: {width: '100%'},
clearable: true,
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false
},
{
__config__: {
label: '多行文本',
labelWidth: null,
showLabel: true,
tag: 'el-input',
tagIcon: 'textarea',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
type: 'textarea',
placeholder: '请输入',
autosize: {
minRows: 4,
maxRows: 4
},
style: {width: '100%'},
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false
},
{
__config__: {
label: '密码',
showLabel: true,
labelWidth: null,
changeTag: true,
tag: 'el-input',
tagIcon: 'password',
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
required: true,
regList: [],
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
__slot__: {
prepend: '',
append: ''
},
placeholder: '请输入',
'show-password': true,
style: {width: '100%'},
clearable: true,
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false
},
{
__config__: {
label: '计数器',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-input-number',
tagIcon: 'number',
defaultValue: undefined,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
document: 'https://element.eleme.cn/#/zh-CN/component/input-number'
},
placeholder: '',
min: undefined,
max: undefined,
step: 1,
'step-strictly': false,
precision: undefined,
'controls-position': '',
disabled: false
},
{
__config__: {
label: '编辑器',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'tinymce',
tagIcon: 'rich-text',
defaultValue: null,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
document: 'http://tinymce.ax-z.cn'
},
placeholder: '请输入',
height: 300, // 编辑器高度
branding: false // 隐藏右下角品牌烙印
}
]
// 选择型组件 【左面板】
export const selectComponents = [
{
__config__: {
label: '下拉选择',
showLabel: true,
labelWidth: null,
tag: 'el-select',
tagIcon: 'select',
layout: 'colFormItem',
span: 24,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/select'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}]
},
placeholder: '请选择',
style: {width: '100%'},
clearable: true,
disabled: false,
filterable: false,
multiple: false
},
{
__config__: {
label: '级联选择',
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/cascaderList',
method: 'get',
dataPath: 'list',
dataConsumer: 'options',
showLabel: true,
labelWidth: null,
tag: 'el-cascader',
tagIcon: 'cascader',
layout: 'colFormItem',
defaultValue: [],
dataType: 'dynamic',
span: 24,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/cascader'
},
options: [{
id: 1,
value: 1,
label: '选项1',
children: [{
id: 2,
value: 2,
label: '选项1-1'
}]
}],
placeholder: '请选择',
style: {width: '100%'},
props: {
props: {
multiple: false,
label: 'label',
value: 'value',
children: 'children'
}
},
'show-all-levels': true,
disabled: false,
clearable: true,
filterable: false,
separator: '/'
},
{
__config__: {
label: '单选框组',
labelWidth: null,
showLabel: true,
tag: 'el-radio-group',
tagIcon: 'radio',
changeTag: true,
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
optionType: 'default',
regList: [],
required: true,
border: false,
document: 'https://element.eleme.cn/#/zh-CN/component/radio'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}]
},
style: {},
size: 'medium',
disabled: false
},
{
__config__: {
label: '多选框组',
tag: 'el-checkbox-group',
tagIcon: 'checkbox',
defaultValue: [],
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
optionType: 'default',
required: true,
regList: [],
changeTag: true,
border: false,
document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}]
},
style: {},
size: 'medium',
min: null,
max: null,
disabled: false
},
{
__config__: {
label: '开关',
tag: 'el-switch',
tagIcon: 'switch',
defaultValue: false,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/switch'
},
style: {},
disabled: false,
'active-text': '',
'inactive-text': '',
'active-color': null,
'inactive-color': null,
'active-value': true,
'inactive-value': false
},
{
__config__: {
label: '滑块',
tag: 'el-slider',
tagIcon: 'slider',
defaultValue: null,
span: 24,
showLabel: true,
layout: 'colFormItem',
labelWidth: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/slider'
},
disabled: false,
min: 0,
max: 100,
step: 1,
'show-stops': false,
range: false
},
{
__config__: {
label: '时间选择',
tag: 'el-time-picker',
tagIcon: 'time',
defaultValue: null,
span: 24,
showLabel: true,
layout: 'colFormItem',
labelWidth: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
placeholder: '请选择',
style: {width: '100%'},
disabled: false,
clearable: true,
'picker-options': {
selectableRange: '00:00:00-23:59:59'
},
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss'
},
{
__config__: {
label: '时间范围',
tag: 'el-time-picker',
tagIcon: 'time-range',
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
defaultValue: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
style: {width: '100%'},
disabled: false,
clearable: true,
'is-range': true,
'range-separator': '至',
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss'
},
{
__config__: {
label: '日期选择',
tag: 'el-date-picker',
tagIcon: 'date',
defaultValue: null,
showLabel: true,
labelWidth: null,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
placeholder: '请选择',
type: 'date',
style: {width: '100%'},
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false
},
{
__config__: {
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
showLabel: true,
labelWidth: null,
required: true,
layout: 'colFormItem',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
style: {width: '100%'},
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false
},
{
__config__: {
label: '评分',
tag: 'el-rate',
tagIcon: 'rate',
defaultValue: 0,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/rate'
},
style: {},
max: 5,
'allow-half': false,
'show-text': false,
'show-score': false,
disabled: false
},
{
__config__: {
label: '颜色选择',
tag: 'el-color-picker',
tagIcon: 'color',
span: 24,
defaultValue: null,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/color-picker'
},
'show-alpha': false,
'color-format': '',
disabled: false,
size: 'medium'
},
{
__config__: {
label: '上传',
tag: 'el-upload',
tagIcon: 'upload',
layout: 'colFormItem',
defaultValue: null,
showLabel: true,
labelWidth: null,
required: true,
span: 24,
showTip: false,
buttonText: '点击上传',
regList: [],
changeTag: true,
fileSize: 2,
sizeUnit: 'MB',
document: 'https://element.eleme.cn/#/zh-CN/component/upload'
},
__slot__: {
'list-type': true
},
action: 'http://localhost:18080/file/upload',
disabled: false,
accept: '',
name: 'file',
'auto-upload': true,
'list-type': 'text',
multiple: false
}
]
// 布局型组件 【左面板】
export const layoutComponents = [
{
__config__: {
layout: 'rowFormItem',
tagIcon: 'row',
label: '行容器',
layoutTree: true,
document: 'https://element.eleme.cn/#/zh-CN/component/layout#row-attributes'
},
type: 'default',
justify: 'start',
align: 'top'
},
{
__config__: {
label: '按钮',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
span: 24,
layout: 'colFormItem',
document: 'https://element.eleme.cn/#/zh-CN/component/button'
},
__slot__: {
default: '主要按钮'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium',
plain: false,
circle: false,
disabled: false
},
// {
// __config__: {
// layout: 'colFormItem',
// tagIcon: 'table',
// tag: 'el-table',
// document: 'https://element.eleme.cn/#/zh-CN/component/table',
// span: 24,
// formId: 101,
// renderKey: 1595761764203,
// componentName: 'row101',
// showLabel: true,
// changeTag: true,
// labelWidth: null,
// label: '表格[开发中]',
// dataType: 'dynamic',
// method: 'get',
// dataPath: 'list',
// dataConsumer: 'data',
// url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/tableData',
// children: [{
// __config__: {
// layout: 'raw',
// tag: 'el-table-column',
// renderKey: 15957617660153
// },
// prop: 'date',
// label: '日期'
// }, {
// __config__: {
// layout: 'raw',
// tag: 'el-table-column',
// renderKey: 15957617660152
// },
// prop: 'address',
// label: '地址'
// }, {
// __config__: {
// layout: 'raw',
// tag: 'el-table-column',
// renderKey: 15957617660151
// },
// prop: 'name',
// label: '名称'
// }, {
// __config__: {
// layout: 'raw',
// tag: 'el-table-column',
// renderKey: 1595774496335,
// children: [
// {
// __config__: {
// label: '按钮',
// tag: 'el-button',
// tagIcon: 'button',
// layout: 'raw',
// renderKey: 1595779809901
// },
// __slot__: {
// default: '主要按钮'
// },
// type: 'primary',
// icon: 'el-icon-search',
// round: false,
// size: 'medium'
// }
// ]
// },
// label: '操作'
// }]
// },
// data: [],
// directives: [{
// name: 'loading',
// value: true
// }],
// border: true,
// type: 'default',
// justify: 'start',
// align: 'top'
// }
]

View File

@@ -0,0 +1,18 @@
const styles = {
'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
'el-upload': '.el-upload__tip{line-height: 1.2;}'
}
function addCss(cssList, el) {
const css = styles[el.__config__.tag]
css && cssList.indexOf(css) === -1 && cssList.push(css)
if (el.__config__.children) {
el.__config__.children.forEach(el2 => addCss(cssList, el2))
}
}
export function makeUpCss(conf) {
const cssList = []
conf.fields.forEach(el => addCss(cssList, el))
return cssList.join('\n')
}

View File

@@ -0,0 +1,37 @@
export default [
{
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
// 正则校验规则
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
},
// 组件的插槽属性
__slot__: {
prepend: '',
append: ''
},
__vModel__: 'mobile',
placeholder: '请输入手机号',
style: { width: '100%' },
clearable: true,
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false
}
]

View File

@@ -0,0 +1,399 @@
/* eslint-disable max-len */
import ruleTrigger from './ruleTrigger'
let confGlobal
let someSpanIsNot24
export function dialogWrapper(str) {
return `<el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" title="Dialog Titile">
${str}
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handelConfirm">确定</el-button>
</div>
</el-dialog>`
}
export function vueTemplate(str) {
return `<template>
<div>
${str}
</div>
</template>`
}
export function vueScript(str) {
return `<script>
${str}
</script>`
}
export function cssStyle(cssStr) {
return `<style>
${cssStr}
</style>`
}
function buildFormTemplate(scheme, child, type) {
let labelPosition = ''
if (scheme.labelPosition !== 'right') {
labelPosition = `label-position="${scheme.labelPosition}"`
}
const disabled = scheme.disabled ? `:disabled="${scheme.disabled}"` : ''
let str = `<el-form ref="${scheme.formRef}" :model="${scheme.formModel}" :rules="${scheme.formRules}" size="${scheme.size}" ${disabled} label-width="${scheme.labelWidth}px" ${labelPosition}>
${child}
${buildFromBtns(scheme, type)}
</el-form>`
if (someSpanIsNot24) {
str = `<el-row :gutter="${scheme.gutter}">
${str}
</el-row>`
}
return str
}
function buildFromBtns(scheme, type) {
let str = ''
if (scheme.formBtns && type === 'file') {
str = `<el-form-item size="large">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>`
if (someSpanIsNot24) {
str = `<el-col :span="24">
${str}
</el-col>`
}
}
return str
}
// span不为24的用el-col包裹
function colWrapper(scheme, str) {
if (someSpanIsNot24 || scheme.__config__.span !== 24) {
return `<el-col :span="${scheme.__config__.span}">
${str}
</el-col>`
}
return str
}
const layouts = {
colFormItem(scheme) {
const config = scheme.__config__
let labelWidth = ''
let label = `label="${config.label}"`
if (config.labelWidth && config.labelWidth !== confGlobal.labelWidth) {
labelWidth = `label-width="${config.labelWidth}px"`
}
if (config.showLabel === false) {
labelWidth = 'label-width="0"'
label = ''
}
const required = !ruleTrigger[config.tag] && config.required ? 'required' : ''
const tagDom = tags[config.tag] ? tags[config.tag](scheme) : null
let str = `<el-form-item ${labelWidth} ${label} prop="${scheme.__vModel__}" ${required}>
${tagDom}
</el-form-item>`
str = colWrapper(scheme, str)
return str
},
rowFormItem(scheme) {
const config = scheme.__config__
const type = scheme.type === 'default' ? '' : `type="${scheme.type}"`
const justify = scheme.type === 'default' ? '' : `justify="${scheme.justify}"`
const align = scheme.type === 'default' ? '' : `align="${scheme.align}"`
const gutter = scheme.gutter ? `:gutter="${scheme.gutter}"` : ''
const children = config.children.map(el => layouts[el.__config__.layout](el))
let str = `<el-row ${type} ${justify} ${align} ${gutter}>
${children.join('\n')}
</el-row>`
str = colWrapper(scheme, str)
return str
}
}
const tags = {
'el-button': el => {
const {
tag, disabled
} = attrBuilder(el)
const type = el.type ? `type="${el.type}"` : ''
const icon = el.icon ? `icon="${el.icon}"` : ''
const round = el.round ? 'round' : ''
const size = el.size ? `size="${el.size}"` : ''
const plain = el.plain ? 'plain' : ''
const circle = el.circle ? 'circle' : ''
let child = buildElButtonChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${type} ${icon} ${round} ${size} ${plain} ${disabled} ${circle}>${child}</${tag}>`
},
'el-input': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : ''
const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : ''
const readonly = el.readonly ? 'readonly' : ''
const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : ''
const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : ''
const showPassword = el['show-password'] ? 'show-password' : ''
const type = el.type ? `type="${el.type}"` : ''
const autosize = el.autosize && el.autosize.minRows
? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"`
: ''
let child = buildElInputChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${tag}>`
},
'el-input-number': el => {
const {
tag, disabled, vModel, placeholder
} = attrBuilder(el)
const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : ''
const min = el.min ? `:min='${el.min}'` : ''
const max = el.max ? `:max='${el.max}'` : ''
const step = el.step ? `:step='${el.step}'` : ''
const stepStrictly = el['step-strictly'] ? 'step-strictly' : ''
const precision = el.precision ? `:precision='${el.precision}'` : ''
return `<${tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${tag}>`
},
'el-select': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const filterable = el.filterable ? 'filterable' : ''
const multiple = el.multiple ? 'multiple' : ''
let child = buildElSelectChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${tag}>`
},
'el-radio-group': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
let child = buildElRadioGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${size} ${disabled}>${child}</${tag}>`
},
'el-checkbox-group': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
const min = el.min ? `:min="${el.min}"` : ''
const max = el.max ? `:max="${el.max}"` : ''
let child = buildElCheckboxGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${tag}>`
},
'el-switch': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : ''
const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : ''
const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : ''
const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : ''
const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : ''
const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : ''
return `<${tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${tag}>`
},
'el-cascader': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const options = el.options ? `:options="${el.__vModel__}Options"` : ''
const props = el.props ? `:props="${el.__vModel__}Props"` : ''
const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"'
const filterable = el.filterable ? 'filterable' : ''
const separator = el.separator === '/' ? '' : `separator="${el.separator}"`
return `<${tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${tag}>`
},
'el-slider': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const min = el.min ? `:min='${el.min}'` : ''
const max = el.max ? `:max='${el.max}'` : ''
const step = el.step ? `:step='${el.step}'` : ''
const range = el.range ? 'range' : ''
const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : ''
return `<${tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${tag}>`
},
'el-time-picker': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
const isRange = el['is-range'] ? 'is-range' : ''
const format = el.format ? `format="${el.format}"` : ''
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : ''
return `<${tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${tag}>`
},
'el-date-picker': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
const format = el.format ? `format="${el.format}"` : ''
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
const type = el.type === 'date' ? '' : `type="${el.type}"`
const readonly = el.readonly ? 'readonly' : ''
return `<${tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${tag}>`
},
'el-rate': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const max = el.max ? `:max='${el.max}'` : ''
const allowHalf = el['allow-half'] ? 'allow-half' : ''
const showText = el['show-text'] ? 'show-text' : ''
const showScore = el['show-score'] ? 'show-score' : ''
return `<${tag} ${vModel} ${max} ${allowHalf} ${showText} ${showScore} ${disabled}></${tag}>`
},
'el-color-picker': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
const showAlpha = el['show-alpha'] ? 'show-alpha' : ''
const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : ''
return `<${tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${tag}>`
},
'el-upload': el => {
const { tag } = el.__config__
const disabled = el.disabled ? ':disabled=\'true\'' : ''
const action = el.action ? `:action="${el.__vModel__}Action"` : ''
const multiple = el.multiple ? 'multiple' : ''
const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : ''
const accept = el.accept ? `accept="${el.accept}"` : ''
const name = el.name !== 'file' ? `name="${el.name}"` : ''
const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : ''
const beforeUpload = `:before-upload="${el.__vModel__}BeforeUpload"`
const fileList = `:file-list="${el.__vModel__}fileList"`
const ref = `ref="${el.__vModel__}"`
let child = buildElUploadChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${tag}>`
},
tinymce: el => {
const { tag, vModel, placeholder } = attrBuilder(el)
const height = el.height ? `:height="${el.height}"` : ''
const branding = el.branding ? `:branding="${el.branding}"` : ''
return `<${tag} ${vModel} ${placeholder} ${height} ${branding}></${tag}>`
}
}
function attrBuilder(el) {
return {
tag: el.__config__.tag,
vModel: `v-model="${confGlobal.formModel}.${el.__vModel__}"`,
clearable: el.clearable ? 'clearable' : '',
placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '',
width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '',
disabled: el.disabled ? ':disabled=\'true\'' : ''
}
}
// el-buttin 子级
function buildElButtonChild(scheme) {
const children = []
const slot = scheme.__slot__ || {}
if (slot.default) {
children.push(slot.default)
}
return children.join('\n')
}
// el-input 子级
function buildElInputChild(scheme) {
const children = []
const slot = scheme.__slot__
if (slot && slot.prepend) {
children.push(`<template slot="prepend">${slot.prepend}</template>`)
}
if (slot && slot.append) {
children.push(`<template slot="append">${slot.append}</template>`)
}
return children.join('\n')
}
// el-select 子级
function buildElSelectChild(scheme) {
const children = []
const slot = scheme.__slot__
if (slot && slot.options && slot.options.length) {
children.push(`<el-option v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
}
return children.join('\n')
}
// el-radio-group 子级
function buildElRadioGroupChild(scheme) {
const children = []
const slot = scheme.__slot__
const config = scheme.__config__
if (slot && slot.options && slot.options.length) {
const tag = config.optionType === 'button' ? 'el-radio-button' : 'el-radio'
const border = config.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
// el-checkbox-group 子级
function buildElCheckboxGroupChild(scheme) {
const children = []
const slot = scheme.__slot__
const config = scheme.__config__
if (slot && slot.options && slot.options.length) {
const tag = config.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
const border = config.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
// el-upload 子级
function buildElUploadChild(scheme) {
const list = []
const config = scheme.__config__
if (scheme['list-type'] === 'picture-card') list.push('<i class="el-icon-plus"></i>')
else list.push(`<el-button size="small" type="primary" icon="el-icon-upload">${config.buttonText}</el-button>`)
if (config.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${config.fileSize}${config.sizeUnit}${scheme.accept}文件</div>`)
return list.join('\n')
}
/**
* 组装html代码。【入口函数】
* @param {Object} formConfig 整个表单配置
* @param {String} type 生成类型,文件或弹窗等
*/
export function makeUpHtml(formConfig, type) {
const htmlList = []
confGlobal = formConfig
// 判断布局是否都沾满了24个栅格以备后续简化代码结构
someSpanIsNot24 = formConfig.fields.some(item => item.__config__.span !== 24)
// 遍历渲染每个组件成html
formConfig.fields.forEach(el => {
htmlList.push(layouts[el.__config__.layout](el))
})
const htmlStr = htmlList.join('\n')
// 将组件代码放进form标签
let temp = buildFormTemplate(formConfig, htmlStr, type)
// dialog标签包裹代码
if (type === 'dialog') {
temp = dialogWrapper(temp)
}
confGlobal = null
return temp
}

View File

@@ -0,0 +1 @@
["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"]

View File

@@ -0,0 +1,271 @@
import { isArray } from 'util'
import { exportDefault, titleCase, deepClone } from '../../utils/index'
import ruleTrigger from './ruleTrigger'
const units = {
KB: '1024',
MB: '1024 / 1024',
GB: '1024 / 1024 / 1024'
}
let confGlobal
const inheritAttrs = {
file: '',
dialog: 'inheritAttrs: false,'
}
/**
* 组装js 【入口函数】
* @param {Object} formConfig 整个表单配置
* @param {String} type 生成类型,文件或弹窗等
*/
export function makeUpJs(formConfig, type) {
confGlobal = formConfig = deepClone(formConfig)
const dataList = []
const ruleList = []
const optionsList = []
const propsList = []
const methodList = mixinMethod(type)
const uploadVarList = []
const created = []
formConfig.fields.forEach(el => {
buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
})
const script = buildexport(
formConfig,
type,
dataList.join('\n'),
ruleList.join('\n'),
optionsList.join('\n'),
uploadVarList.join('\n'),
propsList.join('\n'),
methodList.join('\n'),
created.join('\n')
)
confGlobal = null
return script
}
// 构建组件属性
function buildAttributes(scheme, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created) {
const config = scheme.__config__
const slot = scheme.__slot__
buildData(scheme, dataList)
buildRules(scheme, ruleList)
// 特殊处理options属性
if (scheme.options || (slot && slot.options && slot.options.length)) {
buildOptions(scheme, optionsList)
if (config.dataType === 'dynamic') {
const model = `${scheme.__vModel__}Options`
const options = titleCase(model)
const methodName = `get${options}`
buildOptionMethod(methodName, model, methodList, scheme)
callInCreated(methodName, created)
}
}
// 处理props
if (scheme.props && scheme.props.props) {
buildProps(scheme, propsList)
}
// 处理el-upload的action
if (scheme.action && config.tag === 'el-upload') {
uploadVarList.push(
`${scheme.__vModel__}Action: '${scheme.action}',
${scheme.__vModel__}fileList: [],`
)
methodList.push(buildBeforeUpload(scheme))
// 非自动上传时,生成手动上传的函数
if (!scheme['auto-upload']) {
methodList.push(buildSubmitUpload(scheme))
}
}
// 构建子级组件属性
if (config.children) {
config.children.forEach(item => {
buildAttributes(item, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
})
}
}
// 在Created调用函数
function callInCreated(methodName, created) {
created.push(`this.${methodName}()`)
}
// 混入处理函数
function mixinMethod(type) {
const list = []; const
minxins = {
file: confGlobal.formBtns ? {
submitForm: `submitForm() {
this.$refs['${confGlobal.formRef}'].validate(valid => {
if(!valid) return
// TODO 提交表单
})
},`,
resetForm: `resetForm() {
this.$refs['${confGlobal.formRef}'].resetFields()
},`
} : null,
dialog: {
onOpen: 'onOpen() {},',
onClose: `onClose() {
this.$refs['${confGlobal.formRef}'].resetFields()
},`,
close: `close() {
this.$emit('update:visible', false)
},`,
handelConfirm: `handelConfirm() {
this.$refs['${confGlobal.formRef}'].validate(valid => {
if(!valid) return
this.close()
})
},`
}
}
const methods = minxins[type]
if (methods) {
Object.keys(methods).forEach(key => {
list.push(methods[key])
})
}
return list
}
// 构建data
function buildData(scheme, dataList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const defaultValue = JSON.stringify(config.defaultValue)
dataList.push(`${scheme.__vModel__}: ${defaultValue},`)
}
// 构建校验规则
function buildRules(scheme, ruleList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const rules = []
if (ruleTrigger[config.tag]) {
if (config.required) {
const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
let message = isArray(config.defaultValue) ? `请至少选择一个${config.label}` : scheme.placeholder
if (message === undefined) message = `${config.label}不能为空`
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
}
if (config.regList && isArray(config.regList)) {
config.regList.forEach(item => {
if (item.pattern) {
rules.push(
`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${ruleTrigger[config.tag]}' }`
)
}
})
}
ruleList.push(`${scheme.__vModel__}: [${rules.join(',')}],`)
}
}
// 构建options
function buildOptions(scheme, optionsList) {
if (scheme.__vModel__ === undefined) return
// el-cascader直接有options属性其他组件都是定义在slot中所以有两处判断
let { options } = scheme
if (!options) options = scheme.__slot__.options
if (scheme.__config__.dataType === 'dynamic') { options = [] }
const str = `${scheme.__vModel__}Options: ${JSON.stringify(options)},`
optionsList.push(str)
}
function buildProps(scheme, propsList) {
const str = `${scheme.__vModel__}Props: ${JSON.stringify(scheme.props.props)},`
propsList.push(str)
}
// el-upload的BeforeUpload
function buildBeforeUpload(scheme) {
const config = scheme.__config__
const unitNum = units[config.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const
returnList = []
if (config.fileSize) {
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${config.fileSize}
if(!isRightSize){
this.$message.error('文件大小超过 ${config.fileSize}${config.sizeUnit}')
}`
returnList.push('isRightSize')
}
if (scheme.accept) {
acceptCode = `let isAccept = new RegExp('${scheme.accept}').test(file.type)
if(!isAccept){
this.$message.error('应该选择${scheme.accept}类型的文件')
}`
returnList.push('isAccept')
}
const str = `${scheme.__vModel__}BeforeUpload(file) {
${rightSizeCode}
${acceptCode}
return ${returnList.join('&&')}
},`
return returnList.length ? str : ''
}
// el-upload的submit
function buildSubmitUpload(scheme) {
const str = `submitUpload() {
this.$refs['${scheme.__vModel__}'].submit()
},`
return str
}
function buildOptionMethod(methodName, model, methodList, scheme) {
const config = scheme.__config__
const str = `${methodName}() {
// 注意this.$axios是通过Vue.prototype.$axios = axios挂载产生的
this.$axios({
method: '${config.method}',
url: '${config.url}'
}).then(resp => {
var { data } = resp
this.${model} = data.${config.dataPath}
})
},`
methodList.push(str)
}
// js整体拼接
function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods, created) {
const str = `${exportDefault}{
${inheritAttrs[type]}
components: {},
props: [],
data () {
return {
${conf.formModel}: {
${data}
},
${conf.formRules}: {
${rules}
},
${uploadVar}
${selectOptions}
${props}
}
},
computed: {},
watch: {},
created () {
${created}
},
mounted () {},
methods: {
${methods}
}
}`
return str
}

View File

@@ -0,0 +1,126 @@
import { makeMap } from '@/utils/index'
// 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js
const isAttr = makeMap(
'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,'
+ 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,'
+ 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,'
+ 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,'
+ 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,'
+ 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,'
+ 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,'
+ 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,'
+ 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,'
+ 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,'
+ 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,'
+ 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,'
+ 'target,title,type,usemap,value,width,wrap'
)
function vModel(self, dataObject, defaultValue) {
dataObject.props.value = defaultValue
dataObject.on.input = val => {
self.$emit('input', val)
}
}
const componentChild = {
'el-button': {
default(h, conf, key) {
return conf[key]
},
},
'el-input': {
prepend(h, conf, key) {
return <template slot="prepend">{conf[key]}</template>
},
append(h, conf, key) {
return <template slot="append">{conf[key]}</template>
}
},
'el-select': {
options(h, conf, key) {
const list = []
conf.options.forEach(item => {
list.push(<el-option label={item.label} value={item.value} disabled={item.disabled}></el-option>)
})
return list
}
},
'el-radio-group': {
options(h, conf, key) {
const list = []
conf.options.forEach(item => {
if (conf.optionType === 'button') list.push(<el-radio-button label={item.value}>{item.label}</el-radio-button>)
else list.push(<el-radio label={item.value} border={conf.border}>{item.label}</el-radio>)
})
return list
}
},
'el-checkbox-group': {
options(h, conf, key) {
const list = []
conf.options.forEach(item => {
if (conf.optionType === 'button') {
list.push(<el-checkbox-button label={item.value}>{item.label}</el-checkbox-button>)
} else {
list.push(<el-checkbox label={item.value} border={conf.border}>{item.label}</el-checkbox>)
}
})
return list
}
},
'el-upload': {
'list-type': (h, conf, key) => {
const list = []
if (conf['list-type'] === 'picture-card') {
list.push(<i class="el-icon-plus"></i>)
} else {
list.push(<el-button size="small" type="primary" icon="el-icon-upload">{conf.buttonText}</el-button>)
}
if (conf.showTip) {
list.push(<div slot="tip" class="el-upload__tip">只能上传不超过 {conf.fileSize}{conf.sizeUnit} {conf.accept}文件</div>)
}
return list
}
}
}
export default {
render(h) {
const dataObject = {
attrs: {},
props: {},
on: {},
style: {}
}
const confClone = JSON.parse(JSON.stringify(this.conf))
const children = []
const childObjs = componentChild[confClone.tag]
if (childObjs) {
Object.keys(childObjs).forEach(key => {
const childFunc = childObjs[key]
if (confClone[key]) {
children.push(childFunc(h, confClone, key))
}
})
}
Object.keys(confClone).forEach(key => {
const val = confClone[key]
if (key === 'vModel') {
vModel(this, dataObject, confClone.defaultValue)
} else if (dataObject[key]) {
dataObject[key] = val
} else if (!isAttr(key)) {
dataObject.props[key] = val
} else {
dataObject.attrs[key] = val
}
})
return h(this.conf.tag, dataObject, children)
},
props: ['conf']
}

View File

@@ -0,0 +1,16 @@
/**
* 用于生成表单校验,指定正则规则的触发方式。
* 未在此处声明无触发方式的组件将不生成rule
*/
export default {
'el-input': 'blur',
'el-input-number': 'blur',
'el-select': 'change',
'el-radio-group': 'change',
'el-checkbox-group': 'change',
'el-cascader': 'change',
'el-time-picker': 'change',
'el-date-picker': 'change',
'el-rate': 'change',
tinymce: 'blur'
}

View File

@@ -0,0 +1,435 @@
import { parseTime } from './ruoyi'
/**
* 表格时间格式化
*/
export function formatDate(cellValue) {
if (cellValue == null || cellValue == "") return "";
var date = new Date(cellValue)
var year = date.getFullYear()
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
}
/**
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000
} else {
time = +time
}
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
/**
* @param {string} url
* @returns {Object}
*/
export function getQueryObject(url) {
url = url == null ? window.location.href : url
const search = url.substring(url.lastIndexOf('?') + 1)
const obj = {}
const reg = /([^?&=]+)=([^?&=]*)/g
search.replace(reg, (rs, $1, $2) => {
const name = decodeURIComponent($1)
let val = decodeURIComponent($2)
val = String(val)
obj[name] = val
return rs
})
return obj
}
/**
* @param {string} input value
* @returns {number} output value
*/
export function byteLength(str) {
// returns the byte length of an utf8 string
let s = str.length
for (var i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2
if (code >= 0xDC00 && code <= 0xDFFF) i--
}
return s
}
/**
* @param {Array} actual
* @returns {Array}
*/
export function cleanArray(actual) {
const newArray = []
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i])
}
}
return newArray
}
/**
* @param {Object} json
* @returns {Array}
*/
export function param(json) {
if (!json) return ''
return cleanArray(
Object.keys(json).map(key => {
if (json[key] === undefined) return ''
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
})
).join('&')
}
/**
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}
/**
* @param {string} val
* @returns {string}
*/
export function html2Text(val) {
const div = document.createElement('div')
div.innerHTML = val
return div.textContent || div.innerText
}
/**
* Merges two objects, giving the last one precedence
* @param {Object} target
* @param {(Object|Array)} source
* @returns {Object}
*/
export function objectMerge(target, source) {
if (typeof target !== 'object') {
target = {}
}
if (Array.isArray(source)) {
return source.slice()
}
Object.keys(source).forEach(property => {
const sourceProperty = source[property]
if (typeof sourceProperty === 'object') {
target[property] = objectMerge(target[property], sourceProperty)
} else {
target[property] = sourceProperty
}
})
return target
}
/**
* @param {HTMLElement} element
* @param {string} className
*/
export function toggleClass(element, className) {
if (!element || !className) {
return
}
let classString = element.className
const nameIndex = classString.indexOf(className)
if (nameIndex === -1) {
classString += '' + className
} else {
classString =
classString.substr(0, nameIndex) +
classString.substr(nameIndex + className.length)
}
element.className = classString
}
/**
* @param {string} type
* @returns {Date}
*/
export function getTime(type) {
if (type === 'start') {
return new Date().getTime() - 3600 * 1000 * 24 * 90
} else {
return new Date(new Date().toDateString())
}
}
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
// export function deepClone(source) {
// if (!source && typeof source !== 'object') {
// throw new Error('error arguments', 'deepClone')
// }
// const targetObj = source.constructor === Array ? [] : {}
// Object.keys(source).forEach(keys => {
// if (source[keys] && typeof source[keys] === 'object') {
// targetObj[keys] = deepClone(source[keys])
// } else {
// targetObj[keys] = source[keys]
// }
// })
// return targetObj
// }
/**
* @param {Array} arr
* @returns {Array}
*/
export function uniqueArr(arr) {
return Array.from(new Set(arr))
}
/**
* @returns {string}
*/
export function createUniqueString() {
const timestamp = +new Date() + ''
const randomNum = parseInt((1 + Math.random()) * 65536) + ''
return (+(randomNum + timestamp)).toString(32)
}
/**
* Check if an element has a class
* @param {HTMLElement} elm
* @param {string} cls
* @returns {boolean}
*/
export function hasClass(ele, cls) {
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}
/**
* Add class to element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function addClass(ele, cls) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls
}
/**
* Remove class from element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function removeClass(ele, cls) {
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
ele.className = ele.className.replace(reg, ' ')
}
}
export function makeMap(str, expectsLowerCase) {
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
export const exportDefault = 'export default '
export const beautifierConf = {
html: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'separate',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: false,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
},
js: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'normal',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: true,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
}
}
// 首字母大小
export function titleCase(str) {
return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())
}
// 下划转驼峰
export function camelCase(str) {
return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase())
}
export function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
}
// 深拷贝对象
export function deepClone(obj) {
const _toString = Object.prototype.toString
// null, undefined, non-object, function
if (!obj || typeof obj !== 'object') {
return obj
}
// DOM Node
if (obj.nodeType && 'cloneNode' in obj) {
return obj.cloneNode(true)
}
// Date
if (_toString.call(obj) === '[object Date]') {
return new Date(obj.getTime())
}
// RegExp
if (_toString.call(obj) === '[object RegExp]') {
const flags = []
if (obj.global) { flags.push('g') }
if (obj.multiline) { flags.push('m') }
if (obj.ignoreCase) { flags.push('i') }
return new RegExp(obj.source, flags.join(''))
}
const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}
for (const key in obj) {
result[key] = deepClone(obj[key])
}
return result
}
const toStr = Function.prototype.call.bind(Object.prototype.toString)
export function isObjectObject(t) {
return toStr(t) === '[object Object]'
}

View File

@@ -0,0 +1,30 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
'UP8iWi1Qw0Y='
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对数据进行加密
}
// 解密
export function decrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey) // 设置私钥
return encryptor.decrypt(txt) // 对数据进行解密
}

View File

@@ -0,0 +1,28 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
let beautifierObj
export default function loadBeautifier(cb) {
const { beautifierUrl } = pluginsConfig
if (beautifierObj) {
cb(beautifierObj)
return
}
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '格式化资源加载中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
loadScript(beautifierUrl, () => {
loading.close()
// eslint-disable-next-line no-undef
beautifierObj = beautifier
cb(beautifierObj)
})
}

View File

@@ -0,0 +1,40 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
// monaco-editor单例
let monacoEidtor
/**
* 动态加载monaco-editor cdn资源
* @param {Function} cb 回调,必填
*/
export default function loadMonaco(cb) {
if (monacoEidtor) {
cb(monacoEidtor)
return
}
const { monacoEditorUrl: vs } = pluginsConfig
// 使用element ui实现加载提示
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '编辑器资源初始化中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
!window.require && (window.require = {})
!window.require.paths && (window.require.paths = {})
window.require.paths.vs = vs
loadScript(`${vs}/loader.js`, () => {
window.require(['vs/editor/editor.main'], () => {
loading.close()
monacoEidtor = window.monaco
cb(monacoEidtor)
})
})
}

View File

@@ -0,0 +1,60 @@
const callbacks = {}
/**
* 加载一个远程脚本
* @param {String} src 一个远程脚本
* @param {Function} callback 回调
*/
function loadScript(src, callback) {
const existingScript = document.getElementById(src)
const cb = callback || (() => {})
if (!existingScript) {
callbacks[src] = []
const $script = document.createElement('script')
$script.src = src
$script.id = src
$script.async = 1
document.body.appendChild($script)
const onEnd = 'onload' in $script ? stdOnEnd.bind($script) : ieOnEnd.bind($script)
onEnd($script)
}
callbacks[src].push(cb)
function stdOnEnd(script) {
script.onload = () => {
this.onerror = this.onload = null
callbacks[src].forEach(item => {
item(null, script)
})
delete callbacks[src]
}
script.onerror = () => {
this.onerror = this.onload = null
cb(new Error(`Failed to load ${src}`), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = () => {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
callbacks[src].forEach(item => {
item(null, script)
})
delete callbacks[src]
}
}
}
/**
* 顺序加载一组远程脚本
* @param {Array} list 一组远程脚本
* @param {Function} cb 回调
*/
export function loadScriptQueue(list, cb) {
const first = list.shift()
list.length ? loadScript(first, () => loadScriptQueue(list, cb)) : loadScript(first, cb)
}
export default loadScript

View File

@@ -0,0 +1,29 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
let tinymceObj
export default function loadTinymce(cb) {
const { tinymceUrl } = pluginsConfig
if (tinymceObj) {
cb(tinymceObj)
return
}
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '富文本资源加载中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
loadScript(tinymceUrl, () => {
loading.close()
// eslint-disable-next-line no-undef
tinymceObj = tinymce
cb(tinymceObj)
})
}

View File

@@ -0,0 +1,47 @@
import store from '@/store'
/**
* 字符权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export function checkPermi(value) {
if (value && value instanceof Array && value.length > 0) {
const permissions = store.getters && store.getters.permissions
const permissionDatas = value
const all_permission = "*:*:*";
const hasPermission = permissions.some(permission => {
return all_permission === permission || permissionDatas.includes(permission)
})
return hasPermission;
} else {
console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`)
return false
}
}
/**
* 角色权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export function checkRole(value) {
if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles
const permissionRoles = value
const super_admin = "admin";
const hasRole = roles.some(role => {
return super_admin === role || permissionRoles.includes(role)
})
return hasRole;
} else {
console.error(`need roles! Like checkRole="['admin','editor']"`)
return false
}
}

View File

@@ -0,0 +1,13 @@
const CDN = 'https://lib.baomitu.com/' // CDN Homepage: https://cdn.baomitu.com/
const publicPath = process.env.BASE_URL
function splicingPluginUrl(PluginName, version, fileName) {
return `${CDN}${PluginName}/${version}/${fileName}`
}
export default {
beautifierUrl: splicingPluginUrl('js-beautify', '1.13.5', 'beautifier.min.js'),
monacoEditorUrl: splicingPluginUrl('monaco-editor', '0.19.3', 'min/vs'), // 使用 monaco-editor CDN 链接
// monacoEditorUrl: `${publicPath}libs/monaco-editor/vs`, // 使用 monaco-editor 本地代码
tinymceUrl: splicingPluginUrl('tinymce', '5.7.0', 'tinymce.min.js')
}

View File

@@ -0,0 +1,152 @@
import axios from 'axios'
import { Notification, MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from "@/utils/ruoyi";
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')
return config;
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = '/index';
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
Message({ message: msg, type: 'warning' })
return Promise.reject('error')
} else if (code !== 200) {
Notification.error({ title: msg })
return Promise.reject('error')
} else {
return res.data
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
)
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
export default service

View File

@@ -0,0 +1,233 @@
/**
* 通用js方法封装处理
* Copyright (c) 2019 ruoyi
*/
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields();
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
let search = params;
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof (propName) === 'undefined') {
search.params['beginTime'] = dateRange[0];
search.params['endTime'] = dateRange[1];
} else {
search.params['begin' + propName] = dateRange[0];
search.params['end' + propName] = dateRange[1];
}
return search;
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return "";
}
var actions = [];
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) {
actions.push(datas[key].label);
return true;
}
})
if (actions.length === 0) {
actions.push(value);
}
return actions.join('');
}
// 回显数据字典(字符串、数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length ===0) {
return "";
}
if (Array.isArray(value)) {
value = value.join(",");
}
var actions = [];
var currentSeparator = undefined === separator ? "," : separator;
var temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false;
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) {
actions.push(datas[key].label + currentSeparator);
match = true;
}
})
if (!match) {
actions.push(temp[val] + currentSeparator);
}
})
return actions.join('').substring(0, actions.join('').length - 1);
}
// 字符串格式化(%s )
export function sprintf(str) {
var args = arguments, flag = true, i = 1;
str = str.replace(/%s/g, function () {
var arg = args[i++];
if (typeof arg === 'undefined') {
flag = false;
return '';
}
return arg;
});
return flag ? str : '';
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") {
return "";
}
return str;
}
// 数据合并
export function mergeRecursive(source, target) {
for (var p in target) {
try {
if (target[p].constructor == Object) {
source[p] = mergeRecursive(source[p], target[p]);
} else {
source[p] = target[p];
}
} catch (e) {
source[p] = target[p];
}
}
return source;
};
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
let config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children'
};
var childrenListMap = {};
var nodeIds = {};
var tree = [];
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
}
}
for (let t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result
}
// 验证是否为blob格式
export function blobValidate(data) {
return data.type !== 'application/json'
}

View File

@@ -0,0 +1,58 @@
Math.easeInOutQuad = function(t, b, c, d) {
t /= d / 2
if (t < 1) {
return c / 2 * t * t + b
}
t--
return -c / 2 * (t * (t - 2) - 1) + b
}
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
var requestAnimFrame = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
})()
/**
* Because it's so fucking difficult to detect the scrolling element, just move them all
* @param {number} amount
*/
function move(amount) {
document.documentElement.scrollTop = amount
document.body.parentNode.scrollTop = amount
document.body.scrollTop = amount
}
function position() {
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
}
/**
* @param {number} to
* @param {number} duration
* @param {Function} callback
*/
export function scrollTo(to, duration, callback) {
const start = position()
const change = to - start
const increment = 20
let currentTime = 0
duration = (typeof (duration) === 'undefined') ? 500 : duration
var animateScroll = function() {
// increment the time
currentTime += increment
// find the value with the quadratic in-out easing function
var val = Math.easeInOutQuad(currentTime, start, change, duration)
// move the document.body
move(val)
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll)
} else {
if (callback && typeof (callback) === 'function') {
// the animation is done so lets callback
callback()
}
}
}
animateScroll()
}

View File

@@ -0,0 +1,80 @@
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor']
return valid_map.indexOf(str.trim()) >= 0
}
/**
* @param {string} url
* @returns {Boolean}
*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
}
/**
* @param {string} email
* @returns {Boolean}
*/
export function validEmail(email) {
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(email)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function isString(str) {
return typeof str === 'string' || str instanceof String;
}
/**
* @param {Array} arg
* @returns {Boolean}
*/
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}