分享记录vue3+ts基于element-plus通过父子组件交互实现聊天窗口的转发弹框,支持多选、单选、搜索等基础功能。开整

一、先下载引入element-plus
//直接冲官网指南
https://element-plus.gitee.io/zh-CN/guide/installation.html
//我这边用的scss预处理,直接下载就好了
npm install sass

二、子组件实现代码
//html代码
<template>
    <div>
        <el-dialog v-model="dialogVisible" :show-close="false" :close-on-click-modal="false">
            <div class="dialog">
                <div class="fs-16 bold c-0">选择好友<span class="fs-12 c-80">{{ contactList.length }}/{{ contacts.length
                }}</span><span style="margin-left: 200px;cursor:pointer" @click="chooses()">{{ choose == 1 ? '单选' : '多选'}}</span></div>
                <el-input v-model="contact" placeholder="搜索" clearable :prefix-icon="Search" @input="search" />
                <div class="list">
                    <el-scrollbar height="420px">
                        <div class="li" v-for="(item, index) in contacts" :key="index" @click="forwarding(item)">
                            <img :src="item.choose ? 'https://pic.imgdb.cn/item/6426503fa682492fcc5e327d.png' : 'https://pic.imgdb.cn/item/64265063a682492fcc5e65ad.png'"
                                alt="" class="li-left">
                            <div class="li-right">
                                <img src="https://pic.imgdb.cn/item/64265077a682492fcc5e7d6f.gif" alt="">
                                <!-- <img :src="item.img" alt=""> -->
                                <div class="fs-14 c-3">{{ item.name }}</div>
                            </div>
                        </div>
                    </el-scrollbar>
                </div>
                <div class="bottom">
                    <el-button type='primary' text @click="cancel">取消</el-button>
                    <el-button type='primary' text @click="determine">确认</el-button>
                </div>
            </div>
        </el-dialog>
    </div>
</template>
//组件通过dialog控制和scrollbar滚动条实现,所以先引用这2个组件
import { ref, toRefs, watch } from 'vue'
import { ElScrollbar, ElMessage, } from 'element-plus';
import { Search } from '@element-plus/icons-vue'

//然后定义组件的初始值,组件状态以及组件接受的参数等
let props = defineProps({
    dialogShow: {
        type: Boolean,
        default: false,
    },
});
const refProps = toRefs(props)
const emit = defineEmits(['getContact', 'contactShow'])
let dialogVisible = ref(props.dialogShow)//本地组件状态
watch(refProps.dialogShow, (val, old) => {
    dialogVisible.value = val
}, { deep: true })//监听修改本地
let contact = ref('')//联系人搜索
let contactList = ref([] as any)//选中转发的联系人
let choose = ref(2)//单选为1 多选为2
let contacts: any = ref([{
    img: '',
    name: 'Lingzi',
    choose: false,
    id: 1,
}, {
    img: '',
    name: 'zhangsan0',
    choose: false,
    id: 2,
}, {
    img: '',
    name: 'lisi',
    choose: false,
    id: 3,
}, {
    img: '',
    name: 'Wanger',
    choose: false,
    id: 4,
}, {
    img: '',
    name: 'yangyu',
    choose: false,
    id: 5,
}, {
    img: '',
    name: '007',
    choose: false,
    id: 6,
}, {
    img: '',
    name: 'YangyuS',
    choose: false,
    id: 7,
},])
let contactsCopy: any = ref(contacts.value)
//相关逻辑和方法
const chooses = () => {//单选多选切换
    choose.value = choose.value == 1 ? 2 : 1
    contacts.value.forEach((i:any)=>{
        i.choose=false
    })
    contactList.value=[]
}
const forwarding = (item: any) => {//选择
    if (choose.value == 1) {//单选
        if (!item.choose) {
            let y = contacts.value.some((i: any) => {
                return i.choose
            })
            if (y) {//有选中的
                contacts.value.forEach((item: any) => {
                    item.choose = false
                    contactList.value = []
                })
            }
        }
        item.choose = !item.choose
        if (item.choose) {
            contactList.value.push(item.name)
        } else {
            contactList.value = []
        }
    } else {//多选
        item.choose = !item.choose
        if (item.choose && contactList.value.indexOf(item.name) == -1) {
            contactList.value.push(item.name)
        } else if (!item.choose && contactList.value.indexOf(item.name) != -1) {
            contactList.value.splice(contactList.value.indexOf(item.name), 1)
        }
    }
};
const cancel = () => {//取消操作
    emit('contactShow', false)//通过emit传值给父组件
    contactList.value = []
    contacts.value.forEach((item: any) => {
        item.choose = false
    })
}
const determine = () => {//确定操作
    if (contactList.value && contactList.value.length) {
        emit('getContact', contactList.value)//把选中的信息传递给父组件
        cancel()
    } else {
        ElMessage.error('请选择转发的对象')
    }
}
const search = () => {//搜索
    if (contact.value == '') {
        contacts.value = contactsCopy.value
    }
    let arr = ref([] as any)
    contactsCopy.value.map((item: any) => {
        if (item.name.toLowerCase().indexOf(contact.value.toLowerCase()) != -1) {
            return arr.value.push(item)
        }
    })
    contacts.value = arr.value
}

