首页

按钮的最佳尺寸到底是多少?

涛涛

很多设计师包括我在内对按钮尺寸有着颇多困惑。为什么很多产品甚至苹果本身并没有遵循 44pt 的标准规范?为什么有些场景下的 CTA 按钮那么小?按钮的最佳尺寸到底是多少?按钮规范背后到底是什么样的科学依据?这些依据可否量化?

emmmm,如果你和我一样有着这些困惑,本篇文章应该可以给你很多启发。

按钮尺寸对点击行为的影响

按钮的尺寸具体影响到的依旧是视觉和交互的两种能力。

视觉能力上很好理解。当一个元素尺寸越大,人眼就越容易抓捕到这个元素。所以那些越重要的东西,往往会给予更大的尺寸来强制用户注意到它,这也可以解释为什么甲方总喜欢不停地在背后指指点点嫌弃元素太小,就是因为这些元素对他们来说非常重要,只是他们没有我们那么专业,知道强调一个东西的手法不仅仅是放大一种策略。

因此,相对较大的按钮尺寸从视觉上,可以迅速捕获用户的注意力,对点击行为是有益的。

而在交互能力上,涉及到的依然是前篇提到的菲茨定律——目标尺寸越大,移动至目标所花费的时间就越短。所以,较大的按钮尺寸可以降低用户交互的交互成本,使得目标更加”易点“,对点击行为同样是有益的。

但是,按钮尺寸并非越大越好,一方面是避免视觉上的失衡,另一方面也会受到界面空间限制、以及场景差异等因素的影响。

规范中的定义

我们先来看下 iOS 的。苹果规定的最小点击区域是 44pt,这意味着一旦点击区域低于 44pt,将可能会出现点击失准的情况。当然,一些控件(标签栏图标、文字链)可以在视觉表现上只有 24pt*24pt,但是会在周围加入额外的填充使其达到 44pt。

但是,在实际的 iOS 原生产品界面中,很多按钮并未严格执行 44pt 这个数值。小于 44pt 的按钮比比皆是,比如信息页的发送、App Store 的获取、购买浮层的确认、添加 siri、导航类右上角的工具型按钮,它们的点击区域为按钮本身,但是均未达到 44pt。况且其中有一些还是非常典型的 CTA 按钮,比如 App Store 产品详情页中的获取按钮,它的高度仅仅是 27pt。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

而 Android 中的按钮建议尺寸是 56dp,但是和 iOS 一样存在着大量低于这个尺寸的情况。其中不乏那些 CTA 按钮。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

这些情况的发生其实也很好理解,每个按钮所对应的用户场景、业务诉求不同,因此并不能一招鲜用一个尺寸吃遍所有场景。但是,有没有一些科学的依据来可量化地解释按钮尺寸对点击的影响?

从 Apple Music 说起

著名产品设计师斯科特·赫尔夫就曾在他的文章《Using science to make truly tappable user interfaces》中提过,iOS9 的 Apple Music 在锁屏界面下的按钮过小,经常会发生无法准确点击的情况,他需要集中精力精确得点击才能完成任务。

不过苹果在 iOS10 之后,锁屏界面下的三个按钮、乃至进度、音量的控制球全部被显著地增大。这使得歌曲点击操作的错误率明显下降,不论是在什么场景下(你懂得,跑步、挤地铁这些不可控的场景下总是会有听歌的需求)都可以轻松地点击。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

而他为了解释按钮尺寸所带来的变化,引入了历史上著名的两个实验。

第一次实验

2006 年,芬兰 Oulu 大学,Maryland 大学和 Parck 学院的研究人员组成一个研究小组。他们的研究目标是,确定在触摸屏幕上单手使用最容易的按钮尺寸。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

他们进行了两组不同场景的实验。第一组让受试者执行一次性的任务,点击一个 CTA 按钮、复选框或者多选框;第二组让受试者执行多次连续的任务,比如输入电话号码。并且在实验期间,研究人员测试了每一种场景下按钮的尺寸。最终的实验结果表明,单个任务下,按钮尺寸小于 9.2mm 后错误率显著增加,而多次连续任务下,按钮尺寸小于 9.6mm 后的错误率显著增加。

特别的是,对于多次连续任务,9.6mm 到 11.5mm 之间的错误率基本不变。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

看到这,来稍微总结一下,9.2mm 和 9.6mm 是两个关键的尺寸节点。在单次任务和多次连续任务下,按钮尺寸分别小于 9.2mm 和 9.6mm 会导致错误率的攀升。这个结果和 MIT Touch Lab 研究得出的最佳热区尺寸 10mm 很接近。

第二次实验

当然,这还不算完。5 年后,德国两所大学的研究人员又进行了一项类似的研究,目的是确定触摸屏幕按钮的最佳大小。

他们的实验方法相对就很潮了。他们专门开发了一款安卓游戏,游戏玩法也很无脑:玩家必须要精准地点击到屏幕中任何地方飘动的任意尺寸的圆圈,游戏才能继续。并且速度越快,得分也就越高。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

这款游戏在上线之后被下载了 10w 次,并且悄咪咪得暗中记录了用户所有的点击行为,记录总量约为 1.2 亿次。

最后根据统计分析,得出了错误率和圆圈尺寸的图表关系。你可以看到,和 5 年前的实验同样,呈现类似的指数关系。研究人员根据图表发现:

在圆圈尺寸小于 12mm 后,错误率开始逐步提升。在尺寸小于 8mm 之后,错误率高达 40%以上。并且研究还发现,在圆圈尺寸超过 12mm 之后,玩家的正确率并没有得到显著的提升。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

结论

由上述的两个实验,我们可以概括出一些有用的结论。

  • 根据各自的实验,在目标尺寸分别小于 9.2mm、9.6mm 或者 12mm 后,均会导致错误率的攀升;
  • 当目标尺寸增加到一定程度之后,正确率基本保持不变。

那么,按钮的最佳尺寸到底是多少呢?

斯科特将实验得出的关键尺寸与苹果、谷歌和微软三大规范进行结合,发现了一些有意思的现象——

  • iOS 的 44pt 对应到实际尺寸为 6.9mm,约 7mm;
  • Android 的 56pt 对应到实际尺寸为 8.8mm,约 9mm;
  • 而微软的 9mm+两边 2mm 的热区,对应的实际尺寸为 13mm。

可以看到 Android 和微软的尺寸,基本对应到了这两项实验得出的关键尺寸。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

最后,再来看下开头 iTunes 的锁屏界面的按钮。可以看到从 iOS9 到 iOS10,苹果将按钮可点范围由 7mm(44pt)扩大至 12mm(82pt),结果也正好符合了微软的规范。看到这里,你肯定更困惑了——按钮的最佳尺寸到底是多少?

其实,并不存在什么按钮的最佳尺寸。

不论是 iOS 的 44pt,Android 的 56dp,还是微软的 82pt,都需要具体情况具体分析。界面布局、用户场景、业务诉求等等,都属于按钮尺寸的影响因素。

比如下面这些 iOS 端产品的 CTA 按钮,它们的尺寸最小到 26pt,最大到 87pt,而且每个产品内部的 CTA 按钮也存在差异。你能说出这些按钮哪一个是最佳尺寸吗?

按钮的最佳尺寸到底是多少?这篇给你权威答案!

当然,我们起码可以去界定一些相对可控的范围。

这里我简单根据斯科特文章中的结论,结合市面主流产品的情况划分出按钮的几类尺寸:

1. 常规场景、局部模块

比如 App Store 的产品详情页的获取,知乎中个人主页的关注,都属于当前页的局部模块,点击之后通常是状态的变化或者出现新的弹层。这些按钮的尺寸我建议控制在 28pt~40pt 之间。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

2. 常规场景、全局模块、强业务属性

比如微信个人页的添加好友、各大电商商品详情页的加购、登录注册页的登录注册等等。这些页面的 CTA 按钮隶属于页面全局,所以可以给它极高的权重、甚至全局吸底展示(如详情页),以更快地促进点击。通常,这类按钮在常规场景下具备了最大尺寸。我个人的建议是保持在 40pt~60pt 之间。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

3. 不可控场景

不可控场景的意思就是,用户点击按钮时所处的场景可能比较特殊,并且这种特殊的场景很可能给用户带来一系列无法掌控的风险。

