import { type ActiveRecord, CommonPatternMap, type FieldPattern, isRelation2OField, type RuntimeModelField, RuntimeRelationField, SubmitHandler, SubmitRelationHandler, SubmitRelationValue, SubmitValue, translateValueByKey } from '@oinone/kunlun-engine'; import { type FieldEventName, FieldEventNames, LifeCycleHeart, LifeCycleTypes } from '@oinone/kunlun-event'; import { Expression } from '@oinone/kunlun-expression'; import { isEmptyValue, isValidateEmpty, ModelFieldType, ViewType } from '@oinone/kunlun-meta'; import type { Constructor, ReturnPromise } from '@oinone/kunlun-shared'; import { SPI, type SPIOptions, type SPISingleSelector, type SPITokenFactory } from '@oinone/kunlun-spi'; import { ComputeTrigger } from '@oinone/kunlun-vue-ui-common'; import { InnerWidgetType, PathWidget, Widget } from '@oinone/kunlun-vue-widget'; import { isEmpty, isFunction, isPlainObject, isString } from 'lodash-es'; import { isValidatorError, isValidatorSuccess, type ValidatorInfo } from '../../typing'; import { BaseFormItemWidget, type BaseFormItemWidgetProps } from '../form-item'; /** * Field组件注册可选项 */ export interface BaseFieldOptions extends SPIOptions { /** * 指定视图类型 */ viewType?: ViewType | ViewType[]; /** * 指定组件名称或别称 */ widget?: string | string[]; /** * 指定字段业务类型 */ ttype?: ModelFieldType | ModelFieldType[]; /** * 指定是否多值 */ multi?: boolean; /** * 指定模型 */ model?: string | string[]; /** * 指定视图名称 */ viewName?: string | string[]; /** * 指定字段名称 */ name?: string; } export interface BaseFieldProps extends BaseFormItemWidgetProps { field?: Field; } export type HandlerEvent = (field: BaseFieldWidget) => void; /** * 单字段组件 */ @SPI.Base(Symbol('Field'), ['viewType', 'ttype', 'multi', { key: 'widget', weight: 999 }, 'model', 'viewName', 'name']) export class BaseFieldWidget< Value = unknown, Field extends RuntimeModelField = RuntimeModelField, Props extends BaseFieldProps = BaseFieldProps > extends BaseFormItemWidget { protected $$innerWidgetType = InnerWidgetType.Field; public static Token: SPITokenFactory; public static Selector: SPISingleSelector>; private runtimeField: Field | undefined; @Widget.Reactive() public get field(): Field { const field = this.runtimeField; if (!field) { throw new Error('Invalid field define.'); } return field; } @Widget.Reactive() @Widget.Inject() protected rowIndex: number | undefined; @Widget.Reactive() public get label(): string | undefined { const label = super.label; if ( isEmpty(label) && (Expression.isExpressionStr(this.getDsl().label! as string) || Expression.hasKeywords(this.getDsl().label! as string)) ) { // 有表达式,且计算结果为空的时候需要显示空 return ''; } return super.label || this.field.label || this.field.displayName; } @Widget.Reactive() public get required(): boolean { if (this.viewType === ViewType.Detail) { return false; } return super.required; } @Widget.Reactive() public get readonly(): boolean { if (this.viewType === ViewType.Search) { return false; } if (this.viewType === ViewType.Detail) { return true; } return super.readonly; } public initialize(props: Props) { super.initialize(props); this.runtimeField = props.field; return this; } protected get isSkipValidator(): boolean { if (this.viewType === ViewType.Search) { return true; } return super.isSkipValidator; } /** * 具体校验逻辑 * @param needCheckedValue */ protected async validatorSpecific(needCheckedValue: any): Promise { if (this.isSkipValidator) { return this.validatorSkip(); } if (this.required) { if (isValidateEmpty(needCheckedValue)) { return this.validatorError(); } } // 正则校验 const xmlPatternType = this.getDsl().patternType as string; const patternType = this.executeExpression(xmlPatternType); let { pattern, tips = '' } = this.getDsl(); pattern = this.executeExpression(pattern as string, pattern); if (!isValidateEmpty(patternType)) { const realType = patternType as string; const patternObj = CommonPatternMap.get(realType) as FieldPattern; if (!isEmpty(patternObj)) { pattern = patternObj.pattern; tips = patternObj.errorMsg; } } if (pattern && !isEmptyValue(needCheckedValue)) { let reg: RegExp; const matchedCompleteRegExpStr = pattern.match(/^(\/.*\/)(.*)?$/); // 普通字符串 if (!matchedCompleteRegExpStr) { reg = new RegExp(pattern); } else { const regExpStr = matchedCompleteRegExpStr[1]; const flags = matchedCompleteRegExpStr[2]; // 符合正则表达式语法的字符串,如:`/^[0-9]$/ig` reg = new RegExp(regExpStr.slice(1, -1), flags); } if (!reg.test(needCheckedValue as string)) { return this.validatorError(this.executeExpression(tips, tips) || translateValueByKey('校验失败')); } } return super.validator(); } @Widget.Method() public change(val: Value | null | undefined) { super.change(val); if (isRelation2OField(this.field) && (val == null || isPlainObject(val))) { this.updateX2OValue(val as ActiveRecord).then(() => { this.notify(LifeCycleTypes.ON_FIELD_CHANGE); }); } else { this.notify(LifeCycleTypes.ON_FIELD_CHANGE); } } protected async updateX2OValue(selectedValue: ActiveRecord | null | undefined) { const submitValue = new SubmitValue({ [this.itemData]: selectedValue }); const { field, itemName, viewMode, submitCache, submitType, relationUpdateType } = this; const updateValue = await SubmitRelationHandler.M2O( field as unknown as RuntimeRelationField, itemName, submitValue, selectedValue, viewMode, submitCache, submitType, relationUpdateType ); if (updateValue instanceof SubmitRelationValue) { return; } Object.assign(this.formData, updateValue); } @Widget.Method() public focus() { super.focus(); this.notify(LifeCycleTypes.ON_FIELD_FOCUS); } @Widget.Method() public blur() { super.blur(); this.notify(LifeCycleTypes.ON_FIELD_BLUR); } public async executeValidator(): Promise { this.notify(LifeCycleTypes.ON_FIELD_VALIDATE_START); const result = await super.executeValidator(); if (isValidatorSuccess(result)) { this.notify(LifeCycleTypes.ON_FIELD_VALIDATE_SUCCESS); } else if (isValidatorError(result)) { this.notify(LifeCycleTypes.ON_FIELD_VALIDATE_FAILED); } this.notify(LifeCycleTypes.ON_FIELD_VALIDATE_END); return result; } public submit(submitValue: SubmitValue): ReturnPromise | SubmitRelationValue | undefined> { return SubmitHandler.DEFAULT(this.field, this.itemName, submitValue, this.value); } public validator(): Promise { return this.validatorSpecific(this.getValue()); } protected defaultComputeTrigger: ComputeTrigger[] = [ComputeTrigger.BLUR]; @Widget.Reactive() public get computeTrigger(): ComputeTrigger[] { const { computeTrigger } = this.getDsl(); if (!isEmpty(computeTrigger) && isString(computeTrigger)) { return computeTrigger.split(',').map((v) => v.trim().toLowerCase() as ComputeTrigger); } return this.defaultComputeTrigger; } public executeCompute(trigger: ComputeTrigger) { // fixme @zbh 20231125 表达式运行时计算 // if (!this.computeTrigger.includes(trigger)) { // return; // } // const formData = this.formData || {}; // this.rootComputeContext?.compute( // { // activeRecords: [formData], // rootRecord: this.rootData?.[0] || {}, // openerRecord: this.openerActiveRecords?.[0] || {}, // scene: this.scene, // activeRecord: formData // }, // this.field // ); } protected $$beforeCreated() { super.$$beforeCreated(); this.notify(LifeCycleTypes.ON_FIELD_BEFORE_CREATED); } protected $$created() { super.$$created(); this.notify(LifeCycleTypes.ON_FIELD_CREATED); } protected $$beforeMount() { super.$$beforeMount(); this.notify(LifeCycleTypes.ON_FIELD_BEFORE_MOUNT); } protected $$mounted() { super.$$mounted(); this.viewState?.pushField(this.currentHandle, this.rowIndex); this.fieldWidgetMounted?.(this); this.notify(LifeCycleTypes.ON_FIELD_MOUNTED); } protected $$beforeUpdate() { super.$$beforeUpdate(); this.notify(LifeCycleTypes.ON_FIELD_BEFORE_UPDATE); } protected $$updated() { super.$$updated(); this.notify(LifeCycleTypes.ON_FIELD_UPDATED); } protected $$beforeUnmount() { super.$$beforeUnmount(); this.notify(LifeCycleTypes.ON_FIELD_BEFORE_UNMOUNT); } protected $$unmounted() { super.$$unmounted(); this.viewState?.popField(this.currentHandle, this.rowIndex); this.fieldWidgetUnmounted?.(this); this.notify(LifeCycleTypes.ON_FIELD_UNMOUNTED); } /** * @deprecated widget finder please this.viewState.fields */ @Widget.Method() @Widget.Inject() protected fieldWidgetMounted: ((widget: PathWidget) => void) | undefined; /** * @deprecated widget finder please this.viewState.fields */ @Widget.Method() @Widget.Inject() protected fieldWidgetUnmounted: ((widget: PathWidget) => void) | undefined; private ownEventListeners: Record> = {} as any; /** * 监听字段的事件 * @param event 事件名 / 事件对象 * @param handler? 回调函数 * * @example * * 单个事件监听 * fieldWidget.on('change', (fieldInstance) => {}) * fieldWidget.on('blur', (fieldInstance) => {}) * */ public on(event: { [key in FieldEventName]?: HandlerEvent }): void; /** * 多个事件监听 * * @example * * fieldWidget.on({ * change(fieldInstance) => {}, * blur(fieldInstance) => {}, * }) */ public on(event: FieldEventName, handler: HandlerEvent): void; public on(event: FieldEventName | { [key in FieldEventName]?: HandlerEvent }, handler?: HandlerEvent) { let handlers: { [key in FieldEventName]?: HandlerEvent }; if (isString(event)) { if (!isFunction(handler)) { return; } handlers = { [event]: handler }; } else { if (!isPlainObject(event)) { return; } handlers = event; } Object.entries(handlers).forEach(([key, h]) => { const value = FieldEventNames[key]; if (value) { if (this.ownEventListeners[value]) { this.ownEventListeners[value].push(h); } this.ownEventListeners[value] = [h]; } }); } protected notify(type: LifeCycleTypes) { try { // 字段内部监听 if (this.ownEventListeners[type]) { this.ownEventListeners[type].forEach((h) => h(this)); } const { view, field } = this; if (field?.name && view?.name) { // 全局生命周期监听 LifeCycleHeart.publish(type, `${view.name}:${field.name}`, this); } } catch (error) { console.error(error); } } }