地图组件联动开发

地图组件联动开发

开发目标简介:

  • 1、点击按钮切换到场景2。
  • 2、在该场景的div中创建两个地图组件,分别用于展示原始地图和综合后地图数据。
  • 3、使两个地图组件互相影响,缩放或拖动其中一个子组件中的地图,另外一个也跟着变化

创建地图组件

开发目标:

  • 实现瓦片地图加载啊
  • 地图缩放控件
  • 地图图层切换控件

使用leaflet.chinatmsproviders新建多种图层

基本语法:L.tileLayer.chinaProvider('Geoq.Normal.PurplishBlue',{})

该方法的第一个参数定义了图层类型

首先使用该方法返回并接受图层

接着定义baseLayer对象,其中保存了图层名及其值也就是上述函数返回的图层

然后这是地图初始化需要的值mapValue,是一个对象,其中包括坐标系、中心点经纬度、缩放等级、图层类型、是否添加缩放空间等属性。

定义初始化函数,在挂载时调用。添加控件

1
2
3
4
5
6
7
8
9
10
11
12
13
// map是在初始化函数外定义的响应式数据
map.value = L.map('mapid', mapValue) //第一个参数是放置地图的容器的id名,第二个参数是上述的初始化值
L.control.layers(baseLayers, null).addTo(map.value)

// L.control.layers(baseLayers, null).addTo(map.value) 这行代码做了以下几件事:

// L.control.layers(baseLayers, null) 创建了一个新的图层控制器。baseLayers 是一个对象,
// 它的每个键值对定义了一个基础图层。在这个例子中,null 表示没有叠加层。

// .addTo(map.value) 将这个图层控制器添加到了地图上。map.value 应该是一个 L.Map 实例。

//返回L.Map构造的实例
return map.value

完整的代码,此时还未实现动图组件互动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div id="mapid" style="height: 100%"></div>
</template>

<script setup lang="ts">
import {ref, inject, onUnmounted, watch, nextTick, onMounted} from 'vue'
import L from 'leaflet'
let pscene: any = inject('pscene')
import 'leaflet.chinatmsproviders'

let map = ref<any>(null);
const normalm3 = L.tileLayer.chinaProvider(
'Geoq.Normal.PurplishBlue',
{}
)
// Google map layers
const normalMap = L.tileLayer.chinaProvider('Google.Normal.Map', {})
const satelliteMap = L.tileLayer.chinaProvider(
'Google.Satellite.Map',
{}
)
// 高德地图
const Gaode = L.tileLayer.chinaProvider('GaoDe.Normal.Map', {})
const Gaodimage = L.tileLayer.chinaProvider('GaoDe.Satellite.Map', { })


// Create base layers with different tile layers
const baseLayers = {
智图午夜蓝: normalm3,
谷歌地图: normalMap,
谷歌影像: satelliteMap,
高德地图: Gaode,
高德影像: Gaodimage,
}
//初始化地图
const mapValue = {
crs: L.CRS.EPSG3857,
center: [32.0603, 118.7969],
zoom: 12,
maxZoom: 18,
minZoom: 5,
layers: [normalMap],
zoomControl: true
}
const initMap = () => {
map.value = L.map('mapid', mapValue)

// Add control layers and zoom control to the map

L.control.layers(baseLayers, null).addTo(map.value)
return map.value
}

onMounted(() => {
const mapInstance = initMap()
//等初始化完成后,强制地图重新计算大小和位置
nextTick(() => {
// 强制地图重新计算大小和位置
if (mapInstance){
mapInstance.invalidateSize();
}
});
mapInstance.on('zoomend dragend', () => {
emit('map-moved', { center: mapInstance.getCenter(), zoom: mapInstance.getZoom() })
})

})

onUnmounted(() => {
if (map.value) {
map.value.remove();
map.value = null;
}
})

</script>

<style scoped>
#mapid {
height: 100%;
}
</style>

在父组件中导入组件

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="mainBox" >
<el-button size="small" style="margin:0px; border-color: aliceblue;" icon="CloseBold" @click="changeScene"></el-button>
<div class="mapOuterBox" >
<!-- 左边的地图组件 -->
<mapBox ref="mapBox1" class="leftMap" @map-moved="handelMapMoved2"></mapBox>
<!-- 中间的地图组件 -->
<mapBoxMid ref="mapBox2" class="midMap" @map-moved="handelMapMoved1"></mapBoxMid>
<div class="rightMap">文字展示</div>
</div>
</div>
</template>

