页面设计如下:

开发思路
从大到小,组件封装
从死数据到灵活适配
从简单到特殊,由面到点
整体架构
首先分块,再填充内容

先分为2个块,上面标签下面内容。
再下面内容分为3 大块,结构如上。
因为左侧聊天框和右侧可能以后要扩展拖动功能,所以这里不用Layout布局,通过DIV盒子实现,方便后面调整
(到目前为止不会一个拖动自动变化大小的技巧怎么实现)。
main.js
import { createApp } from 'vue'
import './style.css'
// @ts-ignore
import App from "./App.vue";
/** ssss
* 项目初始化css
*/
import './styles/index.scss'
import NaiveUi from "naive-ui";
/**
* 初始化Element Plus的icon图标
*/
const app = createApp(App)
app.use(NaiveUi)
app.mount('#app')
所以我这里新建2个组件。
src/App.vue
<template>
<div class="bs-wrapper">
<div class="main">
<div class="main__title">
<TabsTitle/>
</div>
<div class="main__content">
<Chat/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Chat from "./components/Chat/index.vue"
import TabsTitle from "./components/TabsTitle/index.vue"
</script>
<style lang="scss" scoped>
// 1rem===100px
.bs-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
//align-items: center;
.main {
//text-align: center;
//clamp:最小值、首选值、最大值
//width: clamp(2rem, calc(100vw - 3rem), 100vw);
//height: clamp(2rem, calc(100vh - 3rem), 100vh);
width: calc(100vw - 3rem);
height: calc(100vh - 3rem);
background: #0091ff;
&__title {
width: 100%;
height: 0.8rem;
background: #0bff00;
}
&__content {
width: 100%;
height: 100%;
}
}
}
</style>
src/components/Chat/index.vue
<template>
<div class="wrapper__chat">
<div class="chat__left-people">
</div>
<div class="chat__content">
<div class="chat__content__info">
</div>
<div class="chat__content__input">
</div>
</div>
</div>
</template>
<script setup lang="ts">
import LeftLinkMan from "./module/LeftLinkMan/index.vue";
import ShowChatInfo from "./module/ShowChatInfo/index.vue";
import Chat from "@com/TabsTitle/index.vue";
</script>
<style lang="scss" scoped>
.wrapper__chat {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
.chat {
&__left-people {
flex: 1;
height: 100%;
background: #ff4c4c;
}
&__content {
flex: 5;
height: 100%;
background: #fff12b;
display: flex;
flex-direction: column;
&__info {
flex: 5;
height: 100%;
background: #40f0ff;
}
&__input {
flex: 2;
height: 100%;
background: #f89751;
}
}
}
}
</style>

顶部Title开发
同样分为3个区域:logo,菜单,右侧按钮

src/components/TabsTitle/index.vue
<template>
<div class="wrapper__tabs-title">
<div class="tabs-title__left"></div>
<div class="tabs-title__right"></div>
</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
.wrapper__tabs-title {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
background: #535bf2;
.tabs-title {
&__left {
flex: 5;
width: 100%;
height: 100%;
background: #eee222;
}
&__right {
flex: 3;
width: 100%;
height: 100%;
background: #d876de;
}
}
}
</style>

完善按钮:
<template>
<div class="wrapper__tabs-title">
<div class="tabs-title__left">
</div>
<div class="tabs-title__middle">
<template v-for="item in btnList" :key="item.code">
<div class="tabs-btn">{{ item.name }}</div>
</template>
</div>
<div class="tabs-title__right"></div>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
interface TypeTitleTabsMenu {
code: string,
name: string
}
const btnList = ref<Array<TypeTitleTabsMenu>>([
{code: "1", name: "艺术画展"},
{code: "2", name: "绘画中学"},
{code: "3", name: "助手中心"}
])
</script>
<style lang="scss" scoped>
.wrapper__tabs-title {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
background: #535bf2;
.tabs-title {
&__left {
flex: 1;
width: 100%;
height: 100%;
background: #eee222;
text-align: left;
}
&__middle {
flex: 6;
width: 100%;
height: 100%;
background: #efa18b;
text-align: left;
display: flex;
align-items: center;
.tabs-btn {
font-size: 0.2rem;
font-weight: 550;
display: inline-block;
padding: 0.05rem;
margin: 0 0.4rem;
background: #0bff00;
}
}
&__right {
flex: 1;
width: 100%;
height: 100%;
background: #d876de;
text-align: right;
}
}
}
</style>

