微语 微语:代码适合中午敲,早晚出BUG

基于Vue3,webscoket聊天室 Vue

基于Vue3,webscoket聊天室

<template>
    <MyCart title="聊天室">
        <template #button>
            <el-button @click="chat" type="primary">加入聊天室</el-button>
        </template>
        <template #default>
            <div class="info" ref="scrollContainer">
                <div class="people"><span>在线人数:{{ number }}</span> </div>
                <div :class="{ 'left': item.state === 0, 'right': item.state === 1 }" v-for="(item, index) in datainfo"
                    :key="index">
                    <div class="img">
                        <img :src="item.head" alt="头像">
                    </div>
                    <div class="title">
                        <div class="Usertitle"> <span>{{ item.userName }}</span></div>
                        <div class="text">
                            <p>{{ item.data }}</p>
                        </div>
                    </div>
                </div>
            </div>
            <div class="info-foot">
                <el-input v-model="input" placeholder="请输入聊天内容" @keyup.enter="handleEnter" />
                <div class="send"> <el-button @click="sends" type="primary">点击发送</el-button></div>
            </div>
        </template>
    </MyCart>
</template>

<script lang="ts" setup>
import { ref, watch } from 'vue';
import MyCart from '../components/MyCard.vue'
let input = ref('')
let datainfo = ref<Array<msg>>([])
let number = ref()//总人数
let img = "https://t9.baidu.com/it/u=954632137,1492850490&fm=218&app=126&size=f242,150&n=0&f=JPEG&fmt=auto?s=9628FE054373C7CE5406AD6D0300506B&sec=1691600400&t=75097cd4d59b3feec556752f3da3b042"
const scrollContainer = ref(null);
interface msg {
    data: string//消息内容
    head: string//头像
    type?: string//是消息
    userName: string//用户名
    state: number//状态
}

// 1.与服务器建立连接
let ws: WebSocket
let chat = () => {
    console.log('加入聊天室')
    ws = new WebSocket('ws://129.211.169.131:10009/globalchat')
    // 3.监听服务器推送事件
    ws.onopen = () => {
        console.log('与服务器建立成功');
        ws.send(JSON.stringify({
            type: 'name',
            data: "小诸葛",
            head: "https://t9.baidu.com/it/u=954632137,1492850490&fm=218&app=126&size=f242,150&n=0&f=JPEG&fmt=auto?s=9628FE054373C7CE5406AD6D0300506B&sec=1691600400&t=75097cd4d59b3feec556752f3da3b042"        //string:头像url
        }))
    }
    // 监听服务器推送事件
    ws.onmessage = (e) => {
        let data = JSON.parse(e.data)
        console.log(data);
        if (typeof data.data == 'number') {
            number.value = data.data
        }
        if (data.type === 'message') {
            if (data.userName === '小诸葛') {
                data.state = 1
            }
            else {
                data.state = 0
            }
            datainfo.value.push(data)
        }
        console.log(datainfo, '123');
        // ElNotification({
        //     title: '通知',
        //     message: data.data,
        // })
    }

}

let msg = () => {
    ws.send(JSON.stringify({
        type: 'message',   //string: 固定message
        data: input.value
    }));
    input.value = ''
}
let sends = () => {
    msg()
}
// 回车
const handleEnter = () => {
    msg()
}
watch(datainfo.value, () => {
    if (scrollContainer.value) {
        setTimeout(() => {
            const container = scrollContainer.value as unknown as HTMLElement;
            if (container) {
                container.scrollTop = container.scrollHeight;
            }
        }, 0);
    }
});
 //处理破图

</script>

<style lang="scss" scoped>
.info-foot {
    margin-top: 10px;
}

.info {
    width: 100%;
    height: 590px;
    background-color: #35a2ef;
    padding: 20px;
    overflow-y: scroll;
    scroll-behavior: smooth;
    /* 添加平滑滚动效果 */
    border-radius: 5px;

}

.el-card__body {
    position: relative;
}

.people {
    position: absolute;
    right: 41px;
    top: 108px;
    min-width: 112px;
    height: 30px;
    border-radius: 15px;
    background-color: #fff;
    font-size: 12px;
    display: flex;
    align-items: center;
    padding: 0 12px;
    justify-content: center;
}

.send {
    text-align: right;
    margin-top: 10px;

    button {
        width: 150px;
    }
}

.left,
.right {
    width: auto;
    display: flex;
    margin-bottom: 30px;

    .img {
        border-radius: 50%;
        overflow: hidden;
        width: 50px;
        height: 50px;
    }

    img {
        width: 50px;
        height: 50px;
    }

    .title {
        display: flex;
        flex-direction: column;
        justify-content: center;
        margin: 0 8px;
        max-width: 40%;
    }

    .Usertitle {
        color: #fff;
    }

    .text {
        background-color: #fff;
        padding: 5px;
        border-radius: 8px;
        min-height: 34px;
        min-width: 50px;

        p {
            word-break: break-all;
        }
    }
}

