測試平臺系列(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方法,並在修改後執行這個方法。
更新後
今天的內容就到這裡了,進度很慢,更新很慢。週末愉快,看RNG VS TES!