0%

capture full size screenshot

  1. F12
  2. Ctrl + Shift + P
  3. type in capture full size screenshot

dom process tips

FreeCodeCamp:你知道 Chrome 自带的开发者工具有这些功能吗

  • $$(‘.className’) chrome自带元素选择器
  • 将页面作为文本进行编辑
    1
    document.body.contentEditable = true;
  • 获取事件以及监听事件
  • console.time()
    1
    2
    3
    4
    console.time('heavy process')
    console.timeLog('heavy process', 'step1 finished')
    console.timeLog('heavy process', 'step2 finished')
    console.timeEnd('heavy process')
  • console.table(array)

    使用chrome 模拟加载较慢网速

  1. F12
  2. Network Tab
  3. Change “Online” to “Slow 3G”

    页面元素断点

    第三方UI控件会动态添加元素事件,使用css的hover,focus无法触发,把光标放上去触发又无法查看code,设置element的breakpoint可以在指定元素被修改时break,无论查看js逻辑还是元素的样式变化都很方便

    headless chrome

    知乎:Headless Chrome入门 即不显示浏览器界面而在命令行运行Chrome功能,该模式主要用于自动化测试工具#### 响应式
    使用 Chrome DevTools 中的 Device Mode 模拟移动设备

performance

current version: 6.5.2

关于响应式编程

知乎:响应式编程

Observable 的发布和订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Create simple observable that emits three values
const myObservable = of(1, 2, 3); // or from([1,2,3])

// Create observer object
const myObserver = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};

// Execute with the observer object
myObservable.subscribe(myObserver);
// Logs:
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification

of(…items) from(iterable)是自带常用产生Observable对象的工具

Observable 可观察对象

在http请求中,可观察对象不是new构造的,而是由httpclient的get或post方法返回一个Observable\. 私以为是成功调用接口后就发布一个结果,这个结果可以用管道加工

参考 Observable 的操作符

1
2
3
4
5
6
7
8
9
10
11
getDict(dictname): Observable<DictItem[]> {
return this.http.get(`dict/${dictname}`).pipe(
map((res: HttpResponse) => {
if (res.status === 'ok') {
return res.data;
} else {
return [];
}
}),
);
}

获取这个结果就需要订阅(subscribe)这个Observable,这个Observable是匿名的,每次获取它需要调用函数来返回

QQs:为什么httpclient方法不需要取消订阅

据说这些方法被实现为只next一次

Subject

A Subject is a special type of Observable which shares a single execution path among observers.

被比喻成广播者

1
2
3
4
5
6
7
const subject = new Subject();

subject.subscribe(log('s1 subject'));
subject.subscribe(log('s2 subject'));

subject.next('r');
subject.next('x');

同步数据转Observable

场景:组件粒度小,在视图中多次实例化,或者重复初始化,为防止频繁调用后台接口应加入数据缓存,
基础数据缓存实现为,第一次调用,从httpclient获取接口数据的Observable对象,并用管道处理加入缓存,之后将缓存数据取出转为Observable对象
basicdata.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Observable, of, from } from 'rxjs';
import { map } from 'rxjs/operators';

users: User[];
getUserList(): Observable<User[]> {
if (this.users) {
return of(this.users);
} else {
return this.http.post(`user/search`, { isvalid: 1 }).pipe(
map((res) => {
this.users = res as User[];
return this.users;
})
);
}
}

app.component.ts
1
2
3
4
this.basicData.getUserList().subscribe(list => {
this.userlist = list;
this.getUserName();
});

Promise转Observable

起初为了用async await编码以及利用链式调用,很多异步操作封装成了Promise,Promise转Observable用from方法转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Observable, of, from } from 'rxjs';
import { map } from 'rxjs/operators';

getCurrentUserToken(): Observable<any> {
if (this.currentUser) {
return of(this.currentUser);
} else {
return from(this.ipcService.call('currentuser')).pipe(
map(arg => {
if (arg) {
return typeof arg === 'string' ? JSON.parse(arg) : arg;
} else {
return null;
}
})
);
}
}

Observable的链式调用

操作符

操作符实在太多了

实现一个乘10的operator

