1
0

docs: 添加博客园文章内容

This commit is contained in:
2025-10-09 13:38:05 +08:00
parent 012865cfbb
commit ed2515a0fc
51 changed files with 1500 additions and 0 deletions

View File

@@ -1,4 +1,88 @@
[
{
"created-at": "2025-07-02 22:20",
"is-hide": "",
"slug": "bluefox-nx1-flash-gsi",
"title": "蓝狐BLUEFOXNX1 刷 GSI 系统",
"updated-at": "2025-07-03 21:54"
},
{
"created-at": "2025-04-18 22:55",
"is-hide": "",
"slug": "ffmpeg-merge-video-files",
"title": "使用 FFmpeg 合并多个视频文件",
"updated-at": "2025-07-03 21:58"
},
{
"created-at": "2025-03-22 22:20",
"is-hide": "",
"slug": "fix-redirect-dingtalk-miniprogram-scheme-blank-page",
"title": "解决钉钉通过短链接跳转小程序 scheme 的方式打开小程序时会有一个空白页面的问题",
"updated-at": "2025-03-22 22:32"
},
{
"created-at": "2025-02-11 00:00",
"is-hide": "",
"slug": "post-2025-02-11-1",
"title": "在用 uni-app 开发钉钉小程序的时候遇到一个奇怪的问题,发送请求拿不到返回的数据",
"updated-at": "2025-02-12 09:51"
},
{
"created-at": "2024-01-19 00:00",
"is-hide": "",
"slug": "fix-ant-tree-select-can-select-disabled-item",
"title": "解决 Ant TreeSelect树选择组件可以使用键盘选中 disabled已禁用项的问题",
"updated-at": "2025-02-20 22:56"
},
{
"created-at": "2023-12-18 00:00",
"is-hide": "",
"slug": "nodejs-check-is-tablet-pc",
"title": "Electron 或 Node.js 判断当前设备是否支持触摸屏",
"updated-at": "2025-02-20 22:57"
},
{
"created-at": "2023-11-07 00:00",
"is-hide": "",
"slug": "miui-dial-plate-commands",
"title": "MIUI 拨号盘指令(代码)合集",
"updated-at": "2025-02-20 22:58"
},
{
"created-at": "2023-11-06 00:00",
"is-hide": "",
"slug": "calculation-test-code",
"title": "运算速度测试代码",
"updated-at": "2025-02-20 22:58"
},
{
"created-at": "2023-10-08 00:00",
"is-hide": "",
"slug": "use-css-function-in-less",
"title": "在 Less 中使用与 Less 内置函数同名的原生 CSS 函数",
"updated-at": "2025-02-20 22:58"
},
{
"created-at": "2023-09-17 00:00",
"is-hide": "",
"slug": "common-usage-git-commands",
"title": "常用的 Git 命令",
"updated-at": "2025-02-20 22:59"
},
{
"created-at": "2023-09-17 00:00",
"is-hide": "",
"slug": "javascript-define-and-init-array",
"title": "JavaScript 创建并初始化任意长度的数组",
"updated-at": "2025-02-20 22:59"
},
{
"created-at": "2023-05-30 00:00",
"is-hide": "",
"slug": "zlm-rtc-client-multi-video-pull-once",
"title": "通过画布Canvas实现 ZLMRTCClient 同一视频流多次显示时只拉取一次",
"updated-at": "2025-02-12 09:50"
},
{
"created-at": "2023-02-21 00:00",
"is-hide": "",
@@ -433,6 +517,13 @@
"title": "百度触屏版首页不同样式的页面",
"updated-at": "2025-03-16 22:32"
},
{
"created-at": "2018-08-12 00:00",
"is-hide": "",
"slug": "windows-copy-command-usage",
"title": "Windows copy 命令的妙用(文件里藏文件、合并文件)",
"updated-at": "2025-03-29 18:30"
},
{
"created-at": "2018-08-09 00:00",
"is-hide": "",

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,77 @@
---
title: 蓝狐BLUEFOXNX1 刷 GSI 系统
date: 2025-07-02T22:27:40Z
lastmod: 2025-07-03T21:54:29Z
tags: [Android,GSI,刷机,系统]
---
# 蓝狐BLUEFOXNX1 刷 GSI 系统
> 主要记录一下操作过程,操作前请确保具备一定的刷机经验。
## 所需工具和文件
1. MTK Client可选主要用于备份分区
2. ADB 工具([下载地址](https://developer.android.google.cn/tools/releases/platform-tools?hl=zh-cn))。
3. GSI 文件包(例如 [Google GSI](https://developer.android.google.cn/topic/generic-system-image/releases?hl=zh-cn),本文中使用的是 `aosp_arm64-exp-BP1A.250405.005.C1-13151952-61d23231.zip`)。
## 操作步骤
### 一、使用 MTK Client 备份分区
手机关机,打开 MTK Client 软件,手机同时按住“音量加”和“音量减”键,然后通过数据线连接到电脑。
![image](assets/image-20250702233608-t3r8wve.png)
使用 MTK Client 的“Read partition”功能读出除“userdata”用户数据以外的分区备份以便刷坏了可以还原。
![image](assets/image-20250702224334-vyg3sj8.png)
### 二、解锁 BootLoader
> 注意:解锁操作会清除手机数据,恢复出厂设置。
进入手机设置的“开发人员选项”打开“USB 调试”和“OEM 解锁”选项的开关。
将手机通过数据线连接到电脑。
电脑在 ADB 工具的目录下执行 `adb devices` 命令,此时手机上应该会弹出授权提示窗口,点击“允许”以授权。
然后执行 `adb reboot bootloader` 命令,等待几秒钟,直到设备重新启动进入引导加载程序。
> 注意:进入 fastboot 模式后,大概率会因为没有驱动程序,执行 `fastboot` 命令失败,需要参考“[https://www.cnblogs.com/changweijinghu/p/16880803.html](https://www.cnblogs.com/changweijinghu/p/16880803.html)”解决。
>
> 驱动程序下载地址:[获取 Google USB 驱动程序](https://developer.android.com/studio/run/win-usb) / [usb_driver_r13-windows.zip](https://dl.google.com/android/repository/usb_driver_r13-windows.zip)
之后执行命令 `fastboot flashing unlock`,手机屏幕上会显示解锁确认:
```text
Press the Volume UP/Down buttons to select Yes or No.
Yes (Volume UP): Unlock (may void warranty).
No (Volume Down): Do not unlock bootloader.
```
此时按下“音量加”键,确认解锁(**将会清除手机数据,恢复出厂设置**)。
等待片刻,如无意外,会显示“解锁成功”。
### 三、刷入 GSI
将下载的 GSI 文件包解压到 ADB 工具所在文件夹内。
> 例如本文使用的 `aosp_arm64-exp-BP1A.250405.005.C1-13151952-61d23231.zip`,可以解压得到 `system.img` 和 `vbmeta.img` 这两个镜像文件。
依次执行以下命令,刷入镜像:
```text
fastboot flash vbmeta vbmeta.img
fastboot reboot fastboot
fastboot flash system system.img
fastboot reboot
```
> 注意:执行 `fastboot reboot fastboot` 命令后如果等待比较久都没出现“Finished”提示则需要插拔一下数据线。
![image](assets/image-20250702232252-k8k0r2r.png)

View File

@@ -0,0 +1,67 @@
---
title: 运算速度测试代码
date: 2025-02-11T20:55:04Z
lastmod: 2025-02-20T22:58:29Z
tags: [测试,代码片段]
---
# 运算速度测试代码
#### C
```c
#include <stdio.h>
#include <math.h>
#include <time.h>
int main()
{
long tStart = time(NULL);
long tEnd = tStart + 10;
long tNow = 0;
long count = 0;
do {
double num = sqrt(count);
tNow = time(NULL);
count++;
} while (tNow < tEnd);
printf("%ld", count);
return 0;
}
```
#### Java
```java
public class CalcTest {
public static void main(String[] args) {
long dMillis = 2000;
long dSeconds = 0;
long tStart = System.currentTimeMillis();
long tEnd = 0;
long tCurrent = 0;
long count = 0;
if (args.length == 1) {
try {
dMillis = Long.parseLong(args[0]);
} catch (Exception err) {
System.out.println("[Error] invalid duration: " + args[0]);
return;
}
}
dSeconds = dMillis / 1000;
tEnd = tStart + dMillis;
System.out.println("[Set] duration = " + dSeconds + "s");
do {
double num = Math.sqrt(count);
count++;
tCurrent = System.currentTimeMillis();
} while (tCurrent < tEnd);
System.out.println("[Result] " + count + " loops in " + dSeconds + "s");
}
}
```

View File

@@ -0,0 +1,371 @@
---
title: 常用的 Git 命令
date: 2025-02-11T20:43:29Z
lastmod: 2025-02-20T22:59:20Z
tags: [Git,命令,开发]
---
# 常用的 Git 命令
参考资料:[Git 大全 - Gitee.com](https://gitee.com/all-about-git)
## git config
### 编辑全局 Git 配置
```bash
git config --global -e
```
### 编辑当前 Git 配置
```bash
# 默认为当前,“--local”可以省略
git config --local -e
```
### 配置用户名和邮箱
```text
git config --global user.name '用户名'
git config --global user.email '邮箱'
```
### 开启 GPG 签名 commit
```text
git config --global commit.gpgsign true
```
注意:需要进行相关配置后才能正确开启([如何在 Gitee 上使用 GPG](https://gitee.com/help/articles/4248)、[使用 GPG 签名你的 commit](https://www.cnblogs.com/xueweihan/p/5430451.html))。
## git log
### 单行显示
`git log --oneline`
把每一条提交压缩到只有一行,仅保留短哈希、提价说明等最必要的信息,以一种更干净的方式查看提交。
### 显示差异
`git log -p`
展示带有改动内容的历史,可以看到每条提交都改动了哪些内容。
### 按作者过滤
添加参数 `--author` 以按作者过滤:
```text
git log --author='example'
```
Git 会使用正则来进行筛选和过滤,因此非准确的名字或大小写不一致也可以。
### 按时间过滤
添加参数 `--after``--before` 以按时间过滤。
2021-01-01 之后:
```text
git log --after='2021-01-01'
```
2022-01-01 到 2022-12-31 之间:
```text
git log --after='2022-01-01' --before='2022-12-31'
```
还可以使用以下格式:
```bash
# 仅今天
git log --after='today'
# 昨天以来
git log --after='yesterday'
# 一周前以来
git log --after='1 week ago'
# 十天之前
git log --before='10 day ago'
```
### 按提交信息过滤
添加参数 `--grep` 以使用正则表达式按提交信息过滤。
列出以“feat: ”开头的提交:
```text
git log --grep='^feat: ' --oneline
```
默认区分大小写,添加 `-i` 参数以不区分大小写:
```text
git log -i --grep='^feat: ' --oneline
```
多个条件:
```text
git log --oneline --grep='^feat: \|^refactor: '
```
### 列出某个文件的历史记录
单个文件:
```text
git log index.html
```
多个文件:
```text
git log index.html index.js
```
### 其它
```bash
# 仅列出合并
git log --merges
# 列出两个分支间的差异
git log main..develop
```
## git remote
刷新远程分支信息:
```text
git remote update origin --prune
```
## git reset
回退记录,保留文件:
```text
git reset --soft head^
```
## 其它命令
### 初始化 Git
在当前文件夹:
```text
git init
```
新建文件夹:
```text
git init [directory]
```
### 下载一个项目到本地
包含全部代码提交记录:
```text
git clone [url]
```
克隆指定分支:
```text
git clone -b [branch] [url]
git clone --branch [branch] [url]
```
只克隆最近一次 commit
```text
git clone --depth=1 [url]
```
### 分支
```bash
# 列出所有本地分支
git branch
# 列出所有远程分支
git branch -r
# 列出所有本地分支和远程分支
git branch -a
# 新建一个分支,但依然停留在当前分支
git branch [branch-name]
# 新建一个分支,并切换到该分支
git checkout -b [branch]
# 新建一个分支指向指定commit
git branch [branch] [commit]
# 新建一个分支,与指定的远程分支建立追踪关系
git branch --track [branch] [remote-branch]
# 切换到指定分支,并更新工作区
git checkout [branch-name]
# 切换到上一个分支
git checkout -
# 建立追踪关系,在现有分支与指定的远程分支之间
git branch --set-upstream [branch] [remote-branch]
# 合并指定分支到当前分支
git merge [branch]
# 选择一个commit合并进当前分支
git cherry-pick [commit]
# 删除分支
git branch -d [branch-name]
# 删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]
```
### 增加 / 删除文件
```bash
# 添加指定文件到暂存区
git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
git add [dir]
# 添加当前目录的所有文件到暂存区
git add .
# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
git add -p
# 删除工作区文件,并且将这次删除放入暂存区
git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
git rm --cached [file]
# 改名文件,并且将这个改名放入暂存区
git mv [file-original] [file-renamed]
```
### 代码提交
```bash
# 提交暂存区到仓库区
git commit -m [message]
# 提交暂存区的指定文件到仓库区
git commit [file1] [file2] ... -m [message]
# 提交工作区自上次 commit 之后的变化,直接到仓库区
git commit -a
# 提交时显示所有diff信息
git commit -v
# 使用一次新的 commit替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次 commit 的提交信息
git commit --amend -m [message]
# 重做上一次 commit并包括指定文件的新变化
git commit --amend [file1] [file2] ...
```
### GC
自动判断:
```text
git gc --auto
```
更积极地优化存储库:
```text
git gc --aggressive --prune=now
```
### 修改历史提交内容
1. 查看提交记录:
```text
git log --oneline
```
假设需要修改的提交记录为 `e2394c2`。
2. 通过 rebase 将 HEAD 回退到需要修改的位置前:
```text
git rebase e2394c2^ --interactive
```
3. 在打开的编辑界面中将需要修改的提交前的 `pick` 改为 `edit`,然后保存退出。
4. 修改文件,然后重新提交。
```text
git add example.html
git commit --amend
```
注意:提交使用的参数是 `--amend`。
5. 执行 `git rebase --continue` 命令逐步前进到最新的提交位置。
注意:修改文件后可能会产生冲突,解决冲突并提交后需要再次执行 `git rebase --continue` 命令以继续。
6. 提交到远程:
```text
git push origin -f
```
### 重置分支内容为另一分支
```text
git checkout 操作分支名
git reset --hard 另一分支名
git push --force origin 操作分支名
```
### 快速迁移仓库
#### 方式一,直接在原仓库的文件夹中操作,需要本地有全部分支和标签信息
```text
git push --all 新的仓库地址
git push --tags 新的仓库地址
git remote set-url origin 新的仓库地址
```
#### 方式二,在新的文件夹中操作,将原仓库的全部分支和标签拉取到本地然后推送到新仓库
```text
git clone --bare 原仓库地址
cd 仓库名称.git
git push --all 新的仓库地址
git push --tags 新的仓库地址
```
回到原仓库的文件夹,更新远程地址
```text
git remote set-url origin 新的仓库地址
```

View File

@@ -0,0 +1,25 @@
---
title: 使用 FFmpeg 合并多个视频文件
date: 2025-04-18T22:50:14Z
lastmod: 2025-07-03T21:58:34Z
tags: [FFmpeg,命令]
---
# 使用 FFmpeg 合并多个视频文件
## 操作步骤
1. 将合并的视频文件都放在同一个文件夹下,例如:`file_1.flv` `file_2.flv`
2. 新建一个 `video.txt` 文件,写入需要合并的视频文件名称,例如:
```plaintext
file 'file_1.flv'
file 'file_2.flv'
```
3. 执行 `ffmpeg` 命令:
```plaintext
ffmpeg -f concat -i video.txt -c copy result.flv
```

View File

@@ -0,0 +1,22 @@
---
title: 解决 Ant TreeSelect树选择组件可以使用键盘选中 disabled已禁用项的问题
date: 2025-02-11T20:55:27Z
lastmod: 2025-02-20T22:56:23Z
tags: [Web 前端,Vue.js]
---
# 解决 Ant TreeSelect树选择组件可以使用键盘选中 disabled已禁用项的问题
最近在使用 Ant Design VueV3.2.20)的 TreeSelect 组件时发现一个问题:`tree-data` 中部分数据的 `disabled` 属性设置为了 `true`,选项是“禁用”状态,无法通过鼠标点击选中,但是可以通过键盘 `↑` `↓` 键切换选项,按下 `Enter` 键选中。
![](assets/network-asset-3280690-20240119205120253-1872302841-20250212094903-w5wx2lc.png)
一开始还以为是 bug后来通过查阅 [文档](https://3x.antdv.com/components/tree-select-cn#API) 和测试发现,该组件还有一个名为 `selectable` 的属性,用于控制选项是否可选。
![](assets/network-asset-3280690-20240119210345097-1090947655-20250212094903-j752pdy.png)
仅将选项的 `selectable` 属性设置为 `false` 时,对应的选项虽然文本颜色不变,但是不可通过点击或键盘选中。
![](assets/network-asset-3280690-20240119210906388-775240144-20250212094903-n43dx0a.png)
因此,如果要实现选项变为灰色且不可选的效果,需要同时将选项的 `disabled` 属性设置为 `true`,将 `seletable` 属性设置为 `false`

View File

@@ -0,0 +1,51 @@
---
title: 解决钉钉通过短链接跳转小程序 scheme 的方式打开小程序时会有一个空白页面的问题
date: 2025-03-22T22:17:11Z
lastmod: 2025-03-22T22:32:06Z
tags: [钉钉,小程序,Web 前端]
---
# 解决钉钉通过短链接跳转小程序 scheme 的方式打开小程序时会有一个空白页面的问题
## 参考资料
- [小程序 scheme - 钉钉开放平台](https://open.dingtalk.com/document/orgapp/scheme-of-mini-programs)
- [关闭当前页面 - 钉钉开放平台](https://open.dingtalk.com/document/orgapp/close-the-current-page)
- [钉钉 JS-API | 钉钉宜搭·开发者中心](https://dingtalk-yida.github.io/developer-site/docs/api/dingAPI/)
## 页面代码
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小程序</title>
</head>
<body>
<div id="result"></div>
<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/2.15.5/dingtalk.open.js"></script>
<script>
window.addEventListener('load', function () {
let element = document.getElementById('test');
let url = 'dingtalk://dingtalkclient/action/open_micro_app?corpId=<corpId>&miniAppId=<miniAppId>&agentId=<agentId>&pVersion=1&packageType=1&page=pages/HomeView/Index';
// 打开小程序
location.href = url;
// 关闭当前页面
window.dd.biz.navigation.close({
onFail : function(error) {
element.textContent = `操作失败:${error}`;
},
onSuccess: function() {
element.textContent = `操作成功`;
},
});
});
</script>
</body>
</html>
```

View File

@@ -0,0 +1,59 @@
---
title: JavaScript 创建并初始化任意长度的数组
date: 2025-02-11T20:45:30Z
lastmod: 2025-02-20T22:59:05Z
tags: [JavaScript,Web 前端]
---
# JavaScript 创建并初始化任意长度的数组
## 直接定义
```javascript
var arr = [0, 0, 0, 0, 0]; // [0, 0, 0, 0, 0]
```
## 使用 push() 方法
```javascript
var arr = [];
for (let i = 0; i < 5; i++) {
arr.push(0);
}
// [0, 0, 0, 0, 0]
```
## 使用 Array 构造函数和 fill() 方法
```javascript
var arr = new Array(5); // [empty × 5]
arr.fill(0); // [0, 0, 0, 0, 0]
```
## 使用 Array 构造函数和数组展开
```javascript
var arr = [...new Array(5)]; // [undefined x 5]
```
```javascript
var arr = [...new Array(5).keys()]; // [0, 1, 2, 3, 4]
```
## 使用 Array.from()
> `Array.from(arrayLike[, mapFn[, thisArg]])`
```javascript
var arr = Array.from({length: 5}); // [undefined x 5]
```
```javascript
var arr = Array.from({length: 5}, () => 0); // [0, 0, 0, 0, 0]
```
```javascript
var arr = Array.from({length: 5}, (v, i) => (i + 1)); // [1, 2, 3, 4, 5]
```

View File

@@ -0,0 +1,31 @@
---
title: MIUI 拨号盘指令(代码)合集
date: 2025-02-11T20:55:13Z
lastmod: 2025-02-20T22:58:12Z
tags: [Android,小米,系统,HyperOS,MIUI]
---
# MIUI 拨号盘指令(代码)合集
参考:[www.coolapk.com](https://www.coolapk.com/feed/50745332?shareKey=YWVjMDU3MjRjNGFhNjU0NTg5MGM~&shareUid=1033889)
部分指令也支持在澎湃 OS 上使用
```text
*#*#54638#*#* 显示 5G 开关
*#*#8667#*#* 显示 VoNR 开关
*#*#86583#*#* 显示 VoLTE 选项
*#*#869434#*#* 显示 VoWiFi 选项
*#*#726633#*#* 开启 5G 网络模式选择
*#*#4636#*#* 手机信息锁网(查看手机信号强弱)
*#*#6484#*#* cit 工程模式
*#*#64663#*#* 手机功能测试
*#*#3223#*#* 开启 DC 调光选项
*#*#284#*#* 生成 bug 报告(可查看手机电池剩余容量)
*#*#6485#*#* 充电与电池相关的信息
*#*#76937#*#* 神隐模式
*#*#37263#*#* 帧率显示
*#*#3646633#*#* MTK 工程模式(可用于锁频段)
*#*#6666#*#* 退出演示模式(仅展示机可用)
*#06# 移动识别码 IMEI号
```

View File

@@ -0,0 +1,70 @@
---
title: Electron 或 Node.js 判断当前设备是否支持触摸屏
date: 2025-02-11T20:55:18Z
lastmod: 2025-02-20T22:57:46Z
tags: [JavaScript,Node.js,Electron,Web 前端]
---
# Electron 或 Node.js 判断当前设备是否支持触摸屏
在 Windows 系统上,可以通过注册表项 `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Tablet PC\IsTabletPC` 获取当前设备是否支持触摸屏。
`IsTabletPC` 的值大于 `0`,则表示支持触摸屏。
![](assets/network-asset-3280690-20231218143106045-884789034-20250212095032-064mwnt.png)
执行以下命令,可以查询到对应的注册表信息:
```text
REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Tablet PC" /v IsTabletPC
```
![](assets/network-asset-3280690-20231218143220384-31705905-20250212095033-ijhooak.png)
在 Node.js 中,可以通过执行上述命令,检测返回内容的方式,判断当前设备是否支持触摸屏(除了使用命令以外,还可以使用 [regedit](https://www.npmjs.com/package/regedit) 模块来获取注册表信息)。
示例代码:
```javascript
const cp = require('child_process');
const os = require('os');
/** 通过注册表信息检测当前设备是否支持触摸屏 */
function isTabletPC() {
if (os.platform() !== 'win32') {
throw new Error('仅支持 Windows 系统');
}
try {
// 命令返回内容:
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Tablet PC
// IsTabletPC REG_DWORD 0x0
// 命令内容
let cmd = 'REG QUERY "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\Tablet PC" /v IsTabletPC';
// 执行命令,转换返回内容为字符串
let result = cp.execSync(cmd).toString();
// 匹配IsTabletPC REG_DWORD 0x0
let matched = result.match(/IsTabletPC\s+REG_\w+\s+([0-9a-fx]+)/i);
// 提取0x0
let value = parseInt(matched ? matched[1] : null);
if (isNaN(value)) {
throw new Error('处理命令返回内容失败');
}
return (value !== 0);
} catch (error) {
console.error('检测触摸屏失败:');
console.error(String(error));
return false;
}
}
```

View File

@@ -0,0 +1,32 @@
---
title: 在用 uni-app 开发钉钉小程序的时候遇到一个奇怪的问题,发送请求拿不到返回的数据
date: 2025-02-11T20:55:40Z
lastmod: 2025-02-12T09:51:42Z
tags: [Web 前端,uni-app,小程序]
---
# 在用 uni-app 开发钉钉小程序的时候遇到一个奇怪的问题,发送请求拿不到返回的数据
今天我一位同事说用 uni-app 新开发的钉钉小程序里发送请求拿不到返回的数据看了下发现调试工具的“Network”栏里显示请求是发送成功的也有返回数据但是没触发请求的回调函数。
原本用的是 `luch-request` 这个库发送的请求,后来试了下 uni-app 内置的 `uni.request` 以及钉钉的 `dd.httpRequest` 都是一样不行。
![](assets/network-asset-3280690-20250211195905643-857124077-20250212095147-hh7adaw.png)
![](assets/network-asset-3280690-20250211195912787-1505858752-20250212095147-uz3bwp6.png)
![](assets/network-asset-3280690-20250211195916244-60822353-20250212095147-qbjotgw.png)
再后来重新创建一个新的项目试了一下是可以的,就觉得是版本的问题,然而把依赖项的版本改成和新建的一样了也不行。
![](assets/network-asset-3280690-20250211201344508-763916890-20250212095148-fijensv.png)
![](assets/network-asset-3280690-20250211201431143-429830505-20250212095148-k78zhfr.png)
看来看去,最后发现原来是这个 `options.value = options` 导致的(刚开始试过在 `App.vue` 里写请求也不行就没怎么在意这里),把它去掉就正常了。
![](assets/network-asset-3280690-20250211195920444-892198191-20250212095148-t4npl5e.png)
![](assets/network-asset-3280690-20250211195924953-725130489-20250212095148-ghq3gp3.png)
然后试了下在新创建的项目里写上这个 `options.value = options`,但是这个问题并没有出现。

View File

@@ -0,0 +1,50 @@
---
title: 在 Less 中使用与 Less 内置函数同名的原生 CSS 函数
date: 2025-02-11T20:47:17Z
lastmod: 2025-02-20T22:58:47Z
tags: [Web 前端,CSS,Less]
---
# 在 Less 中使用与 Less 内置函数同名的原生 CSS 函数
## 参考资料
- [calc() - CSS层叠样式表 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/calc)
- [Getting started - Escaping | Less.js](https://lesscss.org/#escaping)
- [Using The CSS Function calc() Inside The LESS CSS Preprocessor](https://www.bennadel.com/blog/4047-using-the-css-function-calc-inside-the-less-css-preprocessor.htm)
## 问题描述
在原生 CSS 中有以下的函数:`calc()``max()``min()` 等,而在 Less 中也有同名的函数,使用的时候可能会冲突,无法得到需要的结果。
对于 `calc()`Less 进行了处理,不会对数学表达式进行计算。
![image](assets/network-asset-3280690-20231008174044281-2111528680-20250212095025-si5gtja.png)
但如果其中包含变量或嵌套的函数,则会进行计算。例如 `calc()``max()` 嵌套使用的时候:
```css
.element {
width: calc(max(var(--min-width), var(--item-width) + var(--offset-width)) * 1px);
}
```
会出现以下报错:
```text
[less] Error evaluating function `max`: Operation on an invalid type
```
## 如何解决
这时可以使用 Less 的转义字符:在字符串前加上一个 `~` 符号,并将需要转义的字符串放在 `""``''` 中。
![image](assets/network-asset-3280690-20231008175104706-2112146183-20250212095025-306tf8i.png)
```css
.element {
width: ~"calc(max(var(--min-width), var(--item-width) + var(--offset-width)) * 1px)";
}
```
这样就可以使用任意的字符串作为属性或变量值了(当然,前提是使用正确的 CSS 语法)。

View File

@@ -0,0 +1,140 @@
---
title: Windows copy 命令的妙用(文件里藏文件、合并文件)
date: 2025-03-22T22:47:31Z
lastmod: 2025-03-29T18:30:31Z
tags: [Windows,命令,命令提示符]
---
# Windows copy 命令的妙用(文件里藏文件、合并文件)
## 说明和注意事项
- `copy` 是一个基础的DOS命令也许在今天已经很少有人使用但它仍然很实用。
- 本教程基于 Windows 10 x64 专业版。
## 本教程中的命令格式
- `copy /B 文件名1+文件名2 合并后的文件名`
- `copy /B *.扩展名 合并后的文件名`
## 使用前准备
运行“命令提示符”的常用方法:同时按下 Win 键和 R 键,输入 `cmd`,按下 Enter回车键。
![](assets/network-asset-86ee1078081673ad0b9ca441fe7a0b11171a6d41-20250322225306-oz8x6ci.png)
可能需要“以管理员身份运行”命令提示符,其中一种方法:
1. 右键任务栏
2. 点击任务管理器
3. 依次点击“文件”->“运行新任务”
![](assets/network-asset-093fdf1667a63580d5f567eb42b9d4d9bdef895d-20250322225306-4p3zlsx.png)
4. 输入 `cmd`,勾选“以系统管理权限创建此任务”,按下 `Enter`(回车)键
![](assets/network-asset-656fc603f1472b34ba2b61a46455b62301522103-20250322225306-8q8fkna.png)
## 把文件藏进文件里
### 说明
根据测试,可支持把文件放入格式包括但不限于 GIF、JPG、PNG、MP3、OGG、FLV、MP4 的文件中
使用的压缩软件2345 好压
### 步骤
1. 把要藏的文件压缩ZIP 格式)​
![](assets/network-asset-d15c5635ecc3cf0ede17b2f7b2536ed5cb905712-20250322225307-c3t9l6y.png)
2. 把压缩包和用于藏文件的文件放到同一文件夹
3. 使用 `cd /D` 命令 切换到第 2 步的文件夹
例如我第2步的文件夹在 `D:\Files\Desktop\教程-命令提示符-copy`
则执行命令 `cd /D D:\Files\Desktop\教程-命令提示符-copy`
看到左侧显示切换后的路径则更改成功​
![](assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225307-uvt75xh.png)
4. 使用 `copy /B` 命令 合并文件
命令格式:`copy /B 压缩包名+用于藏文件的文件的文件名 合并后的文件名`
文件的顺序不能错,否则合并后的文件无法正常打开
例如我的压缩包名为 `Files.zip`,用于藏文件的文件的文件名为 `Picture.png`
则执行命令 `copy /B Picture.png+Files.zip Picture-Merge.png`
![](assets/network-asset-2a0eb7a467fa83ad163d891247f1085d2acf4ecb-20250322225307-61zkyk9.png)
5. 若文件合并正常达到想要的效果则可以删除压缩包和原图片Files.zip、Picture.png
注意:
用于藏文件的文件的文件名与合并后的文件名的扩展名需要相同,但前缀名不能相同
例如 `Picture.png``Picture-Merge.png`
## 提取隐藏的文件
### 步骤​
1. 右键合并后的文件,点击重命名(可以直接选中合并后的文件,按下 F2
![](assets/network-asset-c87cca41e892c2efb3f7a88b7e8bd6cfab69cc10-20250322225308-0eosuty.png)
2. 在文件名后方加上 `.zip`
例如 `Picture-Merge.png` 改为 `Picture-Merge.png.zip`
![](assets/network-asset-238df0b3448a6d039f5c0c6ca110b7ca18424166-20250322225308-mhd4bcl.png)
然后双击文件,以压缩包方式打开即可​
![](assets/network-asset-eeef567b0064944c367dd97b8d9b6c44d9784a39-20250322225309-1swltad.png)
## 合并分段视频
### 说明
根据测试,可支持合并格式包括但不限于 MP4、TS、M2TS 的分段视频文件。
### 步骤
1. 把视频文件放于同一文件夹内
2. 视频文件的文件名需为 `序号.扩展名`
例如 `001.mp4 002.mp4 003.mp4 004.mp4 ......`
序号不能错,否则合并的视频内容会出错。
3. 使用 `cd /D` 命令 切换到要第 1 步的文件夹
例如我第 1 步的文件夹在 `D:\Files\Desktop\教程-命令提示符-copy`
则执行命令 `cd /D D:\Files\Desktop\教程-命令提示符-copy`
看到左侧显示切换后的路径则更改成功​
![](assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225309-usrho2d.png)
4. 使用 `copy /B 命令` 合并文件
命令格式:`copy /B *.扩展名 合并后的文件名`
例如我的分段视频文件名为 `Test_001.mp4 Test_002.mp4 Test_003.mp4 Test_004.mp4 ......`
则执行命令 `copy /B Test_*.mp4 Merge.mp4`
![](assets/network-asset-dcc928943584c1208aedb29c24af9cd602ed4c21-20250322225310-h4g4rip.png)
5. 测试合并后的视频文件能否正常播放,若能正常播放,则合并成功

View File

@@ -0,0 +1,414 @@
---
title: 通过画布Canvas实现 ZLMRTCClient 同一视频流多次显示时只拉取一次
date: 2025-02-11T20:55:33Z
lastmod: 2025-02-12T09:50:12Z
tags: [Web 前端,JavaScript,视频流]
---
# 通过画布Canvas实现 ZLMRTCClient 同一视频流多次显示时只拉取一次
## 效果预览
视频画面
![](assets/network-asset-3280690-20240530101840972-1174586846-20250212095018-fwdfsp8.png)
网络请求
![](assets/network-asset-3280690-20240530141533655-305870406-20250212095019-khia063.png)
## 代码实现
### ZLMRTCClient.js
> 当前使用的版本:
> `1.0.1` `Mon Mar 27 2023 19:11:59 GMT+0800`
首先需要修改 ZLMRTCClient.js 的代码,解决由于网络导致播放失败时无法触发 `WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED` 事件的问题。
修改前:
![](assets/network-asset-3280690-20240530141504979-885683022-20250212095019-3r2ppbf.png)
修改后:
![](assets/network-asset-3280690-20240530141734986-1223218799-20250212095019-hll9nwa.png)
修改内容:
![](assets/network-asset-3280690-20240530112246315-1527613127-20250212095019-x8acn44.png)
![](assets/network-asset-3280690-20240530102529860-1558126481-20250212095019-t8940to.png)
```javascript
// 添加 catch()
axios({
}).then(() => {
}).catch(() => {
// 网络异常时触发事件
this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, null);
});
```
### video-preview.js
```javascript
// 2024-05-30 初始版本
// 2024-06-06 优化视频是否存在调用检测方式
// 2025-01-08 优化逻辑,减少定时器的使用
import { v4 as uuidv4 } from 'uuid';
/**
* @typedef CacheItem
* @property {string} id 缓存项唯一 ID
* @property {HTMLElement|null} element Video 元素
* @property {boolean} isStopped 是否为主动停止播放
* @property {ZLMPlayer|null} player ZLM 播放器对象
* @property {number} timeCheck 最后一次检测关联画布的时间戳
* @property {number} timeResize 最后一次更新分辨率的时间戳
* @property {number} timeRender 最后一次渲染的时间戳
* @property {boolean} willStop 是否没有关联的画布,在下一次停止播放
*/
/** @typedef {InstanceType<typeof ZLMRTCClient.Endpoint>} ZLMPlayer */
/** 检测视频是否存在调用间隔 */
const INTERVAL_CHECK_VIDEO = 10000;
/** 画布渲染间隔 */
const INTERVAL_RENDER = 100;
/** 画布分辨率更新间隔 */
const INTERVAL_RESIZE = 1000;
/** 循环处理间隔 */
const INTERVAL_TIME = 100;
/** 模块名称 */
const PREFIX = '[video-preview]';
/** 重新播放间隔 */
const RESTART_TIMEOUT = 2000;
/** ZLM 客户端 */
const ZLMRTCClient = window.ZLMRTCClient;
/** 循环检测定时器 */
let loopTimer = null;
/**
* @desc 缓存信息列表
* @type {Record<string, CacheItem | null>}
*/
export const cacheList = {};
/**
* @description 初始化播放器
* @param {string} url 视频流地址
*/
function initPlayer(url = '') {
try {
if (!url) {
throw new Error('缺少 url 参数');
}
/**
* @description 初始化 & 更新数据
* @param {CacheItem} cache
*/
let fnInit = (cache) => {
// 创建 video 元素
let element = document.createElement('video');
// 开启自动播放
// 注:不能用 `setAttribute`,否则没效果
element.autoplay = true;
element.controls = false;
element.muted = true;
// 标记缓存 ID
element.setAttribute('data-video-id', cache.id);
// 添加到页面,否则无法播放
element.setAttribute('style', 'position: fixed; top: 0; left: 0; width: 0; height: 0');
document.body.appendChild(element);
// 初始化播放器
let player = new ZLMRTCClient.Endpoint({
// video 标签
element: element,
// 是否打印日志
debug: false,
// 流地址
zlmsdpUrl: url,
// 功能开关
audioEnable: false,
simulcast: false,
useCamera: false,
videoEnable: true,
// 仅查看,不推流
recvOnly: true,
// 推流分辨率
resolution: { w: 1280, h: 720 },
// 文本收发
// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send
usedatachannel: false,
});
// // 监听事件ICE 协商出错
// player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, function () {
// console.error(PREFIX, 'ICE 协商出错')
// });
// 监听事件:获取到了远端流,可以播放
player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS, function (event) {
console.log(PREFIX, '播放成功', event.streams);
});
// 监听事件offer anwser 交换失败
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, function (event) {
console.error(PREFIX, 'offer anwser 交换失败', event);
// 当前没有主动停止
if (!cache.isStopped) {
// 停止播放
stopPlayer(player, element);
// 重新播放
setTimeout(() => {
fnInit(cache);
}, RESTART_TIMEOUT);
}
});
// 监听事件RTC 状态变化
player.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, function (state) {
console.log(PREFIX, 'RTC 状态变化', state);
// 状态为已断开
if (state === 'disconnected' && !cache.isStopped) {
// 停止播放
stopPlayer(player, element);
// 重新播放
setTimeout(() => {
fnInit(cache);
}, RESTART_TIMEOUT);
}
});
cache.element = element;
cache.player = player;
};
let cacheItem = cacheList[url];
if (cacheItem) {
return cacheItem;
} else {
cacheItem = {
id: uuidv4(),
element: null,
isStopped: false,
player: null,
timeCheck: 0,
timeRender: 0,
timeResize: 0,
willStop: false,
};
}
console.log(PREFIX, '初始化', cacheItem);
// 初始化
fnInit(cacheItem);
// 添加缓存信息
cacheList[url] = cacheItem;
return cacheItem;
} catch (error) {
console.error(PREFIX, '初始化播放器失败:');
console.error(error);
return null;
}
}
/**
* @description 停止播放
* @param {ZLMPlayer} player
* @param {HTMLVideoElement} element
*/
function stopPlayer(player, element) {
try {
if (player) {
console.debug(PREFIX, 'stopPlayer - 停止播放');
player.close();
}
if (element instanceof HTMLVideoElement) {
console.debug(PREFIX, 'stopPlayer - 移除元素');
element.remove();
}
return true;
} catch (error) {
console.error(PREFIX, '停止播放失败:');
console.error(error);
return false;
}
}
/**
* @description 获取视频画面 canvas
* @param {string} url
*/
export function getVideoCanvas(url = '') {
try {
if (!url) {
throw new Error('缺少 url 参数');
}
let cacheItem = initPlayer(url);
let canvas = document.createElement('canvas');
if (cacheItem) {
// 标记缓存 ID
canvas.setAttribute('data-cache-id', cacheItem.id);
} else {
throw new Error('获取缓存数据失败');
}
// 背景填充
canvas.style.backgroundPosition = 'center center';
canvas.style.backgroundSize = '100% 100%';
return canvas;
} catch (error) {
console.error(PREFIX, '获取 canvas 失败:');
console.error(error);
return null;
}
}
/** 开始循环处理视频 */
export function timerStart() {
timerStop();
loopTimer = setInterval(() => {
for (let url in cacheList) {
let cacheItem = cacheList[url];
let currTime = Date.now();
if (!cacheItem) {
continue;
}
let cacheId = cacheItem.id;
let videoElement = cacheItem.element;
/**
* @desc 画布元素列表
* @type {NodeListOf<HTMLCanvasElement>}
*/
let canvasList = document.querySelectorAll(`[data-cache-id="${cacheId}"]`);
let foundCanvas = canvasList.length > 0;
// 渲染画面
if (currTime - cacheItem.timeRender >= INTERVAL_RENDER) {
cacheItem.timeRender = currTime;
canvasList.forEach((canvas) => {
let ctx = canvas.getContext('2d');
let cWidth = canvas.width;
let cHeight = canvas.height;
if (document.contains(videoElement)) {
ctx.drawImage(videoElement, 0, 0, cWidth, cHeight);
}
canvas.style.backgroundImage = '';
});
}
// 更新画布分辨率
if (currTime - cacheItem.timeResize >= INTERVAL_RESIZE) {
cacheItem.timeResize = currTime;
canvasList.forEach((canvas) => {
let parent = canvas.parentElement;
let rect = parent ? parent.getBoundingClientRect() : null;
if (rect) {
let cWidth = Math.round(canvas.width);
let cHeight = Math.round(canvas.height);
let rWidth = Math.round(rect.width);
let rHeight = Math.round(rect.height);
if (cWidth !== rWidth || cHeight !== rHeight) {
// 更新画布分辨率前将画面设置为背景,防止闪烁
canvas.style.backgroundImage = `url(${canvas.toDataURL('image/png')})`;
// 更新画布分辨率(将会自动清空画布内容)
canvas.width = rWidth;
canvas.height = rHeight;
}
}
});
}
// 检测是否存在与视频关联的画布
if (currTime - cacheItem.timeCheck >= INTERVAL_CHECK_VIDEO) {
cacheItem.timeCheck = currTime;
// 当前存在关联的画布,不处理
if (foundCanvas) {
cacheItem.willStop = false;
return;
}
// 若当前不存在关联的画布,检测上一次的查找结果
if (cacheItem.willStop) {
console.debug(PREFIX, '视频没有被调用,停止播放', { url });
cacheItem.isStopped = true;
stopPlayer(cacheItem.player, cacheItem.element);
cacheList[url] = null;
} else {
cacheItem.willStop = true;
}
}
}
}, INTERVAL_TIME);
}
/** 停止循环处理视频 */
export function timerStop() {
if (loopTimer) {
clearInterval(loopTimer);
loopTimer = null;
}
}
JAVASCRIPT 折叠 复制 全屏
```
使用时只需要调用 `getVideoCanvas()` 获取 `canvas`,然后插入到 DOM 即可,画布会自适应父元素宽高。