mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-12-15 05:36:31 +00:00
355 lines
9.9 KiB
TypeScript
355 lines
9.9 KiB
TypeScript
/*
|
||
* 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 = <T = any>(
|
||
mForm: FormState | undefined,
|
||
config: T | FilterFunction<T> | undefined,
|
||
props: any,
|
||
) => {
|
||
if (typeof config === 'function') {
|
||
return (config as FilterFunction<T>)(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]);
|
||
}
|
||
};
|