Files
hzdyjs/.cursor/cursorrules-frontend.md
T
zlwu 9b8e437c31 feat: 新增表格行双击查看和长文本显示规范
1. 添加表格行双击查看功能规范
- 强制要求所有表格支持双击查看详情
- 提供标准实现方式和事件处理
- 包含最佳实践建议

2. 添加表格长文本显示规范
- 统一使用3行显示限制
- 提供标准样式定义
- 包含最佳实践建议
2025-07-09 18:20:50 +08:00

730 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Vue 3 + TypeScript 前端开发规范
## 技术栈规范
- Vue 3.5.13
- TypeScript 5.8.3
- Element Plus UI
- Art Design Pro UI 设计规范
## 表格组件开发规范
### 1. 表格行双击查看功能(强制性)
#### 实现要求
- **✅ 必须实现**: 所有数据表格必须支持行双击查看详情功能
- **🎯 目标**: 提升用户体验,快速查看数据详情
#### 标准实现方式
```vue
<!-- 表格组件 -->
<el-table
v-loading="loading"
:data="dataList"
@selection-change="handleSelectionChange"
@row-dblclick="handleView">
<!-- 表格列配置 -->
</el-table>
<!-- 查看详情对话框 -->
<el-dialog
:title="viewDialog.title"
v-model="viewDialog.visible"
width="800px"
append-to-body>
<!-- 详情表单内容 -->
</el-dialog>
```
#### 标准事件处理
```typescript
/** 查看按钮操作 */
const handleView = async (row: any) => {
// 1. 复制数据到查看表单
Object.assign(viewForm, row);
// 2. 处理特殊字段(如图片列表等)
if (row.images) {
viewImageList.value = row.images.split(',').filter((img: string) => img.trim() !== '');
}
// 3. 显示对话框
viewDialog.visible = true;
viewDialog.title = '查看详情';
};
```
#### 最佳实践建议
1. 查看对话框宽度建议 800px-1000px,根据内容自适应
2. 详情展示采用只读表单形式,提供良好的数据展示结构
3. 对于图片、文件等特殊字段,需要合适的预览组件
4. 长文本内容建议使用滚动容器限制高度
5. 必要时可以在详情页面添加相关操作按钮
### 2. 表格长文本显示规范(强制性)
#### 实现要求
- **✅ 必须实现**: 所有表格中的长文本字段必须限制显示行数
- **🎯 目标**: 保持表格整洁美观,同时提供完整内容的查看方式
#### 标准实现方式
```vue
<!-- 表格长文本列配置 -->
<el-table-column label="描述文本" align="center" prop="description">
<template #default="scope">
<div class="description-cell">{{ scope.row.description }}</div>
</template>
</el-table-column>
```
#### 标准样式定义
```css
/* 长文本单元格样式 */
.description-cell {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* 显示3行 */
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5;
max-height: 4.5em; /* 3行 * 1.5行高 */
text-align: left;
padding: 0 10px;
}
```
#### 最佳实践建议
1. 统一使用3行显示限制,超出显示省略号
2. 使用左对齐方式,提高长文本可读性
3. 设置合适的行高和内边距
4. 配合表格行双击查看功能,在详情弹窗中显示完整内容
5. 必要时可以添加鼠标悬停提示(el-tooltip)显示完整内容
### 3. 组件开发标准和页面模板
```vue
<template>
<div class="component-container">
<!-- 组件内容 -->
</div>
</template>
<script setup lang="ts" name="ComponentName">
// 1. 引入依赖
import { ref, reactive, computed, onMounted } from 'vue';
import type { ComponentProps, ComponentEmits } from './types';
// 2. 定义Props和Emits
const props = defineProps<ComponentProps>();
const emits = defineEmits<ComponentEmits>();
// 3. 响应式数据
const loading = ref(false);
const formData = reactive({});
// 4. 计算属性
const computedValue = computed(() => {
return /* 计算逻辑 */;
});
// 5. 方法定义
const handleClick = () => {
// 方法实现
};
// 6. 生命周期
onMounted(() => {
// 组件挂载后的逻辑
});
</script>
<style lang="scss" scoped>
.component-container {
// 样式实现
}
</style>
```
#### 列表页面标准模板
```vue
<template>
<div class="p-2 page-container">
<!-- 搜索区域 -->
<transition
:enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave"
>
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form
ref="queryFormRef"
:model="queryParams"
:inline="true"
label-width="100px"
>
<!-- 搜索表单项 -->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<!-- 表格区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<!-- ✅ 第一个新增按钮:type="primary" plain -->
<el-button type="primary" plain icon="Plus" @click="handleAdd">
新增
</el-button>
</el-col>
<el-col :span="1.5">
<!-- ✅ 其他按钮:无type,使用默认样式 -->
<el-button icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
</template>
<!-- 数据表格 -->
<el-table v-loading="loading" border :data="dataList" :height="tableHeight">
<el-table-column type="selection" width="55" align="center" />
<!-- 数据列 -->
<el-table-column label="操作" align="center" width="160">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<!-- ✅ 操作列按钮:link type="primary" -->
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</div>
</template>
```
#### 表单页面标准模板
```vue
<template>
<div class="app-container">
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-card shadow="never">
<template #header>
<span>基本信息</span>
</template>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="启用" value="0" />
<el-option label="禁用" value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<!-- ✅ 提交按钮:type="primary" -->
<el-button type="primary" @click="submitForm">提交</el-button>
<!-- ✅ 重置按钮:无type,使用默认样式 -->
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-card>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
// 表单引用
const formRef = ref<FormInstance>();
// 表单数据
const form = reactive({
name: '',
status: '0',
});
// 验证规则
const rules = reactive<FormRules>({
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
});
// 提交表单
const submitForm = async () => {
if (!formRef.value) return;
await formRef.value.validate((valid) => {
if (valid) {
// 提交逻辑
console.log('提交表单:', form);
}
});
};
// 重置表单
const resetForm = () => {
if (!formRef.value) return;
formRef.value.resetFields();
};
</script>
```
### 3. API接口定义和状态管理
```typescript
// api/[module]/index.ts
import request from '@/utils/request';
import type { AxiosPromise } from 'axios';
import type { DataVO, DataForm, DataQuery } from './types';
/**
* 查询数据列表
* @param query 查询参数
*/
export const listData = (query?: DataQuery): AxiosPromise<DataVO[]> => {
return request({
url: '/api/data/list',
method: 'get',
params: query,
});
};
/**
* 查询数据详情
* @param id 数据ID
*/
export const getData = (id: string | number): AxiosPromise<DataVO> => {
return request({
url: `/api/data/${id}`,
method: 'get',
});
};
/**
* 新增数据
* @param data 数据信息
*/
export const addData = (data: DataForm): AxiosPromise<void> => {
return request({
url: '/api/data',
method: 'post',
data: data,
});
};
/**
* 修改数据
* @param data 数据信息
*/
export const updateData = (data: DataForm): AxiosPromise<void> => {
return request({
url: '/api/data',
method: 'put',
data: data,
});
};
/**
* 删除数据
* @param ids 数据ID数组
*/
export const delData = (ids: Array<string | number>): AxiosPromise<void> => {
return request({
url: `/api/data/${ids}`,
method: 'delete',
});
};
```
### TypeScript 类型定义
```typescript
// api/[module]/types.ts
// 查询参数
export interface DataQuery extends PageQuery {
name?: string;
status?: string;
createTime?: [DateModelType, DateModelType];
}
// 数据表单
export interface DataForm {
id?: string | number;
name: string;
status: string;
remark?: string;
}
// 数据视图对象
export interface DataVO {
id: string | number;
name: string;
status: string;
createTime: string;
updateTime: string;
remark?: string;
}
```
## 5. 状态管理规范
### Pinia Store 结构
```typescript
// stores/modules/[module].ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { DataVO, DataQuery } from '@/api/[module]/types';
import { listData, getData } from '@/api/[module]';
export const useDataStore = defineStore('[module]', () => {
// 状态
const dataList = ref<DataVO[]>([]);
const loading = ref(false);
const total = ref(0);
// 计算属性
const hasData = computed(() => dataList.value.length > 0);
// 方法
const getList = async (query: DataQuery) => {
loading.value = true;
try {
const { data } = await listData(query);
dataList.value = data.rows;
total.value = data.total;
} finally {
loading.value = false;
}
};
const getInfo = async (id: string | number) => {
const { data } = await getData(id);
return data;
};
const reset = () => {
dataList.value = [];
total.value = 0;
};
return {
// 状态
dataList,
loading,
total,
// 计算属性
hasData,
// 方法
getList,
getInfo,
reset,
};
});
```
## 6. 路由管理规范
### 路由定义标准
```typescript
// router/modules/[module].ts
import type { RouteRecordRaw } from 'vue-router';
import Layout from '@/layout/index.vue';
const moduleRouter: RouteRecordRaw = {
path: '/[module]',
component: Layout,
redirect: '/[module]/list',
name: 'Module',
meta: {
title: '模块管理',
icon: 'example',
alwaysShow: true,
},
children: [
{
path: 'list',
component: () => import('@/views/[module]/index.vue'),
name: 'ModuleList',
meta: {
title: '列表管理',
activeMenu: '/[module]/list',
},
},
{
path: 'add',
component: () => import('@/views/[module]/form.vue'),
name: 'ModuleAdd',
meta: {
title: '新增数据',
activeMenu: '/[module]/list',
},
hidden: true,
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/[module]/form.vue'),
name: 'ModuleEdit',
meta: {
title: '编辑数据',
activeMenu: '/[module]/list',
},
hidden: true,
},
],
};
export default moduleRouter;
```
## 7. 项目架构规范
### 目录结构
```
web/src/
├── api/ # API接口定义
│ ├── [module]/
│ │ ├── index.ts # 接口方法
│ │ └── types.ts # 类型定义
├── assets/ # 静态资源
│ ├── images/ # 图片资源
│ └── styles/ # 样式文件
│ ├── art-variables.scss # Art Design Pro 变量
│ ├── art-element-ui.scss # Element Plus 样式定制
│ ├── art-global.scss # 全局样式
│ ├── art-dark.scss # 暗色主题
│ └── index.scss # 样式入口
├── components/ # 全局组件
├── hooks/ # 组合式函数
├── layout/ # 布局组件
├── plugins/ # 插件配置
├── router/ # 路由配置
├── stores/ # 状态管理
├── types/ # 全局类型定义
├── utils/ # 工具函数
├── views/ # 页面组件
└── main.ts # 应用入口
```
### 文件命名规范
- **组件文件**: PascalCase,如 `UserForm.vue`
- **页面文件**: kebab-case,如 `user-list.vue`
- **工具文件**: camelCase,如 `formatUtils.ts`
- **类型文件**: PascalCase,如 `UserTypes.ts`
## 8. 样式开发规范
### SCSS 变量使用
```scss
// 使用 Art Design Pro 变量系统
.custom-component {
background-color: var(--art-main-bg-color);
border: 1px solid var(--art-border-color);
border-radius: var(--border-radius-lg);
padding: var(--space-lg);
.title {
color: var(--art-text-gray-900);
font-size: 16px;
font-weight: 500;
}
.content {
color: var(--art-text-gray-700);
margin-top: var(--space-md);
}
}
```
### UnoCSS 工具类
```vue
<template>
<!-- 使用原子化CSS类 -->
<div class="p-4 bg-white rounded-lg shadow-md">
<h3 class="text-lg font-medium text-gray-900 mb-4">标题</h3>
<p class="text-gray-700">内容</p>
</div>
</template>
```
## 9. 性能优化规范
### 组件懒加载
```typescript
// 路由级别的懒加载
const UserList = () => import('@/views/user/index.vue');
// 组件级别的懒加载
const AsyncComponent = defineAsyncComponent(() => import('@/components/HeavyComponent.vue'));
```
### 图片优化
```vue
<template>
<!-- 使用适当的图片格式和尺寸 -->
<img :src="imageSrc" :alt="imageAlt" loading="lazy" width="200" height="150" />
</template>
```
## 10. 开发最佳实践
### 代码规范
1. **组件命名**: 使用 PascalCase
2. **方法命名**: 使用 camelCase,语义化命名
3. **常量命名**: 使用 UPPER_SNAKE_CASE
4. **注释规范**: 使用 JSDoc 格式
### 错误处理
```typescript
// API错误处理
const handleSubmit = async () => {
try {
loading.value = true;
await submitData(formData);
ElMessage.success('操作成功');
} catch (error) {
console.error('提交失败:', error);
ElMessage.error('操作失败');
} finally {
loading.value = false;
}
};
```
### 类型安全
```typescript
// 严格的类型定义
interface UserForm {
name: string;
email: string;
status: 0 | 1;
}
// 类型断言
const user = data as UserForm;
```
## 11. 测试规范
### 单元测试
```typescript
// 使用 Vitest 进行单元测试
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import UserForm from '@/components/UserForm.vue';
describe('UserForm', () => {
it('should render correctly', () => {
const wrapper = mount(UserForm);
expect(wrapper.exists()).toBe(true);
});
});
```
## 12. 构建与部署
### 环境配置
```typescript
// .env.development
VITE_APP_BASE_API = '/dev-api';
VITE_APP_PORT = 80;
// .env.production
VITE_APP_BASE_API = '/prod-api';
VITE_APP_PORT = 80;
```
### 构建优化
```typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]',
},
},
},
});
```
---
## 总结
本规范文档涵盖了杭州第一技师学院智慧校园管理系统前端开发的所有核心规范,包括最新的品牌色配置和统一的按钮使用规范。开发者应严格遵循这些规范,确保代码质量和用户体验的一致性。
**🔴 重要提醒**:
1. **按钮规范**: 工具栏第一个新增按钮必须使用 `type="primary" plain`,其他按钮使用默认样式
2. **颜色系统**: 使用最新的 Art Design Pro 颜色变量
3. **文档同步**: 代码变更后必须同步更新相关文档