angular 图片上传orientation旋转属性

今天讲一个以前遇到的一个懵逼问题
一张后台返回的图片,显示在安卓的内嵌前端页面,倒转了90度(在上传的时候保证是正的)
在浏览器中,查看也是正的,单独的在html查看也是正的(jpeg格式)
各种懵,怎么会出现在这种情况
了解到这是因为 图片orientation的原因,横拍的图片,正常显示,但是这个旋转
属性值为90,造成在安卓上显示就倒转90了
1.最快的解决方式,重新让UI生成一张png格式的图片就ok了
2.如何知道自己上传的图片orientation 不正常(上传拦截这种图片)
因为这种图片很少,情况不建议写大量代码就更改图片的orientation值
后面有更改orientation的写法
因为是 写在angualr项目里面的,所以以angualr为主

1.首先了解一下orientation属性
旋转0度 orientation参数为1
旋转90度 orientation参数为6
旋转180度 orientation参数为3
旋转270度 orientation参数为8
2.这里我们用的是exif.js来获取图片的orientation值
https://github.com/Velg03961485/exif 插件的地址
*3在main.js中 paths 写入插件地址

1
2
3
4
5
6
require.config({
paths:{
// 查取图片的orientation属性
'exif':'assets/exif',
}
})

*4在页面的controller js里面先引入

1
require('exif');

*5前端页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="col-md-12">
<div class="form-group">
<label class="control-label col-md-2">上传缩略图:</label>
<form name="uploadForm">
<div class="col-md-2">
<ul style="list-style-type:none; margin:0;width:800px;">
<li ng-repeat="img in imgs" style="width:130px;float: left;display:inline;margin-right: 10px;position:relative">
<img ng-src="{{ img }}" width="120" height="120" />
<span class="icon-close img-close" style='position:absolute;right:-2px;top:-4px;' ng-click="removeImg($index)"></span>
</li>
<li ng-if="idOlder == 1" ngf-select="uploadPic(file)" ng-model="file" name="file" ngf-pattern="'image/*'" ngf-accept="'image/*'" ngf-max-size="20MB" style="width:200px;float: left;display:inline">
<img style="border:1px;cursor:pointer" src="css/img/updatepic.png" width="120" height="120" alt />
</li>
</ul>
</div>
<!--<input type="hidden" ng-model="pics" name="pics" value="{{pics}}"/>-->
</form>
</div>
</div>

*6app.controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//上传图片
$scope.selectUpload = function() {
$scope.uploadTab.active = true;
};


