1Panel管理后台使用Let'sEncrypt自动续签阿里云域名HTTPS证书

在1Panel后台菜单选择【网站/证书/Acme账户】

然后创建Acme账户

  • 邮箱(输入你自己的邮箱就行)

去阿里云创建RAM用户获取AccessKey和SecretKey

可以参照这个文档:https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair

注意!创建的用户一定要记得添加权限,不然等下申请证书会没权限

在1Panel后台菜单选择【网站/证书/DNS账户】

然后创建DNS账户

  • 名称(随意)
  • Access key(需要在阿里云去创建)
  • Secret key(需要在阿里云去创建)

在1Panel后台菜单选择【网站/证书/申请证书】

这下可以申请证书了

  • 主域名(这里填写你的主域名)
  • 其他域名(这里填写你的子域名)
  • Acme账户(选择你刚才创建的Acme账户)
  • DNS账户(选择你刚才创建的DNS账户)

点击确认 申请!

大概等待5分钟左右就可以申请成功了

这种方式支持自动续签,再也不用阿里云恶心的3个月免费证书了

简单实现一个useSignal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const useSignal = (initialState) => {
const [state, setState] = useState(initialState)
const stateRef = useRef(initialState)

const getState = () => stateRef.current

const editState = (newState) => {
setState(newState)

if (typeof newState === 'function'){
stateRef.current = newState(stateRef.current)
} else {
stateRef.current = newState
}
}

return [state, editState, getState]
}

2024-05-16 一个Npm包"hanzi-utils",提供了一些汉字相关的处理函数.md

汉字处理工具库

简介

本库提供了一系列用于处理汉字(中文字符)的JavaScript函数。这些函数包括查询汉字的异体字、发音、部首、笔画、获取所有Unicode汉字、Unicode编码与汉字字符的转换以及计算汉字字符串的长度等。

安装

1
npm i @vearvip/hanzi-utils

使用

引入模块

首先,确保你已经将@vearvip/hanzi-utils引入到你的项目中。

查询汉字的异体字

1
2
3
4
5
6
7
import { queryVariant } from '@vearvip/hanzi-utils';

const character = '说';
const variants = queryVariant(character);

console.log(`"${character}" 的异体字有:`, variants);
// "说" 的异体字有: [ "說", "説" ]

查询汉字的部首、笔画

1
2
3
4
5
6
7
import { queryRadicalStrokeCount } from '@vearvip/hanzi-utils';

const hanzi = '额';
const result = queryRadicalStrokeCount(hanzi);
console.log(result); // 输出:[ "页", 15 ]

// 解释:汉字"额"的部首是"页",总笔画数为15。

查询汉字在多种方言和语言中的读音,当前支持以下方言/语言的读音查询:

  • 普通话(mandarin)
  • 粤语(cantonese)
  • 日语音读(japaneseOn)
  • 日语训读(japaneseKun)
  • 韩语(korean)
  • 越南语(vietnamese)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { queryReading } from '@vearvip/hanzi-utils';

// 查询汉字“一”的粤语、日语、韩语、普通话及越南语读音
const readings = queryReading('一');
console.log(readings);
/*
输出:
{
kCantonese: "jat1",
kJapaneseKun: "HITOTSU HITOTABI HAJIME",
kJapaneseOn: "ICHI ITSU",
kKorean: "IL",
kMandarin: "yī",
kVietnamese: "nhất",
}
*/

获取所有Unicode的汉字(截止Unicode 版本:15.1,本函数可返回99142个汉字,实际只有99139个,因为部首扩展:2E9A 是空码位,兼容汉字:FA6E、FA6F 是空码位。)

1
2
3
4
5
6
7
import { getAllHanziCharacters } from '@vearvip/hanzi-utils';

const allHanzi = getAllHanziCharacters();
console.log(allHanzi.slice(0, 10));
// [ "一", "丁", "丂", "七", "丄", "丅", "丆", "万", "丈", "三"]
console.log(allHanzi.length);
// 99142

Unicode编码与汉字字符的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { 
unicode2Hanzi,
hanzi2Unicode,
} from '@vearvip/hanzi-utils';

// Unicode编码转汉字字符
const hexCode = '4E2D'; // '中'的Unicode编码
const hanzi = unicode2Hanzi(hexCode);
console.log(hanzi); // 输出:中

// 汉字字符转Unicode编码
const anotherHanzi = '字';
const unicode = hanzi2Unicode(anotherHanzi);
console.log(unicode); // 输出:5B57

计算汉字字符串的长度

