首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

使用mail.rc快速配置linux发邮件服务

1.系统环境。 [root@web02~]#cat/etc/redhat-release CentOSrelease6.8(Final) [root@web02~]#uname-r 2.6.32-642.el6.x86_64 [root@web02~]#uname-m x86_64 2.通过修改配置文件/etc/mail.rc可以使用外部SMTP服务器,轻松实现linux发邮件功能。 [root@web02~]#tail/etc/mail.rc #ForLinuxandBSD,thisshouldbeset. setbsdcompat #sendmailconfig setfrom=user@foxmail.com setsmtp=smtp.qq.com setsmtp-auth-user=user@foxmail.com setsmtp-auth-password=xxxxxxxxxxxxxxx setsmtp-auth=login 注意:目前大部分的外部邮件服务使用第三方客户端时,都需要使用授权码,上面的smtp-auth-password使用的就是授权码,而不是邮件帐号的密码。 3.相关命令和选项的作用。 -s<邮件主题>:指定邮件的主题; -c<地址>:添加邮件抄送人,多个人时用逗号隔开; -b<地址>:添加邮件暗送人; -a<附件>:添加附件Attachthegivenfiletothemessage. (1)方法一:正文内容重定向输入。 [root@web02~]#mail-s"标题"-a/etc/hosts-cuser1@163.com,user2@163.comuser@foxmail.com</etc/hosts #当主送和抄送有多个人时,请使用逗号隔开。 (2)方法二:正文内容通过echo命令输入。 [root@web02~]#echo"正文"|mail-s"标题"-a/etc/hosts-cuser1@163.com,user2@163.comuser@foxmail.com 4.启动postfix服务 [root@web02~]#/etc/init.d/postfixstart Startingpostfix:[OK] [root@mysql01~]#chkconfig--level3postfixon [root@mysql01~]#chkconfig|greppostfix postfix0:off1:off2:off3:on4:off5:off6:off 到此为止,发邮件配置完毕。可以正常测试。

优秀的个人博客,低调大师

Android小技巧之最快速简单的悬浮TAB

