大家好,我是梅巴哥er
。本篇是在写react这个项目时做的流水笔记。这篇是前端部分,也就是基本的页面组件和样式。
项目已完结,等我研究研究github
,到时候把完整代码传上去做个分享。
kdlzx项目笔记
1,安装react脚手架,用来搭建项目
npm install -g create-react-app
2,创建项目kdlzx
create-react-app kdlzx
3,进入安装目录
cd kdlzx
4,启动项目查看安装是否成功
npm start
5,项目集成less
-
解包脚手架
npm run eject
(注:这个命令是为了把隐藏的webpeck文件暴露出来。此操作不可逆,操作须慎重) -
解包后,启动项目。看下该项目是否正常启动。如果由类似报错:
Connot find module '@babel/plugin-transform-react-jsx'
。可以尝试解决办法:- 删除项目下
node_modules
文件夹 node i
- 删除项目下
-
安装less环境
-
安装less 和 less-loader 。
npm i less less-loader -S
-
把less 和 less-loader配置到webpack文件中。大致配置三处。
-
第一处:模仿css来配置less。
代码:
// 添加配置less模块 const lessRegex = /\.less$/; const lessModuleRegex = /\.module\.less$/;
-
第二处:
代码:lessOptions, // 这个是放在上面括号里面的,看图 // 这里也要添加一下less配置和 less-loader { loader: require.resolve('less-loader'), options: lessOptions, },
-
第三处:
代码:// 这里模仿sass 配置less和less-loader // Adds support for LESS Modules, but using LESS // less start { test: lessRegex, exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction && shouldUseSourceMap, }), sideEffects: true, }, { test: lessModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent, }), }, // less end;
-
-
-
-
6,搭建项目文件
-
把src文件夹的文件全部删除。自己写入口文件
index.js
。写入代码,,启动测试。 -
在src文件夹中,新建文件夹
assets
。这个文件夹下,放fonts styles images
等文件夹。 -
ReactDOM.render()
放在App.js
里,而不是index.js
里 -
index.js
里放什么呢?index.js
是入口文件,在项目中,就像一个清单一样。用来引入核心组件,例如./pages/App
,引入初始化的样式,引入全局样式等。
7,测试less的安装和配置情况
-
在
setyles
文件夹,新建core.less
文件,加入代码:@charset "utf-8"; @color: pink; body { background-color: @color; }
npm start
查看运行结果。样式显示出来,就说明配置成功了。
8,登录页面展示,开始布置登录页面。
9,重置样式、设置页面高度
- 安装:
npm install --save normalize.css
https://github.com/necolas/normalize.css
- 在
index.js
中引入import 'normalize.css'
10,封装自己的第一个组件: Img组件,把引入img抽离成一个组件
-
需要抽离组件的原因是, 我们每次引入图片时,前面的路径都是重复的。 比如引入logo图片,
import Img from '../assets/images/logo.jpg'
,其实就是路径'../assets/images/'
+ '文件名logo.jpg
’ -
需要注意的是,平时引入都是用
import
, 但是为了方便写组件,在Img.js
里,用了require
。 require里包含 重复的路径 + 文件名。 文件名通过父组件LoginPage的参数props来获取。
11,用户名,密码的登录表单
-
用户名和密码输入框很像,可以做成一个组件来调用
-
登录,忘记密码,免费注册,游客登录等,都是点击的按钮,也可以做成组件
-
字体的引入。用阿里的
iconfont
,把需要的字体下载下来,然后这6个样式文件放入fonts
文件夹中。用import
引入iconfon.css
。一定不要少引入样式文件,不然图标显示不出来的。 -
设置
input
输入框的样式。达到美观效果。注意全局样式和局部样式。全局样式写在core.less
里面。注意,这里图标是固定的大小和位置,右边输入框是弹性的,所以用到了弹性布局display: flex;
。 -
调整输入框里不一样的内容。比如图标、placeholder的属性值、type类型等。用父传子的参数进行调整。 需要注意的是,图标的className的拼接方式
*className*={"iconfont icon-" + this.props.iconName}
,千万不要忘了大括号{ },且要注意大括号的位置。因为js语法都要写在{}里。 -
4个按钮的组件抽离。边写边考虑4个按钮的不同之处,然后加上相应的参数。通过三元运算符的判断,来决定他们用哪些属性。
-
注意,按钮的组件,我们用的是双标签,和输入框略微有点区别。 因为这样更容易通过
this.props.children
来获得按钮里的字。 -
因为这4个按钮的大小和位置都不一样,所以要通过组件的类和样式,对按钮进行调试和代码简化。需要注意,这里的登录按钮里,
<button *className*={ this.props.isFull ? "btn full" : "btn" } >
,这里面有对类进行判断,所以写法上要注意。 还有一种模板语言的写法:<button className={ 'btn ${this.props.isFull ? "full" : ""} '}
。还有一种用加号+连接的方法:<button className={"btn" + (this.props.isFull ? " full" : " ")}
,这种方法不要少了小括号哦( ),不然意思就不一样了。 -
关于空格的小知识点,
是不换行空格,受字体宽度影响。&emsp
是一个中文宽度的空格。这两者基本不受字体宽度的影响。详情可看html中的空格标记 -
关于
touch-action
的知识点。这个语句在写APP的时候,是必写的。在core.less
全局样式文件里,有一个属性touch-action: pan-y;
,这个意思是启用单指垂直平移手势
,可以用手指拖动页面上下滑动。更多touch-action知识
12,为主页HomePage做准备之移动端UI框架antd-mobile
-
12.1 项目需要用到移动端的UI框架
antd-mobile
(官方文档)。所以需要先熟悉一下框架应用。-
安装
npm install antd-mobile --save
-
在
index.html
中添加html的相关配置。第一个<script>
文件是解决手机端比web端延时300ms的问题。第二个<script>
文件是解决兼容性问题,如果不支持promise,我就会引入一个可以支持promise的文件。<!DOCTYPE html> <html> <head> <!-- set `maximum-scale` for some compatibility issues --> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script> <script> if ('addEventListener' in document) { document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body); }, false); } if(!window.Promise) { document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>'); } </script> </head> <body></body> </html>
-
在
index.js
中引入样式import 'antd-mobile/dist/antd-mobile.css'; // or 'antd-mobile/dist/antd-mobile.less'
(或者在需要用到该样式的js文件里直接引入) -
实现
按需加载
,这里只介绍官方推荐的简便方法。另一种方法相对麻烦点,不推荐。**按需加载:**只需引入业务中需要的组件即可,未 import 进来的组件不会打包进来。当在开发环境的console控制台中出现
这样的警告语句时,就要注意了,说明你还在用整个包来加载,现在就需要用按需加载了。-
使用
babel-plugin-import
(推荐)// .babelrc or babel-loader option { "plugins": [ ["import", { libraryName: "antd-mobile", style: "css" }] // `style: true` 会加载 less 文件 ] }
然后只需要从
antd-mobile
中引入需要的模块即可,无需单独引入某个需要的样式。操作方法:
-
首先,安装:
npm install babel-plugin-import
-
然后,打开
package.json
文件,找到最下面的babel
选项。 -
最后,把代码下面的代码,配置进去。
"plugins": [ ["import", { "libraryName": "antd-mobile", "style": "css" }] // `style: true` 会加载 less 文件 ]
添加后的代码如图:
注:style的属性为true的时候加载未编译为css的less文件,也就可以改变主题如果style:“css”就是加载编译好的css,无法更改主题
现在,就没有那个警告了。解决。
-
-
-
13,header部分
-
用到了UI视图中的
NavBar SearchBar
组件 -
引入合适的组件代码,修改组件中的样式
-
用到了
display: flex
布局,比如撑开盒子flex: 1;
,让盒子回归默认样式flex: 0 1 auto
。关于布局:flex: 0 1 auto啥意思? -
用到了组件嵌套。比如:
<NavBar> <SearchBar placeholder="搜索" maxLength={8} /> </NavBar>
-
用到了
less
样式内的引入和抽离。比如 背景颜色很多用到了background-color: #0A338F
。那就把这个样式抽离出来,专门写到global.less
里。 有哪里需要,就用@import
引入使用。 -
用到了对
antd-mobile
框架自带样式的调整。调整方法就是,在页面F12,找到对应的样式,然后在less文件中进行直接写入修改。
14,banner部分(也叫走马灯,轮播图,焦点图等)
- 用到了UI框架的走马灯组件
Carousel
- 需要把两翼空白的组件标签
<WingBlank size='md'>...</WingBlank>
去掉。让图片把banner部分的容器铺满,不留空白。 - 在
this.setState
里面,把图片换上自己的大小合适的图片。
15,学科导航
- 用到了UI框架的
Flex
组件 - 用到了精灵图,用
backgroundPositionX
来选择精灵图中的具体的对应图片。 - 我没有精灵图,直接截了图片的,看着有点难看。。。
16,小列表资讯
- 这部分是写在
HomePage.js
里的。暂时放这里。 - 用到了
数组.map((v, k) => { })
,进行遍历创建列表。v
是数组中的每一项元素。k
对应的是id或者有顺序的数字,拿来给key
用的。 - 创建的一个小列表里,包含左边的图片,和右边的文字。所以用到了UI框架
<Flex />
。 - 一个小列表里的左右布局,用到了
flex
。 左边需要图片盒子回归到默认大小,而不是让左右两边平均分摊小列表的大小,所以用到了flex: 0 1 auto
。而右边的文字部分,是要把右边部分撑开的,所以用到了flex: 1;
。 - UI框架
<Flex />
中,右边的文字默认是居中的,也就是align-items: center
,但是我们需要修改成文字是挨着顶部的,也就是默认状态。即align-items: stretch;
。点击了解耕多关于align-items
17,Tab栏
- 用到了
<Tabs />
UI框架。注意这里的引入,还跟了个const tabs={ ... }
- 用到了
<List />
UI框架中的Items
。注意这里引入的是List
,同时,还需要加上const Item = List.Item
列表引入。 - 用到了
<Flex />
UI框架,还有font字体。都是之前用到过的了。
18,主页HomePage尾部工作
- 这个尾部的框,和头部的框是相似的,所以可以套用
<Header />
组件。 - 通过三元运算符,做个判断,把组件中不要的部分去掉。
- 复习一下行内标签添加
style
的方式:<div style={{ width: "100%", textAlign: "right"}} >
19,长列表页ListPage
- 首先是头部,直接放上
<Header />
组件。拿过来就能用。这也是组件的好处, - 下面就是长列表了。这个是真的长。而且UI框架里的
<ListView />
代码,也是真滴长啊。 - 这个组件拿过来其实也能直接用。但是一个小的难点在于,要想把小列表设计成自己想要的样式,需要做些什么。
- 对长组件进行修改。仔细看组件代码,找到这里,这就是我们要放置自己对列表设计的地方。
const obj = data[index--]; return ( <**SubListItem** *obj*={ obj } /> )
。这个return
里包含的就是每个小列表的样子。 - 代码越写越多,越来越长。精简是必要的。对于组件,要尽可能的抽离和简化,那么 对于样式,也是如此。比如样式的导入和引用。熟悉
less
的入门知识就必不可少了。
20,详情页DetailPage
- 头部直接用UI框架的
<Header />
组件。但是,有些不一样的地方需要调整。需要加一个判断–调用者是否是详情页isDetail
,如果是详情页,就用详情页的头部属性。用到了三元运算符。这里有两种写法,其中 第二种比较好理解。 - 文章部分,用到了
<article>
和内容<content>
标签。这部分就是往里塞<p>
标签,比较好理解。 - 相关资讯,直接调用写好的组件
<SubList />
。 - 热门评论。这块和相关资讯外形及其相似。 但是还有不少不同点。所以这块是自己写的。用到了
<Flex />
布局框架。细心,多练。这些知识,跟着写一遍两边,肯定也达不到熟练的程度。需要自己多写。
加一个判断–调用者是否是详情页isDetail
,如果是详情页,就用详情页的头部属性。用到了三元运算符。这里有两种写法,其中 第二种比较好理解。
- 文章部分,用到了
<article>
和内容<content>
标签。这部分就是往里塞<p>
标签,比较好理解。 - 相关资讯,直接调用写好的组件
<SubList />
。 - 热门评论。这块和相关资讯外形及其相似。 但是还有不少不同点。所以这块是自己写的。用到了
<Flex />
布局框架。细心,多练。这些知识,跟着写一遍两边,肯定也达不到熟练的程度。需要自己多写。
21,添加路由
- 安装
npm install react-router-dom
- 在Login页面,点击登录跳转到首页/home
- 点击home中的学科导航,跳转到长列表页面/list
- 点击list的小列表内容,跳转到详情页/detail
- 给logo图片加上
<Link to='/home'>
,点击页面的Logo都可以回到首页、 - 给
Header
的返回箭头加上点击事件,点击后都可以返回上一页。onLeftClick={() => window.history.go(-1) }
或者this.props.history.goBack()
(注意这里的props需要继承父组件) - 需要注意这里的写history的方式,这里在官方文档是更新了的。
- 遇到的问题,页面跳转后,小列表内容里的文字不见了。F12查看,文字还在,但是没显示。经查看,可能是a标签的color给的是白色导致的。所以给小列表的样式加上字体颜色就解决了。
22,受控组件
-
受控组件,是让login页面的输入框变成受控组件。组件的核心属性
value
和onChange
-
用户输入的信息,可以实时监测,方便对输入框的内容进行判断,是否符合输入要求。
-
写受控组件有个小技巧。正常情况下,login的form是一个组件,输入框是子组件,应该是输入框的变化,传给父组件login的form表单,也就是数据的子传父。这就有点麻烦。那就改变一下方法,变成父传子,就会方便很多。 在login页面组件里写
constructor
构造函数,初始化状态this.state
,添加value
和onChange
属性。然后在子组件输入框中,添加value={ this.props.value }
和onChange={ this.props.onChange }
来接收父组件传来的value和onChange。 -
简写形式。写好之后,input输入框是这样的:
<input type={this.props.type} placeholder={this.props.placeholder} value={ this.props.value } onChange={ this.props.onChange } />
可以看出来,input里的属性,都是来自于父组件。所以,这里可以简写为:
<input {...this.props} />
。这表示 输入框的属性全部继承父组件的属性。 -
注意一个细节。给icon加不同名字时,要写做
iconname
,而不要写做iconName
23,使用axios发送请求后跳转到首页(而不是用Link)
-
在父组件login里面,写入
onClick
属性,建立点击函数handleClick
。 然后把点击事件传给子组件formbtn,onClick={ this.props.onClick }
-
在父组件的
handleClick
函数里,判断点击登录时,用户输入的用户名和密码是否符合要求。 -
在父组件的
handleClick
函数里,用axios
向后台发起登录请求。 -
安装axios:
npm install axios
-
引入axios:
import axios from 'axios'
-
发起请求 ,以get为例。
axios.get('路径', 参数数据).then(...).catch(...)
-
跳转方法:
this.props.history.push('/home')
有浏览记录的跳转this.props.history.replace('/home')
没有浏览记录的跳转
-
跳转时需要注意,需要阻止按钮在浏览器的默认事件。
e.preventDefault()
不然跳转不过去。 -
跳转提示。用到了
antd-mobile
的{ Toast }组件的API。先引入import { Toast } from 'antd-mobile'
,然后直接引用不同的API即可。括号里面是参数,可以根据自己的需求 进行修改。- 跳转成功:
Toast.success(content, duration, onClose, mask)
- 跳转失败:
Toast.fail(content, duration, onClose, mask)
- 跳转信息:…
- 跳转成功:
24,前后端联调
token知识,不太懂。 稍后回去补习node的知识。
25,subjec数据渲染
- 把
subject.json
里的数据,渲染到Subject.js
组件里。 - 在
componentDidMount() { }
里发起请求。(这个需要记住) - 请求需要用到axios。所以先引入
import axios from 'axios'
- 用到了数据变化,所以需要把
constructor、super、this.state
写出来 - 在发出的请求体里 写上
this.setState
状态的变化,获取到subject.json
的数据 - 用
map
遍历生成每一项。这里需要注意的是,如果一次性把7条数据都生成,就都在同一行显示了。所以这里用if()
进行判断。每4个渲染一次 生成一行。然后下面的4个再渲染一次。
26,subject组件中的路由传参
- 列表页一般都有很多页。比如subject学科就有7个学科。点击每一个学科,怎么跳转到对应的学科列表内容呢?
- 这就用到了传参。 传参就是传的id值
- 这个id值在哪里呢?在this.props的match的params里面。
- 怎么识别每一个列表页呢? 这就用到了列表id。在Route的路径中修改为
/list:id
,然后把a连接中的id加进去。 就给每一个url,加入了id
27,redux
-
Redux是JS状态容器,提供项目中的状态管理
-
Redux的三个重要概念:
store
:数据仓库。存储数据用的,把数据都放在仓库里。action
:组件的动作名和动作的定义。它决定了如何修改仓库数据dispatch
:执行相应的action。它决定了何时修改仓库数据
-
安装redux:
npm install redux
-
引入创建仓库
import {createStore} from redux
-
请一个管理员
reducer
,来管理仓库store。管理员必须是函数const reducer = (state, action) => { ... return { 要传入的数据 //例如 num1: 20 } }
-
创建一个仓库
store
,把仓库管理员请来管理仓库。const store = createStore(reducer)
-
在组件中,获取初始state。
constructor(props) { super(props) // 在这里取初始的state数据 this.state = store.getState() }
-
在组件的return里写入数据
render() { return ( <div> <p>{ this.state.num1 }</p> </div> ) }
-
处理事件。
<div> <button onClick={this.changeNumUp}>增加</button> </div>
-
事件函数
changeNumUp
。修改数据的时候,需要调用store中的dispatch
方法,把新的值放在action
对象中传进去。 每次调用dispatch,会在内部调用仓库管理员reducerchangeNumUp = (e) => { const action = { type: 'up', // 修改数据的动作名称 value: this.state.num1 + 1 // 修改数据的方式,每次修改都让这个数据加1 } store.dispatch(action) // 调用仓库store的dispatch方法 } storeChange=(e) => { this.setState(store.getState()) }
-
回到管理员reducer,把action对象深拷贝到newState。这时reducer中的state发生了变化,但是组件中的state还没有变化,也就是渲染到页面的数据还是显示原来的数据。
const reducer = (state, action) => { if(action.type === 'up') { let newState = JSON.parse(JSON.stringfy(state)) // 做深拷贝 newState.num1 = action.value return newState } // 这里是定义初始的state return { num1: 20 } }
-
store订阅。一旦store数据发生改变,则执行storeChange函数里面的状态。这一步写在this.state的下面:
store.subscribe(this.storeChange) // subscribe监听每次修改情况。然后把变化后的数据渲染到页面
28,react-redux
- 为了方便使用,redux的作者,专门给react封装了一个库–
react-redux
。 - react-redux提供了API。并且遵守它的组件拆分规范。
- react-redux将所有组件拆分为两大类:
- UI组件
- 容器组件
- UI组件
- 只负责UI的呈现,不带有任何业务逻辑
- 没有状态(即不使用
this.state
这个变量) - 所有数据都由参数(
this.props
)提供 - 不使用任何
Redux
的API
- 容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现
- 带有内部状态
- 使用Redux的API
connect()
- react-redux提供了connect()方法,用于从UI组件生成容器组件。把两种组件连接起来。
Provide
组件- react-redux提供Provide组件。它可以让容器组件拿到state状态数据。
<Provide></Provide>
包裹了原来项目的根组件。
- react-redux提供Provide组件。它可以让容器组件拿到state状态数据。
- 安装react-redux:
npm install react-redux
- 注意,为了修改一个数据,要用到以上这么多组件和方法,不是更复杂了吗?所以,这就牵扯到一个问题:到底什么时候需要用到
react-redux
呢? 当然是有复杂的数据需要传递和修改时才用到它,而不是什么情况下都去用它。如果项目本身复杂程度不高,就不需要用它了。
29,项目中使用react-redux----使用它渲染数据
-
以学科列表
Subject.js
为例,第一步,先进入到顶级组件(就是要被Provide包裹的组件)App.js
文件里 -
第二步,在顶级组件里,引入初始状态数据
defaultState
。先请一个管理员reducer
来,然后创建一个仓库store
,交给这个管理员来管理createStore(reducer)
const defaultState = { subject_data: [] } // 请一个仓库管理员(管理员必须是一个函数) const reducer = (state = defaultState, action) => { return state } // 创建一个仓库,把上面的管理员请来管理这个仓库 const store = createStore(reducer)
-
第三步,继续在顶级组件里操作,把
<Router></Router>
包裹在<Provider store={ store } ></Provider>
里。const router2 = <Provider store={ store } > <Router history={ history } > <Route exact path='/' component={ LoginPage } /> <Route path='/home' component={ HomePage } /> <Route path='/list:subjectId' component={ ListPage } /> <Route path='/detail' component={ DetailPage } /> </Router> </Provider>
-
第四步,从
redux
和react-redux
分别引入需要用到的方法和组件。import { createStore } from 'redux' import { Provider } from 'react-redux' // 注意: createStore 是从redux里引入的 // Provider是从react-redux中引入的
-
第五步,
connect
连接。哪个文件需要用到仓库里的数据,就到那个文件里建立connect
连接。这里的项目中,仓库是放在顶级组件App.js
里的,学科列表文件Subject.js
需要用到仓库里的数据,那就到Subject.js
文件里建立连接。connect(定义state数据转props数据的函数,修改数据的函数)(用仓库数据的这个组件)
。在Subject.js中的代码是:// 在文件最上部,引入connect import { connect } from 'react-redux' //然后在文件的最下部 // 定义state数据转props数据的函数 const mapStateToProps = (state) => { return { subject_data: state.subject_data } } // 和仓库数据建立连接 export default connect(mapStateToProps, null)(Subject) // 这里的修改数据函数还没写,所以这个位置先写成null了
-
第六步:
Subject.js
现在是一个UI组件了,所以这里面不能有this.state
了,而是要把所有需要用this.state的地方,都改成this.props
。(而对应的,App.js
就是一个容器组件了,这里面写有状态和API,而不负责UI呈现。于是,这样就完成了组件拆分。) -
第七步,修改数据。
- 在
Subject.js
中,创建一个修改数据的函数mapDispatchToProps
- 创建
action
,用来说明修改数据的方式 - 调用
dispatch(action)
方法。按照action说明的方式,对数据进行修改。 - 在
axios
发起的请求里,调用这个包含action的方法。表示当发起请求时,把数据传给action的value,然后对数据进行修改。 - 把
connect
中的修改数据函数补充进去 - 在管理员
reducer
中,进行深拷贝修改的数据
- 在
具体代码如下:
// Subject.js
componentDidMount() {
// 在这里面发起请求
axios.get('/server/subject.json')
.then((res) =>{
// 拿到subject.json中的数据
console.log(res.data)
this.props.init_subject_data(res.data)
// res.data就是json数据。
// 把这个数据以实参的形式传给init_subject_data这个函数
})
}
// 用dispatch修改数据
const mapDispatchToProps = (dispatch) => {
return {
// 这里不能使用箭头函数 哦
init_subject_data (res_data) {
// 创建一个action,来说明应该如何修改数据
let action = {
type: 'init_subject_data',
value: res_data
}
dispatch(action)
}
}
}
// 和仓库数据建立连接
export default connect(mapStateToProps, mapDispatchToProps)(Subject)
// App.js
// 请一个仓库管理员(管理员必须是一个函数)
// 在这里面进行深拷贝,管理仓库
const reducer = (state = defaultState, action) => {
if (action.type === 'init_subject_data') {
let newState = JSON.parse(JSON.stringify(state)) // 深拷贝数据
newState.subject_data = action.value
return newState
}
return state
}
30,项目部署
-
购买服务器
- 选择云服务器:阿里云服务器
- 个人免费获取:[阿里云试用中心] (https://free.aliyun.com/)
-
配置服务器
-
远程连接到服务器
- 打开终端输入:
ssh root@公网ip地址
- 输入密码
- 看到
root@xxxxxxxxxxxxxxxxx:-#
,表示登录成功
- 打开终端输入:
-
部署项目需要的环境
-
上传项目
npm build
,得到一个build
文件夹 -
把
build
文件夹传到云服务端 -
启动项目
----------------------------------------------End----------------------------------------------------------
---------------------------前端这块,到此基本结束。接下来是后端以及交互的内容。