news 2026/9/19 3:46:19

前端学Docker:从镜像构建到云服务器部署全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端学Docker:从镜像构建到云服务器部署全攻略

1. 一次"本地好好的,上线就挂"引发的学习冲动

我在掘金和知乎上刷到过无数次类似的问题:"前端要不要学 Docker?"评论区永远分成两派,一派说这是运维的事,另一派说容器化是开发的基本功。我的答案很明确:要学,而且越早越好。

原因很简单,几乎所有前端都经历过那个经典场景——本地开发时一切正常,npm run dev一键启动,页面丝滑流畅。结果部署到测试服务器,要么 Node 版本不对,要么环境变量缺失,要么 Nginx 配置把路由 history 模式搞挂,页面白屏。你明明写的代码没变,可就是跑不起来。这个锅到底算谁的?很多时候是环境不一致造成的,而 Docker 就是来解决这个问题的。

还有一层现实原因:越来越多的公司要求前端具备部署能力。打开招聘软件,能看到很多 JD 里明确写着熟悉 Docker、Nginx,能独立完成项目部署。后端倒是想帮你部署,可你要是连构建产物怎么放到镜像里都不清楚,整个交付流程就会卡在你这一环。所以说,Docker 不是纯运维的玩具,它是前端从"写出页面"进阶到"交付一个可用系统"的必经之路。

这篇文章我会用一个前端最熟悉的思维路径来讲 Docker,不扯太多底层原理,直接告诉你每个概念对应到前端是"什么东西",然后带着你把一个 Vue 项目从零构建成镜像,最终部署到云服务器上,跑通一个完整闭环。

2. 把 Docker 的三大核心概念翻译成前端黑话

接触 Docker 的人第一反应就是蒙:镜像、容器、仓库、数据卷、端口映射……这些词单独看都认识,拼在一起就完全不知道它们是什么关系。其实拿前端的体系去类比,一下就通了。

2.1 镜像约等于"npm 包 + Node 运行时"的冷冻体

你可以把镜像理解成一个提前打好包、包含完整运行环境的只读模板。比如一个镜像里可能装着 Node 18 的运行时、项目所有依赖、Nginx 配置、构建后的静态文件,所有东西冻结在一起。

这就好比一个完整的node_modules再加上整个 Node 解释器,以及能把它跑起来的操作系统组件,全部存到一个压缩包里。镜像一旦构建出来就是只读的,你想直接改一个文件是不可能的,只能基于它重新构建或生成新的镜像。

经常有人说"我把镜像发给对方,他本地就能跑起来",这句话的本质是在说:通过镜像把运行环境彻底固化住,杜绝了版本差异带来的不确定性。

2.2 容器等于"你 npm install 之后开启的一次 dev server"

镜像启动时,Docker 会基于这个只读模板创建一个可写的运行层,这个运行中的实例就是容器。

类比前端项目的话,就好比依赖跟代码已经打包好了,每次启动npm run dev就是在创建一个基于这个项目的运行实例。你可以同时启动多个相同的镜像,各自的运行状态互不干扰。

这也是 Docker 最实用的一点:它本质上是一个进程隔离机制,不是虚拟机快照。每个容器里跑的进程,在操作系统层面是独立隔离的,有自己的文件系统、网络、端口空间。这种隔离保证了你在本地怎么折腾都不会污染宿主机。

2.3 仓库等于"npm registry"

镜像一般通过docker pull从仓库拉取,最常用的就是 Docker Hub。你要 Nginx 官方镜像,docker pull nginx就行,跟你npm install vue的体验几乎一样。

我们后面自己构建出的镜像,如果不推送到仓库里,那它只存在于你本机。想要在服务器上使用,可以把本机镜像导出再导入服务器,更方便的是推到 Docker Hub 或私有仓库,然后在服务器docker pull拉取,这就跟发布一个 npm 包到 registry 再安装几乎一样。

2.4 数据卷与端口映射:前端容易忽视的关键点

容器运行时的可写层是临时的——容器删除后,里面的数据就全没了。如果你在容器里存了日志、数据库文件,容器一删,数据就没了。数据卷(Volume)就可以让容器把数据持久化到宿主机的指定目录,类似于把 localStorage 存到浏览器之外,不再受单个页面刷新影响。