.right {
    flex-direction: row-reverse;

    .Usertitle {
        text-align: right;
    }
}

/* 设置滚动条轨道 */
::-webkit-scrollbar {
    width: 5px;
    /* 设置滚动条宽度 */
}

/* 设置滚动条滑块 */
::-webkit-scrollbar-thumb {
    background-color: #35a2ef;

}
</style>

bug记录P标签的内容为字母的时候不会进行自动换行需要设置word-break: break-all; 前端

今天在写项目的时候发现p标签的内容为字母的时候不会进行自动换行,会把宽度撑开。

解决办法:

给p标签设置 word-break: break-all; 属性即可


饿了么Plus组件+TS+Vue3如何使用表单重置 Vue

el-dialog组件关闭的时候清空表单内容,点击按钮也一样,改成点击事件就行了

1.引入FormInstance

import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()

2.定义一个空的ref挂载到el-dialog上面

let elm=ref(null)

3.绑定ref,此处的v-model用于控制是否显示弹窗 ,@closed是关闭弹窗的事件方法。

<el-dialog v-model="OrderQuery"  @closed="sta(formRef)" ref="elm">

</el-dialog>

4.model为表单对象,这里也要使用ref,其中的formRef是引入进来的包创建的,在el-form-item组件上绑定prop,值为你的表单内容。

<el-form :model="numberValidateForm" label-position="top"  ref="formRef">
     <el-form-item label="订单号" :label-width="formLabelWidth" prop="name">
       <el-input v-model="numberValidateForm.name" autocomplete="off"  placeholder="请输入订单编号" />
    </el-form-item>
</el-form>

5.触发关闭窗口回调

let sta = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}

完整代码:去掉了多余的,留下了最重要的,方便观察

<el-form :model="numberValidateForm"  ref="formRef">
     <el-form-item  prop="name">
       <el-input v-model="numberValidateForm.name"/>
    </el-form-item>
</el-form>

<script lang="ts" setup>
//引入包
import type { FormInstance } from 'element-plus'

const formRef = ref<FormInstance>()

const numberValidateForm = ref({
  name: '',
})

//关闭回调
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}
</script>

Element-Plus分页组件默认是英文如何改成中文 Vue

原图


Element-Plus分页组件默认是英文如何改成中文

修改之后:


Element-Plus分页组件默认是英文如何改成中文

解决办法:

在main.js中加入如下代码:

import zhLocale from "element-plus/es/locale/lang/zh-cn";
app.use(ElementPlus, { locale: zhLocale })

Vue3组件的二次封装以及插槽、传值的使用 Vue

使用ElementUI的Card的组件的时候,很多地方都会用到卡片这个组件,以及修改一下他默认的样式,进行了二次的封装。
效果图如下:
Vue3组件的二次封装以及插槽、传值的使用

这里封装主要是针对表格处理的,接收了一个标题参数,和2个具名插槽,一个是是否导出表格按钮,一个是表格内容。

子组件代码如下:

<template>
    <el-card class="box-card">
        <template #header>
            <div class="card-header">
                <span class="title">{{ props.title }}</span>
                <slot name="button"></slot>
            </div>
        </template>
        <slot name="table"></slot>
    </el-card>
</template>

<script lang="ts" setup>
let props = defineProps({
    title: {
        type: String,
        default: '这是标题'
    },
})
</script>

<style lang="scss" scoped>
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.text {
    font-size: 14px;
}

.item {
    margin-bottom: 18px;
}

.box-card {
    width: 100%;
}

:deep(.el-table .cell) {
    text-align: center;
}

.card-header .title {
    position: relative;
    color: #35a2ef;
    padding-left: 10px;
}

.card-header .title::after {
    content: "";
    display: block;
    width: 4px;
    height: 15px;
    background-color: #35a2ef;
    position: absolute;
    left: 0px;
    top: 5px;
}
</style>

父组件代码如下:

<template>
    <MyCart title="账号管理">
        <template #button>
            <el-button type="primary" @click="Table">
                导出表格<el-icon class="el-icon--right">
                    <Download />
                </el-icon>
            </el-button>
        </template>

        <template #table>
            <!-- v-slot:table -->
            <el-table :data="userList" border style="width: 100%">
                <el-table-column type="selection" prop="date" fixed />
                <el-table-column prop="id" label="ID" />
                <el-table-column prop="account" label="账号" />
                <el-table-column prop="userGroup" label="用户组" />
                <el-table-column prop="ctime" label="创建时间">
                    <template #default="props">
                        <span>{{ props.row.ctime }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="120" fixed="right">
                    <template #default>
                        <el-button link type="primary" size="small">编辑</el-button>
                        <el-button link type="primary" size="small">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </template>
    </MyCart>
</template>

<script lang="ts" setup>
import MyCart from '../../components/MyCard.vue'
import * as XLSX from 'xlsx'//表格导入导出插件
import { ref, computed, reactive } from 'vue';
import { $_UsersList } from '../../apis/user'
interface user {
    "id": number,//账号ID
    "ctime": string,//用户名称
    "account": string,//注册时间
    "userGroup": string,//管理权限
    "imgUrl": string//头像地址
}

let userList = ref<Array<user>>([])
$_UsersList({
    currentPage: 1,
    pageSize: 10
}).then(res => {
    // 处理时间
    res.data.data.map((v: user) => {
        v.ctime = new Date(v.ctime).toLocaleString()
    });
    userList.value = res.data.data
})

let Table = () => {
    //1. 创建一个工作簿
    const workBook = XLSX.utils.book_new()
    // 2. 创建工作表 worksheet
    const workSheet = XLSX.utils.json_to_sheet(userList.value)
    // 3. 将工作表放入工作簿中
    XLSX.utils.book_append_sheet(workBook, workSheet)
    XLSX.writeFile(workBook, `账号列表.xlsx`, {
        bookType: 'xlsx'
    })
}
</script>

<style lang="scss" scoped></style>

总结:

1.父传子使用的时候defineProps,在Vue3里面在setup下不需要引入就可以使用
例如:子组件接收参数

let props = defineProps({
    title: {
        type: String,
        default: '这是标题'
    },
})

父组件传递只需要这样:

 <MyCart title="传递的内容"> </MyCart>

2.具名插槽:如果需要插入内容的时候,并且想指定到某个位置需要给插槽定义名字。
例如:在组件通过name定义一个插槽名字为button,这个插槽放到上面位置,传过来的东西就会显示在什么地方,

<slot name="button"></slot>

父组件如下:在vue3里面需要使用template进行包裹 后面跟#定义的名字就可以,或者 v-slot:button 也可以

 <template #button>
    这里面是内容
 </template>

  <template v-slot:button>
    这里面是内容
 </template>

vue3写了一个路由标签导航栏组件 Vue

vue3里面引入就可以使用,vue2需要注册一下在调用。

大致思路,进入页面的时候监听路由,判断当前路由的地址,然后把这个地址用来做激活样式匹配,接下来就是点击事件,点击谁就把激活样式给谁,其次是删除如果删除的是第一个,下次跳转默认下标为0的标签,并且附带激活样式,如果不是就跳转上一个也是index+1。这里面用了一点ts语法!

<LabelNav />
<script lang="ts" setup>
import LabelNav from '../components/LabelNav.vue'
</script>

效果展示:

vue3写了一个路由标签导航栏组件

完整代码

<template>
    <div class="DynamicMenu">
        <el-scrollbar>
            <div class="scrollbar-flex-content">
                <div @click="switching(item.to)" :class="{ 'DynamicMenu-span': true, 'active': item.to == path }"
                    v-for="(item, index) in  label " :key="index">
                    <span>{{ item.title }}</span>
                    <span @click="removeTag(index); $event.stopPropagation()" class="ax"></span>
                </div>
            </div>
        </el-scrollbar>

    </div>
</template>

<script setup lang="ts">
import { reactive, watch, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
let $route = useRoute()
let $router = useRouter()
interface LabelItem {
    title: string;
    to: string;
}
let path = ref()
let label = reactive<LabelItem[]>([

])
path.value = $route.path
// 页面加载的时候读取本地tab标签
const storedTabs = localStorage.getItem('tab');
if (storedTabs) {
    const newTabs = JSON.parse(storedTabs) as LabelItem[];
    newTabs.forEach(newTab => {
        const hasDuplicate = label.some(item => item.title === newTab.title);
        if (!hasDuplicate) {
            label.push(newTab);
        }
    });
} else {
    label.push({ title: '后台首页', to: '/index' })
    localStorage.setItem('tab', JSON.stringify(label))
}

// 监听路由变化,进行激活样式更改和标签存储
watch(() => $route.meta.title, (newTitle) => {
    path.value = $route.path//激活样式
    let tis: string = newTitle as string;
    // 检查是否存在相同的项,存储标签
    const hasDuplicate = label.some(item => item.title === tis);
    if (!hasDuplicate) {
        label.push({ title: tis, to: $route.path });
        localStorage.setItem('tab', JSON.stringify(label))
    }
});

// 删除标签
let removeTag = (index: number) => {
    if (label.length === 1) {
        return;
    } else {
        label.splice(index, 1);
        // 如果删除的是第一个标签,则跳转下一个,反之则跳转上一个
        if (index === 0) {
            path.value = label[0].to;
            $router.push(label[0].to);
        } else {
            path.value = label[index - 1].to;
            $router.push(label[index - 1].to);
        }
        localStorage.setItem('tab', JSON.stringify(label));
    }
}
// 点击标签跳转
let switching = (to: string) => {
    path.value = to
    $router.push(to)
}

</script>

<style lang="scss" scoped>
.DynamicMenu {
    width: 100%;
    height: 30px;
    background-color: #f5f7fa;
    position: relative;
    top: 0;
    display: flex;
    align-items: center;

    .DynamicMenu-span {
        height: 25px;
        min-width: 100px;
        background-color: #35a2ef;
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 0 2px;
        padding: 0 10px;
        font-size: 12px;
        color: #fff;
        position: relative;

        span {
            height: 100%;
            line-height: 25px;
        }
    }

    .ax {
        width: 20px;
        height: 20px;
        position: relative;
        position: absolute;
        right: 0;
    }

    .DynamicMenu-span .ax::after,
    .DynamicMenu-span .ax::before {
        content: "";
        width: 2px;
        height: 10px;
        top: 6px;
        border-radius: 50%;
        background-color: #fff;
        right: 10px;
        position: absolute;
        transition: transform 0.3s ease;
    }

    .DynamicMenu-span .ax::after {
        transform: rotate(-45deg);
    }

    .DynamicMenu-span .ax::before {
        transform: rotate(45deg);
    }

    .DynamicMenu-span .ax:hover::after {
        transform: rotate(45deg);
        background-color: #ff0000;
    }

    .DynamicMenu-span .ax:hover::before {
        transform: rotate(-45deg);
        background-color: #ff0000;
    }

    .active {
        background-color: white;
        color: #35a2ef;
        border: 1px solid #35a2ef;
    }
}

.scrollbar-flex-content {
    display: flex;
}

.scrollbar-demo-item {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100px;
    height: 50px;
    margin: 10px;
    text-align: center;
    border-radius: 4px;
    background: var(--el-color-danger-light-9);
    color: var(--el-color-danger);
}
</style>

Vue中API接口如何二次封装Axios进行高效开发 Vue

1.src目录下新建 utlis文件夹 ->axios.ts

引入axios插件,创建公共服务器IP ,设置请求拦截器和相应拦截器,最后暴露出去

import axios from "axios"
export const SERVER_IP = 'http://XXXXXX/'

export const request = axios.create({
    baseURL: SERVER_IP,
    timeout: 6000,
})
//请求拦截器
request.interceptors.request.use()
//相应拦截器
request.interceptors.response.use()

2.src目录下创建 apis文件夹->里面存储分类接口例如用户接口,登录接口:login.ts

引入封装的axios,进行接口的一个封装,然后暴露出去

import { SERVER_IP, request } from '../utlis/axios'
import type { Login } from './interface'

// 登录接口
export const $_checkLogin = (data: Login) => {
    return request({
        url: '/users/checkLogin',
        method: 'POST',
        data
    })
}
//获取列表
export const $_UsersList = (params: userList) => {
    return request({
        url: '/users/list',
        method: 'GET',
        params
    })
}

3.在需要使用接口的页面只需调用封装的api也就是apis文件夹里面的文件例如:login.ts

<script lang="ts" setup>
import { reactive } from 'vue';
import { $_checkLogin } from '../apis/login'
let form = reactive({
    account: '',
    password: ''
});
let onSubmit = () => {
    $_checkLogin(form).then(res => {
     //请求成功之后操作
    })
}
</script>

封装的好处方便我们后期维护管理API,如果吧api直接在写每个页面进行直接发请求,文件多起来了,就变的难以维护,如果用到了相同的请求也会造成代码冗余度过高。
封装之后对接口统一进行管理,更方便直观的查看API和维护,也降低了代码冗余度,和可读性。
以上示例都是基于Vue3+Ts写的,用Vue2+JS封装流程也是一样,只是不需要reactive这种响应式


vue3如何使用动态class切换 Vue

这里写的是一个旋转按钮使用的是elementPlus


vue3动态class三目预算:class="isRotated ? 'rotate' : 'reverseRotate'"通过点击事件给变量取反来实现class样切换


HTML

 <div class="numE" @click="numE">
    <el-icon ref="numElement" color="#fff" size="30px" :class="isRotated ? 'rotate' : 'reverseRotate'">
     <Expand />
    </el-icon>
 </div>

CSS

.rotate,
.reverseRotate {
    transition: transform 0.5s ease;
}
.rotate {
    transform: rotate(180deg);
}
.reverseRotate {
    transform: rotate(0deg);
}

JS

import { ref } from 'vue'
let isRotated = ref(false)
let numE = () => {
    isRotated.value = !isRotated.value;
}