当前位置: 首页 > news >正文

中国建设银行网站 纪念币预约/腾讯广告推广怎么做

中国建设银行网站 纪念币预约,腾讯广告推广怎么做,视觉设计师培训,零代码开发创建项目 npm init vuelatest // npm create vitelatestVue文件结构 <!-- 开关&#xff1a;经过语法糖的封装&#xff0c;容许在script中书写组合式API --> <!-- setup在beforeCreate钩子之前自动执行 --> <script setup><!-- 不再要求唯一根元素 -->…

创建项目

npm init vue@latest
// npm create vite@latest

Vue文件结构

<!-- 开关:经过语法糖的封装,容许在script中书写组合式API -->
<!-- setup在beforeCreate钩子之前自动执行 -->
<script setup><!-- 不再要求唯一根元素 -->
<template>

setup样例

<script setup>
const message = 'this is message'
</script><template><div>{{message}}</div>
</template>

语法糖,会自动编译成3.0的代码结构
https://play.vuejs.org/
在线编译组合式api代码


响应式数据

reactive():接受对象类型数据的参数传入并返回一个响应式的对象
ref():接受简单类型或者对象类型数据的参数传入并返回一个响应式的对象
ref函数的内部实现依赖于reactive函数

<script setup>
import { ref, reactive } from 'vue';
const state = reactive({count: 0
})const setCount = () => {state.count++
}const count2 = ref(0)
const setCount2 = () => {count2.value++
}
</script><template><button @click="setCount">{{ state.count }}</button><button @click="setCount2">{{ count2 }}</button>
</template>

computed计算属性函数

<script setup>
import { ref, computed } from 'vue';
const list = ref([1, 2, 3, 4, 5, 6, 7,8])const computedList = computed(() => {return list.value.filter(item => item > 2)
})setTimeout(() => {list.value.push(9, 10)
}, 3000)
</script><template>
<div>原始数据:{{ list }}</div>
<div>计算后的数据:{{ computedList }}</div>
</template>
  1. 计算属性中不应该有副作用
  2. 避免直接修改计算属性的值

watch函数

监听一个或者多个数据的变化,数据变化时执行回调函数

有2个参数:
immediate 在监听器创建时,立即触发回调
deep深度监听:通过watch监听的ref对象默认是浅层监听,直接修改嵌套的对象属性不会触发回调执行

可以同时监听多个响应式数据的变化,不管哪个数据变化都需要执行回调

<script setup>
import { ref, watch } from 'vue';
const count = ref(0)
const name = ref('Tom')
/监听ref对象,不需要加.value
watch(count, (newValue, oldValue) => {console.log('count变更 新值:', newValue, '但值:', oldValue)
}, {immediate: true
})// 监听多个对象
// watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
//   console.log('count或者name变化了', [newCount, newName], [oldCount, oldName])
// })const setCount = () => {count.value++
}
</script><template>
<button @click="setCount">{{ count }}</button>
</template>

deep监听

<script setup>
import { ref, watch } from 'vue';
const state = ref({count: 0
})
// 通过watch监听的ref对象默认是浅层监听,需要开启deep
watch(state, (newValue, oldValue) => {console.log('state变更 新值:', newValue, '但值:', oldValue)
}, {deep: true
})// 精确监听某个属性
const info = ref({name: 'Tom',age: 18
})// deep有性能损耗,尽量不开启deep
watch(() => info.value.age, () => console.log('age发生了变化')
)const setCount = () => {state.value.count++
}
const setAge = () => {info.value.age = 30
}
</script><template>
<button @click="setCount">{{ state.count }}</button>
<button @click="setAge">age {{ info.age }}</button>
</template>

生命周期函数

组合式API - setup
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUmmounted

<script setup>
import { onMounted } from 'vue';
onMounted(() => {console.log('组件挂载完毕onMounted执行了')
})
</script>

父子通信–父传子

  1. 父组件中给子组件中绑定属性
  2. 子组件内部通过defineProps接收参数
<script setup>
import { ref } from 'vue';
import SonCom from './son-com.vue'
const count = ref(100)
setTimeout(() => {count.value = 200
}, 3000)
</script><template>
<div>显示</div>
<SonCom message="father message" :count="count"></SonCom>
</template><script setup>
import { defineProps } from 'vue';
const props = defineProps({message: String,count: Number
})
console.log('props', props)
</script><template><div>子组件 {{ message }} - {{ count }}</div>
</template>

父子通信–子传父

  1. 父组件中给子组件标签通过 @ 绑定事件
  2. 子组件内部通过defineEmits方法触发事件
<script setup>
import SonCom from './son-com.vue'
const getMessage = (msg) => {console.log(msg)
}
</script><template>
<div>显示</div>
<SonCom @get-message="getMessage"></SonCom>
</template><script setup>
import { defineEmits } from 'vue';
const emit = defineEmits(["get-message"])
const sendMsg = () => {emit('get-message', 'This is son message')
}
</script><template><button @click="sendMsg">发送消息</button>
</template>

模板引用

通过ref标识获取真实的dom对象或者组件实例对象
默认情况下,子组件在setup语法糖中组件内部的属性和方法是不开放给父组件访问的,使用defineExpose指定可允许访问的属性和方法

<script setup>
import { onMounted, ref } from 'vue';
import testCom from './test-com.vue'
const h1Ref = ref(null)
const comRef = ref(null)// 组件挂载完毕之后才能获取
onMounted(() => {console.log(h1Ref.value)console.log(comRef)
})
</script><template>
<h1 ref="h1Ref">绑定ref测试</h1>
<testCom ref="comRef"></testCom>
</template>//===============================<script setup>
import { ref } from 'vue';
const name = ref('test name')
const setName = () => {name.value = 'test new name'
}// 显示暴露组件内部的属性和方法
defineExpose({name, setName
})
</script><template>
<div>子组件div</div>
</template>

跨层通信provide/inject

