前端

1.采用双token机制【access_token+refresh_token】a.登录成功之后,返回这两个token;b.访问接口时带上access_token访问;c.当access_token过期时,通过refresh_token来刷新,拿到新的access_token和refresh_token有了refresh_token之后,只要带上这个token就能标识用户,不需要传用户名密码就能拿到新token。而access_token一般过期时间设置的比较短,比如30分钟,refresh_token设置的过期时间比较长,比如7天。这样,只要你7天内访问一次,就能刷新token,再续7天,一直不需要登录。但如果你超过7天没访问,那refresh_token也过期了,就需要重新登录了具体执行1.在响应拦截器中通过response返回的状态码判断状态,比如401说明token过期2.如果获取不到【刷新】令牌,说明未登录状态,如果获取到【刷新】令牌则执行刷新令牌操作执行顺序和逻辑初始请求返回401:首次遇到需要刷新令牌的情况时,将isRefreshToken设置为true并尝试刷新令牌。等待刷新:此时,其他进来的请求检测到isRefreshToken为true,知道令牌正在刷新,因此将自己加入到requestList队列中,等待令牌刷新完成。令牌刷新成功:一旦令牌刷新成功,执行requestList.forEach(cb=>cb()),遍历执行队列中的所有请求,这时使用的是新令牌。令牌刷新失败:如果令牌刷新失败,根据具体的错误处理逻辑,可能直接清空队列(避免重复请求)并处理登录状态。以下为响应拦截器代码//响应拦截器axiosInstance.interceptors.response.use(asyncres=>{//未设置状态码则默认成功状态constcode=res.data.code||200;//获取错误信息constmsg=res.data.msgif(code===401){//如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了if(!isRefreshToken){isRefreshToken=true;//1.如果获取不到刷新令牌,则只能执行登出操作//setTimeout(()=>{},1000)if(getRefreshToken()==null||getRefreshToken().trim().length==0){isRefreshToken=falsereturnhandleAuthorized();}//2.进行刷新访问令牌try{constrefreshTokenRes=awaitrefreshToken()if(refreshTokenRes.code==0){//2.1刷新成功,则回放队列的请求+当前请求setToken(refreshTokenRes.data)requestList.forEach(cb=>cb())returnaxiosInstance(res.config)}}catch(e){//为什么需要catch异常呢?刷新失败时,请求因为Promise.reject触发异常。//2.2刷新失败,只回放队列的请求//requestList.forEach(cb=>cb())//提示是否要登出。即不回放当前请求!不然会形成递归requestList=[]isRefreshToken=falsereturnhandleAuthorized();//return;}finally{requestList=[]isRefreshToken=false}}else{//添加到队列,等待刷新获取到新的令牌returnnewPromise(resolve=>{requestList.push(()=>{res.config.headers['Authorization']='Bearer'+getAccessToken()//让每个请求携带自定义token请根据实际情况自行修改resolve(axiosInstance(res.config))})})}}elseif(code!==200){if(msg==='无效的刷新令牌'||msg==='用户未扫码'){//hardcoding:忽略这个提示,直接登出}else{Notification.error({title:msg})}returnPromise.reject('error')}else{returnres}},error=>{let{message}=error;if(message==="NetworkError"){message="系统正在维护升级中";}elseif(message.includes("timeout")){message="系统接口请求超时";}elseif(message.includes("Requestfailedwithstatuscode")){if(message.substr(message.length-3)==='401'){returnhandleAuthorized()}else{message="系统接口"+message.substr(message.length-3)+"异常";}}Message({message:message,type:'error',duration:5*1000})returnPromise.reject(error)})functionhandleAuthorized(){if(!window.location.href.endsWith("loginPage")){//loginPage页面不显示提示Message({message:'无效的会话,或者会话已过期,请重新登录。',type:'warning',duration:1*1000})}returnPromise.reject('无效的会话,或者会话已过期,请重新登录。')}

2024-2-5 135 0
前端

