news 2026/9/3 2:51:20

TypeScript全栈类型贯通:MySQL到Vue3的工程化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TypeScript全栈类型贯通:MySQL到Vue3的工程化实践

简介:本资源是一套基于 TypeScript 全栈技术栈实现的用户登录注册系统实例,面向前端与全栈初学者及中级开发者,解决身份认证模块从接口设计到界面交互的完整落地问题。后端采用 TypeScript + Express + MySQL 构建 RESTful 接口,前端使用 TypeScript + Vue3 + Element Plus 开发响应式管理界面,覆盖表单验证、Token 鉴权、路由守卫等核心业务逻辑。压缩包含 2000 个文件,以 1100 个 JavaScript/TypeScript 源码文件(含服务端路由、数据库模型、Vue 组件与组合式 API 逻辑)、764 个 Markdown 文档(含接口说明、开发流程、部署指南)及 129 个 JSON 配置文件(含环境变量、数据库连接、路由元信息)为主,整体大小为 58.57MB。已有 1102 人学习下载,配套详细开发文档与分阶段视频教程,涵盖 Express 后端搭建、MySQL 表结构设计、Vue3 表单联动、Element Plus 组件定制及跨域调试等实战要点,代码结构清晰、注释完备,便于快速理解与二次开发。

1. 这不是“又一个登录demo”,而是全栈TypeScript工程化落地的最小可行切片

我带过三届前端校招实习生,每次让他们从零搭一个带登录的全栈项目,90%的人卡在“怎么让Vue3表单和Express后端真正对话”这一步——不是不会写axios,而是搞不清TS类型怎么贯穿前后端、Element Plus的校验规则怎么和后端返回的错误码对齐、MySQL字段变更后TS接口要同步改几处。这个标题里五个技术栈的组合,表面看是堆砌,实则暗含了现代Web开发中三个关键断层:类型系统在前后端之间的断裂、UI组件库与业务逻辑的耦合断裂、数据库Schema与应用代码的同步断裂。我去年重构一个老项目时,就用这套组合把登录模块的维护成本降低了70%,核心不是技术多炫,而是让TypeScript真正成为连接器,而不是装饰品。

你可能正面临这些具体问题:Vue3里用ref定义的表单数据,提交时类型推导总报错;Express路由里写req.body.username,但IDE不提示字段;Element Plus的el-form校验规则写了一大堆,后端返回{ code: 4001, message: "用户名已存在" },前端却要手动解析再映射到对应输入框;MySQL建表时加了个is_deleted TINYINT(1) DEFAULT 0,结果Vue3的用户列表页面直接报Property 'is_deleted' does not exist on type 'User'。这些问题背后,不是某个框架用得不熟,而是缺少一套让TS类型流经Express、Vue3、Element Plus、MySQL的完整链路。本文不讲“如何安装Node.js”,而是聚焦在这条链路上每个环节的真实衔接点——比如MySQL的DATETIME字段,在TS里该用string还是Date?Express中间件如何把req.body自动转成TS接口类型?Element Plus的rules配置怎么从后端错误码自动生成?这些细节,才是项目能跑通、还能长期维护的关键。

这个实例适合两类人:一是刚学完Vue3和Express,想验证自己是否真懂“全栈”概念的开发者;二是团队里正在推进TypeScript落地,但卡在“前后端类型不一致导致联调反复返工”的技术负责人。它不追求功能复杂度(没有短信验证码、没有OAuth),但每行代码都经过生产环境验证——比如MySQL连接池的acquireTimeoutMillis设为5000而非默认的30000,是因为我们线上遇到过数据库短暂抖动时,Express请求堆积导致整个服务雪崩;比如Vue3的defineEmits不写泛型而用<string>,是因为Element Plus的el-form事件名是字符串而非Symbol,强行用泛型反而破坏类型安全。所有选择都有明确的生产理由,不是教程式妥协。

2. MySQL Schema设计:从“能存数据”到“驱动TS类型生成”的思维转变

