diff --git a/docs/.vitepress/content-list.json b/docs/.vitepress/content-list.json index c113525..c9ca8ce 100644 --- a/docs/.vitepress/content-list.json +++ b/docs/.vitepress/content-list.json @@ -1,4 +1,88 @@ [ + { + "created-at": "2025-07-02 22:20", + "is-hide": "", + "slug": "bluefox-nx1-flash-gsi", + "title": "蓝狐(BLUEFOX)NX1 刷 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": "", diff --git a/docs/content/assets/fac24876d751521ee3994c178b309ba6-20250304100517-0vs9ngo.png b/docs/content/assets/fac24876d751521ee3994c178b309ba6-20250304100517-0vs9ngo.png new file mode 100644 index 0000000..c1b7fca Binary files /dev/null and b/docs/content/assets/fac24876d751521ee3994c178b309ba6-20250304100517-0vs9ngo.png differ diff --git a/docs/content/assets/image-20250304100243-rzjrgon.png b/docs/content/assets/image-20250304100243-rzjrgon.png new file mode 100644 index 0000000..3ecac50 Binary files /dev/null and b/docs/content/assets/image-20250304100243-rzjrgon.png differ diff --git a/docs/content/assets/image-20250304100347-3im6ft6.png b/docs/content/assets/image-20250304100347-3im6ft6.png new file mode 100644 index 0000000..83aefea Binary files /dev/null and b/docs/content/assets/image-20250304100347-3im6ft6.png differ diff --git a/docs/content/assets/image-20250702224334-vyg3sj8.png b/docs/content/assets/image-20250702224334-vyg3sj8.png new file mode 100644 index 0000000..352e3cc Binary files /dev/null and b/docs/content/assets/image-20250702224334-vyg3sj8.png differ diff --git a/docs/content/assets/image-20250702232252-k8k0r2r.png b/docs/content/assets/image-20250702232252-k8k0r2r.png new file mode 100644 index 0000000..dffdfce Binary files /dev/null and b/docs/content/assets/image-20250702232252-k8k0r2r.png differ diff --git a/docs/content/assets/image-20250702233608-t3r8wve.png b/docs/content/assets/image-20250702233608-t3r8wve.png new file mode 100644 index 0000000..e8d87c7 Binary files /dev/null and b/docs/content/assets/image-20250702233608-t3r8wve.png differ diff --git a/docs/content/assets/network-asset-093fdf1667a63580d5f567eb42b9d4d9bdef895d-20250322225306-4p3zlsx.png b/docs/content/assets/network-asset-093fdf1667a63580d5f567eb42b9d4d9bdef895d-20250322225306-4p3zlsx.png new file mode 100644 index 0000000..8206130 Binary files /dev/null and b/docs/content/assets/network-asset-093fdf1667a63580d5f567eb42b9d4d9bdef895d-20250322225306-4p3zlsx.png differ diff --git a/docs/content/assets/network-asset-238df0b3448a6d039f5c0c6ca110b7ca18424166-20250322225308-mhd4bcl.png b/docs/content/assets/network-asset-238df0b3448a6d039f5c0c6ca110b7ca18424166-20250322225308-mhd4bcl.png new file mode 100644 index 0000000..c04bf69 Binary files /dev/null and b/docs/content/assets/network-asset-238df0b3448a6d039f5c0c6ca110b7ca18424166-20250322225308-mhd4bcl.png differ diff --git a/docs/content/assets/network-asset-2a0eb7a467fa83ad163d891247f1085d2acf4ecb-20250322225307-61zkyk9.png b/docs/content/assets/network-asset-2a0eb7a467fa83ad163d891247f1085d2acf4ecb-20250322225307-61zkyk9.png new file mode 100644 index 0000000..e8010ee Binary files /dev/null and b/docs/content/assets/network-asset-2a0eb7a467fa83ad163d891247f1085d2acf4ecb-20250322225307-61zkyk9.png differ diff --git a/docs/content/assets/network-asset-3280690-20231008174044281-2111528680-20250212095025-si5gtja.png b/docs/content/assets/network-asset-3280690-20231008174044281-2111528680-20250212095025-si5gtja.png new file mode 100644 index 0000000..61cab40 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20231008174044281-2111528680-20250212095025-si5gtja.png differ diff --git a/docs/content/assets/network-asset-3280690-20231008175104706-2112146183-20250212095025-306tf8i.png b/docs/content/assets/network-asset-3280690-20231008175104706-2112146183-20250212095025-306tf8i.png new file mode 100644 index 0000000..c0fca26 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20231008175104706-2112146183-20250212095025-306tf8i.png differ diff --git a/docs/content/assets/network-asset-3280690-20231218143106045-884789034-20250212095032-064mwnt.png b/docs/content/assets/network-asset-3280690-20231218143106045-884789034-20250212095032-064mwnt.png new file mode 100644 index 0000000..3720ea6 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20231218143106045-884789034-20250212095032-064mwnt.png differ diff --git a/docs/content/assets/network-asset-3280690-20231218143220384-31705905-20250212095033-ijhooak.png b/docs/content/assets/network-asset-3280690-20231218143220384-31705905-20250212095033-ijhooak.png new file mode 100644 index 0000000..fa8f520 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20231218143220384-31705905-20250212095033-ijhooak.png differ diff --git a/docs/content/assets/network-asset-3280690-20240119205120253-1872302841-20250212094903-w5wx2lc.png b/docs/content/assets/network-asset-3280690-20240119205120253-1872302841-20250212094903-w5wx2lc.png new file mode 100644 index 0000000..1112a84 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240119205120253-1872302841-20250212094903-w5wx2lc.png differ diff --git a/docs/content/assets/network-asset-3280690-20240119210345097-1090947655-20250212094903-j752pdy.png b/docs/content/assets/network-asset-3280690-20240119210345097-1090947655-20250212094903-j752pdy.png new file mode 100644 index 0000000..67eeab8 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240119210345097-1090947655-20250212094903-j752pdy.png differ diff --git a/docs/content/assets/network-asset-3280690-20240119210906388-775240144-20250212094903-n43dx0a.png b/docs/content/assets/network-asset-3280690-20240119210906388-775240144-20250212094903-n43dx0a.png new file mode 100644 index 0000000..d383c51 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240119210906388-775240144-20250212094903-n43dx0a.png differ diff --git a/docs/content/assets/network-asset-3280690-20240530101840972-1174586846-20250212095018-fwdfsp8.png b/docs/content/assets/network-asset-3280690-20240530101840972-1174586846-20250212095018-fwdfsp8.png new file mode 100644 index 0000000..3434e2b Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240530101840972-1174586846-20250212095018-fwdfsp8.png differ diff --git a/docs/content/assets/network-asset-3280690-20240530102529860-1558126481-20250212095019-t8940to.png b/docs/content/assets/network-asset-3280690-20240530102529860-1558126481-20250212095019-t8940to.png new file mode 100644 index 0000000..450b779 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240530102529860-1558126481-20250212095019-t8940to.png differ diff --git a/docs/content/assets/network-asset-3280690-20240530112246315-1527613127-20250212095019-x8acn44.png b/docs/content/assets/network-asset-3280690-20240530112246315-1527613127-20250212095019-x8acn44.png new file mode 100644 index 0000000..84130ed Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240530112246315-1527613127-20250212095019-x8acn44.png differ diff --git a/docs/content/assets/network-asset-3280690-20240530141504979-885683022-20250212095019-3r2ppbf.png b/docs/content/assets/network-asset-3280690-20240530141504979-885683022-20250212095019-3r2ppbf.png new file mode 100644 index 0000000..166b2ca Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240530141504979-885683022-20250212095019-3r2ppbf.png differ diff --git a/docs/content/assets/network-asset-3280690-20240530141533655-305870406-20250212095019-khia063.png b/docs/content/assets/network-asset-3280690-20240530141533655-305870406-20250212095019-khia063.png new file mode 100644 index 0000000..3f0d008 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240530141533655-305870406-20250212095019-khia063.png differ diff --git a/docs/content/assets/network-asset-3280690-20240530141734986-1223218799-20250212095019-hll9nwa.png b/docs/content/assets/network-asset-3280690-20240530141734986-1223218799-20250212095019-hll9nwa.png new file mode 100644 index 0000000..e0e41bb Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20240530141734986-1223218799-20250212095019-hll9nwa.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211195905643-857124077-20250212095147-hh7adaw.png b/docs/content/assets/network-asset-3280690-20250211195905643-857124077-20250212095147-hh7adaw.png new file mode 100644 index 0000000..cabe946 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211195905643-857124077-20250212095147-hh7adaw.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211195912787-1505858752-20250212095147-uz3bwp6.png b/docs/content/assets/network-asset-3280690-20250211195912787-1505858752-20250212095147-uz3bwp6.png new file mode 100644 index 0000000..799ace9 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211195912787-1505858752-20250212095147-uz3bwp6.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211195916244-60822353-20250212095147-qbjotgw.png b/docs/content/assets/network-asset-3280690-20250211195916244-60822353-20250212095147-qbjotgw.png new file mode 100644 index 0000000..5be21dc Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211195916244-60822353-20250212095147-qbjotgw.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211195920444-892198191-20250212095148-t4npl5e.png b/docs/content/assets/network-asset-3280690-20250211195920444-892198191-20250212095148-t4npl5e.png new file mode 100644 index 0000000..0e98af2 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211195920444-892198191-20250212095148-t4npl5e.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211195924953-725130489-20250212095148-ghq3gp3.png b/docs/content/assets/network-asset-3280690-20250211195924953-725130489-20250212095148-ghq3gp3.png new file mode 100644 index 0000000..2a3ae07 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211195924953-725130489-20250212095148-ghq3gp3.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211201344508-763916890-20250212095148-fijensv.png b/docs/content/assets/network-asset-3280690-20250211201344508-763916890-20250212095148-fijensv.png new file mode 100644 index 0000000..3cfa447 Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211201344508-763916890-20250212095148-fijensv.png differ diff --git a/docs/content/assets/network-asset-3280690-20250211201431143-429830505-20250212095148-k78zhfr.png b/docs/content/assets/network-asset-3280690-20250211201431143-429830505-20250212095148-k78zhfr.png new file mode 100644 index 0000000..b6f4a5f Binary files /dev/null and b/docs/content/assets/network-asset-3280690-20250211201431143-429830505-20250212095148-k78zhfr.png differ diff --git a/docs/content/assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225307-uvt75xh.png b/docs/content/assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225307-uvt75xh.png new file mode 100644 index 0000000..aaeb0f7 Binary files /dev/null and b/docs/content/assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225307-uvt75xh.png differ diff --git a/docs/content/assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225309-usrho2d.png b/docs/content/assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225309-usrho2d.png new file mode 100644 index 0000000..aaeb0f7 Binary files /dev/null and b/docs/content/assets/network-asset-3caa37ede7d882820845bb67ec14db6b611430d1-20250322225309-usrho2d.png differ diff --git a/docs/content/assets/network-asset-656fc603f1472b34ba2b61a46455b62301522103-20250322225306-8q8fkna.png b/docs/content/assets/network-asset-656fc603f1472b34ba2b61a46455b62301522103-20250322225306-8q8fkna.png new file mode 100644 index 0000000..980dfe9 Binary files /dev/null and b/docs/content/assets/network-asset-656fc603f1472b34ba2b61a46455b62301522103-20250322225306-8q8fkna.png differ diff --git a/docs/content/assets/network-asset-86ee1078081673ad0b9ca441fe7a0b11171a6d41-20250322225306-oz8x6ci.png b/docs/content/assets/network-asset-86ee1078081673ad0b9ca441fe7a0b11171a6d41-20250322225306-oz8x6ci.png new file mode 100644 index 0000000..6a9b68c Binary files /dev/null and b/docs/content/assets/network-asset-86ee1078081673ad0b9ca441fe7a0b11171a6d41-20250322225306-oz8x6ci.png differ diff --git a/docs/content/assets/network-asset-c87cca41e892c2efb3f7a88b7e8bd6cfab69cc10-20250322225308-0eosuty.png b/docs/content/assets/network-asset-c87cca41e892c2efb3f7a88b7e8bd6cfab69cc10-20250322225308-0eosuty.png new file mode 100644 index 0000000..b10eb2b Binary files /dev/null and b/docs/content/assets/network-asset-c87cca41e892c2efb3f7a88b7e8bd6cfab69cc10-20250322225308-0eosuty.png differ diff --git a/docs/content/assets/network-asset-d15c5635ecc3cf0ede17b2f7b2536ed5cb905712-20250322225307-c3t9l6y.png b/docs/content/assets/network-asset-d15c5635ecc3cf0ede17b2f7b2536ed5cb905712-20250322225307-c3t9l6y.png new file mode 100644 index 0000000..deb3d28 Binary files /dev/null and b/docs/content/assets/network-asset-d15c5635ecc3cf0ede17b2f7b2536ed5cb905712-20250322225307-c3t9l6y.png differ diff --git a/docs/content/assets/network-asset-dcc928943584c1208aedb29c24af9cd602ed4c21-20250322225310-h4g4rip.png b/docs/content/assets/network-asset-dcc928943584c1208aedb29c24af9cd602ed4c21-20250322225310-h4g4rip.png new file mode 100644 index 0000000..db4b02d Binary files /dev/null and b/docs/content/assets/network-asset-dcc928943584c1208aedb29c24af9cd602ed4c21-20250322225310-h4g4rip.png differ diff --git a/docs/content/assets/network-asset-eeef567b0064944c367dd97b8d9b6c44d9784a39-20250322225309-1swltad.png b/docs/content/assets/network-asset-eeef567b0064944c367dd97b8d9b6c44d9784a39-20250322225309-1swltad.png new file mode 100644 index 0000000..ae7081b Binary files /dev/null and b/docs/content/assets/network-asset-eeef567b0064944c367dd97b8d9b6c44d9784a39-20250322225309-1swltad.png differ diff --git a/docs/content/bluefox-nx1-flash-gsi.md b/docs/content/bluefox-nx1-flash-gsi.md new file mode 100644 index 0000000..49695e1 --- /dev/null +++ b/docs/content/bluefox-nx1-flash-gsi.md @@ -0,0 +1,77 @@ +--- +title: 蓝狐(BLUEFOX)NX1 刷 GSI 系统 +date: 2025-07-02T22:27:40Z +lastmod: 2025-07-03T21:54:29Z +tags: [Android,GSI,刷机,系统] +--- + +# 蓝狐(BLUEFOX)NX1 刷 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)​ + +‍ diff --git a/docs/content/calculation-test-code.md b/docs/content/calculation-test-code.md new file mode 100644 index 0000000..ad6965e --- /dev/null +++ b/docs/content/calculation-test-code.md @@ -0,0 +1,67 @@ +--- +title: 运算速度测试代码 +date: 2025-02-11T20:55:04Z +lastmod: 2025-02-20T22:58:29Z +tags: [测试,代码片段] +--- + +# 运算速度测试代码 + +#### C + +```c +#include +#include +#include +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"); + } +} +``` diff --git a/docs/content/common-usage-git-commands.md b/docs/content/common-usage-git-commands.md new file mode 100644 index 0000000..7d53bde --- /dev/null +++ b/docs/content/common-usage-git-commands.md @@ -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 新的仓库地址 +``` + +‍ diff --git a/docs/content/ffmpeg-merge-video-files.md b/docs/content/ffmpeg-merge-video-files.md new file mode 100644 index 0000000..eceb398 --- /dev/null +++ b/docs/content/ffmpeg-merge-video-files.md @@ -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 + ``` + +‍ diff --git a/docs/content/fix-ant-tree-select-can-select-disabled-item.md b/docs/content/fix-ant-tree-select-can-select-disabled-item.md new file mode 100644 index 0000000..bdf96a3 --- /dev/null +++ b/docs/content/fix-ant-tree-select-can-select-disabled-item.md @@ -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 Vue(V3.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`。 diff --git a/docs/content/fix-redirect-dingtalk-miniprogram-scheme-blank-page.md b/docs/content/fix-redirect-dingtalk-miniprogram-scheme-blank-page.md new file mode 100644 index 0000000..5e38c46 --- /dev/null +++ b/docs/content/fix-redirect-dingtalk-miniprogram-scheme-blank-page.md @@ -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 + + + + + + 小程序 + + +
+ + + + +``` + +‍ diff --git a/docs/content/javascript-define-and-init-array.md b/docs/content/javascript-define-and-init-array.md new file mode 100644 index 0000000..e3217e5 --- /dev/null +++ b/docs/content/javascript-define-and-init-array.md @@ -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] +``` diff --git a/docs/content/miui-dial-plate-commands.md b/docs/content/miui-dial-plate-commands.md new file mode 100644 index 0000000..7c56f68 --- /dev/null +++ b/docs/content/miui-dial-plate-commands.md @@ -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号 +``` diff --git a/docs/content/nodejs-check-is-tablet-pc.md b/docs/content/nodejs-check-is-tablet-pc.md new file mode 100644 index 0000000..a9abdb7 --- /dev/null +++ b/docs/content/nodejs-check-is-tablet-pc.md @@ -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; + } + +} +``` diff --git a/docs/content/post-2025-02-11-1.md b/docs/content/post-2025-02-11-1.md new file mode 100644 index 0000000..2ee753d --- /dev/null +++ b/docs/content/post-2025-02-11-1.md @@ -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`,但是这个问题并没有出现。 diff --git a/docs/content/use-css-function-in-less.md b/docs/content/use-css-function-in-less.md new file mode 100644 index 0000000..cc3da20 --- /dev/null +++ b/docs/content/use-css-function-in-less.md @@ -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 语法)。 diff --git a/docs/content/windows-copy-command-usage.md b/docs/content/windows-copy-command-usage.md new file mode 100644 index 0000000..855181d --- /dev/null +++ b/docs/content/windows-copy-command-usage.md @@ -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. 测试合并后的视频文件能否正常播放,若能正常播放,则合并成功 + +‍ diff --git a/docs/content/zlm-rtc-client-multi-video-pull-once.md b/docs/content/zlm-rtc-client-multi-video-pull-once.md new file mode 100644 index 0000000..aea7c88 --- /dev/null +++ b/docs/content/zlm-rtc-client-multi-video-pull-once.md @@ -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} 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} + */ +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} + */ + 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 即可,画布会自适应父元素宽高。