registService.js//引入axios库importaxiosfrom"axios";importapiListfrom"./apiList";import{Message,MessageBox,Notification}from'element-ui'import{getAccessToken,getRefreshToken,refreshToken,setToken}from'./auth'import{getNextUrl}from'./urlManager';console.log(process.env.NODE_ENV,"<<process.env.NODE_ENV");//设置请求头constheaders={"Content-Type":"application/json","Accept":"application/json","Authorization":"Bearersk-vL5NugbRg65n5s346KxEo3foCebFJURWTk5iGpGntMZFsJMB"};//---------------------------------------分界线--------------------------------------------------------switch(process.env.NODE_ENV){//可以在根目录的package.json配置NODE_ENVcase"production":axios.defaults.baseURL="生产环境地址";break;case"test":axios.defaults.baseURL="测试环境地址";default:axios.defaults.baseURL="开发环境地址";}//Axios无感知刷新令牌,参考https://www.dashingdog.cn/article/11与https://segmentfault.com/a/1190000020210980实现//请求队列letrequestList=[]//是否正在刷新中letisRefreshToken=false/*设置请求传输数据的格式只支持POST请求,根据实际要求决定设置不设置*/axios.defaults.headers["Content-Type"]="application/json";//axios.defaults.headers["Content-Type"]="application/x-www-form-urlencoded";//---------------------------------------分界线--------------------------------------------------------//创建axios实例constaxiosInstance=axios.create({headers:headers,timeout:60000//设置请求超时时间});/**设置请求超时时间和跨域是否允许携带凭证*/axios.defaults.timeout=0;axios.defaults.withCredentials=true;//定义请求拦截器axiosInstance.interceptors.request.use((config)=>{config.baseURL=getNextUrl(config.url);//设置下一个URL//负载均衡//请求发送前对请求配置进行处理,例如加入token等操作//在这里可以做一些全局的请求拦截处理//获取本地存储中的tokenif(config.url.endsWith("loginByPhone")){//登录接口不携带Authorization//config.headers['Authorization']=''//什么都不做}else{config.headers['Authorization']='Bearer'+getAccessToken()//让每个请求携带自定义token请根据实际情况自行修改}returnconfig;},(error)=>{//对请求错误进行处理returnPromise.reject(error);});//响应拦截器axiosInstance.interceptors.response.use(asyncres=>{//未设置状态码则默认成功状态constcode=res.data.code||200;//获取错误信息constmsg=res.data.msgif(code===401){//如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了if(!isRefreshToken){isRefreshToken=true;//1.如果获取不到刷新令牌,则只能执行登出操作//setTimeout(()=>{},1000)if(getRefreshToken()==null||getRefreshToken().trim().length==0){isRefreshToken=falsereturnhandleAuthorized();}//2.进行刷新访问令牌try{constrefreshTokenRes=awaitrefreshToken()if(refreshTokenRes.code==0){//2.1刷新成功,则回放队列的请求+当前请求setToken(refreshTokenRes.data)requestList.forEach(cb=>cb())returnaxiosInstance(res.config)}}catch(e){//为什么需要catch异常呢?刷新失败时,请求因为Promise.reject触发异常。//2.2刷新失败,只回放队列的请求//requestList.forEach(cb=>cb())//提示是否要登出。即不回放当前请求!不然会形成递归requestList=[]isRefreshToken=falsereturnhandleAuthorized();//return;}finally{requestList=[]isRefreshToken=false}}else{//添加到队列,等待刷新获取到新的令牌returnnewPromise(resolve=>{requestList.push(()=>{res.config.headers['Authorization']='Bearer'+getAccessToken()//让每个请求携带自定义token请根据实际情况自行修改resolve(axiosInstance(res.config))})})}}elseif(code!==200){if(msg==='无效的刷新令牌'||msg==='用户未扫码'){//hardcoding:忽略这个提示,直接登出}else{Notification.error({title:msg})}returnPromise.reject('error')}else{returnres}},error=>{let{message}=error;if(message==="NetworkError"){message="系统正在维护升级中";}elseif(message.includes("timeout")){message="系统接口请求超时";}elseif(message.includes("Requestfailedwithstatuscode")){if(message.substr(message.length-3)==='401'){returnhandleAuthorized()}else{message="系统接口"+message.substr(message.length-3)+"异常";}}Message({message:message,type:'error',duration:5*1000})returnPromise.reject(error)})functionhandleAuthorized(){if(!window.location.href.endsWith("loginPage")){//loginPage页面不显示提示Message({message:'无效的会话,或者会话已过期,请重新登录。',type:'warning',duration:1*1000})}returnPromise.reject('无效的会话,或者会话已过期,请重新登录。')}//定义API方法constregistService=(apis,axiosInstance)=>{constservice={};for(letapiNameinapis){const{url,method,local,dataType,isFileUpload,isBlob}=apis[apiName];service[apiName]=async(params,useLocal=false)=>{//是否使用本地json,如果useLocal为true并且json文件,则返回json文件if(useLocal&&local){//如果指定使用本地并且有local配置constlocalAxios=axios.create();returnlocalAxios.get(window.location.origin+'/data/'+local).then(response=>{returnresponse.data;}).catch(error=>{throwerror;});}else{try{letresponse=nullif((method=="post"&&dataType=="params")||method=="get"){response=awaitaxiosInstance({//如果是get请求或者是post请求但是参数携带和get一样url,method,params:params,});}elseif(isFileUpload){//post请求文件上传response=awaitaxiosInstance({//post请求(正儿八经post)url,method,data:params,headers:{'Content-Type':'multipart/form-data'}});}elseif(isBlob){//post请求blobresponse=awaitaxiosInstance({//blobpost请求url,method,data:params,responseType:'blob'});}else{response=awaitaxiosInstance({//post请求(正儿八经post)url,method,data:params,});}returnresponse.data;}catch(error){throwerror;}}};}returnservice;};//导出API实例exportdefaultregistService(apiList,axiosInstance);apiList.jsconstapiList={//获取验证码getPhoneCode:{url:"/app-api/system/user/send-phone-verification-code",method:"post",},//登录接口loginAccount:{url:"/app-api/system/auth/loginByPhone",method:"post",local:"codess.json"//新增},//获取用户信息getUserInfo:{url:"/app-api/system/user/userinfo",method:"get",},//刷新tokengetNewToken:{url:"/app-api/system/auth/refresh-token",method:"post",dataType:"params"//datapost请求但是参数和get一样},//获取验证图片getValidateImg:{url:"/app-api/captcha/get",method:"post",dataType:"data"//data},//h滑动图片getSlideValidate:{url:"/app-api/captcha/check",method:"post",dataType:"data"//data},//h滑动图片refreshToken:{url:"/app-api/system/auth/refresh-token",method:"post",dataType:"params"//data},//图生图genrenateImg:{url:"/app-api/sd/txt2img",method:"post",dataType:"data"//data},//上传文件uploadFile:{url:"/app-api/backend-api/uploadFile",method:"post",isFileUpload:true},//获得语音生成generateSpeech:{url:"/app-api/backend-api/generateSpeech",method:"post",isBlob:true},//获得用户收藏信息favAppPage:{url:"/app-api/backend-api/favAppPage",method:"post",},//---------以下为绘图模块-------------//sd上传文件uploadSdResource:{url:"/app-api/sd/uploadResource",method:"post",isFileUpload:true}};exportdefaultapiList;auth.jsimportapifrom'./registService'constAccessTokenKey='myToken'constRefreshTokenKey='myRefreshToken'//==========Token相关==========//刷新访问令牌exportasyncfunctionrefreshToken(){constresponse=awaitapi.refreshToken({refreshToken:getRefreshToken()})returnresponse}exportfunctiongetAccessToken(){returnlocalStorage.getItem(AccessTokenKey)}exportfunctiongetRefreshToken(){returnlocalStorage.getItem(RefreshTokenKey)}exportfunctionsetToken(token){localStorage.setItem(AccessTokenKey,token.accessToken)localStorage.setItem(RefreshTokenKey,token.refreshToken)}exportfunctionremoveToken(){localStorage.removeItem(AccessTokenKey)localStorage.removeItem(RefreshTokenKey)}//==========账号相关==========constUsernameKey='USERNAME'constPasswordKey='PASSWORD'constRememberMeKey='REMEMBER_ME'exportfunctiongetUsername(){returnlocalStorage.getItem(UsernameKey)}exportfunctionsetUsername(username){localStorage.setItem(UsernameKey,username)}exportfunctionremoveUsername(){localStorage.removeItem(UsernameKey)}exportfunctionremovePassword(){localStorage.removeItem(PasswordKey)}exportfunctiongetRememberMe(){returnlocalStorage.getItem(RememberMeKey)==='true'}exportfunctionsetRememberMe(rememberMe){localStorage.setItem(RememberMeKey,rememberMe)}exportfunctionremoveRememberMe(){localStorage.removeItem(RememberMeKey)}exportclassgetToken{}urlManager.js(url管理+负载均衡)consturls=[//"http://ipv4.haehnoda.tech:8091","http://ai.huguangzhez.cn:90"//更多URL...];letcurrentUrlIndex=0;exportfunctiongetNextUrl(uri){if(uri&&uri.endsWith("getQrCode")){//return"https://api.huguangzhez.cn.cn"//走信任的ipreturn"http://ai.huguangzhez.cn:9293"//走信任的ip}consturl=urls[currentUrlIndex];currentUrlIndex=(currentUrlIndex+1)%urls.length;returnurl;}