完善logo和,数字积分按钮。
<template>
<div class="wrapper__tabs-title">
<div class="tabs-title__left">
<!--logo在实际业务中一般是图片-->
<img src="src/assets/logo.png" alt="logo"/>
</div>
<div class="tabs-title__middle">
<template v-for="item in btnList" :key="item.code">
<div class="tabs-btn">{{ item.name }}</div>
</template>
</div>
<div class="tabs-title__right">
<div class="btn-score">
<div class="btn-score__text">数字积分</div>
<div class="btn-score__image">]
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
interface TypeTitleTabsMenu {
code: string,
name: string
}
const btnList = ref<Array<TypeTitleTabsMenu>>([
{code: "1", name: "艺术画展"},
{code: "2", name: "绘画中学"},
{code: "3", name: "助手中心"}
])
</script>
<style lang="scss" scoped>
.wrapper__tabs-title {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
background: #535bf2;
.tabs-title {
&__left {
flex: 1;
width: 100%;
height: 100%;
background: #eee222;
display: flex;
align-items: center;
justify-content: center;
margin: 0 0.12rem;
}
&__middle {
flex: 6;
width: 100%;
height: 100%;
background: #efa18b;
text-align: left;
display: flex;
align-items: center;
.tabs-btn {
font-size: 0.2rem;
font-weight: 550;
display: inline-block;
padding: 0.05rem;
margin: 0 0.4rem;
background: #0bff00;
}
}
&__right {
flex: 1;
width: 100%;
height: 100%;
background: #d876de;
text-align: right;
display: flex;
align-items: center;
justify-content: center;
.btn-score {
border: 0.01rem solid #6C6D6F;
border-radius: 0.4rem;
display: flex;
align-items: center;
padding: 0.03rem 0.112rem;
&__text {
display: inline-block;
text-align: left;
color: #6C6D6F;
}
&__image {
margin-left: 0.08rem;
display: inline-block;
width: 0.2rem;
height: 0.2rem;
border-radius: 50%;
object-fit: cover;
background-color: #0bff00;
}
}
}
}
}
</style>

完善图片
<template>
<div class="wrapper__tabs-title">
<div class="tabs-title__left">
<!--logo在实际业务中一般是图片-->
<img src="src/assets/logo.png" alt="logo"/>
</div>
<div class="tabs-title__middle">
<template v-for="item in btnList" :key="item.code">
<div class="tabs-btn">{{ item.name }}</div>
</template>
</div>
<div class="tabs-title__right">
<div class="btn-score">
<div class="btn-score__text">数字积分</div>
<div class="btn-score__image">
<img style="width: 100%;height: 100%" src="src/assets/vue.svg" alt="数字积分"/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
interface TypeTitleTabsMenu {
code: string,
name: string
}
const btnList = ref<Array<TypeTitleTabsMenu>>([
{code: "1", name: "艺术画展"},
{code: "2", name: "绘画中学"},
{code: "3", name: "助手中心"}
])
</script>
<style lang="scss" scoped>
.wrapper__tabs-title {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
background: #535bf2;
.tabs-title {
&__left {
flex: 1;
width: 100%;
height: 100%;
background: #eee222;
display: flex;
align-items: center;
justify-content: center;
margin: 0 0.12rem;
}
&__middle {
flex: 6;
width: 100%;
height: 100%;
background: #efa18b;
text-align: left;
display: flex;
align-items: center;
.tabs-btn {
font-size: 0.2rem;
font-weight: 550;
display: inline-block;
padding: 0.05rem;
margin: 0 0.4rem;
}
}
&__right {
flex: 1;
width: 100%;
height: 100%;
background: #d876de;
text-align: right;
display: flex;
align-items: center;
justify-content: center;
.btn-score {
border: 0.01rem solid #6C6D6F;
border-radius: 0.4rem;
display: flex;
align-items: center;
padding: 0.03rem 0.112rem;
&__text {
display: inline-block;
text-align: left;
color: #6C6D6F;
}
&__image {
margin-left: 0.08rem;
display: inline-block;
width: 0.2rem;
height: 0.2rem;
border-radius: 50%;
}
}
}
}
}
</style>