$scope.uploadPic = function(file) {
angular.element(document.getElementById('landmark')).css('display','block');
console.log(file)
EXIF.getData(file, function() {
var Orientation = EXIF.getTag(this, 'Orientation');
if(Orientation == '3' || Orientation == '6' || Orientation == '8'){
alert('您上传的图片有问题,建议采用PNG格式');
}else{
if(file.type == 'image/png' || file.type == 'image/jpeg' || file.type == 'image/gif'){
if(file.size < 1*1024*1024){
file.upload = Upload.upload({
url: '/admin/new/uploadImage',
headers: {
'optional-header': 'header-value'
},
data: {
file: file
}
});
file.upload.then(function(response) {
console.log(response.statusText);
//response.statusText=='OK' 此状态不可用了,可能是因为更新
if(response.status== 200){
//toastr.success("图片上传成功");
$scope.imgs.push(response.data.url);
$scope.idOlder = 0;
}else{
toastr.error("图片上传失败");
// $scope.imgs.push(response.data.url);
}
angular.element(document.getElementById('landmark')).css('display','none');
//$scope.defaultPicFile = "css/img/updatepic.png";
}, function(response) {
if(response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
}, function(evt) {
file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
});
file.upload.xhr(function(xhr) {});
}else{
alert('您选择的文件过大');
angular.element(document.getElementById('landmark')).css('display','none');
}
}else{
alert('您输入的文件不合法,请选择正确文件');
angular.element(document.getElementById('landmark')).css('display','none');
}
}
});

};

这里分别对图片的Orientation 判断并进行拦截和限制图片大小 以及图片格式
angular 简单的图片获取旋转属性并进行判断 就弄好了

3.对于发生旋转的图片如何去纠正,这就要使用canvas对图片进行更改了
CSND上面有各种解决方案,这里暂时就不先做出具体详解
同时还用到一个 mobileBUGFix.mini.js 插件,这里不太熟悉,只能等下期讲解

后记
Orientation 除了这四种属性之外,还有镜像的旋转属性,有兴趣的可以了解一下
对于canvas生成照片以及转化base64,前面的博客有介绍,几乎概括全部的文件来回转换类型

Vue 短信发送倒计时

项目里面,登录注册,都是需要短信验证的
倒计时,就是十分关键的了
倒计时要注意的
1.点击获取验证码之后,点击事件失效
2.60秒之后要回到最初状态
3.打破倒计时

1
<el-col :span="2"><el-button @click="code()" :class="{disabled: !this.canClick}">{{getClickName}}</el-button></el-col>

这里采用动态添加button内容和操作类名 实现倒计时展现和失效事件

失效样式

1
2
3
4
5
6
.disabled{
background-color: #ddd;
border-color: #ddd;
color:#57a3f3;
cursor: not-allowed; // 鼠标变化
}

这个一定不能忘

js 这块
首先定义最初状态

1
2
3
4
5
6
7
data(){
return{
getClickName:'获取验证码',
waitTime:60,
canClick: true,
}
}

定义点击事件,开始倒计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
getMsg(){
if (!this.canClick) return ;
this.canClick = false
this.$data.getClickName = this.$data.waitTime + 's后发送';
clock = window.setInterval(() => { //这里的colock,我在全局定义,方便打破
this.$data.waitTime--;
this.$data.getClickName = this.$data.waitTime + 's后发送';
if (this.$data.waitTime < 0) {
window.clearInterval(clock)
this.$data.getClickName = '发送验证码';
this.$data.waitTime = 60;
this.canClick = true //这里重新开启

}
},1000);
}

接来下就是你是怎么调用倒计时事件了,根据不同的情况,有不同的调用方式
1.发送验证码成功的事件里调用倒计时
2.在点击开始倒计时的时候,调用发送验证码
这个两个不同的时候方法,一定要注意的是,不要重复调用或者调用冲突

打破倒计时,一般出现在,当你重新在手机输入框获取焦点,选择输入新的手机号的时候
倒计时回到最初状态

1
<el-col :span="15"><el-input v-model="passwordForm.phone" @focus="needsC"></el-input></el-col>

1
2
3
4
5
6
7
needsC(){
window.clearInterval(clock)
this.$data.getClickName = '发送验证码';
this.$data.waitTime = 60;
this.canClick = true //这里重新开启
this.$data.passwordForm.phone = '';
}

这样一个倒计时功能就完美实现了

Vue 面包屑导航

最近在写vue的面包屑导航菜单
要实现 在面包屑的基础上能来回跳转页面(页面间交互很多,需要传值)
1.首先 明确页面布局
在 main.vue 里面 调整顶部栏,侧边栏以及content

1
2
3
4
5
6
7
8
9
<template>
<div class="main-box">
<navbar ref="header"></navbar>
<sidebar ref="leftmenu"></sidebar>
<div class="content" ref="content">
<router-view></router-view>
</div>
</div>
</template>

在这里还有一个重要的函数,当路由发生变化的时候,要让面包屑也发生改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
fetchData: function () {
let pathName = this.$route.name
// 判断当前路径,设置菜单效果
let usercenter = this.$refs.content
let sidebar = this.$refs.leftmenu

if (usercenter) {
usercenter.navMenu = pathName
}

switch (pathName) {
// 商家
case 'BusinessList':
case 'Modules':
sidebar.defaultActive = '2-1'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
// 设备
case 'Pallet':
case 'Store':
case 'Device':
sidebar.defaultActive = '3-1'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'Flow':
sidebar.defaultActive = '3-2'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'Face':
sidebar.defaultActive = '3-3'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'Counter':
sidebar.defaultActive = '3-4'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
// 设置
case 'AccountNumber':
sidebar.defaultActive = '4-1'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'Role':
sidebar.defaultActive = '4-2'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'Power':
sidebar.defaultActive = '4-3'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
// 工作日志
case 'LoginList':
sidebar.defaultActive = '5-1'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'ActiveList':
sidebar.defaultActive = '5-2'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
//鹰眼人脸数据测试
case 'FaceSampleGrouping':
sidebar.defaultActive = '6-1'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'FaceSampleManage':
case 'RecognitionResult':
case 'RecognitionList':
sidebar.defaultActive = '6-2'
sidebar.imgSrcHome = 'static/images/main/home.png'
break
case 'RecognitionResultsFound':
// usercenter.topMenu = ''
// usercenter.left_menu_active = '0'
// usercenter.top_menu_active = '0'
// break
}
}

在mounted调用 此函数
2.在router.js 里面设置路由
要记得,把面包屑引入

1
import Templates from '../views/template/Templates'

在 new Router 的路由配置里面 component为 Tempaltes
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
path: 'equipment',
component: Templates,
children: [
// 设备
{ path: 'counter', name: 'Counter', component: Counter },
{ path: 'face', name: 'Face', component: Face },
{ path: 'flow', name: 'Flow', component: Flow },
{ path: 'pallet', name: 'Pallet', component: Pallet },
{ path: 'store/:storeId', name: 'Store', component: Store },
{ path: 'device/:shopId', name: 'Device', component: Device }
]
}

