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 { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue';
|
||||
import { useIsAllowToSetDefaultValue } from '../../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||
import { css } from '@nocobase/client';
|
||||
|
||||
export const fieldSettingsFormItem = new SchemaSettings({
|
||||
name: 'fieldSettings:FormItem',
|
||||
@ -223,6 +224,47 @@ export const fieldSettingsFormItem = new SchemaSettings({
|
||||
},
|
||||
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',
|
||||
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 title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
||||
const onHover = () => {
|
||||
// 保存当前的滚动位置
|
||||
const originalScrollPosition = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
const dom = block.dom;
|
||||
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
||||
if (designer) {
|
||||
designer.style.display = 'block';
|
||||
}
|
||||
dom.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
||||
|
||||
setTimeout(() => {
|
||||
// 使用平滑滚动滚回到原始位置
|
||||
dom.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
// 使用平滑滚动滚回到原始位置
|
||||
window.scrollTo({
|
||||
top: originalScrollPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const onLeave = () => {
|
||||
const dom = block.dom;
|
||||
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
||||
@ -662,8 +678,9 @@ export const SchemaSettingsConnectDataBlocks: FC<SchemaSettingsConnectDataBlocks
|
||||
}).catch(error);
|
||||
dn.refresh();
|
||||
}}
|
||||
onMouseEnter={onHover}
|
||||
// onMouseEnter={onHover}
|
||||
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 PluginCommon from './common/plugin';
|
||||
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 './assets/svg/index';
|
||||
import PluginSwiper from './schma-component/swiper';
|
||||
|
||||
class PluginMobileClient extends Plugin {
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {}
|
||||
|
||||
async afterAdd() {
|
||||
this.pm.add(PluginCommon);
|
||||
this.pm.add(PluginBlock);
|
||||
this.pm.add(PluginTabSearch);
|
||||
this.pm.add(PluginSwiper);
|
||||
this.pm.add(PluginImageSearch);
|
||||
// this.app.router.add()
|
||||
}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {}
|
||||
}
|
||||
|
||||
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 { useCollection, useCollectionManager, useDesignable, useDesigner } from '@nocobase/client';
|
||||
import {
|
||||
canBeCalculatedField,
|
||||
canBeDataField,
|
||||
canBeRelatedField,
|
||||
changFormat,
|
||||
convertFormat,
|
||||
isTabSearchCollapsibleInputItem,
|
||||
} from '../../utils';
|
||||
import { useTabSearchCollapsibleInputItem } from './hooks';
|
||||
@ -17,6 +17,7 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
||||
const collection = useCollection();
|
||||
const cm = useCollectionManager();
|
||||
const { dn } = useDesignable();
|
||||
const [needSort, setNeedSort] = useState(false);
|
||||
const collectionField = useMemo(
|
||||
() => collection?.getField(fieldSchema['fieldName'] as any),
|
||||
[collection, fieldSchema['fieldName']],
|
||||
@ -44,6 +45,11 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
||||
const onSelect = (value) => {
|
||||
let time;
|
||||
let filterKey = `${customLabelKey}.$includes`;
|
||||
|
||||
const canBeCal = canBeCalculatedField(fieldInterface);
|
||||
if (canBeCal) {
|
||||
filterKey = `${customLabelKey}.$notEmpty`;
|
||||
}
|
||||
if (canBeDataField(fieldInterface)) {
|
||||
time = value.split('&').map((value) => JSON.parse(value));
|
||||
filterKey = `${customLabelKey}.$dateBetween`;
|
||||
@ -53,7 +59,12 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
||||
const correlation = fieldSchema['x-component-props']['correlation'];
|
||||
filterKey = `${correlation}.${label}.$includes`;
|
||||
}
|
||||
onSelected(time || [value], filterKey);
|
||||
|
||||
onSelected(time || [value], filterKey, {
|
||||
canBeCalculatedField: canBeCal,
|
||||
customLabelKey,
|
||||
needSort,
|
||||
});
|
||||
};
|
||||
|
||||
const onSelectChange = (label) => {
|
||||
@ -107,5 +118,7 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
|
||||
customLabelKey,
|
||||
fieldInterface,
|
||||
onDateClick,
|
||||
needSort,
|
||||
setNeedSort,
|
||||
};
|
||||
};
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { Grid } from 'antd-mobile';
|
||||
import { SortableItem, css, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { Checkbox, Grid, Switch } from 'antd-mobile';
|
||||
import React from 'react';
|
||||
import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction';
|
||||
import { IButton, IDatePicker, IInput, ISelect } from './TabSearchCollapsibleInputMItemChild';
|
||||
import { canBeDataField } from '../../utils';
|
||||
import { useTranslation } from '../../../../locale';
|
||||
|
||||
export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
||||
(props) => {
|
||||
@ -12,13 +13,16 @@ export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
||||
Designer,
|
||||
options,
|
||||
value,
|
||||
needSort,
|
||||
onSelectChange,
|
||||
onInputChange,
|
||||
onButtonClick,
|
||||
customLabelKey,
|
||||
fieldInterface,
|
||||
onDateClick,
|
||||
setNeedSort,
|
||||
} = useTabSearchCollapsibleInputItemAction(props);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!collectionField) {
|
||||
return null;
|
||||
@ -26,7 +30,26 @@ export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
||||
return (
|
||||
<SortableItem>
|
||||
<Designer />
|
||||
<Grid columns={4} style={{ backgroundColor: '#f9f9f9', borderRadius: '5px', margin: '5px 0 0 0' }}>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
<Grid
|
||||
columns={4}
|
||||
style={{
|
||||
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} />
|
||||
@ -37,6 +60,16 @@ export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
<Checkbox
|
||||
className={css`
|
||||
margin-right: 10px;
|
||||
`}
|
||||
checked={needSort}
|
||||
onChange={setNeedSort}
|
||||
>
|
||||
{t('sort')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</SortableItem>
|
||||
);
|
||||
},
|
||||
|
@ -102,7 +102,7 @@ export const IInput = (props) => {
|
||||
const { options, value, onChange } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Grid.Item span={options.length > 1 ? 2 : 3}>
|
||||
<Grid.Item style={{ flex: 1 }} span={options.length > 1 ? 2 : 3}>
|
||||
<Input
|
||||
placeholder={t('Please enter search content')}
|
||||
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 { useMemo } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const useTabSearchCollapsibleInputItem = () => {
|
||||
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 onSelected = (
|
||||
value,
|
||||
filterKey,
|
||||
options = { canBeCalculatedField: false, customLabelKey: '', needSort: false },
|
||||
) => {
|
||||
const { canBeCalculatedField, customLabelKey, needSort = true } = options;
|
||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||
getDataBlocks().forEach((block) => {
|
||||
const target = targets.find((target) => target.uid === block.uid);
|
||||
@ -22,6 +25,12 @@ export const useTabSearchCollapsibleInputItem = () => {
|
||||
storedFilter[key] = {
|
||||
[filterKey]: value,
|
||||
};
|
||||
|
||||
if (canBeCalculatedField) {
|
||||
storedFilter[key] = {
|
||||
[filterKey]: Number(value?.[0] ?? 0),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (block.dataLoadingMode === 'manual') {
|
||||
return block.clearData();
|
||||
@ -50,14 +59,21 @@ export const useTabSearchCollapsibleInputItem = () => {
|
||||
(value) => value === null || value === undefined || (_.isObject(value) && _.isEmpty(value)),
|
||||
);
|
||||
|
||||
return block.doFilter(
|
||||
{
|
||||
let params1 = {
|
||||
...param,
|
||||
page: 1,
|
||||
filter: mergedFilter,
|
||||
},
|
||||
{ filters: storedFilter },
|
||||
);
|
||||
};
|
||||
if (needSort) {
|
||||
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 = () => {
|
||||
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;
|
||||
};
|
||||
|
@ -5,7 +5,6 @@ import { TabSearchBlockInitializer } from './initializer/TabSearchBlockInitializ
|
||||
import { TabSearchFieldItem } from './components/field-item/TabSearchFieldItem';
|
||||
import { TabSearchCollapsibleInputItem } from './components/field-item/TabSearchCollapsibleInputItem';
|
||||
import { TabSearchFieldSchemaInitializerGadget } from './initializer/TabSearchFieldSchemaInitializerGadget';
|
||||
import { tval } from '../../locale';
|
||||
import { useTabSearchFieldItemRelatedProps } from './components/field-item/TabSerachFieldItemRelatedProps';
|
||||
import { useTabSearchFieldItemProps } from './components/field-item/TabSearchFieldItemProps';
|
||||
import { TabSearchFieldSchemaInitializer } from './initializer/TabSearchFieldSchemaInitializer';
|
||||
@ -32,16 +31,10 @@ class PluginTabSearch extends Plugin {
|
||||
});
|
||||
this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer);
|
||||
this.schemaSettingsManager.add(TabSearchItemFieldSettings);
|
||||
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks', {
|
||||
title: tval('Filter blocks'),
|
||||
type: 'itemGroup',
|
||||
children: [
|
||||
{
|
||||
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks.tabSearch', {
|
||||
name: 'tabSearch',
|
||||
title: 'tabSearch',
|
||||
Component: 'TabSearchBlockInitializer',
|
||||
},
|
||||
],
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('page:addBlock', 'filterBlocks.tabSearch', {
|
||||
name: 'tabSearch',
|
||||
|
@ -10,8 +10,6 @@ const canBeSearchFields = [
|
||||
'phone',
|
||||
'email',
|
||||
'url',
|
||||
'integer',
|
||||
'number',
|
||||
'percent',
|
||||
'password',
|
||||
'color',
|
||||
@ -20,6 +18,8 @@ const canBeSearchFields = [
|
||||
'sequence',
|
||||
];
|
||||
|
||||
const canBeCalculatedFields = ['integer', 'number'];
|
||||
|
||||
export const isTabSearchCollapsibleInputItem = (component: string) =>
|
||||
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 canBeDataField = (_interface: string) => canBeDataFields.includes(_interface);
|
||||
export const canBeSearchField = (_interface: string) => canBeSearchFields.includes(_interface);
|
||||
export const canBeCalculatedField = (_interface: string) => canBeCalculatedFields.includes(_interface);
|
||||
|
||||
export const convertFormat = (currentDate) => {
|
||||
return dayjs(currentDate).format('YYYY-MM-DD');
|
||||
|
@ -11,5 +11,7 @@
|
||||
"Configure fields": "Configure fields",
|
||||
"Configure columns": "Configure columns",
|
||||
"Add block": "Add block",
|
||||
"all": "all"
|
||||
"all": "all",
|
||||
"AllProducts": "AllProducts",
|
||||
"sort":"sort"
|
||||
}
|
||||
|
@ -11,5 +11,7 @@
|
||||
"Configure fields": "配置字段",
|
||||
"Configure columns": "配置字段",
|
||||
"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 {
|
||||
--nb-designer-top: ${token.marginMD}px;
|
||||
}
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
background: ${token.colorBgContainer};
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
|
@ -1764,6 +1764,24 @@ importers:
|
||||
specifier: ^6.26.0
|
||||
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:
|
||||
dependencies:
|
||||
'@nocobase/acl':
|
||||
@ -8708,7 +8726,6 @@ packages:
|
||||
resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==}
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.1
|
||||
dev: true
|
||||
|
||||
/@floating-ui/dom@0.4.5:
|
||||
resolution: {integrity: sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==}
|
||||
@ -8720,7 +8737,6 @@ packages:
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.6.0
|
||||
'@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):
|
||||
resolution: {integrity: sha512-tP2KEh7EHJr5hokSBHcPGojb+AorDNUf0NYfZGg/M+FsMvCOOsSEeEF0O1NDfETIzDnpbHnCs0DuvCFhSMSStg==}
|
||||
@ -8780,7 +8796,6 @@ packages:
|
||||
|
||||
/@floating-ui/utils@0.2.1:
|
||||
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):
|
||||
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/types': 9.6.1
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@react-spring/core@9.6.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==}
|
||||
@ -10223,11 +10237,9 @@ packages:
|
||||
'@react-spring/shared': 9.6.1(react@18.2.0)
|
||||
'@react-spring/types': 9.6.1
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@react-spring/rafz@9.6.1:
|
||||
resolution: {integrity: sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==}
|
||||
dev: true
|
||||
|
||||
/@react-spring/shared@9.6.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==}
|
||||
@ -10237,11 +10249,9 @@ packages:
|
||||
'@react-spring/rafz': 9.6.1
|
||||
'@react-spring/types': 9.6.1
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@react-spring/types@9.6.1:
|
||||
resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==}
|
||||
dev: true
|
||||
|
||||
/@react-spring/web@9.6.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==}
|
||||
@ -10255,7 +10265,6 @@ packages:
|
||||
'@react-spring/types': 9.6.1
|
||||
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):
|
||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
||||
@ -13114,7 +13123,6 @@ packages:
|
||||
|
||||
/@use-gesture/core@10.3.0:
|
||||
resolution: {integrity: sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==}
|
||||
dev: true
|
||||
|
||||
/@use-gesture/react@10.3.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==}
|
||||
@ -13123,7 +13131,6 @@ packages:
|
||||
dependencies:
|
||||
'@use-gesture/core': 10.3.0
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@vercel/ncc@0.36.1:
|
||||
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-source': 7.24.1(@babel/core@7.24.4)
|
||||
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:
|
||||
- supports-color
|
||||
|
||||
@ -13453,7 +13460,6 @@ packages:
|
||||
resize-observer-polyfill: 1.5.1
|
||||
screenfull: 5.2.0
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/ahooks@3.7.8(react@18.2.0):
|
||||
resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==}
|
||||
@ -13683,11 +13689,9 @@ packages:
|
||||
|
||||
/antd-mobile-icons@0.3.0:
|
||||
resolution: {integrity: sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==}
|
||||
dev: true
|
||||
|
||||
/antd-mobile-v5-count@1.0.1:
|
||||
resolution: {integrity: sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==}
|
||||
dev: true
|
||||
|
||||
/antd-mobile@5.35.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-UYr8T2Tlw3eXdyDYpWrPu54RWDcon4biI5VWHtMp9XxmSP5tzypRxoseh1ZGE28boQq9FKpSCC6FZ9rN0HXnQg==}
|
||||
@ -13716,7 +13720,6 @@ packages:
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- 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):
|
||||
resolution: {integrity: sha512-6aC4P9XyuVy0O7eZ+HZXd8GbbFX9HgzsXsJ341ihJhgqrfsQZNx8lDQvS2kCV6ao99QqtyTDphK9gWOgV2bHEw==}
|
||||
@ -22753,7 +22756,6 @@ packages:
|
||||
|
||||
/nano-memoize@3.0.16:
|
||||
resolution: {integrity: sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==}
|
||||
dev: true
|
||||
|
||||
/nanoid@3.3.4:
|
||||
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)
|
||||
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):
|
||||
resolution: {integrity: sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA==}
|
||||
@ -26945,7 +26946,6 @@ packages:
|
||||
|
||||
/runes2@1.1.4:
|
||||
resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==}
|
||||
dev: true
|
||||
|
||||
/rw@1.3.3:
|
||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
||||
@ -27809,7 +27809,6 @@ packages:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/static-extend@0.1.2:
|
||||
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
|
||||
@ -30017,7 +30016,6 @@ packages:
|
||||
rollup: 3.29.4
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: false
|
||||
|
||||
/vite@4.5.2(@types/node@20.12.7)(less@4.1.3):
|
||||
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
|
||||
@ -30054,6 +30052,7 @@ packages:
|
||||
rollup: 3.29.4
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@5.1.5(@types/node@20.12.7)(sass@1.75.0):
|
||||
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
|
||||
|