还有 vue-cli - 底层 webpack
还有 pinia
官网:https://vitejs.cn/
vue3 Vite官网:https://cn.vitejs.dev/
Vite 下一代的前端工具链 为开发提供极速响应
$ cnpm i vite -g
Vite(法语意为 “快速的”,发音 /vit/
,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:
Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。
使用 NPM:
$ npm create vite@latest
使用 Yarn: 如果电脑尚未安装yarn,可通过
cnpm i yarn -g
完成安装$ yarn create vite
使用 PNPM: 如果电脑尚未安装pnpm,可通过
cnpm i pnpm -g
完成安装$ pnpm create vite
然后按照提示操作即可!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Prq95Hvt-1673602369087)(assets/image-20220923090140128.png)]
vue脚手架
要使用 Vite 来创建一个 Vue 项目,非常简单
$ npm init vue@latest
这个命令会安装和执行 create-vue,它是 Vue 提供的官方脚手架工具。跟随命令行的提示继续操作即可。
√ Project name: ... mobile-vue-app # 输入项目名称
√ Add TypeScript? ... No / Yes # Yes 选择使用ts
√ Add JSX Support? ... No / Yes # Yes 选择使用jsx支持
√ Add Vue Router for Single Page Application development? ... No / Yes # Yes 选择使用vue路由
√ Add Pinia for state management? ... No / Yes # No 后期使用vuex作为状态管理器
√ Add Vitest for Unit Testing? ... No / Yes # No 不选择测试
√ Add Cypress for both Unit and End-to-End testing? ... No / Yes # No 不选择
√ Add ESLint for code quality? ... No / Yes # Yes 选择代码格式校验
√ Add Prettier for code formatting? ... No / Yes # Yes 选择Prettier作为代码格式规范
$ cd mobile-vue-app # 进入项目目录
$ npm i # 安装依赖 如果安装出错,建议可以考虑使用手机热点安装
$ npm run lint # 代码格式校验
$ npm run dev # 启动项目
如果同局域网内想要通过ip地址访问项目,可以修改 运行命令
// package.json { ..., "scripts": { "dev": "vite"// ----- "dev": "vite --host" // +++++ ..., }, .... }
|- mobile-vue-app # 项目名称
|- .vscode
|- node_modules # 项目依赖
|- public # 图标文件夹
favicon.ico # 网页图标
|- src # 写代码主场
|- assets # 资源文件
base.css # 基础样式
logo.svg # logo
main.css # 项目样式
|- components # 自定义组件
|- icons # 图标组件
HelloWorld.vue # 自定义组件
TheWelcome.vue # 自定义组件
WelcomeItem.vue # 自定义组件
|- router # 路由文件夹
index.ts # 路由的配置
|- views # 项目页面组件
AboutView.vue # 页面组件
HomeView.vue # 页面组件
App.vue # 项目根组件
main.ts # 项目入口文件
.eslintrc.cjs # 代码格式化规则
.gitignore # git上传忽略文件
.prettierrc.json # 右键格式化的时候,就会自动帮我们补全符号
.env.d.ts # 环境配置声明文件
index.html # 页面的模板
package.json # 项目依赖说明
README.md # 说明文档
tsconfig.config.json # ts配置文件说明
tsconfig.json # ts配置文件
vite.config.ts # vite配置文件
一个 Vue 单文件组件 (SFC),通常使用 *.vue
作为文件扩展名,它是一种使用了类似 HTML 语法的自定义文件格式,用于定义 Vue 组件。一个 Vue 单文件组件在语法上是兼容 HTML 的。
每一个 *.vue
文件都由三种顶层语言块构成:、
和
{{ msg }}
打开 main.ts 发现 引入App.vue 的地方画红线,说明项目中没有说明 .vue文件的声明文件
// src/env.d.ts
///
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
再次打开main.ts 发现,红线消失
复制项目src文件夹,然后保留App.vue以及main.ts的基本内容
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
选项式API
{{ msg }} --- {{ count }}
vue 组合式API
{{ msg }} --- {{ count }}
组合式API简写形式
{{ msg }} --- {{ count }}
说明:如果使用 js 作为脚本语言,是不需要引入
import { defineComponent } from 'vue'
的,组件中只需要 通过export defaut {}
作为组件的js逻辑即可
*.vue
文件最多可以包含一个顶层
块。@vue/compiler-dom
,预编译为 JavaScript 渲染函数,并附在导出的组件上作为其 render
选项。*.vue
文件最多可以包含一个
块。(使用
的情况除外)*.vue
文件可以包含多个
标签。
标签可以使用 scoped
或 module
attribute 来帮助封装当前组件的样式。使用了不同封装模式的多个
标签可以被混合入同一个组件。代码块可以使用 lang
这个 attribute 来声明预处理器语言,最常见的用例就是在 中使用 TypeScript:
<script lang="ts">
// use TypeScript
</script>
lang
在任意块上都能使用,比如我们可以在 标签中使用 SASS 或是
中使用 Pug:
p {{ msg }}
Vite 提供了对 .scss
, .sass
, .less
, .styl
和 .stylus
文件的内置支持。没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖:
# .scss and .sass
$ npm i -D sass
# .less
$ npm i -D less
# .styl and .stylus
$ npm i -D stylus
如果你更喜欢将 *.vue
组件分散到多个文件中,可以为一个语块使用 src
这个 attribute 来导入一个外部文件:
请注意 src
导入和 JS 模块导入遵循相同的路径解析规则,这意味着:
./
开头在每一个语块中你都可以按照相应语言 (HTML、CSS、JavaScript 和 Pug 等等) 的语法书写注释。对于顶层注释,请使用 HTML 的注释语法
选项式 API 中对 props 的类型推导需要用 defineComponent()
来包装组件。有了它,Vue 才可以通过 props
以及一些额外的选项,比如 required: true
和 default
来推导出 props 的类型:
import { defineComponent } from 'vue'
export default defineComponent({
// 启用了类型推导
props: {
name: String,
id: [Number, String],
msg: { type: String, required: true },
metadata: null
},
mounted() {
this.name // 类型:string | undefined
this.id // 类型:number | string | undefined
this.msg // 类型:string
this.metadata // 类型:any
}
})
然而,这种运行时 props
选项仅支持使用构造函数来作为一个 prop 的类型——没有办法指定多层级对象或函数签名之类的复杂类型。
我们可以使用 PropType
这个工具类型来标记更复杂的 props 类型
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
// 提供相对 `Object` 更确定的类型
type: Object as PropType<Book>,
required: true
},
// 也可以标记函数
callback: Function as PropType<(id: number) => void>
},
mounted() {
this.book.title // string
this.book.year // number
// TS Error: argument of type 'string' is not
// assignable to parameter of type 'number'
this.callback?('123')
}
})
案例如下:
子组件 - {{ user.userName }} - {{ user.age }} - {{ user.sex }}
我们可以给 emits
选项提供一个对象来声明组件所触发的事件,以及这些事件所期望的参数类型。试图触发未声明的事件会抛出一个类型错误:
import { defineComponent } from 'vue'
export default defineComponent({
emits: {
addBook(payload: { bookName: string }) {
// 执行运行时校验
return payload.bookName.length > 0
}
},
methods: {
onSubmit() {
this.$emit('addBook', {
bookName: 123 // 类型错误
})
this.$emit('non-declared-event') // 类型错误
}
}
})
案例如下:
子组件 - {{ user.userName }} - {{ user.age }} - {{ user.sex }}
计算属性会自动根据其返回值来推导其类型:
mport { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
greeting() {
return this.message + '!'
}
},
mounted() {
this.greeting // 类型:string
}
})
在某些场景中,你可能想要显式地标记出计算属性的类型以确保其实现是正确的:
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
// 显式标注返回类型
greeting(): string {
return this.message + '!'
},
// 标注一个可写的计算属性
greetingUppercased: {
get(): string {
return this.greeting.toUpperCase()
},
set(newValue: string) {
this.message = newValue.toUpperCase()
}
}
}
})
在某些 TypeScript 因循环引用而无法推导类型的情况下,可能必须进行显式的类型标注。
案例如下:
+
=
{{ fullName }}
在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型。
没有类型标注时,这个 event
参数会隐式地标注为 any
类型。这也会在 tsconfig.json
中配置了 "strict": true
或 "noImplicitAny": true
时抛出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,你可能需要显式地强制转换 event
上的属性:
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
}
})
案例如下:
某些插件会通过 app.config.globalProperties
为所有组件都安装全局可用的属性。举例来说,我们可能为了请求数据而安装了 this.$http
,或者为了国际化而安装了 this.$translate
。为了使 TypeScript 更好地支持这个行为,Vue 暴露了一个被设计为可以通过 TypeScript 模块扩展来扩展的 ComponentCustomProperties
接口:
import axios from 'axios'
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$translate: (key: string) => string
}
}
我们可以将这些类型扩展放在一个 .ts
文件,或是一个影响整个项目的 *.d.ts
文件中。无论哪一种,都应确保在 tsconfig.json
中包括了此文件。对于库或插件作者,这个文件应该在 package.json
的 types
属性中被列出。
为了利用模块扩展的优势,你需要确保将扩展的模块放在 TypeScript 模块 中。 也就是说,该文件需要包含至少一个顶级的 import
或 export
,即使它只是 export {}
。如果扩展被放在模块之外,它将覆盖原始类型,而不是扩展!
// 正常工作。
export {}
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
案例如下:
// src/main.ts
import { createApp } from 'vue'
// import axios from 'axios'
import App from './App.vue'
const app = createApp(App)
app.config.globalProperties.test = '测试'
app.mount('#app')
打印 this.test 时遇到 ts 的问题
某些插件,比如 vue-router
,提供了一些自定义的组件选项,比如 beforeRouteEnter
:
import { defineComponent } from 'vue'
export default defineComponent({
beforeRouteEnter(to, from, next) {
// ...
}
})
如果没有确切的类型标注,这个钩子函数的参数会隐式地标注为 any
类型。我们可以为 ComponentCustomOptions
接口扩展自定义的选项来支持:
import { Route } from 'vue-router'
declare module 'vue' {
interface ComponentCustomOptions {
beforeRouteEnter?(to: Route, from: Route, next: () => void): void
}
}
现在这个 beforeRouteEnter
选项会被准确地标注类型。注意这只是一个例子——像 vue-router
这种类型完备的库应该在它们自己的类型定义中自动执行这些扩展。
只保留src文件夹下的 main.ts 以及 App.vue组件,
// src/App.vue <script lang="ts"> </script> <template> <div>App.vue</div> </template> <style> </style>
// src/main.ts import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app')
$ cnpm i stylus -D
回顾移动端布局
弹性盒布局
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
rem布局
html {
font-size: 100px;
}
div {
/* height: 50px; */
height: 0.5rem;
}
? rem以及em布局的区别
VW/VH布局
html {
font-size: 100px;
/* 100 / 375 * 100 100 进行375等分,然后乘以好计算的100 */
/* iphone6 750 * 1334 375 * 667*/
/* iphne5 640 * 1136 320 * 568 100/320*100 = 31.25vw*/
/* font-size: 26.666667vw; */ /* iphone6 1rem=100px 其余机型自动适配*/
}
body {
/* 设计稿中的最小的字体大小 */
font-size: 12px;
}
div {
/* height: 50px; */
height: 0.5rem;
}
媒体查询
@media screen and (max-width: 300px) {
body {
background-color:lightblue;
}
}
@media only screen and (orientation: landscape) { // 横屏 portrait 竖屏
html {
font-size: 100px;
}
}
ts基础知识(类型注解、类型断言、!、?、interface、type、声明文件*.d.ts 。。。。。。)
https://www.bilibili.com/video/BV1H44y157gq/?spm_id_from=333.337.search-card.all.click
编写重置样式表如下:
// src/assets/main.styl
* {
padding: 0;
margin: 0;// src/assets/main.styl
// stylus语法考验写代码的规范
*
padding 0
margin 0
list-style none
html
height 100%
font-size 26.6666667vw
body
height 100%
font-size 14px
#app
height 100%
list-style: none;
text-decoration: none;
}
html, body, #app {
height: 100%;
}
项目入口文件代码如下:
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.styl'
const app = createApp(App)
app.mount('#app')
项目根组件如下:
header
content
运行项目,发现页面呈现上中下结构,这就是我们的移动端页面的主要布局了。
那么问题来了,假如用户把手机横屏,这个时候如果还是使用vw和rem布局的话就用容易出问题了。(通过点击模拟器旋转屏幕按钮查看)
可以设置横屏情况下不采用vw布局或者给用户展示一个提示信息
// src/assets/main.styl
// stylus语法考验写代码的规范
*
padding 0
margin 0
list-style none
html
height 100%
font-size 26.6666667vw
body
height 100%
font-size 14px
#app
height 100%
@media only screen and (orientation landscape) // 横屏
// 横屏状态下不要使用vw布局
html
font-size 100px
根组件这样写:
header
content
请将屏幕竖向浏览
现在审查元素 查看css 没有自动补全样式,如何通过配置自动补全css
$ cnpm i postcss postcss-preset-env -D
项目根目录下创建 postcss.config.js
// postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')
]
}
给页面的底部添加 user-select: none
,重启项目,审查元素看前后的变化
// 未配置 postcss 时,渲染为
.container .footer {
height: 0.5rem;
background-color: #efefef;
user-select: none;
}
// 配置过 postcss 时,渲染为
.container .footer {
height: 0.5rem;
background-color: #efefef;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
思考每个页面的头部和内容区域是根据用户的选择而一起改变的,那么可以创建以下四个基本页面
home header
home content
kind header
kind content
cart header
cart content
<template>
<div class="box">
<header class="header">user headerheader>
<div class="content">user contentdiv>
div>
template>
<script lang='ts'>
import { defineComponent } from 'vue';
export default defineComponent({})
script>
<style lang='stylus'>
style>
创建项目时,已经选择过使用vue-router,所以不需要安装
如果没有选择,那么使用以下语句安装
$ cnpm i vue-router@4 -S
然后再配置
一般情况下,会将路由设置到 src/router/index.ts
文件中
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router'
// 直接引入组件,不管你用不用这个页面,我都引入了 --- 静态导入
import Home from '@/views/home/index.vue'
// import Kind from '@/views/kind/index.vue'
// import Cart from '@/views/cart/index.vue'
// import User from '@/views/user/index.vue'
// 使用路由懒加载 - 访问该路由时才加载该组件 --- 动态导入
const Kind = () => import('@/views/kind/index.vue')
const Cart = () => import('@/views/cart/index.vue')
const User = () => import('@/views/user/index.vue')
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/kind',
name: 'kind',
component: Kind
},
{
path: '/cart',
name: 'cart',
component: Cart
},
{
path: '/user',
name: 'user',
component: User
}
]
const router: Router = createRouter({
history: createWebHistory(),
routes
})
export default router
调用路由以插件的形式
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router'
import './assets/main.styl'
const app = createApp(App)
app.use(router)
app.mount('#app')
vue-router
内置了
组件
router-view
将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局
请将屏幕竖向浏览
此时地址栏分别输入
http://127.0.0.1:5173/home
、http://127.0.0.1:5173/kind
、http://127.0.0.1:5173/cart
、http://127.0.0.1:5173/user
查看项目运行结果,
可以得知已经可以通过路由显示不同的页面
但是用户一般都是通过底部选项卡来切换页面的
在src
文件夹下创建components
文件夹,在components
文件夹下创建底部组件
因为底部选项卡需要字体图标,可以选择 iconfont阿里字体图标库,搜索图标,加入购物车,添加至项目
mobile-vue-app
,选择font-class
,点击查看在线链接
,拷贝css链接
项目根目录下index.html
中引入css链接
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite Apptitle>
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3665887_h3lsrioddkk.css">
head>
<body>
<div id="app">div>
<script type="module" src="/src/main.ts">script>
body>
html>
底部组件展示如下:
根组件引入底部组件
请将屏幕竖向浏览
声明式跳转 a href
编程式跳转 window.location.href = ‘’
vue-router
中提供了
组件来完成页面的声明式跳转。
===>
vue3 - vue-router4 移除了 tag 属性
会默认渲染为a标签,并且为了凸显出用户选择了哪一个选项,需要使用到vue-router
中router-link
组件的custom
、v-slot
的属性,且需要解构出isActive
、href
、navigate
等属性,具体代码如下
参考:https://router.vuejs.org/zh/api/#custom
请将屏幕竖向浏览
vue-router 3版本中 只需要通过一个tag属性即可生成目标标签。
使用vue实现移动端项目,最好的组件库是 vant,以前还有一个叫做 mint-ui
官网:https://vant-contrib.gitee.io/vant/#/zh-CN
轻量、可靠的移动端 Vue 组件库
安装
$ cnpm i vant -S
按需引入组件
$ cnpm i unplugin-vue-components -D
基于 vite
的项目,在 vite.config.js
文件中配置插件
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
Components({
resolvers: [VantResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
引入函数组件的样式
Vant 中有个别组件是以函数的形式提供的,包括 Toast
,Dialog
,Notify
和 ImagePreview
组件。在使用函数组件时,unplugin-vue-components
无法自动引入对应的样式,因此需要手动引入样式
你可以在项目的入口文件或公共模块中引入以上组件的样式,这样在业务代码中使用组件时,便不再需要重复引入样式了。
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router'
import 'vant/es/toast/style'
import 'vant/es/dialog/style'
import 'vant/es/notify/style'
import 'vant/es/image-preview/style'
import './assets/main.styl'
const app = createApp(App)
app.use(router)
app.mount('#app')
首页组件测试
home header
成功按钮
在vue/react项目中建议使用 axios
作为数据请求的方案
axios官网:http://www.axios-js.com/
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
$ cnpm i axios -S
执行 GET
请求
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 上面的请求也可以这样做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// axios
axios({
url: '/user?ID=12345',
method: 'GET',
}).then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 也可以这么写
axios({
url: '/user',
method: 'GET',
data: {
ID: '12345'
}
}).then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
执行post请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// axios
axios({
url: '/user',
method: 'POST',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
执行多个并发请求
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
}));
一般封装时,需要判断用户当前所处的环境:开发环境、测试环境、生产环境
开发环境:http://localhost:5173 开发阶段
生产环境: https://www.baidu.com 项目上线,被人通过 ip或者域名访问项目
process.env.NODE_ENV 判断环境。development production
如果封装过程中 出现
找不到名称“process”。是否需要为节点安装类型定义? 请尝试使用
npm i --save-dev @types/node,然后将 “node” 添加到类型字段。
$ cnpm i @types/node -D
修改
tsconfig.json
{ "extends": "@vue/tsconfig/tsconfig.web.json", "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, "types": [ // ++++++++ "node" ] }, "references": [ { "path": "./tsconfig.config.json" } ] }
完整封装代码如下: http://121.89.205.189:3000/apidoc/
// src/utils/request.ts
import axios from 'axios'
const isDev = process.env.NODE_ENV === 'development' // 真 - 开发环境,假-生产环境
// http://121.89.205.189:3000/apidoc/
// npm run dev 走?后的
// npm run build 走:后的
const ins = axios.create({
baseURL: isDev ? 'http://121.89.205.189:3000/api' : 'http://121.89.205.189:3000/api'
})
// 拦截器
ins.interceptors.request.use((config) => {
return config
}, (err) => {
return Promise.reject(err)
})
ins.interceptors.response.use((response) => {
return response
}, (err) => {
return Promise.reject(err)
})
export default ins
接口文档:http://121.89.205.189:3000/apidoc/
需要使用接口
遵循模块化开发思想,将数据请求方法统一封装
// src/api/home.ts
import request from '@/utils/request'
interface IPager {
count: number
limitNum: number
}
// 轮播图数据
export function getBannerList () {
return request.get('/banner/list')
}
// 秒杀列表数据
export function getSeckilllist (params?: IPager) {
return request.get('/pro/seckilllist', { params })
}
// 产品列表数据
export function getProList (params?: IPager) {
return request.get('/pro/list', { params })
}
构建轮播图组件
轮播图
首页注册引入组件
home header
首页请求数据以及传递数据
// src/views/home/home.d.ts
export interface IBanner {
bannerid: string
alt: string
link: string
flag: boolean
img: string
}
home header
渲染轮播图组件
构建nav导航组件
nav
首页引入以及注册nav导航组件
home header
首页准备nav导航数据并且传递
// src/views/home/home.d.ts
export interface IBanner {
bannerid: string
alt: string
link: string
flag: boolean
img: string
}
export interface INav {
navid: number
title: string
imgurl: string
}
home header
nav导航组件的渲染
构建秒杀列表组件
秒杀
首页组件引入以及注册秒杀列表组件
home header
首页请求秒杀数据以及传递
// src/views/home/home.d.ts
export interface IBanner {
bannerid: string
alt: string
link: string
flag: boolean
img: string
}
export interface INav {
navid: number
title: string
imgurl: string
}
export interface IPro {
banners: string[]
brand: string
category: string
desc: string
discount: number
img1: string
img2: string
img3: string
img4: string
isrecommend: number
issale: number
isseckill: number
originprice: number
proid: string
proname: string
sales: number
stock: number
}
home header
秒杀列表组件渲染
嗨购秒杀
-
¥{{ item.originprice }}
作业:实现倒计时
构建产品列表组件
产品列表
注册组件以及使用产品列表组件
home header
请求列表组件的数据以及传递
home header
渲染列表组件
-
{{ item.proname }}
¥{{ item.originprice }}
{{ item.category }}
因为列表的数据展示 内容超过了容器的高度,此时将 App.vue 中的box 以及 content中设置 overflow:auto
List 组件通过 loading
和 finished
两个变量控制加载状态,当组件滚动到底部时,会触发 load
事件并将 loading
设置成 true
。此时可以发起异步操作并更新数据,数据更新完毕后,将 loading
设置成 false
即可。若数据已全部加载完毕,则直接将 finished
设置成 true
即可。
隐藏条件 ,每页页码
home header
下拉刷新时会触发 refresh
事件,在事件的回调函数中可以进行同步或异步操作,操作完成后将 v-model
设置为 false
,表示加载完成。
home header
分析清除到底是哪一个容器产生了滚动条
分析得知 content 容器产生了滚动条,可以给它绑定一个 scroll 事件用于判断 回到顶部按钮显示还是不显示
通过 content 的dom的scrollTop 属性可以设置滚动条距离
当使用vantui组件库4版本时使用如下代码
home header
如果使用4版本以前,参考如下代码
home header
自定义头部组件
header
注册及使用组件
完善头部组件
- 太原
-
|
游戏主机
- 登录
// src/views/home/components/index.ts
// import Banner from './Banner.vue'
// import Nav from './Nav.vue'
// import Seckill from './Seckill.vue'
// import Pro from './Pro.vue'
// import Header from './Header.vue'
// export {
// Banner,
// Nav,
// Seckill,
// Pro,
// Header
// }
export { default as Banner } from './Banner.vue'
export { default as Nav } from './Nav.vue'
export { default as Seckill } from './Seckill.vue'
export { default as Pro } from './Pro.vue'
export { default as Header } from './Header.vue'
/detail/1 ======> /detail/:proid =====> this.$route.params.proid ====> params
/detail?proid=1 ======> /detail =====> this.$route.query.proid ====> query
vue中推荐大家使用 params 形式
构建详情页面组件
detail header
detail content
添加详情路由
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router'
// 直接引入组件,不管你用不用这个页面,我都引入了 --- 静态导入
import Home from '@/views/home/index.vue'
// import Kind from '@/views/kind/index.vue'
// import Cart from '@/views/cart/index.vue'
// import User from '@/views/user/index.vue'
// 使用路由懒加载 - 访问该路由时才加载该组件 --- 动态导入
const Kind = () => import('@/views/kind/index.vue')
const Cart = () => import('@/views/cart/index.vue')
const User = () => import('@/views/user/index.vue')
const Detail = () => import('@/views/detail/index.vue')
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/kind',
name: 'kind',
component: Kind
},
{
path: '/cart',
name: 'cart',
component: Cart
},
{
path: '/user',
name: 'user',
component: User
},
{
path: '/detail/:proid',
name: 'detail',
component: Detail
}
]
const router: Router = createRouter({
history: createWebHistory(),
routes
})
export default router
地址栏输入 http://localhost:5173/detail/100
但是此时发现,详情页面底部应该隐藏原来的底部的选项卡
以上案例中 一个路由控制了一个区域的组件,那么可以一个路由控制多个区域的变化吗?
命名视图:https://router.vuejs.org/zh/guide/essentials/named-views.html
{ path: '', name: '', components: { default: '', footer: '' } }
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router'
// 直接引入组件,不管你用不用这个页面,我都引入了 --- 静态导入
import Home from '@/views/home/index.vue'
import Footer from '@/components/Footer.vue'
// import Kind from '@/views/kind/index.vue'
// import Cart from '@/views/cart/index.vue'
// import User from '@/views/user/index.vue'
// 使用路由懒加载 - 访问该路由时才加载该组件 --- 动态导入
const Kind = () => import('@/views/kind/index.vue')
const Cart = () => import('@/views/cart/index.vue')
const User = () => import('@/views/user/index.vue')
const Detail = () => import('@/views/detail/index.vue')
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
// component: Home
components: {
default: Home,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: Kind
components: {
default: Kind,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: Cart
components: {
default: Cart,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: User
components: {
default: User,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
component: Detail
}
]
const router: Router = createRouter({
history: createWebHistory(),
routes
})
export default router
请将屏幕竖向浏览
点击秒杀列表声明式跳转至详情
/detail/${1} >
嗨购秒杀
-
¥{{ item.originprice }}
点击产品列表编程式跳转至详情
window.location.href=""
this.$router.push('/detail/1')
this.$router.push({ name: 'detail', params: { proid: 1}})
this.$router.push({ path: '/detail/1'})
-
{{ item.proname }}
¥{{ item.originprice }}
{{ item.category }}
detail header
detail content
// src/api/detail.ts
import request from '@/utils/request'
interface IPager {
count: number
limitNum?: number
}
export function getProDetail (proid: string) {
return request.get('/pro/detail/' + proid)
}
// 详情 猜你喜欢 - 推荐
export function getRecommendList (params?: IPager) {
return request.get('/pro/recommendlist', { params })
}
detail header
detail content
轮播图组件
{{ current + 1 }} / {{ total }}
详情页导入轮播图
detail header
获取视频时长: dom.duration
获取当前视频播放进度: dom.currentTime
修改当前视频播放进度: dom.currentTime = num
播放视频: dom.play()
暂停视频: dom.pause()
调整音量:dom.volume = 0-1
全屏: dom.requestFullScreen() ---- 需要设置video的宽和高也为全屏
静音: dom.muted = true
产品信息
// src/views/detail/components/index.ts
export { default as Banner } from './Banner.vue'
export { default as ProInfo } from './ProInfo.vue'
detail header
¥{{ originprice }}
销量:{{ sales }}
{{ brand }}
{{ category }}
{{ proname }}
{{ desc }}
拷贝了首页的产品列表组件,同时要保证点击推荐列表修改详情页的数据(通过子组件给父组件传值)
-
{{ item.proname }}
¥{{ item.originprice }}
{{ item.category }}
// src/views/detail/components/index.ts
export { default as Banner } from './Banner.vue'
export { default as ProInfo } from './ProInfo.vue'
export { default as Pro } from './Pro.vue'
detail header
猜你喜欢
直接在父组件监听路由的变化实现
detail header 猜你喜欢
一旦轮播图滑动,切换商品后 轮播图组件的属性 current 没有重置
{{ current + 1 }} / {{ total }}
detail header
猜你喜欢
-
-
- 商品
- 推荐
猜你喜欢
本操作属于纯前端操作
1.进入详情页,需要先判断该商品是否被收藏过,如果收藏过,显示已收藏,如果未被收藏过,显示收藏
2.如果当前商品是收藏的,点击要取消收藏
3.如果当前商品是未收藏的,点击收藏
4.数据可以存到本地,localStorage 只能保存 字符串
5.收藏夹内只保存 产品id,以数组的形式存在
6.检测到路由变化 ,执行判断是否收藏
-
-
-
-
详情
推荐
-
加入购物车(是谁,加了几件,加了哪个商品进入购物车)
是谁
登录
注册
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRZak2KJ-1673602369091)(assets/image-20220923073954061.png)]
一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:
/user/johnny/profile /user/johnny/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
通过 Vue Router,你可以使用嵌套路由配置来表达这种关系。
/register 注册路由
/register/index 注册第一步
/register/sms 注册第二步
/register/pwd 注册第三步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWfKNJk3-1673602369092)(assets/image-20220927101429498.png)]
校验手机号
发送短信验证码
设置密码
// src/views/register/components/index.ts
export { default as CheckTel } from './CheckTel.vue'
export { default as SendTelCode } from './SendTelCode.vue'
export { default as SetPassword } from './SetPassword.vue'
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router'
// 直接引入组件,不管你用不用这个页面,我都引入了 --- 静态导入
import Home from '@/views/home/index.vue'
import Footer from '@/components/Footer.vue'
import { CheckTel, SendTelCode, SetPassword } from '@/views/register/components'
// import Kind from '@/views/kind/index.vue'
// import Cart from '@/views/cart/index.vue'
// import User from '@/views/user/index.vue'
// 使用路由懒加载 - 访问该路由时才加载该组件 --- 动态导入
const Kind = () => import('@/views/kind/index.vue')
const Cart = () => import('@/views/cart/index.vue')
const User = () => import('@/views/user/index.vue')
const Detail = () => import('@/views/detail/index.vue')
const Vdo = () => import('@/views/video/index.vue')
const Register = () => import('@/views/register/index.vue')
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
// component: Home
components: {
default: Home,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: Kind
components: {
default: Kind,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: Cart
components: {
default: Cart,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: User
components: {
default: User,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
// component: Detail
components: { // 与 component: Detail 等价
default: Detail
}
},
{
path: '/vdo',
name: 'vdo',
components: {
default: Vdo
}
},
{
path: '/register',
name: 'register',
redirect: '/register/index',
component: Register,
children: [
{
path: 'index', // /register/index
component: CheckTel
},
{
path: 'sms', // /register/sms
component: SendTelCode
},
{
path: 'pwd', // /register/pwd
component: SetPassword
}
]
}
]
const router: Router = createRouter({
history: createWebHistory(),
routes
})
export default router
下一步
获取验证码
下一步
密码由6-20位大小写字母数字等组成
完成
// src/api/user.ts
import request from '@/utils/request'
// 检测手机号是否被注册过
export function doCheckPhone (params: { tel: string }) {
return request.post('/user/docheckphone', params)
}
// 发送短信验证码
export function doSendMsgCode (params: { tel: string }) {
return request.post('/user/dosendmsgcode', params)
}
// 验证验证码
export function doCheckCode (params: { tel: string, telcode: string }) {
return request.post('/user/docheckcode', params)
}
// 设置密码完成注册
export function doFinishRegister (params: { tel: string, password: string }) {
return request.post('/user/dofinishregister', params)
}
// 登录
export function doLogin (params: { loginname: string, password: string }) {
return request.post('/user/login', params)
}
下一步
发送短信验证码实际上没有发送至手机,请至控制台查看验证码
{{ text }}
下一步
密码由6-20位大小写字母数字等组成
完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWhS9FoA-1673602369093)(assets/image-20220923074218453.png)]
登录
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router'
// 直接引入组件,不管你用不用这个页面,我都引入了 --- 静态导入
import Home from '@/views/home/index.vue'
import Footer from '@/components/Footer.vue'
import { CheckTel, SendTelCode, SetPassword } from '@/views/register/components'
// import Kind from '@/views/kind/index.vue'
// import Cart from '@/views/cart/index.vue'
// import User from '@/views/user/index.vue'
// 使用路由懒加载 - 访问该路由时才加载该组件 --- 动态导入
const Kind = () => import('@/views/kind/index.vue')
const Cart = () => import('@/views/cart/index.vue')
const User = () => import('@/views/user/index.vue')
const Detail = () => import('@/views/detail/index.vue')
const Vdo = () => import('@/views/video/index.vue')
const Register = () => import('@/views/register/index.vue')
const Login = () => import('@/views/login/index.vue')
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
// component: Home
components: {
default: Home,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: Kind
components: {
default: Kind,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: Cart
components: {
default: Cart,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: User
components: {
default: User,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
// component: Detail
components: { // 与 component: Detail 等价
default: Detail
}
},
{
path: '/vdo',
name: 'vdo',
components: {
default: Vdo
}
},
{
path: '/register',
name: 'register',
redirect: '/register/index',
component: Register,
children: [
{
path: 'index', // /register/index
component: CheckTel
},
{
path: 'sms', // /register/sms
component: SendTelCode
},
{
path: 'pwd', // /register/pwd
component: SetPassword
}
]
},
{
path: '/login',
name: 'login',
component: Login
},
]
const router: Router = createRouter({
history: createWebHistory(),
routes
})
export default router
登录
手机号立即注册
前端校验用户登录状态,如果登录,调用后端接口加入购物车,如果前端校验未登录,需要跳转到登录页面
后端校验未登录也需要跳转到登录页面
// src/utils/request.ts
import axios from 'axios'
import router from '@/router'
const isDev = process.env.NODE_ENV === 'development' // 真 - 开发环境,假-生产环境
// http://121.89.205.189:3000/apidoc/
// npm run dev 走?后的
// npm run build 走:后的
const ins = axios.create({
baseURL: isDev ? 'http://121.89.205.189:3000/api' : 'http://121.89.205.189:3000/api'
})
// 拦截器
ins.interceptors.request.use((config: any) => {
(config.headers!.token as string) = localStorage.getItem('token')! // +++++
return config
}, (err) => {
return Promise.reject(err)
})
ins.interceptors.response.use((response: any) => {
// 判断登录标识 是否有效,如果无效跳转至登录页面,如果有效,不做操作
if (response.data.code === '10119') {
// 没有传递token 或者 token过期
// 跳转到登录页面 重新登录
router.push('/login')
return null
} else {
return response
}
}, (err) => {
return Promise.reject(err)
})
export default ins
// src/api/cart.ts
import request from '@/utils/request'
// 加入购物车
export function addCart (params: { userid: string, proid: string, num: number }) {
return request.post('/cart/add', params)
}
// 获取购物车列表数据
export function getCartListData (params: { userid: string }) {
return request.post('/cart/list', params)
}
// 删除某个用户的购物车的所有数据
export function removeAllData (params: { userid: string }) {
return request.post('/cart/removeall', params)
}
// 删除某个用户的一条购物车的数据
export function removeOneData (params: { cartid: string }) {
return request.post('/cart/remove', params)
}
// 更新某个用户的一条购物车的数据的选中状态
export function selectOneData (params: { cartid: string, flag: boolean }) {
return request.post('/cart/selectone', params)
}
// 更新某个用户的购物车的所有数据的选中状态
export function selectAllData (params: { userid: string, type: boolean }) {
return request.post('/cart/selectall', params)
}
// 更新某个用户的购物车的某个产品的数量
export function updateOneDataNum (params: { cartid: string, num: number }) {
return request.post('/cart/updatenum', params)
}
// 推荐商品接口
export function getCartRecommendData () {
return request.get('/pro/recommendlist')
}
-
-
- 商品
- 推荐
猜你喜欢
立即购物
全选
// src/views/cart/cart.d.ts
export interface ICartItem {
cartid: string
discount: number
flag: boolean
img1: string
num: number
originprice: number
proid: string
proname: string
userid: string
}
立即购物
全选
异步删除 - 全局变量传值
立即购物
全选
使用插槽自定义数量位置,使用步进器组件完成数量更改,传参传对象
立即购物
全选
总数位0 按钮不可点
立即购物
全选
实际上总价以及总数需要选中才计算
以 layout组件 以及checkbox 组件完成 基本布局
立即购物
全选
默认全选不选中,通过列表的数据控制全选的效果
立即购物
全选
点击全选控制列表选中状态
立即购物
全选
单个列表选中
立即购物
全选
-
{{ item.proname }}
¥{{ item.originprice }}
{{ item.category }}
// src/views/cart/cart.d.ts
export interface ICart {
cartid: string
discount: number
flag: boolean
img1: string
num: number
originprice: number
proid: string
proname: string
userid: string
}
export interface IPro {
banners: string[]
brand: string
category: string
desc: string
discount: number
img1: string
img2: string
img3: string
img4: string
isrecommend: number
issale: number
isseckill: number
originprice: number
proid: string
proname: string
sales: number
stock: number
}
立即购物
可能你喜欢
猜你还想要
全选
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3GGGsXA4-1673602369095)(assets/未命名文件.png)]
// src/api/order.ts
import request from '../service/request'
// 添加订单
export function addOrderData (params: { userid: string }) {
return request.post('/order/addOrder', params)
}
// 获取订单信息
export function getOrderListData (params: { userid: string, time: string }) {
return request.get('/order/confirmOrder', {params})
}
export interface IOrderAddress {
userid: string
time: string
name: string
tel: string
province: string
city: string
county: string
addressDetail: string
}
// 获取订单信息
export function updateOrderAddressData (params: IOrderAddress) {
return request.post('/order/updateOrderAddress', params)
}
立即购物
快点来看看
可能你想要
全选
order content
// src/router/index.ts
// 1.引入 创建路由的模块 以及 路由历史记录模式的模块
// 路由的历史模式:
// createWebHistory HTML5模式 /home /kind /cart /user 需要后端配合
// createWebHashHistory Hash 模式 /#/home /#/kind /#/cart /#/user
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router' // 路由规则的类型 ---- 路由规则的类型注解
// 2.引入页面组件
import Footer from '../components/FooterComponent.vue'
import HomeView from './../views/home/IndexView.vue'
import KindView from './../views/kind/IndexView.vue'
import CartView from './../views/cart/IndexView.vue'
import UserView from './../views/user/IndexView.vue'
import DetailView from '../views/detail/IndexView.vue'
import RegisterView from '../views/register/IndexView.vue'
import CheckTelComponent from '../views/register/components/CheckTelComponent.vue'
import SendTelCodeComponent from '../views/register/components/SendTelCodeComponent.vue'
import SetPasswordComponent from '../views/register/components/SetPasswordComponent.vue'
import LoginView from '../views/login/IndexView.vue'
import OrderView from '../views/order/IndexView.vue' // ++++++++++
// 3.构建路由的规则
const routes: RouteRecordRaw[] = [
{ // 路由的重定向
path: '/',
redirect: '/home'
},
{
path: '/home', // 地址栏地址 - 路由
name: 'home', // 命名路由 --- 唯一性
// component: HomeView // 路由映射的页面组件
components: {
default: HomeView,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: KindView
components: {
default: KindView,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: CartView
components: {
default: CartView,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: UserView
components: {
default: UserView,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
// component: DetailView
components: {
default: DetailView
}
},
{
path: '/register',
name: 'register',
redirect: '/register/index',
component: RegisterView,
children: [
{
path: 'index',
component: CheckTelComponent
},
{
path: 'sms',
component: SendTelCodeComponent
},
{
path: 'pwd',
component: SetPasswordComponent
}
]
},
{
path: '/login',
name: 'login',
component: LoginView
},
{ // ++++++++++
path: '/order',
name: 'order',
component: OrderView
}
]
// 4.生成路由
const router: Router = createRouter({
// vue2 vue-router3 history: 'history' | 'hash'
history: createWebHistory(),
routes // routes: routes 简写形式
})
// 5.暴露路由
export default router
原价:{{ totalPrice }}
快递费:{{ express }}
点击进入地址选择页面
// src/router/index.ts
// 1.引入 创建路由的模块 以及 路由历史记录模式的模块
// 路由的历史模式:
// createWebHistory HTML5模式 /home /kind /cart /user 需要后端配合
// createWebHashHistory Hash 模式 /#/home /#/kind /#/cart /#/user
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router' // 路由规则的类型 ---- 路由规则的类型注解
// 2.引入页面组件
import Footer from '../components/FooterComponent.vue'
import HomeView from './../views/home/IndexView.vue'
import KindView from './../views/kind/IndexView.vue'
import CartView from './../views/cart/IndexView.vue'
import UserView from './../views/user/IndexView.vue'
import DetailView from '../views/detail/IndexView.vue'
import RegisterView from '../views/register/IndexView.vue'
import CheckTelComponent from '../views/register/components/CheckTelComponent.vue'
import SendTelCodeComponent from '../views/register/components/SendTelCodeComponent.vue'
import SetPasswordComponent from '../views/register/components/SetPasswordComponent.vue'
import LoginView from '../views/login/IndexView.vue'
import OrderView from '../views/order/IndexView.vue'
import OrderAddressListView from '../views/order/AddressListView.vue'
// 3.构建路由的规则
const routes: RouteRecordRaw[] = [
{ // 路由的重定向
path: '/',
redirect: '/home'
},
{
path: '/home', // 地址栏地址 - 路由
name: 'home', // 命名路由 --- 唯一性
// component: HomeView // 路由映射的页面组件
components: {
default: HomeView,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: KindView
components: {
default: KindView,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: CartView
components: {
default: CartView,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: UserView
components: {
default: UserView,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
// component: DetailView
components: {
default: DetailView
}
},
{
path: '/register',
name: 'register',
redirect: '/register/index',
component: RegisterView,
children: [
{
path: 'index',
component: CheckTelComponent
},
{
path: 'sms',
component: SendTelCodeComponent
},
{
path: 'pwd',
component: SetPasswordComponent
}
]
},
{
path: '/login',
name: 'login',
component: LoginView
},
{
path: '/order',
name: 'order',
component: OrderView
},
{ // ++++++++
path: '/orderAddress',
name: 'orderAddress',
component: OrderAddressListView
}
]
// 4.生成路由
const router: Router = createRouter({
// vue2 vue-router3 history: 'history' | 'hash'
history: createWebHistory(),
routes // routes: routes 简写形式
})
// 5.暴露路由
export default router
点击进入地址列表页面
原价:{{ totalPrice }}
快递费:{{ express }}
cnpm i -S @vant/area-data
// src/router/index.ts
// 1.引入 创建路由的模块 以及 路由历史记录模式的模块
// 路由的历史模式:
// createWebHistory HTML5模式 /home /kind /cart /user 需要后端配合
// createWebHashHistory Hash 模式 /#/home /#/kind /#/cart /#/user
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router' // 路由规则的类型 ---- 路由规则的类型注解
// 2.引入页面组件
import Footer from '../components/FooterComponent.vue'
import HomeView from './../views/home/IndexView.vue'
import KindView from './../views/kind/IndexView.vue'
import CartView from './../views/cart/IndexView.vue'
import UserView from './../views/user/IndexView.vue'
import DetailView from '../views/detail/IndexView.vue'
import RegisterView from '../views/register/IndexView.vue'
import CheckTelComponent from '../views/register/components/CheckTelComponent.vue'
import SendTelCodeComponent from '../views/register/components/SendTelCodeComponent.vue'
import SetPasswordComponent from '../views/register/components/SetPasswordComponent.vue'
import LoginView from '../views/login/IndexView.vue'
import OrderView from '../views/order/IndexView.vue'
import OrderAddressListView from '../views/order/AddressListView.vue'
import OrderAddAddressView from '../views/order/AddAddressView.vue'
// 3.构建路由的规则
const routes: RouteRecordRaw[] = [
{ // 路由的重定向
path: '/',
redirect: '/home'
},
{
path: '/home', // 地址栏地址 - 路由
name: 'home', // 命名路由 --- 唯一性
// component: HomeView // 路由映射的页面组件
components: {
default: HomeView,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: KindView
components: {
default: KindView,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: CartView
components: {
default: CartView,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: UserView
components: {
default: UserView,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
// component: DetailView
components: {
default: DetailView
}
},
{
path: '/register',
name: 'register',
redirect: '/register/index',
component: RegisterView,
children: [
{
path: 'index',
component: CheckTelComponent
},
{
path: 'sms',
component: SendTelCodeComponent
},
{
path: 'pwd',
component: SetPasswordComponent
}
]
},
{
path: '/login',
name: 'login',
component: LoginView
},
{
path: '/order',
name: 'order',
component: OrderView
},
{
path: '/orderAddress',
name: 'orderAddress',
component: OrderAddressListView
},
{ // +++++++++
path: '/orderAddAddress',
name: 'orderAddAddress',
component: OrderAddAddressView
}
]
// 4.生成路由
const router: Router = createRouter({
// vue2 vue-router3 history: 'history' | 'hash'
history: createWebHistory(),
routes // routes: routes 简写形式
})
// 5.暴露路由
export default router
添加并使用该地址(添加i地址进入地址列表页面,修改订单的地址)
// src/api/address.ts
import request from '../service/request'
export interface IAddress {
userid: string
name: string
tel: string
province: string
city: string
county: string
addressDetail: string
isDefault: boolean
}
export function addAddRessData (params: IAddress ) {
return request.post('/address/add', params)
}
export function getAddressList (params: {userid: string} ) {
return request.get('/address/add', {params})
}
计算属性即可完成
原价:{{ totalPrice }}
快递费:{{ express }}
cnpm i -S @vant/area-data
// src/router/index.ts
// 1.引入 创建路由的模块 以及 路由历史记录模式的模块
// 路由的历史模式:
// createWebHistory HTML5模式 /home /kind /cart /user 需要后端配合
// createWebHashHistory Hash 模式 /#/home /#/kind /#/cart /#/user
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router' // 路由规则的类型 ---- 路由规则的类型注解
// 2.引入页面组件
import Footer from '../components/FooterComponent.vue'
import HomeView from './../views/home/IndexView.vue'
import KindView from './../views/kind/IndexView.vue'
import CartView from './../views/cart/IndexView.vue'
import UserView from './../views/user/IndexView.vue'
import DetailView from '../views/detail/IndexView.vue'
import RegisterView from '../views/register/IndexView.vue'
import CheckTelComponent from '../views/register/components/CheckTelComponent.vue'
import SendTelCodeComponent from '../views/register/components/SendTelCodeComponent.vue'
import SetPasswordComponent from '../views/register/components/SetPasswordComponent.vue'
import LoginView from '../views/login/IndexView.vue'
import OrderView from '../views/order/IndexView.vue'
import OrderAddressListView from '../views/order/AddressListView.vue'
import OrderAddAddressView from '../views/order/AddAddressView.vue'
// 3.构建路由的规则
const routes: RouteRecordRaw[] = [
{ // 路由的重定向
path: '/',
redirect: '/home'
},
{
path: '/home', // 地址栏地址 - 路由
name: 'home', // 命名路由 --- 唯一性
// component: HomeView // 路由映射的页面组件
components: {
default: HomeView,
footer: Footer
}
},
{
path: '/kind',
name: 'kind',
// component: KindView
components: {
default: KindView,
footer: Footer
}
},
{
path: '/cart',
name: 'cart',
// component: CartView
components: {
default: CartView,
footer: Footer
}
},
{
path: '/user',
name: 'user',
// component: UserView
components: {
default: UserView,
footer: Footer
}
},
{
path: '/detail/:proid',
name: 'detail',
// component: DetailView
components: {
default: DetailView
}
},
{
path: '/register',
name: 'register',
redirect: '/register/index',
component: RegisterView,
children: [
{
path: 'index',
component: CheckTelComponent
},
{
path: 'sms',
component: SendTelCodeComponent
},
{
path: 'pwd',
component: SetPasswordComponent
}
]
},
{
path: '/login',
name: 'login',
component: LoginView
},
{
path: '/order',
name: 'order',
component: OrderView
},
{
path: '/orderAddress',
name: 'orderAddress',
component: OrderAddressListView
},
{ // +++++++++
path: '/orderAddAddress',
name: 'orderAddAddress',
component: OrderAddAddressView
}
]
// 4.生成路由
const router: Router = createRouter({
// vue2 vue-router3 history: 'history' | 'hash'
history: createWebHistory(),
routes // routes: routes 简写形式
})
// 5.暴露路由
export default router
添加并使用该地址(添加i地址进入地址列表页面,修改订单的地址)
// src/api/address.ts
import request from '../service/request'
export interface IAddress {
userid: string
name: string
tel: string
province: string
city: string
county: string
addressDetail: string
isDefault: boolean
}
export function addAddRessData (params: IAddress ) {
return request.post('/address/add', params)
}
export function getAddressList (params: {userid: string} ) {
return request.get('/address/add', {params})
}
计算属性即可完成
原价:{{ totalPrice }}
快递费:{{ express }}