测试平台系列(23) 编写项目详情页面

简介: 编写项目详情页面

回顾


上一节我们以编写项目列表为例子,讲解了一个相对完整的demo,其实只完成了查询新增的功能,由于篇幅和时间的关系,这些笔者都会补全,但是可能不会完全讲解,所以大家可以对照代码查看对应的代码模块。

这一节我们开始设计项目的详情页面。

设计项目页面


项目列表页面,我们只能看到项目的缩略,如果我们点进去项目的话,是需要能够看到这个项目的具体信息的。所以我们设计三个板块,以tab的形式展示:

  • 用例树
  • 成员列表
  • 项目设置

设计用例路由


在antd pro里面支持参数路由,举个例子,我们针对不同的项目要展示不同的内容,这里就要用到参数路由了。举个例子,当项目id是1的时候,我的路由可能是/project/1

配置config/routes.js


1.jpg

image

我们创建了这样一个参数路由,并把hideInMenu设置为true,也就是说不显示于左侧菜单栏。同时,这个路由对应的是ProjectDetail组件。

编写后端接口

我们目前只有一个查询项目列表的接口,但是我们现在是没有用例树的,所以暂时这个项目只获取到项目信息和项目角色。

  • ProjectRoleDao.py中新增list_role方法

2.jpg

image

通过project_id去获取这个项目的所有角色列表

  • ProjectDao.py中新增query_project方法

3.jpg

image

先获取到项目详情,然后获取项目角色,这边的话笔者是没有用join或者子查询的,因为感觉sqlalchemy用起来不是很方便,大家也可以自由发挥


注意,笔者会返回很多err或者None(因为可能受到了go写法的影响,这里大家可以自己按照自己的方式去写)

  • 编写/project/query接口

4.jpg

image

这部很简单,老规矩先挂上权限和路由的装饰器,接着对project_id进行参数检查,然后生成一个空的dict,把role和project信息查询出来以后写入result。

编写页面部分


  • 先看下大致效果:

5.jpg

image

这边分了3个tab,第一个是用例列表,到时候会呈现一个用例树,左侧呢会根据用例的tag/用例的级别去展示该项目下的用例,右边呢则是用例的具体信息。

成员列表会显示这个项目下的成员,页面参考Yapi

项目设置可以让用户对项目的基础信息进行一个更改,大概的页面功能模块是这样。

可以看到最终效果里面是没有具体的成员列表和项目设置的,我们先完成一个空壳,后续再进行补充。

编写ProjectDetail.jsx



import React, { useEffect, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Avatar, Card, Tabs } from 'antd';
import { useParams } from 'umi';
import { process } from '@/utils/utils';
import { queryProject } from '@/services/project';
import auth from '@/utils/auth';
const { TabPane } = Tabs;
export default () => {
  const params = useParams();
  const projectId = params.id;
  const [projectData, setProjectData] = useState({});
  const [roles, setRoles] = useState([]);
  const fetchData = async () => {
    const res = await queryProject({ projectId });
    if (auth.response(res)) {
      setProjectData(res.data.project);
      setRoles(res.data.role);
    }
  };
  useEffect(async () => {
    await process(fetchData);
  }, []);
  return (
    <PageContainer title={<span>
      <Avatar
        style={{ backgroundColor: '#87d068' }}>{projectData.name === undefined ? 'loading...' : projectData.name.slice(0, 2)}</Avatar>{projectData.name}</span>}>
      <Card>
        <Tabs defaultActiveKey='1'>
          <TabPane tab='用例列表' key='1'>
            这里没有用例,暂时替代一下
          </TabPane>
          <TabPane tab='成员列表' key='2'>
            {/* <ProjectRole /> */}
          </TabPane>
          <TabPane tab='项目设置' key='3'>
            {/* <ProjectInfo data={projectData} /> */}
          </TabPane>
        </Tabs>
      </Card>
    </PageContainer>
  );
};

代码很简短,其中设置了projectData和roles2个字段(用来存放项目信息和角色列表),然后组件加载的时候会去请求一下查询项目的接口,projectId我们可以通过useParams hook获取:


const params = useParams();
const projectId = params.id;

剩下的"html"部分很简单了,就是标准的PageContainer+卡片的组合,然后里面嵌入了3个tab。

完善编辑项目功能


可以看到上面有被注释掉的ProjectInfo组件,这个是我们用来修改项目信息的,我们这就来完善它!

编写后端接口


  • ProjectDap.py添加update_project方法


