回顧
上回我們編寫好了添加項目和查詢項目2個接口宴合,那今天我們就把它應(yīng)用到項目中吧!所以本節(jié)內(nèi)容會以前端部分為主迹鹅,目的是為了聯(lián)調(diào)后端編寫好的接口卦洽,并在頁面上能夠給用戶使用。
調(diào)整項目表
是這樣的斜棚,因為我們的項目有對應(yīng)的圖片阀蒂,并且缺少描述字段该窗,所以我們這里把項目表進行一些調(diào)整。
- 圖片字段
- 描述字段
描述字段很好說蚤霞,至于圖片字段就比較麻煩了酗失!如果對于單節(jié)點部署的應(yīng)用,圖片可以放入類似static的目錄中争便,以xxx.jpg的形式存在级零。但是如果我們需要部署到多臺機器的話,假設(shè)是2臺機器滞乙,用戶上傳一次圖片奏纪,其中只有一個服務(wù)接收到了圖片文件,那就會很麻煩斩启。
那么怎么解決這個問題呢序调?
在企業(yè)中,一般公司會有oss(云存儲)服務(wù)兔簇,比如: 阿里云oss发绢,騰訊云cos,還有七牛云等等垄琐。我之前太監(jiān)的項目用的就是七牛云+自有域名边酒,由于沒續(xù)費,所以我們換一種簡單的方式:
直接在數(shù)據(jù)庫加一個TEXT字段狸窘,存放圖片的base64數(shù)據(jù)墩朦,但是注意大小一定要限制,然后html里面通過img標簽解析數(shù)據(jù)即可翻擒。
由于項目可能不太需要頭像這種東西氓涣,以后這種圖片留到個人頭像會比較好一點,antd提供了文字頭像陋气,相信大家用過釘釘?shù)菼M工具的都知道劳吠,有的人如果沒有頭像的話,會用名字當頭像比如"鳴人"巩趁。
所以我們這里就不用那么麻煩了痒玩,直接只加一個描述字段就好了!頭像用默認的就行议慰!
- 調(diào)整models/Project.py
- 調(diào)整ProjectDao.py
- 調(diào)整添加項目接口
把描述字段加進去即可凰荚,因為這個字段不是必填的,所以我們給他一個默認值: 空字符串
構(gòu)思頁面
回憶一下我們當時編寫postman頁面的時候褒脯,我們是把對應(yīng)的組件分層了便瑟。由于我們當時做postman頁面的時候是有成品給我們參考的,但是關(guān)于項目管理頁面番川,我們的的確確是沒有任何參考到涂,所以我們先要在腦海里構(gòu)思一下頁面大概長什么樣脊框!
參考Ant Design Pro,我們就可以有個大概的方向了践啄!
采用這種卡片列表浇雹,可以幫助我們展示項目。
引入滾動條
這個頁面是我從之前一個太監(jiān)了的項目里面拷貝來的屿讽,為了節(jié)約時間就繼續(xù)沿用了昭灵。
- 安裝依賴
cnpm install nprogress --save
在src/utils/utils.js添加方法
import NProgress from 'nprogress';
// 引入樣式
import 'nprogress/nprogress.css'
export const process = async func => {
NProgress.start();
await func();
NProgress.done();
};
這樣當我們需要在某個方法執(zhí)行之前加載進度條的時候,調(diào)用process方法即可伐谈。
項目卡片列表頁面代碼
其實react并不是一個很復(fù)雜的框架烂完,可能對于Jsx需要有一定的了解。
我們的組件都可以細分成4個部分:
-
引用
即引入其他組件或者庫
-
狀態(tài)管理
狀態(tài)是什么概念呢诵棵,舉個例子抠蚣,你頁面有個loading組件,什么時候他loading履澳,什么時候結(jié)束loading嘶窄,你通過什么來判斷它是否loading,這個判斷的變量距贷,就叫做狀態(tài)柄冲。
而咱們的頁面也有很多地方通過狀態(tài)來展示,比如我們通過data(數(shù)組)來存放我們的項目列表忠蝗,初始化為空數(shù)組羊初。當頁面開始渲染,組件開始加載的時候什湘,我們變?nèi)ズ蠖朔?wù)拉取數(shù)據(jù),改寫data數(shù)組晦攒,把項目的信息放入到數(shù)組里面闽撤。
注意,這個時候狀態(tài)就發(fā)生了變更脯颜!react會自動對比變更的狀態(tài)來渲染新的頁面哟旗,這里面過程很復(fù)雜。建議去查閱react相關(guān)資料栋操,筆者也不是很熟悉里面的細節(jié)闸餐,想要了解的話,去搜索virtual dom矾芙。
-
交互方法的編寫
我們有很多組件都有涉及到一些交互方法舍沙,比如說: 點擊某個按鈕,觸發(fā)什么事件剔宪。這些都是需要編寫方法的拂铡,比如上圖中 用戶點擊添加項目壹无,我們需要做什么呢?
我們需要把對話框顯示出來對不對感帅?所以我們需要做的就是: 改變對話框的狀態(tài)斗锭,把他從隱藏改為顯示。
-
類HTML頁面的編寫
這一步比較簡單了失球,采用jsx的語法去編寫html頁面岖是。大體上和html都差不多,只不過這邊可以寫一些js代碼实苞,通過
{}
就可以包裹JS代碼豺撑,相對來說還是很方便的!掌握了jsx硬梁,es6前硫,基本上react上手就很快了!
import React, { useEffect, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Avatar, Button, Card, Col, Empty, Input, Popover, Row, Select, Spin, Tooltip } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import FormForModal from '@/components/PityForm/FormForModal';
import { history } from 'umi';
import { listProject } from '@/services/project';
import auth from '@/utils/auth';
import { process } from '@/utils/utils';
const {Search} = Input;
const {Option} = Select;
export default () => {
const [data, setData] = useState([]);
const [pagination, setPagination] = useState({current: 1, pageSize: 10, total: 0});
const [visible, setVisible] = useState(false);
const [users, setUsers] = useState([]);
const [userMap, setUserMap] = useState({});
useEffect(async () => {
await process(async ()=> {
const res = await listProject({page: pagination.current, size: pagination.size});
if (auth.response(res)) {
setData(res.data)
setPagination({...pagination, total: res.total})
}
});
}, [])
const onSearchProject = projectName => {
// this.props.dispatch({
// type: 'project/fetch',
// payload: {page: 1, size: 1000, projectName}
// })
}
const onHandleModal = status => {
setVisible(status);
}
const onHandleCreate = values => {
// this.props.dispatch({
// type: 'project/insert',
// payload: values,
// })
}
const content = (item) => {
return <div>
{/* <p>負責人: {userMap[item.owner].name}</p> */}
{/* <p>簡介: {item.description || '無'}</p> */}
{/* <p>更新時間: {item.updateTime}</p> */}
</div>
};
const opt = <Select placeholder="請選擇項目組長">
{
users.map(item => <Option key={item.value} value={item.value}>{item.label}</Option>)
}
</Select>
const fields = [
{
name: 'projectName',
label: '項目名稱',
required: true,
message: "請輸入項目名稱",
type: 'input',
placeholder: "請輸入項目名稱",
},
{
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 (
<PageContainer title={false}>
<FormForModal width={600} title="添加項目" left={6} right={18} record={{}}
visible={visible} onCancel={() => {
onHandleModal(false)
}} fields={fields} onFinish={onHandleCreate}
/>
<Row gutter={8} style={{marginBottom: 16}}>
<Col span={18}>
<Button type="primary" onClick={() => {
onHandleModal(true)
}}>創(chuàng)建項目
<Tooltip title="只有超級管理員可以創(chuàng)建項目"><QuestionCircleOutlined/></Tooltip>
</Button>
</Col>
<Col span={6}>
<Search onSearch={onSearchProject} style={{float: 'right'}} placeholder="請輸入項目名稱"/>
</Col>
</Row>
<Spin spinning={false}>
<Row gutter={16}>
{
data.length === 0 ? <Col span={24} style={{textAlign: 'center', marginBottom: 12}}>
<Card><Empty description="暫無項目, 快點擊『創(chuàng)建項目』創(chuàng)建一個吧!"/></Card>
</Col> :
data.map(item =>
<Col 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>
)
}
先大致瞅瞅這段代碼荧止,后續(xù)筆者會跟進講解屹电。今天內(nèi)容就到這里吧~??