3.然后在 Tempaltes.vue 写要呈现的面包屑
主要实现方式,根据URL的地址来判断 菜单点击到哪一步,把需要的面包屑呈现出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<template>
<div class="templates">
<!-- 导航 start -->
<div class="nav-menu">
<!-- 商家 start -->
<el-breadcrumb v-if="navMenu === 'BusinessList'" separator="/">
<el-breadcrumb-item>商家</el-breadcrumb-item>
<el-breadcrumb-item>商家管理</el-breadcrumb-item>
</el-breadcrumb>

<el-breadcrumb v-if="navMenu === 'Modules'" separator="/">
<el-breadcrumb-item>商家</el-breadcrumb-item>
<el-breadcrumb-item><router-link :to="{ name: 'BusinessList' }" replace>商家管理</router-link></el-breadcrumb-item>
<el-breadcrumb-item>模块&nbsp;&nbsp;{{ name }}</el-breadcrumb-item>
</el-breadcrumb>
<!--鹰眼人脸数据测试-->
<el-breadcrumb v-if="navMenu === 'FaceSampleGrouping'" separator="/">
<el-breadcrumb-item>鹰眼人脸数据测试</el-breadcrumb-item>
<el-breadcrumb-item>人脸样本分组管理</el-breadcrumb-item>
</el-breadcrumb>
<el-breadcrumb v-if="navMenu === 'FaceSampleManage'" separator="/">
<el-breadcrumb-item>鹰眼人脸数据测试</el-breadcrumb-item>
<el-breadcrumb-item>
<router-link :to="{ name: 'FaceSampleGrouping' }" replace>人脸样本分组管理</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>人脸样本管理</el-breadcrumb-item>
</el-breadcrumb>
<el-breadcrumb v-if="navMenu === 'RecognitionResult'" separator="/">
<el-breadcrumb-item>鹰眼人脸数据测试</el-breadcrumb-item>
<el-breadcrumb-item>
<router-link :to="{ name: 'FaceSampleGrouping' }" replace>人脸样本分组管理</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>
<router-link :to="{ name: 'FaceSampleManage', params: {'id': id}}" replace>人脸样本管理</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>识别结果查看</el-breadcrumb-item>
</el-breadcrumb>
<el-breadcrumb v-if="navMenu === 'RecognitionList'" separator="/">
<el-breadcrumb-item>鹰眼人脸数据测试</el-breadcrumb-item>
<el-breadcrumb-item>识别结果列表</el-breadcrumb-item>
</el-breadcrumb>
<el-breadcrumb v-if="navMenu === 'RecognitionResultsFound'" separator="/">
<el-breadcrumb-item>鹰眼人脸数据测试</el-breadcrumb-item>
<el-breadcrumb-item>
<router-link :to="{ name: 'RecognitionList' }" replace>识别结果列表</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>识别结果查看</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 导航 end -->
<!-- 主要内容 start -->
<div class="main">
<router-view></router-view>
</div>
<!-- 主要内容 end -->
</div>
</template>

这里还有的点,就是 面包屑的来回跳转
使用包裹 router-link 进行跳转,简单的传值,直接使用重url取值再传过去
建议使用 storage.setLocalStorage 或者 vuex
4.最后就需要在你跳转的页面,进行接参,把面包屑展现出来

1
2
3
4
5
6
7
8
created: function () {
// 刷新时,获取动态数据 设置navmenu
let templates = this.$parent
templates.navMenu = this.$route.name
templates.upperLevelMenu = ''
this.faceListAll()

}

Vue 小方法(处理)

最近在Vue 项目里,有遇到很多小细节处理
1.vue项目在IE下不能运行
抛出 ReferenceError: “Promise” 未定义
这里是因为 IE兼容的问题
最快速有效的解决方式
安装 Babel Polyfill

1
npm install --save babel-polyfill

这样就OK了

如果出现问题,可以继续用下面方式
在webpack.base.conf.js这个文件加入代码 require(“babel-polyfill”)

在main.js里面添加代码 import “babel-polyfill”;

2.修改element默认样式
使用element模板是一种快速开发方式
但是,又不得不面对去改动样式的问题,尤其最近有遇到 要改变 el-input 标签的样式
分析标签层 发现 原生的input是包裹在div标签下面的
直接改动样式是无效的 同时抓到el-input 类名去更改也是无效的

解决方式
去掉 scoped ,让此样式能显示
但是这样做,回去全局样式造成污染

可以把样式放到全局控制样式文件里面
同时为了不让这样的样式影响到其他的el-input
需要给el-input 标签加一个 单独的类名 例如 styleInput

1
2
3
.styleInput .el-input_inner{
border:1px solid red;
}

