不知道从哪个版本的 Chrome 或 Edge 开始,我们无法通过 ctrl+v 快捷键将时间格式的字符串粘贴到 type 为 date 的 input 框中,我们想办法用 JS 来实现。
方式一、监听 paste 事件:
const input = document.querySelector('input[type="date"]');
input.addEventListener('paste', (event) => {
input.value = event.clipboardData.getData('text');
});这段代码实现了从页面获取这个 input 元素,监听它的 paste 事件,然后将粘贴板的文本内容赋值给 input。
经测试,当焦点在“年”的位置时可以粘贴成功,但焦点在“月”或“日”上不会触发 paste 事件。
方式二、监听 keydown 事件:
const input = document.querySelector('input[type="date"]');
input.addEventListener('keydown', (event) => {
if ((navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey) && event.key === 'v') {
event.preventDefault();
var clipboardData = (event.clipboardData || event.originalEvent.clipboardData);
input.value = clipboardData.getData('text');
}
});测试发现报错误:
Uncaught TypeError: Cannot read properties of undefined (reading 'getData')
Uncaught TypeError: Cannot read properties of undefined (reading 'clipboardData')
看来 event 中没有 clipboardData 对象,改为从 window.navigator 获取:
const input = document.querySelector('input[type="date"]');
input.addEventListener('keydown', (event) => {
if ((navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey) && event.key === 'v') {
event.preventDefault();
window.navigator.clipboard.readText().then(text => {
input.value = text;
});
}
});缺点是需要用户授权:

仅第一次需要授权,如果用户拒绝,那么以后就默认拒绝了。
以上两种方式各有优缺点,选择一种适合你的方案就行。接下来继续完善。
兼容更多时间格式,并调整时区
<input type="date" /> 默认的日期格式是 yyyy-MM-dd,如果要兼容 yyyy-M-d 等格式,那么:
const parsedDate = new Date(text);
if (!isNaN(parsedDate.getTime())) {
input.value = parsedDate.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' }).split('/').reverse().join('-');
}以 text 为“2023-4-20”举例,先转为 Date,如果成功,再转为英国时间格式“20-04-2023”,以“/”分隔,逆序,再以“-”连接,就变成了“2023-04-20”。
当然如果希望支持中文的年月日,可以先用正则表达式替换一下:
text = text.replace(/\s*(\d{4})\s*年\s*(\d{1,2})\s*月\s*(\d{1,2})\s*日\s*/, "$1-$2-$3");处理页面上的所有 <input type="date" />
const inputs = document.querySelectorAll('input[type="date"]');
inputs.forEach((input) => {
input.addEventListener(...);
});封装为独立域
避免全局变量污染,使用 IIFE 函数表达式:
(function() {
// 将代码放在这里
})();或者封装为函数,在 jQuery 的 ready 中,或 Vue 的 mounted 中调用。
在 Vue 中使用
如果将粘贴板的值直接赋值到 input.value,在 Vue 中是不能同步更新 v-model 绑定的变量的,所以需要直接赋值给变量:
<div id="app">
<input type="date" v-model="a" data-model="a" v-on:paste="fn_pasteToDateInput" />
{{a}}
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
a: null,
}
},
methods: {
fn_pasteToDateInput: function (event) {
const text = event.clipboardData.getData('text');
const parsedDate = new Date(text);
if (!isNaN(parsedDate.getTime())) {
const att = event.target.getAttribute('data-model');
this[att] = parsedDate.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' }).split('/').reverse().join('-');
}
},
}
});
const vm = app.mount('#app');
</script>示例中 <input /> 添加了 data- 属性,值同 v-model,并使用 getAttribute() 获取,利用 this 对象的属性名赋值。
如果你的 a 中还有嵌套对象 b,那么 data- 属性填写 a.b,方法中以“.”分割逐级查找对象并赋值
let atts = att.split('.');
let target = this;
for (let i = 0; i < atts.length - 1; i++) {
target = target[atts[i]];
}
this.$set(target, atts[atts.length - 1], text); // vue2
target[atts[atts.length - 1]] = text; // vue3Pomelo.EntityFrameworkCore.MySql 升级到 7.0 后出现:
System.InvalidOperationException:“The 'sbyte' property could not be mapped to the database type 'tinyint(1)' because the database provider does not support mapping 'sbyte' properties to 'tinyint(1)' columns. Consider mapping to a different database type or converting the property value to a type supported by the database using a value converter. See https://aka.ms/efcore-docs-value-converters for more information. Alternately, exclude the property from the model using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.”
对比 6.0 生成的 DbContext.cs 发现缺少了 UseLoggerFactory,按旧版修改即可。
找到 OnConfiguring 方法,上方插入:
public static readonly LoggerFactory MyLoggerFactory = new LoggerFactory(new[] {
new DebugLoggerProvider()
});在 OnConfiguring 方法中将:
optionsBuilder.UseMySql("连接字符串");改为:
optionsBuilder.UseLoggerFactory(MyLoggerFactory).UseMySql("连接字符串");新版本 SDK 已解决这个问题

