中州养老 第三章-护理项目前后端开发-1
中州养老 第三章-护理项目前后端开发-1
今日目标
在第一章节中,我们已经基本熟悉了项目背景,以及如何熟悉模块和基本的接口开发,在这个章节,我们会继续解锁项目开发中的一些新的技能,内容如下:
- 掌握评估模块开发工期的基本思路和注意事项
- 能够理解新模块服务管理-护理计划的需求
- 掌握表结构设计的通用思路,以及工具的使用
- 能够完成护理项目分页查询的接口开发
- 能够掌握 TDesign 组件的快速学习和使用
- 能够掌握前端工程师开发的流程
- 能够独立完成列表页组件的开发
1. 更全面的熟悉项目
更全面的熟悉项目
功能清单能够准确地列出项目中需要实现的各项功能,帮助开发团队明确需求。一般是由产品经理创建
回顾后端开发流程

如何评估工期
评估工期是程序员的必备的一个技能,评估的准确与否,一定程度上会影响在当前团队发展的边界。
那如何做工期评估呢?
当我们被分配到需要开发某个模块之后,我们从以下几个方面来评估工期:
- 需求分析 首先需要对需求进行充分的分析和理解,包括功能、性能、安全等方面的需求。确保对需求的理解准确,避免后期需求变更导致工期延误。
- 技术复杂度 评估所涉及的技术栈和相关技术难点,包括数据库设计、性能优化、安全防护等方面的复杂度。技术复杂度高的项目通常需要更多的时间来完成,如果个人之前没有涉及过,还需要花时间去学习。
- 任务分解 将整体任务分解为具体的模块或功能点,对每个模块进行评估,估算每个模块的开发工期。
- 风险评估: 评估项目中可能出现的风险和问题,如第三方依赖、需求变更、人力资源等方面的风险,合理考虑这些风险对工期的影响。
- 团队协作 评估模块完成需要团队其他人来共同协助开发,比如模块间有依赖关系,接口需要联调、功能需要测试等等
- 沟通 提前沟通、经常沟通,了解彼此的日程和需求变更,团队内信息要及时同步与更新
开发计划样例: 🎯
总结
课堂作业
- 来评估一下第一天做的模块:房型设置和床位管理 🎤
2.全方位解读服务管理-护理计划
全方位解读服务管理-护理计划
需求分析
需求分析,该如何入手呢?
入手的基本思路就是原型文档和 PRD 文档,有了这两个文档,我们就有了很重要的开发依据
注意:在实际开发中,有了新需求以后,产品经理会先给项目组的成员开会,来评审新的需求,在会议中,如果有任何不理解的需求,要及时提出来,方便后期设计、开发
原型向导
我们第一个要开发的功能是在服务管理--> 护理计划--> 护理项目

模块作用整体介绍
服务管理-> 护理计划包含:护理等级、护理计划、护理项目共 3 个子模块。
- 护理等级作为护理模块的基础数据,主要是用于识别老人的身体健康状况,护理等级与护理计划进行绑定,管理员可对护理计划进行增删改、修改状态等操作;
- 护理计划作为护理模块的基础数据,护理计划主要是与护理等级和护理项目进行绑定,后台管理人员在创建计划时,可选择该计划要执行哪些任护理项目;后台管理人员可以护理计划进行增删改,修改状态等操作;
- 护理项目作为护理模块的基础数据,主要是由护理员给老年人提供护理服务,常见的有:洗头、助餐、复健运动等,护理项目分为护理计划内与护理计划外,两者之间的区别是:前者是根据护理计划产生的,后者是由家属从用户端进行下单支付产生的
- 三者关系
护理项目
1)第一页 护理项目列表

这个图中分了三部分,第一部分是搜索栏;第二部分是一个新增按钮;第三部分是列表数据展示区域
搜索栏,用户可以按照指定的条件进行检索数据,目前有两个条件,护理项目名称和状态
新增护理项目按钮,弹窗可以对护理项目进行新增
列表数据展示区域,这里面也包含了两部分,第一个是数据,第二个是操作按钮
- 数据中包含了以下字段
- 护理图片
- 护理项目名称
- 价格
- 单位
- 排序字段
- 创建人
- 创建时间
- 操作按钮
- 删除 删除操作
- 编辑 弹窗回显数据,可修改数据
- 禁用 | 启用 修改护理项目的状态
- 数据中包含了以下字段
不过,我们看任何需求的时候,最好能先通篇通读一下需求文档,特别是公共的说明
在当前项目的公共说明中,第 9 条规则是这么定义的,如下图
原型导航:

分页通用规则

如果数据不超过 10 条,则不展示分页信息,超过 10 条,则需要展示分页栏
2)第二页 新增或编辑
我们可以把原型图往右边滑动一下,可以看到点击按钮的一些操作效果和一些逻辑说明

当用户点击了新增护理项目按钮或者是编辑按钮,则会弹窗进行操作
- 可以输入护理项目中的一些数据,包含了图中的所有字段信息
- 其中护理图片需要单独维护一个接口来进行图片上传,并获取到图片的访问路径,进行保存
- 如果点击了编辑按钮,则会先回显数据,然后再次修改
3)其他需求
在原型中还有一段话进行了描述,我们来分析一下