2024-2-5 132 0
前端

闭包概念:闭包是JavaScript中的一个重要概念,它允许一个函数访问并操作函数外部的变量。闭包发生在一个函数内部创建另一个函数时,内部函数会记住并访问其被创建时的环境。闭包的特点访问外部变量:闭包可以访问定义它的函数中的变量,以及全局变量。记忆环境:即使外部函数已经执行完毕,闭包仍然可以记忆并访问外部函数的变量。这是因为闭包保留了包含它的函数的作用域链。封装性:闭包提供了一种封装私有变量的方式,使得这些变量不会被全局作用域直接访问,从而避免全局命名冲突。闭包的优点数据封装:闭包可以用来模仿私有属性和方法,提供对象封装的能力。保持状态:闭包可以在函数外部保持和操作内部变量的状态,适合用于实现回调、事件处理器和任何需要状态保持的场合。模块化代码:可以利用闭包来创建模块化和可重用的代码块,提高代码的组织性和可维护性。闭包的缺点内存消耗:闭包可能会导致比不使用闭包时更高的内存消耗,因为闭包的作用域链中的变量不会在外部函数执行完毕后立即被垃圾回收,只有当闭包本身被销毁后,这些变量的内存才会被释放。性能考虑:在一些性能敏感的应用中,过度使用闭包可能会导致内存占用过高或性能下降。理解难度:对于初学者来说,闭包的概念可能比较难以理解,尤其是闭包的作用域和生命周期。尽管闭包带来了一些性能和内存的考虑,但它们在JavaScript编程中提供了极大的灵活性和功能。合理使用闭包可以极大地提升代码的表达力和封装性。无感刷新token示例://如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了if(!isRefreshToken){//刷新令牌操作}else{//添加到队列,等待刷新获取到新的令牌returnnewPromise(resolve=>{requestList.push(()=>{res.config.headers['Authorization']='Bearer'+getAccessToken()//让每个请求携带自定义tokenresolve(axiosInstance(res.config))})})}在requestList.push(()=>{...})这里使用匿名函数(也称为箭头函数)而不是直接将需要执行的代码或一个已定义的函数推入requestList数组的原因涉及到JavaScript中的闭包和异步执行的特性。以下是几个关键点来解释为什么在这种场景下使用匿名函数是必要的:延迟执行:将函数推入数组而不是直接执行的代码或结果,意味着你希望将这个函数的执行延迟到未来的某个时间点。在这个例子中,当新的令牌被获取之后,数组中的每个函数将被依次取出并执行,这样做可以确保使用最新的令牌来重新发送HTTP请求。闭包:匿名函数创建了一个闭包,这意味着它可以捕获并保持对其创建时作用域中变量的引用。在这个例子中,匿名函数内部引用了res和getAccessToken等外部变量。通过这种方式,每个函数都能够访问到在其被推入数组时的具体res对象以及在执行时能够调用getAccessToken来获取当前的令牌。如果直接推入一段代码或一个非闭包函数,那么这些细节的上下文可能会丢失。关于数组队列中的执行过程res.config.headers['Authorization']='Bearer'+getAccessToken()这句代码会先执行,然后才会执行resolve(axiosInstance(res.config))。这里的执行流程如下:当这个匿名函数(推入requestList数组中的函数)被调用时,它首先会执行res.config.headers['Authorization']='Bearer'+getAccessToken()这一行代码。这一步的作用是更新请求配置(res.config)的Authorization头部,将其设置为最新获取的访问令牌(Token)。更新了请求头部之后,紧接着执行的是resolve(axiosInstance(res.config))。这一步调用axiosInstance,传入更新后的请求配置res.config作为参数,从而重新发送HTTP请求。resolve函数的调用标志着返回的Promise对象将被解决(即成功状态),并且其解决值是axiosInstance(res.config)的返回结果,这通常是另一个Promise对象,表示HTTP请求的异步操作。这种设计确保了在重新发起请求之前,请求的Authorization头部已经被更新为最新的令牌值,这是处理认证令牌更新逻辑中的一个典型步骤。通过这种方式,可以确保每次重试请求时都使用最新的认证信息,避免因为令牌过期而导致请求失败。