升级到 .NET 7 后,Senparc.Weixin 6.15.7 正常,6.15.8.1-6.15.8.3 无法正常启动,行
builder.Services.AddSenparcWeixinServices(builder.Configuration);报错:
System.NullReferenceException:“Object reference not set to an instance of an object.”临时解决方法,改为:
builder.Services.AddSenparcWeixinServices(builder.Configuration, delegate { });错误 BLAZOR102 The scoped css file was defined but no associated razor component or view was found for it.
错误 BLAZOR106 The JS module file was defined but no associated razor component or view was found for it.
解决方法:将相关文件从项目中排除,再包括到项目中即可正常生成。
若仍报错,备份这些文件,删除并新建文件,复制代码进去尝试生成。
若仍报错,通过注释代码的方式排查问题原因。
User-Agent 字符串不会进行更新以区分 Windows 11 和 Windows 10。
但我们可以使用 JS 进行客户端判断是否为 Windows 11:
navigator.userAgentData.getHighEntropyValues(["platformVersion"])
.then(ua => {
if (navigator.userAgentData.platform === "Windows") {
const majorPlatformVersion = parseInt(ua.platformVersion.split('.')[0]);
if (majorPlatformVersion >= 13) {
console.log("Windows 11 or later");
}
else if (majorPlatformVersion > 0) {
console.log("Windows 10");
}
else {
console.log("Before Windows 10");
}
}
else {
console.log("Not running on Windows");
}
});支持客户端提示 User-Agent 浏览器:
| 浏览器 | 通过客户端提示 User-Agent 区别? |
|---|---|
| Microsoft Edge 94 及以上 | 是 |
| Chrome 95 及以上 | 是 |
| Opera | 是 |
| Firefox | 否 |
| Internet Explorer 11 | 否 |
第一步,创建用户和 AccessKey
登录阿里云控制台后,从头像处进入 AccessKey 管理

“开始使用子用户 AccessKey”

点击“创建用户”,填写用户名(本文以 oss-bbs 为例),并勾选“Open API 调用访问”

点击确定创建成功,可以看到 AccessKey ID 和 AccessKey Secret
第二步,创建权限策略
在阿里云控制台左侧菜单“RAM 访问控制”中展开“权限管理”,选择“权限策略”

点击“创建权限策略”,切换到“脚本编辑”

输入以下策略文档(JSON 格式)
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:ListBuckets",
"oss:GetBucketStat",
"oss:GetBucketInfo",
"oss:GetBucketAcl"
],
"Resource": "acs:oss:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"oss:Get*",
"oss:Put*",
"oss:List*",
"oss:DeleteObject"
],
"Resource": "acs:oss:*:*:bbs"
},
{
"Effect": "Allow",
"Action": [
"oss:Get*",
"oss:Put*",
"oss:List*",
"oss:DeleteObject"
],
"Resource": "acs:oss:*:*:bbs/*"
}
]
}脚本中第一段表示允许用户登录,第二段表示允许操作 Bucket,第三段表示允许操作 Bucket 内的文件对象。
示例中的 bbs 是 Bucket Name,请修改为自己的 Bucket 名称。
点击下一步,填写名称,确定即可。
第三步,添加用户权限
转到“用户”页面,在刚才创建的用户行“添加权限”