这里主要说的跟其他模块的关系会影响到的操作,大家主要根据下面这个图来理解上面的需求
4)涉及到的功能列表
根据我们刚才阅读的原型和 PRD,我们可以得出,目前这个需求中共涉及了到 7 个接口,分别是:
- 条件分页查询护理项目列表
- 新增护理项目
- 根据 id 查询护理项目
- 上传图片
- 修改护理项目
- 删除护理项目
- 启用或禁用护理项目
分组讨论
我们目前已经分析完了护理项目的需求了,但是在护理计划这个模块中还有关联的另外两个小模块,分别是护理计划和护理等级。
根据我们刚才分析的方式,以小组的形式分析另外两个模块,最后需要梳理出:
- 护理项目、护理计划、护理等级三个模块的关系?它们是如何进行引用的?
- 梳理出护理计划、护理等级包含的功能有哪些?
表结构设计操作
根据我们刚才看到的原型及 PRD,我们接下来就基于刚才分析的原型来创建表结构
如何设计表?
E-R 图
E-R 图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。
一个栗子:
上述栗子的 E-R 图
画图工具推荐:draw.io (资料包中已提供)
详细设计
命名(见名之意) 参考阿里开发手册
数据类型
- 基本准则
- 可靠性:考虑字段存储的内容长度,尽可能的合适
- 存储空间:在考虑可靠性的前提下,再次考虑存储空间
- 常见数据类型 MySQL 常见数据类型
- 基本准则
主键(必须存在)
- 自增
- UUID
- 雪花算法
冗余字段
- 创建时间
- 修改时间
- 创建人
- 修改人
- 备注
创建护理项目表结构
我们目前的业务就是护理这个模块,我们可以先根据原型图画出对应的 E-R 图
E-R 图(先画出护理项目的详细字段)
- 护理项目与护理计划是多对多的关系
- 护理计划与护理等级是一对一的关系
- 护理项目中这些字段都是基于原型和需求说明来确定,其中冗余字段建议考虑进去,方便后期做权限管理和数据筛选(创建时间、修改时间、创建人、修改人、备注)
- 护理项目表中的主键是 id,自增
- 护理项目名称字段 name,需要添加唯一约束,该字段数据不能重复
基于 E-R,我们就可以很方便来创建表结构,其中护理项目的表结构如下:
CREATE TABLE "nursing_project" (
"id" bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
"name" varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '名称',
"order_no" int DEFAULT NULL COMMENT '排序号',
"unit" varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '单位',
"price" decimal(10,2) DEFAULT NULL COMMENT '价格',
"image" varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片',
"nursing_requirement" varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '护理要求',
"status" int NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
"create_by" varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人',
"update_by" varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人',
"remark" varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"update_time" datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY ("id") USING BTREE,
UNIQUE KEY "name" ("name") USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='护理项目表';
对应实体类,存放位置:zzyl-service 模块下的 entity 包
package com.zzyl.entity;
import com.zzyl.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 实体类:NursingProject(护理项目)
*/
@Data
public class NursingProject extends BaseEntity {
/**
* 编号
*/
@ApiModelProperty(value = "编号")
private Long id;
/**
* 名称
*/
@ApiModelProperty(value = "名称")
private String name;
/**
* 排序号
*/
@ApiModelProperty(value = "排序号")
private Integer orderNo;
/**
* 单位
*/
@ApiModelProperty(value = "单位")
private String unit;
/**
* 价格
*/
@ApiModelProperty(value = "价格")
private BigDecimal price;
/**
* 图片
*/
@ApiModelProperty(value = "图片")
private String image;
/**
* 护理要求
*/
@ApiModelProperty(value = "护理要求")
private String nursingRequirement;
/**
* 状态(0:禁用,1:启用)
*/
@ApiModelProperty(value = "状态(0:禁用,1:启用)")
private Integer status;
}
总结
课堂作业
大家继续补全 E-R 图,然后创建护理计划表、护理等级表、护理计划与护理项目中间表
要求:
- 以小组为单位进行讨论,4 张表的关系该如何使用 E-R 进行表示
- 根据原型和需求说明,确定护理计划、护理等级、护理计划与护理项目中间表中的字段,以 E-R 图展示
接口分析设计
根据我们之前说的开发流程,我们知道,当我们已经分析了需求,并且表结构已经创建了,那么接下来就需要设计功能接口了,目前该模块中的接口涉及到很多,我们可以专门维护一个接口文档,来方便重复查看
文档链接:护理模块-接口文档
3.后端接口开发-护理项目-分页查询
后端接口开发-护理项目-分页查询
通常情况,当我们分析完一个模块业务之后,会把这个业务中所有涉及到的接口一次性的都会开发出来,然后再去开发前端页面,最终它们联调对接,完成一个模块的整体开发。
但是在我们教学阶段,我们可以先写一个后端接口出来,然后去开发前端页面与这个接口进行对接,方便大家用更短的时间来完成前后端的开发工作。
后端开发步骤
如果我们能顺利的做完了需求分析、表结构设计、接口设计,再去完成功能代码的时候,就相对容易些。
现在我们需要确定的是后台代码的具体的开发步骤,一个良好的步骤,可以增加我们的开发效率
- controller 的基本定义(接口四要素)
- mapper 接口和映射文件
- 业务层代码编写
- 单元测试
- 业务层对接控制层
- 接口测试
- 前后端联调(模块所有接口完成后)
代码操作
接口定义
在 zzyl-web 模块下创建一个新的类:NursingProjectController,根据接口文档添加如下代码:
package com.zzyl.controller;
import com.zzyl.base.PageResponse;
import com.zzyl.base.ResponseResult;
import com.zzyl.vo.NursingProjectVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
_/**_
_ * 护理项目Controller类_
_ */_
@RestController
@RequestMapping("/nursing_project")
@Api(tags = "护理项目管理")
public class NursingProjectController {
@GetMapping
@ApiOperation("分页查询护理项目列表")
public ResponseResult<PageResponse<NursingProjectVo>> getByPage(
@ApiParam(value = "护理项目名称") String name,
@ApiParam(value = "状态:0-禁用,1-启用") Integer status,
@ApiParam(value = "当前页码")
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
@ApiParam(value = "每页显示数量")
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return null;
}
}
NursingProjectVo
package com.zzyl.vo;
import com.zzyl.base.BaseVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
_/**_
_ * 护理项目视图对象_
_ */_
@Data
public class NursingProjectVo extends BaseVo {
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "排序号")
private Integer orderNo;
@ApiModelProperty(value = "单位")
private String unit;
@ApiModelProperty(value = "价格")
private BigDecimal price;
@ApiModelProperty(value = "图片")
private String image;
@ApiModelProperty(value = "护理要求")
private String nursingRequirement;
@ApiModelProperty(value = "状态(0:禁用,1:启用)")
private Integer status;
@ApiModelProperty(value = "护理项目绑到计划的个数")
private Integer count;
}
Mapper
在 zzyl-service 模块下新增 mapper 接口和 xml 映射文件
新增 NursingProjectMapper 接口
package com.zzyl.mapper;
import com.github.pagehelper.Page;
import com.zzyl.entity.NursingProject;
import org.apache.ibatis.annotations.Mapper;
_/**_
_ * 护理项目Mapper接口_
_ */_
@Mapper
public interface NursingProjectMapper {
Page<NursingProject> selectByPage(String name, Integer status);
}
在 zzyl-service 模块下的 resources/mapper 接口中新增 NursingProjectMapper.xml 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zzyl.mapper.NursingProjectMapper">
<resultMap id="nursingProjectResultMap" type="com.zzyl.vo.NursingProjectVo">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="order_no" property="orderNo"/>
<result column="unit" property="unit"/>
<result column="price" property="price"/>
<result column="image" property="image"/>
<result column="nursing_requirement" property="nursingRequirement"/>
<result column="status" property="status"/>
<result column="create_by" property="createBy"/>
<result column="update_by" property="updateBy"/>
<result column="remark" property="remark"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="creator" property="creator"/>
</resultMap>
<select id="selectByPage" resultMap="nursingProjectResultMap">
select p.*,su.real_name as creator from nursing_project p left join sys_user su on p.create_by = su.id
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
order by order_no ,create_time desc
</select>
</mapper>
业务层
在 zzyl-service 模块下新增业务层接口并添加方法:NursingProjectService
package com.zzyl.service;
import com.zzyl.base.PageResponse;
import com.zzyl.vo.NursingProjectVo;
_/**_
_ * 护理项目Service接口_
_ */_
public interface NursingProjectService {
_/**_
_ * 分页查询护理项目列表_
_ *_
_ * @param name 护理项目名称_
_ * @param status 状态:0-禁用,1-启用_
_ * @param pageNum 当前页码_
_ * @param pageSize 每页显示数量_
_ * @return 护理项目列表_
_ */_
_ _PageResponse<NursingProjectVo> getByPage(String name, Integer status, Integer pageNum, Integer pageSize);
}
实现类:
package com.zzyl.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.entity.NursingProject;
import com.zzyl.mapper.NursingProjectMapper;
import com.zzyl.service.NursingProjectService;
import com.zzyl.vo.NursingProjectVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
_/**_
_ * 护理项目Service实现类_
_ */_
@Service
public class NursingProjectServiceImpl implements NursingProjectService {
@Autowired
private NursingProjectMapper nursingProjectMapper;
@Override
public PageResponse<NursingProjectVo> getByPage(String name, Integer status, Integer pageNum, Integer pageSize) {
PageHelper._startPage_(pageNum, pageSize);
Page<NursingProject> nursingProjects = nursingProjectMapper.selectByPage(name, status);
PageResponse<NursingProjectVo> projectVoPageResponse = PageResponse._of_(nursingProjects, NursingProjectVo.class);
return projectVoPageResponse;
}
}
控制层
补全控制层代码:
package com.zzyl.controller;
import com.zzyl.base.PageResponse;
import com.zzyl.base.ResponseResult;
import com.zzyl.service.NursingProjectService;
import com.zzyl.vo.NursingProjectVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
_/**_
_ * 护理项目Controller类_
_ */_
@RestController
@RequestMapping("/nursing_project")
@Api(tags = "护理项目管理")
public class NursingProjectController {
@Autowired
private NursingProjectService nursingProjectService;
@GetMapping
@ApiOperation("分页查询护理项目列表")
public ResponseResult<PageResponse<NursingProjectVo>> getByPage(
@ApiParam(value = "护理项目名称")
@RequestParam(value = "name", required = false) String name,
@ApiParam(value = "状态:0-禁用,1-启用")
@RequestParam(value = "status", required = false) Integer status,
@ApiParam(value = "当前页码")
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
@ApiParam(value = "每页显示数量")
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
PageResponse<NursingProjectVo> nursingProjectPageInfo = nursingProjectService.getByPage(name, status, pageNum, pageSize);
return ResponseResult._success_(nursingProjectPageInfo);
}
}
接口测试
接口测试工具
- postman
- ApiFox
- knife4j 在线接口测试工具

