news 2026/10/1 4:59:54

带T的DateTime烦人吗?.NET Core时间格式序列化全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
带T的DateTime烦人吗?.NET Core时间格式序列化全攻略

带T的问题,几乎所有做前后端分离的.NET Core开发都踩过一次。前端拿到的时间不是我们习惯的2024-01-15 08:30:00,而是2024-01-15T08:30:00,有的还带个尾巴+08:00或者结尾多了个Z。用户第一反应通常是“后端是不是返回错格式了”,实际上后端返回的确实是 DateTime 类型,问题出在序列化规则上。这篇就从头到尾把原因、几种解法、各自适用场景和实际踩坑记录整理一遍。

1. T字到底从哪儿来:序列化规则的锅,别急着怪后端

1.1 ISO 8601 标准与 DateTime 的默认序列化行为

那个看起来碍眼的T,是 ISO 8601 日期时间格式标准里的分隔符,作用就是把日期部分和时间部分隔开。比如2024-01-15T08:30:00,T 前面是日期,后面是时间,机器可读性很好,排序也方便,所以各种编程语言的 JSON 序列化库默认都倾向于输出这种格式。

.NET Core后端返回实体对象时,如果属性类型是DateTime,序列化组件会按标准格式输出。区别在于:

  • 使用System.Text.Json(.NET Core 3.0 起内置)时,默认输出 ISO 8601 格式,例如2024-01-15T08:30:00,如果 DateTime 带时区信息,还可能输出为2024-01-15T08:30:00+08:00或2024-01-15T08:30:00Z(Z 表示 UTC 时间)。
  • 使用Newtonsoft.Json(早期 .NET Core 项目常见的第三方库)时,默认行为略有差异,但在 DateTime 上通常也会输出带 T 的 ISO 8601 格式。

我自己调试第一个前后端分离项目时,看 Chromium 控制台里response.data.createTime直接显示2023-08-12T14:23:11,第一反应是后端代码里有字符串拼接,结果查了一圈发现后端什么都没有做,就是很正常的new DateTime(...)返回。后来才意识到这是序列化器的默认行为,跟业务代码没关系。

1.2 DateTimeKind 与时区偏移带来的“隐形坑”

这里有个更隐蔽的点:DateTime 本身有一个Kind属性,取值是Unspecified、Utc、Local三种。

  • 如果后端把DateTime.UtcNow直接塞进实体,序列化器会把它当作 UTC 时间输出,于是前端看到2024-01-15T08:30:00Z,Z 就是零时区标记。
  • 如果后端用DateTime.Now,系统是东八区的话,序列化器可能输出2024-01-15T08:30:00+08:00,表示带了 +8 小时时区偏移。
  • 如果是从数据库读出来的时间,Kind通常是Unspecified,这时序列化器就按字面值输出,不带时区标记。

问题就来了:很多人不关心 Kind,只关心屏幕上显示“08:30”还是“16:30”。前端如果用new Date("2024-01-15T08:30:00Z"),浏览器会把它当成 UTC 时间,在东八区显示为16:30,于是出现了“后端返回 8 点半,前端显示 4 点半”的灵异事件。这不是时间格式问题,是 UTC 与本地时区没有换算的问题。

1.3 为什么不能靠“数据库里存成字符串”一劳永逸

有些团队嫌格式化麻烦,直接在实体里把 DateTime 换成 string,查询时ToString("yyyy-MM-dd HH:mm:ss")。这确实一了百了,不用管序列化规则,前端拿到的就是干净字符串。但副作用很重:

  • 实体属性CreateTime变成 string 后,后端做时间范围筛选就不能直接用>=比较,得先把字符串转回 DateTime 或靠数据库函数处理。
  • 前端做“几天前”“距离现在多久”这类需要时间计算的逻辑时,字符串又得转回日期对象,绕了一大圈。
  • 团队规范一旦开了头,后面所有时间字段都会照这个方式处理,整个项目的时间字段全变成字符串,后续维护成本上去,就很难回正了。