切换到“自定义策略”,将上一步创建的权限策略添加到右侧“已选择”区

确定。
相关信息:
You have no right to access this object because of bucket acl.
项目 - 属性 - 生成 - 输出 - 文档文件,勾选“生成包含 API 文档的文件。”,“XML 文档文件路径”可留空。

在配置方法 AddSwaggerGen 中添加以下两行
var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
将 Windows 更新代理更新到最新版本:从 Microsoft 下载中心手动下载 Windows 更新代理
再次尝试。
public static class EntityFrameworkCoreExtension
{
private static DbCommand CreateCommand(DatabaseFacade facade, string sql, out DbConnection connection, params object[] parameters)
{
var conn = facade.GetDbConnection();
connection = conn;
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = sql;
cmd.Parameters.AddRange(parameters);
return cmd;
}
public static DataTable SqlQuery(this DatabaseFacade facade, string sql, params object[] parameters)
{
var command = CreateCommand(facade, sql, out DbConnection conn, parameters);
var reader = command.ExecuteReader();
var dt = new DataTable();
dt.Load(reader);
reader.Close();
conn.Close();
return dt;
}
public static List<T> SqlQuery<T>(this DatabaseFacade facade, string sql, params object[] parameters) where T : class, new()
{
var dt = SqlQuery(facade, sql, parameters);
return dt.ToList<T>();
}
public static List<T> ToList<T>(this DataTable dt) where T : class, new()
{
var propertyInfos = typeof(T).GetProperties();
var list = new List<T>();
foreach (DataRow row in dt.Rows)
{
var t = new T();
foreach (PropertyInfo p in propertyInfos)
{
if (dt.Columns.IndexOf(p.Name) != -1 && row[p.Name] != DBNull.Value)
{
p.SetValue(t, row[p.Name], null);
}
}
list.Add(t);
}
return list;
}
}调用示例:
var data = db.Database.SqlQuery<List<string>>("SELECT 'title' FROM `table` WHERE `id` = {0};", id);普通的 nginx http 反向代理 https 时是需要配置证书的,但我们又不可能由源域名的证书,所以要使用 nginx 的 stream 模块。普通的 nginx 反向代理属于第七层代理,而 stream 模块是第四层代理,通过转发的 tcp/ip 协议实现高功能,所以不需要证书。
我们这里以使用宝塔安装的 nginx 为例,其实其他系统也是类似,只要找到编译的 nginx 的源码目录就行了
编译前先将已经安装的 nginx 文件进行备份
通过 ps 命令查看 nginx 文件的路径。以下所有步骤都以自身 nginx 路径为准
# ps -elf | grep nginx
# cd /www/server/nginx/sbin/
# cp nginx nginx.bak然后查看当前 nginx 编译的参数
/www/server/nginx/sbin/nginx -V将 ./configure arguents:之后的内容复制到记事本备用(备注:我们这里其实使用的是 Tengine-2.3.1,所以下面的编译参数可能跟普通 nginx 不是很一样)
内容如下:
--user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --with-openssl=/www/server/nginx/src/openssl --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --add-module=/www/server/nginx/src/lua_nginx_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre=pcre-8.42 --with-cc-opt=-Wno-error --add-module=/www/server/nginx/src/ngx-pagespeed
进入 src 目录
cd /www/server/nginx/src我们在上面的内容中加入两个参数
./configure --user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --with-openssl=/www/server/nginx/src/openssl --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --add-module=/www/server/nginx/src/lua_nginx_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre=pcre-8.42 --with-cc-opt=-Wno-error --add-module=/www/server/nginx/src/ngx-pagespeed --with-stream --with-stream_ssl_preread_module
并执行它
然后
make && make install重启 nginx
service nginx restartnginx -V 看看模块是不是加载了

新建个站点,配置反向代理
卸载
使用 nginx.bak 文件替换掉自编译的 nginx 文件,替换后重启 nginx。
