大家好~我是米洛

我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持。

欢迎关注我的公众号米洛的测开日记,获取最新文章教程!

回顾

上一节咱们编写好了操作日志相关功能,这一节我们就来聊聊怎么展示它,展示操作日志是一部分,个人主页也是另一部分。

当我们脑海里没有类似的蓝图时,可以借鉴下其他web的效果。比如掘金(注意这不是硬广):

测试平台系列(100) 完善个人主页 - 图1

可以看到他大致分成了3块:

  • 用户信息,你是谁,你的简介,联系方式等
  • 个人动态
  • 个人成就以及粉丝等数据
    考虑到成就和粉丝这个概念我们内部可能不太实用,所以我们以个人资料和个人动态为主题,构建我们的个人首页。
    参考一家肯定不够的,我们看看github:

测试平台系列(100) 完善个人主页 - 图2

这个热力图动态是我们想要展示的。所以我们采用左侧个人信息+右侧热力图/动态的方式展示个人页。

先来看看最终效果:

测试平台系列(100) 完善个人主页 - 图3

编写获取操作记录的接口

  • 根据时间获取用户的操作数据(对应热力图)
    修改PityOperationDao.py
  1. from sqlalchemy import func, select
  2. from app.crud import Mapper
  3. from app.models import async_session
  4. from app.models.operation_log import PityOperationLog
  5. from app.utils.decorator import dao
  6. from app.utils.logger import Log
  7. @dao(PityOperationLog, Log("PityOperationDao"))
  8. class PityOperationDao(Mapper):
  9. @classmethod
  10. async def count_user_activities(cls, user_id, start_time: str, end_time: str):
  11. """
  12. 根据开始/结束时间 获取用户的活动日历(操作记录的数量)
  13. :param user_id:
  14. :param start_time:
  15. :param end_time:
  16. :return:
  17. """
  18. async with async_session() as session:
  19. async with session.begin():
  20. format_date = func.date_format(PityOperationLog.operate_time, "%Y-%m-%d")
  21. sql = select(format_date, func.count(PityOperationLog.id)).where(
  22. PityOperationLog.operate_time.between(start_time, end_time),
  23. PityOperationLog.user_id == user_id) \
  24. .group_by(format_date).order_by(format_date)
  25. data = await session.execute(sql)
  26. return data.all()

这里的sqlalchemy语句比较复杂,注意2个点即可:

  1. func

这里的func是时间转换的func,我们操作记录存放的是具体的日期+时间,但我们查询用户操作一般是按日期来,所以我们需要转换下:

func.date_format干的就是这个事情,为了group_by。

这个sql就是筛选出user_id等于要查询用户,并且时间在指定日期之内的数据,通过group_by拿到每一天的操作次数,最后根据日期排序后返回。

接着创建app/router/operation/operation_log.py

  1. from fastapi import APIRouter, Depends
  2. from sqlalchemy import desc
  3. from app.crud.operation.PityOperationDao import PityOperationDao
  4. from app.handler.fatcory import PityResponse
  5. from app.models.operation_log import PityOperationLog
  6. from app.routers import Permission
  7. router = APIRouter(prefix="/operation")
  8. # 获取用户操作记录热力图以及参与的项目数量
  9. @router.get("/count")
  10. async def list_user_activities(user_id: int, start_time: str, end_time: str, _=Depends(Permission())):
  11. try:
  12. records = await PityOperationDao.count_user_activities(user_id, start_time, end_time)
  13. ans = list()
  14. for r in records:
  15. # 解包日期和数量
  16. date, count = r
  17. ans.append(dict(date=date, count=count))
  18. return PityResponse.success(ans)
  19. except Exception as e:
  20. return PityResponse.failed(e)

获取到数据后,把日期和date组合成json数组。

  • 编写获取用户操作日志详情的接口
  1. # 获取用户操作记录
  2. @router.get("/list")
  3. async def list_user_operation(start_time: str, end_time: str, user_id: int, tag: str = None, _=Depends(Permission())):
  4. try:
  5. records = await PityOperationDao.list_record(user_id=user_id, tag=tag, condition=[
  6. PityOperationLog.operate_time.between(start_time, end_time)], desc=[desc(PityOperationLog.operate_time)])
  7. return PityResponse.records(records)
  8. except Exception as e:
  9. return PityResponse.failed(e)

调用Mapper自带的list_record方法,根据条件筛选出对应的数据并返回。这把在PityResponse里封装了一个records方法:

  1. @staticmethod
  2. def records(data: list, code=0, msg="操作成功"):
  3. return dict(code=code, msg=msg, data=PityResponse.model_to_list(data))

这样就不用每次都model_to_list了。

前端页面

  • 在route.js加入member路由,不在菜单里展示

测试平台系列(100) 完善个人主页 - 图4

  • 接着是编写UserInfo.jsx
    代码我就不贴了,github都有详细的,有兴趣的可以拉下来看看。
    大概也就是根据接口的返回,生成对应的动态和个人资料页面。

封装个人用户组件

  1. import React from 'react';
  2. import {Avatar} from "antd";
  3. import styles from "@/components/GlobalHeader/index.less";
  4. import {CONFIG} from "@/consts/config";
  5. import {Tooltip} from "antd";
  6. export default ({user, size = 24, marginLeft = 4}) => {
  7. if (user === undefined) {
  8. return '加载中...'
  9. }
  10. // 是否和左边有距离,有的话则为2
  11. return (
  12. <>
  13. <Avatar size={size} className={styles.avatar}
  14. src={user.avatar || `${CONFIG.AVATAR_URL}${user.name}`} alt="avatar"/>
  15. <Tooltip title="点击可查看用户资料">
  16. <a style={{marginLeft: marginLeft}} href={`/#/member/${user.id}`} target="_blank"
  17. rel="noreferrer">{user.name}</a>
  18. </Tooltip>
  19. </>
  20. )
  21. }

这个组件接受用户信息,和头像尺寸。为啥要这个组件呢?

因为我们的用户资料页面没有提供入口,也就是说菜单里找不到。再者,我们需要把用户名改为头像+用户名,这样看起来更nice。

看看实战效果:

测试平台系列(100) 完善个人主页 - 图5

点击链接就可以到个人资料了。

今天的内容就说到这里了,希望对大家有帮助。