3.element-ui 图片上传成功之后,页面图片清除不掉
这个问题,实在是…无语
清除上传记录,清除数据,清除页面 皆无可用
其实element 有方法,但是,官网就是没有说明

在上传的el-upload标签上 绑定ref

1
ref="upload"

然后上传成功函数,或者关闭函数里面

1
this.$refs.upload.clearFiles();

ok 这样就可以清除了,但是图片数组的数据也要记得清空一下,尤其是弹框下的
省的会造成数据污染

后记
后面会有一篇专门介绍 el-upload 上传的使用方式
全面分析 el-upload 的各种事件以及处理方法

Vue 自定义验证规则

vue 使用element 样式模板 校验 el-input 输入规则
普通的校验规则 在:rule 里面完成
但是复杂的规则 ,则需要 写校验函数,全局校验函数

1.创建校验函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const rules = {
uer:{
//用户名验证
username(len_min,len_max,text){
return [
{ required: true, message: text, trigger: 'blur' },
{
validator:(rule,value,callback) =>{
if(value.match(/^[a-zA-Z0-9\u4e00-\u9fa5]{5,16}$/)){
callback();
}else{
callback("只能是数字、字母和汉字5-16位")
}
},
trigger:'blur'
}
]
},

//密码验证
password(len_min,len_max,text) {
return [
{ required: true, message: text, trigger: 'blur' },
{ validator:(rule,value,callback) =>{
if(value.match(/^[a-zA-Z0-9^%&'!@#*()_+[{}:'"<>/,;=?$\x22]{5,16}$/)){
callback();
}if(value.match(/^\s*|\s*$/)){
callback("除空格外数字,字母和任意字符6-16位")
}

},
trigger:'blur'
}
]
},

//手机号验证
phone(){
return [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value.match(/^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/)){
callback();
} else {
callback("请输入正确的手机号");
}
},
trigger: 'blur'
}
]
},

//姓名验证
truename(){
return [
{ required: true, message: '请输入姓名', trigger: 'blur' },

{
validator: (rule, value, callback) => {
if (value.match(/^[a-zA-Z\u4e00-\u9fa5]{2,8}$/)){
callback();
} else {
callback("请输入2-8位汉字或英文");
}
},
trigger: 'blur'
}
]
},
}
}

这里我给出了 三组校验函数,密码,手机号,姓名

2.页面引入 函数所在的文件
import globalRules from ‘@/config/global_rules’

3.在规则里面调用函数的方法

1
2
3
rule:{
passWord:globalRules.rules.user.password(6,20,'请输入当前密码:)
}

这样一个全局封装的 校验方法就完成了,简单的方式,漂亮的代码,完整的规则

后记:
校验函数,除了封装函数,最重要的是 如何通过正则去校验你要输入的内容

下面就写一些,平常会用的校验方法 和 正则使用
一、常用的校验

  1. 只能输入大于0的整数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    check(value) {
    let reg = /^[1-9]\d*$/;
    var _this = this;
    if (value) {
    if (new RegExp(reg).test(value) == false) {
    setTimeout(() => {
    _this.actionDataForm.studNum = '';
    _this.errorTip = true;
            }, 500); }else{ this.errorTip = false; } } },

2.邮箱验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var checkEmail = (rule, value, callback) => {
if (!value) {
return callback();
}
if (value) {
setTimeout(() => {
var reg = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
if (!reg.test(value)) {
callback(new Error('请输入有效的电子邮箱!'));
} else {
callback();
}
}, 500);
}
}

  1. 直接页面验证 有且只能有两位小数

    1
    2
    3
    4
    5
    <el-form-item label="成交总额:" prop="money">
    <el-col :span="15">
    <el-input v-model="item.money" v-on:input="getMoney(item.money)" value="0" :maxlength="inputMaxL" @input="inputMaxL = /^\d+\.?\d{0,1}$/.test(item.money) ? null : item.money.length - 1" v-bind:disabled="isCan==1" ></el-input>
    </el-col>
    </el-form-item>

    这里要记得 函数里 定义一下 inputMaxL

二、正则验证规则
1.验证数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1 数字:^[0-9]*$
2 n位的数字:^\d{n}$
3 至少n位的数字:^\d{n,}$
4 m-n位的数字:^\d{m,n}$
5 零和非零开头的数字:^(0|[1-9][0-9]*)$
6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
7 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
8 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
11 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
12 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
13 非负整数:^\d+$ 或 ^[1-9]\d*|0$
14 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
15 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
16 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
17 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
18 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
19 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

2.校验字符

1
2
3
4
5
6
7
8
9
10
11
12
1 汉字:^[\u4e00-\u9fa5]{0,}$
2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
3 长度为3-20的所有字符:^.{3,20}$
4 由26个英文字母组成的字符串:^[A-Za-z]+$
5 由26个大写英文字母组成的字符串:^[A-Z]+$
6 由26个小写英文字母组成的字符串:^[a-z]+$
7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
11 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
12 禁止输入含有~的字符:[^~\x22]+

3.特殊的校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1 Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
3 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
4 手机号码:^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$
5 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7 18位身份证号码(数字、字母x结尾):^((\d{18})|([0-9x]{18})|([0-9X]{18}))$
8 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
9 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
10 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
11 日期格式:^\d{4}-\d{1,2}-\d{1,2}
12 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
13 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
14 钱的输入格式:
15 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
16 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
17 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
18 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
19 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
20 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
21 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
22 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
23 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
24 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
25 中文字符的正则表达式:[\u4e00-\u9fa5]
26 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
27 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
28 HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
29 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
30 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
31 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
32 IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
33 IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

放个彩蛋,写一些,常使用的原生校验
1.只能输入数字

1
<input type="text" onkeyup="this.value=this.value.replace(/[^\d]/g,'') " onafterpaste="this.value=this.value.replace(/[^\d]/g,'') " name="f_order" value="1"/>

2.只能输入字母和下划线

1
<input onkeyup="this.value=this.value.replace(/[^_a-zA-Z]/g,'')" onpaste="this.value=this.value.replace(/[^_a-zA-Z]/g,'')">

3.只能输入数字、字母、下划线

1
2
3
<input onkeyup="this.value=this.value.replace(/[^\w]/g,'')" onpaste="this.value=this.value.replace(/[^\w]/g,'')"> 

<input onkeyup="this.value=this.value.replace(/[\W]/g,'')" onpaste="this.value=this.value.replace(/[\W]/g,'')">

4.只能输入数字和英文

1
<input onkeyup="value=value.replace(/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))">

5.只能输入数字

1
<input onkeyup="value=value.replace(/[^\d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))">

6.只能输入全角

1
<input onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))">