4. 从 0 到 1 开发前端页面
前言
前端开发流程
下图详细描述了前端开发的流程:

- 需求分析(基于原型和 PRD)
- 开发计划(工期评估)
- 接口设计(基于原型和 PRD)
- 功能实现(基于接口设计 + 原型 +PRD)
- 前后端联调
- 测试提 bug
- 前后端优化,再联调
- 测试回归 bug
- 功能验收
需求分析
我们在开发后端的时候,已经对于护理项目模块的需求进行了说明,不过前端和后端关注点是不一样的。后端更注重逻辑,前端更注重展示效果,并且在完成功能之前,前端还需要查阅 UI 设计师提供的设计稿(图)来辅助完成页面的开发
养老项目设计稿,在资料包中已提供
下面,我们来重新分析一下需求,还是打开原型图,找到护理项目这个小模块
列表页

当点击 新增护理项目
按钮或者是列表项中 编辑
的时候,需要弹窗进行新增或者是编辑,如下图

- 点击【新增护理项目】,出【新增护理项目】弹窗
- 点击
编辑
,出【编辑护理项目】弹窗
当编辑输入框内容的时候,验证规则如下:

关于价格、排序、图片这三个字段,需要进一步查看公共说明文档
在列表项 操作
中的 删除
和 禁用
也会有弹窗,大家可以打开原型的 全局/公共说明
删除弹窗

禁用弹窗

注意:启用与禁用操作、逻辑相反,且不出确认弹窗
结论
通过以上分析,我们现在大概知道了,这个护理项目页面分为了四个部分,分别是:
- 列表页
- 新增和编辑弹窗
- 删除弹窗
- 禁用弹窗
以上所有的页面,我们都可以到 TDesign 组件去寻找,来适配我们当前的项目
技术选型
目前在养老项目中的前端技术栈为:Vue 3 + TypeScript +Tdesign + Vite + pinia
在之前课程中,我们已经学习完了 Vue3 的内容,并没有使用过 TDesign,我们下面我们花点时间来讲解 TDesign 组件,它是类似于 Element plus 组件的
概述
TDesign 具有统一的 价值观,一致的设计语言和视觉风格,帮助用户形成连续、统一的体验认知。在此基础上,TDesign 提供了开箱即用的 UI 组件库、设计指南 和相关 设计资产,以优雅高效的方式将设计和研发从重复劳动中解放出来,同时方便大家在 TDesign 的基础上扩展,更好的的贴近业务需求。
官网地址:https://tdesign.tencent.com/