调整背景色,去掉标识颜色:
<template>
<div class="wrapper__tabs-title">
<div class="tabs-title__left">
<!--logo在实际业务中一般是图片-->
<img src="src/assets/logo.png" alt="logo"/>
</div>
<div class="tabs-title__middle">
<template v-for="item in btnList" :key="item.code">
<div class="tabs-btn">{{ item.name }}</div>
</template>
</div>
<div class="tabs-title__right">
<div class="btn-score">
<div class="btn-score__text">数字积分</div>
<div class="btn-score__image">
<img style="width: 100%;height: 100%" src="src/assets/vue.svg" alt="数字积分"/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
interface TypeTitleTabsMenu {
code: string,
name: string
}
const btnList = ref<Array<TypeTitleTabsMenu>>([
{code: "1", name: "艺术画展"},
{code: "2", name: "绘画中学"},
{code: "3", name: "助手中心"}
])
</script>
<style lang="scss" scoped>
.wrapper__tabs-title {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
background-image: linear-gradient(to right,#0D0D0D, #17181A, #18191B, #0D0D0D);
.tabs-title {
&__left {
flex: 1;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 0.12rem;
}
&__middle {
flex: 6;
width: 100%;
height: 100%;
text-align: left;
display: flex;
align-items: center;
.tabs-btn {
color: #F9FAFC;
font-size: 0.2rem;
font-weight: 550;
display: inline-block;
padding: 0.05rem;
margin: 0 0.4rem;
}
}
&__right {
flex: 1;
width: 100%;
height: 100%;
text-align: right;
display: flex;
align-items: center;
justify-content: center;
.btn-score {
border: 0.01rem solid #F9FAFC;
border-radius: 0.4rem;
display: flex;
align-items: center;
padding: 0.03rem 0.112rem;
&__text {
display: inline-block;
text-align: left;
color: #F9FAFC;
}
&__image {
margin-left: 0.08rem;
display: inline-block;
width: 0.16rem;
height: 0.16rem;
border-radius: 50%;
}
}
}
}
}
</style>

调整主页面
src/App.vue
<template>
<div class="bs-wrapper">
<div class="main">
<div class="main__title">
<TabsTitle/>
</div>
<div class="main__content">
<Chat/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Chat from "./components/Chat/index.vue"
import TabsTitle from "./components/TabsTitle/index.vue"
</script>
<style lang="scss" scoped>
@import "/src/styles/viriables";
// 1rem===100px
.bs-wrapper {
width: $window-width;
height: $window-height;
display: flex;
justify-content: center;
background-color: #0D0D0D;
.main {
width: 100%;
height: 100%;
background: #0091ff;
&__title {
width: 100%;
height: $title-height;
}
&__content {
width: 100%;
height: 100%;
}
}
}
</style>

这里将一些公共scss抽出去:
src/styles/viriables.scss
$title-height: 0.8rem;
$window-width:14rem;
$window-height:7rem;
$content-height: calc($window-height - $title-height);
聊天联系人列表
src/components/Chat/index.vue
<template>
<div class="wrapper__chat">
<div class="chat__left-people">
<LeftLinkMan/>
</div>
<div class="chat__content">
<div class="chat__content__info">
</div>
<div class="chat__content__input">
</div>
</div>
</div>
</template>
src/components/Chat/module/LeftLinkMan/index.vue
<template>
<div class="wrapper__list-link-man">TabsTitle</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
.wrapper__list-link-man{
width: 100%;
height: 100%;
}
</style>

进一步完善:
<template>
<div class="wrapper__list-link-man">
<n-tabs v-model="formData.active" type="line" justify-content="space-evenly" animated>
<n-tab-pane name="myChat" tab="我的聊天">
</n-tab-pane>
<n-tab-pane name="MyAssisted" tab="我协助的">
</n-tab-pane>
</n-tabs>
<ListLinkMan v-if="formData.active==='myChat'"/>
</div>
</template>
<script setup lang="ts">
import ListLinkMan from "./module/ListLinkMan/index.vue";
import {ref} from "vue";
const formData = ref<any>({
active: "myChat"
})
</script>
<style lang="scss" scoped>
.wrapper__list-link-man {
width: 100%;
height: 100%;
background: #e3ff1d;
}
</style>
src/components/Chat/module/LeftLinkMan/module/ListLinkMan/index.vue
<template>
<div>ListLinkMan</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

src/components/Chat/module/LeftLinkMan/index.vue
<template>
<div class="wrapper__list-link-man">
<n-tabs v-model="formData.active" type="line" justify-content="space-evenly" animated>
<n-tab-pane name="myChat" tab="我的聊天">
</n-tab-pane>
<n-tab-pane name="MyAssisted" tab="我协助的">
</n-tab-pane>
</n-tabs>
<ListLinkMan
v-if="formData.active==='myChat'"
:list="selectData.linkList"/>
</div>
</template>
<script setup lang="ts">
import ListLinkMan from "./module/ListLinkMan/index.vue";
import {ref} from "vue";
const formData = ref<any>({
active: "myChat"
})
const selectData = ref<any>({
linkList: [
{
userId: "1",
userName: "泰裤啦小助手😊",
lastLinkTime: "2023-06-25 03:42:26",
headerImg: "src/assets/header.png"
},
{
userId: "2",
userName: "Demo-不愧是我啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦",
lastLinkTime: "2023-06-25 03:32:26",
headerImg: "src/assets/header.png"
},
{
userId: "3",
userName: "程序坞",
lastLinkTime: "2023-06-25 03:11:26",
headerImg: "src/assets/header.png"
},
]
})
</script>
<style lang="scss" scoped>
.wrapper__list-link-man {
width: 100%;
height: 100%;
background: #e3ff1d;
}
</style>
src/components/Chat/module/LeftLinkMan/module/ListLinkMan/index.vue
<template>
<div class="wrapper__list-link-man">
<n-scrollbar class="list-link-man__scrollbar">
<template v-for="(item,index) in props.list">
<LinkManItem :data="item"/>
</template>
</n-scrollbar>
</div>
</template>
<script setup lang="ts">
import LinkManItem from "./module/LinkManItem/index.vue";
const props = defineProps({
//说明:开始时间提示
list: {
type: Array,
default: () => [],
}
})
</script>
<style lang="scss" scoped>
.wrapper__list-link-man {
.list-link-man__scrollbar {
height: calc(100% - 0.42rem);
}
}
</style>
src/components/Chat/module/LeftLinkMan/module/ListLinkMan/module/LinkManItem/index.vue
<template>
<div class="wrapper__link-man-item">
<div class="link-man-item__header">
<img style="border-radius:0.13rem;width: 100%;height: 100%" :src="props.data.headerImg"
:alt="props.data.userName"/>
</div>
<div class="link-man-item__name">
<div class="text__name" v-html="props.data.userName"></div>
<div class="text__time">{{ props.data.lastLinkTime }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import {PropType} from "vue";
const props = defineProps({
//说明:开始时间提示
data: {
type: Object as PropType<any>,
default: () => {
},
}
})
</script>
<style lang="scss" scoped>
@import "/src/styles/viriables";
.wrapper__link-man-item {
display: flex;
align-items: center;
padding: 0.15rem 0.24rem;
margin: 10px;
background-color: #0D0D0D;
border-radius: 0.06rem;
.link-man-item {
&__header {
width: 0.5rem;
height: 0.5rem;
}
&__name {
width: 100%;
height: 100%;
flex: 1;
color: $text-color;
text-align: left;
margin-left: 0.1rem;
display: flex;
flex-direction: column;
.text__name {
font-size: 0.14rem;
font-weight: 550;
/* 裁剪多余的 */
overflow: hidden;
/* 多余的以省略号出现 */
text-overflow: ellipsis;
/* 将对象作为弹性伸缩盒子模型显示 */
display: -webkit-box;
/* 限制再一个块元素再文本显示的行数 */
-webkit-line-clamp: 1;
/* 设置或检索伸缩盒对象的子元素的排列方式 */
-webkit-box-orient: vertical;
}
.text__time {
color: $text-time-color;
}
}
}
}
</style>

高度超出长度暂不理会。
修改背景颜色:
src/components/Chat/module/LeftLinkMan/module/ListLinkMan/module/LinkManItem/index.vue
<template>
<div class="wrapper__link-man-item" @click="handleSelectManClick">
<div class="link-man-item__header">
<img style="border-radius:0.13rem;width: 100%;height: 100%" :src="props.data.headerImg"
:alt="props.data.userName"/>
</div>
<div class="link-man-item__name">
<div class="text__name" v-html="props.data.userName"></div>
<div class="text__time">{{ props.data.lastLinkTime }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import {PropType} from "vue";
const props = defineProps({
//说明:开始时间提示
data: {
type: Object as PropType<any>,
default: () => {
},
}
})
const handleSelectManClick = () => {
}
</script>
<style lang="scss" scoped>
@import "/src/styles/viriables";
.wrapper__link-man-item:hover{
background-color: #35373b;
}
.wrapper__link-man-item {
display: flex;
align-items: center;
padding: 0.15rem 0.24rem;
margin: 10px;
background-color: #0D0D0D;
border-radius: 0.06rem;
cursor: pointer;
.link-man-item {
&__header {
width: 0.5rem;
height: 0.5rem;
}
&__name {
width: 100%;
height: 100%;
flex: 1;
color: $text-color;
text-align: left;
margin-left: 0.1rem;
display: flex;
flex-direction: column;
.text__name {
font-size: 0.14rem;
font-weight: 550;
/* 裁剪多余的 */
overflow: hidden;
/* 多余的以省略号出现 */
text-overflow: ellipsis;
/* 将对象作为弹性伸缩盒子模型显示 */
display: -webkit-box;
/* 限制再一个块元素再文本显示的行数 */
-webkit-line-clamp: 1;
/* 设置或检索伸缩盒对象的子元素的排列方式 */
-webkit-box-orient: vertical;
}
.text__time {
color: $text-time-color;
}
}
}
}
</style>
src/components/Chat/module/LeftLinkMan/module/ListLinkMan/index.vue
<template>
<div class="wrapper__list-link-man">
<n-scrollbar class="list-link-man__scrollbar">
<template v-for="(item,index) in props.list">
<LinkManItem :data="item"/>
</template>
</n-scrollbar>
</div>
</template>
<script setup lang="ts">
import LinkManItem from "./module/LinkManItem/index.vue";
const props = defineProps({
list: {
type: Array,
default: () => [],
}
})
</script>
<style lang="scss" scoped>
@import "/src/styles/viriables";
::v-deep(.n-scrollbar) {
max-height: calc($content-height - 0.24rem - 0.64rem);
}
.wrapper__list-link-man {
width: 100%;
height: 100%;
.list-link-man {
&__scrollbar {
//max-height: calc($content-height - 0.24rem - 0.16rem);
}
}
}
</style>
还有测试用的背景去掉,这里不粘贴代码了:

右侧聊天区域
先调整窗体大小
src/components/Chat/index.vue
.....
<style lang="scss" scoped>
@import "/src/styles/viriables";
.wrapper__chat {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
.chat {
&__left-people {
width:$left-link-man-width;
height: 100%;
}
&__content {
flex: 5;
height: $content-height;
display: flex;
flex-direction: column;
&__info {
flex: 5;
height: 100%;
background: #40f0ff;
}
&__input {
flex: 2;
height: 100%;
background: #f89751;
}
}
}
}
</style>
src/styles/viriables.scss
$title-height: 0.8rem;
$window-width: 14rem;
$window-height: 7rem;
$content-height: calc($window-height - $title-height);
//左侧联系人栏宽度
$left-link-man-width:3.5rem;
//文字颜色
$text-color: #F9FAFC;
//时间的文字颜色
$text-time-color: #9A9B9D;

上部分联调界面开发
src/components/Chat/index.vue
<template>
<div class="wrapper__chat">
<div class="chat__left-people">
<LeftLinkMan/>
</div>
<div class="chat__content">
<div class="chat__content__info">
<ShowChatInfo/>
</div>
<div class="chat__content__input">
<InputContent/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import LeftLinkMan from "./module/LeftLinkMan/index.vue";
import ShowChatInfo from "./module/ShowChatInfo/index.vue";
import InputContent from "./module/InputContent/index.vue";
</script>
<style lang="scss" scoped>
@import "/src/styles/viriables";
.wrapper__chat {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
.chat {
&__left-people {
width:$left-link-man-width;
height: 100%;
}
&__content {
width: 100%;
flex: 5;
height: calc($content-height - 0.12rem);
display: flex;
flex-direction: column;
margin: 0 0.3rem 0.12rem 0.42rem;
border: 1px solid #272727;
border-radius: 0.08rem;
background: #40f0ff;
&__info {
width: 100%;
height: 100%;
flex: 1;
background: #c7c174;
}
&__input {
width: 100%;
height: 2rem;
background: #f89751;
}
}
}
}
</style>
src/components/Chat/module/ShowChatInfo/index.vue
<template>
<div class="wrapper__show-chat-info">
<div class="show-chat-info__title"></div>
<div></div>
</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
.wrapper__show-chat-info {
width: 100%;
height: 100%;
.show-chat-info {
&__title {
width: 100%;
height: 0.45rem;
background-color: #535bf2;
}
}
}
</style>

网友评论