🎨 refactor(EditTagModal): tidy imports & enhance state-sync on open
Motivation
• Remove unused UI components to keep the bundle lean and silence linter warnings.
• Ensure every time the side-sheet opens it reflects the latest tag data, avoiding stale form values (e.g., model / group mismatches).
Key Changes
1. UI Imports
– Dropped `Input`, `Select`, `TextArea` from `@douyinfe/semi-ui` (unused in Form-based version).
2. State Reset & Form Sync
– On `visible` or `tag` change:
• Refresh model & group options.
• Reset `inputs` to clean defaults (`originInputs`) carrying the current `tag`.
• Pre-fill Form through `formApiRef` to keep controlled fields aligned.
3. Minor Cleanup
– Added inline comment clarifying local state reset purpose.
Result
Opening the “Edit Tag” side-sheet now always displays accurate data without residual selections, and build output is cleaner due to removed dead imports.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
API,
|
API,
|
||||||
showError,
|
showError,
|
||||||
@@ -11,15 +11,13 @@ import {
|
|||||||
SideSheet,
|
SideSheet,
|
||||||
Space,
|
Space,
|
||||||
Button,
|
Button,
|
||||||
Input,
|
|
||||||
Typography,
|
Typography,
|
||||||
Spin,
|
Spin,
|
||||||
Select,
|
|
||||||
Banner,
|
Banner,
|
||||||
TextArea,
|
|
||||||
Card,
|
Card,
|
||||||
Tag,
|
Tag,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Form,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
IconSave,
|
IconSave,
|
||||||
@@ -53,9 +51,14 @@ const EditTagModal = (props) => {
|
|||||||
models: [],
|
models: [],
|
||||||
};
|
};
|
||||||
const [inputs, setInputs] = useState(originInputs);
|
const [inputs, setInputs] = useState(originInputs);
|
||||||
|
const formApiRef = useRef(null);
|
||||||
|
const getInitValues = () => ({ ...originInputs });
|
||||||
|
|
||||||
const handleInputChange = (name, value) => {
|
const handleInputChange = (name, value) => {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValue(name, value);
|
||||||
|
}
|
||||||
if (name === 'type') {
|
if (name === 'type') {
|
||||||
let localModels = [];
|
let localModels = [];
|
||||||
switch (value) {
|
switch (value) {
|
||||||
@@ -133,27 +136,25 @@ const EditTagModal = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let data = {
|
const formVals = values || formApiRef.current?.getValues() || {};
|
||||||
tag: tag,
|
let data = { tag };
|
||||||
};
|
if (formVals.model_mapping) {
|
||||||
if (inputs.model_mapping !== null && inputs.model_mapping !== '') {
|
if (!verifyJSON(formVals.model_mapping)) {
|
||||||
if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
|
|
||||||
showInfo('模型映射必须是合法的 JSON 格式!');
|
showInfo('模型映射必须是合法的 JSON 格式!');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data.model_mapping = inputs.model_mapping;
|
data.model_mapping = formVals.model_mapping;
|
||||||
}
|
}
|
||||||
if (inputs.groups.length > 0) {
|
if (formVals.groups && formVals.groups.length > 0) {
|
||||||
data.groups = inputs.groups.join(',');
|
data.groups = formVals.groups.join(',');
|
||||||
}
|
}
|
||||||
if (inputs.models.length > 0) {
|
if (formVals.models && formVals.models.length > 0) {
|
||||||
data.models = inputs.models.join(',');
|
data.models = formVals.models.join(',');
|
||||||
}
|
}
|
||||||
data.new_tag = inputs.new_tag;
|
data.new_tag = formVals.new_tag;
|
||||||
// check have any change
|
|
||||||
if (
|
if (
|
||||||
data.model_mapping === undefined &&
|
data.model_mapping === undefined &&
|
||||||
data.groups === undefined &&
|
data.groups === undefined &&
|
||||||
@@ -202,7 +203,7 @@ const EditTagModal = (props) => {
|
|||||||
const res = await API.get(`/api/channel/tag/models?tag=${tag}`);
|
const res = await API.get(`/api/channel/tag/models?tag=${tag}`);
|
||||||
if (res?.data?.success) {
|
if (res?.data?.success) {
|
||||||
const models = res.data.data ? res.data.data.split(',') : [];
|
const models = res.data.data ? res.data.data.split(',') : [];
|
||||||
setInputs((inputs) => ({ ...inputs, models: models }));
|
handleInputChange('models', models);
|
||||||
} else {
|
} else {
|
||||||
showError(res.data.message);
|
showError(res.data.message);
|
||||||
}
|
}
|
||||||
@@ -213,19 +214,32 @@ const EditTagModal = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fetchModels().then();
|
||||||
|
fetchGroups().then();
|
||||||
|
fetchTagModels().then();
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValues({
|
||||||
|
...getInitValues(),
|
||||||
|
tag: tag,
|
||||||
|
new_tag: tag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setInputs({
|
setInputs({
|
||||||
...originInputs,
|
...originInputs,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
new_tag: tag,
|
new_tag: tag,
|
||||||
});
|
});
|
||||||
fetchModels().then();
|
}, [visible, tag]);
|
||||||
fetchGroups().then();
|
|
||||||
fetchTagModels().then(); // Call the new function
|
useEffect(() => {
|
||||||
}, [visible, tag]); // Add tag to dependency array
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValues(inputs);
|
||||||
|
}
|
||||||
|
}, [inputs]);
|
||||||
|
|
||||||
const addCustomModels = () => {
|
const addCustomModels = () => {
|
||||||
if (customModel.trim() === '') return;
|
if (customModel.trim() === '') return;
|
||||||
// 使用逗号分隔字符串,然后去除每个模型名称前后的空格
|
|
||||||
const modelArray = customModel.split(',').map((model) => model.trim());
|
const modelArray = customModel.split(',').map((model) => model.trim());
|
||||||
|
|
||||||
let localModels = [...inputs.models];
|
let localModels = [...inputs.models];
|
||||||
@@ -233,11 +247,9 @@ const EditTagModal = (props) => {
|
|||||||
const addedModels = [];
|
const addedModels = [];
|
||||||
|
|
||||||
modelArray.forEach((model) => {
|
modelArray.forEach((model) => {
|
||||||
// 检查模型是否已存在,且模型名称非空
|
|
||||||
if (model && !localModels.includes(model)) {
|
if (model && !localModels.includes(model)) {
|
||||||
localModels.push(model); // 添加到模型列表
|
localModels.push(model);
|
||||||
localModelOptions.push({
|
localModelOptions.push({
|
||||||
// 添加到下拉选项
|
|
||||||
key: model,
|
key: model,
|
||||||
text: model,
|
text: model,
|
||||||
value: model,
|
value: model,
|
||||||
@@ -246,7 +258,6 @@ const EditTagModal = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新状态值
|
|
||||||
setModelOptions(localModelOptions);
|
setModelOptions(localModelOptions);
|
||||||
setCustomModel('');
|
setCustomModel('');
|
||||||
handleInputChange('models', localModels);
|
handleInputChange('models', localModels);
|
||||||
@@ -283,7 +294,7 @@ const EditTagModal = (props) => {
|
|||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
theme="solid"
|
theme="solid"
|
||||||
onClick={handleSave}
|
onClick={() => formApiRef.current?.submitForm()}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
icon={<IconSave />}
|
icon={<IconSave />}
|
||||||
>
|
>
|
||||||
@@ -302,146 +313,128 @@ const EditTagModal = (props) => {
|
|||||||
}
|
}
|
||||||
closeIcon={null}
|
closeIcon={null}
|
||||||
>
|
>
|
||||||
<Spin spinning={loading}>
|
<Form
|
||||||
<div className="p-2">
|
key={tag || 'edit'}
|
||||||
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
initValues={getInitValues()}
|
||||||
{/* Header: Tag Info */}
|
getFormApi={(api) => (formApiRef.current = api)}
|
||||||
<div className="flex items-center mb-2">
|
onSubmit={handleSave}
|
||||||
<Avatar size="small" color="blue" className="mr-2 shadow-md">
|
>
|
||||||
<IconBookmark size={16} />
|
{() => (
|
||||||
</Avatar>
|
<Spin spinning={loading}>
|
||||||
<div>
|
<div className="p-2">
|
||||||
<Text className="text-lg font-medium">{t('标签信息')}</Text>
|
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
||||||
<div className="text-xs text-gray-600">{t('标签的基本配置')}</div>
|
{/* Header: Tag Info */}
|
||||||
</div>
|
<div className="flex items-center mb-2">
|
||||||
</div>
|
<Avatar size="small" color="blue" className="mr-2 shadow-md">
|
||||||
|
<IconBookmark size={16} />
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<Text className="text-lg font-medium">{t('标签信息')}</Text>
|
||||||
|
<div className="text-xs text-gray-600">{t('标签的基本配置')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
type="warning"
|
|
||||||
description={t('所有编辑均为覆盖操作,留空则不更改')}
|
|
||||||
className="!rounded-lg mb-4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">{t('标签名称')}</Text>
|
|
||||||
<Input
|
|
||||||
value={inputs.new_tag}
|
|
||||||
onChange={(value) => setInputs({ ...inputs, new_tag: value })}
|
|
||||||
placeholder={t('请输入新标签,留空则解散标签')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
|
||||||
{/* Header: Model Config */}
|
|
||||||
<div className="flex items-center mb-2">
|
|
||||||
<Avatar size="small" color="purple" className="mr-2 shadow-md">
|
|
||||||
<IconCode size={16} />
|
|
||||||
</Avatar>
|
|
||||||
<div>
|
|
||||||
<Text className="text-lg font-medium">{t('模型配置')}</Text>
|
|
||||||
<div className="text-xs text-gray-600">{t('模型选择和映射设置')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">{t('模型')}</Text>
|
|
||||||
<Banner
|
<Banner
|
||||||
type="info"
|
type="warning"
|
||||||
description={t('当前模型列表为该标签下所有渠道模型列表最长的一个,并非所有渠道的并集,请注意可能导致某些渠道模型丢失。')}
|
description={t('所有编辑均为覆盖操作,留空则不更改')}
|
||||||
className="!rounded-lg mb-4"
|
className="!rounded-lg mb-4"
|
||||||
/>
|
/>
|
||||||
<Select
|
|
||||||
placeholder={t('请选择该渠道所支持的模型,留空则不更改')}
|
|
||||||
name='models'
|
|
||||||
multiple
|
|
||||||
filter
|
|
||||||
searchPosition='dropdown'
|
|
||||||
onChange={(value) => handleInputChange('models', value)}
|
|
||||||
value={inputs.models}
|
|
||||||
optionList={modelOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<Input
|
<Form.Input
|
||||||
addonAfter={
|
field='new_tag'
|
||||||
<Button type='primary' onClick={addCustomModels} className="!rounded-r-lg">
|
label={t('标签名称')}
|
||||||
{t('填入')}
|
placeholder={t('请输入新标签,留空则解散标签')}
|
||||||
</Button>
|
onChange={(value) => handleInputChange('new_tag', value)}
|
||||||
}
|
/>
|
||||||
placeholder={t('输入自定义模型名称')}
|
</div>
|
||||||
value={customModel}
|
</Card>
|
||||||
onChange={(value) => setCustomModel(value.trim())}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
||||||
<Text strong className="block mb-2">{t('模型重定向')}</Text>
|
{/* Header: Model Config */}
|
||||||
<TextArea
|
<div className="flex items-center mb-2">
|
||||||
placeholder={t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,留空则不更改')}
|
<Avatar size="small" color="purple" className="mr-2 shadow-md">
|
||||||
name='model_mapping'
|
<IconCode size={16} />
|
||||||
onChange={(value) => handleInputChange('model_mapping', value)}
|
</Avatar>
|
||||||
autosize
|
<div>
|
||||||
value={inputs.model_mapping}
|
<Text className="text-lg font-medium">{t('模型配置')}</Text>
|
||||||
/>
|
<div className="text-xs text-gray-600">{t('模型选择和映射设置')}</div>
|
||||||
<Space className="mt-2">
|
</div>
|
||||||
<Text
|
</div>
|
||||||
className="!text-semi-color-primary cursor-pointer"
|
|
||||||
onClick={() => handleInputChange('model_mapping', JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2))}
|
<div className="space-y-4">
|
||||||
>
|
<Banner
|
||||||
{t('填入模板')}
|
type="info"
|
||||||
</Text>
|
description={t('当前模型列表为该标签下所有渠道模型列表最长的一个,并非所有渠道的并集,请注意可能导致某些渠道模型丢失。')}
|
||||||
<Text
|
className="!rounded-lg mb-4"
|
||||||
className="!text-semi-color-primary cursor-pointer"
|
/>
|
||||||
onClick={() => handleInputChange('model_mapping', JSON.stringify({}, null, 2))}
|
<Form.Select
|
||||||
>
|
field='models'
|
||||||
{t('清空重定向')}
|
label={t('模型')}
|
||||||
</Text>
|
placeholder={t('请选择该渠道所支持的模型,留空则不更改')}
|
||||||
<Text
|
multiple
|
||||||
className="!text-semi-color-primary cursor-pointer"
|
filter
|
||||||
onClick={() => handleInputChange('model_mapping', '')}
|
searchPosition='dropdown'
|
||||||
>
|
optionList={modelOptions}
|
||||||
{t('不更改')}
|
style={{ width: '100%' }}
|
||||||
</Text>
|
onChange={(value) => handleInputChange('models', value)}
|
||||||
</Space>
|
/>
|
||||||
</div>
|
|
||||||
|
<Form.Input
|
||||||
|
field='custom_model'
|
||||||
|
label={t('自定义模型名称')}
|
||||||
|
placeholder={t('输入自定义模型名称')}
|
||||||
|
onChange={(value) => setCustomModel(value.trim())}
|
||||||
|
suffix={<Button size='small' type='primary' onClick={addCustomModels}>{t('填入')}</Button>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.TextArea
|
||||||
|
field='model_mapping'
|
||||||
|
label={t('模型重定向')}
|
||||||
|
placeholder={t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,留空则不更改')}
|
||||||
|
autosize
|
||||||
|
onChange={(value) => handleInputChange('model_mapping', value)}
|
||||||
|
extraText={(
|
||||||
|
<Space>
|
||||||
|
<Text className="!text-semi-color-primary cursor-pointer" onClick={() => handleInputChange('model_mapping', JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2))}>{t('填入模板')}</Text>
|
||||||
|
<Text className="!text-semi-color-primary cursor-pointer" onClick={() => handleInputChange('model_mapping', JSON.stringify({}, null, 2))}>{t('清空重定向')}</Text>
|
||||||
|
<Text className="!text-semi-color-primary cursor-pointer" onClick={() => handleInputChange('model_mapping', '')}>{t('不更改')}</Text>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="!rounded-2xl shadow-sm border-0">
|
||||||
|
{/* Header: Group Settings */}
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<Avatar size="small" color="green" className="mr-2 shadow-md">
|
||||||
|
<IconUser size={16} />
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<Text className="text-lg font-medium">{t('分组设置')}</Text>
|
||||||
|
<div className="text-xs text-gray-600">{t('用户分组配置')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Form.Select
|
||||||
|
field='groups'
|
||||||
|
label={t('分组')}
|
||||||
|
placeholder={t('请选择可以使用该渠道的分组,留空则不更改')}
|
||||||
|
multiple
|
||||||
|
allowAdditions
|
||||||
|
additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
|
||||||
|
optionList={groupOptions}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onChange={(value) => handleInputChange('groups', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Spin>
|
||||||
|
)}
|
||||||
<Card className="!rounded-2xl shadow-sm border-0">
|
</Form>
|
||||||
{/* Header: Group Settings */}
|
|
||||||
<div className="flex items-center mb-2">
|
|
||||||
<Avatar size="small" color="green" className="mr-2 shadow-md">
|
|
||||||
<IconUser size={16} />
|
|
||||||
</Avatar>
|
|
||||||
<div>
|
|
||||||
<Text className="text-lg font-medium">{t('分组设置')}</Text>
|
|
||||||
<div className="text-xs text-gray-600">{t('用户分组配置')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">{t('分组')}</Text>
|
|
||||||
<Select
|
|
||||||
placeholder={t('请选择可以使用该渠道的分组,留空则不更改')}
|
|
||||||
name='groups'
|
|
||||||
multiple
|
|
||||||
allowAdditions
|
|
||||||
additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
|
|
||||||
onChange={(value) => handleInputChange('groups', value)}
|
|
||||||
value={inputs.groups}
|
|
||||||
optionList={groupOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
</SideSheet>
|
</SideSheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user