端口映射则是把容器内部的端口有效暴露到宿主机。比如容器里的 Nginx 监听 80 端口,宿主机也需要访问 80 端口,通过-p 80:80把两者做映射。不映射,宿主机根本访问不到容器里的服务。这也是前端面试中经常被问到的"容器网络"的基础概念。

3. 环境准备:本地先把 Docker Desktop 跑起来

不管你用 macOS 还是 Windows,本地开发环境直接用 Docker Desktop 是最省心的选择。

3.1 macOS 与 Windows 的安装差异

macOS 用户直接官网下载 Docker Desktop 的 dmg 安装,拖入 Applications 就完成了。芯片是 Apple Silicon 的话,建议选择对应 ARM 架构的版本,因为 x86 镜像在 ARM 上默认会用模拟器跑,性能会打折扣。

Windows 用户的情况要分两种说清楚。如果系统是 Windows 10/11 专业版或企业版,推荐开启 WSL 2 后端,这样可以获得更完整的 Linux 内核兼容性;如果你用的是 Windows Home 版,很多教程里推荐的 Hyper-V 压根没有,最简单的方式也是开 WSL 2。装了 WSL 2 后,在 Docker Desktop 设置里把 Use the WSL 2 based engine 打开,基本上安装过程就不会有坑了。

安装完在终端敲docker --version,能看到版本号就说明 Docker CLI 已经可用了。再敲docker run hello-world,如果能看到一段 "Hello from Docker!" 的提示,说明整套环境已经正常。

注意:Windows 如果启动 Docker Desktop 时报错 "virtualization support not detected" 或者 "WSL 2 installation is incomplete",打开 BIOS 把虚拟化(Intel VT-x 或 AMD-V)开启,并且执行wsl --install确保 WSL 2 内核装好,这类问题基本都能解决。

3.2 常用命令先练出手感

我不推荐一上来就死记硬背一堆命令,先反复用最常见的十几个就够了。

命令用途前端类比
docker pull nginx:alpine拉取镜像npm install 某个依赖
docker run -d -p 8080:80 nginx创建并后台启动容器启动 dev server
docker ps查看运行中的容器查看哪些服务还在跑
docker ps -a查看所有容器(含已退出)查看所有启动过但已经停止的进程
docker logs <容器名或ID>查看容器日志控制台输出的 log
docker stop <容器名或ID>停止容器杀掉 dev server
docker rm <容器名或ID>删除容器清理该服务的运行实例
docker images查看本地镜像列表查看当前缓存里的依赖产物
docker rmi <镜像名或ID>删除本地镜像清除本地缓存
docker build -t 镜像名 .根据 Dockerfile 构建镜像执行 npm run build 产出静态文件包
docker exec -it <容器名> sh进入容器内部终端打开容器内的命令行窗口

我在本地练了几遍之后,发现最有手感的操作流程是:docker run -d -p 8080:80 nginx把 Nginx 启起来,浏览器访问 localhost:8080 看到 welcome 页面,然后docker stop关掉,docker rm删掉容器。整个过程十几秒,但对"容器是独立的、启停是自由的、操作是无痕的"这种认知,会有非常直观的感受。

4. 给一个 Vue3 项目写 Dockerfile:多阶段构建是灵魂

掌握了基础命令后,真正的实战就是从写 Dockerfile 开始的。我用一个常见的 Vue3 + Vite 项目来做示范。

4.1 两种方案,为什么推荐多阶段构建

很多初学者接触到的第一版 Dockerfile 长这样:

FROM node:18 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

这个写法有一个致命缺陷:项目构建阶段要用到的 Node 环境和几百兆的node_modules,最终都会留在镜像里。一个构建产物可能只有几 MB,但打包出来的镜像却有 1GB 以上,体积大、传输慢、攻击面也大。

多阶段构建的思路就完全不同了:先从一个包含 Node 环境的镜像开始,完成安装依赖、构建打包,得到一个 dist 目录;然后再换一个只有 Nginx 的轻量镜像,只把 dist 目录复制进来。前一个阶段的所有中间产物都不进入最终镜像。

做一个类比:你不需要在交付给用户的成品里保留"生产这台机器的整个工厂"。你需要的是把成品本身交给用户,而不是把整个产线也一起交付。

4.2 完整的 Dockerfile 逐行注释版

这是我在项目中实际使用并跑通的一条配置。

