添加前端规范,添加原子样式
This commit is contained in:
parent
254748302c
commit
926485b6eb
266
docs/前端项目规范.md
Normal file
266
docs/前端项目规范.md
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
# AI 开发规范(media-science-frontend)
|
||||||
|
|
||||||
|
本文档基于当前仓库的真实配置与现有代码风格整理,目标是让 **AI/新同学**在不“自作主张改风格/改架构”的前提下,稳定产出可合并的代码。
|
||||||
|
|
||||||
|
## 技术栈与关键约束
|
||||||
|
|
||||||
|
- **框架**:Vue 3 + TypeScript + Vite(`type: module`
|
||||||
|
- **路由**:`unplugin-vue-router` 文件路由(`vue-router/auto`),Hash 模式(`createWebHashHistory()`)
|
||||||
|
- **状态**:Pinia(setup store 风格)
|
||||||
|
- **图表**:图表:echarts
|
||||||
|
- **UI**:Ant Design Vue
|
||||||
|
- **样式**:UnoCSS(`virtual:uno.css`),允许在模板里大量使用原子类(复杂样式例如动画,渐变等使用class声明,其余使用原子样式)
|
||||||
|
- **国际化**:`@intlify/unplugin-vue-i18n`(`src/locales/*.json`)
|
||||||
|
- **格式化门禁**:Husky + lint-staged + Prettier(提交前会对 `*.{vue,ts,tsx,js,jsx,md,css,less,json}` 自动 `prettier --write`)
|
||||||
|
- **TypeScript 严格性**:`strict: true`,并开启 `noUnusedLocals/noUnusedParameters`
|
||||||
|
- **自动导入**:`unplugin-auto-import`(`vue`、`pinia`、`@vueuse/core`、router auto imports),因此代码里可能**未显式 import** `ref/computed/watch/...`
|
||||||
|
|
||||||
|
## 项目架构(目录分层)
|
||||||
|
|
||||||
|
以 `src/` 为根:
|
||||||
|
|
||||||
|
- **入口**:`src/main.ts`(创建 app、挂载 router/pinia/Antd、全局指令)
|
||||||
|
- **根组件**:`src/App.vue`(全局初始化逻辑、主题/locale、监听生命周期等)
|
||||||
|
- **页面**:`src/pages/**`(文件路由:一个目录通常对应一个页面模块;页面内可用 `definePage({ alias: [...] })`)
|
||||||
|
- **通用组件**:`src/components/**`(跨页面复用的 UI/布局组件,如 `Layout.vue`)
|
||||||
|
- **状态管理**:`src/store/**`(Pinia stores)
|
||||||
|
- **接口层**:`src/api/**`
|
||||||
|
- `src/api/generated/**`:Orval 自动生成(接口类型与路径的唯一真源,禁止手改)
|
||||||
|
- `src/api/client.ts`:项目侧统一入口/别名层(导出客户端实例与类型工具)
|
||||||
|
- `src/api/*.ts`:业务适配层(可选:解包 Result、分页扁平化、兼容旧页面结构等)
|
||||||
|
- **工具**:`src/utils/**`(如 `useRouteUtil.ts`、`useLocalStorage.ts`、`useWebSocket.ts` 等)
|
||||||
|
- **类型声明**:`src/**/*.d.ts`、`src/global.d.ts`、`src/vite-env.d.ts`
|
||||||
|
|
||||||
|
### 仓库根目录速览(常用)
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├─ src/ # 应用源码
|
||||||
|
├─ public/ # 静态资源(按 Vite 约定)
|
||||||
|
├─ vite/ # Vite 插件/构建辅助(如 LocaleType)
|
||||||
|
├─ vite.config.ts # 构建与插件配置(含 proxy、自动路由、自动导入等)
|
||||||
|
├─ uno.config.ts # UnoCSS 主题/shortcuts
|
||||||
|
├─ tsconfig*.json # TS 严格配置(strict + noUnused*)
|
||||||
|
├─ .prettierrc # 代码格式标准(提交前会强制执行)
|
||||||
|
├─ .husky/pre-commit # 提交钩子:lint-staged
|
||||||
|
├─ .env* # 环境变量(通过 import.meta.env 读取)
|
||||||
|
└─ AI_DEV_GUIDE.md # 本规范
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编码风格(必须遵守)
|
||||||
|
|
||||||
|
### 代码格式(Prettier 为准)
|
||||||
|
|
||||||
|
来自 `.prettierrc`:
|
||||||
|
|
||||||
|
- **单引号**:`'...'`
|
||||||
|
- **缩进**:2 空格,禁用 tab
|
||||||
|
- **行宽**:100
|
||||||
|
|
||||||
|
> 任何“手动对齐/自定义格式化习惯”都会被提交前的 Prettier 重写,AI 不要和格式化对抗。
|
||||||
|
|
||||||
|
### Vue SFC 约定
|
||||||
|
|
||||||
|
- **优先使用**:`<script lang="ts" setup>`(当前仓库主流写法)
|
||||||
|
- **模板类名**:允许 UnoCSS 原子类;尽量复用 `uno.config.ts` 里的 shortcuts(如 `flex-center`)
|
||||||
|
- **样式块**:
|
||||||
|
- 页面级样式:优先 `scoped`
|
||||||
|
- 全局样式:放在明确的全局入口(项目当前也存在在 `App.vue` 内写全局样式的情况;新增时优先放到统一样式文件,除非确实需要跟随根组件)
|
||||||
|
|
||||||
|
### TypeScript/类型策略
|
||||||
|
|
||||||
|
- **避免**:`any`(除非是三方库/遗留代码无法避免,且要把 `any` 限制在最小范围)
|
||||||
|
- **参数与返回值**:对外导出的工具函数、store action、请求函数调用处尽量补齐类型
|
||||||
|
- **未使用变量/参数**:因为启用了 `noUnusedLocals/noUnusedParameters`,新增代码必须确保无未使用项
|
||||||
|
|
||||||
|
## 路由规范(文件路由 + definePage)
|
||||||
|
|
||||||
|
- **页面文件位置**:放在 `src/pages/...` 下,按业务模块分目录
|
||||||
|
- **页面元信息**:在页面组件的 `setup` 中使用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { definePage } from 'vue-router/auto';
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
alias: ['/xxx', '/yyy'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- **路由工具**:推荐使用 `src/utils/useRouteUtil.ts` 获取 `route/router/params<T>()`
|
||||||
|
|
||||||
|
## 状态管理规范(Pinia setup store)
|
||||||
|
|
||||||
|
- **位置**:`src/store/*.ts`
|
||||||
|
- **风格**:`defineStore('name', () => { ... })`,对外返回 `ref/computed` 等
|
||||||
|
- **与请求层鉴权同步**:涉及登录态/鉴权时,确保“状态层的 token”与“API 客户端实际使用的鉴权头/拦截器/存储”一致,避免出现“界面认为已登录但请求未携带 token”或相反的情况
|
||||||
|
|
||||||
|
## API 开发规范(Orval 生成代码)
|
||||||
|
|
||||||
|
本规范以 `src/api/generated/` 为**接口类型与路径的唯一真源**,通过 Orval 从后端 OpenAPI 自动生成 TypeScript 类型 + 客户端方法。
|
||||||
|
|
||||||
|
### 1. 目录与职责边界
|
||||||
|
|
||||||
|
- **`src/api/generated/`**:Orval 自动生成目录,**禁止手改**。
|
||||||
|
- `api.ts`:`getReadingPlatformAPI()` 工厂函数,返回包含全部接口方法的对象。
|
||||||
|
- `model/`:OpenAPI 生成的 DTO/VO/Result/PageResult/Params 类型。
|
||||||
|
- **`src/api/client.ts`**:项目侧的“统一入口/别名层”,导出 `readingApi`(完整客户端实例)以及常用的类型工具(解包、分页别名等)。
|
||||||
|
- **`src/api/*.ts`**:业务侧“适配层”(可选),用于:
|
||||||
|
- 兼容既有页面期望的“扁平结构/字段名/返回形态”
|
||||||
|
- 补齐 OpenAPI 暂未覆盖的历史接口(短期过渡)
|
||||||
|
- 汇聚跨接口的业务逻辑(例如组合请求、额外校验)
|
||||||
|
|
||||||
|
### 2. 基本原则(必须遵守)
|
||||||
|
|
||||||
|
- **生成代码只读**:不得在 `src/api/generated/**` 内做任何手工修改(包括修复类型、改路径、加字段)。
|
||||||
|
- **以生成类型为准**:参数/返回类型优先使用 `src/api/generated/model` 导出的类型,避免手写 `interface` 漂移。
|
||||||
|
- **对外只暴露稳定的业务接口**:页面/组件尽量通过 `src/api/*.ts`(适配层)或 `src/api/client.ts`(直接调用)访问,避免散落调用方式导致难以迁移。
|
||||||
|
|
||||||
|
### 3. 推荐调用方式
|
||||||
|
|
||||||
|
#### 3.1 直接使用 Orval 客户端
|
||||||
|
|
||||||
|
- 统一从 `src/api/client.ts` 引入:
|
||||||
|
- `readingApi`: `getReadingPlatformAPI()` 的实例
|
||||||
|
- `ApiResultOf` / `UnwrapResult` / `PageDataOf` 等类型工具
|
||||||
|
|
||||||
|
示例(以 `Result<T>` 为包裹结构):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { readingApi } from '@/api/client';
|
||||||
|
import type { ResultTenant } from '@/api/generated/model';
|
||||||
|
|
||||||
|
async function loadTenant(id: number) {
|
||||||
|
const res = (await readingApi.getTenant(id)) as ResultTenant;
|
||||||
|
return res.data; // T(可能为 undefined,取决于后端返回与类型定义)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 使用“适配层”稳定返回结构
|
||||||
|
|
||||||
|
当页面已经依赖历史返回结构(例如直接要 `items/total/page/pageSize`),在 `src/api/*.ts` 内做一次性适配,页面只消费适配后的结构。
|
||||||
|
|
||||||
|
分页适配建议统一输出:
|
||||||
|
|
||||||
|
- `items: T[]`
|
||||||
|
- `total: number`
|
||||||
|
- `page: number`
|
||||||
|
- `pageSize: number`
|
||||||
|
|
||||||
|
### 4. Result / PageResult 约定与解包
|
||||||
|
|
||||||
|
后端统一响应通常为:
|
||||||
|
|
||||||
|
- **普通接口**:`Result<T>`,字段一般为 `code/message/data`
|
||||||
|
- **分页接口**:`Result<PageResult<T>>`,字段一般为 `items/total/page/pageSize`
|
||||||
|
|
||||||
|
在生成代码中常见类型形态:
|
||||||
|
|
||||||
|
- `ResultXXX`(如 `ResultTenant`、`ResultUserInfoResponse`)
|
||||||
|
- `ResultPageResultXXX`(如 `ResultPageResultTenant`)
|
||||||
|
- `PageResultXXX`(如 `PageResultTenant`)
|
||||||
|
|
||||||
|
建议做法:
|
||||||
|
|
||||||
|
- **组件/页面层尽量不要直接处理 `ResultXXX`**,而是由适配层解包并做兜底(空数组、默认分页参数等)。
|
||||||
|
- **严禁在页面散落 `as any`**;确需兼容时,集中在 `src/api/*.ts` 适配层进行,并在适配层内把“最终对页面返回的类型”定义清楚。
|
||||||
|
|
||||||
|
### 5. 命名与重复接口(`getXxx`/`getXxx1`/`getXxx2`)
|
||||||
|
|
||||||
|
由于不同角色端点(teacher/school/parent/admin)可能存在同名资源,Orval 在生成时会用 `1/2/3` 后缀消歧,例如:
|
||||||
|
|
||||||
|
- `getTask`(teacher) vs `getTask1`(school) vs `getTask2`(parent)
|
||||||
|
|
||||||
|
规范建议:
|
||||||
|
|
||||||
|
- **业务层不要直接暴露带数字后缀的方法名**;
|
||||||
|
- 在 `src/api/*.ts` 中封装为语义化名称,例如:
|
||||||
|
- `teacherGetTask` / `schoolGetTask` / `parentGetTask`
|
||||||
|
- 或按模块拆分到 `src/api/teacher/task.ts` 等(如后续重构允许)
|
||||||
|
|
||||||
|
### 6. 何时需要更新生成代码
|
||||||
|
|
||||||
|
当后端 Controller 或 DTO/VO 发生变更:
|
||||||
|
|
||||||
|
1. 后端更新 OpenAPI(Knife4j/SpringDoc)
|
||||||
|
2. 前端更新规范并重新生成(项目已有脚本):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run api:update
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 提交生成物(通常包含 `api-spec.*` 与 `src/api/generated/**`)
|
||||||
|
|
||||||
|
> 注意:如果某接口在后端已存在但 OpenAPI 未导出(例如缺少注解/返回类型不规范),应优先修后端文档,而不是在前端“硬编码路径”长期绕过。
|
||||||
|
|
||||||
|
### 7. 禁止事项(高频踩坑)
|
||||||
|
|
||||||
|
- **禁止**:手改 `src/api/generated/**`(下次生成会被覆盖,且会引入不可追踪差异)。
|
||||||
|
- **禁止**:页面里手写 axios 调用去访问 `/api/v1/...`(除非 OpenAPI 暂缺且已在适配层集中兜底)。
|
||||||
|
- **禁止**:在业务代码中扩散 `any` 来“快速通过类型检查”。
|
||||||
|
|
||||||
|
### 8. 迁移策略(从旧 http 到 Orval)
|
||||||
|
|
||||||
|
若已有模块使用 `src/api/index.ts` 的 `http.get/post/...`:
|
||||||
|
|
||||||
|
- **短期**:保留旧实现,但新增/变更接口优先走 `readingApi`
|
||||||
|
- **中期**:逐模块把旧 `http` 调用替换为 `readingApi`,并在适配层维持页面不改
|
||||||
|
- **长期**:页面全面只依赖适配层/生成客户端,减少重复封装
|
||||||
|
|
||||||
|
## 本地存储规范
|
||||||
|
|
||||||
|
- **Key 统一**:只在 `src/utils/useLocalStorage.ts` 的 `LocalStorageKey` 中声明与复用
|
||||||
|
- **登出清理**:使用 `outLoginClearStorage()`(如需扩展清理范围,优先扩展 `LocalStorageKey`)
|
||||||
|
|
||||||
|
## 组件与文件命名
|
||||||
|
|
||||||
|
- **Vue 组件**:
|
||||||
|
- 通用组件:`src/components/PascalCase.vue`(现有仓库既有 PascalCase 也有小写文件名;新增时优先 PascalCase,避免混乱扩大)
|
||||||
|
- 页面组件:`src/pages/<module>/index.vue`(仓库已有此习惯)
|
||||||
|
- 页面子组件:`src/pages/<module>/components/*.vue`
|
||||||
|
- **TS 工具**:`src/utils/camelCase.ts` 或 `useXxx.ts`(hook/组合式函数)
|
||||||
|
- **Store**:`src/store/useXxx.ts` 或 `src/store/xxx.ts`(与现有文件保持一致,避免引入第三种命名体系)
|
||||||
|
|
||||||
|
## 变更边界(AI 必须遵守)
|
||||||
|
|
||||||
|
- **不做无关重构**:只改与需求相关的文件/代码;不要“顺便”换写法、换目录结构、统一命名
|
||||||
|
- **不引入新依赖**:除非需求明确且必要;新增依赖要同步更新 `package.json` 并说明原因
|
||||||
|
- **不改公共行为**:如 `request`、token 同步、路由生成规则、构建配置(除非需求明确)
|
||||||
|
|
||||||
|
## 环境变量与配置(不要绕开)
|
||||||
|
|
||||||
|
- **读取方式**:统一用 `src/utils/env.ts` 的 `getAppEnvConfig()`,不要在业务代码里到处直接读 `import.meta.env.xxx`
|
||||||
|
- **常用字段**:
|
||||||
|
- `VITE_PREFIX_API`:请求前缀(当前 `request` 使用的是 \(`${VITE_PREFIX_API}${url}`\))
|
||||||
|
- `VITE_BASE_API`:存在但当前请求实现未拼接(历史上有注释;如需启用必须全局评估影响)
|
||||||
|
- **注意**:`vite.config.ts` 已配置 proxy(如 `/scienceApi`、`/localhostApi`);开发环境应优先通过前缀 + proxy 工作,而不是写死域名
|
||||||
|
|
||||||
|
## 提交与协作
|
||||||
|
|
||||||
|
- **提交前**:确保 `pnpm dev` 能启动、关键页面可访问(至少覆盖改动路径)
|
||||||
|
- **格式化**:提交时会自动跑 Prettier;如果出现大量无关格式 diff,说明你改动触发了更大范围格式化,需收敛修改范围
|
||||||
|
- **变更说明**:PR/提交信息优先解释“为什么”而不是“改了什么”
|
||||||
|
|
||||||
|
## AI 变更自检清单(提交前必过)
|
||||||
|
|
||||||
|
- **范围**:是否只改了需求相关文件?是否避免“顺手重构/改命名/大面积格式化”?
|
||||||
|
- **类型**:`tsconfig` 已启用 `strict` 与 `noUnused*`,新增代码是否无未使用变量/参数?
|
||||||
|
- **路由**:新增页面是否放在 `src/pages/` 并使用 `definePage`(如需 alias)?
|
||||||
|
- **请求**:是否遵守 Orval 规范(生成代码只读、调用集中在 `src/api/client.ts`/适配层,避免页面散落 axios/fetch)?
|
||||||
|
- **鉴权**:涉及登录态/鉴权时,是否确保 store 中的 token 与 API 客户端实际使用的鉴权头/拦截器/存储一致?
|
||||||
|
- **存储**:localStorage key 是否只使用 `LocalStorageKey`?
|
||||||
|
- **格式**:是否符合 `.prettierrc`(单引号、2 空格、行宽 100),并接受提交前会被 Prettier 重写?
|
||||||
|
|
||||||
|
## 常见开发模板(给 AI 直接套用)
|
||||||
|
|
||||||
|
### 新增页面(文件路由)
|
||||||
|
|
||||||
|
1. 新建 `src/pages/<biz>/index.vue`
|
||||||
|
2. 在 `script setup` 中写 `definePage({ alias: [...] })`
|
||||||
|
3. 使用 `RouterView`/组件拼装页面
|
||||||
|
|
||||||
|
### 新增接口并调用
|
||||||
|
|
||||||
|
1. 后端更新 OpenAPI(确保该接口可被导出)
|
||||||
|
2. 前端执行 `npm run api:update` 重新生成(产物在 `src/api/generated/**`)
|
||||||
|
3. 在 `src/api/client.ts` 或 `src/api/*.ts`(适配层)封装稳定接口
|
||||||
|
4. 页面/组件只从 `src/api/client.ts` 或适配层引入调用,避免散落直连请求
|
||||||
1359
reading-platform-frontend/package-lock.json
generated
1359
reading-platform-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,7 @@
|
|||||||
"orval": "^7.13.2",
|
"orval": "^7.13.2",
|
||||||
"sass-embedded": "^1.97.3",
|
"sass-embedded": "^1.97.3",
|
||||||
"typescript": "~5.4.0",
|
"typescript": "~5.4.0",
|
||||||
|
"unocss": "^66.6.6",
|
||||||
"unplugin-auto-import": "^0.17.5",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
"vite": "^5.1.6",
|
"vite": "^5.1.6",
|
||||||
|
|||||||
22
reading-platform-frontend/src/components.d.ts
vendored
22
reading-platform-frontend/src/components.d.ts
vendored
@ -13,8 +13,13 @@ declare module 'vue' {
|
|||||||
AButton: typeof import('ant-design-vue/es')['Button']
|
AButton: typeof import('ant-design-vue/es')['Button']
|
||||||
ACard: typeof import('ant-design-vue/es')['Card']
|
ACard: typeof import('ant-design-vue/es')['Card']
|
||||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||||
|
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
|
||||||
ACol: typeof import('ant-design-vue/es')['Col']
|
ACol: typeof import('ant-design-vue/es')['Col']
|
||||||
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
||||||
|
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
||||||
|
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
||||||
|
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||||
|
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||||
AForm: typeof import('ant-design-vue/es')['Form']
|
AForm: typeof import('ant-design-vue/es')['Form']
|
||||||
@ -22,33 +27,50 @@ declare module 'vue' {
|
|||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
AImage: typeof import('ant-design-vue/es')['Image']
|
||||||
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
|
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||||
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
||||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
||||||
|
AList: typeof import('ant-design-vue/es')['List']
|
||||||
|
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||||
|
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||||
|
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
||||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||||
|
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||||
|
ARate: typeof import('ant-design-vue/es')['Rate']
|
||||||
ARow: typeof import('ant-design-vue/es')['Row']
|
ARow: typeof import('ant-design-vue/es')['Row']
|
||||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||||
|
ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup']
|
||||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||||
|
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||||
|
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||||
|
AStep: typeof import('ant-design-vue/es')['Step']
|
||||||
|
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||||
|
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||||
ATable: typeof import('ant-design-vue/es')['Table']
|
ATable: typeof import('ant-design-vue/es')['Table']
|
||||||
|
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||||
|
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||||
|
ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker']
|
||||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||||
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
|
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
|
||||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||||
|
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
|
||||||
FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default']
|
FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default']
|
||||||
FileUploader: typeof import('./components/course/FileUploader.vue')['default']
|
FileUploader: typeof import('./components/course/FileUploader.vue')['default']
|
||||||
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { createApp } from 'vue';
|
|||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import Antd from 'ant-design-vue';
|
import Antd from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
|
import 'uno.css';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
|
|||||||
@ -41,14 +41,14 @@
|
|||||||
|
|
||||||
<a-menu-item key="bundles">
|
<a-menu-item key="bundles">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DatabaseOutlined :size="18" :stroke-width="1.5" />
|
<Package :size="18" :stroke-width="1.5" />
|
||||||
</template>
|
</template>
|
||||||
<span>套餐管理</span>
|
<span>套餐管理</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
|
||||||
<a-menu-item key="themes">
|
<a-menu-item key="themes">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<FormatPainterOutlined :size="18" :stroke-width="1.5" />
|
<Palette :size="18" :stroke-width="1.5" />
|
||||||
</template>
|
</template>
|
||||||
<span>主题字典</span>
|
<span>主题字典</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@ -149,8 +149,9 @@ import {
|
|||||||
Building2,
|
Building2,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
Settings,
|
Settings,
|
||||||
|
Package,
|
||||||
|
Palette,
|
||||||
} from 'lucide-vue-next';
|
} from 'lucide-vue-next';
|
||||||
import { DatabaseOutlined, FormatPainterOutlined } from '@ant-design/icons-vue';
|
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import AutoImport from "unplugin-auto-import/vite";
|
|||||||
import Components from "unplugin-vue-components/vite";
|
import Components from "unplugin-vue-components/vite";
|
||||||
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
|
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
|
||||||
import viteCompression from "vite-plugin-compression";
|
import viteCompression from "vite-plugin-compression";
|
||||||
|
import UnoCSS from "unocss/vite";
|
||||||
|
import { presetUno, presetAttributify } from "unocss";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -28,6 +30,9 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
dts: "src/components.d.ts",
|
dts: "src/components.d.ts",
|
||||||
}),
|
}),
|
||||||
|
UnoCSS({
|
||||||
|
presets: [presetUno(), presetAttributify()],
|
||||||
|
}),
|
||||||
viteCompression({
|
viteCompression({
|
||||||
verbose: true,
|
verbose: true,
|
||||||
disable: false,
|
disable: false,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user