1
2
3
4
5
6
7
8
9
function multiplyByTen(input: Observable<any>): Observable<any> {
return Rx.Observable.create(observer => {
input.subscribe({
next: (v) => observer.next(10 * v),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
}

创建操作符 of from interval等:

from

转化Promise对象、类数组对象、迭代器对象转化为 Observables
将数组转化为 Observable

1
2
3
4
5
6
var array = [10, 20, 30];
var result = Rx.Observable.from(array);
result.subscribe(x => console.log(x));

// 结果如下:
// 10 20 30

将一个无限的迭代器(来自于 generator)转化为 Observable。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* generateDoubles(seed) {
var i = seed;
while (true) {
yield i;
i = 2 * i; // double it
}
}

var iterator = generateDoubles(3);
var result = Rx.Observable.from(iterator).take(10);
result.subscribe(x => console.log(x));

// Results in the following:
// 3 6 12 24 48 96 192 384 768 1536

转化操作符 map mapTo merge mergeMap等:

map类似于Array.prototype.map投射函数应用于每个值;mapTo相当于忽略实际订阅接受结果,替换为指定值;

merge将多个订阅捋直成一个订阅;mergeMap将投射函数应用于每个值,并将多个订阅捋直(啥?不懂)

take
filter
tap

Perform a side effect for every emission on the source Observable, but return an Observable that is identical to the source.对源可观察对象的每个‘发射’应用一个副作用,但仍然返回与源相同的可观察对象

1
2
3
4
tap<T>(nextOrObserver?: NextObserver<T> | ErrorObserver<T> | CompletionObserver<T> | (
(x: T) => void),
error?: (e: any) => void,
complete?: () => void): MonoTypeOperatorFunction<T>

参数可以是可观察对象或回调方法
常见于附上一个log操作
1
2
3
4
5
6
7
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(heroes => this.log(`fetched heroes`)),
catchError(this.handleError('getHeroes'))
) as Observable<Hero[]>;
}

BehaviorSubject

Subject 的作用是实现 Observable 的多播。由于其 Observable execution 是在多个订阅者之间共享的,所以它可以确保每个订阅者接收到的数据绝对相等。不仅使用 Subject 可以实现多播,RxJS 还提供了一些 Subject 的变体以应对不同场景,那就是:BehaviorSubject、ReplaySubject 以及 AsyncSubject。

BehaviorSubject 的特性就是它会存储“当前”的值。这意味着你始终可以直接拿到 BehaviorSubject 最后一次发出的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const subject = new Rx.BehaviorSubject(Math.random());

// 订阅者 A
subject.subscribe((data) => {
console.log('Subscriber A:', data);
});

subject.next(Math.random());

// 订阅者 B
subject.subscribe((data) => {
console.log('Subscriber B:', data);
});

subject.next(Math.random());

toPromise deprecated

Rxjs v8版本后toPromise方法弃用, 原因基于Observable与Promise前后返回值不一致的issue
RxJS heads up: toPromise is being deprecated

曾常使用的

1
2
3
4
5
public async loadCategories() {
this.categories = await this.inventoryService
.getCategories()
.toPromise()
}

变更为
1
2
3
4
5
6
import { lastValueFrom } from 'rxjs';
...
public async loadCategories() {
const categories$ = this.inventoryService.getCategories();
this.categories = await lastValueFrom(categories$);
}

rxjs 6中被废弃的toPromise

The lastValueFrom is almost exactly the same as toPromise() meaning that it will resolve with the last value that has arrived when the Observable completes, but with the difference in behavior when Observable completes without emitting a single value. When Observable completes without emitting, toPromise() will successfully resolve with undefined (thus the return type change), while the lastValueFrom will reject with the EmptyError. Thus, the return type of the lastValueFrom is Promise, just like toPromise() had in RxJS 6.lastValueFrom几乎与toPromise() 一个意思,在Observable complete的时候,lastValueFrom会带着Observable最后一个产生的值resolve,但是不同之处在于Observable不带值complete的情况下。
当Observable不产生值并且complete时,toPromise方法会成功resolve为undefined(也就是返回类型改变了,不再是T),但是lastValueFrom会带着EmptyErrorreject。因此,就如同在RxJS 6里一样,它的返回类型是Promise

However, you might want to take the first value as it arrives without waiting an Observable to complete, thus you can use firstValueFrom. The firstValueFrom will resolve a Promise with the first value that was emitted from the Observable and will immediately unsubscribe to retain resources. The firstValueFrom will also reject with an EmptyError if the Observable completes with no values emitted.然而,你可能不等Observable complete,想要使用其第一个生产的值,因此可以用firstValueFrom。firstValueFrom会将Observable第一个生产的值resolve并且立刻unsubscribe保存该值。firstValueFrom也会在Observable没有值产生而complete的情况下带着 EmptyErrorreject。

风险一 用户数据库泄露导致用户密码泄露

风险二 网站管理人员、数据库管理员获取用户密码

如何安全的存储密码
要点如下

  • 加密(对称加密)存储无法防止风险二
  • 通用的以hash(单向散列算法如md5)获取的 digest(摘要)无法还原完整的原数据,但是存在rainbow table的隐患

rainbow table: 多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码(QQs理解的是,这里的密码或许与用户密码并不完全一致,但是可以通过网站的摘要核对)。这个摘要组合也被称为rainbow table。在当今计算机处理能力下,这种破译方式是可行而且可取的。

在线彩虹表

明文 摘要(32bit MD5)
111111 96e79218965eb72c92a549dd5a330112
123456 E10ADC3949BA59ABBE56E057F20F883E
000000 670b14728ad9902aecba32e22fa4f6bd
  • Salted Hash 改进的单项hash算法,明文密码混入“随机因素“,然后进行单向哈希后存储,其结果是rainbow table因salt不同而不同

    QQs按:做到这个份上,应该足够安全了,然而据说随着显卡并行计算能力的发展,存在破解出rainbow table的可能,我想可能是暴力破解salt。因此有了Salted Hash的各种改进方案

  • 慢哈希 如bcrypt,迭代执行多次hash运算,现实是增加了计算时间,使得暴力破解实际不可行

如设置 bcrypt 计算一次需要 0.5 秒,遍历 6 位的简单密码,需要的时间为:((26 * 2 + 10)^6) / 2 秒,约 900 年。

bcrypt

文章略老,基于以上原理,单向hash算法有了更多的发展

docker build

Dockerfile是构建docker的脚本,docker Hub很多应用容器以Dockerfile方式分享。

1
docker build -t tomcat8.5.51 .

命令格式 docker build [option] [url]

  • 选项-t tomcat8.5.51将build完成的镜像命名为tomcat8.5.51
  • . 是当前目录,即执行当前目录下的Dockerfile

执行完成后可在 docker images中查到tomcat8.5.51

docker compose

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」

我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如,要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

安装docker-compose

1
2
3
4
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

菜鸟教程

使用docker为某项开发封装开发环境(存目)

Node.js 官方文档 C++ Addon

基于Node.js c++ Addons机制对接动态链接库,以实现现有Node.js接口外的底层访问

“Chromium和Node.js大部分的代码都是用c++实现的,所以理所当然地也可以用C++为它们开发插件。” ——— <给electron做c开发的那些坑>

node-gyp

GYP is short for ‘Generate Your Projects’,顾名思义,GYP工具用于生成在相应平台上的项目,如在windows平台上生成Visual Studio解决方案(.sln), Mac下则是XCode项目配置以及Scons工具。

node-gyp is a cross-platform command-line tool written in Node.js for compiling native addon modules for Node.js. It contains a fork of the gyp project that was previously used by the Chromium team, extended to support the development of Node.js native addons.

node-gyp 用于编译nodejs原生addon模块的,跨平台的命令行工具,fork了Chromium team使用的gyp项目,该项目用户开发Node.js C++插件

1
2
npm install -g node-gyp
npm install --global --production windows-build-tools

Caution!安装过程中出现 issue#147 Hangs on Python is already installed, not installing again. 原因是VisualStudio有进程占用了相关工具链,结束VS进程并重新安装windows-build-tools即可

当前版本node-gyp使用 vs2017 build tools而不支持2019版本,下文中记述了workaround

python依赖

支持v2.7, v3.5, v3.6, or v3.7 v3.8 (QQs已实践2.7,3.8)

如果安装了多个版本 应使用下述命令注明python路径

1
node-gyp <command> --python /path/to/executable/python

抑或修改npm调用设置
1
npm config set python /path/to/executable/python

否则会调用默认版本(环境变量Path中指向的版本)

binding.gyp

1
2
3
4
5
6
7
8
{
"targets": [
{
"target_name": "hello",
"sources": [ "src/hello.cc" ]
}
]
}

关于gyp配置,但凡要比hello world走的远,都需要阅读下列文档

node-pre-gyp

node-pre-gyp makes it easy to publish and install Node.js C++ addons from binaries.

node-pre-gyp stands between npm and node-gyp and offers a cross-platform method of binary deployment.

Features :

  • 使用node-pre-gyp命令行工具安装依赖二进制C++模块(install your package’s C++ module from a binary)
  • 使用node-pre-gyp模块动态引入js模块 require(‘node-pre-gyp’).find
  • 其他开发命令如 test, publish (详见—help)

配置package.json

  • 依赖 + node-pre-gyp
  • 开发依赖 + aws-sdk
  • install命令脚本 node-pre-gyp install —fallback-to-build
  • 声明需要的二进制模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "dependencies"  : {
    "node-pre-gyp": "0.6.x"
    },
    "devDependencies": {
    "aws-sdk": "2.x"
    }
    "scripts": {
    "install": "node-pre-gyp install --fallback-to-build"
    },
    "binary": {
    "module_name": "your_module",
    "module_path": "./lib/binding/",
    "host": "https://your_module.s3-us-west-1.amazonaws.com"
    }
    这就是为什么英国人的项目使用npm install —build-from-source来打包addon的原理

导学列表:

  • 编译一个hello QQs C++ Addon的实践
  • 可以交互的manager插件
  • 为什么CSActivation可以用 npm install —build-from-source 打包插件

参考 Electron-利用DLL实现不可能

编译方法

官方example

  • nan: C++-based abstraction between Node and direct V8 APIs.
  • napi: C-based API guaranteeing ABI stability across different node versions as well as JavaScript engines.
  • node-addon-api: header-only C++ wrapper classes which simplify the use of the C-based N-API.

文件结构

hello.cc

binding.gyp

package.json

编译后的二进制插件的文件扩展名是 .node

所有的 Node.js 插件必须导出一个如下模式的初始化函数:

1
2
void Initialize(Local<Object> exports);
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

使用 node-gyp 构建插件时,使用宏 NODE_GYP_MODULE_NAME 作为 NODE_MODULE() 的第一个参数将确保会将最终二进制文件的名称传给 NODE_MODULE()。

1
node-gyp configure

为当前平台生成相应的项目构建文件。 这会在 build/ 目录下生成一个 Makefile 文件(Unix 平台)或 vcxproj 文件(Windows平台)。

1
node-gyp build

生成编译后的 *.node 的文件。 它会被放进 build/Release/ 目录。

关于VS2019 找不到C++ build tool的问题

私以为比较好的workaround是创建一个用2017替代2019的shim,详见node-gyp issue#1663

经实践可行

关于rebuild ffi error的问题

electron-rebuild issue#308

需求:使用Crypt32加密(编码/解码)

1
npm i ffi-napi ref ref-struct

后面两个包是映射C语言类型的接口

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
const fs = require("fs");
const ref = require("ref");
const ffi = require("ffi-napi");
const Struct = require("ref-struct");

const DATA_BLOB = Struct({
cbData: ref.types.uint32,
pbData: ref.refType(ref.types.byte)
});
const PDATA_BLOB = new ref.refType(DATA_BLOB);
const Crypto = new ffi.Library('Crypt32', {
"CryptUnprotectData": ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]],
"CryptProtectData" : ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]]
});