快速搭建前端项目
RDesign 是传智研究院对腾讯的 TDesign 组件进行了简易的封装,可以更快的创建脚手架项目,达到快速开发的目的
入门手册请参考:https://czri-admin-doc.itheima.net/
项目样例(在线访问):https://czri-admin.itheima.net/

总结
前端的开发流程中,UIUE,设计稿,前端需要参考它开发样式
TDesign,腾讯出的组件库
RDesign,对于 TDesign 进行了简易封装,加入自己公司的特色
- 搭建环境:
- 安装命令,有可能安装不成功,为啥(网不好)
npm i -g rdesign-starter-cli
- 创建项目
```bash
rdesign create 项目名称
- 可以使用我刚刚创建的项目进行练习
- 安装依赖:yarn install
常见组件
在项目中使用三方组件,我们在项目中使用,都需要参考组件中提供的文档。每个组件主要包含三个内容:
- 代码示例:每个组件的核心功能
- API 文档:描述每一个 API 的功能和注意事项
- 设计指南:交互使用建议

组件预览地址:https://tdesign.tencent.com/vue-next/overview
下面列举了一些组件分类以及常见的组件
组件分类 | 组件名称 |
---|---|
基础 | - Button 按钮- lcon 图标- Link 链接 |
布局 | - Grid 栅格- Layout 布局- Space 间距 |
导航 | - Dropdown 下拉菜单- Pagination 分页- Tabs 选项卡 |
输入 | - Cascader 级联选择器- Checkbox 多选框- DatePicker 日期选择器- Form 表单- Input 输入框- Radio 单选框- Textarea 多行文本框- TimePicker 时间选择器- Upload 上传 |
数据展示 | - Image 图片- lmageViewer 图片预览- Table 表格 |
消息提醒 | - Alert 警告提醒- Dialog 对话框- Message 全局提示 |
代码操作
|
入门案例
需求说明
在创建的好项目中新增路由菜单
- 主菜单:权限管理
- 子菜单:用户管理和角色管理
在用户管理菜单中新增组件,展示用户列表,如下效果
- 使用 TDesign 中的 table 组件展示数据,并可以查看 API 列表来实现数据的展示(序号、性别)
- 使用 TDesign 中的 ImageViewer 组件实现图片展示和预览
最终效果:

路由创建
在 src/router/modules 目录下新建文件:permission.ts 文件,内容如下:
import Layout from '@/layouts/index.vue'
import HomeIcon from '@/assets/test-img/icon_menu_diaodu.svg'
import ModelIcon from '@/assets/test-img/icon_menu_zj.svg'
export default [
{
path: '/permission',
name: 'permission',
component: Layout,
redirect: '/permission/index',
meta: {
title: '权限管理',
icon: HomeIcon
},
children: [
{
path: 'index',
name: '用户管理',
component: () => import('@/pages/permission/user/index.vue'),
meta: {
title: '用户管理',
icon: ModelIcon
}
},
{
path: 'role-index',
name: '角色管理',
component: () => import('@/pages/permission/role/index.vue'),
meta: {
title: '角色管理',
icon: ModelIcon
}
}
]
}
]
页面效果:

页面创建
在 src/pages 下新建目录 permissio,并且在 permission 下创建两个目录,分别是 user 和 role,效果如下:

在 user 目录下新增 index.vue 文件,输入以下内容,可以在前端查看效果
<template>
我不是一名优秀的程序员,我只是一个代码搬运工!
</template>
<script setup lang="jsx">
</script>
页面效果:

Table 组件
打开 TDesign 组件中的 Table 组件,链接:https://tdesign.tencent.com/vue-next/components/table
找到基础表格

把代码拷贝到新建的 index.vue 文件中(全部拷贝),代码如下:
<template>
<t-space direction="vertical">
<!-- 按钮操作区域 -->
<t-radio-group v-model="size" variant="default-filled">
<t-radio-button value="small">小尺寸</t-radio-button>
<t-radio-button value="medium">中尺寸</t-radio-button>
<t-radio-button value="large">大尺寸</t-radio-button>
</t-radio-group>
<t-space>
<t-checkbox v-model="stripe"> 显示斑马纹 </t-checkbox>
<t-checkbox v-model="bordered"> 显示表格边框 </t-checkbox>
<t-checkbox v-model="hover"> 显示悬浮效果 </t-checkbox>
<t-checkbox v-model="tableLayout"> 宽度自适应 </t-checkbox>
<t-checkbox v-model="showHeader"> 显示表头 </t-checkbox>
</t-space>
<!-- 当数据为空需要占位时,会显示 cellEmptyContent -->
<t-table
row-key="index"
:data="data"
:columns="columns"
:stripe="stripe"
:bordered="bordered"
:hover="hover"
:table-layout="tableLayout ? 'auto' : 'fixed'"
:size="size"
:pagination="pagination"
:show-header="showHeader"
cell-empty-content="-"
resizable
lazy-load
@row-click="handleRowClick"
>
</t-table>
</t-space>
</template>
<script setup lang="jsx">
import { ref } from 'vue';
import { ErrorCircleFilledIcon, CheckCircleFilledIcon, CloseCircleFilledIcon } from 'tdesign-icons-vue-next';
const statusNameListMap = {
0: { label: '审批通过', theme: 'success', icon: <CheckCircleFilledIcon /> },
1: { label: '审批失败', theme: 'danger', icon: <CloseCircleFilledIcon /> },
2: { label: '审批过期', theme: 'warning', icon: <ErrorCircleFilledIcon /> },
};
const data = [];
const total = 28;
for (let i = 0; i < total; i++) {
data.push({
index: i + 1,
applicant: ['贾明', '张三', '王芳'][i % 3],
status: i % 3,
channel: ['电子签署', '纸质签署', '纸质签署'][i % 3],
detail: {
email: ['w.cezkdudy@lhll.au', 'r.nmgw@peurezgn.sl', 'p.cumx@rampblpa.ru'][i % 3],
},
matters: ['宣传物料制作费用', 'algolia 服务报销', '相关周边制作费', '激励奖品快递费'][i % 4],
time: [2, 3, 1, 4][i % 4],
createTime: ['2022-01-01', '2022-02-01', '2022-03-01', '2022-04-01', '2022-05-01'][i % 4],
});
}
const stripe = ref(true);
const bordered = ref(true);
const hover = ref(false);
const tableLayout = ref(false);
const size = ref('medium');
const showHeader = ref(true);
const columns = ref([
{ colKey: 'applicant', title: '申请人', width: '100' },
{
colKey: 'status',
title: '申请状态',
cell: (h, { row }) => {
return (
<t-tag shape="round" theme={statusNameListMap[row.status].theme} variant="light-outline">
{statusNameListMap[row.status].icon}
{statusNameListMap[row.status].label}
</t-tag>
);
},
},
{ colKey: 'channel', title: '签署方式' },
{ colKey: 'detail.email', title: '邮箱地址', ellipsis: true },
{ colKey: 'createTime', title: '申请时间' },
]);
const handleRowClick = (e) => {
console.log(e);
};
const pagination = {
defaultCurrent: 1,
defaultPageSize: 5,
total,
};
</script>
上述代码中,columns 字段控制表头,for 循环中展示的列表数据,目前展示的是案例中自带的内容,我们需要进行改造
- for 循环的作用就是 data 属性进行赋值,我们可以把 web 案例的数据直接拷贝过来,删除 for 循环,最终 data 的数据为:
const data = ref([
{
"id": 1,
"name": "谢逊",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/1.jpg",
"gender": 1,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
},
{
"id": 2,
"name": "韦一笑",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/2.jpg",
"gender": 1,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
},
{
"id": 3,
"name": "黛绮丝",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/3.jpg",
"gender": 2,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
},
{
"id": 4,
"name": "殷天正",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/4.jpg",
"gender": 1,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
}
]);
- 根据我们的原始数据,可以修改columns字段,如下效果:
```javascript
//表头
const columns = ref([
{ colKey: 'rowIndex', title: "序号" },
{ colKey: 'name', title: '姓名' },
{ colKey: 'image', title: '头像' },
{ colKey: 'gender', title: '性别' },
{ colKey: 'job', title: '职位' },
{ colKey: 'entrydate', title: '入职时间' },
{ colKey: 'updatetime', title: '更新时间' },
])
- 改造完成后,目前的效果,如下:
在上面的代码中,主要控制数据显示的是 <t-table>
标签,我们来详细说一下这个标签的内容
<t-table
row-key="index"
:data="data"
:columns="columns"
:stripe="stripe"
:bordered="bordered"
:hover="hover"
:table-layout="tableLayout ? 'auto' : 'fixed'"
:size="size"
:pagination="pagination"
:show-header="showHeader"
cell-empty-content="-"
resizable
@row-click="handleRowClick"
>
- row-key
- :data 数据源 (数组)
- :columns 列配置(数组)
- :stripe 是否显示斑马纹
- :bordered 是否显示表格边框
- :hover 是否显示鼠标悬浮状态
- :table-layout 表格布局方式 可选项:auto/fixed
- :size 表格尺寸
- :pagination 分页配置, 用于模块内切换内容的控件
- :show-header 是否显示表头
- cell-empty-content 单元格数据为空时呈现的内容
- resizable 是否允许调整列宽
- @row-click 行点击时触发,泛型 T 指表格数据类型
table 组件更多的 api 参考:https://tdesign.tencent.com/vue-next/components/table?tab=api
序号处理
目前列表中的序号是空白的,我们需要单独处理序号。
在 table 组件已经提供了序号的功能,有一个默认的字段 rowIndex,我们只需要展示即可,默认从 0 开始
<t-table>
<template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template>
</t-table>
<t-table>
标签对中的<template></template>
可以单独处理特殊的字段,标签对里面展示列表中的内容#rowIndex
中的rowIndex
为默认字段
图片预览组件
目前图片展示的一个 url 链接,我们现在需要展示为图片,并且由预览功能
其实在 TDesign 中已经提供了对应的组件:ImageViewer
链接:https://tdesign.tencent.com/vue-next/components/image-viewer
找到缩略图图片查看器

