跳到主要内容位置

vue3: 9 篇

查看所有标签(分类)

解决 Vite 项目启动时端口重复问题的总结


背景#

在前端开发的江湖上,Vite 就像一个轻盈迅捷的剑客,以“快”和“爽”闻名。默认栖身于本地 3000 端口,简直是开发者的“专属包间”。但江湖人多,难免遇到这种情况:你刚起剑练招,发现隔壁的同门师兄已经占了你的包间。于是,你只能抱着代码,哼哼唧唧换个地方继续修炼——这种“被端口抢占”的故事,简直就是开发者的日常笑话!

例如:

  1. 多人协作:团队中的多个开发者同时运行了 Vite 项目,使用了相同的端口。
  2. 多项目运行:本地同时启动了多个 Vite 项目或其他服务(如 Webpack、Node.js 服务器),导致端口冲突。
  3. 默认配置不足:Vite 默认没有处理端口占用的逻辑,遇到冲突会直接报错,开发中断。

为了解决这个问题,我研究了几种方法,最终采用了 detect-port 插件,实现了动态检测和自动调整端口的功能,极大地提升了开发效率。


解决方案#

1. 常见解决方法#

1.1 *手动更改端口**#

vite.config.ts 文件中修改 server.port 的值。例如:

export default defineConfig({
server: {
port: 3001 // 手动指定一个新的端口
}
})

缺点:需要手动调整端口,效率较低,且不方便多人协作。

1.2 尝试端口范围#

一些开发者会通过运行脚本动态尝试多个端口,但编写脚本可能较繁琐且不直观。

1.3 使用第三方工具#

利用工具如 detect-portportfinder,自动检测端口是否被占用并返回可用端口。

2. Detect-port 的解决方案(本人的解决方法)#

我最终采用了 detect-port 插件,因为它简单易用,能够自动检测当前端口是否被占用,并返回一个可用的端口。

Step 1: 安装 detect-port#

在项目根目录下运行以下命令:

pnpm install detect-port --save-dev

或者使用 npm/yarn 安装:

npm install detect-port --save-dev
yarn add detect-port --dev

Step 2: 配置 Vite 项目#

修改 vite.config.ts 文件,引入 detect-port,实现动态端口检测:

import { defineConfig } from 'vite'
import detectPort from 'detect-port'
export default defineConfig(async () => {
const DEFAULT_PORT = 3000 // 默认端口
// 使用 detect-port 检测端口是否被占用
const port = await detectPort(DEFAULT_PORT)
console.log(`Selected port: ${port}`) // 输出实际选用的端口
return {
server: {
host: '0.0.0.0', // 允许局域网访问
port, // 动态端口
open: true, // 自动打开浏览器
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
}
})

Step 3: 效果展示#

  • 如果默认端口(如 3000)已被占用,detect-port 会自动检测下一个可用端口,例如 3001 或 3002。
  • 启动时会输出实际使用的端口号:
Selected port: 3001

这样,我们无需手动修改端口设置,提升了开发效率。lalala~

3. 优化点#

在实践中,我还添加了一些优化:

3.1 添加日志输出#

使用 console.log 记录端口信息,便于调试:

console.log(`Using port ${port}. To change, edit vite.config.ts`)

3.2 范围检测(可选)#

通过扩展 detect-port,可尝试在特定范围内寻找端口:

const port = await detectPort(3000)
if (port !== 3000) {
console.log(`Port 3000 is in use. Using port ${port} instead.`)
}

小结#

问题总结#

Vite 默认端口冲突的问题在多人协作和多项目运行的场景中非常常见。传统的手动解决方案需要频繁调整配置,显得麻烦且低效。

我的解决方案#

利用 detect-port 插件,可以轻松实现动态端口检测和分配:

  • 自动检测端口冲突。
  • 动态分配可用端口,提升开发效率。
  • 配置简单、易于扩展。

未来优化#

  • 扩展端口范围检测:可以尝试自定义检测端口范围(如 3000-3100)。
  • 集成其他工具:例如结合 vite-plugin-inspect,提供更多启动信息。
  • 多环境支持:为不同环境(开发、测试、生产)定制端口策略。

在团队开发中,这种自动化的方式能够显著减少端口冲突问题,提升协作效率,非常值得推广。

Vue3中watch中props监听加箭头函数与不加箭头函数的区别

1 前言#

今天写项目时,遇到一个问题,我需要打开点击一个按钮打开一个弹框页面,然后通过watch去监听传进来的值,但是呢,写了watch我点击按钮只有首次打开能够监听到,尽管加上了deep: true页面也不能监听到变化,点击效果如下图:

image-20240903172257604

2 代码实例#

直接上我的关键代码,这个是我弹框页面的代码:

<template>
<div class="content_container">1111</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, onActivated } from 'vue'
import { ElMessageBox } from 'element-plus'
const props = defineProps(['currentRow'])
console.log('props', props)
watch(
props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal)
},
{
immediate: true,
deep: true
}
)
</script>

