Selaa lähdekoodia

表单嵌套保存之后修复

pm 5 kuukautta sitten
vanhempi
sitoutus
8f033d2aaf
2 muutettua tiedostoa jossa 203 lisäystä ja 40 poistoa
  1. 157 37
      src/components/FormDesigner.tsx
  2. 46 3
      src/utils/auth.ts

+ 157 - 37
src/components/FormDesigner.tsx

@@ -285,17 +285,13 @@ const CustomDragLayer: React.FC<{ onDragEnd?: () => void }> = ({ onDragEnd }) =>
   }));
 
   // 当拖拽结束时,清理预览状态
+  const prevIsDraggingRef = React.useRef(isDragging);
   React.useEffect(() => {
-    if (!isDragging && onDragEnd) {
-      onDragEnd();
-    }
-  }, [isDragging, onDragEnd]);
-
-  // 当拖拽结束时,清理预览状态
-  React.useEffect(() => {
-    if (!isDragging && onDragEnd) {
+    // 只在从拖拽状态变为非拖拽状态时调用
+    if (prevIsDraggingRef.current && !isDragging && onDragEnd) {
       onDragEnd();
     }
+    prevIsDraggingRef.current = isDragging;
   }, [isDragging, onDragEnd]);
 
   // 不显示预览标签,只处理拖拽结束回调
@@ -1161,6 +1157,69 @@ export default function FormDesigner() {
                 console.log(`字段 ${index} - 直接使用对象:`, parsedField);
               }
               
+              // 递归解析 children(支持嵌套结构)
+              const parseChildren = (children: any[]): FormField[] => {
+                if (!Array.isArray(children)) return [];
+                return children.map((child: any) => {
+                  let parsedChild = child;
+                  if (typeof child === 'string') {
+                    try {
+                      parsedChild = JSON.parse(child);
+                    } catch (err) {
+                      console.error('解析子字段失败:', err);
+                      parsedChild = {};
+                    }
+                  }
+                  return {
+                    id: parsedChild.id || `field_${Date.now()}_${Math.random()}`,
+                    type: parsedChild.type || 'input',
+                    label: parsedChild.label || parsedChild.title || '',
+                    name: parsedChild.name || parsedChild.field || '',
+                    required: parsedChild.required || false,
+                    placeholder: parsedChild.placeholder || '',
+                    options: parsedChild.options || [],
+                    defaultValue: parsedChild.defaultValue,
+                    span: parsedChild.span,
+                    rules: parsedChild.rules,
+                    disabled: parsedChild.disabled,
+                    hidden: parsedChild.hidden,
+                    width: parsedChild.width,
+                    size: parsedChild.size,
+                    uploadType: parsedChild.uploadType,
+                    cascaderOptions: parsedChild.cascaderOptions,
+                    maxCount: parsedChild.maxCount,
+                    accept: parsedChild.accept,
+                    readOnly: parsedChild.readOnly,
+                    maxLength: parsedChild.maxLength,
+                    showClear: parsedChild.showClear,
+                    hint: parsedChild.hint,
+                    labelWidth: parsedChild.labelWidth,
+                    fieldId: parsedChild.fieldId,
+                    inputType: parsedChild.inputType,
+                    className: parsedChild.className,
+                    style: parsedChild.style,
+                    margin: parsedChild.margin,
+                    padding: parsedChild.padding,
+                    validationRules: parsedChild.validationRules,
+                    events: parsedChild.events,
+                    requiredMessage: parsedChild.requiredMessage,
+                    alertTitle: parsedChild.alertTitle,
+                    alertDescription: parsedChild.alertDescription,
+                    alertType: parsedChild.alertType,
+                    alertClosable: parsedChild.alertClosable,
+                    alertCloseText: parsedChild.alertCloseText,
+                    alertShowIcon: parsedChild.alertShowIcon,
+                    alertCentered: parsedChild.alertCentered,
+                    alertTheme: parsedChild.alertTheme,
+                    // 容器类型特有字段
+                    gridColumns: parsedChild.gridColumns,
+                    cardTitle: parsedChild.cardTitle,
+                    // 递归解析子字段
+                    children: parsedChild.children ? parseChildren(parsedChild.children) : undefined,
+                  };
+                });
+              };
+
               const mappedField = {
                 id: parsedField.id || `field_${Date.now()}_${index}`, // 复制时生成新的 id
                 type: parsedField.type || 'input',
@@ -1202,6 +1261,11 @@ export default function FormDesigner() {
                 alertShowIcon: parsedField.alertShowIcon,
                 alertCentered: parsedField.alertCentered,
                 alertTheme: parsedField.alertTheme,
+                // 容器类型特有字段
+                gridColumns: parsedField.gridColumns,
+                cardTitle: parsedField.cardTitle,
+                // 递归解析子字段
+                children: parsedField.children ? parseChildren(parsedField.children) : undefined,
               };
               
               console.log(`字段 ${index} - 映射后的字段对象:`, mappedField);
@@ -1741,9 +1805,14 @@ export default function FormDesigner() {
     gap: '16px',
   } : undefined;
 
+  // 使用 useCallback 稳定 onDragEnd 函数引用,避免无限循环
+  const handleDragEnd = useCallback(() => {
+    setDragPreview({ show: false });
+  }, []);
+
   return (
     <DndProvider backend={backend}>
-      <CustomDragLayer onDragEnd={() => setDragPreview({ show: false })} />
+      <CustomDragLayer onDragEnd={handleDragEnd} />
       <div className="h-screen flex flex-col bg-gray-50">
         {/* 顶部工具栏 */}
  
@@ -2919,33 +2988,80 @@ export default function FormDesigner() {
             validateMessages={formConfig.inlineValidation ? undefined : { required: '' }}
           >
             <div style={gridStyle} className={layoutColumns === 1 ? 'space-y-4' : ''}>
-            {fields.map((field) => {
-              const spanStyle = layoutColumns > 1 ? { gridColumn: `span ${Math.min(layoutColumns, field.span || 1)}` } : undefined;
-              if (field.type === 'alert') {
-                const themeClass =
-                  field.alertTheme === 'dark'
-                    ? 'bg-gray-800 text-white border-gray-700'
-                    : '';
-                return (
-                  <div key={field.id} className="mb-4" style={spanStyle}>
-                    <Alert
-                      message={field.alertTitle || field.label}
-                      description={field.alertDescription}
-                      type={field.alertType || 'info'}
-                      showIcon={field.alertShowIcon !== false}
-                      closable={field.alertClosable}
-                      closeText={field.alertCloseText}
-                      className={`${field.alertCentered ? 'text-center' : ''} ${themeClass}`}
-                      banner
-                      style={field.style}
-                    />
-                    {field.hint && <div className="text-xs text-gray-400 mt-1">{field.hint}</div>}
-                  </div>
-                );
-              }
+            {/* 递归渲染字段(支持嵌套) */}
+            {(() => {
+              const renderPreviewField = (field: FormField, parentSpanStyle?: React.CSSProperties): React.ReactNode => {
+                const spanStyle = parentSpanStyle || (layoutColumns > 1 ? { gridColumn: `span ${Math.min(layoutColumns, field.span || 1)}` } : undefined);
+                
+                // 处理容器类型(card 和 grid)
+                if (field.type === 'card') {
+                  const children = field.children || [];
+                  const cardTitle = field.cardTitle || field.label || '卡片容器';
+                  return (
+                    <div key={field.id} style={spanStyle} className="mb-4">
+                      <Card title={cardTitle} className="w-full">
+                        <div className="space-y-4">
+                          {children.map(child => renderPreviewField(child))}
+                        </div>
+                      </Card>
+                    </div>
+                  );
+                }
+                
+                if (field.type === 'grid') {
+                  const gridColumns = field.gridColumns || 2;
+                  const children = field.children || [];
+                  return (
+                    <div key={field.id} style={spanStyle} className="mb-4">
+                      <div
+                        style={{
+                          display: 'grid',
+                          gridTemplateColumns: `repeat(${gridColumns}, minmax(0, 1fr))`,
+                          gap: '16px',
+                        }}
+                      >
+                        {children.map((child) => {
+                          const childSpanStyle = gridColumns > 1 
+                            ? { gridColumn: `span ${Math.min(gridColumns, child.span || 1)}` } 
+                            : undefined;
+                          return (
+                            <div key={child.id} style={childSpanStyle}>
+                              {renderPreviewField(child)}
+                            </div>
+                          );
+                        })}
+                      </div>
+                    </div>
+                  );
+                }
+                
+                // 处理 alert 类型
+                if (field.type === 'alert') {
+                  const themeClass =
+                    field.alertTheme === 'dark'
+                      ? 'bg-gray-800 text-white border-gray-700'
+                      : '';
+                  return (
+                    <div key={field.id} className="mb-4" style={spanStyle}>
+                      <Alert
+                        message={field.alertTitle || field.label}
+                        description={field.alertDescription}
+                        type={field.alertType || 'info'}
+                        showIcon={field.alertShowIcon !== false}
+                        closable={field.alertClosable}
+                        closeText={field.alertCloseText}
+                        className={`${field.alertCentered ? 'text-center' : ''} ${themeClass}`}
+                        banner
+                        style={field.style}
+                      />
+                      {field.hint && <div className="text-xs text-gray-400 mt-1">{field.hint}</div>}
+                    </div>
+                  );
+                }
 
-              return (
-                <div key={field.id} style={spanStyle}>
+                // 处理普通字段
+                return (
+                  <div key={field.id} style={spanStyle}>
                 <AntdForm.Item
                   label={field.label + (formConfig.labelSuffix || '')}
                   name={field.name}
@@ -3176,8 +3292,12 @@ export default function FormDesigner() {
                   )}
                 </AntdForm.Item>
                 </div>
-              );
-            })}
+                );
+              };
+              
+              // 渲染所有根级别字段
+              return fields.map(field => renderPreviewField(field));
+            })()}
             </div>
           </AntdForm>
         </Modal>

+ 46 - 3
src/utils/auth.ts

@@ -340,14 +340,57 @@ const checkTokenAndRefresh = async () => {
               setRefreshToken(newRefreshToken);
             }
             console.log('Token刷新成功');
+          } else {
+            console.warn('刷新token失败:未返回新token,但不退出登录');
+            // 即使刷新失败,也不退出登录,继续保留当前页面
           }
         } catch (refreshError) {
-          console.error('刷新token失败:', refreshError);
+          console.warn('刷新token失败:', refreshError, '但不退出登录,继续保留当前页面');
+          // 即使刷新失败,也不退出登录,继续保留当前页面
+        }
+      } else {
+        // 其他业务错误,不处理
+        console.log('Token检测接口返回其他错误:', errorData);
+      }
+    } else if (error?.response?.status === 401) {
+      // HTTP状态码401,尝试刷新token
+      console.log('检测到HTTP 401错误,开始刷新token...');
+      
+      try {
+        const { loginApi } = await import('../api/Login');
+        const refreshResponse = await loginApi.refreshToken(refreshToken);
+        
+        // 处理返回数据格式
+        let tokenData;
+        if (refreshResponse?.code === 0 && refreshResponse?.data) {
+          tokenData = refreshResponse.data;
+        } else if (refreshResponse?.data) {
+          tokenData = refreshResponse.data;
+        } else {
+          tokenData = refreshResponse;
+        }
+        
+        const newAccessToken = tokenData?.accessToken || tokenData?.token;
+        const newRefreshToken = tokenData?.refreshToken;
+
+        if (newAccessToken) {
+          setToken({ accessToken: newAccessToken, token: newAccessToken });
+          setAccessToken(newAccessToken);
+          if (newRefreshToken) {
+            setRefreshToken(newRefreshToken);
+          }
+          console.log('Token刷新成功');
+        } else {
+          console.warn('刷新token失败:未返回新token,但不退出登录');
+          // 即使刷新失败,也不退出登录,继续保留当前页面
         }
+      } catch (refreshError) {
+        console.warn('刷新token失败:', refreshError, '但不退出登录,继续保留当前页面');
+        // 即使刷新失败,也不退出登录,继续保留当前页面
       }
     } else {
-      // 其他错误(网络问题等),不处理
-      console.log('Token检测接口调用失败:', error?.message || error);
+      // 其他错误(网络问题等),不处理,不退出登录
+      console.log('Token检测接口调用失败:', error?.message || error, '但不退出登录');
     }
   } finally {
     isChecking = false;