代码中需要修改两处,改为自己的图片地址

最终的代码如下:
<template #image="{ row }">
<div>
<div class="tdesign-demo-image-viewer__base">
<t-image-viewer :images="[row.image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image">
<img alt="test" :src="row.image" class="tdesign-demo-image-viewer__ui-image--img" />
<div class="tdesign-demo-image-viewer__ui-image--hover" @click="open">
<span>
<BrowseIcon size="1.4em" /> 预览
</span>
</div>
</div>
</template>
</t-image-viewer>
</div>
</div>
</template>
其中 #image="{ row }"
中,image
是字段名字,row
代表整行数据
性别字段逻辑处理
目前性别展示的还是 1 或 2,我们最终希望展示的男或女,这个处理,我们只需要通过插值表达式即可解决
代码如下:
<template #gender="{ row }">{{ row.gender == 1 ? '男' : '女' }}</template>
案例完整代码:
<template>
<t-space direction="vertical">
<!-- 按钮操作区域 -->
<t-radio-group v-model="size" variant="default-filled">
<t-radio-button value="small">小尺寸</t-radio-button>
<t-radio-button value="medium">中尺寸</t-radio-button>
<t-radio-button value="large">大尺寸</t-radio-button>
</t-radio-group>
<t-space>
<t-checkbox v-model="stripe"> 显示斑马纹 </t-checkbox>
<t-checkbox v-model="bordered"> 显示表格边框 </t-checkbox>
<t-checkbox v-model="hover"> 显示悬浮效果 </t-checkbox>
<t-checkbox v-model="tableLayout"> 宽度自适应 </t-checkbox>
<t-checkbox v-model="showHeader"> 显示表头 </t-checkbox>
</t-space>
<!-- 当数据为空需要占位时,会显示 cellEmptyContent -->
<t-table row-key="index" :data="data" :columns="columns" :stripe="stripe" :bordered="bordered" :hover="hover"
:table-layout="tableLayout ? 'auto' : 'fixed'" :size="size" :pagination="pagination" :show-header="showHeader"
cell-empty-content="-" resizable lazy-load @row-click="handleRowClick">
<template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template>
<template #image="{ row }">
<div>
<div class="tdesign-demo-image-viewer__base">
<t-image-viewer :images="[row.image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image">
<img alt="test" :src="row.image" class="tdesign-demo-image-viewer__ui-image--img" />
<div class="tdesign-demo-image-viewer__ui-image--hover" @click="open">
<span>
<BrowseIcon size="1.4em" /> 预览
</span>
</div>
</div>
</template>
</t-image-viewer>
</div>
</div>
</template>
<template #gender="{ row }">{{ row.gender == 1 ? '男' : '女' }}</template>
</t-table>
</t-space>
</template>
<script setup lang="jsx">
import { ref } from 'vue';
const data = ref([
{
"id": 1,
"name": "谢逊",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/1.jpg",
"gender": 1,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
},
{
"id": 2,
"name": "韦一笑",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/2.jpg",
"gender": 1,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
},
{
"id": 3,
"name": "黛绮丝",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/3.jpg",
"gender": 2,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
},
{
"id": 4,
"name": "殷天正",
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/4.jpg",
"gender": 1,
"job": "班主任",
"entrydate": "2023-06-09",
"updatetime": "2023-07-01 00:00:00"
}
]);
const total = 28;
const stripe = ref(true);
const bordered = ref(true);
const hover = ref(false);
const tableLayout = ref(false);
const size = ref('medium');
const showHeader = ref(true);
//表头
const columns = ref([
{ colKey: 'rowIndex', title: "序号" },
{ colKey: 'name', title: '姓名' },
{ colKey: 'image', title: '头像' },
{
colKey: 'gender', title: '性别'
},
{ colKey: 'job', title: '职位' },
{ colKey: 'entrydate', title: '入职时间' },
{ colKey: 'updatetime', title: '更新时间' },
]);
const handleRowClick = (e) => {
console.log(e);
};
const pagination = {
defaultCurrent: 1,
defaultPageSize: 5,
total,
};
</script>
开发步骤
当我们已经完成了需求分析,接下来就准备开发页面,然后与后端对接,进行交互。不过在开发之前我们可以先说明一下,一般情况下前端开发的基本步骤:
表格组件
目前,我们在后端已经完成了一个接口的开发了,就是护理项目分页条件查询,我们目前可以先对接第一个接口,来完成前端页面的开发。
基于原型图,我们知道这里是表格展示,
在 @/pages/serve/plan/project/
目录中新建 index.vue
组件,我们现在想要一个列表分页展示,可以参考 TDesign 组件提供好的基础内容,链接:https://tdesign.tencent.com/vue-next/components/table
我们找到基础表格