比如 keep 在跑步场景下的按钮,就需要充分考虑到跑步时不稳定的状态,如果按钮和常规场景一样,那很容易发生无法准确点击的情况,增加意外事故发生的概率;包括来电场景、地图导航场景、快递取件场景等等,都属于不可控的场景。各位可以自己代入脑补一下,这些场景中无法准确点击时容易产生什么样的后果。

所以这些场景中的按钮就得够大,以尽可能覆盖到那些极端的不可控情况。我个人的建议保持在 60pt~90pt 之间。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

当然,这仅仅是很粗略的参考区间值,如何结合现有业务诉求、用户场景需求等因素去合理地界定才是重中之重。如果为了追求最佳效果或者尺寸的一致性,反而有些舍本逐末的意思。

其实从知乎去年 10 周年的大改版可以看到一些有意思的细节。很多按钮的高度比以往更高了。比如盐选会员的续费按钮,由之前的 36pt 提升到了 40pt。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

个人主页的关注按钮也由 28pt 提升到了 32pt,你仔细看的话,按钮的宽度也发生了变化,从之前的 90pt 提升到了 100pt。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

邀请回答界面中的写回答按钮,也由原本的文字链,提升到了实心按钮,高度则直接复用了关注按钮的尺寸——32pt。哦,不好意思,这应该是按钮设计形式上的改变。

按钮的最佳尺寸到底是多少?这篇给你权威答案!

总之一句话,知乎这次的改版,CTA 按钮的尺寸更大了。我们从尺寸对点击行为的影响可以推导出,这次改版背后更为明确的业务目标——促进UGC内容生产、促进关系链沉淀(一旦沉淀了复杂的关系链,用户也就更难以离开平台)以及会员付费转化。



蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png



文章来源:优设   作者:转行人的设计笔记

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务




如何快速理解客户设计需求?

seo达人

11.jpeg

作为一个设计者,我们再给客户做设计方案的时候,大家会发现客户的需求点,很难去理解。并不是设计的技术水平问题,而是再理解客户的需求点上出现了问题。就算你设计的东西在同行看来已经很好了。但是客户往往不是很满意。客户再要求我们做设计的时候,其实心里已经有了大概的想法,只是自己无法通过设计去实现而已。我们要做的是用自己的专业知识全区完成客户的想法,已达到客户满意的效果。那么我们如何快速理解客户的设计需求呢?

22.jpeg

第一:设计的能力加强提升,拥有过硬的技术:

客户的需求形形色色,运用到的技能要全面,这是最为基础的点。如果连技术都不过关,就算理解了,也做不到。因此作为一个设计者,我自身就要拥有过硬的技能。而不是滥竽充数,忽悠客户,以低标准去做设计。

第二:换位思考,从客户的角度去想问题:

换位思考就是要从客户的角度去思考。只有从客户的角度看问题,我们才能更好去理解客户的需求。多做假设性思考,如果我是客户,我要注意哪些问题。那些会对我有影响,那些会对我更有利。这样才能做出让客户满意的设计,在这竞争激烈的时代客户就是上帝。不能把握号每一个客户,对于我们来说是最大的损失。就算你不是老板,但是你不会老板想问题,你到哪都不受欢迎干不久,就算你技术再好,能力再强。

第三:用自己的专业角度给予意见。

客户他们站在他们的角度就思考,但是他们并不是专业的,有可能惠发生一些致命性的错误,但是他们不知道,也不觉得这是个问题。那么我们作为专业的工作者,我们也要给专业的意见或者是建议。例如,最新的广告法,规定那些用词是违规的、那些字体不用商用、设计那些颜色搭配是不利于展示的等等。

33.jpeg


 

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

微信图片_20210513163802.png

 

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

 

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

 

vue电商后台管理系统保姆级教程(八)——角色列表结构功能

前端达人

8、角色列表

8.1 通过路由展示角色列表组件

在power中新建一个roles.vue(角色列表)初始化基本结构:
在这里插入图片描述

在路由中加载导入:
在这里插入图片描述
在这里插入图片描述

8.2 绘制基本布局结构并获取列表数据

效果图:
在这里插入图片描述
面包屑导航:
在这里插入图片描述
卡片视图:
在这里插入图片描述
API:
在这里插入图片描述
渲染数据:
定义数据date:
在这里插入图片描述
生命周期函数:
在这里插入图片描述

8.3 渲染角色列表数据

先渲染索引列:
在这里插入图片描述
优化样式:
在这里插入图片描述

在索列之前加和 展开列:
在这里插入图片描述

8.4 说明角色列表需要完成的功能模块

现在还没有具体的样式,后面会写
角色信息的添加,删除功能前面做了,所以这里就不写了

点击对应按钮出现对应的弹出层,然后执行对应的操作。

8.5 分析角色下权限渲染的思路

在展开行中拿到渲染的数据
在请求数据列表中返回了数据children,通过作用域插槽来拿
在这里插入图片描述
实现效果:
在这里插入图片描述
拿到数据之后通过三层for循环渲染出对应的ui结构

8.6 通过第一层for循环渲染一级权限

先做一下栅格布局,区分出三个区域用来放三层for循环:

scope.row是当前对象的数据。 .children 是一级权限的数据, :key每次循环绑定一个唯一的key值,通过插值表达式渲染出数据
在这里插入图片描述
放在el-tag标签中美化样式
在这里插入图片描述

8.7 美化一级权限

思路:
每行之间隔开距离,给每行放一个buttom的边框,第一行放一个top边框(这里使用三元表达判断) 加图标
在这里插入图片描述
每行都放buttom,利用三元表达式,如果i1===0.则bdtop否则空
在这里插入图片描述

8.8 通过二层for循环渲染二级权限

使用栅格布局分出2级权限和3级权限,,方法和之前的一样。
在这里插入图片描述
添加边框 线(上边框)第一个不添加,利用三元表达式判断:
在这里插入图片描述
动态绑定class类,然后判断
在这里插入图片描述
最终效果:
在这里插入图片描述

8.9 通过第三层for循环渲染三级权限

栅格布局:
注使用item2的children
在这里插入图片描述
通过作用域插槽拿到数据 children 拿到子组件数据,通过栅格分隔样式,动态绑定边框值。

8.10 美化角色权限下的UI结构

设置最小宽度:
设置body的最小宽度为1366px
在这里插入图片描述

角色列表设置居中:
定义一个类:哪里用到往哪里加
在这里插入图片描述
最后把代码插槽删了

8.11 点击删除权限按钮弹出确认提示框

使用element组件中的可移除标签属性:
在这里插入图片描述
closable属性:
在这里插入图片描述
绑定点击事件:
removeRightById:
在这里插入图片描述
定义事件:
在这里插入图片描述
然后弹框提示用户是否要删除,使用的还是element组件L

在这里插入图片描述
catch捕获错误:在这里插入图片描述
结果判断用户操作:
在这里插入图片描述
这里只是打印了结果,没有向后端发起请求。

8.12 完成删除角色下指定权限的功能

上一节点击了删除按钮后需要向后端发起删除的请求,删除用户在数据库中的权限:
API:
在这里插入图片描述
发起delect请求:
先把item3.id传进去:
在这里插入图片描述
在处理函数的形参中j接收一下:
在这里插入图片描述
然后再发起delect请求的时候用之前的参数字符串拼接
然后进行判断:
在这里插入图片描述
有个缺点:删除之后展开栏就被关闭了 解决办法:
在这里插入图片描述
把close权限赋值给一级标签二级标签
在这里插入图片描述



蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png


文章来源:csdn   作者:十九万里

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

Jquery选择器中使用变量实现动态选择例子

前端达人

这篇文章主要介绍了Jquery选择器中使用变量实现动态选择例子,这样做的好处我们可以动态选择一些元素,核心思想其实就是用字符串组合,需要的朋友可以参考下


例子一:

例子二:


<table>
  <tr>
    <th>用户名</th>
    <th>状态</th>
  <tr>
  <tr>
    <td>张三</td>
    <td data-uid="10000">正常</td>
  <tr>
  <tr>
    <td>李四</td>
    <td data-uid="10001">冻结</td>
  <tr>
  <tr>
    <td>王二麻子</td>
    <td data-uid=10002>冻结</td>
  <tr>