所以最合理的思路是:后端实体保持DateTime,在序列化层做格式化;前端拿到字符串后,需要参与计算时再转成Date对象。

2. 最小改动方案:在实体时间字段上挂格式化“标签”

如果是小项目、接口数量少,或者只想快速修复某个返回时间带 T 的接口,直接在实体属性上用特性标注是最快的。改一个字段查一个字段,不用动全局配置。

2.1 Newtonsoft.Json 下用 JsonConverter 特性

如果项目里 JSON 序列化用的是Newtonsoft.Json,可以写一个专门的DateTimeConverter,然后挂到时间字段上:

using Newtonsoft.Json; using Newtonsoft.Json.Converters; public class CustomDateTimeConverter : DateTimeConverterBase { private readonly string _format; public CustomDateTimeConverter() : this("yyyy-MM-dd HH:mm:ss") { } public CustomDateTimeConverter(string format) { _format = format; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } DateTime dateTime = (DateTime)value; // 如果是 UTC 时间,先转本地时间,避免前端看到的时间差 8 小时 if (dateTime.Kind == DateTimeKind.Utc) { dateTime = dateTime.ToLocalTime(); } writer.WriteValue(dateTime.ToString(_format)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.Value == null) { return null; } return DateTime.Parse(reader.Value.ToString()); } }

在实体字段上使用:

public class OrderDto { public int Id { get; set; } [JsonConverter(typeof(CustomDateTimeConverter))] public DateTime CreateTime { get; set; } [JsonConverter(typeof(CustomDateTimeConverter), "yyyy-MM-dd")] public DateTime PublishDate { get; set; } }

这里有个细节:CustomDateTimeConverter继承的是DateTimeConverterBase,而不是直接实现JsonConverter<DateTime>,原因是这个类既兼容可空时间类型DateTime?,又不会在ReadJson里引入太多泛型模板逻辑。如果你项目里时间字段全是非空DateTime,直接继承JsonConverter<DateTime>更简单。

2.2 System.Text.Json 下自定义 Converter 的正确姿势

.NET Core 3.0 之后默认序列化器是System.Text.Json,它不再支持[JsonConverter(typeof(CustomDateTimeConverter))]直接指定非泛型类型,写法略有调整:

using System.Text.Json; using System.Text.Json.Serialization; public class DateTimeJsonConverter : JsonConverter<DateTime> { private readonly string _format; public DateTimeJsonConverter() : this("yyyy-MM-dd HH:mm:ss") { } public DateTimeJsonConverter(string format) { _format = format; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return DateTime.Parse(reader.GetString()); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { if (value.Kind == DateTimeKind.Utc) { value = value.ToLocalTime(); } writer.WriteStringValue(value.ToString(_format)); } }

然后在属性上这样挂:

public class OrderDto { public int Id { get; set; } [JsonConverter(typeof(DateTimeJsonConverter))] public DateTime CreateTime { get; set; } }

同样需要处理可空类型,就再写一个DateTimeNullableJsonConverter : JsonConverter<DateTime?>,或者在 Read 方法里判断reader.TokenType == JsonTokenType.Null。

2.3 这个方案的适用边界与真实体验

特性方案的优点是局部可控、一目了然,看到实体就知道哪个字段输出什么格式。适合:

  • 系统里只有少数时间字段需要格式化。
  • 接口已经上线,临时快速修复某个响应体。
  • 老项目迁移.NET Core过程中,不想动全局配置,先把业务跑通。

缺点是每个时间字段都要标注,字段一多代码看起来很啰嗦,而且后来加字段的人未必记得加特性,漏一个又会出现带 T 的接口,治标不治本。实测下来,单接口、小项目用这个方案没问题,但服务一多、实体一多,还是要走全局配置。

3. 全局配置方案:一次性解决所有接口的时间格式

如果项目里时间字段很多,或者所有接口都统一返回yyyy-MM-dd HH:mm:ss,最佳方案是在Startup或Program.cs里全局配置序列化器。全局配置的好处是后期任何新加的DateTime属性都会被格式化,不需要每个实体都额外处理。

3.1 System.Text.Json 全局注册 Converter

.NET Core 6 及以上的项目,一般直接在Program.cs里配置:

using System.Text.Json.Serialization; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter()); options.JsonSerializerOptions.Converters.Add(new DateTimeNullableJsonConverter()); options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; });

