SuperAPI AI Directory

vue-admin

Frontend & websites

View repository

Updated 10-15 23:39

About this skill

Vue 3 Development Standards

Tech Stack

Vue 3 (Composition API) · TypeScript · Vite · Pinia · Vue Router · UnoCSS · SCSS · Element Plus

Directory Structure

src/
├── api/                        # API 请求层(index.ts + types.ts)
├── components/                 # 全局复用组件
├── composables/                # 组合式函数(usePageTable 等)
├── constants/                  # 常量(StorageKeys 等)
├── directives/                 # 自定义指令(v-hasPerm 等)
├── lang/                       # 国际化(index.ts + package/)
├── layouts/                    # 布局组件
├── router/                     # 路由 + 守卫
├── stores/                     # Pinia(扁平结构,无 modules/)
├── styles/                     # 全局样式
├── utils/                      # 工具函数(request.ts, auth.ts)
├── views/                      # 页面(与路由对应)
│   └── system/role/
│       └── index.vue          # 单文件:搜索+表格+弹窗
└── settings.ts                # 项目配置

Naming Conventions

Types Style Example
Variables camelCase userName
Constants UPPER_SNAKE_CASE MAX_COUNT
Functions camelCase, starting with a verb fetchUserList
Classes/Interfaces/Types PascalCase UserInfo
Enums PascalCase StatusEnum
Enum values UPPER_SNAKE_CASE StatusEnum.ACTIVE
Boolean values is/has/can/should prefix isLoading
Vue component files PascalCase UserForm.vue
Page files index.vue system/user/index.vue
TS/JS modules kebab-case format-date.ts
Composables use + camelCase usePageTable
Store use + module name + Store useUserStore

Do not use a frontend type with the VO/DTO suffix. Semantic naming:UserItem(list item),UserForm(form),UserQueryParams(query parameters),UserDetail(details).

Method naming and handle rules

Scenario Naming
Query/Load fetchList / loadOptions
Open/Close dialog openDialog / closeDialog
Submit/Save submitForm / saveX
Add/Edit/Delete createX / updateX / deleteX
Reset resetForm / resetQuery
Event entry point (flow orchestration) handleSubmit / handleEditClick

handle Criterion: if the function does only one thing → do not use handle (reusable); if it combines multiple actions + flow control → use handle (event entry point).

// 单一动作 → 不用 handle
function openDialog() { dialogState.visible = true; }

// 流程编排 → 使用 handle
async function handleSubmit() {
  const valid = await validateForm();
  if (!valid) return;
  await submitForm(formData);
  closeDialog();
  fetchList();
}

fetch/load already implies async semantics; do not add the Async suffix.

CSS / UnoCSS / SCSS boundaries

UnoCSS only handles non-semantic fine-tuning (spacing, alignment); structural styles belong to BEM + SCSS.

Scenario Solution
Global page skeleton Global classes (such as page-*)
Elements with structural semantics BEM + SCSS
Non-semantic layout fine-tuning UnoCSS
Deep selectors/animations/media queries SCSS

Rules:

  • No more than 3 atomic classes on the same element; extract BEM if exceeded
  • Use CSS variables for colors; hardcoding is prohibited
  • BEM format:block__element--modifier, kebab-case, with page prefix
  • Use is-*for state, and BEM Modifier for variants:layout--top

Component Standards

  • SFC block order:template → script setup → style scoped
  • Order inside script: imports → Props/Emits → state → computed properties → watchers → lifecycle → methods → defineExpose
  • Props prefer TypeScript type declarations + withDefaults
  • Components ≤ 300 lines

Types and API conventions

Public types

interface ApiResult<T = unknown> { code: string; data: T; msg: string; }
interface BaseQueryParams { pageNum: number; pageSize: number; sortBy?: string; order?: string; }
interface PageResult<T> { list: T[]; total: number; }

API definitions

const USER_BASE_URL = "/api/v1/users";

const UserAPI = {
  /** 获取用户分页数据 */
  getPage(q: UserQueryParams) { return request<unknown, PageResult<UserItem>>({ url: USER_BASE_URL, method: "get", params: q }); },
  /** 获取用户表单数据 */
  getFormData(id: string) { return request<unknown, UserForm>({ url: `${USER_BASE_URL}/${id}/form`, method: "get" }); },
  /** 新增用户 */
  create(data: UserForm) { return request({ url: USER_BASE_URL, method: "post", data }); },
  /** 更新用户 */
  update(id: string, data: UserForm) { return request({ url: `${USER_BASE_URL}/${id}`, method: "put", data }); },
  /** 批量删除用户 */
  deleteByIds(ids: string) { return request({ url: `${USER_BASE_URL}/${ids}`, method: "delete" }); },
};

