回顧
上一節(jié)已經(jīng)貼出了項(xiàng)目列表頁面的全部前端代碼杖挣,由于筆者前端也沒有深入學(xué)習(xí)肩榕,所以只能給大家大概講解一下吧。還是按照之前的幾個(gè)部分來說吧惩妇!
狀態(tài)管理
設(shè)置了如下變量:
-
data
存放當(dāng)前項(xiàng)目數(shù)組
-
pagination
項(xiàng)目分頁
-
visible
創(chuàng)建項(xiàng)目表單是否可見株汉,默認(rèn)是否
-
users
由于我們需要選擇項(xiàng)目管理員,所以需要一個(gè)userId => 用戶信息的映射歌殃。當(dāng)然這個(gè)接口我們暫時(shí)還沒有實(shí)現(xiàn)乔妈。
方法編寫
const fetchData = async (current=pagination.current, size=pagination.size) => {
await process(async ()=> {
const res = await listProject({page: current, size });
if (auth.response(res)) {
setData(res.data)
setPagination({...pagination, total: res.total})
}
});
}
這個(gè)方法是獲取項(xiàng)目列表的方法,current代表page氓皱,size代表分頁大小路召,如果不傳入則是pagination默認(rèn)的值勃刨。
listProject其實(shí)就是包裝的request方法,請(qǐng)求后端服務(wù)股淡。
useEffect(async () => {
await fetchData();
}, [])
const onSearchProject = async projectName => {
await process(async ()=> {
const res = await listProject({page: 1, size: pagination.size, name: projectName});
if (auth.response(res)) {
setData(res.data)
setPagination({...pagination, current: 1, total: res.total})
}
});
}
useEffect是react
新版本的特性身隐,它支持2個(gè)參數(shù),第一個(gè)是方法揣非,第二個(gè)是變量數(shù)組抡医,傳入空數(shù)組的話,則每次這個(gè)組件開始渲染
的時(shí)候會(huì)調(diào)用且只調(diào)用一次方法早敬。
我們?nèi)绻绞褂玫脑捈缮担菚?huì)有多套測試環(huán)境的。如果我們對(duì)項(xiàng)目做了環(huán)境
的區(qū)分搞监,那么就應(yīng)該在切換環(huán)境的時(shí)候水孩,獲取不同環(huán)境的項(xiàng)目。
假設(shè)我們需要實(shí)現(xiàn)這種功能琐驴,那么我們先創(chuàng)建一個(gè)env
的變量俘种,然后改寫useEffect:
useEffect(async () => {
await fetchData();
}, [env])
這樣的話,每當(dāng)env發(fā)生變化绝淡,這個(gè)方法就會(huì)自動(dòng)執(zhí)行一次宙刘。需要說明的是,這里只是介紹一下useEffect的使用方式牢酵,因?yàn)槲覀冞@邊暫時(shí)還沒有擴(kuò)展到多套環(huán)境悬包,所以此處我們選擇[]即可
。
const onHandleCreate = async values => {
const res = await insertProject(values);
if (auth.response(res, true)) {
// 創(chuàng)建成功后自動(dòng)獲取第一頁的數(shù)據(jù), 因?yàn)轫?xiàng)目會(huì)按創(chuàng)建時(shí)間排序
await fetchData(1);
}
}
這邊也如出一轍馍乙,創(chuàng)建完畢了之后如果接口未返回錯(cuò)誤布近,則刷新項(xiàng)目列表頁面,并且自動(dòng)去第一頁
丝格。
const content = (item) => {
return <div>
{/* <p>負(fù)責(zé)人: {userMap[item.owner].name}</p> */}
<p>簡介: {item.description || '無'}</p>
<p>更新時(shí)間: {item.updated_at}</p>
</div>
};
const opt = <Select placeholder="請(qǐng)選擇項(xiàng)目負(fù)責(zé)人">
{
Object.keys(users).map(id => <Option key={id} value={id}>{users[id].name}</Option>)
}
</Select>
const fields = [
{
name: 'projectName',
label: '項(xiàng)目名稱',
required: true,
message: "請(qǐng)輸入項(xiàng)目名稱",
type: 'input',
placeholder: "請(qǐng)輸入項(xiàng)目名稱",
},
{
name: 'owner',
label: '項(xiàng)目負(fù)責(zé)人',
required: true,
component: opt,
type: 'select',
},
{
name: 'description',
label: '項(xiàng)目描述',
required: false,
message: "請(qǐng)輸入項(xiàng)目描述",
type: 'textarea',
placeholder: "請(qǐng)輸入項(xiàng)目描述",
},
{
name: 'private',
label: '是否私有',
required: true,
message: "請(qǐng)選擇項(xiàng)目是否私有",
type: 'switch',
valuePropName: "checked",
},
]
這里content是一個(gè)方法撑瞧,會(huì)返回一個(gè)div的html結(jié)構(gòu),目的是為了展示項(xiàng)目的詳情显蝌,比如負(fù)責(zé)人预伺,項(xiàng)目描述等。
fields的話曼尊,是表單的字段酬诀,因?yàn)槲裔槍?duì)antd的form進(jìn)行了一點(diǎn)封裝,編寫了一套高階組件涩禀。等于說是規(guī)劃好了表單里面的表單組成料滥,由input和select以及switch組件組成。
待會(huì)會(huì)講這個(gè)組件艾船!
最后看return里面的組件
return (
<PageContainer title={false}>
<FormForModal width={600} title="添加項(xiàng)目" left={6} right={18} record={{}}
visible={visible} onCancel={() => setVisible(false)} fields={fields} onFinish={onHandleCreate}
/>
<Row gutter={8} style={{marginBottom: 16}}>
<Col span={18}>
<Button type="primary" onClick={() => setVisible(true)}>創(chuàng)建項(xiàng)目
<Tooltip title="只有超級(jí)管理員可以創(chuàng)建項(xiàng)目"><QuestionCircleOutlined/></Tooltip>
</Button>
</Col>
<Col span={6}>
<Search onSearch={onSearchProject} style={{float: 'right'}} placeholder="請(qǐng)輸入項(xiàng)目名稱"/>
</Col>
</Row>
<Spin spinning={false}>
<Row gutter={16}>
{
data.length === 0 ? <Col span={24} style={{textAlign: 'center', marginBottom: 12}}>
<Card><Empty description="暫無項(xiàng)目, 快點(diǎn)擊『創(chuàng)建項(xiàng)目』創(chuàng)建一個(gè)吧!"/></Card>
</Col> :
data.map(item =>
<Col key={item.id} span={4} style={{marginBottom: 12}}>
<Popover content={content(item)} placement="rightTop">
<Card hoverable bordered={false} style={{borderRadius: 16, textAlign: 'center'}}
bodyStyle={{padding: 16}} onClick={() => {
history.push(`/project/${item.id}`);
}}>
<Avatar style={{backgroundColor: '#87d068'}} size={64}
>{item.name.slice(0, 2)}</Avatar>
<p style={{
textAlign: 'center',
fontWeight: 'bold',
fontSize: 18,
marginTop: 8
}}>{item.name}</p>
</Card>
</Popover>
</Col>
)
}
</Row>
</Spin>
</PageContainer>
)
}
PageContainer
是最外層葵腹,也就是咱們看到的這一塊:
FormForModal
是一個(gè)對(duì)話框表單高每,默認(rèn)是不顯示的,只有點(diǎn)擊創(chuàng)建項(xiàng)目
才會(huì)顯示践宴。
然后用Row分了2行: 分別是 創(chuàng)建欄/搜索欄和項(xiàng)目展示欄
這邊項(xiàng)目展示欄如果data.length === 0
就展示一個(gè)空狀態(tài)鲸匿,提示用戶去添加項(xiàng)目,否則就把項(xiàng)目展示出來阻肩,每個(gè)項(xiàng)目占屏幕的1/6带欢,因?yàn)镃ol總共有24份。
tips: 這里類似于bootstrap烤惊,一行共有24份乔煞,所以span={4}代表的是4/24,可參考: 官網(wǎng)介紹
其他的就是Card
(卡片)組件和Avatar
(頭像)組件的互相嵌入柒室,Popover
是懸浮窗口渡贾,如圖:
編寫獲取用戶列表的接口
修改app/dao/auth/UserDao.py
篩選出未被刪除的用戶即可。
修改app/controllers/auth/user.py
這里注意開啟一下權(quán)限雄右,但是不能控制太死空骚,能讓登錄用戶訪問即可。
前端頁面編寫listUsers方法
看下效果
發(fā)現(xiàn)一個(gè)問題: 創(chuàng)建成功后擂仍,對(duì)話框沒有關(guān)閉
所以我們需要在創(chuàng)建成果后囤屹,setVisible(false)
去關(guān)閉對(duì)話框。
還有一個(gè)地方就是之前注釋掉的項(xiàng)目負(fù)責(zé)人逢渔,現(xiàn)在可以重新開啟了肋坚。
看下搜索效果
這就是本節(jié)的內(nèi)容了,大部分是講解代碼為主复局,因?yàn)?strong>前端內(nèi)容居多冲簿,所以可能有點(diǎn)懵逼粟判。如果有不理解的地方亿昏,還請(qǐng)多多看看react
和es6
相關(guān)的教程。箭頭函數(shù)档礁,看著別怕角钩,熟悉了就好。
下一節(jié)可能是具體項(xiàng)目的編輯頁面了呻澜,感覺這一講就講了一個(gè)世紀(jì)递礼,再后面就是用例那塊了。