然后拷贝它所有的代码,到我们创建的 index.vue 组件中
<template>
<t-space direction="vertical">
<!-- 按钮操作区域 -->
<t-radio-group v-model="size" variant="default-filled">
<t-radio-button value="small">小尺寸</t-radio-button>
<t-radio-button value="medium">中尺寸</t-radio-button>
<t-radio-button value="large">大尺寸</t-radio-button>
</t-radio-group>
<t-space>
<t-checkbox v-model="stripe"> 显示斑马纹 </t-checkbox>
<t-checkbox v-model="bordered"> 显示表格边框 </t-checkbox>
<t-checkbox v-model="hover"> 显示悬浮效果 </t-checkbox>
<t-checkbox v-model="tableLayout"> 宽度自适应 </t-checkbox>
<t-checkbox v-model="showHeader"> 显示表头 </t-checkbox>
</t-space>
<!-- 当数据为空需要占位时,会显示 cellEmptyContent -->
<t-table
row-key="index"
:data="data"
:columns="columns"
:stripe="stripe"
:bordered="bordered"
:hover="hover"
:table-layout="tableLayout ? 'auto' : 'fixed'"
:size="size"
:pagination="pagination"
:show-header="showHeader"
cell-empty-content="-"
resizable
@row-click="handleRowClick"
>
</t-table>
</t-space>
</template>
<script setup lang="jsx">
import { ref } from 'vue';
import { ErrorCircleFilledIcon, CheckCircleFilledIcon, CloseCircleFilledIcon } from 'tdesign-icons-vue-next';
const statusNameListMap = {
0: { label: '审批通过', theme: 'success', icon: <CheckCircleFilledIcon /> },
1: { label: '审批失败', theme: 'danger', icon: <CloseCircleFilledIcon /> },
2: { label: '审批过期', theme: 'warning', icon: <ErrorCircleFilledIcon /> },
};
const data = [];
const total = 28;
for (let i = 0; i < total; i++) {
data.push({
index: i + 1,
applicant: ['贾明', '张三', '王芳'][i % 3],
status: i % 3,
channel: ['电子签署', '纸质签署', '纸质签署'][i % 3],
detail: {
email: ['w.cezkdudy@lhll.au', 'r.nmgw@peurezgn.sl', 'p.cumx@rampblpa.ru'][i % 3],
},
matters: ['宣传物料制作费用', 'algolia 服务报销', '相关周边制作费', '激励奖品快递费'][i % 4],
time: [2, 3, 1, 4][i % 4],
createTime: ['2022-01-01', '2022-02-01', '2022-03-01', '2022-04-01', '2022-05-01'][i % 4],
});
}
const stripe = ref(true);
const bordered = ref(true);
const hover = ref(false);
const tableLayout = ref(false);
const size = ref('medium');
const showHeader = ref(true);
const columns = ref([
{ colKey: 'applicant', title: '申请人', width: '100' },
{
colKey: 'status',
title: '申请状态',
cell: (h, { row }) => {
return (
<t-tag shape="round" theme={statusNameListMap[row.status].theme} variant="light-outline">
{statusNameListMap[row.status].icon}
{statusNameListMap[row.status].label}
</t-tag>
);
},
},
{ colKey: 'channel', title: '签署方式' },
{ colKey: 'detail.email', title: '邮箱地址', ellipsis: true },
{ colKey: 'createTime', title: '申请时间' },
]);
const handleRowClick = (e) => {
console.log(e);
};
const pagination = {
defaultCurrent: 1,
defaultPageSize: 5,
total,
};
</script>
我们现在启动前端项目,同时也需要启动后端,然后在网页上访问服务管理--> 护理计划--> 护理项目
如果能看到如下效果,就证明刚才的路由和 vue 创建都是成功的

