Merge branch 'main' of https://github.com/webzard-io/sunmao-ui into feat/custom-widget

This commit is contained in:
MrWindlike 2022-03-07 19:04:23 +08:00
commit 8ad33d6938
15 changed files with 429 additions and 418 deletions

View File

@ -51,7 +51,6 @@ export const ColumnSchema = Type.Object({
export const TablePropsSchema = Type.Object({
data: Type.Array(Type.Any(), {
title: 'Data',
widget: 'core/v1/Expression',
category: Category.Data,
weight: 0,
}),

View File

@ -139,25 +139,27 @@ export const Editor: React.FC<Props> = observer(
const renderMain = () => {
const appBox = (
<Box
id="editor-main"
display="flex"
flexDirection="column"
width="full"
height="full"
overflow="auto"
p={1}
transform={`scale(${scale / 100})`}
position="relative"
>
<EditorMaskWrapper services={services}>
{appComponent}
<Box id={DIALOG_CONTAINER_ID} />
</EditorMaskWrapper>
<Flex flexDirection="column" width="full" height="full">
<Box
id="editor-main"
display="flex"
flexDirection="column"
width="full"
height="full"
overflow="auto"
padding="20px"
transform={`scale(${scale / 100})`}
position="relative"
>
<EditorMaskWrapper services={services}>
{appComponent}
<Box id={DIALOG_CONTAINER_ID} />
</EditorMaskWrapper>
</Box>
<Box id="warning-area" height="48px" position="relative" flex="0 0 auto">
<WarningArea services={services} />
</Box>
</Box>
</Flex>
);
if (codeMode) {

View File

@ -12,9 +12,15 @@ const outlineMaskTextStyle = css`
z-index: 1;
right: 0px;
padding: 0 4px;
font-size: 14px;
font-weight: black;
height: 20px;
right: 0;
max-width: 100%;
color: white;
font-size: 14px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
transform: translateY(-100%);
`;
const outlineMaskStyle = css`

View File

@ -74,7 +74,7 @@ export const EditorMaskWrapper: React.FC<Props> = observer(props => {
width="full"
height="0"
flex="1"
overflow="auto"
overflow="visible"
position="relative"
// some components stop click event propagation, so here we should capture onClick
onClickCapture={onClick}

View File

@ -23,6 +23,7 @@ import ScrollIntoComponentUtilMethod from '../utilMethods/ScrollIntoComponent';
import {
ImplementedRuntimeComponent,
ImplementedRuntimeTraitFactory,
ImplementedRuntimeTrait,
ImplementedRuntimeModule,
UIServices,
@ -34,7 +35,7 @@ export type UtilMethodFactory = () => UtilMethod<any>[];
export type SunmaoLib = {
components?: ImplementedRuntimeComponent<string, string, string, string>[];
traits?: ImplementedRuntimeTrait[];
traits?: ImplementedRuntimeTraitFactory[];
modules?: ImplementedRuntimeModule[];
utilMethods?: UtilMethodFactory[];
};
@ -94,7 +95,7 @@ export class Registry {
return res;
}
registerTrait(t: ImplementedRuntimeTrait) {
registerTrait(t: ImplementedRuntimeTraitFactory) {
if (this.traits.get(t.version)?.has(t.metadata.name)) {
throw new Error(
`Already has trait ${t.version}/${t.metadata.name} in this registry.`
@ -103,7 +104,11 @@ export class Registry {
if (!this.traits.has(t.version)) {
this.traits.set(t.version, new Map());
}
this.traits.get(t.version)?.set(t.metadata.name, t);
const trait = {
...t,
impl: t.factory(),
};
this.traits.get(t.version)?.set(t.metadata.name, trait);
}
getTrait(version: string, name: string): ImplementedRuntimeTrait {

View File

@ -1,75 +1,70 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { TraitImpl } from '../../types';
const HasInitializedMap = new Map<string, boolean>();
import { TraitImplFactory } from '../../types';
type KeyValue = { key: string; value: unknown };
const ArrayStateTrait: TraitImpl<Static<typeof PropsSchema>> = ({
key,
initialValue,
componentId,
mergeState,
subscribeMethods,
services,
}) => {
const hashId = `#${componentId}@${key}`;
const hasInitialized = HasInitializedMap.get(hashId);
const ArrayStateTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
const HasInitializedMap = new Map<string, boolean>();
if (!hasInitialized) {
mergeState({ [key]: initialValue || [] });
return ({ key, initialValue, componentId, mergeState, subscribeMethods, services }) => {
const hashId = `#${componentId}@${key}`;
const hasInitialized = HasInitializedMap.get(hashId);
const methods = {
setArray({ key, value }: KeyValue) {
mergeState({ [key]: value });
},
deleteItemByIndex({ key, index }: { key: string; index: number }) {
const _arr = [...services.stateManager.store[componentId][key]];
_arr.splice(index, 1);
mergeState({ [key]: _arr });
},
deleteItemById({
key,
itemIdKey,
itemId,
}: {
key: string;
itemIdKey: string;
itemId: string;
}) {
const _arr = [...services.stateManager.store[componentId][key]].filter(item => {
return item[itemIdKey] !== itemId;
});
mergeState({ [key]: _arr });
},
pushItem({ item, key }: { key: string; item: any }) {
const _arr = [...services.stateManager.store[componentId][key], item];
mergeState({ [key]: _arr });
},
modifyItemById({
key,
itemIdKey,
itemId,
newItem,
}: {
key: string;
itemIdKey: string;
itemId: string;
newItem: any;
}) {
const _arr = [...services.stateManager.store[componentId][key]];
const index = _arr.findIndex(v => v[itemIdKey] === itemId);
_arr.splice(index, 1, newItem);
mergeState({ [key]: _arr });
},
if (!hasInitialized) {
mergeState({ [key]: initialValue || [] });
const methods = {
setArray({ key, value }: KeyValue) {
mergeState({ [key]: value });
},
deleteItemByIndex({ key, index }: { key: string; index: number }) {
const _arr = [...services.stateManager.store[componentId][key]];
_arr.splice(index, 1);
mergeState({ [key]: _arr });
},
deleteItemById({
key,
itemIdKey,
itemId,
}: {
key: string;
itemIdKey: string;
itemId: string;
}) {
const _arr = [...services.stateManager.store[componentId][key]].filter(item => {
return item[itemIdKey] !== itemId;
});
mergeState({ [key]: _arr });
},
pushItem({ item, key }: { key: string; item: any }) {
const _arr = [...services.stateManager.store[componentId][key], item];
mergeState({ [key]: _arr });
},
modifyItemById({
key,
itemIdKey,
itemId,
newItem,
}: {
key: string;
itemIdKey: string;
itemId: string;
newItem: any;
}) {
const _arr = [...services.stateManager.store[componentId][key]];
const index = _arr.findIndex(v => v[itemIdKey] === itemId);
_arr.splice(index, 1, newItem);
mergeState({ [key]: _arr });
},
};
subscribeMethods(methods);
HasInitializedMap.set(hashId, true);
}
return {
props: null,
};
subscribeMethods(methods);
HasInitializedMap.set(hashId, true);
}
return {
props: null,
};
};
@ -133,5 +128,5 @@ export default {
state: {},
},
}),
impl: ArrayStateTrait,
factory: ArrayStateTraitFactory,
};

View File

@ -1,71 +1,71 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { debounce, throttle, delay } from 'lodash-es';
import { CallbackMap, TraitImpl } from '../../types';
import { CallbackMap, TraitImplFactory } from '../../types';
import { EventHandlerSchema } from '../../types/traitPropertiesSchema';
const PropsSchema = Type.Object({
handlers: Type.Array(EventHandlerSchema),
});
const useEventTrait: TraitImpl<Static<typeof PropsSchema>> = ({
trait,
handlers,
services,
}) => {
const callbackQueueMap: Record<string, Array<() => void>> = {};
// setup current handlers
for (const i in handlers) {
const handler = handlers[i];
const cb = () => {
const rawHandlers = trait.properties.handlers as Static<typeof EventHandlerSchema>[];
// Eval before sending event to assure the handler object is evaled from the latest state.
const evaledHandler = services.stateManager.deepEval(rawHandlers[i]);
const EventTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
return ({ trait, handlers, services }) => {
const callbackQueueMap: Record<string, Array<() => void>> = {};
// setup current handlers
for (const i in handlers) {
const handler = handlers[i];
const cb = () => {
const rawHandlers = trait.properties.handlers as Static<
typeof EventHandlerSchema
>[];
// Eval before sending event to assure the handler object is evaled from the latest state.
const evaledHandler = services.stateManager.deepEval(rawHandlers[i]);
if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') {
return;
if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') {
return;
}
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
};
if (!callbackQueueMap[handler.type]) {
callbackQueueMap[handler.type] = [];
}
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
};
if (!callbackQueueMap[handler.type]) {
callbackQueueMap[handler.type] = [];
}
if (!handler.wait || !handler.wait.time) {
callbackQueueMap[handler.type].push(cb);
} else {
callbackQueueMap[handler.type].push(
handler.wait.type === 'debounce'
? debounce(cb, handler.wait.time)
: handler.wait.type === 'throttle'
? throttle(cb, handler.wait.time)
: handler.wait.type === 'delay'
? () => delay(cb, handler.wait!.time)
: cb
);
}
}
const callbackMap: CallbackMap<string> = {};
for (const eventName in callbackQueueMap) {
callbackMap[eventName] = () => {
if (!callbackQueueMap[eventName]) {
// maybe log?
return;
if (!handler.wait || !handler.wait.time) {
callbackQueueMap[handler.type].push(cb);
} else {
callbackQueueMap[handler.type].push(
handler.wait.type === 'debounce'
? debounce(cb, handler.wait.time)
: handler.wait.type === 'throttle'
? throttle(cb, handler.wait.time)
: handler.wait.type === 'delay'
? () => delay(cb, handler.wait!.time)
: cb
);
}
callbackQueueMap[eventName].forEach(fn => fn());
};
}
}
return {
props: {
callbackMap,
},
const callbackMap: CallbackMap<string> = {};
for (const eventName in callbackQueueMap) {
callbackMap[eventName] = () => {
if (!callbackQueueMap[eventName]) {
// maybe log?
return;
}
callbackQueueMap[eventName].forEach(fn => fn());
};
}
return {
props: {
callbackMap,
},
};
};
};
@ -82,5 +82,5 @@ export default {
state: {},
},
}),
impl: useEventTrait,
factory: EventTraitFactory,
};

View File

@ -1,142 +1,145 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { TraitImpl } from '../../types';
import { TraitImplFactory } from '../../types';
import { FetchTraitPropertiesSchema } from '../../types/traitPropertiesSchema';
const hasFetchedMap = new Map<string, boolean>();
const FetchTraitFactory: TraitImplFactory<Static<typeof FetchTraitPropertiesSchema>> =
() => {
const hasFetchedMap = new Map<string, boolean>();
const useFetchTrait: TraitImpl<Static<typeof FetchTraitPropertiesSchema>> = ({
trait,
url,
method,
lazy: _lazy,
headers: _headers,
body,
mergeState,
services,
subscribeMethods,
componentId,
}) => {
const hashId = `#${componentId}@${'fetch'}`;
const hasFetched = hasFetchedMap.get(hashId);
const lazy = _lazy === undefined ? true : _lazy;
const fetchData = () => {
// TODO: clear when component destory
hasFetchedMap.set(hashId, true);
// FIXME: listen to the header change
const headers = new Headers();
if (_headers) {
for (const key in _headers) {
headers.append(key, _headers[key]);
}
}
mergeState({
fetch: {
loading: true,
data: undefined,
error: undefined,
},
});
// fetch data
fetch(url, {
return ({
trait,
url,
method,
headers,
body: method === 'get' ? undefined : JSON.stringify(body),
}).then(
async response => {
if (response.ok) {
// handle 20x/30x
const data = await response.json();
mergeState({
fetch: {
loading: false,
data,
error: undefined,
},
});
const rawOnComplete = trait.properties.onComplete as Static<
typeof FetchTraitPropertiesSchema
>['onComplete'];
rawOnComplete?.forEach(handler => {
const evaledHandler = services.stateManager.deepEval(handler, false);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
});
} else {
// TODO: Add FetchError class and remove console info
const error = new Error(`HTTP${response.status}: ${response.statusText}`);
console.warn(error);
mergeState({
fetch: {
loading: false,
data: undefined,
error,
},
});
const rawOnError = trait.properties.onError as Static<
typeof FetchTraitPropertiesSchema
>['onError'];
rawOnError?.forEach(handler => {
const evaledHandler = services.stateManager.deepEval(handler, false);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
});
lazy: _lazy,
headers: _headers,
body,
mergeState,
services,
subscribeMethods,
componentId,
}) => {
const hashId = `#${componentId}@${'fetch'}`;
const hasFetched = hasFetchedMap.get(hashId);
const lazy = _lazy === undefined ? true : _lazy;
const fetchData = () => {
// TODO: clear when component destory
hasFetchedMap.set(hashId, true);
// FIXME: listen to the header change
const headers = new Headers();
if (_headers) {
for (const key in _headers) {
headers.append(key, _headers[key]);
}
}
},
async error => {
console.warn(error);
mergeState({
fetch: {
loading: false,
loading: true,
data: undefined,
error: error instanceof Error ? error : new Error(error),
error: undefined,
},
});
const rawOnError = trait.properties.onError as Static<
typeof FetchTraitPropertiesSchema
>['onError'];
rawOnError?.forEach(handler => {
const evaledHandler = services.stateManager.deepEval(handler, false);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
});
// fetch data
fetch(url, {
method,
headers,
body: method === 'get' ? undefined : JSON.stringify(body),
}).then(
async response => {
if (response.ok) {
// handle 20x/30x
const data = await response.json();
mergeState({
fetch: {
loading: false,
data,
error: undefined,
},
});
const rawOnComplete = trait.properties.onComplete as Static<
typeof FetchTraitPropertiesSchema
>['onComplete'];
rawOnComplete?.forEach(handler => {
const evaledHandler = services.stateManager.deepEval(handler, false);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
});
} else {
// TODO: Add FetchError class and remove console info
const error = new Error(`HTTP${response.status}: ${response.statusText}`);
console.warn(error);
mergeState({
fetch: {
loading: false,
data: undefined,
error,
},
});
const rawOnError = trait.properties.onError as Static<
typeof FetchTraitPropertiesSchema
>['onError'];
rawOnError?.forEach(handler => {
const evaledHandler = services.stateManager.deepEval(handler, false);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
});
}
},
async error => {
console.warn(error);
mergeState({
fetch: {
loading: false,
data: undefined,
error: error instanceof Error ? error : new Error(error),
},
});
const rawOnError = trait.properties.onError as Static<
typeof FetchTraitPropertiesSchema
>['onError'];
rawOnError?.forEach(handler => {
const evaledHandler = services.stateManager.deepEval(handler, false);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
});
}
);
};
// non lazy query, listen to the change and query;
if (!lazy && url && !hasFetched) {
fetchData();
}
);
};
// non lazy query, listen to the change and query;
if (!lazy && url && !hasFetched) {
fetchData();
}
subscribeMethods({
triggerFetch() {
fetchData();
},
});
return {
props: {
effects: [
() => {
hasFetchedMap.set(hashId, false);
subscribeMethods({
triggerFetch() {
fetchData();
},
],
},
});
return {
props: {
effects: [
() => {
hasFetchedMap.set(hashId, false);
},
],
},
};
};
};
};
export default {
...createTrait({
@ -161,5 +164,5 @@ export default {
],
},
}),
impl: useFetchTrait,
factory: FetchTraitFactory,
};

View File

@ -1,24 +1,23 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { TraitImpl } from '../../types';
import { TraitImplFactory } from '../../types';
const useHiddenTrait: TraitImpl<Static<typeof PropsSchema>> = ({
hidden,
visually,
}) => {
if (visually) {
return {
props: {
customStyle: {
content: hidden ? 'display: none' : '',
const HiddenTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
return ({ hidden, visually }) => {
if (visually) {
return {
props: {
customStyle: {
content: hidden ? 'display: none' : '',
},
},
},
};
}
};
}
return {
props: {},
unmount: hidden,
return {
props: {},
unmount: hidden,
};
};
};
@ -40,5 +39,5 @@ export default {
methods: [],
},
}),
impl: useHiddenTrait,
factory: HiddenTraitFactory,
};

View File

@ -1,7 +1,7 @@
import { Static, Type } from '@sinclair/typebox';
import { createTrait } from '@sunmao-ui/core';
import { isEqual } from 'lodash';
import { TraitImpl } from 'src/types';
import { TraitImplFactory } from '../../types';
function getLocalStorageValue(key: string) {
try {
@ -15,43 +15,40 @@ function getLocalStorageValue(key: string) {
}
}
const HasInitializedMap = new Map<string, boolean>();
const PrevValueCache: Record<string, unknown> = {};
const PropsSchema = Type.Object({
key: Type.String(),
value: Type.Any(),
});
const LocalStorageTraitImpl: TraitImpl<Static<typeof PropsSchema>> = ({
key,
value,
componentId,
mergeState,
}) => {
const hashId = `#${componentId}@${key}`;
const hasInitialized = HasInitializedMap.get(hashId);
const LocalStorageTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
const HasInitializedMap = new Map<string, boolean>();
const PrevValueCache: Record<string, unknown> = {};
if (key) {
if (!hasInitialized) {
PrevValueCache[key] = getLocalStorageValue(hashId);
mergeState({
[key]: PrevValueCache[key],
});
HasInitializedMap.set(hashId, true);
} else {
if (!isEqual(PrevValueCache[key], value)) {
PrevValueCache[key] = value;
return ({ key, value, componentId, mergeState }) => {
const hashId = `#${componentId}@${key}`;
const hasInitialized = HasInitializedMap.get(hashId);
if (key) {
if (!hasInitialized) {
PrevValueCache[key] = getLocalStorageValue(hashId);
mergeState({
[key]: value,
[key]: PrevValueCache[key],
});
localStorage.setItem(hashId, JSON.stringify(value));
HasInitializedMap.set(hashId, true);
} else {
if (!isEqual(PrevValueCache[key], value)) {
PrevValueCache[key] = value;
mergeState({
[key]: value,
});
localStorage.setItem(hashId, JSON.stringify(value));
}
}
}
}
return {
props: null,
return {
props: null,
};
};
};
@ -68,5 +65,5 @@ export default {
methods: [],
},
}),
impl: LocalStorageTraitImpl,
factory: LocalStorageTraitFactory,
};

View File

@ -23,7 +23,7 @@ export default {
methods: [],
},
}),
impl: () => ({
factory: () => () => ({
props: null,
}),
};

View File

@ -1,38 +1,34 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { TraitImpl } from '../../types';
const HasInitializedMap = new Map<string, boolean>();
import { TraitImplFactory } from '../../types';
type KeyValue = { key: string; value: unknown };
const useStateTrait: TraitImpl<Static<typeof PropsSchema>> = ({
key,
initialValue,
componentId,
mergeState,
subscribeMethods,
}) => {
const hashId = `#${componentId}@${key}`;
const hasInitialized = HasInitializedMap.get(hashId);
const StateTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
const HasInitializedMap = new Map<string, boolean>();
if (!hasInitialized) {
mergeState({ [key]: initialValue });
return ({ key, initialValue, componentId, mergeState, subscribeMethods }) => {
const hashId = `#${componentId}@${key}`;
const hasInitialized = HasInitializedMap.get(hashId);
const methods = {
setValue({ key, value }: KeyValue) {
mergeState({ [key]: value });
},
resetValue({ key }: KeyValue) {
mergeState({ [key]: initialValue });
},
if (!hasInitialized) {
mergeState({ [key]: initialValue });
const methods = {
setValue({ key, value }: KeyValue) {
mergeState({ [key]: value });
},
resetValue({ key }: KeyValue) {
mergeState({ [key]: initialValue });
},
};
subscribeMethods(methods);
HasInitializedMap.set(hashId, true);
}
return {
props: null,
};
subscribeMethods(methods);
HasInitializedMap.set(hashId, true);
}
return {
props: null,
};
};
@ -65,5 +61,5 @@ export default {
],
},
}),
impl: useStateTrait,
factory: StateTraitFactory,
};

View File

@ -1,16 +1,18 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { TraitImpl } from '../../types';
import { TraitImplFactory } from '../../types';
const StyleTrait: TraitImpl<Static<typeof PropsSchema>> = ({ styles }) => {
const customStyle: Record<string, string> = {};
styles.forEach(style => {
customStyle[style.styleSlot] = style.style;
});
return {
props: {
customStyle,
},
const StyleTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
return ({ styles }) => {
const customStyle: Record<string, string> = {};
styles.forEach(style => {
customStyle[style.styleSlot] = style.style;
});
return {
props: {
customStyle,
},
};
};
};
@ -36,5 +38,5 @@ export default {
state: {},
},
}),
impl: StyleTrait,
factory: StyleTraitFactory,
};

View File

@ -1,86 +1,87 @@
import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { isEqual } from 'lodash-es';
import { TraitImpl } from '../../types';
import { TraitImplFactory } from '../../types';
import { ValidResultSchema } from '../../types/validResultSchema';
type ValidationResult = Static<typeof ValidResultSchema>;
type ValidationRule = (text: string) => ValidationResult;
const rules = new Map<string, ValidationRule>();
const ValidationTraitFactory: TraitImplFactory<Static<typeof PropsSchema>> = () => {
const rules = new Map<string, ValidationRule>();
export function addValidationRule(name: string, rule: ValidationRule) {
rules.set(name, rule);
}
function addValidationRule(name: string, rule: ValidationRule) {
rules.set(name, rule);
}
addValidationRule('email', text => {
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(text)) {
return {
addValidationRule('email', text => {
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(text)) {
return {
isInvalid: false,
errorMsg: '',
};
} else {
return {
isInvalid: true,
errorMsg: '请输入正确的 email',
};
}
});
addValidationRule('phoneNumber', text => {
if (/^1[3456789]\d{9}$/.test(text)) {
return {
isInvalid: false,
errorMsg: '',
};
} else {
return {
isInvalid: true,
errorMsg: '请输入正确的手机号码',
};
}
});
const ValidationResultCache: Record<string, ValidationResult> = {};
return props => {
const { value, minLength, maxLength, mergeState, componentId, rule } = props;
const result: ValidationResult = {
isInvalid: false,
errorMsg: '',
};
} else {
return {
isInvalid: true,
errorMsg: '请输入正确的 email',
};
}
});
addValidationRule('phoneNumber', text => {
if (/^1[3456789]\d{9}$/.test(text)) {
return {
isInvalid: false,
errorMsg: '',
};
} else {
return {
isInvalid: true,
errorMsg: '请输入正确的手机号码',
};
}
});
const ValidationResultCache: Record<string, ValidationResult> = {};
const ValidationTraitImpl: TraitImpl<Static<typeof PropsSchema>> = props => {
const { value, minLength, maxLength, mergeState, componentId, rule } = props;
const result: ValidationResult = {
isInvalid: false,
errorMsg: '',
};
if (maxLength !== undefined && value.length > maxLength) {
result.isInvalid = true;
result.errorMsg = `最长不能超过${maxLength}个字符`;
} else if (minLength !== undefined && value.length < minLength) {
result.isInvalid = true;
result.errorMsg = `不能少于${minLength}个字符`;
} else {
const rulesArr = rule ? rule.split(',') : [];
for (const ruleName of rulesArr) {
const validateFunc = rules.get(ruleName);
if (validateFunc) {
const { isInvalid, errorMsg } = validateFunc(value);
if (isInvalid) {
result.isInvalid = true;
result.errorMsg = errorMsg;
break;
if (maxLength !== undefined && value.length > maxLength) {
result.isInvalid = true;
result.errorMsg = `最长不能超过${maxLength}个字符`;
} else if (minLength !== undefined && value.length < minLength) {
result.isInvalid = true;
result.errorMsg = `不能少于${minLength}个字符`;
} else {
const rulesArr = rule ? rule.split(',') : [];
for (const ruleName of rulesArr) {
const validateFunc = rules.get(ruleName);
if (validateFunc) {
const { isInvalid, errorMsg } = validateFunc(value);
if (isInvalid) {
result.isInvalid = true;
result.errorMsg = errorMsg;
break;
}
}
}
}
}
if (!isEqual(result, ValidationResultCache[componentId])) {
ValidationResultCache[componentId] = result;
mergeState({
validResult: result,
});
}
if (!isEqual(result, ValidationResultCache[componentId])) {
ValidationResultCache[componentId] = result;
mergeState({
validResult: result,
});
}
return {
props: null,
return {
props: null,
};
};
};
@ -106,5 +107,5 @@ export default {
methods: [],
},
}),
impl: ValidationTraitImpl,
factory: ValidationTraitFactory,
};

View File

@ -21,6 +21,12 @@ export type TraitImpl<T = any> = (
}
) => TraitResult<string, string>;
export type TraitImplFactory<T = any> = () => TraitImpl<T>;
export type ImplementedRuntimeTraitFactory = RuntimeTrait & {
factory: TraitImplFactory;
};
export type ImplementedRuntimeTrait = RuntimeTrait & {
impl: TraitImpl;
};