ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

v3-admin-vite CRUD 页面生成实战指南:基于 Skill 规范与源码的完整实现

v3-admin-vite CRUD 页面生成实战指南:基于 Skill 规范与源码的完整实现 前端【免费下载链接】v3-admin-vite☀️ AI-friendly Vue3 admin template | Vue Admin | Vue Template | Vue3 Admin | Vue3 Template | Vue 后台 | Vue 模板 | Vue3 后台 | Vue3 模板项目地址https://gitcode.com/gh_mirrors/v3a/v3-admin-vite点击查看免费下载导读本文以 v3-admin-vite 仓库中.agents/skills/v3-create-crud/SKILL.md为骨架系统讲解如何在 Vue 3 Element Plus 技术栈下生成一个符合项目规范的增删改查CRUD后台页面。你将掌握从输入要求模块名 字段信息到生成文件index.vueapis/index.tsapis/type.ts的完整流程理解弹窗宽度、工具栏按钮、删除确认文案、搜索参数传递等设计决策并深入usePagination、request封装等底层实现原理最终产出一份可复制、可运行、类型安全的 CRUD 页面。该 Skill 由 Glittering Ma pany 编写版本2026.06.13本文所有代码与配置均取自当前仓库可直接对照源码验证。Skill 是什么项目内置的CRUD 生成器约定SKILL.md的 frontmatter 中description字段明确了触发条件当用户提到创建管理页面、创建列表页、创建表格页等场景时触发。即使没有明确说 CRUD只要意图是创建带表格和表单操作的后台页面就应该使用此 Skill。该 Skill 定义的是默认 CRUD 模式当用户实际需求与 Skill 约定冲突时以用户需求为准灵活调整。整个流程分三步用户提供模块名称如product、order和字段列表字段名、中文标签、类型、是否必填、是否搜索条件、是否需要自定义渲染信息不完整时主动询问补全在src/pages/模块路径/下生成三个文件index.vue— 页面主文件apis/index.ts— 接口文件apis/type.ts— 类型定义仓库中已有完整范例可以对照src/common/apis/tables/index.ts与src/common/apis/tables/type.ts就是按此规范编写的表管理模块接口与类型src/pages/demo/vxe-table/index.vue则展示了基于 vxe-table 的另一种表格实现。本文将以product商品模块为例贯穿全篇。输入要求开始前需要哪些信息用户需提供两部分输入模块名称— 用于目录名和命名。如product、order。字段列表— 每个字段需明确维度说明字段名英文camelCase如productName中文标签如 商品名称类型string / number / boolean / enum / date 等是否必填决定表单验证规则是否作为搜索条件决定是否出现在搜索区域是否需要自定义渲染如 tag 状态展示如果用户信息不完整Skill 会主动询问补全后再生成。以product为例一个合理的输入可能是模块名称product 字段列表 - productName商品名称string必填搜索条件 - price价格number必填 - status状态boolean必填自定义渲染为 tag - category分类enum必填搜索条件自定义渲染 - remark备注string非必填设计决策指引不要机械套用生成代码时需要在以下几个维度做判断弹窗宽度— 默认用30%有复杂布局用50%或更大的比例。字段少、结构简单的表单用 30%包含多个分组或复杂布局时扩大。工具栏按钮— 按需组合不必全部包含新增 几乎总是需要的批量删除 仅在有批量操作需求时添加对应表格 selection 列下载/导出 仅在有导出需求时添加刷新当前页 推荐保留方便调试和手动刷新删除确认文案— 选择对用户最有辨识度的字段作为确认提示如用户名、订单编号、商品名称而不是 id。这样用户看到确认弹窗能立刻识别要删除的对象避免误删。搜索参数传递— 保持简单避免类型体操字段少2-3 个时可以逐个列出字段多时用...searchData展开更简洁核心原则不要出现as any除非明确要求表单字段 vs 表格列— 不是所有字段都同时出现在两处仅创建时需要的字段如密码表单中有表格中无系统生成的字段如创建时间表格中有表单中无用v-ifformData.id undefined控制仅新增时显示的字段这一新增与编辑共用一个弹窗的设计通过id是否存在区分状态共享验证规则和提交逻辑减少重复代码。页面结构规范三区块布局页面整体包裹在div classapp-container中由搜索区域、表格区域、弹窗三部分组成。1. 搜索区域el-cardv-loadingloadingshadowneverclasssearch-wrapper内含el-formrefsearchFormRef:inlinetrue:modelsearchData每个搜索字段设置propresetFields依赖它和label末尾放查询按钮:iconSearchtypeprimary和重置按钮:iconRefreshprop属性是重置功能的基石resetFields通过它找到对应字段并清空。2. 表格区域el-cardv-loadingloadingshadownever工具栏div.toolbar-wrapper分左右两侧左侧文字按钮新增、批量删除等右侧圆形图标按钮 el-tooltip下载、刷新当前页等表格div.table-wrapperel-table :datatableData需要批量操作时加typeselection首列数据列proplabelaligncenter操作列fixedright 适当widthaligncenter内含修改/删除按钮text bg sizesmall操作列按钮将scope.row传给强类型 handler 时使用scope.row as XxxData明确行类型避免 Element Plus 默认DefaultRow导致vue-tsc报错不要使用as any分页div.pager-wrapperel-pagination3. 弹窗el-dialogv-modeldialogVisible 通过formData.id undefined判断新增/修改标题 closedresetForm使用closed而非close确保关闭动画结束后再重置避免用户看到表单内容闪烁表单refformRef:modelformData:rulesformRuleslabel-widthautofooter取消按钮 确认按钮typeprimary:loadingloading代码组织规范script setup 分区注释使用script langts setupdefineOptions({ name: PascalCase 模块名 })。顶部声明共享的loadingref和usePagination解构分页请求统一通过callback、resetCurrentPage、watchPagination组织。逻辑按增删改查分区用// #region和// #endregion标记仓库中src/pages/demo/vxe-table/index.vue也采用了// #region vxe-grid的分区写法。下面逐个区块给出可运行的完整代码。增Createconst DEFAULT_FORM_DATA: CreateOrUpdateProductRequestData { id: undefined, // ... 所有表单字段的初始值 } const dialogVisible refboolean(false) const formRef useTemplateRef(formRef) const formData refCreateOrUpdateProductRequestData(cloneDeep(DEFAULT_FORM_DATA)) const formRules: FormRulesCreateOrUpdateProductRequestData { /* 验证规则 */ } function handleCreateOrUpdate() { formRef.value?.validate((valid) { if (!valid) { ElMessage.error(表单校验不通过); return } loading.value true const api formData.value.id undefined ? createProductApi : updateProductApi api(formData.value).then(() { ElMessage.success(操作成功) dialogVisible.value false }).finally(() { loading.value false getTableData() }) }) } function resetForm() { formRef.value?.clearValidate() formData.value cloneDeep(DEFAULT_FORM_DATA) }为什么新增和编辑共用一个弹窗减少重复代码通过id是否存在区分状态共享验证规则和提交逻辑。handleCreateOrUpdate中的const api formData.value.id undefined ? createProductApi : updateProductApi就是这一模式的落地。删Deletefunction handleDelete(row: ProductData) { ElMessageBox.confirm(正在删除商品${row.productName}确认删除, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { loading.value true deleteProductApi(row.id).then(() { ElMessage.success(删除成功) }).finally(() { loading.value false getTableData() }) }) }删除确认文案选取了productName这个对用户最有辨识度的字段而不是id。ElMessageBox.confirm返回 Promise.then中执行删除。改Updatefunction handleUpdate(row: ProductData) { dialogVisible.value true formData.value cloneDeep(row) }为什么用cloneDeep避免编辑时直接修改表格行数据引用类型用户取消编辑时表格数据不会被污染。这是 lodash-es 的cloneDeep与新增时cloneDeep(DEFAULT_FORM_DATA)的做法一致。查Readconst tableData refProductData[]([]) const searchFormRef useTemplateRef(searchFormRef) const searchData reactive({ /* 搜索字段初始值为空字符串 */ }) function getTableData() { loading.value true getProductApi({ currentPage: paginationData.currentPage, size: paginationData.pageSize, ...searchData }).then(({ data }) { paginationData.total data.total tableData.value data.list }).catch(() { tableData.value [] }).finally(() { loading.value false }) } function handleSearch() { resetCurrentPage() } function resetSearch() { searchFormRef.value?.resetFields() handleSearch() }handleSearch的逻辑直接调用resetCurrentPage。resetCurrentPage会在当前已是第 1 页时直接请求否则重置页码并由分页监听触发请求避免重复调用。resetSearch先resetFields清空搜索条件再触发查询。分页监听watchPagination()为什么用watchPagination而不是onMounted让分页变化和初始加载共享同一个入口数据获取逻辑只写一处。这在下方usePagination源码中可以看到原因watch默认immediate: true组件挂载即触发一次回调完成首屏加载。usePagination 用法与底层原理import { usePagination } from /composables/usePagination const { paginationData, resetCurrentPage, watchPagination } usePagination({ callback: getTableData })paginationData是reactive对象包含total、currentPage、pageSizes、pageSize、layout。模板中使用v-model双向绑定页码和每页条数el-pagination v-model:current-pagepaginationData.currentPage v-model:page-sizepaginationData.pageSize :page-sizespaginationData.pageSizes :totalpaginationData.total :layoutpaginationData.layout background /源码级解析usePagination.ts默认分页参数定义在DEFAULT_PAGINATION_DATAconst DEFAULT_PAGINATION_DATA { currentPage: 1, pageSize: 10, pageSizes: [10, 20, 50], total: 0, layout: total, sizes, prev, pager, next, jumper }usePagination接收callback与初始分页参数内部用reactive({ ...DEFAULT_PAGINATION_DATA, ...initPaginationData })合并resetCurrentPage当前已是第 1 页时直接执行回调否则重置为第 1 页paginationData.currentPage 1 ? callback?.() : (paginationData.currentPage 1)——这正是上面避免重复调用的机制来源watchPaginationwatch([currentPage, pageSize], () callback?.(), options)默认immediate: true所以首屏加载不需要onMounted监听器挂载后立即触发一次getTableData页码或每页条数变化时再次触发同时暴露handleCurrentChange、handleSizeChange两个方法供不使用v-model的场景手动绑定事件。表格列的自定义渲染el-tag 视觉分层对于需要视觉区分的字段状态、角色、类型等使用el-tag渲染。思路是突出重要/异常值其余用温和颜色兜底。!-- boolean二元对立可以用 success / danger 对比 -- el-table-column propstatus label状态 aligncenter template #defaultscope el-tag v-ifscope.row.status typesuccess effectplain disable-transitions启用/el-tag el-tag v-else typedanger effectplain disable-transitions禁用/el-tag /template /el-table-column !-- enum值高亮 -- el-table-column proproles label角色 aligncenter template #defaultscope el-tag v-ifscope.row.roles admin typeprimary effectplain disable-transitionsadmin/el-tag el-tag v-else typewarning effectplain disable-transitions{{ scope.row.roles }}/el-tag /template /el-table-column当 enum 值需要明确区分时逐个用v-if / v-else-if列出最后用v-else兜底。effectplain使标签底色更柔和disable-transitions去除切换动画适用于高频数据刷新场景。表单字段组件选择类型到组件的映射根据字段语义选择组件字段类型组件备注string短文本el-inputstring长文本el-input typetextarea描述、备注等string密码el-input typepasswordnumberel-input-number:min/:max根据业务约束设定booleanel-switchenumel-selectel-option搜索区域加clearabledateel-date-picker typedate value-formatYYYY-MM-DDdatetimeel-date-picker typedatetime value-formatYYYY-MM-DD HH:mm:ss验证规则的trigger也要与组件类型匹配输入型组件input、textarea用blur选择型组件select、date-picker、switch用change。这是因为输入类组件在失焦时才适合校验完整输入而选择类组件一旦 change 就已完成取值。接口规范apis/index.ts 的四函数模式接口文件使用命名空间导入类型import type * as Xxx from ./type。导出四个函数注释用增删改查标记createXxxApi— POSTdeleteXxxApi— DELETE参数为 idupdateXxxApi— PUTgetXxxApi— GET参数为分页 搜索条件使用import { request } from /http/axios发起请求。查询接口需指定泛型requestXxx.XxxResponseData({ ... })。仓库中 tables 模块接口 就是标准范例import type * as Tables from ./type import { request } from /http/axios /** 增 */ export function createTableDataApi(data: Tables.CreateOrUpdateTableRequestData) { return request({ url: tables, method: post, data }) } /** 删 */ export function deleteTableDataApi(id: number) { return request({ url: tables/${id}, method: delete }) } /** 改 */ export function updateTableDataApi(data: Tables.CreateOrUpdateTableRequestData) { return request({ url: tables, method: put, data }) } /** 查 */ export function getTableDataApi(params: Tables.TableRequestData) { return requestTables.TableResponseData({ url: tables, method: get, params }) }request 封装底层原理request来自 src/http/axios.ts其内部机制值得了解响应拦截器读取apiData.codecode 0表示无业务错误并直接返回apiDatacode 401触发useUserStore().logout()其他 code 通过ElMessage.error(apiData.message || Error)提示并 reject。二进制响应blob / arraybuffer直接透传。HTTP 错误映射按response.status映射为中文提示400 请求错误、403 拒绝访问、404 请求地址出错、500 服务器内部错误、504 网关超时等并统一ElMessage.error。默认配置baseURL取import.meta.env.VITE_BASE_URL请求头自动携带Authorization: Bearer tokentoken 来自src/common/utils/local-storage的getToken()timeout: 5000withCredentials: false。最终merge(defaultConfig, config)合并后发出请求。配合 types/api.d.ts 中全局声明的响应格式ApiResponseDataT { code: number; data: T; message: string }查询接口的泛型返回值即XxxResponseData。类型规范apis/type.ts 的四种类型类型文件导出ApiResponseData是全局类型无需导入CreateOrUpdateXxxRequestData— 表单提交数据id?: number 各表单字段XxxRequestData— 列表查询参数currentPage: numbersize: number 搜索字段用?:可选标记XxxData— 表格行数据id: number 各展示字段XxxResponseData—ApiResponseData{ list: XxxData[]; total: number }仓库 tables 类型文件 的对照实现export interface CreateOrUpdateTableRequestData { id?: number username: string password?: string } export interface TableRequestData { /** 当前页码 */ currentPage: number /** 查询条数 */ size: number /** 查询参数用户名 */ username?: string /** 查询参数手机号 */ phone?: string } export interface TableData { createTime: string email: string id: number phone: string roles: string status: boolean username: string } export type TableResponseData ApiResponseData{ list: TableData[] total: number }注意三点CreateOrUpdateTableRequestData中password?: string是仅创建时需要的字段在TableData中不存在印证了表单字段 vs 表格列的决策TableData包含createTime这类系统生成的字段但表单中无此输入印证系统生成字段表格有表单无搜索字段在XxxRequestData中默认用string类型除非明确指定类型因为搜索框传递的默认是字符串值需直接保持类型正常不要随意使用类型断言。页面中使用具名导入import type { CreateOrUpdateXxxRequestData, XxxData } from ./apis/type。导入规范按职责分组// 类型导入 import type { CreateOrUpdateXxxRequestData, XxxData } from ./apis/type import type { FormRules } from element-plus // API 导入 import { createXxxApi, deleteXxxApi, getXxxApi, updateXxxApi } from ./apis // Composable 导入 import { usePagination } from /composables/usePagination // 图标导入按需只导入实际使用的 import { CirclePlus, Delete, Download, Refresh, RefreshRight, Search } from element-plus/icons-vue // 工具导入 import { cloneDeep } from lodash-es以下为自动导入无需手动引入ElMessage、ElMessageBox、ref、reactive、useTemplateRef。路径别名说明指向src目录指向src/common通用目录二者在 vite.config.ts 的resolve.alias中定义alias: { // 符号指向 src 目录 : resolve(__dirname, src), // 符号指向 src/common 通用目录 : resolve(__dirname, src/common) }样式规范scoped SCSS 四件套使用style langscss scoped以下为推荐的基础样式.search-wrapper { margin-bottom: 20px; :deep(.el-card__body) { padding-bottom: 2px; } } .toolbar-wrapper { display: flex; justify-content: space-between; margin-bottom: 20px; } .table-wrapper { margin-bottom: 20px; } .pager-wrapper { display: flex; justify-content: flex-end; }search-wrapper中的:deep(.el-card__body)调整卡片内边距使搜索表单与卡片边界紧凑贴合pager-wrapper的flex-end让分页组件右对齐符合后台列表页的通用习惯。命名约定模块名驱动一切模块名为product时位置命名目录src/pages/.../product/组件 nameProductAPI 函数createProductApi/deleteProductApi/updateProductApi/getProductApi类型CreateOrUpdateProductRequestData/ProductRequestData/ProductData/ProductResponseData命名空间import type * as Product from ./typedefineOptions({ name: Product })的组件名配合keep-alive等场景使用同时ProductData类型在操作列 handler 中用于scope.row as ProductData的强类型断言。路由提示最后一步别忘了生成代码后提醒用户在路由配置中添加对应路由。仓库的 路由配置 使用VITE_ROUTER_HISTORY决定 hash 或 html5 模式dynamic: true表示开启了动态路由需要后端在用户详情接口返回 roles / permissions 字段配合判断加载。新增页面路由时在路由配置中加入path、component指向src/pages/.../product/index.vue、meta标题、图标、权限等若项目开启了动态路由需同步配置后端权限字段刷新页面或重新登录后侧边栏菜单即可看到新页面入口。总结一套可复用的 CRUD 生产流水线回顾整个 Skill 约定核心价值在于把 CRUD 页面的重复劳动抽象为一份带设计决策的规范三区块页面结构搜索卡片 表格卡片 弹窗保证视觉与交互一致性新增/编辑共用弹窗 cloneDeep减少重复代码并防止数据污染usePagination的callbackresetCurrentPagewatchPagination让分页、搜索、首屏加载共享同一数据获取入口强类型贯穿全程四种类型定义、命名空间导入、scope.row as XxxData让vue-tsc严格检查下也能零错误与request封装、ApiResponseData全局类型无缝衔接无需额外适配后端响应格式。按照本文流程从输入模块名与字段开始对照src/common/apis/tables/的接口与类型范例即可在src/pages/下快速产出符合 v3-admin-vite 项目规范的 CRUD 页面。赞分享前端【免费下载链接】v3-admin-vite☀️ AI-friendly Vue3 admin template | Vue Admin | Vue Template | Vue3 Admin | Vue3 Template | Vue 后台 | Vue 模板 | Vue3 后台 | Vue3 模板项目地址https://gitcode.com/gh_mirrors/v3a/v3-admin-vite点击查看免费下载相关推荐go-admin 代码生成器实战从数据表到 CRUD 界面的完整教程go admin 代码生成器实战从数据表到 CRUD 界面的完整教程 go admin 是一款基于 Gin Vue 的前后端分离权限管理系统脚手架内置后端认证鉴权v3-admin-vite数据导出实战Excel与PDF一键生成指南v3 admin vite数据导出实战Excel与PDF一键生成指南 还在为后台管理系统的数据导出功能头疼吗每次都要手动复制粘贴到Excel或者截图保存为前端SuperPlane 代码整洁之道基于 clean-code Skill 的工程化编码规范实战指南SuperPlane 代码整洁之道基于 clean code Skill 的工程化编码规范实战指南 本指南以 SuperPlane 仓库内置的 Clean C上一篇终极指南ModEngine2如何让你的魂系游戏模组体验焕然一新下一篇解密百度网盘秒传技术从文件指纹到极速分享的探索之旅创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表