Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(下篇——多页面VueSSR+热更新Server)

@(HTML/JS)

这是Vue多页面框架系列文章的第二篇,上一篇(纯前端Vue多页面)中,我们尝试从webpack-simple原型项目改造为一个多页面的Vue项目。而这里,我们继续往前,尝试把Vue多页面改造为Nodejs直出。由于步骤较多,所以本文片幅较长。

本文源代码:https://github.com/kenkozheng/HTML5_research/tree/master/Vue-SSR-Multipages-Webpack3

1 认识原理

稍微详细的信息,大家可以参考官网:https://ssr.vuejs.org/zh/

还有官方的例子:https://github.com/vuejs/vue-hackernews-2.0

不过,文档写得并不详细,也没看到文档对应的代码在哪里;而例子呢,下载后无法运行(2017年12月上旬),也是有点麻烦。

我总结一下大概的运行步骤:

  • Nodejs运行vue组件输出html片段:这一步,可以理解为虚拟dom运行在Nodejs环境,换算出html的字符串,很好理解。
  • Nodejs把html片段拼接到整个HTML上:这里跟客户端版本略有不同,上一篇文章中,我们针对多页面生成了多个html,而这里因为有了Nodejs的动态输出能力,就没必要生成多个html了,只需要每次把动态部分拼接到模版html上即可。
  • 对HTML注入数据:上一步有了HTML,但这个html只是死的字符串,到了浏览器解析后只能是普通的dom,无法启动vue还原为虚拟dom。那么就需要原始的数据,好让客户端重建对应的虚拟dom。
  • 浏览器运行vue重建虚拟dom:这一步跟之前纯前端的vue架构类似,不同的是,vue会识别到div已经是服务器渲染好的,并不需要重新渲染dom结构,只需要重建虚拟dom,备好数据,绑定事件即可。

那么从已有的多页面Vue框架出发,要做成多页面nodejs直出,我们需要解决几个问题。

  • 1、怎么打包为Nodejs支持的js?
  • 2、在这个情况下,客户端部分是否要特殊打包?怎么打包?
  • 3、使用什么方式运行打包后的两部分代码,并生成最终的HTML?
  • 4、怎么注入数据?客户端又怎么获取数据作用于Vue?
  • 5、如何启动项目?热更新还能有效吗?

    接下来就带着这几个问题,学习官方资料,看如何实现Vue的SSR。

2 Nodejs和浏览器分别打包

从之前的纯浏览器运行建模+渲染,到现在拆分两个过程:Nodejs输出结构、浏览器端重建虚拟dom和绑定事件,这里必然需要修改已有的webpack打包配置。

官方提供了vue-server-renderer组件。

这个组件分为client-pluginserver-plugin,分别用于客户端和Nodejs部分的打包。针对这个情况,我们需要把webpack文件修改一下,把基础部分抽离出来,把多余部分去除(例如生成html的HtmlWebpackPlugin)。


简单看看webpack.base.config.js

var path = require('path');
var webpack = require('webpack'); module.exports = {
output: {
path: path.resolve(__dirname, `../dist/`),
publicPath: '/dist/', //发布后在线访问的url
filename: `[name].[hash:8].js` //'[name].[chunkhash].js', '[name].[hash:8].js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]' //自动hash命名图片等资源,并修改路径。路径需要根据项目实际情况确定。语法参考:https://doc.webpack-china.org/loaders/file-loader/
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}; if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
//sourceMap: true, //开启max_line_len后会有报错,二选一
compress: {
warnings: false,
drop_debugger: true,
drop_console: true,
pure_funcs: ['alert'] //去除相应的函数
},
output: {
max_line_len: 100
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
]);
}

跟webpack-simple原型项目的配置没什么差异。主要是去掉了entry的配置,因为针对nodejs和客户端将有新的入口文件。


然后,看看Nodejs端怎么处理。

首先,需要新建一个新的app和entry文件。

app.js

