
业务实现 1API 注册管理模块脚手架、布局、通用组件都就位了现在该上战场了。第一个业务模块——API 注册管理是整个项目的列表页范式样板间。把这一篇吃透后面两个业务模块模型汇聚、模型标准发布都是它的变体。一、业务理解API 注册管理是什么管网模型工具是 AI 模型生命周期管理后台。模型要被调用得先有 API。API 注册管理就是把后端已经写好的 API 登记到平台里供模型统一调用。打个比方平台是一个调度中心API 是可调用的能力清单。注册管理就是维护这份清单——谁提供了什么能力、怎么调用、现在能不能用。核心字段字段说明示例名称API 的展示名管网泄漏检测接口协议通信协议HTTP / HTTPS / gRPCURL接口地址https://api.pipe.com/leak-detect请求方式HTTP 方法GET / POST / PUT状态启用 / 禁用启用操作每个 API 有 5 个操作新增、编辑、删除、详情、调试。其中调试是本项目最有特色的功能——直接在平台里发请求看响应不用开 Postman。二、列表页实现用 AppTable 一路平推列表页是 API 注册管理的核心。有了第 7 篇封装的 AppTable整个列表页的 template 只有 20 行!-- views/api-manage/ApiRegistry.vue -- template div classpage-card AppBreadcrumb :items[{ title: API 注册管理 }] / !-- 筛选区 -- div classfilter-bar el-select v-modelfilters.protocol placeholder协议 clearable stylewidth: 140px el-option labelHTTP valueHTTP / el-option labelHTTPS valueHTTPS / el-option labelgRPC valuegRPC / /el-select el-select v-modelfilters.status placeholder状态 clearable stylewidth: 140px el-option label启用 valueenabled / el-option label禁用 valuedisabled / /el-select AppSearchInput v-modelfilters.keyword placeholder搜索 API 名称 searchonSearch / el-button typeprimary :iconPlus clickopenCreate新增 API/el-button /div !-- 表格 -- AppTable :datatableData :columnscolumns :loadingloading :totaltotal v-model:pagepage v-model:selectionselectedRows page-changefetchData template #status{ row } StatusTag :statusrow.status / /template template #action{ row } el-button typeprimary link clickgoDetail(row)详情/el-button el-button typeprimary link clickopenEdit(row)编辑/el-button el-button typedanger link clickdel(row)删除/el-button el-button typeprimary link clickgoDebug(row)调试/el-button /template /AppTable /div /template2.1 columns 配置columns数组告诉 AppTable 表格有哪些列、每列怎么渲染constcolumns[{prop:name,label:API 名称,width:200,align:left},{prop:protocol,label:协议,width:100,align:center},{prop:url,label:接口地址,minWidth:280,align:left},{prop:method,label:请求方式,width:100,align:center},{prop:status,label:状态,width:100,align:center,slot:status},{prop:createTime,label:创建时间,width:180,align:center}]注意status列配了slot: status——意思是这一列不用文本渲染用名为status的 slot。在 template 里我们写了template #status{ row }来渲染StatusTag组件。2.2 数据获取import{ref,reactive,onMounted}fromvueimport{getApiList}from/api/apiManageconsttableDataref([])consttotalref(0)constloadingref(false)constpageref(1)constpageSize30constfiltersreactive({protocol:,status:,keyword:})asyncfunctionfetchData(){loading.valuetruetry{constresawaitgetApiList({page:page.value,pageSize,...filters})tableData.valueres.data.list total.valueres.data.total}finally{loading.valuefalse}}onMounted(fetchData)fetchData是列表页的心脏——筛选条件变了、翻页了、搜索了都调它重新拉数据。它只关心拿数据不关心数据怎么展示——展示是 AppTable 的事。三、筛选 搜索逻辑多条件组合3.1 多条件组合筛选协议下拉 状态下拉 搜索框三个条件可以任意组合。实现方式很简单——把它们都放进filters对象调fetchData时一起传给后端MockfunctiononFilterChange(){page.value1// 筛选后回到第一页fetchData()}注意page.value 1——筛选后必须重置页码否则可能出现筛选后第 3 页没数据的尴尬。3.2 防抖搜索搜索框如果每敲一个字就发一次请求用户快速输入管网泄漏四个字会发 4 次请求。用 500ms 防抖等用户停下来再发import{debounce}from/utils/debounceconstonSearchdebounce((){page.value1fetchData()},500)debounce是一个 10 行的小工具函数// utils/debounce.jsexportfunctiondebounce(fn,delay300){lettimernullreturn(...args){clearTimeout(timer)timersetTimeout(()fn(...args),delay)}}3.3 筛选状态同步到 URL query这是一个加分项——把筛选条件写进 URL刷新页面后筛选条件还在。实现用useRouteuseRouterimport{useRoute,useRouter}fromvue-routerconstrouteuseRoute()constrouteruseRouter()// 初始化时从 URL 读取筛选条件onMounted((){const{protocol,status,keyword,page:p}route.queryif(protocol)filters.protocolprotocolif(status)filters.statusstatusif(keyword)filters.keywordkeywordif(p)page.valueNumber(p)fetchData()})// 筛选变化时写回 URLwatch(filters,(){router.replace({query:{...filters,page:page.value}})},{deep:true})原型阶段不做这个也完全 OK——但做了之后演示效果会好很多客户刷新页面数据不丢。四、复选框 批量操作跨页选择是难点4.1 选中行的获取AppTable 已经把selection-change事件封装好了父组件通过v-model:selection拿到选中行constselectedRowsref([])// 批量删除functionbatchDelete(){if(selectedRows.value.length0){ElMessage.warning(请先选择要删除的 API)return}ElMessageBox.confirm(确认删除选中的${selectedRows.value.length}个 API,批量删除,{type:warning}).then((){// 调用删除接口ElMessage.success(删除成功)fetchData()})}4.2 跨页保持选择第 7 篇提到AppTable 的复选框列设了:reserve-selectiontrue。这保证了用户在第 1 页选了 3 条、翻到第 2 页选了 2 条selectedRows里是 5 条而不是 2 条。但有个前提——el-table必须知道哪行是同一行靠row-key属性。AppTable 默认rowKey: id所以每条数据必须有唯一的id字段。Mock 数据里id生成的 id 正好满足。4.3 全选/反选/单选这些 Element Plus 表格自带不需要额外写代码。AppTable 只是把selection-change事件透传出来。五、行操作四个按钮各司其职行操作列的四个按钮对应四种交互操作行为实现详情跳转到详情页router.push(/api-manage/detail/ row.id)编辑打开编辑弹窗openEdit(row)删除带确认后删除ElMessageBox.confirm 删除接口调试跳转到运行调试 tabrouter.push(/api-manage/detail/ row.id ?tabdebug)import{useRouter}fromvue-routerconstrouteruseRouter()functiongoDetail(row){router.push(/api-manage/detail/${row.id})}functiongoDebug(row){router.push(/api-manage/detail/${row.id}?tabdebug)}functiondel(row){ElMessageBox.confirm(确认删除「${row.name}」,删除确认,{type:warning}).then(async(){awaitdeleteApi(row.id)ElMessage.success(删除成功)fetchData()}).catch((){})}删除操作必须带ElMessageBox.confirm二次确认——这是后台系统的铁律。误删一条生产数据后果可能很严重。六、详情页双 Tab 设计详情页是 API 注册管理最有特色的部分——它有两个 Tab基本信息 Tab只读展示 API 的所有字段运行调试 TabJSON 编辑器 Send 按钮 响应展示6.1 路由与页面结构// router 配置{path:api-manage/detail/:id,name:ApiDetail,component:()import(/views/api-manage/ApiDetail.vue),meta:{title:API 详情,module:/api-manage/registry,hidden:true}}!-- views/api-manage/ApiDetail.vue -- template div classpage-card el-button :iconArrowLeft clickgoBack返回/el-button AppBreadcrumb :items[{ title: API 注册管理, path: /api-manage/registry }, { title: API 详情 }] / el-tabs v-modelactiveTab tab-changeonTabChange el-tab-pane label基本信息 nameinfo / el-tab-pane label运行调试 namedebug / /el-tabs div v-showactiveTab info !-- 基本信息只读展示 -- el-descriptions :column2 border el-descriptions-item labelAPI 名称{{ detail.name }}/el-descriptions-item el-descriptions-item label协议{{ detail.protocol }}/el-descriptions-item el-descriptions-item label接口地址{{ detail.url }}/el-descriptions-item el-descriptions-item label请求方式{{ detail.method }}/el-descriptions-item el-descriptions-item label状态 StatusTag :statusdetail.status / /el-descriptions-item el-descriptions-item label描述 :span2{{ detail.description }}/el-descriptions-item /el-descriptions /div div v-showactiveTab debug !-- 运行调试 -- div classdebug-area div classdebug-request div classdebug-toolbar el-select v-modeldebugMethod stylewidth: 120px el-option labelGET valueGET / el-option labelPOST valuePOST / /el-select el-input v-modeldebugUrl readonly styleflex: 1 / el-button typeprimary :loadingdebugging clicksendRequestSend/el-button /div el-input v-modeldebugBody typetextarea :rows10 placeholder请求体JSON / /div div classdebug-response div classdebug-response-title响应/div pre{{ debugResult }}/pre /div /div /div /div /template6.2 运行调试的实现调试 Tab 的核心是一个假请求——因为后端还没好我们用 Mock 模拟响应import{ref,onMounted}fromvueimport{useRoute}fromvue-routerimport{getApiDetail}from/api/apiManageconstrouteuseRoute()constdetailref({})constactiveTabref(info)constdebugMethodref(GET)constdebugUrlref()constdebugBodyref({\n param: value\n})constdebugResultref()constdebuggingref(false)onMounted(async(){constresawaitgetApiDetail(route.params.id)detail.valueres.data debugUrl.valueres.data.url// 从 URL query 读取初始 tab?tabdebugif(route.query.tab)activeTab.valueroute.query.tab})asyncfunctionsendRequest(){debugging.valuetruetry{// 模拟请求延迟awaitnewPromise(rsetTimeout(r,800))debugResult.valueJSON.stringify({code:200,message:success,{leakProbability:0.87,location:[120.15,30.28],timestamp:newDate().toISOString()}},null,2)}finally{debugging.valuefalse}}调试 Tab 让原型活了起来——客户在演示现场点一下 Send800ms 后看到一段真实的 JSON 响应比这里应该会返回数据有说服力得多。6.3 返回按钮 面包屑返回按钮用router.back()或router.push(/api-manage/registry)。面包屑的第二项带了path点击能跳回列表页。七、注册弹窗新增/编辑复用同一个新增和编辑的表单字段完全一样——区别只是编辑时表单预填了数据。所以用一个弹窗组件通过mode区分!-- 在 ApiRegistry.vue 中 -- AppDialog v-modeldialogVisible :titledialogMode create ? 新增 API : 编辑 API :loadingsubmitting confirmhandleSubmit el-form refformRef :modelform :rulesrules label-width100px el-form-item labelAPI 名称 propname el-input v-modelform.name placeholder请输入 API 名称 / /el-form-item el-form-item label协议 propprotocol el-select v-modelform.protocol stylewidth: 100% el-option labelHTTP valueHTTP / el-option labelHTTPS valueHTTPS / el-option labelgRPC valuegRPC / /el-select /el-form-item el-form-item label接口地址 propurl el-input v-modelform.url placeholderhttps://... / /el-form-item el-form-item label请求方式 propmethod el-select v-modelform.method stylewidth: 100% el-option labelGET valueGET / el-option labelPOST valuePOST / el-option labelPUT valuePUT / /el-select /el-form-item el-form-item label描述 el-input v-modelform.description typetextarea :rows3 / /el-form-item /el-form /AppDialogconstdialogVisibleref(false)constdialogModeref(create)constformRefref()constsubmittingref(false)constformreactive({id:null,name:,protocol:HTTP,url:,method:GET,description:})construles{name:[{required:true,message:请输入 API 名称,trigger:blur}],protocol:[{required:true,message:请选择协议,trigger:change}],url:[{required:true,message:请输入接口地址,trigger:blur}],method:[{required:true,message:请选择请求方式,trigger:change}]}functionopenCreate(){dialogMode.valuecreateresetForm()dialogVisible.valuetrue}functionopenEdit(row){dialogMode.valueeditObject.assign(form,row)dialogVisible.valuetrue}functionresetForm(){form.idnullform.nameform.protocolHTTPform.urlform.methodGETform.description}asyncfunctionhandleSubmit(){awaitformRef.value.validate()submitting.valuetruetry{if(dialogMode.valuecreate){awaitcreateApi(form)ElMessage.success(新增成功)}else{awaitupdateApi(form)ElMessage.success(编辑成功)}dialogVisible.valuefalsefetchData()}finally{submitting.valuefalse}}7.1 必填校验rules对象定义了每个字段的校验规则。formRef.value.validate()在提交前触发校验不通过则不会发请求。这是 Element Plus 表单的标准用法但值得强调——所有用户输入都必须校验原型也不例外。客户演示时填了个空值点提交如果没校验直接报错比提示请填写尴尬得多。7.2 提交 loading 成功提示提交时submitting trueAppDialog 的确认按钮自动进入 loading 态第 7 篇封装的:loadingprop。成功后ElMessage.success弹提示关闭弹窗重新拉列表。八、Mock 数据30 条真实感数据API 注册管理需要 30 条 Mock 数据。第 11 篇会详细讲 Mock 策略这里先给一个预览——数据要有血有肉名称协议URL状态管网泄漏检测接口HTTPShttps://api.pipe.com/leak-detect启用压力监测服务HTTPhttp://10.0.1.23:8080/pressure启用流量分析 APIgRPCgrpc://pipe-grpc/flow-analysis禁用水质预测模型HTTPShttps://api.pipe.com/water-quality启用…………每条数据的名称、URL、状态都有差异时间字段分布在最近 3 个月——这样列表页看起来像真实生产数据而不是测试数据 1/2/3。九、小结列表页范式API 注册管理模块做完我们沉淀出一个列表页范式筛选区下拉 搜索 ↓ 表格复选框 序号 字段列 操作列 ↓ 分页器30 条/页 ↓ 行操作详情/编辑/删除/调试 ↓ 弹窗新增/编辑复用 ↓ 详情页双 Tab基本信息 运行调试这个范式不是 API 注册管理独有的——模型汇聚、模型标准发布都是它的变体。区别只在于表格有哪些列“弹窗有哪些字段”“详情页有几个 Tab”。范式 不变的结构 可变的内容。把结构固化成通用组件AppTable、AppDialog把内容留给每个业务页面填——这就是企业级后台开发效率的秘诀。上一篇07 - 通用组件分页器、表格、对话框的封装下一篇预告列表页范式跑通了下一个模块模型汇聚是项目最复杂的——详情页有 3 个 TabTab 切换还要同步 URL。怎么让多 Tab 详情页不丢数据