注意一点:JsonSerializerOptions的 Converters 是有顺序的,多个 Converter 之间有先后关系,一般自定义 Converter 放在通用类型的前面。这里不会冲突,因为System.Text.Json自带的DateTime转换器内置在运行时流程里,你添加了自己的DateTimeJsonConverter后,它会优先使用你的实现,覆盖默认的 ISO 8601 输出。

DateTimeNullableJsonConverter的写法也很简单:

public class DateTimeNullableJsonConverter : JsonConverter<DateTime?> { public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { return null; } return DateTime.Parse(reader.GetString()); } public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) { if (value == null) { writer.WriteNull(); return; } if (value.Value.Kind == DateTimeKind.Utc) { writer.WriteStringValue(value.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")); } else { writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss")); } } }

如果项目里时间字段不多,不写可空转换器也行,但一旦实体里出现DateTime?属性,没有专门处理时又会出现带 T 的格式,既然是全局方案,两个都要注册,避免漏网之鱼。

3.2 Newtonsoft.Json 阶段的做法(老项目常见)

用过.NET Core 2.x或早期版本的人更熟悉Newtonsoft.Json的配置方式。注意这个配置不是放在AddJsonOptions里,而是单独使用AddNewtonsoftJson:

builder.Services.AddControllers() .AddNewtonsoftJson(options => { options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }); options.SerializerSettings.Converters.Add(new StringEnumConverter()); options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; });

这里有个容易混淆的点:很多人只设置了DateFormatString,但IsoDateTimeConverter的优先级更高,如果同时还加了IsoDateTimeConverter,它的DateTimeFormat才是最终生效的格式。所以要么直接用IsoDateTimeConverter同时设置DateTimeFormat,要么只保留DateFormatString,不要两者都配然后发现配置不生效。

3.3 全局配置之后 Swagger 与前端表现

配置完成后,接口返回的时间就成了2024-01-15 08:30:00这种常规格式,不再带T。不只是实际响应变了,Swagger 页面的示例值也会跟着变。做前端联调的人看到控制台返回的是常规字符串,就不会再去new Date(...)之后莫名其妙多 8 小时了。

有个细节要注意:全局配置影响的是“响应序列化”,如果你用同一个JsonSerializerOptions去解析请求体,那么请求 JSON 里的时间字符串也会按yyyy-MM-dd HH:mm:ss反序列化成DateTime。前端传2024-01-15T08:30:00这种格式时,DateTime.Parse是能解析 ISO 8601 的,所以请求端不受影响,但如果你只允许严格格式,最好在Read方法里处理一下容错逻辑,比如统一走DateTime.Parse而不是解析失败直接抛 400。

4. 前端兜底:不管后端怎么改,你都需要一个格式化函数

后端的方案解决了“不带 T”,但实际开发里还有个很常见的情况:前端调的是第三方接口,对方返回的就是带 T 的 UTC 时间,或者后端跨部门改不了全局序列化配置。这时候后端改不动,只能前端兜底处理。

4.1 不要用 toISOString 转字符串再做拼接

新手最容易踩的坑是拿到2024-01-15T08:30:00之后,先new Date(str),然后date.toISOString()再截取,结果发现时间变了。原因在于toISOString()永远返回 UTC 时间,东八区的早上 8 点半转出来是00:30,完全错误。要转本地时间显示,应该用getFullYear()、getMonth()这一组本地时间方法:

function formatDateTime(input) { if (!input) { return ''; } const date = new Date(input); if (isNaN(date.getTime())) { return input; } const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }

这个函数兼容三种输入:

  • 后端返回的2024-01-15T08:30:00。
  • 带时区偏移的2024-01-15T08:30:00+08:00。
  • UTC 结尾的2024-01-15T08:30:00Z。

