SuperAPI AI Directory

vue-element

Frontend & websites

View repository

Updated 10-16 05:07

About this skill

Vue3 Admin Backend Development Standards

Trigger Conditions

  • Develop Vue3 admin dashboard projects
  • Implement CRUD pages
  • Use Element Plus components
  • Implement permission management
  • Create custom components

Part 1: Tech Stack

  • Vue - Frontend Framework
  • Element Plus - UI Component Library
  • TypeScript - Type Safety
  • Vite - Build Tool
  • Pinia - State Management
  • Vue Router - Routing
  • Axios - HTTP Client

Part 2: Directory Structure

src/
├── api/                    # API 请求
│   ├── system/             # 系统模块 API
│   │   ├── user/           # 用户模块
│   │   │   ├── index.ts    # API 定义
│   │   │   └── types.ts    # 类型定义
│   │   └── role/           # 角色模块
│   └── index.ts            # API 统一导出
├── components/             # 全局组件
│   ├── Form/               # 表单组件
│   │   ├── UserSelect.vue
│   │   └── DeptTree.vue
│   ├── Table/              # 表格组件
│   │   └── ProTable.vue
│   └── Dialog/             # 弹窗组件
│       └── FormDialog.vue
├── composables/            # 组合式函数
│   ├── useTable.ts         # 表格逻辑
│   ├── useForm.ts          # 表单逻辑
│   └── useDialog.ts        # 弹窗逻辑
├── directives/             # 自定义指令
│   ├── permission.ts       # 权限指令
│   └── loading.ts          # 加载指令
├── enums/                  # 枚举定义
│   ├── ResultCode.ts       # 响应码
│   └── MenuType.ts         # 菜单类型
├── hooks/                  # 自定义 hooks
│   ├── useLoading.ts
│   └── usePermission.ts
├── layout/                 # 布局组件
│   ├── components/
│   │   ├── Sidebar.vue
│   │   ├── Navbar.vue
│   │   └── AppMain.vue
│   └── index.vue
├── router/                 # 路由配置
│   ├── index.ts
│   ├── staticRoutes.ts     # 静态路由
│   └── dynamicRoutes.ts    # 动态路由
├── stores/                 # Pinia 状态
│   ├── modules/
│   │   ├── user.ts
│   │   ├── permission.ts
│   │   └── app.ts
│   └── index.ts
├── styles/                 # 全局样式
│   ├── variables.scss      # CSS 变量
│   ├── reset.scss          # 重置样式
│   └── index.scss          # 入口
├── utils/                  # 工具函数
│   ├── request.ts          # Axios 封装
│   ├── auth.ts             # Token 管理
│   ├── permission.ts       # 权限判断
│   └── format.ts           # 格式化
└── views/                  # 页面组件
    ├── system/
    │   ├── user/
    │   │   ├── index.vue       # 列表页
    │   │   ├── components/     # 页面组件
    │   │   │   └── UserForm.vue
    │   │   └── hooks/          # 页面 hooks
    │   │       └── useUser.ts
    │   └── role/
    └── dashboard/

Part 3: Naming Conventions

File Naming

Type Convention Example
Component PascalCase UserSelect.vue
Page kebab-case user-profile.vue
Composable Function camelCase + use useUserStore.ts
Utility Function camelCase formatDate.ts
Constant UPPER_SNAKE_CASE API_PREFIX.ts
Type PascalCase UserVO.ts

Variable Naming

Type Convention Example
Variable camelCase userList
Constant UPPER_SNAKE_CASE MAX_COUNT
Private Variable _ Prefix _internalState
Boolean is/has/can Prefix isLoading, hasPermission

Method Naming

Action Prefix Example
Fetch/Load fetch/load fetchUserList
Submit/Save submit/save submitForm
Create create createUser
Update update updateUser
Delete delete deleteUser
Open/Close open/close openDialog
Event Handling handle handleSubmit

handle Usage Rules:

Type Characteristics Whether to Use handle
Single Action Does Only One Thing No
Process Orchestration Combines Multiple Actions Yes
// ✅ 单一动作 → 不用 handle
function openDialog() {
  dialogState.visible = true;
}

// ✅ 流程编排 → 使用 handle
async function handleSubmit() {
  const { valid } = await formRef.value.validate();
  if (!valid) return;
  await submitForm();
  closeDialog();
}

CSS Naming (BEM)

block__element--modifier
 │      │          │
 │      │          └── 状态/变体(双连字符)
 │      └── 组成部分(双下划线)
 └── 页面前缀 + 功能实体

Block Must Have Page Prefix:

用户管理(user)    → user-card / user-form / user-toolbar
角色管理(role)    → role-card / role-permission / role-dialog
设备授权(device)  → device-auth / device-auth__panel
仪表盘(dashboard) → dashboard-stat / dashboard-chart

Element Common Vocabulary:

Semantics Standard Vocabulary Prohibited
Header __header head / top
Footer __footer bottom
Title __title name / heading
Description __desc text / detail
Icon __icon img / pic
Action Button __action action-btn
Action Area __actions btns
Content __body content
List __list items
List Item __item row
Panel __panel box
Tag __tag badge(use badge for badges)

SCSS Nesting:

.user-card {
  padding: 16px;
  background: var(--el-bg-color);
  border-radius: 8px;

  &__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  &__avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
  }

  &__name {
    font-size: 16px;
    font-weight: 600;
    color: var(--el-text-color-primary);
  }

  &__actions {
    display: flex;
    gap: 8px;
  }

  &--active {
    border: 2px solid var(--el-color-primary);
  }

  &--disabled {
    opacity: 0.5;
    pointer-events: none;
  }
}

Part 4: Component Standards

Component Priority Principle

Requirement Use Avoid
Button <el-button> Custom .btn
Input <el-input> Custom .input
Table <el-table> Custom Table
Modal <el-dialog> Custom Modal
Form <el-form> Custom Form

CRUD Page Template

<template>
  <div class="app-container">
    <!-- 搜索栏 -->
    <el-form :model="queryParams" ref="queryFormRef" :inline="true">
      <el-form-item label="关键词" prop="keywords">
        <el-input
          v-model="queryParams.keywords"
          placeholder="请输入"
          clearable
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleQuery"> <i-ep-search />搜索 </el-button>
        <el-button @click="handleReset"> <i-ep-refresh />重置 </el-button>
      </el-form-item>
    </el-form>

    <!-- 操作栏 -->
    <el-row :gutter="10" class="mb-[10px]">
      <el-col :span="1.5">
        <el-button type="primary" v-permission="['sys:user:add']" @click="handleAdd">
          <i-ep-plus />新增
        </el-button>
      </el-col>
    </el-row>

    <!-- 数据表格 -->
    <el-table v-loading="loading" :data="tableData" border>
      <el-table-column label="ID" prop="id" width="80" />
      <el-table-column label="用户名" prop="username" min-width="120" />
      <el-table-column label="状态" prop="status" width="100">
        <template #default="{ row }">
          <el-tag :type="row.status === 1 ? 'success' : 'danger'">
            {{ row.status === 1 ? "正常" : "禁用" }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="150" fixed="right">
        <template #default="{ row }">
          <el-button link type="primary" v-permission="['sys:user:edit']" @click="handleEdit(row)">
            编辑
          </el-button>
          <el-button
            link
            type="danger"
            v-permission="['sys:user:delete']"
            @click="handleDelete(row)"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <el-pagination
      v-model:current-page="queryParams.pageNum"
      v-model:page-size="queryParams.pageSize"
      :total="total"
      :page-sizes="[10, 20, 50, 100]"
      layout="total, sizes, prev, pager, next, jumper"
      @change="handleQuery"
    />
  </div>
</template>

<script setup lang="ts">
import type { UserVO, UserPageQuery } from "@/api/system/user/types";

defineOptions({ name: "UserList" });

const loading = ref(false);
const tableData = ref<UserVO[]>([]);
const total = ref(0);
const queryParams = reactive<UserPageQuery>({
  pageNum: 1,
  pageSize: 20,
  keywords: "",
});

function handleQuery() {
  loading.value = true;
  UserAPI.getPage(queryParams)
    .then(({ data }) => {
      tableData.value = data.list;
      total.value = data.total;
    })
    .finally(() => {
      loading.value = false;
    });
}

function handleReset() {
  queryFormRef.value?.resetFields();
  handleQuery();
}

function handleAdd() {
  // 打开新增弹窗
}

function handleEdit(row: UserVO) {
  // 打开编辑弹窗
}

async function handleDelete(row: UserVO) {
  const { confirm } = await ElMessageBox.confirm("确认删除该用户?", "警告", {
    type: "warning",
  });
  if (confirm) {
    await UserAPI.deleteById(row.id);
    ElMessage.success("删除成功");
    handleQuery();
  }
}

onMounted(() => handleQuery());
</script>

Part 5: API Standards

API Definition Template

// api/system/user/types.ts
export interface UserVO {
  id: number;
  username: string;
  nickname: string;
  mobile: string;
  email: string;
  status: number;
  createTime: string;
}

export interface UserForm {
  id?: number;
  username: string;
  nickname: string;
  mobile: string;
  email: string;
  status: number;
  roleIds: number[];
}

export interface UserPageQuery extends PageQuery {
  username?: string;
  status?: number;
  deptId?: number;
}

// api/system/user/index.ts
import request from "@/utils/request";
import type { UserVO, UserForm, UserPageQuery } from "./types";

class UserAPI {
  /** 获取用户分页列表 */
  static getPage(params: UserPageQuery) {
    return request<PageResult<UserVO>>({
      url: "/api/v1/users/page",
      method: "get",
      params,
    });
  }

  /** 获取用户详情 */
  static getById(id: number) {
    return request<Result<UserVO>>({
      url: `/api/v1/users/${id}`,
      method: "get",
    });
  }

  /** 获取用户表单数据 */
  static getFormData(id: number) {
    return request<Result<UserForm>>({
      url: `/api/v1/users/${id}/form`,
      method: "get",
    });
  }

  /** 新增用户 */
  static create(data: UserForm) {
    return request<Result<number>>({
      url: "/api/v1/users",
      method: "post",
      data,
    });
  }

  /** 更新用户 */
  static update(data: UserForm) {
    return request<Result<void>>({
      url: "/api/v1/users",
      method: "put",
      data,
    });
  }

  /** 删除用户 */
  static deleteById(id: number) {
    return request<Result<void>>({
      url: `/api/v1/users/${id}`,
      method: "delete",
    });
  }

  /** 批量删除用户 */
  static batchDelete(ids: number[]) {
    return request<Result<void>>({
      url: "/api/v1/users/batch",
      method: "delete",
      data: { ids },
    });
  }

  /** 获取用户选项列表 */
  static getOptions() {
    return request<Result<OptionType[]>>({
      url: "/api/v1/users/options",
      method: "get",
    });
  }
}

export default UserAPI;

Response Format

// 标准响应
interface Result<T> {
  code: number;
  msg: string;
  data: T;
}

// 分页响应
interface PageResult<T> {
  code: number;
  msg: string;
  data: {
    list: T[];
    total: number;
  };
}

// 分页查询参数
interface PageQuery {
  pageNum: number;
  pageSize: number;
  keywords?: string;
}

// 选项类型
interface OptionType {
  value: string | number;
  label: string;
  children?: OptionType[];
}

Part 6: Permission Control

Route Guard

// router/guard.ts
import { useUserStore } from "@/stores/modules/user";

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore();

  // 登录页直接放行
  if (to.path === "/login") {
    return next();
  }

  // 未登录跳转登录页
  if (!userStore.token) {
    return next(`/login?redirect=${to.path}`);
  }

  // 已加载路由直接放行
  if (userStore.routes.length > 0) {
    return next();
  }

  // 加载动态路由
  await userStore.loadRoutes();
  next({ ...to, replace: true });
});

