1. 程式人生 > 其它 >測試平臺系列(23) 編寫專案詳情頁面

測試平臺系列(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

importReact,{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';
importauthfrom'@/utils/auth';

const{TabPane}=Tabs;


exportdefault()=>{
constparams=useParams();
constprojectId=params.id;
const[projectData,setProjectData]=useState({});
const[roles,setRoles]=useState([]);

constfetchData=async()=>{
constres=awaitqueryProject({projectId});
if(auth.response(res)){
setProjectData(res.data.project);
setRoles(res.data.role);
}
};

useEffect(async()=>{
awaitprocess(fetchData);
},[]);


return(
<PageContainertitle={<span>
<Avatar
style={{backgroundColor:'#87d068'}}>
{projectData.name===undefined?'loading...':projectData.name.slice(0,2)}</Avatar>{projectData.name}</span>}>
<Card>
<TabsdefaultActiveKey='1'>
<TabPanetab='用例列表'key='1'>
這裡沒有用例,暫時替代一下
</TabPane>
<TabPanetab='成員列表'key='2'>
{/*<ProjectRole/>*/}
</TabPane>
<TabPanetab='專案設定'key='3'>
{/*<ProjectInfodata={projectData}/>*/}
</TabPane>
</Tabs>
</Card>
</PageContainer>

);
};

程式碼很簡短,其中設定了projectData和roles2個欄位(用來存放專案資訊和角色列表),然後元件載入的時候會去請求一下查詢專案的介面,projectId我們可以通過useParams hook獲取:

constparams=useParams();
constprojectId=params.id;

剩下的"html"部分很簡單了,就是標準的PageContainer+卡片的組合,然後裡面嵌入了3個tab。

完善編輯專案功能

可以看到上面有被註釋掉的ProjectInfo元件,這個是我們用來修改專案資訊的,我們這就來完善它!

編寫後端介面

  • ProjectDap.py新增update_project方法
@staticmethod
defupdate_project(user,role,project_id,name,owner,private,description):
try:
data=Project.query.filter_by(id=project_id,deleted_at=None).first()
ifdataisNone:
return"專案不存在"
data.name=name
#如果修改人不是owner或者超管
ifdata.owner!=ownerand(role<pity.config.get("ADMIN")oruser!=data.owner):
return"您沒有許可權修改專案負責人"
data.owner=owner
data.private=private
data.description=description
data.updated_at=datetime.now()
data.update_user=user
db.session.commit()
exceptExceptionase:
ProjectDao.log.error(f"編輯專案:{name}失敗,{e}")
returnf"編輯專案:{name}失敗,{e}"
returnNone

這裡值得注意的地方是,我們只有專案負責人超級管理員可以編輯專案,所以一旦owner發生變更,則需要對許可權做一個判斷。最後就是記得更改更新時間更新人

  • 編寫/project/update介面
@pr.route("/update",methods=["POST"])
@permission()
defupdate_project(user_info):
try:
user_id,role=user_info["id"],user_info["role"]
data=request.get_json()
ifdata.get("id")isNone:
returnjsonify(dict(code=101,msg="專案id不能為空"))
ifnotdata.get("name")ornotdata.get("owner"):
returnjsonify(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",""))
iferrisnotNone:
returnjsonify(dict(code=110,msg=err))
returnjsonify(dict(code=0,msg="操作成功"))
exceptExceptionase:
returnjsonify(dict(code=111,msg=str(e)))

這邊同樣也先校驗引數,然後呼叫update_project方法。

src/services/project.js編寫更新專案的方法

編寫ProjectInfo.jsx

importReact,{useEffect,useState}from'react';
import{Row,Col,Select,Tooltip}from'antd';
importCustomFormfrom'@/components/PityForm/CustomForm';
import{listUsers}from'@/services/user';
import{updateProject}from'@/services/project';
importauthfrom'@/utils/auth';


const{Option}=Select;

exportdefault({data})=>{

const[users,setUsers]=useState([]);

constfetchUsers=async()=>{
constres=awaitlistUsers();
setUsers(res);
};

useEffect(async()=>{
awaitfetchUsers();
},[]);


constonFinish=async(values)=>{
constproject={
...data,
...values,
};
constres=awaitupdateProject(project);
auth.response(res,true);
};

constopt=<Selectplaceholder='請選擇專案組長'>
{
users.map(item=><Optionkey={item.value}value={item.id}><Tooltip
title={item.email}>
{item.name}</Tooltip></Option>)
}
</Select>
;

constfields=[
{
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(
<Rowgutter={8}>
<Colspan={24}>
<CustomFormleft={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';
importReactfrom'react';
importProjectAvatarfrom'@/components/Project/ProjectAvatar';
import{SaveOutlined}from'@ant-design/icons';


importgetComponentfrom'./index';

const{Item:FormItem}=Form;

exportdefault({left,right,formName,record,onFinish,fields,dispatch})=>{
const[form]=Form.useForm();
constlayout={
labelCol:{span:left},
wrapperCol:{span:right},
}

return(
<Form
form={form}
{...layout}
name={formName}
initialValues={record}
onFinish={onFinish}
>

<Row>
<Colspan={6}/>
<Colspan={12}style={{textAlign:'center'}}>
<Tooltiptitle="點選可修改頭像"placement="rightTop">
<UploadcustomRequest={asyncfileData=>{
awaitdispatch({
type:'project/uploadFile',
payload:{
file:fileData.file,
project_id:record.id,
}
})
}}fileList={[]}>
<Rowstyle={{textAlign:'center',marginBottom:16}}>
<ProjectAvatardata={record}/>
</Row>
</Upload>
</Tooltip>
</Col>
<Colspan={6}/>
</Row>
{
fields.map(item=><Row>
<Colspan={6}/>
<Colspan={12}>
<FormItemlabel={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>
<Colspan={6}/>
</Row>)
}
<Row>
<Colspan={6}/>
<Colspan={12}style={{textAlign:'center'}}>
<FormItem{...{
labelCol:{span:0},
wrapperCol:{span:24},
}}>

<ButtonhtmlType="submit"type="primary"><SaveOutlined/>修改</Button>
</FormItem>
</Col>
<Colspan={6}/>
</Row>
</Form>

)
}

大致就是把fields裡面的json資料取出,然後按照順序解析成表單,最後留一個修改的按鈕,執行儲存操作。

看下效果吧

這裡可以看到最上方的專案名稱還沒有進行更改,所以我們需要重新獲取下專案資料。

要做的就是傳入fetchData方法,並在修改後執行這個方法。

  • 更新後

    可以看到變成了QQ三國

今天的內容就到這裡了,進度很慢,更新很慢。週末愉快,看RNG VS TES

專案前端地址

專案後端地址