Setsuna - 码无止境
type: channel
link: https://kok-s0s.top/index.php/category/Code/
description: 一个字 就是敲
items: 做一个天气查询的 TUI 程序
items: 造个 C++ 线程基类
items: 记一次「 包轮子 」的经历
items: Python -- 用 #ifndef 替换 #pragma once
items: ChatGpt 程序员的好伙伴
items: 目前三平台开发离不开的
items: 如何加快写测试脚本的速度？
items: 前端自动化测试小小学习 Selenium
items: Tailwind CSS 快速感受
items: 平平无奇切图仔

做一个天气查询的 TUI 程序
type: item
date: 2023-08-06T16:03:00+08:00
description: 目标驱动 - 逐步逐步做成这个 TUI 程序 起因 前几天逛 0xFFFF 社区，看到一个帖子 - 为什么我折腾这些小众技术？，主要有个回复让我脑子有点想法 -- 「 现在 GitHub 上出现了这么多活跃的 TUI 项目（支持显示全彩全分辨率图片甚至播放视频的 Terminal、IDE、漂亮的 TUI 文件管理器、音乐播放器等等），反倒是计算机届的文艺复兴了。 」 熟悉我开发习惯的就知道，我挺喜欢在终端 Terminal 下完成一些工作，就心想要不我也开发个小小 TUI 程序用用。 写该文的时候，Vim 的创作者 Brarm Moolenaar 去世了，这里 RIP 下，毕竟自大二起就使用着 Vim，这个东西对我开发真是非常便捷，感谢 Brarm Moolenaar 的贡献。 就想着可以做个简单天气查询的 TUI 程序。 因为我目前办公的位置离窗边蛮远，且现在天气热，太阳晒，全都拉上窗帘，我就看不见窗外啥天气了。 某日中午出去吃饭，居然下太阳雨，直接淋成「 落汤鸡 」。 同样的悲催事可不能再遭遇一次，现在离开座位，要出办公楼前，必看看天气预报，决定是否带个雨伞。 一般我看天气的步骤如下： 拿起手机解锁 滑到最左边的天气界面 那我想更懒惰点，反正每一天下来，我对着电脑比对着手机更多，且我每台电脑的终端我都不会关掉的，常驻后台。 不如做个 TUI 程序，这样我想查看天气的时候，步骤就变成了： 快捷键唤起终端 输入命令，按下回车，就可看到天气信息 且这样我每天就可以少拿起手机了。因为怎么说，我有时拿起手机看个天气，有可能不知不觉的刷了「 亿 」会儿手机，那放下手机，回到电脑前继续做些事情，脑子又可能会有点小卡壳，不知道自己刚才在干嘛（如果在敲代码，又可能会忘了某一个逻辑是要怎么做的，要花些时间回忆下）。 不过其实这影响也不大，只是我想着如果把工作任务早点做完，那我摸鱼就摸的更心安理得，好像更不错耶。（￣︶￣）↗ OK 清楚目标了，那就开始吧! 思考下吧！ 理解业务 这个东西的展现形态为一个 Terminal 下即可运行的程序。 运行这个程序，会显示我所在地的天气情况。 问题来了 - 我想要看到什么样的天气信息呢？ 明确下要展示的天气信息： 目前天气情况，即运行该程序那时刻的天气是咋样的，我要知道 未来天气情况，即未来几个小时或几天内的天气情况，我想知道 确定所用技术栈 我作为一个开发者，我掌握着什么样的技术栈，做这个东西用什么技术比较合适，这些要确定下来。 如果是在 Terminal 下运行的程序，那用很多语言都可以完成的，只不过我最近常用一门叫 Rust 的语言。 下面是我问 ChatGPT，用 Rust 做命令行程序的优势是什么，它给出的回答。见仁见智啦！ Rust 作为一种现代的系统级编程语言，拥有许多优势，使其成为开发命令行程序的理想选择。以下是 Rust 在开发命令行程序方面的一些优势： 1. 性能：Rust 是一门性能优秀的语言，它的执行速度接近于 C 和 C++，这使得命令行程序在处理大规模数据和复杂计算时表现出色。 2. 安全性：Rust 的所有权和借用系统能够在编译时检查内存安全性和数据竞争，有效地避免了许多常见的运行时错误和安全漏洞，使得命令行程序更加健壮和安全。 3. 并发支持：Rust 内置了对并发编程的支持，可以轻松地编写多线程的命令行程序，充分利用现代多核处理器的性能优势。 4. 轻量级：Rust 的标准库非常精简，这使得生成的可执行文件相对较小，节省了系统资源，并且适合用于开发轻量级的命令行工具。 5. 跨平台支持：Rust 提供了广泛的平台支持，可以轻松地在不同操作系统上运行编译好的命令行程序，这为开发跨平台工具提供了便利。 6. 依赖管理：Rust 的包管理器 Cargo 提供了简单易用的依赖管理功能，可以方便地引入外部库和工具，加速开发过程。 7. 社区和生态系统：Rust 社区非常活跃，并且拥有丰富的生态系统，你可以轻松地找到许多现成的库和工具，以及丰富的文档和教程，帮助你更高效地开发命令行程序。 综上所述，Rust 的性能、安全性、并发支持和跨平台能力使其成为开发命令行程序的优秀选择。无论是开发简单的工具还是复杂的命令行应用，Rust 都能够提供高效、安全和可靠的解决方案。 接下来就是关键的环节。 把业务需求翻译成技术需求 开发前准备相关资源 我这里的数据是有关天气的，那我就需要一个天气数据源。 这个网上选择很多，我这里用的是心知天气，调用它提供的 API 返回的数据格式为 JSON 类型，便于处理。 梳理出技术需求吧！ 调用心知天气的 API，获取天气数据；Rust 有提供 reqwest 库，可以用来发起网络请求； 能处理返回来的 JSON 数据，Rust 有提供 serde 库，可以用…

