SegmentFault yunsiyu最新的文章
2019-06-21T16:25:10+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
vue多环境配置方案
https://segmentfault.com/a/1190000019548127
2019-06-21T16:25:10+08:00
2019-06-21T16:25:10+08:00
yunsiyu
https://segmentfault.com/u/yunsiyu
1
<p>前端项目上线的时候 , 不可避免的会考虑到不同的运行环境需要前端请求不同服务地址的问题</p>
<hr>
<p><strong>方案一 :</strong> <br>一般的情况下 , 可以使用vue-cli-service环境变量进行分环境打包 ; <br>开发环境 , 继续使用proxy代理 ; <br>需要编译的环境 , 通过设置环境变量去控制打包过程 , 最终生成适用于不同环境的前端资源 ;<br>有关vue-cli-service环境变量的具体解释在另一篇文章中有详解 , 这里我们只关注实现<br>首先 , 我们需要添加各个环境的env配置文件 , 放在项目的<strong>根目录</strong>下</p>
<p><img src="/img/bVbubvO?w=190&h=103" alt="clipboard.png" title="clipboard.png"></p>
<p>我们以联调环境为例 , 添加了一个.env.build_dev 文件 <br><strong>.env.build_dev</strong></p>
<pre><code>NODE_ENV='production' //表明这是需要编译的环境(需要打包)
VUE_APP_CURRENTMODE='production' // 自定义的模式信息
VUE_APP_BASE_SERVER='http://****:8080' // 自定义的接口地址</code></pre>
<p>在接口管理api.js文件中</p>
<pre><code>import { fetchpost, fetchget, fetchput, fetchdelete } from "./config";
// 分环境打包
let baseUrl = '';
if (process.env.NODE_ENV == 'development') {
baseUrl = "/base"
} else if (process.env.NODE_ENV == 'production') {
baseUrl = process.env.VUE_APP_BASE_SERVER
} else {
baseUrl = ""
}
export default {
// 登录
login(params) {
return fetchpost(
`${baseUrl}/apis/v1/login`,
params
);
},
// 修改密码
setpsw(params) {
return fetchput(
`${baseUrl}/apis/v1/users/password`,
params
);
},
...........略</code></pre>
<p>接下来 , 我们需要在项目的package.json文件中为联调环境添加一个编译指令 "build_dev" , 并指定编译模式<br>package.json</p>
<pre><code>"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build_dev": "vue-cli-service build --mode build_dev",
"build_pre_release": "vue-cli-service build --mode build_pre_release",
"build_test": "vue-cli-service build --mode build_test",
"lint": "vue-cli-service lint"
},</code></pre>
<p>这样 , 我们通过不同的指令去编译对应环境的前端资源</p>
<hr>
<p><strong>方案二 :</strong><br>另一种方式, 是将前端请求的服务地址抽离出来 , 放进配置文件里面 , <br>开发环境 , 继续使用proxy代理 ; <br>需要编译的环境 , 可以直接修改编译后的文件中的配置文件<br>这样的好处如下: <br>1: 前端不需要重复进行打包编译 ;<br>2: 自动化部署的时候可以使用脚本去替换前端资源中的配置文件 ; <br>3: 如果采用前端Docker化 , 可以在制作镜像的过程中修改/替换配置文件 ; </p>
<p>首先 , 我们将config.json文件放置在public文件夹中(防止被webpack编译)</p>
<p><img src="/img/bVbubv6?w=232&h=89" alt="clipboard.png" title="clipboard.png"></p>
<p>在里面我们简单存放一个服务地址字段<br><strong>config.json</strong></p>
<pre><code>{
"SERVER_URL": ""
}</code></pre>
<p>接下来,需要保证项目开始加载前 , 我们已经获取到这个配置文件了 , 所以在main.js 里面 , 我们需要先获取这个配置文件 , 再实例化vue</p>
<p><strong>mian.js</strong></p>
<pre><code>import axios from 'axios'
// vue实例
function createdVue () {
return new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
}
if (process.env.NODE_ENV == 'development') {
// 放在public文件夹下的静态文件需要通过绝对路径去获取
axios.get('/config.json').then(res=>{
// 开发环境通过proxy代理的形式 , 不需要获取配置地址
Vue.prototype.SERVER_URL = ''
createdVue()
})
} else if (process.env.NODE_ENV == 'production') {
// 放在public文件夹下的静态文件需要通过绝对路径去获取
axios.get('/config.json').then(res=>{
// 将获取到的配置文件作为全局变量保存
Vue.prototype.SERVER_URL = res.SERVER_URL
// 成功获取到配置后再去创建vue实例
createdVue()
})
} </code></pre>
<p>一般项目中 , 我们都会对axios进行统一封装 , 为axios创建一个config.js 文件 , 里面对axios请求头 , baseUrl , 请求拦截 , 各种请求方式进行配置<br>在这里我们为每个请求添加baseUrl<br><strong>config.js</strong></p>
<pre><code>import axios from 'axios'
import Vue from "vue";
import qs from 'qs'
import Router from '@/router.js'
import {Message} from 'element-ui'
// axios.defaults.timeout = 10000 // 响应时间
// 获取全局变量中的服务地址
function getBaseUrl () {
return Vue.prototype.SERVER_URL
}
// 配置请求头
axios.defaults.headers.post['Content-Type'] = 'application/json'
// 添加请求拦截器
axios.interceptors.request.use((config) => {
// 为每次请求添加baseUrl
config.baseURL = getBaseUrl()
if (sessionStorage.token) {
config.headers['Authorization'] = sessionStorage.token
}
return config
}, (error) => {
return Promise.reject(error)
})
// 添加响应拦截器
axios.interceptors.response.use((res) => {
// 如果错误码!==0;抛出异常
if (res.data.code && res.data.code !== 0) {
switch (res.data.code) {
case 1001:
// 无权限
Router.replace('/login')
break;
case 1003:
// 访问身份不合法
Router.replace('/login')
break;
default:
break;
}
let message = res.data.msg || '出错了'
return Promise.reject({message: message, err: res})
} else {
return Promise.resolve(res.data)
}
}, (error) => {
let message = '服务器错误'
return Promise.reject({message: message, err: error})
})
// 返回一个Promise(发送post请求)
export function fetchpost (url, params) {
return new Promise((resolve, reject) => {
axios.post(url, params).then(response => {
resolve(response.data)
}).catch((error) => {
reject(error)
})
})
}
// 返回一个Promise(发送get请求)
export function fetchget (url, params) {
return new Promise((resolve, reject) => {
axios.get(url, {params: params}).then(response => {
resolve(response.data)
}).catch((error) => {
reject(error)
})
})
}
// 返回一个Promise(发送put请求)
export function fetchput (url, params) {
return new Promise((resolve, reject) => {
axios.put(url, params).then(response => {
resolve(response.data)
}).catch((error) => {
reject(error)
})
})
}
// 返回一个Promise(发送delete请求)
export function fetchdelete (url, params) {
return new Promise((resolve, reject) => {
axios.delete(url, params).then(response => {
resolve(response.data)
}).catch((error) => {
reject(error)
})
})
}</code></pre>
cordova+vue搭建app实践笔记
https://segmentfault.com/a/1190000018410308
2019-03-06T17:01:52+08:00
2019-03-06T17:01:52+08:00
yunsiyu
https://segmentfault.com/u/yunsiyu
0
<p><strong>实现对原生物理返回键的监听:</strong></p>
<pre><code>var exitAppTicker = 0;
document.addEventListener("deviceready",function(){
document.addEventListener("backbutton", function(){
var pageUrl = window.location.href;
var n = pageUrl.lastIndexOf('?');
var m = pageUrl.lastIndexOf('/');
var str = ''
if (n > 0) {
str = pageUrl.substring(m+1,n); //获取pageName
} else {
str = pageUrl.substring(m+1); //获取pageName
}
if(str == 'index' || str == '' || str == 'loginPhone'){
if(exitAppTicker == 0){
exitAppTicker++;
showToast('再次返回退出', 2000)
setTimeout(function(){
exitAppTicker = 0;
},2000);
}else if(exitAppTicker == 1){
navigator.app.exitApp();
}
}else{
history.back();
}
}, false);
},false);</code></pre>
<p><strong>自定义toast , js实现android中toast效果</strong></p>
<pre><code>/**
* 自定义toast,js实现android中toast效果
* @param msg 显示文字
* @param duration 显示的时间长度
*/
function showToast(msg, duration) {
duration = isNaN(duration) ? 2000 : duration;
var m = document.createElement('div');
m.innerHTML = msg;
m.style.cssText = "width:60%; min-width:150px; background:#000; opacity:0.5; height:40px; color:#fff; line-height:40px; text-align:center; border-radius:5px; position:fixed; top:70%; left:20%; z-index:999999; font-weight:bold;";
document.body.appendChild(m);
setTimeout(function() {
var d = 0.5;
m.style.webkitTransition = '-webkit-transform ' + d
+ 's ease-in, opacity ' + d + 's ease-in';
m.style.opacity = '0';
setTimeout(function() {
document.body.removeChild(m)
}, d * 1000);
}, duration);
}</code></pre>
vue-cli3环境变量与分环境打包
https://segmentfault.com/a/1190000018161776
2019-02-15T11:35:03+08:00
2019-02-15T11:35:03+08:00
yunsiyu
https://segmentfault.com/u/yunsiyu
6
<p><strong>第一步 : 了解环境变量概念</strong></p>
<p><strong>我们可以根目录中的下列文件来指定环境变量:</strong></p>
<pre><code>.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略</code></pre>
<p>环境变量文件只包含环境变量的“键=值”对:</p>
<pre><code>FOO=bar
VUE_APP_SECRET=secret // 只有VUE_APP_开头的环境变量可以在项目代码中直接使用</code></pre>
<p>除了 自定义的<strong>VUE_APP_</strong>* 变量之外,在你的应用代码中始终可用的还有两个特殊的变量:</p>
<ul>
<li>NODE_ENV - 会是 "development"、"production" 或 "test"<br> 中的一个。具体的值取决于应用运行的模式。</li>
<li>BASE_URL - 会和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。</li>
</ul>
<p>为一个特定模式准备的环境文件的 (例如 .env.production) 将会比一般的环境文件 (例如 .env) 拥有更高的优先级。</p>
<p>模式概念: <br>模式是 Vue CLI 项目中一个重要的概念。一般情况下 Vue CLI 项目有三个默认模式:</p>
<ul>
<li>development 模式用于 vue-cli-service serve</li>
<li>production 模式用于 vue-cli-service build 和 vue-cli-service test:e2e</li>
<li>test 模式用于 vue-cli-service test:unit</li>
</ul>
<p>模式不等同于 NODE_ENV,一个模式可以包含多个环境变量。也就是说,每个模式都将 NODE_ENV的值设置为模式的名称(可重新赋值更改)——比如在 development 模式下 NODE_ENV 的值会被设置为 "development"。 </p>
<p>你可以通过为 .env 文件增加后缀来设置某个模式下特有的环境变量。比如,如果你在项目根目录创建一个名为 .env.development 的文件,那么在这个文件里声明过的变量就只会在 development 模式下被载入。</p>
<p>你可以通过传递 --mode 选项参数为命令行覆写默认的模式。例如,如果你想要在构建命令中使用开发环境变量,请在你的 package.json 脚本中加入:</p>
<pre><code>"dev-build": "vue-cli-service build --mode development",</code></pre>
<p><strong>环境变量的使用 :</strong> <br>只有以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中(即在项目代码中使用)。你可以在应用的代码中这样访问它们:</p>
<pre><code>console.log(process.env.VUE_APP_SECRET)</code></pre>
<p><strong>理解指令 , 模式 , 环境变量之间的关系</strong><br>我们在项目中的package.json经常能看见以下这样的指令</p>
<p><img src="/img/bVbomOD?w=391&h=188" alt="clipboard.png" title="clipboard.png"></p>
<p>在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令。<br>vue-cli-service serve</p>
<pre><code>用法:vue-cli-service serve [options] [entry]
选项:
--open 在服务器启动时打开浏览器
--copy 在服务器启动时将 URL 复制到剪切版
--mode 指定环境模式 (默认值:development)
--host 指定 host (默认值:0.0.0.0)
--port 指定 port (默认值:8080)
--https 使用 https (默认值:false)</code></pre>
<p>vue-cli-service build</p>
<pre><code>用法:vue-cli-service build [options] [entry|pattern]
选项:
--mode 指定环境模式 (默认值:production)
--dest 指定输出目录 (默认值:dist)
--modern 面向现代浏览器带自动回退地构建应用
--target app | lib | wc | wc-async (默认值:app)
--name 库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)
--no-clean 在构建项目之前不清除目标目录
--report 生成 report.html 以帮助分析包内容
--report-json 生成 report.json 以帮助分析包内容
--watch 监听文件变化</code></pre>
<p>以上是两个常用的cli指令 , 他们默认对应的分别是development和production模式 , 如果还想了解其他指令 , 可以访问: <a href="https://link.segmentfault.com/?enc=RxqjmpVmWEP58tinXxamyw%3D%3D.mdWMEGf4n35HG9h%2FTwsY2CTuSMC4SslSsIlDCrXCS25kG9IyAHo055O4sFmDK3NMjVIP3mzxFHTre9RF4UkCPw8cLI5TcKtlb1tS9bSNFAU%3D" rel="nofollow">https://cli.vuejs.org/zh/guid...</a> CLI 服务</p>
<p><strong>那么接下来 , 我们就开始创建一个用于打包测试环境的模式;</strong></p>
<p><strong>修改package.json</strong><br>添加一行命令</p>
<pre><code>"test": "vue-cli-service build --mode test"</code></pre>
<p><strong>添加.env.test文件</strong><br>在项目根路径创建.env.test文件,内容为</p>
<pre><code>NODE_ENV='production' //表明这是生产环境(需要打包)
VUE_APP_CURRENTMODE='test' // 表明生产环境模式信息
VUE_APP_BASEURL='http://***.****.com:8000' // 测试服务器地址</code></pre>
<p><strong>修改项目中的api接口文件</strong><br>在我的项目中,一般会创建一个api.js 来管理所有的接口url<br>因为我们在本地开发环境中是通过代理来连接服务器的,所以将url写成这</p>
<pre><code>`${baseUrl}/apis/v1/login`,</code></pre>
<p><strong>在文件开头通过环境变量改变baseUrl</strong></p>
<pre><code>let baseUrl = '';
if (process.env.NODE_ENV == 'development') {
baseUrl = ""
} else if (process.env.NODE_ENV == 'production') {
baseUrl = process.env.VUE_APP_BASEURL
} else {
baseUrl = ""
}
</code></pre>
<p><strong>当需要为测试环境进行打包的时候 , 我们只需要运行下面指令进行打包</strong></p>
<pre><code>npm run test</code></pre>
js关闭当前页面(支付宝,微信,app)
https://segmentfault.com/a/1190000018140005
2019-02-13T15:52:12+08:00
2019-02-13T15:52:12+08:00
yunsiyu
https://segmentfault.com/u/yunsiyu
1
<p>使用js 关闭当前页面 , 一般想到的都是 window.close() , 但是该方法只能关闭通过 window.open() 打开的页面 </p>
<p>所以针对这种情况 , 只能分情况去解决 . </p>
<p>在微信 , 支付宝 , app 中打开外部链接 , 都是使用webview打开页面的 , 所以需要app提供映射方法 . </p>
<p>对于微信 , 支付宝 , 我们能通过开放平台找到对应的方法.</p>
<p>微信:</p>
<pre><code>window.WeixinJSBridge.call('closeWindow')</code></pre>
<p>支付宝:</p>
<pre><code>window.AlipayJSBridge.call('closeWebview')</code></pre>
<p>对应一般的app ,需要开发者封装可以让js调用的方法 . (以下就是js 和 app的交互方法)</p>
<p>Javascript调用Java方法</p>
<p>以Android的Toast的为例,下面看下如何从Javascript代码中调用系统的Toast。<br>先定义一个AndroidToast的Java类,它有一个show的方法用来显示Toast:</p>
<pre><code>public class AndroidToast {
@JavascriptInterface
public void show(String str) {
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
}</code></pre>
<p>再对WebView进行设置,开启JavaScipt,注册JavascriptInterface的方法:</p>
<pre><code>private void initView() {
webView = (WebView) findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDefaultTextEncodingName("UTF-8");
webView.addJavascriptInterface(new AndroidToast(), "AndroidToast");
webView.loadUrl("file:///android_asset/index.html");
}</code></pre>
<p>addJavascriptInterface的作用是把AndroidToast类映射为Javascript中的AndroidToast。这样就可以在JavaScript中调用Java中的方法了。<br>在Javascript中调用Java代码:</p>
<pre><code>function toastClick(){
window.AndroidToast.show('from js');
}</code></pre>
<p>通过window属性可以找到映射的对象AndroidToast,直接调用它的show方法即可。<br>注意这里传输的数据只能是基本数据类型和string,可以传输string就意味着可以使用json传输结构化数据。<br>这里调用的方法并没有返回值,如果需要在JavaScript中需要得到返回值怎么办呢?JavaScript调用Java有返回值<br>如果想从Javascript调的方法里面获取到返回值,只需要定义一个带返回值的@JavascriptInterface方法即可:</p>
<pre><code>public class AndroidMessage {
@JavascriptInterface
public String getMsg() {
return "form java";
}
}</code></pre>
<p>添加Javascript的映射:<br>webView.addJavascriptInterface(new AndroidMessage(), "AndroidMessage");<br>在JavaScript直接调用:</p>
<pre><code>function showAlert(){
var str=window.AndroidMessage.getMsg();
console.log(str);
}</code></pre>
<p>这样就完成了有返回值的方法调用。还有一种场景是,在Java中主动触发JavaScript方法,就需要在Java中调用JavaScript方法了。Java调用JavaScript方法</p>
<p>Java在调用JavaScript方法的时候,需要使用WebView.loadUrl()方法,它可以直接在页面里执行JavaScript方法。<br>首先定义一个JavaScript方法给Java调用:</p>
<pre><code>function callFromJava(str){
console.log(str);
}</code></pre>
<p>在Java中直接调用该方法:</p>
<pre><code>public void javaCallJS(){
webView.loadUrl("javascript:callFromJava('call from java')");
}
</code></pre>
<p>可以在loadUrl中给Javascript方法直接传参,如果JavaScript方法有返回值,使用WebView.loadUrl()是无法获取到返回值的,需要JavaScript返回值给Java的话,可以定义一个Java方法提供给JavaScript调用,然后Java调用JavaScript之后,JavaScript触发该方法把返回值再传递给Java。<br>注意WebView.loadUrl()必须在Ui线程中运行,不然会会报错。</p>
<p>以下是项目中用到的具体代码:</p>
<pre><code>var isLppzApp = false
var ua = navigator.userAgent.toLowerCase()
var uaApp = ua ? ua.match(/BeStore/i) : '' // match方法返回的是对象
var uaAndroid = /android/i.test(ua) // test返回的是true/false
var uaIos = /iphone|ipad|ipod/i.test(ua)
if (uaApp.toString() === 'bestore') { // 必须将match返回的对象转成字符串
isLppzApp = true
} else {
isLppzApp = false
}
if (window.WeixinJSBridge) {
window.WeixinJSBridge.call('closeWindow') // 微信
} else if (window.AlipayJSBridge) {
window.AlipayJSBridge.call('closeWebview') // 支付宝
} else if (isLppzApp && uaAndroid) {
window.obj.closePageLppzRequest('') // 安卓app
} else if (isLppzApp && uaIos) {
window.webkit.messageHandlers.closePageLppzRequest.postMessage('') //ios app
}</code></pre>