1
2
3
4
5
6
import { unicodeLengthIgnoreSequence } from '@vearvip/hanzi-utils';

const str = '豕型';
const strLength = unicodeLengthIgnoreSequence(str);
console.log(str.length); // 输出:4
console.log(strLength); // 输出:2

函数检查一个字符是否是汉字

1
2
3
4
5
import { isHanzi } from '@vearvip/hanzi-utils';

console.log(isHanzi('汉')); // true
console.log(isHanzi('A')); // false
console.log(isHanzi('𠀀')); // true

提取字符串中的汉字

1
2
3
import { extractHanzi } from '@vearvip/hanzi-utils';

console.log(extractHanzi('Hello, 世界! 𠀀✨ 你好,世界!')); // 输出: ["世", "界", "𠀀", "你", "好", "世", "界"]

注意事项

  • 本库中的函数假设你正在使用支持ES6及以上语法的JavaScript环境。
  • unicodeLengthIgnoreSequence使用了Intl.Segmenter,请确保你的JavaScript环境支持该API(通常在较新的浏览器和Node.js版本中可用)。
  • 本库的代码未经优化,可能不适用于大型项目或需要高性能的场景。如有需要,请进行适当的性能优化。

贡献

如果你发现任何错误或想要提出改进建议,请随时通过GitHub或其他方式联系我。欢迎任何形式的贡献!

2023-11-10 JS复制表格到Excel.md

简单示例,这里用的是Fusion的Table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { Table, Button } from "@alifd/next";

function App() {

const dataSource = [
{
item: '棉鞋1',
img: 'https://img.alicdn.com/imgextra/i3/O1CN01oa2oWk1THYOTjD8um_!!6000000002357-2-tps-110-110.png',
sku: '34码',
amount: 999,
induction: {
a: 10,
b: 12
}
},
{
item: '棉鞋1',
img: 'https://img.alicdn.com/imgextra/i2/O1CN010URR3q1qa5aKdpsOh_!!6000000005511-2-tps-110-110.png',
sku: '35码',
amount: 88,
induction: {
a: 23,
b: 77
}
},
{
item: '拖鞋1',
img: 'https://img.alicdn.com/imgextra/i4/O1CN01xUEUfU1TCyQuxXEps_!!6000000002347-2-tps-110-110.png',
sku: '38码',
amount: 0,
induction: {
a: 0,
b: 100
}
},
{
item: '拖鞋2',
img: 'https://img.alicdn.com/imgextra/i2/O1CN01wtSTlr28OmrVgPKQ9_!!6000000007923-2-tps-200-200.png',
sku: '35码',
amount: 123,
induction: {
a: 90,
b: 8
}
},

]
const cellProps = (rowIndex, colIndex) => {
if (rowIndex === 0 && colIndex === 0) {
return {
// take 3 rows's space
rowSpan: 2
};
}

};

const convertTableToExcel = (
id = undefined, // Fusion Table 的id
filterHeader = false, // 复制时是否滤掉表头
) => {
return new Promise((resolve, reject) => {
try {
let htmlString = document.getElementById(id).innerHTML
const filterDOM = (selector) => {
// 创建一个临时div元素
var tempDiv = document.createElement('div');
// 将HTML字符串赋值给临时div的innerHTML属性
tempDiv.innerHTML = htmlString;
// 使用querySelectorAll方法获取所有要过滤的元素,并将其从临时div中移除
tempDiv.querySelectorAll(selector).forEach(function (element) {
element.remove();
});
// 获取过滤后的HTML字符串
return tempDiv.innerHTML;
}
htmlString = filterDOM('.next-table-lock-left')
if (filterHeader) {
htmlString = filterDOM('thead')
}

console.log(htmlString)
const type = "text/plain";
const blob = new Blob([htmlString], { type });
const data = [new ClipboardItem({ [type]: blob })];

navigator.clipboard.write(data).then(
() => resolve(),
(error) => reject(error),
);
} catch (error) {
reject(error)
}
})
}
return (
<div>
<Button type="primary" onClick={() => convertTableToExcel('table1')} style={{ marginRight: 10 }}>复制</Button>
<Button type="primary" onClick={() => convertTableToExcel('table1', true)}>复制(不含表头)</Button>
<Table id="table1" dataSource={dataSource} cellProps={cellProps}>
<Table.Column title="商品" width={300} dataIndex="item" lock="left" />
<Table.ColumnGroup title="信息">
<Table.Column title="规格" width={200} dataIndex="sku" />
{/* <Table.Column title="图片" width={200} dataIndex="img" cell={(val) => <img src={val} />} /> */}
<Table.Column title="库存" width={200} dataIndex="amount" />
<Table.Column title="说明" width={200} dataIndex="induction" cell={(val) => {
return <div>
<div>全包:{val.a}%</div>
<div>半包:{val.b}%</div>
</div>
}} />

</Table.ColumnGroup>
</Table>
</div>
)
}