顶层组件向任意底层组件传递数据或方法,实现跨层组件通信;可以传递响应式数据、方法

  1. 顶层组件provide提供数据 provide('key', data)
  2. 底层组件inject获取数据 const message = inject('key')
<script setup>
import { provide, ref } from 'vue';
import SonCom from './son-com.vue'
provide('data-key', 'This is parent message')// 响应式数据
const count = ref(0)
provide('count-key', count)
setTimeout(() => {count.value = 300
}, 3000)// 传递方法
const setCount = () => {count.value++
}
provide('setCount-key', setCount)
</script><template>
<div>顶层组件</div>
<SonCom></SonCom>
</template>//=======================<script setup>
import { inject } from 'vue';
const parentData = inject('data-key')
const countData = inject('count-key')
const setCount = inject('setCount-key')
</script><template><div>底层组件 {{ parentData }} - {{ countData }}</div><button @click="setCount">更新数据</button>
</template>

Element Plus列表、修改页面

http://git.itcast.cn/heimaqianduan/vue3-basic-project.git

<script setup>
import { ref } from 'vue';
import axios from 'axios';
import Edit from './components/Edit.vue'
import { ElMessageBox } from 'element-plus';// TODO: 列表渲染
const list = ref([])
const getList = async () => {const res = await axios.get('/list')list.value = res.data
}// TODO: 删除功能
const onDelete = async (id) => {try {await ElMessageBox.confirm('确认删除这条数据吗?','删除提醒',{confirmButtonText: '确认',cancelButtonText: '取消',type: 'warning',})await axios.delete(`/del/${id}`)getList()} catch(err) {}
}// TODO: 编辑功能
const editRef = ref(null)
const onEdit = (row) => {editRef.value.openDialog(row)
}getList()
</script><template><div class="app"><el-table :data="list"><el-table-column label="ID" prop="id"></el-table-column><el-table-column label="姓名" prop="name" width="150"></el-table-column><el-table-column label="籍贯" prop="place"></el-table-column><el-table-column label="操作" width="150"><template #default="{row}"><el-button type="primary" link @click="onEdit(row)">编辑</el-button><el-button type="danger" link @click="onDelete(row.id)">删除</el-button></template></el-table-column></el-table></div><Edit ref="editRef" @on-update="getList"/>
</template><style scoped>
.app {width: 980px;margin: 100px auto 0;
}
</style>

/components/Edit.vue

<script setup>
// TODO: 编辑
import { ref, defineEmits } from 'vue'
import axios from 'axios'
// 弹框开关
const dialogVisible = ref(false)
const form = ref({id: '',name: '',place: ''
})// 打开编辑页面
const openDialog = (row) => {form.value.id = row.idform.value.name = row.nameform.value.place = row.placedialogVisible.value = true
}const emit = defineEmits(['on-update'])const onUpdate = async () => {await axios.patch(`/edit/${form.value.id}`, {name: form.value.name,place: form.value.place})dialogVisible.value = false// 父页面重新查询数据emit('on-update')
}// 暴露方法给父页面调用
defineExpose({openDialog
})
</script><template><el-dialog v-model="dialogVisible" title="编辑" width="400px"><el-form label-width="50px" :model="form"><el-form-item label="姓名"><el-input placeholder="请输入姓名" v-model="form.name" /></el-form-item><el-form-item label="籍贯"><el-input placeholder="请输入籍贯" v-model="form.place" /></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="onUpdate">确认</el-button></span></template></el-dialog>
</template><style scoped>
.el-input {width: 290px;
}
</style>

Pinia状态管理库

npm init vue@latest //创建一个空项目
vue-pinia
npm install pinia

修改main.js

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')

添加文件 stores/counter.js

import { computed, ref } from 'vue'
import axios from 'axios'
import { defineStore } from 'pinia'const API_URL = 'http://geek.itheima.net/v1_0/channels'export const useCounterStore = defineStore('counter', () => {const count = ref(0) const increment = () => {count.value++}// Pinia中的getters直接使用computed函数进行模拟const doubleCount = computed(() => count.value * 2)// 定义异步actionconst list = ref([])const getList = async () => {const res = await axios.get(API_URL)list.value = res.data.data.channels}return { count, doubleCount, increment, list, getList }})

使用Pinia

<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
const counterStore = useCounterStore()
// 解构会导致响应式丢失
// const {count, doubleCount} = counterStore// storeToRefs可以辅助保持数据的响应式解构
// 保持数据响应式
const {count, doubleCount} = storeToRefs(counterStore)// 方法需要直接从原来的counterStore中解构赋值
const {increment} = counterStoreonMounted(() => {counterStore.getList()
})
</script><template><button @click="increment">{{ count }}</button><div>{{ doubleCount }}</div><ul><li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li></ul>
</template>

全局属性

在Vue3中使用 <script setup> 语法时,不能直接使用 this 来访问组件的实例属性。但是,如果你想在 <script setup> 组件中使用 app.config.globalProperties 设置的全局属性,可以通过属性注入或直接从应用实例访问它们。

以下是一个使用 <script setup> 语法的示例,展示如何在Vue3应用中使用 app.config.globalProperties

首先,在Vue3应用的入口文件(如 main.js 或 main.ts)中设置全局属性:

import { createApp } from 'vue';const app = createApp({})// 定义全局属性
app.config.globalProperties.$myGlobalProperty = 'Hello World';// 定义全局方法
app.config.globalProperties.myGlobalMethod = function() {console.log('This is a global method');
};// 挂载应用
app.mount('#app');

然后,创建一个使用 <script setup> 语法的组件,并在其中访问全局属性:

<template><div><p>{{ myGlobalProperty }}</p><!-- 如果全局属性是一个方法,可以这样调用: --><button @click="myGlobalMethod">Call Global Method</button></div>
</template><script setup>
import { getCurrentInstance } from 'vue';// 使用 getCurrentInstance 获取组件实例
const instance = getCurrentInstance();
const myGlobalProperty = instance.appContext.config.globalProperties.$myGlobalProperty;
const myGlobalMethod = instance.appContext.config.globalProperties.myGlobalMethod;
</script>

配置src路径别名

创建测试项目

npm init vue@latest
vue3-rabbit

选中Router/Pinia/ESLint

jsconfig.json配置别名路径,使用@自动路径提示

{"compilerOptions": {"paths": {"@/*": ["./src/*"]}},"exclude": ["node_modules", "dist"]
}

在vite.config.js文件中,alias配置会把@路径转换成真实的路径


Element Plus安装

#按需引入

npm install element-plus --save
npm install -D unplugin-vue-components unplugin-auto-import

修改vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'export default defineConfig({plugins: [vue(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],
})

