首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
2,853 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
1,230 阅读
3
解决 nginxProxyManager 申请证书时的SSL失败问题
865 阅读
4
Pointer-Focus:一款功能强大的教学、录屏辅助软件
852 阅读
5
使用cspell对项目做拼写规范检查
698 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
Java
运维
项目
生活
其他
转载
软件
职场
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
frp
RabbitMQ
gitlab
Node
git
工具
MybatisPlus
clickhouse
Syncthing
规范
前端
产品
nginx
markdown
axios
朱治龙
累计撰写
153
篇文章
累计收到
10
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
Java
运维
项目
生活
其他
转载
软件
职场
页面
留言
友链
关于
搜索到
153
篇与
朱治龙
的结果
2022-01-26
[转载]如何从交互维度量化用户体验?
最近在准备一个用户体验相关的PPT作内部分享,收集素材的时候在知乎上发现一篇讲解非常翔实的文章。故转载于此,仅用于副本留底,若有侵权可联系删除。原文地址:https://zhuanlan.zhihu.com/p/39124206https://mp.weixin.qq.com/s/69SUAWmsg7S4J4CvLk1TNA{mtitle title="以下为转载的内容原文"/}之前参加了回音分享会,认识了很多新朋友,线下分享时间有限,可能有很多东西没有讲的很透彻,所以整理了一下我当时的 ppt 和想要表达的观点,写了这篇文章,和大家分享一些在产品和交互设计中的一些自己的方法。一、什么是交互狭义的交互(Interaction)定义交互主体必须是人本身,而客体可以是产品,环境,服务等等,且不论交互客体是什么,只要主体是人,人和客体去进行交互的时候,一定是人带着心理预期施加一个行为,然后客体会根据这个行为给予一个反馈(没有反馈本质也是一个反馈),而人会根据这个反馈是否符合预期去进行心理修正。如下图所示,这就是我理解的最小交互模型:当时我举的例子是用翻页器去控制 ppt 翻页:如上图所示,拆解这一套交互行为:当我点击翻页器的「下一页」按钮,我点击行为附带的心理预期是「PPT翻往下一页」,然后我点击的时候,遥控器塑胶按钮给到我手指一个物理反馈,证明我按下的行为已经完成了,这是「输出端(我的手)的交互与反馈」,这时候遥控器接收到按钮指令,把指令通过红外线传输到 USB接收器上,接收器把指令传到 PC端然后完成翻页动作,再通过大屏幕传到我的眼(输入端)中,我就可以确认这一次交互反馈是符合预期的。这里有一点想要补充:设备对设备(上图中黑色箭头),也属于广义的交互,只不过现阶段大家研究的交互设计都是狭义的,人为主体的交互。在我们日用科技产品的早期,有两个东西是无法跳过的,那就是按键手机和 PC电脑:他们几乎是同步在发展的,而这两个产品的交互行为基本上延续到了触屏手机时代,所以为了弄明白触屏手机的交互,这两个产品是值得讲一讲的。先看按键手机(就是我们小时候用的非智能手机):在按键手机中,最让用户困惑的其实是按键和屏幕之间存在一个映射关系,而不同厂商缺乏一个统一的规范,各家映射规则不一样。大家是否还记得当年的手机说明书那可以说是相当厚,因为说明书必须要给用户建构一个心理模型;比如上图,点击左上角和右上角那两个「-」按钮,其实一一对应的是屏幕左下角的「Goto」和右下角的「Names」。这个一一对应关系作为今天的用户来看应该是很平常而且很易懂的,但是当年没用过手机的人,需要花很长时间阅读说明书,才能够明白物理按键和屏幕上的映射关系,这就是按键手机很难用的地方,也是很反人性的地方。因为作为用户来说,心智上,我们当然希望所触即所得。再来看 PC,作为和按键手机差不多一起出现的载体形式,人们操作 PC端人是通过媒介(也就是鼠标+键盘)输入的,其实本质上也是我们通过鼠标在桌面上滑动 x-y 区域对应到电脑桌面上指针的移动来创造屏幕中x-y的映射关系,然后键盘上几十个键配合输入完成操作。大家发现了么,上述的两种设备其实本身就是在制造一种一一对应的映射关系去完成交互行为,这两种载体从出身开始就是需要很大交互成本的。随着科技的发展,触屏感应技术推出了之后,印象中触屏手机就是两三年时间就摧枯拉朽的淘汰了按键手机,本质上是干掉了一一对应的交互映射,所按即所得:触屏手机出现之后,交互专家们不禁要问一个问题了:手和触摸屏到底有多少种交互方式?答案是有很多种:越是高阶越是隐藏的交互手势越复杂,所谓的「交互成本」也越高,比如锤子三指滑动换屏保那种,就是利用了高阶交互便捷实现边界功能。那这么看起来,iOS 也好还是所有的安卓手机都好,从用户端而言,就是组合交互手势,让信息更好的传达而已。那么同理,在 App 中也是一样,如果我们了解了每一个交互行为的用户心理预期,对设计工作而言就能做到有的放矢:我们以「单击」和「滑动」这两个最简单的交互行为举例。所谓单击手机屏幕,用户其实最核心的是有两个预期:第一是选中一个元素,比如 Radio组件。第二是逻辑上的 Next,比如点了一件衣服,应该 Next 到衣服的详情;点了付款,应该出现付款流程,点了返回,应该 back 到上一路径点等等。划动交互也是一样的,用户在一块手机屏幕上单指划划划,用户内心的预期其实也不复杂,最核心的预期也就两点:第一是查看屏幕外的线索(前提是设计师给用户留下线索了或者是这个 UI组件长得就是可以划动的样子)。第二是查看相邻标签的内容,或者查看同一个标签下的相邻元素,比如 iOS 的 segment controlle组件就是典型例子:当我们了解了这些之后,我们在实际的设计工作中就可以根据上面这些理论来合理选择 UI组件去呈现对应的信息了。二、从交互维度合理选择UI组件我们在设计工作中,选择 UI组件,本质上就是选择信息的呈现形式。每一个常见的 UI界面和 UI组件,都一定也满足上面所说的最小交互模型:在这里我举一些例子说明。第一个例子:同样的内容,选择不同的 UI组件呈现,给用户呈现的是完全不同的产品结构:大家看下面这张图:这两个 UI模块摆在大家面前,大家应该能清晰的感受到,左边是一个 segment 控制下面内容的 UI;而右边是一个所有内容列表的集合页,只不过通过 tab 聚类了而已。第一件事应该想到的是如果需要采用右边的排列形式就必须要控制 tag 的字数;然后由于右边的 tag 占据了推荐贴的位置,导致推荐贴可能没有左边的那种展现形式更加醒目。但是相对的,图右的优势在于,由于竖向排列 tag 可以让一个屏幕显示更多的 tag,可以让用户更方便的定位内容,比如外卖产品之所以用右边这种形式是因为力求一屏展示更多的菜,而且外卖产品的左侧 tag 一般是一家店铺的菜的品类,用户下滑菜品配合点击品类,点完即走,很方便(京东和淘宝电商类平台也是类似的)。但是比如今日头条,新闻类客户端只能采用左边的这种形式,因为新闻类客户端是需要用户长时间沉浸的,比如用户选中一个「体育」的 tag 之后一般要沉浸的看好久好久,用户需要沉浸在这个 tag 下的内容中,那这个时候显然用右边这种设计方式让 tag 常驻屏幕左侧是不合适的。再来看第二个例子,就是 UI 应该会随着内容而进行调整和优化:这里举一个唱吧的例子,唱吧从7.0到8.6之间做了三次改版,大家可以看到,唱吧团队几乎是损失了屏幕效率来加大了间隔和突出了歌名,这是为什么呢?这是因为页面承载的关键任务不同,大家对比着7.0时候和8.6时候的 UI样式,正好是今天快手和唱吧的对比:大家会发现,其实这个页面,快手和唱吧承载的内容都是消费转化,都希望用户点击进去消费内容,但是两款产品做了截然不同的 UI风格,原因是什么呢?快手在这个页面,其实承载的关键任务是:「迅速让用户找到感兴趣的点」,它这么设计的本质原因是因为它的截图可以帮助用户判断内容本身,比如第一张图是一个人在打高尔夫,右边是一个工人,然后第二排左边是一个游戏的镜头,右边是一个传递正能量,大家可以很方便的通过图片识别里面的内容,用户更沉浸更聚焦的去选择自己喜欢的,点击进入消费就可以了。但是唱吧的视频截图其实是不能识别里面内容的,大家可以看到,第一张图是一个妹子,第二张图是一个妹子,第三张图还是一个妹子,那用户点击进去的动力在哪儿了?除了这个照片长相之外,更多的其实是文字决定的,是这个人唱的这首歌的歌曲名是不是我喜欢的,或者是这个演唱者的歌手等级。所以基于这种更深层次的逻辑,唱吧和快手两款产品的这个页面都是为了促进消费转化,但是 UI 长相上完全不同。我们看第三个例子:同样组件下,选择不同的交互方式,也会使得效果完全不同,比如现在有一个 UI页面,主要由一个 tab(iOS叫segment controlled)组件控制下面的内容,长这样:我先假定一个前提:这个 app 中的这个组件不支持横划,只支持点击切换。好了,现在我假设这是一款已经稳定运营了一年的产品,为了说明问题,我假设一个理想数据:假设每天有20W 的 uv 访问这个页面,其中分流情况是:10Wuv消费「推荐」下的内容2Wuv消费「生活」下的内容1Wuv消费「段子」下的内容3Wuv消费「美女」下的内容4Wuv消费「游戏」下的内容这时候,为了优化交互行为,有一天决定把这个 tab组件从不可横向划动改成可以划动的(并且告诉用户这里可以滑动了),然后给你一次机会重新排列这五个 tab 顺序,你会怎么做呢?最简单的办法当然是把五个 tab 按照用户消费意愿逐一排列,即:「推荐、游戏、美女、生活、段子」。这样排列当然没有任何问题,但是还有没有更优解呢?我给出的解决办法是这样的,大家评判一下:按照用户的消费量,「游戏」是消费量第二的一个 tab,毫无疑问我会把它排在第二项,这样可以刺激用户划动行为,然后「美女」是消费量第三的,我会把它放在第四位,这时候我会把「段子」和「生活」这两个消费率最低的 tab 分 AB test 做两个版本放在第三和第五位拿去测,以判断之前的「段子」和「生活」是由于自身内容不够优质,还是之前交互成本太低导致的数据较差:最后我们来看第四个例子:比如一个 app,他的 UI 如下图所示的这个样子。现在假设在运营和市场团队不做任何努力的情况下,单从产品交互的角度,能不能优化上面这个版块的点击率?首先我们来分析一下页面架构:如果我们认为,不管是点击右上角的「>」,还是点击留个圆形入口都算完成转化的话,我们现在的这个红色的 UI组件,入口位置一共有7个。根据长尾理论,如果我们把这个圆形入口从6个扩展到比如九个,是不是一定对转化率有正向影响?答案并不一定:为什么呢?因为主要是这样的改动会带来一个未知的泳道横划交互,它会产生一定的影响,如下图所示。用户看到这个泳道之后可能出现三种行为:用户完全不滑动——那入口就从6个变成了6.5个,别的没有变量影响。用户滑动看完了之后,点击某一个或者左上角的「>」进入——这是我们想要的转化用户滑动看了这些圆形入口之后松手,就是不点击进去——这是我们不愿意看到的结果想到这里,那为什么我们不能让用户直接滑动之后松手就跳转呢?想到这里,所以优化方案如下图所示,给与用户一个 x轴区间,滑动手势操过那个区间则告诉用户你现在松手默认跳转,用户不愿意跳转也可以回划,只要不足这个 x区间就给与用户自主选择的机会:我之前在上家工作的时候,我们把6个圆形入口变成了10个,然后用这个「松手跳转」的交互把单元模块的穿透率从21%提升到了31%,这是一个实战当中的真实例子。当然了,请大家再思考这样的一个问题:一个页面的流量就这么大,一个地方涨了11%,那势必别的地方就会相应的损失11%。一般情况下 app 首页承担着80%以上的分流工作,根据流量漏斗来看的话每一次引流都会导致其他模块的数据下降,所以设计师们应该要根据运营策略和公司大的产品 OKR 来合理选用合适的交互组件,以达到想要的目的,还是那句话:「小孩儿才分对错,大人只看利弊。」三、从交互的维度量化用户体验移动互联网产品设计中,尤其是在中国的 app 产品,有两大分歧阵营:「扁平」阵营表示了,我们需要产品足够扁平,最好就是三次交互可以触达所有 app 界面:「简洁」阵营也表示了,我们需要页面信息足够简洁,最好一个页面只完成一个核心任务:双方你来我往,谁也说服不了谁,如下图所示,「简洁」阵营反驳「扁平」阵营说:你们一点都不遵守席客定律,层级扁平是扁平了,但是相应的页面信息变得越来越多,给用户呈现的干扰就越来越多,用户做出决定的时间也越来越多,所以你们「扁平党」不行。这时候「扁平」阵营也找到了反驳的论点,他们说你看你页面足够简洁了,但是页面层级就很深啊,交互成本这么高,每一次都伴随流失,可用性这么差,你们还有理了?所以「简洁党」你们才不行。中国的互联网产品,很难做到既简洁又扁平,这个问题的根源在于永远有那么多信息需要呈现,永远有那么多功能需要添加,这个是中国的激烈市场竞争导致的,并不是说中国的产品就不如国外的好(我的哥哥之前在 Facebook 现在 Airbnb 工作,他经常感叹道国外的互联网产品到中国来真的都得死…)我想要讨论的是,面对中国现在互联网产品市场现状, 如果一款产品非要你站队上面两派阵营,你会选哪一派?我现在的选择是「扁平党」,因为用户面临一款眼花缭乱的 app,如果是经常使用,缺功能布局信息架构很少改动的前提下,早晚也会习惯和适应的,但是如果一些核心的东西不能第一时间暴露在用户眼中,很有可能用户就不知道你有这种功能。这个就是为什么我们设计经常会说这个产品经理傻逼吧,怎么什么东西都想展现出来,这一堆东西找个入口集合收起来页面多干净多清爽多好看。我早年间也是和诸位一样的观点,但是现在我越来越觉得,界面清爽了,你的大功能 feature 因为设计隐藏没有被发现,不是设计开发测试都白做了么,说好的 ROI 在哪里?我们大家都是互联网从业者,不管看到这篇文章的你是一位设计、产品、还是开发、测试、运营人员,我们都明白用户体验这个词是由 N多维度综合而成的一个过程性评价,它和方方面面都有关系。那既然是这么专业且牵连甚广的一个名词,我们真的就没有办法去量化评价它了吗?永远不要忘记,用户体验是个过程,而我们每个人也都是用户本身。在这里我提供一种普通用户维度的比较好用的用户体验评估方法是「穷举分析用户行为路径」。比如你是一款外卖产品的设计师,那么用户在不同产品模块下订一个外卖的流程路径大概有多少种,都穷举出来。比如你是一款在线演唱类的产品设计师,那么用户在产品中完成一首歌需要的用户路径到底有多少条,穷举所有路径之后一一优化,让路径变得更加扁平,或许是一个最「笨」但是有效的方案,怎么优化呢?用淘宝消息页举个例子:淘宝消息页上面有「交易物流」、「通知」、「互动」三个 tab,这时候我们假设一个用户三个按钮下面都有消息,用户想要看完这三个消息大概需要几次交互?答案是至少6次:「点击第一个进去 – 返回 – 点击第二个进去 – 返回 – 点击第三个进去 – 返回」,这样的交互显得呆板且冗长,淘宝团队巧妙的把三个内页集合成一个页面的三个 tab 形式,大大缩短的交互成本,这就是所谓的「把用户路径变得更扁平」:大家在使用很多产品的过程中,多多留心就会发现原来细节里面总有魔鬼。欢迎关注作者的微信公众号:「SeanyDesign」
2022年01月26日
129 阅读
0 评论
0 点赞
2022-01-25
使用docker-compose部署前端应用
公司测试服务器都使用docker compose 进行应用部署,之前没接触过,但是有不少项目在使用该方式,对相关的部署机制及命令做了简单了解后,开始尝试将近期开发的一个前端应用部署到测试服务器上部署目录:/root/zhuzl/monthly-report-ui/,该目录下有两个目录:一个 dist 为打包后的前端工程文件;一个 nginx为部署相关的目录。将打包后的文件上传到dist目录。在 nginx 目录添加如下文件:nginx.confuser nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #underscores_in_headers on; server { # listen 443 ssl; listen 80; server_name localhost; # ssl_certificate /usr/local/nginx/conf/server.crt; # ssl_certificate_key /usr/local/nginx/conf/server.key; root /usr/share/nginx/html; location /api/ { proxy_pass http://xxx/api/; proxy_redirect off; proxy_ignore_client_abort on; proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; proxy_pass_request_headers on; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }monthly-report-ui-docker-compose.ymlversion: '3' services: monthly-report-ui: #build: # context: . # dockerfile: Dockerfile image: nginx #image: rookiezoe/nginx #image: crunchgeek/nginx-pagespeed volumes: #- ./server.key:/usr/local/nginx/conf/server.key #- ./server.crt:/usr/local/nginx/conf/server.crt - ./nginx.conf:/etc/nginx/nginx.conf - ../dist:/usr/share/nginx/html #- ../helper:/usr/share/nginx/html/helper #- ../api-doc:/usr/share/nginx/html/api-doc ports: - "40001:80" #links: # - hydra #depends_on: # - hydra #environment: # - CONSOLE_VERSION=1.2 command: /bin/bash -c "exec nginx -g 'daemon off;'" networks: default: external: name: dev_oauth 安装以上方式准备好后,进入 nginx 目录,使用如下命令启动或停止应用:# 停止服务 docker-compose -f monthly-report-ui-docker-compose.yml down # 启动服务 docker-compose -f monthly-report-ui-docker-compose.yml up &服务启动后,即可使用http://<SERVER-IP>:40001访问应用本示例配置文件信息:monthly-report-ui.zip 以下内容已加密,请输入密码查看:
2022年01月25日
64 阅读
0 评论
0 点赞
2022-01-24
此内容被密码保护
加密文章,请前往内页查看详情
2022年01月24日
2 阅读
0 评论
0 点赞
2022-01-24
此内容被密码保护
加密文章,请前往内页查看详情
2022年01月24日
1 阅读
0 评论
0 点赞
2022-01-21
自动化项目部署系列:③部署宝塔面板
宝塔面板是一款服务器管理软件,支持windows和linux系统,可以通过Web端轻松管理服务器,提升运维效率。例如:创建管理网站、FTP、数据库,拥有可视化文件管理器,可视化软件管理器,可视化CPU、内存、流量监控图表,计划任务等功能。宝塔官网: https://www.bt.cn/选择使用宝塔,主要是为了更方便的在线管理服务器资源。官方有提供 Docker 版本:https://www.bt.cn/bbs/forum.php?mod=viewthread&tid=79499。但为了更好的应用于服务器管理,我将 Docker 直接安装于宿主机上。安装步骤创建宝塔根目录 /wldata/btroot 并为宝塔应用目录 /www 创建软连接[root@VM-16-6-centos wldata]# mkdir /wldata/btroot [root@VM-16-6-centos wldata]# ln -s /wldata/btroot /www [root@VM-16-6-centos wldata]#开始安装根据官网:https://www.bt.cn/bbs/thread-19376-1-1.html 使用如下命令进行安装yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh安装过程记录如下:[root@VM-16-6-centos wldata]# yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile docker-ce-stable | 3.5 kB 00:00:00 epel | 4.7 kB 00:00:00 extras | 2.9 kB 00:00:00 os | 3.6 kB 00:00:00 updates | 2.9 kB 00:00:00 Package wget-1.14-18.el7_6.1.x86_64 already installed and latest version Nothing to do --2022-01-21 16:16:44-- http://download.bt.cn/install/install_6.0.sh Resolving download.bt.cn (download.bt.cn)... 116.10.184.143, 240e:a5:4200:89::143 Connecting to download.bt.cn (download.bt.cn)|116.10.184.143|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 26258 (26K) [application/octet-stream] Saving to: ‘install.sh’ 100%[========================================================================================================>] 26,258 --.-K/s in 0.03s 2022-01-21 16:16:44 (765 KB/s) - ‘install.sh’ saved [26258/26258] install.sh: line 23: [: : integer expression expected +---------------------------------------------------------------------- | Bt-WebPanel FOR CentOS/Ubuntu/Debian +---------------------------------------------------------------------- | Copyright © 2015-2099 BT-SOFT(http://www.bt.cn) All rights reserved. +---------------------------------------------------------------------- | The WebPanel URL will be http://SERVER_IP:8888 when installed. +---------------------------------------------------------------------- Do you want to install Bt-Panel to the /www directory now?(y/n): y --------------------------------------------- Selected download node... Download node: http://dg2.bt.cn --------------------------------------------- Synchronizing system time... Fri Jan 21 16:16:52 CST 2022 Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile docker-ce-stable | 3.5 kB 00:00:00 epel | 4.7 kB 00:00:00 extras | 2.9 kB 00:00:00 os | 3.6 kB 00:00:00 updates | 2.9 kB 00:00:00 Package ntp-4.2.6p5-29.el7.centos.2.x86_64 already installed and latest version Nothing to do 21 Jan 16:16:52 ntpdate[28087]: the NTP socket is in use, exiting setenforce: SELinux is disabled Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile Package wget-1.14-18.el7_6.1.x86_64 already installed and latest version Package 2:tar-1.26-35.el7.x86_64 already installed and latest version Package gcc-4.8.5-44.el7.x86_64 already installed and latest version Package 1:make-3.82-24.el7.x86_64 already installed and latest version Package zip-3.0-11.el7.x86_64 already installed and latest version Package gcc-4.8.5-44.el7.x86_64 already installed and latest version Package libxml2-2.9.1-6.el7_9.6.x86_64 already installed and latest version Package zlib-1.2.7-19.el7_9.x86_64 already installed and latest version Package zlib-devel-1.2.7-19.el7_9.x86_64 already installed and latest version Package libwebp-0.3.0-10.el7_9.x86_64 already installed and latest version Package freetype-2.8-14.el7_9.1.x86_64 already installed and latest version Package lsof-4.87-6.el7.x86_64 already installed and latest version Package pcre-8.32-17.el7.x86_64 already installed and latest version Package pcre-devel-8.32-17.el7.x86_64 already installed and latest version No package vixie-cron available. Package crontabs-1.11-6.20121102git.el7.noarch already installed and latest version Package xz-devel-5.2.2-1.el7.x86_64 already installed and latest version Resolving Dependencies --> Running transaction check ---> Package bzip2-devel.x86_64 0:1.0.6-13.el7 will be installed ---> Package c-ares.x86_64 0:1.10.0-3.el7 will be installed ---> Package freetype-devel.x86_64 0:2.8-14.el7_9.1 will be installed ---> Package gdbm-devel.x86_64 0:1.10-8.el7 will be installed ---> Package icu.x86_64 0:50.2-4.el7_7 will be installed ---> Package libcurl-devel.x86_64 0:7.29.0-59.el7_9.1 will be installed epel/7/x86_64/filelists_db | 12 MB 00:00:01 os/7/x86_64/filelists_db | 7.2 MB 00:00:00 updates/7/x86_64/filelists_db | 7.2 MB 00:00:00 ---> Package libdb4-devel.x86_64 0:4.8.30-13.el7 will be installed --> Processing Dependency: libdb4(x86-64) = 4.8.30-13.el7 for package: libdb4-devel-4.8.30-13.el7.x86_64 ---> Package libffi-devel.x86_64 0:3.0.13-19.el7 will be installed ---> Package libicu-devel.x86_64 0:50.2-4.el7_7 will be installed ---> Package libjpeg-turbo-devel.x86_64 0:1.2.90-8.el7 will be installed ---> Package libpcap-devel.x86_64 14:1.5.3-12.el7 will be installed ---> Package libpng-devel.x86_64 2:1.5.13-8.el7 will be installed ---> Package libwebp-devel.x86_64 0:0.3.0-10.el7_9 will be installed ---> Package libxml2-devel.x86_64 0:2.9.1-6.el7_9.6 will be installed ---> Package libxslt.x86_64 0:1.1.28-6.el7 will be installed ---> Package libxslt-devel.x86_64 0:1.1.28-6.el7 will be installed --> Processing Dependency: libgcrypt-devel for package: libxslt-devel-1.1.28-6.el7.x86_64 ---> Package libxslt-python.x86_64 0:1.1.28-6.el7 will be installed ---> Package ncurses-devel.x86_64 0:5.9-14.20130511.el7_4 will be installed ---> Package openssl.x86_64 1:1.0.2k-22.el7_9 will be updated ---> Package openssl.x86_64 1:1.0.2k-24.el7_9 will be an update --> Processing Dependency: openssl-libs(x86-64) = 1:1.0.2k-24.el7_9 for package: 1:openssl-1.0.2k-24.el7_9.x86_64 ---> Package openssl-devel.x86_64 1:1.0.2k-22.el7_9 will be updated ---> Package openssl-devel.x86_64 1:1.0.2k-24.el7_9 will be an update ---> Package readline-devel.x86_64 0:6.2-11.el7 will be installed ---> Package sqlite-devel.x86_64 0:3.7.17-8.el7_7.1 will be installed ---> Package tk-devel.x86_64 1:8.5.13-6.el7 will be installed --> Processing Dependency: tcl-devel = 1:8.5.13 for package: 1:tk-devel-8.5.13-6.el7.x86_64 --> Processing Dependency: libXft-devel for package: 1:tk-devel-8.5.13-6.el7.x86_64 --> Processing Dependency: libX11-devel for package: 1:tk-devel-8.5.13-6.el7.x86_64 ---> Package unzip.x86_64 0:6.0-22.el7_9 will be updated ---> Package unzip.x86_64 0:6.0-24.el7_9 will be an update --> Running transaction check ---> Package libX11-devel.x86_64 0:1.6.7-4.el7_9 will be installed --> Processing Dependency: pkgconfig(xcb) >= 1.11.1 for package: libX11-devel-1.6.7-4.el7_9.x86_64 --> Processing Dependency: pkgconfig(xproto) for package: libX11-devel-1.6.7-4.el7_9.x86_64 --> Processing Dependency: pkgconfig(xcb) for package: libX11-devel-1.6.7-4.el7_9.x86_64 --> Processing Dependency: pkgconfig(kbproto) for package: libX11-devel-1.6.7-4.el7_9.x86_64 ---> Package libXft-devel.x86_64 0:2.3.2-2.el7 will be installed --> Processing Dependency: pkgconfig(xrender) for package: libXft-devel-2.3.2-2.el7.x86_64 --> Processing Dependency: pkgconfig(fontconfig) for package: libXft-devel-2.3.2-2.el7.x86_64 ---> Package libdb4.x86_64 0:4.8.30-13.el7 will be installed ---> Package libgcrypt-devel.x86_64 0:1.5.3-14.el7 will be installed --> Processing Dependency: libgpg-error-devel for package: libgcrypt-devel-1.5.3-14.el7.x86_64 ---> Package openssl-libs.x86_64 1:1.0.2k-22.el7_9 will be updated ---> Package openssl-libs.x86_64 1:1.0.2k-24.el7_9 will be an update ---> Package tcl-devel.x86_64 1:8.5.13-8.el7 will be installed --> Running transaction check ---> Package fontconfig-devel.x86_64 0:2.13.0-4.3.el7 will be installed --> Processing Dependency: pkgconfig(uuid) for package: fontconfig-devel-2.13.0-4.3.el7.x86_64 --> Processing Dependency: pkgconfig(expat) for package: fontconfig-devel-2.13.0-4.3.el7.x86_64 ---> Package libXrender-devel.x86_64 0:0.9.10-1.el7 will be installed ---> Package libgpg-error-devel.x86_64 0:1.12-3.el7 will be installed ---> Package libxcb-devel.x86_64 0:1.13-1.el7 will be installed --> Processing Dependency: pkgconfig(xau) >= 0.99.2 for package: libxcb-devel-1.13-1.el7.x86_64 ---> Package xorg-x11-proto-devel.noarch 0:2018.4-1.el7 will be installed --> Running transaction check ---> Package expat-devel.x86_64 0:2.1.0-12.el7 will be installed ---> Package libXau-devel.x86_64 0:1.0.8-2.1.el7 will be installed ---> Package libuuid-devel.x86_64 0:2.23.2-65.el7_9.1 will be installed --> Finished Dependency Resolution Dependencies Resolved ================================================================================================================================================== Package Arch Version Repository Size ================================================================================================================================================== Installing: bzip2-devel x86_64 1.0.6-13.el7 os 218 k c-ares x86_64 1.10.0-3.el7 os 78 k freetype-devel x86_64 2.8-14.el7_9.1 updates 447 k gdbm-devel x86_64 1.10-8.el7 os 47 k icu x86_64 50.2-4.el7_7 os 187 k libcurl-devel x86_64 7.29.0-59.el7_9.1 updates 303 k libdb4-devel x86_64 4.8.30-13.el7 epel 32 k libffi-devel x86_64 3.0.13-19.el7 os 23 k libicu-devel x86_64 50.2-4.el7_7 os 703 k libjpeg-turbo-devel x86_64 1.2.90-8.el7 os 99 k libpcap-devel x86_64 14:1.5.3-12.el7 os 118 k libpng-devel x86_64 2:1.5.13-8.el7 os 122 k libwebp-devel x86_64 0.3.0-10.el7_9 updates 23 k libxml2-devel x86_64 2.9.1-6.el7_9.6 updates 1.1 M libxslt x86_64 1.1.28-6.el7 os 242 k libxslt-devel x86_64 1.1.28-6.el7 os 309 k libxslt-python x86_64 1.1.28-6.el7 os 59 k ncurses-devel x86_64 5.9-14.20130511.el7_4 os 712 k readline-devel x86_64 6.2-11.el7 os 139 k sqlite-devel x86_64 3.7.17-8.el7_7.1 os 104 k tk-devel x86_64 1:8.5.13-6.el7 os 488 k Updating: openssl x86_64 1:1.0.2k-24.el7_9 updates 494 k openssl-devel x86_64 1:1.0.2k-24.el7_9 updates 1.5 M unzip x86_64 6.0-24.el7_9 updates 172 k Installing for dependencies: expat-devel x86_64 2.1.0-12.el7 os 57 k fontconfig-devel x86_64 2.13.0-4.3.el7 os 138 k libX11-devel x86_64 1.6.7-4.el7_9 updates 981 k libXau-devel x86_64 1.0.8-2.1.el7 os 14 k libXft-devel x86_64 2.3.2-2.el7 os 19 k libXrender-devel x86_64 0.9.10-1.el7 os 17 k libdb4 x86_64 4.8.30-13.el7 epel 607 k libgcrypt-devel x86_64 1.5.3-14.el7 os 129 k libgpg-error-devel x86_64 1.12-3.el7 os 16 k libuuid-devel x86_64 2.23.2-65.el7_9.1 updates 93 k libxcb-devel x86_64 1.13-1.el7 os 1.1 M tcl-devel x86_64 1:8.5.13-8.el7 os 165 k xorg-x11-proto-devel noarch 2018.4-1.el7 os 280 k Updating for dependencies: openssl-libs x86_64 1:1.0.2k-24.el7_9 updates 1.2 M Transaction Summary ================================================================================================================================================== Install 21 Packages (+13 Dependent packages) Upgrade 3 Packages (+ 1 Dependent package) Total download size: 12 M Downloading packages: No Presto metadata available for updates (1/38): c-ares-1.10.0-3.el7.x86_64.rpm | 78 kB 00:00:00 (2/38): bzip2-devel-1.0.6-13.el7.x86_64.rpm | 218 kB 00:00:00 (3/38): expat-devel-2.1.0-12.el7.x86_64.rpm | 57 kB 00:00:00 (4/38): gdbm-devel-1.10-8.el7.x86_64.rpm | 47 kB 00:00:00 (5/38): fontconfig-devel-2.13.0-4.3.el7.x86_64.rpm | 138 kB 00:00:00 (6/38): libXau-devel-1.0.8-2.1.el7.x86_64.rpm | 14 kB 00:00:00 (7/38): libXft-devel-2.3.2-2.el7.x86_64.rpm | 19 kB 00:00:00 (8/38): icu-50.2-4.el7_7.x86_64.rpm | 187 kB 00:00:00 (9/38): freetype-devel-2.8-14.el7_9.1.x86_64.rpm | 447 kB 00:00:00 (10/38): libXrender-devel-0.9.10-1.el7.x86_64.rpm | 17 kB 00:00:00 (11/38): libffi-devel-3.0.13-19.el7.x86_64.rpm | 23 kB 00:00:00 (12/38): libX11-devel-1.6.7-4.el7_9.x86_64.rpm | 981 kB 00:00:00 (13/38): libdb4-devel-4.8.30-13.el7.x86_64.rpm | 32 kB 00:00:00 (14/38): libgpg-error-devel-1.12-3.el7.x86_64.rpm | 16 kB 00:00:00 (15/38): libgcrypt-devel-1.5.3-14.el7.x86_64.rpm | 129 kB 00:00:00 (16/38): libcurl-devel-7.29.0-59.el7_9.1.x86_64.rpm | 303 kB 00:00:00 (17/38): libdb4-4.8.30-13.el7.x86_64.rpm | 607 kB 00:00:00 (18/38): libjpeg-turbo-devel-1.2.90-8.el7.x86_64.rpm | 99 kB 00:00:00 (19/38): libicu-devel-50.2-4.el7_7.x86_64.rpm | 703 kB 00:00:00 (20/38): libpcap-devel-1.5.3-12.el7.x86_64.rpm | 118 kB 00:00:00 (21/38): libwebp-devel-0.3.0-10.el7_9.x86_64.rpm | 23 kB 00:00:00 (22/38): libpng-devel-1.5.13-8.el7.x86_64.rpm | 122 kB 00:00:00 (23/38): libuuid-devel-2.23.2-65.el7_9.1.x86_64.rpm | 93 kB 00:00:00 (24/38): libxslt-1.1.28-6.el7.x86_64.rpm | 242 kB 00:00:00 (25/38): libxcb-devel-1.13-1.el7.x86_64.rpm | 1.1 MB 00:00:00 (26/38): libxml2-devel-2.9.1-6.el7_9.6.x86_64.rpm | 1.1 MB 00:00:00 (27/38): libxslt-python-1.1.28-6.el7.x86_64.rpm | 59 kB 00:00:00 (28/38): libxslt-devel-1.1.28-6.el7.x86_64.rpm | 309 kB 00:00:00 (29/38): openssl-1.0.2k-24.el7_9.x86_64.rpm | 494 kB 00:00:00 (30/38): ncurses-devel-5.9-14.20130511.el7_4.x86_64.rpm | 712 kB 00:00:00 (31/38): openssl-devel-1.0.2k-24.el7_9.x86_64.rpm | 1.5 MB 00:00:00 (32/38): sqlite-devel-3.7.17-8.el7_7.1.x86_64.rpm | 104 kB 00:00:00 (33/38): readline-devel-6.2-11.el7.x86_64.rpm | 139 kB 00:00:00 (34/38): tcl-devel-8.5.13-8.el7.x86_64.rpm | 165 kB 00:00:00 (35/38): openssl-libs-1.0.2k-24.el7_9.x86_64.rpm | 1.2 MB 00:00:00 (36/38): unzip-6.0-24.el7_9.x86_64.rpm | 172 kB 00:00:00 (37/38): xorg-x11-proto-devel-2018.4-1.el7.noarch.rpm | 280 kB 00:00:00 (38/38): tk-devel-8.5.13-6.el7.x86_64.rpm | 488 kB 00:00:00 -------------------------------------------------------------------------------------------------------------------------------------------------- Total 4.3 MB/s | 12 MB 00:00:02 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : xorg-x11-proto-devel-2018.4-1.el7.noarch 1/42 Installing : libxslt-1.1.28-6.el7.x86_64 2/42 Updating : 1:openssl-libs-1.0.2k-24.el7_9.x86_64 3/42 Installing : libXau-devel-1.0.8-2.1.el7.x86_64 4/42 Installing : libxcb-devel-1.13-1.el7.x86_64 5/42 Installing : libX11-devel-1.6.7-4.el7_9.x86_64 6/42 Installing : libXrender-devel-0.9.10-1.el7.x86_64 7/42 Installing : 1:tcl-devel-8.5.13-8.el7.x86_64 8/42 Installing : libdb4-4.8.30-13.el7.x86_64 9/42 Installing : libgpg-error-devel-1.12-3.el7.x86_64 10/42 Installing : libgcrypt-devel-1.5.3-14.el7.x86_64 11/42 Installing : expat-devel-2.1.0-12.el7.x86_64 12/42 Installing : ncurses-devel-5.9-14.20130511.el7_4.x86_64 13/42 Installing : libuuid-devel-2.23.2-65.el7_9.1.x86_64 14/42 Installing : libxml2-devel-2.9.1-6.el7_9.6.x86_64 15/42 Installing : 2:libpng-devel-1.5.13-8.el7.x86_64 16/42 Installing : freetype-devel-2.8-14.el7_9.1.x86_64 17/42 Installing : fontconfig-devel-2.13.0-4.3.el7.x86_64 18/42 Installing : libXft-devel-2.3.2-2.el7.x86_64 19/42 Installing : 1:tk-devel-8.5.13-6.el7.x86_64 20/42 Installing : libxslt-devel-1.1.28-6.el7.x86_64 21/42 Installing : readline-devel-6.2-11.el7.x86_64 22/42 Installing : libdb4-devel-4.8.30-13.el7.x86_64 23/42 Updating : 1:openssl-devel-1.0.2k-24.el7_9.x86_64 24/42 Updating : 1:openssl-1.0.2k-24.el7_9.x86_64 25/42 Installing : libxslt-python-1.1.28-6.el7.x86_64 26/42 Installing : sqlite-devel-3.7.17-8.el7_7.1.x86_64 27/42 Installing : libcurl-devel-7.29.0-59.el7_9.1.x86_64 28/42 Installing : icu-50.2-4.el7_7.x86_64 29/42 Installing : libwebp-devel-0.3.0-10.el7_9.x86_64 30/42 Installing : c-ares-1.10.0-3.el7.x86_64 31/42 Installing : bzip2-devel-1.0.6-13.el7.x86_64 32/42 Installing : gdbm-devel-1.10-8.el7.x86_64 33/42 Installing : 14:libpcap-devel-1.5.3-12.el7.x86_64 34/42 Updating : unzip-6.0-24.el7_9.x86_64 35/42 Installing : libffi-devel-3.0.13-19.el7.x86_64 36/42 Installing : libicu-devel-50.2-4.el7_7.x86_64 37/42 Installing : libjpeg-turbo-devel-1.2.90-8.el7.x86_64 38/42 Cleanup : 1:openssl-devel-1.0.2k-22.el7_9.x86_64 39/42 Cleanup : 1:openssl-1.0.2k-22.el7_9.x86_64 40/42 Cleanup : 1:openssl-libs-1.0.2k-22.el7_9.x86_64 41/42 Cleanup : unzip-6.0-22.el7_9.x86_64 42/42 Verifying : libXft-devel-2.3.2-2.el7.x86_64 1/42 Verifying : 2:libpng-devel-1.5.13-8.el7.x86_64 2/42 Verifying : libxslt-python-1.1.28-6.el7.x86_64 3/42 Verifying : libxml2-devel-2.9.1-6.el7_9.6.x86_64 4/42 Verifying : libjpeg-turbo-devel-1.2.90-8.el7.x86_64 5/42 Verifying : 1:openssl-libs-1.0.2k-24.el7_9.x86_64 6/42 Verifying : libicu-devel-50.2-4.el7_7.x86_64 7/42 Verifying : libffi-devel-3.0.13-19.el7.x86_64 8/42 Verifying : unzip-6.0-24.el7_9.x86_64 9/42 Verifying : libuuid-devel-2.23.2-65.el7_9.1.x86_64 10/42 Verifying : libdb4-devel-4.8.30-13.el7.x86_64 11/42 Verifying : 14:libpcap-devel-1.5.3-12.el7.x86_64 12/42 Verifying : gdbm-devel-1.10-8.el7.x86_64 13/42 Verifying : bzip2-devel-1.0.6-13.el7.x86_64 14/42 Verifying : fontconfig-devel-2.13.0-4.3.el7.x86_64 15/42 Verifying : 1:openssl-devel-1.0.2k-24.el7_9.x86_64 16/42 Verifying : libxslt-devel-1.1.28-6.el7.x86_64 17/42 Verifying : ncurses-devel-5.9-14.20130511.el7_4.x86_64 18/42 Verifying : readline-devel-6.2-11.el7.x86_64 19/42 Verifying : libX11-devel-1.6.7-4.el7_9.x86_64 20/42 Verifying : 1:tk-devel-8.5.13-6.el7.x86_64 21/42 Verifying : c-ares-1.10.0-3.el7.x86_64 22/42 Verifying : 1:openssl-1.0.2k-24.el7_9.x86_64 23/42 Verifying : xorg-x11-proto-devel-2018.4-1.el7.noarch 24/42 Verifying : libXrender-devel-0.9.10-1.el7.x86_64 25/42 Verifying : libwebp-devel-0.3.0-10.el7_9.x86_64 26/42 Verifying : icu-50.2-4.el7_7.x86_64 27/42 Verifying : freetype-devel-2.8-14.el7_9.1.x86_64 28/42 Verifying : libgcrypt-devel-1.5.3-14.el7.x86_64 29/42 Verifying : libcurl-devel-7.29.0-59.el7_9.1.x86_64 30/42 Verifying : expat-devel-2.1.0-12.el7.x86_64 31/42 Verifying : libxcb-devel-1.13-1.el7.x86_64 32/42 Verifying : sqlite-devel-3.7.17-8.el7_7.1.x86_64 33/42 Verifying : libxslt-1.1.28-6.el7.x86_64 34/42 Verifying : libgpg-error-devel-1.12-3.el7.x86_64 35/42 Verifying : libdb4-4.8.30-13.el7.x86_64 36/42 Verifying : 1:tcl-devel-8.5.13-8.el7.x86_64 37/42 Verifying : libXau-devel-1.0.8-2.1.el7.x86_64 38/42 Verifying : 1:openssl-1.0.2k-22.el7_9.x86_64 39/42 Verifying : 1:openssl-devel-1.0.2k-22.el7_9.x86_64 40/42 Verifying : 1:openssl-libs-1.0.2k-22.el7_9.x86_64 41/42 Verifying : unzip-6.0-22.el7_9.x86_64 42/42 Installed: bzip2-devel.x86_64 0:1.0.6-13.el7 c-ares.x86_64 0:1.10.0-3.el7 freetype-devel.x86_64 0:2.8-14.el7_9.1 gdbm-devel.x86_64 0:1.10-8.el7 icu.x86_64 0:50.2-4.el7_7 libcurl-devel.x86_64 0:7.29.0-59.el7_9.1 libdb4-devel.x86_64 0:4.8.30-13.el7 libffi-devel.x86_64 0:3.0.13-19.el7 libicu-devel.x86_64 0:50.2-4.el7_7 libjpeg-turbo-devel.x86_64 0:1.2.90-8.el7 libpcap-devel.x86_64 14:1.5.3-12.el7 libpng-devel.x86_64 2:1.5.13-8.el7 libwebp-devel.x86_64 0:0.3.0-10.el7_9 libxml2-devel.x86_64 0:2.9.1-6.el7_9.6 libxslt.x86_64 0:1.1.28-6.el7 libxslt-devel.x86_64 0:1.1.28-6.el7 libxslt-python.x86_64 0:1.1.28-6.el7 ncurses-devel.x86_64 0:5.9-14.20130511.el7_4 readline-devel.x86_64 0:6.2-11.el7 sqlite-devel.x86_64 0:3.7.17-8.el7_7.1 tk-devel.x86_64 1:8.5.13-6.el7 Dependency Installed: expat-devel.x86_64 0:2.1.0-12.el7 fontconfig-devel.x86_64 0:2.13.0-4.3.el7 libX11-devel.x86_64 0:1.6.7-4.el7_9 libXau-devel.x86_64 0:1.0.8-2.1.el7 libXft-devel.x86_64 0:2.3.2-2.el7 libXrender-devel.x86_64 0:0.9.10-1.el7 libdb4.x86_64 0:4.8.30-13.el7 libgcrypt-devel.x86_64 0:1.5.3-14.el7 libgpg-error-devel.x86_64 0:1.12-3.el7 libuuid-devel.x86_64 0:2.23.2-65.el7_9.1 libxcb-devel.x86_64 0:1.13-1.el7 tcl-devel.x86_64 1:8.5.13-8.el7 xorg-x11-proto-devel.noarch 0:2018.4-1.el7 Updated: openssl.x86_64 1:1.0.2k-24.el7_9 openssl-devel.x86_64 1:1.0.2k-24.el7_9 unzip.x86_64 0:6.0-24.el7_9 Dependency Updated: openssl-libs.x86_64 1:1.0.2k-24.el7_9 Complete! Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile Package libxslt-1.1.28-6.el7.x86_64 already installed and latest version Package libxslt-devel-1.1.28-6.el7.x86_64 already installed and latest version Package libxslt-python-1.1.28-6.el7.x86_64 already installed and latest version Nothing to do Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile Package libjpeg-turbo-devel-1.2.90-8.el7.x86_64 already installed and latest version Nothing to do Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile No package vixie-cron available. Error: Nothing to do Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile Package libdb4-devel-4.8.30-13.el7.x86_64 already installed and latest version Nothing to do Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile Resolving Dependencies --> Running transaction check ---> Package epel-release.noarch 0:7-14 will be installed --> Finished Dependency Resolution Dependencies Resolved ================================================================================================================================================== Package Arch Version Repository Size ================================================================================================================================================== Installing: epel-release noarch 7-14 epel 15 k Transaction Summary ================================================================================================================================================== Install 1 Package Total download size: 15 k Installed size: 25 k Downloading packages: epel-release-7-14.noarch.rpm | 15 kB 00:00:00 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : epel-release-7-14.noarch 1/1 Verifying : epel-release-7-14.noarch 1/1 Installed: epel-release.noarch 0:7-14 Complete! https://mirrors.tencent.com/pypi/simple cat: /etc/.productinfo: No such file or directory OS: el - 7 --2022-01-21 16:17:30-- http://dg2.bt.cn/install/pyenv/pyenv-el7-x64.tar.gz Resolving dg2.bt.cn (dg2.bt.cn)... 116.10.184.219, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|116.10.184.219|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 73258911 (70M) [application/octet-stream] Saving to: ‘/www/pyenv.tar.gz’ 100%[========================================================================================================>] 73,258,911 10.0MB/s in 7.2s 2022-01-21 16:17:37 (9.68 MB/s) - ‘/www/pyenv.tar.gz’ saved [73258911/73258911] Install python env... Python 3.7.9 --2022-01-21 16:17:40-- http://dg2.bt.cn/install/src/bt6.init Resolving dg2.bt.cn (dg2.bt.cn)... 116.10.184.219, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|116.10.184.219|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 9873 (9.6K) [application/octet-stream] Saving to: ‘/etc/init.d/bt’ 100%[========================================================================================================>] 9,873 --.-K/s in 0s 2022-01-21 16:17:40 (229 MB/s) - ‘/etc/init.d/bt’ saved [9873/9873] --2022-01-21 16:17:40-- http://dg2.bt.cn/install/public.sh Resolving dg2.bt.cn (dg2.bt.cn)... 42.157.129.47, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|42.157.129.47|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 4267 (4.2K) [application/octet-stream] Saving to: ‘/www/server/panel/install/public.sh’ 100%[========================================================================================================>] 4,267 --.-K/s in 0s 2022-01-21 16:17:40 (419 MB/s) - ‘/www/server/panel/install/public.sh’ saved [4267/4267] --2022-01-21 16:17:40-- http://dg2.bt.cn/install/src/panel6.zip Resolving dg2.bt.cn (dg2.bt.cn)... 42.157.129.47, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|42.157.129.47|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 9230063 (8.8M) [application/zip] Saving to: ‘panel.zip’ 100%[========================================================================================================>] 9,230,063 24.7MB/s in 0.4s 2022-01-21 16:17:40 (24.7 MB/s) - ‘panel.zip’ saved [9230063/9230063] --2022-01-21 16:17:40-- http://dg2.bt.cn/install/src/bt7.init Resolving dg2.bt.cn (dg2.bt.cn)... 42.157.129.47, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|42.157.129.47|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 10438 (10K) [application/octet-stream] Saving to: ‘/etc/init.d/bt’ 100%[========================================================================================================>] 10,438 --.-K/s in 0s 2022-01-21 16:17:41 (238 MB/s) - ‘/etc/init.d/bt’ saved [10438/10438] --2022-01-21 16:17:41-- http://dg2.bt.cn/install/src/bt7.init Resolving dg2.bt.cn (dg2.bt.cn)... 42.157.129.47, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|42.157.129.47|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 10438 (10K) [application/octet-stream] Saving to: ‘/www/server/panel/init.sh’ 100%[========================================================================================================>] 10,438 --.-K/s in 0.006s 2022-01-21 16:17:41 (1.68 MB/s) - ‘/www/server/panel/init.sh’ saved [10438/10438] --2022-01-21 16:17:41-- http://dg2.bt.cn/install/conf/softList.conf Resolving dg2.bt.cn (dg2.bt.cn)... 116.10.184.219, 240e:a5:4200:89::143 Connecting to dg2.bt.cn (dg2.bt.cn)|116.10.184.219|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 3287 (3.2K) [application/octet-stream] Saving to: ‘/www/server/panel/data/softList.conf’ 100%[========================================================================================================>] 3,287 --.-K/s in 0s 2022-01-21 16:17:41 (348 MB/s) - ‘/www/server/panel/data/softList.conf’ saved [3287/3287] Starting Bt-Panel.... done Starting Bt-Tasks... done username: t1c2mbup Stopping Bt-Tasks... done Stopping Bt-Panel... done Starting Bt-Panel.... done Starting Bt-Tasks... done Loaded plugins: fastestmirror, langpacks Repository epel is listed more than once in the configuration Loading mirror speeds from cached hostfile Package firewalld-0.6.3-13.el7_9.noarch already installed and latest version Nothing to do Created symlink from /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service to /usr/lib/systemd/system/firewalld.service. Created symlink from /etc/systemd/system/multi-user.target.wants/firewalld.service to /usr/lib/systemd/system/firewalld.service. success ================================================================== Congratulations! Installed successfully! ================================================================== 外网面板地址: http://110.40.150.131:8888/ac9859fb 内网面板地址: http://172.17.16.6:8888/ac9859fb username: t1c2mbup password: b6abed29 If you cannot access the panel, release the following panel port [8888] in the security group 若无法访问面板,请检查防火墙/安全组是否有放行面板[8888]端口 ================================================================== Time consumed: 1 Minute! [root@VM-16-6-centos wldata]# 根据安装成功的登录信息登录宝塔面板并重置相关登录信息
2022年01月21日
146 阅读
0 评论
0 点赞
2022-01-21
自动化项目部署系列:②部署Docker
容器化部署技术在自动化部署过程中有着举足轻重的位置,本章将讲解docker的安装部署。安装过程主要参考 docker官网的文档:https://docs.docker.com/engine/install/centos/安装相关依赖sudo yum install -y yum-utils显示信息如下:[zhuzl@VM-16-6-centos /]$ sudo yum install -y yum-utils Loaded plugins: fastestmirror, langpacks Determining fastest mirrors epel | 4.7 kB 00:00:00 extras | 2.9 kB 00:00:00 os | 3.6 kB 00:00:00 updates | 2.9 kB 00:00:00 (1/3): epel/7/x86_64/updateinfo | 1.0 MB 00:00:00 (2/3): updates/7/x86_64/primary_db | 13 MB 00:00:00 (3/3): epel/7/x86_64/primary_db | 7.0 MB 00:00:01 Package yum-utils-1.1.31-54.el7_8.noarch already installed and latest version Nothing to do设置yum 仓库sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo显示显示如下:[zhuzl@VM-16-6-centos /]$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo Loaded plugins: fastestmirror, langpacks adding repo from: https://download.docker.com/linux/centos/docker-ce.repo grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo repo saved to /etc/yum.repos.d/docker-ce.repo 安装 Docker[zhuzl@VM-16-6-centos /]$ sudo yum install docker-ce docker-ce-cli containerd.io Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile docker-ce-stable | 3.5 kB 00:00:00 (1/2): docker-ce-stable/7/x86_64/primary_db | 70 kB 00:00:00 (2/2): docker-ce-stable/7/x86_64/updateinfo | 55 B 00:00:01 Resolving Dependencies --> Running transaction check ---> Package containerd.io.x86_64 0:1.4.12-3.1.el7 will be installed --> Processing Dependency: container-selinux >= 2:2.74 for package: containerd.io-1.4.12-3.1.el7.x86_64 ---> Package docker-ce.x86_64 3:20.10.12-3.el7 will be installed --> Processing Dependency: docker-ce-rootless-extras for package: 3:docker-ce-20.10.12-3.el7.x86_64 --> Processing Dependency: libcgroup for package: 3:docker-ce-20.10.12-3.el7.x86_64 ---> Package docker-ce-cli.x86_64 1:20.10.12-3.el7 will be installed --> Processing Dependency: docker-scan-plugin(x86-64) for package: 1:docker-ce-cli-20.10.12-3.el7.x86_64 --> Running transaction check ---> Package container-selinux.noarch 2:2.119.2-1.911c772.el7_8 will be installed --> Processing Dependency: policycoreutils-python for package: 2:container-selinux-2.119.2-1.911c772.el7_8.noarch ---> Package docker-ce-rootless-extras.x86_64 0:20.10.12-3.el7 will be installed --> Processing Dependency: fuse-overlayfs >= 0.7 for package: docker-ce-rootless-extras-20.10.12-3.el7.x86_64 --> Processing Dependency: slirp4netns >= 0.4 for package: docker-ce-rootless-extras-20.10.12-3.el7.x86_64 ---> Package docker-scan-plugin.x86_64 0:0.12.0-3.el7 will be installed ---> Package libcgroup.x86_64 0:0.41-21.el7 will be installed --> Running transaction check ---> Package fuse-overlayfs.x86_64 0:0.7.2-6.el7_8 will be installed --> Processing Dependency: libfuse3.so.3(FUSE_3.2)(64bit) for package: fuse-overlayfs-0.7.2-6.el7_8.x86_64 --> Processing Dependency: libfuse3.so.3(FUSE_3.0)(64bit) for package: fuse-overlayfs-0.7.2-6.el7_8.x86_64 --> Processing Dependency: libfuse3.so.3()(64bit) for package: fuse-overlayfs-0.7.2-6.el7_8.x86_64 ---> Package policycoreutils-python.x86_64 0:2.5-34.el7 will be installed --> Processing Dependency: setools-libs >= 3.3.8-4 for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: libsemanage-python >= 2.5-14 for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: audit-libs-python >= 2.1.3-4 for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: python-IPy for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: libqpol.so.1(VERS_1.4)(64bit) for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: libqpol.so.1(VERS_1.2)(64bit) for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: libapol.so.4(VERS_4.0)(64bit) for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: checkpolicy for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: libqpol.so.1()(64bit) for package: policycoreutils-python-2.5-34.el7.x86_64 --> Processing Dependency: libapol.so.4()(64bit) for package: policycoreutils-python-2.5-34.el7.x86_64 ---> Package slirp4netns.x86_64 0:0.4.3-4.el7_8 will be installed --> Running transaction check ---> Package audit-libs-python.x86_64 0:2.8.5-4.el7 will be installed ---> Package checkpolicy.x86_64 0:2.5-8.el7 will be installed ---> Package fuse3-libs.x86_64 0:3.6.1-4.el7 will be installed ---> Package libsemanage-python.x86_64 0:2.5-14.el7 will be installed ---> Package python-IPy.noarch 0:0.75-6.el7 will be installed ---> Package setools-libs.x86_64 0:3.3.8-4.el7 will be installed --> Finished Dependency Resolution Dependencies Resolved ================================================================================================================================================== Package Arch Version Repository Size ================================================================================================================================================== Installing: containerd.io x86_64 1.4.12-3.1.el7 docker-ce-stable 28 M docker-ce x86_64 3:20.10.12-3.el7 docker-ce-stable 23 M docker-ce-cli x86_64 1:20.10.12-3.el7 docker-ce-stable 30 M Installing for dependencies: audit-libs-python x86_64 2.8.5-4.el7 os 76 k checkpolicy x86_64 2.5-8.el7 os 295 k container-selinux noarch 2:2.119.2-1.911c772.el7_8 extras 40 k docker-ce-rootless-extras x86_64 20.10.12-3.el7 docker-ce-stable 8.0 M docker-scan-plugin x86_64 0.12.0-3.el7 docker-ce-stable 3.7 M fuse-overlayfs x86_64 0.7.2-6.el7_8 extras 54 k fuse3-libs x86_64 3.6.1-4.el7 extras 82 k libcgroup x86_64 0.41-21.el7 os 66 k libsemanage-python x86_64 2.5-14.el7 os 113 k policycoreutils-python x86_64 2.5-34.el7 os 457 k python-IPy noarch 0.75-6.el7 os 32 k setools-libs x86_64 3.3.8-4.el7 os 620 k slirp4netns x86_64 0.4.3-4.el7_8 extras 81 k Transaction Summary ================================================================================================================================================== Install 3 Packages (+13 Dependent packages) Total download size: 95 M Installed size: 387 M Is this ok [y/d/N]: y Downloading packages: (1/16): container-selinux-2.119.2-1.911c772.el7_8.noarch.rpm | 40 kB 00:00:00 (2/16): audit-libs-python-2.8.5-4.el7.x86_64.rpm | 76 kB 00:00:00 (3/16): checkpolicy-2.5-8.el7.x86_64.rpm | 295 kB 00:00:00 warning: /var/cache/yum/x86_64/7/docker-ce-stable/packages/docker-ce-20.10.12-3.el7.x86_64.rpm: Header V4 RSA/SHA512 Signature, key ID 621e9f35: NOKEY Public key for docker-ce-20.10.12-3.el7.x86_64.rpm is not installed (4/16): docker-ce-20.10.12-3.el7.x86_64.rpm | 23 MB 00:03:30 (5/16): containerd.io-1.4.12-3.1.el7.x86_64.rpm | 28 MB 00:11:41 (6/16): docker-ce-rootless-extras-20.10.12-3.el7.x86_64.rpm | 8.0 MB 00:02:47 (7/16): fuse-overlayfs-0.7.2-6.el7_8.x86_64.rpm | 54 kB 00:00:00 (8/16): fuse3-libs-3.6.1-4.el7.x86_64.rpm | 82 kB 00:00:00 (9/16): libcgroup-0.41-21.el7.x86_64.rpm | 66 kB 00:00:00 (10/16): libsemanage-python-2.5-14.el7.x86_64.rpm | 113 kB 00:00:00 (11/16): python-IPy-0.75-6.el7.noarch.rpm | 32 kB 00:00:00 (12/16): policycoreutils-python-2.5-34.el7.x86_64.rpm | 457 kB 00:00:00 (13/16): slirp4netns-0.4.3-4.el7_8.x86_64.rpm | 81 kB 00:00:00 (14/16): setools-libs-3.3.8-4.el7.x86_64.rpm | 620 kB 00:00:00 (15/16): docker-scan-plugin-0.12.0-3.el7.x86_64.rpm | 3.7 MB 00:00:48 (16/16): docker-ce-cli-20.10.12-3.el7.x86_64.rpm | 30 MB 00:15:19 -------------------------------------------------------------------------------------------------------------------------------------------------- Total 86 kB/s | 95 MB 00:18:49 Retrieving key from https://download.docker.com/linux/centos/gpg Importing GPG key 0x621E9F35: Userid : "Docker Release (CE rpm) <docker@docker.com>" Fingerprint: 060a 61c5 1b55 8a7f 742b 77aa c52f eb6b 621e 9f35 From : https://download.docker.com/linux/centos/gpg Is this ok [y/N]: y Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : docker-scan-plugin-0.12.0-3.el7.x86_64 1/16 Installing : 1:docker-ce-cli-20.10.12-3.el7.x86_64 2/16 Installing : libcgroup-0.41-21.el7.x86_64 3/16 Installing : setools-libs-3.3.8-4.el7.x86_64 4/16 Installing : audit-libs-python-2.8.5-4.el7.x86_64 5/16 Installing : python-IPy-0.75-6.el7.noarch 6/16 Installing : slirp4netns-0.4.3-4.el7_8.x86_64 7/16 Installing : libsemanage-python-2.5-14.el7.x86_64 8/16 Installing : fuse3-libs-3.6.1-4.el7.x86_64 9/16 Installing : fuse-overlayfs-0.7.2-6.el7_8.x86_64 10/16 Installing : checkpolicy-2.5-8.el7.x86_64 11/16 Installing : policycoreutils-python-2.5-34.el7.x86_64 12/16 Installing : 2:container-selinux-2.119.2-1.911c772.el7_8.noarch 13/16 setsebool: SELinux is disabled. Installing : containerd.io-1.4.12-3.1.el7.x86_64 14/16 Installing : docker-ce-rootless-extras-20.10.12-3.el7.x86_64 15/16 Installing : 3:docker-ce-20.10.12-3.el7.x86_64 16/16 Verifying : checkpolicy-2.5-8.el7.x86_64 1/16 Verifying : fuse3-libs-3.6.1-4.el7.x86_64 2/16 Verifying : 1:docker-ce-cli-20.10.12-3.el7.x86_64 3/16 Verifying : fuse-overlayfs-0.7.2-6.el7_8.x86_64 4/16 Verifying : libsemanage-python-2.5-14.el7.x86_64 5/16 Verifying : docker-scan-plugin-0.12.0-3.el7.x86_64 6/16 Verifying : slirp4netns-0.4.3-4.el7_8.x86_64 7/16 Verifying : 2:container-selinux-2.119.2-1.911c772.el7_8.noarch 8/16 Verifying : python-IPy-0.75-6.el7.noarch 9/16 Verifying : policycoreutils-python-2.5-34.el7.x86_64 10/16 Verifying : audit-libs-python-2.8.5-4.el7.x86_64 11/16 Verifying : docker-ce-rootless-extras-20.10.12-3.el7.x86_64 12/16 Verifying : containerd.io-1.4.12-3.1.el7.x86_64 13/16 Verifying : setools-libs-3.3.8-4.el7.x86_64 14/16 Verifying : 3:docker-ce-20.10.12-3.el7.x86_64 15/16 Verifying : libcgroup-0.41-21.el7.x86_64 16/16 Installed: containerd.io.x86_64 0:1.4.12-3.1.el7 docker-ce.x86_64 3:20.10.12-3.el7 docker-ce-cli.x86_64 1:20.10.12-3.el7 Dependency Installed: audit-libs-python.x86_64 0:2.8.5-4.el7 checkpolicy.x86_64 0:2.5-8.el7 container-selinux.noarch 2:2.119.2-1.911c772.el7_8 docker-ce-rootless-extras.x86_64 0:20.10.12-3.el7 docker-scan-plugin.x86_64 0:0.12.0-3.el7 fuse-overlayfs.x86_64 0:0.7.2-6.el7_8 fuse3-libs.x86_64 0:3.6.1-4.el7 libcgroup.x86_64 0:0.41-21.el7 libsemanage-python.x86_64 0:2.5-14.el7 policycoreutils-python.x86_64 0:2.5-34.el7 python-IPy.noarch 0:0.75-6.el7 setools-libs.x86_64 0:3.3.8-4.el7 slirp4netns.x86_64 0:0.4.3-4.el7_8 Complete! 启动 Docker 并配置自启动启动Dockersudo systemctl start docker设置开机自启动# 检查docker版本 [zhuzl@VM-16-6-centos wldata]$ docker -v Docker version 20.10.12, build e91ed57 # 查看docker已有镜像 [zhuzl@VM-16-6-centos wldata]$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE # 设置docker开机启动 [zhuzl@VM-16-6-centos wldata]$ sudo systemctl enable docker Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service. [zhuzl@VM-16-6-centos wldata]$ 一些配置使用阿里云配置镜像加速sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://9643wkwg.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker安装portainer链接地址:https://docs.portainer.io/v/ce-2.11/start/install/server/docker/linux安装命令# 创建portainer挂载镜像 sudo docker volume create portainer_data # 查看磁盘卷信息 sudo docker inspect portainer_data # docker方式运行portainer sudo docker run -d -p 8000:8000 -p 9000:9000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:2.11.0可以使用nginx代理9000端口端外提供服务
2022年01月21日
199 阅读
0 评论
0 点赞
2022-01-21
自动化项目部署系列:①环境准备
以前的项目有基于 devops 的方式来实现项目的开发、测试、上线部署,零零散散在带团队的过程中也有过一些尝试,但是一直没时间系统性的实践过。本次新购一台云服务器,准备进行一次实践。服务器在腾讯云上买的4H8G5M的配置,在这一台上部署相关的环境可能会很吃力,不过咱们也就基于学习研究的目的,够用就行。一、解析泛域名到云服务器系统安装完毕,为便于后续对外提供服务,我们先解析一个泛域名到这台服务器上,我注册的域名都在DNSPOD上管理,所以就在DNSPOD上配置了:解析配置好后,*.test.wljy.xyz都将被解析到 110.40.150.131 这个IP.二、初始配置服务器添加普通权限用户此处添加 zhuzl 用户,并为该用户设置登录密码:[root@VM-16-6-centos ~]# useradd zhuzl [root@VM-16-6-centos ~]# passwd zhuzl Changing password for user zhuzl. New password: Retype new password: passwd: all authentication tokens updated successfully.将用户添加到soduers/etc/sudoers文件添加写权限[root@VM-16-6-centos ~]# chmod +w /etc/sudoers编辑 /etc/sudoers[root@VM-16-6-centos ~]# vi /etc/sudoers找到 root ALL=(ALL) ALL 并在其下一行添加如下信息:zhuzl ALL=(ALL) NOPASSWD: ALL调整后的效果截图:取消/etc/sudoers 的写权限[root@VM-16-6-centos ~]# chmod -w /etc/sudoers创建数据目录并授权[root@VM-16-6-centos /]# mkdir /wldata [root@VM-16-6-centos /]# chown -R zhuzl:zhuzl /wldata/ 切换为普通用户[root@VM-16-6-centos /]# su zhuzl使用普通用户执行后续的安装操作,相关操作见后续章节。
2022年01月21日
121 阅读
0 评论
0 点赞
2021-12-29
初入职场注意事项
今天一个前不久离职的同事找到我聊聊天,刚加入了一家新公司,很多东西都不懂,同事关系处理的不是很好,之前经常看我的文章,想知道有没有什么技巧,可以更快学到东西,不用依靠同事,快速获得领导的认可。聊着聊着发现还真可以总结几点初入职场,经常犯的小错误,第一就要属爱当伸手党,有的时候爱向人请教,看上去是一个好习惯,但实际上请教的时候也要分请教的技巧,你不能什么不知道的都问,你应该经过思考以后提问。比如说你不会做一个PPT,你不能问对方说这个PPT应该怎么做呀,更好的提问方式是,这个PPT我想这么做,12345你帮我看看对不对,这样对方会比较容易愿意给出你指导,因为你是经过思考过的,而不是在一片空白的情况下,所有的空需要别人帮你填满。这种伸手党的行为是不可取的,职场不是学校,同事和领导没有义务替你回答所有的问题,替你把人生的空填上,而你什么都用疑问式的提问,很容易给别人留下什么都不懂,自己不爱动脑子的印象。第二,“我是来学东西的”,通过降低自己的身价来获取工作机会。比如说经常有人来跟我面试的时候说,我就是来学习的,只要给我这个学习机会,我什么都愿意做。看似这样很诚恳的发言,其实并不能帮他争取到工作机会,因为在我的视角里,我会这么判定这个人,他对自己的价值没有认知。也就是他提供不出与这个工作岗位匹配的价值。我开的不是学校,我开的是公司,我需要有人来干活儿,我愿意付他匹配的工资,只要他能把活干好,而不是找一群免费劳动力,我反而还要教他,浪费我的时间。刚入职场,一定要区分清楚职场跟学校之间的差别,建立自己的价值,用自己的价值去交换,而不是降低自己的身价。愿意给你这样工作机会的环境,也不是什么好环境,只是想占你便宜。最后说说获得领导的认可,我在一家公司的时候,我的一位领导跟我说了一句话,我一直记忆犹新,他说,向上管理,就是“管理好领导的期望”。这句话我默默记住了很久,后来过了两年才领悟这个意思。不是把一件事儿做完了,完成任务了,就交差了。留给领导去检查去吧,他提了意见我再改,很多人都是这样干的,领导就是你的免费改卷机器。后来改稿越来越多,领导累他也累。接收了一项工作任务,首先是要弄清楚,自己的领导在期待什么,他脑袋里对这件事情的蓝图是怎样的。然后需要做好反馈,她在什么点上需要了解情况,在什么点上她希望被你邀请介入,在什么点上觉得他应该听一次汇报,你得判断得出来这个节奏,然后再每一个期待的点上向领导进行反馈,最后交出的答卷再检查一遍是不是符合领导交代的要求。没有这个反馈的节奏,你就会导致你的上级对你失控,他对你一旦产生失控感,信任度就会下降,不安全感就会上升。以上内容来自老同事【文子】的朋友圈分享
2021年12月29日
103 阅读
0 评论
0 点赞
2021-12-29
王小波的计算机水平有多好?
转载自:https://www.zhihu.com/question/20964366/answer/95202867作者:姚勇今天逛知乎的时候,偶然看到王小波外甥,原水木年华成员,《QQ 炫舞》创始人——姚勇先生的一篇回答,讲述了他回忆中王小波在编程领域中的造诣,看完对王小波有了新的认识,现分享给大家看下。下面是正文:以下文字都是我本人写的。为了让阅读方便,减少模糊性。我在文中以第三人视角描述。而不用 “我舅舅 “,” 我姥姥 “,” 我大舅舅 “,” 我小舅舅 “之类的称呼。这是一个十几年软件工程师的习惯而已。我舅舅是王小波,我是个禁不住人肉的老百姓,资质平庸。王小波一篇《我是怎样做青年思想工作》的杂文在我青年单身适龄的年代,给我个人做了最大程度的广告,算是他老人家留给我最宝贵的遗产之一。除此之外,还有他那台用了很久的 PC,在他去世前一年送了我。他是汇编高手,自己用汇编写了 DOS 下的汉字输入法。输入法有个功能,可以加 4 声。一般拼音输入法没有这功能,这需要普通话读音正确的使用者,90 年代总共也没几种汉字输入法。看过他用自己输入法打汉字,速度几乎和盲打英文一样。那时候我大学,正在疯狂苦练吉他,并没有弄懂他用了何种技术。虽然我从小学也用 Z80 和 APPLE II 做些机器码编程做游戏,但到了中学为了高考,计算机就荒废了。他人生最后几年,WINDOWS 3.2 开始普及,然后是 WIN95。他开始从 DOS 转向 WIN32 SDK 编程。最终非常任性地熬夜苦干了很长时间,把输入法转为 WIN32 SDK 在 WINDOWS 下正常工作。当时 WINDOWS 已经有不少汉字输入法,尤其到了 WIN95 时代,他这么做也只是出于兴趣而不是在 DOS 时代的必须。就 WIN32 SDK 我们还讨论过一些。那时候 win32 sdk 真的是很难用。主要是互联网还没有(我是 96 年后用上的),资料很少,匈牙利命名法陌生。WIN32 的体系和 DOS 大相径庭,全部都很陌生,而且完全抛弃了 DOS 那套东西。所有 90 年代 DOS 底层程序员向 WIN32 的迁徙都是一部血泪史。他人生最后一年,多媒体开始普及。光驱,声卡,2D 加速卡普及。95-96 年他开始和我合计做多媒体出版。原因现在想起来很悲伤 ------ 他的书很难在大陆出版,他不得不想办法让他的作品能够让更多人看见,而平时维持生计,只能忍着严重的痔疮,每日给各种杂志写杂文稿件。多媒体的兴起,让他感觉也许利用他的技术优势,自行制作电子出版物。配上插图,音乐,用电子书籍方式可以让自己作品为大众所见。他给我看了已经制作了一部分的 DEMO,就和现在用 Micromedia 多媒体制作软件制作的电子多媒体制品很像。电子书内容是黄金时代,有图案的背景,按空格可以翻页。同时有音乐,好像偶尔还会有一点动画的特效(我记不清了) 。但当时的 DEMO 完全是他自己用 WIN32 写的 EXE 可执行程序,他那是从 WIN32 SDK 移植输入法时,基本掌握了消息处理,GDI,窗口控制等方法。还有一些底层的 TRICK。我大学后和王小波比较近,那几年每周末都在他母亲家相聚。他母亲 5 个儿女,只有他 1 个人在国内。于是他就经常住在母亲家,怕老人一个人,是个大孝子。母亲家旁边有个筒子楼里 2 间房的小屋,公共厕所和水房,冬天暖气烧得极热。偶尔我会和他住一块,里屋是他的生活起居和写作桌,外屋很小只有张床,他弟弟的孩子和我偶然会睡外屋。印象最深的就是他烧普洱茶的电炉和玻璃茶壶,里面总是有极浓的茶水,下面沉淀着大量茶叶,估计熬夜就靠那个。目睹了他后半截人生(从我记事起到他去世,中间他出过很多年),感慨很多。王小波有个八叔,当时双手打算盘,无比聪明是个天才。年少夭折。王小波父亲因为搞逻辑学,引起毛泽东注意,被毛接见聊了一下逻辑学的学术相关问题。69 心脏病发独自倒在家中过世。王小波去世方式和他父亲一模一样,区别仅在更加年轻。王小波哥哥在受文革影响挖了 10 年煤,后来去美国博士念数理逻辑,解决了一个 100 多年未解的数理逻辑问题。最后的博士毕业论文自己系的教授搞不定,专门找了数学系的教授来看。毕业后他哥哥专心开饭馆挣钱养育 2 个女儿成人。准备财务自由后再著书立传。我的最大感慨是,留得青山在,不怕没柴烧。天才也好,资质普通如我也好,人都只有一个人生。王小波妈妈去年 92 岁,10 月刚去世,白发人送走了自己两个儿子,又过了十几年才走。之前得过癌症,靠自己毅力锻炼,恢复了。王小波去世后一年,王小波弟弟又突然去世。当时孩子都没敢告诉老人。老太太感觉不对,但又无法得知真相,只是和我说她的疑心。当时国内只有我一人,只能瞒着,不停劝慰老人没事。老太太喃喃说,“除死无大事,除死无大事啊” 。除死无大事。人生在世,碰到什么坎,都只会是你人生中一个普通的节点。时间一长,什么都会成为过眼云烟。留份宽心,也许这样导致目标感没有强到能和社会精英逐鹿。但只要有耐心,不怕挫折,假以时日,目标总会实现的。作为一个 42 岁还在以玩游戏看动漫为生活主体的大叔,保持健康和有活力地追求智慧和有趣,做自己热爱的工作,保持善良,做精神自由的人,是我从王小波的人生学到最重要的东西。希望每个喜欢王小波的人,都有自己充实和健康的身体和精神。(完)
2021年12月29日
21 阅读
0 评论
0 点赞
2021-12-19
使用WinSW.exe安装FRP为系统启动服务
本机 FRP 所在目录:D:\greenSoft\FRP\frp_0.36.2_windows_amd64日常使用FRP,我们都需要使用命令行的方式启动,特此我准备了个startFRP.bat的启动文件:frpc.exe -c ./frpc.ini该方式可手工启动,但会打开一个命令行的黑窗口在桌面上,在使用电脑的过程中偶尔会由于误操作导致关闭了,根据以往在windows下使用nginx的经验,我们可以使用WinSW.exe这个小工具实现将FRP注册成系统服务即可,操作步骤如下:1、下载 WinSW.exe 文件到FRP所在目录2、在该目录下新建winsw.xml文件,文件内容如下所示:<service> <id>frp</id> <name>frp</name> <description>用frp发布本地电脑网站到外网</description> <executable>frpc</executable> <arguments>-c frpc.ini</arguments> <logmode>reset</logmode> </service>3、在命令行中使用 winsw install 安装为服务。执行该命令后会显示成功提示。4、进入windows 服务管理器,查看是否存在名称为frp的服务。
2021年12月19日
115 阅读
0 评论
0 点赞
2021-12-07
Typescript的14个基础语法
一.Ts是什么首先,强类型不允许随意的隐式类型转换,而弱类型是允许的。JavaScript就是经典的弱类型语言。而Typescript可以说是JavaScript的超集,在JS的基础上新增了许多语法特性,使得类型不再可以随意转换,能大大减少开发阶段的错误。二. 基本语法1.声明原始数据类型在变量后面指定一个关键字表示其只能为什么类型。// string类型: const a: string = 'auroras' // number类型: const b: number = 666 // 包括 NAN Infinity // boolean类型: const c: boolean = true // null类型: const d: null = null // undefined类型: const e: undefined = undefined // symbol类型: const h: symbol = Symbol()2.声明Object类型首先,object类型不单单可以指定对象,还可以指定数组或函数:const foo1: object = {}; const foo2: object = []; const foo3: object = function(){};如果只想指定为对象,如下,对象属性都要提前声明好类型:const obj: {name: string,age: number} = { name: '北极光', age:18 }3.1声明数组类型可以指定声明Array且通过<>指定元素类型,比如指定声明元素都为数字的数组:const arr: Array<number> = [1,2,3] // OR const arr: number[] = [1,2,3]3.2声明元组类型就是要提前指定数组里每个元素的类型,严格一一对应:const tuple: [number,string,boolean] = [666,'auraros',true]4.声明枚举类型通过关键字enum声明一个枚举类型,如:enum Status { pedding = 1, resolve = 2, reject = '3' } //访问 console.log(Status.pedding);如果全不写值,默认值为从0开始递增。如果第一个元素为字符类型,就必须全部定义值。如果第一个元素指定为一个数字,后面元素不写值,那值为第一个元素值按位置大小递增的结果。5.函数参数与返回类型函数声明式:指定函数传入参数类型,指定返回值类型,调用时传入参数个数与类型都必须相同:括号里指定每个参数类型,括号右边指定返回值的类型。function fun (name:string,age:number):string{ return 'sss' } fun('auroras',18);如果传入参数不确定传不传,那么可以给参数加个‘?’表明它是可选的:function fun (name:string,age?:number):string{ return 'sss' } fun('auroras');或者给参数添加默认值,那也会成为可选参数:function fun (name:string,age:number=666):string{ return 'sss' } fun('auroras');如果参数个数不确定,可以用扩展运算符加解构赋值表示,当然要传入与指定类型一致的:function fun (name:string,age:number=666,...res:number[]):string{ return 'sss' } fun('auroras',1,2,3);函数表达式:const fun2:(name:string,age:number)=>string = function(name:string,age:number){ return 'sss' }6.任意类型通过指定any关键字表示任意类型,跟原来 js 一样,可以任意赋不同类型的值:let num:any = 1; num = 'a'; num = true;7.类型断言类型断言就是明确的告诉typescript这个变量就是某种类型的,百分之百确定。不用typescript在一些情况下要自己推断某些没有明确定义或者多变的场景是什么类型。可以通过 as+类型 断言它就是某种类型的:const res = 1; const num = res as number;也可以通过 <类型> 形式断言(不推荐):const res = 1; const num = <number>res8.接口基本使用接口可以理解为一种规范,一种契约。可以约束一个对象里应该有哪些成员,这些成员都是怎么样的。通过interface定义一个Post接口,这个接口是一个对象,规则为有一个name属性类型为string,age属性类型为number。interface Post { name:string; age:number }然后比如有一个函数 printPost ,它的参数 post 使用我们定义的 Post 接口的规则,那么调用此函数传参时要传入符合 Post 接口规则的数据。interface Post { name:string; age:number } function printPost(post: Post){ console.log(post.name); console.log(post.age); } printPost({name:'asd',age:666})当然,函数传参时可能有些参数是可选的,那么我们可以给接口也定义可选的成员,通过属性后加一个‘ ? ’指定 可选成员 :interface Post { name:string; age:number; sex?:string; } const auroras: Post = { name:'asd', age: 18 }如果用 readonly 修饰成员,那么这个成员属性在初始化后便不可修改:interface Post { name:string; age:number; sex?:string; readonly like:string } const auroras: Post = { name:'asd', age: 18, like: 'natrue' } auroras.name = 'aaaa'; //保错 auroras.like = 'wind';如果连成员属性名称都不确定,那么可以声明 动态成员 ,要指定成员名字类型与成员值的类型,如:interface Post { [prop:string]:string } const auroras: Post = { name:'asd', like: 'natrue' }9.类基本使用描述一类具体事物的抽象特征。ts增强了es6中class类的相关语法。首先,类的属性使用前必须提前声明好:class Person { name: string; age: number; constructor(name:string,age:number){ this.name = name; this.age = age; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); } }10.类的访问修饰符private 修饰私有属性,只能在类内部访问。 public 修饰公用属性(默认),外部也可访问:class Person { public name: string; private age: number; constructor(name:string,age:number){ this.name = name; this.age = age; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); console.log(this.age); } } const jack = new Person('jack',20); //Person类公有属性可以访问 console.log(jack.name); //Person类私有属性不可以访问 console.log(jack.age);protected 修饰为受保护的,外部也不可访问。但与 private 的区别是若是继承的子类是可以访问的。class Person { public name: string; private age: number; // protected protected gender: boolean; constructor(name:string,age:number){ this.name = name; this.age = age; this.gender = true; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); console.log(this.age); } } class children extends Person{ constructor(name:string,age:number){ super(name,age,); //可以访问 console.log(this.gender); } }11.类只读属性给属性设置 readonly 则为只读属性,该属性初始化后便不可再修改。class Person { public name: string; private age: number; // readonly protected readonly gender: boolean; constructor(name:string,age:number){ this.name = name; this.age = age; this.gender = true; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); console.log(this.age); } }12.类与接口一些类与类之间有些许共同的特征,这些共同的特征可以抽象成为接口。比如 Person 类和 Animal 类,虽然是不同类,但是人和动物都会吃东西和走路等,这些共同的特征可以由接口定义。最后一个特征就定义一个接口。//吃接口 interface Eat { eat(food:string):void } //行进接口 interface Run { run(behavior:string):void } //人 class People implements Eat,Run { eat(food:string){ console.log(`在餐桌上吃${food}`); } run(behavior:string){ console.log(`站着${behavior}`); } } //动物 class Animal implements Eat,Run { eat(food:string){ console.log(`在地上上吃${food}`); } run(behavior:string){ console.log(`爬着${behavior}`); } }13.抽象类约束子类必须有某些成员,有点类似接口,不同的是抽象类可以包含一些具体的实现。比如动物类应该为一个抽象类,它的子类有猫,狗,熊猫等。它们都是动物,也有一些共同的特征。定义一个类为抽象类后,就不能再new实例了,只能被其子类继承。其中 abstract 定义抽象类,类里用 abstract 定义一个抽象方法,子类必须实现抽象方法。abstract class Animal { eat(food:string){ console.log(`在地上吃${food}`); } abstract run (behavior:string):void } //猫 class Dog extends Animal{ run(behavior:string):void{ console.log(behavior); } } const d1 = new Dog(); d1.eat('骨头') d1.run('四脚爬行') //兔子 class rabbit extends Animal{ run(behavior:string):void{ console.log(behavior); } } const r1 = new rabbit(); d1.eat('萝卜') d1.run('蹦蹦跳跳') 14.泛型泛型就是在定义函数,接口或者类的时候没有指定具体类型,等到使用时才指定具体类型。极大程度的复用代码。比如有一个 identity 函数,这个函数会返回任何传入它的值,且传入的类型与返回的类型应该是相同的。如果传入数字,不用泛型的话,这个函数可能是下面这样: function identity(arg:number):number{ return arg }如果传入字符串,这个函数可能是下面这样: function identity(arg:string):string{ return arg }这样的话太麻烦,所以可以使用泛型,一般用大写 T 表示泛型,它可以适用于多个类型,且传入类型与返回类型是相同的。 function identity<T>(arg:T):T{ return arg }
2021年12月07日
150 阅读
0 评论
0 点赞
2021-11-30
简单理解 Typescript 的配置文件 tsconfig.json
TS 使用 tsconfig.json 作为其配置文件,他主要包括两块内容:指定待编译的文件定义编译选项一般来说, tsconfig.json 文件所处的路径就是当前 TS 项目的根路径。tsconfig.json 的配置项众多并且复杂。所有的选项可以参考官方文档:https://www.typescriptlang.org/zh/tsconfig 这里我们分析一个简单示例:{ "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "sourceMap": true, "declaration": true }, "files": [ "app.ts", "foo.ts", ] }其中, compilerOptions 用来配置编译选项, files 用来指定待编译文件。这里的待编译文件是指入口文件,任何被入口文件依赖的文件。也可以使用 include 和 exclude 来指定和排除待编译文件:{ "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }下面简述 compilerOptions 中的选项配置,这些配置简单来说就是影响整个编译的过程和编译的结果。module - 生成的 Javascript 模块形式:none、commonjs、amd、system、umd、es6、es2015 或 esnextnoImplicitAny - 存在隐式 any 时抛错 (默认为 false)sourceMap - 生成 map 文件 (默认为 false)declaration - 生成对应的 .d.ts 文件 (默认为 false)
2021年11月30日
307 阅读
0 评论
0 点赞
2021-11-05
win11资源管理器菜单栏+右键菜单改回win10风格
一、上方菜单栏1、win + R 输入 regedit 打开注册表2、依次展开(或地址栏复制)HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions3、右击 Shell Extensions 创建项key命名为 Blocked (如果没有)4、打开 Blocked ,右击空白处,新建字符串值。将值/名称命名为:{e2bf9676-5f8f-435c-97eb-11607a5bedf7}5、重启电脑注:如需调回至win 11 explorer,直接删除blocked 文件夹二、右键菜单1、win+r 输入 regedit 打开注册表2、依次展开(或地址栏复制)HKEY_CURRENT_USER\Software\Classes\CLSID3、右键CLSIDkey 然后新建项Key ,重命名为{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}4、右键此key {86ca1aa0-34aa-4e8b-a509-50c905bae2a2}, 新建一个key并重命名为 InprocServer325、打开 InprocServer32 ,右侧窗口双击打开 default 文件,回车保存。(设置空白text数据)6、关闭注册表,重启电脑或者使用如下修改注册表批处理语句:reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve taskkill /f /im explorer.exe & start explorer.exe若需还原把 reg add 改成 reg delete 即可
2021年11月05日
103 阅读
0 评论
0 点赞
2021-09-06
如何防止他人恶意调试你的web程序
前言看到社区很多都在讨论 如何调试,如何高级的调试,以及一些调试的奇技淫巧 ,今天我想和大家聊聊, 怎么禁止调试,禁止他人调试我们的程序 为什么会有这篇文章呢,源自一次我寻找盗版电影的遭遇,一次好奇心的驱使下,由于很多这种平台都是只做搬运,不做存储,因为存储盗版电影向他人提供是违法的,特别是那种刚出的新电影! 当时好奇想通过看某站的控制台,想了解一下他们是怎么是通过啥接口,怎么请求,请求来的格式啥样的,抱着这样的好奇心,开始了我的奇妙之旅... 看完本篇文章你将学会我无法断定你能学到什么,但是以下是我希望你能从本篇文章中学到的:1.如何简单的防止你的程序被他人恶意调试2.逆向思维学会如何更好的调试具体实现防止调试的方法,这里我们主要是通过不断 debugger 的方法来疯狂输出断点,让控制台打开后程序就无法正常执行我们都知道 debugger 只有在控制台被打开的时候才会执行,所以后面的所有方法都是围绕着这一特性来进行,废话不多说,我将通过以下几个案例向你们展示道高一尺魔高一丈的道理,先上代码:方法一:(() => { function block() { setInterval(() => { debugger; }, 50); } try { block(); } catch (err) {} })(); 通过上方的代码我们可以看到,在页面中打开控制台后,会有以下结果:需要在这里说明以下几点:程序被 debugger 阻泄了,我们无法像以往一样在 Source Tab 中的对应 js 代码处添加断点调试,无法调试程序的执行逻辑.在程序异常复杂且被混淆后的代码是异常难读的!通常我们会在 source 的左边加上 breakpoint 来让程序每次走到加点的地方停下来,以便让我们查看一些变量的值或是步骤的流程逻辑(如下图所示)我们都知道,第一次打开控制台是看不到 Network tab 中的任何请求的,所以我们想通过 Network tab 来查看网页都做了哪些请求,也是看不到的,当我们打开控制台就会出 debugger 阻挡我们,我们可以通过下面的解决方法来处理,或者是用抓包工具来查看具体的请求大家可以先不看解决方法,想想如果是你,这个时候怎么突破这个屏障呢? 第一次遇到这种情况我也是很懵,不知道咋处理,后面发现问题简直不要太简单,我们可以带着疑问来看: 对于第一个示例,我们如何解决?(绕过它) 答案是: 禁止断点 可以看到很简单,在 Chrome 控制台的 Source Tab 页点击 Deactivate breakpoints 按钮或者按下 Ctrl + f8(如下图所示)。但是对于控制台不熟悉的小伙伴,很难会想到这里去.但是,难道这篇文章就这样结束了?那我可顶不住小伙伴们的 "就这?" 其实,上面的解决方法并没有帮我们解决根本问题,我们需要做的是调试,上面虽然把debugger都去掉了,但是我们也无法在通过点击每一行代码左边的行号添加 breakpoint 了,所以根本性的问题,并没有解决,只是去除了那碍眼的疯狂 debugger,我们还是得另辟蹊径方法二:对对应的代码行,通过添加 logpoint 为 false,然后按回车后刷新网页,发现成功跳过无限 debugger,于是我们就可以愉快的自由调试了~对应的还有一种方法即通过 add script ignore list 来添加需要忽略执行代码行或文件可以看到,我们也可以通过删除 script ignore list 里已添加的忽略代码,恢复初始状态但是,你这么聪明,那人家不得想想对策?对于上面的第一个方法将setInterval(() => {debugger;}, 50);写在一行中,你即使通过添加logpoint为 false,也没用,仍然是疯狂 debugger,即使你可能想到,通过左下角的代码格式化,来格式一下setInterval(() => {debugger;}, 50);将它变成多行的,也是没用的,仍然会在刷新后重新弹 debugger(() => { function block() { setInterval(() => {debugger;}, 50); } try { block(); } catch (err) {} })();对于第二个方法,我们对代码进行如下改造(() => { function block() { setInterval(() => { Function("debugger")(); }, 50); } try { block(); } catch (err) {} })();我们可以通过将debugger改写成Function("debugger")();的形式,来应对;Function 构造器生成的 debugger 会在每一次执行时开启一个临时 js 文件,哈哈~对方表示好无奈于是会有以下结果 这无限套娃,真够狠的,我们要坚信正义最后总会胜利,不能给想非法调试我们程序的人机会,所以我们要把各种情况都考虑周全,可以说这种方法是最恨的,但是这还不算完~ (好家伙~ 想非法调试我程序,那你就得战胜我)强化以上方法 上面的代码由于没有加密混淆,多少可能还是会被别人读一些,那么我们加密混淆看看是啥样的好家伙,你这咋读?eval(function(c,g,a,b,d,e){d=String;if(!"".replace(/^/,String)){for(;a--;)e[a]=b[a]||a;b=[function(f){return e[f]}];d=function(){return"\\w+"};a=1}for(;a--;)b[a]&&(c=c.replace(new RegExp("\\b"+d(a)+"\\b","g"),b[a]));return c}('(()=>{1 0(){2(()=>{3("4")()},5)}6{0()}7(8){}})();',9,9,"block function setInterval Function debugger 50 try catch err".split(" "),0,{}));格式化后的样子我们继续对代码进行改造,让对方尽量的难以识别我们的代码将 Function("debugger").call() 改成 (function(){return false;})["constructor"]("debugger")["call"](); 并且,添加条件,当窗口外部宽高,和内部宽高的差值大于一定的值,我把 body 里的内容全部清空掉,看你还能不能操作我的按钮啊啥的~哈哈哈需要特别说明的是 : 像 toG 的项目或者是一些为了保护自己的版权又或者是一些比较敏感的项目,出于安全的考虑在部署到生产环境后最好是不让别人调试的,当然,前端所做的也就那么一些,需要前后端一起配合,便可以很好的对项目或者数据进行私密的保护 最后: 附上这份未混淆的来之不易的的代码(记得混淆后使用哦~) 一定要记得点赞加关注~原创太不容易了.你可以把它当作你的工具函数,在需要不让别人轻易调试的项目中引用 (当然它仍然有很多漏洞,但是我们可以用类似的逻辑方法,变化出很多更复杂的限制,最暴力的甚至可以当控制台打开后就立马通过window.close();来关闭调试窗口),欢迎大家在评论区留下更好玩的方法~共同学习(() => { function block() { if ( window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200 ) { document.body.innerHTML = "检测到非法调试,请关闭后刷新重试!"; } setInterval(() => { (function () { return false; } ["constructor"]("debugger") ["call"]()); }, 50); } try { block(); } catch (err) {} })();推荐一个调试页面的小技巧说了那么多的防止被人调试,那么最后也说一个本人觉得眼前一亮的调试样式的方法通过给 style 标签添加 style="display: block" , contenteditable 两个属性实现在页面中便捷的调试样式复制下方代码到你的 html 文件中,玩一下~<!DOCTYPE html> <body> <div>来调试我吧~</div> <style style="display: block" contenteditable> body { background-color: rgb(140, 209, 230); color: white; } div { background-color: green; width: 300px; height: 300px; line-height: 300px; text-align: center; } </style> </body> 最后我所知道的禁止调试的方法就只有如上所述,但是肯定还有很多好玩的,小伙伴们可以在评论区留言,一起共同学习~最后抛出一个问题, 如何监测控制台是否被打开 (我上面提到的通过window内外高宽的差值其实可以通过独立调试窗口的方法绕过),网上找了一些方法,貌似都没用,感兴趣且有头绪,或者已经有方法的小伙伴可以小伙伴可以在评论下方说说自己的想法,{card-describe title="引用自"}作者:荣顶链接:https://juejin.cn/post/7000784414858805256来源:掘金{/card-describe}
2021年09月06日
62 阅读
0 评论
0 点赞
2021-07-14
在vue-cli3使用sass(scss)定义的全局样式及变量
为统一风格及便于后续维护,在项目中常用的主色调、圆角、阴影相关的样式定义到一个全局变量文件中,常规的使用需要每个Vue组件中进行import,该操作会比较繁琐,经过了解后发现可以基于webpack的loader参数引入全局变量的方式来实现,相关实现过程如下所示:vue.config.jsmodule.exports = { // ... css: { // 为便于在Vue组件中使用,全局引入variables.scss中定义的变量 loaderOptions: { sass: { prependData: `@import "@/styles/variables.scss";` } } } variables.css// base color $blue:#324157; $light-blue:#3A71A8; $red:#C03639; $pink: #E65D6E; $green: #30B08F; $tiffany: #4AB7BD; $yellow:#FEC171; $panGreen: #30B08F; $theme: #004ca1; // ...vue 组件<style lang="scss" scoped> .btn-cursor { cursor:pointer; color: $theme } </style>
2021年07月14日
475 阅读
0 评论
0 点赞
2021-06-30
一个简单的自定义工作流设计器实现
先瞅瞅产品提供的丑陋至极的原型效果再看看经过本人精雕细琢之后的惊艳效果Show me the code流程设计弹出层<template> <el-dialog width="760px" class="hkt-dlg-darkblue" :title="`流程设计:${formData.name}`" :visible.sync="isShow" destroy-on-close :close-on-click-modal="false"> <div class="dialog-wrap" v-loading="loading"> <div style="min-height:50vh"> <div class="info-block"> <el-row class="hkt-block-title"> <el-col :span="12"><div><span class="title-name">流程配置</span></div></el-col> <el-col :span="12" class="text-right"> <el-button type="text" @click="formModalChange('New', null)"><i class="el-icon-plus"></i> 添加审核节点</el-button> </el-col> </el-row> <div class="info-content"> <el-steps direction="vertical" :active="nodes.length"> <el-step v-for="(step, stepIndex) in nodes" class="step-item-wrap" :class="'step-item-signmodel-' + step.signModel" :key="step + stepIndex"> <div slot="title"> <div style="line-height:32px;heihgt:38px;"> <el-row> <el-col :span="12"> <span class="step-name" v-text="step.nodeName"></span> </el-col> <el-col :span="12"> <div class="step-item-oprations text-right"> <el-button size="mini" type="text" @click="stepSort(stepIndex, 'up')" v-if="stepIndex > 0"><i class="el-icon-top"></i> 上移</el-button> <el-button size="mini" type="text"@click="stepSort(stepIndex, 'down')" v-if="stepIndex < nodes.length - 1"><i class="el-icon-bottom"></i> 下移</el-button> <el-button size="mini" type="text" @click="formModalChange('Edit', step, stepIndex)"><i class="el-icon-edit"></i> 修改</el-button> <el-button size="mini" type="text" @click="removeStep(step, stepIndex)"><i class="el-icon-close"></i> 删除</el-button> <el-button size="mini" type="text" @click="addPersons(step, stepIndex)"><i class="el-icon-user"></i> 配置审批人</el-button> </div> </el-col> </el-row> </div> </div> <div slot="description"> <div v-if="step.approveNames.length === 0"> <span class="color-gray">暂未配置审批人员</span> </div> <div v-else class="step-item-content-wrap"> <div class="person-item"> <el-tag v-for="(person, personIndex) in step.approveNames" :key="person" disable-transitions @close="removePerson(stepIndex, personIndex)" closable>{{person}}</el-tag> </div> </div> </div> </el-step> </el-steps> <div v-if="nodes.length === 0"> <h3 style="padding-top:40px;font-weight:normal;color:#dedede;font-size: 22px; text-align:center"><p>当前流程暂未配置审核节点信息,</p><p>请单击右上角的“添加审核节点”按钮进行配置</p></h3> </div> </div> </div> </div> </div> <div slot="footer" class="dialog-footer-wrap text-center"> <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" :loading="submitting" @click="save">{{submitting ? '保存中': '确 定'}}</el-button> </div> <step-form-modal :show.sync="formModalVisible" v-if="formModalVisible" :formData="currentRow" :mode="formMode" @callback="formCallback" /> </el-dialog> </template> <script> import stepFormModal from './stepFormModal.vue' export default { name: 'flowDesignModal', desc: '流程设计器', components: { stepFormModal }, props: { show: { type: Boolean, required: true, default: false }, formData: { // 适用于编辑 type: Object, required: false }, mode: { type: String, required: false } }, data () { return { loading: true, form: { }, nodes: [], formModalVisible: false, currentRow: null, currentStepIndex: null, formMode:'New', // New || Edit submitting: false } }, computed: { isShow: { get () { return this.show }, set (val) { this.$emit('update:show', val) } } }, created() { this.init() }, methods: { init () { if (this.formData) { this.getFlowNodes() // this.form = this.$hktUtils.deepClone(this.formData) } }, stepSort (index, type) { const steps = this.nodes if (type === 'up') { ;[steps[index], steps[index - 1]] = [steps[index - 1], steps[index]] } else { ;[steps[index], steps[index + 1]] = [steps[index + 1], steps[index]] } // 触发页面更新 this.nodes = steps.concat() }, async getFlowNodes () { this.loading = true const res = await this.$request.get(`/v1/manager/flow/node/listByFlowId?flowId=${this.formData.id}`) if (res.success) { this.nodes = res.data } this.loading = false }, // 添加或编辑流程节点 formModalChange (mode, formData, stepIndex) { this.formMode = mode this.currentRow = formData this.currentStepIndex = stepIndex this.formModalVisible = true }, formCallback (item) { // TODO 判断步骤名称是否已存在 if (this.formMode === 'New') { item.flowId = this.formData.id item.approveArray = [] item.approveNames = [] this.nodes.push(item) } else if (this.currentStepIndex >= 0) { this.nodes[this.currentStepIndex] = item this.currentStepIndex = null } }, addPersons (row, stepIndex) { const dialogProps = { multipleFlag: 'Y' } if (row.approveArray && row.approveArray.length > 0) { dialogProps.initRightUsers = row.approveArray.join(',') } window.hktCommon.selectUser(`配置 ${row.nodeName} 节点的审批人`, dialogProps, (res) => { row.approveArray = res.map(item => item.id) row.approveNames = res.map(item => item.name) this.nodes.splice(stepIndex, 1, this.$hktUtils.deepClone(row)) }) }, removePerson(stepIndex, personIndex) { this.nodes[stepIndex].approveArray.splice(personIndex, 1) this.nodes[stepIndex].approveNames.splice(personIndex, 1) }, removeStep(stepItem, stepIndex) { this.$confirm(`本操作不可恢复,确定删除该步骤(${stepItem.nodeName})?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { this.nodes.splice(stepIndex, 1) }).catch(() => {}) }, // 保存 async save () { const flowNodes = this.$hktUtils.deepClone(this.nodes) const existNodeNames = []// 用于校验名称是否重复 for (let i = 0; i < flowNodes.length; i++) { const flowNode = flowNodes[i] if (flowNode.approveArray.length === 0) { this.$message({ message: `审核步骤(${flowNode.nodeName})至少需选择一个的审批人`, type: 'warning' }) return } if (existNodeNames.includes(flowNode.nodeName)) { this.$message({ message: `存在相同的审核节点名称:${flowNode.nodeName}`, type: 'warning' }) return } existNodeNames.push(flowNode.nodeName) flowNode.orders = i +1 flowNode.approveIds = flowNode.approveArray.join(',') } this.submitting = true // 执行保存入库相关操作 const res = await this.$request.post(`/v1/manager/flow/node/add`, flowNodes).catch(err => { this.submitting = false }) this.submitting = false if (res && res.success) { this.$emit('callback', flowNodes) this.isShow = false } } } } </script> <style lang="scss" scoped> .form-body-item { background-color:#fff; padding:10px; border-radius: 4px; margin-bottom: 10px; } .form-item-zones { border-bottom: solid 1px #DCDFE6 ; margin-bottom:20px; .block-title { font-weight:bold; display: inline-block; line-height: 36px; font-size:14px; } } .step-item-oprations { display: none; } .step-item-wrap { /deep/ .el-step__main { padding: 0 10px 10px; } } .step-item-wrap:hover { /deep/ .el-step__main { background-color:#F5F7FA; border-radius: 5px; } .step-item-oprations { display: block; } } .step-item-content-wrap { padding: 10px; } .btn-choose { margin-left:10px; vertical-align: middle; } .person-item /deep/ { display: inline-block; .el-tag { vertical-align: middle; position: relative; overflow: initial; margin-bottom:10px; .el-tag__close { padding: 2px; box-sizing: content-box; position:absolute; background-color: #D1E9FF; // display: none; top:-10px; right: -10px; &:hover { background-color: #1890ff; } } &:hover { .el-tag__close { display:block; } } } .el-tag + .el-tag { margin-left: 40px; &:before { content: '或'; position: absolute; left: -26px; color: #666666; } } } // 会签模式步骤的审批人之间是用和 .info-content /deep/ .step-item-signmodel-1 .person-item { .el-tag + .el-tag { &:before { content: '和' } } } .step-name { color:#101010; font-weight: bold; } </style> 步骤编辑弹出层<template> <el-dialog width="460px" class="hkt-dlg-darkblue" :title="(form.id ? '编辑' : '新建') + '步骤'" :visible.sync="isShow" append-to-body destroy-on-close :close-on-click-modal="false"> <div class="dialog-wrap"> <el-form ref="form" :model="form"> <el-form-item label="步骤名称" prop="nodeName" :rules="[{required: true, message: '请填写步骤名称', trigger: 'change'}]"> <el-input v-model="form.nodeName" style="width:250px"></el-input> </el-form-item> <el-form-item label="是否会签模式"> <el-switch v-model="form.signModel" :active-value="1" :inactive-value="2" active-text="是" inactive-text="否"></el-switch> </el-form-item> <el-form-item label="是否可办结"> <el-switch v-model="form.isOver" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否"></el-switch> </el-form-item> </el-form> </div> <div slot="footer" class="dialog-footer-wrap text-center"> <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" @click="save">确 定</el-button> </div> </el-dialog> </template> <script> export default { name: 'stepFormModal', desc: '流程步骤表单', props: { show: { type: Boolean, required: true, default: false }, formData: { // 适用于编辑 type: Object, required: false }, mode: { type: String, required: false } }, data () { return { form: { nodeName: '', signModel: 2, isOver: 0 } } }, computed: { isShow: { get () { return this.show }, set (val) { this.$emit('update:show', val) } } }, created() { this.init() }, methods: { init () { if (this.formData) { this.form = this.$hktUtils.deepClone(this.formData) } }, save () { this.$refs['form'].validate(async (valid) => { if (valid) { this.$emit('callback', this.form) this.isShow = false } }) } } } </script> <style lang="scss" scoped> .form-body-item { background-color:#fff; padding:10px; border-radius: 4px; margin-bottom: 10px; } .form-item-zones { border-bottom: solid 1px #DCDFE6 ; margin-bottom:20px; .block-title { font-weight:bold; display: inline-block; line-height: 36px; font-size:14px; } } .step-item-oprations { display: none; } .step-item-wrap:hover { background-color:#D1E9FF; .step-item-oprations { display: block; } } .step-item-content-wrap { padding: 10px; } .btn-choose { margin-left:10px; vertical-align: middle; } .person-item /deep/ { display: inline-block; .el-tag { vertical-align: middle; position: relative; overflow: initial; .el-tag__close { padding: 2px; box-sizing: content-box; position:absolute; background-color: #D1E9FF; // display: none; top:-10px; right: -10px; &:hover { background-color: #1890ff; } } &:hover { .el-tag__close { display:block; } } } .el-tag + .el-tag { margin-left: 40px; &:before { content: '或'; position: absolute; left: -26px; color: #666666; } } } .step-name { color:#101010; font-weight: bold; } </style> 该组件涉及人员选择相关公共组件的封装,详细见 分享一个在管理系统中一些公共组件的调用方式
2021年06月30日
232 阅读
0 评论
0 点赞
2021-06-30
分享一个在管理系统中一些公共组件的调用方式
公共组件效果查看合同:地图描点:公共资源文件上传:人员选择-多选:人员选择-单选:增加公共组件目录核心代码如下:<template> <div class="common-dialogs-wrap"> <!-- 公共资源上传 --> <resource-modal :title="resourcePickerDlg.dialogTitle" :resourceDialogProps.sync="resourcePickerDlg.resourceDialogProps" v-if="resourcePickerDlg.show" :show.sync="resourcePickerDlg.show" @callback="resourceCallback"></resource-modal> <!-- 公共人员选择 --> <select-user-modal :title="userSelectDlg.dialogTitle" :dialogProps.sync="userSelectDlg.dialogProps" v-if="userSelectDlg.show" :show.sync="userSelectDlg.show" @callback="userSelectedCallback" ></select-user-modal> <!-- 合同详情弹出层 --> <contract-detail-modal :show.sync="contractDetailDlg.show" v-if="contractDetailDlg.show" :detail-id="contractDetailDlg.dialogProps.contractId"></contract-detail-modal> <!-- 地图描点 --> <picker-q-map-position-modal :show.sync="qMaplDlg.show" v-if="qMaplDlg.show" @callback="qMapCallback"></picker-q-map-position-modal> <!-- 预留后续扩展其他公共组件 --> </div> </template> <script> const ResourceModal = () => import('./ResourceModal.vue') const SelectUserModal = () => import('./SelectUserModal.vue') const ContractDetailModal = () => import('@/views/contract-manage/contractList/DetailContractModel.vue') // 合同详情弹出层 const PickerQMapPositionModal = () => import('./PickerQMapPositionModal.vue') // 腾讯地图描点 var hktCommon = {} var uploadCallbackFn = null // 资源上传回调 var userSelectCallback = null // 用户选择回调 var qMapCallbackFn = null // 腾讯地图描点回调 export default { name: 'HktCommon', desc: '华宽通公共组件封装', components: { ResourceModal, SelectUserModal, ContractDetailModal, PickerQMapPositionModal }, data () { return { // 资源上传对话框默认参数 resourceDialogProps: { businessCode: 'resources', // 业务代码,默认为resources resourceType: 'file', // 资源类型,file||image||document||video||audio accept: '', // 接受上传的文件类型 multipleFlag: 'Y' // 是否允许多个 }, resourcePickerDlg: { show: false, dialogTitle: '选择资源', resourceDialogProps: {} }, userSelectDlg: { show: false, dialogTitle: '选择人员', dialogProps: { multipleFlag: 'N', // 是否支持多选 organizationId: '', // 机构ID ignoreUsernames: '', // 不能选择的人员用户名 initRightUsers: '' // 复选上的用户,多个使用逗号分隔 } }, // 合同详情弹出层 contractDetailDlg: { show: false, dialogTitle: '合同信息', dialogProps: { contractId: '' // 合同ID } }, // 合同详情弹出层 qMaplDlg: { show: false, dialogTitle: '地图描点', dialogProps: { } } } }, created () { window.hktCommon = hktCommon this.init() }, methods: { init () { // 初始化公共上传组件 this.initUpload() // 初始化用户选择组件 this.initSelectUser() // 初始化查看合同详情组件 this.initContractDetailModal() // 初始化查看合同详情组件 this.initQMapModal() // TODO:后续可继续扩展其他公共组件 }, initUpload () { // 上传图片 hktCommon.uploadImage = (title, props, callback) => { if (arguments.length === 1 && typeof arguments[0] === 'function') { title = '上传图片' props = { resourceType: 'image' } callback = arguments[0] } if (arguments.length === 2 && typeof arguments[0] === 'string' && typeof arguments[1] === 'function') { props = { resourceType: 'image' } callback = arguments[1] } if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '上传图片' props = arguments[0] callback = arguments[1] } if (!props) { props = { resourceType: 'image' } } if (!props.accept) { props.accept = '.jpg,.png,.gif' } upload('image', title, props, callback) } // 上传视频 hktCommon.uploadVideo = (title, props, callback) => { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '视频上传' props = arguments[0] callback = arguments[1] } upload('video', title, props, callback) } // 上传音频 hktCommon.uploadAudio = (title, props, callback) => { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '音频上传' props = arguments[0] callback = arguments[1] } upload('audio', title, props, callback) } // 上传资源 hktCommon.uploadResource = (title, props, callback) => { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '文件上传' props = arguments[0] callback = arguments[1] } let resourceType = 'file' if (props.resourceType) { resourceType = props.resourceType } upload(resourceType, title, props, callback) } // 定义公共上传方法 const upload = (type, title, props, callback) => { this.resourcePickerDlg.dialogTitle = title const resourceDialogProps = Object.assign({}, this.resourceDialogProps, props) resourceDialogProps.resourceType = type this.resourcePickerDlg.resourceDialogProps = resourceDialogProps this.resourcePickerDlg.show = true if (callback) { uploadCallbackFn = callback } else { uploadCallbackFn = null } } // 完全自主控制上传方式 hktCommon.upload = upload }, initSelectUser() { const self = this /** * 公共选择用户方法 * @param title 对话框标题 * @param props 人员选择配置信息,配置属性见:userSelectDlg.dialogProps * @param callback 选择人员后的回调 */ hktCommon.selectUser = function(title, props, callback) { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '选择用户' props = arguments[0] callback = arguments[1] } else if (arguments.length === 2 && typeof arguments[0] === 'string' && typeof arguments[1] === 'function') { callback = arguments[1] props = { multipleFlag: 'N', // 是否支持多选 organizationId: '', // 机构ID ignoreUsernames: '' // 不能选择的人员用户名 } } self.userSelectDlg.dialogTitle = title self.userSelectDlg.dialogProps = props self.userSelectDlg.show = true if (callback) { userSelectCallback = callback } else { userSelectCallback = null } } }, // 初始化合同详情界面 initContractDetailModal () { const self = this hktCommon.showContract = function (contractId) { self.contractDetailDlg.dialogProps.contractId = contractId self.contractDetailDlg.show = true } }, // 初始化腾讯地图选择弹出层 initQMapModal () { const self = this hktCommon.pickQMap = function (callback) { self.qMaplDlg.show = true if (callback) { qMapCallbackFn = callback } else { qMapCallbackFn = null } } }, // 选择资源后的回调 resourceCallback (resourceData) { // 可以通过事件机制获取选择后的资源 if (window.eventHub && typeof window.eventHub.$emit === 'function') { window.eventHub.$emit('resourcePicked', resourceData) } if (typeof uploadCallbackFn === 'function') { uploadCallbackFn(resourceData) uploadCallbackFn = null } }, // 选择人员回调 userSelectedCallback(userData) { // 可以通过事件机制获取选择后的资源 if (window.eventHub && typeof window.eventHub.$emit === 'function') { window.eventHub.$emit('userSelected', userData) } if (typeof userSelectCallback === 'function') { userSelectCallback(userData) userSelectCallback = null } }, // 地图描点后的回调 qMapCallback (latLng) { // 可以通过事件机制获取选择后的资源 if (window.eventHub && typeof window.eventHub.$emit === 'function') { window.eventHub.$emit('qMapCallPicked', latLng) } if (typeof qMapCallbackFn === 'function') { qMapCallbackFn(latLng) qMapCallbackFn = null } }, } } </script> main.js主方法中引入公共组件:import HktCommon from './views/hktcommon/HktCommon.vue' // 增加公共组件对象 const commonEl = document.createElement('div') document.body.appendChild(commonEl) new Vue({ name: 'AppCommonRoot', el: commonEl, render: h => h(HktCommon) })在其他组件中调用全局方法:// 上传资源 hktCommon.uploadResource(this.dialogTitle, dialogProps, (resources) => { this.uploadCallback(resources) }) // 选择用户: window.hktCommon.selectUser(`配置 ${row.nodeName} 节点的审批人`, dialogProps, (res) => { row.approveArray = res.map(item => item.id) row.approveNames = res.map(item => item.name) this.nodes.splice(stepIndex, 1, this.$hktUtils.deepClone(row)) }) // 显示合同信息 hktCommon.showContract = function (contractId) { self.contractDetailDlg.dialogProps.contractId = contractId self.contractDetailDlg.show = true } // 地图描点 window.hktCommon.pickQMap((res) => { self.projectForm.areaCode = `${res.latitude},${res.longitude}` })
2021年06月30日
71 阅读
0 评论
0 点赞
2021-06-30
记一次根据经纬度计算距离的项目实践
近期公司接的公租房项目,在小程序模块中有用到离我最近的房源列表的功能,经过项目实践相关实现过程如下:功能演示PC后台配置小区位置:单击地图描点后打开如下图所示的描点对话框,描点选择后单击确定按钮返回经纬度信息:小程序中获取位置后调用接口显示距离:{lamp/}代码实现:管理后台描点Vue组件核心代码<template> <el-dialog width="95%" class="hkt-dlg-darkblue resource-modal" top="5vh" :title="title" :visible.sync="isShow" destroy-on-close :close-on-click-modal="false"> <div class="map-container" ref="mapContainer"></div> <div slot="footer" class="dialog-footer dialog-footer-wrap text-center"> <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" :disabled="!latitude" @click="pickedHandler">{{'确 定'}}</el-button> </div> </el-dialog> </template> <script> export default { name: 'PickerQMapPositionModal', props: { show: { type: Boolean, default: false }, title: { type: String, default: '地图描点' }, dialogProps: { type: Object, reuired: true } }, data () { return { latitude: '', longitude: '' } }, computed: { isShow: { get () { return this.show }, set (val) { this.$emit('update:show', val) } } }, mounted () { this.init() }, methods: { init () { this.initMap() }, async initMap () { // if (!window.TMap) { // } await this.$hktUtils.loadJS('https://map.qq.com/api/gljs?v=1.exp&key=VQ6BZ-ZFSW6-H36SN-E7TZL-QZ6U6-O3F24') var center = new TMap.LatLng(28.239377,112.866161) //定义map变量,调用 TMap.Map() 构造函数创建地图 var map = new TMap.Map(this.$refs.mapContainer, { center: center,//设置地图中心点坐标 zoom: 15, //设置地图缩放级别 // pitch: 43.5, //设置俯仰角 // rotation: 45 //设置地图旋转角度 }); //初始化marker图层 var markerLayer = new TMap.MultiMarker({ id: 'marker-layer', map: map }); map.on("click", (evt) => { if(markerLayer.geometries && markerLayer.geometries.length > 0) { markerLayer.remove(markerLayer.geometries[0].id) } markerLayer.add({ position: evt.latLng }); console.log('evt.latLng', evt.latLng, evt) this.latitude = evt.latLng.lat.toFixed(7) this.longitude = evt.latLng.lng.toFixed(7) }) }, pickedHandler () { this.$emit('callback', { latitude: this.latitude, longitude: this.longitude }) this.isShow = false } } } </script> <style lang="scss" scoped> .map-container { width:100%; height:70vh; } </style> Java端计算距离核心代码package com.hkt.jianfa.rental.housing.manager.wx.util; /** * 根据经纬度计算距离的工具类 * * @author 朱治龙(i@zhuzhilong.cn) * @since 2021-06-29 16:22:18 */ public class DistanceUtil { //地球平均半径 private static final double EARTH_RADIUS = 6378137; //把经纬度转为度(°) private static double rad(double d) { return d * Math.PI / 180.0; } /** * 根据两点间经纬度坐标(double值),计算两点间距离,单位为米 * * @param lng1 第1个坐标的经度值 * @param lat1 第1个坐标的纬度值 * @param lng2 第2个坐标的经度值 * @param lat2 第2个坐标的纬度值 * @return */ public static double getDistance(double lng1, double lat1, double lng2, double lat2) { double radLat1 = rad(lat1); double radLat2 = rad(lat2); double a = radLat1 - radLat2; double b = rad(lng1) - rad(lng2); double s = 2 * Math.asin( Math.sqrt( Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) ) ); s = s * EARTH_RADIUS; s = Math.round(s * 10000) / 10000; return s; } /** * 传入逗号分隔的两个经纬度的值计算巨鹿 * * @param position1 第一个经纬度 * @param position2 第二个经纬度 * @return */ public static double getDistance(String position1, String position2) { if (position1.contains(",") && position2.contains(",")) { String[] point1 = position1.split(","); String[] point2 = position2.split(","); return getDistance(Double.parseDouble(point1[1]), Double.parseDouble(point1[0]), Double.parseDouble(point2[1]), Double.parseDouble(point2[0])); } return 0; } /** * 将距离数值转换为前端显示所需的字符串信息 * * @param distance * @return */ public static String decodeDistance(double distance) { if (distance < 1000) { return "<1km"; } else if (distance / 1000 > 100) { return ">100km"; } else { return new Double(distance / 1000).intValue() + "km"; } } } 业务逻辑类中调用DistanceUtil方法计算距离:小程序中获取位置后,将经纬度信息传给后端计算距离及排序核心代码如下:uni.getLocation({ type: 'gcj02', success: (res) => { this.searchForm.sort = 'distance' this.searchForm.location = res.latitude + ',' + res.longitude this.getProjects() }, fail: (err) => { this.getProjects() } })
2021年06月30日
142 阅读
0 评论
3 点赞
2021-06-02
33个JavaScript常用函数封装方法汇总
这是我在实际开发中常用的一些js函数方法,今天总结了一下,以便以后查询,有需要的小伙伴可以参考下。有些人或许会觉得忘了百度就完事儿,No no no!这事儿我真的亲自实践过好多次,百度一次记住了还好,记不住下次碰着了还得找度娘简直是拉低工作效率。1、加载js || css || style const loadRes = function(name, type, fn) { // 加载js || css || style let ref if (type === 'js') { // 外部js ref = document.createElement('script') ref.setAttribute('type', 'text/JavaScript') ref.setAttribute('src', name) } else if (type === 'css') { // 外部css ref = document.createElement('link') ref.setAttribute('rel', 'stylesheet') ref.setAttribute('type', 'text/css') ref.setAttribute('href', name) } else if (type === 'style') { // style ref = document.createElement('style') ref.innerhtml = name } if (typeof ref !== 'undefined') { document.getElementsByTagName('head')[0].appendChild(ref) ref.onload = function() { // 加载完成执行 typeof fn === 'function' && fn() } } }2、获取url参数const getUrlParam = function(name) { // 获取url参数 let reg = new RegExp('(^|&?)' + name + '=([^&]*)(&|$)', 'i') let r = window.location.href.substr(1).match(reg) if (r != null) { return decodeURI(r[2]) } return undefined }3、本地存储const store = { // 本地存储 set: function(name, value, day) { // 设置 let d = new Date() let time = 0 day = (typeof(day) === 'undefined' || !day) ? 1 : day // 时间,默认存储1天 time = d.setHours(d.getHours() + (24 * day)) // 毫秒 localStorage.setItem(name, JSON.stringify({ data: value, time: time })) }, get: function(name) { // 获取 let data = localStorage.getItem(name) if (!data) { return null } let obj = JSON.parse(data) if (new Date().getTime() > obj.time) { // 过期 localStorage.removeItem(name) return null } else { return obj.data } }, clear: function(name) { // 清空 if (name) { // 删除键为name的缓存 localStorage.removeItem(name) } else { // 清空全部 localStorage.clear() } } }4、cookie操作【set,get,del】const cookie = { // cookie操作【set,get,del】 set: function(name, value, day) { let oDate = new Date() oDate.setDate(oDate.getDate() + (day || 30)) document.cookie = name + '=' + value + ';expires=' + oDate + "; path=/;" }, get: function(name) { let str = document.cookie let arr = str.split('; ') for (let i = 0; i < arr.length; i++) { let newArr = arr[i].split('=') if (newArr[0] === name) { return newArr[1] } } }, del: function(name) { this.set(name, '', -1) } }5、Js获取元素样式【支持内联】const getRealStyle = function(obj, styleName) { // Js获取元素样式【支持内联】 var realStyle = null if (obj.currentStyle) { realStyle = obj.currentStyle[styleName] } else if (window.getComputedStyle) { realStyle = window.getComputedStyle(obj, null)[styleName] } return realStyle }6、时间格式化const formatDate = function(fmt, date) { // 时间格式化 【'yyyy-MM-dd hh:mm:ss',时间】 if (typeof date !== 'object') { date = !date ? new Date() : new Date(date) } var o = { 'M+': date.getMonth() + 1, // 月份 'd+': date.getDate(), // 日 'h+': date.getHours(), // 小时 'm+': date.getMinutes(), // 分 's+': date.getSeconds(), // 秒 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 'S': date.getMilliseconds() // 毫秒 } if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (var k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) } } return fmt }7、原生ajax操作const ajax = function(conf) { // ajax操作 let url = conf.url, data = conf.data, senData = [], // 封装后的数据 async = conf.async !== undefined ? conf.async : true, // ture为异步请求 type = conf.type || 'get', // 默认请求方式get dataType = conf.dataType || 'json', contenType = conf.contenType || 'application/x-www-form-urlencoded', success = conf.success, error = conf.error, isForm = conf.isForm || false, // 是否formdata header = conf.header || {}, // 头部信息 xhr = '' // 创建ajax引擎对象 if (data == null) { senData = '' } else if (typeof data === 'object' && !isForm) { // 如果data是对象,转换为字符串 for (var k in data) { senData.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k])) } senData = senData.join('&') } else { senData = data } try { xhr = new ActiveXObject('microsoft.xmlhttp') // IE内核系列浏览器 } catch (e1) { try { xhr = new XMLHttpRequest() // 非IE内核浏览器 } catch (e2) { if (error != null) { error('不支持ajax请求') } } }; xhr.open(type, type !== 'get' ? url : url + '?' + senData, async) if (type !== 'get' && !isForm) { xhr.setRequestHeader('content-type', contenType) } for (var h in header) { xhr.setRequestHeader(h, header[h]) } xhr.send(type !== 'get' ? senData : null) xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { if (dataType === 'json' && success != null) { let res = '' try { res = eval('(' + xhr.responseText + ')') } catch (e) { console.log(e) } success(res) // 将json字符串转换为js对象 }; } else { if (error != null) { error('通讯失败!' + xhr.status) } } } } }8、fetch请求的封装const fetch = function(url, setting) { // fetch请求的封装 let opts = { // 设置参数的初始值 method: (setting.method || 'GET').toUpperCase(), // 请求方式 headers: setting.headers || {}, // 请求头设置 credentials: setting.credentials || true, // 设置cookie是否一起发送 body: setting.body || {}, mode: setting.mode || 'no-cors', // 可以设置 cors, no-cors, same-origin redirect: setting.redirect || 'follow', // follow, error, manual cache: setting.cache || 'default' // 设置 cache 模式 (default, reload, no-cache) } let dataType = setting.dataType || 'json' // 解析方式 let data = setting.data || '' // 参数 let paramsFormat = function(obj) { // 参数格式 var str = '' for (var i in obj) { str += `${i}=${obj[i]}&` } return str.split('').slice(0, -1).join('') } if (opts.method === 'GET') { url = url + (data ? `?${paramsFormat(data)}` : '') } else { setting.body = data || {} } return new Promise((resolve, reject) => { fetch(url, opts).then(async res => { let data = dataType === 'text' ? await res.text() : dataType === 'blob' ? await res.blob() : await res.json() resolve(data) }).catch(e => { reject(e) }) }) }9、设备判断:android、ios、webconst isDevice = function() { // 判断是android还是ios还是web var ua = navigator.userAgent.toLowerCase() if (ua.match(/iPhone\sOS/i) === 'iphone os' || ua.match(/iPad/i) === 'ipad') { // ios return 'iOS' } if (ua.match(/Android/i) === 'android') { return 'Android' } return 'Web' }10、判断是否为微信const isWx = function() { // 判断是否为微信 var ua = window.navigator.userAgent.toLowerCase() if (ua.match(/MicroMessenger/i) === 'micromessenger') { return true } return false }11、文本复制功能const copyTxt = function(text, fn) { // 复制功能 if (typeof document.execCommand !== 'function') { console.log('复制失败,请长按复制') return } var dom = document.createElement('textarea') dom.value = text dom.setAttribute('style', 'display: block;width: 1px;height: 1px;') document.body.appendChild(dom) dom.select() var result = document.execCommand('copy') document.body.removeChild(dom) if (result) { console.log('复制成功') typeof fn === 'function' && fn() return } if (typeof document.createRange !== 'function') { console.log('复制失败,请长按复制') return } var range = document.createRange() var div = document.createElement('div') div.innerhtml = text div.setAttribute('style', 'height: 1px;fontSize: 1px;overflow: hidden;') document.body.appendChild(div) range.selectNode(div) var selection = window.getSelection() console.log(selection) if (selection.rangeCount > 0) { selection.removeAllRanges() } selection.addRange(range) document.execCommand('copy') typeof fn === 'function' && fn() console.log('复制成功') }12、判断是否是一个数组const isArray = function(arr) { // 判断是否是一个数组 return Object.prototype.toString.call(arr) === '[object Array]' }13、判断两个数组是否相等const arrayEqual = function(arr1, arr2) { //判断两个数组是否相等 if (arr1 === arr2) return true; if (arr1.length != arr2.length) return false; for (let i = 0; i < arr1.length; ++i) { if (arr1[i] !== arr2[i]) return false; } return true; }14、时间与时间戳转换const stamp = { // 时间,时间戳转换 getTime: function(time) { // 时间转10位时间戳 let date = time ? new Date(time) : new Date() return Math.round(date.getTime() / 1000) }, timeToStr: function(time, fmt) { // 10位时间戳转时间 return new Date(time * 1000).pattern(fmt || 'yyyy-MM-dd') } }15、常用正则验证const checkStr = function(str, type) { // 常用正则验证,注意type大小写 switch (type) { case 'phone': // 手机号码 return /^1[3|4|5|6|7|8|9][0-9]{9}$/.test(str) case 'tel': // 座机 return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str) case 'card': // 身份证 return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str) case 'pwd': // 密码以字母开头,长度在6~18之间,只能包含字母、数字和下划线 return /^[a-zA-Z]\w{5,17}$/.test(str) case 'postal': // 邮政编码 return /[1-9]\d{5}(?!\d)/.test(str) case 'QQ': // QQ号 return /^[1-9][0-9]{4,9}$/.test(str) case 'email': // 邮箱 return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str) case 'money': // 金额(小数点2位) return /^\d*(?:\.\d{0,2})?$/.test(str) case 'URL': // 网址 return /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(str) case 'IP': // IP return /((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))/.test(str) case 'date': // 日期时间 return /^(\d{4})\-(\d{2})\-(\d{2}) (\d{2})(?:\:\d{2}|:(\d{2}):(\d{2}))$/.test(str) || /^(\d{4})\-(\d{2})\-(\d{2})$/.test(str) case 'number': // 数字 return /^[0-9]$/.test(str) case 'english': // 英文 return /^[a-zA-Z]+$/.test(str) case 'chinese': // 中文 return /^[\u4E00-\u9FA5]+$/.test(str) case 'lower': // 小写 return /^[a-z]+$/.test(str) case 'upper': // 大写 return /^[A-Z]+$/.test(str) case 'HTML': // HTML标记 return /<("[^"]*"|'[^']*'|[^'">])*>/.test(str) default: return true } }16、是否为PC端const isPC = function() { // 是否为PC端 let userAgentInfo = navigator.userAgent let Agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'] let flag = true for (let v = 0; v < Agents.length; v++) { if (userAgentInfo.indexOf(Agents[v]) > 0) { flag = false break } } return flag }17、去除字符串空格const trim = function(str, type) { // 去除空格, type: 1-所有空格 2-前后空格 3-前空格 4-后空格 type = type || 1 switch (type) { case 1: return str.replace(/\s+/g, '') case 2: return str.replace(/(^\s*)|(\s*$)/g, '') case 3: return str.replace(/(^\s*)/g, '') case 4: return str.replace(/(\s*$)/g, '') default: return str } }18、字符串大小写转换const changeCase = function(str, type) { // 字符串大小写转换 type: 1:首字母大写 2:首页母小写 3:大小写转换 4:全部大写 5:全部小写 type = type || 4 switch (type) { case 1: return str.replace(/\b\w+\b/g, function(word) { return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase() }) case 2: return str.replace(/\b\w+\b/g, function(word) { return word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase() }) case 3: return str.split('').map(function(word) { if (/[a-z]/.test(word)) { return word.toUpperCase() } else { return word.toLowerCase() } }).join('') case 4: return str.toUpperCase() case 5: return str.toLowerCase() default: return str } }19、过滤html代码const filterTag = function(str) { // 过滤html代码(把<>转换) str = str.replace(/&/ig, '&') str = str.replace(/</ig, '<') str = str.replace(/>/ig, '>') str = str.replace(' ', ' ') return str }20、生成随机数范围const random = function(min, max) { // 生成随机数范围 if (arguments.length === 2) { return Math.floor(min + Math.random() * ((max + 1) - min)) } else { return null } }21、阿拉伯数字转中文大写数字const numberToChinese = function(num) { // 将阿拉伯数字翻译成中文的大写数字 let AA = new Array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十') let BB = new Array('', '十', '百', '仟', '萬', '億', '点', '') let a = ('' + num).replace(/(^0*)/g, '').split('.') let k = 0 let re = '' for (let i = a[0].length - 1; i >= 0; i--) { switch (k) { case 0: re = BB[7] + re break case 4: if (!new RegExp('0{4}//d{' + (a[0].length - i - 1) + '}$').test(a[0])) { re = BB[4] + re } break case 8: re = BB[5] + re BB[7] = BB[5] k = 0 break } if (k % 4 === 2 && a[0].charAt(i + 2) !== 0 && a[0].charAt(i + 1) === 0) { re = AA[0] + re } if (a[0].charAt(i) !== 0) { re = AA[a[0].charAt(i)] + BB[k % 4] + re } k++ } if (a.length > 1) { // 加上小数部分(如果有小数部分) re += BB[6] for (let i = 0; i < a[1].length; i++) { re += AA[a[1].charAt(i)] } } if (re === '一十') { re = '十' } if (re.match(/^一/) && re.length === 3) { re = re.replace('一', '') } return re }22、原生dom操作const dom = { $: function(selector) { let type = selector.substring(0, 1) if (type === '#') { if (document.querySelecotor) return document.querySelector(selector) return document.getElementById(selector.substring(1)) } else if (type === '.') { if (document.querySelecotorAll) return document.querySelectorAll(selector) return document.getElementsByClassName(selector.substring(1)) } else { return document['querySelectorAll' ? 'querySelectorAll' : 'getElementsByTagName'](selector) } }, hasClass: function(ele, name) { /* 检测类名 */ return ele.className.match(new RegExp('(\\s|^)' + name + '(\\s|$)')) }, addClass: function(ele, name) { /* 添加类名 */ if (!this.hasClass(ele, name)) ele.className += ' ' + name }, removeClass: function(ele, name) { /* 删除类名 */ if (this.hasClass(ele, name)) { let reg = new RegExp('(\\s|^)' + name + '(\\s|$)') ele.className = ele.className.replace(reg, '') } }, replaceClass: function(ele, newName, oldName) { /* 替换类名 */ this.removeClass(ele, oldName) this.addClass(ele, newName) }, siblings: function(ele) { /* 获取兄弟节点 */ console.log(ele.parentNode) let chid = ele.parentNode.children, eleMatch = [] for (let i = 0, len = chid.length; i < len; i++) { if (chid[i] !== ele) { eleMatch.push(chid[i]) } } return eleMatch }, getByStyle: function(obj, name) { /* 获取行间样式属性 */ if (obj.currentStyle) { return obj.currentStyle[name] } else { return getComputedStyle(obj, false)[name] } }, domToStirng: function(htmlDOM) { /* DOM转字符串 */ var div = document.createElement('div') div.appendChild(htmlDOM) return div.innerHTML }, stringToDom: function(htmlString) { /* 字符串转DOM */ var div = document.createElement('div') div.innerHTML = htmlString return div.children[0] } }23、判断图片加载完成const imgLoadAll = function(arr, callback) { // 图片加载 let arrImg = [] for (let i = 0; i < arr.length; i++) { let img = new Image() img.src = arr[i] img.onload = function() { arrImg.push(this) if (arrImg.length == arr.length) { callback && callback() } } } }24、音频加载完成操作const loadAudio = function(src, callback) { // 音频加载 var audio = new Audio(src) audio.onloadedmetadata = callback audio.src = src }25、光标所在位置插入字符const insertAtCursor = function(dom, val) { // 光标所在位置插入字符 if (document.selection) { dom.focus() let sel = document.selection.createRange() sel.text = val sel.select() } else if (dom.selectionStart || dom.selectionStart == '0') { let startPos = dom.selectionStart let endPos = dom.selectionEnd let restoreTop = dom.scrollTop dom.value = dom.value.substring(0, startPos) + val + dom.value.substring(endPos, dom.value.length) if (restoreTop > 0) { dom.scrollTop = restoreTop } dom.focus() dom.selectionStart = startPos + val.length dom.selectionEnd = startPos + val.length } else { dom.value += val dom.focus() } }26、图片地址转base64const getBase64 = function(img) { //传入图片路径,返回base64,使用getBase64(url).then(function(base64){},function(err){}); let getBase64Image = function(img, width, height) { //width、height调用时传入具体像素值,控制大小,不传则默认图像大小 let canvas = document.createElement("canvas"); canvas.width = width ? width : img.width; canvas.height = height ? height : img.height; let ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); let dataURL = canvas.toDataURL(); return dataURL; } let image = new Image(); image.crossOrigin = ''; image.src = img; let deferred = $.Deferred(); if (img) { image.onload = function() { deferred.resolve(getBase64Image(image)); } return deferred.promise(); } }27、base64图片下载功能const downloadFile = function(base64, fileName) { //base64图片下载功能 let base64ToBlob = function(code) { let parts = code.split(';base64,'); let contentType = parts[0].split(':')[1]; let raw = window.atob(parts[1]); let rawLength = raw.length; let uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], { type: contentType }); }; let aLink = document.createElement('a'); let blob = base64ToBlob(base64); //new Blob([content]); let evt = document.createEvent("HTMLEvents"); evt.initEvent("click", true, true); //initEvent不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为 aLink.download = fileName; aLink.href = URL.createObjectURL(blob); aLink.click(); }28、浏览器是否支持webP格式图片const isSupportWebP = function() { //判断浏览器是否支持webP格式图片 return !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0; }29、url参数转对象const parseQueryString = function(url) { //url参数转对象 url = !url ? window.location.href : url; if (url.indexOf('?') === -1) { return {}; } let search = url[0] === '?' ? url.substr(1) : url.substring(url.lastIndexOf('?') + 1); if (search === '') { return {}; } search = search.split('&'); let query = {}; for (let i = 0; i < search.length; i++) { let pair = search[i].split('='); query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || ''); } return query; }30、对象序列化【对象转url参数】const stringfyQueryString = function(obj) { //对象序列化【对象转url参数】 if (!obj) return ''; let pairs = []; for (let key in obj) { let value = obj[key]; if (value instanceof Array) { for (let i = 0; i < value.length; ++i) { pairs.push(encodeURIComponent(key + '[' + i + ']') + '=' + encodeURIComponent(value[i])); } continue; } pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])); } return pairs.join('&'); }31、H5软键盘缩回、弹起回调const h5Resize = function(downCb, upCb) { //当软件键盘弹起会改变当前 window.innerHeight,监听这个值变化 [downCb 当软键盘弹起后,缩回的回调,upCb 当软键盘弹起的回调] var clientHeight = window.innerHeight; downCb = typeof downCb === 'function' ? downCb : function() {} upCb = typeof upCb === 'function' ? upCb : function() {} window.addEventListener('resize', () => { var height = window.innerHeight; if (height === clientHeight) { downCb(); } if (height < clientHeight) { upCb(); } }); }32、函数防抖const debounce = function(func, wait, immediate) { //函数防抖[func 函数,wait 延迟执行毫秒数,immediate true 表立即执行,false 表非立即执行,立即执行是触发事件后函数会立即执行,然后n秒内不触发事件才能继续执行函数的效果] let timeout; return function() { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function() { func.apply(context, args) }, wait); } } }33、函数节流const throttle = function(func, wait ,type) { //函数节流 [func 函数 wait 延迟执行毫秒数 type 1 表时间戳版,2 表定时器版] if(type===1){ let previous = 0; }else if(type===2){ let timeout; } return function() { let context = this; let args = arguments; if(type===1){ let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } }else if(type===2){ if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } } }整理至此,请用心记!!!!以上方法可通过该工具自动获取生成哦:http://www.fly63.com/tool/code/来源 | http://www.fly63.com/article/detial/10362
2021年06月02日
161 阅读
0 评论
0 点赞
2021-05-26
node安装node-sass失败的处理方案
安装 node-sass 的时候总是会各种不成功,大部分安装不成功的原因都源自这里,因为 GitHub Releases 里的文件都托管在 s3.amazonaws.com上面,而这个网址在国内总是网络不稳定,所以我们需要通过第三方服务器下载这个文件。方法一:使用淘宝源npm config set sass_binary_site=https://npmmirror.com/mirrors/node-sass/ npm config set phantomjs_cdnurl=https://npmmirror.com/mirrors/phantomjs/ npm config set electron_mirror=https://npmmirror.com/mirrors/electron/ npm config set registry=https://registry.npmmirror.com这样使用 npm install 安装 node-sass、electron 和 phantomjs 时都能自动从淘宝源上下载。方法二:安装cnpm >>> 也是淘宝源的做法npm install -g cnpm cnpm install这样也可以成功安装node-sass方法三:使用VPNnpm config set proxy (http://127.0.0.1:1080)此处是VPN的代理地址 npm i node-sass # 下载完成后删除 http 代理 npm config delete proxy
2021年05月26日
133 阅读
0 评论
0 点赞
1
...
5
6
7
8