Button Permission

<template>
  <!-- 指令方式 -->
  <el-button v-permission="['sys:user:add']" type="primary">新增</el-button>

  <!-- 函数方式 -->
  <el-button v-if="hasPermission(['sys:user:edit'])">编辑</el-button>
</template>

<script setup lang="ts">
import { hasPermission } from "@/utils/permission";
</script>

Permission Directive

// directives/permission.ts
import { useUserStore } from "@/stores/modules/user";

export const permission: Directive = {
  mounted(el, binding) {
    const userStore = useUserStore();
    const permissions = binding.value as string[];

    const hasPermission = permissions.some((p) => userStore.permissions.includes(p));

    if (!hasPermission) {
      el.parentNode?.removeChild(el);
    }
  },
};

Part 8: Style Standards

Style Layering Principle

BEM defines semantics, UnoCSS supplements fine-tuning, SCSS only does the dirty work.

Layer Responsibility Example
BEM Component semantic anchors user-card、user-card__header
UnoCSS Simple layout fine-tuning (≤3 properties) flex items-center gap-2
SCSS Things atomic classes can't do, such as pseudo-classes/animations/penetration :deep()、@keyframes

Selection decision

Scenario Solution Example
Simple layout (1-2 properties) Atomic classes class="flex items-center"
Component styles (3+ properties) BEM class="user-card"
Repeated combinations UnoCSS Shortcuts consolidation class="flex-center"
Theme-related CSS variables + BEM background: var(--el-color-primary-light-9)
<!-- ✅ 组件样式归 BEM,微调用原子类 -->
<div class="user-card">
  <el-avatar class="user-card__avatar" :src="user.avatar" />
  <span class="user-card__name">{{ user.name }}</span>
</div>

<!-- ✅ 简单间距直接用原子类 -->
<div class="mt-4 p-2">
  <el-input v-model="keyword" placeholder="搜索" />
</div>