互动效果实现

使用自定义事件emit,当地图发生缩放或者拖拽时,在附件中监听到该自定义事件。在父组件通知另外一个地图组件进行中心点和缩放等级的变化。

因此步骤如下

  • 在左中两个地图组件中,监听地图实例的缩放和拖拽事件
  • 监听到拖拽事件触发自定义事件 map-moved
  • 在父组件中分别获取到两个地图组件的实例,使用ref
  • 在父组件中的监听到map-move的后,分别调用handelMapmove2和handelMapmove1
  • 在父组件中定义handelMapmove2和handelMapmove1函数,前者用于调整中间地图组件的中心和缩放等级,后者用于调整左边地图组件。
  • 分别在两个地图组件中定义setMapView函数,并暴露出来,这样父组件中才能使用$refs调用该方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//地图组件中代码
//自定义事件
const setMapView = (center: L.LatLngExpression, zoom: number) => {
if (map.value) {
map.value.setView(center, zoom)
}
}
//暴露给父组件的方法
defineExpose({ setMapView })
//注意vue3的写法
const emit = defineEmits(['map-moved'])
onMounted(() => {
const mapInstance = initMap()
//等初始化完成后,强制地图重新计算大小和位置
nextTick(() => {
// 强制地图重新计算大小和位置
if (mapInstance){
mapInstance.invalidateSize();
}
});
mapInstance.on('zoomend dragend', () => {
emit('map-moved', { center: mapInstance.getCenter(), zoom: mapInstance.getZoom() })
})
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//父组件中代码
const mapBox1 = ref(null)
const mapBox2 = ref(null)

//重置第二个子组件的center和zoom
const handelMapMoved2 = (e:any)=>{
if(mapBox2.value){
console.log(mapBox2.value.setMapView(e.center, e.zoom))
}
}
const handelMapMoved1 = (e)=>{
if(mapBox1.value){
console.log(mapBox1.value.setMapView(e.center, e.zoom))
}

}

踩到的坑们

setup

1、在子组件的setup中定义的方法,在父组件中通过子组件实例(在父组件中的子组件标签中使用ref=’ziname’)ziname.的方法无法调用,必须在setup中暴露出该方法才行;暴露的方法也踩了坑,由于使用ts,一直提示不能使用return暴露函数。应该使用defineExpose({ setMapView })

2、地图总是加载左上角,因为组件的大小不是固定的px值,所以会发生变化,因此,需要在初始化完成后强制地图进行大小和位置计算。且在组件卸载后要清空地图的配置值。

3、将地图配置对象mapValue设置成响应式对象,对导致第一个被初始化的地图能加载出地图,而另外一个不能。因此放弃了响应式配置值实现互动的方法。

4、vue3中不能使用this,自定义方法,组件实例调用方法,均和vue2有差异

1
2
3
4
5
6
7
const emit = defineEmits(['map-moved'])
mapInstance.on('zoomend dragend', () => {
emit('map-moved', { center: mapInstance.getCenter(), zoom: mapInstance.getZoom() })
})

const mapBox1 = ref(null)
mapBox1.value.setMapView(e.center, e.zoom)

总结

以为自己掌握了知识,实践中却有很多问题。学好底层逻辑有助于快速发现问题和解决问题

登录界面开发

配置

组件

分别注册了两个组件,一个是login页面(src/views/login/index.vue)、另外一个是登陆后展示的layout页面(src/views/layout/index.vue)

路由配置

配置路由(src/router/index.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//引入createRouter和createWebHashHistory方法
import { createRouter, createWebHashHistory } from "vue-router";
//创建router实例
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
path:'/login',
component:()=>import('@/view/login/index.vue'), //导入组件
name:'login',
meta:{
title:'登录',
hidden:true,
icon:'el-icon-s-home'
}
},
{
path:'/',
name:'layout',
component:()=>import('@/view/layout/index.vue'),
meta: {
title: '', //菜单需要的标题
hidden: false,
icon: '',
}
}
],
//滚动行为,只有在history模式下才有用
// scrollBehavior(){
// return{
// left:0,
// top:0
// }
// }
})
export default router