7.只能输入汉字

1
<input onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))">

8.只能输入数字和小数点

1
<input type=text name= caolig value ="" onblur="if (!(/^[\d]+\.?\d*$/.test(this.value)) ){alert('您的输入有误'); this.value='';this.focus();}">

vue 进阶之封装全局插件

刚接触vue 对里面的使用方法十分不熟练
从demo操作转到数据类型操作,中间还是有很多坑要填的0.0
今天,讲解的是,如何自己封装插件转换时间类型
首先,你的插件要能全局调用
在 config.js 创建插件 globalFunction.js 这里的命名最好驼峰式
有遇到“烤串”命名法,不被识别,好坑
//写一个时间转化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let format = (value) => {
return value >= 10 ? value : '0' + value
}

exports.install = function (Vue, options) {
Vue.prototype.TimeOut = function (time, type){
let date = new Date(time * 1000);
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let hours = date.getHours()
let minutes = date.getMinutes()
let second = date.getSeconds()
let result
switch (type) {
case 0: // 01-05
result = `${format(month)}月${format(day)}日`
break
case 1: // 11:12
result = `${format(hours)}:${format(minutes)}:${format(second)}`
break
case 2: // 2015-01-05
result = `${year}-${format(month)}-${format(day)}`
break
case 3: // 2015-01-05 11:12
result = `${year}-${format(month)}-${format(day)} ${format(hours)}:${format(minutes)}`
break
case 4:// 2015-01-05 11:12:06
result = `${year}-${format(month)}-${format(day)} ${format(hours)}:${format(minutes)}:${format(second)}`
break
case 5:// Jan 05, 2015 11:12:06
result = date.format('M dd, yyyy HH:mm:ss')
break
case 6:// Jan 05, 2015
result = date.format('M dd, yyyy')
break
case 7:// Jan 05, 2015
result = `${year}年${format(month)}月${format(day)}日 ${format(hours)}:${format(minutes)}`
break
}

return result;

};
};

Vue.prototype 指向你自己封装的函数,方面以后自己调用
然后在 main.js 里面 引入函数生成全局调用
import globalFunction from ‘@/assets/config/globalFunction’
Vue.use(globalFunction); //这一步一定不能忘

接下来在子组件里面 任何一个函数,获取到时间值,直接调用
this.TimeOut()

例如 console.log(this.TimeOut(1533686888,4)) //2018-08-08 08:08:08