export default App

实际上核心代码就这几行

1
2
3
4
5
6
7
8
9
const htmlString = '' // 这里就是你要复制的东西
const type = "text/plain";
const blob = new Blob([htmlString], { type });
const data = [new ClipboardItem({ [type]: blob })];

navigator.clipboard.write(data).then(
() => resolve(),
(error) => reject(error),
);

2023-08-04 JavaScript统计字符串广义上的长度

就这一个函数就可解决,非常好用

1
2
3
4
5
6
7
8
9
10
function unicodeLengthIgnoreSequence(str) {
let segmenter = new Intl.Segmenter();
let segments = segmenter.segment(str);
return [...segments].length;
}

console.log(
'👪𠄘👨‍👩‍👧‍👦'.length,
unicodeLengthIgnoreSequence('👪𠄘👨‍👩‍👧‍👦'),
)

得到结果👇🏻

1
> 15 3

Hexo怎么部署到Cloudflare Pages、怎么绑定阿里云的域名

先注册并登录Cloudflare Pages的网址

https://pages.cloudflare.com/

点击左侧菜单栏的Workers 和 Pages,然后点击创建应用程序

点击 Pages,然后点击连接到Git

选择好自己的Hexo博客仓库以后,在这里填写构建命令和构建产物目录

点击自定义域后,输入自己想要自定义的域名,例如我是blog.xxx.xxx

然后点击开始CNAME设置

上图的名称对应阿里云的主机记录目标对应阿里云的记录值

然后去阿里云解析域名

解析完域名后,再回到cloudflare pages点击检查DNS记录就大功告成啦

Flutter 3.0首次运行时卡在"Running Gradle task assembleDebug"的解决办法

第一步

找到flutter sdk路径下的这个文件

flutter/packages/flutter_tools/gradle/flutter.gradle

打开,并找到这个位置

1
2
3
4
5
6
7
buildscript {
repositories {
google()
mavenCentral()
}
...
}

注释掉 google()mavenCentral() ,并更改为

1
2
3
4
5
6
7
8
9
10
buildscript {
repositories {
// google()
// mavenCentral()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
}
...
}

第二步

找到当前工程路径下的这个文件

/android/build.gradle

和第一步一样,打开,并注释掉 google()mavenCentral()并更改

注意第二步有两处,所以要改两个地方

第三步

还是这个文件

/android/build.gradle

找到DEFAULT_MAVEN_HOST并替换为国内的镜像地址

1
2
3
4
5
class FlutterPlugin implements Plugin<Project> {
// private static final String DEFAULT_MAVEN_HOST = "https://storage.googleapis.com";
private static final String DEFAULT_MAVEN_HOST = "https://storage.flutter-io.cn";
...
}

基本上完成上面这三步,就可以运行如下命令尝试再次启动了

1
flutter clean
1
flutter run

什么?你还不行啊。。。我倒,接着往下看吧,真没想到你这么倒霉

第四步

有可能是第三步的镜像地址挂了,可以把第三步再改回来,咱们改host

打开这个网站 https://tool.chinaz.com/speedtest/storage.googleapis.com

获取你的能访问到的加速ip地址,改hosts

有好多个呢,你自己试试吧

1
2
3
4
5
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost

142.251.43.16 storage.googleapis.com # 例如这样改,Mac和Linux不做介绍

再试试咯,不行就往下走

1
flutter clean
1
flutter run

第五步

到这基本上宣告你的gradle是死活下载不下来了,只能离线下载了,找到工程目录下的这个文件打开,找到里面的distributionUrl

/android/gradle/wrapper/gradle-wrapper.properties

1
2
...
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

你看啊,我这里是gradle-7.5-all.zip

那就去访问gradle的离线下载网站吧,找到gradle-7.5-all.zip并且下载下来

https://services.gradle.org/distributions/

然后去系统的gradle存放目录替换掉死活下载不下来的那些东西

例如我是C:\Users\vear\.gradle\wrapper\dists\gradle-7.5-all\6qsw290k5lz422uaf8jf6m7co\gradle-7.5-all.zip

那就把C:\Users\vear\.gradle\wrapper\dists\gradle-7.5-all\6qsw290k5lz422uaf8jf6m7co下面的东西全删了

