diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7dce620..789b7b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,4 +29,4 @@ jobs: # 工作流
BASE_BRANCH: master # 要部署的文件夹所在的分支.
BRANCH: gh-pages # 部署到的分支
FOLDER: docs/.vuepress/dist # 要部署的文件夹.
- BUILD_SCRIPT: npm install && npm run build && cd docs/.vuepress/dist && echo 'b.evanblogweb.com' > CNAME # 部署前要执行的命令 #&& npm run list
+ BUILD_SCRIPT: npm install && npm run build && && ls && cd docs/.vuepress/dist && echo 'b.evanblogweb.com' > CNAME # 部署前要执行的命令 #&& npm run list
diff --git a/docs/.vuepress/config/nav.js b/docs/.vuepress/config/nav.js
index ff0f916..09c10ca 100644
--- a/docs/.vuepress/config/nav.js
+++ b/docs/.vuepress/config/nav.js
@@ -7,13 +7,12 @@ module.exports = [
//{text: 'vue', link: '/web/vue/'},
//{text: 'TypeScript', link: '/web/TypeScript/'},
//{text: 'ES6', link: '/web/ES6/'},
- {text: 'CSS/HTML', link: '/web/CSS-HTML/01.flex布局基础'},
]
},
{
- text: 'python',
+ text: '页面',
items: [
- {text: 'python', link: '/python/'}
+ {text: 'HTML/CSS', link: '/ui/00.flex布局语法'},
]
},
{text: '技术杂谈',items:[
@@ -22,11 +21,15 @@ module.exports = [
{text: 'GitHub高级搜索技巧', link: '/other/github'},
{text: 'Markdown使用教程', link: '/other/markdown'},
{text: 'npm使用教程', link: '/other/npm'},
+ {text: 'yaml教程', link: '/other/yaml'},
]},
{text: '学习',items:[
{text: '学习效果低,忘性很大怎么办?',link: '/other/LearningAndMemory'},
{text: '学习网站分享', link: '/other/study'},
]},
+ {text: '学习笔记',items:[
+ {text: '小程序笔记',link: '/other/note/01.小程序笔记'},
+ ]},
{text: '面试',items:[
{text: '面试问答集锦', link: '/other/interview'},
]},
diff --git a/docs/.vuepress/config/sidebar.js b/docs/.vuepress/config/sidebar.js
index 87ef7ca..2b8b840 100644
--- a/docs/.vuepress/config/sidebar.js
+++ b/docs/.vuepress/config/sidebar.js
@@ -12,21 +12,24 @@ module.exports = { // 侧边栏
['JavaScript/06.多种数组去重性能对比','多种数组去重性能对比'],
]
},
+ ],
+ '/ui/': [
{
- title: 'CSS/HTML',
+ title: 'HTML/CSS',
collapsable: false,
children: [
- ['CSS-HTML/01.flex布局基础','flex布局基础'],
- ['CSS-HTML/02.flex布局案例-骰子','flex布局案例-骰子'],
- ['CSS-HTML/03.flex布局案例-网格布局','flex布局案例-网格布局'],
- ['CSS-HTML/04.flex布局案例-圣杯布局','flex布局案例-圣杯布局'],
- ['CSS-HTML/05.flex布局案例-输入框布局','flex布局案例-输入框布局'],
- ['CSS-HTML/06.CSS3之transform过渡','CSS3之transform过渡'],
- ['CSS-HTML/07.CSS3之animation动画','CSS3之animation动画'],
+ ['00.flex布局语法','flex布局语法'],
+ ['01.flex布局案例-基础','flex布局案例-基础'],
+ ['02.flex布局案例-骰子','flex布局案例-骰子'],
+ ['03.flex布局案例-网格布局','flex布局案例-网格布局'],
+ ['04.flex布局案例-圣杯布局','flex布局案例-圣杯布局'],
+ ['05.flex布局案例-输入框布局','flex布局案例-输入框布局'],
+ ['06.CSS3之transform过渡','CSS3之transform过渡'],
+ ['07.CSS3之animation动画','CSS3之animation动画'],
]
},
],
- '/other/': [// 针对不同页面设置不同侧边栏
+ '/other/': [
{
title: '技术',
collapsable: false, //是否可折叠,可选的,默认true
@@ -36,9 +39,9 @@ module.exports = { // 侧边栏
['github','GitHub高级搜索技巧'],
['markdown','Markdown使用教程'],
['npm','npm使用教程'],
+ ['yaml','yaml教程'],
]
},
-
{
title: '学习',
collapsable: false,
@@ -47,7 +50,13 @@ module.exports = { // 侧边栏
['study','学习网站分享'],
]
},
-
+ {
+ title: '学习笔记',
+ collapsable: false,
+ children: [
+ ['note/01.小程序笔记','小程序笔记'],
+ ]
+ },
{
title: '面试',
collapsable: false,
diff --git a/docs/.vuepress/public/img/other.png b/docs/.vuepress/public/img/other.png
index 03d7224..87f8098 100644
Binary files a/docs/.vuepress/public/img/other.png and b/docs/.vuepress/public/img/other.png differ
diff --git a/docs/.vuepress/public/img/other2.png b/docs/.vuepress/public/img/other2.png
new file mode 100644
index 0000000..c6fe28a
Binary files /dev/null and b/docs/.vuepress/public/img/other2.png differ
diff --git a/docs/.vuepress/public/img/ui.png b/docs/.vuepress/public/img/ui.png
new file mode 100644
index 0000000..3ca9721
Binary files /dev/null and b/docs/.vuepress/public/img/ui.png differ
diff --git a/docs/.vuepress/public/img/ui2.png b/docs/.vuepress/public/img/ui2.png
new file mode 100644
index 0000000..d9836a2
Binary files /dev/null and b/docs/.vuepress/public/img/ui2.png differ
diff --git a/docs/.vuepress/public/img/web2.png b/docs/.vuepress/public/img/web2.png
new file mode 100644
index 0000000..c11cac9
Binary files /dev/null and b/docs/.vuepress/public/img/web2.png differ
diff --git a/docs/.vuepress/theme/components/Home.vue b/docs/.vuepress/theme/components/Home.vue
index 2b43a35..debe857 100644
--- a/docs/.vuepress/theme/components/Home.vue
+++ b/docs/.vuepress/theme/components/Home.vue
@@ -157,6 +157,7 @@ export default {
.feature {
max-width: 100%;
padding: 0 2.5rem;
+ margin: 0 auto;
}
}
}
diff --git a/docs/other/note/01.小程序笔记.md b/docs/other/note/01.小程序笔记.md
new file mode 100644
index 0000000..35094f5
--- /dev/null
+++ b/docs/other/note/01.小程序笔记.md
@@ -0,0 +1,2748 @@
+## 基础课程部分
+
+#### 微信公众平台
+
+
+
+注册时可选择类型:订阅号、服务号、小程序、企业微信
+
+**每个邮箱仅能注册一个小程序。**
+
+
+
+个人类型小程序:无法使用微信支付、无法使用卡包功能
+
+##### 小程序文档API
+
+[小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/)
+
+##### 微信开放社区
+
+[微信开发社区](https://developers.weixin.qq.com/community/develop/question)
+
+
+
+#### 目录说明
+
+**默认目录**
+
+pages-----------------------页面相关
+
+ index ----------------- 首页文件夹
+
+ index.js ------------首页js
+
+ index.json---------首页配置
+
+ index.wxml-------首页html
+
+ index.wxss--------首页css
+
+
+
+utils------------------------工具相关
+
+
+
+app.js ----------------------项目总js
+
+app.json-------------------全局配置( 页面路由以及头部、底部导航的配置等)
+
+app.wxss -----------------项目总样式css
+
+project.config.json ----项目配置
+
+
+
+**代码构成**
+
+.json :配置文件,以json格式存储配置
+
+ 项目中有三种配置:项目配置(project.config.json)、全局配置(app.json)、页面配置(index.json)
+
+.wxml: 相当于html文件
+
+.wxss: 相当于css
+
+.js : 就是js
+
+
+
+#### 文件说明
+
+**project.config.json项目配置 部分代码说明**
+
+setting:{
+
+urlCheck 是否检测安全的域名
+
+es6 是否把es6转es5
+
+postcss 是否把css样式自动补全
+
+minified 是否压缩
+
+}
+
+
+
+**app.json 全局配置**
+
+[全局配置API](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html)
+
+
+
+**wxml 相关介绍**
+
+[wxmlAPI](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/)
+
+```html
+{{motto}}
+
+循环渲染
+
+ {{index}} {{item}}
+
+
+改变for循环item和index的名称
+
+ {{inx}} {{data}}
+
+
+条件渲染 (类似vue的v-if、v-else)
+已登录
+请登录
+
+条件显示(类似vue的v-show)
+显示内容
+
+绑定点击事件
+
+
+Page({
+ tapName: function(event) {
+ console.log(event)
+ }
+})
+...
+
+```
+
+
+
+**wxss 相关介绍**
+
+[wxssAPI](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html)
+
+尺寸单位:rpx,根据屏幕宽度自适应。
+
+引入外部wxss:@import ’...‘
+
+
+
+**js相关介绍**
+
+WXS(WeiXin Script)是小程序的一套脚本语言
+
+[wxsAPI](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxs/)
+
+绑定点击事件
+
+```html
+
+{{count}}
+```
+
+```js
+Page({
+ data: {
+ count: 0
+ },
+ onTapHandler: function() {
+ this.setData({
+ count: this.data.count++
+ })
+ }
+})
+```
+
+##### 阻止事件冒泡
+
+把绑定方式 bindtap 换成 catchtap 即可。
+
+
+
+
+
+#### 第三方库
+
+**WeUI**
+
+weUI是一套同微信原生视觉体验一致的基础样式库
+
+
+
+**iView Weapp**
+
+一套高质量的微信小程序UI组件库
+
+
+
+**Vant Weapp**
+
+轻量、可靠的小程序UI组件库
+
+
+
+#### 云开发
+
+
+
+**小程序传统开发模式**
+
+客户端 -----> 服务端(后端代码、数据库)------> 运维(DB维护、文件存储、内容加速、网络防护、容器服务、负载均衡、安全加固等...)
+
+
+
+**小程序云开发模式**
+
+客户端 -----> 云开发(云函数、云数据库、云存储)
+
+
+
+**传统开发 VS 云开发**
+
+开发效率低 Serverless(无服务)
+
+运维成本高 开发者更关注业务逻辑
+
+
+
+**无服务(Serverless)开发是未来的发展趋势**
+
+
+
+
+
+##### **云开发三大基础能力**
+
+##### 云函数
+
+(相当于传统开发中的后台接口)
+
+获取appid、获取openid、生成分享图、调用腾讯云SDK ...
+
+
+
+##### 云数据库
+
+数据的增、删、改、查 ...
+
+
+
+##### 云存储
+
+管理文件、上传文件、下载文件、分享文件 ...
+
+
+
+**每个小程序账号可免费创建两个环境,建议:开发环境、生成环境**
+
+
+
+
+
+#### 云数据库能力
+
+云开发提供了一个json数据库,提供2GB免费存储空间。
+
+
+
+#### 数据类型
+
+String 字符串
+
+Number 数字
+
+Object 对象
+
+Array 数组
+
+Boolean 布尔值
+
+GeoPoint 地理位置点
+
+Date 时间 (精确到毫秒ms,客户端时间)
+
+Null 空
+
+
+
+#### 操作云数据库
+
+小程序控制(读写数据库受权限限制)
+
+云函数控制(拥有所有读写数据库的权限)
+
+控制台控制(拥有所有读写数据库的权限)
+
+
+
+#### 云数据库权限管理
+
+仅创建者可写,所有人可读 (适合于文章)
+
+仅创建者可读写 (适用于私密内容)
+
+仅管理端可写,所有人可读(适用于商品信息)
+
+仅管理端可读写(适用于后台敏感数据)
+
+
+
+#### 操作云数据库
+
+```js
+//初始化数据库
+const db = wx.cloud.database() // 小程序端初始化数据库,如果在云函数端不需要加wx
+
+//切换环境(开发环境/生产环境)
+const testDB = wx.cloud.database({
+ env: 'test'
+})
+```
+
+
+
+## 实战课程部分
+
+
+
+#### serverless(无服务)
+
+概念:函数即服务,当需要后端服务的时候,不需要关心后端的IP地址、域名,只需要像调用普通函数一样既可以实现调用。
+
+
+
+#### 云开发优势
+
+快速上线、专注核心业务、独立开发一个完整的微信小程序、不需要学习新的语言,只需要会javascript、无需运维, 节约成本、数据安全、
+
+
+
+#### 云开发提供能力
+
+云函数:在云端运行的代码,微信私有协议天然鉴权 (理解:相当于后端部分)
+
+云数据库:一个既可以在小程序端操作又可以在云函数中操作的JSON数据库
+
+云存储:在云端存储文件,可以在云端控制台可视化管理
+
+云调用:基于云函数免鉴权使用小程序开放接口的能力(比如说给用户推送消息等)
+
+HTTP API:使用HTTP API开发者可在已有服务器上访问云资源,实现与云开发的互通(作用:对原有传统模式下开发的小程序,可以与云开发进行互通)
+
+
+
+#### appID
+
+每个小程序唯一的id
+
+
+
+#### 云开发项目默认目录结构
+
+
+
+cloudfunctions ----------------------------云函数
+
+ callback ---------------------------------- 回调函数
+
+ config.json ---------------------------
+
+ index.js --------------------------------
+
+ package.json ------------------------
+
+ echo ----------------------------------------
+
+ login ----------------------------------------
+
+ openapi -----------------------------------
+
+miniprogram ------------------------------- 小程序
+
+ images ------------------------------------- 图片
+
+ pages --------------------------------------- 页面
+
+ style ----------------------------------------- 样式
+
+ app.js --------------------------------------- 项目js
+
+ app.json ----------------------------------- 全局配置
+
+ app.wxss ---------------------------------- 项目样式
+
+ sitemap.json ----------------------------- (小程序SEO相关)
+
+project.config.json ----------------------- 项目配置
+
+
+
+#### 云开发环境
+
+云开发可创建两个环境,建议一个为开发环境,一个为生产环境
+
+
+
+#### 开发前的准备
+
+开发工具 > 右上角详情 > 本地设置 > 调试基础库 设置为最新版本
+
+app.js > wx.cloud.init > env 设置环境ID
+
+
+
+#### project.config.json 文件说明
+
+miniprogramRoot 小程序前端代码目录
+
+cloudfunctionRoot 云函数代码目录
+
+
+
+#### app.json
+
+pages 设置页面 ,设置后会自动在pages目录下生成相应的目录和文件
+
+设置底部导航按钮:
+
+```json
+"tabBar": {
+ "color": "#474747", // 文字颜色
+ "selectedColor": "#d43c43", // 文字选中颜色
+ "list": [{ // 按钮列表,2-5项
+ "pagePath": "pages/playlist/playlist", // 按钮对应页面
+ "text": "音乐", // 文字
+ "iconPath": "images/music.png", // 图标路径
+ "selectedIconPath": "images/music-actived.png" // 选中图标的路径
+ },
+ {
+ "pagePath": "pages/blog/blog",
+ "text": "发现",
+ "iconPath": "images/blog.png",
+ "selectedIconPath": "images/blog-actived.png"
+ },
+ {
+ "pagePath": "pages/profile/profile",
+ "text": "我的",
+ "iconPath": "images/profile.png",
+ "selectedIconPath": "images/profile-actived.png"
+ }]
+ }
+```
+
+> 图标来自于
+>
+> 阿里巴巴图标库,包含矢量图标、字体图标、字体等
+
+
+
+
+
+#### 代码规范
+
+很多公司借鉴的代码规范:
+
+
+
+
+
+#### 《音乐》页面开发
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+
+
+#### 自定义组件
+
+**创建组件**
+
+创建目录 components > 组件目录名称 > 右键 `新建Component`
+
+##### **引入组件**
+
+在page的json文件中:
+
+```json
+{
+ "usingComponents": {
+ "x-playlist":"/components/playlist/playlist"
+ }
+}
+```
+
+在page的wxml中:
+
+```html
+
+```
+
+
+
+**页面引入组件以及组件内部在引用子组件的方法是一样的,同样需要设置json文件。**
+
+
+
+
+
+##### **组件传值**
+
+父组件中:在引入组件的时候自定义属性名称,并把数据传入子组件
+
+```html
+
+
+```
+
+子组件中:
+子组件的js文件:
+
+```js
+ /**
+ * 组件的属性列表
+ */
+ properties: {
+ playlist:{ // 接收父组件传输的数据
+ type: Object // 数据类型
+ }
+ },
+```
+
+子组件的wxml文件可直接引入数据{{playlist}}
+
+
+
+
+
+#### wx:key 的使用
+
+key的值不建议使用{{index}},因为当数据发生变化会dom结构产生变化时,使用index的地方不会随之变化。
+
+可以使用数据内部每项不一样的一个数值,如id
+
+```html
+ 这里url不需要双大括号,如使用index则需要{{}}
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+#### async/await 语法
+
+目前,在云函数里,由于 Node 版本最低是 8.9,因此是天然支持 async/await 语法的。而在小程序端则不然。在微信开发者工具里,以及 Android 端手机(浏览器内核是 QQ浏览器的 X5),async/await是天然支持的,但 iOS 端手机在较低版本则不支持,因此需要引入额外的 文件。
+
+可把这个 [runtime.js](https://github.com/xiecheng328/miniprogram/blob/master/regenerator/runtime.js) 文件引用到有使用 async/await 的文件当中。
+
+```js
+// 注意,必须命名为 regeneratorRuntime
+import regeneratorRuntime from '../../utils/runtime.js'
+```
+
+
+
+#### 云函数的使用
+
+cloudfunctions目录 右键 `新建 Node.js 云函数` > 输入目录名 `getPlaylist`
+
+
+
+在云函数中向第三方服务器发送请求要依赖第三方库
+
+**安装依赖包**
+
+云函数目录 `getPlaylist` 右键 `在终端打开` 打开命令行 输入命令:
+
+```sh
+npm install --save request
+npm install --save request-promise
+```
+
+github request-promise:
+
+
+
+然后写相应代码
+
+```js
+// 云函数入口文件
+const cloud = require('wx-server-sdk')
+
+cloud.init()
+
+const rp = require('request-promise') // 需安装依赖包
+
+const URL = 'http://musicapi.xiecheng.live/personalized'
+
+// 云函数入口函数
+exports.main = async (event, context) => {
+ const playlist = await rp(URL).then((res) => {
+ return JSON.parse(res).result
+ })
+ console.log(playlist)
+}
+```
+
+写完代码,云函数目录 `getPlaylist` 右键 `上传并部署:云端安装依赖(不上传node_modules)` 进行上传部署代码到云端,等待上传成功,打开云开发控制台即可看到已经上传的云函数,并可对云函数进行测试。
+
+
+
+##### 数据库操作
+
+数据库> 创建集合 > playlist
+
+```js
+// 云函数入口文件
+const cloud = require('wx-server-sdk')
+
+cloud.init()
+
+const db = cloud.database() // 初始化数据库,如果在小程序端初始化数据库需要在前面加wx.
+
+const rp = require('request-promise') // 需安装依赖包
+
+const URL = 'http://musicapi.xiecheng.live/personalized' // 第三方服务器地址(老师从网易云获取的数据部署在其服务器,每天的数据会更新)
+
+const playlistCollection = db.collection('playlist') // 获取到数据库playlist集合
+
+const MAX_LIMIT = 10 // 定义常量,获取数据库条数最大的限制
+
+// 云函数入口函数
+exports.main = async (event, context) => {
+ /**
+ * 注:
+ * - 关于数据库的操作都是异步操作,都需添加await关键字
+ * - console.log 打印在云开发控制台 云函数测试内查看
+ * - 单次获取数据库数据有条数限制,云函数端最多获取100条,小程序端最多获取20条
+ */
+
+ // const list = await playlistCollection.get() // 获取数据库集合的数据 (因为有条数限制,不直接用此方法)
+
+ // 突破条数限制 (为了读取到全部数据然后与第三方服务器获取的数据进行对比去重)
+ const countResult = await playlistCollection.count() // 获取数据总条数 返回为对象
+ const total = countResult.total // 取得总条数
+ const batchTimes = Math.ceil(total / MAX_LIMIT)
+ const tasks = []
+ for(let i = 0; i < batchTimes; i++) {
+ let promise = playlistCollection.skip(i * MAX_LIMIT).limit(MAX_LIMIT).get() // 从第 skip 条开始取,最多取 limit 条数据
+ tasks.push(promise)
+ }
+ let list = {
+ data: []
+ }
+ if (tasks.length > 0) {
+ list = (await Promise.all(tasks)).reduce((acc, cur) => { // reduce数组方法 累积拼接
+ return {
+ data: acc.data.concat(cur.data)
+ }
+ })
+ }
+
+ // 获取第三方服务器端数据
+ const playlist = await rp(URL).then((res) => {
+ return JSON.parse(res).result
+ })
+
+ // 数据库与服务器数据对比去重(数据已存在数据库的无需再重复添加)
+ const newData = []
+ for(let i = 0, len1 = playlist.length; i < len1; i++) {
+ let flag = true
+ for(let j = 0, len2 = list.data.length; j < len2; j++) {
+ if(playlist[i].id === list.data[j].id){
+ flag = false
+ break
+ }
+ }
+ if(flag){
+ newData.push(playlist[i])
+ }
+ }
+
+ // 把数据插入数据库,需要单条插入
+ for (let i = 0, len = newData.length; i < len; i++) {
+ await playlistCollection.add({ // 给数据库集合添加数据
+ data: {
+ ...newData[i],
+ createTime: db.serverDate(), // db.serverDate() 获取服务器时间
+ }
+ }).then((res) => { // 数据添加成功
+ console.log('数据添加成功')
+ }).catch((err) => { // 失败
+ console.error(err)
+ })
+ }
+ return newData.length // 插入多少条数据
+}
+```
+
+
+
+##### 查询数据库
+
+```js
+//云函数中查询数据库的例子:
+
+// 云函数入口文件
+const cloud = require('wx-server-sdk')
+
+cloud.init()
+
+const TcbRouter = require('tcb-router')
+const db = cloud.database() // 初始化数据库
+const blogCollection = db.collection('blog') // 博客的数据库集合
+
+// 云函数入口函数
+exports.main = async (event, context) => {
+ const app = new TcbRouter({ event }) // 初始化TcbRouter
+
+ app.router('list', async (ctx, next) => {
+ // skip 从第几条开始查,limit 查几条数据,orderBy(排序字段,排序方式) 排序,排序方式desc降序/asc升序
+ ctx.body = await blogCollection.skip(event.start).limit(event.count)
+ .orderBy('createTime', 'desc').get().then((res) => {
+ return res.data
+ })
+
+ })
+
+
+ return app.serve() // 必需返回
+}
+```
+
+
+
+##### 云函数调试
+
+云控制台中可会云函数进行云端测试
+
+在小程序调用云函数后,可查看云函数**日志**
+
+
+
+
+
+#### 定时触发云函数
+
+如果云函数需要定时 / 定期执行,也就是定时触发,我们可以使用云函数定时触发器。配置了定时触发器的云函数,会在相应时间点被自动触发,函数的返回结果不会返回给调用方
+
+云函数目录下新建 config.json
+
+[API](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/triggers.html)
+
+```json
+{
+ "triggers": [
+ {
+ "name": "myTriggers",
+ "type": "timer",
+ "config":"0 0 10,14,16,20 * * * *" //表示每天的10点、14点、16点、20点触发一次
+ }
+ ]
+}
+```
+
+编辑好触发器之后,要在云函数目录 > 右键 > `上传触发器`
+
+
+
+#### 配置云函数超时时间
+
+当云函数比较复杂的时候,默认的超时时间3秒可能不能够满足需求,可以适当的设置更为合理的时间
+
+云开发控制台 > 云函数 > 配置 > 超时时间
+
+
+
+#### 上拉加载与下拉刷新
+
+```js
+page页面json中:
+"enablePullDownRefresh": true
+
+
+page页面js中有这两个函数:
+
+ /**
+ * 页面相关事件处理函数--监听用户下拉动作
+ */
+ onPullDownRefresh: function() {
+ this.setData({
+ playlist: []
+ })
+ this._getPlaylist()
+ },
+
+ /**
+ * 页面上拉触底事件的处理函数
+ */
+ onReachBottom: function() {
+ this._getPlaylist()
+ },
+
+
+
+ 下拉刷新请求完数据后
+ wx.stopPullDownRefresh() // 停止下拉刷新动画
+```
+
+
+
+#### 云函数路由优化tcb-router
+
+一个用户在一个云环境只能创建50个云函数
+
+假如小程序非常复杂,50个云函数不能够满足业务需求怎么办?
+
+
+
+相似的请求归类到同一个云函数处理
+
+
+
+tcb-router是一个koa风格的云函数路由库
+
+
+
+通俗理解就是可以把很多个接口归类到同一个云函数内。
+
+
+
+github-tcb-router:
+
+koa洋葱模型...
+
+
+
+安装:
+
+```sh
+在使用到tcb-router的云函数目录下打开命令行,输入命令进行安装
+npm install --save tcb-router
+```
+
+
+
+```js
+// 云函数的 index.js
+const TcbRouter = require('router'); // 必需
+
+exports.main = (event, context) => {
+ const app = new TcbRouter({ event });// 必需
+
+ // app.use 表示该中间件会适用于所有的路由(全局中间件) 非必需
+ app.use(async (ctx, next) => { // 这个中间件表示所有路由都会调用到,而路由中间件为单独调用
+ ctx.data = {}; // 获取要传给小程序端的数据
+ ctx.data.openId = event.userInfo.openId // 这里获取到的openId将分布到所有路由
+ await next(); // 执行下一中间件
+ });
+
+ // 路由为数组表示,该中间件适用于 user 和 timer 两个路由
+ app.router(['user', 'timer'], async (ctx, next) => {
+ ctx.data.company = 'Tencent'; // 这里获取到的数据将分布到 user 和 timer 两个路由
+ await next(); // 执行下一中间件
+ });
+
+ // 路由为字符串,该中间件只适用于 user 路由
+ app.router('user', async (ctx, next) => {
+ ctx.data.name = 'heyli'; // 获取要传给小程序端的数据
+ await next(); // 执行下一中间件
+ }, async (ctx, next) => {
+ ctx.data.sex = 'male'; // 获取要传给小程序端的数据
+ await next(); // 执行下一中间件
+ }, async (ctx) => {
+ ctx.data.city = 'Foshan'; // 获取要传给小程序端的数据
+ // ctx.body 返回数据到小程序端
+ ctx.body = { code: 0, data: ctx.data}; // 要传给小程序端的数据
+ });
+
+ // 路由为字符串,该中间件只适用于 timer 路由
+ app.router('timer', async (ctx, next) => {
+ ctx.data.name = 'flytam';
+ await next(); // 执行下一中间件
+ }, async (ctx, next) => {
+ ctx.data.sex = await new Promise(resolve => {
+ // 等待500ms,再执行下一中间件
+ setTimeout(() => {
+ resolve('male');
+ }, 500);
+ });
+ await next(); // 执行下一中间件
+ }, async (ctx)=> {
+ ctx.data.city = 'Taishan';
+
+ // ctx.body 返回数据到小程序端
+ ctx.body = { code: 0, data: ctx.data };
+ });
+
+ return app.serve(); // 必需
+
+}
+
+
+小程序端:
+
+// 调用名为 router 的云函数,路由名为 user
+wx.cloud.callFunction({
+ // 要调用的云函数名称
+ name: "router",
+ // 传递给云函数的参数
+ data: {
+ $url: "user", // 要调用的路由的路径,传入准确路径或者通配符*
+ other: "xxx"
+ }
+}).then((res) => {
+ console.log(res)
+})
+```
+
+
+
+上面tcb-router代码会按照洋葱模型执行,即先从上往下逐个进入中间件,再从下往上逐个退出中间件。
+
+
+
+
+
+#### 本地存储(缓存)
+
+```js
+// 存储:
+wx.setStorageSync(key, data) // 同步存储(存储成功再继续下一步操作)
+wx.setStorage(key, data) // 异步存储(即使存储没成功也会执行下一步代码)、
+
+// 读取:
+wx.getStorageSync(key) // 同步 (读取到数据在进行下一步操作)
+wx.setStorage(key) // 异步
+
+```
+
+
+
+#### api设置title
+
+```js
+wx.setNavigationBarTitle({
+ title: '',
+})
+```
+
+
+
+#### 背景播放音
+
+[BackgroundAudioManager](https://developers.weixin.qq.com/miniprogram/dev/api/media/background-audio/BackgroundAudioManager.html) 全局唯一的背景音频管理器
+
+
+
+```json
+// 需要在app.json配置,才能使用后台音乐播放的能力
+
+"requiredBackgroundModes": ["audio", "location"]
+```
+
+
+
+```js
+// 获取全局唯一的背景音频管理器
+const backgroundAudioManager = wx.getBackgroundAudioManager()
+
+
+backgroundAudioManager.src = 音频链接
+backgroundAudioManager.title = 音频标题
+
+```
+
+
+
+
+
+#### createSelectorQuery查询节点信息
+
+[createSelectorQuery](https://developers.weixin.qq.com/miniprogram/dev/api/wxml/wx.createSelectorQuery.html) 小程序的方法,用于查询节点等操作
+
+```js
+const query = wx.createSelectorQuery()
+query.select('#the-id').boundingClientRect() // 节点的布局信息
+query.selectViewport().scrollOffset()
+query.exec(function(res){
+ res[0].top // #the-id节点的上边界坐标
+ res[1].scrollTop // 显示区域的竖直滚动位置
+})
+```
+
+
+
+#### 组件内的方法
+
+[Component(Object object)](https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html)
+
+
+
+#### 组件生命周期
+
+[lifetimes](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html)
+
+```js
+// 生命周期
+lifetimes: {
+ ready() { // 在组件在视图层布局完成后执行
+ ...
+ }
+},
+```
+
+##### 组件所在页面的生命周期
+
+```js
+Component({
+ pageLifetimes: {
+ show: function() {
+ // 页面被展示
+ },
+ hide: function() {
+ // 页面被隐藏
+ },
+ resize: function(size) {
+ // 页面尺寸变化
+ }
+ }
+})
+```
+
+
+
+#### 组件对数据的监听
+
+[observers](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/observer.html)
+
+```js
+observers: { // 对数据的监听(数据初次加载完成也会执行)
+ 监听的数据对象(newData){
+ console.log(newData)
+ }
+},
+```
+
+
+
+#### 子组件自定义事件传递给父组件
+
+```
+子组件js:
+// 触发自定义事件 向父组件传值, 参数x(可选,传递给父组件的参数,可以是对象或其他)
+this.triggerEvent('自定义事件名', 参数x)
+
+
+父组件wxml:
+<子组件标签 bind:自定义事件名="执行的事件" />
+
+父组件js:
+执行的事件(event) {
+ console.log(event.detil.参数)
+}
+```
+
+
+
+#### 父组件自定义事件传递给子组件
+
+```
+父组件wxml:
+<子组件标签 class="子组件类名">
+
+父组件JS:
+// 选择组件,并传入事件和参数
+this.selectComponent('.子组件类名').自定义事件名(传入参数)
+
+子组件js:
+methods: {
+ 自定义事件名(参数x){
+ console.log(参数x)
+ }
+}
+
+```
+
+
+
+#### 兄弟组件间传递事件和传值
+
+```
+例子:子组件1向子组件2传递参数
+
+父组件wxml中:
+<子组件标签1 bind:自定义事件名1="执行的事件">
+<子组件标签2 class="子组件2类名">
+
+父组件js:
+执行的事件(event) {
+ this.selectComponent('.子组件2类名').自定义事件名2(event.detil.参数x) // 向子组件2传值
+}
+
+子组件1js:
+// 触发自定义事件 向父组件传值, 参数x(可选,传递给父组件的参数,可以是对象或其他)
+this.triggerEvent('自定义事件名1', 参数x)
+
+
+
+子组件2js:
+methods: {
+ 自定义事件名2(参数x){
+ console.log(参数x) // 接收父组件传入的值
+ }
+}
+
+```
+
+
+
+#### 获取手机信息
+
+[wx.getSystemInfo(Object object)](https://developers.weixin.qq.com/miniprogram/dev/api/base/system/system-info/wx.getSystemInfo.html)
+
+```js
+wx.getSystemInfo({
+ success(res){
+ console.log(res) //手机信息
+ }
+})
+```
+
+
+
+
+
+#### 滚动组件
+
+[scroll-view](https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html)
+
+```html
+
+
+```
+
+
+
+#### 全局属性、方法(类似vuex)
+
+```
+在app.js中:
+
+onLaunch: function () {
+ this.globalData = {// 设置全局属性、方法
+ test: 0
+ }
+},
+setGlobalData(dataItem, val) { // 设置全局属性
+ this.globalData[dataItem] = val
+},
+getGlobalData(dataItem) { // 获取全局属性
+ return this.globalData[dataItem]
+}
+
+
+在需要调用的页面js中:
+const app = getApp() // 在最顶部先调用app方法
+
+// 设置全局属性
+app.setGlobalData('test', 1)
+
+// 获取全局属性
+app.getGlobalData('test')
+```
+
+
+
+#### 消息提示框
+
+[showToast](https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showToast.html)
+
+```js
+wx.showToast({
+ title: '成功',
+ icon: 'success', //图标: success 成功、loading 加载中、none 无
+ duration: 2000
+})
+```
+
+
+
+
+
+
+
+## 《发现》页面
+
+
+
+#### 调用组件外部的样式
+
+components内部的组件无法直接调用外部的样式。可通过以下方式调用组件外部样式:
+
+**方法一:**
+
+```
+父组件wxml:
+
+
+
+
+子组件js:
+// 组件外部样式
+ externalClasses: [
+ 'iconfont', // 对应的是上面等号前面的名称
+ 'icon-sousuo'
+ ],
+
+ 子组件wxml: 即可实现调用组件外的样式
+
+
+
+ 注意:如果想在组件内部再次修改样式,不能够引用外部传进来的class名称进行修改,可以另起一个class名称进行修改。
+```
+
+
+
+**方法二:**
+
+[消除样式隔离](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
+
+```js
+组件内:
+Component({
+ options: {
+ styleIsolation: 'apply-shared'
+ }
+})
+```
+
+
+
+#### 组件插槽slot
+
+**单个插槽**
+
+```
+父组件调用传入插槽内容:
+<组件标签>
+
+ 插槽内容
+ 插槽内容
+
+组件标签>
+
+ 组件内部定义slot标签:
+
+
+
+
+```
+
+
+
+**如果需要实现多个插槽**
+
+```
+
+父组件调用传入插槽内容:
+<组件标签>
+
+ 插槽1内容
+ 插槽1内容
+
+
+
+ 插槽2内容
+ 插槽2内容
+
+组件标签>
+
+组件js :
+options: {// 设置
+ multipleSlots: true // 打开多个插槽功能
+},
+
+
+组件内部定义slot标签:
+
+
+
+
+
+```
+
+
+
+
+
+#### 判断用户授权
+
+[授权](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html)
+
+```js
+// 判断用户是否授权
+ wx.getSetting({
+ success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
+ console.log(res)
+ if (res.authSetting['scope.userInfo']) { // 已授权
+ wx.getUserInfo({ // 获取用户信息
+ success(res) {
+ console.log(res)
+ }
+ })
+ } else { // 未授权
+
+ }
+ }
+ })
+```
+
+
+
+
+
+#### button的开发能力(获取用户信息)1
+
+```js
+
+
+
+bindgetuserinfo 事件会询问用户是否同意授权
+
+
+js中:
+ onGetUserInfo(event) { // 获取用户信息
+ const userInfo = event.detail.userInfo
+ if (userInfo) { // 用户允许授权
+ this.setData({
+ modalShow: false
+ })
+ this.triggerEvent('loginSuccess', userInfo) // 给父组件传用户数据
+ } else { // 用户拒绝授权
+ this.triggerEvent('loginFail')
+ }
+ }
+```
+
+
+
+#### 原生组件
+
+[原生组件](https://developers.weixin.qq.com/miniprogram/dev/component/native-component.html)
+
+```html
+
+auto-focus 自动获取焦点
+
+
+```
+
+
+
+
+
+
+
+
+
+#### 选择上传图片
+
+[上传图片](https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseImage.html)
+
+```js
+let max = 9 - this.data.images.length // 还能再选几张图片
+wx.chooseImage({
+ count: max, // 还能再选几张图片
+ sizeType: ['original', 'compressed'], // 初始值 and 压缩过的
+ sourceType: ['album', 'camera'], // 手机相册选择 and 拍照选择
+ success: (res) => { // 箭头函数改变this指向
+ console.log(res)
+ },
+ })
+```
+
+
+
+#### 图片裁剪
+
+[图片裁剪](https://developers.weixin.qq.com/miniprogram/dev/component/image.html)
+
+```html
+
+
+```
+
+
+
+#### 获取标签自定义属性data-* (删除图片的实现)
+
+```
+
+
+
+
+
+
+
+
+
+
+ // 删除图片
+ onDelImage(event) {
+ // event.target.dataset.index 获取标签属性data-index的值
+ this.data.images.splice(event.target.dataset.index, 1) // splice会改变原有数组
+ this.setData({
+ images: this.data.images
+ })
+ },
+
+```
+
+
+
+
+
+
+
+#### 全屏预览图片(点击图片放大预览)
+
+[全屏预览图片](https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.previewImage.html)
+
+```js
+// 全屏预览图片
+ onPreviewImage(event) {
+ wx.previewImage({
+ urls: this.data.images, // 图片地址列表
+ current: event.target.dataset.imgsrc // 当前预览图片地址
+ })
+ },
+```
+
+
+
+#### 文件上传云存储(发布博客例子)
+
+[文件上传云存储](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-client-api/storage/uploadFile.html)
+
+```js
+ // 结合'发布'的例子:
+ send() {
+ // 验证是否输入内容
+ if (content.trim() === '') { // trim() 去掉字符串空格
+ wx.showToast({
+ title: '请输入内容',
+ icon: 'none'
+ })
+ return
+ }
+ wx.showLoading({
+ title: '发布中',
+ })
+ /**
+ * 实现思路及步骤:
+ * 1、图片 -> 上传 云存储 -> 生成 图片fineID(云文件ID)
+ * 2、数据 -> 录入 云数据库
+ * 数据包括:文字内容、图片fineID、昵称、头像、发布时间、openId(用户唯一标识,在插入数据库是系统会自动添加_openId字段,不需要另外插入)
+ */
+ let promiseArr = []
+ let fileIds = []
+ // 图片上传云存储
+ this.data.images.forEach((item) => {
+ let p = new Promise((resolve, reject) => {
+ let suffix = /\.\w+$/.exec(item)[0] // 文件扩展名(文件后缀)
+ wx.cloud.uploadFile({ // 每次只能上传一个文件
+ /**
+ * cloudPath 云路径。如果路径相同,后上传的文件会覆盖原文件
+ * 路径:blog/云存储中的文件夹 + Date.now()时间戳 + Math.random()*1000000随机数 + 文件后缀
+ */
+ cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
+ filePath: item, // 文件本地临时路径
+ success: (res) => {
+ fileIds.push(res.fileID)
+ resolve()
+ },
+ fail: (err) => {
+ console.error(err)
+ reject()
+ }
+ })
+ })
+ promiseArr.push(p)
+ })
+
+ // 存入云数据库
+ Promise.all(promiseArr).then((res) => {
+ db.collection('blog').add({
+ data: {
+ ...userInfo, // 昵称、头像
+ content, // 内容
+ img: fileIds, // 图片fileID列表
+ createTime: db.serverDate() // 创建时间,取服务端时间
+ }
+ }).then((res) => {
+ wx.hideLoading()
+ wx.showToast({
+ title: '发布成功',
+ })
+ // 返回博客页面,并刷新
+ wx.navigateBack()
+
+ })
+ }).catch((err) => {
+ wx.hideLoading()
+ wx.showToast({
+ title: '抱歉,发布失败',
+ icon: 'none'
+ })
+ })
+ },
+```
+
+
+
+#### js模块化 (时间格式化)
+
+
+
+在目录utils 中新建formatTime.js文件
+
+```js
+// 时间格式化 模块封装
+module.exports = (date) => { // date 数据格式为 date
+ let fmt = 'yyyy-MM-dd hh:mm:ss' // 预定格式
+ const o = {
+ // + 正则中的1个或多个
+ 'M+': date.getMonth() + 1,
+ 'd+': date.getDate(),
+ 'h+': date.getHours(),
+ 'm+': date.getMinutes(),
+ 's+': date.getSeconds()
+ }
+
+ if (/(y+)/.test(fmt)) {
+ // $1 表示正则中的第一个,即(y+)
+ fmt = fmt.replace(RegExp.$1, date.getFullYear()) // replace 替换
+ }
+
+ for (let k in o) {
+ if (new RegExp('('+ k +')').test(fmt)) {
+ fmt = fmt.replace(RegExp.$1, o[k].toString().length === 1 ? '0' + o[k] : o[k])
+ }
+ }
+
+ return fmt
+}
+```
+
+
+
+在组件引入js模块
+
+```js
+import formatTime from '../../utils/formatTime.js'
+
+使用:
+formatTime(new Date('Wed Aug 28 2019 16:23:06 GMT+0800 (中国标准时间)'))
+```
+
+
+
+
+
+#### 阻止事件冒泡
+
+`bind` 和 `catch` 都可以绑定事件,它们的区别是 `bind` 有事件冒泡,而 `catch` 没有
+
+
+
+#### 返回上一个页面并执行方法
+
+[API](https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateBack.html)
+
+```js
+ // 返回博客页面,并刷新
+ wx.navigateBack()
+ const pages = getCurrentPages() // 获取当前页面栈
+ const prevPage = pages[pages.length - 2] // 取到上一个页面
+ prevPage.onPullDownRefresh() // 执行上一个页面的方法 onPullDownRefresh
+```
+
+
+
+#### 图片懒加载
+
+[API](https://developers.weixin.qq.com/miniprogram/dev/component/image.html)
+
+```html
+给image标签设置 lazy-load 为 true
+
+
+.img {
+ background: #eee;
+}
+```
+
+**懒加载占位图可以给image设置背景图或背景色**
+
+
+
+#### 模糊查询
+
+```js
+// 获取博客列表
+ app.router('blogList', async (ctx, next) => {
+ const keyword = event.keyword // 搜索关键字 调用接口时传递来的数据
+ let w = {}
+ if (keyword.trim() != '') {
+ w = {
+ content: db.RegExp({ // 正则
+ regexp: keyword,
+ options: 'i' // i表示忽略大小写
+ })
+ }
+ }
+
+ // where查询条件 skip 从第几条开始查,limit 查几条数据,orderBy(排序字段,排序方式) 排序,排序方式desc降序/asc升序
+ ctx.body = await blogCollection.where(w).skip(event.start).limit(event.count)
+ .orderBy('createTime', 'desc').get().then((res) => {
+ return res.data
+ })
+
+ })
+```
+
+
+
+#### 提升模糊查询的效率 (添加索引,对数据量大的查询效果明显)
+
+云开发控制台 > 数据库相应的集合 > 索引管理 > 添加索引 > 输入自定义索引名称、该字段的值是否唯一、被查询的字段名、升序/降序 > ok
+
+
+
+
+
+#### 小程序端调用云数据库
+
+一般调用云数据库的操作都写在云函数内,其实小程序端也可以对数据库进行操作。
+
+小程序端一次最多只能查询20条数据,云函数端最多可查询100条数据,可使用多次查询拼接的方式突破限制。
+
+```js
+// 小程序端调用云数据库示例
+ const db = wx.cloud.database() // 初始化数据库
+ db.collection('blog').orderBy('createTime','deac').get().then((res) => {
+ console.log(res)
+ })
+```
+
+
+
+
+
+#### 云数据库权限管理
+
+**注意:云控制台和服务端(云函数)始终有所有数据读写权限,**
+
+**但权限的管理仅对小程序端发起的请求有效。**
+
+
+
+* 仅创建者可写,所有人可读 (适合于文章)
+
+* 仅创建者可读写 (适用于私密内容)
+
+* 仅管理端可写,所有人可读(适用于商品信息)
+
+* 仅管理端可读写(适用于后台敏感数据)
+
+
+
+
+
+#### 数据库中1对N关系的三种设计方式
+
+##### 第一种:N的数量较少 几十个以内
+
+**1 条记录存储 N 个子数据**
+
+ 如一条博客中,最多有9张图片,这9张图片可和其他数据放在一个记录中。
+
+```js
+[
+ {
+ id:...
+ img:[
+ '...', '...', '...', '...', '...', '...', '...', '...', '...'
+ ]
+ }
+]
+```
+
+
+
+##### 第二种:N的数量较多 几十到几百个
+
+**1 存储 每个N的 id**
+
+可分两个数据库集合,
+
+一个为 '目录' 集合,存放 '详情' 集合下的每条数据的 id 目录
+
+一个为 '详情' 集合,每条数据对应一个单独的 id 和 详细数据
+
+
+
+```js
+目录集合:
+[
+ {
+ 'id':"11",
+ 'name': '产品1',
+ 'xqs': ['111','222','333', ... ] // 存放 详情集合 中的每条数据 id
+ }
+]
+
+
+
+详情集合:
+[
+{'id':"111",name:'零件1',title:'...' ...},
+{'id':"222",name:'零件2',title:'...' ...},
+{'id':"333",name:'零件3',title:'...' ...},
+...
+]
+
+
+```
+
+如歌单列表,与歌曲详情的数据组合设计。
+
+
+
+##### 第三种:N的数量巨大 几百成千上万个
+
+**每个 N 都存储 1 的 id**
+
+如新浪博客中的一条博客下面有几千条评论
+
+```js
+一条新浪博客:
+[{
+ 'id':'11',
+ 'content':'博客内容'
+ ...
+}]
+
+
+上千条评价:
+[
+{
+ 'id':'111111'
+ 'blogId':'11', // 这个id对应的是那一条博客的id
+ 'content': '评价内容1'
+},
+{
+ 'id':'222222'
+ 'blogId':'11', // 这个id对应的是那一条博客的id
+ 'content': '评价内容2'
+},
+{
+ 'id':'33333'
+ 'blogId':'11', // 这个id对应的是那一条博客的id
+ 'content': '评价内容3'
+},
+...
+]
+
+```
+
+
+
+#### 云调用
+
+通过云函数调用服务端的开发接口
+
+这些接口如:模板消息推送、生成小程序码...
+
+
+
+
+
+#### 模板消息推送
+
+
+
+**1、使用from表单才能触发消息推送,并设置report-submit="true"**
+
+```html
+
+
+```
+
+**2、需要到微信公众平台做相应的设置:**
+
+微信公众平台 > 功能 > 模板消息 > 添加模板 > 选择相应的模板> 添加成功后会有一个模板ID
+
+
+
+**3、新建一个云函数,用于云调用。在该云函数下新建配置文件:config.json ,用于配置权限**
+
+config.json :
+
+```json
+{
+ "permissions": {
+ "openapi": [
+ "templateMessage.send"
+ ]
+ }
+}
+```
+
+云函数设置消息推送:
+
+```js
+// 云函数入口函数
+exports.main = async (event, context) => {
+ // 获取openid
+ const { OPENID } = cloud.getWXContext()
+
+ // 模板推送消息
+ const result = await cloud.openapi.templateMessage.send({
+ touser: OPENID,
+ page: `/pages/blog-comment/blog-comment?blogId=${event.blogId}`, // 用户点击推送消息打开的页面
+ data: { // 模板的内容,keyword为在公众平台设置模板时对应的字段
+ keyword1: { // 评价内容
+ value: event.context
+ },
+ keyword2: { // 评价时间
+ value: event.time
+ }
+ },
+ templateId: 'LNwKMcYwlz-0HabgBhmZi6CWZrlNSBiNJ2h0SMorcxQ', // 模板id,到公众平台模板消息上获取
+ formId: event.formId // 触发消息推送的form表单的id
+ })
+
+ return result
+}
+```
+
+
+
+**4、在提交表单事件完成后调用消息推送云函数**
+
+```js
+ wx.cloud.callFunction({
+ name: 'sendMessage',
+ data: {
+ content,
+ formId,
+ blogId: this.properties.blogId
+ }
+ }).then((res) => {
+ console.log(res)
+ })
+```
+
+
+
+
+
+#### 云函数多集合查询数据库
+
+```js
+// 博客详情(博客内容、评论)
+ app.router('blogDetail', async(ctx, next) => {
+ let blogId = event.blogId
+
+ // 博客内容
+ let detail = await blogCollection.where({
+ _id: blogId
+ }).get().then((res) => {
+ return res.data
+ })
+
+ // 评论查询
+ const countResult = await blogCollection.count()
+ const total = countResult.total
+ let commentList = {
+ data: []
+ }
+ if (total > 0) {
+ // 突破100条限制
+ const batchTimes = Math.ceil(total / MAX_LIMIT)
+ const tasks = []
+ for (let i = 0; i < batchTimes; i++) {
+ let promise = db.collection('blog-comment').skip(i * MAX_LIMIT)
+ .limit(MAX_LIMIT).where({
+ blogId
+ }).orderBy('createTime', 'desc').get()
+ tasks.push(promise)
+ }
+ if (tasks.length > 0) {
+ commentList = (await Promise.all(tasks)).reduce((acc, cur) => {
+ return {
+ data: acc.data.concat(cur.data)
+ }
+ })
+ }
+
+ }
+ ctx.body = {
+ detail,
+ commentList
+ }
+ })
+```
+
+
+
+#### 分享功能
+
+分享功能需要button标签,设置open-type="share"
+
+```html
+
+
+```
+
+在js中有onShareAppMessage方法,点击button会自动执行此方法
+
+```js
+onShareAppMessage: function (event) {
+ console.log(event)
+
+ // 对分享卡片的设置
+ let blogObj = event.target.dataset.blog
+ return {
+ title: blogObj.content,
+ path: `/pages/blog-comment/blog-comment?blogId=${blogObj._id}`,
+ // imageUrl: '' // 自定义图片,不支持云存储的图片
+ }
+ }
+```
+
+
+
+#### 不同场景获取用户信息的方式
+
+##### 场景一:只想在界面上显示自己的昵称和头像
+
+以组件的方式:根据type类型获取不同用户数据
+
+**该方式不需要授权,只能用于在wxml显示自己的信息**
+
+[open-data](https://developers.weixin.qq.com/miniprogram/dev/component/open-data.html)
+
+```html
+
+
+...
+```
+
+
+
+##### 场景二:在JS中获取用户信息
+
+该方式要在用户授权以后才能获取用户信息
+
+[wx.getUserInfo](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html)
+
+```js
+wx.getUserInfo({
+ success: (res) => {
+ console.log(res)
+ }
+ })
+```
+
+在未授权的情况下需要用户先授权:
+
+```js
+// 判断用户是否授权
+ wx.getSetting({
+ success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
+ if (res.authSetting['scope.userInfo']) { // 已授权
+ wx.getUserInfo({ // 获取用户信息
+ success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
+
+ app.setGlobalData('userInfo', res.userInfo) // 设置app全局属性
+
+ this.onLoginSuccess({
+ detail: res.userInfo
+ })
+ }
+ })
+ } else { // 未授权
+ this.setData({ // 打开弹出层,显示获取用户信息按钮
+ modalShow: true
+ })
+ }
+ }
+ })
+
+
+ 授权按钮
+
+
+
+ onGetUserInfo(event) { // 获取用户信息
+ const userInfo = event.detail.userInfo
+ if (userInfo) { // 用户允许授权
+ this.setData({
+ modalShow: false
+ })
+ this.triggerEvent('loginSuccess', userInfo) // 给父组件传用户数据
+ } else { // 用户拒绝授权
+ this.triggerEvent('loginFail')
+ }
+ }
+
+```
+
+> 注意:上面这种方式没有获取到openId
+
+
+
+
+
+##### 场景三:获取openId
+
+
+
+**获取openId不需要用户授权**
+
+1、传统开发方式获取openId,后台服务器由自己开发,没使用云开发
+
+
+
+小程序端 微信服务器 后端服务器
+
+步骤:
+
+小程序端 调用 wx.login 向微信服务器 获取code
+
+小程序端 调用 wx.request 将 code 传递给 后端服务器
+
+后端服务器 使用code 向微信服务器 换取openid和session_key
+
+后端服务器 将openid 发送给 小程序端
+
+
+
+2、云开发方式获取openId
+
+云函数login中
+
+```js
+// 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)
+ const wxContext = cloud.getWXContext()
+
+ return {
+ event,
+ openid: wxContext.OPENID,
+ appid: wxContext.APPID,
+ unionid: wxContext.UNIONID,
+ }
+```
+
+
+
+
+
+```js
+普通按钮
+
+
+getOpenid() {
+ wx.cloud.callFunction({
+ name: 'login'
+ }).then((res) => {
+ console.log(res)
+ })
+}
+```
+
+
+
+>openid 在小程序和公众号下是不一样的
+>
+>unionid 在小程序和公众号下都是一样的
+
+
+
+
+
+## 《我的》页面
+
+
+
+json文件
+
+```js
+"navigationBarTitleText": "我的",
+ "disableScroll": true // 使页面无法滚动
+```
+
+
+
+
+
+#### 导航页面链接跳转
+
+[navigator](https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html)
+
+
+
+#### 背景图片
+
+wxss背景图片不支持本地相对路径的图片,只支持网络图片和base64图片
+
+建议使用base64图片,图片文件最好不要太大。
+
+
+
+#### 每个页面都有的page标签
+
+```js
+page {
+ background-color: #f1f1f1;
+}
+```
+
+
+
+#### 播放历史与本地存储
+
+方案一:播放历史存储在数据库当中,这样在不同设备访问都可查看播放历史。读取速度相对较慢
+
+方案二:播放历史存储在本地,仅当前设备可查看播放历史。读取速度较快
+
+
+
+本项目采用本地存储:
+
+使用openid作为本地存储的key,播放历史存入value
+
+
+
+在app.js中获取openid,即打开小程序就获取openid。
+
+```js
+// app.js
+onLaunch: function () {
+ this.getOpenid() // 获取openid并存储
+},
+getOpenid() { // 获取openid并存储
+ wx.cloud.callFunction({
+ name: 'login'
+ }).then((res) => {
+ const openid = res.result.openid
+ this.globalData.openid = openid // 保存到全局变量
+ if (wx.getStorageSync(openid) == '') { // 该用户从未打开过小程序,未存储过openid在本地
+ wx.setStorageSync(openid, []) // 存储openid到本地
+ }
+ })
+ }
+```
+
+
+
+歌曲播放时
+
+```js
+// 保存播放历史到本地存储
+ savePlayHistory() {
+ const currentSong = musiclist[nowPlayingIndex] // 当前播放歌曲
+ const openid = app.globalData.openid // 从全局属性获取openid
+ const playHistory = wx.getStorageSync(openid) // 从本地存储获取播放历史数组
+
+ for (let i = 0, len = playHistory.length; i < len; i++) {
+ if (playHistory[i].id === currentSong.id) { // 当前播放歌曲已存在播放历史中
+ playHistory.splice(i, 1) // 删除原纪录
+ break
+ }
+ }
+
+ playHistory.unshift(currentSong) // 在数组开头插入
+ wx.setStorage({ // 存入本地
+ key: openid,
+ data: playHistory
+ })
+
+ },
+```
+
+
+
+播放历史页面获取
+
+```js
+onLoad: function (options) {
+
+ const openid = app.globalData.openid //从全局属性获取openid
+ const playHistory = wx.getStorageSync(openid) // 读取本地播放历史数据
+
+ if (playHistory.length !== 0) { // 有播放历史
+ this.setData({
+ playHistory
+ })
+ wx.setStorage({ // storage里把musiclist(播放列表)的内容换成播放历史的列表
+ key: 'musiclist',
+ data: playHistory,
+ })
+ }
+
+ },
+```
+
+
+
+#### 我的发现
+
+代码分别演示了从云函数和小程序端获取数据,从小程序端获取数据享有**权限管理**的能力,不需要传openid。
+
+
+
+
+
+#### 小程序码
+
+[获取小程序码](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html)
+
+本项目演示使用[接口 B:适用于需要的码数量极多的业务场景](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html) `云调用` 的方式。
+
+步骤:
+
+* 创建云函数 gteQRCode
+
+* gteQRCode云函数下创建config.json配置权限,代码如下:
+
+ ```json
+ {
+ "permissions":{
+ "openapi":[
+ "wxacode.getUnlimited"
+ ]
+ }
+ }
+ ```
+
+
+
+```js
+// 云函数入口函数
+exports.main = async (event, context) => {
+ const wxContext = cloud.getWXContext()
+
+ const result = await cloud.openapi.wxacode.getUnlimited({
+ scene: wxContext.OPENID, // 链接参数 不一定传openid,可传其他任意数据,然后通过此数据,在别人扫码进入时就可用于判断
+ // page: "pages/blog/blog" // 默认进入主页
+ // lineColor: { // 线条颜色
+ // 'r': 211,
+ // 'g': 60,
+ // 'b': 57
+ // },
+ // isHyaline: true // 是否透明
+ })
+
+ // result为二进制数据, 先上传到云存储
+
+ // 上传云存储
+ const upload = await cloud.uploadFile({
+ cloudPath: 'qrcode/qrcode' + Date.now() + Math.random() + '.png',
+ fileContent: result.buffer
+ })
+
+ return upload.fileID
+}
+```
+
+
+
+##### 判断是从扫码小程序码进入,以及参数获取
+
+```js
+// 在从小程序码进入的页面js,onLoad方法中,
+
+onLoad: function (options) {
+ console.log(options.scene) // 获取到小程序码进入的参数
+}
+```
+
+
+
+
+
+### 版本更新检测
+
+```js
+// app.js
+onLaunch: function(options) {
+ this.checkUpate()
+},
+checkUpate(){
+ const updateManager = wx.getUpdateManager()
+ // 检测版本更新
+ updateManager.onCheckForUpdate((res)=>{
+ if (res.hasUpdate){
+ updateManager.onUpdateReady(()=>{
+ wx.showModal({
+ title: '更新提示',
+ content: '新版本已经准备好,是否重启应用',
+ success(res){
+ if(res.confirm){
+ updateManager.applyUpdate()
+ }
+ }
+ })
+ })
+ }
+ })
+ },
+```
+
+
+
+### 性能优化
+
+[官网文档优化建议](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html)
+
+使用开发者工具的调试器,Audits进行评分,然后根据提示针对项目进行优化。
+
+
+
+### 场景值scene的作用与应用场景
+
+[场景值](https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/scene.html)
+
+场景值用来描述用户进入小程序的路径。完整场景值的含义请查看[场景值列表](https://developers.weixin.qq.com/miniprogram/dev/reference/scene-list.html)。
+
+
+
+可根据不同场景进入实现不同业务处理,比如一个点餐小程序,店家内贴了小程序码,用户通过扫码进入,可立即进入点餐页面,等等
+
+
+
+在app.js中的onLaunch(options) 、onShow(options),options包含scene场景值
+
+
+
+**开发者工具中,切后台,可模拟进入场景。**
+
+
+
+### 小程序的"SEO"---页面收录sitemap
+
+在app.js的同级目录下有sitemap.json文件,用于配置收录规则
+
+
+
+[stiemap配置](https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html)
+
+
+
+作用:
+
+使小程序搜索可根据小程序的内容进行搜索到
+
+
+
+使用方法:
+
+1、在微信公众平台,小程序信息 > 页面收录设置 > 打开 (默认是已开启)
+
+2、打开sitemap.json文件,配置收录规则
+
+
+
+``` json
+{
+ "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+ "rules": [{ // 收录规则
+ "action": "allow",// 是否被收录,allow允许被收录,disallow不允许
+ "page": "*" // *星号表示所有页面都被收录
+ }]
+}
+```
+
+
+
+```json
+{
+ "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+ "rules": [{ // 收录规则,可添加多条
+ "action": "allow", // 是否被收录
+ "page":"pages/player/player", // 页面
+ "params": ["musicId","index"], // 链接的动态参数
+ "matching":'exact' // 表示params的参数是否要准确的匹配
+ },{
+ "action": "disallow", // 是否被收录
+ "page":"*", /
+ }]
+}
+
+// 上面配置规则表示除了player页面被收录外,其他页面不被收录
+```
+
+
+
+
+
+### 小程序上线审核流程
+
+
+
+微信公众平台,版本管理>把小程序上传为体验版》提交审核》上线
+
+
+
+
+
+## 后台管理系统
+
+#### 架构示意图
+
+ 前端 后台 小程序云开发
+
+vue-admin-template <---通过ajax--> 基于Koa2;HTTP API 或 tcb-admin-node ---->云函数、云数据库、云存储
+
+
+
+
+
+#### vue-admin-template构建管理系统前端
+
+
+
+[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 基于element的后台管理系统模板
+
+[vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) 是 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)的简化版
+
+
+
+使用方法查看官方文档。
+
+
+
+
+
+#### Koa2构建管理系统后端
+
+官网:
+
+
+
+新建空文件夹wx-music-admin-backend,打开终端:
+
+```sh
+# 生成package.json文件,-y 表示默认的配置
+npm init -y
+
+# 安装koa
+npm install koa
+
+# 新建app.js文件 (win10系统命令),作为项目入口文件
+type nul > app.js
+
+```
+
+
+
+app.js:
+
+```js
+const Koa = require('koa')
+const chalk = require('chalk') // 使console.log打印文字有颜色的插件,需: npm i chalk
+const app = new Koa()
+
+app.use(async (ctx) => {
+ ctx.body = 'Hello Wolrd'
+})
+const port = 3000
+app.listen(port, () => { // 端口号,开启服务后的回调函数
+ console.log(chalk.green(`> 服务已开启,访问:http://localhost:${port}`))
+})
+```
+
+
+
+终端:
+
+```sh
+# node启动项目
+node app.js
+
+# 访问:http://localhost:3000
+```
+
+
+
+#### 接口调用凭证 access_token 的缓存与更新
+
+
+
+access_token,微信的接口调用凭证,详情:
+
+
+
+回到项目wx-music-admin-backend,打开终端:
+
+```sh
+# HTTP 请求 插件
+npm i request
+npm i request-promise
+```
+
+```js
+/**
+ * 获取微信接口调用凭证
+ * 详情:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
+ */
+
+
+const rp = require('request-promise') // node发送http请求的插件
+const fs = require('fs') // node文件模块
+const path = require('path') // node 路径模块
+
+//fileName = __dirname 当前文件所在目录的绝对路径, 加上 './access_token.json'
+const fileName = path.resolve(__dirname, './access_token.json')
+
+// 这两个参数的获取:微信公众平台>开发>开发设置
+const APPID = 'wxc4e0b2d98063b103'
+const APPSECRET = 'xxx' //小程序密钥,注意保密!
+
+// 微信 access_token 请求地址
+const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
+
+// 发送请求获取AccessToken
+const updateAccessToken = async () => {
+ const resStr = await rp(URL)
+ const res = JSON.parse(resStr)
+
+ if (res.access_token) {
+ // node写文件,参数:1 文件路径,2 文件内容, 首次写文件为新建,往后为覆盖
+ fs.writeFileSync(fileName, JSON.stringify({
+ access_token: res.access_token,
+ createTime: new Date()
+ }))
+ } else { // 如获取不到,再次获取
+ await updateAccessToken()
+ }
+}
+
+// 读取access_token
+const getAccessToken = async () => {
+ try {
+ // node读取文件,参数:1 读取的文件,2 字符集
+ const readRes = fs.readFileSync(fileName, 'utf8')
+ const readObj = JSON.parse(readRes)
+
+ // 如果服务器宕机导致setInterval无法定时更新,这里需要再次判断access_token的有效性
+ const createTime = new Date(readObj.createTime).getTime()
+ const nowTime = new Date().getTime()
+ if((nowTime - createTime) / 1000 / 60 / 60 >= 2) {
+ await updateAccessToken()
+ await getAccessToken()
+ return
+ }
+ return readObj.access_token
+
+ } catch (error) { //捕获异常,在未创建文件时,先创建文件
+ await updateAccessToken()
+ await getAccessToken()
+ }
+}
+
+// access_token有效期为2个小时,定时更新
+setInterval(async () => {
+ await updateAccessToken()
+}, (7200 - 300) * 1000)
+
+module.exports = getAccessToken
+```
+
+
+
+#### 后端代码通过HTTP API 触发云函数获取数据
+
+[HTTP API 触发云函数](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/functions/invokeCloudFunction.html)
+
+
+
+
+
+
+
+#### 产生跨域和后端解决跨域问题
+
+管理系统前端向管理系统后端请求数据,产生了跨域问题
+
+```js
+ // 产生跨域的几种情况
+ // http://www.a.com https://www.a.com 协议不同
+ // http://www.a.com http://www.b.com 域名不同
+ // http://www.a.com http://news.a.com 主域与子域不同
+ // http://www.a.com:8080 http://www.a.com:3000 端口不同
+
+// 解决跨域的几种方法
+// jsonp
+// iframe
+// postMessage跨域
+// 跨域资源共享(CORS)
+```
+
+
+
+管理系统后端,安装
+
+```sh
+// 解决跨域问题的koa包
+npm i koa2-cors
+```
+
+
+
+app.js
+
+```js
+//处理跨域
+app.use(cors({
+ origin: ['http://localhost:9528'], // 允许访问本服务的域
+ credentials: true
+}))
+```
+
+
+
+#### 云数据库的增删改查接口
+
+[数据库查询记录](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseQuery.html)
+
+
+
+#### 后端获取前端post请求传来的数据
+
+get请求可以直接通过ctx.request.query获取,但是post请求需要安装koa-body
+
+```sh
+npm i koa-body
+```
+
+
+
+app.js
+
+```js
+const koaBody = require('koa-body') // 对post请求前端传来的数据的获取,需要此依赖
+
+// 接收post参数解析
+app.use(koaBody({
+ multipart: true
+}))
+
+```
+
+
+
+
+
+接口.js
+
+```js
+router.post('/updatePlaylist', async (ctx, next) => {
+
+ const params = ctx.request.body // post请求获取前端传来的数据,需安装和配置koa-body
+
+})
+```
+
+
+
+#### 后端获取云存储图片
+
+云存储中上传图片,云数据库中新建图片的集合,并添加数据字段,字段包含云文件的fileid。
+
+后端项目通过调用云数据库的方式获取数据
+
+```js
+router.get('/list', async (ctx, next) => {
+ // 接口中读取数据库默认最多10条数据
+ const query = `db.collection('swiper').get()`
+ const res = await callCloudDB(ctx, 'databasequery', query)
+ console.log(res)
+
+})
+```
+
+但获取到的数据为fileid,并不能用于显示图片,需要通过微信HTTP API获取云存储的接口来获取图片地址
+
+[获取云存储](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDownloadFile.html)
+
+
+
+#### 后端上传图片到云存储
+
+[文件上传](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/uploadFile.html)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/other/yaml.md b/docs/other/yaml.md
new file mode 100644
index 0000000..cd4108c
--- /dev/null
+++ b/docs/other/yaml.md
@@ -0,0 +1,340 @@
+# yaml教程
+
+## 简介
+
+YAML 是 "YAML Ain't a Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。
+
+YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别**适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲**(例如:许多电子邮件标题格式和YAML非常接近)。
+
+YAML 的配置文件后缀为 **.yml**,如:**runoob.yml** 。
+
+
+
+> yaml常用来用作配置文件,和json类似
+
+## 基本语法
+
+- 大小写敏感
+
+- 使用缩进表示层级关系
+
+- 缩进不允许使用tab,只允许空格
+
+- 缩进的空格数不重要,只要相同层级的元素左对齐即可
+
+- '#'表示注释
+
+
+
+## 数据类型
+
+YAML 支持以下几种数据类型:
+
+- **对象**:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
+- **数组**:一组按次序排列的值,又称为序列(sequence) / 列表(list)
+- **纯量**(scalars):单个的、不可再分的值
+
+
+
+## YAML 对象
+
+对象的几种表示形式:
+
+1、对象键值对使用冒号结构表示 **key: value**,冒号后面要加一个空格。
+
+```yaml
+key: value
+```
+
+
+
+2、也可以使用 **key:{key1: value1, key2: value2, ...}**。
+
+```yaml
+key:{child-key1:value1,child-key2:value2}
+```
+
+
+
+3、还可以使用缩进表示层级关系;
+
+```yaml
+key:
+ child-key1: value1
+ child-key2: value2
+```
+
+同2,只是表示形式不同。
+
+
+
+4、较为复杂的对象格式,可以使用问号加一个空格代表一个复杂的 key,配合一个冒号加一个空格代表一个 value:
+
+```yaml
+?
+ - complexkey1
+ - complexkey2
+:
+ - complexvalue1
+ - complexvalue2
+```
+
+意思即对象的属性是一个数组 [complexkey1,complexkey2],对应的值也是一个数组 [complexvalue1,complexvalue2]
+
+
+
+## YAML 数组
+
+以 **-** 开头的行表示构成一个数组:
+
+```yaml
+- A
+- B
+- C
+```
+
+YAML 支持多维数组,可以使用行内表示:
+
+```yaml
+key: [value1, value2, ...]
+```
+
+数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。
+
+```yaml
+-
+ - A
+ - B
+ - C
+```
+
+一个相对复杂的例子:
+
+```yaml
+companies:
+ -
+ id: 1
+ name: company1
+ price: 200W
+ -
+ id: 2
+ name: company2
+ price: 500W
+```
+
+意思是 companies 属性是一个数组,每一个数组元素又是由 id、name、price 三个属性构成。
+
+数组也可以使用流式(flow)的方式表示:
+
+```yaml
+companies: [{id: 1,name: company1,price: 200W},{id: 2,name: company2,price: 500W}]
+```
+
+
+
+## 复合结构(对象和数组组合)
+
+数组和对象可以构成复合结构,例:
+
+```yaml
+languages:
+ - Ruby
+ - Perl
+ - Python
+websites:
+ YAML: yaml.org
+ Ruby: ruby-lang.org
+ Python: python.org
+ Perl: use.perl.org
+```
+
+转换为 js 为:
+
+```yaml
+{
+ languages: [ 'Ruby', 'Perl', 'Python'],
+ websites: {
+ YAML: 'yaml.org',
+ Ruby: 'ruby-lang.org',
+ Python: 'python.org',
+ Perl: 'use.perl.org'
+ }
+}
+```
+
+
+
+## 纯量
+
+纯量是最基本的,不可再分的值,包括:
+
+- 字符串
+- 布尔值
+- 整数
+- 浮点数
+- Null
+- 时间
+- 日期
+
+使用一个例子来快速了解纯量的基本使用:
+
+```yaml
+boolean: # 布尔值
+ - TRUE #true,True都可以
+ - FALSE #false,False都可以
+float: # 浮点数
+ - 3.14
+ - 6.8523015e+5 #可以使用科学计数法
+int: # 整数
+ - 123
+ - 0b1010_0111_0100_1010_1110 #二进制表示
+null: # Null
+ nodeName: 'node'
+ parent: ~ #使用~表示null
+string: # 字符串
+ - 哈哈 # 字符串默认不使用引号表示
+ - 'Hello world' #可以使用双引号或者单引号包裹特殊字符
+ - newline
+ newline2 #字符串可以拆成多行,每一行会被转化成一个空格
+date:
+ - 2018-02-17 #日期必须使用ISO 8601格式,即yyyy-MM-dd
+datetime:
+ - 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
+```
+
+
+
+ 多行字符串可以使用`|`保留换行符,也可以使用`>`折叠换行。
+
+```yaml
+this: |
+ Foo
+ Bar
+that: >
+ Foo
+ Bar
+```
+
+ 转为js代码如下。
+
+```json
+{ this: 'Foo\nBar\n', that: 'Foo Bar\n' }
+```
+
+ `+`表示保留文字块末尾的换行,`-`表示删除字符串末尾的换行。
+
+```yaml
+s1: |
+ Foo
+
+s2: |+
+ Foo
+
+
+s3: |-
+ Foo
+```
+
+ 转为json代码如下
+
+```json
+{ s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' }
+```
+
+
+
+ 字符串之中可以插入 HTML 标记。
+
+```yaml
+message: |
+
+ 段落
+
+```
+
+
+
+
+
+
+
+## 转换数据格式
+
+ 允许使用两个感叹号,强制转换数据类型。
+
+```yaml
+e: !!str 123
+f: !!str true
+```
+
+转换js结果同:
+
+```json
+{ e: '123', f: 'true' }
+```
+
+
+
+
+
+## 锚点引用
+
+**&** 锚点和< span class="marked">* 别名,可以用来引用:
+
+```yaml
+defaults: &defaults # 添加锚点
+ adapter: postgres
+ host: localhost
+
+development:
+ database: myapp_development
+ <<: *defaults # <<表示合并,*引用锚点
+
+test:
+ database: myapp_test
+ <<: *defaults # <<表示合并,*引用锚点
+```
+
+相当于:
+
+```yaml
+defaults:
+ adapter: postgres
+ host: localhost
+
+development:
+ database: myapp_development
+ adapter: postgres
+ host: localhost
+
+test:
+ database: myapp_test
+ adapter: postgres
+ host: localhost
+```
+
+**&** 用来建立锚点(defaults),**<<** 表示合并到当前数据,***** 用来引用锚点。
+
+下面是另一个例子:
+
+```yaml
+- &showell Steve
+- Clark
+- Brian
+- Oren
+- *showell
+```
+
+转为js代码如下:
+
+```yaml
+[ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ]
+```
+
+
+
+> 参考:
+>
+> - yaml官网:
+> - yaml与js的转换demo:
+> - 菜鸟教程:
\ No newline at end of file
diff --git a/docs/python/README.md b/docs/python/README.md
deleted file mode 100644
index 9e86a96..0000000
--- a/docs/python/README.md
+++ /dev/null
@@ -1 +0,0 @@
-> 敬请期待
\ No newline at end of file
diff --git a/docs/readme.md b/docs/readme.md
index b6b972f..404ba2b 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -6,13 +6,13 @@ tagline: web前端工程师,热衷于学习与总结
features:
- title: 前端
- details: web前端开发相关技术
+ details: JavaScript、ES6、vue框架等相关技术
url: /web/JavaScript/01.JavaScript中的名词概念
imgname: /img/web.png
- - title: python
- details: 人生苦短,我学python
- url: /python/
- imgname: /img/python.png
+ - title: 页面
+ details: html(5)/css(3),前端页面相关技术
+ url: /ui/00.flex布局语法
+ imgname: /img/ui.png
- title: 技术杂谈
details: 技术文档、教程、技巧、学习笔记等
url: /other/git
@@ -28,19 +28,12 @@ footer: Copyright © 2019-present evanblog
* [多种数组去重性能对比](web/JavaScript/06.多种数组去重性能对比)
* [更多... 👉](web/JavaScript/01.JavaScript中的名词概念)
-
-
-* CSS/HTML
- * [flex布局基础](web/CSS-HTML/01.flex布局基础)
- * [flex布局案例-圣杯布局](web/CSS-HTML/04.flex布局案例-圣杯布局)
- * [CSS3之animation动画](web/CSS-HTML/07.CSS3之animation动画)
- * [更多... 👉](web/CSS-HTML/01.flex布局基础)
-
-
-
-## 🐍python
-
-> 敬请期待
+## 🎨 页面
+* HTML/CSS
+ * [flex布局语法](ui/00.flex布局语法)
+ * [flex布局案例-圣杯布局](ui/04.flex布局案例-圣杯布局)
+ * [CSS3之animation动画](ui/07.CSS3之animation动画)
+ * [更多... 👉](ui/00.flex布局语法)
@@ -50,17 +43,17 @@ footer: Copyright © 2019-present evanblog
* [Git使用文档](other/git)
* [GitHub高级搜索技巧](other/github)
* [Markdown使用教程](other/markdown)
+ * [更多... 👉](other/git)
* 学习
* [学习效果低,忘性很大怎么办?](other/LearningAndMemory)
* [学习网站分享](other/study)
+ * [更多... 👉](other/LearningAndMemory)
* 面试
* [面试问题集锦](other/interview)
* 其他
* [在线工具](other/utils)
* [关于本博客搭建](https://github.com/xugaoyi/evanblog)
-* [更多... 👉](other/git)
-
## :email: 联系
- **Github**:
diff --git a/docs/ui/00.flex布局语法.md b/docs/ui/00.flex布局语法.md
new file mode 100644
index 0000000..09301a1
--- /dev/null
+++ b/docs/ui/00.flex布局语法.md
@@ -0,0 +1,305 @@
+# flex布局语法
+
+## 一、flex 布局是什么?
+
+Flex是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。
+
+
+
+**任何一个容器** 都可以指定为 Flex 布局。
+
+```css
+.box{
+ display: flex;
+}
+```
+
+
+
+**行内元素** 也可以使用 Flex 布局。
+
+```css
+.box{
+ display: inline-flex;
+}
+```
+
+
+
+Webkit 内核的浏览器,必须加上`-webkit`前缀。
+
+```css
+.box{
+ display: -webkit-flex; /* Safari */
+ display: flex;
+}
+```
+
+
+
+**注意**,设为 Flex 布局以后,子元素的`float`、`clear`和`vertical-align`属性将失效。
+
+
+
+## 二、基本概念
+
+采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。
+
+
+
+容器默认存在两根轴:水平的主轴(或x轴)(main axis)和垂直的交叉轴(或y轴)(cross axis)。主轴的开始位置(与边框的交叉点)叫做`main start`,结束位置叫做`main end`;交叉轴的开始位置叫做`cross start`,结束位置叫做`cross end`。
+
+项目默认沿主轴排列。单个项目占据的主轴空间叫做`main size`,占据的交叉轴空间叫做`cross size`。
+
+
+
+## 三、容器的属性
+
+以下6个属性设置在容器上。(即设置在 display: flex; 的元素上)
+
+
+
+>- flex-direction 方向 决定项目的排列方向
+>
+>- flex-wrap 换行
+>
+>- flex-flow 方向和换行的简写
+>
+>
+>
+>- justify-content 主轴对齐方式
+>
+>- align-items 交叉轴对齐方式
+>
+>- align-content 多轴线的对齐方式
+
+
+
+### 3.1 flex-direction属性
+
+`flex-direction`属性决定主轴的方向(即项目的排列方向)。
+
+> ```css
+> .box {
+> flex-direction: row | row-reverse | column | column-reverse;
+> }
+> ```
+
+
+
+它可能有4个值。
+
+> - `row`(默认值):主轴为水平方向,起点在左端。
+> - `row-reverse`:主轴为水平方向,起点在右端。
+> - `column`:主轴为垂直方向,起点在上沿。
+> - `column-reverse`:主轴为垂直方向,起点在下沿。
+
+### 3.2 flex-wrap属性
+
+默认情况下,项目都排在一条线(又称"轴线")上。`flex-wrap`属性定义,如果一条轴线排不下,如何换行。
+
+
+
+> ```css
+> .box{
+> flex-wrap: nowrap | wrap | wrap-reverse;
+> }
+> ```
+
+它可能取三个值。
+
+(1)`nowrap`(默认):不换行。会压缩项目的宽度
+
+
+
+(2)`wrap`:换行,第一行在上方。
+
+
+
+(3)`wrap-reverse`:换行,第一行在下方。
+
+
+
+### 3.3 flex-flow
+
+`flex-flow`属性是`flex-direction`属性和`flex-wrap`属性的简写形式,默认值为`row nowrap`。
+
+> ```css
+> .box {
+> flex-flow: || ;
+> }
+> ```
+
+### 3.4 justify-content属性
+
+`justify-content`属性定义了项目在主轴上的对齐方式。
+
+> ```css
+> .box {
+> justify-content: flex-start | flex-end | center | space-between | space-around;
+> }
+> ```
+
+
+
+它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。
+
+> - `flex-start`(默认值):左对齐
+> - `flex-end`:右对齐
+> - `center`: 居中
+> - `space-between`:两端对齐,项目之间的间隔都相等。
+> - `space-around`:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
+
+### 3.5 align-items属性
+
+`align-items`属性定义项目在交叉轴上如何对齐。
+
+> ```css
+> .box {
+> align-items: flex-start | flex-end | center | baseline | stretch;
+> }
+> ```
+
+
+
+它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。
+
+> - `flex-start`:交叉轴的起点对齐。
+> - `flex-end`:交叉轴的终点对齐。
+> - `center`:交叉轴的中点对齐。
+> - `baseline`: 项目的第一行文字的基线对齐。
+> - `stretch`(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
+
+### 3.6 align-content属性
+
+`align-content`属性定义了多根轴线的对齐方式(**一排项目为一根轴线**)。如果项目只有一根轴线,该属性不起作用。
+
+> ```css
+> .box {
+> align-content: flex-start | flex-end | center | space-between | space-around | stretch;
+> }
+> ```
+
+
+
+该属性可能取6个值。
+
+> - `flex-start`:与交叉轴的起点对齐。
+> - `flex-end`:与交叉轴的终点对齐。
+> - `center`:与交叉轴的中点对齐。
+> - `space-between`:与交叉轴两端对齐,轴线之间的间隔平均分布。
+> - `space-around`:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
+> - `stretch`(默认值):轴线占满整个交叉轴。
+
+
+
+## 四、项目的属性
+
+以下6个属性设置在项目上。
+
+> - `order`
+> - `flex-grow`
+> - `flex-shrink`
+> - `flex-basis`
+> - `flex` `flex-grow`, `flex-shrink` 和 `flex-basis`的简写
+> - `align-self`
+
+
+
+### 4.1 order属性
+
+`order`属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
+
+> ```css
+> .item {
+> order: ;
+> }
+> ```
+
+
+
+### 4.2 flex-grow属性
+
+`flex-grow`属性定义项目的放大比例,默认为`0`,即如果存在剩余空间,也不放大。
+
+> ```css
+> .item {
+> flex-grow: ; /* default 0 */
+> }
+> ```
+
+
+
+如果所有项目的`flex-grow`属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的`flex-grow`属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
+
+### 4.3 flex-shrink属性
+
+`flex-shrink`属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
+
+> ```css
+> .item {
+> flex-shrink: ; /* default 1 */
+> }
+> ```
+
+
+
+如果所有项目的`flex-shrink`属性都为1,当空间不足时,都将等比例缩小。如果一个项目的`flex-shrink`属性为0,其他项目都为1,则空间不足时,前者不缩小。
+
+负值对该属性无效。
+
+### 4.4 flex-basis属性
+
+`flex-basis`属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为`auto`,即项目的本来大小。
+
+> ```css
+> .item {
+> flex-basis: | auto; /* default auto */
+> }
+> ```
+
+它可以设为跟`width`或`height`属性一样的值(比如350px),则项目将占据固定空间。
+
+### 4.5 flex属性
+
+`flex`属性是`flex-grow`, `flex-shrink` 和 `flex-basis`的简写,默认值为`0 1 auto`。后两个属性可选。
+
+> ```css
+> .item {
+> flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
+> }
+> ```
+
+该属性有两个快捷值:`auto` (`1 1 auto`) 和 none (`0 0 auto`)。
+
+**建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。**
+
+
+
+### 4.6 align-self属性
+
+`align-self`属性允许单个项目有与其他项目不一样的对齐方式,可覆盖`align-items`属性。默认值为`auto`,表示继承父元素的`align-items`属性,如果没有父元素,则等同于`stretch`。
+
+> ```css
+> .item {
+> align-self: auto | flex-start | flex-end | center | baseline | stretch;
+> }
+> ```
+
+
+
+该属性可能取6个值,除了auto,其他都与align-items属性完全一致。
+
+
+
+(完)
+
+
+
+> 来源:
+>
+>
+>
+>
+
+
\ No newline at end of file
diff --git a/docs/web/CSS-HTML/01.flex布局基础.md b/docs/ui/01.flex布局案例-基础.md
similarity index 99%
rename from docs/web/CSS-HTML/01.flex布局基础.md
rename to docs/ui/01.flex布局案例-基础.md
index b166055..15665d0 100644
--- a/docs/web/CSS-HTML/01.flex布局基础.md
+++ b/docs/ui/01.flex布局案例-基础.md
@@ -1,4 +1,4 @@
-# flex布局基础
+# flex布局案例-基础
先上代码,[效果](#效果)在后面
diff --git a/docs/web/CSS-HTML/02.flex布局案例-骰子.md b/docs/ui/02.flex布局案例-骰子.md
similarity index 100%
rename from docs/web/CSS-HTML/02.flex布局案例-骰子.md
rename to docs/ui/02.flex布局案例-骰子.md
diff --git a/docs/web/CSS-HTML/03.flex布局案例-网格布局.md b/docs/ui/03.flex布局案例-网格布局.md
similarity index 100%
rename from docs/web/CSS-HTML/03.flex布局案例-网格布局.md
rename to docs/ui/03.flex布局案例-网格布局.md
diff --git a/docs/web/CSS-HTML/04.flex布局案例-圣杯布局.md b/docs/ui/04.flex布局案例-圣杯布局.md
similarity index 100%
rename from docs/web/CSS-HTML/04.flex布局案例-圣杯布局.md
rename to docs/ui/04.flex布局案例-圣杯布局.md
diff --git a/docs/web/CSS-HTML/05.flex布局案例-输入框布局.md b/docs/ui/05.flex布局案例-输入框布局.md
similarity index 100%
rename from docs/web/CSS-HTML/05.flex布局案例-输入框布局.md
rename to docs/ui/05.flex布局案例-输入框布局.md
diff --git a/docs/web/CSS-HTML/06.CSS3之transform过渡.md b/docs/ui/06.CSS3之transform过渡.md
similarity index 100%
rename from docs/web/CSS-HTML/06.CSS3之transform过渡.md
rename to docs/ui/06.CSS3之transform过渡.md
diff --git a/docs/web/CSS-HTML/07.CSS3之animation动画.md b/docs/ui/07.CSS3之animation动画.md
similarity index 100%
rename from docs/web/CSS-HTML/07.CSS3之animation动画.md
rename to docs/ui/07.CSS3之animation动画.md
diff --git a/docs/web/CSS-HTML/README.md b/docs/web/CSS-HTML/README.md
deleted file mode 100644
index 9e86a96..0000000
--- a/docs/web/CSS-HTML/README.md
+++ /dev/null
@@ -1 +0,0 @@
-> 敬请期待
\ No newline at end of file