23030816

20230812

20230811

  • ga-tracker:适用于UniApp和微信小程序的谷歌统计 (Google Analytics) SDK,只支持 Universal Analytics,即形式如 UA-XXXXXXXX-1 的 ID。(2023 年 7 月 1 日起就用不了了,最后我直接将 script 代码放到 index.html head 中去了,不知道起不起作用)
  • Google Analytics分析终极入门指南

20230809

20230808

20230807

20230805

20230804

  • 需求:评论要显示刚刚、几分钟前、几个小时前、几天前、几个月前等等状态。考虑使用现成库,但是要注意将时间转换为相同时区再计算。

    • 现状:Response Header 返回的是没有时区差的标准 UTC 时间 Fri, 04 Aug 2023 07:10:11 GMT,Response Body 返回的内容的时间是服务器所在日本本地时间 2023-08-04 15:08:28,dayjs() 返回的是客户端(中国)本地时间 Fri Aug 04 2023 15:19:17 GMT+0800 (China Standard Time)

    • 相关文章

    • 解决方案:将 Body 返回的日本时间解析成当时的 UTC 时间;获取当前 UTC 时间;两者计算相对时间;然后加入国际化。

      import dayjs from 'dayjs'
      import utc from 'dayjs/plugin/utc'
      import timezone from 'dayjs/plugin/timezone'
      import relativeTime from 'dayjs/plugin/relativeTime'
      import 'dayjs/locale/zh-cn'
      
      dayjs.extend(utc)
      dayjs.extend(timezone)
      dayjs.extend(relativeTime)
      
      const commentCreateTimeUTC = dayjs.tz('2023-08-04 17:18:28', 'Asia/Tokyo')
      const nowTimeUTC = dayjs.utc()
      console.log('relative time: ', nowTimeUTC.locale('zh-cn').to(commentCreateTimeUTC))
      
  • 我觉得写代码有两种状态,一种状态是使用非常合适的非常好用的库,可以非常简单方便的实现各种需求,几行代码就可以达到非常不错的效果;另一种状态,就是不存在这样合适的库,这种情况,要么是使用不那么合适的库,结果你需要各种 Hack,代码写的很乱,几百行都不够,要么就是自己去实现一个合适的好用的库,当然这个不仅花费时间更长,对开发者的要求更高。我们都认同,合适的工具能够达到事半功倍的效果,在编程的世界里,就是找到那些合适的编程语言、框架、库来实现自己的需求,找不到就去实现;这就是编程的乐趣,你既可以成为工具的受益者,也可称为工具的创造者。(PS:使用 dayjs 有感)

20230803

20230802

  • 自动国际化/多语言/i18n

  • Regular-Expressions.info

  • 多语言切换,域名会变成服务器的私有 IP 10.2.0.10,再刷新后又正常了,在本地测试没有问题,但是实际上线后就有这个问题。

    • 之前为了获取语言切换后后台返回的多语言数据,使用了 window.location.reload(),现在考虑不用,而是在语言切换的时候重新请求相关的接口;但是之前请求用的是 cookie,会比语言切换慢一步,比如从中文=>日文=>繁体,那么选择日文展示的是之前选择的中文,选择繁体展示的是之前选择的日文,因此考虑用 pinia store 这个状态管理来存 locale,这样就可以获取最新的值;但是第一次使用时会检测浏览器默认语言,store 就没办法知道了检测的值了,只能考虑同时用 store 和 cookie,store 默认值为空,这样第一次用 cookie 的值。关于重新请求接口,Nuxt3 有一个 refreshNuxtData API,重新请求当前页面请求的 API,其他的则需要手动重新请求。
    • 上面修改多语言行为后,发现没有解决跳向私有 IP 的问题,问题重新描述为—— npx nuxi generate 生成静态文件,通过 Nginx 部署到服务器,访问的时候如果 reload 页面,会跳到服务器局域网 IP。
      • 具体描述:直接 https://testweb.iruca.ai/create 会跳到 http://10.2.0.10/create; 但 https://testweb.iruca.ai/abcd 正确显示 404,因为正确 404 了,我猜测应该是前端的问题,但是前端实在找不到问题在哪里。之后我又尝试了 https://testweb.iruca.ai/create/(多了个斜杠),发现可以访问了,因此猜测是 nginx 配置问题。之前一访问就直接跳转了,其实可以用 curl -i https://testweb.iruca.ai/create 可以查看跳转前的内容,显示为 301 Moved Permanently(最好用这个来测,由于浏览器有缓存,更改 nginx 后并不能马上在浏览器上看到结果,还以为修改的无效呢!)。配置 Nginx 将 try_files 行中的 $uri/ 替换成了 $uri/index.html,问题就被解决了。

      • Nginx二级子路径请求,如何去掉尾部多余的"/“以及避免301重定向请求:导致 301 是 Nginx 默认行为——当访问URI时,如果访问资源为一个目录,并且URI没有以正斜杠(/)结尾,Nginx 服务就会返回一个301跳转,目标地址就是加一个正斜杠。

      • 搞懂前端技術名詞 SSR與SPA:这次部署的就是 SPA 应用,特点是生成的 index.html 文件的主要内容是挂载点 <div id="__nuxt"></div>,部署的特点是始终返回这个 index.html,nuxt 生成的静态文件中,pages下的页面都单独一个目录,目录中都是内容完全一样的 index.html。

      • Prepare NuxtJS for static deployment • Today I Learned:Nuxt SPA 部署的 Nginx 配置可以参考这个。

        location /.
        {
            # Remove trailing slash and redirect it
            rewrite ^(.+)/+$ $1 permanent;
        
            # Redirect index.html
            rewrite ^(.+)/index.html$ $1 permanent;
        
            # Serve folder path via index.html
            try_files $uri $uri/index.html =404;
        
            # Serve a custom static error page
            error_page 404 /404.html;
        }
        
  • Nginx

  • 现代 JavaScript 教程

20230731

  • 维基百科有一个 Simple English 的版本,用最常用的单词解释各种事情。
  • coinphd 的 admin 项目的路由,不是那种固定的路由,不像 nuxt 或 uniapp 那样,在 pages 目路下的路径就是路由路径,uniapp 还需要修改 manifest.json,而 nuxt 则是完全不需要了。admin 项目是根据调用 api 返回的路由数组,在全局前置守卫中动态地添加对应的路由。总的来说,路由方便程度 nuxt(web) > uniapp(mobie) > vite(admin),路由安全程度及自由程度 vite(admin) > uniapp(mobie) > nuxt(web)。
  • 不论是前端、后端还是运维,掌握程序运行流程是开发的前提,一个是你要知道如何看现有的代码,一个是你要知道到哪里去改代码。就前端来说,运行流程就是路由,你在浏览器上输入网址,网址对应具体的页面,而该页面就是执行特定的代码文件所产生的,而你的任务就是根据网址找到对应的执行文件;像 Nuxt、Next 这种,网址的路径对应 pages 下的路径,就比较简单直接;像 uniapp、antd 这种,网址与执行文件由路由配置来进行映射,这种关键就是找到路由配置文件。就后端来说,与前端基本上一致,像 Nest 这种,路由就自动对应着 routes 目录下 controller 文件;像 PHP 这种,默认路由就是在 controller 目录下的路径,但是也可以通过配置文件来进行配置。就运维来说,执行文件变成了运行的 docker,你的任务,就是根据特定的域名,找到对应的docker容器,将来自互联网的请求交予它处理。掌握了路由后,你就可以在路由的某个阶段,或者某个路由中添加东西了,进行开发了。万事开头难,掌握了第一步后,接下来的就简单了。你看图灵机,程序的本质,就是按照顺序执行一个又一个的任务的,面向过程、面向对象、函数式等编程方法,还有各种框架,各种工具辅助等等,本质还是按照顺序执行一个又一个的任务,而要去理解,也就是自己去按照顺序执行一个又一个的任务。
  • 项目中前端如何实现无感刷新 token!:评论很有意思。
    • 第一种,accessToken + refreshToken:accessToken的特点是使用频繁但有效期短。使用频繁是因为它会在每个请求中传输,所以它更容易泄露。refreshToken的特点是有效期长,但使用频率很低。它长期存在本地,不会随着每个请求传输,所以不容易泄露,但又能延长真正的过期时间。所以两个token的搭配,既保证了用户可以长时间不退出,又尽量降低了token泄漏的风险。
    • 第二种,单token后端动态延长有效期:当用户一直在操作页面调用接口且当前token没过期时,后端统一在拦截器中自动续时确保token永远不会过期,这样就可以避免突然让用户去登录。当用户长时间未操作页面token失效时,拦截器返回401跳转登录。也起到使用token鉴权的目的。

20230729

20230726

20230725

  • PHP 部署问题
    • file_put_contents (/chat/server/runtime/cache/…): Failed to open stream: No such file or directory
      • file_put_contents:没有文件就创建前提是——你资源路径得存在,并且有权限
      • 在docker-compose.yaml中,./server 目录是 mount 到容器的 chat/server/,因此实际是问题是 ./server/runtime/ 不存在或者没有权限,解决办法就是创建或者加权限
      • 还是没有仔细的看报错,而是去试各种方法期望能幸运地解决问题,对 PHP 有一种新人的恐惧,停留在不知道、不了解的心理状态,失去了对 PHP 的深入学习的动力。
    • SQLSTATE php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution:原因是没有添加 .env 文件,也就没有提供 mysql host。
    • Turning expose_php OFF in php.ini:the X-Powered-By header.
    • 关键文件:server 即为 thinkphp 项目根目录,被直接 mount 到 docker 中
      • /server/.env 环境变量
      • /server/runtime 应用的运行时目录,是一个存储程序运行时生成的临时文件的地方。通常,它包含了缓存文件、日志文件、临时文件等,这些文件不是程序的核心部分,但是却非常重要。在程序运行过程中,这些文件可以被动态生成和更新,以满足程序的运行需求。当程序关闭后,这些文件通常会被删除,因此它们不会对系统造成永久性的影响。
      • /server/public/uploads 上传或者生成的图片等资源
      • /server/public/debug.txt file_put_contents('debug.txt', 'info') 输出位置
  • Nested CSS was detected, but CSS nesting has not been configured correctly:uniapp 项目,给 <style> 添加上 lang="scss" 属性就行了——<style lang="scss">
  • 谈谈像素以及微信小程序的 rpx
  • TypeScript 中的代码清道夫:非空断言操作符:ts变量函数后面的感叹号有什么用处?

20230722

20230721

20230720

  • Safari Reader 打印 PDF。
    • 腾讯云开发者社区的文章很适合打印的。
    • 对于 51CTO,主要是行号导致的问题,通过代码删除:document.querySelectorAll('.pre-numbering').forEach(item => item.parentElement.removeChild(item))
  • 微前端究竟是什么?微前端核心技术揭秘!

20230719

20230718

20230717

20230715

20230714

  • 如果是 PC 跳到 web.iruca.ai,如果是 Mobile 跳到 m.iruca.ai:方法就是加一个判断,然后跳转;可以在服务端判断,也可以在客户端判断;可以用 UserAgent 判断,也可以通过 viewport 尺寸来判断。
    • Detect Mobile Browsers

    • VUE判断当前设备是PC还是移动端

      export function useWebSelection() {
          const pcDomain =
              import.meta.env.VITE_PC_DOMAIN || '<https://testweb.iruca.ai/>'
          const mobileRegex =
              /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
          const isMobile = navigator.userAgent.match(mobileRegex)
          const isWide = matchMedia('(min-width: 1200px)').matches
          if (!isMobile && isWide) {
              window.location.href = pcDomain
          }
      }
      

20230713

  • 经验
    • Apifox:API 文档、API 调试、API Mock、API 自动化测试
    • Mac 配置
      • 安装 Xcode Command Line Tools,包含 git:xcode-select --install
      • 安装 Clash for WindowsClashX/ClashX Pro:否则无法安装 Homebrew。推荐 ClashX Pro 搭配 Clash Premium 开启 Enhanced Mode (tun 模式)使用。
      • 安装 Homebrew/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
      • 开启 openssh server:How to Turn On SSH on Mac
      • 安装 Caffeine: Don’t let your Mac fall asleep.
      • 复制配置文件:.oh-my-zsh、.zshrc、.zprofile、.npmrc、.ssh、.vimrc
      • 安装开发 GUI 工具:item2、visual-studio-code、google-chrome、firefox、tailscale、postman、dbeaver-community、another-redis-desktop-manager
      • 安装开发 CLI 工具:node@16、yarn、rust、openjdk@11
      • 克隆公司仓库:coinphd、iruca
      • 克隆个人仓库:dishizhihui-codes、dishizhihui-logs
      • 安装其他软件:lark、wpsoffice、shottr、adrive、vlc、keyboardcleantool、neteasemusic
      • 复制目录 Movies;备份 Projects

20230712

20230711