三、父组件实现代码
//html代码
<el-button type="primary" @click="show()">打开弹框</el-button>
    <Dialog :dialogShow="dialogShow" @contactShow="contactShow" @getContact="getContact"></Dialog>

//js代码
import {ref} from 'vue'
import Dialog from '../components/forwarding.vue';
let dialogShow=ref(false)
const show=()=>{//控制子组件状态
    dialogShow.value=true
}
const contactShow = (e: boolean) => {//接受子组件传递过来的状态
  dialogShow.value = e
}
const getContact = (e: object) => {//接受子组件传递过来的状态
  console.log(e)
}
四、相关效果图

 


五、整体代码

//父组件
<template>
    <el-button type="primary" @click="show()">打开弹框</el-button>
    <Dialog :dialogShow="dialogShow" @contactShow="contactShow" @getContact="getContact"></Dialog>
</template>

<script setup lang="ts">
import {ref} from 'vue'
import Dialog from '../components/forwarding.vue';
let dialogShow=ref(false)
const show=()=>{//控制子组件显示
    dialogShow.value=true
}
const contactShow = (e: boolean) => {//接受子组件传递过来的状态
  dialogShow.value = e
}
const getContact = (e: object) => {//接受子组件传递过来的状态
  console.log(e)
}
</script>
<style scoped lang="scss"></style>

//子组件
<template>
    <div>
        <el-dialog v-model="dialogVisible" :show-close="false" :close-on-click-modal="false">
            <div class="dialog">
                <div class="fs-16 bold c-0">选择好友<span class="fs-12 c-80">{{ contactList.length }}/{{ contacts.length
                }}</span><span style="margin-left: 200px;cursor:pointer" @click="chooses()">{{ choose == 1 ? '单选' : '多选'}}</span></div>
                <el-input v-model="contact" placeholder="搜索" clearable :prefix-icon="Search" @input="search" />
                <div class="list">
                    <el-scrollbar height="420px">
                        <div class="li" v-for="(item, index) in contacts" :key="index" @click="forwarding(item)">
                            <img :src="item.choose ? 'https://pic.imgdb.cn/item/6426503fa682492fcc5e327d.png' : 'https://pic.imgdb.cn/item/64265063a682492fcc5e65ad.png'"
                                alt="" class="li-left">
                            <div class="li-right">
                                <img src="https://pic.imgdb.cn/item/64265077a682492fcc5e7d6f.gif" alt="">
                                <!-- <img :src="item.img" alt=""> -->
                                <div class="fs-14 c-3">{{ item.name }}</div>
                            </div>
                        </div>
                    </el-scrollbar>
                </div>
                <div class="bottom">
                    <el-button type='primary' text @click="cancel">取消</el-button>
                    <el-button type='primary' text @click="determine">确认</el-button>
                </div>
            </div>
        </el-dialog>
    </div>