# 第一阶段:构建阶段 FROM node:18-alpine AS build-stage # 设置工作目录,等价于 cd 到容器内的 /app WORKDIR /app # 先只复制 package 相关文件,利用 Docker 的层缓存机制 COPY package*.json ./ # 安装依赖,用 npm ci 而不是 npm install,保证按 lock 文件精准安装 RUN npm ci # 复制项目所有源码到容器 COPY . . # 执行构建命令,默认输出到 dist 目录 RUN npm run build # 第二阶段:运行阶段 FROM nginx:alpine AS production-stage # 把第一阶段构建出的 dist 目录复制到 Nginx 的 web 根目录 COPY --from=build-stage /app/dist /usr/share/nginx/html # 如果有自定义 Nginx 配置,复制到容器中 COPY nginx.conf /etc/nginx/conf.d/default.conf # 声明容器运行时对外暴露 80 端口 EXPOSE 80 # 启动 Nginx 并保持在前台运行 CMD ["nginx", "-g", "daemon off;"]

这里有几个值得展开讲的关键点。

第一个是npm cinpm install的区别。npm ci严格要求依据 package-lock.json 安装,不会去更新 lock 文件,也不会做任何版本的浮动判断,这在容器构建这种需要确定性的场景里是最正确的选择。你有多少次因为npm install意外升级了某个小版本,导致构建产物和本地不一致?npm ci直接把这个坑堵死了。

第二个是 COPY 指令拆成两份。先COPY package*.json ./,执行RUN npm ci,再把剩余代码COPY . .。这样做的好处是:只要依赖没变,Docker 就会命中"依赖安装"这一层缓存,不用每次改动代码都重新 npm ci。对于依赖几百个包的前端项目,这个缓存命中能节省大量构建时间。

第三个是nginx -g daemon off;为什么要这样写。Nginx 默认以 daemon(守护进程)模式在后台运行,但 Docker 容器的生命周期跟容器内第一个进程(PID 1)是绑定的。如果 Nginx 后台运行,PID 1 进程会立刻退出,容器随之退出。daemon off让 Nginx 以前台模式运行,进程一直挂在那里,容器也就一直活着。

我见过不少新手用错了 CMD 导致容器启动后秒退,本质就是这个前台/后台进程的问题没搞明白。

4.3 .dockerignore 里必须排除哪些东西

前端项目的 .dockerignore 和 .gitignore 长得几乎一样,但它的作用不是防提交,而是避免把无关文件复制进镜像构建上下文。重点排除这些:

node_modules dist .git .gitignore Dockerfile .dockerignore *.local .vscode .idea

如果你不写 .dockerignore,COPY . .会把整个项目目录都塞进构建上下文,其中 node_modules 动辄几百 MB,不仅会让构建变慢,更糟糕的是如果 node_modules 被复制进镜像,很可能覆盖掉容器里通过npm ci安装的原生依赖,因为宿主机的 node_modules 可能是不同平台编译的,兼容性问题立刻暴露。

5. Nginx 配置:前端部署最容易踩坑的地方

很多前端学 Docker 查到教程,Dockerfile 写得很顺利,结果一访问页面就白屏。这时候十有八九是 Nginx 配置的问题。

5.1 History 路由模式的 rewrite 规则

Vue Router 默认使用 history 模式,地址栏里是/home/about这种路径,不再是哈希模式下的/#/home。但这种模式有一个特点:当浏览器直接访问https://example.com/home时,服务器如果只按路径找静态文件,根本找不到 /home 这个文件,就返回 404。

所以 Nginx 里必须加一条 try_files 规则,把所有路径都引导到 index.html,由前端路由接管后续逻辑:

server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } }

这段配置的意思是:访问/home时,先看真实文件是否存在,不存在就尝试加斜杠的目录,再不存在,就把请求重新指向 /index.html。这样浏览器拿到了 SPA 的入口,然后 Vue Router 识别到地址是 /home,就会渲染对应的组件。

没有配置过这个规则的运维接手前端项目时经常问:为什么我把 dist 放到服务器上访问不了子路由?原因就在这里。

5.2 静态资源缓存策略

前端项目的文件名里通常带 hash(如index-a1b2c3.js),这个 hash 只有在文件内容变化时才会变。所以对于带 hash 的静态资源,可以放心让浏览器和 CDN 长缓存;对于入口的 index.html,则必须禁止缓存或使用协商缓存,保证发版后用户能及时拿到新的入口文件。