function encrypt(plaintext) {
let buf = Buffer.from(plaintext, 'utf16le');
let dataBlobInput = new DATA_BLOB();
dataBlobInput.pbData = buf;
dataBlobInput.cbData = buf.length;
let dataBlobOutput = ref.alloc(DATA_BLOB);
let result = Crypto.CryptProtectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput);
let outputDeref = dataBlobOutput.deref();
let ciphertext = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0);
return ciphertext.toString('base64');
};

function decrypt(ciphertext) {
let buf = Buffer.from(ciphertext, 'base64');
let dataBlobInput = new DATA_BLOB();
dataBlobInput.pbData = buf;
dataBlobInput.cbData = buf.length;
let dataBlobOutput = ref.alloc(DATA_BLOB);
let result = Crypto.CryptUnprotectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput);
let outputDeref = dataBlobOutput.deref();
let plaintext = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0);
return plaintext.toString('utf16le');
};

let text = "有死之荣,无生之耻";
let ciphertext = encrypt(text);
let plaintext = decrypt(ciphertext);

console.log("text:", text);
console.log("ciphertext:", ciphertext);
console.log("plaintext:", plaintext);

以上代码白嫖自Github node-ffi Issue#355 comments from Wackerberg 侵删

ElementUI, a Vue 2.0 based component library