import Vue from 'vue'
import App from './App.vue'
// import '../../css/base.css' //要写到vue文件中 // 从客户端渲染改为SSR
// new Vue({
// el: '#app',
// render: h => h(App)
// }) // 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
const app = new Vue({
// 根实例简单的渲染应用程序组件。
render: h => h(App)
})
return { app }
}

原来客户端渲染是直接new Vue(),而这里改为export一个工厂方法,好让后续服务器和客户端分别用各自的方式创建。这里有个题外话,import css不能写在这了,会导致nodejs运行时缺少document对象而报错,需要写到vue文件中。

然后是server-entry.js

import { createApp } from './app'
export default context => {
const { app } = createApp()
return app
}

就是简单创建Vue实例,然后返回。这个函数接受context参数,是vue-server-renderer传入的,往context中塞数据,可以作用于最终生成的HTML,例如注入数据,这个稍后再说明。

接着再看webpack的配置。

const webpack = require('webpack')
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') module.exports = merge(base, {
target: 'node',
devtool: '#source-map',
entry: './web/pages/page1/entry-server.js',
output: {
filename: `[name].[hash:8].js`,
libraryTarget: 'commonjs2'
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.VUE_ENV': '"server"'
}),
new VueSSRServerPlugin()
]
})

配置不多,利用webpack-merge工具,便于合并前后两份配置。

有几个关键点:

  • target: 'node'。这个让webpack针对nodejs的module做处理。
  • output的libraryTarget:设置module的具体引用方式。
  • plugins中加入VueSSRServerPlugin:这个插件会让文件最后打包为一个json,用于后续运行时读入到Vue的vue-server-renderer中

再看看客户端的修改。

client-entry.js

import { createApp } from './app'
// 客户端特定引导逻辑……
const { app } = createApp()
// 这里假定 App.vue 模板中根元素具有 `id="app"`(服务器渲染后就有这个id)
app.$mount('#app')

跟服务器的略有不同,这个是针对浏览器运行的代码,创建Vue实例后,就手工挂载到已存在的节点#app上。

webpack的配置也要相应处理:

const webpack = require('webpack')
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') const config = merge(base, {
entry: {
app: `./web/pages/page1/entry-client.js`
},
output: {
filename: '[name].[chunkhash:8].js'
},
plugins: [
// strip dev-only code in Vue source
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.VUE_ENV': '"client"'
}),
// extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// a module is extracted into the vendor chunk if...
return (
// it's inside node_modules
/node_modules/.test(module.context) &&
// and not a CSS file (due to extract-text-webpack-plugin limitation)
!/\.css$/.test(module.request)
)
}
}),
// extract webpack runtime & manifest to avoid vendor chunk hash changing
// on every build.
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new VueSSRClientPlugin()
]
}) module.exports = config

这里做了几个关键事情:

  • entry指向客户端打包入口
  • 利用chunkPlugin生成vendor.js,抽离部分库文件
  • 生成manifest文件,记录文件名
  • VueSSRClientPlugin,这个插件生成vue-ssr-client-manifest.json,记录页面所有依赖文件列表,在生成最终HTML时方便注入相应的js链接和css链接。

3 服务器运行

Nodejs端,我们需要引入vue-server-renderer

主要代码如下:

const { createBundleRenderer } = require('vue-server-renderer');
const createRenderer = (bundle, options) => createBundleRenderer(bundle, Object.assign(options, {
// for component caching
cache: LRU({
max: 1000,
maxAge: 1000 * 60 * 15
}),
// recommended for performance
runInNewContext: false
})); const templatePath = resolve('../web/tpl.html');
const template = fs.readFileSync(templatePath, 'utf-8')
const bundle = require('../dist/vue-ssr-server-bundle.json')
const clientManifest = require('../dist/vue-ssr-client-manifest.json')
let renderer = createRenderer(bundle, {
template,
clientManifest
}); let render = (req, res) => {
//context是一个对象,在模版中,使用<title>{{ title }}</title>方式填充 https://ssr.vuejs.org/zh/basic.html
let context = {title: 'VueSSR Multipages'};
renderer.renderToString(context, (err, html) => {
if (err) {
console.log(err);
res.status(500).end('Internal Server Error');
return
}
res.send(html);
res.end();
});
};