前端

parnetCom.vue<template><div><el-buttontype="primary"@click="onDialog">打开</el-button><Dialogtitle="Title":width="720":height="480":content="content":footer="true"cancelText="取消"okText="确认"switchFullscreen@close="onClose"@cancel="onCancel"@ok="onConfirm":visible="showDialog"/></div></template>data(){return{showDialog:false,content:"this.showDialog=true\n1111",}methods:{onDialog(content){localStorage.setItem('troubleDecList',[1,2,77777]);localStorage.setItem('name',"2#风机");//调用Dialog弹出对话框this.content="Somedescriptions...";this.showDialog=true;},onClose(){//关闭dialogthis.showDialog=false;//帮我写就JavaScript},onCancel(){//“取消”按钮回调this.showDialog=false;},onConfirm(){//“确定”按钮回调this.showDialog=false;},}Dialog.vue<template><divclass="m-dialog-mask"@click.self="onBlur"v-show="visible"><divclass="m-dialog":style="`width:${dialogWidth};height:${dialogHeight};`"><divclass="m-dialog-content"><svg@click="onFullScreen"v-show="!fullScreen&&switchFullscreen"class="u-screen"viewBox="6464896896"data-icon="fullscreen"aria-hidden="true"focusable="false"><pathd="M290236.4l43.9-43.9a8.018.01000-4.7-13.6L169160c-5.1-.6-9.53.7-8.98.9L179329.1c.86.68.99.413.64.7l43.7-43.7L370423.7c3.13.18.23.111.30l42.4-42.3c3.1-3.13.1-8.20-11.3L290236.4zm352.7187.3c3.13.18.23.111.30l133.7-133.643.743.7a8.018.0100013.6-4.7L863.9169c.6-5.1-3.7-9.5-8.9-8.9L694.8179c-6.6.8-9.48.9-4.713.6l43.943.9L600.3370a8.038.03000011.3l42.442.4zM845694.9c-.8-6.6-8.9-9.4-13.6-4.7l-43.743.7L654600.3a8.038.03000-11.30l-42.442.3a8.038.03000011.3L734787.6l-43.943.9a8.018.010004.713.6L855864c5.1.69.5-3.78.9-8.9L845694.9zm-463.7-94.6a8.038.03000-11.30L236.3733.9l-43.7-43.7a8.018.01000-13.64.7L160.1855c-.65.13.79.58.98.9L329.2845c6.6-.89.4-8.94.7-13.6L290787.6423.7654c3.1-3.13.1-8.20-11.3l-42.4-42.4z"></path></svg><svg@click="onFullScreen"v-show="fullScreen&&switchFullscreen"class="u-screen"viewBox="6464896896"data-icon="fullscreen-exit"aria-hidden="true"focusable="false"><pathd="M391240.9c-.8-6.6-8.9-9.4-13.6-4.7l-43.743.7L200146.3a8.038.03000-11.30l-42.442.3a8.038.03000011.3L280333.6l-43.943.9a8.018.010004.713.6L401410c5.1.69.5-3.78.9-8.9L391240.9zm10.1373.2L240.8633c-6.6.8-9.48.9-4.713.6l43.943.9L146.3824a8.038.03000011.3l42.442.3c3.13.18.23.111.30L333.7744l43.743.7A8.018.01000391783l18.9-160.1c.6-5.1-3.7-9.4-8.8-8.8zm221.8-204.2L783.2391c6.6-.89.4-8.94.7-13.6L744333.6877.7200c3.1-3.13.1-8.20-11.3l-42.4-42.3a8.038.03000-11.30L690.3279.9l-43.7-43.7a8.018.01000-13.64.7L614.1401c-.65.23.79.58.88.9zM744690.4l43.9-43.9a8.018.01000-4.7-13.6L623614c-5.1-.6-9.53.7-8.98.9L633783.1c.86.68.99.413.64.7l43.7-43.7L824877.7c3.13.18.23.111.30l42.4-42.3c3.1-3.13.1-8.20-11.3L744690.4z"></path></svg><svg@click="onClose"class="u-close"viewBox="6464896896"data-icon="close"aria-hidden="true"focusable="false"><pathd="M563.8512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.70-9.22.1-12.35.7L511.6449.8295.1191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.80-10.57.9-6.113.1L459.4512196.9824.9A7.957.95000203838h79.8c4.709.2-2.112.3-5.7l216.5-258.1216.5258.1c33.67.55.712.35.7h79.8c6.8010.5-7.96.1-13.1L563.8512z"></path></svg><divclass="m-dialog-header"><divclass="u-head">{{title}}</div></div><divclass="m-dialog-body":style="`height:calc(${dialogHeight}-156px);`">dsadsads<divclass="chilfd"></div><!--<divclass="chilfd"></div><divclass="chilfd"></div><divclass="chilfd"></div>--></div><divclass="m-dialog-footer"v-show="footer"><buttonclass="u-cancel"@click="onCancel">{{cancelText}}</button><buttonclass="u-confirm"@click="onConfirm">{{okText}}</button></div></div></div></div></template><script>exportdefault{name:'Dialog',props:{title:{//标题type:String,default:'提示'},content:{//内容type:String,default:''},width:{//宽度,默认640type:Number,default:640},height:{//高度,默认480type:Number,default:480},switchFullscreen:{//是否允许切换全屏(允许后右上角会出现一个按钮)type:Boolean,default:false},cancelText:{//取消按钮文字type:String,default:'取消'},okText:{//确认按钮文字type:String,default:'确定'},footer:{//是否显示底部按钮,默认显示type:Boolean,default:true},visible:{//对话框是否可见type:Boolean,default:false}},data(){return{fullScreen:false}},computed:{dialogWidth(){if(this.fullScreen){return'100%'}else{returnthis.width+'px'}},dialogHeight(){if(this.fullScreen){return'100vh'}else{returnthis.height+'px'}}},watch:{visible(to){if(to){this.fullScreen=false}}},methods:{onBlur(){this.$emit('close')},onFullScreen(){this.fullScreen=!this.fullScreen},onClose(){this.$emit('close')},onCancel(){this.$emit('cancel')},onConfirm(){this.$emit('ok')}}}</script><stylelang="less"scoped>@keyframesslidein{from{top:0;transform:translateY(0%);}to{top:50%;transform:translateY(-50%);}}.m-dialog-mask{position:fixed;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:10000;background:rgba(0,0,0,0.45);.m-dialog{position:relative;animation:slidein300ms;top:50%;//transform:translateY(-50%);-ms-transform:translateY(-50%);;/*IE9*/-webkit-transform:translateY(-50%);/*SafariandChrome*/margin:0auto;transition:all.3sease;.m-dialog-content{position:relative;background:#fff;border-radius:4px;box-shadow:04px12pxrgba(0,0,0,.1);.u-screen{.u-close();right:64px;}.u-close{width:16px;height:16px;position:absolute;top:19px;right:24px;fill:rgba(0,0,0,.45);cursor:pointer;transition:fill.3s;&:hover{fill:rgba(0,0,0,.75);}}.m-dialog-header{padding:16px24px;color:rgba(0,0,0,.65);border-radius:4px4px00;border-bottom:1pxsolid#e8e8e8;.u-head{margin:0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:22px;word-wrap:break-word;}}.m-dialog-body{padding:24px;font-size:16px;line-height:1.5;word-wrap:break-word;overflow:auto;transition:all.3s;display:flex;flex-wrap:wrap;//background-color:#0780ed;.chilfd{width:20%;height:40%;border-radius:10px;//border:1pxsolidgrey;//background-color:#0780ed;box-shadow:0009999emrgba(0,0,0,0.2);margin-left:20px;}}.m-dialog-footer{padding:10px16px;text-align:right;border-top:1pxsolid#e8e8e8;.u-cancel{height:32px;line-height:32px;padding:015px;font-size:16px;border-radius:4px;color:rgba(0,0,0,.65);background:#fff;border:1pxsolid#d9d9d9;cursor:pointer;transition:all.3scubic-bezier(.645,.045,.355,1);&:hover{color:#40a9ff;border-color:#40a9ff;}&:focus{color:#096dd9;border-color:#096dd9;}}.u-confirm{margin-left:8px;height:32px;line-height:32px;padding:015px;font-size:16px;border-radius:4px;background:#1890ff;border:1pxsolid#1890ff;color:#fff;transition:all.3scubic-bezier(.645,.045,.355,1);cursor:pointer;&:hover{color:#fff;background:#40a9ff;border-color:#40a9ff;}&:focus{background:#096dd9;border-color:#096dd9;}}}}}}</style>

2023-12-7 148 0
JavaScript

三维柱状图<template><divclass="container"><divref="barChart"style="width:1000px;height:700px"></div></div></template><script>exportdefault{mounted(){this.createChart();},methods:{createChart(){constmyChart=this.$echarts.init(this.$refs.barChart);constoffsetX=15;//bar宽constoffsetY=5;//倾斜角度constCubeLeft=this.$echarts.graphic.extendShape({shape:{x:0,y:0,},buildPath:function(ctx,shape){//会canvas的应该都能看得懂,shape是从custom传入的constxAxisPoint=shape.xAxisPoint;constc0=[shape.x,shape.y];constc1=[shape.x-offsetX,shape.y-offsetY+10];//左侧面左上点constc2=[xAxisPoint[0]-offsetX,xAxisPoint[1]-offsetY+5];//左侧面左下点constc3=[xAxisPoint[0],xAxisPoint[1]+0];//左侧面右下点ctx.moveTo(c0[0],c0[1]).lineTo(c1[0],c1[1]).lineTo(c2[0],c2[1]).lineTo(c3[0],c3[1]).closePath();},});//绘制右侧面constCubeRight=this.$echarts.graphic.extendShape({shape:{x:0,y:0,},buildPath:function(ctx,shape){constxAxisPoint=shape.xAxisPoint;constc1=[shape.x,shape.y];constc2=[xAxisPoint[0],xAxisPoint[1]+0];//右侧面左下点constc3=[xAxisPoint[0]+offsetX,xAxisPoint[1]-offsetY+5];//右侧面右下点constc4=[shape.x+offsetX,shape.y-offsetY+10];//右侧面右上点ctx.moveTo(c1[0],c1[1]).lineTo(c2[0],c2[1]).lineTo(c3[0],c3[1]).lineTo(c4[0],c4[1]).closePath();},});//绘制顶面constCubeTop=this.$echarts.graphic.extendShape({shape:{x:0,y:0,},buildPath:function(ctx,shape){constc1=[shape.x,shape.y+15];//顶部菱形下点constc2=[shape.x+offsetX,shape.y-offsetY+10];//顶部菱形右点constc3=[shape.x,shape.y-offsetX+10];//顶部菱形上点constc4=[shape.x-offsetX,shape.y-offsetY+10];//顶部菱形左点ctx.moveTo(c1[0],c1[1]).lineTo(c2[0],c2[1]).lineTo(c3[0],c3[1]).lineTo(c4[0],c4[1]).closePath();},});//注册三个面图形this.$echarts.graphic.registerShape("CubeLeft",CubeLeft);this.$echarts.graphic.registerShape("CubeRight",CubeRight);this.$echarts.graphic.registerShape("CubeTop",CubeTop);constMAX=[800,800,800,800,800,800,800];constVALUE=[210.9,260.8,204.2,504.9,740.5,600.3,119.0];constoption={backgroundColor:"rgba(17,42,62,1)",//"#012366",tooltip:{trigger:"axis",axisPointer:{type:"shadow",},formatter:function(params,ticket,callback){constitem=params[1];returnitem.name+":"+item.value;},},grid:{left:40,right:40,bottom:100,top:100,containLabel:true,},xAxis:{type:"category",data:["1月","2月","3月","4月","5月","6月","7月"],axisLine:{show:true,lineStyle:{color:"white",},},offset:25,axisTick:{show:false,length:9,alignWithLabel:true,lineStyle:{color:"#7DFFFD",},},axisLabel:{show:true,fontSize:16,},},yAxis:{min:0,//max:1200,//interval:200,type:"value",axisLine:{show:false,lineStyle:{color:"white",},},splitLine:{show:true,lineStyle:{type:"dashed",color:"rgba(255,255,255,0.1)",},},axisTick:{show:false,},axisLabel:{show:true,fontSize:16,},boundaryGap:["20%","20%"],},series:[{type:"custom",renderItem:function(params,api){constlocation=api.coord([api.value(0),api.value(1)]);return{type:"group",children:[{type:"CubeLeft",shape:{api,x:location[0],y:location[1],xAxisPoint:api.coord([api.value(0),0]),},style:{fill:"#18385A",},},{type:"CubeRight",shape:{api,x:location[0],y:location[1],xAxisPoint:api.coord([api.value(0),0]),},style:{fill:"#33718E",},},{type:"CubeTop",shape:{api,x:location[0],y:location[1],xAxisPoint:api.coord([api.value(0),0]),},style:{fill:"#307E8E",},},],};},data:MAX,},{type:"custom",renderItem:(params,api)=>{constlocation=api.coord([api.value(0),api.value(1)]);constisLastTwoBars=api.value(0)===VALUE.length-2||api.value(0)===VALUE.length-1;varcolor=isLastTwoBars?"red":newthis.$echarts.graphic.LinearGradient(1,1,1,0,[{offset:0,color:"#0097C8",//最左边},{offset:1,color:"#4CF0F9",},]);return{type:"group",children:[{type:"CubeLeft",shape:{api,xValue:api.value(0),yValue:api.value(1),x:location[0],y:location[1],xAxisPoint:api.coord([api.value(0),0]),},style:{fill:color,},},{type:"CubeRight",shape:{api,xValue:api.value(0),yValue:api.value(1),x:location[0],y:location[1],xAxisPoint:api.coord([api.value(0),0]),},style:{fill:color,},},{type:"CubeTop",shape:{api,xValue:api.value(0),yValue:api.value(1),x:location[0],y:location[1],xAxisPoint:api.coord([api.value(0),0]),},style:{fill:color,},},],};},data:VALUE,},{type:"bar",itemStyle:{color:"transparent",},tooltip:{},data:MAX,},],};//使用刚指定的配置项和数据显示图表。myChart.setOption(option);},},};</script>