</table>
 
<script type="text/javascript">
$(document).ready(function(){
  var uid = 1001;
  $("td[data-uid = "+ uid +"]").html('正常');
}
</script>





<script type="text/javascript">
 $(function(){
  alert(123);
  var v=4;
  var test=$("input[type='radio'][value='"+v+"']");//直接拼接字符串就可以了
  console.info(test);
  var testValue=test.attr({"checked":true});
  console.info(testValue);
 });
 </script>
  
 <body>
  This is my JSP page. <br>
  <table>
 <tr>
 <td>性别:</td>
 <td>
  <input name="sex" type="radio" value="0"/>男 0
  <input name="sex" type="radio" value="1"/>女 1
  <input name="sex" type="radio" value="2"/>女 2
  <input name="sex" type="radio" value="3"/>女 3
  <input name="sex" type="radio" value="4"/>女 4
 </td>
 </tr>
  </table>
 </body>




例子三、jQuery中选择器参数使用变量应该注意的问题

这是原来的代码


var li_index = $(this).index();
 
var $content_index = li_index + 2;
 
var $content_progress = $(“div.content:eq(” + $content_index + “)”);
 
var $newavalue = $(this).find(“a”).attr(“name”);
 
var $resource = $(this).find(“a”).html().replace(“首页”,$newavalue);
 
var $afterresource = $resource.replace($newavalue,””);
 
var $afterresource = $newavalue + $afterresource.replace(“首页”,$newavalue);


实现的是关键词替换,不过到第三行时候不执行了,调试啊,替换啊,都不行。 从早上到刚才一直在各种群里面发问,终于 …… 俺们大本营 的Lomu大神一阵见血:

你的写法不对

要连接符

$(“div.content:nth-child($content_index)”);

$(“div.content:nth-child(” + $content_index + “)”);


关键是外面有引号

有引号被当字符串处理了

说真的现在感觉,有些基础的东西出错,光靠自己调试根本找不出问题所在。比如刚才那个 + 号,我看书就没见过。出现这种错误百度也不知道什么关键词。真不知道  选择器 里面用变量 还要用到+号,那个《锋利的jQuery 》也没有明确的说 选择器 里面用变量 还要用到+号,包括我们的w3cschool。





  蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png


文章来源:脚本之家   作者:junjie  

分享此文一切功德,皆悉回向给文章原作者及众读者.

免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务



网站建设-如何建设一个优质的网站?

seo达人

 

网站建设-如何建设一个优质的网站?

1.jpeg网站建设

互联网时代的到来,作为一个企业你如果还没有属于自己的网站,那么你就真的“out”了,互联网的日流量达到上亿,这些不是简单的点击率,而是一笔真真正正的大钱。如果你还不会互联网,不了解互联网,那么这些都与你毫无关系。而网站建设是企业面向互联的主要窗口之一。也是大家公认渠道之一。

网站建设对于企业来说意义重大,它不仅是企业展示的窗口那么简单。互联网时代下,众多企业纷纷进行互联网转型,那是因为这些企业的领导者拥有者敏锐的嗅觉,具有洞察时代大趋势的能力。网站建设使他们首先要做的事情。那么如何建设一个优质的网站呢?

2.jpeg网站建设

第一:网站建设平台要有强大开发团队

网站建设是一门技术活,没有强大的技术作为支撑,就无法保障网站的安全性和功能的实现。开发团队的强大决定了网站质量与品质。拥有一稳定的后台,才能确保企业网站的一系列工作顺利进行。

第二:网站建设要有专业的设计团队

一个网站就好比一个门面,设计人员就是装修的师傅。门面不好看给客户的第一印象就是不专业,第一印象分就大打折扣,不能引起客户的重视。设计的不合理,就会影响到客户的体验度。一个没有体验度网站,很难留住客户,进行流量的转化。一切设计以客户的体验度为核心。

第三:网站建设要跟上时代的步伐

时代变,网站建设的功能和需求点也在变,只有去适应时代的打造需求,企业才能在竞争激烈的时代下生存。功能的更新换代也要跟上时代的步伐,才能满足客户的体验需求。客户才会为我们买单。

3.jpegSEO优化

第四:网站建设要有利于SEO优化

网站建设只是完成面建设,而我们建设网站的目的是为了,引来流量,转化流量,实现利润转化。一个没有不利于SEO优化的网站,就像埋藏再深山的金山,难以被发现,吸引不到客户。SEO的意义在于打通前往金山的通道。海、陆、空全方位立体打通,让客户络绎不绝前往。金山才得以被发现,被开发。

第五:网站坚持维护更新

这个时代不是大鱼吃小鱼的时代,而是快鱼吃慢鱼的时代。只有不断的去完善更新,才能适应生存。网站也一样,只有不断优化升级才能不被淘汰。

第六:网站建设要符合国家政策

要及时了解国家的相关法律法规以及一些相关的政策,例如最新的广告法,不能只用不符合要求的字眼。例如分销系统,不能越过法律底线。那些文字可以商业,哪些是有版权的等。

以上是一些个人的网站建设个人见解,如有更好的意见也欢迎大家一起探讨,相互学习。

 

 


蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png



文章来源:SEO

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。


 

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

用户错了吗? 防错与容错

涛涛

据说只要可能犯错,就一定会有人犯错,用户犯错的可能性比我们想象中要大得多得多得多
做好防错与容错,可以让用户更顺畅...




据说只要可能犯错,就一定会有人犯错,用户犯错的可能性比我们想象中要大得多得多得多得多。

做好防错与容错,可以让用户更顺畅的达成自己的目标。


恩,用户总是那么不可理喻对吧

都做得那么明显了,还犯错。该提醒的也提醒了...诶~

打住,打住,当用户犯错时,别急着怪他,我们需要先判断清楚。


一、用户真的错了吗?

用户委屈的说:“来来来,我就想下载一个资源,大家都是讲道理的设计师,来评评理,我错在哪里?”

这,这... 每一步的确都好像没问题,那,问题究竟在哪?

作为设计师的你是不是已经敏锐的察觉到了什么?

对,关键点在 ' 67bh ' 这里。都是空格惹的祸!

那好,你准备好方案救用户了吗?......(这里理论上应该停顿30s以上吧)。

当然,现在不会公布答案,方式有好多种。留着你看完文章中间部分再跟大家一起找方案吧。

这一部分的重点不是方案,而是思维。


很多时候,用户并没有错,作为一个有担当的设计师,我们可不能甩锅。

有了这个前提,我们就可以去掉心中的那些烦躁和埋怨...开始愿意伸出援手,去拯救用户了。


二、如何避免用户“犯错”

对,这个疑问就已经包含了解决方案了。

真的感叹中文的博大精深,避 (防错)、免 (容错)。

为了让大家能更好的理解什么是防错和容错,我特意举一个例子哈~

(什么,防错容错你都懂?你怎么知道我讲的跟你想的一样呢,既然这么厉害赶紧做下面这道题。)

对,就是图片里那个拿滑板的家伙,估计是第一次来这片海滩,有鲨鱼都不知道,还使劲忘海里冲...


从避(防错)的角度来看,你有什么方法帮他呢?(先不考虑哪种方法更好也不要考虑成本哈,能达到目的的都行。)

友情提示:防错的目的是让用户在操作之前,尽量减少用户出错的可能性。


看看的你脑袋是不是真的转得飞起...

我这边有几个想法,看看我们有没有缘,想到一块儿去了:

  • 大声告诉他,这位拿滑板的大哥,海里有鲨鱼,别忘海里冲了...

  • 警告这位大哥,嘿兄弟,这里只允许坐轮船和直升机去海岛那边。

  • 在通往海岛的路线全程搭起防鲨网。

恩,目前就想到这些了,还有方案的同学可以在评论区炸沙发哈。


这些方式,我们试着抽象一下:

  • 【提前做好引导和提示】大声告诉他,这位拿滑板的大哥,海里有鲨鱼,别忘海里冲了...

  • 【优先选择减少输入】警告这位大哥,嘿兄弟,这里只允许坐轮船和直升机去海岛那边。

  • 【限制原操作范围】在通往海岛的路线全程搭起防鲨网。

大家应该都发现了,这种方式虽然能达到让用户安全的目的,但是用户会有挫败感,同时也会有被限制的感觉。

我只要说到USB插头,大家应该就开始脑补画面了吧“为什么我插的进去的第一次永远是错的方向,OMG!”

那,容错呢?


还是这位拿着滑板的大哥,想要去海上的小岛,从免(容错)的角度来看,你有什么方法帮他呢?

友情提示:容错的目的是在用户操作之后,自动纠正/化解错误或提供挽回的方法。


我有几个奇怪的方法,来跟大家分享一下:

  • 呃...驯化/杀死鲨鱼。

  • 派一直专业驱鲨救援队跟随这位大哥(滑板大哥:我何德何能,太感动了)。


同样,我们继续抽象一下:

  • 【让原本的错误不影响用户行为和结果】呃...驯化/杀死鲨鱼。

  • 【出现错误可挽回】派一直专业驱鲨救援队跟随这位大哥,出现鲨鱼就杀鱼。


诶,成本好像非常高哈,但是用户感觉良好哦~

因为他甚至都感知不到这片海还有过危险,按自己喜欢的方式行动就行。

我提一提微信消息的撤销功能,大家可能就会舒缓一口气了。

不知道挽回了多少个尴尬的瞬间,是吧~

可是...我只是一个有无缚鸡之力的设计师,我肝不过鲨鱼怎么办?

来来来,前面那个喊救命的大哥又来了,大家赶紧想想办法吧~


三、试用一下以上方法

(第一步和第四步就是单纯点击,就先删掉了)

前面讲的方法也列在右侧了,看看你们是不是可以针对每种方法找到对应方案了呢?

看看是不是可以这样?


方案一:使用方法-提前做好引导和提示


方案二:使用方法-优先选择减少输入


方案三:使用方法-限制操作范围


方案四:使用方法-出现错误可挽回


方案五:使用方法-让错误不影响结果


在这个案例中,大家觉得那个方案更好呢?

好吧,这个问题的确很蠢。当然是 方案二 + 方案五 结合使用了。因为是两个平台的交互,而对于各自平台而言,一个控制好输出,一个控制好输入。对平台自身的容错性有很大的帮助。

通过上面的案例,相信大家都已经熟练掌握好防错和容错的方法了。

什么,还没掌握?那你滚回去,(望着你正在慢慢蜷缩的身体)不.........不是你滚,是你的鼠标往回滚!

那,我们先不管那几个往回滚的同学,开始进行强化学习啦~


四、带着思考学习

找了一些生活中经常用到的产品,让大家感受一下防错容错的魅力。

如果你根本无法从图中发现什么,那我只能说... 你还是需要经常用银杏叶擦擦眼睛了(因为擦了之后你就更容易发现(人性)。

案例先到这里,大家一定以为要结束了吧~不,不,前面这些都不是最重要的。

方法总会越来越多,靠“别人”总结的方法也就能应个急。

来来来,放空~~~~抛弃你的职业相关认知、抛弃你的设计师的身份,你再看我们的设计。

你像孩子一样,好奇又“无知”。

你会发现,为什么会有这么多奇怪的看不见的规则,为什么我这么难完成我的任务。


五、提前预知错误风险(重点)

如果当客户遇到问题,你能用合适的方法去解决,那你已经是个不错的设计师了,但离优秀还很远,因为优秀的设计师必须具有敏锐的嗅觉,要在产品团队的前面,感知并规避风险。

所以,更重要的还是,我们必须学会变身。

好了,这一次真的需要大家独立思考了,并且没有标准答案,大家可以交流交流。


在以下场景中,可能会出现什么“错误”,如何有效处理用户“犯错”?

在生活中,其实会有很多很多“让用户犯错”的产品/场景,多参与其中,去发现问题,去思考解决方案,慢慢的,你就会具备非常好的问题发现能力和解决能力了。

说了这么多,可能有一部分同学会想说:

“用户出错的情况应该很少吧,你花这么长的篇幅”

“你一定低估了用户的智商”

我告诉你,出来玩玩,谁带智商做事啊~

不信你看。


六、那些你可能想象不到的用户错误

百度一下,你会发现用户犯错的可能性大大超出你的预期。

只是,你以为与你“无关”而已。


七、防止用户犯错的设计可能比你想象中更难,更重要

要防止用户犯错,你需要不断的模拟用户的操作行为,去捕获那些可能存在的问题,然后将它一一化解;这个过程很难,因为你要抛开自己以往的经验和认知,去感受用户的视角。


想想,用户体验的改进不就是让产品越来越符合用户认知,从而减少用户“犯错”吗?

只是有些错误,我们只作为围观者,将它推向了用户。

从现在起,将“错误”留给自己。




 蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png



文章来源:站酷   作者:得勿

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务






移动端导航设计

涛涛

合理的移动导航设计能够尽可能地减少摩擦,引导用户去他们要去的地方。

这篇文章汇总了移动端导航设计最常见的样式、要注意的设计准则以及优秀的案例分析,一起来系统性地掌握这些知识~

什么是移动端导航?

简单来说,导航是用户从 A 点到 B 点的方式,是他们发现设计点并与产品交互的过程。

可能很多用户认为导航的目标是“在尽可能短的时间内让用户从 A 到 B”,但时间短只属于操作结果,这个结果需要依靠合理且简单的设计才能实现。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 一个页面中可以存在多种导航。例如在油管首页,有顶部导航栏、筛选器导航和底部导航,这些导航相互搭配为产品助力。

移动端导航常见的设计样式

1. 汉堡菜单

围绕汉堡菜单有很多争论,但存在即合理,在合适的场景下汉堡菜单也能发挥大的作用。

来看一下汉堡菜单具备的优势:

  • 视觉空间:节省屏幕空间,包含有价值的信息;
  • 心智模型:大多数用户熟悉这种设计样式并知道如何操作;
  • 使用经验:调节学习曲线,改善使用体验。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 使用用户熟悉的设计可以事半功倍。例如美团和饿了么两个产品有着完全不同的主题色,但外卖点餐流程却是一样的,仍然是用户熟悉的操作,并没有因为产品的不同而改变点餐流程。

2. 底部导航

底部导航栏通常包含产品中最主要的导航链接,用户只需要简单的点击就能直观地在不同页面间切换。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 几乎每款产品都缺少不了底部导航栏,它方便用户单手操作,不需要太费力就能快速访问产品页面,提高可用性。

3. 顶部导航

关于顶部导航,可以看之前分享的文章,里面详细介绍了顶部导航的设计方法。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 顶部导航通常包含页面中最重要的信息,与其他辅助导航结合使用。

4. 卡片式导航

卡片式是一种出色的设计样式,支持改变各种形状和大小,并且能展示文本、链接或照片等各种元素。

随着网络上的内容越来越碎片化和个性化,卡片是在页面中聚合单个信息的好方式。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 为了改进体验,卡片可以个性化显示不同的内容。另外卡片很容易适应不同的屏幕尺寸,配合响应性设计。

5. 标签

标签往往是在一个大主题下同时支持多个选项,每个选项都转到不同的界面。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 标签通常用于在同一页面中的几个视图之间切换,展示内容上的差异性。而顶部导航栏有主页、搜索、收藏夹等多个图标,代表不同的功能。

6. 基于手势的导航

基于手势的导航可以让用户在所需方向上快速滑动,来完成特定的操作。

这种样式的优点在于,即使最没有经验的用户也很容易掌握,因为手势通常是直观的。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 国外大火的约会产品Tinder以及国内的探探,都使用了基于手势的导航样式,为用户带来滑动的乐趣。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 基于手势的导航并不是Tinder发明的,但这款产品无疑将这种流行带给了大众。经典的向左或向右滑动模式保持了事物的动态性、简单性和娱乐性。

7. 全屏导航

全屏导航是指将大部分屏幕用于导航操作,能够很好地将用户的注意力聚焦到具体的产品细节上。这是一种以连贯的方式提供大量导航的方法,可以立即帮助用户了解产品的功能。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 点击图片可以跳转到全屏大图导航中,能够更清晰地查看商品的外观状态。

8. 3D touch

最初是由苹果公司提供给用户的,这是一种创建导航快捷方式的方法,可以显示选定的 APP 的一些关键操作。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 苹果为手机创造了一种全新的快捷方式,同时提供了强大的可用性。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 3D touch另一个用途是内容预览,在处理内容选项时例如收件箱或文章列表时,这是给用户提供预览的好方法。

移动端导航设计准则

1. 导航需要直观明显

对所有类型的导航来说都是如此。在移动端中由于屏幕空间的缩小和交互成本的增加,导航体验的好坏会对产品产生很大的影响。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 从可用性角度来看,直观的导航对目标用户来说至关重要。这意味着需要进行严格的测试和大量的研究,可以使用卡片分类或树状图等方法来验证导航的可用性。

2. 考虑手指的位置

这点对于移动应用来说至关重要,没有用户想反复点击图标却没有反应。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 链接和按钮的尺寸需要足够大,以便大多数用户在第一次点击时就能成功点击。页面中按钮的尺寸最小通常保持在10mm。

3. 建立视觉层级避免混乱

小屏幕意味着更容易陷入混乱。即使页面中有少量的元素,如果元素没有平衡,用户仍然会有混乱的感觉。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 极简主义是UI设计的一种特定风格,通过必备的顶部导航栏、留白以及由大小、版式、颜色划分的视觉层级来规划页面内容。

移动端导航示例分析

1. Facebook

Facebook 的导航构成比较复杂,融合了多种不同样式的导航。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ Facebook主页包括汉堡菜单、顶部导航栏和底部导航栏。通过这种方式,这家社交媒体巨头明确的将主要内容与次要内容分隔开。

2. Spotify

Spotif 作为音乐流媒体业务的巨头,即使对于新用户来说,页面的设计也容易理解和探索。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 以高度视觉化的形式突出每张卡片背后的关键内容,另外底部导航也可以完成繁重的任务指引。

3. App Store

App Store 是使用标签进行导航的好例证,每个标签代表了同一内容的不同方面。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 在排行榜中,用户可以浏览付费、免费和热门的标签页,从而快速建立一致性和对其他页面的感知。

4. Telegram

Telegram 可以供任何人使用,汉堡菜单提供了用户可能需要的所有关键导航选项。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 每个用户都能立即找出汉堡菜单,非常容易使用和理解,而且几乎不占用界面中的宝贵空间。

5. Yelp

yelp 在创建全屏导航体验时采用了一种稍微不同的方法。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ Yelp将屏幕划分为两个不同的区域,实际上并没有将整个屏幕专门用于导航选项,而是将顶部用于导航选项,并在底部留下更多的负空间。

6. Trello

页面中的卡片是拉长的矩形,整齐有序地填满屏幕空间而不会让用户不知所措。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ 卡片系统很好地代表了Trello简化复杂工作流程的能力,为用户带来整洁和方便。

7. Twitter

同样使用了多种导航混合的设计样式。

你了解移动端导航设计吗?收下这篇系统性的总结!

△ Twitter将导航重点放在底部栏上,涵盖了整个平台中四个主要的方面。

最后

没有高速公路,我们很难便利地在城市间穿梭。同理,如果没有导航,一款 APP 的使用也会遇到很多麻烦。导航就像高速,不断在为用户提供必要的指引!




 蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png



文章来源:站酷   作者:Clip设计夹

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务



SpringBoot + Shiro + Mybatis-plus + Kaptcha + vue实现权限管理登录功能

前端达人

登录功能

在这里插入图片描述

使用到的技术

  • shiro
  • Mybatis-plus
  • Springboot
  • kaptcha

参考优秀博文

一个博主做的shiro笔记:https://www.guitu18.com/post/2019/07/26/43.html

引入依赖

 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.7.0</version> </dependency> <!--mybatis-plus 持久层--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <!--   整合swagger     --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> <!-- kaptcha 验证码 --> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency> </dependencies> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

配置shiro

package com.unclebb.zlgl.config; import com.unclebb.zlgl.utils.CustomRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /**
 * @program: zlgl
 * @description: Shiro配置类:将SecurityManager以及Realm都注入到Spring容器中
 * @author: LiuZhiliang
 * @create: 2021-05-10 08:56
 **/ @Configuration public class ShiroConfig { /** 
    * @Description: 代理生成器,需要借助SpringAOP来扫描@RequiresRoles和@RequiresPermissions等注解。生成代理类实现功能增强,从而实现权限控制。需要配合AuthorizationAttributeSourceAdvisor一起使用,否则权限注解无效。
    * @Param:
    * @return:
    * @Author: Liuzhiliang
    * @Date:
    */ @Bean public DefaultAdvisorAutoProxyCreator lifecycleBeanProcessor(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } /** 
    * @Description:  上面配置的DefaultAdvisorAutoProxyCreator相当于一个切面,下面这个类就相当于切点了,两个一起才能实现注解权限控制。
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } /**
     * @Description: Filter工厂,设置对应的过滤条件和跳转条件
     * @Param:
     * @return:
     * @Author: Liuzhiliang
     * @Date:
     */ //权限管理,配置主要是Realm的管理认证 @Bean public DefaultWebSecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); ThreadContext.bind(securityManager); return securityManager; } //将自己的验证方式加入容器 @Bean public CustomRealm myShiroRealm() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //加密 matcher.setHashAlgorithmName("md5"); matcher.setHashIterations(1); CustomRealm customRealm = new CustomRealm(); customRealm.setCredentialsMatcher(matcher); return customRealm; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //        Map<String,String> maps = new HashMap<>(); //        maps.put("/logout","logout"); //        maps.put("/**","authc"); //        shiroFilterFactoryBean.setLoginUrl("/login"); //        shiroFilterFactoryBean.setUnauthorizedUrl("/403.html"); //        shiroFilterFactoryBean.setSuccessUrl("/index"); //        shiroFilterFactoryBean.setFilterChainDefinitionMap(maps); return shiroFilterFactoryBean; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

配置swagger

package com.unclebb.zlgl.config; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /**
 * @Author unclebb
 * @Description Swagger配置类
 * @Date 2021/5/9
 **/ @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket webApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() //过滤掉admin路径下的所有页面 .paths(Predicates.and(PathSelectors.regex("/user/.*"))) //过滤掉所有error或error.*页面 //.paths(Predicates.not(PathSelectors.regex("/error.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("网站-API文档") .description("本文档描述了网站微服务接口定义") .version("1.0") .contact(new Contact("qy", "http://atguigu.com", "55317332@qq.com")) .build(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

配置kaptcha

package com.unclebb.zlgl.config; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import java.util.Properties; /**
 * @program: zlgl
 * @description: Kaptcha配置类
 * @author: LiuZhiliang
 * @create: 2021-05-12 09:03
 **/ @Component public class KaptchaConfig { @Bean public DefaultKaptcha getKaptcha(){ DefaultKaptcha dk = new DefaultKaptcha(); Properties properties = new Properties(); // 图片边框 properties.setProperty("kaptcha.border", "yes"); // 边框颜色 properties.setProperty("kaptcha.border.color", "105,179,90"); // 字体颜色 properties.setProperty("kaptcha.textproducer.font.color", "red"); // 图片宽 properties.setProperty("kaptcha.image.width", "110"); // 图片高 properties.setProperty("kaptcha.image.height", "40"); // 字体大小 properties.setProperty("kaptcha.textproducer.font.size", "30"); // session key properties.setProperty("kaptcha.session.key", "code"); // 验证码长度 properties.setProperty("kaptcha.textproducer.char.length", "4"); // 字体 properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑"); Config config = new Config(properties); dk.setConfig(config); return dk; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

pojo

User

package com.unclebb.zlgl.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.util.Set; /**
 * @Author unclebb
 * @Description 用户实体类
 * @Date 2021/5/9
 **/ @Data @TableName(value = "user") public class User { @TableId(value = "id",type = IdType.AUTO)//指定自增策略 private int id; @TableField(value = "username") private String username; @TableField(value = "password") private String password; @TableField(exist = false) private Set<Role> rolesSet; private String salt; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Role

package com.unclebb.zlgl.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; import java.util.Set; /**
 * @Author unclebb
 * @Description 角色实体类
 * @Date 2021/5/9
 **/ @Data public class Role { @TableId(value = "id",type = IdType.AUTO)//指定自增策略 private int id; @TableField(value = "user_name") private String userName; @TableField(value = "role_name") private String roleName; @TableField(exist = false) private Set<Permission> permissionSet; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

Permission

package com.unclebb.zlgl.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; /**
 * @Author unclebb
 * @Description 权限实体类
 * @Date 2021/5/9
 **/ @Data public class Permission { @TableId(value = "id",type = IdType.AUTO)//指定自增策略 private int id; @TableField(value = "role_name") private String roleName; @TableField(value = "permission_name") private String permissionName; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Mapper

基本格式:

package com.unclebb.zlgl.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.unclebb.zlgl.pojo.User; import org.apache.ibatis.annotations.Mapper; /**
 * @Author unclebb
 * @Description 用户映射
 * @Date 2021/5/9
 **/ @Mapper public interface UserMapper extends BaseMapper<User> { } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Service

Interface

LoginService

package com.unclebb.zlgl.service; import com.baomidou.mybatisplus.extension.service.IService; import com.unclebb.zlgl.pojo.User; import org.springframework.stereotype.Service; /**
 * @Author unclebb
 * @Description 用户登录接口
 * @Date 2021/5/9
 **/ @Service public interface LoginService extends IService<User> { public User getByUsername(String username); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

RoleService

package com.unclebb.zlgl.service; import com.baomidou.mybatisplus.extension.service.IService; import com.unclebb.zlgl.pojo.Role; import org.springframework.stereotype.Service; import java.util.List; @Service public interface RoleService extends IService<Role> { public List<Role> getRole(String userName); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

PermissionService

package com.unclebb.zlgl.service; import com.baomidou.mybatisplus.extension.service.IService; import com.unclebb.zlgl.pojo.Permission; import org.springframework.stereotype.Service; import java.util.List; @Service public interface PermissionService extends IService<Permission> { public List<Permission> getPermissions(String roleName); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

ServiceImpl

LoginServiceImpl

package com.unclebb.zlgl.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.unclebb.zlgl.mapper.UserMapper; import com.unclebb.zlgl.pojo.Role; import com.unclebb.zlgl.pojo.User; import com.unclebb.zlgl.service.LoginService; import com.unclebb.zlgl.service.PermissionService; import com.unclebb.zlgl.service.RoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashSet; import java.util.List; import java.util.Set; /**
 * @Author unclebb
 * @Description 登录实现类
 * @Date 2021/5/9
 **/ @Service public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements LoginService { @Autowired RoleService roleService; @Override public User getByUsername(String username) { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("username",username); User user = this.getOne(wrapper); List<Role> roleList = roleService.getRole(user.getUsername()); user.setRolesSet(new HashSet<Role>(roleList)); return user; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

RoleServiceImpl

package com.unclebb.zlgl.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.unclebb.zlgl.mapper.RoleMapper; import com.unclebb.zlgl.pojo.Permission; import com.unclebb.zlgl.pojo.Role; import com.unclebb.zlgl.service.PermissionService; import com.unclebb.zlgl.service.RoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashSet; import java.util.List; import java.util.Set; /**
 * @program: zlgl
 * @description: Role实现类
 * @author: LiuZhiliang
 * @create: 2021-05-10 16:16
 **/ @Service public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService { @Autowired PermissionService permissionService; @Override public List<Role> getRole(String userName) { QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("user_name",userName); List<Role> roleList = this.list(wrapper); for (Role role:roleList){ List<Permission> permissions = permissionService.getPermissions(role.getRoleName()); role.setPermissionSet(new HashSet<Permission>(permissions)); } return roleList; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

PermissionServiceImpl

package com.unclebb.zlgl.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.unclebb.zlgl.mapper.PermissionMapper; import com.unclebb.zlgl.pojo.Permission; import com.unclebb.zlgl.pojo.User; import com.unclebb.zlgl.service.PermissionService; import org.springframework.stereotype.Service; import java.util.List; import java.util.Set; /**
 * @program: zlgl
 * @description: Permission实现类
 * @author: LiuZhiliang
 * @create: 2021-05-10 16:18
 **/ @Service public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService { @Override public List<Permission> getPermissions(String roleName) { QueryWrapper<Permission> wrapper = new QueryWrapper<>(); wrapper.eq("role_name",roleName); return this.list(wrapper); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Controller

package com.unclebb.zlgl.controller; import com.unclebb.zlgl.pojo.Role; import com.unclebb.zlgl.pojo.User; import com.unclebb.zlgl.service.RoleService; import com.unclebb.zlgl.utils.Result; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; /**
 * @Author unclebb
 * @Description 登录控制器
 * @Date 2021/5/9
 **/ @RestController public class LoginController { @Autowired RoleService roleService; /** 
    * @Description: 登录接口 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @ApiOperation(value = "登录接口") @RequestMapping("/user/login") public Result login(@RequestBody User user){ System.out.println("进入/user/login API接口"); if (StringUtils.isEmpty(user.getUsername())||StringUtils.isEmpty(user.getPassword())){ return Result.fail("请输入用户名密码"); } Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); Object sessionId = session.getId(); Map ret = new HashMap<String,Object>(); ret.put("token",sessionId); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()); try { //登录成功 subject.login(token); return Result.ok(ret); } catch (AuthenticationException e) { e.printStackTrace(); //登录失败 return Result.fail("用户名密码错误"); } } /** 
    * @Description: 基本测试接口 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/noauthority") public Result noauthority(){ return Result.fail("没有权限"); } /** 
    * @Description: 测试session接口 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/demoSession") @ResponseBody public String demoSession(HttpSession session){ System.out.println("测试session"); Enumeration<String> names = session.getAttributeNames(); while (names.hasMoreElements()){ String name = names.nextElement(); Object value = session.getAttribute(name); System.out.println(name + " -------  "+ value); } return "session 取值"; } /** 
    * @Description: 测试session接口 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/demoSession2") @ResponseBody public String demoSession2(){ Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); System.out.println(session.getHost()); System.out.println(session.getId()); System.out.println(session.getLastAccessTime().getTime()); System.out.println(session.getTimeout()); System.out.println(session.getAttribute("test")); return "session 取值"; } /** 
    * @Description: 测试session接口 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/checkPermission") @ResponseBody public Result checkPermission(User user){ if (StringUtils.isEmpty(user.getUsername())||StringUtils.isEmpty(user.getPassword())){ return Result.fail("请输入用户名密码"); } Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); session.setAttribute("test","test"); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()); token.setRememberMe(true); try { //登录成功 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); //登录失败 return Result.fail("用户名密码错误"); } try { subject.checkRole("admin"); return Result.ok("权限检查成功"); } catch (AuthorizationException e) { e.printStackTrace(); return Result.fail("检查权限失败"); } } /** 
    * @Description: 根据token获取用户授权信息接口 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/checkRole") public Result checkRole(@RequestParam String token){ Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); List<Role> roleList = null; if (token.equals(session.getId().toString())){ String username = session.getAttribute("org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY").toString(); System.out.println(username); roleList = roleService.getRole(username); } return Result.ok(roleList); } /** 
    * @Description: 测试kaptcha ,获取验证码
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/testKaptcha") @ResponseBody public String TestKaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException { byte[] captcha = null; ByteArrayOutputStream out = new ByteArrayOutputStream(); try { // 将生成的验证码保存在session中 String createText = defaultKaptcha.createText(); request.getSession().setAttribute("rightCode", createText); BufferedImage bi = defaultKaptcha.createImage(createText); ImageIO.write(bi, "jpg", out); } catch (Exception e) { response.sendError(HttpServletResponse.SC_NOT_FOUND); } captcha = out.toByteArray(); response.setHeader("Cache-Control", "no-store"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0); response.setContentType("image/jpeg"); ServletOutputStream sout = response.getOutputStream(); sout.write(captcha); sout.flush(); sout.close(); return "测试Kaptcha"; } /**
     * 校对验证码
     *
     * @param request
     * @param response
     * @return
     */ @RequestMapping(value = "/user/verifyKaptcha") public Result imgvrifyControllerDefaultKaptcha(HttpServletRequest request, HttpServletResponse response) { ModelAndView model = new ModelAndView(); String rightCode = (String) request.getSession().getAttribute("rightCode"); String tryCode = request.getParameter("tryCode"); System.out.println("rightCode:" + rightCode + " ———— tryCode:" + tryCode); if (!rightCode.equals(tryCode)) { model.addObject("info", "验证码错误,请再输一次!"); model.setViewName("login"); return Result.fail("验证码错误"); } else { model.addObject("info", "登陆成功"); model.setViewName("index"); return Result.ok("验证成功"); } } /** 
    * @Description: 测试 校验权限 permission 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/testShiroPermission") @RequiresPermissions("user:add") public Result TestShiroPermissions(){ System.out.println("访问 TestShiroPermissions API"); Subject subject = SecurityUtils.getSubject(); String username = (String) subject.getPrincipal(); System.out.println(username); return Result.ok(username); } /** 
    * @Description: 登出功能API
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/logout") public Result logout(){ Subject subject = SecurityUtils.getSubject(); String username = (String) subject.getPrincipal(); subject.logout(); return Result.ok(username); } /** 
    * @Description:  测试校验 角色 role
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @RequestMapping("/user/TestRole") @RequiresRoles("admin") public Result TestRole(){ System.out.println("测试TestRole"); return Result.ok("测试Role"); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279

util

Result

package com.unclebb.zlgl.utils; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /**
 * @Author unclebb
 * @Description 统一返回API格式
 * @Date 2021/5/9
 **/ @Data @ApiModel(value = "全局统一返回结果") public class Result<T> { @ApiModelProperty(value = "返回码") private Integer code; @ApiModelProperty(value = "返回消息") private String message; @ApiModelProperty(value = "返回数据") private T data; public Result(){} public static <T> Result<T> build(T data) { Result<T> result = new Result<T>(); if (data != null) result.setData(data); return result; } public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) { Result<T> result = build(body); result.setCode(resultCodeEnum.getCode()); result.setMessage(resultCodeEnum.getMessage()); return result; } public static<T> Result<T> ok(){ return Result.ok(null); } /**
     * 操作成功
     * @param data
     * @param <T>
     * @return
     */ public static<T> Result<T> ok(T data){ Result<T> result = build(data); return build(data, ResultCodeEnum.SUCCESS); } public static<T> Result<T> fail(){ return Result.fail(null); } /**
     * 操作失败
     * @param data
     * @param <T>
     * @return
     */ public static<T> Result<T> fail(T data){ Result<T> result = build(data); return build(data, ResultCodeEnum.FAIL); } public Result<T> message(String msg){ this.setMessage(msg); return this; } public Result<T> code(Integer code){ this.setCode(code); return this; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

ResultCode

package com.unclebb.zlgl.utils; import lombok.Getter; /**
 * @Author unclebb
 * @Description API状态信息
 * @Date 2021/5/9
 **/ @Getter public enum ResultCodeEnum { SUCCESS(200,"成功"), FAIL(201, "失败"), SERVICE_ERROR(202, "服务异常"), DATA_ERROR(204, "数据异常"), SIGN_ERROR(300, "签名错误"), PAY_PASSWORD_ERROR(401, "支付密码错误"), REPEAT_ERROR(402, "重复提交"), INVEST_AMMOUNT_MORE_ERROR(501, "出借金额已经多余标的金额"), RETURN_AMMOUNT_MORE_ERROR(502, "还款金额不正确"), PROJECT_AMMOUNT_ERROR(503, "标的金额不一致") ; private Integer code; private String message; private ResultCodeEnum(Integer code, String message) { this.code = code; this.message = message; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

CustomRealm

package com.unclebb.zlgl.utils; import com.unclebb.zlgl.pojo.Permission; import com.unclebb.zlgl.pojo.Role; import com.unclebb.zlgl.pojo.User; import com.unclebb.zlgl.service.LoginService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; /**
 * @program: zlgl
 * @description: 自定义Realm
 * @author: LiuZhiliang
 * @create: 2021-05-10 09:09
 **/ public class CustomRealm extends AuthorizingRealm { @Autowired LoginService loginService; /** 
    * @Description: 授权配置
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); User user = loginService.getByUsername(username); if (user == null){ return null; }else { SimpleAuthorizationInfo  simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (Role role : user.getRolesSet()){ simpleAuthorizationInfo.addRole(role.getRoleName()); for (Permission permission : role.getPermissionSet()){ simpleAuthorizationInfo.addStringPermission(permission.getPermissionName()); } } return simpleAuthorizationInfo; } } /** 
    * @Description: 认证配置 
    * @Param:  
    * @return:  
    * @Author: Liuzhiliang
    * @Date:  
    */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = token.getPrincipal().toString(); User user = loginService.getByUsername(username); if (user == null){ return null; }else { //匹配密码 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,user.getPassword(),getName()); simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt())); return simpleAuthenticationInfo; } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

前端代码

前端我这里直接用了 开源的管理系统框架
附地址:

https://github.com/PanJiaChen/vue-admin-template

运行截图如下
在这里插入图片描述
在这里插入图片描述

完事儿只需要改一下它的 返回状态码校验,配置下跨域就可以了
在这里插入图片描述

整合验证码

验证码部分包含两个API接口

/user/testKaptcha 获取验证码信息
/user/verifyKaptcha 校验

其中获取验证码图片信息相当于请求静态图片资源,直接将验证码图片的src指向 该接口即可,前端源码如下:

 <el-image :src="kaptcha" @click="refreshCode()" alt="加载失败" style="margin-left:10px;height:40px;margin-top:5px"> <div slot="placeholder" class="image-slot"> <i class="el-icon-loading"></i> </div> </el-image> 
  • 1
  • 2
  • 3
  • 4
  • 5

其中路径定义为:

kaptcha:"http://localhost:8082/user/testKaptcha?t="+ new Date().getTime(), 
  • 1

后面加的时间参数是为了刷新url用的
前端的刷新函数就是将kaptcha重新赋值

 refreshCode(){ console.log("测试切换验证码") this.kaptcha = "http://localhost:8082/user/testKaptcha?t="+ new Date().getTime() console.log(this.kaptcha) }, 
  • 1
  • 2
  • 3
  • 4
  • 5

登录、权限校验、登出效果如下

登录

登录首先会验证 验证码的正确性,登陆成功进入主界面

验证码错误如下:
在这里插入图片描述
用户名密码错误如下:
在这里插入图片描述
登陆成功后请求校验角色 校验成功
在这里插入图片描述请求校验权限 校验成功
在这里插入图片描述

退出,执行两次,data为null
在这里插入图片描述

再次校验权限,报出异常

在这里插入图片描述


 蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png


文章来源:csdn   作者:黑胡子大叔的小屋

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

SpringBoot + Spring Cloud +Vue 管理系统前端搭建(二、visual studio code开发前端项目)

前端达人

我们打开visual studio code , 选择文件------------->将文件夹添加到工作区,导入我们的项目

 

安装Element

导入后,我们安装以下element

官网:https://element.eleme.cn/#/zh-CN/component/installation

安装命令:npm add element-ui或者也可以用yarn

安装完成后,我们在main.js中引入Element

import Vue from 'vue'

import App from './App'

import router from './router'

import ElementUI from 'element-ui'

import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

 

/* eslint-disable no-new */

Vue.use(ElementUI)

new Vue({

el: '#app',

router,

components: { App },

template: '<App/>'

})

页面路由

 我们把components改名为views,并在目录下添加3个页面:Login.vue、Home.vue、404.vue。

页面内容类似:

<template>

<div class="page">

<h2>Login Page</h2>

</div>

</template>

 

<script>

export default {

name: 'Login'

}

</script>

配置路由

打开router/index.js,添加3个路由分别对应主页、登录、404页面

import Vue from 'vue'

import Router from 'vue-router'

import Login from '@/views/Login'

import Home from '@/views/Home'

import NotFound from '@/views/404'

 

Vue.use(Router)

 

export default new Router({

routes: [

{

path: '/',

name: 'Home',

component: Home

}, {

path: '/login',

name: 'Login',

component: Login

}, {

path: '/404',

name: 'notFound',

component: NotFound

}

]

})

配置完后启动项目,在浏览器访问测试

http://localhost:8080/#/

http://localhost:8080/#/login

 

http://localhost:8080/#/404

说明我们的配置已经生效了

安装scss

安装依赖:

npm uninstall sass-loader //卸载当前版本) 
npm install sass-loader@7.3.1 --save-dev //卸了重新安装了一个低版本
npm install node-sass@4.14.1 --save-dev //安装node-sass 

安装的时候注意对应版本,版本不对应,启动会报错

安装后修改404页面

<template>

<div class="site-wrapper site-page--not-found">

<div class="site-content__wrapper">

<div class="site-content">

<h2 class="not-found-title">404</h2>

<p class="not-found-desc">抱歉!您访问的页面<em>失联</em>啦 ...</p>

<el-button @click="$router.go(-1)">返回上一页</el-button>

<el-button type="primary" class="not-found-btn-gohome" @click="$router.push('/')">进入首页</el-button>

</div>

</div>

</div>

</template>

 

<script>

export default {

name: '404'

}

</script>

 

<style lang="scss">

.site-wrapper.site-page--not-found {

position: absolute;

top: 60px;

right: 0;

bottom: 0;

left: 0;

overflow: hidden;

.site-content__wrapper {

padding: 0;

margin: 0;

background-color: #fff;

}

.site-content {

position: fixed;

top: 15%;

left: 50%;

z-index: 2;

padding: 30px;

text-align: center;

transform: translate(-50%, 0);

}

.not-found-title {

margin: 20px 0 15px;

font-size: 8em;

font-weight: 500;

color: rgb(55, 71, 79);

}

.not-found-desc {

margin: 0 0 30px;

font-size: 26px;

text-transform: uppercase;

color: rgb(118, 131, 143);

> em {

font-style: normal;

color: #ee8145;

}

}

.not-found-btn-gohome {

margin-left: 30px;

}

}

</style>

再浏览器访问http://localhost:8080/#/404

 

可以看到样式改变了

安装axios

命令:npm install axios

安装完成后修改Home页面,进行一个简单的测试

<template>

<div class="page">

<h2>Home Page</h2>

<el-button type="primary" @click="testAxios()">测试Axios调用</el-button>

</div>

</template>

 

<script>

import axios from 'axios'

import mock from '@/mock/mock.js'

export default {

name: 'Home',

methods: {

testAxios() {

axios.get('http://localhost:8080').then(res => { alert(res.data) })

}

}

}

</script>

可以看到我们的请求已经成功了

安装Mock.js

为了模拟后台接口提供页面需要的数据,引入mock.js

安装依赖:npm install mockjs -dev

安装完成,在src新建一个mock目录,创建mock.js,在里面模拟两个接口,分别拦截用户和菜单的请求并返回相应数据。

import Mock from 'mockjs'

 

Mock.mock('http://localhost:8080/user', {

'name': '@name', // 随机生成姓名

'name': '@email', // 随机生成邮箱

'age|1-12': 7, // 年龄1-12之间

})

Mock.mock('http://localhost:8080/menu', {

'id': '@increment', // id自增

'name': 'menu', // 名称为menu

'order|1-10': 6, // 排序1-10之间

})

修改Home.vue,在页面添加两个按钮,分别触发用户和菜单请求。成功后弹出返回结果

注意:要在页面引入mock    import mock from '@/mock/mock.js'

Home.vue

<template>
  <div class="page">
    <h2>Home Page</h2>
    <el-button type="primary" @click="testAxios()">测试Axios调用</el-button>
    <el-button type="primary" @click="getUser()">获取用户信息</el-button>
    <el-button type="primary" @click="getMenu()">获取菜单信息</el-button>
  </div>
</template>

<script>
import axios from 'axios'
import mock from '@/mock/mock.js'
export default {
  name: 'Home',
  methods: {
    testAxios() {
      axios.get('http://localhost:8080').then(res => { alert(res.data) })
    },
    getUser() {
      axios.get('http://localhost:8080/user').then(res => { alert(JSON.stringify(res.data)) })
    },
    getMenu() {
      axios.get('http://localhost:8080/menu').then(res => { alert(JSON.stringify(res.data)) })
    }
  }
}
</script>

访问http://localhost:8080/#/

点击获取用户信息

点击获取菜单信息

可以看到我们已经得到响应数据,这样mock就集成进来了

看完记得点赞哦

  蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png


文章来源:csdn   作者:Java璐到底

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


讲真,我从这些Dribbble顶尖图标大神的作品中学到很多,建议你一定要看看

seo达人

今天彩云就跟大家分享一些Dribbble上,我这些年一直珍藏的10位图标设计大佬们,我建议你一定要收藏学习。

 

1、Scott Tusk

https://dribbble.com/Tusk

首先推荐的就是这位大佬了,他在Drbbble上坚持了365天打卡练习,每天更新一张高质量图标或者矢量小插图设计,图形简单好看,非常值得借鉴和临摹练习。

图片

图片

图片

2、Dmitri Litvinov

https://dribbble.com/dmitrilitvinov

一个在图标设计上非常有造诣的自由设计师,他在图标的造型和风格一致性上做的非常好,而且能看出来他的作品大部分都是能落地的稿子,值得学习。

图片

图片

图片

 

3、Paulius Kairevicius

https://dribbble.com/kairevicius

这位大神在设计图标Logo的时候擅长用严谨的比例,而且他会把他的作图过程和辅助线放出来,我觉得从这些作品中可以学到很多设计的思路,推荐给大家。

图片

图片

图片

图片

 

4、Justas Galaburda

https://dribbble.com/jucha

这些大神的作品喜欢加噪点,很有自己的风格特点,图标都做的比较可爱。如果是做些偏萌系的设计,包括一些小表情之类的可以参考他的作品了。

图片

图片

图片

 

5、Martin David

https://dribbble.com/srioz

专业的图标设计师,在图标设计这个领域相当专注,看他的个人主页几乎全是图标的设计,以商业图标设计居多。做项目的时候,非常值得参考了。

图片

图片

图片

 

6、Myicons

https://dribbble.com/lineicons

这位其实是一个图标平台的账号,会定期更新大量的图标资源,把它收藏起来当成灵感库还不错。

图片

 

7、Eddie Lobanovskiy

https://dribbble.com/lobanovskiy

这位大佬(也可能是一个团队账号)非常的全面,不仅仅只是在图标领域,在3D、插画、品牌、网页、UI都顶级优秀。我个人非常喜欢他在图标这块的创意,能看到他展示了很多图形的方案,过程稿,我觉得可以学习到很多,比如用到自己的作品集展示中,就很实用了。

图片

图片

图片

图片

 

8、Eddy Gann

https://dribbble.com/Ed117

这位大佬非常擅长动态图标的设计,他做的图标动效并不夸张,但却是非常容易的落地到实际项目中。在动效创意如此流行的今天,他的这些作品一定可以给你提供灵感,专注在图标动效上的作品并不多见,值得收藏。

图片

图片

图片

 

9、Zach Roszczewski

https://dribbble.com/ZachRoszczewski

这位大佬在图标的多种风格方面把握的都比较好,想要同时看多种风格搭配的可以收藏了。

图片

图片

图片

 

10、Evgenii Dolgov

https://dribbble.com/numicor

最后推荐一位矢量风格图标设计大神,他的作品风格独特,擅长在图标上叠加很多插画形式的明暗纹理,形式感比较强,值得学习。

图片

图片

图片

 

总结

文章中列出来的这些是我从关注列表中再三筛选出来的比较有代表性的图标设计大神,在我的工作学习过程中,他们给了我很多的灵感。当然,这份推荐名单只是我自己的个人喜好,无关粉丝数量,排名也不分先后。

这篇分享,一定是值得收藏的,不论是找灵感,还是临摹学习,不用到处找,这10位大佬的作品就足够你研究了。

在看的过程中,要学会去分析优秀的图标,并用到自己的设计中。不管是练习还是实际项目,好的设计一定是有道理的,多想想别人为什么这么做。比如:为什么对方用这个颜色?颜色的配比怎样?辅助图形用的哪些?一套图标中的辅助图形如何做到丰富又不失一致性等等问题。不要只是依葫芦画瓢,要思考背后的道理,这个很重要。

最后,对于图标设计本身,最重要的还是要保持练习,多看多做才能真正提升

 

原文地址:彩云译设计(公众号) 作者:彩云Sky

转载请注明:学UI网》讲真,我从这些讲真,我从这些Dribbble顶尖图标大神的作品中学到很多,建议你一定要看看

 

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

微信图片_20210513163802.png

 

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

 

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档