造个 C++ 线程基类
type: item
date: 2023-06-10T07:39:00+08:00
description: 写于 2023 年 3 月 24 日。 以剔除 Qt 的 QThread 为目标而诞生的项目。设计的线程基类能搭配类似 Qt 中的信号与槽机制来使用，且利用现代 C++ 提供的互斥量和条件变量，让函数能够同步或异步运行，这样派生类（某业务工作线程）能持有定时器（线程）来做些定时任务（异步）。 cxx_thread 起因 和 记一次「 包轮子 」的经历 的起因一样，只不过这个项目是为了剔除 Qt 的 QThread 而诞生的。 拆分目标 目标 -> 替换掉 QThread 分析下 QThread 这里主要看 QThread 的使用方式，看它如何被使用的。 第一种示例代码 class Worker : public QObject { Q_OBJECT public slots: void doWork(const QString &parameter) { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::operate, worker, &Worker::doWork); connect(worker, &Worker::resultReady, this, &Controller::handleResults); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } public slots: void handleResults(const QString &); signals: void operate(const QString &); }; 第一种是在一个继承自 QObject 的子类中，将 QThread 作为成员变量 workerThread，用 moveToThread() 函数将该类所创建的对象移动到 workerThread 中，connect() 函数将信号与槽连接起来，在槽函数中编写需要在线程中处理的逻辑代码。然后再调用 QThread 的 start() 函数来启动 workerThread，这样槽函数就能在一个新的线程中运行。 第二种示例代码 class WorkerThread : public QThread { Q_OBJECT void run() override { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString &s); }; void MyObject::startWorkInAThread() { WorkerThread *workerThread = new WorkerThread(this); connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults); connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } 第二种是写子类继承自 QThread，然后重写 run() 函数，这样就可以在 run() 函数中写线程的逻辑。 需要剔除 QThread 的代码中使用方式为第一种。 Signals and Slots Across Threads Qt 有提供一些信号与槽的连接选项 Qt::ConnectionType，这些选项能控制信号与槽的连接方式，比如是否是异步的，是否是阻塞的等等。 简单梳理需求 信号与槽…

