API Reference

fieldsConfig Configuration Details

Complete API for Field Configuration in SmartCrudPage and SmartForm


🎯 Overview

fieldsConfig is the core configuration of NextJS Base, one configuration drives four scenarios: table, form, search, and detail.


📋 Basic Structure

interface FieldConfig {
  key: string           // Field name (required)
  title: string         // Display title (required)
  type: FieldType       // Field type (required)
  options?: Option[] | (() => Promise<Option[]>)  // Options
  table?: TableConfig   // Table configuration
  form?: FormConfig     // Form configuration
  search?: SearchConfig // Search configuration
}

🔤 Field Types (type)

TypeDescriptionTableFormSearch
textSingle-line textInput
textareaMulti-line textTextArea
numberNumberInputNumber
selectDropdownSelect
switchSwitchSwitch
dateDateDatePicker
datetimeDateTimeDateTimePicker
tree-selectTree SelectTreeSelect
iconIconIconPicker
imageSingle ImageImageUpload
imagesMultiple ImagesMultiImageUpload
markdownMarkdownMarkdownEditor
jsonJSONJsonEditor

📊 Table Configuration (table)

interface TableConfig {
  // Display Control
  width?: number              // Column width
  fixed?: 'left' | 'right'    // Fixed column
  hidden?: boolean            // Whether to hide
  
  // Text Display
  ellipsis?: boolean          // Ellipsis for overflow
  copyable?: boolean          // Copyable
  
  // Sorting
  sorter?: boolean            // Enable sorting
  defaultSortOrder?: 'ascend' | 'descend'  // Default sort order
  
  // Custom Render
  render?: (value: any, record: any, index: number) => ReactNode
  
  // Value Mapping (for status display)
  valueEnum?: Record<string, {
    text: string
    status?: 'Success' | 'Error' | 'Processing' | 'Warning' | 'Default'
    color?: string
  }>
}

Examples

// Basic Configuration
{
  key: 'name',
  title: 'Name',
  type: 'text',
  table: {
    width: 200,
    ellipsis: true,
    copyable: true,
  },
}

// Fixed Column
{
  key: 'id',
  title: 'ID',
  type: 'text',
  table: {
    width: 80,
    fixed: 'left',
  },
}

// Sorting
{
  key: 'createdAt',
  title: 'Created At',
  type: 'datetime',
  table: {
    width: 180,
    sorter: true,
    defaultSortOrder: 'descend',
  },
}

// Status Mapping
{
  key: 'status',
  title: 'Status',
  type: 'select',
  table: {
    width: 100,
    valueEnum: {
      active: { text: 'Enabled', status: 'Success' },
      inactive: { text: 'Disabled', status: 'Error' },
      pending: { text: 'Pending', status: 'Processing' },
    },
  },
}

// Custom Render
{
  key: 'price',
  title: 'Price',
  type: 'number',
  table: {
    width: 120,
    render: (value) => `$${value?.toFixed(2) || '0.00'}`,
  },
}

// Hidden Column
{
  key: 'remark',
  title: 'Remarks',
  type: 'textarea',
  table: {
    hidden: true,  // Not displayed in table
  },
}

📝 Form Configuration (form)

interface FormConfig {
  // Display Control
  hidden?: boolean            // Whether to hide
  disabled?: boolean          // Whether to disable
  readonly?: boolean          // Whether read-only
  
  // Validation
  required?: boolean          // Whether required
  rules?: Rule[]              // Validation rules
  
  // Default Value
  defaultValue?: any
  
  // Input Hints
  placeholder?: string
  tooltip?: string            // Help icon tooltip
  extra?: string              // Additional description text
  
  // Layout
  colSpan?: number            // Column width (1-24)
  
  // Field Linkage
  dependencies?: string[]     // Dependent fields
  visible?: (form: FormInstance) => boolean  // Dynamic display
  fieldProps?: (form: FormInstance) => object  // Dynamic properties
  
  // Type-specific Configuration
  rows?: number               // textarea rows
  min?: number                // number minimum
  max?: number                // number maximum
  step?: number               // number step
  precision?: number          // number precision
  prefix?: string             // Prefix
  suffix?: string             // Suffix
  maxCount?: number           // Maximum image count
  accept?: string             // File type
  maxSize?: number            // File size limit (MB)
}

Examples

// Required Text
{
  key: 'name',
  title: 'Name',
  type: 'text',
  form: {
    required: true,
    placeholder: 'Enter name',
    rules: [
      { min: 2, message: 'At least 2 characters' },
      { max: 50, message: 'At most 50 characters' },
    ],
  },
}

// Number Input
{
  key: 'price',
  title: 'Price',
  type: 'number',
  form: {
    required: true,
    min: 0,
    max: 99999,
    precision: 2,
    prefix: '$',
    placeholder: 'Enter price',
  },
}

// Multi-line Text
{
  key: 'description',
  title: 'Description',
  type: 'textarea',
  form: {
    rows: 4,
    maxLength: 500,
    showCount: true,
    placeholder: 'Enter description',
  },
}

// Switch
{
  key: 'enable',
  title: 'Enabled',
  type: 'switch',
  form: {
    defaultValue: true,
    checkedChildren: 'On',
    unCheckedChildren: 'Off',
  },
}

