ajax请求后无法打开新页面
最近项目需求需要在系统中打开其他系统的内部网页,一开始是ajax请求本地后台,后台解析得到token后返回,然后前端跳转,发现这样做浏览器不会执行window.open打开新页面.
解决方案:1 同步ajax,2 先新建窗口,然后ajax后对新窗口的location.href赋值
其实第2种方案有特定情况下没法访问的问题.
个人认为最佳方案还是不通过ajax请求,直接打开新窗口,让后台重定向比较好,最终解决方案也是这样去做的
最近项目需求需要在系统中打开其他系统的内部网页,一开始是ajax请求本地后台,后台解析得到token后返回,然后前端跳转,发现这样做浏览器不会执行window.open打开新页面.
解决方案:1 同步ajax,2 先新建窗口,然后ajax后对新窗口的location.href赋值
其实第2种方案有特定情况下没法访问的问题.
个人认为最佳方案还是不通过ajax请求,直接打开新窗口,让后台重定向比较好,最终解决方案也是这样去做的
因为公司项目需要,在页面表格中很多地方的数字需要制作弹出窗口,由于弹出层非常多,弹出层中还需要展示弹出层,并且希望弹出层互相不影响.虽然有不少弹出层开源框架,但是都不支持动态创建弹出层(不在template模板中写),并且能够通过插槽的方式插入其他组件.主要原因是vue中想要使用组件,必须在components中有定义过或者组件是全局组件,这就导致了不好开发类似jquery形式的创建弹出层中传入插槽组件.
考虑到弹出层过多,如果在模板里定义弹出层过于繁琐,针对自己项目写了个弹出层组件.
首先是弹出层样式(这个基本差不多,按照各自项目的样式来写
<template>
<transition enter-active-class="dialog-showIn" leave-active-class="dialog-showOut" @after-enter="afterEnter" @after-leave="afterLeave">
<div v-show="visible" ref="popup" class="popup popup-bground animated" :style="popupStyle()">
<div ref="title" class="title" :style="{cursor:cursor}">
<div class="name" :style="titleStyle">{{ title || '标题' }}</div>
<div @click.stop="close()" class="close">
<i class="iconfont icon-guanbi2"></i>
</div>
</div>
<div ref="main" class="main" :class="mainClassName">
<slot></slot>
</div>
</div>
</transition>
</template>
<script>
import initDrag from '@/assets/js/drag'
export default {
name: 'GridDialog',
props: {
show: Boolean,
mainClassName: {
type: String,
default: ''
},
title: {
default: '',
type: String
},
titleStyle: {
default: ''
},
top: {
default: '',
type: String
},
left: {
default: '',
type: String
},
right: {
default: '',
type: String
},
bottom: {
default: '',
type: String
},
width: {
type: String,
default: '4rem'
},
height: {
type: String,
default: '3rem'
},
fullPage: {
type: Boolean,
default: false
},
fullWindow: {
type: Boolean,
default: false
},
zIndex: {
type: Number,
default: 101
}
},
data() {
return {
y: '',
x: '',
r: '',
b: '',
visible: false
}
},
computed: {
cursor() {
return this.fullPage || this.fullWindow ? 'default' : 'move'
},
popupWidth() {
return this.fullWindow ? '90%' : this.width
},
popupHeight() {
return this.fullWindow ? '90%' : this.height
}
},
created() {
// console.log('right:' + this.right)
if (this.fullPage) {
this.y = '0px'
this.x = '0px'
} else if (this.fullWindow) {
this.x = '5%'
this.y = '5%'
} else {
this.y = this.top
this.x = this.left
this.r = this.right
this.b = this.bottom
}
this.visible = this.show
},
mounted() {
if (this.b) {
this.$refs.popup.style.bottom = this.b
} else if (this.y) {
this.$refs.popup.style.top = this.y
} else {
let height = $(window).height()
this.$refs.popup.style.top =
(height - $(this.$refs.popup).height()) / 2 + 'px'
}
if (this.r) {
this.$refs.popup.style.right = this.r
} else if (this.x) {
this.$refs.popup.style.left = this.x
} else {
let width = $(window).width()
this.$refs.popup.style.left =
(width - $(this.$refs.popup).width()) / 2 + 'px'
}
this.$nextTick(() => {
if (!this.fullPage && !this.fullWindow) {
initDrag(this.$refs.title, this.$refs.popup, data => {
// this.y = data.y + 'px';
// this.x = data.x + 'px';
})
}
})
},
methods: {
close() {
this.$emit('close', false)
},
afterLeave() {
this.$emit('closed')
},
afterEnter() {
this.$emit('opened')
},
popupStyle() {
if (this.fullPage) {
return {
width: '100vw',
height: '100vh',
'z-index': this.zIndex
}
} else {
return {
width: this.popupWidth,
height: this.popupHeight,
'z-index': this.zIndex
}
}
}
},
watch: {
show(val) {
this.visible = val
}
}
}
</script>
上面的弹出层就是普通的弹出层,可以在其他的template模板中引用显示,下面是为jquery形式js代码弹出所写:
import GridDialog from './GridDialog.vue'
import components from './components'
const props = GridDialog.props
export default {
components: components, // 这段是单独一个js引入所有需要加载插槽的组件,如果没有定义,外部弹出会报错
props: {
...props,
slotName: {
type: String,
default: null
},
slotProps: {
type: Object,
default: null
},
id: {
type: String,
default: null
}
},
data() {
return {
visible: false,
isOpened: false
}
},
render(h) {
var child = null
if (this.slotName != null) {
child = h(this.slotName, {
props: { ...this.slotProps, isVisible: this.isOpened }
})
}
return (
<GridDialog
title={this.title}
show={this.visible}
mainClassName={this.mainClassName}
titleStyle={this.titleStyle}
top={this.top}
left={this.left}
right={this.right}
bottom={this.bottom}
width={this.width}
height={this.height}
fullWindow={this.fullWindow}
fullPage={this.fullPage}
zIndex={this.zIndex}
on-close={() => this.close()}
on-closed={() => this.closed()}
on-opened={() => this.opened()}
>
{child}
</GridDialog>
)
},
methods: {
close() {
this.visible = false
this.isOpened = false
},
closed() {
this.$emit('closed')
},
opened() {
this.isOpened = true
}
}
}
然后专门写一个弹出层池,用来管理弹出框
import Vue from 'vue'
import Component from './fun-griddialog.jsx'
import router from '@/router'
import store from '@/store/index'
const Constructor = Vue.extend(Component)
let instances = []
let numbers = 0
let zindexNumbers = 9999999
const removeInstance = instance => {
const index = instances.findIndex(t => instance.id == t.id)
if (index != -1) {
instances.splice(index, 1)
}
}
const getInstanceById = id => {
const instance = instances.find(t => t.id == id);
return instance;
}
const popup = options => {
const props = options.props || {};
const slotName = options.slotName || null;
const slotProps = options.slotProps || {};
// 这里需要把router和store传入,否则的话自定义插槽如果用到了store,这个组件是不在总App组件下,无法使用store的
const instance = new Constructor({
router,
store,
propsData: {
...props,
slotName,
slotProps,
zIndex: ++zindexNumbers,
show: false
}
})
if (options.id) {
let ins = getInstanceById(options.id);
if (ins) {
ins.vm.visible = true;
return;
}
}
instance.id = options.id || `__popup_dialog__${++numbers}`
if (options.id) {
instance.__cache = true;
}
instance.vm = instance.$mount()
document.body.appendChild(instance.vm.$el)
instance.vm.visible = true
instances.push(instance)
instance.vm.$on('close', () => {
instance.vm.visible = false
})
// 有些弹出层希望能缓存起来,不要点关闭后关闭,约定在外部传入id则不销毁.
instance.vm.$on('closed', () => {
if (!instance.__cache) {
removeInstance(instance)
document.body.removeChild(instance.vm.$el)
}
})
// 对弹出层添加鼠标按下事件,改变他的zIndex,从而让弹出层放到最上层
instance.vm.$el.addEventListener('mousedown', e => {
// console.log('mousedown')
instance.vm.zIndex = ++zindexNumbers;
})
return instance.vm
}
export default popup
这样就基本上写好了针对我们项目的弹出层,但是这种弹出层确实很难做到通用(2个问题: 1:Vue组件必须在components中定义才能使用,如果都是全局注册也不太好. 2: 这类组件基本上不在App(根组件)的下面,如果想在根组件下面,仍然会有很多耦合代码,所以基本也要用硬编码去引入类似vuex和vue-router组件)基于上面两点,实在想不到怎么做通用框架,本身的vue组件库确实也没有看到能有这样的的弹出层功能,(只能是基本功能,不能引用自定义组件)
最后使用就很简单,
import GridDialog from './GridDialog.vue'
import popup from './function'
export default (Vue) => {
Vue.component(GridDialog.name, GridDialog)
Vue.prototype.$popup = popup;
}
// 在main入口里面写入
// import xxx from './...js'
// Vue.use(xxx)
// 在需要弹出的地方直接写:
this.$popup({
props: {
title:'标题',
// fullPage: true
//width: '1000px',
//height: '500px'
fullWindow: true
},
slotName: 'UnitContainer', //组件名称(必须在上面的component中引用)
slotProps: { //组件需要传入的参数
areaId: item.areaId,
deviceType: this.activeDeviceType.value
}
})
记录一下,下次需要用到时有地方可以查阅
公司项目有一个需求,在展示平台页面以网格化页面展示,每个网格可以根据用户配置,搭配不同的组件,
可搭配的组件最终可能有上百种,如果不用异步的模式加载组件的话,最终打包的app会有很大一部分的冗余代码(用户没有配置的组件也会打包到js文件中)
这个时候,每个组件分别打包成一个js,最终通过异步加载的形式加载进来是比较好的办法.
关于异步打包,vue官方文档已经有写
官方链接
不过官方写的有点不好理解,其实搭配webpack只是最简单的改一下:
// import xxx from './xx/xxx' // 头部不再需要引入
export default {
components:{
...,
xxx: resolve=> require(['./xx/xxx.vue'],resolve) // 在components对象中引入路径即可,最终webpack打包会是一个单独的js文件
}
}
其他代码都是一样的,不过一般来说,如果是动态引入组件的,基本上不会用template模板去写html标签,会直接在对象中写render方法
组件的名称以字符串的形式引入即可:
export default {
...,
render(h){
h('xxx') // 创建一个xxx组件 等于<xxx /> h的具体用法参考vue官方createElement
}
}
关于render的学习用法,一个简单的方法是自己写一个template模板页面,然后用webpack打包后,在打包后的js中通过定位搜索找到render函数,看官方vue-template-compiler处理后的js代码,看几次就很明白了.(感觉这是最好的办法^_^)
jwt因为他的简单易用,并且有一定的加密性,在restful中会广泛使用.
这里记录一下简单的jwt登录验证实现.
通过nuget管理工具安装jwt管理,可以根据自己项目依赖性选择 4.0 3.0或者2.x,本人用的是3.0.0
凡是口令都是两部分,一部分是加密后发送到客户端,一部分是获取客户端加密后的字符串,解密后得到真实值.
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Newtonsoft.Json;
....
// 对象实体类加密
public static string CreateJWTToken<T>(this T payload, string secret)
{
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
string token = encoder.Encode(payload, secret);
return token;
}
上面是最简单的加密方式,可以根据自己的需要换不同的加密方式.
public static string GetJsonByToken(string token, string secret)
{
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
string json = decoder.Decode(token, secret, true);
return json;
}
catch (TokenExpiredException)
{ //口令过期
//Console.WriteLine("expired");
return "expired";
}
catch (SignatureVerificationException)
{ //无效口令.
//Console.WriteLine("Err");
return "err";
}
catch (Exception)
{
throw;
}
}
上面是简单的把口令解密成json字符串的方法.不过实际上我们希望得到的一般都是对应的实体类,可以再做一层封装:
public static T GetObjectByToken<T>(string token, string secret)
{
var json = JWTHelper.GetJsonByToken(token, secret);
if (json == "expired" || json == "err") return default(T);
try
{
T obj = default(T);
var data = JsonConvert.DeserializeAnonymousType(json, obj);
return data;
}
catch (Exception)
{
return default(T);
}
}
JsonConvert.DeserializeAnonymousType
这个方法很好用,一般是用来有些json对象只用到一次的时候,单单创建一个匿名对象,而不是重新建一个实体类.
jwt口令加密及解密,就写这么多了,具体如何用到webapi中,可以参考之前写的一篇文章:
AuthorizeAttribute权限配置入门
json作为轻量级数据格式,在如今webapi上应用的越来越广了.很多厂商在提供的接口中都用json来输出.
在解析json方面,javascript和python有天生的优势,作为弱类型语言,直接解析了就能当对象用了.在C#里面我们想要解析json,大部分情况下都是先定义一个实体类,然后通过Newtonsoft.Json中的Deserialize方法去反序列化为成这个类对象.
首先引入命名空间using Newtonsoft.Json;
//解析JSON字符串生成对象实体
public static T DeserializeJsonToObject<T>(string json) where T : class
{
JsonSerializer serializer = new JsonSerializer();
StringReader sr = new StringReader(json);
object o = serializer.Deserialize(new JsonTextReader(sr), typeof(T));
T t = o as T;
return t;
}
那当我们碰到好多个接口,json格式多种多样的时候,就要定义N个类,那岂不是非常麻烦....
后来发现,Newtonsoft.Json的静态对象JsonConvert中给我们提供了反序列化的静态方法:
public static T DeserializeAnonymousType<T>(string value, T anonymousTypeObject);
public static T DeserializeAnonymousType<T>(string value, T anonymousTypeObject, JsonSerializerSettings settings);
那就好办一点了,很多只用到一次的格式我们可以通过定义一个匿名对象,这样可以让我们少定义不少的实体类仅仅只是为了解析json
到了这一步,虽然我们对象定义可以免了,但是对比python和js,还是多了一步定义匿名对象啊,还是比较麻烦不是么?
那我们会说没办法嘛,人家弱类型对象天生就自带光环,天生就是有这优势,本身就不需要实体类,强类型语言怎么跟他们比呢,能有什么办法嘛.
想想也是,可是关键是,我以前用VB编程做网抓的时候,可是吃尽了json的苦头,只能用VBScript去解析json,麻烦的要命.后来发现有老外专门为vb写了一个json解析.真是牛的一B.
附上github链接: https://github.com/greatbody/VB6_JSON_Parse
他里面是把json解析成了两种对象,dictionary对应弱类型里面的对象,collection对应弱类型里面的数组,因为vb里面dictionary和collection的item都可以是object类型,这样就完美的解决了json的解析值存储.
那其实C#完全也能做到哇,C#也可以拿Dictionary和List去存嘛.于是我就本着自己照着人家老外写的一套VB写一套C#用的去网上搜索一下资料.这个时候总算被我找到我想要的了.原来人家Newtonsoft.Json老早就考虑到这点了.(我就想嘛,连我都觉得麻烦人家肯定想到了)
Newtonsoft.Json有封装一个类: Newtonsoft.Json.Linq.JObject 他下面有一个静态方法,不管怎么样的json结构,调用Parse方法,就能得到JObject这个类对象.然后就想怎么用就怎么用了,多的不说了,代码截图放出来大家就都明白了.
ps:我们还可以通过JObject类,像弱类型一样添加属性,最后ToString输出json
string json = "{\"name\":\"BeJson\",\"url\":\"http://www.bejson.com\",\"page\":88,\"isNonProfit\":true,\"address\":{\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"},\"links\":[{\"name\":\"Google\",\"url\":\"http://www.google.com\"},{\"name\":\"Baidu\",\"url\":\"http://www.baidu.com\"},{\"name\":\"SoSo\",\"url\":\"http://www.SoSo.com\"}]}";
var jsonObj = Newtonsoft.Json.Linq.JObject.Parse(json);
string url = jsonObj["url"].ToString();
var address = jsonObj["address"];
var street = address["street"].ToString();
注意:如果json最外面是数组格式的话改成Newtonsoft.Json.Linq.JArray.Parse(json);
如果不确定是object或者是array类型,可以用通用的JToken.Parse
,只是比前面两个少了对应的部分方法而已