/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2025 Tencent. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import { cloneDeep } from 'lodash-es'; import { getValueByKeyPath } from '@tmagic/utils'; import { ChildConfig, ContainerCommonConfig, DaterangeConfig, FilterFunction, FormConfig, FormState, FormValue, HtmlField, Rule, SortProp, TabPaneConfig, TypeFunction, } from '../schema'; interface DefaultItem { defaultValue: any; type: string; filter: string; multiple: boolean; } const isTableSelect = (type?: string | TypeFunction) => typeof type === 'string' && ['table-select', 'tableSelect'].includes(type); const asyncLoadConfig = (value: FormValue, initValue: FormValue, { asyncLoad, name, type }: HtmlField) => { // 富文本配置了异步加载 if (type === 'html' && typeof asyncLoad === 'object' && typeof name !== 'undefined') { asyncLoad.name = name; value.asyncLoad = typeof initValue.asyncLoad === 'object' ? initValue.asyncLoad : asyncLoad; } }; const isMultipleValue = (type?: string | TypeFunction) => typeof type === 'string' && ['checkbox-group', 'checkboxGroup', 'table', 'cascader', 'group-list', 'groupList'].includes(type); const initItemsValue = ( mForm: FormState | undefined, value: FormValue, initValue: FormValue, { items, name, extensible }: any, ) => { if (Array.isArray(initValue[name])) { value[name] = initValue[name].map((v: any, index: number) => createValues(mForm, items, v, value[name]?.[index])); } else { value[name] = createValues(mForm, items, initValue[name], value[name]); if (extensible) { value[name] = Object.assign({}, initValue[name], value[name]); } } }; const setValue = (mForm: FormState | undefined, value: FormValue, initValue: FormValue, item: any) => { const { items, name, type, checkbox } = item; // 值是数组, 有可能也有items配置,所以不能放到getDefaultValue里赋值 if (isMultipleValue(type) || (type === 'tab' && item.dynamic)) { value[name] = initValue[name] || []; } // 有子项继续递归,没有的话有初始值用初始值,没有初始值用默认值 if (items) { initItemsValue(mForm, value, initValue, item); } else { value[name] = getDefaultValue(mForm, item as DefaultItem); } // 如果fieldset配置checkbox,checkbox的值保存在value中 if (type === 'fieldset' && checkbox) { const checkboxName = typeof checkbox === 'object' && typeof checkbox.name === 'string' ? checkbox.name : 'value'; const checkboxFalseValue = typeof checkbox === 'object' && typeof checkbox.falseValue !== 'undefined' ? checkbox.falseValue : 0; if (name && typeof value[name] === 'object') { value[name][checkboxName] = typeof initValue[name] === 'object' ? initValue[name][checkboxName] || checkboxFalseValue : checkboxFalseValue; } } }; const initValueItem = function ( mForm: FormState | undefined, item: ChildConfig | TabPaneConfig, initValue: FormValue, value: FormValue, ) { const { items } = item as ContainerCommonConfig; const { names } = item as DaterangeConfig; const { type, name } = item as ChildConfig; if (isTableSelect(type) && name) { value[name] = initValue[name] || ''; return value; } asyncLoadConfig(value, initValue, item as HtmlField); // 这种情况比较多,提前结束 if (name && !items && typeof initValue?.[name] !== 'undefined') { if (typeof value[name] === 'undefined') { if (type === 'number') { value[name] = Number(initValue[name]); } else { value[name] = typeof initValue[name] === 'object' ? initValue[name] : initValue[name]; } } return value; } if (names) { return names.forEach((n: string) => (value[n] = initValue[n] || '')); } if (!name) { // 没有配置name,直接跳过 return createValues(mForm, items, initValue, value); } setValue(mForm, value, initValue, item); if (type === 'table') { if (item.defautSort) { sortChange(value[name], item.defautSort); } else if (item.defaultSort) { sortChange(value[name], item.defaultSort); } if (item.sort && item.sortKey) { value[name].sort((a: any, b: any) => b[item.sortKey] - a[item.sortKey]); } } return value; }; export const createValues = function ( mForm: FormState | undefined, config: FormConfig | TabPaneConfig[] = [], initValue: FormValue = {}, value: FormValue = {}, ) { if (Array.isArray(config)) { config.forEach((item: ChildConfig | TabPaneConfig) => { initValueItem(mForm, item, initValue, value); }); } return value; }; const getDefaultValue = function (mForm: FormState | undefined, { defaultValue, type, filter, multiple }: DefaultItem) { if (typeof defaultValue === 'function') { return defaultValue(mForm); } // 如果直接设置为undefined,在解析成js对象时会丢失这个配置,所以用'undefined'代替 if (defaultValue === 'undefined') { return undefined; } if (typeof defaultValue !== 'undefined') { return defaultValue; } if (type === 'number' || filter === 'number') { return 0; } if (['switch', 'checkbox'].includes(type)) { return false; } if (multiple || type === 'number-range') { return []; } return ''; }; export const filterFunction = ( mForm: FormState | undefined, config: T | FilterFunction | undefined, props: any, ) => { if (typeof config === 'function') { return (config as FilterFunction)(mForm, { values: mForm?.initValues || {}, model: props.model, parent: mForm?.parentValues || {}, formValue: mForm?.values || props.model, prop: props.prop, config: props.config, index: props.index, getFormValue: (prop: string) => getValueByKeyPath(prop, mForm?.values || props.model), }); } return config; }; export const display = function (mForm: FormState | undefined, config: any, props: any) { if (config === 'expand') { return config; } if (typeof config === 'function') { return filterFunction(mForm, config, props); } if (config === false) { return false; } return true; }; export const getRules = function (mForm: FormState | undefined, rules: Rule[] | Rule = [], props: any) { rules = cloneDeep(rules); if (typeof rules === 'object' && !Array.isArray(rules)) { rules = [rules]; } return rules.map((item) => { if (typeof item.validator === 'function') { const fnc = item.validator; (item as any).validator = (rule: any, value: any, callback: Function, source: any, options: any) => fnc( { rule, value: props.config.names ? props.model : value, callback, source, options, }, { values: mForm?.initValues || {}, model: props.model, parent: mForm?.parentValues || {}, formValue: mForm?.values || props.model, prop: props.prop, config: props.config, }, mForm, ); } return item; }); }; export const initValue = async ( mForm: FormState | undefined, { initValues, config }: { initValues: FormValue; config: FormConfig }, ) => { if (!Array.isArray(config)) throw new Error('config应该为数组'); const initValuesCopy = cloneDeep(initValues); let valuesTmp = createValues(mForm, config, initValuesCopy, {}); const [firstForm] = config as [ContainerCommonConfig]; if (firstForm && typeof firstForm.onInitValue === 'function') { valuesTmp = await firstForm.onInitValue(mForm, { formValue: valuesTmp, initValue: initValuesCopy, }); } return valuesTmp || {}; }; export const datetimeFormatter = ( v: string | Date, defaultValue = '-', format = 'YYYY-MM-DD HH:mm:ss', ): string | number => { if (v) { let time: string | number; if (['x', 'timestamp'].includes(format)) { time = dayjs(Number.isNaN(Number(v)) ? v : Number(v)).valueOf(); } else if ((typeof v === 'string' && v.includes('Z')) || v.constructor === Date) { dayjs.extend(utc); // UTC字符串时间或Date对象格式化为北京时间 time = dayjs(v).utcOffset(8).format(format); } else { time = dayjs(v).format(format); } if (time !== 'Invalid Date') { return time; } return defaultValue; } return defaultValue; }; export const getDataByPage = (data: any[] = [], pagecontext: number, pagesize: number) => data.filter( (item: any, index: number) => index >= pagecontext * pagesize && index + 1 <= (pagecontext + 1) * pagesize, ); export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortKey?: string) => { if (newIndex === oldIndex) { return data; } if (newIndex < 0 || newIndex >= data.length || oldIndex < 0 || oldIndex >= data.length) { return data; } const newData = data.toSpliced(newIndex, 0, ...data.splice(oldIndex, 1)); if (sortKey) { for (let i = newData.length - 1, v = 0; i >= 0; i--, v++) { newData[v][sortKey] = i; } } return cloneDeep(newData); }; export const sortChange = (data: any[], { prop, order }: SortProp) => { if (order === 'ascending') { data = data.sort((a: any, b: any) => a[prop] - b[prop]); } else if (order === 'descending') { data = data.sort((a: any, b: any) => b[prop] - a[prop]); } };