فهرست منبع

🎨 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 10 ماه پیش
والد
کامیت
c0a23ffa62
1فایلهای تغییر یافته به همراه152 افزوده شده و 159 حذف شده
  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>
   );
 };