location /assets/ { expires 30d; add_header Cache-Control "public, immutable"; } location / { add_header Cache-Control "no-cache"; try_files $uri $uri/ /index.html; }

地名解释一下为什么要区分:带 hash 的资源相当于"永远不变的版本号",内容变了 hash 自然会变,所以长缓存收益很高;index.html 是指挥入口,如果被浏览器缓存放了一个老版本,用户可能一直加载旧的 JS 文件,页面看起来就像没更新。

如果你在部署后发现"改了代码线上不生效",先把浏览器缓存强刷掉,再检查 Nginx 有没有给 index.html 设置 no-cache,这两种情况分别排查一遍。

5.3 用 gzip 把体积再压一压

Nginx 开启 gzip 对前端体验的改善立竿见影,尤其是 JS 和 CSS 这种文本文件。一般来说,默认的gzip on开启后,再配上压缩级别和最小压缩阈值就够了:

gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1k; gzip_comp_level 6;

级别是 1-9 的一个区间,级别越高压缩率越高,但 CPU 开销也越大。6 是我试下来在性能与体积之间比较均衡的值。阈值设为 1k 是为了避开太小的文件,压缩它们带来的收益很小,反而白白消耗 CPU。

5.4 反向代理:前端如何调用后端接口

前后端分离部署时,前端容器里的项目需要请求/api接口。如果前端直接写后端的 IP 和端口,会产生 CORS 问题,而且后端地址暴露在前端代码里,安全性和灵活性都差。

解决这种问题的思路就是反向代理:前端只请求同源的/api,Nginx 收到后把以/api开头的请求转发给真实的后端服务。

location /api/ { proxy_pass http://backend-service:3000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

注意proxy_pass结尾的斜杠有讲究:http://backend-service:3000/会替换掉/api/这段前缀,也就是说请求/api/user会被转发为http://backend-service:3000/user。如果你想去掉或者保留前缀,调整斜杠就能灵活控制。

backend-service是 Docker Compose 里的服务名,而不是 IP,这一点需要特别记住:在 Docker 内部网络中,服务名就是域名。

6. Docker Compose:前端项目一键启动整套服务

如果你只需要部署一个纯静态前端,一个容器可能就够了。但实际项目里常常是"前端 + 后端 + 数据库 + Redis"全家桶,每个都用 docker run 命令手动启,启动顺序、网络、环境变量会变得非常难维护。

Docker Compose 的定位就类似于在前端里用npm run dev来约定一套统一的启动入口:用 YAML 声明服务清单和依赖关系,一条docker compose up -d把这些服务全部拉起来。

6.1 docker-compose.yml 的核心字段

下面这个例子里有前端 Nginx、后端 Node 和 MySQL,三个服务一次拉起。

version: "3.8" services: frontend: build: context: ./frontend dockerfile: Dockerfile ports: - "80:80" depends_on: - backend backend: build: context: ./backend dockerfile: Dockerfile environment: - DB_HOST=mysql - DB_PORT=3306 - DB_USER=root - DB_PASSWORD=example ports: - "3000:3000" depends_on: mysql: condition: service_healthy mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: example MYSQL_DATABASE: app_db volumes: - mysql_data:/var/lib/mysql ports: - "3306:3306" healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s retries: 10 volumes: mysql_data:

逐段说一下:build指定通过哪个目录下的 Dockerfile 构建镜像;ports做端口映射;environment设置环境变量,后端读到DB_HOST=mysql就是用服务名mysql去连接数据库;depends_on控制启动顺序,MySQL 还配了 healthcheck,后端要等 MySQL 健康检查通过后才真正启动。

volumes里声明的mysql_data是命名数据卷,MySQL 的数据文件持久化在这里,容器删了重建,数据不会丢。

6.2 常用 Compose 命令

  • docker compose up -d:根据配置构建并以后台方式启动所有服务,-d表示后台运行。
  • docker compose down:停止并删除所有容器,数据卷默认不会被删除,除非加-v
  • docker compose ps:查看所有服务状态。
  • docker compose logs -f <服务名>:追踪某个服务的实时日志,相当于前端控制台。

实际项目里,我通常会针对不同环境维护多份配置:docker-compose.yml放公共配置,docker-compose.dev.yml放本地开发配置,docker-compose.prod.yml放生产配置,通过-f指定多份配置文件来组合启动。这种方式灵活性确实很强,但文件多了也容易混乱,建议如果团队规模不大,先维护一份简洁的单文件配置,真的遇到差异化需求再拆分。

6.3 日志是排查问题的主要入口

启动完成后一切正常,不代表不会出问题。前端和后端一旦联调失败,我会按这个顺序排查:

  1. docker compose ps看所有容器是否都在运行。
  2. docker compose logs backend看后端日志有没有报错。
  3. 如果后端日志显示连不上数据库,docker compose exec mysql mysql -uroot -p进 MySQL 容器里,直接在容器内执行 SQL 验证账号权限和数据是否存在。

记住别一上来就改代码,先看日志。日志能回答的问题,就不该靠猜。

7. 从本地到云端:云服务器部署的完整闭环

本地已经能构建镜像、能通过 Compose 一键启动整套服务,接下来就是把这个能力迁移到云服务器,实现真正的线上部署。

我以一台 Linux 云服务器(Ubuntu 22.04)为例,完整走一遍流程。

7.1 服务器初始化:安全组、SSH 登录、装 Docker

购买服务器后,先做两件事:登录云厂商控制台,在安全组规则里放行需要用到的端口(80、443 和 SSH 端口,需要自己手动加上)。不放行规则,就算容器监听端口正常,外部流量也进不来。

然后用 SSH 登录:

ssh root@你的服务器公网IP

登录后先更新系统,再安装 Docker:

apt update apt install -y docker.io docker-compose-v2 systemctl enable docker systemctl start docker

Ubuntu 的官方源里 Docker 版本可能不是最新的,但对绝大多数场景完全够用。如果用apt install docker-compose-v2装好后命令是docker compose,注意带横杠的docker-compose是老版独立的 Python 工具,现在官方已经推荐前者。

7.2 镜像加速:服务器拉镜像更快

国内云服务器直接访问 Docker Hub 时经常超时,配置文件里加镜像加速器是标准操作。编辑/etc/docker/daemon.json

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ] }

