测试平台系列(103) 编写系统设置页面.md

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

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

# 回顾

上一节我们讲了完善个人资料功能,并能够利用oss对我们的用户头像进行管理。

还记得我们之前的configuration.json文件(系统配置文件)吗?

那这一节我们就来讲下系统配置功能,也就是能够在前端页面配置configuration.json。

# 回忆细节

之前我们是编写了配置管理功能的,虽然有更新的操作,但是没有实际的运用。

oss那块读取oss类型和账号密码,以及邮箱部分有用到。

有了实际的方法,那我们编写curd接口就简单很多。

# 实现

# 后端逻辑

  • 修改编辑部分

    编辑的地方,我们需要调整一下,写入json文件的时候,注意ensure_ascii这个参数的关闭,以及indent(缩进)。

  • 新增app/routers/config/system.py文件
from fastapi import Depends

from app.core.configuration import SystemConfiguration
from app.handler.fatcory import PityResponse
from app.routers import Permission
from app.routers.config.gconfig import router
from config import Config


@router.get("/system", description="获取系统配置")
def get_system_config(_=Depends(Permission(Config.ADMIN))):
    configuration = SystemConfiguration.get_config()
    return PityResponse.success(configuration)


@router.post("/system/update", description="更新系统配置")
def get_system_config(data: dict, _=Depends(Permission(Config.ADMIN))):
    SystemConfiguration.update_config(data)
    return PityResponse.success()

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

我们把路由写在config下,那么/config/system是获取配置接口,/config/system/update是修改接口。

最后我们在app/init.py把编写好的路由注册进去即可。

# 前端逻辑

  • 编写service层

分别编写好调用获取/更新的接口即可。

  • 编写model层

在models/gconfig.js里面新增2个方法,调用刚才service层编写的2个接口。

  • 编写view层

    首先我们将页面拆分为3个tab:

  1. oss
  2. email
  3. yapi

这样就对应3个子组件,先看下理想的效果图:

所以我们新建一个src/pages/Config/SystemConfig.jsx文件:

import {PageContainer} from "@ant-design/pro-layout";
import {Button, Card, Col, Form, message, Row, Spin, Tabs} from "antd";
import OssConfig from "@/components/System/OssConfig";
import EmailConfig from "@/components/System/EmailConfig";
import YapiConfig from "@/components/System/YapiConfig";
import {SaveOutlined} from "@ant-design/icons";
import {connect} from "umi";
import {useEffect} from "react";

const {TabPane} = Tabs;

const SystemConfig = ({dispatch, gconfig, loading}) => {

  const [form] = Form.useForm()
  const {configuration} = gconfig;

  const onSetField = () => {
    const {email, oss, yapi} = configuration;
    form.setFieldsValue(email);
    form.setFieldsValue(oss);
    form.setFieldsValue(yapi);
  }

  useEffect(() => {
    dispatch({
      type: 'gconfig/fetchSystemConfig'
    })
  }, [])

  useEffect(() => {
    // 这里批量变更表单数据
    onSetField()
  }, [configuration])

  const onSubmit = async () => {
    let values;
    try {
      values = await form.validateFields();
    } catch (e) {
      message.info("有必填字段未填写,请检查")
      return;
    }
    const requestData = {
      email: {
        "sender": values.sender,
        "password": values.password,
        "host": values.host,
        "to": values.to
      },
      yapi: {
        "token": values.token,
      },
      oss: {
        "oss_type": values.oss_type,
        "access_key_id": values.access_key_id,
        "access_key_secret": values.access_key_secret,
        "bucket": values.bucket,
        "endpoint": values.endpoint
      }
    }
    dispatch({
      type: "gconfig/updateConfiguration",
      payload: requestData,
    })
  }

  return (
    <PageContainer title="系统设置" breadcrumb={null}>
      <Spin spinning={loading.effects['gconfig/updateConfiguration'] || loading.effects['gconfig/fetchSystemConfig']}>
        <Card>
          <Row>
            <Col span={24}>
              <Tabs tabPosition="left">
                <TabPane key="1" tab="邮件设置" forceRender>
                  <EmailConfig form={form}/>
                </TabPane>
                <TabPane key="2" tab="OSS设置" forceRender>
                  <OssConfig form={form}/>
                </TabPane>
                <TabPane key="3" tab="Yapi设置" forceRender>
                  <YapiConfig form={form}/>
                </TabPane>
              </Tabs>
            </Col>
          </Row>
          <Row>
            <div style={{margin: '16px auto'}}>
              <Button type="primary" icon={<SaveOutlined/>} onClick={onSubmit}>保存</Button>
            </div>
          </Row>
        </Card>
      </Spin>
    </PageContainer>
  )
}