20230710

  • 经验
    • 什么是回归测试?

    • ZeroTier实现内网穿透、异地组网

      • FreeBSD:ZeroTier 不需要开放端口

        sudo pkg install zerotier
        sudo service zerotier enable # 自带 service,Nice!
        sudo service zerotier start
        sudo zerotier-cli join 233ccaac270f677c
        
      • How can zerotier on OSX be paused?

        # You can stop the service with:
        sudo launchctl unload /Library/LaunchDaemons/com.zerotier.one.plist
        
        # It will start on next boot or:
        sudo launchctl load /Library/LaunchDaemons/com.zerotier.one.plist
        
    • 利用frp工具实现内网穿透、随时随地访问内网服务

    • Syncthing - P2P文件同步工具

      • Syncthing Docs

      • Ubuntu 参考 Syncthing – Install and Setup Syncthing on Ubuntu/Debian:两个命令就安装好了

        sudo apt install syncthing
        sudo systemctl enable syncthing@username.service
        
      • MacOS

        # Install
        brew install syncthing
        # Test
        /opt/homebrew/opt/syncthing/bin/syncthing -no-browser -no-restart
        # Run as serivce
        brew services start syncthing
        # Access the GUI via the following URL: http://127.0.0.1:8384/
        
      • FreeBSD:需要打开对应的端口

        sudo pkg install syncthing
        sudo sh -c "echo 'pass in proto tcp from ue0:network to port 22000' >> /etc/pf.conf"
        sudo pfctl -f /etc/pf.conf
        sudo crontab -e
          @reboot kurome:kurome sleep 60;/usr/local/bin/syncthing&
        
      • Tips:

        • 在连接设置中,只打开了本地发现;传输速度受限于路由设备(随身 WIFI,不到 5MB/s)
        • 只需要在一个设备添加文件夹就可以了,不需要两个设备同时添加,分享后就是双向同步的了
    • Pkg Primer: pkg search -f packageName 展示 remote package info;pkg info packageName 展示 installed packageName。

20230707

20230706

20230705

20230704

20230703

20230701

20230630

  • 任务
    • JavaScript 媒体查询

      const [isDesktop, setIsDeskop] = useState(true)
      
      useEffect(() => {
        const mql = matchMedia('(max-width: 1024px)')
      
        const handleMediaChange = () => {
          if (mql.matches) {
            // console.log('match')
            setIsDeskop(false)
          } else {
            // console.log('unmatch')
            setIsDeskop(true)
          }
        }
      
        handleMediaChange() // 首次查询
      
        mql.addEventListener('change', handleMediaChange)
      
        return () => {
          mql.removeEventListener('change', handleMediaChange)
        }
      }, [])
      
    • 设置锚点居页面顶部距离:页面滚动高度 = 元素到页面顶部高度 - 固定导航栏高度

      const toAnchor = (id: string) => {
        const el = document.getElementById(id)
        if (el) {
          const react = el.getBoundingClientRect()
          const win = el.ownerDocument.defaultView
          if (win) {
            const offset = react.top + win.document.body.scrollTop
            const headerEl = document.getElementById('desktop-header')
            if (headerEl) {
              console.log('header height', headerEl.offsetHeight)
              window.document.body.style.scrollBehavior = 'smooth'
              window.document.body.scrollTop = offset - headerEl.offsetHeight
            }
          }
        }
      }
      
    • FFMPEG

      ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -b:a 128k -c:a libopus output.webm
      
    • 视频优化

      • 合适的视频格式:WebM(VP9) 相较于 MPEG-4(x264) 来说会更小
      • 视频压缩:HandBrake
      • Video Streaming with Node.js / HLS
        • HLS.js

        • example

          useEffect(() => {
            const hlsVideoSrc = '/videos/hls/chathomeai.m3u8'
            const videoEl = videRef.current
            if (videoEl) {
              videoEl.muted = true
              if (videoEl.canPlayType('application/vnd.apple.mpegurl')) {
                videoEl.src = hlsVideoSrc
                videoEl.onloadeddata = () => {
                  videoEl.play()
                }
              } else if (Hls.isSupported()) {
                const hls = new Hls()
                hls.loadSource(hlsVideoSrc)
                hls.attachMedia(videoEl)
                hls.on(Hls.Events.MANIFEST_PARSED, () => {
                  videoEl.play()
                })
              } else {
                setIsSupportHls(false)
              }
            }
          }, [])
          
        • 报错 SyntaxError: Unexpected keyword or identifier.,则需要在 tsconfig.json、.eslintignore 和 .prettierignore 中将 HLS ts 文件忽略。

20230629

20230628

  • 经验
    • next env
      • Environment Variables

      • next.config.js Options

      • Iconfont:图标库

      • ICONFINDER:图标库

      • seeklogo:图标库

      • ShareButtons 关于图标对齐

        // 如下两个不对齐,后面的比前面的矮一点,但是实际并没有 margin 或 padding 存在
        <button 
          aria-label={singleService} 
          key={singleService} 
          className='cursor-pointer bg-yellow-500 text-white rounded-full mx-1'
        >
          <div onClick={copyUrl} >
            <i className='fas fa-link w-8' />
          </div>
        </button>
        
        <button 
          aria-label={singleService} 
          key={singleService} 
          className='cursor-pointer bg-yellow-500 text-white rounded-full mx-1'
        >
          <div 
            onClick={copyUrl} 
            className="w-8 h-8 flex justify-center items-center" // 为了居中 icon 用 flex
          >
            <LinkIcon width={24} height={24} />
          </div>
        </button>
        
        // 后面的用如下写法则对齐
        <button
          aria-label={singleService}
          key={singleService}
          className="cursor-pointer bg-yellow-500 text-white rounded-full mx-1"
        >
          <div onClick={copyUrl}>
            <LinkIcon width={32} height={32} className="p-1" /> // 利用 padding 居中 icon
          </div>
        </button>
        
      • 内容整体要水平居中,内容间要对齐:包一层

        <div className="flex flex-col justify-start items-center">
          <div>
            <p>a very long sentence</p>
            <p>a word</p>
          </div>
        </div>
        

20230627

  • 经验
    • iphone frame:iPhone 边框

    • 关于css position定位 top百分比的问题:css设置绝对定位后 top,bottom,设置百分比定位是按父元素的高度来计算的,同样left,right,设置百分比定位是按父元素的宽度度来计算的。

    • XFengCloud-小逢云:免费一天试用,可以看有哪些用户使用,感觉靠谱,openvpn 方式免流了 90%,shadowsocket 方式免流了 30%,价格10元/月,还行。

    • OpenVPN 简明手册

      • Windows OpenVPN GUI记住用户名和密码:在ovpn配置文件里增加 auth-user-pass pass.txt,之后在ovpn配置文件所在目录建立一个pass.txt的文件,第一行为用户名,第二行为密码即可。

      • failed to negotiate cipher with server.:很多时候,把第一个错误解决了,剩下的错误都会消失(即所有剩下的错误都是由于第一个错误的产生而存在的)

        cipher none
        # 在 data-ciphers 最后加上 cipher 指定的加密算法,即 none
        data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:none
        
    • How to use curl to get public IP address

      curl ifconfig.me
      curl icanhazip.com
      curl ipecho.net/plain
      curl ipinfo.io/ip
      

20230626

20230625

20230621

  • 经验
    • Paste a rectangular block in Visual Studio: Click to the beginning of what you want to copy, press and hold Shift+Alt (Mac: Shift + Option) then click to the end of what you want to copy. Then release the keys and copy. When pasting, do the same for the column you want to insert.(快速翻译:首先选择并复制冒号后值为空的键放入Google进行翻译翻译,翻译后的文本做为值,之后利用块复制粘贴为值添加键,先放置值再放置键就可以保证同一个column实现块粘贴)
    • Visual studio code - how to copy search results?
      1. CTRL + F
      2. Type your search string
      3. CTRL + SHIFT + L to select all occurrences found (max. 999)
      4. ESC (or close search dialog with top-right X)
      5. CTRL + I to select whole lines
      6. CTRL + C
      7. Open new file
      8. CTRL + V
    • \u3000 是全角的空白符
    • JS给数字加千位分隔符: numObj.toLocaleString([locales [, options]])

20230620

20230619

20230617

20230616

20230615

20230614

  • 经验
    • 伪元素改变number类型input框的默认样式实例页面

      .deal::-webkit-textfield-decoration-container {
          background-color: #f0f3f9; 
      }
      .deal::-webkit-inner-spin-button {
          -webkit-appearance: none;
      }
      .deal::-webkit-outer-spin-button {
          -webkit-appearance: none; /*有无看不出差别*/
      }
      
    • How can I do Base64 encoding in Node.js?

      Buffer.from("Hello World").toString('base64') // encode
      Buffer.from("SGVsbG8gV29ybGQ=", 'base64').toString('ascii') // decode
      
    • 原来浏览器原生支持JS Base64编码解码:就是 window 的方法 atob 和 btoa

    • 踩坑日记:如何修复“System limit for number of file watchers reached”错误

      • 执行如下命令

        sudo sysctl fs.inotify.max_user_watches=524288
        echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
        sudo sysctl -p
        
      • Increase fs.inotify.max_user_watches in docker container: Docker mounts /proc as read-only since many of its knobs are global. If you run that same command on the host, the container will transparently inherit the new setting.

    • how to make non root user as sudo user in docker alpine image?

      • command: docker exec -it --user root mycontainer sh

      • dockerfile:

        USER root
        # Run root operation here
        # Change user back to cassandra
        USER cassandra
        RUN whoami
        
    • node中__dirname、__filename、process.cwd()、process.chdir()表示的路径

      • __dirname 表示__dirname所在文件所在的目录的绝对路径
      • __filename 表示__filename所在文件的绝对路径
      • module.filename ==== __filename 等价
      • process.cwd() 返回Node.js 进程执行时的工作目录的路径
      • process.chdir() 改变工作目录。
    • frp 将内网服务穿透到公网,可以在公网访问内网服务;tailscale 是 VPN,组件虚拟局域网,局域网内的设备可以相互访问。两者都通过公网路线访问,区别在于 frp 是暴露在公网,任何人都可以访问服务,tailscale是成员可以访问服务。就个人需求来说,当然是两者都用,一些服务能暴露到公网,一些服务只能成员访问。

      • Configuring IPv6:FreeBSD 自己添加的 wlan0 并没有自动启用 ipv6
      • Pv4 Gateway to access IPv6:有的路由器并没有支持或开启 ipv6,frp 报错 no route to host,可以利用 hax 的 share ipv4 端口来连接 frps 服务
      • woiden.id 变为 non-renewable 的了,总比永远抢不到服务的好,现在至少能用3天,用来测试也够了。
      • 支持 KCP 协议:降低延迟。报错 i/o deadline reached,用不了啊,quic 也一样。

20230613

20230612

20230610

20230609

20230608

  • IPFS

    • 站在Web3.0 理解IPFS是什么
    • IPFS 使用入门
    • IPFS 新手指北
    • IPFS 日用优化指南:在发布内容后,马上通过公开的 IPFS 网关访问,通常会很慢很慢,甚至到超时都无法访问,这是由 IPFS 的寻址过程导致的。随着访问内容的用户越来越多,他们的 IPFS 节点上会缓存你内容的数据,这个时候新节点再访问同一份内容,通常就会很快。这就是 IPFS 的特性,就跟 BT 下载的原理类似,数据在网络中存在的副本越多,就越能利用 P2P 网络的性能。
  • Zeal/Dash: offline documentation browser for software developers.

  • 博客:https://aandds.com/

20230607

20230606

20230605

20230602

20230601

20230531

  • 经验
    • 问题:正式版,PDF Steam web 端无法显示。原因:通过 devtool 查看,发现数据正确返回了,对比 www.chathome.jpchathome.jp,发现 www.chathome.jp api 调用都多了一个 preflight请求。解决:在部署环境中注释掉 env 中的 NEXT_PUBLIC_WEB_API_DOMAIN=https://chathome.jp 环境变量,让 api 调用自动使用相应的域名,从而不产生 preflight请求。经过排查,发现是 response.headers['content-disposition'].split['='](1) 报错 error TypeError: Cannot read properties of undefined (reading 'split'),因为 www.chathome.jp 的 response header 只有 content-type: "application/force-download",这里可以将 split 放在 try…catch 块中。为什么只返回改 header 字段呢?因为后台在跨域下只允许了特定的 header 可以被访问:allowedHeaders: [...allowedHeaders, 'content-type', 'Authorization'],

    • 为啥JavaScript里,数组正常的索引不能用负数呢?:因为JS的Array实际是个Object,并不是真的array,arr[-1] = 'a',但可以反向索引 arr.reverse()[1] 或 使用 arr.at(-1)

20230530

