zoukankan      html  css  js  c++  java
  • 测试平台系列(23) 编写项目详情页面

    回顾

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

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

    设计项目页面

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

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

    设计用例路由

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

    配置config/routes.js

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

    编写后端接口

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

    • ProjectRoleDao.py中新增list_role方法

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

    • ProjectDao.py中新增query_project方法

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


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

    • 编写/project/query接口

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

    编写页面部分

    • 先看下大致效果:

    这边分了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编写更新项目的方法

    编写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'项目名称',
          requiredtrue,
          message'请输入项目名称',
          type'input',
          placeholder'请输入项目名称',
          componentnull,
        },
        {
          name'owner',
          label'项目负责人',
          requiredtrue,
          component: opt,
          type'select',
        },
        {
          name'description',
          label'项目描述',
          requiredfalse,
          message'请输入项目描述',
          type'textarea',
          placeholder'请输入项目描述',
        },
        {
          name'private',
          label'是否私有',
          requiredtrue,
          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.requiredmessage: 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数据取出,然后按照顺序解析成表单,最后留一个修改的按钮,执行保存操作。

    看下效果吧

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

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

    • 更新后

      可以看到变成了QQ三国
      可以看到变成了QQ三国

    今天的内容就到这里了,进度很慢,更新很慢。周末愉快,看RNG VS TES

    项目前端地址

    项目后端地址

  • 相关阅读:
    UVALive-3989 Ladies' Choice (稳定婚姻问题)
    UVA-11383 Golden Tiger Claw (KM算法)
    UVA-10816 Travel in Desert (最小瓶颈最短路)
    UVA-10369 Arctic Network (最小生成树)
    内核态信号量(todo)
    openBMC(todo)
    uboot on qemu
    install ubuntu iso on windows
    在qemu上运行linux
    todo:register_sysctl_table
  • 原文地址:https://www.cnblogs.com/we8fans/p/14932222.html
Copyright © 2011-2022 走看看