详细代码请查github:

https://github.com/kenkozheng/HTML5_research/blob/master/Vue-SSR-Single-Page-Webpack3/server/server.js

上述代码做的是大概是:

1、读入模版html文件、打包后的两个json,从而生成bundleRenderer

2、创建render函数,接受req和res(例如用于express),使用renderToString方法,简单把整个网页拼装好返回。其中context是作用于模版html的参数对象,用法跟普通的模版引擎类似。例如填充title:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
</head>
<body>
<!--The template should contain a comment <!--vue-ssr-outlet- -> which serves as the placeholder for rendered app content.-->
<!--vue-ssr-outlet-->
</body>
</html>

顺带说一句,HTML中需要有特殊标记<!--vue-ssr-outlet-->,用于替换为动态的Vue html片段。

vue-server-renderer会自动向模版填充js和css的外链。这个是默认的行为,如果想要把各种js和css做特殊处理,或输出更多内容,可以参考手工注入:

https://ssr.vuejs.org/zh/build-config.html#manual-asset-injection

如果想更进一步,例如css、js打入html中,还可以抛弃template(createRenderer时不传入template),改为自行拼接html,只需要renderer返回vue的html片段。


至此,粗略的SSR就已经完成了。

project.json中加入

  "scripts": {
"start": "cross-env NODE_ENV=production node server/server",
"build": "rimraf dist && npm run build:client && npm run build:server",
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js --progress --hide-modules",
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js --progress --hide-modules"
},

npm run build,然后npm start就可以了。

跟上一篇文章完成的架构不一样,这里不通过webpack-dev-server启动,所以没有热更新的功能。对于实际开发而言,每次修改都要build再run,肯定太麻烦。

4 搭建热更新功能

这里,借鉴了官方例子,可以简单copy setup-dev-server.js

setup-dev-server.js的代码比较长,就不列出来了。github:https://github.com/kenkozheng/HTML5_research/blob/master/Vue-SSR-Single-Page-Webpack3/build/setup-dev-server.js

实现原理跟webpack-dev-server是相同的,基于express的服务。做的主要是:

  • 引入webpack-hot-middlewarewebpack-dev-middleware,建立客户端和服务器之间热更新websocket,另外把临时文件生成到内存中
  • 使用webpack和chokidar,监控vue、js、html等的变化
  • 实现了异步的编译回调和不断的监控

我们自己主要需要修改server.js,判断是否开发环境。如果是,则使用dev-server特殊的renderer。

const devServerSetup = require('../build/setup-dev-server');
let renderer;
var promise = devServerSetup(server, templatePath, (bundle, options) => {
renderer = createRenderer(bundle, options); //刷新renderer
});
render = (req, res) => {
promise.then(() => baseRender(renderer, req, res)); //需要等待文件初始化
};

devServerSetup每次callback都返回最新的bundle和clientManifest,用于刷新renderer。

那么,使用node server/server就能启动热更新服务器了。

到这里,我们实现了一个没有动态数据的SSR版本,方便初学者对整个概念的理解。代码在:https://github.com/kenkozheng/HTML5_research/tree/master/Vue-SSR-Single-Page-Webpack3

5 数据注入

接下来,我们在已有基础上,再实现动态数据。这里列出我认为比较简单易懂的两种方式和相应例子,可能实现的方式有更多。

情况1:不使用Vuex

先考虑没有Vuex的情况,只是简单粗暴的组件式从上往下传递数据。这个情况适合一些简单页面,纯粹的展示信息和几个简单的点击处理。

各个文件,我们都稍作修改。

app.vue

<script>
export default {
name: 'app2',
props: ['appData'],
methods: {
}
}
</script>

vue的写法从原来固定data,改为从父节点传入的props标签(appData)获取数据。

app.js