改完执行systemctl restart docker。这一步能明显提升docker pull nginx这类操作的速度。

7.3 把代码传到服务器并构建镜像

有几种方式:最简单的是git clone项目仓库到服务器,然后在项目目录里执行docker build。这种方式在操作上最顺手,也是我最常用的做法。

假设项目已经 clone 到/opt/app/frontend

cd /opt/app/frontend docker build -t my-frontend:latest .

构建完之后,用docker images查看本地镜像,确认 my-frontend 已经存在。然后运行容器:

docker run -d -p 80:80 --name frontend-container my-frontend:latest

浏览器访问http://服务器公网IP,看到你的页面就是成功。

如果是前后端整套服务,直接git clone后到项目根目录docker compose up -d,一次拉起所有服务。

7.4 更新部署:改代码之后怎么发新版本

部署不是一次性的,线上项目每周甚至每天都要迭代。手动部署的更新流程是:

cd /opt/app/frontend git pull docker build -t my-frontend:latest . docker stop frontend-container docker rm frontend-container docker run -d -p 80:80 --name frontend-container my-frontend:latest

这套流程虽然原始,但每一步清晰可控,适合中小型项目和刚开始接触容器化的团队。等跑熟之后再考虑接入 CI/CD 自动化流程,让提交代码自动构建、自动部署。

7.5 用 Docker 做自动化部署的思路

前面那套手动流程,最终可以升级为一条 CI 流水线:代码 push 到仓库后,自动触发构建镜像,推送到镜像仓库,再通过 SSH 远程到服务器拉取新镜像并重启容器。GitHub Actions 里很常见的步骤大概是这样的:checkout 代码,登录镜像仓库,构建并 push 镜像,SSH 到服务器执行docker pulldocker compose up -d

这个过程里 Docker 的价值再次体现出来:镜像在构建阶段就把环境固定住了,服务器上只需要有 Docker,不需要关心 Node 版本、Nginx 版本这些依赖是否安装正确,因为镜像本身已经包含了完整可运行环境。

8. 前端部署实战中的高频问题与我的排查习惯

最后分享一些我在实际部署中遇到很多次、也帮初学者排查过很多次的常见问题。

8.1 容器启动后立即退出

