Skip to content

Commit 1de7ac3

Browse files
authored
refactor(token): 优化ModelLimitSelector组件性能与结构 (#859)
- 使用useCallback和useMemo优化渲染性能,减少不必要的重渲染 - 提取公共函数和常量到组件外部,提升代码复用性 - 将onChange、renderOption、renderTags逻辑抽离为独立函数,增强可维护性 - 统一导入格式,优化代码风格
1 parent 8f3fa80 commit 1de7ac3

File tree

1 file changed

+100
-78
lines changed

1 file changed

+100
-78
lines changed

web/src/views/Token/component/ModelLimitSelector.jsx

Lines changed: 100 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
1-
import { useTranslation } from 'react-i18next';
2-
import { useTheme } from '@mui/material/styles';
3-
import { useFormikContext } from 'formik';
4-
import { Autocomplete, Box, Chip, FormControl, FormHelperText, Grid, Paper, TextField, Typography } from '@mui/material';
1+
import {useTranslation} from 'react-i18next';
2+
import {useTheme} from '@mui/material/styles';
3+
import {useFormikContext} from 'formik';
4+
import {Autocomplete, Box, Chip, FormControl, FormHelperText, Grid, Paper, TextField, Typography} from '@mui/material';
55
import PropTypes from 'prop-types';
6-
import { useMemo } from 'react';
6+
import {useCallback, useMemo} from 'react';
7+
8+
const groupBy = (option) => option.owned_by;
9+
const getOptionLabel = (option) => option.name || '';
10+
const isOptionEqualToValue = (option, value) => option.id === value.id;
11+
const listboxProps = {
12+
style: {
13+
maxHeight: '400px'
14+
}
15+
};
16+
17+
const CustomPaper = (props) => (
18+
<Paper
19+
{...props}
20+
style={{
21+
boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.15)',
22+
borderRadius: '8px'
23+
}}
24+
/>
25+
);
726

827
const ModelLimitSelector = ({ modelOptions, getModelIcon }) => {
928
const { t } = useTranslation();
1029
const theme = useTheme();
1130
const { values, setFieldValue } = useFormikContext();
31+
1232
const sortedModelOptions = useMemo(() => {
1333
return [...modelOptions].sort((a, b) => a.owned_by.localeCompare(b.owned_by));
1434
}, [modelOptions]);
@@ -18,93 +38,95 @@ const ModelLimitSelector = ({ modelOptions, getModelIcon }) => {
1838
return modelOptions.filter((option) => selectedIds.has(option.id));
1939
}, [values.setting?.limits?.limit_model_setting?.models, modelOptions]);
2040

