CRM或客户关系管理系统是管理与客户关系的好方法。这是一个很好的了解工具,也是一种纯粹的乐趣。
CRM源码下载及演示站:c.xsymz.icu
在这篇文章中,我们将讨论如何使用React.js创建一个CRM应用程序,材料UI,我们也将使用其他集成,如Asana创建一个票,当我们的应用程序上创建一个谷歌表,每当交易添加到用户。
建立反应
让我们首先使用create-react-app创建一个样板
npx create-react-app crm-app
安装材料界面
我们将使用材质UI作为一个UI库。它加快了开发速度,因为我们不需要手动使用太多CSS。
创建一个ContentTable组件
我们正在使用一个由材质UI组成的表格组件来快速建立一个支持分页的表格,我们将使用自定义分页操作,你可以在这里阅读更多关于它的信息。这会给我们一个单独的分量。它处理分页,为了简单起见,我们将把该函数提取到另一个组件中。
要创建第一个组件,请在src目录中创建一个目录,并将其称为components。继续在' components '中创建另一个目录,我们将其命名为ContentTable,我们将在其中创建一个同名的文件ContentTable.js。
const customers = [
{
id: 1,
name: "Lillian Carter",
email: "xcollier@goodwin.com",
phone: "+1-267-551-8666",
company: "Larkin Group",
label: "Marketing",
},
{
id: 2,
name: "Otto Walker",
email: "stokes.hubert@hotmail.com",
phone: "+1-580-977-4361",
company: "Bednar-Sawayn",
label: "Newsletter",
},
{
id: 3,
name: "Kaylee Taylor",
email: "diana45@hotmail.com",
phone: "+1-202-918-2132",
company: "Rolfson and Sons ",
label: "Ads",
},
{
id: 4,
name: "Aiden Houston",
email: "ctromp@kassulke.info",
phone: "+1-215-480-3687",
company: "Wisoky, Windler and Nienow",
label: "Newsletter",
},
{
id: 5,
name: "Davis Houston",
email: "voreilly@yahoo.com",
phone: "+1-203-883-5460",
company: "Schmidt, Streich and Schuster",
label: "Ads",
},
];
src /组件/ ListCustomer / ListCustomer.js
并且,将从Material UI的文档中复制表的代码,我们将通过我们的静态数组来填充表中的数据。
import { React, useState } from "react";
import {
Table,
TableBody,
TableContainer,
TableFooter,
TablePagination,
TableRow,
Paper,
TableCell,
TableHead,
} from "@mui/material";
const customers = [
{
id: 1,
name: "Lillian Carter",
email: "xcollier@goodwin.com",
phone: "+1-267-551-8666",
company: "Larkin Group",
label: "Marketing",
},
{
id: 2,
name: "Otto Walker",
email: "stokes.hubert@hotmail.com",
phone: "+1-580-977-4361",
company: "Bednar-Sawayn",
label: "Newsletter",
},
{
id: 3,
name: "Kaylee Taylor",
email: "diana45@hotmail.com",
phone: "+1-202-918-2132",
company: "Rolfson and Sons ",
label: "Ads",
},
{
id: 4,
name: "Aiden Houston",
email: "ctromp@kassulke.info",
phone: "+1-215-480-3687",
company: "Wisoky, Windler and Nienow",
label: "Newsletter",
},
{
id: 5,
name: "Davis Houston",
email: "voreilly@yahoo.com",
phone: "+1-203-883-5460",
company: "Schmidt, Streich and Schuster",
label: "Ads",
},
];
const ContentTable = () => {
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - customers.length) : 0;
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
return (
<TableContainer component={Paper} sx={{ margin: "2rem", width: "95%" }}>
<Table sx={{ minWidth: 500 }} aria-label="custom pagination table">
<TableHead>
<TableRow>
<TableCell
align="left"
sx={{
backgroundColor: "black",
color: "white",
borderRight: "1px solid white",
}}
>
Name
</TableCell>
<TableCell
align="left"
sx={{
backgroundColor: "black",
color: "white",
borderRight: "1px solid white",
}}
>
Company
</TableCell>
<TableCell
align="left"
sx={{
backgroundColor: "black",
color: "white",
borderRight: "1px solid white",
}}
>
</TableCell>
<TableCell
align="left"
sx={{
backgroundColor: "black",
color: "white",
borderRight: "1px solid white",
}}
>
Phone
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{customers &&
(rowsPerPage > 0
? customers.slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
)
: customers
).map((row, index) => (
<TableRow key={index}>
<TableCell
component="th"
scope="row"
sx={{ width: 160, borderRight: "1px solid black" }}
>
{row.name}
</TableCell>
<TableCell
sx={{ width: 160, borderRight: "1px solid black" }}
align="left"
>
{row.company}
</TableCell>
<TableCell
sx={{ width: 160, borderRight: "1px solid black" }}
align="left"
>
{row.email}
</TableCell>
<TableCell sx={{ width: 160 }}>
{row.phone}
</TableCell>
</TableRow>
))}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, { label: "All", value: -1 }]}
colSpan={3}
count={customers}
rowsPerPage={rowsPerPage}
page={page}
SelectProps={{
inputProps: {
"aria-label": "rows per page",
},
native: true,
}}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
);
};
export default ContentTable;
src /组件/ ContentTable / ContentTable.js
上面的代码会抱怨TablePaginationActions是未定义的,所以让我们处理它。
接下来,我们将创建一个实用组件,它将帮助我们进行分页,材质UI使用它作为一个演示,但提取它的外部使ContentTable更干净。
在componenets中创建一个目录,命名为Pagination,并在其中创建一个index.js文件。
import { React } from "react";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material/styles";
import { Box, IconButton } from "@mui/material";
import FirstPageIcon from "@mui/icons-material/FirstPage";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import LastPageIcon from "@mui/icons-material/LastPage";
export const TablePaginationActions = (props) => {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (event) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (event) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (event) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (event) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
);
};
TablePaginationActions.propTypes = {
count: PropTypes.number.isRequired,
onPageChange: PropTypes.func.isRequired,
page: PropTypes.number.isRequired,
rowsPerPage: PropTypes.number.isRequired,
};
src /组件/分页/ index.js
现在我们可以在ContentTable组件中导入Pagination组件,错误就会消失。最终结果应该是这样的
创建一个模态组件
现在我们的表格已经准备好了,让我们来看看模态组件,我们想要这个模态在用户点击表格中的任何一行时弹出。我们将使用材质UI的模态组件,你可以在这里阅读更多
在模态中,我们需要3列:
第一列将显示关于特定客户的更多信息
第二列将包含注释功能,用户可以在此给客户留下注释。
第三栏是一个功能,为用户添加交易,并在Asana上创建一个任务。
就像这样
import { React } from "react";
import { styled } from "@mui/material/styles";
import { Typography, Fade, Modal, Grid, Paper, Backdrop } from "@mui/material";
const Item = styled(Paper)(({ theme }) => ({
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: "center",
color: theme.palette.text.secondary,
boxShadow: "none",
}));
export default function Modals() {
return (
<p>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={open}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<Grid
container
spacing={1}
sx={{
width: "95%",
height: "95%",
backgroundColor: "white",
position: "absolute",
top: "4%",
left: "3%",
}}
>
<Grid item xs={3} sx={{ padding: "5px" }}>
<Typography variant="h6" gutterBottom component="p">
Name
</Typography>
<Typography variant="h6" gutterBottom component="p">
Company
</Typography>
<Typography variant="h6" gutterBottom component="p">
Phone
</Typography>
<Typography variant="h6" gutterBottom component="p">
</Typography>
<Typography variant="h6" gutterBottom component="p">
Label
</Typography>
</Grid>
<Grid
item
xs={6}
sx={{ backgroundColor: "lightgray", padding: "5px" }}
>
<Item sx={{ backgroundColor: "inherit", marginTop: "2rem" }}>
<Typography variant="h6" gutterBottom component="p">
Notes
</Typography>
</Item>
</Grid>
<Grid item xs={3} sx={{ padding: "5px", marginTop: "2rem" }}>
<Item>
<Typography variant="h6" gutterBottom component="p">
Deals
</Typography>
<Typography variant="p" gutterBottom component="p">
Track the revenue opportunities associated with this record
</Typography>
</Item>
<Item sx={{ marginTop: "2rem" }}>
<Typography variant="h6" gutterBottom component="p">
Tickets
</Typography>
<Typography variant="p" gutterBottom component="p">
Track the customer requests associated with this record
</Typography>
</Item>
</Grid>
</Grid>
</Fade>
</Modal>
</p>
);
}
src /组件/模态/ Modal.js
我们大量利用材质UI的网格布局来制作这个列结构,你可以在这里获得更多的阅读
最终结果是这样的
整合CRM有很多好处,从加强你的营销努力到提供一个个性化的访客资料。这是一个极好的工具,可以促进你与客户的关系。最重要的是,它提供了一致的信息,以提高您的转化率。
「艾尔登法环」梅琳娜手办开订 立体手办▪
万代「艾尔登法环」白狼战鬼手办开订 立体手办▪
「夏目友人帐」猫咪老师粘土人开订 立体手办▪
「五等分的新娘∬」中野三玖·白无垢版手办开订 立体手办▪
「海贼王」乌索普Q版手办开订 立体手办▪
良笑社「初音未来」新手办开订 立体手办▪
「黑岩射手DAWN FALL」死亡主宰手办开订 立体手办▪
「盾之勇者成名录」菲洛手办登场 立体手办▪
「魔法少女小圆」美树沙耶香手办开订 立体手办▪
「咒术回战」七海建人粘土人登场 立体手办▪
「五等分的新娘」中野二乃白无垢手办开订 立体手办▪
「为美好的世界献上祝福!」芸芸粘土人开订 立体手办▪
「公主连结 与你重逢」六星可可萝手办开订 立体手办▪
「女神异闻录5」Joker雨宫莲手办开订 立体手办▪
「间谍过家家」约尔・福杰粘土人登场 立体手办▪
「街角魔族 2丁目」吉田优子手办开订 立体手办▪
「火影忍者 疾风传」旗木卡卡西·暗部版粘土人登场 立体手办▪
「佐佐木与宫野」宫野由美粘土人开订 立体手办▪
「盾之勇者成名录」第2季拉芙塔莉雅手办开订 立体手办▪
「咒术回战」两面宿傩Q版坐姿手办开订 立体手办▪
「DATE·A·BULLET」时崎狂三手办开订 立体手办▪
「狂赌之渊××」早乙女芽亚里粘土人开订 立体手办▪
「魔道祖师」魏无羨粘土人开订 立体手办▪
「新·奥特曼」奥特曼手办现已开订 立体手办▪