Compare commits

..

1 Commits

Author SHA1 Message Date
xiaomakuaiz 5078e93a4a
Merge f7c0fe273b into 3032384457 2025-11-07 07:11:51 +00:00
50 changed files with 3291 additions and 1238 deletions

View File

@ -1,59 +0,0 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { ComponentType } from 'react';
export interface SortableItemProps<T extends { id?: string | null }> {
id: string;
item: T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ItemComponent: ComponentType<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
function SortableItem<T extends { id?: string | null }>({
id,
item,
ItemComponent,
...rest
}: SortableItemProps<T>) {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<ItemComponent
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function createSortableItem(ItemComponent: ComponentType<any>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const WrappedComponent = (props: any) => (
<SortableItem {...props} ItemComponent={ItemComponent} />
);
WrappedComponent.displayName = `SortableItem(${ItemComponent.displayName || ItemComponent.name || 'Component'})`;
return WrappedComponent;
}
export default SortableItem;

View File

@ -1,5 +0,0 @@
export { default as DragList } from './DragList';
export type { DragListProps } from './DragList';
export { default as SortableItem, createSortableItem } from './SortableItem';
export type { SortableItemProps } from './SortableItem';

View File

@ -13,53 +13,26 @@ import {
arrayMove, arrayMove,
rectSortingStrategy, rectSortingStrategy,
SortableContext, SortableContext,
SortingStrategy,
} from '@dnd-kit/sortable'; } from '@dnd-kit/sortable';
import { Stack, SxProps, Theme } from '@mui/material'; import { Stack } from '@mui/material';
import { import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
ComponentType, import Item from './Item';
CSSProperties, import SortableItem from './SortableItem';
Dispatch,
SetStateAction,
useCallback,
useState,
} from 'react';
export interface DragListProps<T extends { id?: string | null }> { type ItemType = {
data: T[]; id: string;
onChange: (data: T[]) => void; text: string;
type: 'contained' | 'outlined' | 'text';
href: string;
};
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>; setIsEdit: Dispatch<SetStateAction<boolean>>;
SortableItemComponent: ComponentType<{
id: string;
item: T;
handleRemove: (id: string) => void;
handleUpdateItem: (item: T) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}>;
ItemComponent: ComponentType<{
isDragging?: boolean;
item: T;
style?: CSSProperties;
setIsEdit: Dispatch<SetStateAction<boolean>>;
handleUpdateItem?: (item: T) => void;
}>;
containerSx?: SxProps<Theme>;
sortingStrategy?: SortingStrategy;
direction?: 'row' | 'column';
gap?: number;
} }
function DragList<T extends { id?: string | null }>({ const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
data,
onChange,
setIsEdit,
SortableItemComponent,
ItemComponent,
containerSx,
sortingStrategy = rectSortingStrategy,
direction = 'row',
gap = 2,
}: DragListProps<T>) {
const [activeId, setActiveId] = useState<string | null>(null); const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor)); const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
@ -71,8 +44,8 @@ function DragList<T extends { id?: string | null }>({
(event: DragEndEvent) => { (event: DragEndEvent) => {
const { active, over } = event; const { active, over } = event;
if (active.id !== over?.id) { if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => (item.id || '') === active.id); const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => (item.id || '') === over!.id); const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex); const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData); onChange(newData);
} }
@ -87,16 +60,21 @@ function DragList<T extends { id?: string | null }>({
const handleRemove = useCallback( const handleRemove = useCallback(
(id: string) => { (id: string) => {
const newData = data.filter(item => (item.id || '') !== id); const newData = data.filter(item => item.id !== id);
onChange(newData); onChange(newData);
}, },
[data, onChange], [data, onChange],
); );
const handleUpdateItem = useCallback( const handleUpdateItem = useCallback(
(updatedItem: T) => { (updatedItem: {
id: string;
text: string;
type: 'contained' | 'outlined' | 'text';
href: string;
}) => {
const newData = data.map(item => const newData = data.map(item =>
(item.id || '') === (updatedItem.id || '') ? updatedItem : item, item.id === updatedItem.id ? updatedItem : item,
); );
onChange(newData); onChange(newData);
}, },
@ -114,19 +92,14 @@ function DragList<T extends { id?: string | null }>({
onDragCancel={handleDragCancel} onDragCancel={handleDragCancel}
> >
<SortableContext <SortableContext
items={data.map(item => item.id || '')} items={data.map(item => item.id)}
strategy={sortingStrategy} strategy={rectSortingStrategy}
> >
<Stack <Stack direction={'row'} flexWrap={'wrap'} gap={2}>
direction={direction}
flexWrap={'wrap'}
gap={gap}
sx={containerSx}
>
{data.map(item => ( {data.map(item => (
<SortableItemComponent <SortableItem
key={item.id || ''} key={item.id}
id={item.id || ''} id={item.id}
item={item} item={item}
handleRemove={handleRemove} handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem} handleUpdateItem={handleUpdateItem}
@ -137,9 +110,9 @@ function DragList<T extends { id?: string | null }>({
</SortableContext> </SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}> <DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? ( {activeId ? (
<ItemComponent <Item
isDragging isDragging
item={data.find(item => (item.id || '') === activeId)!} item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem} handleUpdateItem={handleUpdateItem}
/> />
@ -147,6 +120,6 @@ function DragList<T extends { id?: string | null }>({
</DragOverlay> </DragOverlay>
</DndContext> </DndContext>
); );
} };
export default DragList; export default DragList;

View File

@ -1,141 +0,0 @@
import { Box, IconButton, Stack, TextField } from '@mui/material';
import { Icon } from '@ctzhian/ui';
import {
CSSProperties,
Dispatch,
forwardRef,
HTMLAttributes,
SetStateAction,
} from 'react';
type HotSearchItem = {
id: string;
text: string;
};
export type HotSearchItemProps = Omit<
HTMLAttributes<HTMLDivElement>,
'onChange'
> & {
item: HotSearchItem;
withOpacity?: boolean;
isDragging?: boolean;
dragHandleProps?: React.HTMLAttributes<HTMLDivElement>;
handleRemove?: (id: string) => void;
handleUpdateItem?: (item: HotSearchItem) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
};
const HotSearchItem = forwardRef<HTMLDivElement, HotSearchItemProps>(
(
{
item,
withOpacity,
isDragging,
style,
dragHandleProps,
handleRemove,
handleUpdateItem,
setIsEdit,
...props
},
ref,
) => {
const inlineStyles: CSSProperties = {
opacity: withOpacity ? '0.5' : '1',
borderRadius: '10px',
cursor: isDragging ? 'grabbing' : 'grab',
backgroundColor: '#ffffff',
width: '100%',
...style,
};
return (
<Box ref={ref} style={inlineStyles} {...props}>
<Stack
direction={'row'}
alignItems={'center'}
justifyContent={'space-between'}
gap={0.5}
sx={{
py: 1.5,
px: 1,
border: '1px solid',
borderColor: 'divider',
borderRadius: '10px',
}}
>
<Stack
direction={'column'}
gap={'20px'}
sx={{
flex: 1,
p: 1.5,
}}
>
<TextField
label='搜索关键词'
slotProps={{
inputLabel: {
shrink: true,
},
}}
sx={{
height: '36px',
'& .MuiOutlinedInput-root': {
height: '36px',
padding: '0 12px',
'& .MuiOutlinedInput-input': {
padding: '8px 0',
},
},
}}
fullWidth
placeholder='请输入搜索关键词'
variant='outlined'
value={item.text}
onChange={e => {
const updatedItem = { ...item, text: e.target.value };
handleUpdateItem?.(updatedItem);
setIsEdit(true);
}}
/>
</Stack>
<Stack
direction={'column'}
sx={{ justifyContent: 'space-between', alignSelf: 'stretch' }}
>
<IconButton
size='small'
onClick={e => {
e.stopPropagation();
handleRemove?.(item.id);
}}
sx={{
color: 'text.tertiary',
':hover': { color: 'error.main' },
width: '28px',
height: '28px',
}}
>
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
</IconButton>
<IconButton
size='small'
sx={{
cursor: 'grab',
color: 'text.secondary',
'&:hover': { color: 'primary.main' },
}}
{...(dragHandleProps as any)}
>
<Icon type='icon-drag' />
</IconButton>
</Stack>
</Stack>
</Box>
);
},
);
export default HotSearchItem;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import Item, { ItemProps } from './Item';
type FaqSortableItemProps = ItemProps & {};
const FaqSortableItem: FC<FaqSortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<Item
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default FaqSortableItem;

View File

@ -1,21 +1,18 @@
import React, { useEffect, useRef } from 'react'; import React, { useState, useEffect } from 'react';
import { TextField } from '@mui/material'; import { TextField, Chip, Autocomplete, Box } from '@mui/material';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import HotSearchItem from './HotSearchItem';
import UploadFile from '@/components/UploadFile'; import UploadFile from '@/components/UploadFile';
import { DEFAULT_DATA } from '../../../constants'; import { DEFAULT_DATA } from '../../../constants';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
import { handleLandingConfigs, findConfigById } from '../../../utils'; import { handleLandingConfigs, findConfigById } from '../../../utils';
import { Empty } from '@ctzhian/ui';
const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => { const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
const { appPreviewData } = useAppSelector(state => state.config); const { appPreviewData } = useAppSelector(state => state.config);
const [inputValue, setInputValue] = useState('');
const { control, watch, setValue, subscribe } = useForm< const { control, watch, setValue, subscribe } = useForm<
typeof DEFAULT_DATA.banner typeof DEFAULT_DATA.banner
>({ >({
@ -27,49 +24,6 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
const debouncedDispatch = useDebounceAppPreviewData(); const debouncedDispatch = useDebounceAppPreviewData();
const btns = watch('btns') || []; const btns = watch('btns') || [];
const hotSearch = watch('hot_search') || [];
// 使用 ref 来维护稳定的 ID 映射
const idMapRef = useRef<Map<number, string>>(new Map());
// 将string[]转换为对象数组用于显示,保持 ID 稳定
const hotSearchList = Array.isArray(hotSearch)
? hotSearch.map((text, index) => {
// 如果该索引没有 ID生成一个新的
if (!idMapRef.current.has(index)) {
idMapRef.current.set(
index,
`${Date.now()}-${index}-${Math.random()}`,
);
}
return {
id: idMapRef.current.get(index)!,
text: String(text),
};
})
: [];
// 清理不再使用的 ID并确保所有索引都有 ID
useEffect(() => {
const currentIndexes = new Set(hotSearch.map((_, index) => index));
// 清理不存在的索引
const keysToDelete: number[] = [];
idMapRef.current.forEach((_, key) => {
if (!currentIndexes.has(key)) {
keysToDelete.push(key);
}
});
keysToDelete.forEach(key => idMapRef.current.delete(key));
// 确保每个索引都有 ID
hotSearch.forEach((_, index) => {
if (!idMapRef.current.has(index)) {
idMapRef.current.set(index, `${Date.now()}-${index}-${Math.random()}`);
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hotSearch.length]);
const handleAddButton = () => { const handleAddButton = () => {
const nextId = `${Date.now()}`; const nextId = `${Date.now()}`;
@ -79,31 +33,6 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
]); ]);
}; };
const handleAddHotSearch = () => {
const newIndex = hotSearch.length;
const nextId = `${Date.now()}-${newIndex}-${Math.random()}`;
idMapRef.current.set(newIndex, nextId);
// 转换回string[]格式
setValue('hot_search', [...hotSearch, '']);
setIsEdit(true);
};
const handleHotSearchChange = (newList: { id: string; text: string }[]) => {
// 重建 ID 映射关系
const newIdMap = new Map<number, string>();
newList.forEach((item, index) => {
newIdMap.set(index, item.id);
});
idMapRef.current = newIdMap;
// 转换回string[]格式
setValue(
'hot_search',
newList.map(item => item.text),
);
setIsEdit(true);
};
useEffect(() => { useEffect(() => {
const callback = subscribe({ const callback = subscribe({
formState: { formState: {
@ -130,7 +59,6 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
return () => { return () => {
callback(); callback();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [subscribe]); }, [subscribe]);
return ( return (
@ -204,20 +132,60 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
render={({ field }) => <TextField {...field} placeholder='请输入' />} render={({ field }) => <TextField {...field} placeholder='请输入' />}
/> />
</CommonItem> </CommonItem>
<CommonItem title='热门搜索' onAdd={handleAddHotSearch}> <CommonItem title='热门搜索'>
{hotSearchList.length === 0 ? ( <Controller
<Empty /> control={control}
) : ( name='hot_search'
<DragList render={({ field }) => (
data={hotSearchList} <Autocomplete
onChange={handleHotSearchChange} {...field}
setIsEdit={setIsEdit} value={field.value || []}
SortableItemComponent={sortableProps => ( multiple
<SortableItem {...sortableProps} ItemComponent={HotSearchItem} /> freeSolo
)} fullWidth
ItemComponent={HotSearchItem} options={[]}
/> inputValue={inputValue}
)} onInputChange={(_, newInputValue) => setInputValue(newInputValue)}
onChange={(_, newValue) => {
setIsEdit(true);
const newValues = [...new Set(newValue as string[])];
field.onChange(newValues);
}}
onBlur={() => {
setIsEdit(true);
const trimmedValue = inputValue.trim();
if (trimmedValue && !field.value?.includes(trimmedValue)) {
field.onChange([...(field.value || []), trimmedValue]);
}
setInputValue('');
}}
renderValue={(value, getTagProps) => {
return value.map((option, index: number) => {
return (
<Chip
variant='outlined'
size='small'
label={
<Box sx={{ fontSize: '12px' }}>
{option as React.ReactNode}
</Box>
}
{...getTagProps({ index })}
key={index}
/>
);
});
}}
renderInput={params => (
<TextField
{...params}
placeholder='回车确认,填写下一个热门搜索'
variant='outlined'
/>
)}
/>
)}
/>
</CommonItem> </CommonItem>
<CommonItem title='主按钮' onAdd={handleAddButton}> <CommonItem title='主按钮' onAdd={handleAddButton}>
<DragList <DragList
@ -227,10 +195,6 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
setIsEdit(true); setIsEdit(true);
}} }}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
</CommonItem> </CommonItem>
</StyledCommonWrapper> </StyledCommonWrapper>

View File

@ -0,0 +1,114 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item from './Item';
import { DomainRecommendNodeListResp } from '@/request/types';
import SortableItem from './SortableItem';
interface DragListProps {
data: DomainRecommendNodeListResp[];
onChange: (data: DomainRecommendNodeListResp[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: DomainRecommendNodeListResp) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id!)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
// handleUpdateItem={handleUpdateItem}
// setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
// setIsEdit={setIsEdit}
// handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {};
const FaqSortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id! });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default FaqSortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect, useState } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import BasicDocDragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { Empty } from '@ctzhian/ui'; import { Empty } from '@ctzhian/ui';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
@ -118,17 +116,13 @@ const BasicDocConfig = ({ setIsEdit, id }: ConfigProps) => {
{nodes.length === 0 ? ( {nodes.length === 0 ? (
<Empty /> <Empty />
) : ( ) : (
<DragList <BasicDocDragList
data={nodes} data={nodes}
onChange={value => { onChange={value => {
setIsEdit(true); setIsEdit(true);
setValue('nodes', value); setValue('nodes', value);
}} }}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,113 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item, { type ItemType } from './Item';
import SortableItem from './SortableItem';
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import Item, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<Item
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -93,10 +91,6 @@ const Config = ({ setIsEdit, id }: ConfigProps) => {
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,120 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item from './Item';
import FaqSortableItem from './SortableItem';
type ItemType = {
id: string;
title: string;
url: string;
desc: string;
};
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<FaqSortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { ItemProps } from './Item';
type FaqSortableItemProps = ItemProps & {};
const FaqSortableItem: FC<FaqSortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default FaqSortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -123,10 +121,6 @@ const Config = ({ setIsEdit, id }: ConfigProps) => {
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,113 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item, { type ItemType } from './Item';
import SortableItem from './SortableItem';
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { ItemTypeProps } from './Item';
type SortableItemProps = ItemTypeProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -92,10 +90,6 @@ const CaseConfig = ({ setIsEdit, id }: ConfigProps) => {
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,113 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item, { type ItemType } from './Item';
import SortableItem from './SortableItem';
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { ItemTypeProps } from './Item';
type SortableItemProps = ItemTypeProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -95,10 +93,6 @@ const Config = ({ setIsEdit, id }: ConfigProps) => {
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,114 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item from './Item';
import { DomainRecommendNodeListResp } from '@/request/types';
import SortableItem from './SortableItem';
interface DragListProps {
data: DomainRecommendNodeListResp[];
onChange: (data: DomainRecommendNodeListResp[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: DomainRecommendNodeListResp) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id!)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
// handleUpdateItem={handleUpdateItem}
// setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
// setIsEdit={setIsEdit}
// handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import Item, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id! });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<Item
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect, useState } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import BasicDocDragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import { Empty } from '@ctzhian/ui'; import { Empty } from '@ctzhian/ui';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
@ -119,17 +117,13 @@ const DirDocConfig = ({ setIsEdit, id }: ConfigProps) => {
{nodes.length === 0 ? ( {nodes.length === 0 ? (
<Empty /> <Empty />
) : ( ) : (
<DragList <BasicDocDragList
data={nodes} data={nodes}
onChange={value => { onChange={value => {
setIsEdit(true); setIsEdit(true);
setValue('nodes', value); setValue('nodes', value);
}} }}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,119 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import FaqItem from './FaqItem';
import FaqSortableItem from './FaqSortableItem';
type FaqItemType = {
id: string;
question: string;
link: string;
};
interface FaqDragListProps {
data: FaqItemType[];
onChange: (data: FaqItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const FaqDragList: FC<FaqDragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: FaqItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<FaqSortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<FaqItem
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default FaqDragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { FaqItemProps } from './FaqItem';
type FaqSortableItemProps = FaqItemProps & {};
const FaqSortableItem: FC<FaqSortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default FaqSortableItem;

View File

@ -2,14 +2,13 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import FaqDragList from './FaqDragList';
import SortableItem from '../../components/SortableItem';
import FaqItem from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
import { Empty } from '@ctzhian/ui'; import { Empty } from '@ctzhian/ui';
import { DEFAULT_DATA } from '../../../constants'; import { DEFAULT_DATA } from '../../../constants';
import ColorPickerField from '../../components/ColorPickerField';
import { findConfigById, handleLandingConfigs } from '../../../utils'; import { findConfigById, handleLandingConfigs } from '../../../utils';
const FaqConfig = ({ setIsEdit, id }: ConfigProps) => { const FaqConfig = ({ setIsEdit, id }: ConfigProps) => {
@ -112,14 +111,10 @@ const FaqConfig = ({ setIsEdit, id }: ConfigProps) => {
{list.length === 0 ? ( {list.length === 0 ? (
<Empty /> <Empty />
) : ( ) : (
<DragList <FaqDragList
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={FaqItem} />
)}
ItemComponent={FaqItem}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,113 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item, { type ItemType } from './Item';
import SortableItem from './SortableItem';
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import Item, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<Item
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -91,10 +89,6 @@ const Config = ({ setIsEdit, id }: ConfigProps) => {
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,113 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item, { type ItemType } from './Item';
import SortableItem from './SortableItem';
interface DragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { ItemTypeProps } from './Item';
type SortableItemProps = ItemTypeProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -92,10 +90,6 @@ const MetricsConfig = ({ setIsEdit, id }: ConfigProps) => {
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,113 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item, { ItemType } from './Item';
import SortableItem from './SortableItem';
interface FaqDragListProps {
data: ItemType[];
onChange: (data: ItemType[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const FaqDragList: FC<FaqDragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: ItemType) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
handleUpdateItem={handleUpdateItem}
setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
setIsEdit={setIsEdit}
handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default FaqDragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import Item, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {};
const SortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<Item
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default SortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import FaqDragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData'; import useDebounceAppPreviewData from '@/hooks/useDebounceAppPreviewData';
@ -90,14 +88,10 @@ const FaqConfig = ({ setIsEdit, id }: ConfigProps) => {
{list.length === 0 ? ( {list.length === 0 ? (
<Empty /> <Empty />
) : ( ) : (
<DragList <FaqDragList
data={list} data={list}
onChange={handleListChange} onChange={handleListChange}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -0,0 +1,114 @@
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { Stack } from '@mui/material';
import { Dispatch, FC, SetStateAction, useCallback, useState } from 'react';
import Item from './Item';
import { DomainRecommendNodeListResp } from '@/request/types';
import SortableItem from './SortableItem';
interface DragListProps {
data: DomainRecommendNodeListResp[];
onChange: (data: DomainRecommendNodeListResp[]) => void;
setIsEdit: Dispatch<SetStateAction<boolean>>;
}
const DragList: FC<DragListProps> = ({ data, onChange, setIsEdit }) => {
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = data.findIndex(item => item.id === active.id);
const newIndex = data.findIndex(item => item.id === over!.id);
const newData = arrayMove(data, oldIndex, newIndex);
onChange(newData);
}
setActiveId(null);
},
[data, onChange],
);
const handleDragCancel = useCallback(() => {
setActiveId(null);
}, []);
const handleRemove = useCallback(
(id: string) => {
const newData = data.filter(item => item.id !== id);
onChange(newData);
},
[data, onChange],
);
const handleUpdateItem = useCallback(
(updatedItem: DomainRecommendNodeListResp) => {
const newData = data.map(item =>
item.id === updatedItem.id ? updatedItem : item,
);
onChange(newData);
},
[data, onChange],
);
if (data.length === 0) return null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext
items={data.map(item => item.id!)}
strategy={rectSortingStrategy}
>
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{data.map(item => (
<SortableItem
key={item.id}
id={item.id}
item={item}
handleRemove={handleRemove}
// handleUpdateItem={handleUpdateItem}
// setIsEdit={setIsEdit}
/>
))}
</Stack>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? (
<Item
isDragging
item={data.find(item => item.id === activeId)!}
// setIsEdit={setIsEdit}
// handleUpdateItem={handleUpdateItem}
/>
) : null}
</DragOverlay>
</DndContext>
);
};
export default DragList;

View File

@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { FC } from 'react';
import FaqItem, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {};
const FaqSortableItem: FC<SortableItemProps> = ({ item, ...rest }) => {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id! });
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
};
return (
<FaqItem
ref={setNodeRef}
style={style}
withOpacity={isDragging}
dragHandleProps={{
...attributes,
...listeners,
}}
item={item}
{...rest}
/>
);
};
export default FaqSortableItem;

View File

@ -2,9 +2,7 @@ import React, { useEffect, useState } from 'react';
import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon'; import { CommonItem, StyledCommonWrapper } from '../../components/StyledCommon';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import DragList from '../../components/DragList'; import DragList from './DragList';
import SortableItem from '../../components/SortableItem';
import Item from './Item';
import { Empty } from '@ctzhian/ui'; import { Empty } from '@ctzhian/ui';
import type { ConfigProps } from '../type'; import type { ConfigProps } from '../type';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
@ -126,10 +124,6 @@ const SimpleDocConfigConfig = ({ setIsEdit, id }: ConfigProps) => {
setValue('nodes', value); setValue('nodes', value);
}} }}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
SortableItemComponent={sortableProps => (
<SortableItem {...sortableProps} ItemComponent={Item} />
)}
ItemComponent={Item}
/> />
)} )}
</CommonItem> </CommonItem>

View File

@ -3,6 +3,7 @@ import { useURLSearchParams } from '@/hooks';
import { ConstsUserRole } from '@/request/types'; import { ConstsUserRole } from '@/request/types';
import { useAppDispatch, useAppSelector } from '@/store'; import { useAppDispatch, useAppSelector } from '@/store';
import { setKbC, setKbId } from '@/store/slices/config'; import { setKbC, setKbId } from '@/store/slices/config';
import custom from '@/themes/custom';
import { Ellipsis, Icon, message } from '@ctzhian/ui'; import { Ellipsis, Icon, message } from '@ctzhian/ui';
import { import {
Box, Box,
@ -116,7 +117,7 @@ const KBSelect = () => {
borderRadius: '5px', borderRadius: '5px',
bgcolor: 'background.paper3', bgcolor: 'background.paper3',
'&:hover': { '&:hover': {
bgcolor: 'rgba(50,72,242,0.1)', bgcolor: custom.selectedMenuItemBgColor,
}, },
}} }}
fullWidth fullWidth

View File

@ -0,0 +1,6 @@
import custom from './custom';
import dark from './dark';
import light from './light';
export { custom, dark, light };
export type ThemeColor = typeof light;

View File

@ -0,0 +1,4 @@
export default {
selectPopupBoxShadow: '0px 10px 20px 0px rgba(54,59,76,0.2)',
selectedMenuItemBgColor: 'rgba(50,72,242,0.1)',
};

View File

@ -0,0 +1,91 @@
const dark = {
cssVariables: true,
primary: {
main: '#fdfdfd',
contrastText: '#000',
},
secondary: {
main: '#2196F3',
lighter: '#D6E4FF',
light: '#84A9FF',
dark: '#1939B7',
darker: '#091A7A',
contrastText: '#fff',
},
info: {
main: '#1890FF',
lighter: '#D0F2FF',
light: '#74CAFF',
dark: '#0C53B7',
darker: '#04297A',
contrastText: '#fff',
},
success: {
main: '#00DF98',
lighter: '#E9FCD4',
light: '#AAF27F',
dark: '#229A16',
darker: '#08660D',
contrastText: 'rgba(0,0,0,0.7)',
},
warning: {
main: '#F7B500',
lighter: '#FFF7CD',
light: '#FFE16A',
dark: '#B78103',
darker: '#7A4F01',
contrastText: 'rgba(0,0,0,0.7)',
},
neutral: {
main: '#1A1A1A',
contrastText: 'rgba(255, 255, 255, 0.60)',
},
error: {
main: '#D93940',
lighter: '#FFE7D9',
light: '#FFA48D',
dark: '#B72136',
darker: '#7A0C2E',
contrastText: '#fff',
},
text: {
primary: '#fff',
secondary: 'rgba(255,255,255,0.7)',
auxiliary: 'rgba(255,255,255,0.5)',
disabled: 'rgba(255,255,255,0.26)',
slave: 'rgba(255,255,255,0.05)',
inverseAuxiliary: 'rgba(0,0,0,0.5)',
inverseDisabled: 'rgba(0,0,0,0.15)',
},
divider: '#ededed',
background: {
paper: '#18181b',
paper2: '#060608',
paper3: '#27272a',
default: 'rgba(255,255,255,0.6)',
disabled: 'rgba(15,15,15,0.8)',
chip: 'rgba(145,147,171,0.16)',
circle: '#3B476A',
focus: '#542996',
},
common: {},
shadows: 'transparent',
table: {
head: {
backgroundColor: '#484848',
color: '#fff',
},
row: {
backgroundColor: 'transparent',
hoverColor: 'rgba(48, 58, 70, 0.4)',
},
cell: {
borderColor: '#484848',
},
},
charts: {
color: ['#7267EF', '#36B37E'],
},
};
export default dark;

View File

@ -0,0 +1,95 @@
const light = {
cssVariables: true,
primary: {
main: '#3248F2',
contrastText: '#fff',
lighter: '#E6E8EC',
},
secondary: {
main: '#3366FF',
lighter: '#D6E4FF',
light: '#84A9FF',
dark: '#1939B7',
darker: '#091A7A',
contrastText: '#fff',
},
info: {
main: '#0063FF',
lighter: '#D0F2FF',
light: '#74CAFF',
dark: '#0C53B7',
darker: '#04297A',
contrastText: '#fff',
},
success: {
main: '#82DDAF',
lighter: '#E9FCD4',
light: '#AAF27F',
mainShadow: '#36B37E',
dark: '#229A16',
darker: '#08660D',
contrastText: 'rgba(0,0,0,0.7)',
},
warning: {
main: '#FEA145',
lighter: '#FFF7CD',
light: '#FFE16A',
shadow: 'rgba(255, 171, 0, 0.15)',
dark: '#B78103',
darker: '#7A4F01',
contrastText: 'rgba(0,0,0,0.7)',
},
neutral: {
main: '#FFFFFF',
contrastText: 'rgba(0, 0, 0, 0.60)',
},
error: {
main: '#FE4545',
lighter: '#FFE7D9',
light: '#FFA48D',
shadow: 'rgba(255, 86, 48, 0.15)',
dark: '#B72136',
darker: '#7A0C2E',
contrastText: '#FFFFFF',
},
divider: '#ECEEF1',
text: {
primary: '#21222D',
secondary: 'rgba(33,34,35,0.7)',
auxiliary: 'rgba(33,34,35,0.5)',
slave: 'rgba(33,34,35,0.3)',
disabled: 'rgba(33,34,35,0.2)',
inverse: '#FFFFFF',
inverseAuxiliary: 'rgba(255,255,255,0.5)',
inverseDisabled: 'rgba(255,255,255,0.15)',
},
background: {
paper: '#FFFFFF',
paper2: '#F1F2F8',
paper3: '#F8F9FA',
default: '#FFFFFF',
chip: '#FFFFFF',
circle: '#E6E8EC',
hover: 'rgba(243, 244, 245, 0.5)',
},
shadows: 'rgba(68, 80 ,91, 0.1)',
table: {
head: {
height: '50px',
backgroundColor: '#FFFFFF',
color: '#000',
},
row: {
hoverColor: '#F8F9FA',
},
cell: {
height: '72px',
borderColor: '#ECEEF1',
},
},
charts: {
color: ['#673AB7', '#36B37E'],
},
};
export default light;

View File

@ -0,0 +1,460 @@
import { addOpacityToColor } from '@/utils';
import { custom, ThemeColor } from './color';
declare module '@mui/material/styles' {}
declare module '@mui/material/styles' {
interface TypeBackground {
paper0?: string;
paper2?: string;
chip?: string;
circle?: string;
hover?: string;
focus?: string;
disabled?: string;
}
}
const componentStyleOverrides = (theme: ThemeColor) => {
return {
MuiTabs: {
styleOverrides: {
root: {
borderRadius: '10px !important',
overflow: 'hidden',
minHeight: '36px',
height: '36px',
padding: '0px !important',
},
indicator: {
borderRadius: '0px !important',
overflow: 'hidden',
backgroundColor: '#21222D !important',
},
},
},
MuiTab: {
styleOverrides: {
root: {
borderRadius: '0px !important',
fontWeight: 'normal',
fontSize: '14px !important',
lineHeight: '34px',
padding: '0 16px !important',
},
},
},
MuiFormLabel: {
styleOverrides: {
asterisk: {
color: theme.error.main,
},
},
},
MuiButton: {
styleOverrides: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
root: ({ ownerState }: { ownerState: any }) => {
return {
height: '36px',
fontSize: 14,
lineHeight: '36px',
paddingLeft: '16px',
paddingRight: '16px',
boxShadow: 'none',
transition: 'all 0.2s ease-in-out',
borderRadius: '10px',
fontWeight: '400',
...(ownerState.variant === 'contained' && {
color: theme.text.inverse,
backgroundColor: theme.text.primary,
}),
...(ownerState.variant === 'text' && {}),
...(ownerState.variant === 'outlined' && {
color: theme.text.primary,
border: `1px solid ${theme.text.primary}`,
}),
...(ownerState.disabled === true && {
cursor: 'not-allowed !important',
}),
...(ownerState.size === 'small' && {
height: '32px',
lineHeight: '32px',
}),
'&:hover': {
boxShadow: 'none',
...(ownerState.variant === 'contained' && {
backgroundColor: addOpacityToColor(theme.text.primary, 0.9),
}),
...(ownerState.variant === 'text' && {
backgroundColor: theme.background.paper3,
}),
...(ownerState.variant === 'outlined' && {
backgroundColor: theme.background.paper3,
}),
...(ownerState.color === 'neutral' && {
color: theme.text.primary,
}),
},
};
},
startIcon: {
marginLeft: 0,
marginRight: 8,
'>*:nth-of-type(1)': {
fontSize: 14,
},
},
},
},
MuiTooltip: {
styleOverrides: {
tooltip: {
borderRadius: '10px',
maxWidth: '600px',
padding: '8px 16px',
backgroundColor: theme.text.primary,
fontSize: '12px',
lineHeight: '20px',
color: theme.primary.contrastText,
},
arrow: {
color: theme.text.primary,
},
},
},
MuiFormHelperText: {
styleOverrides: {
root: {
color: theme.error.main,
},
},
},
MuiFormControl: {
styleOverrides: {
root: {
'.MuiFormLabel-asterisk': {
color: theme.error.main,
},
},
},
},
MuiFormControlLabel: {
styleOverrides: {
root: {
marginLeft: '0 !important',
},
},
},
MuiTableBody: {
styleOverrides: {
root: {
'.MuiTableRow-root:hover': {
'.MuiTableCell-root:not(.cx-table-empty-td)': {
backgroundColor: theme.table.row.hoverColor,
overflowX: 'hidden',
'.primary-color': {
color: theme.primary.main,
},
'.no-title-url': {
color: `${theme.primary.main} !important`,
},
'.error-color': {
opacity: 1,
},
},
},
},
},
},
MuiCheckbox: {
styleOverrides: {
root: {
padding: 0,
svg: {
fontSize: '18px',
},
},
},
},
MuiTableCell: {
styleOverrides: {
root: {
background: theme.background.paper,
lineHeight: 1.5,
height: theme.table.cell.height,
fontSize: '14px',
paddingTop: '16px !important',
paddingBottom: '16px !important',
paddingLeft: 0,
'&:first-of-type': {
paddingLeft: '0px',
},
'&:not(:first-of-type)': {
paddingLeft: '0px',
},
'.MuiCheckbox-root': {
color: '#CCCCCC',
svg: {
fontSize: '16px',
},
'&.Mui-checked': {
color: theme.text.primary,
},
},
},
head: {
backgroundColor: theme.background.paper3,
color: theme.table.head.color,
fontSize: '12px',
height: theme.table.head.height,
paddingTop: '0 !important',
paddingBottom: '0 !important',
borderSpacing: '12px',
zIndex: 100,
},
body: {
borderBottom: '1px dashed',
borderColor: theme.table.cell.borderColor,
borderSpacing: '12px',
},
},
},
MuiPopover: {
styleOverrides: {
paper: {
borderRadius: '10px',
boxShadow: custom.selectPopupBoxShadow,
},
},
},
MuiMenu: {
styleOverrides: {
paper: {
padding: '4px',
borderRadius: '10px',
backgroundColor: theme.background.paper,
boxShadow: custom.selectPopupBoxShadow,
},
list: {
paddingTop: '0px !important',
paddingBottom: '0px !important',
},
},
defaultProps: {
elevation: 0,
},
},
MuiMenuItem: {
styleOverrides: {
root: {
height: '40px',
borderRadius: '5px',
':hover': {
backgroundColor: theme.background.paper3,
},
'&.Mui-selected': {
fontWeight: '500',
backgroundColor: `${custom.selectedMenuItemBgColor} !important`,
color: theme.primary.main,
},
},
},
},
MuiPaper: {
defaultProps: {
elevation: 1,
},
styleOverrides: {
root: ({ ownerState }: { ownerState: { elevation?: number } }) => {
return {
...(ownerState.elevation === 0 && {
backgroundColor: theme.background.paper2,
}),
...(ownerState.elevation === 2 && {
backgroundColor: theme.background.paper3,
}),
backgroundImage: 'none',
};
},
},
},
MuiChip: {
styleOverrides: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
root: ({ ownerState }: { ownerState: any }) => {
return {
height: '24px',
lineHeight: '24px',
borderRadius: '8px',
'.MuiChip-label': {
padding: '0 8px 0 4px',
},
...(ownerState.color === 'default' && {
backgroundColor: theme.background.chip,
borderColor: theme.text.disabled,
'.Mui-focusVisible': {
backgroundColor: theme.background.chip,
},
}),
...(ownerState.color === 'error' && {
backgroundColor: addOpacityToColor(theme.error.main, 0.1),
}),
};
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
label: ({ ownerState }: { ownerState: any }) => {
return {
padding: '0 14px',
fontSize: '14px',
lineHeight: '24px',
...(ownerState.color === 'default' && {
color: theme.text.primary,
}),
};
},
deleteIcon: {
fontSize: '14px',
color: theme.text.disabled,
},
},
},
MuiAppBar: {
defaultProps: {
elevation: 1,
},
},
MuiDialog: {
styleOverrides: {
root: {
'h2.MuiTypography-root button': {
marginRight: '2px',
},
'.MuiDialogActions-root': {
paddingTop: '24px',
button: {
width: '88px',
height: '36px !important',
},
'.MuiButton-text': {
width: 'auto',
minWidth: 'auto',
color: `${theme.text.primary} !important`,
},
},
},
container: {
height: '100vh',
bgcolor: theme.text.secondary,
backdropFilter: 'blur(5px)',
},
paper: {
pb: 1,
border: '1px solid',
borderColor: theme.divider,
borderRadius: '10px',
backgroundColor: theme.background.paper,
textarea: {
borderRadius: '8px 8px 0 8px',
},
},
},
},
MuiDialogTitle: {
styleOverrides: {
root: {
paddingTop: '24px',
'> button': {
top: '20px',
},
},
},
},
MuiAlert: {
styleOverrides: {
root: {
lineHeight: '22px',
paddingTop: '1px',
paddingBottom: '1px',
borderRadius: '10px',
boxShadow: 'none',
},
icon: {
padding: '10px 0',
},
standardInfo: {
backgroundColor: addOpacityToColor(theme.primary.main, 0.1),
color: theme.text.primary,
},
},
},
MuiRadio: {
styleOverrides: {
root: {
padding: 0,
marginRight: '8px',
},
},
},
MuiTextField: {
styleOverrides: {
root: {
label: {
color: theme.text.secondary,
},
'label.Mui-focused': {
color: theme.text.primary,
},
'& .MuiInputBase-input::placeholder': {
fontSize: '12px',
},
},
},
},
MuiInputBase: {
styleOverrides: {
root: {
borderRadius: '10px !important',
backgroundColor: theme.background.paper3,
'.MuiOutlinedInput-notchedOutline': {
borderColor: `${theme.background.paper3} !important`,
borderWidth: '1px !important',
},
'&.Mui-focused': {
'.MuiOutlinedInput-notchedOutline': {
borderColor: `${theme.text.primary} !important`,
borderWidth: '1px !important',
},
},
'&:hover': {
'.MuiOutlinedInput-notchedOutline': {
borderColor: `${theme.text.primary} !important`,
borderWidth: '1px !important',
},
},
input: {
height: '19px',
'&.Mui-disabled': {
color: `${theme.text.secondary} !important`,
WebkitTextFillColor: `${theme.text.secondary} !important`,
},
},
},
},
},
MuiSelect: {
styleOverrides: {
root: {
height: '36px',
borderRadius: '10px !important',
backgroundColor: theme.background.paper3,
},
select: {
paddingRight: '0 !important',
},
},
},
};
};
export default componentStyleOverrides;

View File

@ -25,6 +25,7 @@
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"html-react-parser": "^5.2.5", "html-react-parser": "^5.2.5",
"html-to-image": "^1.11.13", "html-to-image": "^1.11.13",
"import-in-the-middle": "^1.14.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"katex": "^0.16.22", "katex": "^0.16.22",
"markdown-it": "13.0.1", "markdown-it": "13.0.1",
@ -41,6 +42,7 @@
"remark-breaks": "^4.0.0", "remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"require-in-the-middle": "^7.5.2",
"uuid": "^11.1.0" "uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -97,198 +97,199 @@ const Footer = React.memo(
}, },
})} })}
> >
<Box {showBrand && (
pt={ <Box
customStyle?.footer_show_intro pt={
? 5 customStyle?.footer_show_intro
: (footerSetting?.brand_groups?.length || 0) > 0
? 5 ? 5
: 0 : (footerSetting?.brand_groups?.length || 0) > 0
} ? 5
> : 0
{customStyle?.footer_show_intro !== false && ( }
<Box sx={{ mb: 3 }}> >
<Stack direction={'row'} alignItems={'center'} gap={1}> {customStyle?.footer_show_intro !== false && (
{footerSetting?.brand_logo && ( <Box sx={{ mb: 3 }}>
<img <Stack direction={'row'} alignItems={'center'} gap={1}>
src={footerSetting.brand_logo} {footerSetting?.brand_logo && (
alt='PandaWiki' <img
height={24} src={footerSetting.brand_logo}
/> alt='PandaWiki'
height={24}
/>
)}
<Box
sx={{
fontWeight: 'bold',
lineHeight: '32px',
fontSize: 24,
color: 'white',
}}
>
{footerSetting?.brand_name}
</Box>
</Stack>
{footerSetting?.brand_desc && (
<Box
sx={{
fontSize: 12,
lineHeight: '26px',
mt: 2,
color: 'rgba(255, 255, 255, 0.70)',
}}
>
{footerSetting.brand_desc}
</Box>
)} )}
<Box <Stack direction={'column'} gap={2.5} mt={2}>
sx={{ {customStyle?.social_media_accounts?.map(
fontWeight: 'bold', (account, index) => {
lineHeight: '32px', return (
fontSize: 24, <Stack
color: 'white', direction={'row'}
}} key={index}
> sx={theme => ({
{footerSetting?.brand_name} position: 'relative',
</Box> color: alpha(theme.palette.text.primary, 0.7),
</Stack> })}
{footerSetting?.brand_desc && ( gap={1}
<Box onClick={() => {
sx={{ setCurOverlayType(account.channel || '');
fontSize: 12, if (account.channel === 'phone') {
lineHeight: '26px', setPhoneData({
mt: 2, phone: account.phone || '',
color: 'rgba(255, 255, 255, 0.70)', text: account.text || '',
}} });
> setOpen(true);
{footerSetting.brand_desc} }
</Box> if (account.channel === 'wechat_oa') {
)} setWechatData({
<Stack direction={'column'} gap={2.5} mt={2}> src: account.icon || '',
{customStyle?.social_media_accounts?.map( text: account.text || '',
(account, index) => { });
return ( setOpen(true);
<Stack }
direction={'row'}
key={index}
sx={theme => ({
position: 'relative',
color: alpha(theme.palette.text.primary, 0.7),
})}
gap={1}
onClick={() => {
setCurOverlayType(account.channel || '');
if (account.channel === 'phone') {
setPhoneData({
phone: account.phone || '',
text: account.text || '',
});
setOpen(true);
}
if (account.channel === 'wechat_oa') {
setWechatData({
src: account.icon || '',
text: account.text || '',
});
setOpen(true);
}
}}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '20px', color: 'inherit' }}
/>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '20px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '14px',
color: 'inherit',
}} }}
> >
{account.text} {account.channel === 'wechat_oa' && (
</Box> <IconWeixingongzhonghao
{account.channel === 'wechat_oa' && sx={{ fontSize: '20px', color: 'inherit' }}
(account?.text || account?.icon) && ( />
<Stack
direction={'column'}
alignItems={'center'}
bgcolor={'#fff'}
p={1.5}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={83}
height={83}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: '#21222D',
maxWidth: '83px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)} )}
</Stack> {account.channel === 'phone' && (
); <IconDianhua
}, sx={{ fontSize: '20px', color: 'inherit' }}
)} ></IconDianhua>
</Stack> )}
</Box> <Box
)} sx={{
lineHeight: '24px',
fontSize: '14px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
direction={'column'}
alignItems={'center'}
bgcolor={'#fff'}
p={1.5}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={83}
height={83}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: '#21222D',
maxWidth: '83px',
<Stack direction={'row'} flexWrap={'wrap'} gap={2}> textAlign: 'center',
{footerSetting?.brand_groups?.map((group, idx) => ( }}
<Stack >
gap={1} {account.text}
key={group.name} </Box>
sx={{ )}
fontSize: 14, </Stack>
lineHeight: '22px', )}
width: 'calc(50% - 8px)', </Stack>
...(idx > 1 && { );
mt: 1, },
}), )}
'& a:hover': { </Stack>
color: 'primary.main', </Box>
}, )}
}}
> <Stack direction={'row'} flexWrap={'wrap'} gap={2}>
<Box {footerSetting?.brand_groups?.map((group, idx) => (
<Stack
gap={1}
key={group.name}
sx={{ sx={{
fontSize: 16, fontSize: 14,
lineHeight: '24px', lineHeight: '22px',
mb: 1, width: 'calc(50% - 8px)',
color: '#ffffff', ...(idx > 1 && {
mt: 1,
}),
'& a:hover': {
color: 'primary.main',
},
}} }}
> >
{group.name}
</Box>
{group.links?.map(link => (
<Box <Box
sx={theme => ({ sx={{
color: alpha(theme.palette.text.primary, 0.5), fontSize: 16,
})} lineHeight: '24px',
key={link.name} mb: 1,
color: '#ffffff',
}}
> >
<Link {group.name}
href={link?.url || ''} </Box>
target='_blank' {group.links?.map(link => (
<Box
sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5),
})}
key={link.name} key={link.name}
> >
{link.name} <Link
</Link> href={link?.url || ''}
</Box> target='_blank'
))} key={link.name}
</Stack> >
))} {link.name}
</Stack> </Link>
</Box> </Box>
))}
</Stack>
))}
</Stack>
</Box>
)}
{!( {!(
customStyle?.footer_show_intro === false && customStyle?.footer_show_intro === false &&
footerSetting?.brand_groups?.length === 0 footerSetting?.brand_groups?.length === 0
@ -477,239 +478,241 @@ const Footer = React.memo(
// }), // }),
}} }}
> >
<Box {showBrand && (
py={ <Box
customStyle?.footer_show_intro py={
? 6 customStyle?.footer_show_intro
: (footerSetting?.brand_groups?.length || 0) > 0
? 6 ? 6
: 0 : (footerSetting?.brand_groups?.length || 0) > 0
} ? 6
> : 0
<Stack
direction={'row'}
gap={10}
justifyContent={
customStyle?.footer_show_intro === false
? 'center'
: 'flex-start'
} }
> >
{customStyle?.footer_show_intro !== false && (
<Stack
direction={'column'}
sx={{ width: '30%', minWidth: 200 }}
gap={3}
>
<Stack direction={'row'} alignItems={'center'} gap={1}>
{footerSetting?.brand_logo && (
<img
src={footerSetting.brand_logo}
alt='PandaWiki'
height={36}
/>
)}
<Box
sx={{
fontWeight: 'bold',
lineHeight: '32px',
fontSize: 24,
color: 'text.primary',
}}
>
{footerSetting?.brand_name}
</Box>
</Stack>
{footerSetting?.brand_desc && (
<Box
sx={theme => ({
fontSize: 14,
lineHeight: '26px',
color: alpha(theme.palette.text.primary, 0.7),
})}
>
{footerSetting.brand_desc}
</Box>
)}
<Stack direction={'column'} gap={'26px'}>
{customStyle?.social_media_accounts?.map(
(account, index) => {
return (
<Stack
direction={'row'}
key={index}
sx={theme => ({
position: 'relative',
'&:hover': {
color: theme.palette.primary.main,
},
'&:hover .popup': {
display: 'flex !important',
},
color: alpha(theme.palette.text.primary, 0.7),
cursor: 'default',
})}
gap={1}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '20px', color: 'inherit' }}
></IconWeixingongzhonghao>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '20px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '14px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
className={'popup'}
direction={'column'}
alignItems={'center'}
bgcolor={'#fff'}
p={1.5}
sx={theme => ({
position: 'absolute',
top: '40px',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={120}
height={120}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
maxWidth: '120px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)}
{account.channel === 'phone' && account?.phone && (
<Stack
className={'popup'}
bgcolor={'#fff'}
px={1.5}
py={1}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
'0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
display={'none'}
zIndex={999}
>
{account.phone && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
textAlign: 'center',
}}
>
{account.phone}
</Box>
)}
</Stack>
)}
</Stack>
);
},
)}
</Stack>
</Stack>
)}
<Stack <Stack
direction={'row'} direction={'row'}
width={'100%'} gap={10}
justifyContent={'flex-start'} justifyContent={
flexWrap='wrap' customStyle?.footer_show_intro === false
? 'center'
: 'flex-start'
}
> >
{footerSetting?.brand_groups?.map(group => ( {customStyle?.footer_show_intro !== false && (
<Stack <Stack
gap={1.5} direction={'column'}
key={group.name} sx={{ width: '30%', minWidth: 200 }}
sx={{ gap={3}
flex: '0 0 33.33%',
fontSize: 14,
lineHeight: '22px',
minWidth: '100px',
'& a:hover': {
color: 'primary.main',
},
}}
> >
<Box <Stack direction={'row'} alignItems={'center'} gap={1}>
sx={{ {footerSetting?.brand_logo && (
fontSize: 16, <img
lineHeight: '24px', src={footerSetting.brand_logo}
mb: 1, alt='PandaWiki'
color: 'text.primary', height={36}
}} />
> )}
{group.name} <Box
</Box> sx={{
{group.links?.map(link => ( fontWeight: 'bold',
lineHeight: '32px',
fontSize: 24,
color: 'text.primary',
}}
>
{footerSetting?.brand_name}
</Box>
</Stack>
{footerSetting?.brand_desc && (
<Box <Box
sx={theme => ({ sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5), fontSize: 14,
lineHeight: '26px',
color: alpha(theme.palette.text.primary, 0.7),
})} })}
key={link.name}
> >
<Link {footerSetting.brand_desc}
href={link?.url || ''} </Box>
target='_blank' )}
<Stack direction={'column'} gap={'26px'}>
{customStyle?.social_media_accounts?.map(
(account, index) => {
return (
<Stack
direction={'row'}
key={index}
sx={theme => ({
position: 'relative',
'&:hover': {
color: theme.palette.primary.main,
},
'&:hover .popup': {
display: 'flex !important',
},
color: alpha(theme.palette.text.primary, 0.7),
cursor: 'default',
})}
gap={1}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '20px', color: 'inherit' }}
></IconWeixingongzhonghao>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '20px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '14px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
className={'popup'}
direction={'column'}
alignItems={'center'}
bgcolor={'#fff'}
p={1.5}
sx={theme => ({
position: 'absolute',
top: '40px',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={120}
height={120}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
maxWidth: '120px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)}
{account.channel === 'phone' &&
account?.phone && (
<Stack
className={'popup'}
bgcolor={'#fff'}
px={1.5}
py={1}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
'0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
display={'none'}
zIndex={999}
>
{account.phone && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
textAlign: 'center',
}}
>
{account.phone}
</Box>
)}
</Stack>
)}
</Stack>
);
},
)}
</Stack>
</Stack>
)}
<Stack
direction={'row'}
width={'100%'}
justifyContent={'flex-start'}
flexWrap='wrap'
>
{footerSetting?.brand_groups?.map(group => (
<Stack
gap={1.5}
key={group.name}
sx={{
flex: '0 0 33.33%',
fontSize: 14,
lineHeight: '22px',
minWidth: '100px',
'& a:hover': {
color: 'primary.main',
},
}}
>
<Box
sx={{
fontSize: 16,
lineHeight: '24px',
mb: 1,
color: 'text.primary',
}}
>
{group.name}
</Box>
{group.links?.map(link => (
<Box
sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5),
})}
key={link.name} key={link.name}
> >
{link.name} <Link
</Link> href={link?.url || ''}
</Box> target='_blank'
))} key={link.name}
</Stack> >
))} {link.name}
</Link>
</Box>
))}
</Stack>
))}
</Stack>
</Stack> </Stack>
</Stack> </Box>
</Box> )}
{!( {!(
customStyle?.footer_show_intro === false && customStyle?.footer_show_intro === false &&
footerSetting?.brand_groups?.length === 0 footerSetting?.brand_groups?.length === 0

View File

@ -94,196 +94,197 @@ const Footer = React.memo(
bgcolor: alpha(theme.palette.text.primary, 0.05), bgcolor: alpha(theme.palette.text.primary, 0.05),
})} })}
> >
<Box {showBrand && (
pt={ <Box
customStyle?.footer_show_intro pt={
? 5 customStyle?.footer_show_intro
: (footerSetting?.brand_groups?.length || 0) > 0
? 5 ? 5
: 0 : (footerSetting?.brand_groups?.length || 0) > 0
} ? 5
> : 0
{customStyle?.footer_show_intro !== false && ( }
<Box sx={{ mb: 3 }}> >
<Stack direction={'row'} alignItems={'center'} gap={1}> {customStyle?.footer_show_intro !== false && (
{footerSetting?.brand_logo && ( <Box sx={{ mb: 3 }}>
<img <Stack direction={'row'} alignItems={'center'} gap={1}>
src={footerSetting.brand_logo} {footerSetting?.brand_logo && (
alt='PandaWiki' <img
height={24} src={footerSetting.brand_logo}
/> alt='PandaWiki'
)} height={24}
<Box />
sx={{ )}
fontWeight: 'bold', <Box
lineHeight: '32px', sx={{
fontSize: 24, fontWeight: 'bold',
color: 'text.primary', lineHeight: '32px',
}} fontSize: 24,
> color: 'text.primary',
{footerSetting?.brand_name} }}
</Box> >
</Stack> {footerSetting?.brand_name}
{footerSetting?.brand_desc && ( </Box>
<Box </Stack>
sx={theme => ({ {footerSetting?.brand_desc && (
fontSize: 12,
lineHeight: '26px',
mt: 2,
color: alpha(theme.palette.text.primary, 0.7),
})}
>
{footerSetting.brand_desc}
</Box>
)}
<Stack direction={'column'} gap={2.5} mt={2}>
{customStyle?.social_media_accounts?.map(
(account, index) => {
return (
<Stack
direction={'row'}
key={index}
sx={theme => ({
position: 'relative',
color: alpha(theme.palette.text.primary, 0.7),
})}
gap={1}
onClick={() => {
setCurOverlayType(account.channel || '');
if (account.channel === 'phone') {
setPhoneData({
phone: account.phone || '',
text: account.text || '',
});
setOpen(true);
}
if (account.channel === 'wechat_oa') {
setWechatData({
src: account.icon || '',
text: account.text || '',
});
setOpen(true);
}
}}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '20px', color: 'inherit' }}
/>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '20px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '12px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
direction={'column'}
alignItems={'center'}
p={1.5}
sx={theme => ({
position: 'absolute',
top: '40px',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={83}
height={83}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
maxWidth: '83px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)}
</Stack>
);
},
)}
</Stack>
</Box>
)}
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{footerSetting?.brand_groups?.map((group, idx) => (
<Stack
gap={1}
key={group.name}
sx={{
fontSize: 14,
lineHeight: '22px',
width: 'calc(50% - 8px)',
...(idx > 1 && {
mt: 1,
}),
'& a:hover': {
color: 'primary.main',
},
}}
>
<Box
sx={{
fontSize: 14,
lineHeight: '24px',
mb: 1,
color: 'text.primary',
}}
>
{group.name}
</Box>
{group.links?.map(link => (
<Box <Box
sx={theme => ({ sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5), fontSize: 12,
lineHeight: '26px',
mt: 2,
color: alpha(theme.palette.text.primary, 0.7),
})} })}
key={link.name}
> >
<Link {footerSetting.brand_desc}
href={link?.url || ''} </Box>
target='_blank' )}
<Stack direction={'column'} gap={2.5} mt={2}>
{customStyle?.social_media_accounts?.map(
(account, index) => {
return (
<Stack
direction={'row'}
key={index}
sx={theme => ({
position: 'relative',
color: alpha(theme.palette.text.primary, 0.7),
})}
gap={1}
onClick={() => {
setCurOverlayType(account.channel || '');
if (account.channel === 'phone') {
setPhoneData({
phone: account.phone || '',
text: account.text || '',
});
setOpen(true);
}
if (account.channel === 'wechat_oa') {
setWechatData({
src: account.icon || '',
text: account.text || '',
});
setOpen(true);
}
}}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '20px', color: 'inherit' }}
/>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '20px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '12px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
direction={'column'}
alignItems={'center'}
p={1.5}
sx={theme => ({
position: 'absolute',
top: '40px',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={83}
height={83}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
maxWidth: '83px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)}
</Stack>
);
},
)}
</Stack>
</Box>
)}
<Stack direction={'row'} flexWrap={'wrap'} gap={2}>
{footerSetting?.brand_groups?.map((group, idx) => (
<Stack
gap={1}
key={group.name}
sx={{
fontSize: 14,
lineHeight: '22px',
width: 'calc(50% - 8px)',
...(idx > 1 && {
mt: 1,
}),
'& a:hover': {
color: 'primary.main',
},
}}
>
<Box
sx={{
fontSize: 14,
lineHeight: '24px',
mb: 1,
color: 'text.primary',
}}
>
{group.name}
</Box>
{group.links?.map(link => (
<Box
sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5),
})}
key={link.name} key={link.name}
> >
{link.name} <Link
</Link> href={link?.url || ''}
</Box> target='_blank'
))} key={link.name}
</Stack> >
))} {link.name}
</Stack> </Link>
</Box> </Box>
))}
</Stack>
))}
</Stack>
</Box>
)}
{!( {!(
customStyle?.footer_show_intro === false && customStyle?.footer_show_intro === false &&
footerSetting?.brand_groups?.length === 0 footerSetting?.brand_groups?.length === 0
@ -455,237 +456,239 @@ const Footer = React.memo(
width: '100%', width: '100%',
}} }}
> >
<Box {showBrand && (
py={ <Box
customStyle?.footer_show_intro py={
? 6 customStyle?.footer_show_intro
: (footerSetting?.brand_groups?.length || 0) > 0
? 6 ? 6
: 0 : (footerSetting?.brand_groups?.length || 0) > 0
} ? 6
> : 0
<Stack
direction={'row'}
gap={10}
justifyContent={
customStyle?.footer_show_intro === false
? 'center'
: 'flex-start'
} }
> >
{customStyle?.footer_show_intro !== false && (
<Stack
direction={'column'}
sx={{ width: '30%', minWidth: 200 }}
gap={3}
>
<Stack direction={'row'} alignItems={'center'} gap={1}>
{footerSetting?.brand_logo && (
<img
src={footerSetting.brand_logo}
alt='PandaWiki'
height={36}
/>
)}
<Box
sx={{
fontWeight: 'bold',
lineHeight: '32px',
fontSize: 24,
}}
>
{footerSetting?.brand_name}
</Box>
</Stack>
{footerSetting?.brand_desc && (
<Box
sx={theme => ({
fontSize: 14,
lineHeight: '26px',
color: alpha(theme.palette.text.primary, 0.7),
})}
>
{footerSetting.brand_desc}
</Box>
)}
<Stack direction={'column'} gap={'26px'}>
{customStyle?.social_media_accounts?.map(
(account, index) => {
return (
<Stack
direction={'row'}
key={index}
alignItems='center'
sx={theme => ({
position: 'relative',
'&:hover': {
color: theme.palette.primary.main,
},
'&:hover .popup': {
display: 'flex !important',
},
color: alpha(theme.palette.text.primary, 0.7),
cursor: 'default',
})}
gap={1}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '18px', color: 'inherit' }}
></IconWeixingongzhonghao>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '16px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '14px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
className={'popup'}
direction={'column'}
alignItems={'center'}
p={1.5}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={120}
height={120}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
maxWidth: '120px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)}
{account.channel === 'phone' && account?.phone && (
<Stack
className={'popup'}
px={1.5}
py={1}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
'0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
display={'none'}
zIndex={999}
>
{account.phone && (
<Box
sx={{
fontSize: '14px',
lineHeight: '16px',
color: 'text.primary',
textAlign: 'center',
}}
>
{account.phone}
</Box>
)}
</Stack>
)}
</Stack>
);
},
)}
</Stack>
</Stack>
)}
<Stack <Stack
direction={'row'} direction={'row'}
width={'100%'} gap={10}
justifyContent={'flex-start'} justifyContent={
flexWrap='wrap' customStyle?.footer_show_intro === false
? 'center'
: 'flex-start'
}
> >
{footerSetting?.brand_groups?.map(group => ( {customStyle?.footer_show_intro !== false && (
<Stack <Stack
gap={1.5} direction={'column'}
key={group.name} sx={{ width: '30%', minWidth: 200 }}
sx={{ gap={3}
flex: '0 0 33.33%',
fontSize: 14,
lineHeight: '22px',
minWidth: '100px',
'& a:hover': {
color: 'primary.main',
},
}}
> >
<Box <Stack direction={'row'} alignItems={'center'} gap={1}>
sx={{ {footerSetting?.brand_logo && (
fontSize: 16, <img
lineHeight: '24px', src={footerSetting.brand_logo}
mb: 1, alt='PandaWiki'
}} height={36}
> />
{group.name} )}
</Box> <Box
{group.links?.map(link => ( sx={{
fontWeight: 'bold',
lineHeight: '32px',
fontSize: 24,
}}
>
{footerSetting?.brand_name}
</Box>
</Stack>
{footerSetting?.brand_desc && (
<Box <Box
sx={theme => ({ sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5), fontSize: 14,
lineHeight: '26px',
color: alpha(theme.palette.text.primary, 0.7),
})} })}
key={link.name}
> >
<Link {footerSetting.brand_desc}
href={link?.url || ''} </Box>
target='_blank' )}
<Stack direction={'column'} gap={'26px'}>
{customStyle?.social_media_accounts?.map(
(account, index) => {
return (
<Stack
direction={'row'}
key={index}
alignItems='center'
sx={theme => ({
position: 'relative',
'&:hover': {
color: theme.palette.primary.main,
},
'&:hover .popup': {
display: 'flex !important',
},
color: alpha(theme.palette.text.primary, 0.7),
cursor: 'default',
})}
gap={1}
>
{account.channel === 'wechat_oa' && (
<IconWeixingongzhonghao
sx={{ fontSize: '18px', color: 'inherit' }}
></IconWeixingongzhonghao>
)}
{account.channel === 'phone' && (
<IconDianhua
sx={{ fontSize: '16px', color: 'inherit' }}
></IconDianhua>
)}
<Box
sx={{
lineHeight: '24px',
fontSize: '14px',
color: 'inherit',
}}
>
{account.text}
</Box>
{account.channel === 'wechat_oa' &&
(account?.text || account?.icon) && (
<Stack
className={'popup'}
direction={'column'}
alignItems={'center'}
p={1.5}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
' 0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
gap={1}
display={'none'}
zIndex={999}
>
{account.icon && (
<img
src={account.icon}
width={120}
height={120}
></img>
)}
{account.text && (
<Box
sx={{
fontSize: '12px',
lineHeight: '16px',
color: 'text.primary',
maxWidth: '120px',
textAlign: 'center',
}}
>
{account.text}
</Box>
)}
</Stack>
)}
{account.channel === 'phone' &&
account?.phone && (
<Stack
className={'popup'}
px={1.5}
py={1}
sx={theme => ({
position: 'absolute',
bottom: '100%',
transform: 'translateY(-10px)',
left: 0,
boxShadow:
'0px 4px 8px 0px ' +
alpha(theme.palette.text.primary, 0.25),
borderRadius: '4px',
bgcolor: theme.palette.background.default,
})}
display={'none'}
zIndex={999}
>
{account.phone && (
<Box
sx={{
fontSize: '14px',
lineHeight: '16px',
color: 'text.primary',
textAlign: 'center',
}}
>
{account.phone}
</Box>
)}
</Stack>
)}
</Stack>
);
},
)}
</Stack>
</Stack>
)}
<Stack
direction={'row'}
width={'100%'}
justifyContent={'flex-start'}
flexWrap='wrap'
>
{footerSetting?.brand_groups?.map(group => (
<Stack
gap={1.5}
key={group.name}
sx={{
flex: '0 0 33.33%',
fontSize: 14,
lineHeight: '22px',
minWidth: '100px',
'& a:hover': {
color: 'primary.main',
},
}}
>
<Box
sx={{
fontSize: 16,
lineHeight: '24px',
mb: 1,
}}
>
{group.name}
</Box>
{group.links?.map(link => (
<Box
sx={theme => ({
color: alpha(theme.palette.text.primary, 0.5),
})}
key={link.name} key={link.name}
> >
{link.name} <Link
</Link> href={link?.url || ''}
</Box> target='_blank'
))} key={link.name}
</Stack> >
))} {link.name}
</Link>
</Box>
))}
</Stack>
))}
</Stack>
</Stack> </Stack>
</Stack> </Box>
</Box> )}
{!( {!(
customStyle?.footer_show_intro === false && customStyle?.footer_show_intro === false &&
footerSetting?.brand_groups?.length === 0 footerSetting?.brand_groups?.length === 0

View File

@ -262,6 +262,9 @@ importers:
html-to-image: html-to-image:
specifier: ^1.11.13 specifier: ^1.11.13
version: 1.11.13 version: 1.11.13
import-in-the-middle:
specifier: ^1.14.2
version: 1.15.0
js-cookie: js-cookie:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.0.5 version: 3.0.5
@ -310,6 +313,9 @@ importers:
remark-math: remark-math:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
require-in-the-middle:
specifier: ^7.5.2
version: 7.5.2
uuid: uuid:
specifier: ^11.1.0 specifier: ^11.1.0
version: 11.1.0 version: 11.1.0