feat init
This commit is contained in:
382
.claude/skills/vue-project-structure/SKILL.md
Normal file
382
.claude/skills/vue-project-structure/SKILL.md
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
---
|
||||||
|
name: vue-project-structure
|
||||||
|
description: cz-web 项目(Vue 3 + TypeScript + Vite 网页端站点)的目录结构与编码规范强制 skill。当在本项目中新增、创建、移动、重构或修改任何 src/ 下的代码文件(.vue/.ts/.less)、路由、store、API、组件、页面时,必须加载本 skill,先读取项目目录、再按本规范确定文件归属与写法。本项目是**网页端站点(非中后台)**:页面按名称组织(home/news/product…),接口层在 api/、数据层在 Pinia、页面只经 store 取数,样式使用**less**。凡是涉及"新文件放哪"、"页面数据怎么拿"、"页面/公共组件归属"、"命名"、"功能对应哪些文件"的任务也必须使用本 skill。
|
||||||
|
version: 2.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# cz-web 项目目录结构规范(强制 · 网页端)
|
||||||
|
|
||||||
|
> 完整版调试文档见 `docs/vue-project-guide.md`(其中中后台相关部分已废弃),**本 skill 为唯一现行规范,以其为准**;未覆盖场景参照 Vue 3 官方目录约定。
|
||||||
|
|
||||||
|
## 第一步:读取项目目录(每次加载必做)
|
||||||
|
|
||||||
|
动手写任何代码前,先用工具映射当前项目结构,确认正在操作的正确层级:
|
||||||
|
|
||||||
|
1. 用 `codegraph_files` 或 `glob` 列出 `src/` 下的现行目录结构(如某目录尚不存在,应在对应职责目录创建,而不是自创新顶层目录)。
|
||||||
|
2. 读取目标文件及其同级文件,确认现有命名风格与组织方式(页面名、store 名、api 名必须一致)。
|
||||||
|
3. 只有在目录树确认完毕后,才允许创建/修改文件。
|
||||||
|
|
||||||
|
## 核心原则
|
||||||
|
|
||||||
|
1. **三层数据流(页面 → store → api)**:页面组件只通过 **Pinia store** 获取数据;store 是数据层,负责调用 **api 层**接口取数并维护状态;api 层只发请求。**页面禁止直接 import api**。
|
||||||
|
2. **按页面/页面名组织(一页一套)**:`views/<page>/`、`api/<page>.ts`、`stores/modules/<page>.ts`、`types/business/<page>.d.ts` 四者同名对应,方便分层查看。
|
||||||
|
3. **组件两级归属**:`components/` 只放**跨页面复用的公共组件**;`views/<page>/cpns/` 放**该页面独有的组件**。
|
||||||
|
4. **网页端(非中后台)**:无侧边栏菜单/权限路由/TabsBar 体系;页面以业务(首页、新闻、产品、关于我们…)划分。
|
||||||
|
5. **样式使用 less**:全局样式为 `.less` 文件,组件内 `<style lang="less" scoped>`。
|
||||||
|
|
||||||
|
## 数据流架构(强约束)
|
||||||
|
|
||||||
|
```text
|
||||||
|
页面组件 (views/home/index.vue)
|
||||||
|
│ ① 页面只调用 store 的 action / 读取 store 的 state(模板渲染数据)
|
||||||
|
▼
|
||||||
|
Pinia store (stores/modules/home.ts) —— 数据层
|
||||||
|
│ ② store 的 action 内部调用 api 接口函数 → await → 写入 state / 返回结果
|
||||||
|
▼
|
||||||
|
API (api/home.ts) —— 接口层
|
||||||
|
│ ③ 经 utils/request.ts 发 HTTP 请求
|
||||||
|
▼
|
||||||
|
后端服务器
|
||||||
|
```
|
||||||
|
|
||||||
|
铁律:
|
||||||
|
|
||||||
|
- **页面禁止 `import` api 层文件**;获取数据必须调对应页面 store 的方法。
|
||||||
|
- **api 层是纯接口层**:只发请求、返回数据(带类型),不做业务状态处理、不缓存。
|
||||||
|
- **store 承担取数 + 数据状态 + 页面间共享 + 缓存**;与页面同名的 store 责任边界即该页面。
|
||||||
|
- 调用链必须是 `页面 → store → api` 单向,禁止跳过 store 或反向依赖。
|
||||||
|
|
||||||
|
## src/ 目录结构(强制划分)
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── api/ # ★ 接口层:按页面命名,一页一文件(home.ts、news.ts、product.ts、about.ts)
|
||||||
|
├── assets/ # 参与构建的静态资源(images/、icons/、fonts/)
|
||||||
|
├── components/ # ★ 公共组件:仅跨页面复用的全站通用组件(禁含具体页面业务)
|
||||||
|
│ ├── AppHeader.vue # 全站头部(导航)
|
||||||
|
│ ├── AppFooter.vue # 全站页脚
|
||||||
|
│ ├── AppButton.vue # 通用按钮
|
||||||
|
│ ├── AppSwiper.vue # 通用轮播
|
||||||
|
│ └── index.ts # 统一导出
|
||||||
|
├── composables/ # 通用组合式逻辑(useScroll、useLoading、useCountDown、useIntersection)
|
||||||
|
├── directives/ # 自定义指令(lazy 图片懒加载、debounce、throttle + index.ts 统一注册)
|
||||||
|
├── layout/ # 全站布局骨架:index.vue = <AppHeader/> + <router-view/> + <AppFooter/>
|
||||||
|
├── router/ # 路由:index.ts(入口)、routes.ts(静态路由表)、guard.ts(守卫,可选)
|
||||||
|
├── stores/ # ★ 数据层(Pinia):按页面命名,一页一 store
|
||||||
|
│ ├── index.ts # pinia 实例(main.ts 中 app.use(pinia))
|
||||||
|
│ └── modules/
|
||||||
|
│ ├── home.ts # useHomeStore:首页数据(banner、推荐、公告)
|
||||||
|
│ ├── news.ts # useNewsStore:新闻列表/分页/详情
|
||||||
|
│ ├── product.ts # useProductStore:产品列表/详情
|
||||||
|
│ └── site.ts # useSiteStore:站点公共数据(站点配置、全局 loading),非页面级
|
||||||
|
├── styles/ # ★ 全局样式(less)
|
||||||
|
│ ├── index.less # 样式入口(main.ts 引入),组织以下文件
|
||||||
|
│ ├── variables.less # ★ less 变量唯一来源(颜色/间距/字号/圆角),禁止组件内魔法色值
|
||||||
|
│ ├── mixins.less # 混合宏(flex、文本省略、滚动条)
|
||||||
|
│ ├── reset.less # 样式重置
|
||||||
|
│ └── common.less # 通用工具类(容器宽度、间距类)
|
||||||
|
├── types/ # 类型定义
|
||||||
|
│ ├── global.d.ts # 全局声明(Window 扩展、import.meta.env 兜底)
|
||||||
|
│ ├── api.d.ts # 接口通用结构:ApiResponse<T>、PageResult<T>
|
||||||
|
│ ├── router.d.ts # RouteMeta 扩展
|
||||||
|
│ └── business/ # 按页面与 api 对应:home.d.ts / news.d.ts / product.d.ts
|
||||||
|
├── utils/ # 纯函数工具(无 Vue 依赖):request.ts、storage.ts、validate.ts、format.ts
|
||||||
|
└── views/ # ★ 页面:一页一目录
|
||||||
|
├── home/
|
||||||
|
│ ├── index.vue # 页面主组件(路由通过 index.ts 加载)
|
||||||
|
│ ├── index.ts # 页面出口(barrel):export default 页面 + 导出本页独有组件
|
||||||
|
│ └── cpns/ # 页面独有组件(head、banner、news-list 等,仅本页面使用)
|
||||||
|
│ ├── Head.vue
|
||||||
|
│ └── Banner.vue
|
||||||
|
├── news/
|
||||||
|
│ ├── index.vue # 列表页
|
||||||
|
│ ├── detail.vue # 详情页
|
||||||
|
│ ├── index.ts
|
||||||
|
│ └── cpns/
|
||||||
|
├── product/ # 同上结构
|
||||||
|
├── about/ # 同上结构
|
||||||
|
└── login/ # 登录(仅存在会员体系时)
|
||||||
|
```
|
||||||
|
|
||||||
|
**命名一致性(可分层互查)**:页面 `home` → `api/home.ts` + `stores/modules/home.ts`(`useHomeStore`) + `types/business/home.d.ts` + `views/home/`,四者同名。
|
||||||
|
|
||||||
|
## src/ 每个文件的作用(新建/修改文件前先对照本节定位)
|
||||||
|
|
||||||
|
### 入口与根文件
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `src/main.ts` | 应用入口:`createApp(App)`,注册 `pinia`、`router`、自定义指令(`setupDirectives`)、引入全局 `styles/index.less`。不写业务逻辑。 |
|
||||||
|
| `src/App.vue` | 根组件,承载 `<router-view/>` 与全局过渡;不含业务代码。 |
|
||||||
|
| `src/env.d.ts` | Vite 环境变量类型声明(`ImportMetaEnv` / `ImportMeta`)。 |
|
||||||
|
|
||||||
|
### `api/` —— 接口层(一页一文件,与页面同名)
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| ---------------- | ------------------------------------------------------ |
|
||||||
|
| `api/home.ts` | 首页接口:`getHomeBanner`、`getRecommend`、`getNotice` |
|
||||||
|
| `api/news.ts` | 新闻接口:`getNewsList`、`getNewsDetail` |
|
||||||
|
| `api/product.ts` | 产品接口:`getProductList`、`getProductDetail` |
|
||||||
|
| `api/about.ts` | 关于我们/公司介绍接口 |
|
||||||
|
| `api/auth.ts` | 登录/注册/用户信息(仅会员体系需要时) |
|
||||||
|
|
||||||
|
> **纯接口层**:函数只负责 `request<T>({ url, method, params/data })`,不做数据加工、不维护状态。新增页面 → 新建 `api/<page>.ts`。
|
||||||
|
|
||||||
|
### `components/` —— 公共组件(仅跨页面复用)
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| -------------------------- | ------------------------------------------------------------ |
|
||||||
|
| `components/AppHeader.vue` | 全站头部:logo、导航菜单(路由跳转) |
|
||||||
|
| `components/AppFooter.vue` | 全站页脚:版权、友链 |
|
||||||
|
| `components/AppButton.vue` | 通用按钮(disabled/loading/类型统一) |
|
||||||
|
| `components/AppSwiper.vue` | 通用轮播(首页 banner、产品图) |
|
||||||
|
| `components/index.ts` | 统一导出(barrel),页面与页面组件统一从 `@/components` 导入 |
|
||||||
|
|
||||||
|
> 判定:被 ≥2 个页面使用 → 放这里;仅 1 个页面使用 → 放 `views/<page>/cpns/`。
|
||||||
|
|
||||||
|
### `composables/` —— 通用组合式逻辑(与 UI 解耦)
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| -------------------------------- | ------------------------ |
|
||||||
|
| `composables/useScroll.ts` | 滚动监听/吸顶/回到顶部 |
|
||||||
|
| `composables/useLoading.ts` | 请求 loading 状态管理 |
|
||||||
|
| `composables/useCountDown.ts` | 倒计时(验证码等) |
|
||||||
|
| `composables/useIntersection.ts` | 元素可见性(配合懒加载) |
|
||||||
|
|
||||||
|
### `directives/` —— 自定义指令
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| ------------------------ | --------------------------------------------- |
|
||||||
|
| `directives/lazy.ts` | `v-lazy` 图片懒加载(网页端常用) |
|
||||||
|
| `directives/debounce.ts` | `v-debounce` 防抖 |
|
||||||
|
| `directives/throttle.ts` | `v-throttle` 节流 |
|
||||||
|
| `directives/index.ts` | `setupDirectives(app)` 统一注册,main.ts 调用 |
|
||||||
|
|
||||||
|
### `layout/` —— 全站布局骨架
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| -------------------- | -------------------------------------------------------------------------- |
|
||||||
|
| `layout/index.vue` | 全站公共布局:`<AppHeader/>` + `<router-view/>` + `<AppFooter/>`,仅此一层 |
|
||||||
|
| `layout/components/` | 布局内部组件(如返回顶部按钮);需要全站复用的上移 `components/` |
|
||||||
|
|
||||||
|
### `router/` —— 路由系统(网页端:静态路由为主)
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| ------------------ | ------------------------------------------------------------------------- |
|
||||||
|
| `router/index.ts` | 创建 `createRouter`(createWebHistory)+ 注册静态路由 + 挂守卫 |
|
||||||
|
| `router/routes.ts` | 静态路由表:`/home`、`/news`、`/news/:id`、`/product`、`/about`、404 兜底 |
|
||||||
|
| `router/guard.ts` | 路由守卫(可选):登录态校验(会员页)、滚动位置恢复、document.title |
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 路由加载页面(指向 views/home/index.ts —— 页面出口)
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
name: 'Home',
|
||||||
|
component: () => import('@/views/home'), // 解析到 index.ts 的 default(index.vue)
|
||||||
|
meta: { title: '首页' },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `stores/modules/` —— 数据层(Pinia,一页一 store,与页面同名)
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| --------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
|
| `stores/modules/home.ts` | `useHomeStore`:state(banner/推荐/公告)+ actions(`fetchHomeData`,内部调 `api/home.ts`) |
|
||||||
|
| `stores/modules/news.ts` | `useNewsStore`:列表/分页/详情 state + `fetchNewsList`、`fetchNewsDetail` |
|
||||||
|
| `stores/modules/product.ts` | `useProductStore`:产品列表/详情/分类筛选 |
|
||||||
|
| `stores/modules/site.ts` | `useSiteStore`:站点配置、全局 loading、主题(非页面级公共数据) |
|
||||||
|
| `stores/modules/user.ts` | `useUserStore`:登录态/token(仅会员体系需要时) |
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// stores/modules/home.ts —— 数据层写法示例(页面 → store → api)
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { getHomeBanner, getRecommend } from '@/api/home'
|
||||||
|
import type { Banner, RecommendItem } from '@/types/business/home'
|
||||||
|
|
||||||
|
export const useHomeStore = defineStore('home', () => {
|
||||||
|
const banners = ref<Banner[]>([])
|
||||||
|
const recommends = ref<RecommendItem[]>([])
|
||||||
|
|
||||||
|
async function fetchHomeData() {
|
||||||
|
// ① store 调 api 层拿数据
|
||||||
|
const [bannerData, recommendData] = await Promise.all([
|
||||||
|
getHomeBanner(),
|
||||||
|
getRecommend(),
|
||||||
|
])
|
||||||
|
// ② 写入 state——完成页面所需数据
|
||||||
|
banners.value = bannerData
|
||||||
|
recommends.value = recommendData
|
||||||
|
}
|
||||||
|
|
||||||
|
return { banners, recommends, fetchHomeData }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `styles/` —— 全局样式(less)
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| ----------------------- | ------------------------------------------------------------------------------------ |
|
||||||
|
| `styles/index.less` | 入口,main.ts `import '@/styles/index.less'` |
|
||||||
|
| `styles/variables.less` | ★ 变量唯一来源:`@primary-color`、`@text-color`、`@font-size-base`、`@spacing-base`… |
|
||||||
|
| `styles/mixins.less` | 混合宏(flex、文本省略) |
|
||||||
|
| `styles/reset.less` | 重置 |
|
||||||
|
| `styles/common.less` | 通用工具类 |
|
||||||
|
|
||||||
|
组件内样式:`<style lang="less" scoped>`,变量经 vite `css.preprocessorOptions.less.additionalData` 自动注入 `@import '@/styles/variables.less';`,组件内直接使用 `@primary-color`。
|
||||||
|
|
||||||
|
### `types/` —— 类型定义
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| -------------------------- | --------------------------------------------------------------------- |
|
||||||
|
| `types/api.d.ts` | `ApiResponse<T> { code, msg, data }`、`PageResult<T> { list, total }` |
|
||||||
|
| `types/router.d.ts` | `declare module 'vue-router'` 扩展 `RouteMeta`(title 等) |
|
||||||
|
| `types/business/home.d.ts` | 首页实体类型(Banner/RecommendItem),与 `api/home.ts` 对应 |
|
||||||
|
| `types/business/news.d.ts` | 新闻实体(NewsItem/NewsQuery),与 `api/news.ts` 对应 |
|
||||||
|
|
||||||
|
> 新页面 → 新建 `types/business/<page>.d.ts`;禁止在组件内散落实体类型。
|
||||||
|
|
||||||
|
### `utils/` —— 纯函数工具
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `utils/request.ts` | ★ 唯一 axios 实例:拦截器注入 token/统一解包 `ApiResponse`/统一错误提示,导出 `request<T>`。组件与页面禁止再 import axios |
|
||||||
|
| `utils/storage.ts` | localStorage/sessionStorage 泛型封装 |
|
||||||
|
| `utils/validate.ts` | 校验规则(手机号/邮箱/URL) |
|
||||||
|
| `utils/format.ts` | 格式化(金额/日期/千分位) |
|
||||||
|
|
||||||
|
### `views/` —— 页面(一页一目录,三件套:index.vue + index.ts + cpns/)
|
||||||
|
|
||||||
|
| 文件/目录 | 作用 |
|
||||||
|
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `views/home/index.vue` | 首页主组件:组合 `cpns/` 的独有组件,数据由 `useHomeStore` 提供 |
|
||||||
|
| `views/home/index.ts` | **页面出口(barrel)**:`export { default } from './index.vue'` + 导出 `cpns/` 组件;路由与外部引用统一走它"展示"页面 |
|
||||||
|
| `views/home/cpns/Head.vue` | 首页独有头部区块(banner 下导航条等),仅本页使用 |
|
||||||
|
| `views/home/cpns/Banner.vue` | 首页独有轮播区块(渲染 store 的 banners) |
|
||||||
|
| `views/news/index.vue` | 新闻列表页 |
|
||||||
|
| `views/news/detail.vue` | 新闻详情页(与 index.vue 同目录,同页面模块) |
|
||||||
|
| `views/news/index.ts` | 出口 barrel(同上) |
|
||||||
|
| `views/news/cpns/` | 新闻页独有组件(筛选栏、列表项卡片) |
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// views/home/index.ts —— 页面出口(barrel)写法
|
||||||
|
export { default } from './index.vue' // 页面主组件(default)
|
||||||
|
export { default as HomeHead } from './cpns/Head.vue'
|
||||||
|
export { default as HomeBanner } from './cpns/Banner.vue'
|
||||||
|
```
|
||||||
|
|
||||||
|
> 页面独有组件的归属判定:**只属于某一个页面 → 进该页面 `cpns/`;被 ≥2 个页面共用 → 上移 `components/`**。
|
||||||
|
|
||||||
|
## 项目功能模块图(功能 → 涉及文件)
|
||||||
|
|
||||||
|
| 功能 | 涉及文件 | 说明 |
|
||||||
|
| --------------------- | --------------------------------------------------------------------------------------- | ------------------------------ |
|
||||||
|
| 全站布局(头部/页脚) | `layout/index.vue` + `components/AppHeader.vue` + `components/AppFooter.vue` | 路由首层挂在 layout 下 |
|
||||||
|
| 首页 | `api/home.ts` + `stores/modules/home.ts` + `views/home/`(index.vue + index.ts + cpns) | 页面 → store → api 取数 |
|
||||||
|
| 新闻中心 | `api/news.ts` + `stores/modules/news.ts` + `views/news/` | 列表 + 详情 |
|
||||||
|
| 产品展示 | `api/product.ts` + `stores/modules/product.ts` + `views/product/` | 列表 + 详情 + 分类 |
|
||||||
|
| 关于我们 | `api/about.ts` + `stores/modules/about.ts` + `views/about/` | 公司介绍内容 |
|
||||||
|
| 会员登录(可选) | `api/auth.ts` + `stores/modules/user.ts` + `views/login/` + `router/guard.ts` | 登录态/token |
|
||||||
|
| 轮播/动效 | `components/AppSwiper.vue` + `composables/useScroll.ts` + `directives/lazy.ts` | 首页 banner、图片懒加载 |
|
||||||
|
| 站点公共数据 | `stores/modules/site.ts` + `styles/variables.less` | 站点配置、全局 loading、主题色 |
|
||||||
|
|
||||||
|
## 统一规范速查
|
||||||
|
|
||||||
|
### 路由规范
|
||||||
|
|
||||||
|
- 路由 `component` 指向页面出口:`() => import('@/views/<page>')`(解析到 `index.ts` 的 default = `index.vue`)
|
||||||
|
- 每个路由必须声明 `meta.title`;`RouteMeta` 扩展在 `src/types/router.d.ts`
|
||||||
|
- 网页端以静态路由为主,无需动态权限路由;嵌套页面(news 列表/详情)在同一页面目录下
|
||||||
|
- 公共页面挂载在 `layout/index.vue` 下,页面自身不重复写 header/footer
|
||||||
|
|
||||||
|
### 状态管理(Pinia)规范 —— 数据层
|
||||||
|
|
||||||
|
- store 文件:`src/stores/modules/<page>.ts`,命名 `use<Page>Store`(PascalCase),与 api/views 同名
|
||||||
|
- **统一组合式(setup)写法**:state 用 `ref`,getters 用 `computed`,actions 用普通函数,最后 return 全部
|
||||||
|
- **store 的 action 负责调用对应页面 api 层的接口取数**,页面只调 action
|
||||||
|
- 组件内:state/getters 用 `storeToRefs` 解构;actions 直接解构调用
|
||||||
|
- 简单展示型数据也走 store(保持数据层统一),页面禁止直接 import api
|
||||||
|
|
||||||
|
### 接口 API 规范 —— 接口层
|
||||||
|
|
||||||
|
- **全项目唯一 axios 实例**在 `src/utils/request.ts`,禁止页面/组件 import axios
|
||||||
|
- 统一响应结构 `ApiResponse<T> { code, msg, data }`(见 `types/api.d.ts`)
|
||||||
|
- 接口函数名 = 动词 + 资源:`getHomeBanner` / `getNewsList` / `getNewsDetail` / `getProductList`
|
||||||
|
- `api/<page>.ts` 只做请求分发,不做数据处理;处理与状态归 store
|
||||||
|
- baseURL 用环境变量 `import.meta.env.VITE_API_BASE_URL` 区分环境(.env / .env.development / .env.production)
|
||||||
|
- 错误统一由拦截器兜底,store/页面仅在需特殊处理时 catch
|
||||||
|
|
||||||
|
### 组件规范
|
||||||
|
|
||||||
|
- 公共组件 `components/`:文件 PascalCase(AppHeader.vue);被 ≥2 页面使用才放这里
|
||||||
|
- 页面独有组件 `views/<page>/cpns/`:**严禁被其他页面 import**;语义化命名(Head.vue、Banner.vue)
|
||||||
|
- 页面结构三件套:`index.vue`(页面) + `index.ts`(出口 barrel) + `cpns/`(独有组件)
|
||||||
|
- 组件统一 `<script setup lang="ts">`;props 用 `defineProps<Props>()` + `withDefaults`;emits 用 `defineEmits<{ 事件名: [参数类型] }>()`
|
||||||
|
- 组件事件 camelCase 命名;模板内不放复杂表达式;样式默认 `<style lang="less" scoped>`
|
||||||
|
- 单文件超 400 行即拆分到 `cpns/`
|
||||||
|
|
||||||
|
### 样式规范(less)
|
||||||
|
|
||||||
|
- 全项目使用 **less**,禁止新增 `.scss/.css` 业务样式
|
||||||
|
- 全局变量统一在 `styles/variables.less`;组件内禁止魔法色值
|
||||||
|
- vite `css.preprocessorOptions.less` 配置 `additionalData` 自动注入 variables
|
||||||
|
- 依赖:`pnpm add -D less`(vite 内置 less 编译支持)
|
||||||
|
|
||||||
|
### 工程化硬性约定
|
||||||
|
|
||||||
|
- **导入一律 `@/` 别名**;禁止跨目录相对路径(`../` 超过一层即违规)
|
||||||
|
- utils 与 composables 判据:**需要响应式状态或生命周期 → composables;否则 → utils**
|
||||||
|
- 类型归位:实体类型 → `types/business/<page>.d.ts`(与 api 同名);接口结构 → `types/api.d.ts`
|
||||||
|
- **禁止 `as any` / `@ts-ignore` / `@ts-expect-error`**
|
||||||
|
|
||||||
|
## 后续使用方式(工作流)
|
||||||
|
|
||||||
|
### 场景速查:想做什么 → 去找哪个文件
|
||||||
|
|
||||||
|
| 想做的事 | 动哪个文件 | 注意 |
|
||||||
|
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
|
||||||
|
| 给首页加一块新数据 | `api/home.ts` 加接口函数 → `stores/modules/home.ts` 加 action/state → `views/home/index.vue` 调 store action,结构放 `views/home/cpns/` 新组件 | 遵循数据流:页面 → store → api |
|
||||||
|
| 新增一个页面模块(如 news) | `types/business/news.d.ts` → `api/news.ts` → `stores/modules/news.ts` → `views/news/`(index.vue+index.ts+cpns) → `router/routes.ts` 注册 | 四者同名,走七步流程 |
|
||||||
|
| 页面要取数据 | 调对应 `use<Page>Store` 的 action(`storeToRefs` 取 state) | **禁止直接 import api** |
|
||||||
|
| 一个组件多个页面要用 | 放 `components/`(公共组件) | 从 `@/components` 导入 |
|
||||||
|
| 一个组件只有本页面用 | 放 `views/<page>/cpns/` | 禁止其他页面引用 |
|
||||||
|
| 改全站颜色/字体/间距 | `styles/variables.less` | 禁止组件内魔法值 |
|
||||||
|
| 图片懒加载 | 模板 `v-lazy`(`directives/lazy.ts`) | 勿手写滚动判断 |
|
||||||
|
| 通用逻辑(响应式) | `composables/` | 纯函数则 `utils/` |
|
||||||
|
| 弹窗/表单等小块 | 页面内 `cpns/` | 复用时上移 components/ |
|
||||||
|
| 路由新增页面 | `router/routes.ts` 注册 | component 指向 `@/views/<page>` |
|
||||||
|
|
||||||
|
### 新增页面模块七步流程(以"新闻 news"为例)
|
||||||
|
|
||||||
|
1. **类型**:`types/business/news.d.ts` 定义 `NewsItem / NewsQuery / PageResult`
|
||||||
|
2. **接口层**:`api/news.ts`(`getNewsList`、`getNewsDetail`,走 `request<T>`)
|
||||||
|
3. **数据层**:`stores/modules/news.ts`(`useNewsStore`,action 内调用 api 函数取数)
|
||||||
|
4. **页面**:`views/news/index.vue`(列表)+ `views/news/detail.vue`(详情)+ `views/news/index.ts`(出口 barrel)+ `views/news/cpns/`(独有组件)
|
||||||
|
5. **路由**:`router/routes.ts` 注册(`component: () => import('@/views/news')`,配 `meta.title`)
|
||||||
|
6. **复用检查**:被 ≥2 页面使用的组件上移 `components/`
|
||||||
|
7. **验证**:`pnpm build`(含 vue-tsc 类型检查)零错误
|
||||||
|
|
||||||
|
### 日常开发姿势
|
||||||
|
|
||||||
|
- **每次动手前**:按"第一步"读取目录,确认目标文件存在与现状,再决定新建还是修改。
|
||||||
|
- **新建文件前**:对照"src/ 每个文件的作用"确认归属(api/stores/views 四者同名)。
|
||||||
|
- **改数据相关代码**:先确认数据流方向——页面只碰 store,store 只碰 api,api 只发请求。
|
||||||
|
- **组件归属**:先自问"有几个页面用它"——1 个 → cpns/;≥2 个 → components/。
|
||||||
|
- **规格冲突时**:本 skill 为唯一现行规范;未覆盖场景 → 询问用户或按最贴近的既有模式执行后复述假设。
|
||||||
|
|
||||||
|
## 修改代码时的强制检查清单
|
||||||
|
|
||||||
|
修改任何已存在文件时,逐条核对(不满足即视为违反规范,必须先修正再继续):
|
||||||
|
|
||||||
|
- [ ] 文件是否位于其职责对应的目录(接口在 api/、数据在 stores/modules/、页面在 views/<page>/ 等)
|
||||||
|
- [ ] **页面是否直接 import 了 api 层?(违规——数据必须经 store)**
|
||||||
|
- [ ] **store 名 / api 文件名 / 页面目录名 / 类型文件名 是否与页面一致(可分层互查)**
|
||||||
|
- [ ] 导入是否使用 `@/` 别名
|
||||||
|
- [ ] 组件归属是否正确:≥2 页面共用 → components/;单页面独有 → views/<page>/cpns/
|
||||||
|
- [ ] 不在组件里直接 new axios 实例 / 直接调用原生 fetch
|
||||||
|
- [ ] 样式是否使用 **less**(`<style lang="less" scoped>` / \*.less),颜色是否取自 variables.less
|
||||||
|
- [ ] 类型是否齐全(无 `as any`、无 `@ts-ignore`)
|
||||||
|
- [ ] 新页面是否有完整三件套(index.vue + index.ts + cpns/)并在 router 注册
|
||||||
|
- [ ] 运行时 `pnpm build`(含 vue-tsc 类型检查)零错误
|
||||||
|
|
||||||
|
> 本规范为项目强约束:**新增与修改的代码必须严格遵循**。发现不符合结构约定的代码,应指出问题并要求按规范调整,而不是放任通过。
|
||||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
use-node-version=20.19.0
|
||||||
|
package-manager-strict=true
|
||||||
|
engine-strict=true
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>cz-web</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "cz-web",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0",
|
||||||
|
"pnpm": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc -b && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"preinstall": "npx only-allow pnpm"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.20.0",
|
||||||
|
"element-plus": "^2.14.5",
|
||||||
|
"pinia": "^4.0.3",
|
||||||
|
"vue": "^3.5.42",
|
||||||
|
"vue-router": "^4.6.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.13.3",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.8",
|
||||||
|
"@vue/tsconfig": "^0.9.1",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"vite": "^8.3.0",
|
||||||
|
"vue-tsc": "^3.3.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
1165
pnpm-lock.yaml
generated
Normal file
1165
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
public/favicon.svg
Normal file
1
public/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
24
public/icons.svg
Normal file
24
public/icons.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||||
|
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||||
|
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||||
|
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
5
src/App.vue
Normal file
5
src/App.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>hello world</h1>
|
||||||
|
</template>
|
||||||
BIN
src/assets/hero.png
Normal file
BIN
src/assets/hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
1
src/assets/vite.svg
Normal file
1
src/assets/vite.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
4
src/main.ts
Normal file
4
src/main.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
20
tsconfig.app.json
Normal file
20
tsconfig.app.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
|
||||||
|
/* Path alias (@ -> src) */
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
23
tsconfig.node.json
Normal file
23
tsconfig.node.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"module": "nodenext",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
14
vite.config.ts
Normal file
14
vite.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user