记一次「 包轮子 」的经历
type: item
date: 2023-06-10T07:25:00+08:00
description: 写于 2023 年 3 月 17 日。 重新拾起 C++ 开发已经有 8 个月了，自己主导做的一件事情。 三个看法，一是记住 C++ 能不造轮子，就别造轮子了；二是能做单元测试的东西，就要做单元测试；三是好好利用 ChatGPT 之类的东西来优化代码 -/- doge cxx_crud_file 起因 被分配了一个任务，用现今的 C++ 标准改写一个原先用 Qt 编写的代码。就是要去除掉那些以 Q 开头的代码（QString、QVariant、QSetting 和 QFile 等等）。 重新用 C++ 标准改写的这份代码，扩展性就会更强，当然 Qt 的生态也不错，不过是业务需求如此罢了，不展开具体内容。 开始「 模仿 」 这份 Qt 编写的代码中有自己的文件读写工具类 FileTools（是根据实际业务产生的，即各种规定好的读写要求，怎么读取，怎么写入数据等等）。 用 Qt 构建的 FileTools 类， 其代码长度大概为两千多行左右 只不过这个类是基于 Qt 整个语言生态构造的，现在打算要去除 Qt 罢了。 我一开始，就是简单仿造这个 FileTools 类，函数名大致不变，函数内部的处理逻辑也大致不变，入参和返回值的类型由 Qt 的数据结构改成 C++ 标准下的，例如 QString 用 std::string 代替。 但其实，在修改这个 FileTools 类时，发现这其实算不上是个类，只是把功能函数全当作成员函数罢了，要使用这些函数时，就用这个类创建一个对象，通过这个对象来调用所需的功能函数。 大概是刚从前端转到做 C++ 的开发，我一开始也觉得没什么问题，就是 函数式编程 嘛。 但是 C++ 的一大精髓就是 OOP（面向对象开发），这个类弄得更像面向过程的。 且这个类里有蛮多方法其实可以继承复用的，阅读其中的代码，有点乱。 越开发，越能闻到代码传出的那股 「 坏味道 」。 该换思路来做。 开始「 包轮子 」 包包包，有现成的，优秀的，经过试验和测试的功能函数，我是绝对不会重复造轮子的，我是个很懒的开发者 /doge。 测试驱动开发 Test-Driven Development (TDD) VSCode | CMake | C++ | TDD 整个项目使用 GoogleTest 来做单元测试，开发过程中，先编写测试用例，然后再编写代码，最后再运行测试用例，看看是否通过，不通过，则修改代码，直到通过为止。 记住，测试代码应当与你功能代码的「 体量 」差不多大。 UFile 重新拾起 C++ 开发，看了蛮多 C++ 的书籍（《clean C++》、《Effective C++》等），以及看 cppreference 官网内容，感觉要做的文件读写工作。一些功能函数可以基于 C++ 标准提供的 <filesystem> 文件系统库来做。 UFile #ifndef UFILE_HPP_ #define UFILE_HPP_ #include <iostream> #include <string> #include "Tools.hpp" #if defined(__clang__) || defined(__GNUC__) #define CPP_STANDARD __cplusplus #elif defined(_MSC_VER) #define CPP_STANDARD _MSVC_LANG #endif #if CPP_STANDARD >= 201103L && CPP_STANDARD < 201703L #include <cstring> #include <experimental/filesystem> namespace fs = std::experimental::filesystem; #endif #if CPP_STANDARD >= 201703L #include <filesystem> namespace fs = std::filesystem; #endif class UFile { private: fs::path _p; public: explicit UFile(const std::string &path) : _p(path) {} explicit UFile(const fs::path &path) : _p(path) {} std::string path() { return _p.string(); } }; #endif // UFILE_HPP_ 将 <filesystem> 的 path 类作为私有变量使用； 因为要适配 Linux 和 Windows 平台以及不同的 C++ 标准，加个宏定义来做判断。 这样就是一个基类，接下来对各种类型处…