// Image Upload
{
  key: 'avatar',
  title: 'Avatar',
  type: 'image',
  form: {
    maxCount: 1,
    accept: 'image/*',
    maxSize: 2,
    tip: 'Supports jpg, png format, max 2MB',
  },
}

// Field Linkage
{
  key: 'discountType',
  title: 'Discount Type',
  type: 'select',
  options: [
    { label: 'Fixed Amount', value: 'fixed' },
    { label: 'Percentage', value: 'percent' },
  ],
},
{
  key: 'discountValue',
  title: 'Discount Value',
  type: 'number',
  form: {
    dependencies: ['discountType'],
    fieldProps: (form) => ({
      suffix: form.getFieldValue('discountType') === 'percent' ? '%' : '',
      max: form.getFieldValue('discountType') === 'percent' ? 100 : undefined,
    }),
  },
}

// Conditional Display
{
  key: 'reason',
  title: 'Rejection Reason',
  type: 'textarea',
  form: {
    dependencies: ['status'],
    visible: (form) => form.getFieldValue('status') === 'rejected',
    required: (form) => form.getFieldValue('status') === 'rejected',
  },
}

// Hidden Field
{
  key: 'id',
  title: 'ID',
  type: 'text',
  form: {
    hidden: true,
  },
}

interface SearchConfig {
  enabled?: boolean           // Whether to enable search
  mode?: 'like' | 'exact' | 'in' | 'range'  // Search mode
  placeholder?: string        // Placeholder
  defaultValue?: any          // Default value
}

Search Mode Details

ModeDescriptionApplicable TypesPrisma Conversion
likeFuzzy Matchtext, textarea{ contains: value }
exactExact Matchselect, switch, numbervalue
inMulti-select Matchselect (multi){ in: values }
rangeRange Matchdate, datetime, number{ gte: start, lte: end }

Examples

// Fuzzy Search
{
  key: 'title',
  title: 'Title',
  type: 'text',
  search: {
    enabled: true,
    mode: 'like',
    placeholder: 'Search title',
  },
}

// Exact Match
{
  key: 'status',
  title: 'Status',
  type: 'select',
  options: [
    { label: 'Enabled', value: 'active' },
    { label: 'Disabled', value: 'inactive' },
  ],
  search: {
    enabled: true,
    mode: 'exact',
  },
}

// Multi-select Match
{
  key: 'tags',
  title: 'Tags',
  type: 'select',
  options: [...],
  search: {
    enabled: true,
    mode: 'in',
  },
}

// Date Range
{
  key: 'createdAt',
  title: 'Created At',
  type: 'datetime',
  search: {
    enabled: true,
    mode: 'range',
  },
}

// Number Range
{
  key: 'price',
  title: 'Price',
  type: 'number',
  search: {
    enabled: true,
    mode: 'range',
  },
}

🎨 Options Configuration (options)

Static Options

{
  key: 'status',
  title: 'Status',
  type: 'select',
  options: [
    { label: 'Enabled', value: 'active' },
    { label: 'Disabled', value: 'inactive' },
  ],
}

Dynamic Options

{
  key: 'categoryId',
  title: 'Category',
  type: 'select',
  options: async () => {
    const res = await getCategoryListAction()
    return res.data.map(item => ({
      label: item.name,
      value: item.id,
    }))
  },
}

Tree Options

{
  key: 'parentId',
  title: 'Parent',
  type: 'tree-select',
  options: async () => {
    const res = await getCategoryTreeAction()
    return res.data  // Must be tree structure
  },
}

📚 Complete Example

const fieldsConfig = [
  // ID
  {
    key: 'id',
    title: 'ID',
    type: 'text',
    table: { width: 80, fixed: 'left', copyable: true },
    form: { hidden: true },
  },
  
  // Name
  {
    key: 'name',
    title: 'Name',
    type: 'text',
    table: { width: 200, ellipsis: true },
    form: { required: true, placeholder: 'Enter name' },
    search: { enabled: true, mode: 'like' },
  },
  
  // Category
  {
    key: 'categoryId',
    title: 'Category',
    type: 'select',
    options: async () => {
      const res = await getCategoryListAction()
      return res.data.map(item => ({ label: item.name, value: item.id }))
    },
    table: { width: 120 },
    form: { required: true },
    search: { enabled: true, mode: 'exact' },
  },
  
  // Price
  {
    key: 'price',
    title: 'Price',
    type: 'number',
    table: {
      width: 100,
      sorter: true,
      render: (value) => `$${value?.toFixed(2)}`,
    },
    form: { required: true, min: 0, precision: 2, prefix: '$' },
    search: { enabled: true, mode: 'range' },
  },
  
  // Status
  {
    key: 'enable',
    title: 'Status',
    type: 'switch',
    table: {
      width: 80,
      render: (value) => value ? '✅ Enabled' : '❌ Disabled',
    },
    form: { defaultValue: true },
    search: { enabled: true, mode: 'exact' },
  },
  
  // Remarks
  {
    key: 'remark',
    title: 'Remarks',
    type: 'textarea',
    table: { hidden: true },
    form: { rows: 3 },
  },
  
  // Created At
  {
    key: 'createdAt',
    title: 'Created At',
    type: 'datetime',
    table: { width: 180, sorter: true },
    form: { hidden: true },
    search: { enabled: true, mode: 'range' },
  },
]