Zory 1 week ago
parent
commit
6f7467e391
39 changed files with 4306 additions and 81 deletions
  1. BIN
      public/img/msn.mp3
  2. 9 2
      src/api/model/chat.js
  3. 26 0
      src/api/model/order.js
  4. BIN
      src/assets/msn.mp3
  5. 91 0
      src/components/cache-image/cache-image.vue
  6. 269 0
      src/components/chat/index.vue
  7. 144 0
      src/components/chat/m-article.vue
  8. 166 0
      src/components/chat/m-audio.vue
  9. 44 0
      src/components/chat/m-emoji-img.vue
  10. 143 0
      src/components/chat/m-functional-module.vue
  11. 68 0
      src/components/chat/m-image.vue
  12. 139 0
      src/components/chat/m-map.vue
  13. 50 0
      src/components/chat/m-order.vue
  14. 46 0
      src/components/chat/m-price.vue
  15. 243 0
      src/components/chat/m-redPacket.vue
  16. 159 0
      src/components/chat/m-share-mall.vue
  17. 159 0
      src/components/chat/m-share-sbcf.vue
  18. 164 0
      src/components/chat/m-text.vue
  19. 93 0
      src/components/chat/m-video.vue
  20. 42 0
      src/components/chat/quoteType/m-audio.vue
  21. 54 0
      src/components/chat/quoteType/m-image.vue
  22. 75 0
      src/components/chat/quoteType/m-other.vue
  23. 58 0
      src/components/chat/quoteType/m-text.vue
  24. 542 72
      src/components/imFloat/imFloat.vue
  25. 1 1
      src/layout/index.vue
  26. 27 0
      src/store/emoji.js
  27. 10 6
      src/style/app.scss
  28. 180 0
      src/style/index.scss
  29. 186 0
      src/util/EmojiDecoder.js
  30. 49 0
      src/util/formatDate.js
  31. 40 0
      src/util/getStatusBar.js
  32. 35 0
      src/util/index.js
  33. 30 0
      src/util/jsonUrl.js
  34. 29 0
      src/util/openimg.js
  35. 833 0
      src/util/push.js
  36. 24 0
      src/util/show.js
  37. 22 0
      src/util/throttle.js
  38. 52 0
      src/util/to.js
  39. 4 0
      src/util/vibrateShort.js

BIN
public/img/msn.mp3


+ 9 - 2
src/api/model/chat.js

@@ -9,8 +9,15 @@ export default {
             return await http.get(this.url, data);
         },
     },