Python -- 用 #ifndef 替换 #pragma once
type: item
date: 2023-06-10T06:46:00+08:00
description: 写于 2023 年 3 月 2 日。 需要重复处理的文件过多了，就别 Ctrl C、Ctrl V 了,写点 Python 最合适了。 代码背景 #pragma once 和 #ifndef 都可用于防止头文件被两次编译。 不过 #pragma once 是依托微软编译器來使用，不支持跨平台。 #ifndef 的写法是 C++ 标准支持的，能够跨平台使用。 因此用 #ifndef 來替换掉原先代码中存在的 #pragma once。 用用 Python 利器 一般处理多文本文件，我就会分成两块： 遍历多文件，这里即 traverse_directory，目录遍历文件的代码可复用； 文件文本处理，这里即 replace_the_marco，文本处理的代码根据需求來做修改； import os def replace_the_marco(cur_file_name: str) -> bool: flag: bool = False basename: str = os.path.basename(cur_file_name) file_name: str = basename.split(".")[0].upper() suffix: str = basename.split(".")[1].upper() macro: str = file_name + "_" + suffix + "_" first_sentence: str = "#ifndef " + macro second_sentence: str = "#define " + macro third_sentence: str = "#endif // " + macro lines: list[str] = [] with open(cur_file_name, mode="r", encoding="utf-8") as file: index: int = 0 # 只找前三行 for line in file: temp_item: str = line.strip() if index < 3: if temp_item == "#pragma once": flag = True index = index + 1 lines.append(line) file.close() if flag: print("\nModify the macro for " + basename + "!\n") with open(cur_file_name, mode="w", encoding="utf-8") as file: for line in lines: if "#pragma once" in line: file.write("%s\n" % first_sentence) file.write("%s\n" % second_sentence) else: file.write("%s" % line) file.write("\n%s\n" % third_sentence) file.close() else: print("The " + basename + " does not need to be modified!\n") return flag def traverse_directory(cur_dir: str): for cur_dir_item in os.listdir(cur_dir): cur_path_name: str = cur_dir + "/" + cur_dir_item if cur_dir_item[-4:] == ".hpp" or cur_dir_item[-2:] == ".h": replace_the_marco(cur_path_name) elif os.path.isdir(cur_path_name): traverse_directory(cur_path_name) if __name__ == "__main__": folder_absolute_path: str = "" traverse_directory(folder_absolute_path) 小坑，遇到一些 Visual Studio 下创建的文件，格式为 ansi，需要转为 utf-8 才可再用上面的脚本处理，分另一个脚本文件处理此过程。这样跑完一个脚本，可以验证一个阶段。 import chardet import codecs import os def ansi_to_utf_8(cur_file_name: str): with open(cur_file_name, "rb") as f: r = …

