主题
角色管理与合同详情(一)
本页对应 角色管理与合同详情,共 8 段视频,约 1 小时 39 分。
学习目标
- 能够为合同详情配置路由,在查看操作中携带企业 ID 跳转,并调用详情接口渲染租赁合同表格。
- 能够拼接固定预览地址与合同文件地址,通过新窗口链接实现在线预览,并通过合同地址触发浏览器下载。
- 能够调用角色列表接口完成角色列表渲染,通过记录当前索引或 ID 与动态 class 实现 tab 点击激活切换。
- 能够根据 tab 激活状态动态切换 SVG 图标名称,正确渲染激活与未激活图标。
- 能够封装计费规则列表接口,完成 table 表格字段渲染,并配置分页器实现页码切换、请求参数更新与数据重新请求。
- 能够复述 Word 在线预览、业务状态驱动按钮控制及 Excel 导出前端主导与后端主导的核心实现流程。
知识点
路由与页面骨架:先把“能进来”这件事做稳
为什么需要:计费规则管理是行车管理下的子页面,如果路由没挂好,后面表格、接口、分页都无从验证。转写稿里也是先把静态结构复制到业务组件,再配置路由并手动访问地址测试跳转。
怎么想:父级路由用 Layout 承载后台框架,meta.title 写“行车管理”;子路由 path: 'rule' 不要加前导 /,最终访问路径是 /parking/rule。组件用懒加载 () => import('@/views/car/car-rule')。
怎么做:按笔记保留路由配置:
vue
{
path: '/parking',
component: Layout,
meta: { title: '行车管理', icon: 'el-icon-guide' },
children: [
{
path: 'rule',
component: () => import('@/views/car/car-rule'),
meta: { title: '计费规则管理' }
}]
},易错点:子路由 path 写成 /rule 会变成根路径;component 路径拼错时页面空白,先看控制台和 Network;路由配完先手动访问一次,确认能进页面再写表格。
顶部操作区与表格静态列:先让字段和接口对齐
为什么需要:静态结构不是“摆样子”,而是提前确定页面分区、按钮位置、表格列字段。转写稿强调先复制结构、再调接口渲染,避免边写样式边猜字段。
怎么想:页面分两块:.create-container 放“增加停车计费规则”“导出Excel”;.table 放 el-table。表格列分三类:序号列、普通字段列、需要插槽格式化的列(计费方式、操作)。ruleList 先给空数组占位,等接口返回 data.rows 再赋值。
怎么做:保留笔记模板与脚本。注意 chargeType 用 #default="scope" 是为了后续格式化;ruleNameView 是展示字段;操作列 fixed="right" width="120" 防止横向滚动时按钮丢失。
vue
<template>
<div class="rule-container">
<div class="create-container">
<el-button type="primary">增加停车计费规则</el-button>
<el-button>导出Excel</el-button>
</div>
<!-- 表格区域 -->
<div class="table">
<el-table :data="ruleList" style="width: 100%">
<el-table-column type="index" label="序号" />
<el-table-column label="计费规则编号" prop="ruleNumber" />
<el-table-column label="计费规则名称" prop="ruleName" />
<el-table-column label="免费时长(分钟)" prop="freeDuration" />
<el-table-column label="收费上线(元)" prop="chargeCeiling" />
<el-table-column label="计费方式">
<template #default="scope">
{{ scope.row.chargeType }}
</template>
</el-table-column>
<el-table-column label="计费规则" prop="ruleNameView"/>
<el-table-column label="操作" fixed="right" width="120">
<template #default="scope">
<el-button size="mini" type="text">编辑</el-button>
<el-button size="mini" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
name: 'Building',
data() {
return {
ruleList: [],
params: {
page: 1,
pageSize: 10
},
total: 0,
dialogVisible: false
}
}
}
</script>
<style lang="scss" scoped>
.rule-container {
padding: 20px;
background-color: #fff;
}
.search-container {
display: flex;
align-items: center;
border-bottom: 1px solid rgb(237, 237, 237, .9);
padding-bottom: 20px;
.search-label {
width: 100px;
}
.search-main {
width: 220px;
margin-right: 10px;
}
}
.create-container{
margin: 10px 0px;
}
.page-container{
padding:4px 0px;
text-align: right;
}
.form-container{
padding:0px 80px;
}
</style>易错点:笔记中“收费上线”按业务语义应为“收费上限”,落地时按产品文案确认,但 prop 仍要对准接口字段 chargeCeiling;type="index" 不需要 prop;插槽里是 scope.row,不要写成 scope;name: 'Building' 是复制遗留,建议按组件语义调整,但不影响运行。
数据与分页预留:静态结构要为联调服务
为什么需要:params.page/pageSize、total、dialogVisible 现在没用上,但它们是后续列表请求、分页、弹窗的接口位。转写稿中列表接口是常规 get 请求,参数 page、pageSize,返回里取 data.rows 赋给 ruleList,data.total 赋给 total。
怎么想:先写 getList 小方法,created 调用;接口成功后 this.ruleList = res.data.rows、this.total = res.data.total。分页器后续绑定 total、page-size="params.pageSize",在 current-change 里先覆盖 params.page,再重新 getList。
怎么做:分页区域可放在表格下方 .page-container;调试时打开 Network,确认请求参数确实是 page=2&pageSize=2 这类形态。想看 scope.row 字段时,临时写方法 console.log(scope.row),比模板里直接输出更清晰。
易错点:不要以代码里“写了参数”为准,要以 Network 实际 query 为准;数据不符合预期先查请求参数,转写稿明确说“期待的数据没回来,往往是要的参数不对”。分页 layout 需要总条数就加 total,需要切每页条数就加 sizes,不是所有结构默认都有。
先明确本节要封装什么
本节笔记的核心不是页面交互,而是把“获取规则列表”的请求收口到 API 文件里。笔记代码如下,保留原样:
javascript
import request from '@/utils/request'
/**
* 获取规则列表
* @param { page, pageSize} params
* @returns
*/
export function getRuleListAPI(params) {
return request({
url: 'parking/rule/list',
params
})
}为什么需要: 如果每个业务组件里都直接写 request({ url: 'parking/rule/list', params }),请求地址、参数名、导入路径会散落在页面中;一旦接口调整,就要到处改。封装成 getRuleListAPI 后,页面只关心“取规则列表”,不关心请求细节。
怎么想: 先判断这是常规 GET 列表请求,参数是分页参数 page、pageSize,因此 API 层只负责“地址 + 参数透传”,不做页面状态管理。
怎么做: 新建 API 文件,默认导入 request,再导出一个具名函数 getRuleListAPI(params),把 params 原样交给 request 的 params 字段。
易错点: 转写稿里专门调试过模块导入导出问题:request 若是默认导出,就必须默认导入,不能随意加花括号;路径也要检查到 @/utils/request,不要把 utils 拼错。接口路径 parking/rule/list 不要多空格,params 不要写成 param。
在业务组件里调用并拿数据
为什么需要: API 函数只是“请求定义”,真正取数据还要在业务组件里调用,并把返回结果放进响应式数据,表格才能渲染。
怎么想: 按转写稿中的流程:先导入 getRuleListAPI,再写 getRuleList 小方法,在方法里 await 接口,拿到 res.data.rows 赋给列表,拿到 res.data.total 赋给总数;组件挂载时调用一次。
怎么做: 业务组件中维护 ruleList、total、params: { page: 1, pageSize: 2 } 等状态;getRuleList 里调用 getRuleListAPI(params),成功后赋值;表格用 v-for 或表格组件的 data 绑定 ruleList,列上通过 prop 或作用域插槽取字段。
易错点: 不要只看代码“觉得传了参数”,要到浏览器 Network 面板确认请求 URL 上是否真的出现 page=1&pageSize=2。转写稿强调:如果期待的数据没回来,先检查请求参数是否要错;分页点击时要先把当前页 page 覆盖进请求参数,再重新请求。
用转写稿中的调试过程校正封装习惯
为什么需要: 封装接口不是写完就结束,必须验证“接口能通、参数正确、数据能渲染”。
怎么想: 转写稿视频6中先封装 getRuleListAPI,再在组件里导入调用;表格渲染时用作用域插槽拿 row,如果不知道 row 里有什么,可以临时写函数把 row 打印出来看结构,而不是猜字段。
怎么做: 先刷新页面看 Network 是否 200;再看响应里列表字段和总数字段;确认表格列绑定的字段与响应一致;分页切换时观察请求参数是否变化。
易错点: 常见错误包括:API 文件默认导入/按需导入写反、路径拼错、url 多空格、params 拼写错误、分页参数没传给接口、表格字段名猜错。转写稿还提到“以 Network 面板实际显示结果为准”,不要只看代码自我感觉正确。
本节小结
为什么需要: 把“规则列表”接口封装起来,是为后续表格渲染、分页、导出等操作提供稳定入口。
怎么想: API 层只做请求定义;组件层负责参数、状态和渲染;调试时用 Network 和打印响应字段确认。
怎么做: 保留 getRuleListAPI(params),在业务组件中导入并调用,按接口返回的 rows、total 渲染表格和分页。
易错点: 不编造字段,不跳过参数验证;接口地址、导入方式、参数名、分页参数是本小节最需要盯住的四类问题。
本节要做什么
这一节完成的是“计费规则”列表页最基础的一步:进入页面后调用规则列表接口,把返回的列表数据保存到响应式数据里,再交给 el-table 渲染。为什么需要先做这一步?因为列表页后续的新增、导出、分页、格式化展示,都建立在“数据能拿到、表格能渲染”这个基础上。怎么想?先看接口返回结构,再确定组件里用哪个响应式字段承接数组,最后把字段绑定到表格。怎么做?本节笔记的核心代码是:
vue
import { getRuleListAPI } from '@/apis/car'
mounted() {
this.getRuleList()
}
methods: {
// 获取规则列表
async getRuleList() {
const res = await getRuleListAPI(this.params)
this.ruleList = res.data.rows
}
}
<el-table :data="ruleList" style="width: 100%">易错点:不要一上来就在模板里写死数据;先确认接口是否调通、返回层级是否是 res.data.rows,再谈渲染。
先封装并导入列表接口
为什么需要:组件里直接写请求地址会让接口散落各处,后续改地址、加参数都不好维护。怎么想:计费规则列表是一个常规 GET 请求,通常带 page、pageSize 两个参数;接口方法统一放在 @/apis/car 这类 API 文件里。怎么做:在 API 文件中导出 getRuleListAPI,业务组件里按笔记写法命名导入。转写稿里也强调过:request 的导入导出方式要匹配,默认导出和按需导入不能混。易错点:路径拼写错误、导入方式不匹配、相对地址中多写空格,都会导致接口发不出去;调试时先看控制台报错,再看 Network。
在 mounted 中触发获取
为什么需要:列表页进入后就应该展示数据,不能等用户点按钮才请求。怎么想:这是页面初始化数据,放在 mounted 中调用一次即可。怎么做:在 mounted() 里执行 this.getRuleList()。易错点:不要把请求直接写在 data 或模板表达式里;getRuleList 是异步方法,调用时要保证 this 指向组件实例,按笔记中的 this.getRuleList() 写即可。
async 请求并赋值给 ruleList
为什么需要:接口返回的数据要变成组件可响应的数据,表格才会跟着更新。怎么想:先确认返回结构。转写稿中调试时看到列表在 res.data.rows 里,所以不能只写 res.data。怎么做:在 data 中准备 ruleList: [],请求成功后执行 this.ruleList = res.data.rows。易错点:层级写错是最常见问题,比如写成 res.rows、res.data.data.rows;另外 this.params 要提前在 data 中维护好,通常包含 page 和 pageSize,否则请求参数不对,后端返回的数据也不会符合预期。转写稿里有一句话很关键:期待的数据没按期待返回,往往是请求参数要错了。
el-table 绑定数据与列渲染
为什么需要:el-table 是数据驱动组件,绑定数组后按列展示字段。怎么想::data 绑定 ruleList,每一列用 prop 指定接口字段;需要格式化时再用 scope 插槽。怎么做:保留笔记中的 <el-table :data="ruleList" style="width: 100%">,列组件按项目静态结构补齐。转写稿提到序号、楼宇及时间等列,字段名要和接口返回一致。易错点:data 前漏冒号会变成普通字符串;prop 写错表格就是空列;用 scope 时不要猜 scope.row 的结构,可以先把它作为参数传给一个方法,在方法里 console.log 出来看。
调试与验证
为什么需要:表格渲染看起来简单,但接口、参数、返回层级、字段名任何一处错都会导致“页面没数据”。怎么想:按“Network 是否发出请求 → 参数是否正确 → 响应里是否有 rows → 赋值后 ruleList 是否变化 → 表格列 prop 是否匹配”的顺序排查。怎么做:打开 Network 面板刷新页面,确认请求 URL、page、pageSize 和响应结构;必要时在 getRuleList 后打印 res,或在插槽中打印 scope.row。易错点:不要只看代码“觉得应该对”,要以 Network 里实际发送的参数和实际响应为准;接口通了但状态/数据没变,也不能算业务跑通。
代码示例
1. 计费规则接口封装(src/apis/car.js)
javascript
import request from '@/utils/request'
/**
* 获取规则列表
* @param { page, pageSize } params
* @returns
*/
export function getRuleListAPI(params) {
return request({
url: 'parking/rule/list',
params
})
}关键行与易错点
import request from '@/utils/request':request是默认导出,导入时不能加花括号(写成import { request }会报“模块导入导出”的经典错误)。url: 'parking/rule/list':相对地址,中间不能加空格,否则请求路径拼错。- 接口是常规 GET,参数只有
page、pageSize,直接把params透传即可。 - 封装完要给方法加注释(
获取规则列表),方便队友看。
2. 计费规则表格渲染(src/views/car/car-rule.vue)
vue
<template>
<div class="rule-container">
<div class="create-container">
<el-button type="primary">增加停车计费规则</el-button>
<el-button>导出Excel</el-button>
</div>
<!-- 表格区域 -->
<div class="table">
<el-table :data="ruleList" style="width: 100%">
<el-table-column type="index" label="序号" />
<el-table-column label="计费规则编号" prop="ruleNumber" />
<el-table-column label="计费规则名称" prop="ruleName" />
<el-table-column label="免费时长(分钟)" prop="freeDuration" />
<el-table-column label="收费上线(元)" prop="chargeCeiling" />
<el-table-column label="计费方式">
<template #default="scope">
{{ scope.row.chargeType }}
</template>
</el-table-column>
<el-table-column label="计费规则" prop="ruleNameView" />
<el-table-column label="操作" fixed="right" width="120">
<template #default="scope">
<el-button size="mini" type="text">编辑</el-button>
<el-button size="mini" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import { getRuleListAPI } from '@/apis/car'
export default {
name: 'Building',
data() {
return {
ruleList: [], // 规则列表
params: {
// 请求参数
page: 1,
pageSize: 10
},
total: 0, // 规则总数(分页用)
dialogVisible: false // 弹窗开关
}
},
mounted() {
this.getRuleList()
},
methods: {
// 获取规则列表
async getRuleList() {
const res = await getRuleListAPI(this.params)
this.ruleList = res.data.rows
}
}
}
</script>
<style lang="scss" scoped>
.rule-container {
padding: 20px;
background-color: #fff;
}
.create-container {
margin: 10px 0px;
}
.page-container {
padding: 4px 0px;
text-align: right;
}
</style>关键行与易错点
data里ruleList必须是数组,因为将来要用v-for式的表格遍历渲染;params用一个对象统一维护page/pageSize。- 表格绑定固定是
:data="ruleList",列组件用prop指定要渲染的字段;type="index"才会出现序号列。 - 计费方式这一列不能用
prop,因为它要做格式化,所以用<template #default="scope">插槽 + 插值表达式取scope.row.chargeType。 - 取值是
res.data.rows(接口里data下直接就是rows,没有再套一层),写成res.data.data.rows会报错。 - 调试小技巧:想知道
scope.row里到底有什么,可以写个函数把scope.row当参数传出去console.log打印,比在模板里直接插值看得清楚。 mounted里调一次getRuleList完成首屏渲染。
3. 计费规则分页实现(在上一段基础上补全)
vue
<template>
<div class="rule-container">
<div class="create-container">
<el-button type="primary">增加停车计费规则</el-button>
<el-button>导出Excel</el-button>
</div>
<!-- 表格区域 -->
<div class="table">
<el-table :data="ruleList" style="width: 100%">
<el-table-column type="index" label="序号" />
<el-table-column label="计费规则编号" prop="ruleNumber" />
<el-table-column label="计费规则名称" prop="ruleName" />
<el-table-column label="免费时长(分钟)" prop="freeDuration" />
<el-table-column label="收费上线(元)" prop="chargeCeiling" />
<el-table-column label="计费方式">
<template #default="scope">
{{ scope.row.chargeType }}
</template>
</el-table-column>
<el-table-column label="计费规则" prop="ruleNameView" />
<el-table-column label="操作" fixed="right" width="120">
<template #default="scope">
<el-button size="mini" type="text">编辑</el-button>
<el-button size="mini" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页区域 -->
<div class="page-container">
<el-pagination
layout="prev, pager, next"
:total="total"
:page-size="params.pageSize"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script>
import { getRuleListAPI } from '@/apis/car'
export default {
name: 'Building',
data() {
return {
ruleList: [], // 规则列表
params: {
// 请求参数
page: 1,
pageSize: 2
},
total: 0, // 规则总数
dialogVisible: false
}
},
mounted() {
this.getRuleList()
},
methods: {
// 获取规则列表
async getRuleList() {
const res = await getRuleListAPI(this.params)
this.ruleList = res.data.rows
this.total = res.data.total
},
// 页码切换
handleCurrentChange(p) {
this.params.page = p
this.getRuleList()
}
}
}
</script>
<style lang="scss" scoped>
.rule-container {
padding: 20px;
background-color: #fff;
}
.create-container {
margin: 10px 0px;
}
.page-container {
padding: 4px 0px;
text-align: right;
}
</style>关键行与易错点
layout="prev, pager, next"是最简分页结构;想显示总条数就加total,想切换每页条数就加sizes,想跳页就加jumper—— 需要哪块结构就往layout里加哪个。:total="total"不能写死,必须用接口返回的总条数赋值(this.total = res.data.total);total / pageSize才是页数。:page-size="params.pageSize"必须和请求参数里的pageSize保持一致,不能各写一个值。- 页码切换事件是
@current-change,回调参数p就是当前点击的页码;先覆盖this.params.page = p,再重新调getRuleList()用新参数请求数据。 - 每页条数示例改成了
2,如果发现“期待每页 2 条但没生效”,一定是请求参数里pageSize没真的传 2 —— 以 Network 面板里实际发出的 query 参数为准,不要只看代码。
常见错误
详情页取不到路由参数,接口传
undefined
现象:点击“查看”能跳转,但详情接口参数为空、页面无数据。
原因:跳转未带query、参数名前后不一致,或详情页读取路径写错。
修法:统一router.push({ path, query: { id } })与this.$route.query.id,请求前做空值兜底。接口封装后导入报错或
request is not a function
现象:页面编译/运行报模块导入错误,接口无法调用。
原因:默认导出与按需导入混用,或@/utils/request路径拼写错误。
修法:核对export default request对应import request from '@/utils/request',逐段检查路径与文件名。表格列空白或字段错位
现象:表格有行但某些列不显示,序号列也异常。
原因:prop与后端字段不一致;插槽未用scope.row.xxx;序号列误写成普通字段。
修法:对照接口返回逐列改prop,插槽统一scope.row,序号列使用type="index"。分页点击后数据不刷新或
pageSize不对
现象:点第 2 页,列表没变;或每页条数仍按默认值请求。
原因:只改了页码未覆盖请求参数,total/page-size未绑定,或未重新请求。
修法:分页回调中先更新params.page/pageSize,再调用列表接口;以 Network 面板实际参数为准。Tab 激活态错乱,图标不同步切换
现象:点击后多个项同时激活、无激活,或文字变了图标没变。
原因:比较的标识不统一(index/id 混用)、默认值未设、icon-class条件写死。
修法:统一唯一标识与默认首项,:class和icon-class共用同一匹配条件。预览空白或下载变成打开文件
现象:点预览新窗口空白;点下载却在浏览器里打开文件。
原因:预览地址未拼?src=文件地址或未target="_blank";下载误用了预览前缀。
修法:预览按“固定预览地址 +?src=+ 文件地址”拼接;下载<a>直接指向文件地址。金额计算接口被空参调用
现象:选择楼宇或日期时接口报错,金额不回填。
原因:两个条件未同时具备就发请求,缺少非空判断。
修法:两个组件绑定同一回调,先判断楼宇与时间均存在,再请求并回填只读金额。
练习
- 完成计费规则列表:封装
getRuleListAPI,在mounted请求并渲染表格,包含序号列、计费方式插槽、操作列固定,并用 Network 验证请求参数。 - 实现角色列表 Tab 切换:分别用
index和id两种方式记录当前项,要求背景、文字、SVG 图标同步切换。 - 完成合同详情链路:列表“查看”携带
id跳转,详情页取参请求详情并渲染合同表格,再实现“预览”和“下载”两个入口。 - 完成计费规则分页:设置
pageSize=2,点击页码后 Network 中page变化,列表与total正确更新。 - 实现物业费金额计算:楼宇与日期变化触发同一回调,非空判断后请求接口,回填金额并禁止编辑。
课堂画面






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 合同详情-路由跳转和基础数据渲染 | 10:11 |
| 2 | 重点知识回顾和实战思路解析 | 26:21 |
| 3 | 合同详情-在线预览和下载功能 | 12:01 |
| 4 | 角色管理-渲染基础角色列表 | 11:02 |
| 5 | 角色管理-tab点击切换激活实现 | 10:06 |
| 6 | 计费规则-table表格渲染 | 08:21 |
| 7 | 角色管理-tab点击激活交互实现icon图标渲染 | 14:25 |
| 8 | 计费规则-分页实现 | 06:55 |
小结
- 列表页闭环要按“接口封装 → 请求 → 响应式赋值 → 表格/分页绑定 → Network 验证”走完。
- 路由传参必须统一参数名,详情页要对参数缺失做兜底处理。
- Tab 切换的通用模式是“记录唯一标识 + 动态 class/icon 条件匹配”,index 与 id 都可。
- 预览和下载是两个入口:预览需要固定预览地址拼接,下载直接使用文件地址。
- 分页异常优先查请求参数,不要只盯模板代码;
total、pageSize必须与接口一致。 - 业务状态会影响显示和按钮权限,自测标准是接口通且状态确实变化。
- 导出 Excel 先分清前端主导与后端主导;前端主导按工作簿、工作表、表头替换、导出流程组织。
- 使用第三方组件先看
props和事件,再通过改传参验证渲染结果。