20230529

  • 经验
    • 生产环境的配置里面都不要用 10 开头的 ip 地址,10 开头的是虚拟lan 地址,依赖于 tailscale 服务器,万一这个服务器出问题了,会有影响;但是在本地运行 api 项目的时候,需要使用 10 开头的 ip 地址,例如 ClusterAllFailedError: Failed to refresh slots cache. 问题就是用 192 开头 ip 没有连接上 redis。

    • PDF stream:

      • 后台:Streaming files:用 file stream 替代之前的 pdf url,解决安全隐患,防盗链,防爬虫遍历 static pdf 文件。
      • 前台:
        • 前端如何下载文件流:注意 a 标签的 download="filename.pdf",Safari 没这个下载为 Unknow.pdf。结果是 IOS QQ浏览器无法显示无法下载;IOS Chrome 可以显示,可以下载到本地 Chrome 目录;Safari 可以显示,点击下载后打开新页面,新页面可以选择 save to files ;IOS Firefox 可以显示,点击下载后打开新页面,无法下载;IOS Lark 打开的内嵌网页可以显示,点击下载无反应。
        • 这将是你看到过最全的pdf预览解决方案:使用的是 iframe 来预览,在 chrome 浏览器开发者工具中选择手机端 device 后无法预览 pdf,但在 IOS 的 Safari 可以直接预览。
        • file-saver: Due to restrictions in iOS saveAs opens in a new window instead of downloading。IOS 下无法实现点击直接下载文件,除了 Safari 开发者没有解决办法。

20230526

20230525

  • 经验
    • useSWR 如果验证没变,那么返回的应该是 304(作用于 GET,用的 etag 字段),如果是重新请求成功,POST 是 201,GET 是 200.
    • Jackson Wu:研究某个复杂软件的源代码是一件很难的事情,推荐的做法是看它最早期版本,比如说 Linux Kernel,别看它现在有千万行代码,但其实它的核心设计在它只有几万行的时候就确定了。另一个推荐的做法是卡马克说的,自己尝试着去克隆某个项目,做一个玩具版的 xx,这样就能深刻理解它的运行原理。(500 Lines or Less)

20230524

  • 经验
    • 1-1评审了20份前端简历,其实每个人都有亮点:简历美化
    • 伟大的公司需要多少人:不融资,不追求团队规模,更关注公司盈利能力
    • ip 地址怎么到 kong 服务?:在域名解析只能解析到服务器的ip地址,不能具体指定到某一个端口,只有域名默认解析到 80 端口。要使用 kong 网关,需要将 80 端口转发到 kong 服务的 8000 端口,如果 kong 是通过 docker 运行,则有端口映射 0.0.0.0:80->8000/tcp, :::80->8000/tcp, 0.0.0.0:443->8443/tcp, :::443->8443/tcp
    • Next Image 以 width 为主,height 是等比例缩放的,高度值其实可以是任意值的,完全不会影响 width。

20230523

  • 经验
    • 扇形进度条:Filling circle sector in CSS

      • 原理是,渐变函数相当于画一个圆,这个圆一半为红色,一半为透明色;两个渐变函数就是画两个这样的圆叠起来,透明重叠的部分即为小扇形,红色重叠部分即为大扇形。面积大的扇形用渐变函数涂,面积小的扇形用背景色。

      • 代码如下

        if (radius <= 180) {
            return (
              <div
                className="h-5 w-5 rounded-full border-2 border-solid border-[#6fba2c] bg-[#6fba2c]"
                style={{
                  backgroundImage: `linear-gradient(${90}deg, #fff 50%, transparent 50%), linear-gradient(${
                    radius - 90
                  }deg, #fff 50%, transparent 50%)`,
                }}
              ></div>
            )
          } else {
            return (
              <div
                className="h-5 w-5 rounded-full border-2 border-solid border-[#6fba2c] bg-white"
                style={{
                  backgroundImage: `linear-gradient(${-90}deg, #6fba2c 50%, transparent 50%), linear-gradient(${
                    (radius + 90) % 360
                  }deg, #6fba2c 50%, transparent 50%)`,
                }}
              ></div>
            )
          }
        
    • Spring 中文文档

    • Closing a stale SSH connection

20230522

  • 经验
    • How to Use TeamViewer Remote
    • 小课堂:
      • 举个例子,给你 1000块钱,100一张 ,10张,这10张100的,有没区别?从价值角度来说,没区别,因为都是100的,这个就是同质化的;从防伪角度来说,是不是一样的,不是,每个100的纸币,都有防伪码,唯一不可篡改的编码,这个就是非同质化。明白了吗?非同质化代币,对于区块链网络来说,就是在区块链上,不同编号的币,你先这么理解。
      • RAW,real assets wrod ,他的目的是引入真实世界的资产到虚拟世界。在股票市有一个名词:资产证券化,和这个比较接近。例如你有一个价值100w的房子,你抵押给银行,银行给你 80万人民币。这个就可以理解为你把真的房子的资产映射到了金融世界。因为从物理角度来说,房子和钱币来说属于两个世界,raw 在区块链世界,就是你抵押你的房子,给你虚拟世界的资产,例如比特币。抵押给银行了,这个动作发生在真实世界,在虚拟世界就不能抵押,虚拟世界也没有真实世界的房子。但是你抵押给银行了,可以在虚拟世界,给你虚拟世界的账户,打一笔钱,这样你房子的价值,是不是映射到虚拟世界了。明白了吗?记住是价值映射,raw 本质就是价值传导

20230521

  • 经验
    • 负责淘宝业务前端开发9年,聊聊我的心得:第一步做好本职工作——既快又好(保质保量)的完成业务需求,并尝试做些改变(纯粹造个轮子、换个理念重新实现轮子、尝试前端领域一些新特性或者新框架)。
    • 软件开发团队如何高质量、高效率?:软件开发要达到高质量、高效率,就需要在代码可读性、可维护性和可扩展性等多方面都达到较好的水平才可以。“代码编程”是软件开发中最关键的环节。首先要保证质量,才能提升进度。遵照“权限编程”的实践思路来进行培养高水平的开发人员(其实就是大佬轮流带队,向大佬学习)
    • 技术的广度与深度:我提这个问题,目标是提升自己在就业大军中的竞争优势
      • 全能程序员 vs 特长程序员:“绝大多数成熟的程序员都专攻某一个技术栈,因为这样更容易找到工作。 一些专家甚至认为,在不同的技术栈中工作是简历的污点。"(以公司角度)
      • 知识广度 vs 知识深度:知识的广度能告诉你什么是正确的方向,知识的深度则可以让你在该方向上快速前进。雇主愿意为知识深度买单。因为你克服了技术难题,才有机会来领导团队。(以各自优势为角度)
      • 技术的深度与广度,该如何取舍?:取决于你的屁股坐在哪一边。如果你的职业规划是在企业里步步高升,那么专注某个领域,并且努力成为那个领域的专家,会更符合企业对人才的要求。而如果你的志向在于体制之外,则各方面都懂一点,眼观六路耳听八方,时刻保持警惕,才是在残酷的大自然中繁衍生息的不二法则。(以人的不同位置为角度)
      • 对于一个领域,深度重要还是广度重要?(可以拿自己所在领域举例,为后来人提供一些经验)?:初期的广度重要,“选择大于努力”;中期的深度重要,“高端人才永远短缺,初级人才永远内卷”;成熟期又变成广度重要。(以人发展的不同时期为角度)
      • 如何平衡知识结构广度与深度?:知识的广度往往并不会产生直接的自我增值,知识广度本身是附着在某一个专业深度上的。(以学习的先后顺序为角度,先深后广)

20230520

  • 经验
    • 对于服务端来说,params 是动态路由,定义如 /test/:key,如果实际 url 为 /test/value,则 params 对象为 { key: value },query 是查询参数,url 为 /test?key1=value1&test2=value2,则 query 对象为 { key1: value1, key2: value2 };对于客户端来说,查询参数就是 params,即 url ? 后面的部分。

    • next.js 下 css modules 选择器 .privacypolicy > p:first-child 不起作用,经过测试,我的写法是有效的,最后解决办法是不使用伪类,用 .privacypolicy > nav

    • How can I get (query string) parameters from the URL in Next.js?

      const router = useRouter()
      console.log(router.query);
      

20230519

20230518

20230517

20230516

20230515

纪念左耳朵耗子

20230512

20230511

  • 经验
    • margin: 0 auto; 水平居中条件
      • 父元素有剩余空间
      • 目标子元素为块级元素

20230510

20230509

20230508

  • 经验
    • Chatfo 分享到微信:在桌面浏览器与不支持的手机浏览器中将展示二维码;在支持的手机浏览器上利用原生分享组件直接跳转:

20230506

20230505

20230428

20230427

  • 经验
    • Vue深层级过渡与显式过渡时长:要在在深层级的元素上触发过渡效果,关键在于传入 duration prop 来显式指定过渡。
    • 通过项目驱动来学习技术,这里的项目不是指学习者从零开始自己写一个项目,对于新人来说,这太难了,而且很容易走错方向,这个项目指的是现有的需求类似的项目,学习者这参照这个项目从头来实现,在实现的过程中,学习其写法、添加自己的想法等,最终实现自己的需求、学会这套技术栈。
    • PlayStation®4用户指南

20230426

  • 经验
    • How can I accept all current changes in VSCode at once?: Its very easy just go to vs code and press Ctrl+shift+p (command palette) or go to view and open command palette manually and type “merge” in your command palette, now you can see the Accept all current changes.

20230425

20230424

20230423

  • 经验
    • 写后端 api 不仅是后端的事情,前端也需要根据自己的需要来向后端提供需求,帮助其完善 api。前端最好再自己画 UI 之前,把需要的 api 列表交给后端,这样前端 UI 与后端 api 可以同步进行。

20230421

  • 经验
    • 公司自动化流程是:GitHub =webhook=> Jenkins =ssh=> Shell 脚本 => Kong 服务。一般先写脚本,脚本是核心,当脚本能够正确运行了,再添加到 Jenkins,配置 kong 服务的等。

20230420

20230419

  • 经验

    • 部署:正向合并 main => test => dev,反向合并 dev => test => main。

    • Github PAT

    • 可以通过爱思助手将音乐导入 iphone apple music 中,如果提示有重复文件,就选择覆盖,否则可能出现音乐图片与音乐不匹配的情况,虽然我确实是删除了的。

    • 待定的地方用 ### 填位置,也方便搜索。

    • 如果页面分别写了手机端与电脑端的页面,且页面差异较大,在客户端用媒体查询切换非常不自然,那么可以通过 Next Middleware 实现在服务端切换 UI,如:

      // middleware.ts
      import type { NextRequest } from 'next/server'
      import { NextResponse } from 'next/server'
      import { UAParser } from 'ua-parser-js'
      
      // This function can be marked `async` if using `await` inside
      export function middleware(request: NextRequest) {
        const locale = request.nextUrl.locale
        const ua = request.headers.get('user-agent')
      
        const uap = UAParser(ua || '')
        const type = uap.device.type
        if (type && type.toLowerCase() === 'mobile') {
          if (!locale || locale === 'en' || locale === 'default') {
            return NextResponse.redirect(new URL('/mobile', request.url))
          }
      
          return NextResponse.redirect(new URL(`/${locale}/mobile`, request.url))
        }
      
        return NextResponse.next()
      }
      
      // See "Matching Paths" below to learn more
      export const config = {
        matcher: '/',
      }
      
    • 密码学系列之:bcrypt加密算法详解

    • 彻底搞清楚JS的模块化import/export/require…问题

    • 大部分 require 改写 import,需安装 @types/bcrypt

    // require 写法
    const bcrypt = require('bcrypt')
    
    // 对应的 import 写法
    import * as bcrypt from 'bcrypt'
    

20230418

  • 经验
    • 写 api 的时候,要想着,如果要换一个产品,那么当下产品哪些东西可以轻松地复用,而无关的东西可以轻松的删除掉,即,要把所有功能解耦合,这样才能在各个产品中方便地复用各种功能。

    • 不论是写 api 还是看 api,都是通过 client 请求到 server 处理的流程来进行的。在 nest 中,就是先写 controller,在写 service,然后再不断完善相关的东西。比如写用户登录api也是如此,先添在 controller 中添加 register 的路由,在 service 中实现 register,先只考虑操作数据库,然后加上参数验证、验证码之类的东西。总之,先从简单的开始写起来,然后再在基础上不断添加东西。

    • nest.js grpc 遇到一个天坑

      // 写法一:这样写无法调用 grcp 服务
      await this.grpcMailService.sendMail({ from: '', to: captchaDto.email, ...this.getMailContent(code) })
      return this.redis.set(key, code, 'EX', 3 * 60)
      
      // 写法二:这样写可以调用对应的 grpc 服务
      await this.redis.set(key, code, 'EX', 3 * 60)
      return this.grpcMailService.sendMail({ from: '', to: captchaDto.email, ...this.getMailContent(code) })
      

      参照 How to get non-Observable response when calling service from gRPC client?,原因是 sendMail 定义的返回类型是 Promise<SendMailResponse>,实际的返回类型是 Observable,如果按照写法一来写,那么就不会等待 sendMail 执行完成就直接返回了。下面这样写就没问题了。

      const result = firstValueFrom(
        this.grpcMailService.sendMail({
          from: '',
          to: captchaDto.email,
          ...this.getMailContent(code),
        }) as unknown as Observable<unknown>,
      )
      await result
      

      这也表明 js 根本不知道数据的实际类型,也不回去检查。