结尾:
对插件稍微介绍一下
timeOut 是封装的函数,通过传值和要转化的时间类型,对值进行解析转化,
这个函数还可以放在 filter里面 封装一个 html页面的时间过滤插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
export const dateFilter = (time, type) => {
let date = new Date(time * 1000)
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let hours = date.getHours()
let minutes = date.getMinutes()
let second = date.getSeconds()
let result
switch (type) {
case 0: // 01-05
result = `${format(month)}月${format(day)}日`
break
case 1: // 11:12
result = `${format(hours)}:${format(minutes)}:${format(second)}`
break
case 2: // 2015-01-05
result = `${year}-${format(month)}-${format(day)}`
break
case 3: // 2015-01-05 11:12
result = `${year}-${format(month)}-${format(day)} ${format(hours)}:${format(minutes)}`
break
case 4:// 2015-01-05 11:12:06
result = `${year}-${format(month)}-${format(day)} ${format(hours)}:${format(minutes)}:${format(second)}`
break
case 5:// Jan 05, 2015 11:12:06
result = date.format('M dd, yyyy HH:mm:ss')
break
case 6:// Jan 05, 2015
result = date.format('M dd, yyyy')
break
case 7:// Jan 05, 2015
result = `${year}年${format(month)}月${format(day)}日 ${format(hours)}:${format(minutes)}`
break
}
return result
}

然后在main.js 里面调用
//import * as filters from ‘./filters’
//Vue.filter(‘date’, filters.dateFilter)

vue页面里面直接 数据绑定,进行过滤
scope.row.time | date(4)

(^▽^),结束…完美封装一个插件
推荐一首歌 Lifted -Allie X ,飘飘欲仙 让人觉得,放纵,是一种飘在
云端的逍遥

Vue 进阶之调用电脑摄像头

在开发过程中,硬件是你不得不面对的一件事
软硬结合是现代发展趋势,今天
我就讲解一下,最近面对的一个问题
如何快速调用电脑的摄像头,拍照并生成一张图片进行人脸识别(识别这块放在下期)
最近在用vue开发,今天的主题也是在vue的基础上O(∩_∩)O哈哈~
先上代码w(゚Д゚)w
<!–前端代码–/>
开启摄像头的时候,前端页面先给一个实时录像


点击拍照事件

拍摄
生成的照片,用canvas生成

实现起来超简单的

camera_process(){
//访问用户媒体设备的兼容方法
function getUserMedia(constraints, success, error) {
if (navigator.mediaDevices.getUserMedia) {
//最新的标准API
navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit核心浏览器
navigator.webkitGetUserMedia(constraints,success, error)
} else if (navigator.mozGetUserMedia) {
//firfox浏览器
navigator.mozGetUserMedia(constraints, success, error);
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constraints, success, error);
}
}

  function success(stream) {
    //兼容webkit核心浏览器
    let CompatibleURL = window.URL || window.webkitURL;
    //将视频流设置为video元素的源
    //console.log(stream);

    //video.src = CompatibleURL.createObjectURL(stream);
    video.srcObject = stream;
    video.play();
  }

  function error(error) {
    console.log(`访问用户媒体设备失败${error.name}, ${error.message}`);
  }

  let video = document.getElementById('video');
  let canvas = document.getElementById('canvas');
  let context = canvas.getContext('2d');
  let image = new Image();

  if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {
    //调用用户媒体设备, 访问摄像头
    getUserMedia({video : {width: 480, height: 320}}, success, error);
  } else {
    alert('不支持访问用户媒体');
  }


}

这里是调用实时摄像的代码,在vue中,最好把camera_process函数放在 mounted里面
进入页面就开始缓加载

pImage() {
let context = canvas.getContext(‘2d’);
let image = new Image();
context.drawImage(video, 0, 0, 480, 320);
// console.log(context.drawImage);
image = canvas.toDataURL(“image/jpeg”);//base64

  this.$data.takeImages = image;
  // console.log(this.$data.takeImages)

  document.getElementById('getVideo').style.display = 'none';
  //  获取头像
  this.getShowVideo = false;
  this.dialogVisible = true;
}

点击开始拍摄,并生成canvas 照片

(^▽^),这些都很简单的,只要代码copy一份就能实现
但是,接下来,你要传给后台了,万事离不开最终实践

canvas生成的图片是不能直接传给后台的,需要做一系列处理

1.首先 先把canvas转化为base64格式
image = canvas.toDataURL(“image/jpeg”);//base64

2.转化之后发现,卧槽,数据流也太长了吧,传过去完全是消耗性能
一定要转化为file模式才行
dataURLtoFile(dataurl, filename){
var arr = dataurl.split(‘,’), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n–){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}

这是从base64转化file的函数
3.调用函数,转化为file,发给后台
let file = this.dataURLtoFile(this.$data.takeImages,’testaaa.jpg’);
let list = new FormData();
list.append(‘file’, file);

(^▽^)简简单单的调用摄像头拍摄照片就这样完成了

后记:
稍微讲解一下关于 DataUrl 与 file,Blob,canvas之间的相互转化

1.canvas转化为dataURl
let a = canvas.toDataURl(‘b/png’) //base64

2.file转化为 dataURL / Blob 转化为dataURL (两者方式一样)
function readBlobAsDataURL(blob, callback) {
var a = new FileReader();
a.onload = function(e) {callback(e.target.result);};
a.readAsDataURL(blob);
}