41+
const handleOnChange = useCallback(
42+
(event, newValue) => {
43+
const modelIds = newValue.map((option) => option.id);
44+
setFieldValue('setting.limits.limit_model_setting.models', modelIds);
45+
},
46+
[setFieldValue]
47+
);
48+
49+
const renderOption = useCallback(
50+
(props, option) => (
51+
<Box component="li" {...props} key={option.id} sx={{ alignItems: 'flex-start' }}>
52+
<Grid container spacing={1} sx={{ alignItems: 'center' }}>
53+
<Grid item xs={'auto'}>
54+
<img
55+
src={getModelIcon(option.owned_by)}
56+
alt={option.owned_by}
57+
style={{ width: 24, height: 24, borderRadius: '4px' }}
58+
onError={(e) => {
59+
e.target.src = '/src/assets/images/icons/unknown_type.svg';
60+
}}
61+
/>
62+
</Grid>
63+
<Grid item xs>
64+
<Typography variant="body1" sx={{ fontWeight: 500 }}>
65+
{option.name}
66+
</Typography>
67+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 0.5 }}>
68+
{option.groups.map((group) => (
69+
<Chip key={group} label={group} size="small" variant="outlined" />
70+
))}
71+
</Box>
72+
</Grid>
73+
</Grid>
74+
</Box>
75+
),
76+
[getModelIcon]
77+
);
78+
79+
const renderTags = useCallback(
80+
(value, getTagProps) =>
81+
value.map((option, index) => {
82+
const { key, ...otherTagProps } = getTagProps({ index });
83+
return (
84+
<Chip
85+
key={key}
86+
label={option.name}
87+
{...otherTagProps}
88+
size="small"
89+
variant="outlined"
90+
color="primary"
91+
sx={{
92+
margin: 1,
93+
color: theme.palette.primary.main,
94+
'& .MuiChip-deleteIcon': {
95+
color: theme.palette.primary.main,
96+
transition: 'color 0.2s ease-in-out'
97+
},
98+
'& .MuiChip-deleteIcon:hover': {
99+
color: theme.palette.primary.main
100+
}
101+
}}
102+
/>
103+
);
104+
}),
105+
[theme]
106+
);
107+
21108
return (
22109
<FormControl fullWidth sx={{ ...theme.typography.otherInput }}>
23110
<Autocomplete
24111
multiple
25112
disableListWrap
26113
options={sortedModelOptions}
27-
groupBy={(option) => option.owned_by}
114+
groupBy={groupBy}
28115
value={selectedValue}
29-
onChange={(event, newValue) => {
30-
const modelIds = newValue.map((option) => option.id);
31-
setFieldValue('setting.limits.limit_model_setting.models', modelIds);
32-
}}
33-
isOptionEqualToValue={(option, value) => option.id === value.id}
34-
getOptionLabel={(option) => option.name || ''}
35-
ListboxProps={{
36-
style: {
37-
maxHeight: '400px'
38-
}
39-
}}
116+
onChange={handleOnChange}
117+
isOptionEqualToValue={isOptionEqualToValue}
118+
getOptionLabel={getOptionLabel}
119+
ListboxProps={listboxProps}
40120
renderInput={(params) => (
41121
<TextField
42122
{...params}
43123
label={t('token_index.limit_models')}
44124
placeholder={values.setting?.limits?.limit_model_setting?.models?.length > 0 ? '' : t('token_index.limit_models_info')}
45125
/>
46126
)}
47-
renderOption={(props, option) => (
48-
<Box component="li" {...props} key={option.id} sx={{ alignItems: 'flex-start' }}>
49-
<Grid container spacing={1} sx={{ alignItems: 'center' }}>
50-
<Grid item xs={'auto'}>
51-
<img
52-
src={getModelIcon(option.owned_by)}
53-
alt={option.owned_by}
54-
style={{ width: 24, height: 24, borderRadius: '4px' }}
55-
onError={(e) => {
56-
e.target.src = '/src/assets/images/icons/unknown_type.svg';
57-
}}
58-
/>
59-
</Grid>
60-
<Grid item xs>
61-
<Typography variant="body1" sx={{ fontWeight: 500 }}>
62-
{option.name}
63-
</Typography>
64-
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 0.5 }}>
65-
{option.groups.map((group) => (
66-
<Chip key={group} label={group} size="small" variant="outlined" />
67-
))}
68-
</Box>
69-
</Grid>
70-
</Grid>
71-
</Box>
72-
)}
73-
renderTags={(value, getTagProps) =>
74-
value.map((option, index) => {
75-
const { key, ...otherTagProps } = getTagProps({ index });
76-
return (
77-
<Chip
78-
key={key}
79-
label={option.name}
80-
{...otherTagProps}
81-
size="small"
82-
variant="outlined"
83-
color="primary"
84-
sx={{
85-
margin: 1,
86-
color: theme.palette.primary.main,
87-
'& .MuiChip-deleteIcon': {
88-
color: theme.palette.primary.main,
89-
transition: 'color 0.2s ease-in-out'
90-
},
91-
'& .MuiChip-deleteIcon:hover': {
92-
color: theme.palette.primary.main
93-
}
94-
}}
95-
/>
96-
);
97-
})
98-
}
99-
PaperComponent={(props) => (
100-
<Paper
101-
{...props}
102-
style={{
103-
boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.15)',
104-
borderRadius: '8px'
105-
}}
106-
/>
107-
)}
127+
renderOption={renderOption}
128+
renderTags={renderTags}
129+
PaperComponent={CustomPaper}
108130
disableCloseOnSelect
109131
/>
110132
<FormHelperText>{t('token_index.limit_models_info')}</FormHelperText>

0 commit comments

Comments
 (0)