</template>
<!-- 多选组件---------------- -->
<script setup lang="ts">
import { ref, toRefs, watch } from 'vue'
import { ElScrollbar, ElMessage, } from 'element-plus';
import { Search } from '@element-plus/icons-vue'
let props = defineProps({
    dialogShow: {
        type: Boolean,
        default: false,
    },
});
const refProps = toRefs(props)
const emit = defineEmits(['getContact', 'contactShow'])
let dialogVisible = ref(props.dialogShow)//本地组件状态
watch(refProps.dialogShow, (val, old) => {
    dialogVisible.value = val
}, { deep: true })//监听修改本地
let contact = ref('')//联系人搜索
let contactList = ref([] as any)//选中转发的联系人
let choose = ref(2)//单选为1 多选为2
let contacts: any = ref([{
    img: '',
    name: 'Lingzi',
    choose: false,
    id: 1,
}, {
    img: '',
    name: 'zhangsan0',
    choose: false,
    id: 2,
}, {
    img: '',
    name: 'lisi',
    choose: false,
    id: 3,
}, {
    img: '',
    name: 'Wanger',
    choose: false,
    id: 4,
}, {
    img: '',
    name: 'yangyu',
    choose: false,
    id: 5,
}, {
    img: '',
    name: '007',
    choose: false,
    id: 6,
}, {
    img: '',
    name: 'YangyuS',
    choose: false,
    id: 7,
},])
let contactsCopy: any = ref(contacts.value)
const chooses = () => {//单选多选切换
    choose.value = choose.value == 1 ? 2 : 1
    contacts.value.forEach((i:any)=>{
        i.choose=false
    })
    contactList.value=[]
}
const forwarding = (item: any) => {//选择
    if (choose.value == 1) {//单选
        if (!item.choose) {
            let y = contacts.value.some((i: any) => {
                return i.choose
            })
            if (y) {//有选中的
                contacts.value.forEach((item: any) => {
                    item.choose = false
                    contactList.value = []
                })
            }
        }
        item.choose = !item.choose
        if (item.choose) {
            contactList.value.push(item.name)
        } else {
            contactList.value = []
        }
    } else {//多选
        item.choose = !item.choose
        if (item.choose && contactList.value.indexOf(item.name) == -1) {
            contactList.value.push(item.name)
        } else if (!item.choose && contactList.value.indexOf(item.name) != -1) {
            contactList.value.splice(contactList.value.indexOf(item.name), 1)
        }
    }
};
const cancel = () => {//取消操作
    emit('contactShow', false)//通过emit传值给父组件
    contactList.value = []
    contacts.value.forEach((item: any) => {
        item.choose = false
    })
}
const determine = () => {//确定操作
    if (contactList.value && contactList.value.length) {
        emit('getContact', contactList.value)//把选中的信息传递给父组件
        cancel()
    } else {
        ElMessage.error('请选择转发的对象')
    }
}
const search = () => {//搜索
    if (contact.value == '') {
        contacts.value = contactsCopy.value
    }
    let arr = ref([] as any)
    contactsCopy.value.map((item: any) => {
        if (item.name.toLowerCase().indexOf(contact.value.toLowerCase()) != -1) {
            return arr.value.push(item)
        }
    })
    contacts.value = arr.value
}
</script>
<style scoped lang="scss">
.fs-16 {
    font-size: 16px;
    font-family: Source Han Sans CN;
    font-weight: 400;
    color: #333333;
}

.fs-12 {
    font-size: 12px;
    font-family: SourceHanSansSC;
    font-weight: 400;
    color: #999999;
}

.fs-14 {
    font-size: 14px;
    font-family: Source Han Sans CN;
    font-weight: 400;
    color: #FFFFFF;
}

.fs-20 {
    font-size: 20px;
    font-family: Source Han Sans CN;
    font-weight: 500;
    color: #333333;
}

.c-0 {
    color: #000000;
}

.c-3 {
    color: #333333;
    font-weight: 500;
}

.c-80 {

    color: #808080;
}

.bold {
    font-weight: bold;
}

:deep(.el-dialog) {
    width: 362px;
    height: 580px;
    background: #FFFFFF;
    border-radius: 6px;
    position: absolute;
    top: 2%;
    left: 40%;

    .el-dialog__header {
        padding: 0;
    }

    .el-dialog__body {
        padding: 0;
    }

    .dialog {
        padding: 20px;
        text-align: left;

        .el-input__wrapper {
            margin-top: 30px;
            padding: 0;
            border-bottom: 1px solid #f2f2f2;
            box-shadow: none
        }

        .list {
            margin-top: 2px;

            .li {
                display: flex;
                align-items: center;
                padding: 10px 0;

                // padding:0 20px;
                .li-left {
                    width: 18px;
                    height: 18px;
                    margin-right: 10px;
                }

                .li-right {
                    display: flex;
                    align-items: center;

                    img {
                        width: 46px;
                        height: 46px;
                        border-radius: 50%;
                        margin-right: 12px;
                    }
                }
            }

            .li:hover {
                background: #e6eaf2;
            }
        }

        .bottom {
            display: flex;
            margin-top: 10px;
            justify-content: flex-end;
        }
    }
}</style>

