中恒智造官网 — 面试问答
项目时间:2022年7月 - 2022年9月 | 已上线运营 技术栈:Nuxt2 + Vue2 + ElementUI + JSEncrypt + AOS + ECharts + vue-baidu-map + v-viewer + cheerio + nuxt-precompress 线上地址:http://www.goomaker.com
Q1: Nuxt2 SSR 静态站点生成
面试官可能问:"为什么选择 Nuxt2?说说静态站点生成是怎么实现的?"
回答:
中恒智造官网是企业级 B2B 官网,展示产品、案例、公司信息。选 Nuxt2 有三个核心原因:
- SEO 需求:B2B 企业官网需要被搜索引擎收录,纯 SPA 的 JS 渲染对搜索引擎不友好。Nuxt2 的 SSR 模式在服务端渲染完整 HTML,搜索引擎爬虫能直接拿到带内容的页面。
- 静态站点部署:官网内容更新频率低,最终用了
target: 'static'+npm run generate生成纯静态 HTML 文件,部署到 Nginx 的html目录下,不需要 Node.js 进程,运维成本极低。 - Vue2 生态兼容:项目时间是 2022 年,Element UI 等依赖仍以 Vue2 为主,Nuxt2 是当时最成熟的 Vue2 SSR 方案。
静态生成流程:nuxt.config.js 设置 target: 'static' → npm run generate → Nuxt 遍历所有路由(/, /product, /news, /aboutus 等)→ 每个路由生成一个 index.html → 输出到 dist/ 目录 → 部署到 Nginx。
路由别名处理:做了一个 extendRoutes 的配置,给每个路由加了 /index.html 别名:
router: {
extendRoutes(routes) {
routes.forEach((route) => {
const alias = route.path.length > 1
? `${route.path}/index.html`
: "/index.html";
route.alias = alias;
});
},
}这样 /product/index.html 和 /product 都能访问,兼容一些老旧的 Nginx 配置对目录路径的处理。
追问:"为什么不用 Nuxt3?"
项目是 2022 年 7 月启动的,当时 Nuxt3 还在 RC 阶段,生态不成熟——Element Plus 的 Nuxt3 适配还没出、很多 Nuxt2 的 module 和 plugin 不能直接用。对生产项目来说,稳定性优先于追新。
Q2: 首屏性能优化(5.87s→1.16s)
面试官可能问:"首屏加载时间从 5.87s 降到 1.16s,怎么做到的?"
回答:
性能优化是项目的核心亮点之一,做了四个层面的优化:
第一层:资源压缩——Gzip + Brotli 双格式预压缩。
用 nuxt-precompress 模块在 npm run generate 时自动为每个静态文件(JS/CSS/HTML/SVG)同时生成 .gz 和 .br 两种压缩版本:
nuxtPrecompress: {
gzip: { enabled: true, compressionOptions: { level: 9 } },
brotli: { enabled: true, compressionOptions: { level: 11 } },
test: /\.(js|css|html|txt|xml|svg)$/,
}Nginx 端配置 gzip_static on,请求到达时如果存在预压缩文件就直接返回,不用实时压缩。浏览器支持 Brotli 就返回 .br(压缩率更高),不支持就降级 .gz。
第二层:Webpack 代码分割 + Tree Shaking。
Nuxt2 基于 Webpack 4,在生产构建时自动做 code splitting——把 node_modules 的第三方库拆成单独的 chunk(如 vendors.app.js),业务代码按路由懒加载。通过 splitChunks 的 maxSize: 244000 控制 chunk 大小,避免单个文件过大阻塞加载。
第三层:图片资源优化。
产品图片从 PNG 格式替换为 WebP 格式,体积平均减少 60-70%。Banner 背景图做了响应式适配——大屏用高清图,小屏用压缩图。
第四层:生产环境 Console 清理。
用 babel-plugin-transform-remove-console 在生产构建时自动移除所有 console.log,减少不必要的 JS 执行和内存占用。
追问:"怎么验证首屏时间从 5.87s 降到 1.16s?"
用 Chrome DevTools 的 Lighthouse 和 Network 面板分别测试。优化前首页完整加载 5.87s,主要是大图未压缩、JS bundle 未分割、没有 Gzip 导致的。优化后 1.16s,核心指标提升明显。
Q3: SEO 优化与 Cheerio 冗余标签清理
面试官可能问:"Nuxt SSR 输出的 HTML 有什么问题?怎么用 Cheerio 清理的?"
回答:
Nuxt2 的 SSR 渲染会在 HTML 标签上添加很多服务端渲染特有的属性,比如 data-n-head-ssr、data-n-head、data-hid、data-vue-ssr-id、data-server-rendered 等。这些属性对浏览器渲染没用,但会增加 HTML 体积、影响 SEO 评分、让人一看就知道是 Vue SSR。
在 nuxt.config.js 的 hooks.generate.page 钩子里用 Cheerio 做了清理:
hooks: {
generate: {
page(page) {
const cheerio = require("cheerio");
const $ = cheerio.load(page.html, { decodeEntities: false });
const attrs = [
"data-n-head-ssr", "data-n-head", "data-hid",
"data-vue-ssr-id", "data-server-rendered",
];
attrs.forEach((value) => {
$("*[" + value + "]").removeAttr(value);
});
page.html = $.html();
},
},
}这个钩子在每个页面生成静态 HTML 后触发,Cheerio 用 jQuery 风格的 API 遍历 DOM 树,批量移除这些属性,然后写回 page.html。最终生成的 HTML 干净整洁,和手写静态页面一样。
追问:"Cheerio 在这里做了什么?和 Puppeteer 有什么区别?"
Cheerio 是 Node.js 端的 HTML 解析库,提供 jQuery 风格的 DOM 操作 API。它不执行 JS、不渲染 CSS,只是解析和修改 HTML 字符串——速度极快、内存占用低,非常适合批量处理静态 HTML。Puppeteer 会启动完整浏览器,在这个场景下太「重」了——几百个 HTML 页面用 Puppeteer 处理会非常慢。
SEO 其他方面:还配置了 Open Graph 标签(og:title、og:description、og:image),分享到微信/Facebook 时能正确显示标题、描述和缩略图。head() 里动态设置每个页面的 title,百度统计脚本注入,keywords meta 标签覆盖核心关键词。
Q4: RSA 非对称加密全链路
面试官可能问:"为什么在官网里用 RSA 加密?怎么用的?"
回答:
官网有用户账户系统——注册、登录、密码重置等。为了保护用户的敏感信息(密码、手机号等)在传输过程中的安全,用 JSEncrypt 实现了 RSA 非对称加密,覆盖登录/注册/密码重置全链路。
核心技术选型:
- JSEncrypt:前端用 JSEncrypt 做常规加密,适合短文本
- encryptlong:前端用 encryptlong 做分段加密,解决 RSA 单次加密长度限制问题(RSA-1024 最多加密 117 字节,超长文本需要分段加密后再拼接)
插件封装(plugins/rsa.js):
Vue.prototype.$RSA = {
rsaPublicData(data) {
// 用 JSEncrypt + 公钥加密
},
encrypt(data) {
// 用 encryptlong 分段加密(超长文本)
},
decrypt(data) {
// 用 encryptlong + 私钥解密
},
};通过 Vue 插件挂载到原型上,所有组件通过 this.$RSA.encrypt(password) 直接调用。
使用场景:
- 登录:用户输入密码 → 前端
$RSA.encrypt(password)加密 → POST 到后端 → 后端用私钥解密验证 - 注册:密码、确认密码都经过 RSA 加密传输
- 密码重置:新密码加密后提交
追问:"公钥写死在前端代码里安全吗?"
RSA 是非对称加密——公钥本来就是公开的,任何人都可以拿到。安全性在于只有后端的私钥才能解密。公钥暴露在前端不影响安全性。真正的风险是中间人攻击(MITM)——如果攻击者替换了公钥就能解密。生产环境最好配合 HTTPS + HSTS,确保传输层安全。
Q5: 用户账户体系与试用申请工作流
面试官可能问:"用户系统和试用申请怎么设计的?"
回答:
官网有完整的用户账户体系,包括注册/登录/密码重置/实名认证,以及一个"申请试用"的业务工作流。
账户体系:
- 登录认证:JWT Token 机制——登录成功后后端返回 Token,前端存
localStorage,通过 Axios 拦截器自动在所有请求 Header 里带上token字段 - Token 管理:
plugins/axios.js的请求拦截器从localStorage.getItem("loginToken")读取 Token,自动注入到每个请求的config.headers.token - 路由守卫:
middleware/account-authentication.js和login-authentication.js做页面级权限控制——未登录访问账户页自动跳转登录页
Axios 封装:
const service = axios.create({ baseURL, timeout: 60 * 1000 });
// 请求拦截器:JSON 序列化 + Token 注入
service.interceptors.request.use((config) => {
config.data = JSON.stringify(config.data);
const token = localStorage.getItem("loginToken");
if (token) config.headers.token = token;
return config;
});
// 响应拦截器:统一错误处理(400/401/403/404/500 等)试用申请工作流:用户在页面点击"申请试用"→ 弹出 applyForTrial 组件 → 填写企业名称、姓名、联系方式 → 表单校验(手机号正则 + 必填项)→ POST 到 /api/userInfo/commitUserInfo → 后端记录后销售人员跟进。支持提交成功/失败的差异化提示,提交成功后自动清空表单。
追问:"实名认证是怎么做的?"
components/account/realNameAuthentication.vue 里收集用户的真实姓名、身份证号等信息,提交到后端做验证。这是为 B2B 业务场景设计的——只有实名认证的企业用户才能正式申请试用,确保销售线索的真实性。
Q6: 五档响应式断点与自定义指令
面试官可能问:"响应式是怎么设计的?移动端有什么特殊处理?"
回答:
官网做了五档响应式断点适配,覆盖从超宽屏到小手机的完整设备:
| 断点 | 宽度范围 | 适配策略 |
|---|---|---|
| 大屏 | >1920px | Banner 内容左侧留白 25%,视觉居中 |
| 桌面 | 1280-1920px | 标准 5 列布局,导航栏完整展示 |
| 平板 | 1000-1280px | 导航栏压缩间距,核心模块 flex 变 grid |
| 大手机 | 600-1000px | 导航栏隐藏→汉堡菜单,grid 缩为 2 列 |
| 小手机 | <600px | 单列布局,Banner 背景右对齐,Logo 简化 |
关键技术:
- CSS Media Query:用 SCSS 变量统一管理断点和间距,一个
_variables.scss文件全局引入 - JS 适配:
clientWidth通过resize事件实时获取,用于计算carouselHeight(响应式 Banner 高度)、组件切换(AOS 动画方向随屏幕宽度变化) - 移动端导航:<1000px 隐藏桌面导航栏,显示汉堡图标 → 点击滑入毛玻璃遮罩 Drawer,带动画延迟入场效果
自定义 v-ellipsis 指令(plugins/ellipsisDirective.js):
Vue.directive("ellipsis", {
inserted(el, bindings) {
// 设置 CSS 文本溢出省略
el.style.overflow = "hidden";
el.style.textOverflow = "ellipsis";
el.style.whiteSpace = "nowrap";
// 检测是否溢出 → 绑定 hover 事件 → 显示 tooltip
if (el.scrollWidth > el.offsetWidth) {
el.addEventListener("mouseenter", handleMouseEnter);
el.addEventListener("mouseleave", removeTooltip);
}
},
});核心思路:先用 CSS 做文本截断 → 再判断是否真的溢出了(scrollWidth > offsetWidth)→ 溢出时才绑定 hover 事件显示 tooltip。没溢出就不绑事件,不创建 DOM,性能最优。
tooltip 的边界检测:根据鼠标位置判断显示在上方还是下方、左侧还是右侧,避免 tooltip 超出视口边界。
追问:"响应式为什么要拆五档而不是用 Bootstrap 的栅格系统?"
B2B 企业官网的设计稿是高度定制的——每个模块在不同屏幕下的布局差异很大,不是简单的 col-md-6 能解决的。比如首页的服务模块在桌面端是横排 4 列、平板是 2x2 网格、手机是 2 列,每种布局都需要独立设计。用原生 Media Query + CSS Grid 的组合更灵活。
Q7: 九大产品线动态路由与详情页
面试官可能问:"九个产品的详情页是怎么组织的?动态路由怎么做?"
回答:
产品中心展示了 9 个产品,分三大类:智能装备(4 个)、智慧工厂(1 个)、ERP 定制产品(3 个)。产品列表页用 URL query 参数实现动态路由。
路由设计:产品详情页路由是 /product/details?type=1,type 从 1 到 9 对应不同产品。
数据驱动:details.vue 里用一个 product 对象映射所有产品信息:
product: {
1: { title: "成衣业务管理系统", subTitle: "...", preTitle: "ERP定制产品" },
2: { title: "面料生产管理系统", subTitle: "...", preTitle: "ERP定制产品" },
// ... 共 9 个产品
}computed 属性 currentTitle/currentSubTitle/currentPreTitle 根据 $route.query.type 动态取值,模板里用 v-if/v-else-if 加载对应的详情子组件。
子组件拆分:每个产品详情页是独立的 Vue 组件,放在 components/product/ 下——fineClothBussinessSystem.vue、fabricProduceSystem.vue、steamSystem.vue、bobbinMoistureMeter.vue、sizingMoistureSystem.vue 等。每个组件内包含产品概述、技术参数、功能亮点等模块。底部统一带"预约现场演示"按钮,点击打开申请试用弹窗。
面包屑导航:ElementUI 的 el-breadcrumb 实现"首页 > 产品中心 > 当前产品名"的三级面包屑,支持点击跳转。
产品中心页导航:用滚动监听实现了吸顶快速导航栏——三个分类(智能装备/智慧工厂/ERP定制产品)锚点,滚动到对应区域时导航栏高亮当前分类,支持点击平滑滚动到对应区域。
追问:"为什么用 query 参数而不是动态路由 /product/:id?"
产品数量固定(9 个),用 query 参数更简单——不用在 nuxt.config.js 里配 generate.routes 动态生成。而且 B2B 官网的产品详情页本身就有被 SEO 收录的需求,用静态生成的方式每个产品独立一个页面更友好。
Q8: 第三方集成与交互细节
面试官可能问:"集成了哪些第三方服务?有什么交互细节可以讲?"
回答:
1. 百度地图(vue-baidu-map)
用在"联系我们"页,展示北京总部和广州分公司两个办公地点。插件注册在 plugins/baiduMaps.js,配置 ssr: false 避免服务端渲染时报错(百度地图依赖 window 对象)。地图上加了自定义标注点、信息窗口,支持路况图层切换。
2. 图片预览(v-viewer)
用在资质证书展示、微信公众号二维码放大等场景。通过 plugins/picViewer.js 注册为全局方法 this.$viewerApi({ images: [...] }),调用时传入图片数组即可弹出全屏预览轮播。
3. 百度统计 + 51la 统计
nuxt.config.js 的 head.script 直接注入百度统计脚本。51la 通过插件 plugins/51la.js 在客户端加载。
4. AOS 滚动动画
全局注册 aos.js 插件,所有模块的入场动画通过 data-aos 属性声明——fade-up、zoom-in-down、zoom-out-right 等,配合 data-aos-delay 和 data-aos-offset 控制动画时序。移动端部分动画方向做了适配(比如概念模块的第三个卡片在大屏用 zoom-out-left,小屏用 fade-up)。
5. 交互细节——关于我们页资质证书无限轮播
用 requestAnimationFrame 实现了一个带焦点缩放的无限轮播动画。证书图片从左向右自动滚动,鼠标悬浮时暂停,悬停的证书放大(transform: scale(1.1)),离开后恢复。核心逻辑是不断更新 translateX 偏移量,超出边界后重置到起点,形成无缝循环。用 requestAnimationFrame 而非 setInterval 是因为 rAF 与浏览器刷新率同步,动画更流畅且页面不可见时自动暂停。
6. 背景粒子特效
plugins/stars.js(星空粒子背景)、plugins/waves.js(波浪背景)在特定页面使用,通过 Canvas 绘制实现。
追问:"AOS 初始化时机怎么控制?"
AOS 依赖 DOM 和 window,所以插件设了 ssr: false。初始化在 mounted 里调 AOS.init(),设置 duration: 700, easing: 'ease-out-cubic'。部分模块通过 v-if="isDone" 等待数据加载完成后再渲染,确保 AOS 能正确检测到元素的初始位置。
行为面试常见题快速答
"为什么做这个项目?"
公司的官网是业务的门面。之前用的是 WordPress 模板站,加载慢、不好维护、不支持定制化。老板提需求要做一套自主可控的企业官网,能展示 9 大产品线、支持用户注册和试用申请、在搜索引擎上有好的排名。我从零搭建,独立完成了整个前端项目。
"遇到的最大难点?"
Gzip + Brotli 预压缩的 Nginx 配置调试。nuxt-precompress 生成的 .gz 和 .br 文件放在静态资源目录,但 Nginx 需要正确配置 gzip_static on 才能优先返回预压缩文件。如果 Nginx 版本太低(< 1.11.x),Brotli 模块需要额外编译安装。花了不少时间查文档、改 nginx.conf,最终用 encodingsPriority: ["br", "gzip"] 确保浏览器优先尝试 Brotli。
还有 IE 兼容性问题——部分工厂客户还在用 IE11,而 Nuxt2 默认不转译 node_modules 里的包。通过 build.transpile: [/^element-ui/] 强制转译 Element UI,解决了 IE 下的白屏问题。
"有什么遗憾?"
- 没有 TypeScript——项目是纯 JavaScript 写的,类型安全全靠记忆和注释。如果重来会用 Nuxt2 + TypeScript 或直接上 Nuxt3。
- 没有 CI/CD——部署流程是手动
npm run generate→ 手动上传到服务器,应该配 GitHub Actions 自动构建部署。 - 样式变量管理不够统一——早期页面各自写了硬编码的颜色值,后来才抽取了
_variables.scss。还有一些老页面的样式没有完全迁移到变量体系。
"怎么跟设计协作的?"
设计师出 PSD 设计稿,我切图 + 写页面。因为没有 UI 组件库的约束,我封装了一些通用组件——common-banner(带背景图的页面标题横幅)、common-header、common-footer、applyForTrial(申请试用表单)、rightSideBar(右侧浮动工具栏)——风格统一,20+ 页面共用同一套组件。
"项目上线后的维护情况?"
上线后持续迭代——加了新产品(筒子纱智能回潮仪、浆纱水分测量系统),优化了首页 Banner 和产品入口卡片的交互动效(3D 倾斜 + 聚光灯特效),升级了 Header 导航栏的毛玻璃效果。虽然不是高频迭代的项目,但公司有新需求时我会快速响应。
上一篇:08-EMI云数据平台 回到首页:README