在 Vue Router 中,router 和 route 是两个不同的概念。

  • router 是 Vue Router 的实例,它包含了一些方法和属性,如 push、> replace、go 等,用于控制路由的跳转和行为。你可以通过 this.$router 在 Vue 组件中访问到 router 实例。
  • route 是当前激活的路由信息对象,它包含了当前路由的路径、参数、查询字符串等信息。你可以通过 this.$route 在 Vue 组件中访问到当前的 route 对象。
  • router 和 route 的主要区别在于,router 是用于控制路由的跳转和行为的,而 route 是表示当前路由状态的信息对象。
  • 在大多数情况下,你可能需要使用 this.$router 来进行路由跳转,如 this.$router.push('/path')。而当你需要获取当前路由的信息,如路径、参数等,你可以使用 this.$route,如 this.$route.params.id。

在app.vue中使用router-view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="outer">
<router-view></router-view>
</div>
</template>
<script setup lang="ts">

</script>
<style scoped lang="scss">
.outer{
width: 100vw;
height:100vh;
margin: 0px;
padding: 0px;
}
</style>

静态页面

el-row/el-col

官网介绍:https://element-plus.org/zh-CN/component/layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
<div class="login_box">
<el-row>
{/* :span总和是24,各设置为12,那就是两列对半分。 */}
<el-col :span="12" :xs="24">
<el-form class="login_form">
<h2>地表异常预警平台</h2>
<el-form-item prop="username">
<el-input prefix-icon="User"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" prefix-icon="Lock"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login_but">登录</el-button>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12" :xs="0"></el-col>
</el-row>
</div>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
.login_box {
width: 100%;
height: 100%;
background-image: url('../../assets/login/bg.gif');
background-size: cover;
.login_form {
position: relative;
padding: 40px;
width: 60%;
top: 30vh;
.login_but{
width: 100%;
}
}
}
</style>


css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<style lang="scss" scoped>
.login_box {
//能用100%是因为父盒子有定义width和height。这里的百分比是相对于父盒子的
//如果父盒子没有明确的width和height,那么百分比会失效
width: 100%;
height: 100%;
background-image: url('../../assets/login/bg.gif');
//cover和contain都会保持图片的宽高比,区别在于contain会留白,cover会完全覆盖,
//也就是cover会导致,图片某部分被裁剪
background-size: cover;
.login_form {
//position属性可能有static、realative、sbsolute、sticky和fixed
position: relative;
padding: 40px;
width: 60%;
top: 30vh;
.login_but{
width: 100%;
}
}
}
</style>

positon的值

  • static:这是元素的默认值。静态定位的元素不会受到 topbottomleftright 的影响。
  • relative:元素按照正常的文档流进行定位,然后相对于它的正常位置进行偏移。偏移不会影响其他元素的位置。
  • absolute:元素脱离正常的文档流,并相对于最近的已定位父元素(position 属性为 relativeabsolutefixedsticky 的元素)进行定位。如果没有已定位的父元素,那么它会相对于 body 元素进行定位。
  • fixed:元素脱离正常的文档流,并相对于浏览器窗口进行定位。即使页面滚动,它仍然会停留在同一的位置。
  • sticky:元素在滚动范围内表现为 relative,而在滚动范围外表现为 fixed。也就是说,元素会“粘”在特定位置,然后随着页面滚动。

动态绑定

mock模拟服务器

