企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充(Vue项目版)。。。

引子

关于企业微信PC版应用跳转到默认浏览器,我之前写过一篇文章:企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充。。。

以前的文章里用的前后端一体的Jsp项目,这次我使用的是前后端分离的Vue项目,这两者实现的方式差异较大,我在开发的过程中也碰到了不少问题,所以我再写一篇文章作为补充。

官方文档推荐是使用最新的WECOM-JSSDK,我刚开始也是用WECOM-JSSDK,但是碰到莫名其妙的问题,无法实现跳转(文章中有说明),最后不了了之。

后来我就采用了JS-SDK,最后成功实现跳转功能。

使用JS-SDK的前提是通过config接口注入权限验证配置。

在这里插入图片描述
在这里插入图片描述

然后调用openDefaultBrowser方法打开默认浏览器。

在这里插入图片描述

具体请查看企微微信客户端API文档。

实现思路

由于Vue项目采用前后端分离架构,客户端和服务器端通常部署在不同的服务器上。在企业微信上跳转到默认浏览器的时候,如何把服务器的token传递到客户端浏览器是个难点。

我想出来的实现思路是这样的:

  1. 在跳转到默认浏览器之前,先通过企业微信的OAuth2方式,获取服务器端的登录token
  2. 在跳转的URL后面加上token参数,传递到用默认浏览器打开的项目页面(storeTonken.vue)中
  3. 在项目页面(storeTonken.vue)中提取URL中token,保存到Cookie中,然后跳转到项目首页,实现单点登录

整体的流程图如下:
在这里插入图片描述

操作步骤

引入JS文件

首先在vue项目中的index.html中引入JS文件。

在这里插入图片描述

<script src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>

新建vue文件

然后在views文件夹下,新建一个文件夹“openDefaultBrowser”(名字任意取),然后新建一个index.vue和storeTonken.vue文件。

在这里插入图片描述
index.vue页面是跳转到默认浏览器用的,storeTonken.vue文件是保存token并跳转到首页用的。

配置白名单

然后在router中把这两个路径加入到白名单中,不用登录也能访问:

在这里插入图片描述

我这个项目还需要在permission.js中添加入白名单:

在这里插入图片描述

每个项目的架构不一样,请你根据实际情况配置。

需要注意的问题

设置可信域名

如应用页面需使用微信JS-SDK、跳转小程序等, 需完成域名归属验证。

就是在你的域名后面加上企业微信提供的这个 WW_verify***yo0iITyAeb6.txt 文件的URL,能够直接访问txt文件里面的内容。

如果你的域名是 abc.com,在浏览器中输入 http://abc.com/WW_verify***yo0iITyAeb6.txt,确保可以显示txt里面的内容。

在这里插入图片描述
那如何在服务器上设置呢?

我以nginx为例说一下步骤:

1、下载WW_verify***yo0iITyAeb6.txt 文件

2、把txt文件复制到服务器上,比如目录:/usr/local/etc 下

在这里插入图片描述
3、 在nginx.conf文件中添加 location = /WW_verify_Pmj1eyo0iITyAeb6.txt,记得把/usr/local/etc改成你自己的路径。

location = /WW_verify***yo0iITyAeb6.txt {alias /usr/local/etc/WW_verify***yo0iITyAeb6.txt;try_files $uri =404;}

4、重启nginx

最后我们来看下效果:

在这里插入图片描述
然后,点击验证,就可以验证通过了:

在这里插入图片描述

设置企业可信IP

记得要设置企业可信IP,不然会报:not allow to access from your ip 的错误。

在这里插入图片描述

Connection reset 问题

我把项目发布到正式服务器后,结果后台报 Connection reset 的异常:

在这里插入图片描述
这个应该是网络连接的问题,我在服务器上运行:curl https://qyapi.weixin.qq.com

果然报错了:

在这里插入图片描述
看来真的是网络问题。

后来发现是服务器的外网权限没开~