export default connect(({gconfig, loading}) => ({gconfig, loading}))(SystemConfig);

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

可以看到,结构很简单,主要是一个tabs加上具体tab页里面的内容,每个类型的tab又对应了不同的组件:

分别在src/components/System建立下面3个组件的jsx:

  • 邮件组件EmailConfig.jsx:
import {Col, Form, Input, Row} from "antd";
import {CONFIG} from "@/consts/config";

export default ({form}) => {
  return (
    <Row gutter={8}>
      <Col span={4}/>
      <Col span={16}>
        <Form form={form} {...CONFIG.LAYOUT}>
          <Form.Item label="发件人" name="sender" rules={[{required: true, message: '请输入发件人邮箱'}]}>
            <Input placeholder="请输入发件人邮箱"/>
          </Form.Item>
          <Form.Item label="host" name="host" rules={[{required: true, message: '请输入服务器host'}]}>
            <Input placeholder="请输入服务器host"/>
          </Form.Item>
          <Form.Item label="邮箱秘钥" name="password" rules={[{required: true, message: '请输入邮箱秘钥'}]}>
            <Input placeholder="请输入邮箱秘钥"/>
          </Form.Item>
          <Form.Item label="收件人名称" name="to" rules={[{required: true, message: '请输入收件人名称'}]}>
            <Input placeholder="请输入收件人名称"/>
          </Form.Item>
        </Form>
      </Col>
      <Col span={4}/>
    </Row>

  )
}
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
  • yapi组件YapiConfig.jsx
import {Button, Col, Form, Input, Row} from "antd";
import {CONFIG} from "@/consts/config";

export default ({form}) => {
  return (
    <Row gutter={8}>
      <Col span={4}/>
      <Col span={16}>
        <Form form={form} {...CONFIG.LAYOUT}>
          <Form.Item label="token" name="token">
            <Input placeholder="请输入yapi token"/>
          </Form.Item>
        </Form>
      </Col>
      <Col span={4}/>
    </Row>

  )
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • OssConfig.jsx
import {Col, Form, Input, Row, Select} from "antd";
import {CONFIG} from "@/consts/config";

export default ({form}) => {
  return (
    <Row gutter={8}>
      <Col span={4}/>
      <Col span={16}>
        <Form form={form} {...CONFIG.LAYOUT}>
          <Form.Item label="类型" name="oss_type" rules={[{required: true, message: '请选择oss类型'}]}>
            <Select placeholder="请选择oss类型">
              <Select.Option value="aliyun">阿里云</Select.Option>
              <Select.Option value="gitee">gitee</Select.Option>
              <Select.Option value="cos">腾讯云</Select.Option>
              <Select.Option value="qiniu">七牛云</Select.Option>
            </Select>
          </Form.Item>
          <Form.Item label="access_id" name="access_key_id" rules={[{required: true, message: '请输入access_key_id'}]}>
            <Input placeholder="请输入access_key_id"/>
          </Form.Item>
          <Form.Item label="access_secret" name="access_key_secret" rules={[{required: true, message: '请输入access_secret'}]}>
            <Input placeholder="请输入access_secret"/>
          </Form.Item>
          <Form.Item label="bucket" name="bucket" rules={[{required: true, message: '请输入bucket'}]}>
            <Input placeholder="请输入bucket"/>
          </Form.Item>
          <Form.Item label="endpoint" name="endpoint">
            <Input placeholder="请输入endpoint, 可不填"/>
          </Form.Item>
        </Form>
      </Col>
      <Col span={4}/>
    </Row>

  )
}

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
31
32
33
34
35
36
37

需要注意的是,保存按钮按下的时候,需要获取3个组件的数据,所以每个组件都接受了父组件的form

forceRender可以强制渲染每个tab页面,就算它没有被打开过。

# 最终效果图

# 结语

系统设置暂时只有这几块的功能,后续我们也许会有包括主题jira账号密码等的配置项。