使用mock模拟后端

  • 安装mock
    pnpm i mockjs --save
  • 模拟数据和服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    //导入mock库
    import Mock from 'mockjs'
    //模拟数据
    const user = Mock.mock({
    fixedUser: [
    {
    username: 'admin',
    password: 'admin111',
    avatar:
    ' https://tse4-mm.cn.bing.net/th/id/OIP-C.NNF59vntIv7760rOlX1zMgAAAA?w=187&h=188&c=7&r=0&o=5&cb=10&dpr=1.2&pid=1.7'
    },
    {
    username: 'user',
    password: 'user111',
    avatar:
    'https://tse1-mm.cn.bing.net/th/id/OIP-C.D34PjxR7ud-vxeTDvs5Z8gAAAA?w=135&h=150&c=7&r=0&o=5&cb=10&dpr=1.2&pid=1.7'
    }
    ]
    })

    export default()=>{
    Mock.mock('/api/login', 'post', (req: any) => {
    const { username, password } = JSON.parse(req.body)
    //从模拟数据中找
    const userItem = user.fixedUser.find(
    (item: any) => item.username === username && item.password === password
    )
    //如果用户数据在模拟数据中,return200,和token
    if (userItem) {
    return {
    code: 200,
    data: {
    //返回用户名,用户头像和token
    username: userItem.username,
    avatar: userItem.avatar,
    token: 'admin-token'
    }
    }
    } else {
    return {
    code: 400,
    message: '用户名或密码错误'
    }
    }
    })
    }
  • 在main.ts中引入mock
    1
    2
    import configureMockServer from './mock/user.ts';
    configureMockServer()

新建仓库

新建仓库用来存放用户数据,发起登录请求等

  • 安装pinia
    pnpm i pinia

  • 新建一个大仓库(src/store/index.ts)

    1
    2
    3
    import {createPinia} from 'pinia'
    const pinia = createPinia()
    export default pinia
  • 封装本地存储、获取和移除token的功能(src/utils/token)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //封装本地存储和读取数据的方法
    //本地存储数据
    export const SET_TOKEN = (token: string) => {
    localStorage.setItem('TOKEN', token)
    }
    //本地存储获取数据
    export const GET_TOKEN = () => {
    return localStorage.getItem('TOKEN')
    }
    export const REMOVE_TOKEN = () => {
    return localStorage.removeItem('TOKEN')
    }
  • 在main.ts中注册pinia

    1
    2
    import pinia from './store'
    app.use(pinia)
  • 创建用户仓库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //存放用户数据的仓库
    import { defineStore } from 'pinia'
    import { reqLogin } from '@/api/user'
    import { SET_TOKEN, GET_TOKEN, } from '@/utils/token'
    export const userStore = defineStore({
    id: 'user',
    state: () => {
    return {
    token: GET_TOKEN(),
    username: '',
    avatar: ''
    }
    },
    //异步操作
    actions: {
    //登录
    async userLogin(data: any) {
    const res = await reqLogin(data)
    if (res.code === 200) {
    //将用户名、头像存入仓库
    this.username = res.data.username
    this.avatar = res.data.avatar
    //将token存入localStorage
    SET_TOKEN(res.data.token)
    } else {
    //登录失败
    return Promise.reject(new Error(res.message))
    }
    }
    }
    })

创建并暴露用户登录api

在(src/api/user/index.ts)中书写相关代码

1
2
3
4
5
6
7
8
9
//导入二次封装的axios
import request from '@/utils/request'

// 枚举接口
enum API{
LOGIN_URL = '/login'
}
//暴露登录接口
export const reqLogin = (data:any)=> request.post<any, any>(API.LOGIN_URL,data)

