外观
代码规范
具体可以参考 阿里巴巴前端规约。
相关文档
快速参考
- 项目开发规范 - 核心原则、技术栈概览、快速开发指南
技术规范
开发指南
TypeScript 类型导入规范
从第三方库导入类型时使用 type 关键字
重要:从第三方库(如 Element Plus、Vue 等)导入类型声明时,必须使用 type 关键字,以明确标识这是类型导入,而不是值导入。这有助于:
- 提高代码可读性
- 避免在运行时引入不必要的代码
- 符合 TypeScript 最佳实践
正确示例:
typescript
import {
ElMessage,
ElMessageBox,
type FormInstance,
type FormRules,
} from "element-plus";1
2
3
4
5
6
2
3
4
5
6
错误示例:
typescript
// ❌ 错误:类型导入没有使用 type 关键字
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";1
2
2
说明:
- 值(如
ElMessage、ElMessageBox)不需要type关键字 - 类型(如
FormInstance、FormRules)必须使用type关键字 - 可以在同一个 import 语句中混合使用值和类型,但类型必须使用
type关键字
优先使用具名导入
重要:导入模块时,优先使用具名导入(named imports),而不是 import * as 的命名空间导入。这有助于:
- 提高代码可读性:明确知道使用了哪些函数
- 便于 tree-shaking:打包工具可以更好地优化代码
- 更好的 IDE 支持:自动补全和跳转更准确
- 明确依赖关系:一眼就能看出依赖了哪些函数
正确示例:
typescript
// ✅ 正确:使用具名导入
import {
createFileRecord,
queryFileList,
deleteFileRecord,
validateFileType,
} from "#services/file";
// 使用时直接调用函数名
const [err, result] = await queryFileList(params);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
错误示例:
typescript
// ❌ 错误:使用命名空间导入
import * as FileService from "#services/file";
// 使用时需要加命名空间前缀
const [err, result] = await FileService.queryFileList(params);1
2
3
4
5
2
3
4
5
说明:
- 优先使用具名导入,明确列出需要使用的函数
- 只有在确实需要导入大量函数且使用频率很高时,才考虑使用命名空间导入
- 即使是命名空间导入,也应该尽量改为具名导入,提高代码可读性
数据库查询规范
ONLY_FULL_GROUP_BY 模式下的分页查询
在 MySQL ONLY_FULL_GROUP_BY 模式下,使用聚合函数(如 count())时,不能与非聚合列同时出现在 SELECT 中。分页查询必须将列表查询和计数查询分开执行。
错误示例:
typescript
// ❌ 错误:会生成 select *, count(*) as total
const list = await query.limit(limit).offset(offset);
const totalResult = await query.clone().count({ total: "*" }).first();1
2
3
2
3
正确示例:
typescript
// ✅ 正确:分别执行列表查询和计数查询
const [list, totalResult] = await Promise.all([
query.clone().limit(limit).offset(offset),
query
.clone()
.clearSelect() // 清除之前的 select,避免冲突
.count("id as count")
.first(),
]);
const total = totalResult ? Number(totalResult.count) : 0;1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
关键要点:
- 使用
Promise.all并行执行两个查询,提升性能 - 在计数查询前使用
.clearSelect()清除之前的select("*") - 使用
count("id as count")而不是count({ total: "*" }) - 从结果中正确读取
count字段
更多详细信息请参考 数据库设计文档。
Vue 组件开发规范
组件命名规范
重要:组件名必须至少包含 2 个单词,使用 PascalCase 命名。这有助于:
- 避免与 HTML 原生元素冲突
- 提高代码可读性
- 符合 Vue 官方规范
正确示例:
vue
<!-- ✅ 正确:至少包含 2 个单词 -->
<SiteNavbar />
<PageContainer />
<MarkdownRender />
<SiteFooter />1
2
3
4
5
2
3
4
5
错误示例:
vue
<!-- ❌ 错误:只有一个单词 -->
<Navbar />
<Container />
<Footer />1
2
3
4
2
3
4
组件封装和复用
原则:当多个布局或页面中存在重复的代码时,应该将其封装成可复用组件。
最佳实践:
- 识别重复代码:检查多个文件中的重复部分(如导航栏、页脚、容器等)
- 创建组件:在
components目录下创建独立的组件文件 - 使用 Props:通过 Props 传递可变数据,保持组件的灵活性
- 统一样式:将样式封装在组件内部,使用
scoped避免样式污染
示例:
vue
<!-- components/SiteNavbar.vue -->
<template>
<header class="navbar">
<!-- 导航栏内容 -->
</header>
</template>
<script setup lang="ts">
interface Props {
activeLink?: "home" | "services" | "about" | "contact";
siteName?: string;
}
withDefaults(defineProps<Props>(), {
activeLink: "home",
siteName: "默认网站名称",
});
</script>
<style lang="scss" scoped>
/* 组件样式 */
</style>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用方式:
vue
<!-- layouts/PageHome.vue -->
<template>
<SiteNavbar :active-link="'home'" :site-name="siteName" />
</template>1
2
3
4
2
3
4
移动端适配最佳实践
重要:所有面向用户的组件都应该考虑移动端适配,确保在不同设备上都有良好的用户体验。
1. 响应式布局
使用媒体查询:使用 @media 查询适配不同屏幕尺寸
scss
/* 桌面端样式(默认) */
.navbar {
padding: 16px 20px;
}
/* 移动端样式 */
@media (max-width: 768px) {
.navbar {
padding: 12px 16px;
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
关键断点:
768px:移动端和桌面端的分界点- 可以根据实际需求调整断点值
2. 移动端导航栏优化
问题:在移动端,导航栏文字过长会导致布局问题(文字竖排、菜单溢出等)
解决方案:
简化标题:在移动端显示简化版本
vue<span class="site-name site-name-full">{{ siteName }}</span> <span class="site-name site-name-short">简化名称</span>1
2使用汉堡菜单:移动端使用汉堡菜单(hamburger menu)隐藏导航项
vue<button class="mobile-menu-toggle" @click="toggleMobileMenu"> <span></span> <span></span> <span></span> </button>1
2
3
4
5防止文字换行:使用
white-space: nowrap防止文字意外换行scss.site-name { white-space: nowrap; }1
2
3
3. 移动端交互优化
最佳实践:
触摸友好:按钮和链接要有足够的点击区域(至少 44x44px)
自动关闭菜单:点击菜单项后自动关闭移动端菜单
窗口大小监听:监听窗口大小变化,自动调整布局
typescriptconst handleResize = () => { if (window.innerWidth > 768) { isMobileMenuOpen.value = false; } }; onMounted(() => { window.addEventListener("resize", handleResize); }); onUnmounted(() => { window.removeEventListener("resize", handleResize); });1
2
3
4
5
6
7
8
9
10
11
12
13
4. 移动端样式注意事项
关键要点:
固定定位高度:使用固定定位的导航栏时,注意不同设备的高度差异
- 桌面端:通常 68px
- 移动端:通常 56px(可根据实际情况调整)
内容区域间距:主内容区域需要为固定导航栏留出空间
scss.main-content { padding-top: 100px; /* 桌面端 */ @media (max-width: 768px) { padding-top: 76px; /* 移动端:56px导航栏 + 20px间距 */ } }1
2
3
4
5
6
7避免水平滚动:确保内容不会超出屏幕宽度
scss.container { max-width: 100%; padding: 0 16px; /* 移动端增加内边距 */ }1
2
3
4
5. 测试建议
移动端测试清单:
- ✅ 在不同屏幕尺寸下测试(320px、375px、414px、768px 等)
- ✅ 测试文字是否正常显示,不会竖排或溢出
- ✅ 测试交互元素(按钮、链接)是否易于点击
- ✅ 测试菜单展开/收起是否流畅
- ✅ 测试横屏和竖屏切换
- ✅ 测试触摸滑动操作
VitePress 项目开发规范
1. 布局文件组织
目录结构(示例):
config/.vitepress/theme/
├── components/ # 可复用组件
│ ├── SiteNavbar.vue
│ ├── PageContainer.vue
│ ├── MarkdownRender.vue
│ └── SiteFooter.vue
├── layouts/ # 页面布局
│ ├── PageHome.vue
│ ├── PageServices.vue
│ ├── PageAbout.vue
│ └── PageContact.vue
├── Layout.vue # 布局入口
└── index.ts # 主题入口1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
原则:
- 可复用组件放在
components/目录 - 页面布局放在
layouts/目录 - 避免在布局文件中重复代码
2. 结构化数据(JSON-LD)配置
重要:结构化数据应该在页面初始渲染时就存在,而不是通过 JavaScript 动态添加。
正确方式:在 config.mts 的 head 配置中直接输出
typescript
head: [
[
"script",
{
type: "application/ld+json",
},
JSON.stringify({
"@context": "https://schema.org",
"@type": "LocalBusiness",
// ... 结构化数据
}),
],
];1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
错误方式:在 onMounted 钩子中动态添加
typescript
// ❌ 错误:搜索引擎可能无法及时抓取
onMounted(() => {
const script = document.createElement("script");
script.textContent = JSON.stringify(structuredData);
document.head.appendChild(script);
});1
2
3
4
5
6
2
3
4
5
6
优势:
- SEO 友好:搜索引擎可以直接从 HTML 源码中读取
- 性能更好:无需等待 JavaScript 执行
- 更可靠:即使 JavaScript 被禁用,结构化数据仍然存在
3. SEO 优化最佳实践
关键要点:
标题优化:包含关键词,长度适中(50-60 字符)
typescripttitle: "上海滕氏棉胎加工店 - 专业棉被加工、棉胎定制、被子翻新服务";1描述优化:包含关键信息(地址、电话、服务内容)
typescriptdescription: "上海滕氏棉胎加工店位于宝山区南蕴藻路1883号,专业加工棉被、腈纶、羊毛...电话15800427263...";1关键词标签:添加相关的搜索关键词
typescript[ "meta", { name: "keywords", content: "上海棉胎加工,棉被加工,棉胎定制,被子翻新,宝山棉被加工...", }, ];1
2
3
4
5
6
7结构化数据:使用 JSON-LD 格式,包含完整的商家信息
- 商家名称、电话、地址
- 营业时间
- 服务类型
- 网站 URL
4. 样式优化建议
配色方案:
- 避免使用过于鲜艳的颜色(大红大绿)
- 使用低饱和度的配色,更有高级感
- 保持配色统一,使用主题色贯穿整个网站
示例:
scss
// ✅ 推荐:深蓝灰色系,精致高级
$primary-color: #4a5568;
$primary-dark: #2d3748;
// ❌ 不推荐:过于鲜艳
$primary-color: #ff6b6b; // 红色
$primary-color: #4caf50; // 绿色1
2
3
4
5
6
7
2
3
4
5
6
7
Banner 设计:
- 避免使用过于深色的背景
- 使用浅色渐变,更清爽高级
- 文字颜色要与背景形成良好对比
卡片设计:
- 避免使用过于夸张的渐变背景
- 使用白色背景 + 浅色边框 + 轻微阴影
- 保持简洁统一的设计风格
未使用代码处理规范
未使用的 API 函数处理原则
重要:当发现已定义但未使用的 API 函数时,不应直接移除,而应先检查是否为未完成的功能。
处理流程:
检查是否为未完成功能:
- 查看 API 函数的定义位置和命名(如
apiDeleteRoom、apiCreateArticle等) - 检查是否有对应的类型定义(如
ParamsApiDeleteRoom、ReturnApiDeleteRoom) - 检查后端是否有对应的接口实现
- 检查是否有相关的业务需求文档
- 查看 API 函数的定义位置和命名(如
如果是未完成功能:
- 保留 API 函数,不要移除
- 实现对应的功能,确保功能完整
- 例如:如果发现
apiDeleteRoom未使用,应该在房间管理页面添加删除功能
如果确认是废弃代码:
- 只有在确认该功能确实不再需要时,才考虑移除
- 移除前应确认:
- 后端接口已废弃
- 没有其他页面或组件使用
- 相关类型定义也可以移除
正确示例:
typescript
// ✅ 正确:发现 apiDeleteRoom 未使用,实现删除功能
import { apiDeleteRoom } from "@/api/bookingRoom";
const handleDelete = async () => {
await ElMessageBox.confirm("确定要删除吗?", "确认删除");
const [err] = await apiDeleteRoom({ id: room.value.id });
if (err) {
ElMessage.error(err.message || "删除失败");
return;
}
ElMessage.success("删除成功");
router.back();
};1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
错误示例:
typescript
// ❌ 错误:直接移除未使用的 API 函数
// import { apiDeleteRoom } from "@/api/bookingRoom"; // 被移除了
// 这会导致功能不完整,用户无法删除房间1
2
3
2
3
关键要点:
- 优先实现功能:未使用的 API 函数通常是未完成的功能,应该实现它而不是移除它
- 保持功能完整性:确保 CRUD 操作(创建、读取、更新、删除)的完整性
- 检查业务逻辑:删除、创建、更新等操作通常都有对应的业务需求
- 参考类似功能:如果其他类似模块有相同功能,可以参考其实现方式
适用场景:
- API 函数已定义但未在组件中使用
- 后端接口已实现但前端未调用
- 类型定义已存在但未使用
- 功能需求明确但实现不完整