解决后的代码:

<template>
<div class="content_container">1111</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, onActivated } from 'vue'
import { ElMessageBox } from 'element-plus'
const props = defineProps(['currentRow'])
console.log('props', props)
watch(
() => props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal)
},
{
immediate: true,
deep: true
}
)
</script>

其实,只是修改了一句话,多加了一个箭头函数~ 就可以了!他就可以每次进来就都监听了,为啥呢?请看下面的代码精读环节

3 代码精读#

3.1 直接监听 props 属性#

javascriptwatch(
props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal);
},
{
immediate: true,
deep: true
}
);

当你直接监听 props.currentRow 时,watch 函数会尝试将 props.currentRow 当作一个响应式引用进行监听。然而,props 本身并不是一个响应式引用,而是一个对象。因此,这种方式可能会导致以下问题:

  • 非响应式引用: 如果 props.currentRow 是一个简单的值(如字符串或数字),那么 watch 可能无法正确地监听到它的变化。
  • 对象或数组内部属性变化: 即使设置了 deep: truewatch 仍然可能无法正确地监听到对象或数组内部属性的变化。

3.2 使用箭头函数监听 props 属性#

javascriptwatch(
() => props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal);
},
{
immediate: true,
deep: true
}
);

使用箭头函数 () => props.currentRow 可以确保每次 props.currentRow 发生变化时,都会重新计算并触发 watch 回调。这种方式更可靠,因为它明确地告诉 watch 如何获取最新的 props.currentRow 值。

4 总结#

在正常的开发中,我们应该使用箭头函数组合watch来进行props值的变化,来确保每次 props 变化时都能重新计算。

Vue3使用Element Plus单个Tag标签文字过长自动换行代码实现

1 需求引入#

直接上图,原先程序显示效果是这样:

image-20240807172107258

由上图可见,element plus 的 tag 标签组件里面内容过长时,把页面都撑开了,不能自动换行,我的 tag 标签代码原先是这样写的

<div style="width: 100%; margin-bottom: 5px">
<el-tag v-if="uploadMessage.successMessage.length > 0" :type="'success'" effect="plain">
{{ uploadMessage.successMessage }}
</el-tag>
</div>

2 解决需求,代码实现#

先看解决效果图:

image-20240807172500463

如何解决的呢,只要我们再单独写一下 css 的样式就 ok 了,看下面的 css 样式代码:

.el-tag {
white-space: normal;
height: auto;
padding: 2px;
display: inline-block;
}

好了,直接加上上面这段 css 代码就 ok 了。

3 小结#

我当时用这个标签是显示错误信息的,后来发现 element 的 Alert 组件用于现实错误信息才比较合适,因为那个有个关闭的按钮,既然已经写了,就记录一下吧~ 希望对看文章的你也有些用吧 ~ >_< ~

router.go()妙用

1 背景#

最近在做动态路由相关的功能实现,涉及到一些页面跳转的功能,我们都知道返回上一页用的是 router.go(-1),它的意思就是说返回上一个路由地址。

但是,现在有这么一个场景(技术栈:vue3+"vue-router": "^4.1.6"),比如原来我是在路由地址 A,然后我点击了路由 B,我会在前置路由钩子里面先检测当前访问的路由地址 B,如果 B 没有权限访问那么就会默认跳转地址 C(这个是 404 页面),在地址 C 的 404 页面里面有个返回上一页按钮。

我给返回上一页按钮写的事件如下:

<script setup lang="ts">
import { useRouter } from 'vue-router'
import { House } from '@element-plus/icons-vue'
const router = useRouter()
const goIndex = () => {
router.go(-1)
}
</script>
<template>
<div class="container">
<img src="../assets/picture/common/404.png" alt="" class="img" />
<div class="text">当前页面不存在</div>
<el-link type="primary" :icon="House" @click="goIndex">回到上一页</el-link>
</div>
</template>

可是呀,事实情况总是事与愿违,但我执行router.go(-1) 这句话的时候并不能直接返回我原来的路由地址(A 地址),而是返回了那个没有权限访问的 B 地址(尽管页面没有真正跳转到 B 地址,但是路由地址里面会显示),而且再点击一下才会真正返回到想要的上一页路由地址 A 页面。

显然,这不是我想要的,那咋办呢,请看我下面的实现:

2 突发奇想的实现#

我想既然跳转一次不行,那我跳转 2 次呢,实现如下:

const goIndex = () => {
router.go(-1)
router.go(-1)
}

哈哈,我运行了上面这段代码,显然不行,于是乎,我又改成了下面这样,

