Vue3知识点汇总

news2024/9/9 5:03:14

创建项目

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.id
  form.value.name = row.name
  form.value.place = row.place
  dialogVisible.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)
    // 定义异步action
    const 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} = counterStore

onMounted(() => {
  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 = 1
    disabled.value = false
    getGoodsList(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 * 2
    positionY.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-position
    background-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) => {
    // 所有表单项校验通过才为true
    if (valid) {
      const {account, password} = form.value
      await 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) => {
    // 所有表单项校验通过才为true
    if (valid) {
      const {account, password} = form.value
      await 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} = goods
    if (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 = null
    const time = ref(0)
    const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))
    // 传入倒计时的秒数
    const start = (currentTime) => {
        time.value = currentTime
        timer = 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 = index
  getOrderList()
}

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.items
    total.value = res.result.counts
} 

onMounted(() => getOrderList(params.value))
const tabChange = (type) => {
    params.value.page = 1
    params.value.orderState = type
    getOrderList(params.value)
}

const pageChange = (page) => {
    params.value.page = page
    getOrderList(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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1961492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rem实现屏幕适配(jQuery)

一、rem换算 1.根据视口宽度动态计算字体大小&#xff0c;如果宽度大于750px&#xff0c;则将字体大小设置为100px&#xff0c;否则按比例缩小。 tips:使用时记得引入jQuery.js // 在文档加载完成后执行函数&#xff0c;确保DOM已经准备就绪$(function () {// 定义一个自执行…

增量学习中Task incremental、Domain incremental、Class incremental 三种学习模式的概念及代表性数据集?

1 概念 在持续学习领域&#xff0c;Task incremental、Domain incremental、Class incremental 是三种主要的学习模式&#xff0c;它们分别关注不同类型的任务序列和数据分布变化。 1.1 Task Incremental Learning (Task-incremental) 任务增量学习&#xff0c;也称为任务增…

盐分反演关键:批量计算常用的盐分指数反演变量

盐分反演关键&#xff1a;批量计算常用的盐分指数反演变量 一、引言 盐分指数反演是遥感应用中的一个重要方面&#xff0c;尤其在农业和环境监测中有着广泛的应用。通过遥感影像&#xff0c;研究人员可以高效地获取和分析地表盐分信息&#xff0c;为土地管理和作物生产提供重…

YOLOX+PyQt5交通路口智能监测平台设计与实现

1.概述 交通要道的路口上人车穿行&#xff0c;特别是上下班早高峰&#xff0c;且时常发生交通事故。因此对交通路口的车流量和人流量的监测必不可少。 2.检测模型 使用的检测模型为YOLOX模型&#xff0c;模型权重为训练VOC数据集得来&#xff0c;其中包括了二十个类别&#…

ONLYOFFICE 协作空间 2.6 已发布:表单填写房间、LDAP、优化房间和文件管理等

更新后的 ONLYOFFICE 协作空间带来了超过 20 项新功能和优化&#xff0c;让工作更加高效和舒适。阅读本文了解详情。 表单填写房间 这次更新增加了一种新的房间类型&#xff0c;可在 ONLYOFFICE 协作空间中组织简单的表单填写流程。 通过表单填写房间&#xff0c;目前可以完成…

仓库物品与装备物品位置更换

一、装备物品与选中的仓库物品位置交换 1、准备工作 2、Inventory Items 3、给Warehouse添加Grid Layout Group组件 4、复制Inventory Items&#xff0c;设置Grid Layout Group组件 5、创建文本ItemName和ItemDescription 6、设置物品数据 (1) 创建 ItemData.cs using Syst…

Spring boot tomcat 读写超时时间设置

yaml配置 connection-timeout: 20000 server:port: 9898servlet:context-path: /testtomcat:connection-timeout: 20000max-connections: 250accept-count: 300 spring源码设置自定义tomcat参数 customizeConnector(connector); Overridepublic WebServer getWebServer(Serv…

【MySQL】表的约束{ 常见约束 空属性 默认值 列描述comment zerofill 主键 复合主键 自增长 唯一键 外键 }

文章目录 常见约束空属性默认值列描述commentzerofill主键复合主键自增长唯一键外键 2.总结 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&#xff0c;从业务逻辑角度保证数据的正确性。比…

MySQL基础练习题12-使用唯一标识码替换员工ID

题目&#xff1a;展示每位用户的 唯一标识码&#xff08;unique ID &#xff09;&#xff1b;如果某位员工没有唯一标识码&#xff0c;使用 null 填充即可。 准备数据 分析数据 题目&#xff1a;展示每位用户的 唯一标识码&#xff08;unique ID &#xff09;&#xff1b;如果…

一, 创建工程,引入依赖

一&#xff0c; 创建工程&#xff0c;引入依赖 文章目录 一&#xff0c; 创建工程&#xff0c;引入依赖创建工程工程间的关系的建立配置各个工程当中的 pow 配置信息&#xff0c;相关的依赖父工程(也就是总项目工程)的 pow 配置demo-module06-generate 模块中pow 配置&#xff…

基于IEC61499标准的在线工业编程平台open61499

基于IEC61499标准的在线工业编程平台open61499是一个专为工业自动化领域设计的编程环境&#xff0c;它遵循IEC 61499标准&#xff0c;为开发者提供了一种高效、灵活的方式来创建、配置和管理分布式控制系统&#xff08;DCS&#xff09;的应用程序。以下是对open61499的详细解析…

LeetCode热题 翻转二叉树、二叉树最大深度、二叉树中序遍历

目录 一、翻转二叉树 1.1 题目链接 1.2 题目描述 1.3 解题思路 二、二叉树最大深度 2.1 题目链接 2.2 题目描述 2.3 解题思路 三、二叉树中序遍历 3.1 题目链接 3.2 题目描述 3.3 解题思路 一、翻转二叉树 1.1 题目链接 翻转二叉树 1.2 题目描述 1.3 解题思路 根…

【多模态大模型】 BLIP in ICML 2022

一、引言 论文&#xff1a; BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 作者&#xff1a; Salesforce Research 代码&#xff1a; BLIP 特点&#xff1a; 该方法分别使用ViT和BERT进行图像和文本特征提取&am…

【changchain-community安装失败】‘EntryPoints‘ object has no attribute ‘get‘报错解决

在安装changchain-community时报错信息如下&#xff1a; WARNING: Keyring is skipped due to an exception: EntryPoints object has no attribute get ERROR: Could not find a version that satisfies the requirement changchain-community ERROR: No matching distributio…

进程间通信与线程间通信的方法汇总

目录 一、进程间通信机制 管道(pipe)&#xff1a; 命名管道(FIFO)&#xff1a; 消息队列(MQ)&#xff1a; 信号量(semaphore)&#xff1a; 共享内存(shared memory)&#xff1a; 信号(signal)&#xff1a; 内存映射(mapped memory)&#xff1a; 内存映射和共享内存的区…

华杉研发九学习日记20 LinkedHashMap TreeMap Arrays 函数式接口 方法引用

华杉研发九学习日记20 一&#xff0c;LinkedHashMap 与HashMap相比&#xff0c;key是有序的 Map<Integer,String> map new LinkedHashMap<Integer,String>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.…

GitHub Desktop commit文件到repository

1. Clone a repository到本地 2. 在本地仓库修改/添加需要提交的文件或者文档 3. 添加comments并commit 4. 提交完成&#xff0c;点击Push origin提交代码到Github远程仓库 上传成功后&#xff0c;刷新Github网站页面就会出现上传的项目

鸿蒙应用框架开发【自绘编辑框】 输入法框架

自绘编辑框 介绍 本示例通过输入法框架实现自会编辑框&#xff0c;可以绑定输入法应用&#xff0c;从输入法应用输入内容&#xff0c;显示和隐藏输入法。 效果预览 使用说明 1.点击编辑框可以绑定并拉起输入法&#xff0c;可以从输入法键盘输入内容到编辑框。 2.可以点击a…

SSM老人服务管理系统小程序-计算机毕业设计源码91022

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

跨网段 IP 地址通信故障分析

现如今计算机网络的规模和复杂性不断增加&#xff0c;跨网段通信成为网络运行中的常见需求。但如果设备处于不同网段且路由设置出现偏差时就会导致通信故障&#xff0c;严重影响网络的正常运行和数据传输。 1.跨网段通信的基本原理 跨网段通信依赖于路由器的路由功能。路由器根…