1import retrieveSchema from './schema/retriev'; 2import { getPathVal } from './vueUtils'; 3 4import { getSchemaType, isObject } from './utils'; 5 6// 通用的处理表达式方法 7// 这里打破 JSON Schema 规范 8const regExpression = /{{(.*)}}/; 9function handleExpression(rootFormData, curNodePath, expression, fallBack) { 10 // 未配置 11 if (undefined === expression) { 12 return undefined; 13 } 14 15 // 配置了 mustache 表达式 16 const matchExpression = regExpression.exec(expression); 17 regExpression.lastIndex = 0; // 重置索引 18 if (matchExpression) { 19 const code = matchExpression[1].trim(); 20 21 // eslint-disable-next-line no-new-func 22 const fn = new Function('parentFormData', 'rootFormData', `return ${code}`); 23 24 return fn(getPathVal(rootFormData, curNodePath, 1), rootFormData); 25 } 26 27 // 回退 28 return fallBack(); 29} 30 31export function replaceArrayIndex({ schema, uiSchema } = {}, index) { 32 const itemUiOptions = getUiOptions({ 33 schema, 34 uiSchema, 35 containsSpec: false 36 }); 37 38 return ['title', 'description'].reduce((preVal, curItem) => { 39 if (itemUiOptions[curItem]) { 40 preVal[`ui:${curItem}`] = String(itemUiOptions[curItem]).replace(/\$index/g, index + 1); 41 } 42 return preVal; 43 }, {}); 44} 45 46// 是否为 hidden Widget 47export function isHiddenWidget({ 48 schema = {}, 49 uiSchema = {}, 50 curNodePath = '', 51 rootFormData = {} 52}) { 53 const widget = uiSchema['ui:widget'] || schema['ui:widget']; 54 const hiddenExpression = uiSchema['ui:hidden'] || schema['ui:hidden']; 55 56 // 支持配置 ui:hidden 表达式 57 return widget === 'HiddenWidget' 58 || widget === 'hidden' 59 || !!handleExpression(rootFormData, curNodePath, hiddenExpression, () => { 60 // 配置了函数 function 61 if (typeof hiddenExpression === 'function') { 62 return hiddenExpression(getPathVal(rootFormData, curNodePath, 1), rootFormData); 63 } 64 65 // 配置了常量 ?? 66 return hiddenExpression; 67 }); 68} 69 70// 解析当前节点 ui field 71export function getUiField(FIELDS_MAP, { 72 schema = {}, 73 uiSchema = {}, 74}) { 75 const field = schema['ui:field'] || uiSchema['ui:field']; 76 77 // vue 组件,或者已注册的组件名 78 if (typeof field === 'function' || typeof field === 'object' || typeof field === 'string') { 79 return { 80 field, 81 fieldProps: uiSchema['ui:fieldProps'] || schema['ui:fieldProps'], // 自定义field ,支持传入额外的 props 82 }; 83 } 84 85 // 类型默认 field 86 const fieldCtor = FIELDS_MAP[getSchemaType(schema)]; 87 if (fieldCtor) { 88 return { 89 field: fieldCtor 90 }; 91 } 92 93 // 如果包含 oneOf anyOf 返回空不异常 94 // SchemaField 会附加onyOf anyOf信息 95 if (!fieldCtor && (schema.anyOf || schema.oneOf)) { 96 return { 97 field: null 98 }; 99 } 100 101 // 不支持的类型 102 throw new Error(`不支持的field类型 ${schema.type}`); 103} 104 105// 解析用户配置的 uiSchema options 106export function getUserUiOptions({ 107 schema = {}, 108 uiSchema = {}, 109 curNodePath, // undefined 不处理 表达式 110 rootFormData = {} 111}) { 112 // 支持 uiSchema配置在 schema文件中 113 return Object.assign({}, ...[schema, uiSchema].map(itemSchema => Object.keys(itemSchema) 114 .reduce((options, key) => { 115 const value = itemSchema[key]; 116 // options 内外合并 117 if (key === 'ui:options' && isObject(value)) { 118 return { ...options, ...value }; 119 } 120 121 // https://github.com/lljj-x/vue-json-schema-form/issues/170 122 // ui:hidden需要作为内置属性使用,不能直接透传给widget组件,如果组件需要只能在ui:options 中使用hidden传递 123 if (key !== 'ui:hidden' && key.indexOf('ui:') === 0) { 124 // 只对 ui:xxx 配置形式支持表达式 125 return { 126 ...options, 127 [key.substring(3)]: curNodePath === undefined ? value : handleExpression(rootFormData, curNodePath, value, () => value) 128 }; 129 } 130 131 return options; 132 }, {}))); 133} 134 135// 解析当前节点的ui options参数 136export function getUiOptions({ 137 schema = {}, 138 uiSchema = {}, 139 containsSpec = true, 140 curNodePath, 141 rootFormData, 142}) { 143 const spec = {}; 144 if (containsSpec) { 145 spec.readonly = !!schema.readOnly; 146 if (undefined !== schema.multipleOf) { 147 // 组件计数器步长 148 spec.step = schema.multipleOf; 149 } 150 if (schema.minimum || schema.minimum === 0) { 151 spec.min = schema.minimum; 152 } 153 if (schema.maximum || schema.maximum === 0) { 154 spec.max = schema.maximum; 155 } 156 157 if (schema.minLength || schema.minLength === 0) { 158 spec.minlength = schema.minLength; 159 } 160 if (schema.maxLength || schema.maxLength === 0) { 161 spec.maxlength = schema.maxLength; 162 } 163 164 if (schema.format === 'date-time' || schema.format === 'date') { 165 // 数组类型 时间区间 166 // 打破了schema的规范,type array 配置了 format 167 if (schema.type === 'array') { 168 spec.isRange = true; 169 spec.isNumberValue = !(schema.items && schema.items.type === 'string'); 170 } else { 171 // 字符串 ISO 时间 172 spec.isNumberValue = !(schema.type === 'string'); 173 } 174 } 175 } 176 177 if (schema.title) spec.title = schema.title; 178 if (schema.description) spec.description = schema.description; 179 180 // 计算ui配置 181 return { 182 ...spec, 183 184 // 用户配置最高优先级 185 ...getUserUiOptions({ 186 schema, 187 uiSchema, 188 curNodePath, 189 rootFormData 190 }) 191 }; 192} 193 194// 获取当前节点的ui 配置 (options + widget) 195// 处理成 Widget 组件需要的格式 196export function getWidgetConfig({ 197 schema = {}, 198 uiSchema = {}, 199 curNodePath, 200 rootFormData, 201}, fallback = null) { 202 const uiOptions = getUiOptions({ 203 schema, 204 uiSchema, 205 curNodePath, 206 rootFormData, 207 }); 208 209 // 没有配置 Widget ,各个Field组件根据类型判断 210 if (!uiOptions.widget && fallback) { 211 Object.assign(uiOptions, fallback({ 212 schema, 213 uiSchema 214 })); 215 } 216 217 const { 218 widget, 219 title: label, 220 labelWidth, 221 description, 222 attrs: widgetAttrs, 223 class: widgetClass, 224 style: widgetStyle, 225 widgetListeners, 226 fieldAttrs, 227 fieldStyle, 228 fieldClass, 229 emptyValue, 230 width, 231 getWidget, 232 renderScopedSlots, 233 renderChildren, 234 onChange, 235 ...uiProps 236 } = uiOptions; 237 238 return { 239 widget, 240 label, 241 labelWidth, 242 description, 243 widgetAttrs, 244 widgetClass, 245 widgetStyle, 246 fieldAttrs, 247 width, 248 fieldStyle, 249 fieldClass, 250 emptyValue, 251 getWidget, 252 renderScopedSlots, 253 renderChildren, 254 onChange, 255 widgetListeners, 256 uiProps 257 }; 258} 259 260// 解析用户配置的 errorSchema options 261export function getUserErrOptions({ 262 schema = {}, 263 uiSchema = {}, 264 errorSchema = {} 265}) { 266 return Object.assign({}, ...[schema, uiSchema, errorSchema].map(itemSchema => Object.keys(itemSchema) 267 .reduce((options, key) => { 268 const value = itemSchema[key]; 269 // options 内外合并 270 if (key === 'err:options' && isObject(value)) { 271 return { ...options, ...value }; 272 } 273 274 if (key.indexOf('err:') === 0) { 275 return { ...options, [key.substring(4)]: value }; 276 } 277 278 return options; 279 }, {}))); 280} 281 282// ui:order object-> properties 排序 283export function orderProperties(properties, order) { 284 if (!Array.isArray(order)) { 285 return properties; 286 } 287 288 const arrayToHash = arr => arr.reduce((prev, curr) => { 289 prev[curr] = true; 290 return prev; 291 }, {}); 292 const errorPropList = arr => (arr.length > 1 293 ? `properties '${arr.join("', '")}'` 294 : `property '${arr[0]}'`); 295 const propertyHash = arrayToHash(properties); 296 const orderFiltered = order.filter( 297 prop => prop === '*' || propertyHash[prop] 298 ); 299 const orderHash = arrayToHash(orderFiltered); 300 301 const rest = properties.filter(prop => !orderHash[prop]); 302 const restIndex = orderFiltered.indexOf('*'); 303 if (restIndex === -1) { 304 if (rest.length) { 305 throw new Error( 306 `uiSchema order list does not contain ${errorPropList(rest)}` 307 ); 308 } 309 return orderFiltered; 310 } 311 if (restIndex !== orderFiltered.lastIndexOf('*')) { 312 throw new Error('uiSchema order list contains more than one wildcard item'); 313 } 314 315 const complete = [...orderFiltered]; 316 complete.splice(restIndex, 1, ...rest); 317 return complete; 318} 319 320/** 321 * 单个匹配 322 * 常量,或者只有一个枚举 323 */ 324export function isConstant(schema) { 325 return ( 326 (Array.isArray(schema.enum) && schema.enum.length === 1) 327 || schema.hasOwnProperty('const') 328 ); 329} 330 331export function toConstant(schema) { 332 if (Array.isArray(schema.enum) && schema.enum.length === 1) { 333 return schema.enum[0]; 334 } if (schema.hasOwnProperty('const')) { 335 return schema.const; 336 } 337 throw new Error('schema cannot be inferred as a constant'); 338} 339 340/** 341 * 是否为选择列表 342 * 枚举 或者 oneOf anyOf 每项都只有一个固定常量值 343 * @param _schema 344 * @param rootSchema 345 * @returns {boolean|*} 346 */ 347export function isSelect(_schema, rootSchema = {}) { 348 const schema = retrieveSchema(_schema, rootSchema); 349 const altSchemas = schema.oneOf || schema.anyOf; 350 if (Array.isArray(schema.enum)) { 351 return true; 352 } if (Array.isArray(altSchemas)) { 353 return altSchemas.every(altSchemasItem => isConstant(altSchemasItem)); 354 } 355 return false; 356} 357 358// items 都为一个对象 359export function isFixedItems(schema) { 360 return ( 361 Array.isArray(schema.items) 362 && schema.items.length > 0 363 && schema.items.every(item => isObject(item)) 364 ); 365} 366 367// 是否为多选 368export function isMultiSelect(schema, rootSchema = {}) { 369 if (!schema.uniqueItems || !schema.items) { 370 return false; 371 } 372 return isSelect(schema.items, rootSchema); 373} 374 375// array additionalItems 376// https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation 377export function allowAdditionalItems(schema) { 378 if (schema.additionalItems === true) { 379 console.warn('additionalItems=true is currently not supported'); 380 } 381 return isObject(schema.additionalItems); 382} 383 384// 下拉选项 385export function optionsList(schema, uiSchema, curNodePath, rootFormData) { 386 // enum 387 if (schema.enum) { 388 const uiOptions = getUserUiOptions({ 389 schema, 390 uiSchema, 391 curNodePath, 392 rootFormData 393 }); 394 395 // ui配置 enumNames 优先 396 const enumNames = uiOptions.enumNames || schema.enumNames; 397 return schema.enum.map((value, i) => { 398 const label = (enumNames && enumNames[i]) || String(value); 399 return { label, value }; 400 }); 401 } 402 403 // oneOf | anyOf 404 const altSchemas = schema.oneOf || schema.anyOf; 405 const altUiSchemas = uiSchema.oneOf || uiSchema.anyOf; 406 return altSchemas.map((curSchema, i) => { 407 const uiOptions = (altUiSchemas && altUiSchemas[i]) ? getUserUiOptions({ 408 schema: curSchema, 409 uiSchema: altUiSchemas[i], 410 curNodePath, 411 rootFormData 412 }) : {}; 413 const value = toConstant(curSchema); 414 const label = uiOptions.title || curSchema.title || String(value); 415 return { label, value }; 416 }); 417 418} 419 420export function fallbackLabel(oriLabel, isFallback, curNodePath) { 421 if (oriLabel) return oriLabel; 422 if (isFallback) { 423 const backLabel = curNodePath.split('.').pop(); 424 425 // 过滤纯数字字符串 426 if (backLabel && (backLabel !== `${Number(backLabel)}`)) return backLabel; 427 } 428 429 return ''; 430} 431