const goIndex = () => {
router.go(-2)
}

果然,将-1 改成了-2,就可以直接跳转到前两页的位置,竟然就是这么简单!

3 小结#

果然还是得实践。

vue3+element puls upload组件回显图片base64的实现

1 背景#

最近遇到个需求,需要基于 vue3+element plus 的 upload 组件回显图片,通常我们是通过后端直接返回的 url 来回显就行了,而且在 element plus 也给出了示例:

image-20240123171340359

不过,o_O,我们下面将要以 base64 的形式来填充,其实也很简单,自己构造一个这样的对象就行了,url 里面放我们 base64 字符串,然后其他的造成即可,下面请看我的实现样例:

2 实现#

image-20240126173506038

  • 构造一个用 base64 字符串填充的文件列表
let fileList = ref([
{
// 这是文件名字
name: '文件名1',
// 这里是我自己定义的自定义属性,可有可无
fileId: '1',
// 这里是base64字符串,咳咳我们后端返回的格式有点特殊,所以我又给转换了一下,成为真正的base64字符串
url: 'data:image/png;base64,' + new BaseTool().arrayBufferToBase64(temp?.arrayBuffer)
}
])
  • 将图片列表渲染到组件里
<el-upload action="#" list-type="picture-card" v-model:file-list="fileList" :auto-upload="false">
<el-icon><Plus /></el-icon>
<template #file="{ file }">
<div>
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<el-icon><zoom-in /></el-icon>
</span>
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleRemove(file, item.dataValue)"
>
<el-icon><Delete /></el-icon>
</span>
</span>
</div>
</template>
</el-upload>

重点就是替换上面 fileList 就可。

3 小结#

ok 啦。

vue3+element plus实现查询条件展开和收起功能

1 需求来源#

image-20231129134425954

如图所示,这样一个查询页面,上面的条件太多,使得下面的列表展示的空间就变得很小了。所以,需要有一个东西控制,当条件太多时,就展示一个展开/收起按钮,可以控制查询条件的展开和收起。

2 实现效果图#

我们先直接来看下最终实现的效果图

screenshots

如果这就是你想要的,可以继续看下面的实现关键代码

3 具体实现关键代码#

<script setup lang="ts">
const conditionFold = ref(true)
const conditionInitShowLength = 6
const areConditionFold = () => {
conditionFold.value = !conditionFold.value
}
</script>
<template>
<div class="header customDiv">
<el-form ref="formRef" :inline="true" :model="formDataConfig" class="demo-form-inline">
<el-row>
<el-col :span="6">
<el-form-item label="查询逻辑">
<el-select v-model="filtersLogic" placeholder="默认同时符合">
<el-option label="同时符合" :value="0" />
<el-option label="部分符合" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col
:span="6"
v-for="(item, index) in formDataConfig.slice(
0,
conditionFold ? conditionInitShowLength : formDataConfig.length
)"
:key="item.key"
>
<el-form-item :label="item.label" :prop="`[${index}]value`">
<el-date-picker
v-if="item.type === 'date'"
v-model="item.value"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm:ss"
date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss"
/>
<el-input
v-else-if="item.type === 'input' || !item.type"
v-model="item.value"
placeholder="请输入"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button type="primary" @click="onSubmit(formRef)">查询</el-button>
<el-button type="primary" @click="resetForm(formRef)">重置</el-button>
<el-button
v-if="formDataConfig.length > conditionInitShowLength"
type="primary"
link
@click="areConditionFold"
>
{{ conditionFold ? '展开' : '收起' }}
<el-icon v-if="conditionFold"><ArrowDown /></el-icon>
<el-icon v-else><ArrowUp /></el-icon>
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>

ok,代码就是上面的代码,可能少了一些变量,但是思路还是挺清晰的,主要就是通过conditionFold来控制按钮是否展示,conditionInitShowLength来控制收起时显示的默认长度。

4 技术小结#

当我们使用“展开/收起”按钮时,需要搭配row、col,列数需要是固定的,不然我们不知道“展开/收起”按钮展示及切换的时机。

vue3+element plus图片预览直接点击按钮就显示图片的预览形式

1 需求#

直接上需求:

我想要直接点击下面这个“预览”按钮,然后呈现出预览图片的形式

image-20231120090930791

也就是点击完“预览”按钮,会像下面这样:

image-20231120091054028

ok,需求知道了,下面让我们来看看如何实现吧 ~

2 实现#

template 部分

<el-button type="primary" size="small" @click="handlePreview(scope.$index, scope.row)">预览</el-button>
<!-- 图片预览 -->
<el-image-viewer v-if="showImagePreview" :zoom-rate="1.2" @close="closePreview" :url-list="imgPreviewList" />

