123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- <template>
- <view>
- <view class="tips color_fff size_12 align_c" :class="{ 'show':ajax.loading }" @tap="getHistoryMsg">
- {{ajax.loadText}}
- </view>
- <view class="box-1" id="list-box">
- <view class="talk-list">
- <view v-for="(item,index) in talkList" :key="index" :id="`msg-${item.id}`">
- <view class="item flex_col" :class=" item.type == 1 ? 'push':'pull' ">
- <image :src="item.pic" mode="aspectFill" class="pic"></image>
- <view class="content">{{item.content}}</view>
- </view>
- </view>
- </view>
- </view>
- <view class="box-2">
- <view class="flex_col">
- <view class="flex_grow">
- <input type="text" class="content" v-model="content" placeholder="请输入聊天内容"
- placeholder-style="color:#DDD;" :cursor-spacing="6">
- </view>
- <button class="send" @tap="send">发送</button>
- </view>
- </view>
- </view>
- </template>
- <script>
- export default {
- data() {
- return {
- talkList: [],
- ajax: {
- rows: 20, //每页数量
- page: 1, //页码
- flag: true, // 请求开关
- loading: true, // 加载中
- loadText: '正在获取消息'
- },
- content: ''
- }
- },
- mounted() {
- this.$nextTick(() => {
- this.getHistoryMsg();
- });
- },
- onPageScroll(e) {
- if (e.scrollTop < 5) {
- this.getHistoryMsg();
- }
- },
- methods: {
- // 获取历史消息
- getHistoryMsg() {
- if (!this.ajax.flag) {
- return; //
- }
- // 此处用到 ES7 的 async/await 知识,为使代码更加优美。不懂的请自行学习。
- let get = async () => {
- this.hideLoadTips();
- this.ajax.flag = false;
- let data = await this.joinHistoryMsg();
- console.log('----- 模拟数据格式,供参考 -----');
- console.log(data); // 查看请求返回的数据结构
- // 获取待滚动元素选择器,解决插入数据后,滚动条定位时使用
- let selector = '';
- if (this.ajax.page > 1) {
- // 非第一页,则取历史消息数据的第一条信息元素
- selector = `#msg-${this.talkList[0].id}`;
- } else {
- // 第一页,则取当前消息数据的最后一条信息元素
- selector = `#msg-${data[data.length-1].id}`;
- }
- // 将获取到的消息数据合并到消息数组中
- this.talkList = [...data, ...this.talkList];
- // 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
- this.$nextTick(() => {
- // 设置当前滚动的位置
- this.setPageScrollTo(selector);
- this.hideLoadTips(true);
- if (data.length < this.ajax.rows) {
- // 当前消息数据条数小于请求要求条数时,则无更多消息,不再允许请求。
- // 可在此处编写无更多消息数据时的逻辑
- } else {
- this.ajax.page++;
- // 延迟 200ms ,以保证设置窗口滚动已完成
- setTimeout(() => {
- this.ajax.flag = true;
- }, 200)
- }
- })
- }
- get();
- },
- // 拼接历史记录消息,正式项目可替换为请求历史记录接口
- joinHistoryMsg() {
- let join = () => {
- let arr = [];
- //通过当前页码及页数,模拟数据内容
- let startIndex = (this.ajax.page - 1) * this.ajax.rows;
- let endIndex = startIndex + this.ajax.rows;
- for (let i = startIndex; i < endIndex; i++) {
- arr.push({
- "id": i, // 消息的ID
- "content": `这是历史记录的第${i+1}条消息`, // 消息内容
- "type": Math.random() > 0.5 ? 1 : 0, // 此为消息类别,设 1 为发出去的消息,0 为收到对方的消息,
- "pic": "https://assets.api.uizard.io/api/cdn/stream/db1ab9f9-00bf-4b03-b2b2-1fc8ece9ba19.png" // 头像
- })
- }
- /*
- 颠倒数组中元素的顺序。将最新的数据排在本次接口返回数据的最后面。
- 后端接口按 消息的时间降序查找出当前页的数据后,再将本页数据按消息时间降序排序返回。
- 这是数据的重点,因为页面滚动条和上拉加载历史的问题。
- */
- arr.reverse();
- return arr;
- }
- // 此处用到 ES6 的 Promise 知识,不懂的请自行学习。
- return new Promise((done, fail) => {
- // 无数据请求接口,由 setTimeout 模拟,正式项目替换为 ajax 即可。
- setTimeout(() => {
- let data = join();
- done(data);
- }, 1500);
- })
- },
- // 设置页面滚动位置
- setPageScrollTo(selector) {
- let view = uni.createSelectorQuery().in(this).select(selector);
- view.boundingClientRect((res) => {
- uni.pageScrollTo({
- scrollTop: res.top - 30, // -30 为多显示出大半个消息的高度,示意上面还有信息。
- duration: 0
- });
- }).exec();
- },
- // 隐藏加载提示
- hideLoadTips(flag) {
- if (flag) {
- this.ajax.loadText = '消息获取成功';
- setTimeout(() => {
- this.ajax.loading = false;
- }, 300);
- } else {
- this.ajax.loading = true;
- this.ajax.loadText = '正在获取消息';
- }
- },
- // 发送信息
- send() {
- if (!this.content) {
- uni.showToast({
- title: '请输入有效的内容',
- icon: 'none'
- })
- return;
- }
- uni.showLoading({
- title: '正在发送'
- })
- setTimeout(() => {
- uni.hideLoading();
- // 将当前发送信息 添加到消息列表。
- let data = {
- "id": new Date().getTime(),
- "content": this.content,
- "type": 1,
- "pic": "https://assets.api.uizard.io/api/cdn/stream/db1ab9f9-00bf-4b03-b2b2-1fc8ece9ba19.png"
- }
- this.talkList.push(data);
- this.$nextTick(() => {
- // 清空内容框中的内容
- this.content = '';
- uni.pageScrollTo({
- scrollTop: 999999, // 设置一个超大值,以保证滚动条滚动到底部
- duration: 0
- });
- })
- }, 1500);
- }
- }
- }
- </script>
- <style lang="scss">
- /* 设置常用元素尺寸规则 */
- view,
- textarea,
- input,
- label,
- form,
- button,
- image {
- box-sizing: border-box;
- }
- /* 按钮样式处理 */
- button {
- font-size: 28rpx;
- }
- /* 取消按钮默认的边框线效果 */
- button:after {
- border: none;
- }
- /* 设置图片默认样式,取消默认尺寸 */
- image {
- display: block;
- height: auto;
- width: auto;
- }
- /* 输入框默认字体大小 */
- textarea,
- input {
- font-size: 28rpx;
- }
- ;
- /* 列式弹性盒子 */
- .flex_col {
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- justify-content: flex-start;
- align-items: center;
- align-content: center;
- }
- /* 行式弹性盒子 */
- .flex_row {
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- justify-content: flex-start;
- align-items: flex-start;
- align-content: flex-start;
- }
- /* 弹性盒子弹性容器 */
- .flex_col .flex_grow {
- width: 0;
- flex-grow: 1;
- }
- .flex_row .flex_grow {
- flex-grow: 1;
- }
- /* 弹性盒子允许换行 */
- .flex_col.flex_wrap {
- flex-wrap: wrap;
- }
- /* 弹性盒子居中对齐 */
- .flex_col.flex_center,
- .flex_row.flex_center {
- justify-content: center;
- }
- /* 列式弹性盒子两端对齐 */
- .flex_col.flex_space {
- justify-content: space-between;
- }
- /* 弹性盒子快速分栏 ,这里非常郁闷 uniapp 居然不支持 * 选择器 */
- .flex_col.flex_col_2>view {
- width: 50%;
- }
- .flex_col.flex_col_3>view {
- width: 33.33333%;
- }
- .flex_col.flex_col_4>view {
- width: 25%;
- }
- .flex_col.flex_col_5>view {
- width: 20%;
- }
- .flex_col.flex_col_6>view {
- width: 16.66666%;
- }
- /* 字体颜色 */
- .color_333 {
- color: #333;
- }
- .color_666 {
- color: #666;
- }
- .color_999 {
- color: #999;
- }
- .color_ccc {
- color: #ccc;
- }
- .color_fff {
- color: #fff;
- }
- .color_6dc {
- color: #6dca6d;
- }
- .color_d51 {
- color: #d51917;
- }
- .color_09f {
- color: #0099ff;
- }
- /* 背景色*/
- .bg_fff {
- background-color: #ffffff;
- }
- /* 字体大小 */
- .size_10 {
- font-size: 20rpx;
- }
- .size_12 {
- font-size: 24rpx;
- }
- .size_14 {
- font-size: 28rpx;
- }
- .size_16 {
- font-size: 32rpx;
- }
- .size_18 {
- font-size: 36rpx;
- }
- .size_20 {
- font-size: 40rpx;
- }
- /* 字体加粗 */
- .font_b {
- font-weight: bold;
- }
- /* 对齐方式 */
- .align_c {
- text-align: center;
- }
- .align_l {
- text-align: left;
- }
- .align_r {
- text-align: right;
- }
- /* 遮罩 */
- .shade {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- background-color: rgba(0, 0, 0, 0.8);
- z-index: 100;
- }
- /* 弹窗 */
- .shade_box {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- margin: auto;
- z-index: 101;
- min-width: 200rpx;
- min-height: 200rpx;
- }
- /* 加载数据提示 */
- .tips {
- position: fixed;
- left: 0;
- top: var(--window-top);
- width: 100%;
- z-index: 9;
- background-color: rgba(0, 0, 0, 0.15);
- height: 72rpx;
- line-height: 72rpx;
- transform: translateY(-80rpx);
- transition: transform 0.3s ease-in-out 0s;
- &.show {
- transform: translateY(0);
- }
- }
- .box-1 {
- width: 100%;
- height: auto;
- padding-bottom: 100rpx;
- box-sizing: content-box;
- /* 兼容iPhoneX */
- margin-bottom: 0;
- margin-bottom: constant(safe-area-inset-bottom);
- margin-bottom: env(safe-area-inset-bottom);
- }
- .box-2 {
- position: fixed;
- left: 0;
- width: 100%;
- bottom: 0;
- height: auto;
- z-index: 2;
- border-top: #e5e5e5 solid 1px;
- box-sizing: content-box;
- background-color: #F3F3F3;
- /* 兼容iPhoneX */
- padding-bottom: 0;
- padding-bottom: constant(safe-area-inset-bottom);
- padding-bottom: env(safe-area-inset-bottom);
- >view {
- padding: 0 20rpx;
- height: 100rpx;
- }
- .content {
- background-color: #fff;
- height: 64rpx;
- padding: 0 20rpx;
- border-radius: 32rpx;
- font-size: 28rpx;
- }
- .send {
- background-color: #42b983;
- color: #fff;
- height: 64rpx;
- margin-left: 20rpx;
- border-radius: 32rpx;
- padding: 0;
- width: 120rpx;
- line-height: 62rpx;
- &:active {
- background-color: #5fc496;
- }
- }
- }
- .talk-list {
- padding-bottom: 20rpx;
- /* 消息项,基础类 */
- .item {
- padding: 20rpx 20rpx 0 20rpx;
- align-items: flex-start;
- align-content: flex-start;
- color: #333;
- .pic {
- width: 92rpx;
- height: 92rpx;
- border-radius: 50%;
- border: #fff solid 1px;
- }
- .content {
- padding: 20rpx;
- border-radius: 4px;
- max-width: 500rpx;
- word-break: break-all;
- line-height: 52rpx;
- position: relative;
- }
- /* 收到的消息 */
- &.pull {
- .content {
- margin-left: 32rpx;
- background-color: #fff;
- &::after {
- content: '';
- display: block;
- width: 0;
- height: 0;
- border-top: 16rpx solid transparent;
- border-bottom: 16rpx solid transparent;
- border-right: 20rpx solid #fff;
- position: absolute;
- top: 30rpx;
- left: -18rpx;
- }
- }
- }
- /* 发出的消息 */
- &.push {
- /* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
- flex-direction: row-reverse;
- .content {
- margin-right: 32rpx;
- background-color: #a0e959;
- &::after {
- content: '';
- display: block;
- width: 0;
- height: 0;
- border-top: 16rpx solid transparent;
- border-bottom: 16rpx solid transparent;
- border-left: 20rpx solid #a0e959;
- position: absolute;
- top: 30rpx;
- right: -18rpx;
- }
- }
- }
- }
- }
- </style>
|