主题定制

npm i sass -D

对Element Plus样式进行覆盖
添加文件 styles/element/index.scss

/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with ($colors: ('primary': (// 主色'base': #27ba9b,),'success': (// 成功色'base': #1dc779,),'warning': (// 警告色'base': #ffb302,),'danger': (// 危险色'base': #e26237,),'error': (// 错误色'base': #cf4444,),)
)

配置Element Plus采用sass样式配色系统
修改文件vite.config.js

import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [// 配置ElementPlus采用sass样式配色系统	  ElementPlusResolver({ importStyle: 'sass'})],})],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}},css: {preprocessorOptions: {scss: {// 自动导入定制化样式文件进行样式覆盖additionalData: `@use "@/styles/element/index.scss" as *;`,}}}
})

axios基础配置

安装

npm i axios

添加工具类 utils/http.js

import axios from "axios";// 创建axios实例
const http = axios.create({baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',timeout: 5000
})// axios请求拦截器
http.interceptors.request.use(config => {return config
}, e => Promise.reject(e))// axios响应式拦截器
http.interceptors.response.use(res => res.data, e => {return Promise.reject(e)
})export default http

添加测试方法 api/testAPI.js

import http from "@/utils/http";export function getCategory() {return http({url: 'home/category/head'})
}

使用方法

import { getCategory } from './apis/testAPI'
getCategory().then((res) => console.log(res))

路由设计原则

找内容切换的区域,如果是页面整体切换,则为一级路由;
如果是在一级路由页的内部切换,则为二级路由

import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/',name: 'layout',component: () => import('../views/Layout/index.vue'),children: [{path: '',name: 'home',component: () => import('../views/Home/index.vue')}, {path: 'category',name: 'category',component: () => import('../views/Category/index.vue')}]}, {path: '/login',name: 'login',component: () => import('../views/Login/index.vue')}]
})export default router

scss文件中变量的自动导入