@staticmethod
    def update_project(user, role, project_id, name, owner, private, description):
        try:
            data = Project.query.filter_by(id=project_id, deleted_at=None).first()
            if data is None:
                return "项目不存在"
            data.name = name
            # 如果修改人不是owner或者超管
            if data.owner != owner and (role < pity.config.get("ADMIN") or user != data.owner):
                return "您没有权限修改项目负责人"
            data.owner = owner
            data.private = private
            data.description = description
            data.updated_at = datetime.now()
            data.update_user = user
            db.session.commit()
        except Exception as e:
            ProjectDao.log.error(f"编辑项目: {name}失败, {e}")
            return f"编辑项目: {name}失败, {e}"
        return None

这里值得注意的地方是,我们只有项目负责人超级管理员可以编辑项目,所以一旦owner发生变更,则需要对权限做一个判断。最后就是记得更改更新时间更新人

  • 编写/project/update接口


@pr.route("/update", methods=["POST"])
@permission()
def update_project(user_info):
    try:
        user_id, role = user_info["id"], user_info["role"]
        data = request.get_json()
        if data.get("id") is None:
            return jsonify(dict(code=101, msg="项目id不能为空"))
        if not data.get("name") or not data.get("owner"):
            return jsonify(dict(code=101, msg="项目名称/项目负责人不能为空"))
        private = data.get("private", False)
        err = ProjectDao.update_project(user_id, role, data.get("id"), data.get("name"), data.get("owner"), private,
                                        data.get("description", ""))
        if err is not None:
            return jsonify(dict(code=110, msg=err))
        return jsonify(dict(code=0, msg="操作成功"))
    except Exception as e:
        return jsonify(dict(code=111, msg=str(e)))

这边同样也先校验参数,然后调用update_project方法。

src/services/project.js编写更新项目的方法

6.jpg

image

编写ProjectInfo.jsx


import React, { useEffect, useState } from 'react';
import { Row, Col, Select, Tooltip } from 'antd';
import CustomForm from '@/components/PityForm/CustomForm';
import { listUsers } from '@/services/user';
import { updateProject } from '@/services/project';
import auth from '@/utils/auth';
const { Option } = Select;
export default ({ data }) => {
  const [users, setUsers] = useState([]);
  const fetchUsers = async () => {
    const res = await listUsers();
    setUsers(res);
  };
  useEffect(async () => {
    await fetchUsers();
  }, []);
  const onFinish = async (values) => {
    const project = {
      ...data,
      ...values,
    };
    const res = await updateProject(project);
    auth.response(res, true);
  };
  const opt = <Select placeholder='请选择项目组长'>
    {
      users.map(item => <Option key={item.value} value={item.id}><Tooltip
        title={item.email}>{item.name}</Tooltip></Option>)
    }
  </Select>;
  const fields = [
    {
      name: 'name',
      label: '项目名称',
      required: true,
      message: '请输入项目名称',
      type: 'input',
      placeholder: '请输入项目名称',
      component: null,
    },
    {
      name: 'owner',
      label: '项目负责人',
      required: true,
      component: opt,
      type: 'select',
    },
    {
      name: 'description',
      label: '项目描述',
      required: false,
      message: '请输入项目描述',
      type: 'textarea',
      placeholder: '请输入项目描述',
    },
    {
      name: 'private',
      label: '是否私有',
      required: true,
      message: '请选择项目是否私有',
      type: 'switch',
      valuePropName: 'checked',
    },
  ];
  return (
    <Row gutter={8}>
      <Col span={24}>
        <CustomForm left={6} right={18} record={data} onFinish={onFinish} fields={fields} />
      </Col>
    </Row>
  );
}

其实这里fields和之前创建项目的fields重复定义了,等于存放了2份,但是这里我图方便就没有抽出来,因为怕以后这里有什么变化(说白了就是懒,但是千万别和我一样,能封装的还是封装)

然后在组件加载的时候会获取所有用户(因为我们需要修改组员),但是我突然想到,角色列表也会获取组员身份,所以我们把user的获取放到最外层,也就是Project层,这里就不多展示了,详细可看源码。

CustomForm是自己封装的一套通用表单,里面也是解析fields然后展示表单:


import { Button, Col, Form, Row, Tooltip, Upload } from 'antd';
import React from 'react';
import ProjectAvatar from '@/components/Project/ProjectAvatar';
import { SaveOutlined } from '@ant-design/icons';
import getComponent from './index';
const {Item: FormItem} = Form;
export default ({left, right, formName, record, onFinish, fields, dispatch}) => {
  const [form] = Form.useForm();
  const layout = {
    labelCol: {span: left},
    wrapperCol: {span: right},
  }
  return (
    <Form
      form={form}
      {...layout}
      name={formName}
      initialValues={record}
      onFinish={onFinish}
    >
      <Row>
        <Col span={6}/>
        <Col span={12} style={{textAlign: 'center'}}>
          <Tooltip title="点击可修改头像" placement="rightTop">
            <Upload customRequest={async fileData => {
              await dispatch({
                type: 'project/uploadFile',
                payload: {
                  file: fileData.file,
                  project_id: record.id,
                }
              })
            }} fileList={[]}>
              <Row style={{textAlign: 'center', marginBottom: 16}}>
                <ProjectAvatar data={record}/>
              </Row>
            </Upload>
          </Tooltip>
        </Col>
        <Col span={6}/>
      </Row>
      {
        fields.map(item => <Row>
          <Col span={6}/>
          <Col span={12}>
            <FormItem label={item.label} colon={item.colon || true}
                      rules={
                        [{required: item.required, message: item.message}]
                      } name={item.name} valuePropName={item.valuePropName || 'value'}
            >
              {getComponent(item.type, item.placeholder, item.component)}
            </FormItem>
          </Col>
          <Col span={6}/>
        </Row>)
      }
      <Row>
        <Col span={6}/>
        <Col span={12} style={{textAlign: 'center'}}>
          <FormItem {...{
            labelCol: {span: 0},
            wrapperCol: {span: 24},
          }}>
            <Button htmlType="submit" type="primary"><SaveOutlined/>修改</Button>
          </FormItem>
        </Col>
        <Col span={6}/>
      </Row>
    </Form>
  )
}

大致就是把fields里面的json数据取出,然后按照顺序解析成表单,最后留一个修改的按钮,执行保存操作。

看下效果吧


7.PNG

image

这里可以看到最上方的项目名称还没有进行更改,所以我们需要重新获取下项目数据。

8.jpg

image

要做的就是传入fetchData方法,并在修改后执行这个方法。

  • 更新后
    9.PNG
    可以看到变成了QQ三国



相关文章
|
8天前
|
人工智能 供应链 安全
AI辅助安全测试案例某电商-供应链平台平台安全漏洞
【11月更文挑战第13天】该案例介绍了一家电商供应链平台如何利用AI技术进行全面的安全测试,包括网络、应用和数据安全层面,发现了多个潜在漏洞,并采取了有效的修复措施,提升了平台的整体安全性。
|
18天前
|
网络协议 关系型数据库 应用服务中间件
【项目场景】请求数据时测试环境比生产环境多花了1秒是怎么回事?
这是一位粉丝(谢同学)给V哥的留言,描述了他在优化系统查询时遇到的问题:测试环境优化达标,但生产环境响应时间多出1秒。通过抓包分析,发现MySQL请求和响应之间存在500毫秒的延迟,怀疑是网络传输开销。V哥给出了以下优化建议:
|
18天前
|
监控 安全 测试技术
构建高效的精准测试平台:设计与实现指南
在软件开发过程中,精准测试是确保产品质量和性能的关键环节。一个精准的测试平台能够自动化测试流程,提高测试效率,缩短测试周期,并提供准确的测试结果。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
82 1
|
1月前
|
测试技术
自动化测试项目学习笔记(五):Pytest结合allure生成测试报告以及重构项目
本文介绍了如何使用Pytest和Allure生成自动化测试报告。通过安装allure-pytest和配置环境,可以生成包含用例描述、步骤、等级等详细信息的美观报告。文章还提供了代码示例和运行指南,以及重构项目时的注意事项。
207 1
自动化测试项目学习笔记(五):Pytest结合allure生成测试报告以及重构项目
|
1月前
|
人工智能 监控 测试技术
云应用开发平台测试
云应用开发平台测试
53 2
|
18天前
|
监控 安全 测试技术
构建高效精准测试平台:设计与实现全攻略
在软件开发过程中,精准测试是确保产品质量的关键环节。一个高效、精准的测试平台能够自动化测试流程,提高测试覆盖率,缩短测试周期。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
43 0
|
1月前
|
测试技术 Python
自动化测试项目学习笔记(四):Pytest介绍和使用
本文是关于自动化测试框架Pytest的介绍和使用。Pytest是一个功能丰富的Python测试工具,支持参数化、多种测试类型,并拥有众多第三方插件。文章讲解了Pytest的编写规则、命令行参数、执行测试、参数化处理以及如何使用fixture实现测试用例间的调用。此外,还提供了pytest.ini配置文件示例。
34 2
|
1月前
|
测试技术 Python
自动化测试项目学习笔记(二):学习各种setup、tearDown、断言方法
本文主要介绍了自动化测试中setup、teardown、断言方法的使用,以及unittest框架中setUp、tearDown、setUpClass和tearDownClass的区别和应用。
65 0
自动化测试项目学习笔记(二):学习各种setup、tearDown、断言方法
|
1月前
|
测试技术 数据安全/隐私保护
北邮人论坛登录页面测试用例
北邮人论坛登录页面测试用例
37 1
|
1月前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
143 0
下一篇
无影云桌面