很多人建MySQL表时只考虑“字段够用”,但在这个TS+Express+Vue3架构里,表结构直接决定了TS接口的可靠性。我见过最典型的错误是:users表里created_at字段用TIMESTAMP类型,后端Express返回JSON时自动转成字符串,但前端Vue3里却用Date对象处理,结果new Date(user.created_at)在某些时区下解析失败。这不是Bug,是Schema设计没考虑类型流。

2.1 字段类型与TS类型的精确映射表

MySQL类型推荐TS类型理由实际案例
INT,TINYINT(1)number避免boolean陷阱:MySQL的TINYINT(1)存储0/1,但JSON序列化后是数字,Boolean(0)falseBoolean(1)true,看似正确,但user.status === true会失效(因为1 === truefalse用户状态字段status TINYINT(1) DEFAULT 1,TS接口用status: number,Vue3中判断用user.status === 1
VARCHAR(255),TEXTstring统一处理空字符串和NULL:MySQL允许VARCHAR为空字符串或NULL,Express默认将空字符串转为"",NULL转为null,TS接口必须声明name: string | null,否则user.name.length会报错用户昵称nickname VARCHAR(50),TS接口nickname: string | null,Vue3模板中用{{ user.nickname ?? '未设置' }}
DATETIME,TIMESTAMPstring强制约定:后端Express统一返回ISO格式字符串(如"2023-10-15T08:30:00.000Z"),前端Vue3不自行解析,交由dayjsdate-fns处理。避免new Date()在不同环境下的时区差异登录时间last_login DATETIME,Express返回"2023-10-15T08:30:00.000Z",Vue3中直接绑定到<el-date-picker v-model="user.last_login" />
JSONRecord<string, any>或具体接口MySQL 5.7+支持JSON类型,但Express的body-parser默认不解析,需额外配置express.json({ type: 'application/json', limit: '10mb' })用户配置settings JSON,TS接口settings: UserSettings,其中UserSettings是独立接口

提示:不要用ENUM类型!MySQL的ENUM('active','inactive')在TS里会生成联合类型'active' \| 'inactive',看似类型安全,但一旦后端新增状态值,前端必须同步改TS接口,且MySQL迁移脚本容易出错。改用VARCHAR(20)+ 后端校验更灵活。

2.2 生产级建表SQL:兼顾可读性与可维护性

-- users表:核心用户信息 CREATE TABLE `users` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名,唯一索引', `email` VARCHAR(255) NOT NULL UNIQUE COMMENT '邮箱,唯一索引', `password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希值,BCRYPT算法', `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态:1-启用,0-禁用', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), INDEX `idx_username` (`username`), INDEX `idx_email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户主表'; -- 补充:为什么不用`AUTO_INCREMENT`的`INT`? -- 因为用户量超过21亿时会溢出,`BIGINT`支持9.2e18,足够支撑十年增长。 -- 为什么`password_hash`长度设为255? -- BCRYPT哈希值固定长度60字符,但预留空间以防算法升级(如SCRYPT)。

2.3 TS接口自动生成:用mysql-schema-ts消除手写接口的维护噩梦

手写TS接口最大的问题是:MySQL字段改了,前端忘了改,运行时才报错。我们用mysql-schema-ts工具自动生成,流程如下:

  1. 安装依赖:npm install -D mysql-schema-ts
  2. 创建配置文件mysql-schema.config.ts
import { defineConfig } from 'mysql-schema-ts'; export default defineConfig({ host: 'localhost', port: 3306, user: 'root', password: 'your_password', database: 'auth_db', outputDir: './src/types/generated', // 关键配置:将MySQL类型映射到TS typeMap: { 'BIGINT': 'number', 'VARCHAR': 'string', 'DATETIME': 'string', 'TINYINT': 'number', }, });
  1. 执行生成命令:npx mysql-schema-ts --config mysql-schema.config.ts
  2. 自动生成./src/types/generated/users.ts
// 自动生成,勿手动修改 export interface UsersRow { id: number; username: string; email: string; password_hash: string; status: number; created_at: string; updated_at: string; }

注意:生成的接口名是UsersRow而非User,因为MySQL表名是复数,符合数据库命名习惯。Vue3中使用时重命名:import type { UsersRow as User } from '@/types/generated/users';。这样既保持生成代码的规范性,又提升业务代码可读性。

2.4 连接池配置:不只是“能连上”,而是“连得稳”

Express连接MySQL不能用mysql.createConnection(),必须用连接池。我们生产环境的配置参数有明确依据:

import mysql from 'mysql2/promise'; const pool = mysql.createPool({ host: 'localhost', port: 3306, user: 'app_user', password: 'strong_password', database: 'auth_db', waitForConnections: true, // 必须为true,否则连接耗尽时直接抛错 connectionLimit: 10, // 根据服务器CPU核心数:4核服务器设为10,8核设为20 queueLimit: 0, // 0表示无限制,避免请求被丢弃 acquireTimeoutMillis: 5000, // 关键!默认30000ms太长,5秒超时可快速失败,防止线程阻塞 waitForTimeoutMillis: 10000, // 获取连接超时10秒,比acquireTimeout长,确保有缓冲 timezone: 'Z', // 强制UTC时区,避免时区转换错误 });

踩坑实录:某次数据库主从切换,从库延迟10秒,acquireTimeoutMillis设为30000导致Express请求排队,TPS从1200骤降到200。改成5000后,超时请求快速失败,监控告警触发扩容,系统平稳过渡。

3. Express后端:用TS类型守门,让每个请求都带着“契约”

Express本身是JS框架,但加上TS后,它的价值在于把HTTP协议的隐式契约变成显式类型约束。比如POST /api/login的请求体,不再靠文档约定,而是由TS接口强制校验。

3.1 请求体类型守卫:zodjoi更适合TS生态

我们放弃joi,选择zod,因为它的类型推导与TS无缝集成:

import { z } from 'zod'; // 定义登录请求体Schema export const LoginBodySchema = z.object({ username: z.string().min(3, '用户名至少3位').max(50, '用户名最多50位'), password: z.string().min(6, '密码至少6位'), }); // 自动推导TS类型 export type LoginBody = z.infer<typeof LoginBodySchema>; // 使用:在路由中校验 app.post('/api/login', async (req, res) => { try { // ✅ 类型安全:parse后req.body就是LoginBody类型 const body = LoginBodySchema.parse(req.body); // ✅ IDE智能提示:body.username, body.password const user = await findUserByUsername(body.username); if (!user || !await verifyPassword(body.password, user.password_hash)) { return res.status(401).json({ code: 4001, message: '用户名或密码错误' }); } res.json({ code: 0, data: { token: generateToken(user) } }); } catch (error) { if (error instanceof z.ZodError) { // ✅ Zod错误自动转为标准格式 return res.status(400).json({ code: 4000, message: '参数错误', errors: error.errors.map(e => ({ path: e.path.join('.'), message: e.message })) }); } res.status(500).json({ code: 5000, message: '服务器内部错误' }); } });

为什么不用Express内置的req.body类型?因为any类型无法提供字段提示,且无法做运行时校验。zodparse方法在开发时提供TS类型,在运行时做校验,一举两得。

3.2 响应体类型:用泛型统一错误结构,让前端解耦

前端不需要关心每个接口的message字段在哪,我们定义统一响应结构:

// src/types/api.ts export interface ApiResponse<T = unknown> { code: number; // 0成功,非0失败 message: string; // 人类可读消息 data?: T; // 成功时的数据 errors?: Array<{ path: string; message: string }>; // 失败时的字段级错误 } // 在路由中使用 app.post('/api/login', async (req, res) => { // ... 业务逻辑 res.json<ApiResponse<{ token: string }>>({ code: 0, message: '登录成功', data: { token: 'xxx' } }); });

关键技巧:Vue3调用时,axios.post<ApiResponse<User>>('/api/login', data),响应data自动是User类型,无需as断言。

3.3 MySQL查询的TS类型安全:mysql2/promise+zod双重防护

直接pool.execute('SELECT * FROM users WHERE id = ?', [id])返回any[],类型不安全。我们封装查询函数:

import { UsersRow } from '@/types/generated/users'; import { z } from 'zod'; // 查询单个用户,返回严格类型 export async function findUserById(id: number): Promise<UsersRow | null> { const [rows] = await pool.execute<UsersRow[]>('SELECT * FROM users WHERE id = ?', [id]); // ✅ zod二次校验:防止数据库脏数据 if (rows.length > 0) { try { return UserSchema.parse(rows[0]); // UserSchema是基于UsersRow的zod校验 } catch (error) { console.error('User data validation failed:', error); throw new Error('Invalid user data'); } } return null; } // UserSchema定义 export const UserSchema = z.object({ id: z.number().int().positive(), username: z.string().min(3), email: z.string().email(), password_hash: z.string().min(60), // BCRYPT哈希长度 status: z.number().int().min(0).max(1), created_at: z.string().datetime(), updated_at: z.string().datetime(), });

3.4 密码安全:BCRYPT不是“选配”,而是“标配”

bcryptsaltRounds设为12,不是拍脑袋:

  • saltRounds=10:约需10ms,对登录场景稍快,但安全性不足
  • saltRounds=12:约需40ms,平衡安全与性能,符合OWASP推荐
  • saltRounds=14:约需160ms,登录体验明显卡顿
import bcrypt from 'bcrypt'; // 加密密码 export async function hashPassword(password: string): Promise<string> { return bcrypt.hash(password, 12); // 12是生产环境黄金值 } // 校验密码 export async function verifyPassword( plainPassword: string, hashedPassword: string ): Promise<boolean> { return bcrypt.compare(plainPassword, hashedPassword); }

踩坑实录:曾用saltRounds=15,高峰期登录接口平均响应时间达200ms,用户投诉“卡顿”。降为12后稳定在45ms内。

4. Vue3前端:让Element Plus的表单成为TS类型的延伸

Vue3的<script setup>语法糖和TS结合,让Element Plus不再只是UI组件,而是类型系统的可视化部分。

4.1 表单数据与TS接口的双向绑定:ref的类型声明是关键

<script setup lang="ts"> import { ref, reactive } from 'vue'; import { ElForm, ElFormItem, ElInput, ElButton } from 'element-plus'; import type { LoginBody } from '@/types/api'; // 复用后端TS接口 // ✅ 正确:用LoginBody类型声明ref const loginForm = ref<LoginBody>({ username: '', password: '' }); // ✅ 正确:用ref包装,保持响应式 const formRef = ref<InstanceType<typeof ElForm> | null>(null); // ✅ 错误:any类型,失去类型保护 // const loginForm = ref({ username: '', password: '' }); </script>

4.2 Element Plus校验规则:从后端错误码动态生成

硬编码校验规则(如{ required: true, message: '请输入用户名' })会导致前后端不一致。我们让后端返回错误码,前端动态映射:

// src/utils/formRules.ts export const generateFormRules = (errors: Array<{ path: string; message: string }>) => { const rules: Record<string, any[]> = {}; errors.forEach(error => { const field = error.path; // 如 'username', 'password' if (!rules[field]) { rules[field] = []; } rules[field].push({ validator: (rule: any, value: string, callback: (msg?: string) => void) => { callback(error.message); // 直接显示后端消息 }, trigger: 'blur' }); }); return rules; }; // 在组件中使用 const rules = reactive({ username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, message: '用户名至少3位', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, message: '密码至少6位', trigger: 'blur' } ] }); // 提交失败时,用后端errors覆盖rules const onSubmit = async () => { if (!formRef.value) return; try { const res = await api.login(loginForm.value); // 成功逻辑 } catch (error: any) { if (error.response?.data?.errors) { // ✅ 动态更新rules Object.keys(rules).forEach(key => { rules[key] = []; // 清空原有规则 }); Object.assign(rules, generateFormRules(error.response.data.errors)); } } };

4.3 API调用的TS类型流:axios拦截器注入类型

axios默认不识别TS泛型,我们用拦截器统一处理:

// src/utils/request.ts import axios from 'axios'; import type { ApiResponse } from '@/types/api'; // 创建实例 const request = axios.create({ baseURL: '/api', timeout: 10000, }); // 响应拦截器:统一处理API响应 request.interceptors.response.use( response => { // ✅ 假设后端返回结构统一为ApiResponse const data = response.data as ApiResponse; if (data.code !== 0) { // 抛出错误,便于try/catch捕获 throw new Error(data.message); } return data.data; // 直接返回data字段,类型由泛型决定 }, error => { // 统一错误处理 if (error.response?.status === 401) { // 跳转登录页 router.push('/login'); } throw error; } ); // 封装登录API export const api = { login: (data: LoginBody) => request.post<ApiResponse<{ token: string }>>('/login', data), register: (data: RegisterBody) => request.post<ApiResponse<{ userId: number }>>('/register', data), };

效果api.login(form).then(data => { /* data是{ token: string }类型 */ }),无需as断言。

4.4 Element Plus主题定制:不改源码,用CSS变量精准控制

Element Plus的暗色模式、圆角等需求,不用deep穿透或全局覆盖:

// src/styles/element-plus.scss @use 'element-plus/theme-chalk' as *; // ✅ 全局修改CSS变量 :root { --el-color-primary: #1890ff; // 主色 --el-border-radius-base: 8px; // 圆角 --el-font-size-extra-large: 18px; // 大号字体 } // ✅ 按需覆盖组件变量 .el-button--primary { --el-button-bg-color: var(--el-color-primary); --el-button-border-color: var(--el-color-primary); }

为什么不用el-buttontype属性?因为type="primary"的样式由CSS变量控制,改变量即可全局生效,比写一堆class更可靠。

5. 全链路类型贯通:从MySQL到Vue3的“一次定义,处处可用”

真正的工程效率提升,不在于单点技术多炫,而在于类型定义如何像血液一样流经整个系统。我们用一个实际案例展示:当MySQL增加avatar_url字段时,整个链路如何自动响应。

5.1 字段变更的最小改动清单

  1. MySQL执行DDL
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500) NULL COMMENT '头像URL';
  1. 触发TS接口生成mysql-schema-ts自动更新UsersRow):
// ./src/types/generated/users.ts 新增字段 export interface UsersRow { // ... 其他字段 avatar_url: string | null; // ✅ 自动生成,类型准确 }
  1. 后端接口自动获得新字段(Express路由中findUserById返回值包含avatar_url):
// 不需要改代码,因为UsersRow已更新 const user = await findUserById(1); // user.avatar_url 可访问
  1. 前端Vue3组件自动获得类型提示
<script setup lang="ts"> import type { UsersRow as User } from '@/types/generated/users'; const user = ref<User | null>(null); // ✅ IDE提示:user.value?.avatar_url </script> <template> <el-avatar :src="user?.avatar_url" /> </template>

5.2 类型流断裂的典型场景与修复方案

断裂点现象修复方案工具/配置
MySQL → TS接口字段改了,前端报Property 'xxx' does not exist每次DDL后执行npx mysql-schema-tsmysql-schema-tsCLI
Express → Vue3后端返回{ avatar_url: null },前端v-if="user.avatar_url"不渲染在TS接口中声明avatar_url: string | null,Vue3中用v-if="user.avatar_url != null"zod.nullable()校验
Element Plus → 业务逻辑el-input绑定user.avatar_url,但用户输入空字符串,后端期望null在表单提交前,将空字符串转为null
if (form.avatar_url === '') form.avatar_url = null;
Vue3onSubmit钩子

5.3 生产环境构建优化:TS类型检查不拖慢CI/CD

vue-tsc --noEmit在CI中检查类型,但默认会检查所有.d.ts文件,导致构建慢。我们优化tsconfig.json

{ "compilerOptions": { "skipLibCheck": true, // 跳过node_modules中.d.ts检查 "types": ["node", "webpack-env"] // 显式指定类型,避免自动扫描 }, "include": [ "src/**/*", "types/**/*" ], "exclude": [ "node_modules", "dist" ] }

实测效果:CI中vue-tsc检查从120秒降至8秒。

6. 部署与运维:让TypeScript的严谨性延续到生产环境

开发阶段的类型安全,最终要体现在生产环境的稳定性上。我们用三个实践确保这一点。

6.1 Express进程管理:pm2不是“启动就行”,而是“按需重启”

pm2 start ecosystem.config.js的配置:

// ecosystem.config.js module.exports = { apps: [{ name: 'auth-api', script: './dist/server.js', instances: 'max', // 根据CPU核心数自动分配 exec_mode: 'cluster', // 集群模式,充分利用多核 watch: false, // ❌ 禁用watch,生产环境不热更新 max_memory_restart: '500M', // 内存超500MB自动重启,防内存泄漏 env: { NODE_ENV: 'production', DB_HOST: '127.0.0.1', DB_PORT: '3306', // ✅ 敏感配置不写死,用环境变量 } }] };

6.2 MySQL连接健康检查:不只是ping,而是“查表”

Express启动时,不只ping数据库,而是执行真实查询:

// src/server.ts async function checkDatabase() { try { // ✅ 执行真实查询,验证连接和权限 await pool.execute('SELECT 1'); console.log('✅ Database connected'); } catch (error) { console.error('❌ Database connection failed:', error); process.exit(1); } }

6.3 Vue3构建产物分析:用source-map-explorer定位TS类型残留

构建后,检查dist目录是否有未使用的TS类型代码:

npx source-map-explorer dist/assets/index.*.js

发现并修复:曾因import type { User } from '@/types/user'.d.ts文件中被引用,导致构建产物包含类型代码(约12KB)。解决方案:确保所有import type只在.ts文件中,.d.ts中用declare

最后分享一个小技巧:在Vue3组件中,用defineProps<{ user: User }>()时,如果User接口过大,可拆分为UserBase(必填字段)和UserDetail(可选字段),减少组件props的类型体积,提升编译速度。

这个登录注册实例,本质上是一套TypeScript驱动的全栈开发范式。它不追求技术堆砌,而是让每个技术栈都服务于同一个目标:让类型信息从数据库Schema开始,流经Express路由、TS接口、Vue3组件,最终在浏览器中呈现为可预测、可维护的代码。当你下次再看到“ts+express+vue3+elementplus+mysql”这样的标题,别再把它当成技术列表,而要意识到——这是现代Web开发中,类型系统贯通全链路的最小完整证据。

本文还有配套的精品资源,点击获取

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

本地AI生成项目评估指南:从部署到验证的完整流程

这次我们不铺垫背景&#xff0c;直接说一个事&#xff1a;你手上可能刚拿到一个名字很“不正经”的项目——Ready, Set, BANG。名字里带着心形和爆炸符号&#xff0c;看起来更像是创意工坊里的产物&#xff0c;而不是那种一本正经的工程框架。但在本地 AI 工具越来越卷的现在&a…

作者头像 李华
网站建设 2026/9/3 2:46:26

基于Python与深度学习的垃圾分类系统:从模型选型到部署实战

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

作者头像 李华
网站建设 2026/9/3 2:44:38

MATLAB实现OFDM系统SLM算法抑制PAPR的工程实践

简介&#xff1a;本资源是一套面向通信工程专业本科生及无线通信方向初学者的MATLAB仿真实验包&#xff0c;聚焦OFDM系统中关键挑战——峰值平均功率比&#xff08;PAPR&#xff09;抑制问题。通过实现空间调制&#xff08;SLM&#xff09;与限幅滤波两类主流降PAPR技术&#x…

作者头像 李华
网站建设 2026/9/3 2:43:31

半导体刻蚀技术:从湿法到干法,掌握芯片制造的核心工艺

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

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

Spring Boot+Vue构建简易完整仓库管理系统:从设计到部署实战

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

作者头像 李华
网站建设 2026/9/3 2:39:54

茶叶商城源码:PHP全栈实现微信支付宝支付与三级分销

简介&#xff1a;这是一套面向茶叶电商创业者的全栈型商城源码&#xff0c;专为零基础或轻开发能力的个体商户与中小企业设计&#xff0c;解决从建站、支付到分销拓客的一站式落地难题。资源共2007个文件&#xff0c;主体为395个PHP后端逻辑文件、378个JS交互脚本、264个GIF动效…

作者头像 李华