把新下载的gradle-7.5-all.zip拷贝进去

再回来试试咯,反正我是行了,你不行就只能另请高明了

1
flutter clean
1
flutter run

简单手写防抖节流

不多逼逼,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

export const debounce = (func: Function, timeout: number) => {
let timeoutList: Array<NodeJS.Timeout> = [];

return (...args: any[]) => {
let timeoutId: NodeJS.Timeout;
timeoutList.forEach(_timeoutId => clearTimeout(_timeoutId))
timeoutId = setTimeout(() => {
func(...args);
}, timeout);
timeoutList.push(timeoutId)
};
};



export const throttle = (func: Function, timeout: number) => {
let flag = true
return (...args: any[]) => {
if (flag) {
flag = false
setTimeout(() => {
func(...args);
flag = true
}, timeout);
}
};
};

域名访问失效?小心浏览器自动添加的 `www` 陷阱!

服务器明明已经配置了好了静态文件却无法访问是为什么

你是否精心配置好了服务器上的静态文件,满怀期待地在浏览器地址栏输入自己的一级域名(比如 example.com),结果却遭遇了冷冰冰的 “无法访问此网站” 错误?明明本地测试或通过其他方式访问 example.com 是正常的,为什么直接输入就不行了呢?别急着怀疑人生,问题很可能出在一个容易被忽视的细节上——现代浏览器对 www 前缀的自动补全行为。

现象:输入域名A,浏览器访问了域名B

简单来说:

  1. 你输入: example.com (一级域名)
  2. 浏览器实际访问: www.example.com (浏览器自动在域名前加上了 www!)

原因:Chromium内核的“贴心”特性

如今,绝大多数主流浏览器(Chrome、Edge、Brave、Opera、新版 Firefox 等)都基于 Chromium 内核。这个内核有一个设计初衷是方便用户的功能:**当用户直接在地址栏输入一个没有子域名部分的一级域名时,浏览器会尝试自动在它前面加上 www.**。

  • 输入 example.com -> 浏览器尝试访问 www.example.com

冲突点:服务器配置的局限性

问题就出在这里!你的服务器配置通常是这样的:

  • 你为 example.com 这个域名配置了静态文件服务或虚拟主机。当请求直接访问 example.com 时,服务器知道该去哪里找文件并正确响应。
  • 但是,你很可能没有为 www.example.com 做任何配置。 当浏览器自作主张地请求 www.example.com 时,服务器就懵了:
    • 它找不到为 www.example.com 配置的服务。
    • 或者,该域名指向了错误的目录或根本不存在。
    • 结果自然是服务器无法响应有效的页面,浏览器只能报错。

这就好比你把派对地址告诉了朋友是“公园路123号”(example.com),但朋友导航时却习惯性地输入了“公园路123号大厦”(www.example.com),而这个地方根本不存在或者大门紧锁。

解决方案:一劳永逸的 301 重定向

解决这个问题的核心思路很简单:**告诉服务器,所有访问 www.example.com 的请求,都应该自动、永久地跳转到 example.com**。这就是 301 永久重定向 的用武之地。

为什么是 301

  1. 用户友好:
    • 用户输入 example.com -> 浏览器尝试访问 www.example.com -> 服务器立即返回 301 状态码并指示:“永久搬家到 example.com 了,去那边!” -> 浏览器自动跳转到 example.com -> 成功加载内容。
    • 用户输入 www.example.com -> 同样被重定向到 example.com -> 成功访问。
    • 无论用户输入哪种形式,最终都能无缝抵达正确的 example.com
  2. SEO 优化: 301 重定向明确告诉搜索引擎(如 Google、Bing),www 版本是次要的或已废弃,所有权重、排名和索引都应集中在你的主域名 example.com 上,有效避免内容重复导致的 SEO 问题。

如何配置?

配置方法取决于你使用的 Web 服务器软件。下面以主流服务器 Nginx 配置为例:

Nginx 配置

在 Nginx 的配置文件中(通常在 /etc/nginx/sites-available//etc/nginx/conf.d/ 下,具体看你的配置),为 www.example.com 创建一个专门的 server 块:

server {
    listen 80;
    listen [::]:80;       # IPv6 监听
    server_name www.example.com;  # 捕获所有 www.example.com 的请求

    # 核心:301 永久重定向到无 www 的主域名
    return 301 http://example.com$request_uri;  # 保留原始请求的路径

    # 重要提示:如果站点已启用 HTTPS,应重定向到 HTTPS 版本!
    # return 301 https://example.com$request_uri;
}