这个可以去npm官网上搜索https://www.npmjs.com/,也可以直接install看看,是否存在
创建一个文件夹,我在这里取名为welkin-test
cd welkin-test
npm init
需要注意的是,需要和package.json的name对应
{
"name": "welkin-test",
"version": "1.0.0", //这个分别代表 主版本号.次版本号.修订号
"description": "基本布局",
}
针对version简单介绍一下:
在welkin-test里面新建index.js,随便写上一个函数
const a = () => {
return a * 5;
};
export default a;
然后,npm publish
,终端上没有报错就发布成功了
使用create-react-app 创建一个测试项目,将我们刚刚发布的包下载下来看看里面的内容是不是我们写的a函数,如果是那就正常
我这里是准备发布一个基本组件,样式和结构如下所示:【antd里也有:https://ant.design/components/layout-cn/】
红色框内的就是准备自己开发的
create-react-app xxxxxx --template typescript
其实可以先开发出自己的组件然后再分析,以我的为例子,主要分为三块组件:
我们将这三个组件放在一个目录下:
import React, { CSSProperties } from 'react';
import { Button, Dropdown, Layout, Menu } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import './header.scss';
import getPrefixCls from '../_utils/get-prefix-cls'; //这个函数后面介绍
const { Header: LayoutHeader } = Layout;
//这里是header组件用到的props
export interface Props {
logoUrl?: string;
title?: string;
userName?: string;
logoutUrl?: string;
isFixed?: boolean;
}
const Header: React.FC<Props> = ({
title,
logoUrl,
userName,
logoutUrl,
isFixed = false,
}) => {
//根据是否固定做出的不一样的header style
const style: CSSProperties = isFixed
? { position: 'fixed', zIndex: 3, width: '100%', backgroundColor: '#fff' }
: { width: '100%', backgroundColor: '#fff' };
const menu = (
<Menu>
<Menu.Item key="1">{logoutUrl && <a href={logoutUrl}>退出</a>}</Menu.Item>
</Menu>
);
return (
<LayoutHeader className={getPrefixCls('header')} style={style}>
<div className={getPrefixCls('header__logo')}>
{logoUrl && <img src={logoUrl} alt="logo" />}
{title && <span>{title}</span>}
</div>
<nav className={getPrefixCls('header__nav')} />
{userName && (
<div className={getPrefixCls('header__user')}>
<Dropdown overlay={menu}>
<Button type="link">
<span>{userName}</span>
<DownOutlined />
</Button>
</Dropdown>
</div>
)}
</LayoutHeader>
);
};
export default Header;
header组件的scss如下:
@import '../style/var.scss'; //这个后面介绍
.#{$name}-header {
display: flex;
border-bottom: 1px solid #dcdcdc;
&__logo {
display: flex;
justify-content: center;
img {
width: 75px;
align-self: center;
}
span {
font-size: 14px;
font-family: unset;
color: #383838;
}
}
&__nav {
flex: 1;
}
&__user {
:global {
.ant-avatar {
cursor: pointer;
}
}
}
}
页面组件:
import React, { useMemo, useCallback } from 'react';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
import './leftMenu.scss';
import { Location } from 'history';
import getPrefixCls from '../_utils/get-prefix-cls';
const { SubMenu } = Menu;
//菜单项结构
export interface MenuItem {
key: string;
name: string;
icon?: any;
type: string;
children?: MenuItem[];
}
//leftMenu组件用到的props
export interface Props {
location: Location;
MenuData: MenuItem[];
}
const LeftMenu: React.FC<Props> = ({ location, MenuData }) => {
const getDefaultOpenKeys = useCallback(
(
data: MenuItem[] = [],
openKeys: string[] = [],
parentKeys: string[] = []
): string[] =>
data.reduce((prev, curr): any => {
if (curr.key === location.pathname) {
return [...prev, ...openKeys, curr.key, ...parentKeys];
}
if (curr.children && curr.children.length !== 0) {
return [
...prev,
...getDefaultOpenKeys(curr.children, openKeys, [
...parentKeys,
curr.key,
]),
];
}
return [...prev];
}, []),
[location.pathname]
);
const defaultOpenKeys = useMemo(() => getDefaultOpenKeys(MenuData), [
getDefaultOpenKeys,
]);
// 遍历定义的菜单
const MenuList: JSX.Element[] = useMemo(
() =>
MenuData.map((item) => {
if (item.children && item.children.length !== 0) {
return (
<SubMenu
key={item.key}
title={
<span>
{item.icon &&
React.createElement(item.icon, {
style: { verticalAlign: 'middle' },
})}
<span>{item.name}</span>
</span>
}
>
{item.children.map((subItem) => (
<Menu.Item key={subItem.key}>
{subItem.type === 'link' ? (
<Link to={subItem.key}>{subItem.name}</Link>
) : (
<a target="_blank " href={subItem.key}>
{subItem.name}
</a>
)}
</Menu.Item>
))}
</SubMenu>
);
}
return (
<Menu.Item key={item.key}>
{item.type === 'link' ? (
<Link to={item.key}>
{item.icon &&
React.createElement(item.icon, {
style: { verticalAlign: 'middle' },
})}
<span>{item.name}</span>
</Link>
) : (
<a target="_blank " href={item.key}>
{item.icon &&
React.createElement(item.icon, {
style: { verticalAlign: 'middle' },
})}
{item.name}
</a>
)}
</Menu.Item>
);
}),
[]
);
return (
<Menu
className={getPrefixCls('leftMenu')}
theme="light"
mode="inline"
defaultSelectedKeys={[location.pathname]}
defaultOpenKeys={defaultOpenKeys}
>
{MenuList}
</Menu>
);
};
export default LeftMenu;
用到的scss
@import '../style/var.scss';
.#{$name}-leftMenu {
border-right: none;
}
.#{$name}-iconStyle {
display: flex;
align-items: center;
}
import React, { CSSProperties, useCallback, useState } from 'react';
import { Layout } from 'antd';
import Header from '../Header/header';
import LeftMenu, { MenuItem } from '../LeftMenu/leftMenu';
import { Location } from 'history';
const { Sider, Content } = Layout;
export interface Props {
logoUrl?: string;
title?: string;
userName?: string;
logoutUrl?: string;
location: Location;
MenuData: MenuItem[];
isFixed?: boolean;
}
const Base: React.FC<Props> = ({
children,
MenuData,
location,
title,
logoUrl,
userName,
logoutUrl,
isFixed = false,
}) => {
const [collapsed, setCollapsed] = useState(false);
// 点击展开,再次点击关闭
const handleTootle = useCallback(() => {
setCollapsed((v) => !v);
}, [setCollapsed]);
const layoutStyle: CSSProperties = isFixed
? {
paddingTop: '70px',
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
zIndex: 1,
}
: {};
const contentStyle = isFixed
? {
margin: '24px 16px',
marginTop: 90,
padding: 20,
marginLeft: collapsed ? '100px' : '220px',
background: '#fff',
}
: {
margin: '24px 16px',
padding: 20,
background: '#fff',
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Header
title={title}
logoUrl={logoUrl}
logoutUrl={logoutUrl}
userName={userName}
isFixed={isFixed}
/>
<Layout>
<Sider
collapsible
collapsed={collapsed}
onCollapse={handleTootle}
theme="light"
style={layoutStyle}
>
<LeftMenu MenuData={MenuData} location={location} />
</Sider>
<Layout>
<Content style={contentStyle}>{children}</Content>
</Layout>
</Layout>
</Layout>
);
};
export default Base;
因为在打包中,我们的包被引用之后可能出现css找不到或者被覆盖的情况,所以要给我们自己的css加一个前缀
该函数是给页面级组件加前缀
const getPrefixCls = (
suffixCls: string,
customizePrefixCls = 'fui'
): string => {
return `${customizePrefixCls}-${suffixCls}`;
};
export default getPrefixCls;
这是个scss函数主要在组件scss中引入,给scss加前缀
$name:fui
import Base from './Base';
import Header from './Header';
import LeftMenu from './LeftMenu';
export { Base, Header, LeftMenu };
export default { Base, Header, LeftMenu };
其实这个时候也可以在app中引入自己写的组件看看是否无误,就比如我在这里写了一个测试组件,之后在app中引入就可以看到该组件的样式:
测试组件代码如下:
import React, { FC } from 'react';
import { Base } from './components';
import { FormOutlined, FileSyncOutlined } from '@ant-design/icons';
import { MenuItem } from './components/LeftMenu/leftMenu';
import { useLocation } from 'react-router-dom';
const Ceshi: React.FC = () => {
const location = useLocation();
const menuData: MenuItem[] = [
{
key: '/search/*',
name: '账号审计',
icon: FormOutlined,
type: 'link',
children: [
{
key: '/search',
name: '账号全量查询',
type: 'link',
},
{
key: '/search/inactive',
name: '账号不活跃查询',
type: 'link',
},
{
key: '/search/match',
name: '一人多账号查询',
type: 'link',
},
{
key: '/search/share',
name: '账号共享查询',
type: 'link',
},
],
},
{
key: '/config',
name: '账号审计配置',
icon: FileSyncOutlined,
type: 'link',
children: [
{
key: '/config/inactive',
name: '账号不活跃管理',
type: 'link',
},
{
key: '/config/match',
name: '一人多账号报备',
type: 'link',
},
],
},
];
return (
<Base
MenuData={menuData}
location={location}
title={'ceshi'}
userName={'welkin'}
logoutUrl={'/logout/ceshi'}
></Base>
);
};
export default Ceshi;
app组件如下:
import React from 'react';
import Ceshi from './ceshi';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
const App: React.FC = () => {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Ceshi />} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
yarn eject
在config/path中添加路径,因为我的组件是在components目录下的,所以添加
comIndexJs: resolveModule(resolveApp, 'src/components/index'),
//这个地方就是我们的导出组件文件index
//修改入口文件
entry: isEnvProduction ? paths.comIndexJs : paths.appIndexJs,
//修改output 部分
output:{
//修改filename
filename: isEnvProduction
? 'fui.js'
: isEnvDevelopment && 'static/js/bundle.js',
// 修改devtoolModuleFilenameTemplate
devtoolModuleFilenameTemplate: isEnvProduction
? (info) =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
((info) =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
//添加如下:
library: 'fui',
libraryTarget: 'umd',
libraryExport: 'default',
}
//将 HtmlWebpackPlugin 设置为isEnvDevelopment 判断
plugins: [
isEnvDevelopment &&
new HtmlWebpackPlugin(
Object.assign(xxxx)//....
)
]
//增加 externals 里面的内容要和package.json中的peerDependencies挂钩
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
root: 'React',
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM',
},
'react-router-dom': {
commonjs: 'react-router-dom',
commonjs2: 'react-router-dom',
amd: 'react-router-dom',
root: 'ReactRouterDom',
},
},
添加如下:
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.1"
},
修改你的版本号【每一次npm publish都要在之前修改】,记住package.json重的name跟我们最开始发布的测试组件保持一致,因为这个项目是我们用create-react-app新建的,所以检查一下package.json
{
"name": "welkin-test",
"version": "1.0.1", //这个分别代表 主版本号.次版本号.修订号
"description": "基本布局",
}
{
"name": "welkin-test",
"version": "1.0.1",
"description": "基本布局",
"main": "build/fui.js",
}
首先需要你另起一个项目,然后下载我们的包
npm install welkin-test
我在这里写了一个测试组件,如下:Fui.Base中是我瞎写的,你们随便写
import React from 'react';
import { FormOutlined, FileSyncOutlined } from '@ant-design/icons';
import { useLocation } from 'react-router-dom';
import Fui from '@didi/layout-face';
import { Row, Col, Statistic, Button, Descriptions, Badge, Timeline } from 'antd';
import logo from './assets/img/logo.jpg';
const Ceshi: React.FC = () => {
const location = useLocation();
const menuData = [
{
key: '/search/*',
name: '菜单',
icon: FormOutlined,
type: 'link',
children: [
{
key: '/search',
name: '子菜单1',
type: 'link',
},
{
key: '/search/inactive',
name: '子菜单2',
type: 'link',
},
{
key: '/search/match',
name: '子菜单3',
type: 'link',
},
{
key: '/search/share',
name: '子菜单4',
type: 'link',
},
],
},
{
key: '/config',
name: '菜单2',
icon: FileSyncOutlined,
type: 'link',
children: [
{
key: '/config/inactive',
name: '子菜单1',
type: 'link',
},
{
key: '/config/match',
name: '子菜单2',
type: 'link',
},
],
},
];
return (
<Fui.Base
isFixed
MenuData={menuData}
location={location}
logoUrl={logo}
title="测试xxx系统"
userName="welkin"
logoutUrl="/logout/ceshi"
>
<Row gutter={16}>
<Col span={12}>
<Statistic title="Active Users" value={112893} />
</Col>
<Col span={12}>
<Statistic title="Account Balance (CNY)" value={112893} precision={2} />
<Button style={{ marginTop: 16 }} type="primary">
Recharge
</Button>
</Col>
<Col span={12}>
<Statistic title="Active Users" value={112893} loading />
</Col>
</Row>
<Row>
<Descriptions title="User Info" bordered>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
<Descriptions.Item label="Order time">2018-04-24 18:00:00</Descriptions.Item>
<Descriptions.Item label="Usage Time" span={2}>
2019-04-24 18:00:00
</Descriptions.Item>
<Descriptions.Item label="Status" span={3}>
<Badge status="processing" text="Running" />
</Descriptions.Item>
<Descriptions.Item label="Negotiated Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1<br />
</Descriptions.Item>
</Descriptions>
</Row>
<Row>
<Timeline>
<Timeline.Item color="green">Create a services site 2015-09-01</Timeline.Item>
<Timeline.Item color="green">Create a services site 2015-09-01</Timeline.Item>
<Timeline.Item color="red">
<p>Solve initial network problems 1</p>
<p>Solve initial network problems 2</p>
<p>Solve initial network problems 3 2015-09-01</p>
</Timeline.Item>
<Timeline.Item>
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</Timeline.Item>
<Timeline.Item color="gray">
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</Timeline.Item>
<Timeline.Item color="gray">
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</Timeline.Item>
</Timeline>
</Row>
</Fui.Base>
);
};
export default Ceshi;
import React, { FC } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import style from './app.module.scss';
import Ceshi from './ceshi';
const App: FC = () => (
<div className={style.app}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Ceshi />} />
</Routes>
</BrowserRouter>
</div>
);
export default App;
当你启动项目看见没有报错,但却没有样式时,需要在index.tsx中引入样式
import ReactDOM from 'react-dom';
import React from 'react';
import App from './app';
//如下所示,因为基于的antd,所以也要引入antd
import 'antd/dist/antd.css';
import '@didi/layout-face/build/fui.css';
ReactDOM.render(<App />, document.getElementById('app'));
然后打开页面就可以看见我们的组件了,基本上已经完成了一个npm组件的发布
红色框内的就是准备开发的
create-react-app xxxxxx --template typescript
其实可以先开发出自己的组件然后再分析,以我的为例子,主要分为三块组件:
我们将这三个组件放在一个目录下:
import React, { CSSProperties } from 'react';
import { Button, Dropdown, Layout, Menu } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import './header.scss';
import getPrefixCls from '../_utils/get-prefix-cls'; //这个函数后面介绍
const { Header: LayoutHeader } = Layout;
//这里是header组件用到的props
export interface Props {
logoUrl?: string;
title?: string;
userName?: string;
logoutUrl?: string;
isFixed?: boolean;
}
const Header: React.FC<Props> = ({
title,
logoUrl,
userName,
logoutUrl,
isFixed = false,
}) => {
//根据是否固定做出的不一样的header style
const style: CSSProperties = isFixed
? { position: 'fixed', zIndex: 3, width: '100%', backgroundColor: '#fff' }
: { width: '100%', backgroundColor: '#fff' };
const menu = (
<Menu>
<Menu.Item key="1">{logoutUrl && <a href={logoutUrl}>退出</a>}</Menu.Item>
</Menu>
);
return (
<LayoutHeader className={getPrefixCls('header')} style={style}>
<div className={getPrefixCls('header__logo')}>
{logoUrl && <img src={logoUrl} alt="logo" />}
{title && <span>{title}</span>}
</div>
<nav className={getPrefixCls('header__nav')} />
{userName && (
<div className={getPrefixCls('header__user')}>
<Dropdown overlay={menu}>
<Button type="link">
<span>{userName}</span>
<DownOutlined />
</Button>
</Dropdown>
</div>
)}
</LayoutHeader>
);
};
export default Header;
header组件的scss如下:
@import '../style/var.scss'; //这个后面介绍
.#{$name}-header {
display: flex;
border-bottom: 1px solid #dcdcdc;
&__logo {
display: flex;
justify-content: center;
img {
width: 75px;
align-self: center;
}
span {
font-size: 14px;
font-family: unset;
color: #383838;
}
}
&__nav {
flex: 1;
}
&__user {
:global {
.ant-avatar {
cursor: pointer;
}
}
}
}
页面组件:
import React, { useMemo, useCallback } from 'react';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
import './leftMenu.scss';
import { Location } from 'history';
import getPrefixCls from '../_utils/get-prefix-cls';
const { SubMenu } = Menu;
//菜单项结构
export interface MenuItem {
key: string;
name: string;
icon?: any;
type: string;
children?: MenuItem[];
}
//leftMenu组件用到的props
export interface Props {
location: Location;
MenuData: MenuItem[];
}
const LeftMenu: React.FC<Props> = ({ location, MenuData }) => {
const getDefaultOpenKeys = useCallback(
(
data: MenuItem[] = [],
openKeys: string[] = [],
parentKeys: string[] = []
): string[] =>
data.reduce((prev, curr): any => {
if (curr.key === location.pathname) {
return [...prev, ...openKeys, curr.key, ...parentKeys];
}
if (curr.children && curr.children.length !== 0) {
return [
...prev,
...getDefaultOpenKeys(curr.children, openKeys, [
...parentKeys,
curr.key,
]),
];
}
return [...prev];
}, []),
[location.pathname]
);
const defaultOpenKeys = useMemo(() => getDefaultOpenKeys(MenuData), [
getDefaultOpenKeys,
]);
// 遍历定义的菜单
const MenuList: JSX.Element[] = useMemo(
() =>
MenuData.map((item) => {
if (item.children && item.children.length !== 0) {
return (
<SubMenu
key={item.key}
title={
<span>
{item.icon &&
React.createElement(item.icon, {
style: { verticalAlign: 'middle' },
})}
<span>{item.name}</span>
</span>
}
>
{item.children.map((subItem) => (
<Menu.Item key={subItem.key}>
{subItem.type === 'link' ? (
<Link to={subItem.key}>{subItem.name}</Link>
) : (
<a target="_blank " href={subItem.key}>
{subItem.name}
</a>
)}
</Menu.Item>
))}
</SubMenu>
);
}
return (
<Menu.Item key={item.key}>
{item.type === 'link' ? (
<Link to={item.key}>
{item.icon &&
React.createElement(item.icon, {
style: { verticalAlign: 'middle' },
})}
<span>{item.name}</span>
</Link>
) : (
<a target="_blank " href={item.key}>
{item.icon &&
React.createElement(item.icon, {
style: { verticalAlign: 'middle' },
})}
{item.name}
</a>
)}
</Menu.Item>
);
}),
[]
);
return (
<Menu
className={getPrefixCls('leftMenu')}
theme="light"
mode="inline"
defaultSelectedKeys={[location.pathname]}
defaultOpenKeys={defaultOpenKeys}
>
{MenuList}
</Menu>
);
};
export default LeftMenu;
用到的scss
@import '../style/var.scss';
.#{$name}-leftMenu {
border-right: none;
}
.#{$name}-iconStyle {
display: flex;
align-items: center;
}
import React, { CSSProperties, useCallback, useState } from 'react';
import { Layout } from 'antd';
import Header from '../Header/header';
import LeftMenu, { MenuItem } from '../LeftMenu/leftMenu';
import { Location } from 'history';
const { Sider, Content } = Layout;
export interface Props {
logoUrl?: string;
title?: string;
userName?: string;
logoutUrl?: string;
location: Location;
MenuData: MenuItem[];
isFixed?: boolean;
}
const Base: React.FC<Props> = ({
children,
MenuData,
location,
title,
logoUrl,
userName,
logoutUrl,
isFixed = false,
}) => {
const [collapsed, setCollapsed] = useState(false);
// 点击展开,再次点击关闭
const handleTootle = useCallback(() => {
setCollapsed((v) => !v);
}, [setCollapsed]);
const layoutStyle: CSSProperties = isFixed
? {
paddingTop: '70px',
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
zIndex: 1,
}
: {};
const contentStyle = isFixed
? {
margin: '24px 16px',
marginTop: 90,
padding: 20,
marginLeft: collapsed ? '100px' : '220px',
background: '#fff',
}
: {
margin: '24px 16px',
padding: 20,
background: '#fff',
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Header
title={title}
logoUrl={logoUrl}
logoutUrl={logoutUrl}
userName={userName}
isFixed={isFixed}
/>
<Layout>
<Sider
collapsible
collapsed={collapsed}
onCollapse={handleTootle}
theme="light"
style={layoutStyle}
>
<LeftMenu MenuData={MenuData} location={location} />
</Sider>
<Layout>
<Content style={contentStyle}>{children}</Content>
</Layout>
</Layout>
</Layout>
);
};
export default Base;
因为在打包中,我们的包被引用之后可能出现css找不到或者被覆盖的情况,所以要给我们自己的css加一个前缀
该函数是给页面级组件加前缀
const getPrefixCls = (
suffixCls: string,
customizePrefixCls = 'fui'
): string => {
return `${customizePrefixCls}-${suffixCls}`;
};
export default getPrefixCls;
这是个scss函数主要在组件scss中引入,给scss加前缀
$name:fui
import Base from './Base';
import Header from './Header';
import LeftMenu from './LeftMenu';
export { Base, Header, LeftMenu };
export default { Base, Header, LeftMenu };
其实这个时候也可以在app中引入自己写的组件看看是否无误,就比如我在这里写了一个测试组件,之后在app中引入就可以看到该组件的样式:
测试组件代码如下:
import React, { FC } from 'react';
import { Base } from './components';
import { FormOutlined, FileSyncOutlined } from '@ant-design/icons';
import { MenuItem } from './components/LeftMenu/leftMenu';
import { useLocation } from 'react-router-dom';
const Ceshi: React.FC = () => {
const location = useLocation();
const menuData: MenuItem[] = [
{
key: '/search/*',
name: '账号审计',
icon: FormOutlined,
type: 'link',
children: [
{
key: '/search',
name: '账号全量查询',
type: 'link',
},
{
key: '/search/inactive',
name: '账号不活跃查询',
type: 'link',
},
{
key: '/search/match',
name: '一人多账号查询',
type: 'link',
},
{
key: '/search/share',
name: '账号共享查询',
type: 'link',
},
],
},
{
key: '/config',
name: '账号审计配置',
icon: FileSyncOutlined,
type: 'link',
children: [
{
key: '/config/inactive',
name: '账号不活跃管理',
type: 'link',
},
{
key: '/config/match',
name: '一人多账号报备',
type: 'link',
},
],
},
];
return (
<Base
MenuData={menuData}
location={location}
title={'ceshi'}
userName={'welkin'}
logoutUrl={'/logout/ceshi'}
></Base>
);
};
export default Ceshi;
app组件如下:
import React from 'react';
import Ceshi from './ceshi';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
const App: React.FC = () => {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Ceshi />} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
yarn eject
在config/path中添加路径,因为我的组件是在components目录下的,所以添加
comIndexJs: resolveModule(resolveApp, 'src/components/index'),
//这个地方就是我们的导出组件文件index
//修改入口文件
entry: isEnvProduction ? paths.comIndexJs : paths.appIndexJs,
//修改output 部分
output:{
//修改filename
filename: isEnvProduction
? 'fui.js'
: isEnvDevelopment && 'static/js/bundle.js',
// 修改devtoolModuleFilenameTemplate
devtoolModuleFilenameTemplate: isEnvProduction
? (info) =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
((info) =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
//添加如下:
library: 'fui',
libraryTarget: 'umd',
libraryExport: 'default',
}
//将 HtmlWebpackPlugin 设置为isEnvDevelopment 判断
plugins: [
isEnvDevelopment &&
new HtmlWebpackPlugin(
Object.assign(xxxx)//....
)
]
//增加 externals 里面的内容要和package.json中的peerDependencies挂钩
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
root: 'React',
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM',
},
'react-router-dom': {
commonjs: 'react-router-dom',
commonjs2: 'react-router-dom',
amd: 'react-router-dom',
root: 'ReactRouterDom',
},
},
添加如下:
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.1"
},
修改你的版本号【每一次npm publish都要在之前修改】,记住package.json重的name跟我们最开始发布的测试组件保持一致,因为这个项目是我们用create-react-app新建的,所以检查一下package.json
{
"name": "welkin-test",
"version": "1.0.1", //这个分别代表 主版本号.次版本号.修订号
"description": "基本布局",
}
{
"name": "welkin-test",
"version": "1.0.1",
"description": "基本布局",
"main": "build/fui.js",
}
首先需要你另起一个项目,然后下载我们的包
npm install welkin-test
我在这里写了一个测试组件,如下:Fui.Base中是我瞎写的,你们随便写
import React from 'react';
import { FormOutlined, FileSyncOutlined } from '@ant-design/icons';
import { useLocation } from 'react-router-dom';
import Fui from '@didi/layout-face';
import { Row, Col, Statistic, Button, Descriptions, Badge, Timeline } from 'antd';
import logo from './assets/img/logo.jpg';
const Ceshi: React.FC = () => {
const location = useLocation();
const menuData = [
{
key: '/search/*',
name: '菜单',
icon: FormOutlined,
type: 'link',
children: [
{
key: '/search',
name: '子菜单1',
type: 'link',
},
{
key: '/search/inactive',
name: '子菜单2',
type: 'link',
},
{
key: '/search/match',
name: '子菜单3',
type: 'link',
},
{
key: '/search/share',
name: '子菜单4',
type: 'link',
},
],
},
{
key: '/config',
name: '菜单2',
icon: FileSyncOutlined,
type: 'link',
children: [
{
key: '/config/inactive',
name: '子菜单1',
type: 'link',
},
{
key: '/config/match',
name: '子菜单2',
type: 'link',
},
],
},
];
return (
<Fui.Base
isFixed
MenuData={menuData}
location={location}
logoUrl={logo}
title="测试xxx系统"
userName="welkin"
logoutUrl="/logout/ceshi"
>
<Row gutter={16}>
<Col span={12}>
<Statistic title="Active Users" value={112893} />
</Col>
<Col span={12}>
<Statistic title="Account Balance (CNY)" value={112893} precision={2} />
<Button style={{ marginTop: 16 }} type="primary">
Recharge
</Button>
</Col>
<Col span={12}>
<Statistic title="Active Users" value={112893} loading />
</Col>
</Row>
<Row>
<Descriptions title="User Info" bordered>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
<Descriptions.Item label="Order time">2018-04-24 18:00:00</Descriptions.Item>
<Descriptions.Item label="Usage Time" span={2}>
2019-04-24 18:00:00
</Descriptions.Item>
<Descriptions.Item label="Status" span={3}>
<Badge status="processing" text="Running" />
</Descriptions.Item>
<Descriptions.Item label="Negotiated Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1<br />
</Descriptions.Item>
</Descriptions>
</Row>
<Row>
<Timeline>
<Timeline.Item color="green">Create a services site 2015-09-01</Timeline.Item>
<Timeline.Item color="green">Create a services site 2015-09-01</Timeline.Item>
<Timeline.Item color="red">
<p>Solve initial network problems 1</p>
<p>Solve initial network problems 2</p>
<p>Solve initial network problems 3 2015-09-01</p>
</Timeline.Item>
<Timeline.Item>
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</Timeline.Item>
<Timeline.Item color="gray">
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</Timeline.Item>
<Timeline.Item color="gray">
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</Timeline.Item>
</Timeline>
</Row>
</Fui.Base>
);
};
export default Ceshi;
import React, { FC } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import style from './app.module.scss';
import Ceshi from './ceshi';
const App: FC = () => (
<div className={style.app}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Ceshi />} />
</Routes>
</BrowserRouter>
</div>
);
export default App;
当你启动项目看见没有报错,但却没有样式时,需要在index.tsx中引入样式
import ReactDOM from 'react-dom';
import React from 'react';
import App from './app';
//如下所示,因为基于的antd,所以也要引入antd
import 'antd/dist/antd.css';
import '@didi/layout-face/build/fui.css';
ReactDOM.render(<App />, document.getElementById('app'));
然后打开页面就可以看见我们的组件了,基本上已经完成了一个npm组件的发布