前端 Vue / TypeScript 落地参考(admin_client)
目录结构
src/
├── api/ 按业务模块封装请求(auth / user / member / role / menu / dept / sms / wechat / payment / storage / platform / upload / operation_log / dashboard)
├── types/ 与 api 同名的请求与响应类型
├── enums/ 前端枚举(common / menu / member / channel / storage / platform)
├── utils/ auth(token)、datetime、echarts、views
├── store/ Pinia:app / user / platform / tags
├── router/ 静态路由 + 后端菜单驱动的动态路由
├── directives/ v-perm 等
├── components/ 通用组件(如 AvatarUpload)
├── styles/ 主题变量与公共样式
└── views/ config / dashboard / error / login / maintain / profile / redirect / system / user页面路径与后端菜单 path 对应:/system/user → src/views/system/user/index.vue。
接口层
统一 request<T>(),禁止在页面直接使用 axios:
ts
import { request } from './http'
import type { UserItem, UserListReq, UserSaveReq } from '@/types/user'
import type { PageResult } from '@/types/common'
export const getUserList = (params?: Partial<UserListReq>) => {
return request<PageResult<UserItem>>({ url: '/admin/user/list', method: 'get', params })
}
export const createUser = (data: UserSaveReq) => {
return request<UserItem>({ url: '/admin/user/add', method: 'post', data })
}命名:getXxxList / getXxxTree / createXxx / updateXxx / deleteXxx / saveXxx。 无返回数据的操作返回 request<null>。
类型
公共类型(src/types/common.ts)优先复用:
ts
ApiResponse<T> // { code, msg, data }
PageQuery // { page, page_size }
PageResult<T> // { list, total }
TreeNode<T> // { data: T, children?: TreeNode<T>[] }
BaseEntity // { id, created_at, updated_at }- 禁止
any,不确定用unknown。 - 列表查询参数继承
PageQuery;列表响应用PageResult<T>;实体继承BaseEntity。 - 后端树为
{ data, children },组件需要平铺时写转换函数并注释原因。 - 富文本编辑统一复用
src/components/RichTextEditor.vue,业务页面和插件只绑定内容与提示配置,不重复维护 WangEditor 上传、主题或销毁逻辑。
枚举
ts
export const Status = { Enabled: 1, Disabled: 2 } as const
export const StatusLabels: Record<number, string> = { 1: '启用', 2: '禁用' }数值必须与后端 internal/common/enums 一致,从 1 开始。
页面模板要点
vue
<script setup lang="ts">
// 人员管理:人员 CRUD、角色分配、重置密码、踢下线
import { onMounted, reactive, ref } from 'vue'
import { getUserList } from '@/api/user'
import { formatDateTimeCell } from '@/utils/datetime'
import type { UserItem } from '@/types/user'
import type { PageResult } from '@/types/common'
const loading = ref(false)
const list = ref<UserItem[]>([])
const query = reactive({ keyword: '', page: 1, page_size: 20 })
const load = async () => {
loading.value = true
try {
const res = await getUserList({ keyword: query.keyword || undefined, page: query.page, page_size: query.page_size })
list.value = res.list
} finally {
loading.value = false
}
}
onMounted(load)
</script>- 使用
<script setup lang="ts">,组合式 API,函数统一箭头函数。 - 加载态在
finally关闭,错误由http.ts拦截器统一提示。 - 公共样式:
.page-card、.toolbar、.table-operations;主题变量取自src/styles/index.css。 - 明暗主题都要可用,禁止硬编码仅适配白底的颜色。
- 日期时间用
formatDateTime/formatDateTimeCell格式化到秒。 - 按钮权限:
v-perm="'POST:/admin/user/add'"。 - 列表行操作统一采用 Element Plus 图标,不直接展示“编辑”“删除”等文字链接。使用
.table-operations作为容器、.op-icon及is-edit/is-danger等状态类;每个图标必须包裹el-tooltip,绑定对应v-perm,删除操作仍须二次确认。工具栏中的查询、重置、新增等主操作可以保留“图标 + 文字”,保证含义清晰。 - 上传:提交业务数据时传
relative_path。
vue
<div class="table-operations">
<el-tooltip content="编辑" placement="top">
<el-icon v-perm="'POST:/admin/example/save'" class="op-icon is-edit" @click="openEdit(row)">
<Edit />
</el-icon>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-icon v-perm="'POST:/admin/example/delete'" class="op-icon is-danger" @click="remove(row)">
<Delete />
</el-icon>
</el-tooltip>
</div>请求与环境
baseURL来自import.meta.env.VITE_ADMIN_API_BASE_URL(.env.development/.env.production)。- 拦截器自动携带
Authorization: Bearer <token>,业务码 401 或 HTTP 401 时清 token 并跳转登录页。 - 相同错误信息 2 秒内只提示一次。
- 环境变量必须以
VITE_开头;禁止存放密钥。
提交前
bash
pnpm exec vue-tsc --noEmit
pnpm build确认:亮/暗主题正常、无权限角色看不到按钮且后端返回 403、空态与错误态处理正确、字段与后端一致、未提交 .env.local / 构建产物 / 密钥。