WECOM-JSSDK 报错:not be accessed on strict mode

我在使用WECOM-JSSDK的时候,前端会报错:not be accessed on strict mode,这个问题,后来一直没有解决。

不知道是不是这个错误导致浏览器跳转失败。

在这里插入图片描述

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to themat Arguments.invokeGetter (<anonymous>:3:28)at Object._getConfigSignature (http://localhost:9528/afterSale/app.js:24558:11)at Object.getConfigSignature (http://localhost:9528/afterSale/app.js:24538:38)at http://localhost:9528/afterSale/app.js:2714:49at step (http://localhost:9528/afterSale/app.js:2133:23)at Object.next (http://localhost:9528/afterSale/app.js:2074:20)at asyncGeneratorStep (http://localhost:9528/afterSale/app.js:2046:28)at _next (http://localhost:9528/afterSale/app.js:2061:17)at http://localhost:9528/afterSale/app.js:2066:13at new Promise (<anonymous>)

openDefaultBrowser\index.vue 使用WECOM-JSSDK的代码的版本:

<template><div>正在跳转到默认浏览器...</div>
</template><script>
import { getWeiXinPermissionsValidationConfig } from '@/api/openDefaultBrowser'
import * as ww from '@wecom/jssdk'export default {data() {return {paramObject: {url: '',type: ''}}},created() {this.getConfig()},methods: {// 获取相关验证配置信息getConfig() {// 该paramUrl 为你使用微信sdk-js相关接口的页面地址 该地址需要配置到应用后台的可信域名下const paramUrl = window.location.href.split('#')[0]this.paramObject.url = paramUrlthis.companyConfigInit()},// 企业验证配置companyConfigInit() {const that = thisgetWeiXinPermissionsValidationConfig(that.paramObject).then(response => {ww.register({appId: response.data.corpid, // 必填,企业微信的corpIDagentId: response.data.agentid, // 必填,当前应用的AgentIDjsApiList: ['openDefaultBrowser'], // 你要调用的sdk接口必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来getConfigSignature, // 必填,根据url生成企业签名的回调函数getAgentConfigSignature // 必填,根据url生成应用签名的回调函数})async function getConfigSignature() {console.log('calling getConfigSignature')// 根据 url 生成应用签名,生成方法同上,但需要使用应用登录授权的 jsapi_ticketreturn { timestamp: response.data.timestamp, nonceStr: response.data.nonceStr, signature: response.data.signature }}async function getAgentConfigSignature() {console.log('calling getAgentConfigSignature')return { timestamp: response.data.timestamp, nonceStr: response.data.nonceStr, signature: response.data.agentSignature }}ww.openDefaultBrowser({// 在默认浏览器打开redirect_uri,并附加code参数;也可以直接指定要打开的url,此时不会附带上code参数。url: 'https://work.weixin.qq.com/',success(result) {// 成功回调,result.errMsg 固定格式为“方法名:ok”console.log('success:' + result)},fail(result) {// 失败回调,通过 result.errMsg 查看失败详情console.log('fail:' + result)},complete(result) {// 完成回调,无论调用成功还是失败,都会回调该方法console.log('complete:' + result)}})})}}
}
</script>

后来实在解决不了,我就放弃使用WECOM-JSSDK了,转而使用JS-SDK,这段代码就算留个纪念吧。

如果你知道怎么解决,请得留言告诉我,谢谢。

项目代码

前端代码

openDefaultBrowser\index.vue

<template><div>正在跳转到默认浏览器...</div>
</template><script>
import { weiXinLogin, getWeiXinPermissionsValidationConfig } from '@/api/openDefaultBrowser'
import { setToken } from '@/utils/auth'
const wx = window.wxexport default {data() {return {paramObject: {url: ''},token: ''}},created() {// 微信登录this.weiXinLogin()},methods: {// 微信登录weiXinLogin() {const url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=***&redirect_uri=http%3A%2F%2Fabc.com%3A8890%2FweixinLogin&response_type=code&scope=snsapi_userinfo&agentid=***&state=STATE#wechat_redirect'weiXinLogin(url).then(response => {console.log(response)this.token = response.data.tokensetToken(response.data.token)// 跳转到默认浏览器this.getConfig()})},// 获取相关验证配置信息getConfig() {// 该paramUrl 为你使用微信sdk-js相关接口的页面地址 该地址需要配置到应用后台的可信域名下const paramUrl = window.location.href.split('#')[0]this.paramObject.url = paramUrlthis.companyConfigInit()},// 企业验证配置companyConfigInit() {const that = thisgetWeiXinPermissionsValidationConfig(that.paramObject).then(response => {wx.config({beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId: response.data.corpid, // 必填,企业微信的corpid,必须与当前登录的企业一致timestamp: response.data.timestamp, // 必填,生成签名的时间戳nonceStr: response.data.nonceStr, // 必填,生成签名的随机串signature: response.data.signature, // 必填,签名,见附录-JS-SDK使用权限签名算法jsApiList: ['openDefaultBrowser'] // 必填,传入需要使用的接口名称})wx.ready(function() {that.openDefaultBrowser()})wx.error(function(res) {// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。console.log(res)})})},openDefaultBrowser() {const that = thiswx.invoke('openDefaultBrowser', {// 在默认浏览器打开的uri,附加token参数'url': 'http://abc.com:8889/***/#/storeTonken?token=' + that.token}, function(res) {console.log('res------------->', res)if (res.err_msg !== 'openDefaultBrowser:ok') {// 错误处理}})}}
}
</script>

以上代码中,通过 weiXinLogin 方法获取服务器的 token,先获取 token 然后再跳转到默认浏览器。

wx.config 中需要的参数通过 getWeiXinPermissionsValidationConfig 方法从服务器端获取,然后调用 openDefaultBrowser 方法实现默认浏览器跳转。

token通过url的参数传递:

'url': 'http://abc.com:8889/***/#/storeTonken?token=' + that.token

openDefaultBrowser.js

import request from '@/utils/request'/*** 微信登录* @param {*} url* @returns*/
export function weiXinLogin(url) {return request({url: url,method: 'get'})
}/*** 获取配置信息* @param {*} id*/
export function getWeiXinPermissionsValidationConfig(params) {return request({url: `/getWeiXinPermissionsValidationConfig`,method: 'get',// 如果参数是个对象,不加{}params: params})
}

openDefaultBrowser\storeTonken.vue

<template><div />
</template>
<script>export default {created() {// 获取URL中的tokenconst token = window.location.href.split('token=')[1]this.$store.dispatch('user/singleSignIn', token).then(() => {// 跳转到首页this.$router.push({ path: '/' })})}
}
</script>

this.$store.dispatch(‘user/singleSignIn’, token) 是调用 Vuex 中的 actions,相关代码如下:

const actions = {// 省略其他代码// 单点登录设置tokensingleSignIn({ commit }, token) {return new Promise(resolve => {commit('SET_TOKEN', token)setToken(token)resolve()})}
}

后端代码

WeiXinController

package com.zhanyd.app.controller;import com.alibaba.fastjson.JSONObject;
import com.zhanyd.app.common.ApiResult;
import com.zhanyd.app.common.util.HttpService;
import com.zhanyd.app.common.util.JwtUtils;
import com.zhanyd.app.common.util.StringHelp;
import com.zhanyd.app.common.weixin.WeixinHelper;
import com.zhanyd.app.service.UserLogService;
import com.zhanyd.app.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
@EnableAutoConfiguration
public class WeiXinController {private Logger logger = LoggerFactory.getLogger(this.getClass());@AutowiredUserService userInfoService;@AutowiredUserLogService userLogService;/*** 企业微信域名校验* @return*/@ResponseBody@GetMapping(value = "/WW_ver*****TyAeb6.txt")public String wxPrivateKey(){return "Pm*****Aeb6";}/*** 单点登录,获取config接口注入权限验证配置* @param url* @return* @throws NoSuchAlgorithmException*/@ResponseBody@GetMapping(value = "/getWeiXinPermissionsValidationConfig")public ApiResult<Map<String, Object>> getWeiXinPermissionsValidationConfig(String url) throws NoSuchAlgorithmException {logger.info("获取config信息");ApiResult<Map<String, Object>> apiResult = new ApiResult<Map<String, Object>>();Map<String, Object> resultMap = new HashMap<>(16);// 获取jsapi_ticketString ticket = WeixinHelper.getJsApiTicket(WeixinHelper.AFTER_SALE_PC_CORPSECRET, "");//当前时间戳转成秒long timestamp = System.currentTimeMillis() / 1000;//随机字符串String nonceStr = "Wm3W****cnW";// 获取JS-SDK使用权限签名String signature = WeixinHelper.getJSSDKSignature(ticket, nonceStr, timestamp, url);resultMap.put("corpid", WeixinHelper.APP_ID);resultMap.put("agentid", WeixinHelper.AFTER_SALE_PC_AGENT_ID);resultMap.put("timestamp", timestamp);resultMap.put("nonceStr", nonceStr);resultMap.put("signature", signature);logger.info("config resultMap = " + resultMap);return apiResult.success(resultMap);}/*** 企业微信登录* @return*/@ResponseBody@GetMapping(value = "/weixinLogin")public ApiResult<Map<String, Object>> weixinLogin(String code,HttpServletRequest request){ApiResult<Map<String, Object>> apiResult = new ApiResult<>();Map<String, Object> resultMap = new HashMap(16);logger.info("微信登录:");logger.info("code = " + code);//获取tonkenString getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + WeixinHelper.APP_ID + "&corpsecret=" + WeixinHelper.AFTER_SALE_PC_CORPSECRET;String tokenContent = HttpService.post(getTokenUrl);logger.info("tokenContent = " + tokenContent);JSONObject jsonObject = JSONObject.parseObject(tokenContent);String accessToken = jsonObject.getString("access_token");logger.info("accessToken = " + accessToken);//获取用户信息String getUserUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=" + accessToken + "&code=" + code;String userContent = HttpService.post(getUserUrl);logger.info("userContent = " + userContent);jsonObject = JSONObject.parseObject(userContent);String userId = jsonObject.getString("UserId");logger.info("userId = " + userId);if(!StringHelp.isEmpty(userId)){// 登录逻辑,自行实现logger.info("{} 登录成功", StringHelp.valueOf(user.get("userName")));resultMap.put("userInfo", user);resultMap.put("token", token);logger.info("userInfo = " + user);logger.info("token = " + token);return apiResult.success(resultMap);}logger.info("{} 登录失败", userId);}}

WeixinHelper

package com.zhanyd.app.common.weixin;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhanyd.app.common.util.HttpService;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;public class WeixinHelper {private static final Logger LOGGER = LoggerFactory.getLogger(WeixinHelper.class);// 企业idpublic static final String APP_ID = "****";// 售后平台应用public static final String AFTER_SALE_PC_AGENT_ID = "****";public static final String AFTER_SALE_PC_CORPSECRET = "****";/*** 存放ticket的容器*/private static Map<String, Ticket> ticketMap = new HashMap<>();/*** 获取token* @return*/public static String getAccessToken(String secret) {//获取tokenString getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + WeixinHelper.APP_ID + "&corpsecret=" + secret;String tokenContent = HttpService.get(getTokenUrl);LOGGER.info("tokenContent = " + tokenContent);JSONObject jsonObject = JSONObject.parseObject(tokenContent);String accessToken = jsonObject.getString("access_token");LOGGER.info("accessToken = " + accessToken);return accessToken;}/*** 获取jsapi_ticket* @param secret* @param type* @return*/public static String getJsApiTicket(String secret, String type) {String accessToken = getAccessToken(secret);String key = accessToken;if (!StringUtils.isEmpty(accessToken)) {if ("agent_config".equals(type)){key = type + "_" + accessToken;}Ticket ticket = ticketMap.get(key);if (!ObjectUtils.isEmpty(ticket)) {long now = Calendar.getInstance().getTime().getTime();Long expiresIn = ticket.getExpiresIn();//有效期内的ticket 直接返回if (expiresIn - now > 0) {return ticket.getTicket();}}ticket = getJsApiTicketFromWeChatPlatform(accessToken, type);if (ticket != null) {ticketMap.put(key, ticket);return ticket.getTicket();}}return null;}/*** 获取企业的jsapi_ticket或应用的jsapi_ticket* @param accessToken* @param type 为agent_config时获取应用的jsapi_ticket,否则获取企业的jsapi_ticket* @return*/public static Ticket getJsApiTicketFromWeChatPlatform(String accessToken, String type) {String url;if ("agent_config".equals(type)) {url = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=" + accessToken+ "&type=" + type;} else {url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=" + accessToken;}Long now = System.currentTimeMillis();if (!StringUtils.isEmpty(accessToken)) {String body = HttpService.get(url);LOGGER.info("ticketContent = " + body);if (!StringUtils.isEmpty(body)) {JSONObject object = JSON.parseObject(body);if (object.getIntValue("errcode") == 0) {Ticket ticket = new Ticket();ticket.setTicket(object.getString("ticket"));ticket.setExpiresIn(now + object.getLongValue("expires_in") * 1000);return ticket;}}}return null;}/*** 获取JS-SDK使用权限签名* @param ticket* @param nonceStr* @param timestamp* @param url* @return* @throws NoSuchAlgorithmException*/public static String getJSSDKSignature(String ticket, String nonceStr, long timestamp, String url) throws NoSuchAlgorithmException{String unEncryptStr = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url;MessageDigest sha = MessageDigest.getInstance("SHA");// 调用digest方法,进行加密操作byte[] cipherBytes = sha.digest(unEncryptStr.getBytes());String encryptStr = Hex.encodeHexString(cipherBytes);return encryptStr;}}

Ticket

package com.zhanyd.app.common.weixin;/*** @author zhanyd* @date 2022-07-18*/
public class Ticket {private String ticket;private Long expiresIn;public Ticket() {}public Ticket(String ticket, Long expiresIn) {this.ticket = ticket;this.expiresIn = expiresIn;}public String getTicket() {return ticket;}public void setTicket(String ticket) {this.ticket = ticket;}public Long getExpiresIn() {return expiresIn;}public void setExpiresIn(Long expiresIn) {this.expiresIn = expiresIn;}
}

HttpService

package com.zhanyd.app.common.util;import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class HttpService {private static int readTimeout=25000;private static int connectTimeout=25000;private static final Logger LOGGER = LoggerFactory.getLogger(HttpService.class);/*** POST方法* @param sendUrl* @param sendParam* @return*/public static String post(String sendUrl, String sendParam) {StringBuffer receive = new StringBuffer();BufferedWriter dos = null;BufferedReader rd = null;HttpURLConnection URLConn = null;LOGGER.info("sendUrl = " + sendUrl + " sendParam = " + sendParam);try {URL url = new URL(sendUrl);URLConn = (HttpURLConnection) url.openConnection();URLConn.setReadTimeout(readTimeout);URLConn.setConnectTimeout(connectTimeout);URLConn.setDoOutput(true);URLConn.setDoInput(true);URLConn.setRequestMethod("POST");URLConn.setUseCaches(false);URLConn.setAllowUserInteraction(true);URLConn.setInstanceFollowRedirects(true);URLConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");if (sendParam!=null && sendParam.length()>0) {URLConn.setRequestProperty("Content-Length", String.valueOf(sendParam.getBytes().length));dos = new BufferedWriter(new OutputStreamWriter(URLConn.getOutputStream(), "UTF-8"));dos.write(sendParam);dos.flush();}rd = new BufferedReader(new InputStreamReader(URLConn.getInputStream(), "UTF-8"));String line;while ((line = rd.readLine()) != null) {receive.append(line);}} catch (java.io.IOException e) {receive.append("访问产生了异常-->").append(e.getMessage());e.printStackTrace();} finally {if (dos != null) {try {dos.close();} catch (IOException ex) {ex.printStackTrace();}}if (rd != null) {try {rd.close();} catch (IOException ex) {ex.printStackTrace();}}URLConn.disconnect();}String content = receive.toString();LOGGER.info("content = "+content);return content;}public static String post(String sendUrl, String sendParam,String ContentType) {StringBuffer receive = new StringBuffer();BufferedWriter dos = null;BufferedReader rd = null;HttpURLConnection URLConn = null;LOGGER.info("sendUrl = " + sendUrl + " sendParam = " + sendParam);try {URL url = new URL(sendUrl);URLConn = (HttpURLConnection) url.openConnection();URLConn.setReadTimeout(readTimeout);URLConn.setConnectTimeout(connectTimeout);URLConn.setDoOutput(true);URLConn.setDoInput(true);URLConn.setRequestMethod("POST");URLConn.setUseCaches(false);URLConn.setAllowUserInteraction(true);URLConn.setInstanceFollowRedirects(true);URLConn.setRequestProperty("Content-Type", ContentType);if (sendParam!=null && sendParam.length()>0) {URLConn.setRequestProperty("Content-Length", String.valueOf(sendParam.getBytes().length));dos = new BufferedWriter(new OutputStreamWriter(URLConn.getOutputStream(), "UTF-8"));dos.write(sendParam);dos.flush();}rd = new BufferedReader(new InputStreamReader(URLConn.getInputStream(), "UTF-8"));String line;while ((line = rd.readLine()) != null) {receive.append(line);}} catch (java.io.IOException e) {receive.append("访问产生了异常-->").append(e.getMessage());e.printStackTrace();} finally {if (dos != null) {try {dos.close();} catch (IOException ex) {ex.printStackTrace();}}if (rd != null) {try {rd.close();} catch (IOException ex) {ex.printStackTrace();}}URLConn.disconnect();}String content = receive.toString();LOGGER.info("content = "+content);return content;}public static String get(String sendUrl) {StringBuffer receive = new StringBuffer();HttpURLConnection URLConn = null;BufferedReader in = null;try {URL url = new URL(sendUrl);URLConn = (HttpURLConnection) url.openConnection();URLConn.setDoInput(true);URLConn.connect();in = new BufferedReader(new InputStreamReader(URLConn.getInputStream(), "UTF-8"));String line;while ((line = in.readLine()) != null) {receive.append(line);}} catch (IOException e) {receive.append("访问产生了异常-->").append(e.getMessage());e.printStackTrace();} finally {if (in != null) {try {in.close();} catch (java.io.IOException ex) {ex.printStackTrace();}in = null;}URLConn.disconnect();}return receive.toString();}public static String post(String sendUrl) {return post(sendUrl, null);}
}

最后

最后我想说,我们在做项目的过程中肯定会碰到不少问题,碰到问题不要怕,遇到问题,我们就解决问题。

其实,带着问题去学习是一种很高效的学习方式,比如说Vuex,我以前也看过文档,但是一直看不太懂,始终一知半解,直到这次手动设置token Cookie的时候一直无法实现自动登录,原来项目中用了Vuex,要用Vuex的方式去设置。

借此机会,我又重新学了一遍Vuex文档,带着问题去学,答案都在文档里找到了,这样的学习效果就好很多了。

所以,遇到问题的时候,是我们学习的最佳时机,不要浪费任何一个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/47638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 通讯录管理 完整代码

这份代码&#xff0c;是我从网上找的。目前是能运行。我正在读。有些不懂的地方&#xff0c;等下再记录下来。 有些地方的命名&#xff0c;还需要重新写一下。 比如: PersonInfo* info &address_book->all_address[address_book->size]; 应该改为&#xff1a; Perso…

使用SpringCloud搭建分布式配置中心

在现代的分布式系统中&#xff0c;配置管理是一个非常重要的组成部分。传统的做法是将配置文件放在每个服务的本地进行配置&#xff0c;这样的做法在规模较小的系统中还能够接受&#xff0c;但是当系统规模逐渐扩大时&#xff0c;配置管理将变得非常困难&#xff0c;容易出错。…

QT--文件操作和文件读写

文件操作和文件读写 QFile 类用于对文件进行操作&#xff0c;它继承自 QIODevice&#xff0c;可以进行读写操作。主要用于打开、关闭、读取、写入和管理文件。 1. 首先要指定文件路径 QFile fn(“文件路径”);也可以通过文件对话框来选择文件getOpenFileName 函数原型 QStr…

【大数据面试题】37 Doris 是怎么保证性能的?

一步一个脚印&#xff0c;一天一道大数据面试题 博主希望能够得到大家的点赞收藏支持&#xff01;非常感谢 点赞&#xff0c;收藏是情分&#xff0c;不点是本分。祝你身体健康&#xff0c;事事顺心&#xff01; Doris 是当下大热的 MPP 数据库&#xff0c;下面来聊聊它如何保证…

AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理

AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理 目录 AGI 之 【Hugging Face】 的【零样本和少样本学习】之一 [构建标记任务] / [ 基线模型 ] 的简单整理 一、简单介绍 二、零样本学习 (Zero-shot Learning) 和少样本学习…

【博主推荐】HTML5实现简洁的实用的个人网站、个人主页七个页面源码

文章目录 1.设计来源1.1 个人主页界面1.2 关于我界面1.3 我的技能界面1.4 我的经验界面1.5 我的教育界面1.6 我的项目界面1.7 联系我界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;…

ipsec协议簇(详解)

IPSEC协议簇 IPSEC协议簇 --- 基于网络层的&#xff0c;应用密码学的安全通信协议组 IPV6中&#xff0c;IPSEC是要求强制使用的&#xff0c;但是&#xff0c;IPV4中作为可选项使用 IPSEC可以提供的安全服务 机密性 --- 数据加密 完整性 --- 防篡改可用性 数据源鉴别 -- 身份…

长效代理IP如何选用及代理服务分析

在这个数据为王、信息瞬息万变的时代&#xff0c;长效代理IP成为了众多开发者、数据科学家乃至普通网民手中的一把利器。它不仅能帮助我们解决地域管理&#xff0c;还能在保护隐私的同时&#xff0c;确保数据传输的稳定与安全。但面对市面上琳琅满目的代理服务&#xff0c;如何…

IVI(In-Vehicle Infotainment,智能座舱的信息娱乐系统)

IVI能够实现包括三维导航、实时路况、辅助驾驶等在线娱乐功能。 IVI人机交互形式&#xff08;三板斧&#xff09;&#xff1a;声音、图像、文字 IVI人机交互媒介I&#xff08;四件套&#xff09;&#xff1a;中控屏幕&#xff08;显示、触控&#xff09;、仪表显示、语言、方…

目标检测 | YOLO v1、YOLO v2、YOLO v3与YOLO v3 SPP理论讲解

☀️教程&#xff1a;霹雳吧啦Wz ☀️链接&#xff1a;https://www.bilibili.com/video/BV1yi4y1g7ro?p1&vd_sourcec7e390079ff3e10b79e23fb333bea49d 一、YOLO v1 针对于two-stage目标检测算法普遍存在的运算速度慢的缺点&#xff0c;YOLO创造性的提出了one-stage目标检测…

2024-07-20 Unity插件 Odin Serializer2 —— 序列化教程

文章目录 1 由根对象决定序列化2 实现 Odin 序列化器2.1 继承已有序列化类2.2 自定义序列化类 3 避免 Unity 无限深度警告4 指定序列化秘钥4.1 External String Reference Resolver4.2 External GUID Reference Resolver4.3 External Index Reference Resolver 4 功能与限制4.1…

为什么我不建议用Excel做进销存系统?

进销存管理系统是一个企业中非常关键的部分&#xff0c;它涉及商品的采购、销售和库存管理等复杂流程。虽然EXCEL作为一个办公软件&#xff0c;它的通用性和灵活性使其能够处理这类数据&#xff0c;但实际上&#xff0c;使用它来构建专业的进销存管理系统存在一些明显的局限性。…

haproxy服务介绍

haproxy 搭建使用开启HAProxy的界面UI配置负载均衡配置web代理 HAProxy&#xff08;High Availability Proxy&#xff09;是一个高性能的TCP/HTTP负载均衡器和代理服务器&#xff0c;广泛用于提升Web应用的可用性和性能。[官网说明](https://docs.haproxy.org/2.8/intro.html#3…

NLP: 词袋模型和TFIDF模型

文章目录 词袋模型TF-IDF模型词汇表模型 词袋模型 文本特征提取有两个非常重要的模型&#xff1a; 词集模型&#xff1a;单词构成的集合&#xff0c;集合自然每个元素都只有一个&#xff0c;也即词集中的每个单词都只有一个。 词袋模型&#xff1a;在词集的基础上如果一个单词…

autoxjs的安装与配置

AutoxJs 是一个基于 JavaScript 的自动化工具&#xff0c;用于在 Android 平台上创建自动化脚本。它是在原 Auto.js 项目的基础上继续维护和升级而来的。 AutoxJs 的优势主要包括以下几点&#xff1a; 无需 root 权限&#xff1a;可以在没有 root 权限的设备上运行大部分功能&…

JavaWeb系列二十三: web 应用常用功能(文件上传下载)

文件上传下载 基本介绍文件上传基本原理文件上传应用实例文件上传注意事项和细节 文件下载基本原理文件下载应用实例文件下载注意事项 ⬅️ 上一篇: JavaWeb系列二十二: 线程数据共享和安全(ThreadLocal) &#x1f389; 欢迎来到 JavaWeb系列二十三: web 应用常用功能(文件上传…

创建最佳实践创建 XML 站点地图--SEO

您是否正在努力让您的网站被搜索引擎索引&#xff1f;您想提高您网站的搜索引擎知名度吗&#xff1f;如果是&#xff0c;您可能会错过 XML 站点地图的重要性。XML 站点地图在改善您网站的 SEO 方面发挥着至关重要的作用。‍ XML 站点地图是您网站结构的蓝图&#xff0c;可帮助…

YOLOv5项目梳理

1 项目介绍 参考项目&#xff1a;YOLO项目 1.1训练模型 YOLOv5模型 train.py 训练预训练模型 ... ... def parse_opt(knownFalse):# 命令行参数解析器初始化parser argparse.ArgumentParser()# 初始权重路径&#xff0c;默认为 ROOT / yolov5s.pt&#xff0c;用于指定模…

Navicat 17 for Mac 数据库管理软件

Mac分享吧 文章目录 效果一、准备工作二、开始安装1. 双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕。2. 应用程序/启动台显示Navicat图标&#xff0c;表示安装成功。 二、运行测试运行后提示&#xff1a;“Navicat Premium.pp”已损坏&#x…

在qt的c++程序嵌入一个qml窗口

//拖拽一个QQuickWidget c端和qml通信的桥梁 找到qml的main.qml的路径 ui->quickWidget->setSource(QUrl::fromLocalFile("../../../code/main.qml"));// QML 与 Qt Widgets 通信//窗口就成了一个类实例对象pRoot (QObject*)ui->quickWidget->rootObje…