浏览器会把带 Z 的时间自动转成本地时间再展示,所以最终显示的始终是本地时区的时间,不会出现 8 小时偏差。

4.2 纯字符串替换法到底能不能用

有一种偷懒方案是直接replace('T', ' '):

const timeStr = rawTime.replace('T', ' ');

简单场景下确实有效,比如后端返回2024-01-15T08:30:00,替换后就是2024-01-15 08:30:00,后台列表页展示出处没什么问题。但有两个场景会出问题:

  • 时间字符串带了时区后缀,比如2024-01-15T08:30:00+08:00,替换后变成2024-01-15 08:30:00+08:00,带着+08:00直接展示给用户,很难看。
  • 字符串带Z,比如2024-01-15T08:30:00Z,替换后变成2024-01-15 08:30:00Z,用户看到 Z 会一头雾水,而且这个时间本质是 UTC,你直接展示会跟本地时间差 8 小时。

所以字符串替换只能用于“明确知道后端返回的是不带时区标记的本地时间”这个前提。只要返回的时间字符串里带了Z或者+08:00,就老老实实走new Date()转换。

4.3 Vue 项目里封装全局过滤器

Vue 2 / Vue 3 的模板里,直接写{{ createTime }}只会输出原始字符串,需要处理一下:

// utils/filters.js export function formatDateTime(value) { if (!value) { return ''; } const date = new Date(value); if (isNaN(date.getTime())) { return value; } return [ date.getFullYear(), String(date.getMonth() + 1).padStart(2, '0'), String(date.getDate()).padStart(2, '0') ].join('-') + ' ' + [ String(date.getHours()).padStart(2, '0'), String(date.getMinutes()).padStart(2, '0'), String(date.getSeconds()).padStart(2, '0') ].join(':'); }

在 main.js 里全局注册:

import * as filters from './utils/filters'; app.config.globalProperties.$filters = filters;

模板里直接用:

<p>{{ createTime | formatDateTime }}</p>

React 里就封装成一个公共函数,在组件里调用,思路完全一致。关键点是把这个函数放在前端公共工具目录里,所有列表页、详情页共用一份,不要每个页面自己写一套时间处理逻辑,否则不同页面一个显示 T、一个不显示 T,全乱套。

5. 实际项目中遇到的几个高频坑和对应解法

前面几节把方案说全了,但真正上线前还有几个坑值得单独列出来,这些都是我实际踩过的,或者帮别人排查接口问题时见过的情况。

5.1 时间字段在返回给前端之前被手动 ToString 截断

有次排查一个接口,发现createTime返回的是2024-01-15 08:30:00,但是updateTime返回的是2024-01-15T08:30:00。全局配置已经生效了,为什么一个字段是好的,另一个带 T ?查代码发现updateTime在实体里不是DateTime,而是string,代码里写的是:

updateTime = row.UpdateTime.ToString();

DateTime.ToString()默认输出的就是 ISO 8601 标准格式,等于序列化器还没来得及格式化,这个字段已经是字符串了。所以排查这类问题时,先确认前端看到的时间字符串到底是从序列化器出来的,还是被业务代码手动转成了字符串。后者的话,全局配置再怎么改也没用,得改业务代码。

5.2 Swagger 里显示带 T,但前端拿到的是格式化后的时间

Swagger 页面有时候展示的是"createTime": "2024-01-15T08:30:00",但前端实际请求拿到的却是"createTime": "2024-01-15 08:30:00"。这个现象容易让人误以为配置没生效。

原因通常在 Swagger 的示例值生成机制上。Swagger 生成示例时,有时是根据模型元数据自动生成,而不是真实序列化结果。如果模型属性类型是DateTime,Swagger 默认按 ISO 8601 显示;但实际 API 响应经过全局序列化配置后,已经变成了格式化字符串。验证方法很简单:直接用 Postman 或浏览器控制台看真实响应,只要真实响应是对的,Swagger 上的展示不影响使用。如果强迫症想连 Swagger 示例也改,可以自定义 Swagger 的ISchemaFilter,但那属于锦上添花,不是必须的。

