测试平台系列(100) 完善个人主页.md

2022/6/13 测试平台接口测试FastApiPythonReact

大家好~我是米洛
我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持。
欢迎关注我的公众号米洛的测开日记,获取最新文章教程!

# 回顾

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

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

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

  • 用户信息,你是谁,你的简介,联系方式等

  • 个人动态

  • 个人成就以及粉丝等数据

    考虑到成就和粉丝这个概念我们内部可能不太实用,所以我们以个人资料和个人动态为主题,构建我们的个人首页。

    参考一家肯定不够的,我们看看github:

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

先来看看最终效果:

# 编写获取操作记录的接口

  • 根据时间获取用户的操作数据(对应热力图)

    修改PityOperationDao.py

from sqlalchemy import func, select

from app.crud import Mapper
from app.models import async_session
from app.models.operation_log import PityOperationLog
from app.utils.decorator import dao
from app.utils.logger import Log


@dao(PityOperationLog, Log("PityOperationDao"))
class PityOperationDao(Mapper):

    @classmethod
    async def count_user_activities(cls, user_id, start_time: str, end_time: str):
        """
        根据开始/结束时间 获取用户的活动日历(操作记录的数量)
        :param user_id:
        :param start_time:
        :param end_time:
        :return:
        """
        async with async_session() as session:
            async with session.begin():
                format_date = func.date_format(PityOperationLog.operate_time, "%Y-%m-%d")
                sql = select(format_date, func.count(PityOperationLog.id)).where(
                    PityOperationLog.operate_time.between(start_time, end_time),
                    PityOperationLog.user_id == user_id) \
                    .group_by(format_date).order_by(format_date)
                data = await session.execute(sql)
                return data.all()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

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

  1. func

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

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

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

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

from fastapi import APIRouter, Depends
from sqlalchemy import desc

from app.crud.operation.PityOperationDao import PityOperationDao
from app.handler.fatcory import PityResponse
from app.models.operation_log import PityOperationLog
from app.routers import Permission

router = APIRouter(prefix="/operation")


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

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

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

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

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

这样就不用每次都model_to_list了。

# 前端页面

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

  • 接着是编写UserInfo.jsx

    代码我就不贴了,github都有详细的,有兴趣的可以拉下来看看。

    大概也就是根据接口的返回,生成对应的动态和个人资料页面。

# 封装个人用户组件

import React from 'react';
import {Avatar} from "antd";
import styles from "@/components/GlobalHeader/index.less";
import {CONFIG} from "@/consts/config";
import {Tooltip} from "antd";

export default ({user, size = 24, marginLeft = 4}) => {
  if (user === undefined) {
    return '加载中...'
  }
  // 是否和左边有距离,有的话则为2
  return (
    <>
      <Avatar size={size} className={styles.avatar}
              src={user.avatar || `${CONFIG.AVATAR_URL}${user.name}`} alt="avatar"/>
      <Tooltip title="点击可查看用户资料">
        <a style={{marginLeft: marginLeft}} href={`/#/member/${user.id}`} target="_blank"
           rel="noreferrer">{user.name}</a>
      </Tooltip>
    </>
  )
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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

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

看看实战效果:

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

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