对接后端接口
定义接口
现在页面中的数据,都是静态的,我们需要让它去调用后台的接口,完成数据交互。
目前的前端工程中,所有的接口定义都在:src/api 目录中

其中 api 目录下还有一个 model 文件夹,这里面主要存储的是接口中需要使用的对象类型,比如:interface
我们下面需要对接的就是护理项目列表查询,我们可以通过在线接口文档查看或者阅读离线接口文档,来定义这个接口的返回值类型,使用的技术就是 TS
定义模型对象:在 src/api/model 中新增一个文件 serveModel.ts,代码如下
export interface ProjecListModel {
createBy: number
createTime: string
creator: string
id: number
image: string
name: string
nursingRequirement: string
orderNo: number
price: number
status: number
unit: string
updateBy: number
updateTime: string
}
以上类型定义需要与后台接口字段和类型进行对应,参考在线接口文档
在 src/api 目录中新增一个 serve.ts 文件,定义接口,代码如下:
import { request } from '@/utils/request'
import type { ProjecListModel } from '@/api/model/nursingModel'
// 分页查询护理项目信息
export function getProjectList(params) {
return request.get<ProjecListModel>({
url: `/nursing_project`,
params
})
}
- request 为当前项目的通用工具配置,所有的请求都需要用到 request 对象
- 定义的接口必须导出才能让其他组件引用
页面数据与接口对接渲染数据
定义表头
我们查看原型,表格中的表格如下:

在 <t-table>
标签中有一个属性叫做 :columns
,它是一个数组,里面可以定义表头
为了方便管理,我们在 @/pages/serve/plan/project/
文件夹下创建一个 constants.ts
文件
export const COLUMNS = [
{
title: '序号',
align: 'left',
width: 100,
minWidth: 100,
colKey: 'rowIndex'
},
{
title: '护理图片',
width: 150,
minWidth: '150px',
colKey: 'image'
},
{
title: '护理项目名称',
minWidth: '150px',
colKey: 'name'
},
{
title: '价格(元)',
minWidth: '160px',
colKey: 'price'
},
{
title: '单位',
minWidth: '150px',
colKey: 'unit'
},
{
title: '排序',
minWidth: '150px',
colKey: 'orderNo'
},
{
title: '创建人',
minWidth: '200px',
colKey: 'creator'
},
{
title: '创建时间',
minWidth: '180px',
colKey: 'createTime'
},
{
title: '状态',
colKey: 'status',
width: 120,
minWidth: '120px',
cell: (h, { row }) => {
const statusList = {
1: {
label: '启用'
},
0: {
label: '禁用'
}
}
return h(
'span',
{
class: `status-dot status-dot-${row.status}`
},
statusList[row.status].label
)
}
},
{
align: 'left',
fixed: 'right',
width: 154,
minWidth: '154px',
colKey: 'op',
title: '操作'
}
]
然后在 index.vue 中删除掉关于 columns 定义的属性,然后引入我们刚才定义的 constants.ts 文件
import { COLUMNS } from './constants'
修改 <t-table :columns="COLUMNS">
让标签中的 :columns
指向我们引入的 COLUMNS
静态数据展示
我们打开 knife4j 在线接口文档,找到护理项目接口,做一个测试,获得到 json 相关的数据

获取 json 之后,我们可以修改 index.vue 中的 for 循环,模拟真实的接口数据
const total = 16
for (let i = 0; i < total; i++) {
data.push({
index: i + 1,
createTime: '2023-07-11 15:24:52',
updateTime: '2023-07-18 14:41:51',
creator: '张三李四王五',
name: '喂饭',
orderNo: 1,
unit: '次',
price: 20,
image:
'https://yjy-slwl-oss.oss-cn-hangzhou.aliyuncs.com/2a6ababd-6f0d-4e07-9dff-ca34123912dc.png',
status: 1,
count: 0
})
}
保存查看效果如下:

这样就可以展示真实的数据了
添加样式
目前前端开发人员已经对页面样式进行了修复,在 table 标签外部,添加以下三个 div,来修饰样式
<template>
<div class="min-h serveProject bg-wt">
<div class="baseList">
<div class="tableBoxs">
<t-tabel>
<!-- 其他代码省略 -->
</t-tabel>
</div>
</div>
</div>
</template>
图片预览处理
目前在列表中展示的是图片的路径,我们的需求是,需要展示小图,并且可以预览图片(大图)
这个在 TDesign 组件中已经提供了
网址:https://tdesign.tencent.com/vue-next/components/image-viewer
我们在 <t-table></t-table>
标签对内处理图片的展示,代码如下:
<t-table>
<!-- 图片预览及展示 -->
<template #image="{ row }">
<div class="tdesign-demo-image-viewer__base">
<t-image-viewer :images="[row.image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image">
<img
alt="test"
:src="row.image"
class="tdesign-demo-image-viewer__ui-image--img"
/>
<div
class="tdesign-demo-image-viewer__ui-image--hover"
@click="open"
>
<span><BrowseIcon size="1.4em" /> 预览</span>
</div>
</div>
</template>
</t-image-viewer>
</div>
</template>
</t-table>
价格展示处理
在列表展示的价格需要保留小数点后两位,如果是整数,需要补零
我们同样在 <t-table></t-table>
标签对内处理字段的展示,代码如下:
<t-table>
<!-- 价格拼接 -->
<template #price="{ row }">
{{ isDecimals(row.price) ? row.price : row.price + '.00' }}
</template>
</t-table>
<script setup lang="ts">
//在js代码中添加isDecimals方法,判断当前字段是否包含了小数点
const isDecimals = (val) => {
if (String(val).indexOf('.') > -1) {
return true
}
return false
}
</script>
操作按钮处理
在 table 的最右侧,有三个按钮,我们先来看效果