readBlobAsDataURL(blob, function (dataurl){
console.log(dataurl);
});
readBlobAsDataURL(file, function (dataurl){
console.log(dataurl);
});

3.dataURL转化为Blob / dataURL转化为File (IE不支持File对象构造函数)
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(‘,’), mime = arr[0].match(/:(.?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n–){
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {type:mime});
}
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(‘,’), mime = arr[0].match(/:(.
?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n–){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
//test:
var blob = dataURLtoBlob(‘data:text/plain;base64,YWFhYWFhYQ==’);
var file = dataURLtoFile(‘data:text/plain;base64,YWFhYWFhYQ==’, ‘test.txt’);

4.dataURL 转化为canvas ((^▽^)很好玩的)
var img = new Image();
img.onload = function(){
canvas.drawImage(img);
};
img.src = dataurl;

5.File/Blob 绘制到canvas上
readBlobAsDataURL(file, function (dataurl){
var img = new Image();
img.onload = function(){
canvas.drawImage(img);
};
img.src = dataurl;
});

6.Canvas转换为Blob对象并使用Ajax发送
var dataurl = canvas.toDataURL(‘image/png’);
var blob = dataURLtoBlob(dataurl);
//使用ajax发送
var fd = new FormData();
fd.append(“image”, blob, “image.png”);
var xhr = new XMLHttpRequest();
xhr.open(‘POST’, ‘/server’, true);
xhr.send(fd);

git 使用命令(工作流)

给大家分享一下,在工作中常用到的git 基本命令

先针对于,你工作中,搬砖常用到的

首先你要了解,你自己的砖分别放在什么区域

工作区 本地代码存放的地方,一般使用 git status 查看修改的文件

暂存区 使用git add . 把工作区的砖 搬到 暂存区

仓库区 git commit 命令 搬进去,记得要写搬砖记录的

远程仓库 远程代码管理仓库 使用 git push 提交 ,把砖推送远程

+++++++++++++++++在master主枝干下++++++++++++++++++++++++++++
1.平时提交工作
git status
git add .
git commit -m “自己的任务/名称”
git push
2.如果推送的时候,报错没有提交成功需要拉去项目
git pull
3.拉去之后会出现文本写入页面
按Esc
shift + : (就是输入:)
wq
enter 退出
4.继续提交
git push


git rm -cached filename(文件名) 把暂缓区的文件拉工作区
git log 查看提交日志

===============================================================

你电脑公钥的生成
1,检查自己的电脑有没有存在秘钥
ls -al ~/.ssh
2.如果没有显示秘钥,则生成一个
ssh-kengen -t rsa -C “*********@qq.com
一路enter下来,直到有生成秘钥显示
3,打开git(码云),设置/SHH公钥,把id_rsa.pub里面的粘贴复制进去,点击确认
4,在本地建立一个新的文件夹,打开git
5,git clone git@gitee.com:hangzhou_bosom_friend/hangzhoubison.git
把远程的代码克隆下来

==================================================
新项目在分支下 提交代码
git status 查看自己的代码状态
git fetch 拉去远程分支代码,建议不要这样做,切换到分支下面拉取
git checkout -b hangzhoubison origin/dev1.0 切换分支
git pull origin dev1.0 拉取分支代码

提交自己的代码之前最好要先拉取一次,保持自己的代码最新
git add . 提交到暂缓区
git commit -m “提交备注” 提交到本地仓库,一定要有队这次更改的详细记录
git branch 查看本地以及远程的所有分支 (一定要保持在本地分支上进行提交)
(如果不小心切换分支了,git checkout hangzhoubison [本地分支名,不知道的话自己查看分支再进行切换])
git push origin HEAD:dev1.0 推送本地代码到远程(我查到的远程分支名是HEAD:dev1.0,自己可以查看远程分支是什么)


如果你提交远程的时候没有拉取保持最新就提交的话,请参考 3

git log 查看提交记录

git rm -cached filename 把暂存区的文件拉回工作区

git diff filename 查看提交文件之间的不同

知道这些命令,你就成为一个入门的搬砖工啦(^▽^)

要想码砖,就要继续努力O(∩_∩)O哈哈~

搭建blog of second

(^▽^)上次讲解的搭建blog,大家很容易上手了吧

但是hexo自带的页面,好丑啊,而且输入的地址也是github的,一点也不NG
没关系,这篇blog,将会给大家讲解一下,如何安装自己喜欢的blog主题
和设置自己的专属域名

先来安装主题吧,这个才是自己网站炫酷的起点

https://github.com/Velg03961485/hexo-theme-cyanstyle.git

https://github.com/Velg03961485/hexo-theme-landfarz.git

这是我github 中,叉的人家的主题,感觉还是不错的

要是想要更多炫酷的主题,你可以知乎上找一下

https://www.zhihu.com/question/24422335

这个话题的小伙伴,整理很多炫酷的主题,除此之外,官网也给出了很多

https://hexo.io/zh-cn/docs/themes.html

好了,接下来开始安装自己的主题吧

一、下载安装主题
敲命令

cd blog/themes/

git clone https://github.com/Velg03961485/hexo-theme-cyanstyle.git

下载完成之后,你可以把,这个主题的文件名字修改一下,我改为了,canystyle

cd ../

用记事本,打开blog下面的 _config.yml

查询 theme,更改为 canystyle

敲命令

hexo g 把主题生成成功

hexo s 启本地服务,localhost:4000

输入地址进去看看,是不是你本地的blog的主题已经换掉啦ヾ(@^▽^@)ノ

ctrl + c 退出本地服务

二、接下来就是要更新要线上了
在此之前一定要把刚才clone 下来的主题文件的 .git给删掉啊,不然,你自己的git是会识别错乱的

敲命令走起 O(∩_∩)O哈哈~

cd themes/cyanstyle/

ls

rm -rf .git 删除文件的命令一定要小心┌(。Д。)┐,继续走起

cd ../../

cd public/

git status

git add .

git commit -m “change my theme”

git push

显示上传成功,这还没有完成呢

接下来要在远程仓库,再进行一次主题编译,继续敲

hexo g

打开你的线上地址,(^▽^),是不是主题已经给换掉了

三、接下来就是最后一个NG操作了

1.打开自己的阿里云控制台

2.点击控制台

3.左侧菜单栏,倒数第二个,域名与网站(万网)0.0,好难找

4.点击域名,跳转到,自己以前买的域名列表(没有的小伙伴,在阿里云首页,按着他们的推荐购买一个域名就行啦)

5.在自己的域名,操作下面,有解析,点击进入

6.在 解析设置里面, 点击 添加解析

7.记录类型 选择 CNAME

8.主机记录 填入 @

9.解析线路 为 默认

10.记录值 为 你的GitHub 项目的地址,但是是后面 必须加一个 . (比如 velg03961485.github.io. )
其实这就是一个简单的地址重定向,还能用阿里的域名监听你的地址

11.TTL值 选用十分钟 取最小的时间,启动速度,TTL为缓存时间,数值越小,修改记录各地生效时间越快

12.点击确认 这样就ok啦(^▽^) ,你就可以通过你的域名访问你的blog

四、接下来就说说,我遇到的大坑,自己手贱造成的

在我点击确认的时候,报错,请求失败,提示说,我的CNAME 和 MX 记录冲突

因为我的主机记录填写了邮箱,而且邮箱还与我的GitHub账号相同

域名自动为我填写 MX 邮箱记录

阿里云的东西有时候还真是很难搞懂的,可又不得不用

扒文档,问大神

大神给我解决方案,它不是冲突嘛,采用二次地址定义喽,用一X定义你编译后的地址

然后再用 Link 把地址重定义

哎,我翻遍了阿里的文档,都没有这个Link 属性嘛o(╥﹏╥)o

不多说,继续翻文档,找解决方案

因为记录冲突,自己又不能删除冲突的记录,想着,我给暂停了,这样就可以添加了吧

结果文档给我来句

解析记录暂停后,用户仍然能够修改、删除解析记录,且被暂停的解析记录,也会参与到解析记录冲突判断的规则中。

啥话不说,删了,删了,既然冲突那就干掉一个呗,email的记录,全部删掉

照着上面来一遍,ok 添加成功,等待生效吧(^▽^)

五、接下来的文档,会给大家讲解,如何新增自己的blog,和对blog设置新的功能,调整页面,更改页面风格。和后续更新我的,坑神之路O(∩_∩)O哈哈~


title: 我的第一篇文章
date: 2018-05-23 10:12:14
gs:
—如果路还长,那就请你坚持的走下去,
因为你并不知道,下一刻,那条路还是不是你的
有人说
诗和远方是最好的一首歌

如果有来生

-三毛

如果有来生,要做一棵树,

站成永恒。没有悲欢的姿势,

一半在尘土里安详,

一半在风里飞扬;

一半洒落荫凉,

一半沐浴阳光。

非常沉默、非常骄傲。

从不依靠、从不寻找。

如果有来生,要化成一阵风,

一瞬间也能成为永恒。

没有善感的情怀,没有多情的眼睛。

一半在雨里洒脱,

一半在春光里旅行;

寂寞了,孤自去远行,

把淡淡的思念统带走,

从不思念、从不爱恋;

如果有来生,要做一只鸟,

飞越永恒,没有迷途的苦恼。

东方有火红的希望,

南方有温暖的巢床,

向西逐退残阳,向北唤醒芬芳。

如果有来生,

希望每次相遇,

都能化为永恒。