docker logs <容器ID>大概率会显示具体报错。常见原因不外乎:CMD 命令写错、Nginx 没有前台运行、端口被占用。前两个前面已经讲过,端口被占用则是在宿主机执行netstat -tlnp | grep 80检查一下哪个进程霸占了 80 端口,把冲突的进程关掉或改用其他宿主端口。

8.2 页面 404 或白屏

先确认路由模式。如果是 history 模式,Nginx 有没有配置try_files;如果是纯静态托管,要确认root指向的目录里是不是真的有index.html。我见过很多次实际组合是:Dockerfile 里COPY的源路径跟构建产物目录对不上,dist 根本没被复制进去,容器里当然找不到页面。

进容器里验证一下是最直接的方式:

docker exec -it <容器ID> sh ls /usr/share/nginx/html

看到没有文件,就说明 Dockerfile 里 COPY 的路径有问题,或者构建阶段根本没有产出 dist。

8.3 前端请求接口 502

502 的本质是 Nginx 在后端地址上连不上一个有效服务。常见原因有这么几个:proxy_pass配置的地址端口不对;后端容器没有在 Compose 网络里;后端启动失败。前端能访问到 Nginx 页面,说明 Nginx 本身是好的,问题出在 Nginx 和后端之间的链路上。先docker compose ps确认后端在不在,再docker compose logs backend看启动日志,逐步缩小范围。

8.4 环境变量在构建时失效

这个坑很多前端会踩:在 Dockerfile 里写RUN npm run build,然后在构建命令里指定环境变量:

docker build --build-arg API_BASE_URL=https://api.example.com -t my-frontend .

但 Dockerfile 里没有声明 ARG 或 ENV,导致变量没有传递进去。正确做法是在 Dockerfile 里增加:

ARG API_BASE_URL ENV VITE_API_BASE_URL=$API_BASE_URL

Vite 的环境变量默认只支持以VITE_前缀开头的变量在构建时被注入到代码中。这个细节不搞清楚,构建出来的前端可能会在运行时一直报接口地址没配置的错误。

8.5 我个人的部署习惯

经过一段时间的项目历练后,我现在养成了几个固定习惯,也变成了一套操作模板:镜像标签永远打明确版本,而不是一直用 latest,方便回滚;构建命令固定用docker compose up -d --build组合,确保代码更新时镜像同步构建;每次部署完一定在浏览器用无痕窗口访问一次线上地址,避免本地缓存干扰判断;日志别等出问题了才看,养成docker compose logs随便翻一翻的习惯,能提前发现很多隐患。

还有一点对前端特别友好的认知:Docker 和前后端分离架构天然就是配合的。前端构建出的产物本质只是静态文件,而静态文件的托管用 Docker + Nginx 本来就是最标准、最正规的组合。掌握了这套流程以后,你会发现自己对"怎么把一个前端项目安全、稳定、可更新地跑在服务器上"这件事,从模糊变成清晰,这种掌控感还挺值得拥有的。

以后不管是参加面试被问到 Docker 的底层原理,还是工作中要给团队发一个测试包,你都具备了动手做出来、而不是只说概念的能力,这种"能做出来"的底气,比背熟一堆命令更有价值。

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

浏览器端隐私工具,AnyDoc WebAssembly 转换,TaoToken 发 Key

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

作者头像 李华
网站建设 2026/9/19 3:40:58

BabelDOC:免费开源的PDF翻译工具,一条命令拿到双语对照版

BabelDOC&#xff1a;免费开源的PDF翻译工具&#xff0c;一条命令拿到双语对照版 【免费下载链接】BabelDOC Yet Another Document Translator 项目地址: https://gitcode.com/GitHub_Trending/ba/BabelDOC BabelDOC是一个开源的PDF翻译工具。它把PDF文本交给大语言模型…

作者头像 李华
网站建设 2026/9/19 3:40:18

iOS 18.4 + Xcode 27.1 的 iPhone Duo 架构演进与 SwiftUI 自适应布局实战

1. 项目概述&#xff1a;这不是“双屏iPhone”&#xff0c;而是开发者必须直面的系统级分形演进“iPhone Duo”这个称呼在中文开发者社区里火得有点突然&#xff0c;但翻遍苹果官网、WWDC视频和Xcode 27.1正式版发布日志&#xff0c;你根本找不到这个词。它不是一款新硬件&…

作者头像 李华