六、总结
一个简单的转发弹框,有相同业务逻辑的项目可以直接copy现用,代码写的有点渣,有相关代码优化的地方还望各位大佬能指点一二。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来给你讲一下具体的步骤。 1. 初始化项目 首先,我们需要使用 Vue CLI 4+ 创建一个新的项目,选择 TypeScript 作为语言: ``` vue create my-project ``` 然后,在项目根目录下执行以下命令安装 Element Plus、Vite、GWT 等依赖: ``` npm install element-plus vite vue-router vuex axios qs ``` 2. 配置 Vite 我们需要对 Vite 进行一些配置以实现换肤和全局 loading 控制。在项目根目录下创建一个 `vite.config.ts` 文件,添加以下内容: ```typescript import { defineConfig } from 'vite'; export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: ` @import "~element-plus/packages/theme-chalk/src/index"; @import "@/styles/variables.scss"; ` } } }, server: { proxy: { '/api': { target: 'http://localhost:3000', // GWT 后端地址 changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } }) ``` 其中,我们使用了 Element Plus 自带的 SCSS 变量来实现换肤,需要在 `variables.scss` 中定义相应的变量。`server.proxy` 则是用于配置 GWT 后端地址的代理。 3. 配置 Element Plus 在 `main.ts` 中,我们需要引入 Element Plus 并使用它的样式和组件: ```typescript import { createApp } from 'vue'; import ElementPlus from 'element-plus'; import App from './App.vue'; import router from './router'; import store from './store'; import 'element-plus/lib/theme-chalk/index.css'; const app = createApp(App); app.use(ElementPlus); app.use(router); app.use(store); app.mount('#app'); ``` 4. 配置路由和状态管理 我们使用 Vue Router 来实现路由,Vuex 来实现全局状态管理。在 `store` 目录下创建 `index.ts` 文件,添加以下内容: ```typescript import { createStore } from 'vuex'; export default createStore({ state: { loading: false }, mutations: { setLoading(state, payload) { state.loading = payload; } }, actions: { setLoading({ commit }, payload) { commit('setLoading', payload); } }, modules: { } }) ``` 在 `router` 目录下创建 `index.ts` 文件,添加以下内容: ```typescript import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import Home from '../views/Home.vue'; const routes: Array<RouteRecordRaw> = [ { path: '/', name: 'Home', component: Home } ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }); export default router; ``` 5. 实现换肤功能 在 `styles` 目录下创建 `variables.scss` 文件,添加以下内容: ```scss /* 主题色 */ $--color-primary: #409EFF; /* 辅助色 */ $--color-success: #67C23A; $--color-warning: #E6A23C; $--color-danger: #F56C6C; $--color-info: #909399; /* 背景色 */ $--color-background: #f5f7fa; $--color-card: #fff; /* 字体 */ $--font-family: 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif; ``` 我们可以在 `App.vue` 中通过修改 `document.documentElement.style` 中的 CSS 变量来实现换肤。具体代码如下: ```html <template> <div id="app"> <!-- ... --> </div> </template> <script> import { defineComponent } from 'vue'; import { useStore } from 'vuex'; export default defineComponent({ setup() { const store = useStore(); function switchTheme(theme: string) { const app = document.documentElement; app.style.setProperty('--color-primary', theme); } return { switchTheme }; } }); </script> ``` 6. 实现全局 loading 控制 在 `App.vue` 中,我们可以使用 Vuex 中的 `loading` 状态来控制全局 loading。具体代码如下: ```html <template> <div id="app"> <div v-if="loading" class="loading-mask"> <div class="loading-spinner"></div> </div> <!-- ... --> </div> </template> <script> import { defineComponent } from 'vue'; import { useStore } from 'vuex'; export default defineComponent({ setup() { const store = useStore(); return { loading: store.state.loading }; } }); </script> <style scoped> .loading-mask { position: fixed; z-index: 9999; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255, 255, 255, 0.5); } .loading-spinner { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border: 3px solid #ccc; border-top-color: #409EFF; border-radius: 50%; width: 30px; height: 30px; animation: spin 0.6s infinite linear; } @keyframes spin { to { transform: rotate(360deg); } } </style> ``` 然后,在需要控制 loading 的地方,我们可以使用以下代码来控制全局 loading: ```typescript import { useStore } from 'vuex'; const store = useStore(); // 显示 loading store.dispatch('setLoading', true); // 隐藏 loading store.dispatch('setLoading', false); ``` 到这里,我们就完成了一个基于 Vue 3、TypeScript、Element Plus、Vite 的代码框架,并且支持换肤和全局 loading 控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值