Caution! 限定的引入顺序:

UI.css —> template —> vue.js —> UI.js —> vue controller

其中 template 和 vue.js 交换没有影响

响应式栅格布局

基础栅格,基于24划分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<el-row>
<el-col :span="6">
<div class="grid-content bg-purple"></div>
</el-col>
<el-col :span="6">
<div class="grid-content bg-purple-light"></div>
</el-col>
<el-col :span="6">
<div class="grid-content bg-purple"></div>
</el-col>
<el-col :span="6">
<div class="grid-content bg-purple-light"></div>
</el-col>
</el-row>

间隔( :gutter )

栅格之间的水平间距
1
2
3
<el-row :gutter="20">
...
</el-col>

偏移( :offset )
1
<el-col :span="6" :offset="6"><div class="grid-content"></div></el-col>

弹性盒子( flex )
1
2
3
<el-row type="flex" justify="center" align="center">
...
</el-row>

水平值:start, center, end, space-between, space-around

垂直值:top, middle, bottom

响应式( Responsive )

1
2
3
<el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11">
<div class="grid-content"></div>
</el-col>

隐藏

  • hidden-xs-only - 当视口在 xs 尺寸时隐藏
  • hidden-sm-only - 当视口在 sm 尺寸时隐藏
  • hidden-sm-and-down - 当视口在 sm 及以下尺寸时隐藏
  • hidden-sm-and-up - 当视口在 sm 及以上尺寸时隐藏

    Form表单

    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
    <div id="app">
    <el-form ref="form" :model="formData" :rules="formValidators" label-width="80px">
    <el-form-item label="title" prop="title">
    <el-input v-model="formData.title"></el-input>
    </el-form-item>
    <el-form-item label="Date">
    <el-col :span="11">
    <el-date-picker type="date" placeholder="Pick a date" v-model="formData.date1" style="width: 100%;">
    </el-date-picker>
    </el-col>
    <el-col class="line" :span="2">-</el-col>
    <el-col :span="11">
    <el-time-picker placeholder="Pick a time" v-model="formData.date2" style="width: 100%;">
    </el-time-picker>
    </el-col>
    </el-form-item>
    <el-form-item label="Subscribe">
    <el-switch v-model="formData.isSubscribe"></el-switch>
    </el-form-item>
    <el-form-item label="TAG">
    <el-checkbox-group v-model="formData.tags">
    <el-checkbox label="Activity" name="type"></el-checkbox>
    <el-checkbox label="Bonus" name="type"></el-checkbox>
    <el-checkbox label="Online" name="type"></el-checkbox>
    </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Comments">
    <el-input type="textarea" v-model="formData.Comments"></el-input>
    </el-form-item>
    <el-form-item>
    <el-button type="primary" @click="onSubmit">Create</el-button>
    <el-button>Cancel</el-button>
    </el-form-item>
    </el-form>
    </div>
    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
    var Main = {
    data() {
    return {
    formData: {
    title: '',
    date1: '',
    date2: '',
    isSubscribe: false,
    tags: [],
    Comments: ''
    },
    formValidators: {
    title: [{
    required: true,
    message: '请输入标题',
    trigger: 'blur'
    },
    {
    min: 3,
    max: 5,
    message: '长度在 3 到 5 个字符',
    trigger: 'blur'
    }
    ]
    }
    }
    },
    methods: {
    onSubmit(event) {
    this.$refs[formName].validate((valid) => {
    if (valid) {
    console.log(event);
    console.log(this.formData)
    } else {
    console.log('validate fail');
    return false;
    }
    });

    }
    }
    }
    var Ctor = Vue.extend(Main)
    new Ctor().$mount('#app')
    以上几乎就是官网的example,官方文档未给出submit事件触发form action的表单提交方式,api文档中未提及action属性。有资料表示存在“ this.$refs.formName.submit ”(QQs未验证)

    这种方式也造成了做表单校验时,要在提交方法中调用form.validate,form.validate又是个异步操作,后续动作(一般是http post)要放在其回调函数中

    而在我大Angular的响应式表单中,ngSubmit监听FormGroup当前值,在校验不通过时将禁用type=”submit”提交按钮

