Skip to content

角色管理与合同详情(一) ​

本页对应 角色管理与合同详情,共 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 参数为准,不要只看代码。

常见错误 ​

  1. 详情页取不到路由参数,接口传 undefined
    现象:点击“查看”能跳转,但详情接口参数为空、页面无数据。
    原因:跳转未带 query、参数名前后不一致,或详情页读取路径写错。
    修法:统一 router.push({ path, query: { id } }) 与 this.$route.query.id,请求前做空值兜底。

  2. 接口封装后导入报错或 request is not a function
    现象:页面编译/运行报模块导入错误,接口无法调用。
    原因:默认导出与按需导入混用,或 @/utils/request 路径拼写错误。
    修法:核对 export default request 对应 import request from '@/utils/request',逐段检查路径与文件名。

  3. 表格列空白或字段错位
    现象:表格有行但某些列不显示,序号列也异常。
    原因:prop 与后端字段不一致;插槽未用 scope.row.xxx;序号列误写成普通字段。
    修法:对照接口返回逐列改 prop,插槽统一 scope.row,序号列使用 type="index"。

  4. 分页点击后数据不刷新或 pageSize 不对
    现象:点第 2 页,列表没变;或每页条数仍按默认值请求。
    原因:只改了页码未覆盖请求参数,total/page-size 未绑定,或未重新请求。
    修法:分页回调中先更新 params.page/pageSize,再调用列表接口;以 Network 面板实际参数为准。

  5. Tab 激活态错乱,图标不同步切换
    现象:点击后多个项同时激活、无激活,或文字变了图标没变。
    原因:比较的标识不统一(index/id 混用)、默认值未设、icon-class 条件写死。
    修法:统一唯一标识与默认首项,:class 和 icon-class 共用同一匹配条件。

  6. 预览空白或下载变成打开文件
    现象:点预览新窗口空白;点下载却在浏览器里打开文件。
    原因:预览地址未拼 ?src=文件地址 或未 target="_blank";下载误用了预览前缀。
    修法:预览按“固定预览地址 + ?src= + 文件地址”拼接;下载 <a> 直接指向文件地址。

  7. 金额计算接口被空参调用
    现象:选择楼宇或日期时接口报错,金额不回填。
    原因:两个条件未同时具备就发请求,缺少非空判断。
    修法:两个组件绑定同一回调,先判断楼宇与时间均存在,再请求并回填只读金额。

练习 ​

  1. 完成计费规则列表:封装 getRuleListAPI,在 mounted 请求并渲染表格,包含序号列、计费方式插槽、操作列固定,并用 Network 验证请求参数。
  2. 实现角色列表 Tab 切换:分别用 index 和 id 两种方式记录当前项,要求背景、文字、SVG 图标同步切换。
  3. 完成合同详情链路:列表“查看”携带 id 跳转,详情页取参请求详情并渲染合同表格,再实现“预览”和“下载”两个入口。
  4. 完成计费规则分页:设置 pageSize=2,点击页码后 Network 中 page 变化,列表与 total 正确更新。
  5. 实现物业费金额计算:楼宇与日期变化触发同一回调,非空判断后请求接口,回填金额并禁止编辑。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
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

小结 ​

  1. 列表页闭环要按“接口封装 → 请求 → 响应式赋值 → 表格/分页绑定 → Network 验证”走完。
  2. 路由传参必须统一参数名,详情页要对参数缺失做兜底处理。
  3. Tab 切换的通用模式是“记录唯一标识 + 动态 class/icon 条件匹配”,index 与 id 都可。
  4. 预览和下载是两个入口:预览需要固定预览地址拼接,下载直接使用文件地址。
  5. 分页异常优先查请求参数,不要只盯模板代码;total、pageSize 必须与接口一致。
  6. 业务状态会影响显示和按钮权限,自测标准是接口通且状态确实变化。
  7. 导出 Excel 先分清前端主导与后端主导;前端主导按工作簿、工作表、表头替换、导出流程组织。
  8. 使用第三方组件先看 props 和事件,再通过改传参验证渲染结果。