分别代表了不同的操作,我们目前先处理显示的问题,后期我们再做具体的操作
我们同样在 <t-table></t-table>
标签对内处理字段的展示,代码如下:
<t-table>
<!-- 操作栏 -->
<template #op="{ row }">
<div class="operateCon">
<a
class="btn-dl"
>删除</a>
<a
class="font-bt"
>编辑</a>
<a
class="delete"
>禁用</a>
</div>
</template>
</t-table>
标签中的样式,在提供的前端工程中已经包含,大家直接使用即可
接口调用,并调试分页组件
把刚才我们定义的接口先引入到 index.vue 中,然后编写调用接口的方法,代码如下:
<template>
<!-- 当数据为空需要占位时,会显示 cellEmptyContent -->
<t-table
:row-key="rowKey"
:data="data"
:columns="COLUMNS"
vertical-align="middle"
:hover="hover"
:loading="dataLoading"
tabel-content-width="100%"
table-layout="fixed"
>
<!-- 价格拼接 -->
<template #price="{ row }">
{{ isDecimals(row.price) ? row.price : row.price + '.00' }}
</template>
<!-- 图片预览及展示 -->
<template #image="{ row }">
<div class="tdesign-demo-image-viewer__base">
<t-image-viewer :images="[row.image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image">
<img
alt="test"
:src="row.image"
class="tdesign-demo-image-viewer__ui-image--img"
/>
<div
class="tdesign-demo-image-viewer__ui-image--hover"
@click="open"
>
<span><BrowseIcon size="1.4em" /> 预览</span>
</div>
</div>
</template>
</t-image-viewer>
</div>
</template>
<!-- 操作栏 -->
<template #op="{ row }">
<div class="operateCon">
<a class="btn-dl">删除</a>
<a class="font-bt">编辑</a>
<a class="delete">禁用</a>
</div>
</template>
</t-table>
<!-- 分页 -->
<t-pagination
v-if="total > 10"
v-model="pagination.pageNum"
v-model:pageSize="pagination.pageSize"
:total="total"
@change="onPageChange"
/>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { COLUMNS } from './constants'
import { getProjectList } from '@/api/serve'
var data = ref([])
var total = ref(0)
const dataLoading = ref(false) // 加载中
//分页对象
const pagination = ref({
pageSize: 10,
pageNum: 1
})
//生命周期
onMounted(() => {
getList()
})
//获取列表数据
const getList = async () => {
const res = await getProjectList(pagination.value)
data.value = res.data.records
total.value = Number(res.data.total)
}
// 翻页设置当前页
const onPageChange = (val) => {
pagination.value.pageNum = val.current
pagination.value.pageSize = val.pageSize
getList()
}
const isDecimals = (val) => {
if (String(val).indexOf('.') > -1) {
return true
}
return false
}
</script>
我们现在,可以刷新网页,就可以看到跟原型图和 UI 设计稿几乎一样的效果了

序号处理
但是我们发现,在上述的效果展示中,没有序号了
因为我们之前在使用 for 循环模拟数据的时候是给了设置了一个 index 字段的,但是我们查询的接口中并没有这个字段,现在我们可以使用前端 TDesign 中的表格属性 rowIndex 解决,代码如下:
我们同样在 <t-table></t-table>
标签对内处理字段的展示,代码如下:
<template>
<t-table>
<!-- 序号 -->
<template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template>
</t-table>
</template>
效果如下:

最终代码:
<template>
<div class="min-h serveProject bg-wt">
<div class="baseList">
<div class="tableBoxs">
<!-- 当数据为空需要占位时,会显示 cellEmptyContent -->
<t-table
:row-key="rowKey"
:data="data"
:columns="COLUMNS"
vertical-align="middle"
:hover="hover"
:loading="dataLoading"
tabel-content-width="100%"
table-layout="fixed"
>
<!-- 序号 -->
<template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template>
<!-- 图片预览 -->
<template #image="{ row }">
<div>
<div class="tdesign-demo-image-viewer__base">
<t-image-viewer :images="[row.image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image">
<img alt="test" :src="row.image" class="tdesign-demo-image-viewer__ui-image--img" />
<div class="tdesign-demo-image-viewer__ui-image--hover" @click="open">
<span>
<BrowseIcon size="1.4em" /> 预览
</span>
</div>
</div>
</template>
</t-image-viewer>
</div>
</div>
</template>
<!-- 价格拼接 -->
<template #price="{ row }">
{{ isDecimals(row.price) ? row.price : row.price + '.00' }}
</template>
<!-- 按钮处理 -->
<template #op="{ row }">
<div class="operateCon">
<a class="btn-dl">删除</a>
<a class="font-bt">编辑</a>
<a class="delete">禁用</a>
</div>
</template>
</t-table>
<!-- 分页 -->
<t-pagination
v-if="total > 10"
v-model="pagination.pageNum"
v-model:pageSize="pagination.pageSize"
:total="total"
@change="onPageChange"
/>
</div>
</div>
</div>
</template>
<script setup lang="jsx">
import { ref,onMounted } from 'vue';
import { COLUMNS } from './constants'
import { getProjectList } from '@/api/serve'
const data = ref([]);
const total = ref(0);
const dataLoading = ref(false) // 加载中
const pagination = ref({
pageSize: 10,
pageNum: 1
})
//生命周期
onMounted(() => {
getList()
})
//调用接口
const getList = async () => {
const res = await getProjectList(pagination.value)
data.value = res.data.records
total.value = Number(res.data.total)
}
// 翻页设置当前页
const onPageChange = (val) => {
pagination.value.pageNum = val.current
pagination.value.pageSize = val.pageSize
getList()
}
//判断当前参数是否包含小数点
const isDecimals = (val) => {
if (String(val).indexOf('.') > -1) {
return true;
}
return false;
}
</script>