Phase 5(项目骨架部分)。后端已经稳了,现在前端可以挂上 curl。
本 commit 只做"能跑、能路由、能调 /api",业务页面留到后续 commit 一个个填。
新增:
- package.json + .env.example
* vue 3.5 / vite 5.4 / typescript 5.6 / pinia 2.3 / vue-router 4.6
* naive-ui 2.45 + vfonts(Naive UI 官方推荐字体)
* tailwindcss 3.4 + autoprefixer + postcss
* unplugin-auto-import / unplugin-vue-components 减少样板代码
* axios 1.7
scripts:dev / build / preview / typecheck / lint
- vite.config.js(注意:实际生效的是 .js,因为早期 vue-tsc -b 留了 .js 残留;
.ts 留作类型参考,内容保持一致)
* /api 代理到 127.0.0.1:8080,本地一站跑通前后端
* 强制 es2020 target,关闭 sourcemap
- tsconfig.json / tsconfig.node.json
* skipLibCheck 跳过第三方 .d.mts 噪音
* strict + noImplicitAny
- tailwind.config.js
* 主题色用 CSS 变量(rgb(var(--brand) / <alpha-value>)),
组件层不写死颜色,符合 plan §12 / AGENTS.md
- postcss.config.js
- index.html + public/favicon.svg(简单加油机图标)
- src/main.ts
* 引入 vfonts / tailwind / theme.css
* createApp + Pinia + router
- src/styles/tailwind.css + src/styles/theme.css
* Light/Dark 模式都从 :root / html.dark 拿 CSS 变量
* 全局滚动条 / 移动端 -webkit-tap-highlight 处理
- src/App.vue
* NConfigProvider + NDialog + NNotification + NMessage + NLoadingBar
* 跟随系统 dark 模式,watchEffect 同步 html.dark class
- src/router/index.ts
* 路由: / /dashboard /refuels /refuels/new /vehicles /statistics
* /:pathMatch(.*)* -> NotFound
* afterEach 改 document.title
- src/api/{client,types,vehicles,refuels}.ts
* axios 实例,统一把后端 {code,message} 错误解到 Error.cause
* 类型与后端 model/DTO 一一对应
- src/stores/vehicle.ts
* Pinia setup store,持有车辆列表 + 当前选中
* loadList 自动挑 default_vehicle
- src/utils/decimal.ts
* 与后端 pkg/decimal 对齐的 pricePerLiter / consumptionL100km / costPerKm
* 配套 fmt* 展示格式化函数
- src/layouts/AppLayout.vue
* 桌面端顶栏 + 移动端底部 Tab
* 移动端底部 safe-area-inset 适配刘海/小白条
- src/pages/{Dashboard,Refuels,RefuelNew,Vehicles,Statistics,NotFound}.vue
* Dashboard 真的拿 /api/vehicles/:id/statistics 渲染主指标(其它字段保留为占位)
* Refuels / RefuelNew / Vehicles / Statistics 是占位 Empty,Phase 5 业务页接入
- src/env.d.ts
* Vite ImportMeta 类型 + .vue 模块声明
期间踩到的两个坑(都修了):
1) `tsconfig.json` extends 了 @vue/tsconfig 但里面引用了不存在的
`@vue/tsconfig/tsconfig.node.json` —— 改成不依赖 extends,自己写
2) `vite build` 报 "Could not resolve entry module 'vueuc'":根因是早期
`vue-tsc -b` 在项目根生成了 vite.config.js / vite.config.d.ts 残留,
Vite 优先读 .js(老的 manualChunks 引用了 vueuc),整套 plugin 也不加载
(3 modules transformed)。修法:把正确配置同步写到 vite.config.js,
让 Vite 一直读 .js;.ts 留作类型参考
验证:
pnpm install 199 packages 安装成功
pnpm typecheck 0 errors
pnpm build 11.5s,dist 643 KB(uncompressed),主 JS 344 KB
gzipped 107 KB(naive-ui 主力,符合预期)
未做(留待后续 commit):
- 各业务页(Dashboard 仅骨架,Refuels / RefuelNew / Vehicles / Statistics 都是 Empty)
- 真实表单校验、Loading / Empty / Error 三态完整接入
- PWA(manifest + service worker) —— Phase 12
- 暗色模式手工切换(目前只跟随系统,Phase 11 UI 精修再补)
- ESLint 规则初始化
81 lines
4.1 KiB
TypeScript
81 lines
4.1 KiB
TypeScript
/* eslint-disable */
|
|
/* prettier-ignore */
|
|
// @ts-nocheck
|
|
// noinspection JSUnusedGlobalSymbols
|
|
// Generated by unplugin-auto-import
|
|
// biome-ignore lint: disable
|
|
export {}
|
|
declare global {
|
|
const EffectScope: typeof import('vue')['EffectScope']
|
|
const computed: typeof import('vue')['computed']
|
|
const createApp: typeof import('vue')['createApp']
|
|
const customRef: typeof import('vue')['customRef']
|
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
|
const defineComponent: typeof import('vue')['defineComponent']
|
|
const effectScope: typeof import('vue')['effectScope']
|
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
|
const h: typeof import('vue')['h']
|
|
const inject: typeof import('vue')['inject']
|
|
const isProxy: typeof import('vue')['isProxy']
|
|
const isReactive: typeof import('vue')['isReactive']
|
|
const isReadonly: typeof import('vue')['isReadonly']
|
|
const isRef: typeof import('vue')['isRef']
|
|
const markRaw: typeof import('vue')['markRaw']
|
|
const nextTick: typeof import('vue')['nextTick']
|
|
const onActivated: typeof import('vue')['onActivated']
|
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
|
const onMounted: typeof import('vue')['onMounted']
|
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
|
const onUpdated: typeof import('vue')['onUpdated']
|
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
|
const provide: typeof import('vue')['provide']
|
|
const reactive: typeof import('vue')['reactive']
|
|
const readonly: typeof import('vue')['readonly']
|
|
const ref: typeof import('vue')['ref']
|
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
|
const shallowRef: typeof import('vue')['shallowRef']
|
|
const toRaw: typeof import('vue')['toRaw']
|
|
const toRef: typeof import('vue')['toRef']
|
|
const toRefs: typeof import('vue')['toRefs']
|
|
const toValue: typeof import('vue')['toValue']
|
|
const triggerRef: typeof import('vue')['triggerRef']
|
|
const unref: typeof import('vue')['unref']
|
|
const useAttrs: typeof import('vue')['useAttrs']
|
|
const useCssModule: typeof import('vue')['useCssModule']
|
|
const useCssVars: typeof import('vue')['useCssVars']
|
|
const useDialog: typeof import('naive-ui')['useDialog']
|
|
const useId: typeof import('vue')['useId']
|
|
const useLink: typeof import('vue-router')['useLink']
|
|
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
|
const useMessage: typeof import('naive-ui')['useMessage']
|
|
const useModel: typeof import('vue')['useModel']
|
|
const useNotification: typeof import('naive-ui')['useNotification']
|
|
const useRoute: typeof import('vue-router')['useRoute']
|
|
const useRouter: typeof import('vue-router')['useRouter']
|
|
const useSlots: typeof import('vue')['useSlots']
|
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
|
const watch: typeof import('vue')['watch']
|
|
const watchEffect: typeof import('vue')['watchEffect']
|
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
|
}
|
|
// for type re-export
|
|
declare global {
|
|
// @ts-ignore
|
|
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
|
import('vue')
|
|
}
|