-    save: {
-        url: `${config.API_URL}/category/save`,
+    msg: {
+        url: `${config.API_URL}/service/chat/msg`,
+        name: "-",
+        get: async function (data = {}) {
+            return await http.get(this.url, data);
+        },
+    },
+    send: {
+        url: `${config.API_URL}/service/chat/send`,
         name: "-",
         post: async function (data = {}) {
             return await http.post(this.url, data);

+ 26 - 0
src/api/model/order.js

@@ -0,0 +1,26 @@
+import config from "@/config";
+import http from "@/utils/request";
+
+export default {
+    list: {
+        url: `${config.API_URL}/order/list`,
+        name: "-",
+        get: async function (data = {}) {
+            return await http.get(this.url, data);
+        },
+    },
+    detail: {
+        url: `${config.API_URL}/order/detail`,
+        name: "-",
+        get: async function (data = {}) {
+            return await http.get(this.url, data);
+        },
+    },
+    send: {
+        url: `${config.API_URL}/service/chat/send`,
+        name: "-",
+        post: async function (data = {}) {
+            return await http.post(this.url, data);
+        },
+    },
+}

BIN
src/assets/msn.mp3


+ 91 - 0
src/components/cache-image/cache-image.vue

@@ -0,0 +1,91 @@
+<template>
+	<el-image class="img" :src="img_url" :style="mstyle" mode="aspectFill"></el-image>
+</template>
+
+<script>
+export default {
+	name: 'cache-image',
+	props: {
+		src: {
+			type: String,
+			default: ''
+		},
+		mstyle: {
+			type: String,
+			default: ''
+		},
+		ext: {
+			type: String,
+			default: 'gif'
+		}
+	},
+	data() {
+		return {
+			img_url: ''
+		};
+	},
+	watch: {
+		src: {
+			handler: async function (n) {
+				if (!n) return;
+				let isCache = await this.isCache(this.filename);
+				if (isCache) {
+					this.img_url = this.filename;
+				} else {
+					this.img_url = n;
+					this.createDownload();
+				}
+			},
+			immediate: true
+		}
+	},
+	computed: {
+		filename() {
+			let filename = `_doc/IM_emoji_pack/${this.src.replace(/\./g, '_').replace(/\//g, '_')}.${this.ext}`;
+			return filename;
+		}
+	},
+	methods: {
+		// 判断是否已经缓存
+		isCache(filePath) {
+			return new Promise((r) => {
+				uni.getFileInfo({
+					filePath,
+					success: (res) => {
+						if (res.errMsg === 'getFileInfo:ok') {
+							return r(true);
+						}
+						return r(false);
+					},
+					fail: (e) => {
+						return r(false);
+					}
+				});
+			});
+		},
+		// 下载存储
+		createDownload() {
+			let dtask = plus.downloader.createDownload(
+				this.src,
+				{
+					filename: this.filename
+				},
+				//
+				(download, status) => {
+					if (status == 200) {
+						let image = download.options.filename; //设置的名字
+						// this.image = download.filename; //实际生成的名字
+						// 将本地URL路径转换成平台绝对路径
+						this.img_url = plus.io.convertLocalFileSystemURL(image);
+					} else {
+						this.img_url = src;
+					}
+				}
+			);
+			dtask.start();
+			// 下载进度
+			// dtask.addEventListener('statechanged', function (task, status) {});
+		}
+	}
+};
+</script>

+ 269 - 0
src/components/chat/index.vue

@@ -0,0 +1,269 @@
+<template>
+	<div>
+		<!-- 群公告 -->
+		<div class="text_26 icon_ group_notice" v-if="item.type === 'group_notice'">
+			管理员设置了新的
+			<text style="color: #fe6702; margin: 0 10px">群公告</text>
+			请及时查看
+		</div>
+		<div class="text_26 icon_ group_notice" v-else-if="item.type === 'update_group_name'">
+			管理员修改了群名称为:"
+			<text style="color: #fe6702; margin: 0 10px">{{ item.payload.name }}</text>
+			"
+		</div>
+		<div class="flex_r item" :class="{ item_: isMy }" v-else>
+			<div class="item-img">
+				<!-- <div class="item-img-pendant">
+					<image class="img" :src="item.senderData.avatar_pendant" mode="aspectFill"></image>
+				</div> -->
+				<div class="z_index2 item-img-url">
+					<el-image class="img" style="width: 56px;height: 56px;" :src="item.senderData.avatar" fit="aspectFill"></el-image>
+				</div>
+			</div>
+			<div style="width: 22px"></div>
+			<div class="item-content" :class="{ item_content: !isMy, item_content2: !item.senderData.name }">
+				<div class="text_24 color__ item-name" v-if="item.senderData.name">{{ item.senderData.name }}</div>
+				<div class="flex_r fa_c item-content-box" :class="{ row_reverse: isMy }">
+					<div class="flex_r fa_c item-content-box-box" :class="'A' + item.timestamp" @longpress.stop="longpress">
+						<div v-if="isMy">
+							<div class="loading" v-if="item.status === 'new' || item.status === 'sending'">
+								<m-loading-icon iconColor="#989898"></m-loading-icon>
+							</div>
+							<div class="loading" v-if="item.status === 'error'">
+								<el-image
+									class="img"
+									src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTUxMiAzN2MyNjIuMzEgMCA0NzUgMjEyLjY5IDQ3NSA0NzVTNzc0LjMxIDk4NyA1MTIgOTg3IDM3IDc3NC4zMSAzNyA1MTIgMjQ5LjY5IDM3IDUxMiAzN3ptMCA2NjEuMDhjLTI5LjI4IDAtNTMuMDEgMjMuNzMtNTMuMDEgNTMuMDEgMCAyOS4yOCAyMy43MyA1My4wMSA1My4wMSA1My4wMSAyOS4yOCAwIDUzLjAxLTIzLjczIDUzLjAxLTUzLjAxIDAtMjkuMjgtMjMuNzMtNTMuMDEtNTMuMDEtNTMuMDF6bTAtNDc4LjE4Yy0zNy40MyAwLTY3Ljg2IDMwLjQzLTY3Ljg2IDY3Ljk2bDI1LjQ1IDMxOS43OC41OSA2LjEyYzMuMzkgMTkuOTggMjAuODEgMzUuMjMgNDEuODIgMzUuMjMgMjMuMTEgMCA0MS43Ny0xOC40NSA0Mi40MS00MS4zNWwyNS40NS0zMTkuNzgtLjQtNy40MWMtMy42OS0zNC4wNS0zMi41My02MC41NS02Ny40Ni02MC41NXoiIGZpbGw9IiNmNDM0MmYiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guc2VhcmNoX2luZGV4LjAuaTkuNjJlMTNhODF0ejVJRE4iIGNsYXNzPSJzZWxlY3RlZCIvPjwvc3ZnPg=="
+									fit="aspectFill"
+								></el-image>
+							</div>
+						</div>
+						<div v-if="item.type === 'text' || item.type === 'text_quote'">
+							<m-text :isMy="isMy" :value="item" @click="onClick"></m-text>
+						</div>
+						<div v-else-if="item.type === 'image' || item.type === 'image_transmit'" @click="onClick">
+							<m-image :isMy="isMy" :value="item" @imgLoad="imgLoad"></m-image>
+						</div>
+						<div v-else-if="item.type === 'video'" @click="onClick">
+							<m-video :isMy="isMy" :value="item"></m-video>
+						</div>
+						<div v-else-if="item.type === 'order'" @click="onClick">
+							<m-order :isMy="isMy" :value="item"></m-order>
+						</div>
+						<div v-else-if="item.type === 'pay'" @click="onClick">
+							<m-price :isMy="isMy" :value="item"></m-price>
+						</div>
+						<div v-else-if="item.type === 'audio'">
+							<m-audio :isMy="isMy" :value="item" @click="onClick"></m-audio>
+						</div>
+						<!-- 红包 -->
+						<div v-else-if="item.type === 'red_envelope'">
+							<m-red-packet :isMy="isMy" :myid="myid" :value="item" @onClick="onClick"></m-red-packet>
+						</div>
+						<!-- 表情包 -->
+						<div v-else-if="item.type === 'emoji_pack'" @tap.stop="onClick">
+							<m-emoji-img :isMy="isMy" :value="item"></m-emoji-img>
+						</div>
+						<!-- 位置 -->
+						<div v-else-if="item.type === 'map'">
+							<m-map :isMy="isMy" :value="item" @click="onClick"></m-map>
+						</div>
+						<!-- 文章 -->
+						<div v-else-if="item.type === 'article'">
+							<m-article :isMy="isMy" :value="item" @click="onClick"></m-article>
+						</div>
+						<!-- 商家分享 -->
+						<div v-else-if="item.type === 'share_SBCF'">
+							<m-share-sbcf :isMy="isMy" :value="item" @click="onClick"></m-share-sbcf>
+						</div>
+						<div v-else-if="item.type === 'share_mall'">
+							<m-share-mall :isMy="isMy" :value="item" @click="onClick"></m-share-mall>
+						</div>
+						<div v-else-if="item.type === 'functional_module'">
+							<m-functional-module :isMy="isMy" :value="item" @click="onClick"></m-functional-module>
+						</div>
+
+						<div class="flex1"></div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { to, vibrateShort } from '@/util/index.js';
+import mText from './m-text.vue';
+import mImage from './m-image.vue';
+import mVideo from './m-video.vue';
+import mAudio from './m-audio.vue';
+import mRedPacket from './m-redPacket.vue';
+import mMap from './m-map.vue';
+import mEmojiImg from './m-emoji-img.vue';
+import mArticle from './m-article.vue';
+import mShareSbcf from './m-share-sbcf.vue';
+import mShareMall from './m-share-mall.vue';
+import mOrder from './m-order.vue';
+import mPrice from './m-price.vue';
+
+import mFunctionalModule from './m-functional-module.vue';
+
+let isLongpress = false;
+let isLongpressAvatar = false;
+export default {
+	components: {
+		mText,
+		mImage,
+		mVideo,
+		mAudio,
+		mRedPacket,
+		mMap,
+		mEmojiImg,
+		mArticle,
+		mShareSbcf,
+		mShareMall,
+		mFunctionalModule,
+		mOrder,
+		mPrice
+	},
+	props: {
+		myid: {
+			type: [String, Number],
+			default: null
+		},
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		item: {
+			type: Object,
+			default: {}
+		},
+		index: {
+			type: Number,
+			default: 0
+		}
+	},
+	data() {
+		return {
+			userInforData: {}
+		};
+	},
+	created() {},
+	methods: {
+		imgLoad(e){
+			this.$emit('imgLoad', e);
+		},
+		onClick() {
+			if (isLongpress) return (isLongpress = false);
+			if (this.item.status === 'error') return;
+			this.$emit('onClick', this.item, this.index);
+		},
+		// @某人
+		longpressAvatar() {
+			isLongpressAvatar = true;
+			this.$emit('mention', this.item, this.index);
+		},
+		onItem() {
+			if (isLongpressAvatar) return (isLongpressAvatar = false);
+			console.log(this.item);
+			to('/pagesGoEasy/group_member_infor/index', { member_id: this.item.senderId, group_id: this.item.groupId });
+		},
+
+		// 长按
+		longpress(e) {
+			isLongpress = true;
+			vibrateShort();
+			console.log(this.item);
+			this.$nextTick(() => {
+				// let div = uni.createSelectorQuery().select(`.A${this.item.timestamp}`);
+				// div.boundingClientRect((data) => {
+				// 	this.$emit('onLongpress', this.item, data);
+				// }).exec();
+
+				const query = uni.createSelectorQuery().in(this);
+				query
+					.select(`.A${this.item.timestamp}`)
+					.boundingClientRect((data) => {
+						this.$emit('onLongpress', this.item, data);
+					})
+					.exec();
+			});
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.group_notice {
+	width: 100%;
+	height: 80px;
+	color: #a3a3a3;
+}
+.row_reverse {
+	flex-direction: row-reverse !important;
+}
+.item {
+	box-sizing: border-box;
+	padding-bottom: 20px;
+	position: relative;
+	z-index: 0;
+	width: calc(100% - 50px);
+	margin: 0 auto;
+	.item-name {
+		margin-bottom: 4px;
+	}
+}
+.item_ {
+	flex-direction: row-reverse !important;
+	.item-name {
+		position: relative;
+		top: 0px;
+		display: none;
+	}
+}
+.flex_r {
+    display: flex;
+    flex-direction: row;
+}
+.item-img {
+	position: relative;
+	width: 56px;
+	height: 56px;
+	.item-img-pendant {
+		position: absolute;
+		z-index: 3;
+		top: -2px;
+		left: -2px;
+		right: -2px;
+		bottom: -2px;
+	}
+	.item-img-url {
+		width: 56px;
+		height: 56px;
+		overflow: hidden;
+		border-radius: 6px;
+		background-color: #fff;
+	}
+}
+.item-content {
+	width: calc(100% - 164px);
+	.item-content-box {
+		position: relative;
+		.item-content-box-box {
+		}
+		.loading {
+			width: 40px;
+			height: 40px;
+			margin: 0 10px;
+		}
+	}
+}
+.item_content {
+	position: relative;
+	top: -10px;
+}
+.item_content2 {
+	position: relative;
+	top: 0px !important;
+}
+</style>

+ 144 - 0
src/components/chat/m-article.vue

@@ -0,0 +1,144 @@
+<template>
+	<div class="flex_c row">
+		<div class="flex_r text-box" :class="{ text_box: isMy }" @tap.stop="onClick">
+			<div class="text" :class="isMy ? 'text_r' : 'text_l'">
+				<div class="flex_c_c article">
+					<div class="text_32 ellipsis_2 article-title">{{ value.payload.title }}</div>
+					<div class="flex_r text_26 color__ article-box">
+						<div class="flex1 article-box-text">
+							{{ value.payload.short_title }}
+						</div>
+						<div class="article-box-img">
+							<el-image class="img" :src="value.payload.share_image" fit="aspectFill"></el-image>
+						</div>
+					</div>
+
+					<div class="m-line">
+						<m-line color="#f0f0f0" length="100%" :hairline="true"></m-line>
+					</div>
+
+					<div class="flex_r fa_c article-b">
+						<div class="article-b-icon"></div>
+						<div class="text_20 color__ article-b-text">xxxx</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row {
+}
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+}
+
+.text_r {
+	position: relative;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+
+.article {
+	box-sizing: border-box;
+	padding: 14rpx 20rpx 4rpx 20rpx;
+	width: 490rpx;
+	border-radius: 10rpx;
+	overflow: hidden;
+	background-color: #fff;
+	border: 0.5px solid #fff;
+	.article-title {
+		box-sizing: border-box;
+		width: 100%;
+		margin-bottom: 10rpx;
+	}
+	.article-box {
+		width: 100%;
+		.article-box-text {
+		}
+		.article-box-img {
+			width: 110rpx;
+			height: 110rpx;
+			background-color: #f1f1f1;
+		}
+	}
+	.m-line {
+		width: 100%;
+		height: 1px;
+		margin-top: 20rpx;
+	}
+	.article-b {
+		width: 100%;
+		margin-top: 4rpx;
+		.article-b-icon {
+			width: 26rpx;
+			height: 26rpx;
+			background-color: #f1f1f1;
+			border-radius: 50%;
+			overflow: hidden;
+			margin-right: 10rpx;
+		}
+		.article-b-text {
+		}
+	}
+}
+</style>

+ 166 - 0
src/components/chat/m-audio.vue

@@ -0,0 +1,166 @@
+<template>
+	<div class="flex_c">
+		<div class="flex_r" :class="{ text_box: isMy }">
+			<div class="text-box" @tap.stop="onClick">
+				<div
+					class="text_30 flex_r fa_c text"
+					:class="isMy ? 'text_r' : 'text_l'"
+					:style="{ width: setWidth }"
+					:hover-class="isMy ? 'hover_classr' : 'hover_classl'"
+					:hover-stay-time="60"
+				>
+					<div class="text-icon" v-if="value.pause !== 3">
+						<el-image
+							class="img"
+							src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTY3NC44MDYgNzcuMTI2YTQ2LjAwOCA0Ni4wMDggMCAwIDAtNjYuMTc2IDAgNDYuMDA4IDQ2LjAwOCAwIDAgMCAwIDYzLjAyNSA1MjAuNTg4IDUyMC41ODggMCAwIDEgMCA3MzUuNTA0IDQ3LjI2OSA0Ny4yNjkgMCAwIDAgMCA2Ni44MDcgNDcuMjY5IDQ3LjI2OSAwIDAgMCA2Ni44MDcgMCA2MTQuNDk2IDYxNC40OTYgMCAwIDAtLjYzLTg2NS4zMzZ6Ii8+PHBhdGggZD0iTTQ2Ny40NTMgMjQyLjg4MmE0Ny4yNjkgNDcuMjY5IDAgMCAwLTYzLjAyNSAwIDQ2LjYzOSA0Ni42MzkgMCAwIDAgMCA2My4wMjYgMjk5LjM3IDI5OS4zNyAwIDAgMSAwIDQyMi4yNjggNDcuMjY5IDQ3LjI2OSAwIDAgMCAwIDYzLjAyNiA0Ni42MzkgNDYuNjM5IDAgMCAwIDYzLjAyNSAwIDM5My45MDggMzkzLjkwOCAwIDAgMCAwLTU0OC4zMnptLTI3Ny4zMSAyMTQuOTE2YTc4LjE1MSA3OC4xNTEgMCAwIDAgMCAxMTAuOTI1IDc4Ljc4MiA3OC43ODIgMCAxIDAgMC0xMTAuOTI1eiIvPjwvc3ZnPg=="
+							mode="aspectFill"
+						></el-image>
+					</div>
+					<div class="text-icon" v-if="value.pause == 3">
+						<el-image class="img" style="transform: scale(1.35)" mode="aspectFill"></el-image>
+					</div>
+					<div class="text-duration">{{ Math.ceil(value.payload.duration) || 1 }}''</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+export default {
+	components: {},
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		setWidth() {
+			let width = 0;
+			const duration = Math.ceil(this.value.payload.duration);
+			// 基础长度
+			let basis = duration - 10;
+			if (basis >= 0) {
+				width = 10 * 8 + 60;
+				width = width + basis * 2;
+			} else {
+				width = duration * 8 + 60;
+			}
+			return `${width}px`;
+		}
+	},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text-box {
+	border-radius: 8rpx;
+}
+
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+	padding: 18rpx 16rpx;
+	border-radius: 8rpx;
+	word-break: break-all;
+	vertical-align: center;
+	.text-icon {
+		width: 32rpx;
+		height: 32rpx;
+		margin: 0 4rpx;
+	}
+	.text-duration {
+		margin: 0 8rpx;
+	}
+}
+
+.text_r {
+	position: relative;
+	z-index: 2;
+	background-color: #95ec6a;
+	flex-direction: row-reverse;
+	.text-icon {
+		transform: rotate(180deg);
+	}
+}
+.text_l {
+	position: relative;
+	z-index: 2;
+	background-color: #fff;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 28rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #95ec6a;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 28rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+.hover_classr {
+	background-color: #89d961;
+}
+.hover_classl {
+	background-color: #e2e2e2;
+}
+
+.hover_classr::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 28rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #89d961;
+}
+.hover_classl::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 28rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #e2e2e2;
+}
+</style>

+ 44 - 0
src/components/chat/m-emoji-img.vue

@@ -0,0 +1,44 @@
+<template>
+	<div class="flex_r m-image">
+		<!-- #ifdef H5 || MP -->
+		<el-image class="img" :src="value.payload.url" mode="aspectFill"></el-image>
+		<!-- #endif -->
+		<!-- #ifdef APP -->
+		<cacheImage :src="value.payload.url" :ext="value.payload.ext" mstyle="{width: 250px;height: 250px;}"></cacheImage>
+		<!-- #endif -->
+	</div>
+</template>
+<script>
+import cacheImage from '../cache-image/cache-image.vue';
+export default {
+	components: {
+		cacheImage
+	},
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {}
+};
+</script>
+
+<style scoped lang="scss">
+.m-image {
+	border-radius: 2px;
+	overflow: hidden;
+	.img {
+		width: 250px;
+		height: 250px;
+	}
+}
+</style>

+ 143 - 0
src/components/chat/m-functional-module.vue

@@ -0,0 +1,143 @@
+<template>
+	<div class="flex_c row">
+		<div class="flex_r text-box" :class="{ text_box: isMy }" @tap.stop="onClick">
+			<div class="text" :class="isMy ? 'text_r' : 'text_l'">
+				<div class="flex_c_c article">
+					<div class="flex_r fa_c article-info">
+						<div class="article-img">
+							<image class="img" :src="value.payload.image" mode="aspectFill"></image>
+						</div>
+						<div class="flex1">
+							<div class="text_32 article-title">{{ value.payload.title }}</div>
+							<div class="text_26 color__ article-title">{{ value.payload.text }}</div>
+						</div>
+					</div>
+
+					<div class="m-line">
+						<m-line color="#f0f0f0" length="100%" :hairline="true"></m-line>
+					</div>
+					<div class="flex_r fa_c article-b">
+						<!-- <div class="article-b-icon"></div> -->
+						<div class="text_20 color__ article-b-text">功能分享</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row {
+}
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+}
+
+.text_r {
+	position: relative;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+
+.article {
+	box-sizing: border-box;
+	padding: 14rpx 20rpx 4rpx 20rpx;
+	width: 490rpx;
+	border-radius: 10rpx;
+	overflow: hidden;
+	background-color: #fff;
+	border: 0.5px solid #fff;
+	.article-info {
+		width: 100%;
+		.article-img {
+			width: 100rpx;
+			height: 100rpx;
+			margin-right: 20rpx;
+			border-radius: 10rpx;
+			overflow: hidden;
+		}
+		.article-title {
+			box-sizing: border-box;
+			margin-bottom: 14rpx;
+		}
+	}
+
+	.m-line {
+		width: 100%;
+		height: 1px;
+		margin-top: 20rpx;
+	}
+	.article-b {
+		width: 100%;
+		margin-top: 4rpx;
+		.article-b-icon {
+			width: 26rpx;
+			height: 26rpx;
+			background-color: #f1f1f1;
+			border-radius: 50%;
+			overflow: hidden;
+			margin-right: 10rpx;
+		}
+		.article-b-text {
+		}
+	}
+}
+</style>

+ 68 - 0
src/components/chat/m-image.vue

@@ -0,0 +1,68 @@
+<template>
+	<div class="flex_r m-image" :style="{ height: getImageHeight(value.payload) + 'px' }">
+		<!-- <image class="img" :src="value.payload.thumbnail || value.payload.url" mode="widthFix"></image> -->
+		<el-image class="img" :src="value.payload.url" fit="fit" @load="imgLoad"></el-image>
+		<!-- {{value.payload.thumbnail}} -->
+	</div>
+</template>
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		/**
+		 * 核心就是设置高度,产生明确占位
+		 *
+		 * 小  (宽度和高度都小于预设尺寸)
+		 *    设高=原始高度
+		 * 宽 (宽度>高度)
+		 *    高度= 根据宽度等比缩放
+		 * 窄  (宽度<高度)或方(宽度=高度)
+		 *    设高=MAX height
+		 *
+		 * @param width,height
+		 * @returns number
+		 */
+		getImageHeight(payload) {
+			const { width, height } = payload;
+			const IMAGE_MAX_WIDTH = 130;
+			const IMAGE_MAX_HEIGHT = 94;
+			if (width < IMAGE_MAX_WIDTH && height < IMAGE_MAX_HEIGHT) {
+				return height * 3;
+			} else if (width > height) {
+				return (IMAGE_MAX_WIDTH / width) * height * 3;
+			} else if (width === height || width < height) {
+				return IMAGE_MAX_HEIGHT * 3;
+			}
+		},
+		
+		
+		imgLoad(e){
+			this.$emit('imgLoad',e)
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.m-image {
+	border-radius: 10px;
+	overflow: hidden;
+	.img {
+		min-height: 100px;
+		background-color: #fff;
+	}
+}
+</style>

+ 139 - 0
src/components/chat/m-map.vue

@@ -0,0 +1,139 @@
+<template>
+	<div class="flex_c row">
+		<div class="flex_r text-box" :class="{ text_box: isMy }" @tap.stop="onClick">
+			<div class="text" :class="isMy ? 'text_r' : 'text_l'">
+				<div class="flex_c_c nowrap_ map">
+					<div class="nowrap_ text_32 map-title">
+						{{ value.payload.title }}
+					</div>
+					<div class="nowrap_ text_22 map-text">
+						{{ value.payload.address }}
+					</div>
+					<div class="flex1 map-img">
+						<div class="str"></div>
+						<image class="z_index2 img" :src="value.payload.image" mode="aspectFill"></image>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		onClick() {
+			this.$emit('onClick')
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row {
+}
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+}
+
+.text_r {
+	position: relative;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+
+.map {
+	width: 490rpx;
+	height: 300rpx;
+	border-radius: 10rpx;
+	overflow: hidden;
+	background-color: #fff;
+	border: 0.5px solid #fff;
+	.map-title {
+		box-sizing: border-box;
+		width: 100%;
+		padding: 14rpx 20rpx 4rpx 20rpx;
+	}
+	.map-text {
+		box-sizing: border-box;
+		width: 100%;
+		color: #afafaf;
+		padding: 0rpx 20rpx 6rpx 20rpx;
+	}
+	.map-img {
+		position: relative;
+		width: 100%;
+		.str {
+			box-sizing: border-box;
+			position: absolute;
+			z-index: 3;
+			top: calc(50% - 60rpx);
+			left: calc(50% - 25rpx);
+			width: 50rpx;
+			height: 50rpx;
+			border: 12rpx solid #07be5b;
+			border-radius: 50%;
+			background-color: #fff;
+		}
+		.str::before {
+			position: absolute;
+			z-index: 0;
+			content: '';
+			bottom: -40rpx;
+			left: 12rpx;
+			width: 6rpx;
+			height: 40rpx;
+			border-radius: 3rpx;
+			background-color: #07be5b;
+		}
+	}
+}
+</style>

+ 50 - 0
src/components/chat/m-order.vue

@@ -0,0 +1,50 @@
+<template>
+	<div>
+		<div class="m-order">
+			<div class="order-img"><el-image style="width: 60px;height: 60px;" :src="value.payload.order.img" fit="fit"></el-image></div>
+			<div class="order-info">
+				<div class="title">{{value.payload.order.name}}</div>
+				<div class="price">¥{{value.payload.order.price}}</div>
+			</div>
+			<div class="status info" v-if="value.payload.order.status=='0'">未支付</div>
+			<div class="status primary" v-if="value.payload.order.status=='1'">待使用</div>
+			<div class="status success" v-if="value.payload.order.status=='2'">已完成</div>
+			<div class="status danger" v-if="value.payload.order.status=='3'">已退款</div>
+			<div class="status warn" v-if="value.payload.order.status=='4'">退款中</div>
+			<div class="status warn" v-if="value.payload.order.status=='5'">已关闭</div>
+		</div>
+	</div>
+</template>
+
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+}
+</script>
+
+<style>
+.m-order{background-color: #fff;border-radius: 5px;display: flex;font-size: 14px;position: relative;min-width: 400px;}
+.m-order .order-img image{width: 60px;height: 60px;}
+.m-order .order-info {padding: 10px;}
+.m-order .order-info .title{font-size: 14px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
+.m-order .order-info .price{font-size: 14px;color: #f00;}
+.m-order .status{position: absolute;right: 0;bottom: 0;border-radius: 5px 0 5px 0;background-color: #909399;font-size: 12px;color: #fff;padding: 5px;}
+.m-order .status.info{background-color: #909399;}
+.m-order .status.primary{background-color: #409eff;}
+.m-order .status.success{background-color: #67c23a;}
+.m-order .status.danger{background-color: #f56c6c;}
+.m-order .status.warn{background-color: #e6a23c;}
+</style>

+ 46 - 0
src/components/chat/m-price.vue

@@ -0,0 +1,46 @@
+<template>
+	<div>
+		<div class="m-order">
+			<div class="order-img"><el-image style="width: 60px;height: 60px;" :src="value.payload.pay.img" fit="fit"></el-image></div>
+			<div class="order-info">
+				<div class="title">{{value.payload.pay.name}}</div>
+				<div class="price">¥{{value.payload.pay.price}}</div>
+			</div>
+			<div class="status danger" v-if="value.payload.pay.status==0">待支付</div>
+			<div class="status success" v-if="value.payload.pay.status==1">已支付</div>
+		</div>
+	</div>
+</template>
+
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+}
+</script>
+
+<style>
+.m-order{background-color: #fff;border-radius: 5px;display: flex;font-size: 28upx;position: relative;}
+.m-order .order-img image{width: 60px;height: 60px;}
+.m-order .order-info {padding: 15upx;}
+.m-order .order-info .title{font-size: 14px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
+.m-order .order-info .price{font-size: 14px;color: #f00;}
+.m-order .status{position: absolute;right: 0;bottom: 0;border-radius: 5px 0 5px 0;background-color: #909399;font-size: 12px;color: #fff;padding: 5px;}
+.m-order .status.info{background-color: #909399;}
+.m-order .status.primary{background-color: #409eff;}
+.m-order .status.success{background-color: #67c23a;}
+.m-order .status.danger{background-color: #f56c6c;}
+.m-order .status.warn{background-color: #e6a23c;}
+</style>

+ 243 - 0
src/components/chat/m-redPacket.vue

@@ -0,0 +1,243 @@
+<template>
+	<div class="flex_r flex_c text-box">
+		<div>
+			<div class="text_30 size_white text" :class="[{ text_: item.had_draw || item.isClick }, { text_r: isMy }, { text_l: !isMy }]" @tap.stop="onClick">
+				<!-- <div class="red_packet_bg_mini">
+					<image class="img" :src="item.payload.red_packet_bg_mini" mode="aspectFill"></image>
+				</div> -->
+				<div class="flex_r z_index2 redPacket-row">
+					<div class="redPacket-icon">
+						<!-- 开启 -->
+						<image
+							class="img"
+							src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTkxNS4wNzkgNTcwLjIwN3YtNDQ4LjdjMC0xMC4yMTQtNS41NTItMTkuNzUzLTE0LjU1Mi0yNC41NUE4MjUuMzk3IDgyNS4zOTcgMCAwIDAgNTExLjQzNC4wMDFhODI1LjI5IDgyNS4yOSAwIDAgMC0zODkuMDY2IDk2Ljk1NiAyNy43ODMgMjcuNzgzIDAgMCAwLTE0LjU3OCAyNC41NXY0NDguN2MwIDE0Ljg3NCAxMi4wNDUgMjYuOTIgMjYuOTIgMjYuOTJoNzUzLjQ0OGEyNi45MiAyNi45MiAwIDAgMCAyNi45Mi0yNi45MiIgZmlsbD0iI2ZmZjNlNyIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pNS41ZmM4M2E4MVJVTGFkOSIvPjxwYXRoIGQ9Ik0xMDcuNzkgMzQ3LjYyMXY2NDguMDNjMCAxNC44NzUgMTIuMDQ1IDI2Ljk0OCAyNi45MiAyNi45NDhoNzUzLjQ0OGEyNi45MiAyNi45MiAwIDAgMCAyNi45Mi0yNi45NDh2LTY0OC4wM2E4MjUuMzE3IDgyNS4zMTcgMCAwIDEtNDAzLjY0NCAxMDQuOTA2QTgyNS4zMTcgODI1LjMxNyAwIDAgMSAxMDcuNzkgMzQ3LjYyMSIgZmlsbD0iI2VmN2I2NCIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pNi41ZmM4M2E4MVJVTGFkOSIvPjxwYXRoIGQ9Ik0zNzUuNjQ2IDYxMC41NzRhMTM0LjUyMSAxMzQuNTIxIDAgMSAwIDI2OS4xMjQgMCAxMzQuNTQ4IDEzNC41NDggMCAwIDAtMjY5LjA5NyAwIiBmaWxsPSIjZjJkMzljIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4LnNlYXJjaF9pbmRleC4wLmk5LjVmYzgzYTgxUlVMYWQ5Ii8+PHBhdGggZD0iTTU2Ni4yOTkgNjEzLjg4OHYtMjEuODgxaC0zNC44NDNsMzEuMjMyLTMxLjU4Mi0yMC41MzQtMTguODEtMzIuMDEzIDI5LjIxMS0zMi4wNjgtMjkuMjEtMjAuNTA3IDE4Ljc1NSAzMS4yNiAzMS42MDloLTM0LjY4MnYyMS44MjdoNDAuMDQ0djE0LjU3OWgtNDAuMDQ0djI5LjE4NGg0MC4wNDR2MjEuOTM1aDIzLjk4M3YtMjkuMTg0aDQ4LjA3NFY2MjguNDRoLTQ4LjA0N3YtMTQuNTUyeiIgZmlsbD0iI2ViYjM3NyIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pMTEuNWZjODNhODFSVUxhZDkiIGNsYXNzPSJzZWxlY3RlZCIvPjwvc3ZnPg=="
+							mode="aspectFill"
+							v-if="item.had_draw || item.isClick"
+						></image>
+
+						<!-- 未开启 -->
+						<image
+							class="img"
+							src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTg3OC41OTIgMTAyNEgxNDUuNDA4Yy0yNC4wNjQgMC00My41Mi0xOS40NTYtNDMuNTItNDMuNTJWNDMuNTJjMC0yNC4wNjQgMTkuNDU2LTQzLjUyIDQzLjUyLTQzLjUyaDczMy4xODRjMjQuMDY0IDAgNDMuNTIgMTkuNDU2IDQzLjUyIDQzLjUydjkzNi40NDhjMCAyNC41NzYtMTkuNDU2IDQ0LjAzMi00My41MiA0NC4wMzJ6IiBmaWxsPSIjREY0OTQ5Ii8+PHBhdGggZD0iTTg3OC41OTIgMEgxNDUuNDA4Yy0yNC4wNjQgMC00NC4wMzIgMTkuNDU2LTQ0LjAzMiA0My41MnYzNzMuMjQ4QzIxMC45NDQgNDUzLjEyIDMzNS44NzIgNDczLjYgNDY4Ljk5MiA0NzMuNmMxNjguOTYgMCAzMjUuNjMyLTMzLjI4IDQ1My4xMi05MC4xMTJWNDMuNTJjMC0yNC4wNjQtMTkuNDU2LTQzLjUyLTQzLjUyLTQzLjUyeiIgZmlsbD0iI0ZCNTM1MiIvPjxwYXRoIGQ9Ik0zNzUuMjk2IDQ4OS45ODRjMCA3NS4yNjQgNjEuNDQgMTM2LjcwNCAxMzYuNzA0IDEzNi43MDRzMTM2LjcwNC02MS40NCAxMzYuNzA0LTEzNi43MDRTNTg3LjI2NCAzNTMuMjggNTEyIDM1My4yOHMtMTM2LjcwNCA2MS40NC0xMzYuNzA0IDEzNi43MDR6IiBmaWxsPSIjRkNDRTNFIi8+PHBhdGggZD0iTTU2MS42NjQgNDgzLjg0aDMuMDcydi0uNTEyYzQuMDk2LTEuNTM2IDcuMTY4LTUuMTIgNy4xNjgtOS43MjggMC01LjYzMi00LjYwOC0xMC4yNC0xMC4yNC0xMC4yNEg1MjIuMjR2LS41MTJsNDEuNDcyLTQwLjQ0OCAyLjA0OC0yLjA0OGMxLjAyNC0xLjUzNiAxLjUzNi0zLjA3MiAxLjUzNi00LjYwOCAwLTIuNTYtLjUxMi01LjEyLTIuNTYtNy42OC0zLjU4NC00LjA5Ni0xMC4yNC00LjYwOC0xNC4zMzYtLjUxMmwtMzcuODg4IDM3LjM3Ni0zOS45MzYtMzguOTEyYy00LjA5Ni0zLjU4NC0xMC43NTItMy4wNzItMTQuMzM2IDEuMDI0LTMuMDcyIDMuNTg0LTMuNTg0IDguMTkyLTEuMDI0IDEyLjI4OHYuNTEybDQ1LjA1NiA0NC4wMzJ2MS4wMjRoLTQyLjQ5NnYuNTEyYy00LjA5NiAxLjUzNi03LjE2OCA1LjEyLTcuMTY4IDkuNzI4czMuMDcyIDguMTkyIDcuMTY4IDkuNzI4di41MTJoNDEuOTg0djMxLjIzMmgtNDMuMDA4di41MTJjLTQuMDk2IDEuNTM2LTcuMTY4IDUuMTItNy4xNjggOS43MjhzMy4wNzIgOC4xOTIgNy4xNjggOS43Mjh2LjUxMmg0My4wMDh2MzQuMzA0aC41MTJjMS41MzYgNC4wOTYgNS4xMiA3LjE2OCA5LjcyOCA3LjE2OHM4LjE5Mi0zLjA3MiA5LjcyOC03LjE2OGguNTEydi0zNC4zMDRoNDIuNDk2di0uNTEyYzQuMDk2LTEuNTM2IDcuMTY4LTUuMTIgNy4xNjgtOS43MjggMC01LjYzMi00LjYwOC0xMC4yNC0xMC4yNC0xMC4yNEg1MjIuMjR2LTMxLjIzMmwzOS40MjQtMS41MzZ6IiBmaWxsPSIjRDg4NjE5Ii8+PC9zdmc+"
+							mode="aspectFill"
+							v-else
+						></image>
+					</div>
+					<div class="flex1 flex_c fj_a z_index2 nowrap_ redPacket-title">
+						<div class="nowrap_ text_34 bold_">{{ item.payload.remark }}</div>
+						<div class="text_24" style="margin-top: 6rpx" v-if="item.had_draw && item.payload.type !== 'exclusive'">已领取</div>
+
+						<div class="text_24" style="margin-top: 6rpx" v-if="item.payload.type === 'exclusive'">
+							{{ item.payload.exclusive.name || item.payload.exclusive.realname }}
+							{{ item.had_draw ? '已领取' : '的专属红包' }}
+						</div>
+					</div>
+				</div>
+				<div class="m-line">
+					<m-line color="rgba(255, 255, 255, 0.4)" width="100%" margin="20rpx auto 6rpx auto" :hairline="true"></m-line>
+				</div>
+				<div class="text_24 redPacket-text">{{ item.payload.account_type_text }}</div>
+			</div>
+		</div>
+
+		<!-- 你的专属红包 -->
+		<div class="text_22 color__ flex_r fa_c exclusive" v-if="item.payload.type === 'exclusive' && item.payload.exclusive.member_id === myid">
+			这是你的专属红包
+			<div class="label-icon">
+				<image
+					class="img"
+					src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTAgMGgxMDI0djEwMjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNOTg1LjYgMzUyYzAtNDAuMzItMzItNzIuMzItNzEuMDQtNzIuMzItMzkuNjggMC03MS4wNCAzMi42NC03MS4wNCA3Mi4zMiAwIDE1LjM2IDQuNDggMjkuNDQgMTIuOCA0MS42LTQzLjUyIDc0LjI0LTc0Ljg4IDE3NC43Mi0xMzUuNjggMTc0LjcyLTkuNiAwLTE5LjItLjY0LTI4LjE2LTIuNTYtOTYtMTcuMjgtMTQ3LjItMjUzLjQ0LTE1Mi4zMi0yODYuNzIgMjMuMDQtMTIuMTYgMzkuNjgtMzcuMTIgMzkuNjgtNjUuOTIgMC00MC4zMi0zMi03Mi4zMi03MS4wNC03Mi4zMmE3Mi41MTIgNzIuNTEyIDAgMCAwLTI0Ljk2IDE0MC4xNmMtNS4xMiAzNy4xMi01My43NiAyNzYuNDgtMTQ3LjIgMjg5LjkyLTcuNjggMS4yOC0xNS4zNiAxLjkyLTIzLjY4IDEuOTItNjAuMTYgMC0xMDIuNC0xMDQuOTYtMTM3LjYtMTc0LjcyIDMuODQtOC4zMiA1Ljc2LTE3LjkyIDUuNzYtMjguMTYgMC00MC4zMi0zMi03Mi4zMi03MS4wNC03Mi4zMi0xOS4yIDAtMzYuNDggNy42OC00OS4yOCAyMC40OC0xNC4wOCAxMy40NC0yMi40IDMyLTIyLjQgNTEuODQgMCA0MC4zMiAzMiA3Mi4zMiA3MS4wNCA3Mi4zMiAxMjQuMTYgMzEwLjQgMTg1LjYgNDY1LjkyIDE4NS42IDQ2NS45MlM0MDEuOTIgOTYwIDUxMS4zNiA5NjBzMjE3LjYtNTEuODQgMjE3LjYtNTEuODQgNjQuNjQtMTYxLjI4IDE5My4yOC00ODQuNDhjMzUuMi0zLjg0IDYzLjM2LTM0LjU2IDYzLjM2LTcxLjY4eiIgZmlsbD0iI0ZCRDMwRiIvPjxwYXRoIGQ9Ik0yOTQuNCA5MDQuMzJjMCAyOS40NCAxMDYuODggNTkuNTIgMjE1LjA0IDU5LjUyUzcyOS42IDkzNC40IDcyOS42IDkwNC4zMmMwLTI5LjQ0LTExMi42NC01OS41Mi0yMjAuMTYtNTkuNTJTMjk0LjQgODc0LjI0IDI5NC40IDkwNC4zMnoiIGZpbGw9IiNGRkE3MDYiLz48L3N2Zz4="
+					mode="aspectFill"
+				></image>
+			</div>
+		</div>
+		<div class="icon_ redPacket" v-if="item.had_draw">
+			<div class="redPacket-icon">
+				<image
+					class="img"
+					src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTg3OC41OTIgMTAyNEgxNDUuNDA4Yy0yNC4wNjQgMC00My41Mi0xOS40NTYtNDMuNTItNDMuNTJWNDMuNTJjMC0yNC4wNjQgMTkuNDU2LTQzLjUyIDQzLjUyLTQzLjUyaDczMy4xODRjMjQuMDY0IDAgNDMuNTIgMTkuNDU2IDQzLjUyIDQzLjUydjkzNi40NDhjMCAyNC41NzYtMTkuNDU2IDQ0LjAzMi00My41MiA0NC4wMzJ6IiBmaWxsPSIjREY0OTQ5Ii8+PHBhdGggZD0iTTg3OC41OTIgMEgxNDUuNDA4Yy0yNC4wNjQgMC00NC4wMzIgMTkuNDU2LTQ0LjAzMiA0My41MnYzNzMuMjQ4QzIxMC45NDQgNDUzLjEyIDMzNS44NzIgNDczLjYgNDY4Ljk5MiA0NzMuNmMxNjguOTYgMCAzMjUuNjMyLTMzLjI4IDQ1My4xMi05MC4xMTJWNDMuNTJjMC0yNC4wNjQtMTkuNDU2LTQzLjUyLTQzLjUyLTQzLjUyeiIgZmlsbD0iI0ZCNTM1MiIvPjxwYXRoIGQ9Ik0zNzUuMjk2IDQ4OS45ODRjMCA3NS4yNjQgNjEuNDQgMTM2LjcwNCAxMzYuNzA0IDEzNi43MDRzMTM2LjcwNC02MS40NCAxMzYuNzA0LTEzNi43MDRTNTg3LjI2NCAzNTMuMjggNTEyIDM1My4yOHMtMTM2LjcwNCA2MS40NC0xMzYuNzA0IDEzNi43MDR6IiBmaWxsPSIjRkNDRTNFIi8+PHBhdGggZD0iTTU2MS42NjQgNDgzLjg0aDMuMDcydi0uNTEyYzQuMDk2LTEuNTM2IDcuMTY4LTUuMTIgNy4xNjgtOS43MjggMC01LjYzMi00LjYwOC0xMC4yNC0xMC4yNC0xMC4yNEg1MjIuMjR2LS41MTJsNDEuNDcyLTQwLjQ0OCAyLjA0OC0yLjA0OGMxLjAyNC0xLjUzNiAxLjUzNi0zLjA3MiAxLjUzNi00LjYwOCAwLTIuNTYtLjUxMi01LjEyLTIuNTYtNy42OC0zLjU4NC00LjA5Ni0xMC4yNC00LjYwOC0xNC4zMzYtLjUxMmwtMzcuODg4IDM3LjM3Ni0zOS45MzYtMzguOTEyYy00LjA5Ni0zLjU4NC0xMC43NTItMy4wNzItMTQuMzM2IDEuMDI0LTMuMDcyIDMuNTg0LTMuNTg0IDguMTkyLTEuMDI0IDEyLjI4OHYuNTEybDQ1LjA1NiA0NC4wMzJ2MS4wMjRoLTQyLjQ5NnYuNTEyYy00LjA5NiAxLjUzNi03LjE2OCA1LjEyLTcuMTY4IDkuNzI4czMuMDcyIDguMTkyIDcuMTY4IDkuNzI4di41MTJoNDEuOTg0djMxLjIzMmgtNDMuMDA4di41MTJjLTQuMDk2IDEuNTM2LTcuMTY4IDUuMTItNy4xNjggOS43MjhzMy4wNzIgOC4xOTIgNy4xNjggOS43Mjh2LjUxMmg0My4wMDh2MzQuMzA0aC41MTJjMS41MzYgNC4wOTYgNS4xMiA3LjE2OCA5LjcyOCA3LjE2OHM4LjE5Mi0zLjA3MiA5LjcyOC03LjE2OGguNTEydi0zNC4zMDRoNDIuNDk2di0uNTEyYzQuMDk2LTEuNTM2IDcuMTY4LTUuMTIgNy4xNjgtOS43MjggMC01LjYzMi00LjYwOC0xMC4yNC0xMC4yNC0xMC4yNEg1MjIuMjR2LTMxLjIzMmwzOS40MjQtMS41MzZ6IiBmaWxsPSIjRDg4NjE5Ii8+PC9zdmc+"
+					mode="heightFix"
+				></image>
+			</div>
+			<div class="text_24 color__" v-if="item.senderData.name">你领取了{{ item.senderData.name }}的红包</div>
+			<div class="text_24 color__" v-else>你领取了红包</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { show } from '@/util/index.js';
+export default {
+	props: {
+		myid: {
+			type: [String, Number],
+			default: null
+		},
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	created() {
+		// console.log(this.value);
+		// this.infr();
+	},
+	data() {
+		return {
+			item: {},
+			pageObj: {}
+		};
+	},
+	watch: {
+		value: {
+			handler: function (newV) {
+				this.item = newV;
+				// this.infr();
+			},
+			immediate: true
+		}
+	},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		},
+		async infr() {
+			const res = await this.API_red_div(this.value.payload.message_id);
+			if (res) {
+				const data = res.data.data;
+				this.item.had_draw = data.had_draw;
+			}
+		},
+		API_red_div(message_id) {
+			return new Promise((res) => {
+				http.get(
+					'Group/red_div',
+					{
+						message_id
+					},
+					true,
+					(r) => {
+						if (r.data.code == 0) return res(r);
+						return res(false);
+					}
+				);
+			});
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+	width: 490rpx;
+	min-height: 180rpx;
+	padding: 22rpx 30rpx 0 30rpx;
+	border-radius: 10rpx;
+	background-color: #fa9e3b;
+	.red_packet_bg_mini {
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 0;
+		overflow: hidden;
+		opacity: 0.2;
+		border-radius: 10rpx;
+	}
+
+	.redPacket-row {
+		width: 100%;
+		min-height: 94rpx;
+		.redPacket-icon {
+			width: 76rpx;
+			height: 88rpx;
+			border-radius: 10rpx;
+			overflow: hidden;
+			margin-right: 26rpx;
+		}
+		.redPacket-title {
+		}
+	}
+	.m-line {
+	}
+	.redPacket-text {
+		width: 100%;
+		min-height: 30rpx;
+		padding: 4rpx 0 6rpx 0;
+		font-weight: 300;
+	}
+}
+
+.text_r {
+	position: relative;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fa9e3b;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fa9e3b;
+}
+.text_ {
+	background-color: #fde2c4;
+}
+
+.text_::after {
+	background-color: #fde2c4;
+}
+
+.text-box {
+}
+
+.exclusive {
+	width: 100%;
+	flex-direction: row-reverse;
+	margin-top: 2rpx;
+	.label-icon {
+		width: 34rpx;
+		height: 34rpx;
+		margin-right: 10rpx;
+	}
+}
+
+.redPacket {
+	width: 100%;
+	height: 80rpx;
+
+	.redPacket-icon {
+		height: 34rpx;
+		border-radius: 4px;
+		margin: 0 12rpx 0 0;
+		overflow: hidden;
+	}
+}
+</style>

+ 159 - 0
src/components/chat/m-share-mall.vue

@@ -0,0 +1,159 @@
+<template>
+	<div class="flex_c row">
+		<div class="flex_r text-box" :class="{ text_box: isMy }" @tap.stop="onClick">
+			<div class="text" :class="isMy ? 'text_r' : 'text_l'">
+				<div class="flex_c_c article">
+					<div class="flex_r fa_c article-infr">
+						<div class="article-infr-img">
+							<image class="img" :src="value.payload.share_image" mode="aspectFill"></image>
+						</div>
+						<div class="text_22 color__ article-infr-text">
+							{{ value.payload.title }}
+						</div>
+					</div>
+					<div class="text_30 nowrap_ article-title">
+						{{ value.payload.short_title }}
+					</div>
+					<div class="article-img">
+						<image class="img" :src="value.payload.share_image" mode="aspectFill"></image>
+					</div>
+					<div class="m-line">
+						<m-line color="#f0f0f0" length="100%" :hairline="true"></m-line>
+					</div>
+					<div class="flex_r fa_c article-b">
+						<div class="article-b-icon"></div>
+						<div class="text_20 color__ article-b-text">xxxx</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row {
+}
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+}
+
+.text_r {
+	position: relative;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+
+.article {
+	box-sizing: border-box;
+	padding: 14rpx 20rpx 4rpx 20rpx;
+	width: 490rpx;
+	border-radius: 10rpx;
+	overflow: hidden;
+	background-color: #fff;
+	border: 0.5px solid #fff;
+
+	.article-infr {
+		width: 100%;
+		height: 46rpx;
+		margin-bottom: 10rpx;
+		.article-infr-img {
+			width: 40rpx;
+			height: 40rpx;
+			margin-right: 10rpx;
+			border-radius: 50%;
+			overflow: hidden;
+			background-color: #f1f1f1;
+		}
+		.article-infr-text {
+		}
+	}
+
+	.article-title {
+		box-sizing: border-box;
+		width: 100%;
+		margin-bottom: 14rpx;
+	}
+	.article-img {
+		width: 450rpx;
+		height: 350rpx;
+		background-color: #f1f1f1;
+	}
+	.m-line {
+		width: 100%;
+		height: 1px;
+		margin-top: 20rpx;
+	}
+	.article-b {
+		width: 100%;
+		margin-top: 4rpx;
+		.article-b-icon {
+			width: 26rpx;
+			height: 26rpx;
+			background-color: #f1f1f1;
+			border-radius: 50%;
+			overflow: hidden;
+			margin-right: 10rpx;
+		}
+		.article-b-text {
+		}
+	}
+}
+</style>

+ 159 - 0
src/components/chat/m-share-sbcf.vue

@@ -0,0 +1,159 @@
+<template>
+	<div class="flex_c row">
+		<div class="flex_r text-box" :class="{ text_box: isMy }" @tap.stop="onClick">
+			<div class="text" :class="isMy ? 'text_r' : 'text_l'">
+				<div class="flex_c_c article">
+					<div class="flex_r fa_c article-infr">
+						<div class="article-infr-img">
+							<image class="img" :src="value.payload.share_image" mode="aspectFill"></image>
+						</div>
+						<div class="text_22 color__ article-infr-text">
+							{{ value.payload.title }}
+						</div>
+					</div>
+					<div class="text_30 nowrap_ article-title">
+						{{ value.payload.short_title }}
+					</div>
+					<div class="article-img">
+						<image class="img" :src="value.payload.share_image" mode="aspectFill"></image>
+					</div>
+					<div class="m-line">
+						<m-line color="#f0f0f0" length="100%" :hairline="true"></m-line>
+					</div>
+					<div class="flex_r fa_c article-b">
+						<div class="article-b-icon"></div>
+						<div class="text_20 color__ article-b-text">联盟商家</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row {
+}
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+}
+
+.text_r {
+	position: relative;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	right: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26rpx;
+	left: -8rpx;
+	width: 18rpx;
+	height: 18rpx;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+
+.article {
+	box-sizing: border-box;
+	padding: 14rpx 20rpx 4rpx 20rpx;
+	width: 490rpx;
+	border-radius: 10rpx;
+	overflow: hidden;
+	background-color: #fff;
+	border: 0.5px solid #fff;
+
+	.article-infr {
+		width: 100%;
+		height: 46rpx;
+		margin-bottom: 10rpx;
+		.article-infr-img {
+			width: 40rpx;
+			height: 40rpx;
+			margin-right: 10rpx;
+			border-radius: 50%;
+			overflow: hidden;
+			background-color: #f1f1f1;
+		}
+		.article-infr-text {
+		}
+	}
+
+	.article-title {
+		box-sizing: border-box;
+		width: 100%;
+		margin-bottom: 14rpx;
+	}
+	.article-img {
+		width: 450rpx;
+		height: 450rpx;
+		background-color: #f1f1f1;
+	}
+	.m-line {
+		width: 100%;
+		height: 1px;
+		margin-top: 20rpx;
+	}
+	.article-b {
+		width: 100%;
+		margin-top: 4rpx;
+		.article-b-icon {
+			width: 26rpx;
+			height: 26rpx;
+			background-color: #f1f1f1;
+			border-radius: 50%;
+			overflow: hidden;
+			margin-right: 10rpx;
+		}
+		.article-b-text {
+		}
+	}
+}
+</style>

+ 164 - 0
src/components/chat/m-text.vue

@@ -0,0 +1,164 @@
+<template>
+	<div class="flex_c row">
+		<div class="flex_r text-box" :class="{ text_box: isMy }" @tap.stop="onClick">
+			<div
+				class="text_32 text"
+				:class="isMy ? 'text_r' : 'text_l'"
+				:hover-class="isMy ? 'hover_classr' : 'hover_classl'"
+				:hover-stay-time="60"
+				:style="{ whiteSpace: 'pre-wrap' }"
+				v-html="renderTextMessage"
+			></div>
+		</div>
+		<div class="text_26 flex_r" :class="{ row_: isMy }" v-if="value.type === 'text_quote'">
+			<div v-if="value.payload.quoteSource.type === 'image' || value.payload.quoteSource.type === 'image_transmit'">
+				<m-image :value="value.payload.quoteSource"></m-image>
+			</div>
+			<div class="" v-else-if="value.payload.quoteSource.type === 'audio' || value.payload.quoteSource.type === 'audio_quote'">
+				<m-audio :value="value.payload.quoteSource"></m-audio>
+			</div>
+			<div class="" v-else-if="value.payload.quoteSource.type === 'text' || value.payload.quoteSource.type === 'text_quote'">
+				<m-text :value="value.payload.quoteSource"></m-text>
+			</div>
+			<div class="" v-else>
+				<m-other :value="value.payload.quoteSource"></m-other>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import mText from './quoteType/m-text.vue';
+import mImage from './quoteType/m-image.vue';
+import mAudio from './quoteType/m-audio.vue';
+import mOther from './quoteType/m-other.vue';
+
+import { EmojiDecoder, emojiMap } from '@/util/EmojiDecoder.js';
+const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
+const decoder = new EmojiDecoder(emojiUrl, emojiMap);
+export default {
+	components: {
+		mText,
+		mImage,
+		mAudio,
+		mOther
+	},
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		//渲染文本消息,如果包含表情,替换为图片
+		//todo:本不需要该方法,可以在标签里完成,但小程序有兼容性问题,被迫这样实现
+		renderTextMessage() {
+			const { text = '' } = this.value.payload;
+			if (!text) return '<span>' + '[未知内容]' + '</span>';
+			return '<span>' + decoder.decode(text) + '</span>';
+		}
+	},
+	methods: {
+		onClick() {
+			this.$emit('onClick');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.row {
+}
+.row_ {
+	flex-direction: row-reverse;
+}
+.text_box {
+	flex-direction: row-reverse;
+}
+.text {
+	position: relative;
+	z-index: 99;
+	box-sizing: border-box;
+	padding: 16px 26px;
+	border-radius: 8px;
+	background-color: #fff;
+	word-break: break-all;
+	vertical-align: center;
+	font-size: 14px;
+	span{
+		font-size: 14px;
+	}
+}
+
+.text_r {
+	position: relative;
+	background-color: #95ec6a;
+}
+.text_l {
+	position: relative;
+}
+
+.text_r::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26px;
+	right: -8px;
+	width: 18px;
+	height: 18px;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #95ec6a;
+}
+.text_l::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26px;
+	left: -8px;
+	width: 18px;
+	height: 18px;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #fff;
+}
+
+.hover_classr {
+	background-color: #89d961;
+}
+.hover_classl {
+	background-color: #e2e2e2;
+}
+
+.hover_classr::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26px;
+	right: -8px;
+	width: 18px;
+	height: 18px;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #89d961;
+}
+.hover_classl::after {
+	position: absolute;
+	z-index: -1;
+	content: '';
+	top: 26px;
+	left: -8px;
+	width: 18px;
+	height: 18px;
+	border-radius: 2px;
+	transform: rotate(45deg);
+	background-color: #e2e2e2;
+}
+</style>

+ 93 - 0
src/components/chat/m-video.vue

@@ -0,0 +1,93 @@
+<template>
+	<div class="flex_r m-video">
+		<!-- heightFix -->
+		<image class="z_index2" :style="getImageHeight" :src="value.payload.thumbnail.url" mode="aspectFill"></image>
+		<div class="m-video-icon">
+			<el-image
+				class="img"
+				src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4LnNlYXJjaF9pbmRleC4wLmk1LjU1YjUzYTgxcDB1a0U0IiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTUxNi42NTUgOS4zMUMyMzcuMzgyIDkuMzEgMTAuODYgMjM1LjgzIDEwLjg2IDUxNS4xMDJzMjI2LjUyIDUwNS43OTQgNTA1Ljc5NCA1MDUuNzk0IDUwNS43OTMtMjI2LjUyMSA1MDUuNzkzLTUwNS43OTRjMC0yNzcuNzIxLTIyNi41Mi01MDUuNzk0LTUwNS43OTMtNTA1Ljc5NHptMCA5ODIuMTA4Yy0yNjIuMjA3IDAtNDc0Ljc2NC0yMTIuNTU3LTQ3NC43NjQtNDc0Ljc2MyAwLTI2Mi4yMDcgMjEyLjU1Ny00NzQuNzY0IDQ3NC43NjQtNDc0Ljc2NCAyNjIuMjA2IDAgNDc0Ljc2MyAyMTIuNTU3IDQ3NC43NjMgNDc0Ljc2NCAwIDI2Mi4yMDYtMjEyLjU1NyA0NzQuNzYzLTQ3NC43NjMgNDc0Ljc2M3oiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guc2VhcmNoX2luZGV4LjAuaTcuNTViNTNhODFwMHVrRTQiIGNsYXNzPSJzZWxlY3RlZCIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik03MDUuOTQgNDc0Ljc2NEw0NzAuMTEgMzI1LjgxOGMtMjEuNzIyLTEzLjk2My01MS4yLTcuNzU3LTYzLjYxMyAxMy45NjQtNC42NTUgNy43NTctNy43NTggMTUuNTE1LTcuNzU4IDI0LjgyNHYyOTcuODkxYzAgMjYuMzc2IDIwLjE3IDQ2LjU0NSA0Ni41NDYgNDYuNTQ1IDkuMzA5IDAgMTcuMDY3LTMuMTAzIDI0LjgyNC03Ljc1N2wyMzcuMzgyLTE0OC45NDZjMjEuNzIxLTEzLjk2MyAyNy45MjctNDEuODkgMTMuOTY0LTYzLjYxMi00LjY1NS00LjY1NC05LjMxLTkuMzA5LTE1LjUxNi0xMy45NjN6IiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4LnNlYXJjaF9pbmRleC4wLmk2LjU1YjUzYTgxcDB1a0U0IiBjbGFzcz0ic2VsZWN0ZWQiIGZpbGw9IiNmZmYiLz48L3N2Zz4="
+				mode="aspectFill"
+			></el-image>
+		</div>
+		<div class="size_white text_22 m-video-time">{{ getTimes }}</div>
+	</div>
+</template>
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		getImageHeight() {
+			let { width, height } = this.value.payload.thumbnail;
+			if (width < 300) {
+				width = width * 1.1;
+				height = height * 1.1;
+				if (width < 200) {
+					width = width * 1.2;
+					height = height * 1.2;
+					if (width < 150) {
+						width = width * 1.3;
+						height = height * 1.3;
+					}
+				}
+			}
+			if (width >= 360) {
+				width = width * 0.9;
+				height = height * 0.9;
+			}
+			return {
+				width: `${width}rpx`,
+				height: `${height}rpx`
+			};
+		},
+		getTimes() {
+			const t = this.value.payload.video.duration;
+			let h = parseInt((t / 60 / 60) % 24);
+			let m = parseInt((t / 60) % 60);
+			let s = parseInt(t % 60);
+			h = h < 10 ? '0' + h : h;
+			m = m < 10 ? '0' + m : m;
+			s = s < 10 ? '0' + s : s;
+			if (h === '00') return `${m}:${s}`;
+			return `${h}:${m}:${s}`;
+		}
+	},
+	methods: {}
+};
+</script>
+
+<style scoped lang="scss">
+.m-video {
+	position: relative;
+	border-radius: 10rpx;
+	overflow: hidden;
+	background-color: #fff;
+	.m-video-icon {
+		position: absolute;
+		z-index: 3;
+		width: 90rpx;
+		height: 90rpx;
+		top: calc(50% - 45rpx);
+		left: calc(50% - 45rpx);
+		border-radius: 50%;
+		background-color: rgba(000, 000, 000, 0.2);
+	}
+	.m-video-time {
+		position: absolute;
+		z-index: 3;
+		bottom: 10rpx;
+		right: 10rpx;
+	}
+}
+</style>

+ 42 - 0
src/components/chat/quoteType/m-audio.vue

@@ -0,0 +1,42 @@
+<template>
+	<div class="quote-box">
+		<div class="quote-name">
+			{{ value.senderData.name }}:
+			<div>[语音]{{ Math.ceil(value.payload.duration) || 1 }}''</div>
+		</div>
+	</div>
+</template>
+<script>
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {}
+};
+</script>
+
+<style scoped lang="scss">
+.quote-box {
+	box-sizing: border-box;
+	padding: 12rpx 16rpx;
+	border-radius: 10rpx;
+	margin-top: 6rpx;
+	background-color: #e1e1e1;
+	color: #6b6b6b;
+}
+.quote-name {
+}
+.quote-content {
+}
+</style>

+ 54 - 0
src/components/chat/quoteType/m-image.vue

@@ -0,0 +1,54 @@
+<template>
+	<div class="quote-box">
+		<div class="flex_r quote-name">
+			<div class="">{{ value.senderData.name }}:</div>
+			<div class="flex_r m-image" @tap.stop="openimg(value.payload.url)">
+				<el-image class="img" :src="value.payload.thumbnail || value.payload.url" fit="aspectFill"></el-image>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+import { openimg } from '@/util/index.js';
+export default {
+	props: {
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {},
+	methods: {
+		openimg
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.quote-box {
+	box-sizing: border-box;
+	padding: 12px 16px;
+	border-radius: 10px;
+	margin-top: 6px;
+	background-color: #e1e1e1;
+	color: #6b6b6b;
+}
+.quote-name {
+}
+.quote-content {
+}
+.m-image {
+	border-radius: 10px;
+	overflow: hidden;
+	.img {
+		width: 80px;
+		height: 80px;
+		border-radius: 6px;
+		overflow: hidden;
+		background-color: #fff;
+	}
+}
+</style>

+ 75 - 0
src/components/chat/quoteType/m-other.vue

@@ -0,0 +1,75 @@
+<template>
+	<div class="quote-box">
+		<div class="quote-name">
+			{{ value.senderData.name }}:
+			<div>{{ renderTextMessage }}</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { EmojiDecoder, emojiMap } from '@/util/EmojiDecoder.js';
+const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
+const decoder = new EmojiDecoder(emojiUrl, emojiMap);
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	created() {},
+	computed: {
+		renderTextMessage() {
+			const { type = '' } = this.value;
+			const typeText = {
+				video: `[视频]${this.getTimes()}`,
+				red_envelope: '[蝌蚪红包]',
+				map: `[位置]${this.value.payload?.title}`,
+				emoji_pack: `[表情包]`,
+				article: '[文章分享]',
+				share_SBCF: '[商家分享]',
+				share_mall: '[商品分享]',
+				functional_module: '[功能分享]'
+			};
+			return typeText[type];
+		}
+	},
+	methods: {
+		getTimes() {
+			if (this.value.type !== 'video') return;
+			const t = this.value.payload.video.duration;
+			let h = parseInt((t / 60 / 60) % 24);
+			let m = parseInt((t / 60) % 60);
+			let s = parseInt(t % 60);
+			h = h < 10 ? '0' + h : h;
+			m = m < 10 ? '0' + m : m;
+			s = s < 10 ? '0' + s : s;
+			if (h === '00') return `${m}:${s}`;
+			return `${h}:${m}:${s}`;
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.quote-box {
+	box-sizing: border-box;
+	padding: 12rpx 16rpx;
+	border-radius: 10rpx;
+	margin-top: 6rpx;
+	background-color: #e1e1e1;
+	color: #6b6b6b;
+}
+.quote-name {
+}
+.quote-content {
+}
+</style>

+ 58 - 0
src/components/chat/quoteType/m-text.vue

@@ -0,0 +1,58 @@
+<template>
+	<div class="quote-box">
+		<div class="quote-name">
+			<div>
+				{{ value.senderData.name }}:
+				<div v-html="renderTextMessage"></div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { EmojiDecoder, emojiMap } from '@/util/EmojiDecoder.js';
+const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
+const decoder = new EmojiDecoder(emojiUrl, emojiMap);
+export default {
+	props: {
+		isMy: {
+			type: [Boolean, Number],
+			default: false
+		},
+		value: {
+			type: Object,
+			default: {}
+		}
+	},
+	data() {
+		return {};
+	},
+	created() {},
+	computed: {
+		//渲染文本消息,如果包含表情,替换为图片
+		//todo:本不需要该方法,可以在标签里完成,但小程序有兼容性问题,被迫这样实现
+		renderTextMessage() {
+			let { text = '' } = this.value.payload;
+			text = text.replace(/\n/g, '');
+			if (!text) return '<span>' + '[未知内容]' + '</span>';
+			return '<span>' + decoder.decode(text) + '</span>';
+		}
+	},
+	methods: {}
+};
+</script>
+
+<style scoped lang="scss">
+.quote-box {
+	box-sizing: border-box;
+	padding: 12rpx 16rpx;
+	border-radius: 10rpx;
+	margin-top: 6rpx;
+	background-color: #e1e1e1;
+	color: #6b6b6b;
+}
+.quote-name {
+}
+.quote-content {
+}
+</style>

+ 542 - 72
src/components/imFloat/imFloat.vue

@@ -3,26 +3,28 @@
         <!-- 悬浮按钮与角标 -->
         <div class="im-trigger" :class="{ active: visible }" @click="toggleChat">
             <el-icon :size="18"><el-icon-chat-round /></el-icon>
-            <el-badge :value="unreadCount" :hidden="unreadCount === 0" :max="99">
-                <i class="el-icon-chat-dot-round" style="font-size: 24px"></i>
-            </el-badge>
+            <div class="unreadCount">
+                <el-badge :value="unreadCount" :hidden="unreadCount === 0" :max="99">
+                    <i class="el-icon-chat-dot-round" style="font-size: 24px"></i>
+                </el-badge>
+            </div>
         </div>
     </div>
     <el-drawer v-model="visible" title="在线客服" size="100%" destroy-on-close :close-on-click-modal="false" :with-header="false">
-        <el-container class="flex-column" v-loading="loading">
+        <el-container class="flex-column">
             <div class="drawer-detail-main">
                 <div class="drawer-detail-header">
                     <div class="drawer-detail-header-body">
                         <div class="drawer-detail-header-left">在线客服</div>
                         <div class="drawer-detail-header-left">
-                            <el-button type="default" icon="el-icon-close" @click="visible=false">隐藏窗口</el-button>
+                            <el-button type="default" icon="el-icon-close" @click="hideWindow">隐藏窗口</el-button>
                         </div>
                     </div>
                 </div>
             </div>
             <el-main class="nopadding">
                 <el-container>
-                    <el-aside width="300px" v-loading="menuloading">
+                    <el-aside width="300px" v-loading="loadMsg">
                       <div class="chat-contact">
                         <div :class="checkChat==index?'contact-item active':'contact-item'" v-for="(item,index) in chatContact" :key="index" @click="checkChatItem(index)">
                           <div class="img"><el-image :src="item.avatar" fit="contain" style="width: 40px; height: 40px"></el-image></div>
@@ -43,10 +45,73 @@
                     <el-container v-if="chatItem">
                         <el-header><div class="msg-title">与{{ chatItem.user.nickname }}的对话</div></el-header>
                         <el-main class="nopadding" ref="main">
-                            <div class="chat-container">
-                                <div class="message-list" ref="messageListRef">
+                            <div class="chat-container" v-if="chatItem">
+                                <div class="loading-text" v-if="msgLoad">努力加载中...</div>
+                                <div class="message-list" ref="messageListRef" @scroll="handleScroll">
+                                    <template v-for="(item, index) in history.messages">
+                                    <div class="z_index2" style="transform: rotate(-180deg)" :key="item.messageId" v-if="!item.isHide">
+                                        <div class="icon_ text_26 color__ time">
+                                            {{ renderMessageDate(item, index) }}
+                                        </div>
+                                        <div :key="item.messageIds" v-if="!item.recalled">
+                                            <item :isMy="isSelf(item.senderId)" :myid="myid" :item="item" @onClick="onItem"
+                                                @onLongpress="onLongpress" @mention="mention"></item>
+                                        </div>
+                                        <div class="icon_ text_26 recalled" v-else>
+                                            <div class="">
+                                                <span v-if="isSelf(item.senderId)">你</span>
+                                                <span v-else>{{ item.senderData.name }}</span>
+                                                撤回了一条消息
+                                            </div>
+                                            <div class="recalled-edit" v-if="item.type === 'text' && isSelf(item.senderId)"
+                                                @click="recalledEdit(item)">重新编辑</div>
+                                        </div>
+                                    </div>
+                                    </template>
                                 </div>
                                 <div class="input-area">
+                                    <div class="quick-group">
+                                        <div class="emoji-item" @click="showEmoji">
+                                            <el-popover placement="top" :width="400" trigger="click">
+                                                <template #reference>
+                                                    <div class="item-btn">
+                                                        <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/face.svg"></el-image>
+                                                        <div class="name">表情</div>
+                                                    </div>
+                                                </template>
+                                                <div class="swiper-item-box">
+                                                    <div class="flex_r swiper-item-box-l" v-for="(im, ix) in emojiList" :key="ix">
+                                                        <div
+                                                            class="icon_ emoji-item"
+                                                            v-for="(item, index) in im"
+                                                            :key="index"
+                                                            :style="{ opacity: ix === opacityGroup && (index == 5 || index == 6 || index == 7) ? opacity : 1 }"
+                                                        >
+                                                            <el-image class="img" :src="emojiUrl + item.image" @click="chooseEmoji(item.text)"></el-image>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </el-popover>
+                                        </div>
+                                        <div class="emoji-item" @click="showUpload">
+                                            <div class="item-btn">
+                                                <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/more.svg"></el-image>
+                                                <div class="name">图片</div>
+                                            </div>
+                                        </div>
+                                        <div class="emoji-item" @click="showQuick">
+                                            <div class="item-btn">
+                                                <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/quick.svg"></el-image>
+                                                <div class="name">快捷回复</div>
+                                            </div>
+                                        </div>
+                                        <div class="emoji-item" @click="showQuick">
+                                            <div class="item-btn">
+                                                <el-image style="width:26px;height:26px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/money.svg"></el-image>
+                                                <div class="name">补差价</div>
+                                            </div>
+                                        </div>
+                                    </div>
                                     <el-input
                                         type="textarea"
                                         :rows="3"
@@ -56,37 +121,6 @@
                                         @keydown.native="handleKeyCode"
                                     ></el-input>
                                     <div class="send-btn-group">
-                                        <div class="send-emoji">
-                                            <div class="emoji-item" @click="showEmoji">
-                                                <el-popover placement="top" :width="400" trigger="click">
-                                                    <template #reference>
-                                                        <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/face.svg"></el-image>
-                                                    </template>
-                                                    <div class="swiper-item-box">
-                                                        <div class="flex_r swiper-item-box-l" v-for="(im, ix) in emojiList" :key="ix">
-                                                            <div
-                                                                class="icon_ emoji-item"
-                                                                v-for="(item, index) in im"
-                                                                :key="index"
-                                                                :style="{ opacity: ix === opacityGroup && (index == 5 || index == 6 || index == 7) ? opacity : 1 }"
-                                                            >
-                                                                <el-image class="img" :src="emojiUrl + item.image" @click="chooseEmoji(item.text)"></el-image>
-                                                            </div>
-                                                        </div>
-                                                    </div>
-                                                </el-popover>
-                                            </div>
-                                            <div class="emoji-item" @click="showUpload">
-                                                <el-tooltip effect="dark" content="上传图片" placement="top-start">
-                                                    <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/more.svg"></el-image>
-                                                </el-tooltip>
-                                            </div>
-                                            <div class="emoji-item" @click="showQuick">
-                                                <el-tooltip effect="dark" content="快捷回复" placement="top-start">
-                                                    <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/order.svg"></el-image>
-                                                </el-tooltip>
-                                            </div>
-                                        </div>
                                         <div class="send-btn-right">
                                             <el-button type="primary" @click="sendMessage">发送</el-button>
                                         </div>
@@ -95,9 +129,59 @@
                             </div>
                         </el-main>
                     </el-container>
-                    <el-aside width="400px">
+                    <el-container v-else>
+                        <div class="empty-data">
+                            请选择左侧的聊天列表
+                        </div>
+                    </el-container>
+                    <el-aside width="400px" v-if="chatItem">
                         <el-container>
-                            <el-header><div class="msg-title">个人资料</div></el-header>
+                            <el-header>
+                                <div class="msg-title">用户信息</div>
+                            </el-header>
+                            <el-main class="nopadding">
+                                <el-card shadow="never" class="border-none">
+                                    <el-descriptions :column="1" border>
+                                        <el-descriptions-item label="昵称">{{ chatItem.user.nickname }}</el-descriptions-item>
+                                        <el-descriptions-item label="OpenId">
+                                            <el-tooltip effect="dark" placement="top-start" :content="chatItem.user.openid">
+                                                查看
+                                            </el-tooltip>
+                                        </el-descriptions-item>
+                                        <el-descriptions-item label="手机号码">{{chatItem.user.mobile}}</el-descriptions-item>
+                                        <el-descriptions-item label="注册时间">{{chatItem.user.create_at}}</el-descriptions-item>
+                                    </el-descriptions>
+                                </el-card>
+                                <el-card shadow="never" class="border-none" v-loading="orderLoad">
+                                    <template #header>
+                                        <div class="msg-title">订单列表</div>
+                                    </template>
+                                    <div class="order-list-chat" v-if="orderList.length > 0">
+                                        <div class="m-order-s" v-for="(item,index) in orderList" :key="index">
+                                            <div class="order-img"><el-image style="width: 60px;height: 60px;" :src="item.img" fit="fit"></el-image></div>
+                                            <div class="order-info">
+                                                <div class="title">{{item.product.product_name}}</div>
+                                                <div class="title">{{item.order_sn}}</div>
+                                                <div class="price">{{this.$TOOL.money(item.price)}}</div>
+                                            </div>
+                                            <div class="order-status-btn">
+                                                <div class="status info cus" @click="sendOrder(item)">发送</div>
+                                                <div class="status info" v-if="item.status=='0'">未支付</div>
+                                                <div class="status primary" v-if="item.status=='1'">待使用</div>
+                                                <div class="status success" v-if="item.status=='2'">已完成</div>
+                                                <div class="status danger" v-if="item.status=='3'">已退款</div>
+                                                <div class="status warn" v-if="item.status=='4'">退款中</div>
+                                            </div>
+                                        </div>
+                                        <div class="order-page">
+                                            <el-pagination :default-page-size="msgSize" small layout="prev, pager, next" @size-change="orderPageChange" @current-change="orderPageChange" :total="orderTotal" :hide-on-single-page="true" />
+                                        </div>
+                                    </div>
+                                    <div class="order-list-chat" v-else>
+                                        <el-empty description="没有订单" />
+                                    </div>
+                                </el-card>
+                            </el-main>
                         </el-container>
                     </el-aside>
                 </el-container>
@@ -107,38 +191,86 @@
 </template>
 
 <script>
+import {
+	mapState
+} from 'vuex';
 import { Push } from "@/utils/push";
 import { emojiMap } from "@/utils/emoji";
+import item from '@/components/chat/index';
 var pushObj = null;
 let height2 = null;
 const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
+import {
+	formatDate,
+	to as tofn
+} from '@/util/index.js';
+let imageList = [];
 export default {
     name: 'GlobalIM',
+    components:{
+        item
+    },
+    props: {
+        user: { type: Object, default: () => {} }
+    },
     data() {
         return {
+            loadMsg:false,
+            history: {
+				messages: [],
+				allLoaded: false
+			},
+            msgLoad:false,
+            orderList:[],
+            orderLoad:false,
+            orderTotal:0,
+            orderPage:1,
             emojiUrl,
             loading:false,
             visible: false,           // 聊天窗口可见性
             unreadCount: 0,          // 未读消息数量
             inputMessage: '',        // 输入的消息
             timer: null,             // 定时器
-            messages: [              // 消息列表
-                { id: 1, name: '系统助手', avatar: '', text: '欢迎使用 SCUI!', time: '10:00', self: false },
-                { id: 2, name: '我', avatar: '', text: '你好,这个全局IM功能不错', time: '10:01', self: true },
-            ],
+            messages: [],
             wsAuth:"http://127.0.0.1:9881/plugin/webman/push/auth",
             wsUrl: 'ws://127.0.0.1:3131',
             page:1,
             size:10,
+            msgPage:1,
+            msgSize:10,
             chatContact:[],
-            checkChat:0,
+            checkChat:null,
             chatItem:null,
-            emojiList:[]
+            emojiList:[],
+			sendId:null,
+			myid: this.user.userId,
+            userId:null
         }
     },
+	computed: mapState({
+		//显示时间
+		renderMessageDate() {
+			return (message, index) => {
+				if (message.timestamp - this.history.messages[index + 1]?.timestamp > 3 * 60 * 1000) {
+					return formatDate(message.timestamp, 'timestamp');
+				}
+				return '';
+			};
+		},
+		// 是否本人isMy
+		isSelf() {
+			return (senderId) => {
+				const {
+					member_id = this.myid
+				} = {"member_id":this.myid};
+				return senderId === `${member_id}`;
+			};
+		}
+	}),
     mounted() {
-        console.log("pushObj")
+        // console.log("pushObj")
         this.initEmoji()
+        this.initPush();
         // this.simulateNewMessage()
     },
     beforeDestroy() {
@@ -147,8 +279,44 @@ export default {
         // }
     },
     methods: {
-        chooseEmoji(){
-
+        orderPageChange(e){
+            this.orderPage = e;
+            this.getOrderList()
+        },
+        hideWindow(){
+            this.chatItem = null;
+            this.msgPage = 1;
+            this.unreadCount = 0;
+            this.checkChat = null;
+            this.orderList = [];
+            this.orderPage = 1;
+            this.visible = false;
+        },
+        handleScroll(e){
+            const { scrollTop, clientHeight, scrollHeight } = e.target
+            const isBottom = scrollTop + clientHeight >= scrollHeight - 5   // 5px 缓冲区
+            const isTop = scrollTop <= 5
+            if (isBottom && !this.history.allLoaded) {
+                console.log('到达底部')
+                // 加载更多等操作
+                this.getMsgMore()
+            }
+            if (isTop && this.history.allLoaded) {
+                // console.log('到达顶部')
+            }
+        },
+        playAudio(){
+            const audio = new Audio('/img/msn.mp3');
+            audio.play().catch(error => {
+                console.error("播放音频时出错:", error);
+            });
+        },
+        onLongpress(){},
+        onItem(data){
+            console.log(data)
+        },
+        chooseEmoji(data){
+            this.inputMessage += data;
         },
         initEmoji() {
 			let newList = [];
@@ -170,19 +338,62 @@ export default {
 			this.emojiList = A.map((item, index) => {
 				return newList.slice(A[index].index, A[index + 1]?.index || newList.length);
 			});
-            console.log(this.emojiList)
 			// this.scroll({ detail: { scrollTop: 10 } });
 		},
-        sendMessage(){
+        /**
+         * 发送订单消息
+         * @param data 
+         */
+        async sendOrder(data){
+            var orderData = {"img":data.img,"name":data.product.product_name,"goods_id":data.life_goods_id,"order":data.out_order_no,"price":data.pay_money,"status":data.status};
+            var resp = await this.sendMessageApi("order",orderData);
+            if (!resp) {
+                return ;
+            }
+            this.sendMessageData({
+                payload: {
+                    order: orderData
+                },
+                type: 'order'
+            });
+        },
+        async sendMessage(){
             if (!this.inputMessage) return ;
+            var resp = await this.sendMessageApi("text",this.inputMessage);
+            if (!resp) {
+                this.inputMessage = "";
+                return ;
+            }
+            this.sendMessageData({
+                payload: {
+                    text: this.inputMessage
+                },
+                type: 'text'
+            });
             this.inputMessage = ""
         },
+        sendMessageData({payload,type}){
+			const message = {
+				groupId: this.chatItem.poi_id,
+				senderData: {
+                    avatar:this.chatItem.avatar
+                },
+				senderId: this.myid,
+				messageId: Date.now(),
+				payload: payload,
+				timestamp: Date.now(),
+				type: type,
+				recalled: false,
+				status: 'success',
+				isHide: 0
+			};
+			this.pushList(message);
+        },
         handleKeyCode(event){
-            console.log(event)
             if (event.keyCode == 13) {
                 if (!event.ctrlKey) {
-                    event.preventDefault();
                     this.sendMessage()
+                    event.preventDefault();
                 } else {
                     this.inputMessage += "\n";
                 }
@@ -193,57 +404,261 @@ export default {
         checkChatItem(index){
             this.checkChat = index;
             this.chatItem = this.chatContact[index]
+            this.chatContact[index].last.num = 0;
+            this.msgPage = 1;
+            this.history.messages = []
+            this.history.allLoaded = false
+            this.orderList = [];
+            this.orderPage = 1;
+            this.getMsgData();
+            this.getOrderList();
         },
         initPush(){
-          var _this = this;
-          this.pushObj = new Push({
-            "url":this.wsUrl,
-            "app_key":"265c33b73d5c04f918978577df2c48d2",
-            "auth":this.wsAuth
-          });
-          var user_channel = this.pushObj.subscribe('service-' + this.myid);
-          user_channel.on('message', function (data) {
-            // console.log(data);
-            // _this.formatMsg(data);
-          })
+            var _this = this;
+            if (!this.pushObj) {
+                this.pushObj = new Push({
+                    "url":this.wsUrl,
+                    "app_key":"265c33b73d5c04f918978577df2c48d2",
+                    "auth":this.wsAuth
+                });
+            }
+            var user_channel = this.pushObj.subscribe('service-' + this.myid);
+            user_channel.on('message', function (data) {
+                _this.playAudio()
+                _this.unreadCount ++;
+                if (_this.chatItem && _this.chatItem.openid == data.openid) { // 当前窗口实时推送
+                    _this.formatMsg(data);
+                }
+                // 给消息标红并更新最后一条消息
+                var msgData = _this.chatContact;
+                for (var i=0; i <= msgData.length;i ++) {
+                    var item = msgData[i];
+                    if (item && item.openid == data.openid) {
+                        item.last.type = data.type;
+                        item.last.num ++;
+                        item.last.content = data.content;
+                        item.last.create_at = data.create_at
+                    }
+                }
+            })
         },
+        formatMsg(item){
+			var payload = {};
+			if (item.type == 'text') {
+				payload.text = item.content
+			}
+			if (item.type == 'order') {
+				var order = JSON.parse(item.content)
+				payload.order = order
+			}
+			if (item.type == 'pay') {
+				var order = JSON.parse(item.content)
+				payload.pay = order
+			}
+			if (item.type == 'image') {
+				payload = {
+					contentType: 'image/png',
+					name: 'uni-image.png',
+					size: 82942,
+					url: item.content,
+					width: 2732,
+					height: 2732,
+					thumbnail: item.content
+				};
+			}
+			const message = {
+				groupId: item.poi_id,
+				senderData: {
+					avatar:item.avatar
+				},
+				senderId: (item.source==1?item.openid:item.service_id),
+				messageId: item.msgId,
+				payload: payload,
+				timestamp: item.time,
+				type: item.type,
+				recalled: false,
+				status: 'success',
+				isHide: 0
+			};
+			this.pushList(message);
+		},
+        async pushList(message) {
+			this.initMessageItem(message);
+			this.history.messages.unshift(message);
+            console.log(this.history.messages)
+			// 是否触发文字动效果
+			if (message.type === 'text' || message.type === 'text_quote') {
+				this.onSetText(message.payload.text);
+			}
+			// 缓存照片地址,
+			if (message.type === 'image' || message.type === 'image_transmit') {
+				imageList.push(message.payload.url);
+			}
+		},
+		// 组装item
+		initMessageItem(message, index) {
+			message['isHide'] = 0;
+			if (index === 0 && (message.type === 'text' || message.type === 'text_quote')) {
+				this.onSetText(message.payload.text);
+			}
+		},
+		// 文本触发效果相关========
+		onSetText(text) {},
         // 切换聊天窗口
         async toggleChat() {
             this.visible = !this.visible
+            this.loadMsg = true;
             this.chatContact = [];
             if (this.visible) {
                 // 打开窗口时清除未读角标
                 this.unreadCount = 0
             }
             var resp = await this.$API.chat.list.get({page:this.page,pageSize:this.size})
+            this.loadMsg = false;
             if (resp.code == 0) {
               return this.$message.error(resp.msg)
             }
             this.chatContact = this.chatContact.concat(resp.data.rows)
-            this.chatItem = this.chatContact[0]
+            // this.chatItem = this.chatContact[0]
+            // this.userId = this.chatItem.openid;
+            // this.getMsgData();
+        },
+        /**
+         * 加载更多
+         */
+        async getMsgMore(){
+            this.history.allLoaded = true;
+            this.msgLoad = true;
+            var resp = await this.$API.chat.msg.get({"page":this.msgPage,"size":this.msgSize,"openid":this.chatItem.openid})
+            this.msgLoad = false;
+            if (resp.data.rows.length > 0) {
+                this.msgPage ++;
+                var msgData = []
+				var list = resp.data.rows
+                // 同步混入数据
+                list.forEach((item, ix) => {
+                    var payload = {};
+                    if (item.type == 'text') {
+                        payload.text = item.content
+                    }
+                    if (item.type == 'order') {
+                        var order = JSON.parse(item.content)
+                        payload.order = order
+                    }
+                    if (item.type == 'pay') {
+                        var order = JSON.parse(item.content)
+                        payload.pay = order
+                    }
+                    if (item.type == 'image') {
+                        payload = {
+                            contentType: 'image/png',
+                            name: 'uni-image.png',
+                            size: 82942,
+                            url: item.content,
+                            width: 2732,
+                            height: 2732,
+                            thumbnail: item.content
+                        };
+                    }
+                    const message = {
+                        groupId: item.poi_id,
+                        senderData: {
+                            avatar:item.avatar
+                        },
+                        senderId: (item.source==1?item.openid:item.service_id),
+                        messageId: item.msgId,
+                        payload: payload,
+                        timestamp: item.time,
+                        type: item.type,
+                        recalled: false,
+                        status: 'success',
+                        isHide: 0
+                    };
+                    msgData[ix] = message
+                });
+                this.history.messages = [...this.history.messages, ...msgData];
+                this.history.allLoaded = false;
+            } else {
+                this.history.allLoaded = true;
+            }
+        },
+        /**
+         * 首次加载
+         */
+        async getMsgData(){
+            var resp = await this.$API.chat.msg.get({"page":this.msgPage,"size":this.msgSize,"openid":this.chatItem.openid})
+            if (resp.data.rows.length > 0) {
+                var msgData = resp.data.rows
+                msgData.forEach((item,index)=>{
+                    this.formatMsg(item);
+                })
+                this.msgPage ++;
+            }
+        },
+        async sendMessageApi(type,content){
+            this.chatContact[this.checkChat].last.num = 0;
+            var resp = await this.$API.chat.send.post({type:type,content: content,groupId: this.chatItem.poi_id,openid:this.chatItem.openid})
+            if (resp.code == 0) return false;
+            this.chatContact[this.checkChat].last.type = type;
+            this.chatContact[this.checkChat].last.content = content;
+            this.chatContact[this.checkChat].last.create_at = resp.data.time;
+            return true;
+        },
+        async getOrderList(){
+            this.orderLoad = true;
+            var resp = await this.$API.order.list.get({"page":this.orderPage,"pageSize":this.msgSize,"openid":this.chatItem.openid});
+            this.orderLoad = false;
+            if (resp.code == 0) {
+                return ;
+            }
+            this.orderList = resp.data.rows;
+            this.orderTotal = resp.data.total;
         }
     }
 }
 </script>
 
 <style scoped lang="scss">
+@import '@/style/index.scss';
+.border-none{border: 0;}
 .msg-title{font-size: 18px;font-weight: bold;}
+.loading-text{background-color: #f8f8f8;text-align: center;font-size: 12px;}
+.m-order-s{background-color: #f8f8f8;border-radius: 5px;display: flex;font-size: 14px;position: relative;margin-bottom: 10px;}
+.m-order-s .order-img image{width: 60px;height: 60px;}
+.m-order-s .order-info {padding: 10px;}
+.m-order-s .order-info .title{font-size: 14px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
+.m-order-s .order-info .price{font-size: 14px;color: #f00;}
+.m-order-s .order-status-btn{position: absolute;right: 0;bottom: 0;display: flex;align-items: center;gap: 5px;}
+.m-order-s .status{border-radius: 5px 0 5px 0;background-color: #909399;font-size: 12px;color: #fff;padding: 5px;}
+.m-order-s .status.cus{cursor: pointer;}
+.m-order-s .status.info{background-color: #909399;}
+.m-order-s .status.primary{background-color: #409eff;}
+.m-order-s .status.success{background-color: #67c23a;}
+.m-order-s .status.danger{background-color: #f56c6c;}
+.m-order-s .status.warn{background-color: #e6a23c;}
+
+.hideStr{overflow: hidden;width: 80%;}
 .contact-item{display: flex;align-items: center;background-color: #fff;padding: 10px;border-bottom: 1px solid #f8f8f8;cursor: pointer;}
 .contact-item.active{background-color: #f8f8f8;}
 .contact-item .kefu-store{margin-left: 10px;}
 .contact-item .kefu-store .name{font-size: 14px;color: #333;display: flex;align-items: center;}
-.contact-item .kefu-store .name text{background-color: #f00;color: #fff;width: 20px;height: 20px;display: flex;align-items: center;justify-content: center;border-radius: 20px;font-size: 12px;margin-left: 10px;}
+.contact-item .kefu-store .name span{background-color: #f00;color: #fff;width: 20px;height: 20px;display: flex;align-items: center;justify-content: center;border-radius: 20px;font-size: 12px;margin-left: 10px;}
 .contact-item .kefu-store .desc{font-size: 12px;color: #666;margin-top: 5px;}
 .contact-item .time{margin-left: auto;margin-right: 0;font-size: 12px;color: #666;}
 .send-btn-group{margin-top: 10px;display: flex;align-items: center;justify-content: space-between;}
 .send-btn-group .send-emoji{display: flex;align-items: center;gap: 15px;cursor: pointer;}
-
+.quick-group{display: flex;align-items: center;gap: 10px;padding: 15px 0;}
+.quick-group .item-btn{background-color: #f8f8f8;padding: 5px;border-radius: 3px;display: flex;align-items: center;gap: 5px;cursor: pointer;}
+.z_index2{position: relative;z-index: 2;}
+.empty-data{display: flex;width: 100%;text-align: center;justify-content: center;font-size: 18px;background-color: #f8f8f8;align-items: center;}
 .swiper-item-box {
     position: relative;
     z-index: 1;
     box-sizing: border-box;
     width: 100%;
     height: 100%;
+    max-height: 300px;
+    overflow: auto;
     flex-wrap: wrap;
     align-items: flex-start;
 
@@ -264,11 +679,15 @@ export default {
         height: calc(12.5%);
         flex-shrink: 0;
         transition: all 0.1s;
+        padding: 10px 0;
         .img {
-            width: 70%;
-            height: 70%;
+            width: 55%;
+            height: 55%;
             cursor: pointer;
         }
+        &:hover {
+            background-color: #f8f8f8;
+        }
     }
 }
 .flex_r {
@@ -304,8 +723,57 @@ export default {
     margin-right: 10rpx;
     background-color: #05c160;
 }
+.page {
+	position: fixed;
+	z-index: 1;
+	top: 0;
+	left: 0;
+	bottom: 0;
+	right: 0;
+	background-color: #ededed;
+}
+.scroll-Y {
+	width: 100%;
+	height: 0;
+	transition: all 0.2s;
+	transform: rotate(180deg);
+	background-color: #ededed;
+	::-webkit-scrollbar {
+		display: none;
+	}
+}
+.scroll-view-str {
+	width: 100%;
+}
+
+.message-list .time {
+	width: 100%;
+	color: #a3a3a3;
+	line-height: 50px;
+}
+
+.message-list .recalled {
+	width: 100%;
+	height: 50rpx;
+	margin: 20rpx 0;
+	color: #a3a3a3;
 
+	.recalled-edit {
+		color: #5a6693;
+		margin-left: 14rpx;
+	}
+}
+@keyframes simple-blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0; }
+}
 .global-im {
+    .unreadCount{
+        position: fixed;
+        right: 42px;
+        bottom: 62px;
+        animation: simple-blink 2s ease-in-out infinite;
+    }
   .im-trigger {
     position: fixed;
     right: 24px;
@@ -323,7 +791,6 @@ export default {
     transition: all 0.3s;
     color: white;
     &:hover {
-      transform: scale(1.05);
       background: #66b1ff;
     }
     &.active {
@@ -338,6 +805,9 @@ export default {
   .message-list {
     flex: 1;
     overflow-y: auto;
+    background-color: #f8f8f8;
+    transition: all 0.2s;
+    transform: rotate(180deg);
     .message-item {
       display: flex;
       margin-bottom: 16px;
@@ -380,7 +850,7 @@ export default {
     }
   }
   .input-area {
-    padding: 16px;
+    padding: 0 16px 16px 16px;
     border-top: 1px solid #eee;
   }
 }

+ 1 - 1
src/layout/index.vue

@@ -209,7 +209,7 @@
 
 	<auto-exit></auto-exit>
 	<div v-if="userInfo.type==3">
-		<imFloat></imFloat>
+		<imFloat :user="userInfo"></imFloat>
 	</div>
 </template>
 

+ 27 - 0
src/store/emoji.js

@@ -0,0 +1,27 @@
+import Vuex from 'vuex'
+const store = new Vuex.Store({
+	state: {
+		// 文字大小
+		page_font_size: localStorage.getItem('page_font_size') || 'page_font_size',
+		// 导航栏高度
+		StatusBar: {
+			statusBar: 0,
+			customBar: 0,
+		},
+	},
+	mutations: {
+		// 设置导航栏高度
+		SET_STATUSBAR(state, value) {
+			state.StatusBar = value
+		},
+		SET_page_font_size(state, value) {
+			state.page_font_size = value
+			localStorage.setItem({
+				key: 'page_font_size',
+				data: value,
+				success: function() {}
+			});
+		}
+	}
+})
+export default store

+ 10 - 6
src/style/app.scss

@@ -67,24 +67,28 @@ textarea {
 }
 
 ::-webkit-scrollbar {
-	width: 5px;
-	height: 5px;
+	width: 0;
+	background-color: transparent;
 }
 
 ::-webkit-scrollbar-thumb {
-	background-color: rgba(50, 50, 50, 0.3);
+	// background-color: rgba(50, 50, 50, 0.3);
+	background-color: transparent;
 }
 
 ::-webkit-scrollbar-thumb:hover {
-	background-color: rgba(50, 50, 50, 0.6);
+	// background-color: rgba(50, 50, 50, 0.6);
+	background-color: transparent;
 }
 
 ::-webkit-scrollbar-track {
-	background-color: rgba(50, 50, 50, 0.1);
+	// background-color: rgba(50, 50, 50, 0.1);
+	background-color: transparent;
 }
 
 ::-webkit-scrollbar-track:hover {
-	background-color: rgba(50, 50, 50, 0.2);
+	// background-color: rgba(50, 50, 50, 0.2);
+	background-color: transparent;
 }
 @keyframes flash {
 	0% {

+ 180 - 0
src/style/index.scss

@@ -0,0 +1,180 @@
+@for $i from 1 to 60 {
+	.text_#{$i} {
+		font-size: #{$i}rpx;
+	}
+}
+/* 小号 */
+#page_font_size_min {
+	@for $i from 1 to 60 {
+		.text_#{$i} {
+			font-size: calc(#{$i}rpx - 2rpx);
+		}
+	}
+}
+/* 标准 */
+#page_font_size {
+	@for $i from 1 to 60 {
+		.text_#{$i} {
+			font-size: #{$i}rpx;
+		}
+	}
+}
+
+/* 大 */
+#page_font_size_max {
+	$font_size: 6rpx;
+	@for $i from 1 to 60 {
+		.text_#{$i} {
+			font-size: calc(#{$i}rpx + #{$font_size});
+		}
+	}
+}
+
+/* 偏大 */
+#page_font_size_max_plus {
+	$font_size: 10rpx;
+	@for $i from 1 to 60 {
+		.text_#{$i} {
+			font-size: calc(#{$i}rpx + #{$font_size});
+		}
+	}
+}
+/* 特大 */
+#page_font_size_max_plus_pro {
+	$font_size: 16rpx;
+	@for $i from 1 to 60 {
+		.text_#{$i} {
+			font-size: calc(#{$i}rpx + #{$font_size});
+		}
+	}
+}
+
+.flex_r {
+	display: flex;
+	flex-direction: row;
+}
+
+.flex_c {
+	display: flex;
+	flex-direction: column;
+}
+.flex1 {
+	flex: 1;
+}
+.fj_b {
+	justify-content: space-between;
+}
+
+.fj_a {
+	justify-content: space-around;
+}
+
+.fj_c {
+	justify-content: center;
+}
+
+.fa_c {
+	align-items: center;
+}
+
+.flex_c_c {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+}
+
+.icon_ {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+}
+
+.z_index2 {
+	position: relative;
+	z-index: 2;
+}
+
+.rotate_45 {
+	transform: rotate(45deg);
+}
+
+.rotate_90 {
+	transform: rotate(90deg);
+}
+
+.rotate_180 {
+	transform: rotate(180deg);
+}
+
+.text-indent {
+	text-indent: 2em;
+	text-align: justify;
+}
+
+/* 文本css */
+.nowrap_ {
+	text-overflow: ellipsis;
+	/* #ifndef APP-PLUS-NVUE*/
+	overflow: hidden;
+	white-space: nowrap;
+	/*  #endif  */
+
+	/*#ifdef APP-PLUS-NVUE*/
+	lines: 1;
+	/*  #endif  */
+}
+
+.ellipsis_2 {
+	text-overflow: ellipsis;
+	/* #ifndef APP-PLUS-NVUE*/
+	overflow: hidden;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-line-clamp: 2;
+	/*  #endif  */
+
+	/* #ifdef  APP-PLUS-NVUE*/
+	lines: 2;
+	/*  #endif  */
+}
+
+.hover_class::after {
+	position: absolute;
+	z-index: 2;
+	content: '';
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0, 0, 0, 0.1);
+}
+
+.size_white {
+	color: #fff;
+}
+
+.color_ {
+	color: #b5b5b6;
+}
+
+.color___ {
+	color: #7fade8;
+}
+
+.color__ {
+	color: #7e7e7e;
+}
+
+.color_4a {
+	color: #4a4a4a;
+}
+
+.color_rot {
+	color: #ff0000;
+}
+
+.bold_ {
+	font-weight: bold;
+}

+ 186 - 0
src/util/EmojiDecoder.js

@@ -0,0 +1,186 @@
+import store from '@/store/emoji.js';
+let font_size = {
+	page_font_size: 0,
+	page_font_size_max: 2,
+	page_font_size_max_plus: 4,
+	page_font_size_max_plus_pro: 6
+};
+export class EmojiDecoder {
+	emojiMap = null;
+	url = "";
+	patterns = [];
+	metaChars = /[[\]{}()*+?.\\|^$\-,&#\s]/g;
+
+	constructor(url, emojiMap) {
+		this.url = url || '';
+		this.emojiMap = emojiMap || {};
+		for (let i in this.emojiMap) {
+			if (this.emojiMap.hasOwnProperty(i)) {
+				this.patterns.push('(' + i.replace(this.metaChars, "\\$&") + ')');
+			}
+		}
+	}
+
+	decode(text, size, top = '4px') {
+		if (!size) {
+			size = `${22 + font_size[store.state.page_font_size]}px`
+		}
+		return text.replace(new RegExp(this.patterns.join('|'), 'g'), (match) => {
+			return typeof this.emojiMap[match] != 'undefined' ?
+				`<img style="position: relative;top: ${top};" height="${size}" width="${size}" src="${this.url}${this.emojiMap[match]}" />` :
+				match;
+		}).replace(/\n/g, '<br/>');
+	}
+	// 无需换行
+	decodeNo(text, size = '22rpx', top = '4px') {
+		return text.replace(new RegExp(this.patterns.join('|'), 'g'), (match) => {
+			return typeof this.emojiMap[match] != 'undefined' ?
+				`<img style="position: relative;top: ${top};" height="${size}" width="${size}" src="${this.url}${this.emojiMap[match]}" />` :
+				match;
+		});
+	}
+};
+export const emojiMap = {
+	"[微笑]": "emoji_49@2x.png",
+	"[NO]": "emoji_0@2x.png",
+	"[OK]": "emoji_1@2x.png",
+	"[下雨]": "emoji_2@2x.png",
+	"[么么哒]": "emoji_3@2x.png",
+	"[乒乓]": "emoji_4@2x.png",
+	"[便便]": "emoji_5@2x.png",
+	"[信封]": "emoji_6@2x.png",
+	"[偷笑]": "emoji_7@2x.png",
+	"[傲慢]": "emoji_8@2x.png",
+	"[再见]": "emoji_9@2x.png",
+	"[冷汗]": "emoji_10@2x.png",
+	"[凋谢]": "emoji_11@2x.png",
+	"[刀]": "emoji_12@2x.png",
+	"[删除]": "emoji_13@2x.png",
+	"[勾引]": "emoji_14@2x.png",
+	"[发呆]": "emoji_15@2x.png",
+	"[发抖]": "emoji_16@2x.png",
+	"[可怜]": "emoji_17@2x.png",
+	"[可爱]": "emoji_18@2x.png",
+	"[右哼哼]": "emoji_19@2x.png",
+	"[右太极]": "emoji_20@2x.png",
+	"[右车头]": "emoji_21@2x.png",
+	"[吐]": "emoji_22@2x.png",
+	"[吓]": "emoji_23@2x.png",
+	"[咒骂]": "emoji_24@2x.png",
+	"[咖啡]": "emoji_25@2x.png",
+	"[啤酒]": "emoji_26@2x.png",
+	"[嘘]": "emoji_27@2x.png",
+	"[回头]": "emoji_28@2x.png",
+	"[困]": "emoji_29@2x.png",
+	"[坏笑]": "emoji_30@2x.png",
+	"[多云]": "emoji_31@2x.png",
+	"[大兵]": "emoji_32@2x.png",
+	"[大哭]": "emoji_33@2x.png",
+	"[太阳]": "emoji_34@2x.png",
+	"[奋斗]": "emoji_35@2x.png",
+	"[奶瓶]": "emoji_36@2x.png",
+	"[委屈]": "emoji_37@2x.png",
+	"[害羞]": "emoji_38@2x.png",
+	"[尴尬]": "emoji_39@2x.png",
+	"[左哼哼]": "emoji_40@2x.png",
+	"[左太极]": "emoji_41@2x.png",
+	"[左车头]": "emoji_42@2x.png",
+	"[差劲]": "emoji_43@2x.png",
+	"[弱]": "emoji_44@2x.png",
+	"[强]": "emoji_45@2x.png",
+	"[彩带]": "emoji_46@2x.png",
+	"[彩球]": "emoji_47@2x.png",
+	"[得意]": "emoji_48@2x.png",
+	"[心碎了]": "emoji_50@2x.png",
+	"[快哭了]": "emoji_51@2x.png",
+	"[怄火]": "emoji_52@2x.png",
+	"[怒]": "emoji_53@2x.png",
+	"[惊恐]": "emoji_54@2x.png",
+	"[惊讶]": "emoji_55@2x.png",
+	"[憨笑]": "emoji_56@2x.png",
+	"[手枪]": "emoji_57@2x.png",
+	"[打哈欠]": "emoji_58@2x.png",
+	"[抓狂]": "emoji_59@2x.png",
+	"[折磨]": "emoji_60@2x.png",
+	"[抠鼻]": "emoji_61@2x.png",
+	"[抱抱]": "emoji_62@2x.png",
+	"[抱拳]": "emoji_63@2x.png",
+	"[拳头]": "emoji_64@2x.png",
+	"[挥手]": "emoji_65@2x.png",
+	"[握手]": "emoji_66@2x.png",
+	"[撇嘴]": "emoji_67@2x.png",
+	"[擦汗]": "emoji_68@2x.png",
+	"[敲打]": "emoji_69@2x.png",
+	"[晕]": "emoji_70@2x.png",
+	"[月亮]": "emoji_71@2x.png",
+	"[棒棒糖]": "emoji_72@2x.png",
+	"[汽车]": "emoji_73@2x.png",
+	"[沙发]": "emoji_74@2x.png",
+	"[流汗]": "emoji_75@2x.png",
+	"[流泪]": "emoji_76@2x.png",
+	"[激动]": "emoji_77@2x.png",
+	"[灯泡]": "emoji_78@2x.png",
+	"[炸弹]": "emoji_79@2x.png",
+	"[熊猫]": "emoji_80@2x.png",
+	"[爆筋]": "emoji_81@2x.png",
+	"[爱你]": "emoji_82@2x.png",
+	"[爱心]": "emoji_83@2x.png",
+	"[爱情]": "emoji_84@2x.png",
+	"[猪头]": "emoji_85@2x.png",
+	"[猫咪]": "emoji_86@2x.png",
+	"[献吻]": "emoji_87@2x.png",
+	"[玫瑰]": "emoji_88@2x.png",
+	"[瓢虫]": "emoji_89@2x.png",
+	"[疑问]": "emoji_90@2x.png",
+	"[白眼]": "emoji_91@2x.png",
+	"[皮球]": "emoji_92@2x.png",
+	"[睡觉]": "emoji_93@2x.png",
+	"[磕头]": "emoji_94@2x.png",
+	"[示爱]": "emoji_95@2x.png",
+	"[礼品袋]": "emoji_96@2x.png",
+	"[礼物]": "emoji_97@2x.png",
+	"[篮球]": "emoji_98@2x.png",
+	"[米饭]": "emoji_99@2x.png",
+	"[糗大了]": "emoji_100@2x.png",
+	"[红双喜]": "emoji_101@2x.png",
+	"[红灯笼]": "emoji_102@2x.png",
+	"[纸巾]": "emoji_103@2x.png",
+	"[胜利]": "emoji_104@2x.png",
+	"[色]": "emoji_105@2x.png",
+	"[药]": "emoji_106@2x.png",
+	"[菜刀]": "emoji_107@2x.png",
+	"[蛋糕]": "emoji_108@2x.png",
+	"[蜡烛]": "emoji_109@2x.png",
+	"[街舞]": "emoji_110@2x.png",
+	"[衰]": "emoji_111@2x.png",
+	"[西瓜]": "emoji_112@2x.png",
+	"[调皮]": "emoji_113@2x.png",
+	"[象棋]": "emoji_114@2x.png",
+	"[跳绳]": "emoji_115@2x.png",
+	"[跳跳]": "emoji_116@2x.png",
+	"[车厢]": "emoji_117@2x.png",
+	"[转圈]": "emoji_118@2x.png",
+	"[鄙视]": "emoji_119@2x.png",
+	"[酷]": "emoji_120@2x.png",
+	"[钞票]": "emoji_121@2x.png",
+	"[钻戒]": "emoji_122@2x.png",
+	"[闪电]": "emoji_123@2x.png",
+	"[闭嘴]": "emoji_124@2x.png",
+	"[闹钟]": "emoji_125@2x.png",
+	"[阴险]": "emoji_126@2x.png",
+	"[难过]": "emoji_127@2x.png",
+	"[雨伞]": "emoji_128@2x.png",
+	"[青蛙]": "emoji_129@2x.png",
+	"[面条]": "emoji_130@2x.png",
+	"[鞭炮]": "emoji_131@2x.png",
+	"[风车]": "emoji_132@2x.png",
+	"[飞吻]": "emoji_133@2x.png",
+	"[飞机]": "emoji_134@2x.png",
+	"[饥饿]": "emoji_135@2x.png",
+	"[香蕉]": "emoji_136@2x.png",
+	"[骷髅]": "emoji_137@2x.png",
+	"[麦克风]": "emoji_138@2x.png",
+	"[麻将]": "emoji_139@2x.png",
+	"[鼓掌]": "emoji_140@2x.png",
+	"[龇牙]": "emoji_141@2x.png"
+}

+ 49 - 0
src/util/formatDate.js

@@ -0,0 +1,49 @@
+// 格式化时间
+// this.timestampFormat('2022-07-05 15:10:10'); 
+// timestamp:时间戳
+// time:2022-07-05 15:10:10'
+export const formatDate = (date, type = "time") => {
+	let timestamp = null
+	if (type === "time") {
+		timestamp = Date.parse(date.replace(/-/g, '/')) / 1000 //可解析一个日期时间字符串,并返回 1970/1/1 午夜距离该日期时间的毫秒数。
+	} else {
+		timestamp = date / 1000
+	}
+
+	function zeroize(num) {
+		return (String(num).length == 1 ? '0' : '') + num;
+	}
+	let curTimestamp = parseInt(new Date().getTime() / 1000); //当前时间戳
+
+	let timestampDiff = curTimestamp - timestamp; // 参数时间戳与当前时间戳相差秒数
+	let curDate = new Date(curTimestamp * 1000); // 当前时间日期对象
+	let tmDate = new Date(timestamp * 1000); // 参数时间戳转换成的日期对象
+	let Y = tmDate.getFullYear(),
+		m = tmDate.getMonth() + 1,
+		d = tmDate.getDate();
+	let H = tmDate.getHours(),
+		i = tmDate.getMinutes(),
+		s = tmDate.getSeconds();
+	if (timestampDiff < 60) { // 一分钟以内
+		return "刚刚";
+	} else if (timestampDiff < 3600) { // 一小时前之内
+		return Math.floor(timestampDiff / 60) + "分钟前";
+	} else if (curDate.getFullYear() == Y && curDate.getMonth() + 1 == m && curDate.getDate() == d) {
+		if (H <= 12) {
+			return '上午' + zeroize(H) + ':' + zeroize(i);
+		}
+		return '下午' + zeroize(H) + ':' + zeroize(i);
+	} else {
+		let newDate = new Date((curTimestamp - 86400) * 1000); // 参数中的时间戳加一天转换成的日期对象
+		if (newDate.getFullYear() == Y && newDate.getMonth() + 1 == m && newDate.getDate() == d) {
+			if (H <= 12) {
+				return '昨天' + ' ' + '上午' + zeroize(H) + ':' + zeroize(i);
+			}
+			return '昨天' + ' ' + '下午' + zeroize(H) + ':' + zeroize(i);
+		} else if (curDate.getFullYear() == Y) {
+			return zeroize(m) + '月' + zeroize(d) + '日 ' + zeroize(H) + ':' + zeroize(i);
+		} else {
+			return Y + '年' + zeroize(m) + '月' + zeroize(d) + '日 ' + zeroize(H) + ':' + zeroize(i);
+		}
+	}
+}

+ 40 - 0
src/util/getStatusBar.js

@@ -0,0 +1,40 @@
+import store from '@/store/index.js';
+export const getStatusBar = () => {
+	return new Promise((success, fail) => {
+		uni.getSystemInfo({
+			success: (e) => {
+				// 获取手机状态栏高度
+				let statusBar = e.statusBarHeight
+				let customBar
+				// #ifndef MP
+				customBar = statusBar + (e.platform == 'android' ? 50 : 45)
+				// #endif
+				// #ifdef MP-WEIXIN
+				// 获取胶囊按钮的布局位置信息
+				let menu = wx.getMenuButtonBoundingClientRect()
+				// 导航栏高度 = 胶囊下距离 + 胶囊上距离 - 状态栏高度
+				customBar = menu.bottom + menu.top - statusBar
+				// #endif
+				// #ifdef MP-ALIPAY
+				customBar = statusBar + e.titleBarHeight
+				// #endif
+				const compensateHeight = 10; //非刘海屏状态栏高度偏低,补充10
+				store.commit('SET_STATUSBAR', {
+					statusBar: statusBar + compensateHeight,
+					customBar: customBar + compensateHeight,
+					// #ifdef MP
+					statusBar: statusBar + compensateHeight + 10,
+					customBar: customBar + compensateHeight + 10
+					// #endif
+				});
+				success({
+					statusBar,
+					customBar
+				})
+			}
+		})
+
+
+	})
+
+}

+ 35 - 0
src/util/index.js

@@ -0,0 +1,35 @@
+import {
+	to
+} from './to.js';
+import {
+	show
+} from './show.js';
+import {
+	jsonUrl
+} from './jsonUrl.js';
+import {
+	openimg
+} from './openimg.js';
+import {
+	formatDate
+} from './formatDate.js';
+import {
+	getStatusBar
+} from './getStatusBar.js';
+import {
+	throttle
+} from './throttle.js';
+import {
+	vibrateShort
+} from './vibrateShort.js';
+
+export {
+	to,
+	show,
+	openimg,
+	jsonUrl,
+	formatDate,
+	getStatusBar,
+	throttle,
+	vibrateShort
+}

+ 30 - 0
src/util/jsonUrl.js

@@ -0,0 +1,30 @@
+function isObj(obj) {
+	let array = Object.keys(obj);
+	if (array.length === 0) { //如果数组array的长度为0 ,则返回为true,说明data对象为空
+		return false;
+	}
+	return true;
+};
+// 配合to方法使用
+export const jsonUrl = (e) => {
+	// #ifdef MP
+	if (e.data) {
+		e.data = decodeURIComponent(e.data)
+	}
+	// #endif
+	if (isObj(e)) {
+		if (e.data) {
+			var data = JSON.parse(e.data)
+		} else {
+			return e;
+		}
+		return data;
+	} else {
+		uni.showToast({
+			title: '参数错误',
+			duration: 1000,
+			icon: 'none',
+			mask: true,
+		});
+	}
+}

+ 29 - 0
src/util/openimg.js

@@ -0,0 +1,29 @@
+export const openimg = (index,item,attributes='') => {
+	if(item) {
+		// 数组对象请况
+		if (attributes) {
+			let arr = []
+			item.forEach((item,ix)=>{
+				if (item[attributes] ) {
+					arr.push(item[attributes])
+				}
+			})
+			uni.previewImage({
+				urls: arr,
+				current: arr[index]
+			});
+		} else {// 数组请况
+			uni.previewImage({
+				urls: item,
+				current: item[index]
+			});
+		}
+	} else if(!item) {//传入单张照片
+		let arr = []
+		arr.push(index)
+		uni.previewImage({
+			urls: arr,
+			current: arr[1]
+		});
+	}
+}

+ 833 - 0
src/util/push.js

@@ -0,0 +1,833 @@
+
+function Push(options) {
+    this.doNotConnect = 0;
+    options = options || {};
+    options.heartbeat  = options.heartbeat || 25000;
+    options.pingTimeout = options.pingTimeout || 10000;
+    this.config = options;
+    this.uid = 0;
+    this.channels = {};
+    this.connection = null;
+    this.pingTimeoutTimer = 0;
+    Push.instances.push(this);
+    this.createConnection();
+}
+
+Push.prototype.checkoutPing = function() {
+    var _this = this;
+    _this.checkoutPingTimer && clearTimeout(_this.checkoutPingTimer);
+    _this.checkoutPingTimer = setTimeout(function () {
+        _this.checkoutPingTimer = 0;
+        if (_this.connection.state === 'connected') {
+            _this.connection.send('{"event":"pusher:ping","data":{}}');
+            if (_this.pingTimeoutTimer) {
+                clearTimeout(_this.pingTimeoutTimer);
+                _this.pingTimeoutTimer = 0;
+            }
+            _this.pingTimeoutTimer = setTimeout(function () {
+                _this.connection.closeAndClean();
+                if (!_this.connection.doNotConnect) {
+                    _this.connection.waitReconnect();
+                }
+            }, _this.config.pingTimeout);
+        }
+    }, this.config.heartbeat);
+};
+
+Push.prototype.channel = function (name) {
+    return this.channels.find(name);
+};
+Push.prototype.allChannels = function () {
+    return this.channels.all();
+};
+Push.prototype.createConnection = function () {
+    if (this.connection) {
+        throw Error('Connection already exist');
+    }
+    var _this = this;
+    var url = this.config.url;
+    function updateSubscribed () {
+        for (var i in _this.channels) {
+            _this.channels[i].subscribed = false;
+        }
+    }
+    this.connection = new Connection({
+        url: url,
+        app_key: this.config.app_key,
+        onOpen: function () {
+            _this.connection.state  = 'connecting';
+            _this.checkoutPing();
+        },
+        onMessage: function(params) {
+            if(_this.pingTimeoutTimer) {
+                clearTimeout(_this.pingTimeoutTimer);
+                _this.pingTimeoutTimer = 0;
+            }
+
+            params = JSON.parse(params.data);
+            var event = params.event;
+            var channel_name = params.channel;
+
+            if (event === 'pusher:pong') {
+                _this.checkoutPing();
+                return;
+            }
+            if (event === 'pusher:error') {
+                throw Error(params.data.message);
+            }
+            var data = JSON.parse(params.data), channel;
+            if (event === 'pusher_internal:subscription_succeeded') {
+                channel = _this.channels[channel_name];
+                channel.subscribed = true;
+                channel.processQueue();
+                channel.emit('pusher:subscription_succeeded');
+                return;
+            }
+            if (event === 'pusher:connection_established') {
+                _this.connection.socket_id = data.socket_id;
+                _this.connection.updateNetworkState('connected');
+                _this.subscribeAll();
+            }
+            if (event.indexOf('pusher_internal') !== -1) {
+                console.log("Event '"+event+"' not implement");
+                return;
+            }
+            channel = _this.channels[channel_name];
+            if (channel) {
+                channel.emit(event, data);
+            }
+        },
+        onClose: function () {
+            updateSubscribed();
+        },
+        onError: function () {
+            updateSubscribed();
+        }
+    });
+};
+Push.prototype.disconnect = function () {
+    this.connection.doNotConnect = 1;
+    this.connection.close();
+};
+
+Push.prototype.subscribeAll = function () {
+    if (this.connection.state !== 'connected') {
+        return;
+    }
+    for (var channel_name in this.channels) {
+        //this.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
+        this.channels[channel_name].processSubscribe();
+    }
+};
+
+Push.prototype.unsubscribe = function (channel_name) {
+    if (this.channels[channel_name]) {
+        delete this.channels[channel_name];
+        if (this.connection.state === 'connected') {
+            this.connection.send(JSON.stringify({event:"pusher:unsubscribe", data:{channel:channel_name}}));
+        }
+    }
+};
+Push.prototype.unsubscribeAll = function () {
+    var channels = Object.keys(this.channels);
+    if (channels.length) {
+        if (this.connection.state === 'connected') {
+            for (var channel_name in this.channels) {
+                this.unsubscribe(channel_name);
+            }
+        }
+    }
+    this.channels = {};
+};
+Push.prototype.subscribe = function (channel_name) {
+    if (this.channels[channel_name]) {
+        return this.channels[channel_name];
+    }
+    if (channel_name.indexOf('private-') === 0) {
+        return createPrivateChannel(channel_name, this);
+    }
+    if (channel_name.indexOf('presence-') === 0) {
+        return createPresenceChannel(channel_name, this);
+    }
+    return createChannel(channel_name, this);
+};
+Push.instances = [];
+
+function createChannel(channel_name, push)
+{
+    var channel = new Channel(push.connection, channel_name);
+    push.channels[channel_name] = channel;
+    channel.subscribeCb = function () {
+        push.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
+    }
+    channel.processSubscribe();
+    return channel;
+}
+
+function createPrivateChannel(channel_name, push)
+{
+    var channel = new Channel(push.connection, channel_name);
+    push.channels[channel_name] = channel;
+    channel.subscribeCb = function () {
+        __ajax({
+            url: push.config.auth,
+            type: 'POST',
+            data: {channel_name: channel_name, socket_id: push.connection.socket_id},
+            success: function (data) {
+                data = JSON.parse(data);
+                data.channel = channel_name;
+                push.connection.send(JSON.stringify({event:"pusher:subscribe", data:data}));
+            },
+            error: function (e) {
+                throw Error(e);
+            }
+        });
+    };
+    channel.processSubscribe();
+    return channel;
+}
+
+function createPresenceChannel(channel_name, push)
+{
+    return createPrivateChannel(channel_name, push);
+}
+
+uni.onNetworkStatusChange(function (res) {
+    if(res.isConnected) {
+        for (var i in Push.instances) {
+            var con = Push.instances[i].connection;
+            con.reconnectInterval = 1;
+            if (con.state === 'connecting') {
+                con.connect();
+            }
+        }
+    }
+});
+
+function Connection(options) {
+    this.dispatcher = new Dispatcher();
+    __extends(this, this.dispatcher);
+    var properies = ['on', 'off', 'emit'];
+    for (var i in properies) {
+        this[properies[i]] = this.dispatcher[properies[i]];
+    }
+    this.options = options;
+    this.state = 'initialized'; //initialized connecting connected disconnected
+    this.doNotConnect = 0;
+    this.reconnectInterval = 1;
+    this.connection = null;
+    this.reconnectTimer = 0;
+    this.connect();
+}
+
+Connection.prototype.updateNetworkState = function(state){
+    var old_state = this.state;
+    this.state = state;
+    if (old_state !== state) {
+        this.emit('state_change', { previous: old_state, current: state });
+    }
+};
+
+Connection.prototype.connect = function () {
+    this.doNotConnect = 0;
+    if (this.networkState == 'connecting' || this.networkState == 'established') {
+        console.log('networkState is ' + this.networkState + ' and do not need connect');
+        return;
+    }
+    if (this.reconnectTimer) {
+        clearTimeout(this.reconnectTimer);
+        this.reconnectTimer = 0;
+    }
+
+    this.closeAndClean();
+
+    var options = this.options;
+    var _this = this;
+    _this.updateNetworkState('connecting');
+    var cb = function(){
+        uni.onSocketOpen(function (res) {
+            _this.reconnectInterval = 1;
+            if (_this.doNotConnect) {
+                _this.updateNetworkState('closing');
+                uni.closeSocket();
+                return;
+            }
+            _this.updateNetworkState('established');
+            if (options.onOpen) {
+                options.onOpen(res);
+            }
+        });
+
+        if (options.onMessage) {
+            uni.onSocketMessage(options.onMessage);
+        }
+
+        uni.onSocketClose(function (res) {
+            _this.updateNetworkState('disconnected');
+            if (!_this.doNotConnect) {
+                _this.waitReconnect();
+            }
+            if (options.onClose) {
+                options.onClose(res);
+            }
+        });
+
+        uni.onSocketError(function (res) {
+            _this.close();
+            if (!_this.doNotConnect) {
+                _this.waitReconnect();
+            }
+            if (options.onError) {
+                options.onError(res);
+            }
+        });
+    };
+    uni.connectSocket({
+        url: options.url,
+        fail: function (res) {
+            console.log('uni.connectSocket fail');
+            console.log(res);
+            _this.updateNetworkState('disconnected');
+            _this.waitReconnect();
+        },
+        success: function() {
+
+        }
+    });
+    cb();
+}
+
+Connection.prototype.connect = function () {
+    this.doNotConnect = 0;
+    if (this.state === 'connected') {
+        console.log('networkState is "' + this.state + '" and do not need connect');
+        return;
+    }
+    if (this.reconnectTimer) {
+        clearTimeout(this.reconnectTimer);
+        this.reconnectTimer = 0;
+    }
+
+    this.closeAndClean();
+
+    var options = this.options;
+
+    this.updateNetworkState('connecting');
+
+    var _this = this;
+    var cb = function(){
+        uni.onSocketOpen(function (res) {
+            _this.reconnectInterval = 1;
+            if (_this.doNotConnect) {
+                _this.updateNetworkState('disconnected');
+                uni.closeSocket();
+                return;
+            }
+            if (options.onOpen) {
+                options.onOpen(res);
+            }
+        });
+
+        if (options.onMessage) {
+            uni.onSocketMessage(options.onMessage);
+        }
+
+        uni.onSocketClose(function (res) {
+            _this.updateNetworkState('disconnected');
+            if (!_this.doNotConnect) {
+                _this.waitReconnect();
+            }
+            if (options.onClose) {
+                options.onClose(res);
+            }
+        });
+
+        uni.onSocketError(function (res) {
+            _this.close();
+            if (!_this.doNotConnect) {
+                _this.waitReconnect();
+            }
+            if (options.onError) {
+                options.onError(res);
+            }
+        });
+    };
+    uni.connectSocket({
+        url: options.url+'/app/'+options.app_key,
+        fail: function (res) {
+            console.log('uni.connectSocket fail');
+            console.log(res);
+            _this.updateNetworkState('disconnected');
+            _this.waitReconnect();
+        },
+        success: function() {
+
+        }
+    });
+    cb();
+}
+
+Connection.prototype.closeAndClean = function () {
+    if (this.state === 'connected') {
+        uni.closeSocket();
+    }
+    this.updateNetworkState('disconnected');
+};
+
+Connection.prototype.waitReconnect = function () {
+    if (this.state === 'connected' || this.state === 'connecting') {
+        return;
+    }
+    if (!this.doNotConnect) {
+        this.updateNetworkState('connecting');
+        var _this = this;
+        if (this.reconnectTimer) {
+            clearTimeout(this.reconnectTimer);
+        }
+        this.reconnectTimer = setTimeout(function(){
+            _this.connect();
+        }, this.reconnectInterval);
+        if (this.reconnectInterval < 1000) {
+            this.reconnectInterval = 1000;
+        } else {
+            // 每次重连间隔增大一倍
+            this.reconnectInterval = this.reconnectInterval * 2;
+        }
+        // 有网络的状态下,重连间隔最大2秒
+        if (this.reconnectInterval > 2000) {
+            uni.getNetworkType({
+                success: function (res) {
+                    if (res.networkType != 'none') {
+                        _this.reconnectInterval = 1000;
+                    }
+                }
+            });
+        }
+    }
+}
+
+Connection.prototype.send = function(data) {
+    if (this.state !== 'connected') {
+        console.trace('networkState is "' + this.state + '", can not send ' + data);
+        return;
+    }
+    uni.sendSocketMessage({
+        data: data
+    });
+}
+
+Connection.prototype.close = function(){
+    this.updateNetworkState('disconnected');
+    uni.closeSocket();
+}
+
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) {d[p] = b[p];}
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+
+function Channel(connection, channel_name) {
+    this.subscribed = false;
+    this.dispatcher = new Dispatcher();
+    this.connection = connection;
+    this.channelName = channel_name;
+    this.subscribeCb = null;
+    this.queue = [];
+    __extends(this, this.dispatcher);
+    var properies = ['on', 'off', 'emit'];
+    for (var i in properies) {
+        this[properies[i]] = this.dispatcher[properies[i]];
+    }
+}
+
+Channel.prototype.processSubscribe = function () {
+    if (this.connection.state !== 'connected') {
+        return;
+    }
+    this.subscribeCb();
+};
+
+Channel.prototype.processQueue = function () {
+    if (this.connection.state !== 'connected' || !this.subscribed) {
+        return;
+    }
+    for (var i in this.queue) {
+        this.queue[i]();
+    }
+    this.queue = [];
+};
+
+Channel.prototype.trigger = function (event, data) {
+    if (event.indexOf('client-') !== 0) {
+        throw new Error("Event '" + event + "' should start with 'client-'");
+    }
+    var _this = this;
+    this.queue.push(function () {
+        _this.connection.send(JSON.stringify({ event: event, data: data, channel: _this.channelName }));
+    });
+    this.processQueue();
+};
+
+////////////////
+var Collections = (function () {
+    var exports = {};
+    function extend(target) {
+        var sources = [];
+        for (var _i = 1; _i < arguments.length; _i++) {
+            sources[_i - 1] = arguments[_i];
+        }
+        for (var i = 0; i < sources.length; i++) {
+            var extensions = sources[i];
+            for (var property in extensions) {
+                if (extensions[property] && extensions[property].constructor &&
+                    extensions[property].constructor === Object) {
+                    target[property] = extend(target[property] || {}, extensions[property]);
+                }
+                else {
+                    target[property] = extensions[property];
+                }
+            }
+        }
+        return target;
+    }
+
+    exports.extend = extend;
+    function stringify() {
+        var m = ["Push"];
+        for (var i = 0; i < arguments.length; i++) {
+            if (typeof arguments[i] === "string") {
+                m.push(arguments[i]);
+            }
+            else {
+                m.push(safeJSONStringify(arguments[i]));
+            }
+        }
+        return m.join(" : ");
+    }
+
+    exports.stringify = stringify;
+    function arrayIndexOf(array, item) {
+        var nativeIndexOf = Array.prototype.indexOf;
+        if (array === null) {
+            return -1;
+        }
+        if (nativeIndexOf && array.indexOf === nativeIndexOf) {
+            return array.indexOf(item);
+        }
+        for (var i = 0, l = array.length; i < l; i++) {
+            if (array[i] === item) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    exports.arrayIndexOf = arrayIndexOf;
+    function objectApply(object, f) {
+        for (var key in object) {
+            if (Object.prototype.hasOwnProperty.call(object, key)) {
+                f(object[key], key, object);
+            }
+        }
+    }
+
+    exports.objectApply = objectApply;
+    function keys(object) {
+        var keys = [];
+        objectApply(object, function (_, key) {
+            keys.push(key);
+        });
+        return keys;
+    }
+
+    exports.keys = keys;
+    function values(object) {
+        var values = [];
+        objectApply(object, function (value) {
+            values.push(value);
+        });
+        return values;
+    }
+
+    exports.values = values;
+    function apply(array, f, context) {
+        for (var i = 0; i < array.length; i++) {
+            f.call(context || (window), array[i], i, array);
+        }
+    }
+
+    exports.apply = apply;
+    function map(array, f) {
+        var result = [];
+        for (var i = 0; i < array.length; i++) {
+            result.push(f(array[i], i, array, result));
+        }
+        return result;
+    }
+
+    exports.map = map;
+    function mapObject(object, f) {
+        var result = {};
+        objectApply(object, function (value, key) {
+            result[key] = f(value);
+        });
+        return result;
+    }
+
+    exports.mapObject = mapObject;
+    function filter(array, test) {
+        test = test || function (value) {
+            return !!value;
+        };
+        var result = [];
+        for (var i = 0; i < array.length; i++) {
+            if (test(array[i], i, array, result)) {
+                result.push(array[i]);
+            }
+        }
+        return result;
+    }
+
+    exports.filter = filter;
+    function filterObject(object, test) {
+        var result = {};
+        objectApply(object, function (value, key) {
+            if ((test && test(value, key, object, result)) || Boolean(value)) {
+                result[key] = value;
+            }
+        });
+        return result;
+    }
+
+    exports.filterObject = filterObject;
+    function flatten(object) {
+        var result = [];
+        objectApply(object, function (value, key) {
+            result.push([key, value]);
+        });
+        return result;
+    }
+
+    exports.flatten = flatten;
+    function any(array, test) {
+        for (var i = 0; i < array.length; i++) {
+            if (test(array[i], i, array)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    exports.any = any;
+    function all(array, test) {
+        for (var i = 0; i < array.length; i++) {
+            if (!test(array[i], i, array)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    exports.all = all;
+    function encodeParamsObject(data) {
+        return mapObject(data, function (value) {
+            if (typeof value === "object") {
+                value = safeJSONStringify(value);
+            }
+            return encodeURIComponent(base64_1["default"](value.toString()));
+        });
+    }
+
+    exports.encodeParamsObject = encodeParamsObject;
+    function buildQueryString(data) {
+        var params = filterObject(data, function (value) {
+            return value !== undefined;
+        });
+        return map(flatten(encodeParamsObject(params)), util_1["default"].method("join", "=")).join("&");
+    }
+
+    exports.buildQueryString = buildQueryString;
+    function decycleObject(object) {
+        var objects = [], paths = [];
+        return (function derez(value, path) {
+            var i, name, nu;
+            switch (typeof value) {
+                case 'object':
+                    if (!value) {
+                        return null;
+                    }
+                    for (i = 0; i < objects.length; i += 1) {
+                        if (objects[i] === value) {
+                            return {$ref: paths[i]};
+                        }
+                    }
+                    objects.push(value);
+                    paths.push(path);
+                    if (Object.prototype.toString.apply(value) === '[object Array]') {
+                        nu = [];
+                        for (i = 0; i < value.length; i += 1) {
+                            nu[i] = derez(value[i], path + '[' + i + ']');
+                        }
+                    }
+                    else {
+                        nu = {};
+                        for (name in value) {
+                            if (Object.prototype.hasOwnProperty.call(value, name)) {
+                                nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']');
+                            }
+                        }
+                    }
+                    return nu;
+                case 'number':
+                case 'string':
+                case 'boolean':
+                    return value;
+            }
+        }(object, '$'));
+    }
+
+    exports.decycleObject = decycleObject;
+    function safeJSONStringify(source) {
+        try {
+            return JSON.stringify(source);
+        }
+        catch (e) {
+            return JSON.stringify(decycleObject(source));
+        }
+    }
+
+    exports.safeJSONStringify = safeJSONStringify;
+    return exports;
+})();
+
+var Dispatcher = (function () {
+    function Dispatcher(failThrough) {
+        this.callbacks = new CallbackRegistry();
+        this.global_callbacks = [];
+        this.failThrough = failThrough;
+    }
+    Dispatcher.prototype.on = function (eventName, callback, context) {
+        this.callbacks.add(eventName, callback, context);
+        return this;
+    };
+    Dispatcher.prototype.on_global = function (callback) {
+        this.global_callbacks.push(callback);
+        return this;
+    };
+    Dispatcher.prototype.off = function (eventName, callback, context) {
+        this.callbacks.remove(eventName, callback, context);
+        return this;
+    };
+    Dispatcher.prototype.emit = function (eventName, data) {
+        var i;
+        for (i = 0; i < this.global_callbacks.length; i++) {
+            this.global_callbacks[i](eventName, data);
+        }
+        var callbacks = this.callbacks.get(eventName);
+        if (callbacks && callbacks.length > 0) {
+            for (i = 0; i < callbacks.length; i++) {
+                callbacks[i].fn.call(callbacks[i].context || (window), data);
+            }
+        }
+        else if (this.failThrough) {
+            this.failThrough(eventName, data);
+        }
+        return this;
+    };
+    return Dispatcher;
+}());
+
+var CallbackRegistry = (function () {
+    function CallbackRegistry() {
+        this._callbacks = {};
+    }
+    CallbackRegistry.prototype.get = function (name) {
+        return this._callbacks[prefix(name)];
+    };
+    CallbackRegistry.prototype.add = function (name, callback, context) {
+        var prefixedEventName = prefix(name);
+        this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
+        this._callbacks[prefixedEventName].push({
+            fn: callback,
+            context: context
+        });
+    };
+    CallbackRegistry.prototype.remove = function (name, callback, context) {
+        if (!name && !callback && !context) {
+            this._callbacks = {};
+            return;
+        }
+        var names = name ? [prefix(name)] : Collections.keys(this._callbacks);
+        if (callback || context) {
+            this.removeCallback(names, callback, context);
+        }
+        else {
+            this.removeAllCallbacks(names);
+        }
+    };
+    CallbackRegistry.prototype.removeCallback = function (names, callback, context) {
+        Collections.apply(names, function (name) {
+            this._callbacks[name] = Collections.filter(this._callbacks[name] || [], function (oning) {
+                return (callback && callback !== oning.fn) ||
+                    (context && context !== oning.context);
+            });
+            if (this._callbacks[name].length === 0) {
+                delete this._callbacks[name];
+            }
+        }, this);
+    };
+    CallbackRegistry.prototype.removeAllCallbacks = function (names) {
+        Collections.apply(names, function (name) {
+            delete this._callbacks[name];
+        }, this);
+    };
+    return CallbackRegistry;
+}());
+function prefix(name) {
+    return "_" + name;
+}
+
+function __ajax(options){
+    options=options||{};
+    options.type=(options.type||'GET').toUpperCase();
+    options.dataType=options.dataType||'json';
+    var params=formatParams(options.data);
+
+    var xhr;
+    if(window.XMLHttpRequest){
+        xhr=new XMLHttpRequest();
+    }else{
+        xhr=ActiveXObject('Microsoft.XMLHTTP');
+    }
+
+    xhr.onreadystatechange=function(){
+        if(xhr.readyState === 4){
+            var status=xhr.status;
+            if(status>=200 && status<300){
+                options.success&&options.success(xhr.responseText,xhr.responseXML);
+            }else{
+                options.error&&options.error(status);
+            }
+        }
+    }
+
+    if(options.type==='GET'){
+        xhr.open('GET',options.url+'?'+params,true);
+        xhr.send(null);
+    }else if(options.type==='POST'){
+        xhr.open('POST',options.url,true);
+        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+        xhr.send(params);
+    }
+}
+
+function formatParams(data){
+    var arr=[];
+    for(var name in data){
+        arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
+    }
+    return arr.join('&');
+}
+
+export default Push

+ 24 - 0
src/util/show.js

@@ -0,0 +1,24 @@
+// str 文案
+// time 时间
+// icon 图标
+// mask is防止触摸穿透
+export const show = (title = "文案", duration = 2000, icon = 'none', mask = true) => {
+	uni.showToast({
+		title,
+		duration,
+		icon,
+		mask,
+	});
+	return new Promise((r, e) => {
+		setTimeout(() => {
+			r()
+		}, duration)
+	})
+}
+// icon:
+// success		显示成功图标,此时 title 文本在小程序平台最多显示 7 个汉字长度
+// error		显示错误图标,此时 title 文本在小程序平台最多显示 7 个汉字长度。	
+// fail	 		显示对号图标,此时 title 文本无长度显示。	
+// exception	显示异常图标,此时 title 文本无长度显示。	
+// loading		显示加载图标,此时 title 文本在小程序平台最多显示 7 个汉字长度。
+// none			不显示图标,此时 title 文本在小程序最多可显示两行,App仅支持单行显示

+ 22 - 0
src/util/throttle.js

@@ -0,0 +1,22 @@
+// 节流函数
+let timer;
+let flag;
+export const throttle = (func, wait = 500, immediate = true) => {
+	if (immediate) {
+		if (!flag) {
+			flag = true
+			// 如果是立即执行,则在wait毫秒内开始时执行
+			typeof func === 'function' && func()
+			timer = setTimeout(() => {
+				flag = false
+			}, wait)
+		}
+	} else if (!flag) {
+		flag = true
+		// 如果是非立即执行,则在wait毫秒内的结束处执行
+		timer = setTimeout(() => {
+			flag = false
+			typeof func === 'function' && func()
+		}, wait)
+	}
+}

+ 52 - 0
src/util/to.js

@@ -0,0 +1,52 @@
+function isObj(obj) {
+	let array = Object.keys(obj);
+	if (array.length === 0) { //如果数组array的长度为0 ,则返回为true,说明data对象为空
+		return false;
+	}
+	return true;
+};
+
+export const to = (url = "", data = {}, type = 'navigateTo', oNexit = "/pages/home/index") => {
+	if (!url) {
+		let pages = getCurrentPages(); // 获取当前页面栈的实例
+		if (pages.length === 1) {
+			// 没有上一页就跳转oNexit
+			uni.reLaunch({
+				url: oNexit
+			})
+			return;
+		}
+		uni.navigateBack()
+		return;
+	};
+	if (isObj(data)) {
+		const route_data = JSON.stringify(data)
+		if (type === 'navigateTo') {
+			uni.navigateTo({
+				url: `${url}?data=${route_data}`
+			})
+		} else if (type === 'redirectTo') {
+			uni.redirectTo({
+				url: `${url}?data=${route_data}`
+			})
+		} else {
+			uni.reLaunch({
+				url: `${url}?data=${route_data}`
+			})
+		}
+	} else {
+		if (type === 'navigateTo') {
+			uni.navigateTo({
+				url: `${url}`
+			})
+		} else if (type === 'redirectTo') {
+			uni.redirectTo({
+				url: `${url}`
+			})
+		} else {
+			uni.reLaunch({
+				url: `${url}`
+			})
+		}
+	}
+}

+ 4 - 0
src/util/vibrateShort.js

@@ -0,0 +1,4 @@
+let vibrateShortFn = () => {
+	console.log('短震动')
+}
+export const vibrateShort = vibrateShortFn