ChatGpt 程序员的好伙伴
type: item
date: 2023-04-01T23:26:00+08:00
description: 水篇博文 /doge。 开头挂个教程 Learning Prompt 开头挂几个视频链接 使用 ChatGPT 的一些例子 超级小華 不高兴就喝水 个人感觉，如果说互联网的出现，将知识从 “实体” 形式转换为 “数字” 形式存储起来，让人们相较过往，更容易接触到各种各样的知识。那么，ChatGPT 这种可对话的 AI，很容易地将知识做一个整合，将整合的内容再反馈给提问者。 Web Searching Skills 过往，一般（说我自己），在计算机方面打算做一个小玩具（桌面计时器啦），会组合几个搜索的关键词，去 Google 上搜索，然后再从搜索结果中找到自己想要的答案。整个过程会花费掉一些时间，而且，搜索结果中的内容，往往不是自己想要的，还需要再花费一些时间去过滤掉不想要的内容。最终还要我自己整合出我所需要的信息。 但现在，我可以直接描述我打算做一个什么东西，ChatGPT 会给我一些尽可能接近的回答，我再继续追问，ChatGPT 返回的答案，会越来越接近我想要的答案。这个过程，相较我自己去搜索，会更加快速。 但我对于 ChatGPT 返回的回答，其所述说的知识准确性也是需要再三确认的。记住，最终做判断还是我们人类本身，AI 只是辅助我们做决策。 网络上有关 ChatGPT 这类 AI 的应用有很多视频或文章，基本现在各大资讯渠道有关这方面的资讯应该不少。我这里只介绍我个人所用到的，谈谈使用感受。 Easy 生成配置环境教程 太爽了，说清楚你什么系统（最好能说版本号就说），你要做什么事情，做这个事情大概会安装什么，做个甲方，给 ChatGPT 提需求，ChatGPT 就能从训练的数据（无数前人配置环境踩过的坑）中找到适合你的配置环境教程。当你想去学习某些新技术，很难再卡在配置环境那一环节了。 小白入门新技术，输出 “Hello World！”。 生成目标脚本 年初，还打算自我增值，学一波 Linux 下的 shell 脚本知识，ChatGPT 一出，我已经失去继续学习的欲望。我只要面向 ChatGPT 编程即可。而且本身 shell 脚本的很多知识就很模块化，将各种 shell 命令根据你所描述的需求去组合并不是一件难事（对 ChatGPT 而言）。不仅是 shell，对于 Python 这门脚本语言来说，ChatGPT 也能生成目标脚本去处理一些文本数据或者做一些自动化操作。 生成函数（函数式编程） 函数式编程中的函数 == 真正的数学函数，其引用透明，即只要使用相同的输入调用函数，将始终得到相同的输出。 这类函数让 ChatGPT 生成真是嘎嘎简单。只要你描述清楚你的需求，ChatGPT 就能生成你想要的函数。 这是我重新拾起 C++ 开发 5 个月后写的一个字符串分割函数（网上找了一份 Linux 下 C 语言，加了个 Windows 的版本，测试能跑就没管了）。 std::vector<std::string> split(const std::string &data, const std::string &separator) { std::vector<std::string> result; if (data == "") return result; #if defined(_MSC_VER) char *next_token = NULL; char *token = strtok_s((char *)data.c_str(), separator.c_str(), &next_token); while (token) { result.push_back(token); token = strtok_s(NULL, separator.c_str(), &next_token); } #elif defined(__GNUC__) char *token = strtok((char *)data.c_str(), separator.c_str()); while (token) { result.push_back(token); token = strtok(NULL, separator.c_str()); } #endif return result; } 这是 ChatGPT 生成的函数，我只是把我上面写的这个函数丢给它，让它帮我优化，就返回一个十分简练的函数，一对比，就很不错。ChatGPT 非常适合写这种和业务逻辑无关的函数。毕竟网络上的相关实现，想来这么多年也是积攒了不少。 std::vector<std::string> split(const std::string &data, const std::string &separator) { std::vector<std::st…

目前三平台开发离不开的
type: item
date: 2023-01-01T08:58:00+08:00
description: 起因 年终 -- 工具总结 我真的需要很多的工具吗？ 我的编程习惯貌似差不多定下来了，谨以此文记录下来！ 同时面向对象编程 OOP 有一个基本原则 --- Don't repeat yourself（不要重复你自己，简称 DRY），要不然你想 WET --- Write everything twice。 因为工作上的需求，需要在 Linux 环境下做些工作，上班期间花了些时间配置了两台 Linux 电脑的开发环境，花一个下午折腾所需开发环境。之后那个工作可能会在多个新的 Linux 电脑上进行，我便做了简易的 PPT 记录如何配置开发环境，让后续参与该项目的同事有个参考。 顺带我手头上的电子设备蛮多，我也喜欢折腾电子产品，而现在一些操作习惯也逐渐确立，那肯定于我个人而言，我希望在我每一个电脑（或者 Docker 容器）上都能没什么操作障碍地敲代码，就是我配置的那些快捷键啊，一些我自定义的缩写指令呀这些，都是一样的。一种操作上的自在。 就有整理一个 GitHub 仓库，可以参考下。（不包含 IDE 的快捷键设置，IDE 见仁见智，各有所爱） 顺带一提，编程开发看做什么吧，大部份开发就关注电脑的系统、内存和硬盘这三个就够了。如果你有软件安装失败的情况，那大概率会和这三方面挂钩，有些可能需要考虑显卡或者一些驱动。 Dev Platform 我个人目前在使用中的一些操作系统 Linux Ubuntu 22.04 Kail Linux Arch Linux Windows 10/11 -- WSL2（Ubuntu 20.04） macOS（Unix） Tools Terminal 我很喜欢在终端下完成我的一些工作（只要是敲代码于我而言就是工作）： 用 Vim 写一些 shell 脚本 喜欢开着一个 top 面板看着（如果我当时的工作环境有个副屏的话），可以用来看看软件的使用情况 用 ssh 远程连接一些服务器，同时做些事情 用 Python 来充当计算器（进制计算、移位计算和异或等等） macOS 下的 Terminal 终端就是 iTerm2（以上截图都为其效果） Linux 下的都为其自带的 Terminal 终端即可 Windows 下建议下载其商店的 Terminal 终端来使用，而非选择 Powershell Editor 邪？少儿不宜？看我博客的不强求，上手难度有点大。 就是 Vim + Tmux 这套，这套组合其实蛮老的，也常被一些程序员称其为邪教组合，但是只要你习惯终端开发，可能会对这一套开发组合感兴趣。 VSCode（基本三平台我都会安装），VSCode 的扩展各个平台也会有不同的选择，可看我上面提到的 GitHub 仓库。 Sublime Text （Linux 环境下我才安装，Windows 的界面太丑太拉了，我就不用） Visual Studio （工作用） JetBrains 全家桶（看工作需要安装） Browser Edge（Windows 必备） Chrome（基本三平台都安装） Vivaldi（macOS 安装，也只有 macOS 下的 UI 值得一看，否则其他平台太拉了，看的太臃肿了） Safari（macOS 必有，但我超少用） Commands 我最常使用的 20 个指令，看得出我现在主要的工作都围绕 C++和 Web 这两个领域展开 history | awk '{print $2}' | sort | uniq -c | sort -nr | head -20 2824 git 1964 cls 1009 cd 700 yarn 499 ls 470 npm 254 rm 178 code 117 brew 103 g++ 101 npx 97 ./a.out 88 md 87 vim 83 pnpm 71 mv 67 node 58 cat 54 sudo 43 tree

如何加快写测试脚本的速度？
type: item
date: 2022-04-09T00:03:00+08:00
description: 搜索到一个东西，叫 Katalon Recorder，是一个能通过录制用户操作生成测试脚本的插件（各个主流浏览器都会有，推荐 Firefox，我 Chrome 卡的丫皮） 录制结束可按 play 重新回放你的操作。 当然重点是 Export！ 可以导出蛮多种语言编写形式的脚本，我这里如果是要求 JavaScript，导出格式要求为 WebDriver.io。 欧！熟悉的节点操作，这个 $ 就是 Jquery 那味道了。 So，我只用做个翻译工，把上面代码转换为 async/await 形式的脚本即可。 const { Builder } = require("selenium-webdriver"); const { By } = require("selenium-webdriver"); (async function myFunction() { let driver = await new Builder().forBrowser("chrome").build(); // 测试网址 await driver.get( "不见了～～" ); // 输入账户名 await driver .findElement(By.xpath("//input[@type='text']")) .sendKeys("～～"); // 输入密码 await driver .findElement(By.xpath("//input[@type='password']")) .sendKeys("～～"); // 验证码 await driver .findElement( By.xpath( "//div[@id='userLayout']/div/div[2]/div/form/div/div[3]/div/div[2]/form/div[3]/div/div/div/div/span/span/input" ) ) .sendKeys("～～"); // 点击登陆 const loginButton = await driver.findElement( By.xpath("//button[@type='submit']") ); await loginButton.click(); })();

前端自动化测试小小学习 Selenium
type: item
date: 2022-04-08T18:30:00+08:00
description: Selenium 官网链接 https://www.selenium.dev/ 能够多种语言做自动化测试，这里使用的是 JavaScript。 准备 使用之前需要安装两个东西： 下载 Selenium 的 npm 包 npm install selenium-webdriver -g 需要下载 browser drivers 即如果需要在哪个浏览器下进行自动化测试运行，都需要安转其相应版本号的驱动。 目前在 Chrome 浏览器下做自动化测试，在官网给出的链接选择我浏览器对应的版本号即可。 https://chromedriver.storage.googleapis.com/index.html 将下载好的压缩包解压到测试项目中 测试项目 . ├── chromedriver ├── login_test.gif ├── login_test.js ├── login_test.mov ├── node_modules ├── package-lock.json └── package.json 注意：驱动需与脚本在同一级目录！ 登陆测试 查询 Selenium 官方文档学习基本对浏览器的操作，根据文档说明编写脚本。 const { Builder } = require("selenium-webdriver"); const { By } = require("selenium-webdriver"); (async function myFunction() { let driver = await new Builder().forBrowser("chrome").build(); // 测试网址 await driver.get( "它不见了～～～" ); // 输入账户名 await driver .findElement(By.xpath("//input[@type='text']")) .sendKeys("～～"); // 输入密码 await driver .findElement(By.xpath("//input[@type='password']")) .sendKeys("～～"); // 验证码 await driver .findElement( By.xpath( "//div[@id='userLayout']/div/div[2]/div/form/div/div[3]/div/div[2]/form/div[3]/div/div/div/div/span/span/input" ) ) .sendKeys("～～"); // 点击登陆 const loginButton = await driver.findElement( By.xpath("//button[@type='submit']") ); await loginButton.click(); })(); 原本想发测试图，可图泄露东西有点多～～

Tailwind CSS 快速感受
type: item
date: 2022-02-17T23:58:00+08:00
description: 起因 最近看到 freeCodeCamp 微信公众号的一篇文章的； 那篇文章当时看到一句话 在这篇文章里，我们将使用 React、Tailwind CSS、Netlify 等等行业标准工具创建一个作品集网站。 这句话中有一个关键词 Tailwind CSS ，这是我还没整过的玩意，最近也是在准备一个春招，有遇到面试官提过一下，我当时实话说自己还没用过 doge ，不过记住了这个玩意，想着来摸索下这个项目是如何使用 Tailwind CSS 的，顺带再水篇博文； 动起来！ 那肯定就先按照那个公众号把整个项目弄起来，部署起来，先看看这个跑不跑得起来（不能跑就不看了 doge ，bushi）； 部署后的网站链接：https://kok-s0s-portfolio.netlify.app/ 先清楚它是如何被调用的 Google 一搜 Tailwind CSS creat react app ，ok！ 第一篇就是你了 --> 在 Create React App 中安装 Tailwind CSS 它的官方文档就是写的很详细了（自己看去吧） 阅读它的代码 公众号文章有事先说明 Tailwind 允许我们通过组合类名（classnames）将多种样式轻松应用到 React 项目上，给应用程序一个专业的外观。 个人我肯定先看项目被挂载的主要组件 App.js ； import React from "react"; import About from "./components/About"; import Contact from "./components/Contact"; import Navbar from "./components/Navbar"; import Projects from "./components/Projects"; import Skills from "./components/Skills"; import Testimonials from "./components/Testimonials"; export default function App() { return ( <Navbar /> <About /> <Projects /> <Skills /> <Testimonials /> <Contact /> ); } 接下来就是挺“简朴”的操作，在 Tailwind CSS 官方文档搜索这些组件文件中用到的类名，接下来列出这个项目我搜索过的所有有结果的类名以及该类名的作用： App.js text-gray-400 字体颜色 bg-gray-900 背景颜色 body-font 文档没搜到，大概是使用浏览器默认字体的意思 About.js container 容器 --> 看文档很详细 mx-auto 能使一个容器居中 px-10 添加水平内边距，左右都有 py-20 添加垂直方向的内边距 pr-24 即 padding-right: 6rem; py-2 即 padding-top: 0.5rem; padding-bottom: 0.5rem; px-6 即 padding-left: 1.5rem; padding-right: 1.5rem; 响应式设计 sm 断点 --> 最小宽度 640px，即 @media (min-width: 640px) { ... } md 断点 --> 最小宽度 768px，即 @media (min-width: 768px) { ... } lg 断点 --> 最小宽度 1024px 即 @media (min-width: 1024px) { ... } flex 看文档 flex-row 使用 flex-row 用来沿与文本相同的方向水平放置 flex 子项 --> 横向 flex-col 使用 flex-col 用来沿垂直方向放置 flex 子项 --> 纵向 flex-grow 使用 flex-grow 允许一个 flex 项目放大，以填充任何可用空间 items-center 使用 items-center 沿着容器的交叉轴中心对齐项目，即垂直居中效果 items-start 使用 items-start 将项目沿着容器交叉轴的起点对齐： w-1/2 即宽度为 width: 50% 分数为比例，数字就是具体数值 text-left 文字向左对齐 text-center 文字居中对齐 mb-16 即 margin-bottom: 4rem; mb-0 即 margin-bottom: 0px; title-font text-4xl 即 font-size: 2.25rem; line-height: 2.5rem; text-3…

平平无奇切图仔
type: item
date: 2022-02-16T12:34:00+08:00
description: doge 为狗头意思 应发布于1月14日（离职） 第一份前端实习工作 --- 历险记？ 记录下这份实习工作的收获和感受 面试进的是某医疗机构的研发部门，人不多，都是为医务人员打工； 做的东西就是医生为患者看病时，管理患者数据的一个大系统： 现在患者看病 --> 小程序； 医生电脑看患者信息 -> 管理后台； 医生手机端APP看患者信息 （时代的一个趋势吧，现在手机太方便了，原先医生都是在电脑或者手记管理患者信息） --> 貌似原计划在安卓、iOS上做APP应用变成在企业微信上做H5页面应用开发（成本低，开发快，满足需求）； 熟悉下 其实原先这家公司早有一个在用着的管理后台系统让医务人员使用，但是时代在进步，大概医疗人员觉得原先那个太丑太拉了不想用了，那这甲方老哥有需求，这不得要做出些改变呀！ 上级那几位研发也确定好了技术选择，基于一个开源框架 Jeecg-Boot 来搭建这个后台。 这个框架是一个低代码平台，能便于前后端（这个项目两个前端，两个后端）快速开发，后端我就不清楚到底有多便捷了，前端倒是真的方便太多了。 头一星期 看 README 做些换色换皮操作，Jeecg-Boot 这个框架源码下载下来有自带的信息 (dddd)； 配置页面路由信息，该框架是基于 Vue2 的，可以在其框架提供的 @/config/router.config.js 做路由配置操作，但更好的是用该框架提供的选择-在线菜单配置； 简单做个页面测试是否可行，OK了继续做下去。。。 之后 切图切图还是切图！ 真的，切图时回忆起高考后当时大家流传着一句话“劝人学医，天打雷劈！”，这句话真的有几分道理，学医的要掌握太多知识了，是一份即吃下功夫的努力和大量时间的工作。 我切的那些图都不怎么难了，但是一个页面数据量巨多； 举个例子，一个患者如果患有糖尿病，现在时代变了，搞多些数字化检测，患者每天需要对自己的血糖做个三次检测，每次检测都在吃饭前后，早餐午餐晚餐，那就有6个数据了，这个检测还不是只做一天那种； 再有，原来看病有时需要考虑些并发症和合并症的问题（一个糖尿病就需要考虑11种并发症），如果患者真的有这些疾病了，又需要结合患者的既往病史考虑这个患者的情况，好家伙，我这就是要不停地切图呀！ 切的人都麻了，好处就是了解很多关于疾病的信息，某些疾病会和某些疾病有所关联，属实是带薪学“医”了。 这个切图经历让我感觉高资历医生其实真的很配拿高福利高工资的。 这个后台用到的技术 基于 Jeecg-Boot 框架搭建的后台系统中前端部分所用技术为： Vue2 主要是页面的制作，不同的数据（文本信息，时间，图片）显示与修改，文件的上传下载等等以及父子组件的一些问题（子组件调用父组件的方法，父组件传递数据给子组件等等） Vuex 用于处理数据的流动，主要是患者的唯一ID，在进入某一个患者的信息档案后，该患者关联的所有子信息档案的患者ID只能为该患者的。 因为是管理，会有一个大列表有着每一位患者的基本信息，这个流程是这样的，医务人员点击某个患者信息后，会进入该患者的详细信息档案（很多很多不同信息档案），就需要将列表中点击的那个患者ID传入这些信息档案中； 做法就是在 store 相关文件下的 mutation-types.js 中设置自己需要的变量，用 export const 导出，在列表入口做个赋值操作并且能打开新窗口显示该患者的信息档案，即相关代码 Vue router.resolve 如下 this.$router.resolve({ path: '...', query }) // query包含患者ID这个参数 而在其它需要使用到这个变量的子信息档案页面中使用 Vue $route.query 的方法获取URI上的患者ID这个参数信息即可。 Vue Router 用 Jeecg-Boot 的在线菜单路由进行配置； UI库 -- Ant Design of Vue Jeecg-Boot 推荐使用的组件库，搭配 Vue2 实现一些功能，主要使用到的组件为 Button 按钮 Grid 栅格 Dropdown 下拉菜单 Checkbox 多选框 DatePicker 日期选择框 Form 表单 信息校验 自定义校验 -> 手机号|身份证号（正则表达式） Input 输入框 Radio 单选框 Select 选择器 Upload 上传 Table 表格 Tabs 标签页 Drawer 抽屉 Message 全局提示 Modal 对话框 axios Jeecg-Boot 基于 axios 内置了大量的请求方法，主要使用为 GET 和 POST ; 调用接口注意点 接口地址（测试和正式） 传参（post方法会用到） 返回值（是否规范？） Jeecg-Boot 自带组件 主要配合…