5.3 时区转换后时间差了 8 小时 / 12 小时

前后端分离项目最容易出事的就是“后端存的是 UTC,前端按本地时间展示”。比如后端服务部署在海外服务器上,数据库里存的是 UTC 时间,实体字段类型是DateTime,Kind 为Utc。

  • 如果按照我上面的方案,在序列化 Converter 里做了 UTC 到本地时间的转换,前端拿到的是服务器所在时区转换成东八区后的正确时间,这种没问题。
  • 如果没有做转换,前端拿到2024-01-15T08:30:00Z,直接替换掉 T 当成字面时间展示,就会跟本地时间差 8 小时。

处理策略要统一:如果项目是国际化项目,用户分布在不同时区,最好的做法是后端返回 UTC 时间并使用 ISO 8601 标准格式,前端在展示时统一用本地时间格式化。如果项目只面向国内用户,后端直接按东八区本地时间存和返回,前端展示时不需要做额外转换,DateTime的 Kind 保持Unspecified就最省事。

这里我建议后端做全局配置时,Converter 里不要无条件做ToLocalTime(),因为服务器时区可能不是东八区。更稳妥的做法是:

// 统一转东八区 public static readonly TimeSpan ChinaStandardTimeOffset = TimeSpan.FromHours(8); if (value.Kind == DateTimeKind.Utc) { value = value.Add(ChinaStandardTimeOffset); }

不要依赖服务器本机时区,直接在代码里指定目标时区,这样部署到任何服务器上,返回前端的时间都是恒定的东八区时间。

5.4 前端把格式化后的字符串再传回后端,后端解析失败

这个坑经常出现在提交表单的时候。后端全局配置了yyyy-MM-dd HH:mm:ss格式,前端列表页显示正常。用户编辑数据,把2024-01-15 08:30:00这个字符串传回后端,后端在反序列化请求体时,如果用的还是同一个严格格式化转换器,会按yyyy-MM-dd HH:mm:ss去解析,能解析成功就没问题。

但如果前端在提交前做了一次new Date("2024-01-15 08:30:00"),再通过date.toISOString()转成字符串,你就会发现提交给后端的是2024-01-15T00:30:00.000Z。后端如果只允许严格格式,就会报 400。所以前端在编辑回显时,尽量避免对时间字符串再做一次转换;如果转换了,提交时就要用能解析 ISO 8601 的宽松解析逻辑。

后端 Converter 的Read方法建议写成:

public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var value = reader.GetString(); if (DateTime.TryParse(value, out var result)) { return result; } return DateTime.MinValue; }

这样无论前端传2024-01-15 08:30:00还是2024-01-15T08:30:00+08:00,都能解析。实际项目里这个容错很有价值,能省掉不少前后端联调过程中的“时间格式不对”类问题。

5.5 Postman 测试没问题,前端 axios 里就是带 T

出现这种差异,首先要看前端请求的响应是否经过了二次处理。比如axios的transformResponse里如果做了JSON.parse,而后端返回的 JSON 里时间字符串被某个拦截器再处理过一次,格式可能就变了。

另一个常见原因:后端返回的Content-Type不是application/json; charset=utf-8,前端走了不同的解析逻辑,导致浏览器显示原始字符串时看到的和实际展示的似乎不一样。不过这个情况较少,排查时用控制台console.log直接看response.data最靠谱,不要用 Network 面板里的 Preview,因为 Preview 有时经过浏览器美化。

6. 方案选型参考:到底该用哪一种

把前面的方案整理成一张对比表,方便做技术决策:

方案改动范围优点缺点适用场景
实体字段加特性每个时间字段局部控制灵活,不影响其他接口字段多了啰嗦,容易漏标小项目、临时修复
全局 System.Text.Json 配置一处全局所有 DateTime 统一格式化影响全局,有历史包袱时需回归.NET Core 3.0+ 主方案
全局 Newtonsoft.Json 配置一处全局兼容老项目,配置成熟需要引入第三方包.NET Core 2.x / 老项目
前端格式化函数前端工具库后端不用动,灵活兜底每个展示点都要调用,容易漏后端不能改或第三方接口

