Tech Stack
- Language:TypeScript
- Framework:Vue 3 Composition API (
<script setup>) - CSS: Tailwind CSS (avoid using
<style>tags, except in scenarios where Tailwind classes cannot cover) - State Management:Pinia
- UI Framework: Giime (enhanced based on Element Plus,
el-*→gm-*)
Naming Conventions
| Scenario | Style | Example |
|---|---|---|
| Types/Interfaces | PascalCase | UserInfo, ApiResponse |
| Variables/Functions/Folders | camelCase | userName, handleSubmit() |
| Environment Constants | UPPER_CASE | VITE_BASE_URL |
| Component Files | PascalCase | UserProfile.vue |
| Composables | use prefix | useUserInfo, useFormValidation |
| Booleans | Auxiliary verb prefix | isLoading, hasError, canSubmit |
| Event Handling | handle prefix | handleClick, handleSubmit |
Variable Naming: Avoid data/info/list/result/status and other generic names, use formData/userInfo/taskList and other specific business names.
General Coding Rules
- Comments: Add JSDoc comments to every method and variable, in Chinese; add an appropriate amount of single-line Chinese comments inside functions
- Function Declarations: Prefer
constarrow functions overfunction, unless overloading is needed - Async: Prefer
async/await, do not use Promise chaining - Modern ES: Prefer
?.、??、Promise.allSettled、replaceAll、Object.groupBy, etc. - Guard Clauses: Exit early on conditions to reduce nesting depth
- Function Parameters: 1-2 main parameters + options object, avoid excessively long parameters
// ✅ 函数参数设计示例
const urlToFile: (url: string, options?: FileConversionOptions) => Promise<File>;
- Prefer Utility Libraries: Reduce reinventing the wheel
- Date:
dayjs - Vue Utilities:
vueuse(tryOnMounted、useEventListener、useLocalStorage, etc.) - Data Processing:
lodash-es(compact、cloneDeep、uniqByand other methods not provided by ES)
- Date:
- Git Operations: Do not operate git commands unless the user explicitly requests it
- Formatting: After modifying code, execute
npx eslint --fix <文件路径>, find and automatically fix code style and syntax issues - Type Checking: After modifying code, execute
npx vue-tsc --noEmit -p tsconfig.app.json, find type errors and potential runtime issues
For more examples, see coding-conventions.md
Composition API Specification
1. Reduce watch, prefer event binding
watch is an implicit dependency, making it difficult to trace the source of changes. Prefer handling logic directly in event callbacks for a clearer data flow:
// ❌ 隐式监听,难以定位谁触发了变更
watch(count, () => {
console.log('changed');
});
// ✅ 在事件中直接处理,数据流清晰
const handleCountChange = () => {
console.log('changed');
};
// <gm-input v-model="count" @change="handleCountChange" />
2. Use defineModel
Introduced in Vue 3.4+, defineModel simplifies the props + emit + computed trio into one line, reducing boilerplate code:
// ❌ 三件套写法,样板代码多
const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{ 'update:modelValue': [value: string] }>();
// ✅ defineModel 一行搞定
const value = defineModel<string>({ required: true });
3. Use useTemplateRef
Vue 3.5+ recommends using useTemplateRef to obtain template refs, which is type-safe and avoids coupling between ref variable names and template ref attribute names:
// ❌ 变量名必须与 template 中的 ref="inputRef" 完全一致
const inputRef = ref<FormInstance | null>(null);
// ✅ 类型安全,解耦变量名和模板 ref
const inputRef = useTemplateRef('inputRef');
4. Prefer ref, avoid reactive without reason
reactive loses reactivity when destructured, and requires assigning fields one by one when replacing the whole object.ref behavior is more predictable, and through .value you can directly replace the whole object:
// ❌ reactive 解构丢失响应性,整体替换需 Object.assign
const profileData = reactive({ name: '', age: 0 });
// ✅ ref 行为统一,profileData.value = newData 即可整体替换
const profileData = ref({ name: '', age: 0 });
5. Direct Props Destructuring
Vue 3.5+ supports defineProps maintaining reactivity while destructuring and setting default values,withDefaults no longer needed:
// ❌ withDefaults 在 Vue 3.5+ 已不必要
const props = withDefaults(defineProps<{ size?: number }>(), { size: 10 });
// ✅ 直接解构,简洁且响应式
const { size = 10 } = defineProps<{ size?: number }>();
6. Dialog exposes openDialog
The dialog component exposes the defineExpose method through openDialog , and the parent component calls it via ref, avoiding state synchronization issues caused by controlling visible through props:
const dialogVisible = ref(false);
const openDialog = (data?: SomeType) => {
dialogVisible.value = true;
};
defineExpose({ openDialog });
7. Auto-import, do not explicitly import Vue API
ref、computed、watch、onMounted etc. are already auto-imported through unplugin-auto-import . Explicitly import { ref } from 'vue' produces redundant code and is inconsistent with the project configuration.
8. Use Tailwind CSS for styling
All styles are implemented through Tailwind classes. Only use :deep(), complex animations). <style>。
9. Extract configuration data
Extract configuration data unrelated to page logic, such as option lists, form rules, tableId, etc., in modules/**/composables/useXxxOptions.ts , keeping components focused on interaction logic:
export const useXxxOptions = () => {
const tableId = 'xxx-xxx-xxx-xxx-xxx';
const xxxOptions = [{ label: '选项1', value: 1, tagType: 'primary' as const }];
const rules = {
xxx: [{ required: true, message: 'xxx不能为空', trigger: 'blur' }],
};
return { tableId, xxxOptions, rules };
};
10. Component code structure
Uniformly use the template → script → style order.
Code splitting conventions
Each time you add a new feature to an existing .vue file, or create a new module, you should first read directory-structure.md to understand the splitting principles.
When to trigger splitting
| Trigger scenario | Action |
|---|---|
| Create a new CRUD module | First read crud.md to learn the complete CRUD code template and file splitting approach, and generate all files according to the template |
| Add features to an existing page(new forms, dialogs, etc.) | First check the current file line count; if it exceeds 300 lines, it should be split; evaluate whether the new content should be an independent child component |
| Create a new module/page | First plan the directory structure (components/、composables/、stores/), and pre-split by functional area |
| Modify and iterate existing features | If you find that the current file is already bloated (>300 lines), split it while completing the requirement; do not let the file continue to grow |
Core principles
- Generally,
.vuefiles should not exceed 300 lines: except for entry-level files (such asindex.vue), child components, dialogs, and other files should be kept within 300 lines - Entry files can be appropriately relaxed:
index.vueAs a module entry, it undertakes orchestration responsibilities, so the line count can appropriately exceed the limit, but it should still be as concise as possible - Split UI components by functional area: Each independent functional area (search bar, table, dialog, form area, etc.) should be an independent child component
- Core business logic stays in
index.vue: The main page is responsible for data fetching, state management, and child component orchestration - UI display logic sinks into child components: Child components are only responsible for rendering and user interaction
- Avoid excessive prop passing: When the props hierarchy exceeds 2 levels, use Pinia instead of deep props passing
Standard module directory structure
modules/xxx/
├── index.vue # 主页面(编排子组件、管理数据)
├── components/ # UI 子组件
│ ├── Search.vue
│ ├── Table.vue
│ └── EditDialog.vue
├── composables/ # 逻辑复用
│ └── useXxxOptions.ts
└── stores/ # 状态管理(需要时)
└── useXxxStore.ts
For detailed splitting strategies, example code, and Props passing principles, see directory-structure.md
API call conventions
File restrictions
/src/api Each module under (such as src/api/agent/) contains the following:
| Path | Description | Can be modified manually |
|---|---|---|
controller/ |
Interface methods automatically generated by the code generation tool | Do not modify/create/delete; re-running the generation command will overwrite |
interface/ |
Type definitions automatically generated by the code generation tool | Do not modify/create/delete; re-running the generation command will overwrite |
request.ts |
createAxios The axios instance created |
Modifiable (manually maintained) |
swagger.config.json |
Code generation configuration | Modifiable (manually maintained) |
File structure
Two files are generated for each interface:
postXxxYyyZzz.ts— raw axios methodusePostXxxYyyZzz.ts— useAxios reactive wrapper
The file name is generated from "request method + route address":post /open/v1/system/list → postOpenV1SystemList.ts + usePostOpenV1SystemList.ts
Selection rules
| Scenario | Version to use | Reason |
|---|---|---|
| Default | useXxx Wrapped version |
Provides reactive data (data、isLoading) and automatic race condition cancellation |
| Loops/batch requests | Raw version (without use prefix) |
useAxios cancels the previous request, so when called in a loop only the last one takes effect |
// ✅ 默认:useAxios 封装
const { exec: getListExec } = usePostXxxV1ListPage();
await getListExec();
// ✅ 批量:原始接口 + Promise.all
await Promise.all(ids.map(id => deleteXxxV1Item({ id })));
API import rules
All interfaces and types are imported uniformly from controller/index.ts or interface/index.ts to reduce coupling and facilitate later refactoring:
// ✅ 从 controller 统一导入
import type { PostGmpV1CrowdListInput } from '@/api/gmp/controller';
import { postGmpV1CrowdList } from '@/api/gmp/controller';
// ✅ 公共类型从 interface 导入
import type { CrowdItemVo } from '@/api/gmp/interface';
// ❌ 不要从具体文件导入
import { postGmpV1CrowdDetail } from '@/api/gmp/controller/RenQunGuanLi/postGmpV1CrowdList';
Error handling
- No try/catch needed:
createAxiosThe interceptor automatically pops up an error prompt; business code does not need to manually catch - No need to check code: abnormal response codes are automatically rejected by the interceptor; no need for
if (code !== 200) - When finally is needed: use try/finally (without catch) for cleaning up side effects
Usage flow
- If an interface address is provided, the first step is to find
@/api/xxx/controllerthe request method defined in - Carefully read the type definitions in the interface file; do not make up parameters yourself
- Choose based on the scenario
useXxxversion or the raw version - Data sources such as dropdowns and radio buttons can be obtained from the interface documentation comments; after obtaining them, extract them in
useXxxOptionsfor reuse
For more usage examples, see api-conventions.md
Pinia state management
Store file organization
modules/xxx/
├── stores/
│ └── useXxxStore.ts # 模块专用 store
└── index.vue
When to use
Pinia is suitable for scenarios such as sharing state across multiple component layers and polling asynchronous tasks. Do not overuse it—only use props/emits for data passed between parent and child components, use ref for local state, and use v-model for simple form data.
Usage conventions
// 命名体现业务含义,不要用 const store = useXxxStore
const xxxStore = useXxxStore();
xxxStore.resetStore(); // 进入页面时重置,确保干净状态
Cross-component shared state
When components are nested more than 2 levels deep and need to share state, use Pinia instead of passing props down layer by layer:
index.vue → Result.vue → ResultSuccess.vue → ImageSection.vue
↓
各组件直接访问 store,不需要 props 逐层传递
For standard Store writing, polling task patterns, etc., see pinia-patterns.md
Reference documentation
| Topic | Description | Reference |
|---|---|---|
| CRUD code template | Required reading when creating a new CRUD page, complete file splitting and code templates | crud.md |
| Detailed coding conventions | Complete examples of guard clauses, function parameter design, utility library usage, etc. | coding-conventions.md |
| Interface invocation guide | Complete explanation of useAxios usage, type definitions, and import rules | api-conventions.md |
| Pinia Usage Patterns | Patterns for store creation, reset, cross-component sharing, etc. | pinia-patterns.md |
| Component Splitting Guidelines | When to split, directory structure, Props passing principles | directory-structure.md |
| Dictionary Module Guidelines | useDictionary, store creation, naming conventions | dictionary.md |
| Environment Variable Configuration | .env layering principles, domain rules, multi-environment builds | env.md |