如上例所示,表单验证需添加表单的:rules属性,表单项的prop属性(注意在label,值同name),提交方法中调用form.validate

Table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column fixed prop="date" label="日期" width="150">
</el-table-column>
<el-table-column prop="name" label="姓名" width="120">
</el-table-column>
<el-table-column prop="address" label="地址" width="300">
</el-table-column>
<el-table-column prop="zip" label="邮编" width="120">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</el-table-column>
</el-table>
</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
var Main = {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-04',
name: '王大虎',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
}]
}
},
methods: {
handleClick(row) {
console.log(row);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

比较有意思的是这个el-table-column label可以设置 多级表头

模板与绑定

1
2
3
4
5
6
<span>Message: {{ msg }}</span>
<span v-html="rawHtml"></span>
<div v-bind:id="dynamicId"></div>
<p v-if="seen">现在你看到我了</p>
<a v-on:click="doSomething">...</a>
<li v-for="item in items">...</li>

@click is short for v-on:click

方法的括号()不是必须的

支持动态属性名和事件名称

1
2
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>

Vue实例

1
2
3
4
5
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})

当一个 Vue 实例被创建时,它将 data 对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

常用实例方法:

1
2
3
4
5
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})

生命周期钩子
生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
mounted: function () {
console.log('a is: ' + this.a)
}
beforeDestroy:function(){

}
}

