name, description, version
name
description
version
vue-project-structure
cz-web 项目( Vue 3 + TypeScript + Vite 网页端站点)的目录结构与编码规范强制 skill。当在本项目中新增、创建、移动、重构或修改任何 src/ 下的代码文件(.vue/.ts/.less) 、路由、store、API、组件、页面时, 必须加载本 skill, 先读取项目目录、再按本规范确定文件归属与写法。本项目是**网页端站点(非中后台)**: 页面按名称组织( home/news/product…) , 接口层在 api/、数据层在 Pinia、页面只经 store 取数,样式使用**less**。凡是涉及"新文件放哪"、"页面数据怎么拿"、"页面/公共组件归属"、"命名"、"功能对应哪些文件"的任务也必须使用本 skill。
2.0.0
cz-web 项目目录结构规范(强制 · 网页端)
完整版调试文档见 docs/vue-project-guide.md(其中中后台相关部分已废弃),本 skill 为唯一现行规范,以其为准 ;未覆盖场景参照 Vue 3 官方目录约定。
第一步:读取项目目录(每次加载必做)
动手写任何代码前,先用工具映射当前项目结构,确认正在操作的正确层级:
用 codegraph_files 或 glob 列出 src/ 下的现行目录结构(如某目录尚不存在,应在对应职责目录创建,而不是自创新顶层目录)。
读取目标文件及其同级文件, 确认现有命名风格与组织方式( 页面名、store 名、api 名必须一致)。
只有在目录树确认完毕后,才允许创建/修改文件。
核心原则
三层数据流(页面 → store → api) :页面组件只通过 Pinia store 获取数据; store 是数据层,负责调用 api 层 接口取数并维护状态; api 层只发请求。页面禁止直接 import api 。
按页面/页面名组织(一页一套) : views/<page>/、api/<page>.ts、stores/modules/<page>.ts、types/business/<page>.d.ts 四者同名对应,方便分层查看。
组件两级归属 : components/ 只放跨页面复用的公共组件 ; views/<page>/cpns/ 放该页面独有的组件 。
网页端(非中后台) :无侧边栏菜单/权限路由/TabsBar 体系;页面以业务(首页、新闻、产品、关于我们…)划分。
样式使用 less :全局样式为 .less 文件,组件内 <style lang="less" scoped>。
数据流架构(强约束)
铁律:
页面禁止 import api 层文件 ;获取数据必须调对应页面 store 的方法。
api 层是纯接口层 :只发请求、返回数据(带类型),不做业务状态处理、不缓存。
store 承担取数 + 数据状态 + 页面间共享 + 缓存 ;与页面同名的 store 责任边界即该页面。
调用链必须是 页面 → store → api 单向,禁止跳过 store 或反向依赖。
src/ 目录结构(强制划分)
命名一致性(可分层互查) :页面 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
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( 仅会员体系需要时)
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/
新闻页独有组件(筛选栏、列表项卡片)
页面独有组件的归属判定:只属于某一个页面 → 进该页面 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"为例)
类型 : types/business/news.d.ts 定义 NewsItem / NewsQuery / PageResult
接口层 : api/news.ts( getNewsList、getNewsDetail,走 request<T>)
数据层 : stores/modules/news.ts( useNewsStore, action 内调用 api 函数取数)
页面 : views/news/index.vue(列表)+ views/news/detail.vue(详情)+ views/news/index.ts(出口 barrel) + views/news/cpns/(独有组件)
路由 : router/routes.ts 注册(component: () => import('@/views/news'),配 meta.title)
复用检查 :被 ≥2 页面使用的组件上移 components/
验证 : pnpm build(含 vue-tsc 类型检查)零错误
日常开发姿势
每次动手前 :按"第一步"读取目录,确认目标文件存在与现状,再决定新建还是修改。
新建文件前 :对照"src/ 每个文件的作用"确认归属( api/stores/views 四者同名)。
改数据相关代码 :先确认数据流方向——页面只碰 store, store 只碰 api, api 只发请求。
组件归属 :先自问"有几个页面用它"——1 个 → cpns/; ≥2 个 → components/。
规格冲突时 :本 skill 为唯一现行规范;未覆盖场景 → 询问用户或按最贴近的既有模式执行后复述假设。
修改代码时的强制检查清单
修改任何已存在文件时,逐条核对(不满足即视为违反规范,必须先修正再继续):
本规范为项目强约束:新增与修改的代码必须严格遵循 。发现不符合结构约定的代码,应指出问题并要求按规范调整,而不是放任通过。