feat: 三聪头相关移动端支持逻辑 (#798)
Co-authored-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#798 Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
@ -15,6 +15,7 @@ import { isPatternDisabled } from '../../../../schema-settings';
|
|||||||
import { ActionType } from '../../../../schema-settings/LinkageRules/type';
|
import { ActionType } from '../../../../schema-settings/LinkageRules/type';
|
||||||
import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue';
|
import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue';
|
||||||
import { useIsAllowToSetDefaultValue } from '../../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
import { useIsAllowToSetDefaultValue } from '../../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
|
import { css } from '@nocobase/client';
|
||||||
|
|
||||||
export const fieldSettingsFormItem = new SchemaSettings({
|
export const fieldSettingsFormItem = new SchemaSettings({
|
||||||
name: 'fieldSettings:FormItem',
|
name: 'fieldSettings:FormItem',
|
||||||
@ -223,6 +224,47 @@ export const fieldSettingsFormItem = new SchemaSettings({
|
|||||||
},
|
},
|
||||||
Component: SchemaSettingsDefaultValue,
|
Component: SchemaSettingsDefaultValue,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'layoutDirection',
|
||||||
|
type: 'select',
|
||||||
|
useComponentProps() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const initialValue = fieldSchema['x-decorator-props']?.layoutDirection ?? 'column';
|
||||||
|
return {
|
||||||
|
title: t('Layout Direction'),
|
||||||
|
options: [
|
||||||
|
{ label: t('Row'), value: 'row' },
|
||||||
|
{ label: t('Column'), value: 'column' },
|
||||||
|
],
|
||||||
|
value: initialValue,
|
||||||
|
onChange(v) {
|
||||||
|
const schema: ISchema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const styleValue = {
|
||||||
|
layoutDirection: v ?? 'column',
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: `${v === 'row' ? 'row' : 'column'}`,
|
||||||
|
alignItems: 'baseline',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
_.set(fieldSchema, 'x-decorator-props', styleValue);
|
||||||
|
_.set(schema, 'x-decorator-props', styleValue);
|
||||||
|
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
dn.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'pattern',
|
name: 'pattern',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
@ -0,0 +1,173 @@
|
|||||||
|
import { useLockFn, useThrottleFn } from 'ahooks';
|
||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
// import { useConfig } from '../config-provider';
|
||||||
|
// import DotLoading from '../dot-loading';
|
||||||
|
import { NativeProps, getScrollParent, mergeProps, withNativeProps } from './utils';
|
||||||
|
|
||||||
|
// TODO: 待继续
|
||||||
|
const useConfig = () => {
|
||||||
|
return {
|
||||||
|
locale: {
|
||||||
|
InfiniteScroll: {
|
||||||
|
retry: false,
|
||||||
|
noMore: false,
|
||||||
|
failedToLoad: '范德萨发',
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 待继续
|
||||||
|
const DotLoading = () => null;
|
||||||
|
|
||||||
|
function isWindow(element: any | Window): element is Window {
|
||||||
|
return element === window;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classPrefix = `adm-infinite-scroll`;
|
||||||
|
|
||||||
|
export type InfiniteScrollProps = {
|
||||||
|
loadMore: (isRetry: boolean) => Promise<void>;
|
||||||
|
hasMore: boolean;
|
||||||
|
threshold?: number;
|
||||||
|
children?: ReactNode | ((hasMore: boolean, failed: boolean, retry: () => void) => ReactNode);
|
||||||
|
} & NativeProps;
|
||||||
|
|
||||||
|
const defaultProps: Required<Pick<InfiniteScrollProps, 'threshold' | 'children'>> = {
|
||||||
|
threshold: 250,
|
||||||
|
children: (hasMore: boolean, failed: boolean, retry: () => void) => (
|
||||||
|
<InfiniteScrollContent hasMore={hasMore} failed={failed} retry={retry} />
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InfiniteScroll: FC<InfiniteScrollProps> = (p) => {
|
||||||
|
const props = mergeProps(defaultProps, p);
|
||||||
|
|
||||||
|
const [failed, setFailed] = useState(false);
|
||||||
|
const doLoadMore = useLockFn(async (isRetry: boolean) => {
|
||||||
|
try {
|
||||||
|
await props.loadMore(isRetry);
|
||||||
|
} catch (e) {
|
||||||
|
setFailed(true);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Prevent duplicated trigger of `check` function
|
||||||
|
const [flag, setFlag] = useState({});
|
||||||
|
const nextFlagRef = useRef(flag);
|
||||||
|
|
||||||
|
const [scrollParent, setScrollParent] = useState<Window | Element | null | undefined>();
|
||||||
|
|
||||||
|
const { run: check } = useThrottleFn(
|
||||||
|
async () => {
|
||||||
|
if (nextFlagRef.current !== flag) return;
|
||||||
|
if (!props.hasMore) return;
|
||||||
|
const element = elementRef.current;
|
||||||
|
if (!element) return;
|
||||||
|
if (!element.offsetParent) return;
|
||||||
|
const parent = getScrollParent(element);
|
||||||
|
setScrollParent(parent);
|
||||||
|
if (!parent) return;
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const elementTop = rect.top;
|
||||||
|
const current = isWindow(parent) ? window.innerHeight : parent.getBoundingClientRect().bottom;
|
||||||
|
if (current >= elementTop - props.threshold) {
|
||||||
|
const nextFlag = {};
|
||||||
|
nextFlagRef.current = nextFlag;
|
||||||
|
try {
|
||||||
|
await doLoadMore(false);
|
||||||
|
setFlag(nextFlag);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('%c Line:88 🍯 e', 'font-size:18px;color:#2eafb0;background:#3f7cff', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wait: 100,
|
||||||
|
leading: true,
|
||||||
|
trailing: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure to trigger `loadMore` when content changes
|
||||||
|
useEffect(() => {
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = elementRef.current;
|
||||||
|
if (!element) return;
|
||||||
|
if (!scrollParent) return;
|
||||||
|
function onScroll() {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
scrollParent.addEventListener('scroll', onScroll);
|
||||||
|
return () => {
|
||||||
|
scrollParent.removeEventListener('scroll', onScroll);
|
||||||
|
};
|
||||||
|
}, [scrollParent]);
|
||||||
|
|
||||||
|
async function retry() {
|
||||||
|
setFailed(false);
|
||||||
|
try {
|
||||||
|
await doLoadMore(true);
|
||||||
|
setFlag(nextFlagRef.current);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('%c Line:121 🥥 e', 'font-size:18px;color:#3f7cff;background:#2eafb0', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return withNativeProps(
|
||||||
|
props,
|
||||||
|
<div className={classPrefix} ref={elementRef}>
|
||||||
|
{typeof props.children === 'function' ? props.children(props.hasMore, failed, retry) : props.children}
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const InfiniteScrollContent: FC<{
|
||||||
|
hasMore: boolean;
|
||||||
|
failed: boolean;
|
||||||
|
retry: () => void;
|
||||||
|
}> = (props) => {
|
||||||
|
const { locale } = useConfig();
|
||||||
|
|
||||||
|
if (!props.hasMore) {
|
||||||
|
return <span>{locale.InfiniteScroll.noMore}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.failed) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={`${classPrefix}-failed-text`}>{locale.InfiniteScroll.failedToLoad}</span>
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
props.retry();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{locale.InfiniteScroll.retry}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span>{locale.common.loading}</span>
|
||||||
|
<DotLoading />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const keepComp = (props) => {
|
||||||
|
return <InfiniteScroll {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
keepComp({});
|
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
// TODO: 待继续
|
||||||
|
// import { Locale } from '../../locales/base';
|
||||||
|
// import zhCN from '../../locales/zh-CN';
|
||||||
|
type Locale = {};
|
||||||
|
const zhCN = {};
|
||||||
|
|
||||||
|
type Config = {
|
||||||
|
locale: Locale;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultConfigRef: {
|
||||||
|
current: Config;
|
||||||
|
} = {
|
||||||
|
current: {
|
||||||
|
locale: zhCN,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setDefaultConfig(config: Config) {
|
||||||
|
defaultConfigRef.current = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultConfig() {
|
||||||
|
return defaultConfigRef.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigContext = React.createContext<Config | null>(null);
|
||||||
|
|
||||||
|
export type ConfigProviderProps = Config & {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfigProvider: FC<ConfigProviderProps> = (props) => {
|
||||||
|
const { children, ...config } = props;
|
||||||
|
const parentConfig = useConfig();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigContext.Provider
|
||||||
|
value={{
|
||||||
|
...parentConfig,
|
||||||
|
...config,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ConfigContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useConfig() {
|
||||||
|
return useContext(ConfigContext) ?? getDefaultConfig();
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { CSSProperties, ReactElement } from 'react';
|
||||||
|
import React, { AriaAttributes } from 'react';
|
||||||
|
|
||||||
|
const canUseDom = !!(
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
typeof document !== 'undefined' &&
|
||||||
|
window.document &&
|
||||||
|
window.document.createElement
|
||||||
|
);
|
||||||
|
|
||||||
|
type ScrollElement = HTMLElement | Window;
|
||||||
|
|
||||||
|
const defaultRoot = canUseDom ? window : undefined;
|
||||||
|
|
||||||
|
const overflowStylePatterns = ['scroll', 'auto', 'overlay'];
|
||||||
|
|
||||||
|
function isElement(node: Element) {
|
||||||
|
const ELEMENT_NODE_TYPE = 1;
|
||||||
|
return node.nodeType === ELEMENT_NODE_TYPE;
|
||||||
|
}
|
||||||
|
export function getScrollParent(
|
||||||
|
el: Element,
|
||||||
|
root: ScrollElement | null | undefined = defaultRoot,
|
||||||
|
): Window | Element | null | undefined {
|
||||||
|
let node = el;
|
||||||
|
|
||||||
|
while (node && node !== root && isElement(node)) {
|
||||||
|
if (node === document.body) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
const { overflowY } = window.getComputedStyle(node);
|
||||||
|
if (overflowStylePatterns.includes(overflowY) && node.scrollHeight > node.clientHeight) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
node = node.parentNode as Element;
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeProps<A, B>(a: A, b: B): B & A;
|
||||||
|
export function mergeProps<A, B, C>(a: A, b: B, c: C): C & B & A;
|
||||||
|
export function mergeProps(...items: any[]) {
|
||||||
|
const ret: any = {};
|
||||||
|
items.forEach((item) => {
|
||||||
|
Object.keys(item).forEach((key) => {
|
||||||
|
if (item[key] !== undefined) {
|
||||||
|
ret[key] = item[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NativeProps<S extends string = never> = {
|
||||||
|
className?: string;
|
||||||
|
style?: CSSProperties & Partial<Record<S, string>>;
|
||||||
|
tabIndex?: number;
|
||||||
|
} & AriaAttributes;
|
||||||
|
|
||||||
|
export function withNativeProps<P extends NativeProps>(props: P, element: ReactElement) {
|
||||||
|
const p = {
|
||||||
|
...element.props,
|
||||||
|
};
|
||||||
|
if (props.className) {
|
||||||
|
p.className = classNames(element.props.className, props.className);
|
||||||
|
}
|
||||||
|
if (props.style) {
|
||||||
|
p.style = {
|
||||||
|
...p.style,
|
||||||
|
...props.style,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (props.tabIndex !== undefined) {
|
||||||
|
p.tabIndex = props.tabIndex;
|
||||||
|
}
|
||||||
|
for (const key in props) {
|
||||||
|
// if (!props.hasOwnProperty(key)) continue;
|
||||||
|
if (!Object.hasOwn(props, key)) continue;
|
||||||
|
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
||||||
|
p[key] = props[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return React.cloneElement(element, p);
|
||||||
|
}
|
@ -620,17 +620,33 @@ export const SchemaSettingsConnectDataBlocks: FC<SchemaSettingsConnectDataBlocks
|
|||||||
const Content = dataBlocks.map((block) => {
|
const Content = dataBlocks.map((block) => {
|
||||||
const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
||||||
const onHover = () => {
|
const onHover = () => {
|
||||||
|
// 保存当前的滚动位置
|
||||||
|
const originalScrollPosition = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
|
||||||
const dom = block.dom;
|
const dom = block.dom;
|
||||||
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
||||||
if (designer) {
|
if (designer) {
|
||||||
designer.style.display = 'block';
|
designer.style.display = 'block';
|
||||||
}
|
}
|
||||||
dom.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
dom.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
||||||
dom.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
setTimeout(() => {
|
||||||
block: 'center',
|
// 使用平滑滚动滚回到原始位置
|
||||||
});
|
dom.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// 使用平滑滚动滚回到原始位置
|
||||||
|
window.scrollTo({
|
||||||
|
top: originalScrollPosition,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLeave = () => {
|
const onLeave = () => {
|
||||||
const dom = block.dom;
|
const dom = block.dom;
|
||||||
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
||||||
@ -662,8 +678,9 @@ export const SchemaSettingsConnectDataBlocks: FC<SchemaSettingsConnectDataBlocks
|
|||||||
}).catch(error);
|
}).catch(error);
|
||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
onMouseEnter={onHover}
|
// onMouseEnter={onHover}
|
||||||
onMouseLeave={onLeave}
|
onMouseLeave={onLeave}
|
||||||
|
onMouseUp={onHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Plugin, tval } from '@nocobase/client';
|
||||||
|
|
||||||
|
class PluginCommon extends Plugin {
|
||||||
|
async afterAdd() {}
|
||||||
|
async beforeLoad() {}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
// 扩展原移动端插件的 block 类型
|
||||||
|
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks', {
|
||||||
|
title: tval('Filter blocks'),
|
||||||
|
type: 'itemGroup',
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginCommon;
|
@ -1,19 +1,24 @@
|
|||||||
import { Plugin } from '@nocobase/client';
|
import { Plugin } from '@nocobase/client';
|
||||||
|
import PluginCommon from './common/plugin';
|
||||||
import PluginBlock from './schma-block';
|
import PluginBlock from './schma-block';
|
||||||
|
import PluginImageSearch from './schma-component/image-search/plugin';
|
||||||
|
import PluginSwiper from './schma-component/swiper';
|
||||||
import PluginTabSearch from './schma-component/tab-search';
|
import PluginTabSearch from './schma-component/tab-search';
|
||||||
import './assets/svg/index';
|
import './assets/svg/index';
|
||||||
import PluginSwiper from './schma-component/swiper';
|
|
||||||
|
|
||||||
class PluginMobileClient extends Plugin {
|
class PluginMobileClient extends Plugin {
|
||||||
async beforeLoad() {}
|
|
||||||
|
|
||||||
async load() {}
|
|
||||||
|
|
||||||
async afterAdd() {
|
async afterAdd() {
|
||||||
|
this.pm.add(PluginCommon);
|
||||||
this.pm.add(PluginBlock);
|
this.pm.add(PluginBlock);
|
||||||
this.pm.add(PluginTabSearch);
|
this.pm.add(PluginTabSearch);
|
||||||
this.pm.add(PluginSwiper);
|
this.pm.add(PluginSwiper);
|
||||||
|
this.pm.add(PluginImageSearch);
|
||||||
|
// this.app.router.add()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async beforeLoad() {}
|
||||||
|
|
||||||
|
async load() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PluginMobileClient;
|
export default PluginMobileClient;
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { SchemaInitializer, useCollection, useCollectionManager } from '@nocobase/client';
|
||||||
|
import { tval } from '../../locale';
|
||||||
|
import { useIsMobile } from '../tab-search/components/field-item/hooks';
|
||||||
|
import { canBeOptionalField, canBeRelatedField } from '../tab-search/utils';
|
||||||
|
import { createSchemaImageSearchItem } from './search-item/ImageSearchItem.schema';
|
||||||
|
|
||||||
|
export const ImageSearchConfigureFields = new SchemaInitializer({
|
||||||
|
name: 'ImageSearchView:configureFields',
|
||||||
|
title: tval('Configure fields'),
|
||||||
|
style: { marginTop: 16 },
|
||||||
|
icon: 'SettingOutlined',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'choicesFields',
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: tval('Choices fields'),
|
||||||
|
useChildren: useChildrenChoicesFieldSchemas,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
function useChildrenChoicesFieldSchemas() {
|
||||||
|
const collection = useCollection();
|
||||||
|
const fields = collection?.fields;
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const choicesFieldsSchemas = fields
|
||||||
|
.map((field) => {
|
||||||
|
const { interface: _interface, target: collectionName } = field;
|
||||||
|
const label = cm.getCollection(collectionName)?.getPrimaryKey() ?? 'id';
|
||||||
|
const isCanBeOptional = canBeOptionalField(_interface);
|
||||||
|
const isCanBeRelated = canBeRelatedField(_interface);
|
||||||
|
|
||||||
|
if (isCanBeOptional || isCanBeRelated) {
|
||||||
|
const schema = createSchemaImageSearchItem({
|
||||||
|
collection,
|
||||||
|
field,
|
||||||
|
isMobile,
|
||||||
|
label,
|
||||||
|
isCanBeOptional,
|
||||||
|
isCanBeRelated,
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return choicesFieldsSchemas;
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import { DataBlockInitializer, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
import { createSchemaImageSearchBlock } from './ImageSearch.schema';
|
||||||
|
|
||||||
|
interface ItemConfig {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ImageSearchInitializer(props) {
|
||||||
|
const { filterCollections } = props;
|
||||||
|
const itemConfig: ItemConfig = useSchemaInitializerItem();
|
||||||
|
|
||||||
|
const { insert } = useSchemaInitializer();
|
||||||
|
|
||||||
|
const onCreateBlockSchema = async ({ item }) => {
|
||||||
|
const { dataSource, collectionName, name } = item;
|
||||||
|
|
||||||
|
const schema = createSchemaImageSearchBlock({
|
||||||
|
dataSource,
|
||||||
|
collection: collectionName || name,
|
||||||
|
blockType: 'filter',
|
||||||
|
});
|
||||||
|
|
||||||
|
insert(schema);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataBlockInitializer
|
||||||
|
{...itemConfig}
|
||||||
|
componentType={'ImageSearch'}
|
||||||
|
filter={filterCollections}
|
||||||
|
onCreateBlockSchema={onCreateBlockSchema}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { DataBlockProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ImageSearchProvider = ({ collection, children }) => {
|
||||||
|
return <DataBlockProvider collection={collection}>{children}</DataBlockProvider>;
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
import { ISchema } from '@tachybase/schema';
|
||||||
|
import { uid } from '@nocobase/utils/client';
|
||||||
|
|
||||||
|
interface OptionsType {
|
||||||
|
collection: string;
|
||||||
|
dataSource: string;
|
||||||
|
blockType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSchemaImageSearchBlock(options: OptionsType): ISchema {
|
||||||
|
const { collection, dataSource, blockType } = options;
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
// TODO: 困惑, 这里不能直接用 DataBlockProvider, 因为它没有collection和dataSource属性
|
||||||
|
'x-decorator': 'ImageSearchProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection,
|
||||||
|
dataSource,
|
||||||
|
blockType,
|
||||||
|
},
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:filterCollapse',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-action': 'imageSearch',
|
||||||
|
'x-initializer': 'ImageSearchView:configureFields',
|
||||||
|
'x-component': 'ImageSearchView',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { useDesigner, useSchemaInitializerRender } from '@nocobase/client';
|
||||||
|
import { RecursionField, useFieldSchema } from '@tachybase/schema';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ImageSearchView = () => {
|
||||||
|
const Designer = useDesigner();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Designer />
|
||||||
|
<RecursionField schema={fieldSchema} onlyRenderProperties />
|
||||||
|
<React.Fragment>{render()}</React.Fragment>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageSearchView;
|
@ -0,0 +1,22 @@
|
|||||||
|
export const images = [
|
||||||
|
{
|
||||||
|
label: '全部商品',
|
||||||
|
imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_all_select-884b60ce.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '中国电信',
|
||||||
|
imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_dx-7df8411f.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '中国移动',
|
||||||
|
imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_yd-7b994595.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '中国联通',
|
||||||
|
imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_lt-d9888233.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '中国广电',
|
||||||
|
imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_gd-cc48f3e6.png',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,61 @@
|
|||||||
|
import { useCollection, useDesigner } from '@nocobase/client';
|
||||||
|
import { useFieldSchema } from '@tachybase/schema';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export function useActionImageSearchItemView(props) {
|
||||||
|
const { list, onSelected, valueKey: _valueKey, labelKey: _labelKey, filterKey } = props;
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const collection = useCollection();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const collectionField = React.useMemo(
|
||||||
|
() => collection?.getField(fieldSchema['fieldName'] as any),
|
||||||
|
[collection, fieldSchema['fieldName']],
|
||||||
|
);
|
||||||
|
const Designer = useDesigner();
|
||||||
|
const valueKey = _valueKey || collectionField?.targetKey || 'id';
|
||||||
|
const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
||||||
|
|
||||||
|
const onSelect = (itemKey) => {
|
||||||
|
const key = itemKey.keyPath?.[0] || itemKey;
|
||||||
|
onSelected([key], filterKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldNames = {
|
||||||
|
title: labelKey || valueKey,
|
||||||
|
key: valueKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemsData = (list || []).map((item) => {
|
||||||
|
const { type, ['image_show']: imageObj, [fieldNames.title]: label, [fieldNames.key]: key } = item;
|
||||||
|
|
||||||
|
// TODO: 需要更好的处理url方式
|
||||||
|
const origin = location?.origin || '';
|
||||||
|
const sourceUrl = imageObj?.[0]?.url;
|
||||||
|
let imageUrl = sourceUrl ?? '';
|
||||||
|
if (!imageUrl.includes('http')) {
|
||||||
|
imageUrl = `${origin}${sourceUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'all') {
|
||||||
|
return {
|
||||||
|
label: t('AllProducts'),
|
||||||
|
key: 'all',
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
key,
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionField,
|
||||||
|
Designer,
|
||||||
|
items: itemsData,
|
||||||
|
onSelect,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import { findFilterTargets, mergeFilter, useCollection, useFilterBlock } from '@nocobase/client';
|
||||||
|
import { useFieldSchema } from '@tachybase/schema';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useGetSelected = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const collection = useCollection();
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
|
||||||
|
|
||||||
|
const onSelected = (value, filterKey) => {
|
||||||
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
|
getDataBlocks().forEach((block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
const key = `${uid}${fieldSchema.name}`;
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
let storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
if (value.length) {
|
||||||
|
storedFilter[key] = {
|
||||||
|
[filterKey]: value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (block.dataLoadingMode === 'manual') {
|
||||||
|
return block.clearData();
|
||||||
|
}
|
||||||
|
delete storedFilter[key];
|
||||||
|
}
|
||||||
|
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
|
||||||
|
if (value.length === 1 && value[0] === 'all') {
|
||||||
|
const currentKey = Object.keys(storedFilter[key]);
|
||||||
|
if (mergedFilter['$and']) {
|
||||||
|
currentKey.forEach((currentValue) => {
|
||||||
|
mergedFilter['$and'].forEach((value, index) => {
|
||||||
|
if (value[currentValue]) {
|
||||||
|
mergedFilter['$and'].splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
mergedFilter['$and'] = mergedFilter['$and'].filter(Boolean);
|
||||||
|
} else {
|
||||||
|
delete mergedFilter[currentKey[0]];
|
||||||
|
}
|
||||||
|
storedFilter[key] = {};
|
||||||
|
}
|
||||||
|
storedFilter = _.omitBy(
|
||||||
|
storedFilter,
|
||||||
|
(value) => value === null || value === undefined || (_.isObject(value) && _.isEmpty(value)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
onSelected,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIsMobile = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const isMobile = Object.values(fieldSchema.root.properties).some((value) => value['x-component'] === 'MContainer');
|
||||||
|
return isMobile;
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
import { Plugin } from '@nocobase/client';
|
||||||
|
import { ImageSearchView } from './ImageSearch.view';
|
||||||
|
import { ImageSearchInitializer } from './ImageSearch.initializer';
|
||||||
|
import { ImageSearchConfigureFields } from './ImageSearch.configure';
|
||||||
|
import { ImageSearchProvider } from './ImageSearch.provider';
|
||||||
|
import { ImageSearchItemIntializer } from './search-item/ImageSearchItem.intializer';
|
||||||
|
import { ImageSearchItemToolbar } from './search-item/ImageSearchItem.toolbar';
|
||||||
|
import { ImageSearchItemView } from './search-item/ImageSearchItem.view';
|
||||||
|
import { ImageSearchItemFieldSettings } from './search-item/ImageSearchItem.setting';
|
||||||
|
import { usePropsOptionalImageSearchItemField } from './search-item/useProps.Optional';
|
||||||
|
import { usePropsRelatedImageSearchItemField } from './search-item/useProps.Related';
|
||||||
|
|
||||||
|
class PluginImageSearch extends Plugin {
|
||||||
|
async load() {
|
||||||
|
this.app.addScopes({
|
||||||
|
usePropsOptionalImageSearchItemField: usePropsOptionalImageSearchItemField,
|
||||||
|
usePropsRelatedImageSearchItemField: usePropsRelatedImageSearchItemField,
|
||||||
|
});
|
||||||
|
this.app.addComponents({
|
||||||
|
ImageSearchView: ImageSearchView,
|
||||||
|
ImageSearchInitializer: ImageSearchInitializer,
|
||||||
|
ImageSearchProvider: ImageSearchProvider,
|
||||||
|
ImageSearchItemIntializer: ImageSearchItemIntializer,
|
||||||
|
ImageSearchItemToolbar: ImageSearchItemToolbar,
|
||||||
|
ImageSearchItemView: ImageSearchItemView,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.schemaInitializerManager.add(ImageSearchConfigureFields);
|
||||||
|
this.schemaSettingsManager.add(ImageSearchItemFieldSettings);
|
||||||
|
|
||||||
|
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks.imageSearch', {
|
||||||
|
name: 'imageSearch',
|
||||||
|
title: 'imageSearch',
|
||||||
|
icon: 'tab-search',
|
||||||
|
Component: 'ImageSearchInitializer',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginImageSearch;
|
@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
SchemaInitializerSwitch,
|
||||||
|
useCurrentSchema,
|
||||||
|
useSchemaInitializer,
|
||||||
|
useSchemaInitializerItem,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { merge } from '@tachybase/schema';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ImageSearchItemIntializer = () => {
|
||||||
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
const { insert } = useSchemaInitializer();
|
||||||
|
const { schema: oldSchema, title } = itemConfig;
|
||||||
|
const { exists, remove } = useCurrentSchema(oldSchema.name, 'name', itemConfig.find, itemConfig.remove);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (exists) {
|
||||||
|
return remove();
|
||||||
|
}
|
||||||
|
const { schema: latestSchema } = itemConfig;
|
||||||
|
|
||||||
|
const newSchema = merge(oldSchema || {}, latestSchema || {});
|
||||||
|
// itemConfig?.schemaInitialize?.(newSchema);
|
||||||
|
insert(newSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <SchemaInitializerSwitch checked={exists} title={title} onClick={onClick} />;
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
import { SchemaInitializerItemType } from '@nocobase/client';
|
||||||
|
|
||||||
|
interface OptionsType {
|
||||||
|
label?: string;
|
||||||
|
field?: any;
|
||||||
|
collection?: any;
|
||||||
|
isMobile?: boolean;
|
||||||
|
isCanBeOptional?: boolean;
|
||||||
|
isCanBeRelated?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSchemaImageSearchItem(options: OptionsType): SchemaInitializerItemType {
|
||||||
|
const { field, isMobile, label, isCanBeOptional, isCanBeRelated, collection } = options;
|
||||||
|
const { key, name, uiSchema, interface: _interface } = field;
|
||||||
|
const title = uiSchema?.title;
|
||||||
|
const indexOfUseProps = [isCanBeOptional, isCanBeRelated].indexOf(true);
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
title: title,
|
||||||
|
Component: 'ImageSearchItemIntializer',
|
||||||
|
schema: {
|
||||||
|
name: `${name}-choice`,
|
||||||
|
fieldName: name,
|
||||||
|
title: title,
|
||||||
|
type: 'void',
|
||||||
|
'x-toolbar': 'ImageSearchItemToolbar',
|
||||||
|
'x-settings': 'fieldSettings:component:ImageSearchItem',
|
||||||
|
'x-component': 'ImageSearchItemView',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label,
|
||||||
|
// 固定字段, 用于取数据表对外展示的图片字段
|
||||||
|
imageShow: 'image_show',
|
||||||
|
},
|
||||||
|
interface: _interface,
|
||||||
|
collectionName: collection?.name,
|
||||||
|
correlation: name,
|
||||||
|
},
|
||||||
|
'x-use-component-props': ['usePropsOptionalImageSearchItemField', 'usePropsRelatedImageSearchItemField'][
|
||||||
|
indexOfUseProps
|
||||||
|
],
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
SchemaSettings,
|
||||||
|
SchemaSettingsDataScope,
|
||||||
|
useCollection,
|
||||||
|
useCollectionManager,
|
||||||
|
useCompile,
|
||||||
|
useDesignable,
|
||||||
|
useFormBlockContext,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { useField, useFieldSchema } from '@tachybase/schema';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { isTabSearchCollapsibleInputItem } from '../../tab-search/utils';
|
||||||
|
|
||||||
|
export const ImageSearchItemFieldSettings = new SchemaSettings({
|
||||||
|
name: 'fieldSettings:component:ImageSearchItem',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'decoratorOptions',
|
||||||
|
type: 'itemGroup',
|
||||||
|
hideIfNoChildren: true,
|
||||||
|
useComponentProps() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return {
|
||||||
|
title: t('Generic properties'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
useChildren() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'setTheDataScope',
|
||||||
|
Component: SchemaSettingsDataScope,
|
||||||
|
useComponentProps() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
const field = useField();
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
const c = useCollection();
|
||||||
|
const collectionField =
|
||||||
|
c.getField(fieldSchema['name']) || cm.getCollectionField(fieldSchema['x-collection-field']);
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionName: collectionField?.target,
|
||||||
|
defaultFilter: fieldSchema?.['x-component-props']?.params?.filter || {},
|
||||||
|
form: form,
|
||||||
|
onSubmit: ({ filter }) => {
|
||||||
|
_.set(field.componentProps, 'params', {
|
||||||
|
...field.componentProps?.params,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
fieldSchema['x-component-props']['params'] = field.componentProps.params;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-component-props': fieldSchema['x-component-props'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
useVisible() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return !isTabSearchCollapsibleInputItem(fieldSchema['x-component']);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'titleField',
|
||||||
|
type: 'select',
|
||||||
|
useComponentProps() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
const c = useCollection();
|
||||||
|
const fieldCollection = fieldSchema['x-component-props']?.['collectionName'];
|
||||||
|
const correlation = fieldSchema['x-component-props']?.['correlation'];
|
||||||
|
const collectionField =
|
||||||
|
c.getField(fieldSchema['fieldName']) ||
|
||||||
|
cm.getCollectionField(fieldSchema['x-collection-field']) ||
|
||||||
|
cm.getCollection(fieldCollection + '.' + correlation);
|
||||||
|
const compile = useCompile();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const targetFields = collectionField?.target ? cm.getCollectionFields(collectionField?.target) : [];
|
||||||
|
const options = targetFields
|
||||||
|
.filter((field) => !field?.target && field.type !== 'boolean')
|
||||||
|
.map((field) => ({
|
||||||
|
value: field?.name,
|
||||||
|
label: compile(field?.uiSchema?.title) || field?.name,
|
||||||
|
}));
|
||||||
|
const onTitleFieldChange = (label) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldNames = {
|
||||||
|
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
|
||||||
|
...fieldSchema['x-component-props']?.['fieldNames'],
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||||
|
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'title-field',
|
||||||
|
title: t('Title field'),
|
||||||
|
options: options,
|
||||||
|
value: fieldSchema['x-component-props']?.fieldNames?.label,
|
||||||
|
onChange: onTitleFieldChange,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
useVisible() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return (
|
||||||
|
isTabSearchCollapsibleInputItem(fieldSchema['x-component']) ||
|
||||||
|
fieldSchema['x-component-props']?.['correlation']
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'divider',
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'remove',
|
||||||
|
sort: 100,
|
||||||
|
useComponentProps() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
return {
|
||||||
|
removeParentsIfNoChildren: true,
|
||||||
|
confirm: {
|
||||||
|
title: t('Delete field'),
|
||||||
|
},
|
||||||
|
breakRemoveOn: (s) => {
|
||||||
|
if (isTabSearchCollapsibleInputItem(fieldSchema['x-component'])) {
|
||||||
|
Object.values(s.properties).forEach((value) => {
|
||||||
|
if (isTabSearchCollapsibleInputItem(value['x-component']) && value.name !== fieldSchema.name) {
|
||||||
|
delete s.properties[value.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dn.emit('patch', { schema: s });
|
||||||
|
}
|
||||||
|
return s['x-component'] === 'TabSearch';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,6 @@
|
|||||||
|
import { SchemaToolbar } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ImageSearchItemToolbar = (props) => {
|
||||||
|
return <SchemaToolbar draggable showBorder showBackground initializer={false} {...props} />;
|
||||||
|
};
|
@ -0,0 +1,54 @@
|
|||||||
|
import { SortableItem, css, withDynamicSchemaProps } from '@nocobase/client';
|
||||||
|
import { Image, JumboTabs } from 'antd-mobile';
|
||||||
|
import React from 'react';
|
||||||
|
import { useActionImageSearchItemView } from '../hooks/useAction.ImageSearchItemView';
|
||||||
|
|
||||||
|
export const ImageSearchItemView = withDynamicSchemaProps(
|
||||||
|
(props) => {
|
||||||
|
const { collectionField, Designer, items, onSelect } = useActionImageSearchItemView(props);
|
||||||
|
|
||||||
|
if (!collectionField) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableItem>
|
||||||
|
<Designer />
|
||||||
|
<JumboTabs onChange={onSelect}>
|
||||||
|
{items.map(({ key, label, imageUrl }) => (
|
||||||
|
<JumboTabs.Tab key={key} title={''} description={<ImageDescription srcUrl={imageUrl} label={label} />} />
|
||||||
|
))}
|
||||||
|
</JumboTabs>
|
||||||
|
</SortableItem>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ displayName: 'ImageSearchItemView' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImageDescription = (props) => {
|
||||||
|
const { srcUrl, label } = props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
display: 'flex';
|
||||||
|
flex-direction: 'column';
|
||||||
|
width: '100%';
|
||||||
|
height: '100%';
|
||||||
|
:active {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Image src={srcUrl} width={100} height={100} fit="fill" />
|
||||||
|
<p
|
||||||
|
className={css`
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 17.6px;
|
||||||
|
line-height: 1;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useCollection, useCollectionManager } from '@nocobase/client';
|
||||||
|
import { useFieldSchema } from '@tachybase/schema';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useTabSearchCollapsibleInputItem } from '../../tab-search/components/field-item/hooks';
|
||||||
|
import { canBeOptionalField } from '../../tab-search/utils';
|
||||||
|
|
||||||
|
export const usePropsOptionalImageSearchItemField = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const collection = useCollection();
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
const optionalFieldList = getOptionalFieldList({ collection });
|
||||||
|
const fieldName = fieldSchema['fieldName'];
|
||||||
|
const collectionField = useMemo(() => collection?.getField(fieldName), [collection, fieldName]);
|
||||||
|
|
||||||
|
const { onSelected } = useTabSearchCollapsibleInputItem();
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = { list: null, valueKey: '', labelKey: '', filterKey: '' };
|
||||||
|
result.valueKey = collectionField?.target ? cm.getCollection(collectionField.target)?.getPrimaryKey() : 'id';
|
||||||
|
result.labelKey = fieldSchema['x-component-props']?.fieldNames?.label || result.valueKey;
|
||||||
|
const fieldInterface = fieldSchema['x-component-props'].interface;
|
||||||
|
if (canBeOptionalField(fieldInterface)) {
|
||||||
|
const field = optionalFieldList.find((field) => field.name === fieldSchema['fieldName']);
|
||||||
|
const operatorMap = {
|
||||||
|
select: '$in',
|
||||||
|
multipleSelect: '$anyOf',
|
||||||
|
checkboxGroup: '$anyOf',
|
||||||
|
radioGroup: '$in',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _list = field?.uiSchema?.enum || [];
|
||||||
|
result.valueKey = 'value';
|
||||||
|
result.labelKey = 'label';
|
||||||
|
result.list = _list;
|
||||||
|
result.filterKey = `${field.name}.${operatorMap[field.interface]}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: result.list,
|
||||||
|
valueKey: result.valueKey,
|
||||||
|
labelKey: result.labelKey,
|
||||||
|
onSelected,
|
||||||
|
filterKey: result.filterKey,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOptionalFieldList = ({ collection }) => {
|
||||||
|
const currentFields = collection?.fields ?? [];
|
||||||
|
return currentFields.filter((field) => canBeOptionalField(field.interface) && field.uiSchema.enum);
|
||||||
|
};
|
@ -0,0 +1,87 @@
|
|||||||
|
// 关系字段类型
|
||||||
|
import { useCollection, useCollectionManager, useDataSourceHeaders, useRequest } from '@nocobase/client';
|
||||||
|
import { useField, useFieldSchema } from '@tachybase/schema';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { canBeRelatedField } from '../../tab-search/utils';
|
||||||
|
import { useGetSelected } from '../hooks/useSelect';
|
||||||
|
|
||||||
|
interface requestResultType {
|
||||||
|
data: { [key: string]: any }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePropsRelatedImageSearchItemField = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const collection = useCollection();
|
||||||
|
const field = useField();
|
||||||
|
// TODO: 这里需要替换成获取 source id 的方式,不过我们暂时没有用多数据源,就不着急
|
||||||
|
const blockProps = { dataSource: 'main' };
|
||||||
|
const headers = useDataSourceHeaders(blockProps?.dataSource);
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
|
||||||
|
const collectionField = useMemo(
|
||||||
|
() => collection?.getField(fieldSchema['fieldName'] as any),
|
||||||
|
[collection, fieldSchema['fieldName']],
|
||||||
|
);
|
||||||
|
|
||||||
|
const collectionFieldName = collectionField?.name;
|
||||||
|
const fieldInterface = fieldSchema['x-component-props'].interface;
|
||||||
|
const { onSelected } = useGetSelected();
|
||||||
|
|
||||||
|
const result = { list: null, valueKey: '', labelKey: '', filterKey: '' };
|
||||||
|
|
||||||
|
result.valueKey = collectionField?.target ? cm.getCollection(collectionField.target)?.getPrimaryKey() : 'id';
|
||||||
|
|
||||||
|
result.labelKey = fieldSchema['x-component-props']?.fieldNames?.label || result.valueKey;
|
||||||
|
|
||||||
|
const imageShow = fieldSchema['x-component-props']?.fieldNames?.imageShow || 'imageShow';
|
||||||
|
|
||||||
|
const { data } = useRequestRelatedField({ headers, collectionField, result, imageShow, field, fieldInterface });
|
||||||
|
|
||||||
|
result.filterKey = `${collectionFieldName}.${result.valueKey}.$in`;
|
||||||
|
result.list = data?.data || [];
|
||||||
|
|
||||||
|
if (!collectionField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: result.list,
|
||||||
|
valueKey: result.valueKey,
|
||||||
|
labelKey: result.labelKey,
|
||||||
|
filterKey: result.filterKey,
|
||||||
|
onSelected,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useRequestRelatedField = ({ headers, collectionField, result, imageShow, field, fieldInterface }) => {
|
||||||
|
const { data, run } = useRequest<requestResultType>(
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
resource: collectionField?.target,
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
fields: [result.labelKey, result.valueKey, imageShow, 'type'],
|
||||||
|
pageSize: 200,
|
||||||
|
page: 1,
|
||||||
|
...field.componentProps?.params,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
debounceWait: 300,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (canBeRelatedField(fieldInterface) && collectionField?.target) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
result.labelKey,
|
||||||
|
result.valueKey,
|
||||||
|
JSON.stringify(field.componentProps?.params || {}),
|
||||||
|
canBeRelatedField(fieldInterface),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { data };
|
||||||
|
};
|
@ -2,10 +2,10 @@ import { useMemo, useRef, useState } from 'react';
|
|||||||
import { useFieldSchema } from '@tachybase/schema';
|
import { useFieldSchema } from '@tachybase/schema';
|
||||||
import { useCollection, useCollectionManager, useDesignable, useDesigner } from '@nocobase/client';
|
import { useCollection, useCollectionManager, useDesignable, useDesigner } from '@nocobase/client';
|
||||||
import {
|
import {
|
||||||
|
canBeCalculatedField,
|
||||||
canBeDataField,
|
canBeDataField,
|
||||||
canBeRelatedField,
|
canBeRelatedField,
|
||||||
changFormat,
|
changFormat,
|
||||||
convertFormat,
|
|
||||||
isTabSearchCollapsibleInputItem,
|
isTabSearchCollapsibleInputItem,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { useTabSearchCollapsibleInputItem } from './hooks';
|
import { useTabSearchCollapsibleInputItem } from './hooks';
|
||||||
@ -17,6 +17,7 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
|||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const cm = useCollectionManager();
|
const cm = useCollectionManager();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
|
const [needSort, setNeedSort] = useState(false);
|
||||||
const collectionField = useMemo(
|
const collectionField = useMemo(
|
||||||
() => collection?.getField(fieldSchema['fieldName'] as any),
|
() => collection?.getField(fieldSchema['fieldName'] as any),
|
||||||
[collection, fieldSchema['fieldName']],
|
[collection, fieldSchema['fieldName']],
|
||||||
@ -44,6 +45,11 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
|||||||
const onSelect = (value) => {
|
const onSelect = (value) => {
|
||||||
let time;
|
let time;
|
||||||
let filterKey = `${customLabelKey}.$includes`;
|
let filterKey = `${customLabelKey}.$includes`;
|
||||||
|
|
||||||
|
const canBeCal = canBeCalculatedField(fieldInterface);
|
||||||
|
if (canBeCal) {
|
||||||
|
filterKey = `${customLabelKey}.$notEmpty`;
|
||||||
|
}
|
||||||
if (canBeDataField(fieldInterface)) {
|
if (canBeDataField(fieldInterface)) {
|
||||||
time = value.split('&').map((value) => JSON.parse(value));
|
time = value.split('&').map((value) => JSON.parse(value));
|
||||||
filterKey = `${customLabelKey}.$dateBetween`;
|
filterKey = `${customLabelKey}.$dateBetween`;
|
||||||
@ -53,7 +59,12 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
|||||||
const correlation = fieldSchema['x-component-props']['correlation'];
|
const correlation = fieldSchema['x-component-props']['correlation'];
|
||||||
filterKey = `${correlation}.${label}.$includes`;
|
filterKey = `${correlation}.${label}.$includes`;
|
||||||
}
|
}
|
||||||
onSelected(time || [value], filterKey);
|
|
||||||
|
onSelected(time || [value], filterKey, {
|
||||||
|
canBeCalculatedField: canBeCal,
|
||||||
|
customLabelKey,
|
||||||
|
needSort,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectChange = (label) => {
|
const onSelectChange = (label) => {
|
||||||
@ -107,5 +118,7 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
|||||||
customLabelKey,
|
customLabelKey,
|
||||||
fieldInterface,
|
fieldInterface,
|
||||||
onDateClick,
|
onDateClick,
|
||||||
|
needSort,
|
||||||
|
setNeedSort,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
|
import { SortableItem, css, withDynamicSchemaProps } from '@nocobase/client';
|
||||||
import { Grid } from 'antd-mobile';
|
import { Checkbox, Grid, Switch } from 'antd-mobile';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction';
|
import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction';
|
||||||
import { IButton, IDatePicker, IInput, ISelect } from './TabSearchCollapsibleInputMItemChild';
|
import { IButton, IDatePicker, IInput, ISelect } from './TabSearchCollapsibleInputMItemChild';
|
||||||
import { canBeDataField } from '../../utils';
|
import { canBeDataField } from '../../utils';
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
|
||||||
export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
||||||
(props) => {
|
(props) => {
|
||||||
@ -12,13 +13,16 @@ export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
|||||||
Designer,
|
Designer,
|
||||||
options,
|
options,
|
||||||
value,
|
value,
|
||||||
|
needSort,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
onButtonClick,
|
onButtonClick,
|
||||||
customLabelKey,
|
customLabelKey,
|
||||||
fieldInterface,
|
fieldInterface,
|
||||||
onDateClick,
|
onDateClick,
|
||||||
|
setNeedSort,
|
||||||
} = useTabSearchCollapsibleInputItemAction(props);
|
} = useTabSearchCollapsibleInputItemAction(props);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!collectionField) {
|
if (!collectionField) {
|
||||||
return null;
|
return null;
|
||||||
@ -26,17 +30,46 @@ export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
|||||||
return (
|
return (
|
||||||
<SortableItem>
|
<SortableItem>
|
||||||
<Designer />
|
<Designer />
|
||||||
<Grid columns={4} style={{ backgroundColor: '#f9f9f9', borderRadius: '5px', margin: '5px 0 0 0' }}>
|
<div
|
||||||
<ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} />
|
className={css`
|
||||||
{canBeDataField(fieldInterface) ? (
|
display: flex;
|
||||||
<IDatePicker value={value} onChange={onDateClick} onInputChange={onInputChange} options={options} />
|
flex-direction: row;
|
||||||
) : (
|
align-items: center;
|
||||||
<>
|
`}
|
||||||
<IInput options={options} value={value} onChange={onInputChange} />
|
>
|
||||||
<IButton onClick={onButtonClick} />
|
<Grid
|
||||||
</>
|
columns={4}
|
||||||
)}
|
style={{
|
||||||
</Grid>
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
padding: '10px',
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
borderRadius: '5px',
|
||||||
|
margin: '5px 0 0 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} />
|
||||||
|
{canBeDataField(fieldInterface) ? (
|
||||||
|
<IDatePicker value={value} onChange={onDateClick} onInputChange={onInputChange} options={options} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<IInput options={options} value={value} onChange={onInputChange} />
|
||||||
|
<IButton onClick={onButtonClick} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Checkbox
|
||||||
|
className={css`
|
||||||
|
margin-right: 10px;
|
||||||
|
`}
|
||||||
|
checked={needSort}
|
||||||
|
onChange={setNeedSort}
|
||||||
|
>
|
||||||
|
{t('sort')}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -102,7 +102,7 @@ export const IInput = (props) => {
|
|||||||
const { options, value, onChange } = props;
|
const { options, value, onChange } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Grid.Item span={options.length > 1 ? 2 : 3}>
|
<Grid.Item style={{ flex: 1 }} span={options.length > 1 ? 2 : 3}>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('Please enter search content')}
|
placeholder={t('Please enter search content')}
|
||||||
value={value}
|
value={value}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { findFilterTargets, mergeFilter, useApp, useCollection, useFilterBlock } from '@nocobase/client';
|
import { findFilterTargets, mergeFilter, useCollection, useFilterBlock } from '@nocobase/client';
|
||||||
import { useFieldSchema } from '@tachybase/schema';
|
import { useFieldSchema } from '@tachybase/schema';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export const useTabSearchCollapsibleInputItem = () => {
|
export const useTabSearchCollapsibleInputItem = () => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const { getDataBlocks } = useFilterBlock();
|
const { getDataBlocks } = useFilterBlock();
|
||||||
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
|
|
||||||
|
|
||||||
const onSelected = (value, filterKey) => {
|
const onSelected = (
|
||||||
|
value,
|
||||||
|
filterKey,
|
||||||
|
options = { canBeCalculatedField: false, customLabelKey: '', needSort: false },
|
||||||
|
) => {
|
||||||
|
const { canBeCalculatedField, customLabelKey, needSort = true } = options;
|
||||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
getDataBlocks().forEach((block) => {
|
getDataBlocks().forEach((block) => {
|
||||||
const target = targets.find((target) => target.uid === block.uid);
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
@ -22,6 +25,12 @@ export const useTabSearchCollapsibleInputItem = () => {
|
|||||||
storedFilter[key] = {
|
storedFilter[key] = {
|
||||||
[filterKey]: value,
|
[filterKey]: value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (canBeCalculatedField) {
|
||||||
|
storedFilter[key] = {
|
||||||
|
[filterKey]: Number(value?.[0] ?? 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (block.dataLoadingMode === 'manual') {
|
if (block.dataLoadingMode === 'manual') {
|
||||||
return block.clearData();
|
return block.clearData();
|
||||||
@ -50,14 +59,21 @@ export const useTabSearchCollapsibleInputItem = () => {
|
|||||||
(value) => value === null || value === undefined || (_.isObject(value) && _.isEmpty(value)),
|
(value) => value === null || value === undefined || (_.isObject(value) && _.isEmpty(value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
return block.doFilter(
|
let params1 = {
|
||||||
{
|
...param,
|
||||||
...param,
|
page: 1,
|
||||||
page: 1,
|
filter: mergedFilter,
|
||||||
filter: mergedFilter,
|
};
|
||||||
},
|
if (needSort) {
|
||||||
{ filters: storedFilter },
|
params1 = {
|
||||||
);
|
...params1,
|
||||||
|
['sort[]']: `-${customLabelKey}`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
params1 = _.omit(params1, ['sort[]']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return block.doFilter(params1, { filters: storedFilter });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,6 +84,6 @@ export const useTabSearchCollapsibleInputItem = () => {
|
|||||||
|
|
||||||
export const useIsMobile = () => {
|
export const useIsMobile = () => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const isMobile = Object.values(fieldSchema.root.properties).find((value) => value['x-component'] === 'MContainer');
|
const isMobile = Object.values(fieldSchema.root.properties).some((value) => value['x-component'] === 'MContainer');
|
||||||
return isMobile;
|
return isMobile;
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,6 @@ import { TabSearchBlockInitializer } from './initializer/TabSearchBlockInitializ
|
|||||||
import { TabSearchFieldItem } from './components/field-item/TabSearchFieldItem';
|
import { TabSearchFieldItem } from './components/field-item/TabSearchFieldItem';
|
||||||
import { TabSearchCollapsibleInputItem } from './components/field-item/TabSearchCollapsibleInputItem';
|
import { TabSearchCollapsibleInputItem } from './components/field-item/TabSearchCollapsibleInputItem';
|
||||||
import { TabSearchFieldSchemaInitializerGadget } from './initializer/TabSearchFieldSchemaInitializerGadget';
|
import { TabSearchFieldSchemaInitializerGadget } from './initializer/TabSearchFieldSchemaInitializerGadget';
|
||||||
import { tval } from '../../locale';
|
|
||||||
import { useTabSearchFieldItemRelatedProps } from './components/field-item/TabSerachFieldItemRelatedProps';
|
import { useTabSearchFieldItemRelatedProps } from './components/field-item/TabSerachFieldItemRelatedProps';
|
||||||
import { useTabSearchFieldItemProps } from './components/field-item/TabSearchFieldItemProps';
|
import { useTabSearchFieldItemProps } from './components/field-item/TabSearchFieldItemProps';
|
||||||
import { TabSearchFieldSchemaInitializer } from './initializer/TabSearchFieldSchemaInitializer';
|
import { TabSearchFieldSchemaInitializer } from './initializer/TabSearchFieldSchemaInitializer';
|
||||||
@ -32,16 +31,10 @@ class PluginTabSearch extends Plugin {
|
|||||||
});
|
});
|
||||||
this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer);
|
this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer);
|
||||||
this.schemaSettingsManager.add(TabSearchItemFieldSettings);
|
this.schemaSettingsManager.add(TabSearchItemFieldSettings);
|
||||||
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks', {
|
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks.tabSearch', {
|
||||||
title: tval('Filter blocks'),
|
name: 'tabSearch',
|
||||||
type: 'itemGroup',
|
title: 'tabSearch',
|
||||||
children: [
|
Component: 'TabSearchBlockInitializer',
|
||||||
{
|
|
||||||
name: 'tabSearch',
|
|
||||||
title: 'tabSearch',
|
|
||||||
Component: 'TabSearchBlockInitializer',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
this.app.schemaInitializerManager.addItem('page:addBlock', 'filterBlocks.tabSearch', {
|
this.app.schemaInitializerManager.addItem('page:addBlock', 'filterBlocks.tabSearch', {
|
||||||
name: 'tabSearch',
|
name: 'tabSearch',
|
||||||
|
@ -10,8 +10,6 @@ const canBeSearchFields = [
|
|||||||
'phone',
|
'phone',
|
||||||
'email',
|
'email',
|
||||||
'url',
|
'url',
|
||||||
'integer',
|
|
||||||
'number',
|
|
||||||
'percent',
|
'percent',
|
||||||
'password',
|
'password',
|
||||||
'color',
|
'color',
|
||||||
@ -20,6 +18,8 @@ const canBeSearchFields = [
|
|||||||
'sequence',
|
'sequence',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const canBeCalculatedFields = ['integer', 'number'];
|
||||||
|
|
||||||
export const isTabSearchCollapsibleInputItem = (component: string) =>
|
export const isTabSearchCollapsibleInputItem = (component: string) =>
|
||||||
component === 'TabSearchCollapsibleInputItem' || component === 'TabSearchCollapsibleInputMItem';
|
component === 'TabSearchCollapsibleInputItem' || component === 'TabSearchCollapsibleInputMItem';
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ export const canBeOptionalField = (_interface: string) => canBeOptionalFields.in
|
|||||||
export const canBeRelatedField = (_interface: string) => canBeRelatedFields.includes(_interface);
|
export const canBeRelatedField = (_interface: string) => canBeRelatedFields.includes(_interface);
|
||||||
export const canBeDataField = (_interface: string) => canBeDataFields.includes(_interface);
|
export const canBeDataField = (_interface: string) => canBeDataFields.includes(_interface);
|
||||||
export const canBeSearchField = (_interface: string) => canBeSearchFields.includes(_interface);
|
export const canBeSearchField = (_interface: string) => canBeSearchFields.includes(_interface);
|
||||||
|
export const canBeCalculatedField = (_interface: string) => canBeCalculatedFields.includes(_interface);
|
||||||
|
|
||||||
export const convertFormat = (currentDate) => {
|
export const convertFormat = (currentDate) => {
|
||||||
return dayjs(currentDate).format('YYYY-MM-DD');
|
return dayjs(currentDate).format('YYYY-MM-DD');
|
||||||
|
@ -11,5 +11,7 @@
|
|||||||
"Configure fields": "Configure fields",
|
"Configure fields": "Configure fields",
|
||||||
"Configure columns": "Configure columns",
|
"Configure columns": "Configure columns",
|
||||||
"Add block": "Add block",
|
"Add block": "Add block",
|
||||||
"all": "all"
|
"all": "all",
|
||||||
|
"AllProducts": "AllProducts",
|
||||||
|
"sort":"sort"
|
||||||
}
|
}
|
||||||
|
@ -11,5 +11,7 @@
|
|||||||
"Configure fields": "配置字段",
|
"Configure fields": "配置字段",
|
||||||
"Configure columns": "配置字段",
|
"Configure columns": "配置字段",
|
||||||
"Add block": "创建区块",
|
"Add block": "创建区块",
|
||||||
"all": "全部"
|
"all": "全部",
|
||||||
|
"AllProducts": "全部商品",
|
||||||
|
"sort": "排序"
|
||||||
}
|
}
|
||||||
|
2
packages/plugins/@hera/plugin-sancongtou/.npmignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/src
|
1
packages/plugins/@hera/plugin-sancongtou/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# @hera/plugin-sancongtou
|
2
packages/plugins/@hera/plugin-sancongtou/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/client';
|
||||||
|
export { default } from './dist/client';
|
1
packages/plugins/@hera/plugin-sancongtou/client.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/client/index.js');
|
18
packages/plugins/@hera/plugin-sancongtou/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@hera/plugin-sancongtou",
|
||||||
|
"displayName": "Mobile client: Sancongtou Customization",
|
||||||
|
"version": "0.21.24",
|
||||||
|
"description": "Provide mobile client customization for the sancongtou project.",
|
||||||
|
"main": "dist/server/index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.3.6",
|
||||||
|
"antd-mobile": "^5.35.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nocobase/client": "0.x",
|
||||||
|
"@nocobase/server": "0.x",
|
||||||
|
"@nocobase/test": "0.x"
|
||||||
|
},
|
||||||
|
"description.zh-CN": "提供移动端: 定制化的三聪头项目页面,.",
|
||||||
|
"displayName.zh-CN": "移动端: 三聪头,定制化页面配置"
|
||||||
|
}
|
2
packages/plugins/@hera/plugin-sancongtou/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/server';
|
||||||
|
export { default } from './dist/server';
|
1
packages/plugins/@hera/plugin-sancongtou/server.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/server/index.js');
|
After Width: | Height: | Size: 361 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 423 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 387 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 369 KiB |
@ -0,0 +1,63 @@
|
|||||||
|
import { Modal, Toast, Image, Button } from 'antd-mobile';
|
||||||
|
import { ShareAltOutlined } from '@ant-design/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import downloadImage from '../assets/download.svg';
|
||||||
|
import { CustomComponentType } from '@hera/plugin-core/client';
|
||||||
|
|
||||||
|
export const ShareProduct = () => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
alignSelf: 'center',
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
block
|
||||||
|
onClick={showModal}
|
||||||
|
>
|
||||||
|
分享产品
|
||||||
|
<ShareAltOutlined />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ShareProduct.displayName = 'ShareProduct';
|
||||||
|
ShareProduct.__componentType = CustomComponentType.CUSTOM_FORM_ITEM;
|
||||||
|
ShareProduct.__componentLabel = '移动端-三聪头-分享产品';
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
Modal.show({
|
||||||
|
bodyStyle: {
|
||||||
|
background: 'transparent',
|
||||||
|
},
|
||||||
|
content: <ImageShow />,
|
||||||
|
closeOnMaskClick: true,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
key: 'share',
|
||||||
|
text: '复制推广链接',
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onAction: async () => {
|
||||||
|
await copyHrefToClipboard();
|
||||||
|
Toast.show('复制成功!');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageShow = () => {
|
||||||
|
return <Image style={{ flex: 1 }} src={downloadImage} width="100%" fit="scale-down" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function copyHrefToClipboard() {
|
||||||
|
const location = window.location;
|
||||||
|
const href = location.href;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(href);
|
||||||
|
console.log('文本已复制到剪贴板', href);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('无法复制文本: ', err);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Button } from 'antd-mobile';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const TestComponent = () => {
|
||||||
|
const handleClick = () => {};
|
||||||
|
return (
|
||||||
|
<Button style={{ alignSelf: 'center', fontSize: '5rem' }} block color="primary" size="large" onClick={handleClick}>
|
||||||
|
Hello
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Plugin } from '@nocobase/client';
|
||||||
|
import { ProductDetail } from './pages/ProductDetail';
|
||||||
|
import { ShareProduct } from './components/ShareProduct';
|
||||||
|
|
||||||
|
export class PluginSancongtouClient extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
// await this.app.pm.add()
|
||||||
|
}
|
||||||
|
|
||||||
|
async beforeLoad() {}
|
||||||
|
|
||||||
|
// You can get and modify the app instance here
|
||||||
|
async load() {
|
||||||
|
this.addRoutes();
|
||||||
|
this.app.addComponents({
|
||||||
|
ShareProduct,
|
||||||
|
});
|
||||||
|
console.log(this.app);
|
||||||
|
// this.app.addComponents({})
|
||||||
|
// this.app.addScopes({})
|
||||||
|
// this.app.addProvider()
|
||||||
|
// this.app.addProviders()
|
||||||
|
}
|
||||||
|
|
||||||
|
addRoutes() {
|
||||||
|
this.app.router.add('sancongtou/detail', {
|
||||||
|
path: '/mobile/products/detail',
|
||||||
|
Component: ProductDetail,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginSancongtouClient;
|
@ -0,0 +1,49 @@
|
|||||||
|
import { NavBar, Image } from 'antd-mobile';
|
||||||
|
import React from 'react';
|
||||||
|
import detailHeaderImpage from '../assets/detail_lt_header.png';
|
||||||
|
import detailContentImage from '../assets/detail_lt_content.png';
|
||||||
|
import { css } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const ProductDetail = () => {
|
||||||
|
const onBack = () => {
|
||||||
|
const history = window.history;
|
||||||
|
history.back();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NavBar
|
||||||
|
className={css`
|
||||||
|
.adm-nav-bar-left,
|
||||||
|
.adm-nav-bar-title {
|
||||||
|
font-size: 2vw;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
zIndex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
width: '100%',
|
||||||
|
height: '5%',
|
||||||
|
}}
|
||||||
|
back="返回"
|
||||||
|
onBack={onBack}
|
||||||
|
>
|
||||||
|
产品详情
|
||||||
|
</NavBar>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image src={detailHeaderImpage} width="100%" />
|
||||||
|
<Image src={detailContentImage} width="100%" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
2
packages/plugins/@hera/plugin-sancongtou/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './server';
|
||||||
|
export { default } from './server';
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Plugin } from '@nocobase/server';
|
||||||
|
|
||||||
|
export class PluginSancongtouServer extends Plugin {
|
||||||
|
async afterAdd() {}
|
||||||
|
|
||||||
|
async beforeLoad() {}
|
||||||
|
|
||||||
|
async load() {}
|
||||||
|
|
||||||
|
async install() {}
|
||||||
|
|
||||||
|
async afterEnable() {}
|
||||||
|
|
||||||
|
async afterDisable() {}
|
||||||
|
|
||||||
|
async remove() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginSancongtouServer;
|
@ -22,7 +22,7 @@ const useStyles = createStyles(({ token, css }) => {
|
|||||||
& > .general-schema-designer {
|
& > .general-schema-designer {
|
||||||
--nb-designer-top: ${token.marginMD}px;
|
--nb-designer-top: ${token.marginMD}px;
|
||||||
}
|
}
|
||||||
position: absolute;
|
position: fixed;
|
||||||
background: ${token.colorBgContainer};
|
background: ${token.colorBgContainer};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -1764,6 +1764,24 @@ importers:
|
|||||||
specifier: ^6.26.0
|
specifier: ^6.26.0
|
||||||
version: 6.35.2
|
version: 6.35.2
|
||||||
|
|
||||||
|
packages/plugins/@hera/plugin-sancongtou:
|
||||||
|
dependencies:
|
||||||
|
'@ant-design/icons':
|
||||||
|
specifier: ^5.3.6
|
||||||
|
version: 5.3.6(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@nocobase/client':
|
||||||
|
specifier: 0.x
|
||||||
|
version: link:../../../core/client
|
||||||
|
'@nocobase/server':
|
||||||
|
specifier: 0.x
|
||||||
|
version: link:../../../core/server
|
||||||
|
'@nocobase/test':
|
||||||
|
specifier: 0.x
|
||||||
|
version: link:../../../core/test
|
||||||
|
antd-mobile:
|
||||||
|
specifier: ^5.35.0
|
||||||
|
version: 5.35.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
|
||||||
packages/plugins/@nocobase/plugin-acl:
|
packages/plugins/@nocobase/plugin-acl:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nocobase/acl':
|
'@nocobase/acl':
|
||||||
@ -8708,7 +8726,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==}
|
resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/utils': 0.2.1
|
'@floating-ui/utils': 0.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@floating-ui/dom@0.4.5:
|
/@floating-ui/dom@0.4.5:
|
||||||
resolution: {integrity: sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==}
|
resolution: {integrity: sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==}
|
||||||
@ -8720,7 +8737,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/core': 1.6.0
|
'@floating-ui/core': 1.6.0
|
||||||
'@floating-ui/utils': 0.2.1
|
'@floating-ui/utils': 0.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@floating-ui/react-dom-interactions@0.3.1(@types/react@18.2.75)(react-dom@18.1.0)(react@18.1.0):
|
/@floating-ui/react-dom-interactions@0.3.1(@types/react@18.2.75)(react-dom@18.1.0)(react@18.1.0):
|
||||||
resolution: {integrity: sha512-tP2KEh7EHJr5hokSBHcPGojb+AorDNUf0NYfZGg/M+FsMvCOOsSEeEF0O1NDfETIzDnpbHnCs0DuvCFhSMSStg==}
|
resolution: {integrity: sha512-tP2KEh7EHJr5hokSBHcPGojb+AorDNUf0NYfZGg/M+FsMvCOOsSEeEF0O1NDfETIzDnpbHnCs0DuvCFhSMSStg==}
|
||||||
@ -8780,7 +8796,6 @@ packages:
|
|||||||
|
|
||||||
/@floating-ui/utils@0.2.1:
|
/@floating-ui/utils@0.2.1:
|
||||||
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
|
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@formily/antd-v5@1.2.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5):
|
/@formily/antd-v5@1.2.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5):
|
||||||
resolution: {integrity: sha512-6K1fV7pwvy7j5KoO/5dEe5llosann7211I/bH/WJkLQtDSDK+XLFUQ4/lU7CdNpwHMrspzXS3lF82EM3hGyM8w==}
|
resolution: {integrity: sha512-6K1fV7pwvy7j5KoO/5dEe5llosann7211I/bH/WJkLQtDSDK+XLFUQ4/lU7CdNpwHMrspzXS3lF82EM3hGyM8w==}
|
||||||
@ -10211,7 +10226,6 @@ packages:
|
|||||||
'@react-spring/shared': 9.6.1(react@18.2.0)
|
'@react-spring/shared': 9.6.1(react@18.2.0)
|
||||||
'@react-spring/types': 9.6.1
|
'@react-spring/types': 9.6.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@react-spring/core@9.6.1(react@18.2.0):
|
/@react-spring/core@9.6.1(react@18.2.0):
|
||||||
resolution: {integrity: sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==}
|
resolution: {integrity: sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==}
|
||||||
@ -10223,11 +10237,9 @@ packages:
|
|||||||
'@react-spring/shared': 9.6.1(react@18.2.0)
|
'@react-spring/shared': 9.6.1(react@18.2.0)
|
||||||
'@react-spring/types': 9.6.1
|
'@react-spring/types': 9.6.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@react-spring/rafz@9.6.1:
|
/@react-spring/rafz@9.6.1:
|
||||||
resolution: {integrity: sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==}
|
resolution: {integrity: sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@react-spring/shared@9.6.1(react@18.2.0):
|
/@react-spring/shared@9.6.1(react@18.2.0):
|
||||||
resolution: {integrity: sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==}
|
resolution: {integrity: sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==}
|
||||||
@ -10237,11 +10249,9 @@ packages:
|
|||||||
'@react-spring/rafz': 9.6.1
|
'@react-spring/rafz': 9.6.1
|
||||||
'@react-spring/types': 9.6.1
|
'@react-spring/types': 9.6.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@react-spring/types@9.6.1:
|
/@react-spring/types@9.6.1:
|
||||||
resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==}
|
resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@react-spring/web@9.6.1(react-dom@18.2.0)(react@18.2.0):
|
/@react-spring/web@9.6.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==}
|
resolution: {integrity: sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==}
|
||||||
@ -10255,7 +10265,6 @@ packages:
|
|||||||
'@react-spring/types': 9.6.1
|
'@react-spring/types': 9.6.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@redis/bloom@1.2.0(@redis/client@1.5.14):
|
/@redis/bloom@1.2.0(@redis/client@1.5.14):
|
||||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
||||||
@ -13114,7 +13123,6 @@ packages:
|
|||||||
|
|
||||||
/@use-gesture/core@10.3.0:
|
/@use-gesture/core@10.3.0:
|
||||||
resolution: {integrity: sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==}
|
resolution: {integrity: sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@use-gesture/react@10.3.0(react@18.2.0):
|
/@use-gesture/react@10.3.0(react@18.2.0):
|
||||||
resolution: {integrity: sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==}
|
resolution: {integrity: sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==}
|
||||||
@ -13123,7 +13131,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@use-gesture/core': 10.3.0
|
'@use-gesture/core': 10.3.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@vercel/ncc@0.36.1:
|
/@vercel/ncc@0.36.1:
|
||||||
resolution: {integrity: sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==}
|
resolution: {integrity: sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==}
|
||||||
@ -13140,7 +13147,7 @@ packages:
|
|||||||
'@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4)
|
'@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4)
|
||||||
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4)
|
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4)
|
||||||
react-refresh: 0.14.0
|
react-refresh: 0.14.0
|
||||||
vite: 4.5.2(@types/node@20.12.7)(less@4.1.3)
|
vite: 4.5.2(@types/node@20.12.2)(less@4.1.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -13453,7 +13460,6 @@ packages:
|
|||||||
resize-observer-polyfill: 1.5.1
|
resize-observer-polyfill: 1.5.1
|
||||||
screenfull: 5.2.0
|
screenfull: 5.2.0
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ahooks@3.7.8(react@18.2.0):
|
/ahooks@3.7.8(react@18.2.0):
|
||||||
resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==}
|
resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==}
|
||||||
@ -13683,11 +13689,9 @@ packages:
|
|||||||
|
|
||||||
/antd-mobile-icons@0.3.0:
|
/antd-mobile-icons@0.3.0:
|
||||||
resolution: {integrity: sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==}
|
resolution: {integrity: sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/antd-mobile-v5-count@1.0.1:
|
/antd-mobile-v5-count@1.0.1:
|
||||||
resolution: {integrity: sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==}
|
resolution: {integrity: sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/antd-mobile@5.35.0(react-dom@18.2.0)(react@18.2.0):
|
/antd-mobile@5.35.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-UYr8T2Tlw3eXdyDYpWrPu54RWDcon4biI5VWHtMp9XxmSP5tzypRxoseh1ZGE28boQq9FKpSCC6FZ9rN0HXnQg==}
|
resolution: {integrity: sha512-UYr8T2Tlw3eXdyDYpWrPu54RWDcon4biI5VWHtMp9XxmSP5tzypRxoseh1ZGE28boQq9FKpSCC6FZ9rN0HXnQg==}
|
||||||
@ -13716,7 +13720,6 @@ packages:
|
|||||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- react-dom
|
- react-dom
|
||||||
dev: true
|
|
||||||
|
|
||||||
/antd-style@3.4.5(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0):
|
/antd-style@3.4.5(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-6aC4P9XyuVy0O7eZ+HZXd8GbbFX9HgzsXsJ341ihJhgqrfsQZNx8lDQvS2kCV6ao99QqtyTDphK9gWOgV2bHEw==}
|
resolution: {integrity: sha512-6aC4P9XyuVy0O7eZ+HZXd8GbbFX9HgzsXsJ341ihJhgqrfsQZNx8lDQvS2kCV6ao99QqtyTDphK9gWOgV2bHEw==}
|
||||||
@ -22753,7 +22756,6 @@ packages:
|
|||||||
|
|
||||||
/nano-memoize@3.0.16:
|
/nano-memoize@3.0.16:
|
||||||
resolution: {integrity: sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==}
|
resolution: {integrity: sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/nanoid@3.3.4:
|
/nanoid@3.3.4:
|
||||||
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||||
@ -25224,7 +25226,6 @@ packages:
|
|||||||
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
|
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: true
|
|
||||||
|
|
||||||
/rc-field-form@1.44.0(react-dom@18.2.0)(react@18.2.0):
|
/rc-field-form@1.44.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA==}
|
resolution: {integrity: sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA==}
|
||||||
@ -26945,7 +26946,6 @@ packages:
|
|||||||
|
|
||||||
/runes2@1.1.4:
|
/runes2@1.1.4:
|
||||||
resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==}
|
resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/rw@1.3.3:
|
/rw@1.3.3:
|
||||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
||||||
@ -27809,7 +27809,6 @@ packages:
|
|||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/static-extend@0.1.2:
|
/static-extend@0.1.2:
|
||||||
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
|
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
|
||||||
@ -30017,7 +30016,6 @@ packages:
|
|||||||
rollup: 3.29.4
|
rollup: 3.29.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: false
|
|
||||||
|
|
||||||
/vite@4.5.2(@types/node@20.12.7)(less@4.1.3):
|
/vite@4.5.2(@types/node@20.12.7)(less@4.1.3):
|
||||||
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
|
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
|
||||||
@ -30054,6 +30052,7 @@ packages:
|
|||||||
rollup: 3.29.4
|
rollup: 3.29.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vite@5.1.5(@types/node@20.12.7)(sass@1.75.0):
|
/vite@5.1.5(@types/node@20.12.7)(sass@1.75.0):
|
||||||
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
|
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
|
||||||
|