v-model绑定

  • 双向绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <template>
    <div class="login_box">
    <el-row>
    <el-col :span="12" :xs="24">
    {/* 使用ref获取el-form组件 */}
    <el-form class="login_form" :model="LoginForm" :rules="rules" ref="loginFormRef">
    <h2>我的平台</h2>
    <el-form-item prop="username">
    {/* 使用v-model双向绑定 */}
    <el-input prefix-icon="User" v-model="LoginForm.username"></el-input>
    </el-form-item>
    <el-form-item prop="password">
    <el-input type="password" prefix-icon="Lock" v-model="LoginForm.password"></el-input>
    </el-form-item>
    <el-form-item>
    {/* 绑定点击事件发起登录请求 */}
    <el-button type="primary" class="login_but" @click="login">登录</el-button>
    </el-form-item>
    </el-form>
    </el-col>
    <el-col :span="12" :xs="0"></el-col>
    </el-row>
    </div>
    </template>
  • 点击事件触发login

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    <script setup lang="ts">
    import { ref } from 'vue'
    import { userStore } from '@/store/modules/user'
    import {useRouter, useRoute} from 'vue-router'
    import { ElNotification } from 'element-plus';
    //新建一个loginForm用于收集表单数据
    let LoginForm = ref({
    username: '',
    password: ''
    })
    //获取用户仓库
    let ustore = userStore()
    //获取路由器
    let $router = useRouter()
    let $route = useRoute()
    //获取表单组件
    let loginFormRef = ref()
    const login = async()=>{
    await loginFormRef.value.validate()
    //登录逻辑
    //首先,发起请求,将用户名和密码发送给后端
    //然后,后端返回一个token,将token存储在本地
    //最后,跳转到首页
    try{
    await ustore.userLogin(LoginForm.value)
    //RU: 如果有redirect参数,就跳转到redirect,否则跳转到首页
    let redirect: any = $route.query.redirect
    $router.push({ path: redirect || '/' })
    // 提示登录成功
    ElNotification({
    title: `欢迎回来${ustore.username}`,
    message: '欢迎回来',
    type: 'success'
    });
    } catch(error){

    // 提示登录失败
    ElNotification({
    type: 'error',
    message: (error as Error).message,
    });
    }
    }

    const validatorUsername = (rule:any , value:string, callback:any)=>{
    if(value== undefined){
    callback(new Error('用户名不能为空'))
    }
    if(value.length>=5 && value.length<=20){
    callback()
    }else{
    callback(new Error('用户名必须是5-20位的字符串'))
    }
    }

    const validatorPassword = (rule:any , value:string, callback:any)=>{
    if(value== undefined){
    callback(new Error('密码不能为空'))
    }
    if(value.length > 5){
    callback()
    }else{
    callback(new Error('密码长度必须大于5位'))
    }
    }
    //定义表达验证需要配置的对象
    const rules = {
    username: [
    //validatorUsername在上面定义
    {trigger:'blur', validator: validatorUsername}
    ],
    password: [
    {trigger:'blur', validator:validatorPassword}
    ]
    }
    </script>

规则校验

使用elementPlus规定好的关键字,实现进入规则校验

  • 在表单中绑定rules属性为rules对对象
    1
    <el-form class="login_form" :model="LoginForm" :rules="rules" ref="loginFormRef">
  • 在script中,定义表单验证的配置对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //这点代码中定义的前两个对象用于rules对象中
    const validatorUsername = (rule:any , value:string, callback:any)=>{
    if(value== undefined){
    callback(new Error('用户名不能为空'))
    }
    if(value.length>=5 && value.length<=20){
    callback()
    }else{
    callback(new Error('用户名必须是5-20位的字符串'))
    }
    }

    const validatorPassword = (rule:any , value:string, callback:any)=>{
    if(value== undefined){
    callback(new Error('密码不能为空'))
    }
    if(value.length > 5){
    callback()
    }else{
    callback(new Error('密码长度必须大于5位'))
    }
    }
    //定义表达验证需要配置的对象
    const rules = {
    username: [
    //validatorUsername在上面定义
    //失去焦点后触发
    {trigger:'blur', validator: validatorUsername}
    ],
    password: [
    {trigger:'blur', validator:validatorPassword}
    ]
    }
  • 在登录点击事件中,发起请求之前先进行规则校验,校验通过后才能发起请求

路由跳转

登录成功后跳转到页面

1
2
3
4
5
6
7
8
let $router = useRouter()
let $route = useRoute()

登录函数中:
登录成功后:
//如果有redirect参数,就跳转到redirect,否则跳转到首页
let redirect: any = $route.query.redirect
$router.push({ path: redirect || '/' })

项目集成配置

项目集成配置

1、集成element-plus

官网地址:https://element-plus.gitee.io/zh-CN/

安装命令: pnpm install element-plus @element-plus/icons-vue

1
2
3
4
5
6
//在入口文件中main.ts中全局安装element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
////@ts-ignore忽略当前文件ts类型的检测否则有红色提示(打包会失败)
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus, { locale: zhCn })

同样别忘了把icon图标都注册成全局组件,注册方式和svg图标全局化的方式一样

1
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

2、别名配置

在开发项目时文件与文件关系很复杂,因此需要给src文件配置一个别名
安装:pnpm i --save-dev @types/path

1
2
3
4
5
6
7
8
9
10
11
12
13
//在vite.config.ts中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve('./src') // 相对路径别名配置,使用 @ 代替 src
}
}
})
1
2
3
4
5
6
7
8
9
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
}
}
}

3、环境变量配置

