锁死 npm 版本号
npm config set save-prefix=''1. 创建项目
以下下令二选一
pnpm create vite@2.9.0 mangosteen-fe-1 -- --template vue-tsnpm create vite@2.9.0 mangosteen-fe-1 -- --template vue-ts然后进入项目,分别运行
pnpm run devpnpm run build运行 build 的时间报错
办理方法:在 tsconfig.json 里添加
{ "compilerOptions": { + "skipLibCheck": true, }}build path
把 HTML、CSS、JS 摆设到 GitHub 或服务器时必须设置 build path
设置规则见文档
在那里配
vite.config.js 里添加 base: '/' 或 '/reponame/' 等
run preview
- 运行目标
看看 dist 目次是否能正常运行
- 约莫等价于
pnpm i http-serverhttp-server -p 4173 dist2.摆设到 Github
1). 将我们的 dist 目次上传,然后把 dist 目次的路径添加到 vite.config.ts 的 base 字段里
export default defineConfig({ + base: '/bill-fe/dist/',})2). 重新运行
pnpm run build3). push
4). 删除长途的 dist 目次
将我们的 dist 加入到 ignore 里,然后运行
git rm -r --cached dist然后再重新 add commit push
3. template vs tsx
template 写法
<script setup lang="ts">import { ref } from 'vue';const count = ref(0)const onClick = () => { count.value += 1}</script>tsx 写法
1). 新建一个 .tsx 文件
import { defineComponent, ref } from 'vue';export const App = defineComponent({ setup() { const refCount = ref(0); const onClick = () => { refCount.value += 1; } // 这里必要返回一个函数 return () => ( <> <div> {refCount.value} </div> <div> <button onClick={onClick}>+1</button> </div> </> ) }})2). 安装 @vitejs/plugin-vue-jsx 插件
pnpm i -D @vitejs/plugin-vue-jsx3). 在 vite.config.ts 里设置 vueJsx
import vueJsx from '@vitejs/plugin-vue-jsx'export default defineConfig({ plugins: [+ vueJsx({ transformOn: true, mergeProps: true, }) ]})4. 引入 vue router 4
1). 安装
pnpm i vue-router@42). 利用
import { createApp } from 'vue'import { createRouter, createWebHashHistory } from 'vue-router';import {App} from './App';import { Bar } from './views/Bar';import { Foo } from './views/Foo';const routes = [ { path: '/', component: Foo }, { path: '/about', component: Bar }]const router = createRouter({ history: createWebHashHistory(), routes,})const app = createApp(App)app.use(router)app.mount('#app')
import { defineComponent } from 'vue';import { RouterView } from 'vue-router';export const App = defineComponent({ setup() { return () => ( <RouterView /> ) }})5. 利用 css module 和全局 css
利用 css module
1). 在当前目次下创建一个.module.scss 文件
2). 引入这个 css 文件通过变量名的情势
3). 通过 s.样式名来利用
.wrapper { color: red;}
import { defineComponent } from 'vue';import s from './Welcome.module.scss';export const Welcome = defineComponent({ setup: (props, context) => { return () => ( <div class={s.wrapper}> aaa </div> ) }});由于我们用的是 sass 以是必要利用 pnpm i sass
利用全局 css
1). 新建一个.css 文件
2). 直接通过 import './***.css' 引入
6. 利用 slot 插槽
import { defineComponent } from 'vue';import s from './First.module.scss';export const First = defineComponent({ setup: (props, {slots}) => { return () => ( <div class={s.wrapper}> <div class={s.card}> {slots.icon?.()} {slots.title?.()} </div> <div class={s.actions}> {slots.buttons?.()} </div> </div> ) }})
import { WelcomeLayout } from './WelcomeLayout';export const First = defineComponent({ setup: (props, context) => { const slots = { icon: () => <span>icon</span>, title: () => 'hi', buttons: () => <><button>+1</button></> } return () => ( <WelcomeLayout v-slots={slots} /> ) }})大概export const First = defineComponent({ setup: (props, context) => { return () => ( <WelcomeLayout> {{ icon: () => <span>icon</span>, title: () => 'hi', buttons: () => <><button>+1</button></> }} </WelcomeLayout> ) }})7. 利用多个 RouterView
router.tsx
{ path: '/welcome', component: Welcome, children: [ { path: '', redirect: '/welcome/1', }, { path: '1', components: { main: First, footer: FirstActions }, }, { path: '2', components: { main: Second, footer: SecondActions }, }, { path: '3', components: { main: Third, footer: ThirdActions }, }, { path: '4', components: { main: Forth, footer: ForthActions }, }, ] }
import { RouterView } from 'vue-router';export const Welcome = defineComponent({ setup: (props, context) => { return () => <div class={s.wrapper}> <header> <img src={logo} /> <h1>山竹记账</h1> </header> <main class={s.main}><RouterView name="main" /></main> <footer> <RouterView name="footer" /> </footer> </div> }})路由动画
<main class={s.main}> <RouterView name="main"> {({Component: Content, route: R}: { Component: VNode, route: RouteLocationNormalizedLoaded}) => ( <Transition enterFromClass={s.slide_fade_enter_from} enterActiveClass={s.slide_fade_enter_active} leaveToClass={s.slide_fade_leave_to} leaveActiveClass={s.slide_fade_leave_active} > {Content} </Transition> )} </RouterView> </main>.slide_fade_enter_active,.slide_fade_leave_active { position: absolute; left: 0; top: 0; width: 100%; height: 100%; transition: all 0.5s ease-out;}.slide_fade_enter_from { transform: translateX(100vw);}.slide_fade_leave_to { transform: translateX(-100vw);}8. 写一个svg vite 插件用来预加载所有的svg
标题:我们页面的svg在路由切换的时间有大概还没加载完成,会出现图片加载慢的标题
办理:
1). 安装 svgo 和 svgstore
pnpm i svgo svgstore2). 创建 vite_plugins/svgstore.js
/* eslint-disable */import path from 'path'import fs from 'fs'import store from 'svgstore' // 用于制作 SVG Spritesimport { optimize } from 'svgo' // 用于优化 SVG 文件export const svgstore = (options = {}) => { const inputFolder = options.inputFolder || 'src/assets/icons'; return { name: 'svgstore', // 分析 假如文件是 @svgstore 直接加载 svg_bundle.js // 引入的时间直接利用 import '@svgstore' resolveId(id) { if (id === '@svgstore') { return 'svg_bundle.js' } }, load(id) { if (id === 'svg_bundle.js') { // 创建一个大的 svg const sprites = store(options); const iconsDir = path.resolve(inputFolder); // 遍历所有的svg,然后把每一个都添加到这个大的里 for (const file of fs.readdirSync(iconsDir)) { const filepath = path.join(iconsDir, file); const svgid = path.parse(file).name let code = fs.readFileSync(filepath, { encoding: 'utf-8' }); sprites.add(svgid, code) } // 优化大的 svg const { data: code } = optimize(sprites.toString({ inline: options.inline }), { plugins: [ 'cleanupAttrs', 'removeDoctype', 'removeComments', 'removeTitle', 'removeDesc', 'removeEmptyAttrs', { name: "removeAttrs", params: { attrs: "(data-name|data-xxx)" } } ] }) // 把这个大的 svg 变成js文件 return `const div = document.createElement('div')div.innerHTML = \`${code}\`const svg = div.getElementsByTagName('svg')[0]if (svg) { svg.style.position = 'absolute' svg.style.width = 0 svg.style.height = 0 svg.style.overflow = 'hidden' svg.setAttribute("aria-hidden", "true")}// listen dom ready eventdocument.addEventListener('DOMContentLoaded', () => { if (document.body.firstChild) { document.body.insertBefore(div, document.body.firstChild) } else { document.body.appendChild(div) }})` } } }}3). 在 vite.config.ts 里注册这个设置
import { svgstore } from './src/vite_plugins/svgstore';export default defineConfig({ plugins: [ + svgstore(), ]})4). 在入口文件中引入我们的svgstore
import '@svgstore';5). 将我们的 <img> 标签换成 svg
<svg> <use xlinkHref='#chart'></use></svg>9. hooks
import { computed, onMounted, onUnmounted, ref, Ref } from "vue"type Point = { x: number; y: number;}export const useSwipe = (element: Ref<HTMLElement | null>) => { const start = ref<oint | null>(null) const end = ref<oint | null>(null) const swiping = ref(false) const distance = computed(() => { if (!start.value || !end.value) { return null } return { x: end.value.x - start.value.x, y: end.value.y - start.value.y, } }) const direction = computed(() => { if (!distance.value) { return '' } const { x, y } = distance.value if (Math.abs(x) > Math.abs(y)) { return x > 0 ? 'right' : 'left' } else { return y > 0 ? 'down' : 'up' } }) const onStart = (event: TouchEvent) => { swiping.value = true end.value = start.value = { x: event.touches[0].screenX, y: event.touches[0].screenY } } const onMove = (event: TouchEvent) => { if (!start.value) { return } end.value = { x: event.touches[0].screenX, y: event.touches[0].screenY, } } const onEnd = (event: TouchEvent) => { swiping.value = false } onMounted(() => { if (element.value) { element.value.addEventListener('touchstart', onStart) element.value.addEventListener('touchmove', onMove) element.value.addEventListener('touchend', onEnd) } }) onUnmounted(() => { if (element.value) { element.value.removeEventListener('touchstart', onStart) element.value.removeEventListener('touchmove', onMove) element.value.removeEventListener('touchend', onEnd) } }) return { swiping, direction, distance }}利用
export const Welcome = defineComponent({ setup: (props, context) => { const main = ref<HTMLElement | null>(null) const { direction, swiping } = useSwipe(main) return () => ( <main ref={main/> )}10. 自界说组件范例声明
// 方法1interface Props { onClick: (event: MouseEvent) => void; name: 'add' | 'chart';}export const Button = defineComponent<rops>({ setUp: (props, context) => { // 利用<rops> 这种方式只有内置的属性才气访问到 onClick 是内置的以是能访问到 console.log(props.onClick) // name 内部没有界说以是访问不到 console.log(props.name) }})// 方法2(获取我们本身界说的 props)export const Button = defineComponent({ props: { name: { // String 是js PropType内里是 ts type: String as PropType<'add' | 'chart'> } } setUp: (props, context) => { console.log(props.name) }})
const onClick = () => {}<Button onClick={onClick} name={'lifa'}>按钮</Button>11. 打包静态资源
假如我们必要引入图片资源有两种方式
1). 把图片资源放到 public 目次里,直接通过 public 目次下的路径引入
<img src="/images/logo.png" />如许我们打包后 dist 目次下就会多一个 images 文件内里有我们的 logo.png
2). 我们本身创建的目次,好比我在 src/assets/icons/logo.png
那么我们可以通过 import 语法
import logo from "@/assets/icons/logo.png";<img src={logo}如许打包后就会天生一个 asset/logo.chunk值.png
12. proxy
利用 proxy 就是 将你当地的 localhost:3000/api 署理到对应的后端域名,
以是肯定要保证我们是通过 localhost 来调这个接口的,假如利用axios的话,baseUrl 要写成 /
server: { // Listening on all local IPs cors: true, proxy: { "/api": { target: "http://f2e-sit.ccc.com", changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ""), }, }, },
如许我们调 localhost:3000/api 就会署理到 http://f2e-sit.ccc.com |