export default UserAPI;
export * from "./types";

Response interceptor automatically unwraps:code === "00000" returns response.data.data. When the token expires (A0230), refreshTokenOnce() single-flight refresh.

Page development patterns

By default, use the Composables pattern to develop pages. Only when it is explicitly specified to use a CURD page, use the Config-driven pattern:

Pattern Applicable scenarios Reference
Composables pattern (default) List pages, form pages, pages with custom interactions references/new-page-guide.md
Simple pages Dashboards, settings pages, detail pages references/new-page-guide.md
Config-driven CURD (must be specified) Standard create, read, update, delete with no special interactions references/curd-development.md

Use a unified page root node class="page-container",Internal sections:page-search(search area),page-content(content area),page-toolbar(toolbar).

Store

Setup Store syntax, flat directory. Use outside components useXxxStoreHook() to avoid Pinia not being initialized.

export const useUserStore = defineStore("user", () => {
  const userInfo = ref<UserInfo>({} as UserInfo);
  return { userInfo };
});

export function useUserStoreHook() {
  return useUserStore(store);
}

Composables

use prefix + camelCase. Parameters use an options object, returning reactive refs and methods.

Comments

Comments write "why" and "pitfalls", not what the code already says. Functions use /** */, not //。

format:

  • Functions/methods → /** */ JSDoc
  • Types/interfaces/properties → single-line /** */
  • Inside functions "why" → // inline
  • Configuration grouping → // brief

Function comments depend on the situation:

/** 打开角色表单弹窗 */
function openDialog() { ... }

/**
 * 打开编辑角色弹窗
 *
 * @param roleId 角色 ID
 */
async function handleEditClick(roleId: string) { ... }

/**
 * 校验并提交角色表单
 *
 * 非自定义数据权限时丢弃部门 ID
 */
async function handleSubmit() { ... }

A simple function is fine with one line. If it has parameters, add @param. If there are pitfalls, add one more line in multiple lines—only add "why", do not restate what the function name already makes clear.

Common mistakes:

// ❌ 复述代码:函数名已经说了"打开弹窗",注释是废话
/** 打开弹窗 */
function openDialog() { ... }

// ❌ 描述"做什么"而非"为什么"
/** 遍历列表并过滤状态为启用的项 */
const enabledList = list.filter(item => item.status === 1);

// ✅ 写"为什么"
// status=1 是启用,数据库默认值是 0(禁用)
const enabledList = list.filter(item => item.status === 1);

// ❌ 每个函数都写注释,哪怕函数名一目了然
/** 获取用户信息 */
function getUserInfo() { ... }

// ✅ 函数名能说清的不写
function getUserInfo() { ... }

Anti-pattern quick reference

Anti-pattern Correct approach
Types use VO/DTO suffix Semantic naming:UserItem、UserForm
Hardcoded colors CSS variables
More than 3 atomic classes Extract BEM + SCSS
class UserAPI static method const UserAPI = {} object literal
stores/modules/ subdirectory Flat stores/ structure
Use directly outside components useXxxStore() Use useXxxStoreHook()
Manually add to CURD buttons v-hasPerm Use permPrefix automatic concatenation
meta.title Write Chinese directly Write language pack key
Functions use // comments Use /** */ JSDoc format
Comments restate "what to do" Write "why" and constraints

Self-check list

  • type has no VO/DTO suffix
  • boolean has is/has/can/should prefix
  • handle only for process orchestration
  • API uses const XXXAPI = {} object literal
  • BEM with page prefix, atomic classes ≤ 3
  • colors use CSS variables
  • SFC block order: template → script → style
  • Store uses Setup Store + useXxxStoreHook()
  • components ≤ 300 lines
  • meta.title Use language pack key
  • permission identifiers follow 模块:资源:操作 format
  • API modules follow index.ts + types.ts structure
  • Functions use /** */ comments, not //
  • comments write "why", not restate "what to do"

Reference documentation

The following documents are loaded on demand, covering detailed guides for specific development scenarios:

Reference file Applicable scenario
references/new-page-guide.md Adding APIs and pages: create API modules, Composables pattern CURD, simple pages, pattern selection
references/curd-development.md Config-driven CURD: configuration APIs, column templates, form items, usePage()
references/router-menu.md Configuring routes and menus: static/dynamic routes, Meta fields, multi-level menus
references/permission.md Permission control: v-hasPerm directive, hasPerm() function, permission identifier naming
references/page-caching.md Page caching: keepAlive configuration, caching mechanism, refreshing the current page
references/i18n.md Menu internationalization: translateRouteTitle, language pack configuration, language switching
references/project-config.md Project configuration: settings.ts, environment variables, StorageKey management
references/components.md Common components: Upload, Dict, TableSelect, IconSelect