项目开发过程中,至少会经历 开发环境、测试环境和生产环境(即正式环境) 三个阶段。不同阶段请求的状态(如接口地址等)不尽相同,若手动切换接口地址是相当繁琐且易出错的。于是环境变量配置的需求就应运而生,我们只需做简单的配置,把环境状态切换的工作交给代码。

项目根目录分别添加 开发、生产和测试环境的文件!

1
2
3
.env.development
.env.production
.env.test

文件内容

1
2
3
4
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/dev-api'
1
2
3
NODE_ENV = 'production'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/prod-api'
1
2
3
4
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'test'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/test-api'

配置运行命令:package.json

1
2
3
4
5
6
"scripts": {
"dev": "vite --open",
"build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production",
"preview": "vite preview"
},

通过import.meta.env获取环境变量

4、SVG图标配置

在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后,页面上加载的不再是图片资源,

安装依赖 pnpm install vite-plugin-svg-icons -D

在vite.config.ts中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
return {
plugins: [
createSvgIconsPlugin({
// 指定要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定 symbolId 格式
symbolId: 'icon-[dir]-[name]',
}),
],
}
}

main.ts入口文件中引入 import 'virtual:svg-icons-register'

将svg封装为全局组件

步骤如下

  • 创建一个Svgicon组件 src/components/SvgIcon.vue
  • 在src/components文件夹下创建一个index.t文件,用于注册全部全局文件
  • 在入口文件引入src/index.ts文件,通过app.use方法安装自定义插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/components/SvgIcon.vue
<template>
<div>
<svg :style="{ width: width, height: height }">
<use :xlink:href="prefix + name" :fill="color"></use>
</svg>
</div>
</template>

<script setup lang="ts">
defineProps({
//xlink:href属性值的前缀
prefix: {
type: String,
default: '#icon-'
},
//svg矢量图的名字
name: String,
//svg图标的颜色
color: {
type: String,
default: ""
},
//svg宽度
width: {
type: String,
default: '16px'
},
//svg高度
height: {
type: String,
default: '16px'
}

})
</script>
<style scoped></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/componebts/index.ts
import SvgIcon from './SvgIcon/index.vue'
//分页器
import Pagination from './Pagination/index.vue'
//一个仓库
import Category from './Category/index.vue'
//引入element-plus提供全部图标组件
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
//全局对象
const allGloablComponent: any = { SvgIcon, Pagination, Category }
//对外暴露插件对象
export default {
//务必叫做install方法
install(app: any) {
//注册项目全部的全局组件
Object.keys(allGloablComponent).forEach((key) => {
//注册为全局组件
app.component(key, allGloablComponent[key])
})
//将element-plus提供图标注册为全局组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
}
}
1
2
3
// main.ts
import gloablComponent from './components/index';
app.use(gloablComponent);

5、集成sass

安装sass和sass-loader: pnpm install --save-dev sass sass-loader

接下来我们为项目添加一些全局的样式

在src/styles目录下创建一个index.scss文件,当然项目中需要用到清除默认样式,因此在index.scss引入reset.scss

1
@import reset.scss

但是你会发现在src/styles/index.scss全局样式文件中没有办法使用$变量.因此需要给项目中引入全局变量$.

在./src/style 中创建一个variable.scss文件! 在这里可以写入一些全局可用的样式变量。

在vite.config.ts文件配置如下:

1
2
3
4
5
6
7
8
9
10
export default defineConfig((config) => {
css: {
preprocessorOptions: {
scss: {
javascriptEnabled: true,
additionalData: '@import "./src/styles/variable.scss";', //;不要忘记
},
},
},
})

6、axios二次封装

安装:pnpm install axios

在开发项目的时候避免不了与后端进行交互,因此我们需要使用axios插件实现发送网络请求。在开发项目的时候我们经常会把axios进行二次封装。

目的:
1:使用请求拦截器,可以在请求拦截器中处理一些业务(开始进度条、请求头携带公共参数)
2:使用响应拦截器,可以在响应拦截器中处理一些业务(进度条结束、简化服务器返回的数据、处理http网络错误)