script 部分

const imgPreviewList = ref < any > []
const showImagePreview = ref(false)
const currentBase64FileData = reactive({
base64: '',
name: ''
})
const handlePreview = async (index: number, row: any) => {
let res = await handleDownload(index, row, true)
currentBase64FileData.base64 = 'data:image/png;base64,' + res?.base64
currentBase64FileData.name = res?.name
showImagePreview.value = true
// 下面数组里可以放一个url,如'https://raw.githubusercontent.com/JACK-ZHANG-coming/map-depot/master/2023image-20231120091054028.png',我这里放的是一个base64数据,也可以用来显示图片
imgPreviewList.value = [currentBase64FileData.base64]
}
const closePreview = () => {
imgPreviewList.value = []
showImagePreview.value = false
}

ok,经过上面简单几句代码,就实现了“点击按钮直接显示图片的预览形式”啦 ~

3 技术小结#

技术栈: vue3+ element plus,其中 vue3 采用的是 script setup 组合式语法的形式。

这部分功能其实在 element plus 官方文档中有写,

https://element-plus.org/zh-CN/component/image.html#image-viewer-api

image-20231120100147616

不同的是,这里 element plus 并没有给出实际样例,只是用文字描述了下,咱就是说,家人们,这坑不坑,我还是看了别人的博客才知道这块的用处>_<

Vue3+Vue Router跳转相同路由监听页面刷新并执行某个操作

1 起源#

最近遇到了个这样的需求,大概就是:点击某个按钮,进入某个页面,然后再在这个页面执行某个操作(比如请求某个接口、赋初始值啥的)。

image-20231116163902611

这个需求看似简单,其实也不难。但是,我遇到了个问题,就是当在那个页面点击这个按钮的时候,因为跳转路由路径是一样的原因,页面是不会刷新的,那我怎么判断我是否我是否点击了那个按钮并且跳到了这个页面呢?

于是,我想到了路由传参,通过路由传参的方式,判断这个参数是否变化了,变化了就代表这个路由再次进入了。

2 解决方案#

用 query 的方式传参,参数附上时间戳,这样每进来一次都是不同的参数

点击按钮如下操作:

const router = useRouter()
const goDocumentNotification = () => {
router.push({
path: `/documentNotification`,
query: {
t: Date.now()
}
})
}

在进入的那个页面增加如下代码:

// 使用 watch 监听 route 的变化
watch(
() => route.query.t,
(newPath, oldPath) => {
// 路由变化,执行相应操作
query()
}
)

ok,经过上面的操作便可以在跳转相同路由下,监听页面刷新并执行某个操作啦。

3 知识扩展-关于 Vue Router 路由传参的几种常用方式#

说到这里,vue-router 传参的几种方式也顺便总结一下吧

3.1 params 传参(显示参数)#

浏览器里路由地址显示为这样:

http://127.0.0.1:5190/drs/index.html#/documentNotification/0

声明式:

// 子路由配置 { path: '/documentNotification/:id?', // ?代表这个参数为可传可不传 name: 'documentNotification', component: () => import('@/views/documentNotification/index.vue'), meta: { title: '发放通知', } } // 父路由组件
<router-link :to="/documentNotification/123">进入documentNotification路由</router-link>

编程式:

// 子路由配置
{
path: '/documentNotification/:id?', // ?代表这个参数为可传可不传
name: 'documentNotification',
component: () => import('@/views/documentNotification/index.vue'),
meta: {
title: '发放通知',
}
}
// 父路由编程式传参(一般通过事件触发)
router.push({
path:'/documentNotification/${yourParam}',
})

关于参数的获取:

route.params.id

3.2 params 传参(不显示参数)#

由于从 Vue Router 的 2022-8-22 这次更新后,便不能再用这种方式来写,关于不显示参数的传参,可以参考下面这篇博客:

https://blog.csdn.net/m0_57033755/article/details/129927829

3.3 query 传参#

浏览器里路由地址显示为这样:

http://localhost:3000/#/documentNotification?t=1700140985974

声明式:

//子路由配置
{
path: '/documentNotification',
name: 'documentNotification',
component: () => import('@/views/documentNotification/index.vue'),
meta: {
title: '发放通知'
}
}
//父路由组件
<router-link :to="{name:'documentNotification',query:{t:123}}">进入documentNotification路由</router-link>

编程式:

//子路由配置
{
path: '/documentNotification',
name: 'documentNotification',
component: () => import('@/views/documentNotification/index.vue'),
meta: {
title: '发放通知'
}
}
router.push({
path: `/documentNotification`,
query: {
t: Date.now()
}
})

关于参数的获取:

route.query.t

4 结语#

ok ,就到这里啦,对此你有何看法或想法呢,欢迎提出讨论呀~