在项目中一般把颜色值以scss变量的方式放到var.scss文件中,在使用时需要先导入var.scss文件
修改vite.config.js

  css: {preprocessorOptions: {scss: {// 自动导入定制化样式文件进行样式覆盖additionalData: `@use "@/styles/element/index.scss" as *;@use "@/styles/var.scss" as *;`,}}}

测试

<template><div class="test">test</div>
</template><style scoped lang="scss">
.test {color: $priceColor;
}
</style>

VS code安装插件Error Lens,语法错误提示


吸顶导航

准备吸顶导航组件,获取滚动距离,以滚动距离做判断条件控制组件盒子展示隐藏

https://vueuse.org/
常用的组合式api集合

#安装Vue3组合式api集合
npm i @vueuse/core
<script setup>
import { useScroll } from '@vueuse/core'
// 滚动时实时获取滚动条的位置
const { y } = useScroll(window)
</script>// css:在满足条件时,样式类自动生效
:class="{show: y > 78}"

element-plus banner轮播图组件

    <el-carousel height="500px"><el-carousel-item v-for="item in bannerList" :key="item.id"><img :src="item.imgUrl" alt=""></el-carousel-item></el-carousel>

图片懒加载

使用自定义组件实现
使用useIntersectionObserver函数,监听绑定的dom对象是否进入可视窗口区域

// main.js
import { useIntersectionObserver } from '@vueuse/core'// 定义全局指令
app.directive('img-lazy', {mounted(el, binding) {// el:指令绑定的那个元素// binding:binding.value 指令等号后面绑定的表达式const { stop } = useIntersectionObserver(el,([{ isIntersecting }]) => {if (isIntersecting) {// 进入可视窗口区域el.src = binding.value// 停止监听stop()}},)}
})// 页面使用
<img v-img-lazy="item.picture" alt="" />

代码优化,抽取成插件

// directives/index.js
import { useIntersectionObserver } from '@vueuse/core'// 定义懒加载插件
export const lazyPlugin = {install(app) {app.directive('img-lazy', {mounted(el, binding) {// el:指令绑定的那个元素// binding:binding.value 指令等号后面绑定的表达式const { stop } = useIntersectionObserver(el,([{ isIntersecting }]) => {if (isIntersecting) {// 进入可视窗口区域el.src = binding.value// 停止监听stop()}},)}})}
}// main.js
import { lazyPlugin } from '@/directives'
app.use(lazyPlugin)

动态路由

<li v-for="item in categoryStore.categoryList" :key="item.id"><RouterLink active-class="active" :to="`/category/${item.id}`">{{ item.name }}</RouterLink>
</li>

获取参数

import {useRoute} from 'vue-router'
const route = useRoute()
console.log(route.params.id)
// console.log(route.query.id)
// route.params 用于访问当前路由的路由参数(route parameters),即路由路径中的动态参数部分
// route.query 用于访问当前路由的查询参数,通常用于传递额外的信息(获取到的路由参数是字符串)

激活路由链接中的样式
active-class配置选中路由的样式

<RouterLink active-class="active" :to="`/category/${item.id}`">{{ item.name }}</RouterLink>.active {color: $xtxColor;border-bottom: 1px solid $xtxColor;
}

路由缓存问题

使用带有参数的路由时(/category/${item.id}),相同的组件实例将被重复使用,
导致组件的生命周期钩子函数不会被调用

		{path: 'category/:id',name: 'category',component: () => import('../views/Category/index.vue')}

解决方式

  1. 让组件实例不复用,强制销毁重建
// 添加key,破坏复用机制,强制销毁重建
<RouterView :key="$route.fullPath"/>
  1. 监听路由变化,变化之后执行数据更新操作
// useCategory.js
import {useRoute, onBeforeRouteUpdate} from 'vue-router'onBeforeRouteUpdate((to) => {// console.log('路由变化了', to)getCategory(to.params.id)
})

以下是useCategory.js完整代码

import { ref, onMounted } from "vue"
import { onBeforeRouteUpdate, useRoute } from 'vue-router';
import { getCategoryAPI } from '@/apis/category';export function useCategory() {const categoryData = ref({})const getCategory = async (id) => {const res = await getCategoryAPI(id)categoryData.value = res.result}// 解决组件复用生命周期钩子不调用的问题onBeforeRouteUpdate((to) => {// console.log('to', to)getCategory(to.params.id)})// 首次调用会用到const route = useRoute()onMounted(() => {getCategory(route.params.id)})return {categoryData}
}

面包屑导航

      <el-breadcrumb separator=">"><el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item><el-breadcrumb-item :to="{ path: `/category/${categoryData.parentId}` }">{{ categoryData.parentName }}</el-breadcrumb-item><el-breadcrumb-item>{{ categoryData.name }}</el-breadcrumb-item></el-breadcrumb>

Infinite Scroll无限滚动

在要实现滚动加载的列表上添加指令v-infinite-scroll,可实现滚动到底部时自动执行加载方法

      <div class="body" v-infinite-scroll="load" :infinite-scroll-disabled="disabled"><!-- 商品列表--><GoodsItem :good="good" v-for="good in goodsList" :key="good.id"/></div>// 下拉加载	  
const disabled = ref(false)
const load = async () => {reqData.value.page++const res = await getSubCategoryAPI(reqData.value)goodsList.value = [...goodsList.value, ...res.result.items]if (res.result.items.length < reqData.value.pageSize) {disabled.value = true}
}	  

tab排序方式切换

v-model绑定的是tab-pane对应的name属性值

      <el-tabs v-model="reqData.sortField" @tab-change="tabChange"><el-tab-pane label="最新商品" name="publishTime"></el-tab-pane><el-tab-pane label="最高人气" name="orderNum"></el-tab-pane><el-tab-pane label="评论最多" name="evaluateNum"></el-tab-pane></el-tabs>// 切换排序方式
const tabChange = () => {console.log('change', reqData.value.sortField)reqData.value.page = 1disabled.value = falsegetGoodsList(reqData.value)
}	  

定制路由滚动行为

在切换路由时,自动滚动到页面的顶部

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/',name: 'layout',component: () => import('../views/Layout/index.vue')}, {path: '/login',name: 'login',component: () => import('../views/Login/index.vue')}], scrollBehavior() {return {top: 0}}
})export default router

激活某个样式

格式 :class="{ className: condition == true }"

<li v-for="(img, i) in imageList" :key="i" @mouseenter="enterHandler(i)" :class="{ active: activeIndex === i }"><img :src="img" alt="" />
</li>

图片放大镜功能

<script setup>
import { ref, watch } from 'vue';
import { useMouseInElement } from '@vueuse/core'// 图片列表
// const imageList = [
//   "https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png",
//   "https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg",
//   "https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg",
//   "https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg",
//   "https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg"
// ]defineProps({imageList: {type: Array,default: () => []}
})const activeIndex = ref(0)
const enterHandler = (i) => {activeIndex.value = i
} // 鼠标位置
const target = ref(null)
const top = ref(0)
const left = ref(0)
const positionX = ref(0)
const positionY = ref(0)
const { elementX, elementY, isOutside } = useMouseInElement(target)
watch([elementX, elementY, isOutside], () => {if (isOutside.value) return// 有效范围内控制滑块距离if (elementX.value > 100 && elementX.value < 300) {left.value = elementX.value - 100}if (elementY.value > 100 && elementY.value < 300) {top.value = elementY.value - 100}// 边界if (elementX.value > 300) {left.value = 200}if (elementX.value < 100) {left.value = 0}if (elementY.value > 300) {top.value = 200}if (elementY.value < 100) {top.value = 0}positionX.value = -left.value * 2positionY.value = -top.value * 2
})
</script><template><div class="goods-image"><!-- 左侧大图--><div class="middle" ref="target"><img :src="imageList[activeIndex]" alt="" /><!-- 蒙层小滑块 --><div class="layer" v-show="!isOutside" :style="{ left: `${left}px`, top: `${top}px` }"></div></div><!-- 小图列表 --><ul class="small"><li v-for="(img, i) in imageList" :key="i" @mouseenter="enterHandler(i)" :class="{active: activeIndex === i}"><img :src="img" alt="" /></li></ul><!-- 放大镜大图 --><div class="large" :style="[{backgroundImage: `url(${imageList[activeIndex]})`,backgroundPositionX: `${positionX}px`,backgroundPositionY: `${positionY}px`,},]" v-show="!isOutside"></div></div>
</template><style scoped lang="scss">
.goods-image {width: 480px;height: 400px;position: relative;display: flex;.middle {width: 400px;height: 400px;background: #f5f5f5;}.large {position: absolute;top: 0;left: 412px;width: 400px;height: 400px;z-index: 500;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);background-repeat: no-repeat;// 背景图:盒子的大小 = 2:1  将来控制背景图的移动来实现放大的效果查看 background-positionbackground-size: 800px 800px;background-color: #f8f8f8;}.layer {width: 200px;height: 200px;background: rgba(0, 0, 0, 0.2);// 绝对定位 然后跟随咱们鼠标控制left和top属性就可以让滑块移动起来left: 0;top: 0;position: absolute;}.small {width: 80px;li {width: 68px;height: 68px;margin-left: 12px;margin-bottom: 15px;cursor: pointer;&:hover,&.active {border: 2px solid $xtxColor;}}}
}
</style>

使用第三方组件的关注点

关注propsemit
props接收参数,emit返回数据


配置全局组件插件

编写插件时引入组件

// 使用插件形式,把组件全局注册(components/index.js)
import ImageView from './ImageView/index.vue'
import Sku from './XtxSku/index.vue'
export const componentPlugin = {install(app) {// app.component('组件名字', 组件配置对象)app.component('XtxImageView', ImageView)app.component('XtxSku', Sku)}
}

注册全局插件

// main.js
import { componentPlugin } from './components'const app = createApp(App)app.use(componentPlugin)

Form表单

表单校验

<script setup>
import {ref} from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/el-message.css'
// 1.表单对象
const form = ref({account: '',password: '',agree: true
})
// 2.规则对象
const rules = {account: [{required: true,  message: '用户名不能为空', trigger: 'blur'}],password: [{required: true,  message: '密码不能为空', trigger: 'blur'},{min: 6, max: 14, message: '密码长度为6-14个字符', trigger: 'blur'}], // 自定义规则agree: [{validator: (rule, value, callback) => {if (!value) {callback(new Error('请勾选协议'))} else {callback()}}}]
}
const router = useRouter()
// 3.表单校验
const formRef = ref(null)
const doLogin = () => {formRef.value.validate((valid) => {// 所有表单项校验通过才为trueif (valid) {const {account, password} = form.valueawait loginAPI({account,password})ElMessage({ type: 'success', message: '登录成功' })router.replace({ path: '/' })}})
}
</script><template><el-form label-position="right" label-width="60px" :model="form" :rules="rules" ref="formRef"status-icon><el-form-item label="账户" prop="account"><el-input v-model="form.account"/></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="form.password"/></el-form-item><el-form-item label-width="22px" prop="agree"><el-checkbox  size="large" v-model="form.agree">我已同意隐私条款和服务条款</el-checkbox></el-form-item><el-button size="large" class="subBtn" @click="doLogin">点击登录</el-button></el-form>
</template>

用户数据存入Pinia

// userStore.js
import { defineStore } from "pinia";
import { ref } from "vue";
import {loginAPI} from '@/apis/user'export const useUserStore = defineStore('user', () => {const userInfo = ref({})const getUserInfo = async ({account, password}) => {const res = await loginAPI({account, password})userInfo.value = res.result}return {userInfo,getUserInfo}
})//登录时直接调用getUserInfo方法
import {useUserStore} from '@/stores/user'
const userStore = useUserStore()const doLogin = async () => {formRef.value.validate(async (valid) => {// 所有表单项校验通过才为trueif (valid) {const {account, password} = form.valueawait userStore.getUserInfo({account, password})ElMessage({ type: 'success', message: '登录成功' })router.replace({ path: '/' })}})
}

Pinia用户数据持久化

// 安装插件
npm i pinia-plugin-persistedstate// 注册插件main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const app = createApp(App)const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)

在定义defineStore方法时,传入第3个参数:{ persist: true }

// userStore.js完整代码
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { loginAPI } from '@/apis/user';
import { useCartStore } from './cartStore';export const useUserStore = defineStore('user', () => {const cartStore = useCartStore()const userInfo = ref({})// 登录const getUserInfo = async ({account, password}) => {const res = await loginAPI({account, password})userInfo.value = res.result// 合并购物车await cartStore.mergeCart()// 查询最新购物车cartStore.updateNewList()}const clearUserInfo = () => {userInfo.value = {}// 退出时删除购物车cartStore.clearCart()}return { userInfo, getUserInfo, clearUserInfo }
}, {    persist: true  
})

Form确认框

退出登录

<script setup>
import { useRouter } from 'vue-router';
import {useUserStore} from '@/stores/user'
const userStore = useUserStore()
const router = useRouter()
const confirm = () => {userStore.clearUserInfo()router.push('/login')
}
</script><template><el-popconfirm title="确认退出吗?" @confirm="confirm" confirm-button-text="确认" cancel-button-text="取消"><template #reference><a href="javascript:;">退出登录</a></template></el-popconfirm>
</template>

常用的页面点击跳转方式

# 1. RouterLink<li class="home" v-for="item in categoryStore.categoryList" :key="item.id"><RouterLink active-class="active" :to="`/category/${item.id}`">{{ item.name }}</RouterLink></li># 2. $router
<el-button size="large" type="primary" @click="$router.push('/cartlist')">去购物车结算</el-button>		# 3. router
import { useRouter } from 'vue-router';
const router = useRouter()
router.push('/login')# push跳转带参数
router.push({path: '/pay', query: {id: orderId}
})# 4. 超链接
<a href="javascript:;" @click="$router.push('/login')">请先登录</a>

单选全选框选中

把单选框值与pinia中的数据绑定
v-model双向绑定指令不合适命令式的操作,使用独立的2个指令:model-value@change

// 默认change事件只有一个selected参数,使用匿名箭头函数是为了扩展传参
<el-checkbox :model-value="item.selected" @change="(selected) => changeHandler(item.skuId, selected)"/>// 单选回调
const changeHandler = (skuId, selected) => {// selected的值为true/false,即当前选中的状态console.log(skuId, selected)cartStore.singleCheck(skuId, selected)
}

全选功能

<el-checkbox :model-value="cartStore.isAll" @change="allCheck"/>// 是否全选
const isAll = computed(() => cartList.value.every((item) => item.selected))const allCheck = (selected) => {cartList.value.forEach(item => item.selected = selected)
}

cartStore.js完整代码

import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { useUserStore } from './userStore'
import { insertCartAPI, findNewCartListAPI, delCartAPI, mergeCartAPI } from '@/apis/cart'export const useCartStore = defineStore('cart', () => {const userStore = useUserStore()const isLogin = computed(() => userStore.userInfo.token)const cartList = ref([])const addCart = async (goods) => {const {skuId, count} = goodsif (isLogin.value) {await insertCartAPI({skuId, count})await updateNewList()} else {// 未登录,本地购物车const item = cartList.value.find(item => item.skuId === goods.skuId)if (item) {item.count = item.count + goods.count} else {cartList.value.push(goods)}}}const delCart = async (skuId) => {if (isLogin.value) {await delCartAPI([skuId])await updateNewList()} else {const index = cartList.value.findIndex(item => item.skuId === skuId)cartList.value.splice(index, 1)// const newList = cartList.value.filter(item => item.skuId !== skuId)// cartList.value = newList}}// 合并本地购物车,在登录时使用const mergeCart = async () => {if (cartList.value.length > 0) {await mergeCartAPI(cartList.value.map(item => {return {skuId: item.skuId,count: item.count,selected: item.selected}}))}}// 清除购物车const clearCart = () => {cartList.value = []}// 获取最新购物车列表const updateNewList = async () => {const res = await findNewCartListAPI()cartList.value = res.result}// 单选功能const singleCheck = (skuId, selected) => {const item = cartList.value.find(item => item.skuId === skuId)item.selected = selected}// 全选const allCheck = (selected) => {cartList.value.forEach(item => item.selected = selected)}// 是否全选 const isAll = computed(() => cartList.value.every(item => item.selected))// 总数量const allCount = computed(() => cartList.value.reduce((a, c) => a + c.count, 0))// 总价const allPrice = computed(() => cartList.value.reduce((a, c) => a + c.count * c.price, 0))// 已选择数量const selectedCount = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count, 0))// 已选择商品总份const selectedPrice = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count * c.price, 0))return { cartList, allCount, allPrice, selectedCount, selectedPrice, isAll,addCart, delCart, clearCart, mergeCart, singleCheck, allCheck, updateNewList  }
}, {    persist: true  
})

弹窗组件

  <el-dialog v-model="showDialog" title="切换收货地址" width="30%" center><div class="addressWrapper"><div class="text item" :class="{active : activeAddress.id === item.id }" @click="switchAddress(item)" v-for="item in checkInfo.userAddresses"  :key="item.id"><ul><li><span>收<i />货<i />人:</span>{{ item.receiver }} </li><li><span>联系方式:</span>{{ item.contact }}</li><li><span>收货地址:</span>{{ item.fullLocation + item.address }}</li></ul></div></div><template #footer><span class="dialog-footer"><el-button @click="showDialog = false">取消</el-button><el-button type="primary" @click="confirmAddress">确定</el-button></span></template></el-dialog>

倒计时组件

// useCountDown.js
// 倒计时
import { ref, computed, onUnmounted } from "vue";
import dayjs from "dayjs";
export const useCountDown = () => {let timer = nullconst time = ref(0)const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))// 传入倒计时的秒数const start = (currentTime) => {time.value = currentTimetimer = setInterval(() => {if (time.value > 0) {time.value --} else {timer && clearTimeout(timer)}}, 1000)}// 组件销毁时清除定时器onUnmounted(() => {timer && clearInterval(timer)})return {formatTime,start}
}

自定义列表空数据显示样式

<div class="holder-container" v-if="orderList.length === 0"><el-empty description="暂无订单数据" />
</div>
<div v-else><!-- 订单列表 --><div class="order-item" v-for="order in orderList" :key="order.id"></div>
</div>  

tab页切换

<script setup>
import {ref, onMounted} from 'vue'
import {getUserOrder} from '@/apis/order'// tab列表
const tabTypes = [{ name: "all", label: "全部订单" },{ name: "unpay", label: "待付款" },{ name: "deliver", label: "待发货" },{ name: "receive", label: "待收货" },{ name: "comment", label: "待评价" },{ name: "complete", label: "已完成" },{ name: "cancel", label: "已取消" }
]
// 订单列表
const orderList = ref([])
const params = ref({orderState: 0,page: 1,pageSize: 2
})
const getOrderList = async () => {const res = await getUserOrder(params.value)orderList.value = res.result.items
}const tabChange = (index) => {params.value.orderState = indexgetOrderList()
}onMounted(() => getOrderList())
</script><template><div class="order-container"><el-tabs @tab-change="tabChange"><!-- tab切换 --><el-tab-pane v-for="item in tabTypes" :key="item.name" :label="item.label" /></div><div>内容</div>	</el-tabs></div>
</template>	

列表分页

// 订单列表
const orderList = ref([])
const params = ref({orderState: 0,page: 1,pageSize: 2
})
const total = ref(0)
const getOrderList = async (params) => {const res = await getUserOrder(params) orderList.value = res.result.itemstotal.value = res.result.counts
} onMounted(() => getOrderList(params.value))
const tabChange = (type) => {params.value.page = 1params.value.orderState = typegetOrderList(params.value)
}const pageChange = (page) => {params.value.page = pagegetOrderList(params.value)
}<!-- 分页 -->
<div class="pagination-container"><el-pagination :total="total" :page-size="params.pageSize" @current-change="pageChange" background layout="prev, pager, next" />
</div><style scoped lang="scss">
.order-container {padding: 10px 20px;.pagination-container {display: flex;justify-content: center;}.main-container {min-height: 500px;.holder-container {min-height: 500px;display: flex;justify-content: center;align-items: center;}}
}</style>

参考文档

https://www.bilibili.com/video/BV1Ac411K7EQ/?spm_id_from=333.999.0.0&vd_source=0311265fb5615f1fc596bb4c1dcdcd20  
黑马程序员前端Vue3小兔鲜电商项目实战,vue3全家桶从入门到实战电商项目一套通关https://zsjie.blog.csdn.net/article/details/131323373  
黑马程序员前端 Vue3 小兔鲜电商项目  https://www.yuque.com/fechaichai/trash-1cydvph9  
Vue3小兔鲜新版文档https://apifox.com/apidoc/shared-fa9274ac-362e-4905-806b-6135df6aa90e
黑马前端api文档  https://play.vuejs.org/  
在线编译组合式api代码https://vueuse.org/  
vue官方提供的vue3组合式函数测试登录账号  
xiaotuxian001  
123456支付宝沙箱账号  
scobys4865@sandbox.com  
登录密码111111  
支付密码111111  支付回调地址  
http://127.0.0.1:5173/paycallback?payResult=true&orderId=1809449837100273665

相关文章:

Vue3知识点汇总

创建项目 npm init vuelatest // npm create vitelatestVue文件结构 <!-- 开关&#xff1a;经过语法糖的封装&#xff0c;容许在script中书写组合式API --> <!-- setup在beforeCreate钩子之前自动执行 --> <script setup><!-- 不再要求唯一根元素 -->…...

C++设计模式--单例模式

单例模式的学习笔记 单例模式是为了&#xff1a;在整个系统生命周期内&#xff0c;保证一个类只能产生一个实例&#xff0c;确保该类的唯一性 参见链接1&#xff0c;链接2 #include <iostream> #include <mutex>using namespace std;/*懒汉模式&#xff1a;只有在…...

数据驱动未来:构建下一代湖仓一体电商数据分析平台,引领实时商业智能革命

1.1 项目背景 本项目是一个创新的湖仓一体实时电商数据分析平台&#xff0c;旨在为电商平台提供深度的数据洞察和业务分析。技术层面&#xff0c;项目涵盖了从基础架构搭建到大数据技术组件的集成&#xff0c;采用了湖仓一体的设计理念&#xff0c;实现了数据仓库与数据湖的有…...

学习JavaScript第五天

文章目录 1.HTML DOM1.1 表单相关元素① form 元素② 文本输入框类和文本域&#xff08;input 和 textarea&#xff09;③ select 元素 1.2 表格相关元素① table 元素② tableRow 元素&#xff08;tr 元素&#xff09;③ tableCell 元素 &#xff08;td 或 th&#xff09; 1.3…...

pythonGame-实现简单的坦克大战

通过python简单复现坦克大战游戏。 使用到的库函数&#xff1a; import turtle import math import random import time 游戏源码&#xff1a; import turtle import math import random import time# 设置屏幕 screen turtle.Screen() screen.setup(800, 600) screen.tit…...

不太常见的asmnet诊断

asm侦听 [griddb1-[ASM1]-/home/grid]$ srvctl config asm ASM home: <CRS home> Password file: OCR/orapwASM Backup of Password file: OCRDG/orapwASM_backup ASM listener: LISTENER ASM instance count: 3 Cluster ASM listener: ASMNET1LSNR_ASM[rootdb1:/root]# …...

双指针-【3,4,5,6,7,8】

第三题&#xff1a;快乐数 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/happy-number/算法思想&#xff1a; 1.每个…...

react Vant中如何获取步进器的值

在React中使用Vant&#xff08;一个轻量、可靠的移动端Vue组件库&#xff0c;虽然原生是为Vue设计的&#xff0c;但如果你在使用的是React版本的Vant&#xff0c;比如通过某些库或框架桥接Vue组件到React&#xff0c;或者是一个类似命名的React UI库&#xff09;&#xff0c;获…...

Windows下Git Bash乱码问题解决

Windows下Git Bash乱码问题解决 缘起 个人用的电脑是Mac OS&#xff0c;系统和终端编码都是UTF-8&#xff0c;但公司给配发的电脑是Windows&#xff0c;装上Git Bash在使用 git commit -m "中文"时会乱码 解决 确认有以下配置 # 输入 git config --global --lis…...

HTML5 + CSS3

HTML 基础 准备开发环境 1.vscode 使用 新建文件夹 ---> 左键拖入 vscode 中 2.安装插件 扩展 → 搜索插件 → 安装打开网页插件&#xff1a;open in browser汉化菜单插件&#xff1a;Chinese 3.缩放代码字号 放大,缩小&#xff1a;Ctrl 加号&#xff0c;减号 4.设…...

NFTScan | 07.22~07.28 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.07.22~ 2024.07.28 NFT Hot News 01/ 数据&#xff1a;NFT 系列 Liberty Cats 地板价突破 70000 MATIC 7 月 22 日&#xff0c;据 Magic Eden 数据&#xff0c;NFT 系列 Liberty C…...

24年第三届钉钉杯大学生大数据挑战赛

...

探索分布式光伏运维系统的组成 需要几步呢?

前言 随着光伏发电的不断发展&#xff0c;对于光伏发电监控系统的需求也日益迫切&#xff0c;“互联网”时代&#xff0c;“互联网”的理念已经转化为科技生产的动力&#xff0c;促进了产业的升级发展&#xff0c;本文结合“互联网”技术提出了一种针对分散光伏发电站运行数据…...

做知识付费项目还能做吗?知识付费副业项目如何做?能挣多少钱?

hello,我是阿磊&#xff0c;一个20年的码农&#xff0c;6年前代码写不动了&#xff0c;转型专职做副业项目研究&#xff0c;为劳苦大众深度挖掘互联网副业项目&#xff0c;共同富裕。 现在做知识付费项目还能做吗&#xff1f; 互联网虚拟资源项目我一直在做&#xff0c;做了有…...

K210视觉识别模块学习笔记7:多线程多模型编程识别

今日开始学习K210视觉识别模块: 图形化操作函数 亚博智能 K210视觉识别模块...... 固件库: canmv_yahboom_v2.1.1.bin 训练网站: 嘉楠开发者社区 今日学习使用多线程、多模型来识别各种物体 这里先提前说一下本文这次测试实验的结果吧&#xff1a;结果是不太成…...

Go语言教程(一看就会)

全篇文章 7000 字左右&#xff0c; 建议阅读时长 1h 以上。 Go语言是一门开源的编程语言&#xff0c;目的在于降低构建简单、可靠、高效软件的门槛。Go平衡了底层系统语言的能力&#xff0c;以及在现代语言中所见到的高级特性。它是快速的、静态类型编译语言。 第一个GO程序…...

【Golang 面试 - 基础题】每日 5 题(十)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…...

OD C卷 - 密码输入检测

密码输入检测 &#xff08;100&#xff09; 给定一个密码&#xff0c;‘<’ 表示删除前一个字符&#xff0c;输出最终得到的密码&#xff0c;并判断是否满足密码安全要求&#xff1a; 密码长度>8;至少包含一个大写字母&#xff1b;至少包含一个小写字母&#xff1b;至少…...

【每日一题】【逆推法 + 贪心】【数学】造数 河南萌新联赛2024第(一)场:河南农业大学 A题 C++

河南萌新联赛2024第&#xff08;一&#xff09;场&#xff1a;河南农业大学 A题 造数 题目描述 样例 #1 样例输入 #1 2样例输出 #1 1样例 #2 样例输入 #2 5样例输出 #2 3做题思路 本题可以用逆推法 将三种操作反过来变为 − 1 , − 2 , / 2 -1 , -2 , /2 −1,−2,/2 …...

刷题计划 day4 【双指针、快慢指针、环形链表】链表下

⚡刷题计划day4继续&#xff0c;可以点个免费的赞哦~ 下一期将会开启哈希表刷题专题&#xff0c;往期可看专栏&#xff0c;关注不迷路&#xff0c; 您的支持是我的最大动力&#x1f339;~ 目录 ⚡刷题计划day4继续&#xff0c;可以点个免费的赞哦~ 下一期将会开启哈希表刷题…...

最高200万!苏州成都杭州的这些AI政策补贴,你拿到了吗?

随着全球人工智能技术的迅猛发展&#xff0c;地方政府纷纷出台相关政策以抢占未来科技的制高点。苏州 成都 杭州这三个城市更是推出了一系列AI政策补贴&#xff0c;旨在通过多方面支持&#xff0c;推动本地AI产业的发展。本文将带你了解目前不完全统计到的苏州 成都 杭州三地AI…...

使用两台虚拟机分别部署前端和后端项目

使用两台虚拟机分别部署前端和后端项目 1 部署方案2 准备两台虚拟机&#xff0c;并配置网络环境3 部署后端项目3.1 打包服务3.2 上传jar包到服务器3.3 集成Systemd3.3.1 移动端服务集成Systemd3.3.2 后台管理系统集成Systemd 4 配置域名映射5 部署前端项目5.1 移动端5.1.1 打包…...

Halcon学习之derivate_gauss

HALCON 图像处理库中的一个常用算子&#xff0c;用于计算图像的高斯导数。高斯导数是一种平滑导数&#xff0c;在计算过程中结合了高斯滤波&#xff0c;具有平滑噪声的效果。这个算子可以计算图像的不同导数&#xff0c;如梯度、一阶导数、二阶导数、以及 Hessian 行列式等。 …...

智能优化算法(三):遗传算法

文章目录 1.问题描述2.遗传算法2.1.算法概述2.2.编码操作2.3.选择操作2.4.交叉操作2.5.变异操作2.6.算法流程 3.算法实现3.1.MATLAB代码实现3.2.Python代码实现 4.参考文献 1.问题描述 \quad 在利用启发式算法求解问题时&#xff0c;我们常常需要应用遗传算法解决函数最值问题&…...

Docker部署nacos...用户名密码错误

前提 镜像选择v2.3.0版本&#xff0c;因为最新的没拉下来用的别的地方save load的镜像。 官方示例 官方文档 数据库脚本&#xff0c;直接去数据库新建数据库nacos吧&#xff0c;执行脚本&#xff0c;执行完成后&#xff0c;发现只有建表语句&#xff0c;查询得知&#xff0c…...

搭建Vue开发环境

一、下载Vue.js 进入官网教程安装 — Vue.js (vuejs.org) 下载开发版本到本地 二、安装 Vue Devtools 安装完成后...

富格林:防范虚假可信投资经验

富格林指出&#xff0c;现货黄金投资作为一种全球性的金融衍生品交易&#xff0c;吸引了无数投资者的目光。它不仅具备避险属性&#xff0c;还是资产配置中不可或缺的一部分。然而&#xff0c;要想在市场中防范虚假陷阱&#xff0c;投资者必须要掌握并且利用可信的投资经验。下…...

Intent的数据传递

在Android开发中&#xff0c;使用Intent在Activity之间传递数据是一种常见的方式。然而&#xff0c;Intent确实有一些大小和类型的限制。 Intent的限制 数据大小限制&#xff1a;虽然官方没有明确说明Intent的数据大小限制&#xff0c;但是Intent是通过Binder机制进行IPC&…...

【NPU 系列专栏 3.1 -- - ARM NPU 有哪些型号?】

请阅读【嵌入式及芯片开发学必备专栏】 文章目录 ARM X 系列和 Z 系列 NPU 详解ARM X 系列 NPUARM X 系列 NPU型号和算力ARM X 系列 NPU 应用场景ARM Z 系列 NPU 简介ARM Z 系列 NPU 型号和算力ARM Z 系列 NPU 应用场景SummaryARM X 系列和 Z 系列 NPU 详解 ARM 的 NPU(Neura…...

如何运行别人的vue项目

文章目录 如何运行别人的vue项目一、删除现有的node_modules二、npm换源三、清理缓存四、进行依赖安装五、运行服务器 如何运行别人的vue项目 一、删除现有的node_modules 二、npm换源 换成淘宝的镜像源 查看当前镜像源 npm config get registry更换淘宝镜像源 npm confi…...