export function createApp (data) {
const app = new Vue({
components: {App}, //演示如何从初始化地方传递数据给子组件。这个页面不使用vuex,展示简单粗暴的方式,配合global event bus即可https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
template: '<App :appData="appData"/>',
data: {
//数据先在服务器渲染一遍,到了客户端会在重建一遍,如果客户端部分数据不一致,会重新渲染
appData: data
},
mounted : function () {
console.log('mounted')
}
});
return { app };
}

entry-server.js

import { createApp } from './app'

export default context => {
return new Promise((resolve, reject) => {
setTimeout(() => { //模拟拉取接口获取数据
var data = {
msg: 'page1 data'
};
context.state = data; //生成到tpl.html中作为浏览器端全局变量
const { app } = createApp(data);
resolve(app);
}, 100);
//reject({code: 500}); //对应server.js的baseRender方法
})
}

server除了像之前那样直接返回app还可以返回promise对象,从而实现异步处理。关键点是把data赋值给context.state。state会被自动注入到html中,作为全局js变量__INITIAL_STATE__

entry-client.js

import { createApp } from './app'
const { app } = createApp(__INITIAL_STATE__)
app.$mount('#app')

最后在client的代码中,拿到这个全局对象,并赋值给Vue。。。完成。。。

情况2:使用Vuex

这里建了一个例子,模拟初始化时获取数据,然后再返回给Server去渲染。

先建立一个Store

import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex); import { getData } from 'page2Data' //一个别名,不指向具体的js,需要在webpack配置中alias指定,目的是让浏览器端和nodejs端引入不同的文件,实现不同的获取方式 export function createStore () {
return new Vuex.Store({
//state就是数据
state: {
msg: 'default'
},
//通过事件触发action的函数,而不是直接调用
actions: {
//vue文件中调用getData时,传入id。commit是vuex内部方法
getData ({ commit }, id) {
return getData(id).then(data => {
commit('setMsg', data.msg) //调用mutations的方法
})
},
setData ({ commit }, msg) {
commit('setMsg', msg) //调用mutations的方法
},
},
//mutations做所有数据的修改
mutations: {
setMsg (state, msg) {
state.msg = msg;
}
}
})
}

上述代码使用了page2Data别名,利用webpack的alias功能,可以快速实现一份代码,同时对接浏览器和服务器不同的数据获取方式。这也许就是“同构”的一种思路吧,有利于客户端做一些刷新逻辑时,不需要整个页面重载。

app.vue

<script>
export default {
name: 'app',
methods: {
change (event) {
this.$store.dispatch('setData', 'hello click');
}
},
/**
* 动态类型数据
*/
computed: {
msg () {
return this.$store.state.msg
}
}
}
</script>

app.js

import Vue from 'vue'
import App from './App.vue'
import {createStore} from './store.js' export function createApp () {
const store = createStore();
const app = new Vue({
store,
// 根实例简单的渲染应用程序组件。
render: h => h(App)
});
return { app, store }
}

Vue使用store,而不是组件式的传递数据。

entry-server.js

export default context => {
return new Promise((resolve, reject) => {
setTimeout(() => { //模拟拉取接口获取数据
const {app, store} = createApp();
// 调用store actions的方法
store.dispatch('getData', 'page2').then(() => {
context.state = store.state; //生成到tpl.html中作为浏览器端全局变量
resolve(app);
}).catch(reject);
}, 100);
})
}

初始化时,调用store的方法,获得数据后再返回渲染。跟不用Vuex类似,数据也是塞到context.state中。

entry-client.js

// 客户端特定引导逻辑……
const { app, store } = createApp();
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
} // 这里假定 App.vue 模板中根元素具有 `id="app"`(服务器渲染后就有这个id)
app.$mount('#app')

客户端手工设置store的数据。

运行测试,可以发现两种方式都能正常完成页面渲染。

6 多页面并存

上边提到的例子都只针对一个页面,因为webpack后,生成的vue-ssr-client-manifest.json等都只有一份。我们需要做一些优化。

既然是多页面Nodejs,那肯定需要一个路由表。我们可以在路由表中配置访问url(express正则)和代码目录。例如:

router.js

module.exports = {
'page1': {
url: '/page1.html', //访问的url规则,用于express的get
dir: './web/pages/page1', //页面目录,默认有app.js作为入口
title: 'Page1', //生成html的title
template: './web/pages/page1/tpl.html' //特殊指定一个html
},
'page2': {
url: '/page2.html', //访问的url规则,用于express的get
dir: './web/pages/page2', //页面目录,默认有app.js作为入口
title: 'Page2' //生成html的title
}
}

然后根据每个页面,动态生成相应的webpack配置,用于build和dev-server。

const isProd = process.env.NODE_ENV === 'production';

let webpackConfigMap = {};
for (let pageName in router) {
let config = router[pageName];
let cConfig = merge({}, clientConfig, {
entry: {
[pageName]: `${config.dir}/entry-client.js` //buildEntryFiles生成的配置文件
},
output: {
filename: isProd ? `js/${pageName}/[name].[chunkhash:8].js` : `js/${pageName}/[name].js` //dist目录
},
plugins: [
new VueSSRClientPlugin({
filename: `server/${pageName}/vue-ssr-client-manifest.json`//dist目录
})
]
});
let sConfig = merge({}, serverConfig, {
entry: {
[pageName]: `${config.dir}/entry-server.js` //buildEntryFiles生成的配置文件
},
plugins: [
new VueSSRServerPlugin({
filename: `server/${pageName}/vue-ssr-server-bundle.json` //dist目录
})
]
});
webpackConfigMap[pageName] = {clientConfig: cConfig, serverConfig: sConfig};
}

这里关键点是动态设置entry和设置VueSSRClientPlugin/VueSSRServerPlugin的filename。

filename这个字段官方文档是没有的,不过,node_modules基本都能找到源码,可以发现有这个动态设置的办法。

通过上述配置,让浏览器使用的js和服务器打包后的json文件分开,便于设置访问权限,防止服务器信息泄漏。build之后的dist目录结构如下所示:

相应的,server.js中运行时和build的脚本都需要调整。

server.js

for (let pageName in router) {
let pageConfig = router[pageName];
server.get(pageConfig.url, ((pageName) => {
return (req, res) => {
render(pageName, req, res);
}
})(pageName));
}

server是express实例,设置路由时,创建闭包,每个处理器都能带上对应的pageKey,从而访问对应的renderer。

build.js

const appEntry = require('./multipageWebpackConfig');
const webpack = require('webpack'); console.log('building...');
for (var page in appEntry) {
webpack(appEntry[page].clientConfig, ()=>{});
webpack(appEntry[page].serverConfig, ()=>{});
}

build改为我们自建的js脚本。

至此,一个多页面VueSSR就完成了,后续可以根据项目的具体情况添加实际的Vue组件和插件。


