Browse Source

🎨 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.
t0ng7u 6 months ago
parent
commit
c0a23ffa62
1 changed files with 152 additions and 159 deletions
  1. 152 159
      web/src/pages/Channel/EditTagModal.js

+ 152 - 159
web/src/pages/Channel/EditTagModal.js

@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import {
   API,
   showError,
@@ -11,15 +11,13 @@ import {
   SideSheet,
   Space,
   Button,
-  Input,
   Typography,
   Spin,
-  Select,
   Banner,
-  TextArea,
   Card,
   Tag,
   Avatar,
+  Form,
 } from '@douyinfe/semi-ui';
 import {
   IconSave,
@@ -53,9 +51,14 @@ const EditTagModal = (props) => {
     models: [],
   };
   const [inputs, setInputs] = useState(originInputs);
+  const formApiRef = useRef(null);
+  const getInitValues = () => ({ ...originInputs });
 
   const handleInputChange = (name, value) => {
     setInputs((inputs) => ({ ...inputs, [name]: value }));
+    if (formApiRef.current) {
+      formApiRef.current.setValue(name, value);
+    }
     if (name === 'type') {
       let localModels = [];
       switch (value) {
@@ -133,27 +136,25 @@ const EditTagModal = (props) => {
     }
   };
 
-  const handleSave = async () => {
+  const handleSave = async (values) => {
     setLoading(true);
-    let data = {
-      tag: tag,
-    };
-    if (inputs.model_mapping !== null && inputs.model_mapping !== '') {
-      if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
+    const formVals = values || formApiRef.current?.getValues() || {};
+    let data = { tag };
+    if (formVals.model_mapping) {
+      if (!verifyJSON(formVals.model_mapping)) {
         showInfo('模型映射必须是合法的 JSON 格式!');
         setLoading(false);
         return;
       }
-      data.model_mapping = inputs.model_mapping;
+      data.model_mapping = formVals.model_mapping;
     }
-    if (inputs.groups.length > 0) {
-      data.groups = inputs.groups.join(',');
+    if (formVals.groups && formVals.groups.length > 0) {
+      data.groups = formVals.groups.join(',');
     }
-    if (inputs.models.length > 0) {
-      data.models = inputs.models.join(',');
+    if (formVals.models && formVals.models.length > 0) {
+      data.models = formVals.models.join(',');
     }
-    data.new_tag = inputs.new_tag;
-    // check have any change
+    data.new_tag = formVals.new_tag;
     if (
       data.model_mapping === undefined &&
       data.groups === undefined &&
@@ -202,7 +203,7 @@ const EditTagModal = (props) => {
         const res = await API.get(`/api/channel/tag/models?tag=${tag}`);
         if (res?.data?.success) {
           const models = res.data.data ? res.data.data.split(',') : [];
-          setInputs((inputs) => ({ ...inputs, models: models }));
+          handleInputChange('models', models);
         } else {
           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({
       ...originInputs,
       tag: tag,
       new_tag: tag,
     });
-    fetchModels().then();
-    fetchGroups().then();
-    fetchTagModels().then(); // Call the new function
-  }, [visible, tag]); // Add tag to dependency array
+  }, [visible, tag]);
+
+  useEffect(() => {
+    if (formApiRef.current) {
+      formApiRef.current.setValues(inputs);
+    }
+  }, [inputs]);
 
   const addCustomModels = () => {
     if (customModel.trim() === '') return;
-    // 使用逗号分隔字符串,然后去除每个模型名称前后的空格
     const modelArray = customModel.split(',').map((model) => model.trim());
 
     let localModels = [...inputs.models];
@@ -233,11 +247,9 @@ const EditTagModal = (props) => {
     const addedModels = [];
 
     modelArray.forEach((model) => {
-      // 检查模型是否已存在,且模型名称非空
       if (model && !localModels.includes(model)) {
-        localModels.push(model); // 添加到模型列表
+        localModels.push(model);
         localModelOptions.push({
-          // 添加到下拉选项
           key: model,
           text: model,
           value: model,
@@ -246,7 +258,6 @@ const EditTagModal = (props) => {
       }
     });
 
-    // 更新状态值
     setModelOptions(localModelOptions);
     setCustomModel('');
     handleInputChange('models', localModels);
@@ -283,7 +294,7 @@ const EditTagModal = (props) => {
           <Space>
             <Button
               theme="solid"
-              onClick={handleSave}
+              onClick={() => formApiRef.current?.submitForm()}
               loading={loading}
               icon={<IconSave />}
             >
@@ -302,146 +313,128 @@ const EditTagModal = (props) => {
       }
       closeIcon={null}
     >
-      <Spin spinning={loading}>
-        <div className="p-2">
-          <Card className="!rounded-2xl shadow-sm border-0 mb-6">
-            {/* Header: Tag Info */}
-            <div className="flex items-center mb-2">
-              <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"
-            />
+      <Form
+        key={tag || 'edit'}
+        initValues={getInitValues()}
+        getFormApi={(api) => (formApiRef.current = api)}
+        onSubmit={handleSave}
+      >
+        {() => (
+          <Spin spinning={loading}>
+            <div className="p-2">
+              <Card className="!rounded-2xl shadow-sm border-0 mb-6">
+                {/* Header: Tag Info */}
+                <div className="flex items-center mb-2">
+                  <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>
 
-            <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
-                  type="info"
-                  description={t('当前模型列表为该标签下所有渠道模型列表最长的一个,并非所有渠道的并集,请注意可能导致某些渠道模型丢失。')}
+                  type="warning"
+                  description={t('所有编辑均为覆盖操作,留空则不更改')}
                   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>
-                <Input
-                  addonAfter={
-                    <Button type='primary' onClick={addCustomModels} className="!rounded-r-lg">
-                      {t('填入')}
-                    </Button>
-                  }
-                  placeholder={t('输入自定义模型名称')}
-                  value={customModel}
-                  onChange={(value) => setCustomModel(value.trim())}
-                />
-              </div>
+                <div className="space-y-4">
+                  <Form.Input
+                    field='new_tag'
+                    label={t('标签名称')}
+                    placeholder={t('请输入新标签,留空则解散标签')}
+                    onChange={(value) => handleInputChange('new_tag', value)}
+                  />
+                </div>
+              </Card>
 
-              <div>
-                <Text strong className="block mb-2">{t('模型重定向')}</Text>
-                <TextArea
-                  placeholder={t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,留空则不更改')}
-                  name='model_mapping'
-                  onChange={(value) => handleInputChange('model_mapping', value)}
-                  autosize
-                  value={inputs.model_mapping}
-                />
-                <Space className="mt-2">
-                  <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>
-            </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>
 
-          <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">
+                  <Banner
+                    type="info"
+                    description={t('当前模型列表为该标签下所有渠道模型列表最长的一个,并非所有渠道的并集,请注意可能导致某些渠道模型丢失。')}
+                    className="!rounded-lg mb-4"
+                  />
+                  <Form.Select
+                    field='models'
+                    label={t('模型')}
+                    placeholder={t('请选择该渠道所支持的模型,留空则不更改')}
+                    multiple
+                    filter
+                    searchPosition='dropdown'
+                    optionList={modelOptions}
+                    style={{ width: '100%' }}
+                    onChange={(value) => handleInputChange('models', value)}
+                  />
 
-            <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>
+                  <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>
-          </Card>
-        </div>
-      </Spin>
+          </Spin>
+        )}
+      </Form>
     </SideSheet>
   );
 };