在根目录下创建utils/request.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import axios from "axios";
import { ElMessage } from "element-plus";
//创建axios实例
let request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000
})
//请求拦截器
request.interceptors.request.use(config => {
return config;
});
//响应拦截器
request.interceptors.response.use((response) => {
return response.data;
}, (error) => {
//处理网络错误
let msg = '';
let status = error.response.status;
switch (status) {
case 401:
msg = "token过期";
break;
case 403:
msg = '无权访问';
break;
case 404:
msg = "请求地址错误";
break;
case 500:
msg = "服务器出现问题";
break;
default:
msg = "无网络";

}
ElMessage({
type: 'error',
message: msg
})
return Promise.reject(error);
});
export default request;

7、API接口统一管理

在src目录下创建api文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//示例
//统一管理咱们项目用户相关的接口

import request from '@/utils/request'

import type {

loginFormData,
loginResponseData,
userInfoReponseData,

} from './type'

//项目用户相关的请求地址

enum API {
LOGIN_URL = '/admin/acl/index/login',
USERINFO_URL = '/admin/acl/index/info',
LOGOUT_URL = '/admin/acl/index/logout',

}
//登录接口
export const reqLogin = (data: loginFormData) =>
request.post<any, loginResponseData>(API.LOGIN_URL, data)
//获取用户信息

export const reqUserInfo = () =>

request.get<any, userInfoReponseData>(API.USERINFO_URL)

//退出登录
export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)

数组的方法

数组的方法

some

检测数组中所有元素是否满足制定条件,满足返回true,否则false

该方法依次检测数组中每个元素。如果有一个元素满足条件,则返回true,剩余的元素不会再执行检测。如果没有满足条件的元素,则返回false

一真即真

⚠️注意:some()不对空数组进行检测、some()不改变原数组。

every

检测数组中所有元素是否符合指定条件,符合返回true,否则false

该方法根据指定条件检测数组中所有元素。如果监测到有一个元素不满足,则返回false,且剩余元素不会再次检测。如果所有条件都满足则返回true。

一假即假 ,而且只要有一个元素是假,其后面的元素将不再遍历

⚠️注意:every()不对空数组进行检测、every()不改变原数组。

https://juejin.cn/post/6996892724100562974

forEach

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

forEach(): 没有返回值,本质上等同于 for 循环,对每一项执行 function 函数。map是返回一个新数组,原数组不变,forEach 是不改变原数组(尽管回调函数 callbackFn 在被调用时可能会改变原数组)。
不支持 continue,用 return false 或 return true 代替。
不支持 break,用 try catch/every/some 代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
arr = [1,2,3,4,5,6]
arr.forEach(item=>console.log(item)) //1 2 3 4 5 6
//实现 continue
arr.forEach((item)=>{
if (item == 3){
return;
}
console.log(item) //1 2 4 5 6
})
arr.some((item)=>{
if(item == 3){
return;
}
console.log(item)
})
//实现break
arr.every((item)=>{
if(item == 3){
return false;
}
else{
console.log(item)
return true
}
})

try {
arr.forEach((item) => {
if (item == 3) {
throw new Error('wrong')
}
console.log(item)
})
} catch (e) {
console.log(e)
}

map

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
返回值是一个新数组,每个元素都是回调函数的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 语法:
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
// 参数:
// callback 生成新数组元素的函数,使用三个参数:
// currentValue callback 数组中正在处理的当前元素。
// index可选 callback 数组中正在处理的当前元素的索引。
// array可选 callback map 方法被调用的数组。
// thisArg可选 执行 callback 函数时使用的this 值。

arr = ['1', '2',' 3', '4', '5', '6']
const arry = arr.map( str => parseInt(str))
console.log(arry)

https://blog.csdn.net/qq_43437571/article/details/95634526

filter

和map类似,Array的filter也接收一个函数。但是和map不同的是, filter把传入的函数依次作用于每个元素,然后根据返回值是 true 还是false决定保留还是丢弃该元素。

filter 接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是变量arr
return true;
});

//巧用filter进行数组去重
arr = [1, 2, 3, 3, 4, 5, 2, 6, 1, 7]
const r = arr.filter((item, index, self) => {
if (self.indexOf(item) === index) {
return true
}
})
console.log(r)

https://blog.csdn.net/qq_39207948/article/details/80357367

reduce

图 0

https://blog.csdn.net/qq_43441476/article/details/105559561