# antdesign-front **Repository Path**: feengqi/antdesign-front ## Basic Information - **Project Name**: antdesign-front - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2020-03-16 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 1.前端项目最佳实践 ### 1. 工具选择 |类别|选择| |:----|:----| |框架|[react](https://reactjs.org/)| |JS语言|[TypeScript](http://www.typescriptlang.org)| |CSS语言|[css-modules](https://github.com/css-modules/css-modules)+[less](http://lesscss.org)+[postcss](https://github.com/postcss/postcss)| |JS编译|[babel](https://www.babeljs.cn/)| |模块打包|[webpack全家桶](https://webpack.github.io/)| |单元测试|[jest](https://github.com/facebook/jest)+[enzyme](https://github.com/airbnb/enzyme)+[puppteer](https://github.com/puppeteer/puppeteer)+[jsdom](https://github.com/jsdom/jsdom) | |路由|[react-router](https://github.com/ReactTraining/react-router)| |数据流|[dva](https://dvajs.com)+[redux生态](https://www.redux.org.cn/)| |代码风格|[eslint](https://eslint.org/)+[prettier](https://prettier.io/)| |JS压缩|[TerserJS](https://github.com/terser/terser)| |CSS压缩|[cssnano](https://github.com/cssnano/cssnano)| |请求库|[umi-request](https://github.com/umijs/umi-request#readme)| |UI|[AntDesign](https://ant.design/docs/react/introduce-cn)+[AntDesignPro](https://pro.ant.design/index-cn)| |国际化|[react-intl](https://github.com/formatjs/react-intl)| |hooks库|[umi-hooks](https://hooks.umijs.org/)| |静态文档|[docz](https://www.docz.site/)| |微前端|[qiankun](https://github.com/umijs/qiankun)| |图表库|[antv](https://antv.vision/)| ### 2.技术栈选型 #### 2.1 固定化 - React框架 - TypeScript语言 - Less+CSS Modules - Eslint+Prettier+固定配置 - 固定数据流方案dva - 固定babel插件 - Jest+Enzyme - 框架版本不允许锁定,^前缀必须有 - 主要依赖不允许自定义依赖版本 #### 2.2 配置化 - 不仅是框架功能,还有UI界面 - 路由、布局、菜单、导航、面包屑、权限、请求、埋点、错误处理 - 只管写Page页面就可以了 ##### 2.2.1 编译态配置 - 给node.js使用,比如webpack、babel相关配置,静态路由配置 ##### 2.2.2 运行态配置 - 给浏览器用、比如渲染逻辑、动态修改路由、获取用户信息 #### 2.3 约定化 - 国际化 - 数据流 - MOCK - 目录结构 - 404 - 权限策略 - Service - 配置文件 ### 1.3 理念 - 通过最佳实践减少不必要的选择的差异 - 通过插件和插件集的架构方式,满足不同场景的业务 - 通过资产市场和场景市场着力解决70%的开发者问题 - 通过对垂直场景采取强约束的方式,进一步提升研发效率 - 不给选择、配置化、约定化 ## 2.前端 ### 2.1. Ant Design Pro项目初始化 - [pro.ant.design](https://pro.ant.design/docs/getting-started-cn) - [Pro的区块](https://github.com/ant-design/pro-blocks) - [ant-design-pro-layout](https://github.com/ant-design/ant-design-pro-layout) - [ant-design-pro-layout](https://ant-design.github.io/ant-design-pro-layout/?path=/story/basiclayout--readecn) - `Ant Design Pro` 是一个企业级中后台前端/设计解决方案,我们秉承 Ant Design 的设计价值观,致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。 ### 2.2 启动项目 #### 2.2.1 安装 - 新建一个空的文件夹作为项目目录,并在目录下执行 ```js yarn create umi ``` #### 2.2.2 目录结构 - 我们已经为你生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。 ```js ├── config # umi 配置,包含路由,构建等配置 ├── mock # 本地模拟数据 ├── public │ └── favicon.png # Favicon ├── src │ ├── assets # 本地静态资源 │ ├── components # 业务通用组件 │ ├── e2e # 集成测试用例 │ ├── layouts # 通用布局 │ ├── models # 全局 dva model │ ├── pages # 业务页面入口和常用模板 │ ├── services # 后台接口服务 │ ├── utils # 工具库 │ ├── locales # 国际化资源 │ ├── global.less # 全局样式 │ └── global.ts # 全局 JS ├── tests # 测试工具 ├── README.md └── package.json ``` #### 2.2.3 本地开发 安装依赖 ```js npm install ``` 启动项目 ```js npm start ``` ### 2.3 用户注册 - [pro-blocks](https://github.com/ant-design/pro-blocks) - [umijs-block](https://umijs.org/zh/config/#block) - [uset-typescript-cn](https://pro.ant.design/docs/uset-typescript-cn) - [umi-config](https://umijs.org/zh/config/#chainwebpack) ```js umi block list ``` ```js UserRegister 预览 (https://preview.pro.ant.design/https://preview.pro.ant.design/user/ register) Exception403 预览 (https://preview.pro.ant.design/https://preview.pro.ant.design/excep tion/403) Exception500 预览 (https://preview.pro.ant.design/https://preview.pro.ant.design/excep tion/500) 预览 (https://preview.pro.ant.design/https://preview.pro.ant.design /user/regis ter/result) 请输入输出安装区块的路径 /user/register-result ``` #### 2.3.1 config.ts ```diff export default { plugins, + block: { + defaultGitUrl: 'https://github.com/ant-design/pro-blocks', + }, hash: true, manifest: { basePath: '/', }, + chainWebpack(config) { + config.devtool('source-map'); + }, + proxy: { + '/server/api/': { + target: 'http://localhost:4000/', + changeOrigin: true, + pathRewrite: { '^/server': '' }, + }, + }, ``` #### 2.3.2 user\register\index.tsx src\pages\user\register\index.tsx ```diff onGetCaptcha = () => { + const { dispatch, form } = this.props; + const mobile = form.getFieldValue('mobile'); + dispatch({ + type: 'login/getCaptcha', + payload: mobile, + }) let count = 59; this.setState({ count }); this.interval = window.setInterval(() => { count -= 1; this.setState({ count }); if (count === 0) { clearInterval(this.interval); } }, 1000); }; + + {getFieldDecorator('currentAuthority', { + rules: [ + { + required: true, + message: formatMessage({ id: 'userandregister.currentAuthority.required' }), + } + ], + })( + + )} + ``` #### 2.3.3 user\register\locales\zh-CN.ts src\pages\user\register\locales\zh-CN.ts ```diff + 'userandregister.currentAuthority.placeholder': '角色', + 'userandregister.currentAuthority.required': '请输入邮箱地址!', ``` #### 2.3.4 user\register\service.ts src\pages\user\register\service.ts ```diff import request from '@/utils/request'; import { UserRegisterParams } from './index'; export async function fakeRegister(params: UserRegisterParams) { + return request('/server/api/register', { method: 'POST', data: params, }); } ``` #### 2.3.5 src\services\login.ts src\services\login.ts ```diff export async function getFakeCaptcha(mobile: string) { + return request(`/server/api/login/captcha?mobile=${mobile}`); } ``` #### 2.3.6 user\register-result\index.tsx src\pages\user\register-result\index.tsx ```diff + const styles = require('./style.less'); const actions = (
+
); ``` ### 2.4. 用户登录 #### 2.4.1 locales\zh-CN\menu.ts locales\zh-CN\menu.ts ```diff + 'menu.403': '403', + 'menu.404': '404', + 'menu.500': '500', ``` #### 2.4.2 services\login.ts src\services\login.ts ```diff export async function fakeAccountLogin(params: LoginParamsType) { + return request('/server/api/login/account', { method: 'POST', data: params, }); } ``` #### 2.4.3 src\services\user.ts src\services\user.ts ```diff export async function queryCurrent(): Promise { + return request('/server/api/currentUser'); } ``` ### 2.5. 权限菜单 #### 2.5.1 config\config.ts ```diff { name: '403', icon: 'smile', path: '/403', + hideInMenu: true, component: './403', }, { name: '500', icon: 'smile', path: '/500', + hideInMenu: true, component: './500', }, ``` #### 2.5.2 layouts\UserLayout.tsx src\layouts\UserLayout.tsx ```diff +import logo from '../assets/zfjglogo.png';
logo + 珠峰架构学院
+
前端架构师的黄埔军校
{children}
``` #### 2.5.3 src\models\login.ts src\models\login.ts ```diff if (response.status === 'ok') { + if (response.token) { + localStorage.setItem('token', response.token); + } const urlParams = new URL(window.location.href); ``` #### 2.5.4 pages\user\login\index.tsx src\pages\user\login\index.tsx ```diff
+ + + +
``` #### 2.5.5 src\utils\request.ts src\utils\request.ts ```diff const request = extend({ errorHandler, // 默认错误处理 credentials: 'include', // 默认请求是否带上cookie }); +const baseURL = 'http://localhost:4000'; +request.interceptors.request.use((url: any, options: any) => { + if (localStorage.getItem('token')) { + options.headers.Authorization = 'Bearer ' + localStorage.getItem('token') + } + if (url.startsWith('/server')) { + url = baseURL + url.slice(7); + } + console.log('url', url); + return { url, options }; +}); export default request; ``` ## 3.后端 ### 3.1 api.ts ```js let express = require("express"); let path = require('path'); let bodyParser = require("body-parser"); let settings = require('./settings'); let jwt = require('jwt-simple'); let cors = require("cors"); let Models = require('./db'); let uuid = require('uuid'); let sendCode = require('./sms'); let session = require("express-session"); let MongoStore = require('connect-mongo')(session); let app = express(); app.use( cors({ origin: ["http://localhost:8000", "http://localhost:8001"], credentials: true, allowedHeaders: "Content-Type,Authorization", methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS" }) ); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.use( session({ secret: settings.secret, resave: false, saveUninitialized: true, store: new MongoStore({ url: settings.dbUrl, mongoOptions: { useNewUrlParser: true, useUnifiedTopology: true } }) }) ); app.get('/api/login/captcha', async (req: any, res: any) => { let mobile = req.query.mobile; let captcha = rand(); req.session.captcha = captcha; await sendCode(mobile, captcha); res.json({ code: 0, data: `[仅限测试环境验证码]: ${captcha}` }); }); app.post('/api/register', async (req: any, res: any) => { let user = req.body; if (user.captcha != req.session.captcha) { return res.json({ code: 1, error: '验证码不正确' }); } let avatarValue = require('crypto').createHash('md5').update(user.mail).digest('hex'); user.avatar = `https://secure.gravatar.com/avatar/${avatarValue}?s=48`; user = await Models.UserModel.create(user); res.send({ status: 'ok', currentAuthority: 'user' }); }); app.post('/api/login/account', async (req: any, res: any) => { let user = req.body; let query: any = {}; if (user.type == 'account') { query.mail = user.userName; } else if (user.type == 'mobile') { query.mobile = user.mobile; if (user.captcha != req.session.captcha) { return res.send({ status: 'error', type: user.type, currentAuthority: 'guest', }); } } let dbUser = await Models.UserModel.findOne(query); if (dbUser) { dbUser.userid = dbUser._id; dbUser.name = dbUser.mail; let token = jwt.encode(dbUser, settings.secret); res.send({ status: 'ok', token, type: user.type, currentAuthority: dbUser.currentAuthority }); } else { return res.send({ status: 'error', type: user.type, currentAuthority: 'guest', }); } }); app.get('/api/currentUser', async (req: any, res: any) => { let authorization = req.headers['authorization']; if (authorization) { try { let user = jwt.decode(authorization.split(' ')[1], settings.secret); user.userid = user._id; user.name = user.mail; res.json(user); } catch (err) { res.status(401).send({}); } } else { res.status(401).send({}); } }); app.listen(4000, () => { console.log('服务器在4000端口启动!'); }); function rand() { let min: number = 1000, max: number = 9999; return Math.floor(Math.random() * (max - min)) + min; } ``` ### 3.2 settings.ts settings.ts ```js module.exports = { secret: 'zhufengcms', dbUrl: "mongodb://127.0.0.1:27017/zhufengcms" } ``` ### 3.3 db.ts db.ts ```js const mongoose = require('mongoose'); const Schema = mongoose.Schema; const ObjectId = Schema.Types.ObjectId; const { dbUrl } = require('./settings'); const conn = mongoose.createConnection(dbUrl, { useNewUrlParser: true, useUnifiedTopology: true }); const UserModel = conn.model('User', new Schema({ userid: { type: String, required: true },//邮箱 name: { type: String, required: true }, mail: { type: String, required: true },//邮箱 password: { type: String, required: true },//密码 mobile: { type: String, required: true },//手机号 avatar: { type: String, required: true },//头像 currentAuthority: { type: String, required: true }//当前用户的权限 })); module.exports = { UserModel } ``` ### 3.4 sms.ts sms.ts ```js const axios = require('axios'); const smsConfig = require('./smsConfig'); //http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1:sendsms module.exports = async (mobile: any, captcha: any) => { const url = 'https://open.ucpaas.com/ol/sms/sendsms'; let result = await axios({ method: 'POST', url, data: { sid: smsConfig.sid, token: smsConfig.token, appid: smsConfig.appid, templateid: smsConfig.templateid, param: captcha, mobile }, headers: { "Content-Type": "application/json;charset=utf-8", "Accept": "application/json" } }) return result; } ``` ### 3.5 smsConfig.ts smsConfig.ts ```js module.exports = { sid: '', //开发者账号id token: '', //开发者token appid: '', //应用id templateid: '' //短信模板id } ``` ## 4.服务器布署 ### 4.1 安装配置服务器 ```js #升级所有包同时也升级软件和系统内核 yum update -y #只升级所有包,不升级软件和系统内核 yum upgrade ``` ### 4.2 docker是什么? - `Docker` 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。 - `Docker` 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样 ![dockercontainer](http://img.zhufengpeixun.cn/dockercontainer.png) ### 4.3 安装docker ```js yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager \ --add-repo \ https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io ``` ### 4.4 阿里云加速 ```js mkdir -p /etc/docker tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://fwvjnv59.mirror.aliyuncs.com"] } EOF # 重载所有修改过的配置文件 systemctl daemon-reload systemctl restart docker ``` ### 4.5 生成公钥并添加github - https://github.com/settings/keys ```js ssh-keygen -t rsa -b 4096 -C "zhufengnodejs@126.com" cat /root/.ssh/id_rsa.pub ``` ### 4.6 安装git ```js yum install git -y git clone git@github.com:zhufengnodejs/vue-front.git git clone git@github.com:zhufengnodejs/vue-back.git git clone git@github.com:zhufengnodejs/vue-webhook.git ``` ~\.gitconfig ```js [alias] a = add -A c = commit -m"msg" p = push origin master ``` ### 4.7 安装node和npm - [nvm](https://github.com/nvm-sh/nvm) ```js curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash source /root/.bashrc nvm ls-remote nvm install v12.13.1 npm i cnpm -g ``` ### 4.8 创建docker网络 ```js docker network create --driver bridge antdesign docker network connect antdesign antdesign-mongo docker network inspect antdesign ``` ### 4.9 启动mongodb #### 4.9.1 启动mongo的docker容器 ```js docker pull mongo:latest docker images docker run -d --name antdesign-mongo -p 27017:27017 --net antdesign mongo docker ps docker exec -it antdesign-mongo bash mongo ``` #### 4.9.2 本地安装mongodb ##### 4.9.2.1 配置MongoDB的yum源 ```js vim /etc/yum.repos.d/mongodb-org-4.0.repo #添加以下内容: [mongodb-org-4.0] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.0/x86_64/ gpgcheck=0 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc #这里可以修改 gpgcheck=0, 省去gpg验证 [root@localhost ~]# yum makecache ``` ##### 4.9.2.2 安装MongoDB ```js yum -y install mongodb-org whereis mongod vim /etc/mongod.conf ``` ##### 4.9.2.3 启动MongoDB ```js systemctl start mongod.service #启动 systemctl stop mongod.service #停止 systemctl status mongod.service #查看状态 ``` ##### 4.9.2.4 外网访问 ```js systemctl stop firewalld.service #停止firewall systemctl disable firewalld.service #禁止firewall开机启动 ``` ### 4.10 启动后台服务器 ```js git clone https://gitee.com/zhufengpeixun/antdesign-server.git cd antdesign-server docker build -t antdesign-server . docker image ls docker run -d --name antdesign-server -p 4000:4000 --net antdesign antdesign-server curl http://localhost:4000 ``` ### 4.11 启动前台服务 ```js git clone https://gitee.com/zhufengpeixun/antdesign-front.git cd antdesign-front cnpm install cnpm run build docker build -t antdesign-front . docker run -d --name antdesign-front -p 80:80 --net antdesign antdesign-front ``` ## 5.后端服务 ### 5.1 api.js api.js ```js let express = require("express"); let bodyParser = require("body-parser"); let jwt = require('jwt-simple'); let cors = require("cors"); let Models = require('./db'); let sendCode = require('./sms'); let session = require("express-session"); let MongoStore = require('connect-mongo')(session); let config = process.env.NODE_ENV == 'production' ? require('./config/config.prod') : require('./config/config.dev'); let app = express(); app.use( cors({ origin: config.origin, credentials: true, allowedHeaders: "Content-Type,Authorization", methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS" }) ); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.use( session({ secret: config.secret, resave: false, saveUninitialized: true, store: new MongoStore({ url: config.dbUrl, mongoOptions: { useNewUrlParser: true, useUnifiedTopology: true } }) }) ); app.get('/', async (req, res) => { res.json({ code: 0, data: `hello` }); }); app.get('/api/login/captcha', async (req, res) => { let mobile = req.query.mobile; let captcha = rand(); req.session.captcha = captcha; await sendCode(mobile, captcha); res.json({ code: 0, data: `[仅限测试环境验证码]: ${captcha}` }); }); app.post('/api/register', async (req, res) => { let user = req.body; if (user.captcha != req.session.captcha) { return res.json({ code: 1, error: '验证码不正确' }); } let avatarValue = require('crypto').createHash('md5').update(user.mail).digest('hex'); user.avatar = `https://secure.gravatar.com/avatar/${avatarValue}?s=48`; user = await Models.UserModel.create(user); res.send({ status: 'ok', currentAuthority: 'user' }); }); app.post('/api/login/account', async (req, res) => { let user = req.body; let query = {}; if (user.type == 'account') { query.mail = user.userName; } else if (user.type == 'mobile') { query.mobile = user.mobile; if (user.captcha != req.session.captcha) { return res.send({ status: 'error', type: user.type, currentAuthority: 'guest', }); } } let dbUser = await Models.UserModel.findOne(query); if (dbUser) { dbUser.userid = dbUser._id; dbUser.name = dbUser.mail; let token = jwt.encode(dbUser, config.secret); res.send({ status: 'ok', token, type: user.type, currentAuthority: dbUser.currentAuthority }); } else { return res.send({ status: 'error', type: user.type, currentAuthority: 'guest', }); } }); app.get('/api/currentUser', async (req, res) => { let authorization = req.headers['authorization']; if (authorization) { try { let user = jwt.decode(authorization.split(' ')[1], config.secret); user.userid = user._id; user.name = user.mail; res.json(user); } catch (err) { res.status(401).send({}); } } else { res.status(401).send({}); } }); app.listen(4000, () => { console.log('服务器在4000端口启动!'); }); function rand() { let min = 1000, max = 9999; return Math.floor(Math.random() * (max - min)) + min; } ``` ### 5.2 db.js db.js ```js const mongoose = require('mongoose'); const Schema = mongoose.Schema; const ObjectId = Schema.Types.ObjectId; const { dbUrl } = require('./config/config.prod'); const conn = mongoose.createConnection(dbUrl, { useNewUrlParser: true, useUnifiedTopology: true }); const UserModel = conn.model('User', new Schema({ userid: { type: String },//邮箱 name: { type: String }, mail: { type: String, required: true },//邮箱 password: { type: String, required: true },//密码 mobile: { type: String, required: true },//手机号 avatar: { type: String, required: true },//头像 currentAuthority: { type: String, required: true }//当前用户的权限 })); module.exports = { UserModel } ``` ### 5.3 sms.js sms.js ```js const axios = require('axios'); const smsConfig = require('./smsConfig'); //http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1:sendsms module.exports = async (mobile, captcha) => { const url = 'https://open.ucpaas.com/ol/sms/sendsms'; let result = await axios({ method: 'POST', url, data: { sid: smsConfig.sid, token: smsConfig.token, appid: smsConfig.appid, templateid: smsConfig.templateid, param: captcha, mobile }, headers: { "Content-Type": "application/json;charset=utf-8", "Accept": "application/json" } }) return result; } ``` ### 5.4 smsConfig.js smsConfig.js ```js module.exports = { sid: '32548fb951ac0df279db0e6e9a515566', //开发者账号id token: 'aa0309c08920ca38201de69eb3c745b6', //开发者token appid: '16129d504b7c484c9e8f09b4ec929983', //应用id templateid: '387675' //短信模板id } ``` ### 5.5 config\config.dev.js config\config.dev.js ```js module.exports = { secret: 'zhufengcms', dbUrl: "mongodb://localhost:27017/zhufengcms", origin: ["http://localhost:8000"] } ``` ### 5.6 config\config.prod.js config\config.prod.js ```js module.exports = { secret: 'zhufengcms', dbUrl: "mongodb://antdesign-mongo:27017/zhufengcms", origin: ["http://47.104.204.74", "http://47.104.204.74:8000"] } ``` ### 5.6 Dockerfile Dockerfile ```js FROM node ENV NODE_ENV production LABEL name="antdesign-server" LABEL version="1.0" COPY . /app WORKDIR /app RUN npm install EXPOSE 4000 CMD npm start ``` ### 5.7 .dockerignore .dockerignore ```js .git node_modules package-lock.json Dockerfile .dockerignore ```