20230417

  • 经验
    • Lingui.js 在 React Component 之外使用翻译

      • Question: Can I use Translation outside react component ?: The t function uses the React context to know the available language and namespaces to use during the translation. Then, you can use the t function outside components as long as you pass the t function(这位其实已经给了思路,但是在不同项目中,细节是不同的。这里的 t 在我司项目里其实指的是 const { i18n } = useLingui() 中的 i18n)

      • Localizing your app

      • 最终结果

        
        import { I18n } from '@lingui/core'
        import { t } from '@lingui/macro'
        
        export const translate = (i18n: I18n) => {
          const result = i18n._(t`The Buddha said, AI intelligent platform.`)
          console.log(result)
          return result
        }
        
    • 部署的时候,如果是网页应用,则只需要部署 test 与 prod,dev 相当前端本地起的服务,网页应用两个端口交替使用。如果是后端 api,则是需要部署 dev、test 与 prod,dev 用于前端本地起的服务使用,api 只需要一个端口。

    • 花了三天时间终于搞懂 Docker 网络了

20230414

  • 经验

    • 问题:本地开发即使开了代理也无法访问 openai api,但是服务器可以正常访问

      • chatbot 服务:建立一个 chatbot 服务,部署在服务器端,处理 openai api 相关的请求(唐伟现在使用的)

        • 搭建 OpenAI 代理(成功的解决方案): Express + http-proxy-middleware 进行反向代理
      • 代理 TinyProxy(无效)

        • Unauthorized connection:在于 Allow host,要么注释允许所有,要么正确设置

        • Node.js behind a proxy

          npm config set proxy http://10.2.0.10:8888
          npm config set https-proxy http://10.2.0.10:8888
          
        • axios proxy

          axios.post(url, data, {
            proxy: {
                host: "127.0.0.1",
                port: 7890,
                protocol: "http"
            }
          })
          
        • 运行命令

          docker run --rm --name tinyproxy -p 8888:8888  -e "ALLOWED=10.2.0.9" ajoergensen/tinyproxy
          curl --proxy 10.2.0.10:8888 -k https://www.google.com/
          
      • Remote Development using SSH

    • Docker compose global level logging

      • 配置 Dockerfile

        • YAML anchor

          version: "2"
          
          services:
          
            proxy:
              build: proxy
              image: kinoulink/proxy
              ports:
                - 80:80
                - 443:443
              volumes:
                - /var/run/docker.sock:/var/run/docker.sock:ro
              container_name: ktv_manager_proxy
              environment:
                  - HTTP_AUTH_PASSWORD=$KTV_MANAGER_PASSWORD
              logging: &logging
                driver: "awslogs"
                options:
                awslogs-region: eu-west-1
                awslogs-group: docker
          
            rancher:
              image: rancher/server:v1.1.3
              volumes:
                - rancher_mysql:/var/lib/mysql
                - rancher_cattle:/var/lib/cattle
              labels:
                ktv.infra.proxy.domain: 'rancher'
                ktv.infra.proxy.port: '8080'
              logging:
                <<: *logging
          
        • Extension field

          version: "3.4"
          
          x-logging:
                &default-logging
                driver: "awslogs"
                options:
                awslogs-region: eu-west-1
                awslogs-group: docker
          
          services:
          proxy:
              build: proxy
              image: kinoulink/proxy
              ports:
                - 80:80
                - 443:443
              volumes:
                - /var/run/docker.sock:/var/run/docker.sock:ro
              container_name: ktv_manager_proxy
              environment:
                  - HTTP_AUTH_PASSWORD=$KTV_MANAGER_PASSWORD
              logging: *default-logging
          
            rancher:
              image: rancher/server:v1.1.3
              volumes:
                - rancher_mysql:/var/lib/mysql
                - rancher_cattle:/var/lib/cattle
              labels:
                ktv.infra.proxy.domain: 'rancher'
                ktv.infra.proxy.port: '8080'
              logging: *default-logging
          
      • 配置 Daemon configuration file

        cat <<EOF > /etc/docker/daemon.json
        {
          "log-driver": "json-file",
          "log-opts": {
            "max-size": "10m",
            "max-file": "3"
          }
        }
        EOF
        systemctl restart docker
        
      • 其他

        # 在重启 docker 前记住哪些容器是启动的,重启后对比一下(唐伟)
        docker container ls | awk '{print $1, $2}' > ~/containers
        # 在配置好后通过如下命令验证
        docker inspect 89c551bd7ca0 | grep -i LogConfig -A 5
        
    • openai api stream

      • How to use stream: true?

        try {
            const res = await openai.createCompletion({
                model: "text-davinci-002",
                prompt: "It was the best of times",
                max_tokens: 100,
                temperature: 0,
                stream: true,
            }, { responseType: 'stream' });
        
            res.data.on('data', data => {
                const lines = data.toString().split('\n').filter(line => line.trim() !== '');
                for (const line of lines) {
                    const message = line.replace(/^data: /, '');
                    if (message === '[DONE]') {
                        return; // Stream finished
                    }
                    try {
                        const parsed = JSON.parse(message);
                        console.log(parsed.choices[0].text);
                    } catch(error) {
                        console.error('Could not JSON parse stream message', message, error);
                    }
                }
            });
        } catch (error) {
            if (error.response?.status) {
                console.error(error.response.status, error.message);
                error.response.data.on('data', data => {
                    const message = data.toString();
                    try {
                        const parsed = JSON.parse(message);
                        console.error('An error occurred during OpenAI request: ', parsed);
                    } catch(error) {
                        console.error('An error occurred during OpenAI request: ', message);
                    }
                });
            } else {
                console.error('An error occurred during OpenAI request', error);
            }
        }
        
      • HTTP Stream using Axios (Node JS)

        const response = await axios.get('https://stream.example.com', {
            headers: {Authorization: `Bearer ${token}`,
            responseType: 'stream'
        });
        
        const stream = response.data;
        
        stream.on('data', data => {
            console.log(data);
        });
        
        stream.on('end', () => {
            console.log("stream done");
        });
        
    • Nestjs Docs

      • Configuration:管理自定义配置
      • HTTP module:用的 axiosInstance,返回的是 Observable
      • Cookies:在服务端,需要通过多语言调用 chatgpt api,那么可以通过 cookies 获取客户端选择的语言,客户端应该尽量将语言存储到 cookies 中。
      • Prisma:数据库
      • Redis:缓存
      • RabbitMQ:消息队列
      • Authentication:使用 JWT 作认证
        • Passport

        • Validation:Dto 添加参数验证,支持类型自动转换

        • Exception filters:过滤 Exception

        • gRPC:通过 grpc 调用 mail microserver。

          • Assets:通过在 nest-cli.json 添加如下配置,自动将 .proto 文件复制到 dist 目录下

            {
              "compilerOptions": {
                "assets": [{ "include": "**/*.proto", "outDir": "dist/src" }],
                "watchAssets": false
              }
            }
            
        • Rate Limiting:限流

        • Guards

          • JwtGuard 或者 RoleGuard。
          • 注意 SetMetadata 只作用于特定的请求,不能作用于整个的 controller。
      • Interceptors:用于自定定义返回格式,如 { code: 1, message: 'ok', data }
      • Swagger
        • 自动生成 api 文档,方便测试 api,可替代 postman
        • 使用 DTO 和 @ApiProperty();如果是可选参数,参考 Automatically detect optional @Query,应该使用 swagger 的 @ApiPropertyOptional() 而非 class-validator 的 @IsOptional()
        • Bearer authentication:在 swagger 中测试需要认证的 api
      • CORS:允许特定域名跨域资源共享,设置在跨域下哪些 headers、methods 可以使用,详细配置查阅 Configuration Options
      • File upload:文件上传。如果包含多个字段,只能用 FileFieldsInterceptor。
    • 前端js如何根据文字识别语种?

    • 文本内容相似度计算方法:simhash

    • redis可以多key对应一个value吗?:以手机号可以直接查询用户数据(mobile->hash),如使用身份证的话,先找出对应的手机号(id->mobile),再根据手机号取hash数据;

  • Prisma:The table (not available) does not exist in the current database:执行package.json中如下命令

    "generate": "prisma format && prisma generate",
    "dbpush": "yarn generate && prisma db push"
    

20230413

  • 经验
    • 后续技术组的所有docker文件,都加上log的最大限制

      logging:
        options:
          max-size: 10m 
      

20230412

  • 经验
    • 清除 Mac DNS 缓存sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder

    • lark 基于现有表格建立新表格:右键表格tab栏,选择 dulicate,然后在新表格中最左侧勾选选择全部checkbox,删除即可。

    • 多语言不能存储在 state 中,否则更新不及时,如果要为多语言文本设置相关状态,可以用数组下标来对应

      const { prompts } = usePrompts() // 多语言内容
      const initSelectedPrompts = prompts.map(() => ({ selected: false })) // 对应的状态
      const [selectedPrompts, setSelectedPrompts] = useState(initSelectedPrompts) // 对应的状态用于 state
      
      selectedPrompts
        .map((item, index) => ({ prompt: prompts[index], selected: item.selected }))
        .filter((item) => item.selected)
        .map((item) => item.prompt)
        .join(', ') // 处理
      
    • 138 张图带你 MySQL 入门

20230411

20230410

20230407

20230406

20230405

  • 经验
    • 后端开发除了增删改查还有什么?:使用某种技术的目的,是为了解决问题、满足需求。对于平凡公司的平凡工人,我觉得提升自我的一个方法就是自己创造需求,只有喜欢钻研折腾的人,才能深挖技术。关键是你要有兴趣,去做了,才会遇到问题,去查了,才会学习成长。
    • 重学 TS
    • 一个项目并不是独立的,而是各种库、框架有机结合而成的。项目学习法,是以解决某个问题、满足某个需求为导向的,而不是以学会某个库、框架为导向的。因此,项目学习法,首先是去看能解决该问题、满足该需求的现有的项目;其次,是使其运行起来,有个直观感受;然后在阅读代码过程中,不断去学习相关的知识,使自己能够读懂代码;最后是,动手写一写代码,使其满足自己的具体需求、解决具体问题。(纯粹地阅读学习效果很不好,不仅没有学会、用不起来,而且还很容易枯燥,导致削减了学习兴趣,打击了学习热情)
    • 独立博客:JSPang,技术胖
    • 二十行代码,搞懂 Observable
    • How do you grep a file and get the next 5 lines: grep -A 5 '19:55' file
    • Hard reset of a single file:
      • git checkout HEAD -- my-file.txt
      • The special “option” – means “treat every argument after this point as a file name, no matter what it looks like.” This is not Git-specific, it’s a general Unix command line convention. e.g. rm -- -f deletes a file named “-f”.ref
      • 06.Git从放弃到入门: 命令checkout图解: git checkout -- ./ 回滚整个项目到最近一次的暂存或提交状态。

20230404

  • 任务
    • 解决 chatfo 头像缩略图:公司使用 node:16 作为基础 docker 镜像,要求在该 docker 中可以方便使用的方案。
      • webp-converter
      • gm:参考nodejs使用graphicsmagick生成缩略图
        • 优点:使用业界知名的处理库,功能比较全
        • 缺点:额外依赖 linux 包 ImageMagick 或 GraphicsMagick
        • 替代:
          • 同样知名的 sharp ,自带编译好了的 libvips,next.js 同样推荐使用 Sharp Missing In Production

            let useThumb = thumbPrefer
            let thumbUrl = ''
            if (thumbPrefer) {
              try {
                const thumbFile = path.join(uploadPath, `${fileNameWithoutExt}_thumb.${ext}`)
                const img = sharp(fileFullPath)
                const { width, height } = await img.metadata()
                const thumbSize = this.configService.get<number>('upload.thumbSize')
                if (width > thumbSize || height > thumbSize) {
                  if (width >= height) {
                    await img.resize({ width: thumbSize }).toFile(thumbFile)
                  } else {
                    await img.resize({ height: thumbSize }).toFile(thumbFile)
                  }
                } else {
                  useThumb = false
                }
                thumbUrl = new URL(thumbFile.replace(uploadConfig.rootPath, ''), uploadConfig.staticUrl).href
              } catch (e) {
                console.log('Sharp: ', e)
              }
            }
            
          • node-canvascanvas 也可以生成缩略图

      • imaginary: image processing microservice。
  • 经验

20230403

  • 经验

    • 前端组件讲究复用,但 api 讲究调用次数越少越好,能一次返回就一次性返回。

    • How to Fix “localStorage is not defined” in Next.js: Next.js performs a server render before the client render.

      if (typeof window !== 'undefined') {
        // Perform localStorage action
        const item = localStorage.getItem('key')
      }
      
    • 你真的知道 Cookie 吗? SameSite 、 Secure 、 HttpOnly

    • …expression of type string cannot be used to index…

      const someObj:ObjectType = data;
      const field = 'username';
      
      // This gives an error
      const temp = someObj[field];
      
      // Solution 1: When the type of the object is known
      const temp = someObj[field as keyof ObjectType]
      
      // Solution 2: When the type of the object is not known
      const temp = someObj[field as keyof typeof someObj]
      
    • js判断当前系统语言、浏览器语言

      • 需求是:

        • 分享的时候,根据 url 链接中语言参数来选择语言;(通过 router.locale 来判断)
        • 初次使用自动加载浏览器默认语言;可以通过语言选择按钮切换语言。(通过 cookie 来判断)
        • 总结:路由中的 locale > cookie 中的语言选择 > 浏览器语言偏好 > 默认英文
      • 解决方案:

        useEffect(() => {
          async function load(locale: string) {
            // @ts-ignore TYPE NEEDS FIXING
            i18n.loadLocaleData(locale, { plurals: plurals[locale.split['_'](0)] })
        
            const { messages } = await import(`@lingui/loader!./../../locales/${locale}.json?raw-lingui`)
            i18n.load(locale, messages)
            console.log(locale)
            i18n.activate(locale)
          }
        
          const LANG_TO_LANG = {
            'zh-CN': 'zh_CN',
            'zh-TW': 'zh_TW',
            ja: 'ja',
          }
        
          const defaultLanguage = LANG_TO_LANG[navigator.language as keyof typeof LANG_TO_LANG]
        
          // 路由中的 locale 参数
          if (locale) {
            const localeCache = Cookies.get('NEXT_LOCALE')
            if (Object.values(LANG_TO_LANG).includes(locale)) {
              if (!localeCache || localeCache !== locale) {
                Cookies.set('NEXT_LOCALE', locale, {
                  sameSite: 'lax',
                })
              }
              load(locale)
            } else {
              // cookie 中的 locale 设定
              if (localeCache === 'en') {
                load('en')
              } else {
                // 浏览器中的语言偏好
                if (defaultLanguage) {
                  router.replace(router.asPath, undefined, { locale: defaultLanguage })
                } else {
                  // 默认语言
                  load('en')
                }
              }
            }
          } else {
            load('en')
          }
        }, [locale, router])
        
  • js-cookie: A simple, lightweight JavaScript API for handling cookies

20230401

20230331

20230330

  • 任务
    • 在手机上调试(访问 macbook 上开启的 node 服务)

      • 手机访问,加 http,因为默认是 https,可能访问不了
      • 打开关闭飞行模式
      • 关闭移动网络
    • 课堂:

      • 域名 => DNS解析 => IP(服务器)

        • 域名(web) => Kong Router (Path 规则 /) => Service (web) => Host:Port
        • 域名(api) => Kong Router (Path 规则 /api/v1) => Service (api) => Host:Port
        • 细节:
          • Router 通过 Path 规则将不同域名请求分配给对应的 service 处理,而 service 对应实际的服务。

          • 主要关注 Service 的 Host 与 Port(容器宿主机 ip 与 port),Router 的 Hosts 与 Paths(域名与 path)

            • Router 需关闭 Strip path(默认开启),作用是将 /api/v1/hello 改为 /hello
            • Unable to add a Route to a Service:不是普通的输入框,输入内容后需按 Enter
          • 每一个正式发布都需要给源码打一个 tag(v1.0.0)

            # 列出标签
            git tag
            # 新增本地标签
            git tag -a v1.0.0 -m "v1.0.0"
            # 删除本地标签
            git tag -d <tag_name>
            # 推送标签到远程
            git push origin --tags
            # 列出远程标签
            git ls-remote --tags origin
            # 删除远程标签
            git push --delete origin tagname
            
      • 如果 svg 图标显示不全,可以尝试清除 id 属性和 <defs><mask> 元素,如果不能删除的考虑修改 id 属性名称,保持不冲突。实在不行,就使用其他图标替代掉。

      • 团队合作注意多沟通,编写页面时看看某个部分是否有在多个页面使用,考虑某一个人编写,其他人使用。

    • grep display filename before matching line: Try this little trick to coax grep into thinking it is dealing with multiple files, so that it displays the filename

      grep 'pattern' file /dev/null
      
    • “url” parameter is not allowed next.js:为了保护您的应用程序免受恶意用户的侵害,需要进行配置才能使用外部图像。如果用 domain 的话,就不能指定 pathname,对于在子路径中的图片,可能会报错。

      module.exports = {
        images: {
          remotePatterns: [
            {
              protocol: 'https',
              hostname: 'example.com',
              port: '',
              pathname: '/account123/**',
            },
          ],
        },
      }
      
    • 原创 | 微服务网关 Kong 科普

20230328

20230327

  • 经验
    • Attach Authorization header for all axios requests

      • Login 成功后,存储 token 在本地并设置默认 Heaer

        localStorage.setItem('token', `Bearer ${result.token}`)
        axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${result}`
        
      • 页面刷新后,需从本地重新取回 token

        axiosInstance.interceptors.request.use((config) => {
          if (localStorage.getItem('token')) {
            config.headers.Authorization = localStorage.getItem('token')
          }
          return config
        })          
        
    • 接口要考虑别人使用,不要只考虑自己用着方便。

      • 返回类型要写全,不要只写自己需要的部分。例如

        export type UserInfoType = {
          id: number
          email: string
          username: string
          createdAt: string
        }
        
      • 像这种信息可以存储在全局,而不是每次都需要获取。

    • React 滚动到底部加载数据

      function DataInfo() {
          const [dom, setDom] = useState(HTMLDivElement | null);
          const [logData, setLogData] = useState('');
      
          // 获取数据的方法
          function getLogPages () {
              ...
          }
      
          // 监听页面滚动
          const handleOnScroll = () => {
              if (dom) {
                  const contentScrollTop = dom.scrollTop; //滚动条距离顶部
                  const clientHeight = dom.clientHeight; //可视区域
                  const scrollHeight = dom.scrollHeight; //滚动条内容的总高度
                  if (contentScrollTop + clientHeight >= scrollHeight) {
                      getLogPages();    // 获取数据的方法
                  }
              }
          };
          return (
            <div  className="modal-body logHeight"
                  style={{ height: '480px' }}
                  ref={(dom) => {
                            setDom(dom);
                        }}
                  onScrollCapture={() => handleOnScroll()}
                  >
                  {logData}
            </div>    
        )
      }
      
    • React keyboard 事件

      const keyDownHandler = (e: React.KeyboardEvent<HTMLDivElement>) => {
        const key = e.code;
      }
      
    • How to create an array containing 1…N: Array.from(Array(10).keys())

    • react-simple-pull-to-refresh:下拉更新组件

    • CSS loaders and Spinners

  • ChatGPT: AI提示词玩家

20230325

  • 经验
    • du 命令计算隐藏文件夹或文件du -sh * .[^.]*

    • web查看当前是哪个元素触发了滚动

      function findscroller(element){
        element.onscroll = function () {
            console.log(element)
        }
        Array.from(element.children).forEach(findscroller)
      }
      findscroller(document.body)
      
    • getServerSideProps 函数:与 getStaticProps 只在build时生成一遍静态页面,或者定期重新生成页面不同,使用getServerSideProps,页面在每接收到一条传入请求就重新生成一遍。因为每次请求都是从数据库读数据重新生成页面,页面加载速度会变慢。但是使用getServerSideProps 可以访问 request 对象。如果页面不是需要每秒更新几次,或者不需要访问 request 对象,那么使用 getStaticProps 函数更好。getStaticProps 和 getServerSideProps 不能同时使用,参见 Use getStaticProps and getServerSideProps together

20230324

  • 经验
    • 记住,不要好逸恶劳,要生产自己的价值,要被人需要。
    • 小课堂:
      • 后端接口还没好,依旧要考虑接口,模拟接口返回数据,考虑全,后面可以直接改个链接。
      • 在 fetcher.js,不知道接口请求参数、返回数据类型,可以用对象,方便添加内容。
      • 付费的 copilot 与免费的 codeium:这个真的超赞,如果没人教的话,不知道怎么写,那么这个会给你一个范例。
    • 公司的部署流程:本地项目 push 到 Github => 触发 Github hooks => Jenkins 执行宿主机脚本 => 脚本拉取 Github 源码、build docker 镜像、运行镜像 => 其他(Kong服务、域名绑定等)。每一个 web 服务拥有两个端口,新的端口用起来后,停用旧端口,保证服务可用性。

20230323

20230322

  • 经验
    • 获取焦点时移除placeholder,失去焦点重新获得placeholder内容

      • css

        /*WebKit browsers*/
        input:focus::-webkit-input-placeholder { color:transparent; }
        
        /*Mozilla Firefox 4 to 18*/
        input:focus:-moz-placeholder { color:transparent; }
        
        /*Mozilla Firefox 19+*/
        input:focus::-moz-placeholder { color:transparent; }
        
        /*Internet Explorer 10+*/
        input:focus:-ms-input-placeholder { color:transparent; }
        
    • lodash.shuffle:将数组打乱顺序

    • Next SEO - Title Template

      • 在 seo.ts:{ titleTemplate = 'Next SEO | %s'; }
      • 在某个页面:<NextSeo title={i18n._(tThis is my title)} />

20230321

  • 经验
    • md: 作为 Desktop 与 Mobile 的短点比 sm: 要好。

    • Viewport meta tags should not be used in _document.js's <Head>

      • Set your viewport meta tag in pages/_app.js

      • Next.js 暴露了一个内置组件 Head,用于将 HTML 标签添加到页面的 head 中。

        <Head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        </Head>
        
    • 禁止 IOS 移动端缩放

      const lastTouchEnd = useRef(0)
      useEffect(() => {
      
        // 双指缩放
        const handleTouchstart = (event: TouchEvent) => {
          if (event.touches.length > 1) {
            event.preventDefault()
          }
        }
      
        // 双击缩放
        const handleTouchend = (event: TouchEvent) => {
          var now = Date.now()
          if (now - lastTouchEnd.current <= 300) {
            event.preventDefault()
          }
          lastTouchEnd.current = now
        }
      
        document.documentElement.addEventListener('touchstart', handleTouchstart, { passive: false })
        document.documentElement.addEventListener('touchend', handleTouchend, false)
      
        return () => {
          document.documentElement.removeEventListener('touchstart', handleTouchstart, false)
          document.documentElement.removeEventListener('touchend', handleTouchend, false)
        }
      }, [])
      

20230320

  • 经验
    • Apple Cannot Check It for Malicious Software:在 Finder 中右键打开就行。

    • 首先考虑移动端怎么做,移动端优先;用 devTool 开手机模式,然后可以拖放检查。

    • daisyUI 可以选主题,可以配置主题,在 tailwindcss.config 中 daisyui 部分修改。

    • Tailwinds 有 vscode 插件 Tailwind CSS IntelliSense。可以看到像素对应的 tailwind css 类,选择最近似的就行。

    • Dropdown CSS 代码是看的懂得吧!

      <style>
      .dropdown {
        position: relative;
        display: inline-block;
      }
      .dropdown-content {
        display: none;
        position: absolute;
        background-color: #f9f9f9;
        min-width: 160px;
        box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
        padding: 12px 16px;
      }
      .dropdown:hover .dropdown-content {
        display: block;
      }
      </style>
      <div class="dropdown">
        <span>鼠标移动到我这!</span>
        <div class="dropdown-content">
          <p>菜鸟教程</p>
          <p>www.runoob.com</p>
        </div>
      </div>
      

20230318

  • 任务
  • 经验
    • 开发规范

      • git 使用技巧
        • git rebase 合并 commit,p 选择该 commit,s 向上合并,注意只能 rebase 没有 push 的 commit;
        • git commit —amend 修改 commit message;
        • git add -p 进入 patch mode ,让我们可以挑选一部分改动进行提交。
      • 对开发的新功能,则命名为 feat-[功能名称];对修复的bug,则命名为 fix-[bug描述];命名时使用 - 减号来分割单词。
      • 从dev上开新分支,完成后,合并dev分支到功能分支上,解决冲突(如果有)后提交PR到dev上。
      • 在框架自动生成的多语言内容时,如果对应语言没有翻译内容,则不提交该文件。如果需要添加翻译内容,则运行yarn lingui让程序重新补充未翻译的内容。
      • 网站内的路由跳转,使用链接(Next Link 标签)的方式跳转,而非 onClick 和 router.push,利于 SEO。站外链接统一用 “_blank” 方式打开。
      • 可以使用 yarn lingui 生产对应的条目,git push 时需删除没有翻译的条目。
    • headlessui 写的手机端菜单侧滑动画,关键在于清楚 CSS 的 transition 和 transform,不要被 tailwindcss 迷惑了。

      <Transition show={isShow} as={Fragment}>
        <div className={classNames('fixed left-0 top-0 h-full w-full', maskClass)} onClick={onMaskClick}>
          <Transition.Child
            className={classNames('relative h-full w-1/2 bg-white', menuClass)}
            style={{ marginLeft: reverse ? 'auto' : '0' }}
            enter="transition ease-out duration-100"
            enterFrom={reverse ? 'translate-x-full' : '-translate-x-full'}
            enterTo="translate-x-0"
            leave="transition ease-in duration-90"
            leaveFrom="translate-x-0"
            leaveTo={reverse ? 'translate-x-full' : '-translate-x-full'}
            as="div"
            onClick={handleClick}
          >
            {children}
          </Transition.Child>
        </div>
      </Transition>
      
    • 监听 window 的 resize 事件,如果调整窗口大于1280 (preWidth < 1280 && width >= 1280) 或小于 1280 (preWidth > 1280 && width <= 1280),则 reload 一次页面。

      const { width } = useWindowSize()
      const preWidth = usePrevious(width)
      if (preWidth && width) {
        if ((preWidth > 1280 && width <= 1280) || (preWidth < 1280 && width >= 1280)) {
          window.location.reload()
        }
      }
      
    • next/image 要求固定的 width 和 height,可通过一下方式实现 next/image 响应式改变大小

        <div className="relative w-16 h-16 shrink-0">
          <Image src={avatar || DEFAULT_AVATAR} alt="avatar" fill className="object-cover rounded-full" />
        </div>
      
    • 在 React 中,不再 useEffect 中清除 timerID,通过 useRef 实现。

      const timer = useRef<NodeJS.Timeout>()
      timer.current = setTimeout(() => {}, delay * 1000);
      clearTimeout(timer.current as number | undefined)
      
    • Material Design 指南中文版

    • Prevent scroll when modal is open

      useEffect(() => {
        if (isShow) {
          document.body.style.overflow = 'hidden'
          return () => {
            document.body.style.overflow = 'auto'
          }
        }
      }, [isShow])
      

20230317

  • 经验
    • 有问题,一定要说出来,就不是自己的锅了,而是大家的。
    • 产品问题找产品负责人,技术问题找技术负责人,但最终所有问题都属于产品问题,由产品负责人负责。
    • 所有问题(UI、产品、测试)都汇总在一个表格之中,解决后@负责人。
    • 编写代码后,本地测试通过,自测通过后合并需再测试,总之,在所有问题解决之前,不要说完成了,不要部署到正式服务器。

20230316

20230315

  • 经验
    • 要在 Dropdown menu 中使用 Next.js 的 Link 标签,需要将 Link 标签放在最顶层,并且参考 Closing menus manually 手动关闭。

      <Menu>
        <Menu.Button>Menu</Menu.Button>
      
        <Menu.Items>
          {menuItems.map((menuItem) => (
            <Menu.Item key={menuItem.name}>
              {({ close }) => ( // get close function
                <Link href={menuItem.href} legacyBehavior>
                  <a>
                    // Component
                    <div onClick={close}>{menuItem.name}</div>
                  </a>
                </Link>
              )}
            </Menu.Item>
          ))}
        </Menu.Items>
      </Menu>
      
    • Using different API url for development and production with React and axios:利用 .env.development.env.production 文件来自动切换生产环境与开发环境的 API 的域名:

      const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT
      

20230314

  • 经验
    • next/router:路由

      const router = useRouter()
      router.push(url)
      
    • 项目所有 url 都放在 src/config/share_url

    • Lark 日历使用文档

    • next-seo:每一个 Page 都需要 <NextSeo title="desc" />,至少要有 title 属性,用于 SEO,具体位置不限——不要求放在最顶层,因为最后其会生成在 head 标签中作为 meta 元素存在。

    • Next Link 链接写法:

      <Link legacyBehavior key={item.key} href={item.href}>
        <a className="underline" target="_blank">{item.text}</a>
      </Link>
      

      我偏向使用原生的 a 标签,没懂这样写是干嘛的。写法具体参考 next/link - If the child is a functional component,也就是如果你想使用 Link 组件,必须遵守对应的语法规则。

      采用这种写法,可用通过 DevTool 来检查是否生效,可以看到其渲染为普通的带 href 属性的 a 元素,具体缘由参考 Next.JS “Link” vs “router.push()” vs “a” tag——即 Link 生成 a 元素并且不 reload page,单独用 a 元素会 reload page。

    • How to deploy a react app to a subdirectory: 今天张新勇老大遇到个问题,需要将 React App 到服务器 register 子目录,但是一直报错,因为其访问的资源指向根目录,解决办法需要将所有资源引用指向 register 这个子目录,解决方法如下:In package.json add homepage like below

      {
      "homepage": "https://domain-name.com/directory-name",
      
      //OR
      
      "homepage": "/directory-name",
      }
      

      但问题并没有结束,其结果能让静态资源的引用指向 /directory-name 目录(index.html 中引用),但并没有解决超链接跳转问题。该项目使用 react 的类组件,使用 react-router-dom@5 来做路由。主要参考 React Router V4 but on a different homepage in package.json… fix 404 not found? 这篇文章

      • 方法一:除了要在 package.json 添加 homepage 属性外,还需要在 <BrowserRoter> 中添加 basename 属性,即<BrowserRoter basename="/directory-name",并且将所有 a 标签替换为 react-router-dom 的 Link 标签,因为其自动添加 basename。

      • 方法二:使用 <HashRouter> 来解决,除了不需要添加 basename 外,其他一样,然后可以如下方式写(马梦圆喜欢这样写😂)

        onClick={() => {
          window.location.href = "#/Privacy_Policy";
        }}
        

        HashRouter 作用参考 React-router URLs don’t work when refreshing or writing manually,即客户端生成的路由,如 <www.example.com/home,服务器是不接受的,利用> hash history,即 <www.example.com/#/home> 就可以绕过这个问题,因为总是请求根域名 <www.example.com。>

      总之,这些配置基于这样一个目的:在本地(开发环境)下可以直接运行,方便代码编写与调试;打包的时候自动处理从开发环境到生产环境的迁移,例如调整链接、精简代码之类的;打包后可以直接在服务器上部署运行。

    • 为了告诉开发环境的服务器去代理任何开发环境中未知的请求到我们自己的api服务器,添加一个proxy到package.json的字段,但在 next 中无效,需要使用下面的 next-http-proxy-middleware

20230313

20230311(🛌)

  • IBM LinuxONE Community CloudVirtual Server Deployment Guide

    成功注册了。很简单,不需要银行卡,就一个 icloud 邮箱,理由写的 “open source“。说是有 120 天的试用,不知道试用时间完了之后怎么说,反正如果邮箱足够的话,是不是有无线使用期限呢?反正我现在有个小鸡使用就行了,也没有什么复杂的需求,也有不了,因为用的 IBM s390x 架构。

    ssh linux1@148.100.79.64
    
  • How to Kill user sessions in Linux

    pkill -9 -t pts/2
    
  • Are IPv4 ports separated from IPv6 ports?:是的,在防火墙中需要分别为 IPv4 与 IPv6 设置端口与协议。但是要确定 VPS 的 IPv6 地址是公网地址,不是就没必要设置了。

  • How to curl using IPv6 address?:浏览器也是同样格式

    curl -g -6 'http://[fe80::3ad1:35ff:fe08:cd%eth0]:80/'
    

20230311

  • 经验
    • 知道一个工具能干什么的比知道该工具具体怎么用更重要,很多教程一上来就讲这样做、那样做就成了,但是没讲我为什么要这样做、那样做。

    • tailscale 是 VPN(虚拟私有网络),作用是让你可以在公开的网络线路上建立一个私有的子网,起连接设备作用。要翻墙需要配合 proxy server,作用是将客户端请求流量转发到目标网站或应用程序,然后将响应转发回客户端。

    • 先有现在,才有未来,先把公司任务完成了,才有机会去学习更多的知识。总之,学习知识和掌握技能不是目的,发挥自己的价值获取报酬才是目的。

    • 画 UI 的话,先把内容填上去,然后再逐渐地添加样式,最后添加行为(即先只写 HTML,再写 CSS,最后再写 JS)。

    • Change Visual Studio Code’s title bar color

      "workbench.colorCustomizations": {
          "[Name of the Current Theme you are using]": {
              "titleBar.activeBackground": "#191919cc",
              "titleBar.activeForeground":"#ffffff",
          },
      },
      "window.titleBarStyle": "custom"
      

20230310

20230309

20230308

  • 经验

    • Hardhat:帮助开发人员管理和自动化构建智能合约和dApp的过程中固有的重复任务。

    • iTerm2 相关问题

      • Dedicated Hotkey Windows:设置系统快捷键 Command + Return 在其他 Window 上打开一个 iTerm Session

      • iterm2 default window size

        You have to specify the default window size in rows and columns. The setting can be found in iTerm > Preferences > Profiles > Window. (160 x 35)

      • How to open a new tab in iTerm in the same folder as the one that is open

        Select “Reuse previous session’s directory” from the preferences of your profile.

      • How can I set an environment variable only for the duration of the script? VAR=value 是我想要的, env VAR=value 会 export 到当前环境之中。

        https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
        
      • ohmyzsh Git 镜像

        git clone https://mirrors.tuna.tsinghua.edu.cn/git/ohmyzsh.git
        cd ohmyzsh/tools
        REMOTE=https://mirrors.tuna.tsinghua.edu.cn/git/ohmyzsh.git sh install.sh
        
      • ohmyzsh plugin

        git clone --depth=1 git@github.com:zsh-users/zsh-autosuggestions.git $ZSH_CUSTOM/plugins/zsh-autosuggestions
        git clone --depth=1 git@github.com:zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting
        

        .zshrc

        plugins=(git macos zsh-autosuggestions zsh-syntax-highlighting)
        
  • 运维相关

    • 服务器:hp server、orcale server、google cloud、desktop、pi

    • 操作系统:Ubuntu

    • mysql/mongodb

    • redis cluster

    • elasticsearch

    • ipfs

    • ribbitmq 消息队列

    • koa/nest.js 服务

    • 管理:Portainer、Kong

    • ansible:批量系统配置、批量程序部署、批量运行命令等功能。

    • auditd:审计工具

    • cloud-init:云平台为Linux操作系统的虚拟机做系统初始化配置的开源服务软件。

    • tailscale P2P VPN 工具:

      • 安装配置:

        brew install --cask tailscale
        defaults write io.tailscale.ipn.macsys ControlURL http://104.199.128.36:8082
        
      • 运行tailscale,点击log in,获取类似headscale nodes register --user USERNAME --key nodekey:...命令,发给管理员让他加你进入。

        • 注意关闭系统代理。
        • 注意设置防火墙让 tailscale 通过。
        • 如果无网则取消勾选 Use Tailscale DNS Setting。(Mac dns 也在 resolve.conf 中看)
      • 最后与管理员沟通获取登陆具体服务器的信息,登陆用 VLAN IP。

      • 连接之后是可以与 CFW 一起使用的!

    • OneDev CI/CD 工具:配置仓库同步(已弃,彭梓旭不喜欢 yaml)

      • 获取 Github Person Token:就在个人设置的 Developer settings 中。
      • 在面板中 Projects 栏中,点击 Import 导入 Github 仓库,根据提示填入对应内容。
      • 查看 Commit and Push in Job配置 job secret,并为项目设置一个 checkout step(关键,否则不能 pull push),用 http(s) 认证。
      • 参考在《OneDev里配置仓库同步》:
        • 添加一个 pull(勾选 force) step(定义做什么),用于拉取 github 仓库。
        • 设定 Cron Tigger 的 trigger(定义何时运行)。(Bruch update 只适合 push,pull 是不会自动的,除非用 github webhooks
        • 编辑后查看 .onedev-buildspec.yml 源文件,添加到项目中并同步到 github。
        • 运行 Job 拉取 github 进行测试。
        • 解释:同时加 push 与 pull step 的并勾选 force (必须,否则不能拉取最新的内容)的话,如果先 push,则会将 github 所有更改删除,先 pull 则会将 onedev 所有修改删除(即 .onedev-buildspec.yml 文件),因此干脆只用一个。
      • 关键点在于 .onedev-buildspec.yml 这个文件。

20230307

  • 经验
    • Mac 与 Windows 传输文件。我尝试了 FileZilla 与 Samba,都没成功,很烦。尝试使用 Snapdrop,体验没有 Aridrop 好(只有 2 MB/s 左右),问题例如:不能刷新,不能开代理,传输大文件中断。最后通过 python3 -m http.server 起个 http server 解决,http 本来就是分享资源的,完全可行。这里有个误区,传输老想着用传输工具,而不是使用现有的能够传输的东西。这里也发现 Mac 默认不开防火墙。

    • Orcale 永久免费云服务:很难申请成功。

    • Docker 相关问题:

      • How do I start the docker daemon on macOS?

        • 我用的 Macbook air 是 arm(M1),安装不了 virtualbox 啊!
        • brew install --cask docker 安装,在桌面启动 docker.app,在 terminal 运行 docker 命令就行了。
      • Dockerfile RUN,CMD,ENTRYPOINT命令区别

      • Dockerfile 中的 COPY 与 ADD 命令:COPY 可用于 multistage 场景下,把前一阶段构建的产物拷贝到另一个镜像中;ADD 解压压缩文件并把它们添加到镜像中。

      • 常用命令:

        # 创建 image 文件
        docker image build -t freefrom -f Dockerfile.hardhat --platform linux/amd64 .
        
        # 查看镜像元数据,Layers 可看到镜像所有层
        docker image inspect hello-world
        
        # 新建容器,容器的 3000 端口映射到本机的 8000 端口。
        docker container run --rm -p 8000:3000 --name container-name -it koa-demo /bin/sh
        
        # 通过 docker-compose 新建容器
        docker-compose up --build -d
        
        # 查看容器 logs,不论是 running 的还是 stop 的
        docker contianer logs 69d1
        
        # 进入容器
        docker container exec -it 11fd20be74b6 sh
        
        # 清理 image、container、network、build cache
        docker system prune -af
        
        # 查看容器状态
        docker container stats b901a13001a0
        
        
        
      • Puppeteer 报错:Puppeteer is a Node.js library which provides a high-level API to control Chrome/Chromium over the DevTools Protocol.

        • 问题一:The chromium binary is not available for arm64

          因该是默认用当前系统架构,改架构用 --platform linux/amd64。 但是问题是成功创建了 linux/amd64 镜像后,在 M1 的 Mac 上也运行运行不了啊!

        • 问题二:ERROR: Failed to set up Chromium r800071!

          因为只是测试 node_modules layer,所以暂时通过 --ignore-scripts 跳过。

    • How to copy multiple files in one layer using a Dockerfile?

      COPY README.md package.json gulpfile.js __BUILD_NUMBER ./
      COPY dir1/* dir2/* ./
      
    • docker build 前端项目的话,每次都需要下载 node_modules,这也太耗费流量了吧。 解决

      FROM node:16
      
      COPY package.json /tmp/package.json
      COPY yarn.lock /tmp/yarn.lock
      RUN cd /tmp && yarn install --frozen-lockfile
      RUN mkdir -p /usr/app && cp -a /tmp/node_modules /usr/app
      
      WORKDIR /usr/app
      

      解析

      • 所有docker 只用在命令“没有变化”时才会用缓存。而且,一但某一条命令没能利用缓存,那么后续的所有命令都不会使用缓存。除了 COPY 跟 ADD,只要 Dockerfile 里的指令行没有变化就认为没有变化。对于 COPY 跟 ADD ,还要比较文件内容跟属性。所以,如果先 COPY 整个项目,那么大多数情况下会有变化(没变化就不需要重打镜像了),于是不能使用缓存。
      • docker image 是分层的 (layer) ,每一个命令增加一个新的 layer 。打包结束之后,在打包的机器上,dockerfile 每一条命令执行之后“镜像”都存在,只要把相应的 layer 取出来就可以了。dockerfile 指令的执行,其实是先拿到上一个指令生成的镜像,执行当前指令,再把结果保存成一个新的镜像。两次执行 dockerfile ,如果发现执行当前指令的镜像已经存在,就直接拿过来用了,而不会重新执行指令,这就是所谓“缓存”。

20230306

  • 经验:
    • OpenSea, the largest NFT marketplace

    • 帕依提提-人工智能数据集开放平台

    • UNPKG - three.js

    • 骨骼动画—从基础建模到Threejs渲染

        loader.load('./girl.fbx', function (fbx) {
                  fbx.scale.set(0.1, 0.1, 0.1);
                  scene.add(fbx);
                  loader.load('./Walking.fbx', function (anim) {
                      //创建了一个针对于该模型的混合器
                      mixer = new THREE.AnimationMixer(fbx);
                      // 动画片段
                      var clip = anim.animations[0]; 
                      // 使用混合器和动画片段创建一个动画播放器来播放
                      var action = mixer.clipAction(clip); 
                      action.setDuration(1);
                      action.play();
                  });
              });
        var clock = new THREE.Clock();
        function animate() {
              // 向浏览器发送请求我希望你稍后调用函数,默认是每秒调用60次
              requestAnimationFrame(animate)
      
              var time = clock.getDelta();
              if (mixer) {
                  mixer.update(time);
              }
        }
      

20230305(🛌)

  • three.js 关注点(笔记):
    • render(渲染器) 渲染 scene(场景) 和 camera(相机),scene 中添加 mesh(模型)和 light(光源),mesh 由 geometry(几何体) 和 material(材质) 构成。
    • OrbitControls 鼠标操作三维场景。
    • 参数widthSegments、heightSegments约束的是球面的精度,球体你可以理解为正多面体,就像圆一样是正多边形,当分割的边足够多的时候,正多边形就会无限接近于圆,球体同样的的道理。
    • 辅助三维坐标系AxisHelper,光源辅助对象SpotLightHelper、PointLightHelper、DirectionalLightHelper,骨骼辅助显示 SkeletonHelper。
    • 漫反射、镜面反射分别对应两个构造函数MeshLambertMaterial()、MeshPhongMaterial()。
    • 类型数组之所以有类型这个定语,就是因为每一个构造函数对应一种 number类型里面细分的数据类型,例如构造函数Int8Array()创建的数组每个元素占据的内存是8位。
    • 对于网格模型Mesh而言,几何体geometry由三个顶点为一组渲染出来的三角形组成。
    • 平行光漫反射简单数学模型:漫反射光的颜色 = 网格模型材质颜色值 x 光线颜色 x 光线入射角余弦值
    • 可以通过 .traverse() 递归遍历的算法去遍历Threejs一个模型对象的所有后代
    • 所谓本地坐标系或者说模型坐标系,就是模型对象相对模型的父对象而言。世界坐标系默认就是对Threejs整个场景Scene建立一个坐标系,一个模型相对世界坐标系的坐标值就是该模型对象所有父对象以及模型本身位置属性.position的叠加。
    • 纹理坐标:以图片左下角为坐标原点,右上角为坐标(1.0,1.0),图片上所有位置纵横坐标都介于0.0~1.0之间。纹理UV坐标和顶点位置坐标是一一对应关系。
    • 关键帧KeyframeTrack和剪辑AnimationClip两个API来完成关键帧动画,通过操作AnimationAction和混合器AnimationMixer两个API播放已有的帧动画数据。
    • 通过Bone类可以实例化一个骨关节对象,然后通过多个骨关节对象可以构成一个骨骼层级系统;通过Skeleton类可以把所有骨关节对象Bone包含进来;SkinnedMesh类的字面意思就是骨骼网格模型。
    • 导出模型信息:console.log(JSON.stringify(geometry.toJSON()));

20230304(🛌)

  • 2022大前端总结和2023就业分析
    • 未来:AI 和 Cropto(PS:公司之前为区块链,现在人工智能机器人,完美覆盖啊)
    • 2022年前端主流趋势
      • 性能——构建工具:make => Grunt => Gulp => Webpack => Vite => Turbopack
      • 运行时:Node.js、Deno、Bun
      • 体积:SSR
        • 通常产品开发都是先实现功能,然后再优化,然后再做周边
        • 当你想提升性能的时候,除了万金油缓存外,减小体积就是最简单的方式
        • 跨度领域:Gluon、Electron、Tauri、Neutralinojs
      • Rust 写前端基建,是当下趋势
      • 前端垂类,其实是最吃香的部分:比如互动游戏,3d,webrtc,比如可视化编辑器,AFFiNE,QUill,X6

20230303

  • 经验
    • 在 vscode 中,Command+点击 lingo 组件属性,就可以到类型定义文件 d.ts,然后就可以看到支持哪些属性及其类型,然后见名知意,或查找这些属性都起什么作用。
    • 像 onClick 这样的属性,按照上面操作后定位到类型定义文件后,就可以看其路径,然后在原文件中导入类型就可以使用了。
    • VSCode 插件 Live Server 解决跨域问题。
  • 任务:

20230302

  • 经验
    • lingo3d 关注点:官网可以用来调各种属性,看其作用(不完善且旧);GitHub 上有以 example- 开头的仓库,都是例子。
      • World 组件
        • Model 组件:放人物或场景模型
          • src 属性:加载模型 fbx 文件,建议使用绝对路径
          • animations 属性:加载自定义动画 fbx 文件
          • animation 属性:使用模型本身包含的动画或自定义动画
          • physics 属性:值为 map 时,src 需要使用相应 scene(场景)模型,否则会发现,人物模型加了physics="character"后,会不断的往下掉
          • ref 属性:控制模型的移动
            • ref.current.lookAt() 方法:人物看向目标点
            • ref.current.moveForward(-1) 方法:往自己前方走
            • onLoop 方法:每帧
            • ref.current.lookTo 方法:看向,最后为转向速度
            • ref.current.moveTo 方法:移动,最后为移动速度
            • ref.current.onMoveToEnd 方法:移动结束事件
          • innerY 属性:模型相对于 xyz 中心移动
          • intersectIDsonIntersect 属性:当碰撞 intersectIDs 中指定 id 的模型时,会触发 onIntersect 事件
          • roughnessFactor 属性:粗糙度系数
          • Find 组件:定鼠标瞄准时状态
        • Dummy 组件:官方提供的人体模型
        • Cube 组件:立方体
          • xyz/depth 属性: x 轴、y 轴、z 轴
          • id 属性:可用于碰撞检测
          • texture 属性:材质
          • textureRepeat 属性:如果糊的话,就使之重复次数变高
          • onClick 属性:
            • e.point:位置(x、y、z)
        • OrbitCamera 组件:使相机按照一定的路径运动
          • autoRotate:自动绕y轴旋转
        • ThirdPersonCamera 组件:使相机看向并跟随放入其中的人物模型(Model 是其子元素)
          • ative 属性:激活相机
          • mouseControl 属性:鼠标控制
          • innerY 属性:移动内部的相机
          • lockTargetRotation 属性:解决人物不受控制
          • fov 属性:焦距,人眼在 50 左右,焦距越小,中心面越广
        • Skybox 组件:需要360度环绕背景图片(equirectangular),或者 Poly Haven
        • Keyboard 组件:同 useKeyboard
        • LingoEditor 组件:同 Editor
        • HTML 组件:直接添加 html
        • Editor 组件:需要关闭相机的 active,同失效的 map-debug(放在 Word 外部会在屏幕中间挡住内容)
      • Reticle 组件:准星
      • Setup 组件:代替在 World 组件中设置属性
      • Reflector 组件:反光板
      • useLoop(function, boolean) hook:循环,不断做某事
      • useKeyboard hook:鼠标事件
      • usePreload hook:预加载模型,返回 progress
    • Mixamo
      • 绑好骨骼的模型: with skin 下载(t-pose)
      • 仅动作: without skin 下载,in place 复选框就不往前走(原地走)
    • @xstate/react:状态机(超赞),例子链接,还有 xstate vscode 插件。
    • VSCode 快捷键
      • Ctrl+鼠标左击:跳到定义
      • Ctrl+-:返回
      • Command+i:显示智能补全
      • Command+.:quick fix
      • Shift+Option+F:格式化代码
      • Command+Option+左方括号:折叠代码块
      • Command+Option+右方括号:展开代码块
      • Command+/:单行注释
      • Shift+Option+A:多行注释
    • typescript 自定义类型:TypeScript 入门教程:如果需要看源代码的话,例如 lingo.js 官方文档不全,那么只能通过源码来看如何使用了,这就要求有足够的 typescript 知识。我之前虽然系统学过 typescript,但是在看 lingo 源码还是疑惑 class 即是类又是类型的,经过查找回忆了起来,总结如下
      • 在定义 class 的时候会创建同名的实例类型
        • 定义:class ClassName {}
        • 使用:
          • let a: ClassName = new ClassName()
          • class ClassNameB extends classNameA
      • interface 定义接口类型
        • 定义:interface InterfaceName
        • 使用:
          • let b: InterfaceName
          • class ClassNameB extends classNameA implements InterfaceNameA, InterfaceNameB
      • type 用于类型别名
        • 定义:type TypeName = ClassName
        • 使用:let c: TypeName
      • declare 声明类型:例如指定某变量为某种类型,用于代码补全、接口提示,多写在 d.ts 类型声明文件中
        • 定义:declare let d: ClassName

20230301

20230227

  • 经验
    • VSCode Bookmarks 用于代码间跳转

      • 使用 Command+Shift+P 搜索 bookmarks 可以看到所有快捷键
      • 使用 Option+Command+K 为 Toggle
      • 使用 Option+Command+L/J 为跳转
    • scrollbar 滚动到底部

      • 在 typescript 中,需使用 useRef 并注意类型
        • 创建时需要设定初始值范型 const inputEl = useRef<HTMLInputElement>(null);
        • 访问时需要在条件语句中 if (inputEl.current) { inputEl.current.focus(); // Works! }
      • 可在 componentDidMount/componentDidUpdate 或 useEffect 中调用
      • 作用在父容器,滚动到最底部方法为: $div.scrollTop = $div.scrollHeight
      • 作用在子元素,可用 scrollIntoView例子
    • 在 useEffect 中访问之前的状态(How to compare oldValues and newValues on React Hooks useEffect?):使用 usePrevious

      // modified from https://usehooks.com/usePrevious/
      export default function usePrevious<T>(value: T) {
        // The ref object is a generic container whose current property is mutable ...
        // ... and can hold any value, similar to an instance property on a class
        const ref = useRef<T>()
      
        // Store current value in ref
        useEffect(() => {
          ref.current = value
        }, [value]) // Only re-run if value changes
      
        // Return previous value (happens before update in useEffect above)
        return ref.current
      }
      

      原理:jsx的渲染比useEffect早,ref.current 是 preState,useEffect 是最新 state

    • Mutation:类似 useState,用来修改更改远程数据及相关缓存

      • mutate()/mutate(key):将数据标记为已过期并触发重新获取(revalidate is deprecated)
      • mutate(key, data, options):修改本地缓存数据,可选是否重新获取

20230226

  • 经验

    • 如果不想在 useEffect 第二个参数数组中加入某个状态,但是在 useEffect 中又需要改变与访问该状态,则可以将一个返回新状态的函数传递给 setState

      const [currentState, setCurrentState] = useState(defaultState)
      
      useEffect(() => {
      // prevState为上一次的状态(previous)
        setCurrentState(prevState) => {
          const newState = {
          ...prevState,
          ...appendState
          }
          return newState
        })
      },[])
      
  • setInterval in React:我记得在 React 文档中看到过,但是找不到了。

    useEffect(() => {
      const interval = setInterval(() => {
        console.log('This will be called every 2 seconds');
      }, 2000);
    
      return () => clearInterval(interval);
    }, []);
    
  • 判断数组中是否存在某个元素:indexOf、find、some、includes

  • GitLens 可以增强 VSCode 内置 Git 的功能。例如 commits 搜索,历史记录和显示的代码作者身份

20230225

  • 经验
    • 参考建议添加以下规则,osx下有可能会导致偷跑流量 添加如下规则

      - DOMAIN-SUFFIX,googletraveladservices.com,DIRECT
      - DOMAIN,dl.google.com,DIRECT
      - DOMAIN,mtalk.google.com,DIRECT
      - DOMAIN,safebrowsing.googleapis.com,DIRECT
      
    • 最小的屏幕是 280px,Galaxy Fold

    • 监听动画结束

      • transitionEnd事件会在CSS transition动画结束后触发。
      • 当存在多个属性过渡变化时,结束时会多次触发transitionend事件。

20230224

  • 经验

    • NEXT_PUBLIC_DEFAULT_CHAINID 就是不同的区块链,其是固定的,97 是公司测试链,1 是以太坊

    • ETIMEDOUT:This is caused when your request response is not received in given time(by timeout request module option).

    • 代理下 git 无法 clone,cfw 可以绕过系统代理,这样就 OK 了,但在 TUN 模式下这个是无效的。

      bypass:
        - "github.com" # 下面字段可不删除
        - 127.0.0.1
        - ...
      

      更新:这种方式不好,这样用 git 确实是可以 clone 了,但是浏览 github 网页却很慢(即没被代理)——我的需求是,可以 git clone,同时在网页也能快速访问 github,即 git clone 不被代理,github 被代理。 尝试配置文件预处理 一直没成功,烦,现在暂时只能在需要 git clone 的时候关闭代理了。 CFW 本身就是用的 clash Premium,因此可以参考clash更新订阅时保留自己的规则,自己写一个带 proxy-providers 的配置文件,然后添加以下 rule 即可

      - PROCESS-NAME,git,DIRECT
      
    • SVG Screenshot:可以将网页保存为 svg (选择框或视口内容)

  • 任务

    • 除了 UI 图,还有产品文档,还有测试网站。根据产品文档+测试网站开发网站(我觉得应该用开发网站,即用最新的nofar分支,yarn dev 来体验,这是最新的,bug 也是最新的)走通流程,然后去细看 UI 设计+项目代码,快速熟悉。
  • 观点

    • APP 的价值在于其对用户是有用的,要有用,前提是能用,所以没有 bug ,或者说没有影响正常使用的 bug 至关重要。
    • 没有 bug 比各种新奇或者强大的功能更重要,老话说:”不要没学会走就开始跑“;比现代化、炫酷的 UI 更重要,Linux 上有很对 ui 简陋的工具,但是却是许多人的生活的一部分,
    • 我还是赞同 Polly 总结的:产品没 bug + 很多人知道

20230223

  • 经验

    • Error: Run autofix to sort these imports!:与simple-import-sort/imports相关,就是 import 导入顺序问题,需要在 VSCode 中安装 ESLint 插件,并在相关页面用 Command+. 快速 fix。

    • commit 提交需要根据 https://github.com/conventional-changelog/commitlint/#what-is-commitlint 填写信息。例如 git commit -m "feat(官网页面) : 多语言修改"

    • 可以看《i18n与lingui-js总结》了解国家化原理:1.0 为哈希表,键为原文,值为翻译的文本,通过键取值;2.0 为键为原文,值为函数,通过函数实现支持变量插入;3.0 是引入 babel 实现开发友好。

    • CSS实战–实现侧边栏滑进滑出:菜单的遮罩层是必要的,这样就可以通过点击遮罩层来隐藏侧边栏菜单了。滑进滑出原理就是利用定位移出元素到视口外。

      .mask {
        position: fixed;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
      }
      
    • MouseEeventHander 区别于 MouseEvent

      const handleAsideClick = (e: React.MouseEvent<HTMLElement>) => {
        e.stopPropagation()
      }
      
    • next-http-proxy-middleware:用于在开发环境中测试 api 调用,解决跨域问题。在 next.config.js 中添加

      async rewrites() {
        return [
          {
            source: '/api/v1/:path*',
            destination: 'http://10.2.0.10:9188/api/v1/:path*',
          },
        ]
      },
      

      添加还需要重启服务。

    • 【flex布局】解决view中两个元素一个居左一个居中:其实就是建立第三个元素,其与第一个元素一样宽度,但是没有内容。

      <div className="flex justify-between items-center">
       <Menu className="w-[50px]" />
        <LogoIcon />
        <div className="w-[50px]" />
      </div>
      
  • 观点:

  • 问题:

20230222

  • 经验:

    • Icon 都在 components/Icon/index.tsx

      export const AppleIcon = (props: React.ComponentProps<'svg'>) => {
        return (<svg {...props} ></svg>);
      }
      

      这里需要删除原图标的 wedithheight 属性,并用 {...props} 代替。还需要将 clip-path 等替换为小驼峰命名方式 clipPath

    - 先完成大致的 UI 与 页面逻辑,细节之后再慢慢调。先完成任务,再把任务完成得好。

    • 遇到什么页面问题需要与 UI 沟通调整的,可以先记录在记事本中,之后再在 UI 能回应时处理。
    • 移动 UI 图为 750px,其是 2 倍图,需要除以 2 才等于实际的值,包括元素的宽度与字体大小,反像素值都除以 2。
    • 用 tailwind css 的 last:first: 选取元素。
  • 问题:

    • How to hide scroll bar in react app with allowing scrolling

      可以在全局 css 样式表 src/styles/index.css 中添加如下内容

      .noscrollbar {
        /* hide scrollbar for IE, Edge and Firefox */
        -ms-overflow-style: none;
        scrollbar-width: none;
      }
      
      .noscrollbar::-webkit-scrollbar {
        display: none;
      }
      
    • Horizontal Scroll Image Pure CSS

      .scroll-container{
        overflow: auto;
        white-space: nowrap;
      }
      .gridscroll{
        display:inline-block;
      }
      .gridscroll img {
        margin: 0 15px;
      }
      

20230221

  • 任务

    • 基于 dev 分支开发新 Freefrom 官网。
  • 经验

    • Figma 导出 3x 图片就很清晰了
    • svg 可以作为 React 组件,不能作为 img 的 src。使用为 <SvgName className="w-full h-full text-white" />
    • 随便点击某个颜色属性,就会弹出color palettes(调色板),其中有 color picker
  • 前端工具:

    • Lighthouse:可以在 DevTools 中使用 Lighthouse 来审核页面的可访问性并生成报表。
    • Measuremate:页面元素距离检查工具

20230220

  • 任务

    • 翻译NTFTOPSHOW官网内容
    • 新建项目,迁移 Freefrom 官网,只要静态页面,不要多语言,能删的都删掉
    • 图片要优化,不能太大,考虑webp格式。
      • 可以使用 https://convertio.co/ 转换要不断切换页面,限制最多转换两个
      • 可以使用 cwebp,brew install webp
    • 学习优化,如优化工具 https://www.pingdom.com/
  • 经验

    • 使用多语言步骤

      // 引入
      import { t } from '@lingui/macro'
      import { useLingui } from '@lingui/react'
      // 使用,需提前在在 locale 添加相应的翻译
      const { i18n } = useLingui()
      {i18n._(t`Translation`)}
      
    • 简单的库的话,直接看官方文档;大型的如 React 就先看入门教程

    • 使用的话优先使用 import 而非 requires,因为有些时候 requires 无报错,但import话就有报错。

    • 因为要支持多语言,因此最好不要使用 uppercase 等 text-transfrom 属性

    • 公司项目使用的 UI/CSS 库

  • featrues 特色的地方:

    • 内容即NFT - 发布内容自动生成NFT,让内容上链,确定权利。
    • 完全去中心化 - 通过集成开放性协议nostr,没有任何平台可以禁止或审查你。你可以控制你的数据和言论。
    • 发帖赚钱 - 通过创造高质量内容获得粉丝的打赏奖励,同时获得平台激励。
    • DAO社区增强 - 通过团结内容及粉丝来创建自己的去中心化的DAO社区
    • 数字ID - 通过建立DID,创建一个Web3的独特身份,组织自己链上资产建立自己的Profile。
    • 加密私信 - 端到端的加密的私人信息,让大公司远离你的DMs。
  • Mac 快捷键

    • 切换vscode页面上面的标签 Option+Command+左右方向键
    • Shift+Command+3 拍摄截屏幕
    • Shift+Control+Command+3键会在剪贴板里获取整个屏幕的截图
    • Shift+Command+4 捕捉的屏幕区域,再按下空格键可捕捉窗口
    • Shift+Control+Command+4键则能使你通过拖动鼠标来截取部分屏幕,并复制图片到剪贴板,再按下空格键可捕捉窗口
    • 退出全屏 Ctrl+Command+F 或 Fn+F
    • 刷新浏览器:Command+R,强制刷新:Command+Shift+R
    • Option + 方向键左,将光标移动到前一个单词
    • Option + 方向键右,将光标移动到后一个单词
  • 问题

20230218

  • 公司 next.js 项目流程和目录含义

    • 首先看 src/layout:布局类
    • 其次看 src/pages:页面,pages 页面对应着路由,src/pages/home/ 路由就是 localhost:3000/home/,开发的时候也可以根据浏览器 URL 找对应的页面。
    • 然后看 src/features:每个 pages 对应的组件
    • tailwind.config.js 有些样式写在这里
      • src/styles/index.css 为全局样式表
      • src/src/styles/variables.css 定义全局字体
    • Typography 组件用来排版的,主要是字体相关的属性
  • 任务:

    • https://pancakeswap.finance/ 首页的图片,后续我们换成这样会动的,用css的来实现,你研究下他们这个。

      • 首先通过 devtool 的 animation 找到对应的元素为 path.eye。

      • 由于页面这个部分不断地跳(好像是不断的重新创建),因此用 command + s 保存离线网页。之后看到

        .cSXBDs .eye {
        animation-delay: 20ms;
        }
        

        实现动画的底层技术就是CSS或JS,这里可以看到用了 css 控制动画时长

      • 通过 devtool 的 network 获取 css 样式表,用 css fromatter 格式化,找到 @keyframe,但好像都无关动画定义。

      • 参考 SVG 动画SVG 闪烁动画(Blink) 我认为使用 JS 实现的动画可能性更大。而且仔细看 devtool 的 animation 中的动画时长为 280 ms,远大于 animation-delay 的 20ms,而这个类主要用于 JS 控制元素。

  • 技巧:

    • flex flex-1 justify-center align-center gap-[74px] 的 gap 来控制两个子元素间距,而非 margin

    • 尽量不要用 absolute 之类的定位写 ui,尽量让元素在正常文档流之内。

    • React.jsでlocation.hrefを用いた外部URLへの遷移

      <div onClick={() => {
          window.location.href = "https://google.com";
        }}>Googleへ</div>
      
    • Mailto on submit button

      <form action="mailto:youraddr@domain.tld" method="GET">
          <input name="subject" type="text" />
          <textarea name="body"></textarea>
          <input type="submit" value="Send" />
      </form>
      
    • Chrome 原生截长图

      打开浏览器,f12 进入开发者模式,使用快捷键 Ctrl+shift+p 呼出命令面板,输入 full 来过滤命令,找到一个 名叫 Screenshot capture full size screenshot 的那一项,点击后稍等一会就会生成一整图片保存到本地。

    • 使用yarn英文官方网站corepack 安装yarn方法在m1上老是失败,报错must be built because it never has been before or the last one failed,如果用普通的 npm -g 则没这个问题。

20230217

  • 数支用到的工具:

    • team task manager:asana
    • 沟通工具:前端 钉钉,后端 teams(一星期两会)
    • API:postman
  • 现公司用

    • 沟通工具 Lark(一天一会)
    • team task manager:Trello
  • 感悟:

    • 我发现如果只是编写简单的 UI 的话,可以直接开个项目,当 UI 编写完毕之后再迁移到原来的项目。因为原项目东西太多,加载运行都非常慢,非常浪费时间,不如直接在简单环境中达到目标,然后再在复杂环境中。
  • 问题:

  • MacOS pkg(例如 node.js)清理工具 UninstallPKG

20230216

20230215

20230214