733 lines
20 KiB
Markdown
733 lines
20 KiB
Markdown
[TOC]
|
||
|
||
# 前端通用框架
|
||
|
||
## 框架介绍
|
||
|
||
基于`vue@2.6`, `ant-design-vue@1.7`, `axios`, `vue-router`, `vuex`, `webpack@4.x` 的通用后台管理系统
|
||
|
||
功能:
|
||
|
||
1. 根据路由动态生成侧边导航
|
||
2. 使用`mock.js`和`mock-serve`进行本地mock
|
||
3. 集成`eslint`和`prettier`配置
|
||
4. 集成`commit插件`
|
||
5. 集成`git`钩子, 必须通过`eslit`代码检查和`commit`提交规范检查才能提交代码
|
||
6. `ant-design-vue` 组件按需引入, ant-design icon 按需引入
|
||
7. 响应错误统一处理
|
||
8. `vue-ls`本地持久化
|
||
9. 支持设置主题
|
||
10. 权限自定义指令
|
||
|
||
## not Support
|
||
|
||
1. ie11 兼容
|
||
2. h5 兼容
|
||
3. 响应式
|
||
|
||
## 快速开始
|
||
|
||
推荐使用vscode开发, 需要安装`prettier`, `eslint`, `vetur`插件
|
||
|
||
```bash
|
||
# 需要全局安装commitizen才能使用 git cz
|
||
npm install commitizen -g
|
||
# 克隆项目
|
||
git clone xxx
|
||
git cd xxx
|
||
# 安装插件
|
||
yarn
|
||
# 启动服务
|
||
yarn run dev
|
||
# 打包
|
||
yarn run build
|
||
```
|
||
|
||
## 目录结构
|
||
|
||
```json
|
||
├─.browserslistrc ------------------------ // 目标浏览器配置
|
||
├─.env.development ----------------------- // 开发环境变量配置
|
||
├─.env.production ------------------------ // 开发环境变量配置
|
||
├─.eslintignore -------------------------- // eslint忽略文件
|
||
├─.eslintrc.js --------------------------- // eslisnt配置
|
||
├─.gitignore ----------------------------- // git忽略配置
|
||
├─.prettierignore ------------------------ // prettier忽略配置
|
||
├─.prettierrc.js ------------------------- // 代码风格配置
|
||
├─.vscode -------------------------------- // vscode项目设置
|
||
│ └─settings.json ------------------------ // vscode配置
|
||
├─README.md
|
||
├─babel.config.js ------------------------ // js编译器设置
|
||
├─commitlint.config.js ------------------- // git commit lint
|
||
├─jsconfig.json -------------------------- // JavaScript项目的根目录配置
|
||
├─package.json --------------------------- // npm包管理
|
||
├─public --------------------------------- // 静态资源文件夹
|
||
│ ├─color.less --------------------------- // 主题颜色配置
|
||
│ ├─favicon.ico -------------------------- // 网站图标
|
||
│ └─index.html --------------------------- // 主页
|
||
├─src ------------------------------------ // 代码目录
|
||
│ ├─App.vue ------------------------------ // 项目根组件
|
||
│ ├─api ---------------------------------- // api目录
|
||
│ │ └─user.js ---------------------------- // 用户相关接口
|
||
│ ├─assets ------------------------------- // 资源文件夹
|
||
│ │ ├─avatar.jpg ------------------------- // 默认头像
|
||
│ │ ├─background.svg --------------------- // 登录页背景
|
||
│ │ ├─checkcode.png ---------------------- // 验证码默认图
|
||
│ │ ├─icons ------------------------------ // 图标相关
|
||
│ │ │ ├─index.js
|
||
│ │ │ ├─svg ------------------------------ // svg图标存放路径
|
||
│ │ │ │ ├─develop.svg
|
||
│ │ │ │ └─svgtest.svg
|
||
│ │ │ └─svgo.yml ------------------------- // svg图标格式化配置
|
||
│ │ ├─less ------------------------------- // 样式
|
||
│ │ │ └─common.less ---------------------- // 公共样式
|
||
│ │ └─logo.png
|
||
│ ├─components --------------------------- // 公共组件
|
||
│ │ ├─Header.vue ------------------------- // 头部
|
||
│ │ ├─SettingDrawer ---------------------- // 系统设置(主题)
|
||
│ │ │ ├─index.vue
|
||
│ │ │ └─settingConfig.js ----------------- // 主题颜色生成
|
||
│ │ ├─SvgIcon.vue ------------------------ // svg图标组件
|
||
│ │ ├─TopTabs.vue ------------------------ // tab栏导航
|
||
│ │ ├─UserPassword.vue
|
||
│ │ ├─layouts ---------------------------- // 布局相关
|
||
│ │ │ ├─AggregateLayout(可以删除).vue
|
||
│ │ │ ├─BlankLayout.vue ------------------ // 空白布局
|
||
│ │ │ ├─GlobalLayout.vue ----------------- // 全局部局
|
||
│ │ │ ├─SideTabsLayout.vue --------------- // 侧边切换tab布局
|
||
│ │ │ └─UserLayout.vue ------------------- // 登录/注册/找回密码共用布局
|
||
│ │ └─menu
|
||
│ │ ├─Contextmenu.vue ------------------ // 上下文菜单(在顶部导航鼠标右键菜单用到)
|
||
│ │ └─SideMenu.js ---------------------- // 左侧导航栏生成
|
||
│ ├─config
|
||
│ │ └─defaultSettings.js ----------------- // 全局主题颜色/vue-ls配置
|
||
│ ├─main.js ------------------------------ // 入口
|
||
│ ├─mock --------------------------------- // mock
|
||
│ │ ├─constant --------------------------- // 存放较大的数据
|
||
│ │ │ └─userPermission.js ---------------- // 权限模拟数据
|
||
│ │ ├─index.js
|
||
│ │ ├─mock-server.js --------------------- // mock-server本地服务
|
||
│ │ ├─table.js --------------------------- // mock语法示例
|
||
│ │ ├─user.js ---------------------------- // 模拟接口
|
||
│ │ └─utils.js --------------------------- // mock服务工具方法
|
||
│ ├─router ------------------------------- // 路由
|
||
│ │ ├─generateIndexRouter.js ------------- // 动态路由生成
|
||
│ │ └─index.js
|
||
│ ├─store -------------------------------- // vuex
|
||
│ │ ├─index.js
|
||
│ │ └─modules ---------------------------- // vuex模块
|
||
│ │ ├─app.js --------------------------- // 全局
|
||
│ │ └─user.js -------------------------- // 用户相关
|
||
│ ├─utils -------------------------------- // 工具类
|
||
│ │ ├─lazy ------------------------------- // 按需加载
|
||
│ │ │ ├─icons.js ------------------------- // 图标
|
||
│ │ │ └─lazyAntd.js ---------------------- // ant-design-vue组件
|
||
│ │ └─request.js ------------------------- // axios封装
|
||
│ └─views -------------------------------- // 页面
|
||
│ ├─About.vue -------------------------- // 关于
|
||
│ ├─Home.vue --------------------------- // 首页
|
||
│ ├─account ---------------------------- // 账号相关
|
||
│ │ ├─Center.vue ----------------------- // 个人中心页
|
||
│ │ └─settings ------------------------- // 个人设置页
|
||
│ │ ├─BaseSetting.vue
|
||
│ │ ├─Binding.vue
|
||
│ │ ├─Custom.vue
|
||
│ │ ├─Notification.vue
|
||
│ │ └─Security.vue
|
||
│ ├─exception -------------------------- // 404/403/500等错误页
|
||
│ │ └─404.vue
|
||
│ ├─list ------------------------------- // 多级菜单 示例
|
||
│ │ ├─StandardList.vue
|
||
│ │ └─TableList.vue
|
||
│ ├─modules ---------------------------- // 存放页面公共组件
|
||
│ │ └─online
|
||
│ │ └─desform ------------------------ // 列表页跳详情页 示例
|
||
│ │ ├─DesignFormList.vue
|
||
│ │ └─DesignFormTempletList.vue
|
||
│ ├─oneLevelMenu ----------------------- // 一级菜单示例页面
|
||
│ │ └─index.vue
|
||
│ ├─profile ---------------------------- // 路由聚合示例
|
||
│ │ ├─Advanced.vue
|
||
│ │ └─Basic.vue
|
||
│ ├─sideTabs --------------------------- // 侧边栏tab标签 示例
|
||
│ │ ├─components
|
||
│ │ │ ├─Tab1.vue
|
||
│ │ │ ├─Tab2.vue
|
||
│ │ │ ├─Tab3.vue
|
||
│ │ │ └─Tab4.vue
|
||
│ │ └─index.vue
|
||
│ ├─title
|
||
│ │ └─index.vue
|
||
│ └─user ------------------------------- // 登录/注册/找回密码 相关
|
||
│ ├─Login.vue ------------------------ // 登录页
|
||
│ └─LoginSimple.vue
|
||
├─test ----------------------------------- // 测试用例
|
||
│ └─test.js
|
||
├─vue.config.js -------------------------- // vue-cli配置
|
||
└─yarn.lock ------------------------------ // npm包版本控制
|
||
```
|
||
|
||
### vue组件的存放路径:
|
||
|
||
1. 框架相关的组件, 存放在`@/components`
|
||
2. 布局相关的, 存在在`@/components/layouts`
|
||
3. 不相关的两个页面的公共组件, 存放在`@/viewmodules`
|
||
4. 父子关系/兄第关系的页面的公共组件, 且没有其他页面使用, 存放在他们的公共根目录下`@/views/xxx/components`
|
||
|
||
## 动态路由
|
||
|
||
### 路由菜单不要超过3层
|
||
|
||
<img src="https://i.loli.net/2021/09/18/opbsBDxKWn2vMId.png" alt="image.png" style="zoom:50%;" />
|
||
|
||
### 路由生成
|
||
|
||
<img src="https://i.loli.net/2021/09/18/1XJzZxdUqrluGDN.png" alt="image.png" style="zoom:50%;" />
|
||
|
||
### 路由配置项:
|
||
|
||
```json
|
||
{
|
||
// "hidden": true, // (默认不传)不在左侧菜单中显示
|
||
// "redirect":null, // (默认不传)重定向
|
||
// "alwaysShow": false, // (默认不传)路由聚合, 从menu菜单展示变为tab栏展示
|
||
"path":"/dashboard/analysis", // 路径
|
||
"component":"dashboard/Analysis", // 组件
|
||
"name":"dashboard-analysis", // 路由名称, 使用<keep-alive>时必填
|
||
"meta":{
|
||
"keepAlive":true, // 是否缓存
|
||
"icon":"home", // 图标
|
||
"title":"首页", // 侧边栏和面包屑中展示的名字
|
||
// "url": "http://www.baidu.com" // (默认不传)当外部链接跳转时必须传, 必须带http:// 或者httPs://
|
||
},
|
||
},
|
||
```
|
||
|
||
举例:
|
||
|
||
"系统管理"为一级菜单
|
||
|
||
"用户管理"为子菜单
|
||
|
||
```json
|
||
{
|
||
"path":"/isystem",
|
||
"component":"layouts/RouteView",
|
||
"name":"isystem",
|
||
"meta":{
|
||
"keepAlive":false,
|
||
"icon":"setting",
|
||
"title":"系统管理"
|
||
},
|
||
"children":[
|
||
{
|
||
"path":"/isystem/user",
|
||
"component":"system/UserList",
|
||
"name":"isystem-user",
|
||
"meta":{
|
||
"keepAlive":false,
|
||
"title":"用户管理"
|
||
},
|
||
},
|
||
{
|
||
"path":"/isystem/roleUserList",
|
||
"component":"system/RoleUserList",
|
||
"name":"isystem-roleUserList",
|
||
"meta":{
|
||
"keepAlive":true,
|
||
"title":"角色管理"
|
||
},
|
||
},
|
||
{
|
||
"path":"/isystem/newPermissionList",
|
||
"component":"system/NewPermissionList",
|
||
"name":"isystem-newPermissionList",
|
||
"meta":{
|
||
"keepAlive":true,
|
||
"title":"菜单管理"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 路由聚合
|
||
|
||
<img src="https://i.loli.net/2021/09/18/zZns3Lr89ClJ2iD.png" alt="image.png" style="zoom:50%;" />
|
||
|
||
1. children中的路由不会在左侧菜单栏中显示
|
||
2. children中的路由只能有一层
|
||
3. children中的路由所对应的组件, 聚合到tab栏组件中, 通过tab栏切换
|
||
4. redirect无效, tab栏顺序为children顺序, 默认展示第一个
|
||
|
||
```json
|
||
{
|
||
path: '/account/settings',
|
||
redirect: '/account/settings/notification',
|
||
component: 'layouts/BlankLayout',
|
||
alwaysShow: true,
|
||
meta: {
|
||
title: '个人设置',
|
||
},
|
||
name: 'account-settings-Index',
|
||
children: [
|
||
{
|
||
path: '/account/settings/notification',
|
||
component: 'account/settings/Notification',
|
||
meta: {
|
||
title: '新消息通知',
|
||
},
|
||
name: 'account-settings-notification',
|
||
},
|
||
{
|
||
path: '/account/settings/base-setting',
|
||
component: 'account/settings/BaseSetting',
|
||
meta: {
|
||
title: '基本设置',
|
||
},
|
||
name: 'account-settings-base',
|
||
},
|
||
{
|
||
path: '/account/settings/binding',
|
||
component: 'account/settings/Binding',
|
||
meta: {
|
||
title: '账户绑定',
|
||
},
|
||
name: 'account-settings-binding',
|
||
},
|
||
{
|
||
path: '/account/settings/security',
|
||
component: 'account/settings/Security',
|
||
meta: {
|
||
title: '安全设置',
|
||
},
|
||
name: 'account-settings-security',
|
||
},
|
||
{
|
||
path: '/account/settings/custom',
|
||
component: 'account/settings/Custom',
|
||
meta: {
|
||
title: '个性化设置',
|
||
},
|
||
name: 'account-settings-custom',
|
||
},
|
||
],
|
||
}
|
||
```
|
||
|
||
### 列表页和详情页
|
||
|
||
1. 简单的详情页, 直接通过`抽屉`/ `模态框`渲染
|
||
2. 复杂的详情页, 通过`/id`跳转到新页面
|
||
|
||
<img src="https://i.loli.net/2021/09/18/FL21JyDI5mWwh3s.png" alt="image.png" style="zoom:50%;" />
|
||
|
||
<img src="https://i.loli.net/2021/09/18/HgJaSM5ik9vwFCR.png" alt="image.png" style="zoom:50%;" />
|
||
|
||
```json
|
||
// 路由结构 举例
|
||
{
|
||
"path": "/test",
|
||
// 加载默认子路由, 不能有name
|
||
"component":"layouts/BlankLayout",
|
||
"meta": {
|
||
"title":"测试页面",
|
||
redirectDefaultChild: true, // 子路由不在菜单栏中显示, 进入子路由显示父路由高亮
|
||
},
|
||
"children":[
|
||
{
|
||
"path": "", // 默认子路由
|
||
"name": "test-list",
|
||
"component":"test/List",
|
||
"meta": {
|
||
"title":"测试页面列表页",
|
||
// "keepAlive": true // 可以设置keepAlive使详情页回到列表页, 不重新渲染组件
|
||
},
|
||
},
|
||
{
|
||
"path": "/test/:id", // 详情页
|
||
"name": "test-detail",
|
||
"component":"test/Detail",
|
||
"meta": {
|
||
"title":"测试页面详情页"
|
||
},
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 外链url跳转
|
||
|
||
<img src="https://i.loli.net/2021/09/18/efP12VRlLObi36v.png" alt="image.png" style="zoom:50%;" />
|
||
|
||
```json
|
||
{
|
||
path: '/urllink',
|
||
component: 'layouts/BlankLayout',
|
||
name: 'baidu',
|
||
meta: {
|
||
title: 'url跳转',
|
||
url: 'https://www.baidu.com', // url必须完整, 带http://或者https://
|
||
},
|
||
}
|
||
```
|
||
|
||
|
||
|
||
## 动态权限
|
||
|
||
待补充完善
|
||
|
||
## mock
|
||
|
||
### (本地)开发模式
|
||
|
||
1. 启动`yarn run dev`时, 会同时启动`mock-serve`, 与mock相关的接口会转发到`mock-serve`
|
||
|
||
2. mock文件示例
|
||
|
||
```js
|
||
// /mock/user.js
|
||
const tokens = {
|
||
admin: {
|
||
token: 'admin-token',
|
||
},
|
||
editor: {
|
||
token: 'editor-token',
|
||
},
|
||
}
|
||
|
||
module.exports = [
|
||
// 登录接口
|
||
{
|
||
url: '/user/login',
|
||
type: 'post',
|
||
response: config => {
|
||
const { username } = config.body
|
||
const token = tokens[username]
|
||
|
||
// mock error
|
||
if (!token) {
|
||
return {
|
||
errcode: 1,
|
||
errmsg: 'Account and password are incorrect.',
|
||
}
|
||
}
|
||
|
||
return {
|
||
errcode: 0,
|
||
data: token,
|
||
}
|
||
},
|
||
},
|
||
|
||
// 其他接口...
|
||
]
|
||
```
|
||
|
||
```js
|
||
// /mock/index.js
|
||
// ...
|
||
const user = require('./user')
|
||
const mocks = [
|
||
...user,
|
||
]
|
||
// ...
|
||
```
|
||
|
||
### (本地)生产模式
|
||
|
||
生产模式只能通过`mock.js`模拟数据, 与`mock-serve`共同使用同一份模拟数据
|
||
|
||
相关配置:
|
||
|
||
```js
|
||
// /src/main.js
|
||
if (process.env.NODE_ENV === 'production') {
|
||
const { mockXHR } = require('./mock')
|
||
mockXHR()
|
||
}
|
||
```
|
||
|
||
请求路径与mock.js中匹配的会被拦截, 但因为是重写了xhr方法, 无法在浏览器的network中看到请求, 可以通过控制台打印
|
||
|
||
==所以上线前需要在`/mock/index.js`把后端已完成的相关接口注释掉, 或者直接在`src/main.js`注释掉mock功能==
|
||
|
||
### 数据切换
|
||
|
||
1. 本地开发模式:
|
||
|
||
修改`vue.config.js`中的`mockUrls`
|
||
|
||
2. 本地生产模式
|
||
|
||
注释掉`/mock/index.js`中的对应接口
|
||
|
||
```js
|
||
const mocks = [
|
||
...user,
|
||
// ...table
|
||
]
|
||
```
|
||
|
||
全部取消:
|
||
|
||
```js
|
||
// src/main.js
|
||
// 注释掉这部分
|
||
// if (process.env.NODE_ENV === 'production') {
|
||
// const { mockXHR } = require('../mock')
|
||
// mockXHR()
|
||
// }
|
||
```
|
||
|
||
## ui组件, icon按需引入
|
||
|
||
1. ui组件在`src/utils/lazy/lazyAntd.js`中按需引入
|
||
|
||
2. ant-design-icon在`src/utils/lazy/icons.js`中按需引入
|
||
|
||
3. 其他icon:
|
||
|
||
1. 使用`Iconfont(阿里巴巴图标库)`下载svg图标, 统一放在`@/assets/icon/svg中`
|
||
|
||
2. 执行`yarn run svgo`去掉多余信息格式化图标
|
||
|
||
3. 使用`<svg-icon icon-class="xxx" />`调用
|
||
|
||
```html
|
||
<svg-icon style="font-size: 18px; color: pink" icon-class="svgtest" />
|
||
```
|
||
|
||
## 公共js , css, 工具js
|
||
|
||
* 公共js, css: 框架里只保留最小使用
|
||
|
||
* 工具js: 通用性高的工具js和框架一起维护, 需要保持工具js的单一性, 完善注释
|
||
|
||
## 体积较大的第三方js插件
|
||
|
||
例如 "excel"的导出插件, 统一放在`/src/vender`中
|
||
|
||
## 泛用组件
|
||
|
||
例如"季度选择组件", 存放到gitlab公共组件仓库中
|
||
|
||
todo:
|
||
|
||
- [ ] 打包成npm包
|
||
- [ ] git submodules
|
||
|
||
## 开发规范
|
||
|
||
### 数据处理
|
||
|
||
* 后端接口获取的数据, 只取页面需要的, 不能直接存到data中
|
||
|
||
* 当某个接口的数据有多个页面使用时, 考虑存放到vuex/localstorage中
|
||
* 退出登录时, 需要考虑清空localStorage/sessionStorage中的敏感数据, 例如用户的登录信息, 需要更新的信息
|
||
|
||
### 列表页跳详情页
|
||
|
||
优先使用抽屉, 模态框, 无法满足需求时才考虑跳转新页面
|
||
|
||
### vue单文件的可维护性
|
||
|
||
建议不超过500行, 超过的需要考虑是否可以优化
|
||
|
||
> 1.抽离组件再引入
|
||
> 2.将css通过@import方式抽离再引入
|
||
> 3.将css/js/html全部抽离,只保留模块主入口文件再引入
|
||
|
||
### ui组件
|
||
|
||
优先使用ant-design-vue, 按需引用
|
||
|
||
### icon
|
||
|
||
优先使用ant-design icon , 按需引用
|
||
|
||
### ui风格
|
||
|
||
1. 滚动条/滚动范围:
|
||
|
||
以表格为例: 横向溢出时, 内容加滚动条
|
||
|
||
纵向溢出, 随页面滚动, 表头页脚不固定(优先考虑分页)
|
||
|
||
### 组件通讯
|
||
|
||
* 父 -> 子 : props
|
||
|
||
```js
|
||
//App.vue父组件
|
||
<template>
|
||
<div id="app">
|
||
<users :users="users"></users> <!-- 前者自定义名称便于子组件调用,后者要传递数据名 -->
|
||
</div>
|
||
</template>
|
||
<script>
|
||
import Users from "./components/Users"
|
||
export default {
|
||
name: 'App',
|
||
data(){
|
||
return{
|
||
users:["Henry","Bucky","Emily"]
|
||
}
|
||
},
|
||
components:{
|
||
"users":Users
|
||
}
|
||
}
|
||
|
||
//users子组件
|
||
<template>
|
||
<div class="hello">
|
||
<ul>
|
||
<li v-for="user in users">{{user}}</li> <!-- 遍历传递过来的值,然后呈现到页面 -->
|
||
</ul>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
export default {
|
||
name: 'HelloWorld',
|
||
props:{
|
||
users:{ //这个就是父组件中子标签自定义名字
|
||
type:Array,
|
||
required:true
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
* 子 -> 父 : $emit
|
||
|
||
```js
|
||
// 子组件
|
||
<template>
|
||
<header>
|
||
<h1 @click="changeTitle">{{title}}</h1> <!-- 绑定一个点击事件 -->
|
||
</header>
|
||
</template>
|
||
<script>
|
||
export default {
|
||
name: 'app-header',
|
||
data() {
|
||
return {
|
||
title:"Vue.js Demo"
|
||
}
|
||
},
|
||
methods:{
|
||
changeTitle() {
|
||
this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值”
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
// 父组件
|
||
<template>
|
||
<div id="app">
|
||
<app-header v-on:titleChanged="updateTitle" ></app-header>
|
||
<!-- 与子组件titleChanged自定义事件保持一致 -->
|
||
<!-- updateTitle($event)接受传递过来的文字 -->
|
||
<h2>{{title}}</h2>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
import Header from "./components/Header"
|
||
export default {
|
||
name: 'App',
|
||
data(){
|
||
return{
|
||
title:"传递的是一个值"
|
||
}
|
||
},
|
||
methods:{
|
||
updateTitle(e){ //声明这个函数
|
||
this.title = e;
|
||
}
|
||
},
|
||
components:{
|
||
"app-header":Header,
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
* 兄弟1 -> 兄弟2 :
|
||
* 如果兄弟组件有共同的父组件(子 - 父 - 子), 通过父组件传递
|
||
* 如果链路传递超过2层, 优先考虑vuex模块( 孙 - 子 - 父 - 子)
|
||
|
||
```js
|
||
// 参考 子 -> 父 父 -> 子
|
||
```
|
||
|
||
* 链路传递超过2层, 优先考虑vuex模块
|
||
|
||
* 避免使用事件总线(待补充)
|
||
|
||
* 使用插槽向引用组件传递模板
|
||
|
||
* 普通插槽
|
||
|
||
```js
|
||
// 父组件
|
||
<MyButton>注册</MyButton>
|
||
|
||
// 自定义组件
|
||
<button>
|
||
<slot>按钮</slot>
|
||
</button>
|
||
```
|
||
|
||
* 作用域插槽
|
||
|
||
```js
|
||
<current-user>
|
||
<template v-slot:default="slotProps">
|
||
{{ slotProps.user.firstName }}
|
||
</template>
|
||
|
||
<template v-slot:other="otherSlotProps">
|
||
...
|
||
</template>
|
||
</current-user>
|
||
```
|
||
|
||
|
||
|
||
## 数据持久化
|
||
|
||
### localStorage
|
||
|
||
**在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。**
|
||
|
||
```js
|
||
let defaultCity = "上海"
|
||
try { // 用户关闭了本地存储功能,此时在外层加个try...catch
|
||
if (!defaultCity){
|
||
defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
|
||
}
|
||
}catch(e){}
|
||
export default new Vuex.Store({
|
||
state: {
|
||
city: defaultCity
|
||
},
|
||
mutations: {
|
||
changeCity(state, city) {
|
||
state.city = city
|
||
try {
|
||
window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
|
||
// 数据改变的时候把数据拷贝一份保存到localStorage里面
|
||
} catch (e) {}
|
||
}
|
||
}
|
||
})
|
||
```
|
||
|
||
### sessionStorage
|
||
|
||
参考`localStorage`
|
||
|