内联处理器方法
1
2
3
4
5
6
7
new Vue({
data: {

},
methods:{
// TODO functions
}

官网

爬行起步方式

Caution! 限定的引入顺序:
template —> vue.js —> vue controller
其中 template 和 vue.js 交换没有影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
<meta charset="UTF-8">
<title>Hi vue</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
message: 'here here'
}
})
</script>
</body>
</html>

常规起步方式

1
2
3
npm install -g @vue/cli

vue create vueproj

阅读 vue-cli指引

原力起飞方式

webpack 打包 vue 基础

RazorEngine: Open source templating engine based on Microsoft’s Razor parsing engine.

Razor文件类型

Razor可以在vb.net和C#中使用。分别对应了两种文件类型,.vbhtml和.cshtml

Razor的标识符

@字符被定义为Razor服务器代码块的标识符,表示是服务器代码。web form中使用<%%>中写服务器代码一个道理。

1
2
3
4
5
@{
string userName= "邓星林";
} <!-- 块级作用域 -->
<span>@userName</span>
<span>@DateTime.Now.ToString("yyyy-MM-hh")</span>

HTML

@Href(“~/“)//表示网站的根目录

@Html.Raw(Module.Content) 输出HTML

Razor标识混合HTML

1
2
3
4
5
6
7
8
9
@{
// html标签
<div></div>
var str = "abc";
// 下面会输出:this is a mail:dxl0321@qq.com, this is var: abc,this is mail@str,this is @;
@: this is a mail:dxl0321@qq.com, this is var: @str,this is mail@str,this is @@;
// 下面输出abc
@str
}

a.在作用域内如果是以html标签开始则视为文本输出

b.如果要输出符号@,则使用@@

c.如果要输出非html标签和非Razor语句的代码,则用@:,他的作用是相当于在处于html下面编写一样了,如在@:后面可以加上@就是表示Razor语句的变量

注释

1
2
3
4
5
6
7
@{
@*
多行注释
多行注释
*@
var i = 10; @* asdfasf *@
}

Layout 视图片段

插入指定路径定义的布局视图模板

1
2
3
4
5
6
7
@{
ViewData["Title"] = "分享优惠券管理";
Layout = "~/Views/Shared/_Layoutv2.cshtml";
}
<div class="main">
...
</div>

插入Page

1
2
3
<div class="area">
@RenderPage("/b.cshtml")
</div>

Section

Helper

helper就是可以定义可重复使用的帮助器方法,不仅可以在同一个页面不同地方使用,还可以在不同的页面使用。

1
2
3
4
5
6
7
8
9
10
@helper sum(int a,int b)
{
var result=a+b;
  @result

}
<div >
<p>2+3=@sum(2,3)</p>
<p>5+9=@sum(5,9)</p>
</div>

自带类型转换

  • AsInt()
  • IsInt()
  • AsBool()
  • IsBool()
  • AsFloat()
  • IsFloat()
  • AsDecimal()
  • IsDecimal()
  • AsDateTime()
  • IsDateTime()
  • ToString()