个人建议顺序:

  1. 新项目直接全局配置System.Text.Json,Converter 统一处理 DateTime 和 DateTime?,指定东八区,前后端都省心。
  2. 旧项目还在用 Newtonsoft.Json 的,把配置集中在AddNewtonsoftJson里,顺手排查一下源码里有没有手动ToString()的时间字段。
  3. 前端无论后端做没做,公共工具库里始终放一个formatDateTime函数,防止后端接口不统一时临时救火。

这三种方案在实际项目里不是互斥的。很多团队是后端全局配置 + 前端兜底函数同时做,双保险。后端配置解决 90% 的接口,前端兜底函数处理跨部门旧接口、第三方接口等极端情况。这样做下来,前端再遇到时间格式问题,基本不用找后端改代码了。

至于“到底要不要允许前端传带 T 的时间”这种请求体兼容问题,我的经验是后端解析时尽量宽松,展示时尽量统一。请求体是机器对机器,ISO 8601 带 T 本身没问题;响应体是给人看的,统一yyyy-MM-dd HH:mm:ss比较好。这个原则定下来,前后端联调会顺畅很多,时间格式这类小问题一般不会在测试阶段反复出现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:59:25

从硬编码到数据驱动:无代码战斗系统架构设计与实战优化

前阵子有个做ARPG的朋友跟我吐槽&#xff0c;说他们战斗改版改了三个月&#xff0c;每次策划调技能数值都要排队等程序改代码&#xff0c;连招重做更是要动逻辑层&#xff0c;改一版测一版&#xff0c;发个包过去来回折腾。我听完跟他说&#xff0c;这就是典型的战斗系统“硬编…

作者头像 李华
网站建设 2026/10/1 4:59:25

西门子TIA博图FB/FC七种接口变量:存储位置、生命周期与选型避坑

上周在一条灌装线上追一个“幽灵停机”&#xff1a;设备正常运行中偶尔自己停&#xff0c;复位之后又能安稳跑几个小时&#xff0c;监控表里翻遍了也没看到任何报警被置位。最后顺着交叉引用一层层剥下去&#xff0c;问题出在一个操作工认为“只是个临时量”的 Temp 变量上——…

作者头像 李华
网站建设 2026/10/1 4:59:14

AX Agent集群编排实战:Go语言下的状态机与依赖管理

1. 从 9.5K Star 的 AX 说起&#xff1a;Agent 集群编排到底在解决什么问题第一次看到 AX 这个项目的时候&#xff0c;我正被一堆散落在不同机器上的 Agent 进程搞得焦头烂额。每个 Agent 单独跑都没问题&#xff0c;但一旦需要它们协同完成一个稍复杂的任务链&#xff0c;问题…

作者头像 李华
网站建设 2026/10/1 4:59:08

博图V18连接Factory IO:PLCSIM Advanced仿真链路与IO映射

做自动化这行&#xff0c;只要你想在没硬件的情况下把一条产线逻辑跑通&#xff0c;就绕不开博图加仿真这套组合。这两年我身边不少做电气设计和程序调试的朋友都在琢磨同一个问题&#xff1a;博图 V18 和 Factory IO 到底怎么连。表面上看&#xff0c;这就是两个软件之间拉一根…

作者头像 李华
网站建设 2026/10/1 4:58:19

A4草图+豆包,二十分钟零代码做出可点击网页

画了一张A4纸上的网页草图&#xff0c;拍照扔给豆包&#xff0c;二十分钟后拿到一个能点的网页——这件事我干过&#xff0c;而且不止一次。先说结论&#xff1a;这不是玄学&#xff0c;是完全可以复制的操作流程。所谓“不会写代码”&#xff0c;其实只是不懂HTML、CSS、JavaS…

作者头像 李华
网站建设 2026/10/1 4:58:00

Python植物大战僵尸识字版:从解压到打包的完整实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华