Frontend CRUD Page Skeleton Developer Guide
ResourceCrudPage.vue (located at src/components/ResourceCrudPage.vue) is the core reusable component in the TestNet frontend project. All 8 asset management modules, project management, role permissions, user configuration, and over a dozen other pages are built on this component. It provides standardized data listing, form entry, paginated filtering, Excel import/export, custom columns, and batch operations.
Core Design Principles
ResourceCrudPage follows declarative configuration-driven and Chinese REST convention-adapted design principles:
graph TD
A[View Component DomainList.vue] -->|Props Config| B[ResourceCrudPage]
B -->|Dynamic Form Rendering| C[NForm & NDataTable]
B -->|API Auto-binding| D[Axios HTTP Request]
D -->|Chinese REST Convention| E[Backend Controller]By passing a specific endpoint route and configuration parameters, the component automatically binds to backend Chinese REST endpoints, automatically renders lists and CRUD forms, thereby avoiding large amounts of redundant code.
Component API (Props) Reference
const props = defineProps<{
title: string // Page title, supports custom i18n auto-translation
description?: string // Page description
endpoint: string // Bound API prefix route, e.g. '/domain', '/api/v1/project'
columns: DataTableColumns<any> // Naive UI table column config (no need to include action/selection columns)
formFields: FormField[] // CRUD modal form field configuration
initialValues: Record<string, any> // Default initial values for form fields
searchPlaceholder?: string // Fuzzy search box placeholder (default: Enter keyword to search)
searchParam?: string // Fuzzy search query parameter name (default: 'q')
rowActions?: RowAction[] // Extra inline or dropdown action buttons (first two inline, rest in "more")
fixedQuery?: Record<string, any> // Fixed list query parameters (e.g. specific asset category filter)
fixedPayload?: Record<string, any> // Fixed form submission payload fields
importConfig?: { // Excel import/export config
typeName: string // Asset import type name (e.g. domain)
importUrl: string // Import file endpoint
exportUrl: string // Export Excel endpoint
}
assetType?: string // Asset type identifier (e.g. 'DOMAIN'), enables change history drawer
statusType?: 'asset' | 'user' // Status change mapping rule
stats?: (rows: any[], total: number) => StatItem[] // Top stats panel calculation callback
onEditOpened?: (id: string, formModel: Record<string, any>) => Promise<void> // Hook when edit modal opens
customRules?: Record<string, FormRule | FormRule[]> // Custom form validation rules
customRenderers?: Record<string, (value: any, onUpdate: (v: any) => void) => any> // Custom form field renderers
transformPayload?: (payload: Record<string, any>) => Record<string, any> // Payload transform interceptor before submit
batchAddConfig?: { // Enable textarea batch add (e.g. enter multiple domains at once)
identifier: string // Batch add core field (e.g. 'domains')
label: string // Input box Label
placeholder?: string
}
hideAdd?: boolean // Hide "Add" button
hideEdit?: boolean // Hide inline "Edit" button
skipProjectFilter?: boolean // Skip project context isolation filter
refreshOnSave?: boolean // Refresh project & asset options cache after save
defaultHiddenColumns?: string[] // Default hidden table columns
}>()Path Alias
The frontend path alias @ maps to src/ (configured in vite.config.ts and tsconfig.app.json).
Key Data Structure Configuration
1. Form Field Definition (FormField)
FormField declares how form items are rendered in the modal:
export interface FormField {
key: string // Form data binding key
label: string // Form item label
type?: FieldType // Field input type (see below)
placeholder?: string // Input hint
options?: any[] | { value: any[] } // Dropdown data source (supports ref dynamic linkage)
multiple?: boolean // Allow multi-select in dropdown
required?: boolean // Is required field
span?: number // Grid column span (default 24, full row)
disabled?: boolean // Disable input
filterable?: boolean // Allow search/filter in dropdown
labelField?: string // Custom dropdown option label field name
keyField?: string // Custom dropdown option value field name
childrenField?: string // Tree dropdown children field name
min?: number // Minimum constraint (for number or string length)
max?: number // Maximum constraint
extra?: string // Gray hint text below form item
}Supported FieldType:
input: Standard single-line text inputtextarea: Multi-line text inputnumber: Number stepperselect: Single/multi-select dropdowntree-select: Tree selector (for department selection etc.)color: Color pickerdatetime: Date-time pickercheckbox: Checkboxswitch: Switch toggleradio: Radio button
Slots Extension Mechanism
ResourceCrudPage provides multiple scoped slots, allowing developers to render custom content in specific parts of the page without modifying the component source code:
<!-- 1. header-actions: Top action bar slot (after "Add", "Delete" buttons) -->
<template #header-actions>
<n-button type="info" @click="handleBatchExport">Custom Batch Export</n-button>
</template>
<!-- 2. search-bar: Custom search area slot (overrides default simple search box) -->
<template #search-bar="{ keyword, onSearch }">
<custom-filter-panel v-model:keyword="keyword" @search="onSearch" />
</template>
<!-- 3. stats: Top data summary slot (overrides SummaryStatsGrid) -->
<template #stats>
<custom-dashboard-banner />
</template>
<!-- 4. form-item-{key}: Custom form item rendering (when FormField type is insufficient, override rendering for specified key) -->
<template #form-item-password="{ model, field }">
<n-input v-model:value="model.password" type="password" show-password-on="click" />
</template>Integrated Composables
The component internally depends on and integrates deeply with the following Vue Composables:
useColumnPreferences(located atsrc/composables/useColumnPreferences.ts)- Function: Manage table column visibility and width ordering.
- Mechanism: After each user click on "Column Settings" to toggle visibility or reorder columns, the state is automatically synced to LocalStorage (Key format:
testnet:table-columns:{endpoint}), achieving persistent personalized configuration.
useAssetOptions(located atsrc/composables/useAssetOptions.ts)- Function: Cache and manage global asset-related option data (e.g. domain lists, company lists, etc. for cross-page shared dropdown options).
Typical Integration Example (DomainList.vue)
Below shows how to introduce and use the component in an actual page to configure a domain asset management interface:
<template>
<ResourceCrudPage
title="Domain Management"
endpoint="/api/v1/asset/domain"
asset-type="DOMAIN"
:columns="columns"
:form-fields="formFields"
:initial-values="initialValues"
:import-config="importConfig"
/>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { NTag } from 'naive-ui'
import ResourceCrudPage from '@/components/ResourceCrudPage.vue'
import { useAssetOptions } from '@/composables/useAssetOptions'
const { companyOptions } = useAssetOptions()
const columns = [
{ title: 'Domain', key: 'domainName', sorter: 'default' },
{
title: 'Company',
key: 'companyName',
render: (row: any) => h(NTag, { type: 'info' }, { default: () => row.companyName || 'N/A' })
},
{ title: 'Created', key: 'createTime', width: 180 }
]
const formFields = [
{ key: 'domainName', label: 'Domain', type: 'input', required: true },
{
key: 'companyId',
label: 'Company',
type: 'select',
options: companyOptions,
required: true
},
{ key: 'description', label: 'Notes', type: 'textarea' }
]
const initialValues = {
domainName: '',
companyId: null,
description: ''
}
const importConfig = {
typeName: 'domain',
importUrl: '/api/v1/asset/domain/import',
exportUrl: '/api/v1/asset/domain/export'
}
</script>Component Size
ResourceCrudPage.vue is approximately 1332 LOC, the most critical reusable component in the frontend. Modifications must be done carefully to ensure all dependent pages are not affected.