先看效果图吧 image 原理 其实实现这样的效果是十分的简单的,继承ScrollView,监听onScrollChanged,根据滑动的距离,不断的layout需要悬浮的tab的位置。这只是一个简单的demo,主要提供的是一种思路,利用到实际中还是有点距离。 编码 继承ScrollView,暴露一个外接口,重写onScrollChanged方法,向接口提供scrollY位置。 getViewTreeObserver().addOnGlobalLayoutListener,通过添加视图观察者监听来初始化tab的位置。 public class ScrollLevitateTabView extends ScrollView { private OnScrollLintener onScrollLintener; public void setOnScrollLintener(OnScrollLintener onScrollLintener) { this.onScrollLintener = onScrollLintener; } public ScrollLevitateTabView(Context context) { this(context,null); } public ScrollLevitateTabView(Context context, AttributeSet attrs) { this(context, attrs,0); } public ScrollLevitateTabView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { //增加视图监听 在整个视图树绘制完成后会回调 getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { onScrollLintener.onScroll(getScrollY()); } }); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (onScrollLintener != null) { onScrollLintener.onScroll(t); } } public interface OnScrollLintener{ void onScroll(int scrollY); } } 布局中使用 直接将ScrollLevitateTabView作为根布局,嵌套一个FrameLayout方便更改tab的位置。FrameLayout中放真正的tab,嵌套的其它ViewGroup中正常布局,必须留有一个TAB的占位View <?xml version="1.0" encoding="utf-8"?> <aohanyao.com.scolltabview.ScrollLevitateTabView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/sltv" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> ................放自己的View <!--占位的Tab--> <TextView android:id="@+id/flow_tab2" android:layout_width="match_parent" android:layout_height="48dp" android:textColor="#fff" android:textSize="20sp" /> <!--占位的Tab--> ................放自己的View </LinearLayout> <!--真正的Tab--> <TextView android:id="@+id/flow_tab" android:layout_width="match_parent" android:layout_height="48dp" android:background="#03a9f4" android:gravity="center" android:text="这里是悬浮的TAB" android:textColor="#fff" android:textSize="20sp" /> <!--真正的Tab--> </FrameLayout> </aohanyao.com.scolltabview.ScrollLevitateTabView> MainActivity 没什么代码量,FVB和设置滑动监听,然后在回调方法中不断的layout真正tab的位置 public class MainActivity extends AppCompatActivity implements ScrollLevitateTabView.OnScrollLintener { //外层的ScrollView private ScrollLevitateTabView sltv; //真正的Tab private TextView flow_tab; //占位的Tab private TextView flow_tab2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); flow_tab = (TextView) findViewById(R.id.flow_tab); flow_tab2 = (TextView) findViewById(R.id.flow_tab2); sltv = (ScrollLevitateTabView) findViewById(R.id.sltv); //设置监听 sltv.setOnScrollLintener(this); } @Override public void onScroll(int scrollY) { //layout Tab的位置 int top = Math.max(scrollY, flow_tab2.getTop()); flow_tab.layout(0, top, flow_tab.getWidth(), top + flow_tab.getHeight()); } } 最终就能达到效果了。 源码地址

优秀的个人博客,低调大师

小黑的日常折腾-快速建立私有CA的shell脚本

小黑又开始折腾新的东西了,上周刚学习完openssl建设私有CA,周六就花了点时间写了这个脚本,时间仓促,搞完就去撸DNS了,如果有啥BUG请见谅,本脚本纯属练习,用来练openssl、awk、sed等知识点。 先来介绍下建设私有CA的简单步骤(以下路劲为默认安装路径): (1) 生成私钥; ~]# (umask 077; openssl genrsa -out /etc/pki/CA/private/cakey.pem 4096) (2) 生成自签证书; ~]# openssl req -new -x509 -key /etc/pki/CA/private/cakey.pem -out /etc/pki/CA/cacert.pem -days 3655 -new:生成新证书签署请求; -x509:生成自签格式证书,专用于创建私有CA时; -key:生成请求时用到的私有文件路径; -out:生成的请求文件路径;如果自签操作将直接生成签署过的证书; -days:证书的有效时长,单位是day; (3) 为CA提供所需的目录及文件; ~]# mkdir -pv /etc/pki/CA/{certs,crl,newcerts} ~]# touch /etc/pki/CA/{serial,index.txt} ~]# echo 01 > /etc/pki/CA/serial 本脚本通过分析openssl.conf文件来得到相关路径,再通过命令建设私有CA,自签证书的相关信息通过数组保存通过echo命令传给生成自签证书的命令。具体的脚本内容如下: #!/bin/bash #Program: #ThisprogramisusedtocreatCA #History: #2016/4/9xiaoheiv1.0 #blog:http://zww577593841.blog.51cto.com/6145493/1750689 # #私有CA存放目录 #dir #已颁发的证书的存放目录 #certs #已吊销的证书的存放目录 #crl_dir #新证书的存放目录 #new_certs_dir #当前证书的序列号保存文件 #serial #已颁发证书的索引文件 #database #CA的自签证书 #certificate #CA的私钥 #private_key #openssl.conf配置文件的位置 declareconffile=/etc/pki/tls/openssl.cnf #定义一个数组保存CA配置文件中需要保存的一些属性名称 declare-avar var=("dir""certs""crl_dir""new_certs_dir""serial""database""certificate""private_key") #临时文件和目录 declare-atempfile tempfile=("./ca_default.txt""./ca_value.txt") #必要的文件和目录 declare-acreatfile declare-acreatdir creatfile=("serial""database") creatdir=("certs""crl_dir""new_certs_dir") #openssl.conf关于CA的相关属性 declare-Aca #自签证书需要填写的信息:国家(大写的两个字母);省;市;公司;部门;主机名;邮箱(可选) cainfo=("CN""Beijing""Beijing""blackboy""ops""ca.blackboy.com""blackboy@163.com") #信号捕捉 trap'mytrap'INT mytrap(){ clean_temp echo-e"\033[31mexit\033[0m" exit } #清理临时文件和变量及数组 clean_temp(){ for((i=0;i<${#tempfile[*]};i++));do mv-f${tempfile[$i]}/tmp/ done unset-vconffile unset-vvar unset-vtempfile unset-vca unset-vcreatfile unset-vcreatdir unset-vcainfo } #分析文件或者目录是否存在 analyse_file_dir(){ if[-f"$1"];then echo-e"\033[32mfile$1exist\033[0m" return0 elif[-d"$1"];then echo-e"\033[32m$1isexist\033[0m" return0 else echo-e"\033[31m$1isnotexist\033[0m" return1 fi } #创建需要的文件 creat_file(){ touch"$1" analyse_file_dir"$1"&>/dev/null&&echo-e"\033[32mcreat$1successful\033[0m"||return1 return0 } #创建需要的目录 creat_dir(){ mkdir-pv"$1" analyse_file_dir"$1"&>/dev/null&&echo-e"\033[33mcreat$1successful\033[0m"||return1 return0 } #分析并获得conffile变量保存的路径所指的openssl.conf文件的一些信息 analyse_conf(){ analyse_file_dir$1||exit1 #通过sed获得配置文件自己需要的部分的内容 sed-n'/\[CA_default/,/##/s@[#].*@@gp'$1|sed's/=/=/g'>${tempfile[0]} #通过awk格式化上步得到内容并保存到临时文件中 awk'/^[^[:space:]]/{a[$1]=$3}END{for(iina){printf""i"=%s\n",a[i]};}'${tempfile[0]}>${tempfile[1]} #定义局部变量保存dir项的路径 localdir=$(sed-n's/^dir=//p'${tempfile[1]}) #配置文件中$dir代替了dir的路径,还原为其真实路径,并保存在临时文件中 sed-i"s@[$]dir@$dir@g"${tempfile[1]} echo"ThecurrentOpenSSLconfiguration" cat./ca_value.txt echo"#################################" #把配置文件中查找到的属性信息保存到关联数组中 for((i=0;i<${#var[*]};i++));do ca["${var[$i]}"]=$(awk-F"="'{if($1~/^'${var[$i]}'$/){print$2}}'${tempfile[1]}) done } #创建私钥 creat_private_key(){ #取文件所在目录名,如果目录不存在则创建 analyse_file_dir${1%/*}||creat_dir${1%/*}||return1 #创建私钥,大小为4096,并将创建的私钥文件的权限设置为只有当前用户可读写 (umask077;opensslgenrsa-out$14096) #分析私钥是否创建完成 analyse_file_dir$1&>/dev/null&&echo-e"\033[32mcreatprivatekeysuccessful\033[0m"||return1 echo"ok" return0 } #生成自签证书 creat_cacert(){ localinfo #生成签发证书时要填写的信息 for((i=0;i<${#cainfo[*]};i++));do info="$info${cainfo[$i]}\n" done #签发证书 echo-e"$info"|opensslreq-new-x509-key$1-out$2-days3655 #分析证书是否正常生成 analyse_file_dir$2&>/dev/null&&echo-e"\033[32mcreatcacertsuccessful\033[0m"||return1 } #如果openssl.conf文件不存在直接退出 analyse_conf$conffile||exit1 #创建必要的文件和目录,如果创建失败则退出 for((i=0;i<${#creatfile[*]};i++));do analyse_file_dir${ca[${creatfile[$i]}]}||creat_file${ca[${creatfile[$i]}]}||exit1 done for((i=0;i<${#creatdir[*]};i++));do analyse_file_dir${ca[${creatdir[$i]}]}||creat_dir${ca[${creatdir[$i]}]}||exit1 done #向证书编号文件serial中添加编号 echo"01>>>>>${ca[serial]}" echo"01">${ca[serial]} #调用函数创建CA的私钥 creat_private_key${ca[private_key]}||exit1 #创建CA自签证书 creat_cacert${ca[private_key]}${ca[certificate]}||exit1 #查看证书内容 opensslx509-in${ca[certificate]}-noout-serial-subject #清理安装过程中生成的变量、数组、和临时文件 clean_temp 脚本的运行结果如下: 时间仓促,未解释的部分,请见谅。

优秀的个人博客,低调大师

给前端同学看的跨平台游戏开发入门

大家好,我是一个游戏引擎技术探索者,同时也是一名做过不少前端开发工作的程序员。如果你想知道如何从编写网页到开发游戏,那你来对地方了! 今天我们聊聊如何使用 Dora SSR,一个支持 TSX 且跨平台在 native 运行的游戏引擎,助你轻松跨入游戏开发的世界。不必担心,说到游戏引擎并不是啥高不可攀的技术,反而和我们熟悉的前端开发工具可以有惊人相似之处。 一、游戏客户端开发也可以是一种前端开发 首先,让我们解释一下什么是游戏引擎。简单来说,游戏引擎就是一套工具和库的集合,帮助开发者构建游戏,管理图形、声音、物理计算或碰撞检测等。对于前端开发者来说,你可以把它想象成就是一种特殊的浏览器,专门用来运行游戏。 Dora SSR 的游戏场景管理使用了类似于 HTML DOM 的树形结构,这对我们来说再熟悉不过了。想象一下,将 div 元素换成游戏中的各种对象,CSS 动画换成游戏动画,概念也差不多,代码写法上可能也差不多,是不是觉得有点意思了? 二、从 TypeScript 到 TSX:前端技术在游戏中的应用 许多前端开发者都熟悉 TypeScript 和 React 的 JSX 语法。在 Dora SSR 开源游戏引擎中,我们通过支持 TSX,提供了与前端开发编程模式相似的游戏开发接口。是的你没听错,就是那个 TSX。 使用 TSX 开发游戏,意味着你可以利用已有的前端技术栈—组件、模块和其他现代前端技术,直接在游戏开发中复用这些概念。而且,Dora SSR 的性能优化确保了即使是在复杂的游戏场景中,也能保持流畅的运行。 三、挑战 100 行代码以内,教你写一个“愤怒的小鸟”like的游戏 好了,理论知识够多了,让我们来点实际操作吧。来看看如何在 Dora SSR 中用 100 行以内的 TSX 代码编写一个类似“愤怒的小鸟”的小游戏。当然,在开始之前还是要准备开发环境,做这个事用 Dora SSR 就很简单:我有一个安装包一装,我有一个浏览器一开,嗯,开始写代码运行吧。安装启动参见:Dora 启动! 不小心装成了APK包在手机上?那就在同局域网下访问,直接在手机上进行开发调试吧 1. 编写最简单游戏场景 在编写实际的代码之前,我们可以先写一个有特别功能的注释,它可以告诉 Dora SSR 的 Web IDE 在我们按下 Ctrl + S 保存文件时,自动热更新运行的代码,以实现代码运行结果的实时预览功能。 // @preview-file on 然后,我们引入必要的库和组件。当然我们的代码编辑器也会提示辅助我们自动引入需要的模块,可以放到后面编码过程中再完成: import { React, toNode, useRef } from 'dora-x'; import { Body, BodyMoveType, Ease, Label, Line, Scale, TypeName, Vec2, tolua } from 'dora'; 在 Dora SSR 中显示一个图片很简单,只要使用<sprite>标签,最后通过toNode()函数将标签实例化为一个游戏对象就可以了。 toNode(<sprite file='Image/logo.png' scaleX={0.2} scaleY={0.2}/>); 好的,至此你已经基本掌握大部分 Dora SSR 游戏开发的诀窍了,开始做你自己的游戏吧(认真)。 2. 编写游戏箱子组件 接下来我们在游戏中碰撞的箱子会由 Box 组件定义,它接受 num、x、y 和 children 等属性: interface BoxProps { num: number; x?: number; y?: number; children?: any | any[]; } const Box = (props: BoxProps) => { const numText = props.num.toString(); return ( <body type={BodyMoveType.Dynamic} scaleX={0} scaleY={0} x={props.x} y={props.y} tag={numText}> <rect-fixture width={100} height={100}/> <draw-node> <rect-shape width={100} height={100} fillColor={0x8800ffff} borderWidth={1} borderColor={0xff00ffff}/> </draw-node> <label fontName='sarasa-mono-sc-regular' fontSize={40}>{numText}</label> {props.children} </body> ); }; 我们使用仿 React 的函数组件的写法来完成我们箱子组件的定义,其中: body 组件的 tag 属性:用于存储箱子的分数。 rect-fixture :定义了箱子的碰撞形状。 draw-node :用于绘制箱子的外观。 label :用于显示盒子的分数。 3. 创建 TSX 实例化后的对象引用 使⽤ useRef 创建两个引⽤变量进行备用,分别指向⼩⻦和分数标签: const bird = useRef<Body.Type>(); const score = useRef<Label.Type>(); 4. 创建发射线 发射线由 line 变量创建,并添加触摸(同时也是鼠标点击)的事件处理: let start = Vec2.zero; let delta = Vec2.zero; const line = Line(); toNode( <physics-world onTapBegan={(touch) => { start = touch.location; line.clear(); }} onTapMoved={(touch) => { delta = delta.add(touch.delta); line.set([start, start.add(delta)]); }} onTapEnded={() => { if (!bird.current) return; bird.current.velocity = delta.mul(Vec2(10, 10)); start = Vec2.zero; delta = Vec2.zero; line.clear(); }} > {/* ...在物理世界下创建其它游戏元素 ... */} </physics-world> ); 在 onTapBegan 事件中,记录触摸开始的位置并清除发射线。 在 onTapMoved 事件中,计算触摸移动的距离并更新发射线。 在 onTapEnded 事件中,根据触摸移动的距离设置小鸟的发射速度并清除发射线。 5. 创建其它游戏元素 接下来,我们以 <physics-world> 作为游戏场景的父级标签,在它下面继续创建游戏场景中的各个元素: 5.1 地面 首先,我们使用 body 组件创建一个地面,并将其设置为静态刚体: <body type={BodyMoveType.Static}> <rect-fixture centerY={-200} width={2000} height={10}/> <draw-node> <rect-shape centerY={-200} width={2000} height={10} fillColor={0xfffbc400}/> </draw-node> </body> type={BodyMoveType.Static}:表明这是一个静态刚体,不会受到物理模拟的影响。 rect-fixture:定义地面碰撞形状为一个矩形。 draw-node:用于绘制地面的外观。 rect-shape:绘制一个矩形,颜色为黄色。 5.2 箱子 接下来,我们使用之前写好的 Box 组件创建 5 个箱子,并设置不同的初始位置和分数,在创建时播放出场动画: { [10, 20, 30, 40, 50].map((num, i) => ( <Box num={num} x={200} y={-150 + i * 100}> <sequence> <delay time={i * 0.2}/> <scale time={0.3} start={0} stop={1}/> </sequence> </Box> )) } map 函数:用于遍历分数数组从 10 到 50,并为每个分数创建一个需要小鸟撞击的箱子。 Box 组件:用于创建箱子,并传入以下属性: num={num}:箱子的分数,对应数组中的数字。 x={200}:箱子的初始 x 轴位置,为 200。 y={-150 + i * 100}:箱子的初始 y 轴位置,根据创建序号递增。 sequence 组件:用于创建要在父节点上播放的动画序列,包含以下动画: delay time={i * 0.2}:延迟播放动画,延迟时间根据创建序号递增。 scale time={0.3} start={0} stop={1}:缩放动画,从不显示到完全显示,耗时 0.3 秒。 5.3 小鸟 最后,我们使用 body 组件创建小鸟,并设置碰撞形状、外观和分数标签: <body ref={bird} type={BodyMoveType.Dynamic} x={-200} y={-150} onContactStart={(other) => { if (other.tag !== '' && score.current) { // 累加积分 const sc = parseFloat(score.current.text) + parseFloat(other.tag); score.current.text = sc.toString(); // 清除被撞箱子上的分数 const label = tolua.cast(other.children?.last, TypeName.Label); if (label) label.text = ''; other.tag = ''; // 播放箱子被撞的动画 other.perform(Scale(0.2, 0.7, 1.0)); } }}> <disk-fixture radius={50}/> <draw-node> <dot-shape radius={50} color={0xffff0088}/> </draw-node> <label ref={score} fontName='sarasa-mono-sc-regular' fontSize={40}>0</label> <scale time={0.4} start={0.3} stop={1.0} easing={Ease.OutBack}/> </body> ref={bird}:使用 ref 创建引用变量,方便后续操控小鸟。 type={BodyMoveType.Dynamic}:表明这是一个动态刚体,会受到物理模拟的影响。 onContactStart={(other) => {}}:小鸟的物理体接触到其它物体时触发的回调处理函数。 disk-fixture:定义小鸟形状为一个圆盘。 draw-node :用于绘制小鸟的外观。 label :用于显示小鸟的累积分数。 scale :用于播放小鸟的出场动画。 6. 完成游戏逻辑 至此,我们已经完成了小游戏的核心逻辑。你可以根据自己的想法进一步完善游戏逻辑和增加功能。完整的 demo 代码可以见这个链接:Dora-SSR/Assets/Script/Test/Birdy.tsx。下面是一些运行效果的截图。 <p align=center>拖拽屏幕发射了“愤怒的小鸟”</p> <p align=center>高超的技巧,使我一击获得了所有得分</p> 四、简单揭秘一下 1. 是鹿还是马 事实上我们写的这段游戏代码,在 Dora SSR 引擎的能力下是可以确保在跨 Linux、Android、iOS、macOS 和 Windows 获得一致的运行结果。但是为了运行这段代码,我们的 Dora SSR 引擎甚至都没有做 JavaScript 运行环境的支持……(你说什么?) 是的,Dora SSR 的底层技术实现其实是基于 Lua 和 WASM 虚拟机作为脚本语言运行环境的。对 TypeScript 的支持其实是通过整合了 TypescriptToLua(https://github.com/TypeScriptToLua/TypeScriptToLua )这个编译器提供的。TSTL 通过重新编写了 TypeScript 语言编译器的后端,将 TS 和 TSX 的代码编译为了等价运行的 Lua 代码,从而使得 TS 代码可以在 Dora 上加载运行。在 Dora 自带的 Web IDE 的代码编辑器下,可以帮助大家做 TS 的语言检查和补全以及 Dora 内置库 API 的提示。最终的使用体验,大家就可以不用管最后是鹿还是马,只要代码能通过了 TS 的编译检查,拉出来那都是一样的跑啦。 2. 和 React 有关系吗 这个问题的答案目前是:可以有(所以截至发文前还没有)。React 最重要的能力是通过 Virtual DOM 和执行 Tree Diff 处理的过程来进行渲染组件和业务数据的状态同步,目前这个机制还没有在 Dora SSR 中实现,所以大家目前看到的用 TSX 编写出的类似 VDOM 的构建代码只会在运行时做一次性的游戏渲染对象的构建,往后都是底层 C++ 实现的引擎功能在负责继续处理。也许有一天我们会为游戏 UI 的开发,提供仿 React 通过执行 Tree Diff 做状态同步的能力,或是仿 SolidJS 基于 TSX 实现其它的渲染组件状态同步的机制。所以在这里也诚挚地邀请广大前端开发的朋友,加入我们,一起玩 Dora SSR 项目,一起研究怎么运用前端开发技术思想,为游戏开发也引入更多好用便捷的轮子吧。 最后我们的Q群在这里,欢迎过来玩:512620381

优秀的个人博客,低调大师

Svelte入门——Web Components实现跨框架组件复用(二)

使用** Svelte 开发 VUE 和 React **都支持的自定义单元格组件 在上节中,我们一起了解了如何使用Svelte封装Web Component,从而实现在不同页面间使用电子表格组件。 Svelte封装组件跨框架复用,带来的好处也十分明显: 1、使用框架开发,更容易维护 2、发布后没有框架依赖,其他任何场景都可以使用 3、发布的Web Component体积小 这些得天独厚的优势,使得Svelte进行组件封装有着格外优势。之前我们了解了如何在不同页面间,自由使用电子表格组件。那如果要真正实现跨越不同的框架,使用相同的表格组件,该怎么做呢? 接着我们接着上节内容,继续为大家介绍,封装完成电子表格组件后,如何跨框架让电子表格组件在原生环境和各种框架中都可以使用。 跨框架组件开发 一、使用Svelte开发AutoComplete Web Component Svelte如今的生态很丰富,通过搜索我们可以找到一款Svelte开发的AutoComplete的组件,地址:https://github.com/pstanoev/simple-svelte-autocomplete。 我们一起来fork这个项目,做一些简单修改,让他生成一个Web Component出来(这里大家需要注意三方组建协议内容中,是否包含运行修改发布)。 1、修改src/SimpleAutocomplete.svelte 在头部添加: <svelte:options tag="auto-complete" /> 同时在代码中修改items添加一些默认信息: // the list of items the user can select from export let items = []; items = ["White", "Red", "Yellow", "Green", "Blue", "Black"]; 2、修改rollup.config.js 在plugins中配置customElement 设置后的结果为: import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import svelte from 'rollup-plugin-svelte'; import pkg from './package.json'; export default [ { input: 'src/SimpleAutocomplete.svelte', output: [ { file: pkg.module, format: 'es' }, { file: pkg.main, format: 'umd', name: 'Autocomplete' } ], plugins: [svelte({ customElement: true, }), commonjs(), resolve()] } ]; 3、运行npm run build打包生成Web Component 运行后会在根目录生成index.js和index.mjs两个文件,js是umd的支持,mjs是ES版本,后面我们直接使用UMD支持的index.js文件。 二、无框架页面测试 <div id="ss" style="height: 600px;"></div> <script type="text/javascript" src="index.js"></script> <script type="text/javascript"> window.onload = function(){ var spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss")); var sheet = spread.getActiveSheet(); sheet.setCellType(1, 1, new AutoComplateCellType()) } function AutoComplateCellType() { } AutoComplateCellType.prototype = new GC.Spread.Sheets.CellTypes.Base(); AutoComplateCellType.prototype.createEditorElement = function () { var ac = document.createElement('auto-complete'); ac.setAttribute("gcUIElement", "gcEditingInput"); return ac; } AutoComplateCellType.prototype.updateEditor = function(editorContext, cellStyle, cellRect) { if (editorContext) { editorContext.style.width=cellRect.width; editorContext.style.height=32; editorContext.parentElement.parentElement.style.overflow = "visible"; return {height: 32}; } }; AutoComplateCellType.prototype.getEditorValue = function (editorContext) { if (editorContext) { return editorContext.value; } }; AutoComplateCellType.prototype.setEditorValue = function (editorContext, value) { if (editorContext) { editorContext.value = value } }; </script> 引入生成的index.js 创建AutoComplateCellType,设置到单于格中,效果如图: 三、Vue框架中使用 通过import的方式引入AutoComplate Web Component <script> import '@grapecity/spread-sheets-vue' import '../static/index' // 复制打包的index.js到static文件夹下 import * as GC from "@grapecity/spread-sheets" function AutoComplateCellType() { } AutoComplateCellType.prototype = new GC.Spread.Sheets.CellTypes.Base(); AutoComplateCellType.prototype.createEditorElement = function () { var ac = document.createElement('auto-complete'); ac.setAttribute("gcUIElement", "gcEditingInput"); return ac; } AutoComplateCellType.prototype.updateEditor = function(editorContext, cellStyle, cellRect) { if (editorContext) { editorContext.style.width=cellRect.width; editorContext.style.height=32; editorContext.parentElement.parentElement.style.overflow = "visible"; return {height: 32}; } }; AutoComplateCellType.prototype.getEditorValue = function (editorContext) { if (editorContext) { return editorContext.value; } }; AutoComplateCellType.prototype.setEditorValue = function (editorContext, value) { if (editorContext) { editorContext.value = value } }; export default { // name: 'sample-header' methods:{ workbookInitialized(spread){ var sheet = spread.getActiveSheet(); sheet.setCellType(1, 1, new AutoComplateCellType()) } } } </script> 这里注意打包的index.js 引入后会报一个关于TS的错误,删除文件中以下内容即可。 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 在React中方式相同,这里就不赘述了。 大家如果有其他想法、实现思路,也欢迎评论交流。

优秀的个人博客,低调大师

2.2 Go语言从入门到精通:Go语言变量

变量来源于数学,是计算机语言中能储存计算结果或能表示值的抽象概念。 Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。 变量可以通过变量名访问。 Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。 1、变量的声明 声明变量的一般形式是使用 var 关键字: var name type 其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型。 还可以一次声明多个类型相同的变量: var name1,name2,name3 type 需要注意的是,Go语言和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。这样做的好处就是可以避免像C语言中那样含糊不清的声明形式,例如:int* a, b; 。其中只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在 Go 中,则可以和轻松地将它们都声明为指针类型: var a, b *int 当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。 示例如下: package main import "fmt" func main() { var a,b int fmt.Println(a,b) } 输出结果如下: 0 0 变量的声明有多种形式,整理归纳如下。 1.1 标准声明格式 Go语言的变量声明的标准格式如下: var 变量名 变量类型 或 var 变量名1,变量名2 变量类型 变量声明以关键字 var 开头,后置变量类型,行尾无须分号。 如果没有初始化,则变量默认为零值。(零值就是变量没有做初始化时系统默认设置的值。) var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。 示例如下: package main import "fmt" func main() { // 声明一个没有初始化的int类型变量,默认为零值0 var a int fmt.Println(a) // 声明一个没有初始化的bool类型变量,默认为零值false var b bool fmt.Println(b) } 输出结果如下: 0 false 1.2 根据值自行判定变量类型 var 变量名 = 变量值 示例如下: package main import "fmt" func main() { var c = true fmt.Println(c) } 输出结果如下: true 1.3 短变量声明 在函数中,一种称为短变量声明的可选形式可以用来声明和初始化局部变量。格式如下: 变量名 := 变量值 这里省略了关键字var,变量名的类型由变量值的类型决定,变量值可以是任何函数或表达式计算的结果值。 因其短小、灵活,故而在局部变量的声明和初始化中主要使用。 短变量声明有以下注意点: 定义变量,同时显式初始化。 不能直接提供数据类型。 只能用在函数内部。 :=表示声明,而=表示赋值。 :=左侧如果是已声明的变量,就会产生编译错误。如: var intVal int intVal := 1 // 会产生编译错误 示例如下: package main import "fmt" func main() { // 短变量声明 d := "xcbeyond" fmt.Println(d) } 输出结果如下: xcbeyond 1.4 批量声明 觉得每行都用 var 声明变量比较烦琐?没关系,还有一种为懒人提供的定义变量的方法: var ( a int b string c float64 ) 使用关键字 var 和括号,可以将一组变量定义放在一起。 示例如下: package main import "fmt" func main() { // 批量声明 var ( e int f string g float64 ) fmt.Println(e,f,g) } 输出结果如下: 0 0 2、变量的初始化 变量初始化标准格式如下: var 变量名 类型 = 变量值 如: var i int = 100 i为变量名,类型为int,i的初始值为100。 上面代码中,100 和 int 同为 int 类型,int 可以认为是冗余信息,因此可以进一步简化初始化的写法。 在标准格式的基础上,将 类型int省略后,编译器会尝试根据等号右边的变量值推导出变量的类型。如: var i = 100 短变量声明并初始化,如: i := 100 这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型。 3、变量的多重赋值 赋值是用来更新变量所指的值,它最简单的形式由赋值符=,以及符号左边的变量和右边的表达式组成。 x = 1 person.name = "xcbeyond" count[x] = count[x] * 5 另一种形式的赋值是多重赋值,它允许几个变量一次性被赋值。 在Go语言语法中,变量初始化和变量赋值是两个不同的概念,Go语言的变量赋值与其他语言一样,但是Go语言提供了其他程序员期待已久的多重赋值功能,可以实现变量交换。多重赋值让Go语言比其他语言减少了代码量。 以简单的算法交换变量为例,传统写法如下所示: var a int = 10 var b int = 20 var tmp int tmp = a a = b b = t fmt.Println(a, b) 新定义的变量tmp是需要内存的,于是有人设计了新的算法来取代中间变量,其中一种写法如下所示: var a int = 10 var b int = 20 a = a ^ b b = b ^ a a = a ^ b fmt.Println(a, b) Go语言有了多重赋值功能,则简单写法如下所示: var a int = 10 var b int = 20 b, a = a, b fmt.Println(a, b) 从以上例子来看,Go语言的写法明显简洁了许多,需要注意的是,多重赋值时,左值和右值按照从左到右的顺序赋值。这种方法在错误处理和函数当中会大量使用。 4、匿名变量 在编码过程中,可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。 匿名变量的特点是一个下画线_,_本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。 例如,我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量。在Go中这种情况可以通过结合使用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。 假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName,若只想获得nickName,则可以用如下方式编写: package main import "fmt" func main() { // 匿名变量 firstName, _, _ := getName() _, lastName, _ := getName() _, _, nickName := getName() fmt.Println(firstName, lastName, nickName) } func getName() (firstName, lastName, nickName string) { return "X", "C", "xcbeyond" } 输出结果如下: X C xcbeyond 这种用法可以让代码非常清晰,基本上屏蔽掉了可能混淆代码阅读者视线的内容,从而大幅降低沟通的复杂度和代码维护的难度。 匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。 5、变量的作用域 一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。 了解变量的作用域对我们学习Go语言来说是比较重要的,因为Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。如果不能理解变量的作用域,就有可能会带来一些不明所以的编译错误。 根据变量定义位置的不同,可以分为以下三个类型: 局部变量:函数内定义的变量。 全局变量:函数外定义的变量。 形式参数:函数定义中的变量。 下面就来分别介绍一下。 5.1 局部变量 在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。 局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。 main()函数中使用到了局部变量a、b、c,示例如下: package main import "fmt" func main() { // 声明局部变量a和b并赋值 var a int = 3 var b int = 4 // 声明局部变量c.并计算a和b的和 c := a + b fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c) } 输出结果如下: a = 3, b = 4, c = 7 5.2 全局变量 在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用“import”关键字引入全局变量所在的源文件之后才能使用这个全局变量。 全局变量声明必须以var关键字定义,如果想要在外部包中使用全局变量的首字母必须大写。 定义全局变量c,示例如下: package main import "fmt" // 声明全局变量 var c int func main() { //声明局部变量 var a, b int //初始化参数 a = 3 b = 4 c = a + b fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c) } 输出结果如下: a = 3, b = 4, c = 7 Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。 5.3 形式参数 在定义函数时,函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。 形式参数会作为函数的局部变量来使用。 函数sum(a, b int)定义了形式参数a、b,示例如下: package main import "fmt" func main() { sum := sum(1,3) fmt.Println(sum) } // 两数求和 func sum(a, b int) int{ num := a + b return num } 输出结果如下: 4

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册