文章写得干货不多,算是针对例子做的补充说明,间隙中提到了一些官方文档中没有说明的细节,希望对大家有所帮助。

Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(下篇——多页面VueSSR+热更新Server)的更多相关文章

  1. Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(上篇——纯前端多页面)

    Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(上篇--纯前端多页面) @(HTML/JS) 一般来说,使用vue做成单页应用比较好,但特殊情况下,需要使用多页面也有另外 ...

  2. windows10风格 springboot vue.js html 跨域 前后分离 activiti 整合项目框架源码

    官网:www.fhadmin.org 此项目为Springboot工作流版本 windows 风格,浏览器访问操作使用,非桌面应用程序. 1.代码生成器: [正反双向](单表.主表.明细表.树形表,快 ...

  3. 轻松理解webpack热更新原理

    一.前言 - webpack热更新 Hot Module Replacement,简称HMR,无需完全刷新整个页面的同时,更新模块.HMR的好处,在日常开发工作中体会颇深:节省宝贵的开发时间.提升开发 ...

  4. vue学习前奏——webpack

    "工欲善其事必先利其器",要想学习vue,首先需要我们去了解webpack,便于后期快速构建运行项目.废话不多说,下面开始介绍在开始一个vue项目前我们需要对webpack有一定的 ...

  5. Vue(四)之webpack和vue-cli

    01-webpack介绍 官方文档:https://www.webpackjs.com/concepts/ 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(modu ...

  6. RAP、Mock.js、Vue.js、Webpack

    最近做项目使用的是RAP1的接口,但是昨天开始,RAP1 出现了问题,接口都不能用了. 所以补充一下Mock.js的用法,以便在这种突发的情况时候时自己通过Mock的方式来处理接口. npm init ...

  7. 从零开始搭建Electron+Vue+Webpack项目框架,一套代码,同时构建客户端、web端(一)

    摘要:随着前端技术的飞速发展,越来越多的技术领域开始被前端工程师踏足.从NodeJs问世至今,各种前端工具脚手架.服务端框架层出不穷,“全栈工程师”对于前端开发者来说,再也不只是说说而已.在NodeJ ...

  8. vue+node.js+webpack开发微信公众号功能填坑——v -for循环

    页面整体框架实现,实现小功能,循环出数据,整体代码是上一篇 vue+node.js+webpack开发微信公众号功能填坑--组件按需引入 修改部门代码 app.vue <yd-flexbox&g ...

  9. Vue笔记:webpack项目vue启动流程

    VUE启动流程 1. package.json 在执行npm run dev的时候,会在当前目录中寻找 package.json 文件, 有点类似 Maven 的 pom.xml 文件,包含项目的名称 ...

随机推荐

  1. angular路由详解三(路由参数传递)

    我们经常用路由传递参数,路由主要有三种方式: 第一种:在查询参数中传递数据 {path:"address/:id"}   => address/1  => Activa ...

  2. MySQL多数据源笔记1-MySQL主从复制

    1.为什么要做主从复制? 1.在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出 ...

  3. 关于android studio 的高德配置

    1.获得key 进入控制台,创建一个新应用.在创建的应用上点击"添加新Key"按钮,在弹出的对话框中,依次:输入应用名名称,选择绑定的服务为"Android平台SDK&q ...

  4. VirboxLM许可管理平台,一站式软件保护解决方案

    安全,易用,灵活 轻松解决开发者软件版权保护难题 Virbox LM为企业提供安全易用的软件保护管理平台,实现高安全强度的软件防护,防止盗版及逆向工程.实现便捷.安全的软件授权,包括创建灵活的许可模式 ...

  5. READ TABLE 的用法

    SORT ITAB BY '你想比较的列'. " 排序以增加二分查找的速度 READ TABLE itab with key 'itab中某列' = ‘目标列' BINARY SEARCH. ...

  6. Java设计模式(六)Adapter适配器模式

    一.场景描述 “仪器数据采集器”包含采集数据以及发送数据给服务器两行为,则可定义“仪器数据采集器”接口,定义两方法“采集数据capture”和“发送数据sendData”. “PDF文件数据采集器”实 ...

  7. 关于脱离laravel框架使用Illuminate/Validation验证器

    1.关于Illuminate/Validation验证器 Validation 类用于验证数据以及获取错误消息. github地址:github.com/illuminate/validation 文 ...

  8. 误删 /user/bin目录后的补救

    当危险的动作发生, 误删 /user/bin目录后的补救 以下是昨天晚上真实的误操作现场,模拟记录一下 (这是测试环境,所以操作得很随意,有些执行动作很不规范) 在上面编译一个软件Dboop,完事以后 ...

  9. C++模板类与Qt信号槽混用

    一.正文 目前正在做一个视频处理相关的项目.项目的技术栈是这样的,UI层采用Qt来实现基本的数据展示和交互,底层音视频采用的是一套基于FFmpeg的视频处理框架.这是一套类似Microsoft Med ...

  10. 【Python】 用户图形界面GUI wxpython II 布局和事件

    wxpython - 布局和事件 这章主要记录布局器Sizer以及事件的用法. // 目前还需要记录的:Sizer的Add方法加空白,Sizer的Layout,Sizer的Remove如何有效 ■ 布 ...