Просмотр исходного кода

表单设计保存后页面跳转问题修复,表单管理页面详情查看修复

pm 5 месяцев назад
Родитель
Сommit
c2031f995d
3 измененных файлов с 542 добавлено и 91 удалено
  1. 159 39
      src/components/FormDesigner.tsx
  2. 337 49
      src/components/FormManagement.tsx
  3. 46 3
      src/utils/auth.ts

+ 159 - 39
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);
@@ -1703,8 +1767,8 @@ export default function FormDesigner() {
       setHasUnsavedChanges(false);
 
       // 保存成功后返回表单列表页
-      // 导航到表单管理页面路径
-      navigate('/jobTicket/form');
+      // 使用 navigate(-1) 返回上一页(表单管理页面)
+      navigate(-1);
     } catch (error: any) {
       toast.error(error.message || '保存失败');
     } finally {
@@ -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>

+ 337 - 49
src/components/FormManagement.tsx

@@ -1,7 +1,8 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
 import { useNavigate, useLocation } from 'react-router-dom';
 import { Plus, Edit2, Trash2, Copy, Eye, X, Search, RotateCcw } from 'lucide-react';
-import { Button, Space, Table as AntdTable, Modal, message, Input, Form as AntdForm, Select, DatePicker, InputNumber, Switch, Radio, Checkbox } from 'antd';
+import { Button, Space, Table as AntdTable, Modal, message, Input, Form as AntdForm, Select, DatePicker, InputNumber, Switch, Radio, Checkbox, Card, Alert, Cascader, Upload } from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
 import type { ColumnsType } from 'antd/es/table';
 import { toast } from 'sonner';
 import * as FormApi from '../api/bpm/form';
@@ -48,41 +49,330 @@ export default function FormManagement() {
     showResetButton: false,
   };
 
-  const renderFieldPreview = (field: any) => {
+  // 渲染字段预览(支持嵌套结构)
+  const renderFieldPreview = (field: any, parentSpanStyle?: React.CSSProperties): React.ReactNode => {
+    const formConfig = detailData.option?.formConfig || defaultFormConfig;
+    const layoutColumns = formConfig.layoutColumns || 1;
+    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: any) => renderFieldPreview(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: any) => {
+              const childSpanStyle = gridColumns > 1 
+                ? { gridColumn: `span ${Math.min(gridColumns, child.span || 1)}` } 
+                : undefined;
+              return (
+                <div key={child.id} style={childSpanStyle}>
+                  {renderFieldPreview(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>
+      );
+    }
+
+    // 处理普通字段
     switch (field.type) {
       case 'textarea':
-        return <Input.TextArea placeholder={field.placeholder || '请输入'} rows={3} />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
+              help={field.hint}
+            >
+              <Input.TextArea 
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'} 
+                rows={4}
+                allowClear={field.showClear}
+                maxLength={field.maxLength}
+                readOnly={field.readOnly}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
       case 'password':
-        return <Input.Password placeholder={field.placeholder || '请输入'} />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
+              help={field.hint}
+            >
+              <Input.Password 
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
+                allowClear={field.showClear}
+                maxLength={field.maxLength}
+                readOnly={field.readOnly}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
       case 'number':
-        return <InputNumber style={{ width: '100%' }} placeholder={field.placeholder || '请输入'} />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
+              help={field.hint}
+            >
+              <InputNumber 
+                style={{ width: '100%' }} 
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
+                max={field.maxLength}
+                readOnly={field.readOnly}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
       case 'select':
         return (
-          <Select
-            placeholder={field.placeholder || '请选择'}
-            options={(field.options || []).map((opt: any) => ({ label: opt.label, value: opt.value }))}
-          />
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
+              help={field.hint}
+            >
+              <Select
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择'}
+                allowClear={field.showClear}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              >
+                {(field.options || []).map((opt: any, idx: number) => (
+                  <Select.Option key={idx} value={opt.value}>{opt.label}</Select.Option>
+                ))}
+              </Select>
+            </AntdForm.Item>
+          </div>
         );
       case 'date':
-        return <DatePicker style={{ width: '100%' }} placeholder={field.placeholder || '请选择日期'} />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择日期' }] : []}
+              help={field.hint}
+            >
+              <DatePicker 
+                style={{ width: '100%' }} 
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择日期'}
+                allowClear={field.showClear}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
       case 'daterange':
-        return <DatePicker.RangePicker style={{ width: '100%' }} placeholder={['开始日期', '结束日期']} />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择日期范围' }] : []}
+              help={field.hint}
+            >
+              <DatePicker.RangePicker 
+                style={{ width: '100%' }} 
+                placeholder={Array.isArray(field.placeholder) ? field.placeholder as [string, string] : ['开始日期', '结束日期']}
+                allowClear={field.showClear}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
       case 'switch':
-        return <Switch />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
+              help={field.hint}
+              valuePropName="checked"
+            >
+              <Switch disabled={field.disabled} />
+            </AntdForm.Item>
+          </div>
+        );
       case 'radio':
         return (
-          <Radio.Group
-            options={(field.options || []).map((opt: any) => ({ label: opt.label, value: opt.value }))}
-          />
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
+              help={field.hint}
+            >
+              <Radio.Group disabled={field.disabled}>
+                {(field.options || []).map((opt: any, idx: number) => (
+                  <Radio key={idx} value={opt.value}>{opt.label}</Radio>
+                ))}
+              </Radio.Group>
+            </AntdForm.Item>
+          </div>
         );
       case 'checkbox':
         return (
-          <Checkbox.Group
-            options={(field.options || []).map((opt: any) => ({ label: opt.label, value: opt.value }))}
-          />
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
+              help={field.hint}
+            >
+              <Checkbox.Group disabled={field.disabled}>
+                {(field.options || []).map((opt: any, idx: number) => (
+                  <Checkbox key={idx} value={opt.value}>{opt.label}</Checkbox>
+                ))}
+              </Checkbox.Group>
+            </AntdForm.Item>
+          </div>
+        );
+      case 'cascader':
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
+              help={field.hint}
+            >
+              <Cascader
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择'}
+                options={field.cascaderOptions || []}
+                className="w-full"
+                allowClear={field.showClear}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
+      case 'upload':
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请上传' }] : []}
+              help={field.hint}
+            >
+              <Upload
+                disabled={field.disabled}
+                maxCount={field.uploadType === 'single-image' ? 1 : field.maxCount || (field.uploadType === 'multiple-image' ? 9 : undefined)}
+                accept={field.accept || (field.uploadType === 'single-image' || field.uploadType === 'multiple-image' ? 'image/*' : undefined)}
+                listType={(field.uploadType === 'file' ? 'text' : 'picture-card') as 'text' | 'picture-card' | 'picture'}
+                multiple={field.uploadType !== 'single-image'}
+              >
+                {field.uploadType === 'file' ? (
+                  <Button icon={<UploadOutlined />}>上传文件</Button>
+                ) : (
+                  <div>
+                    <UploadOutlined />
+                    <div style={{ marginTop: 8 }}>上传</div>
+                  </div>
+                )}
+              </Upload>
+            </AntdForm.Item>
+          </div>
         );
       default:
-        return <Input placeholder={field.placeholder || '请输入'} />;
+        return (
+          <div key={field.id} style={spanStyle}>
+            <AntdForm.Item
+              label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
+              name={field.name || field.field}
+              required={field.required && !formConfig.hideRequiredMark}
+              rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
+              help={field.hint}
+            >
+              <Input 
+                type={field.inputType || 'text'}
+                placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
+                allowClear={field.showClear}
+                maxLength={field.maxLength}
+                readOnly={field.readOnly}
+                disabled={field.disabled}
+                size={field.size || 'middle'}
+              />
+            </AntdForm.Item>
+          </div>
+        );
     }
   };
 
@@ -370,36 +660,34 @@ export default function FormManagement() {
         width={800}
       >
         <div className="p-2">
-          <AntdForm
-            form={detailForm}
-            layout={(detailData.option?.formConfig?.labelPosition || defaultFormConfig.labelPosition) === 'top' ? 'vertical' : 'horizontal'}
-            size={detailData.option?.formConfig?.formSize || defaultFormConfig.formSize}
-            labelCol={{
-              style: {
-                width: `${detailData.option?.formConfig?.labelWidth || defaultFormConfig.labelWidth}px`,
-                textAlign:
-                  (detailData.option?.formConfig?.labelPosition || defaultFormConfig.labelPosition) === 'left'
-                    ? 'left'
-                    : (detailData.option?.formConfig?.labelPosition || defaultFormConfig.labelPosition) === 'right'
-                      ? 'right'
-                      : 'left',
-              },
-            }}
-            requiredMark={detailData.option?.formConfig?.hideRequiredMark ? false : undefined}
-            className="space-y-3"
-          >
-            {(detailData.rule || []).map((field: any) => (
-              <AntdForm.Item
-                key={field.id || field.name}
-                label={(field.label || field.title || '') + (detailData.option?.formConfig?.labelSuffix || '')}
-                name={field.name || field.field}
-                required={field.required}
-                rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
+          {(() => {
+            const formConfig = detailData.option?.formConfig || defaultFormConfig;
+            const layoutColumns = formConfig.layoutColumns || 1;
+            const gridStyle = layoutColumns > 1 ? {
+              display: 'grid',
+              gridTemplateColumns: `repeat(${layoutColumns}, minmax(0, 1fr))`,
+              gap: '16px',
+            } : undefined;
+
+            return (
+              <AntdForm
+                form={detailForm}
+                layout={formConfig.labelPosition === 'top' ? 'vertical' : formConfig.labelPosition === 'left' ? 'horizontal' : 'horizontal'}
+                size={formConfig.formSize === 'default' ? 'middle' : formConfig.formSize}
+                requiredMark={formConfig.hideRequiredMark ? false : undefined}
+                labelCol={formConfig.labelWidth ? { 
+                  style: { 
+                    width: `${formConfig.labelWidth}px`,
+                    textAlign: formConfig.labelPosition === 'left' ? 'left' : formConfig.labelPosition === 'right' ? 'right' : 'left'
+                  } 
+                } : undefined}
               >
-                {renderFieldPreview(field)}
-              </AntdForm.Item>
-            ))}
-          </AntdForm>
+                <div style={gridStyle} className={layoutColumns === 1 ? 'space-y-4' : ''}>
+                  {(detailData.rule || []).map((field: any) => renderFieldPreview(field))}
+                </div>
+              </AntdForm>
+            );
+          })()}
         </div>
       </Modal>
     </div>

+ 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;