<!-- ❌ 同一元素混用过多原子类 + BEM -->
<div class="user-card flex flex-col p-4 bg-white rounded-lg shadow-sm">
</div>

Theme variables

Prefer using Element Plus built-in CSS variables, no need for a custom theme color system:

Category Variable Description
Primary color --el-color-primary Theme blue
Primary color light --el-color-primary-light-3/5/7/8/9 Auto-generated by Element Plus
Success --el-color-success / --el-color-success-light-* Green family
Warning --el-color-warning / --el-color-warning-light-* Orange family
Danger --el-color-danger / --el-color-danger-light-* Red family
Error --el-color-error / --el-color-error-light-* Red family
Background color --el-bg-color / --el-bg-color-overlay / --el-bg-color-page Page/overlay/base color
Text color --el-text-color-primary / --el-text-color-regular / --el-text-color-secondary / --el-text-color-placeholder / --el-text-color-disabled Decreasing hierarchy
Border color --el-border-color / --el-border-color-light / --el-border-color-lighter / --el-border-color-extra-light Decreasing hierarchy
Fill color --el-fill-color / --el-fill-color-light / --el-fill-color-lighter / --el-fill-color-blank Background fill

Custom variables(project-level, defined in _theme.scss):

Variable Purpose
--menu-background Sidebar background color
--menu-text Menu text color
--menu-active-text Menu active text color
--menu-hover Menu hover background color
// ✅ 使用 Element Plus 变量
.card { background: var(--el-bg-color); border: 1px solid var(--el-border-color-lighter); }

// ❌ 禁止硬编码
.card { background: #ffffff; border: 1px solid #e5e7eb; }

Layout classes (global)

The project defines standard layout classes in _layout.scss ,CRUD pages must use:

Class name Purpose CSS
.page-container Page container padding: 16px
.page-content Content area (el-card root element) display: flex; flex: 1; flex-direction: column
.page-search Search bar Card style + padding override
.page-toolbar Toolbar flex + space-between + gap
.page-toolbar__left Toolbar left side flex + gap
.page-toolbar__right Toolbar right side flex + gap

⚠️ Do not remove .page-content: this class provides display: flex; flex: 1; flex-direction: column, and removing it will cause the table height to collapse.

z-index layering

Only set z-index for position: fixed/sticky/absolute and elements with stacking competition; magic numbers are prohibited.

--z-dropdown: 100;   // 下拉菜单、Popover
--z-sticky:   200;   // 吸顶栏
--z-overlay:  300;   // 遮罩层
--z-dialog:   500;   // Dialog / Drawer
--z-toast:    600;   // Message / Notification

Spacing and border radius

Spacing(4px grid base):

Semantic Value Scenario
xs 4px Icon and text spacing
sm 8px Button spacing, compact padding
md 16px Card padding, module spacing
lg 24px Block spacing

Border radius:

Scenario Value
Small elements (Tag, Badge) 4px
Buttons, input boxes var(--el-border-radius-base)
Cards, Dialog var(--el-border-radius-base)

Shadows

Level Value Scenario
sm 0 1px 2px rgba(0,0,0,0.05) Card
md 0 4px 6px rgba(0,0,0,0.1) Popup
lg 0 10px 15px rgba(0,0,0,0.1) Modal

Anti-pattern

Anti-pattern Correct practice
Hard-coded color values Use var(--el-*) variables
Remove .page-content class Keep it, it provides the critical flex layout
:deep() Abuse Prefer Element Plus props/slots
Block names are too generic (e.g. .card) Use page/feature prefixes (e.g. .user-card)
height: 100vh Use min-height: 100vh
z-index magic numbers Use var(--z-*)
SCSS large amounts of @apply Keep atomic classes in the template
More than 3 atomic classes on the same element Group into BEM classes
:deep(.el-card__body) add flex el-card__body keeps the default block layout

Style self-check list

  • BEM class names have a page prefix
  • All colors use CSS variables
  • .page-content Keep (provides flex layout)
  • At most 3 atomic classes in the template; if more, group into BEM
  • z-index uses var(--z-*), normal-flow content has no z-index set
  • Do not apply .el-card__body add flex override

Part 9: Code Quality Checklist

  • Use TypeScript strict mode
  • Define types for all variables and functions
  • Prefer Element Plus components
  • Follow naming conventions
  • Add JSDoc comments for public functions
  • Handle loading and error states
  • Implement permission checks
  • Use composables to reuse logic
  • Keep components within 300 lines
  • Use <script setup> syntax
  • Organize APIs by module
  • BEM naming with a page prefix
  • Colors use var(--el-*) variables
  • Keep .page-content layout class
  • At most 3 atomic classes; if more, use BEM