whx 2 mesi fa
parent
commit
398635f8b5
47 ha cambiato i file con 1200 aggiunte e 38 eliminazioni
  1. 1 1
      virgo.wzfrontend/src/main/resources/static/workark/index.html
  2. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/1243.f0ab5e5c.css
  3. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/3853.a0457e8c.css
  4. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/615.85b305a8.css
  5. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/7950.7a41cd3b.css
  6. 0 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/802.40e63305.css
  7. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/858.fb403e5e.css
  8. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/9242.f9edad1a.css
  9. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/css/9466.7daf9bec.css
  10. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/css/9647.abda892f.css
  11. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/css/app.2a336a82.css
  12. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/css/app.857f62c7.css
  13. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/2262.59b678d4.js
  14. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/3142.a42c617c.js
  15. 19 8
      virgo.wzfrontend/src/main/resources/static/workark/static/js/7199.f94a0e01.js
  16. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/3853.2defc927.js
  17. 1 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/5353.63300c3e.js
  18. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/5483.e0b88f94.js
  19. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/5895.50d7286b.js
  20. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/615.7049762f.js
  21. 1 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/7039.3d0f44b7.js
  22. 1 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/9466.a5620740.js
  23. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/802.d96fba4b.js
  24. 1 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/8083.43738b4f.js
  25. 1 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/8337.e8605ad5.js
  26. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/858.febee7b5.js
  27. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/9242.2cd499b2.js
  28. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/9563.160bcb73.js
  29. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/9647.fa781f51.js
  30. 1 0
      virgo.wzfrontend/src/main/resources/static/workark/static/js/app.67519f38.js
  31. 0 1
      virgo.wzfrontend/src/main/resources/static/workark/static/js/app.ba82d42b.js
  32. 10 0
      virgo.wzfrontend/workark/package-lock.json
  33. 3 1
      virgo.wzfrontend/workark/package.json
  34. 37 0
      virgo.wzfrontend/workark/src/api/chat.js
  35. 200 0
      virgo.wzfrontend/workark/src/components/common/uniDateformat/date-format.js
  36. 88 0
      virgo.wzfrontend/workark/src/components/common/uniDateformat/index.vue
  37. 43 2
      virgo.wzfrontend/workark/src/components/work/serve/content/edit.vue
  38. 168 0
      virgo.wzfrontend/workark/src/layout/components/chat.vue
  39. 370 0
      virgo.wzfrontend/workark/src/layout/components/chatBox.vue
  40. 2 1
      virgo.wzfrontend/workark/src/layout/components/login.vue
  41. 56 7
      virgo.wzfrontend/workark/src/layout/workLayout.vue
  42. 4 0
      virgo.wzfrontend/workark/src/main.js
  43. 3 1
      virgo.wzfrontend/workark/src/store/getters.js
  44. 19 1
      virgo.wzfrontend/workark/src/store/modules/app.js
  45. 132 0
      virgo.wzfrontend/workark/src/uitls/chat.js
  46. 2 1
      virgo.wzfrontend/workark/src/views/login/login.vue
  47. 22 3
      virgo.wzfrontend/workark/src/views/website/serveDetail.vue

File diff suppressed because it is too large
+ 1 - 1
virgo.wzfrontend/src/main/resources/static/workark/index.html


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/1243.f0ab5e5c.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/3853.a0457e8c.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/615.85b305a8.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/7950.7a41cd3b.css


virgo.wzfrontend/src/main/resources/static/workark/static/css/3142.40e63305.css → virgo.wzfrontend/src/main/resources/static/workark/static/css/802.40e63305.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/858.fb403e5e.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/9242.f9edad1a.css


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/css/9466.7daf9bec.css


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/css/9647.abda892f.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/css/app.2a336a82.css


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/css/app.857f62c7.css


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/2262.59b678d4.js


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/3142.a42c617c.js


File diff suppressed because it is too large
+ 19 - 8
virgo.wzfrontend/src/main/resources/static/workark/static/js/7199.f94a0e01.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/3853.2defc927.js


File diff suppressed because it is too large
+ 1 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/5353.63300c3e.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/5483.e0b88f94.js


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/5895.50d7286b.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/615.7049762f.js


File diff suppressed because it is too large
+ 1 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/7039.3d0f44b7.js


File diff suppressed because it is too large
+ 1 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/9466.a5620740.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/802.d96fba4b.js


File diff suppressed because it is too large
+ 1 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/8083.43738b4f.js


File diff suppressed because it is too large
+ 1 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/8337.e8605ad5.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/858.febee7b5.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/9242.2cd499b2.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/9563.160bcb73.js


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/9647.fa781f51.js


File diff suppressed because it is too large
+ 1 - 0
virgo.wzfrontend/src/main/resources/static/workark/static/js/app.67519f38.js


File diff suppressed because it is too large
+ 0 - 1
virgo.wzfrontend/src/main/resources/static/workark/static/js/app.ba82d42b.js


+ 10 - 0
virgo.wzfrontend/workark/package-lock.json

@@ -5785,6 +5785,11 @@
         "@sideway/pinpoint": "^2.0.0"
       }
     },
+    "js-md5": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmmirror.com/js-md5/-/js-md5-0.8.3.tgz",
+      "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="
+    },
     "js-message": {
       "version": "1.0.7",
       "resolved": "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz",
@@ -9647,6 +9652,11 @@
       "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
       "dev": true
     },
+    "yeim-uni-sdk": {
+      "version": "1.2.83",
+      "resolved": "https://registry.npmmirror.com/yeim-uni-sdk/-/yeim-uni-sdk-1.2.83.tgz",
+      "integrity": "sha512-DeoY323kIkfvHYRdganysAe62wQMdsoyVVU2QQCp2C+FMnEyBEQj0aOR5Bit/Pj0Ppt+cnh45G3d/ucqPuDJdw=="
+    },
     "yorkie": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/yorkie/-/yorkie-2.0.0.tgz",

+ 3 - 1
virgo.wzfrontend/workark/package.json

@@ -12,11 +12,13 @@
 		"core-js": "^3.8.3",
 		"element-ui": "^2.15.14",
 		"fabric": "^6.6.1",
+		"js-md5": "^0.8.3",
 		"pdfvuer": "^1.10.0",
 		"sortablejs": "^1.15.6",
 		"vue": "^2.6.14",
 		"vue-router": "^3.1.3",
-		"vuex": "^3.0.1"
+		"vuex": "^3.0.1",
+		"yeim-uni-sdk": "^1.2.83"
 	},
 	"devDependencies": {
 		"@babel/core": "^7.12.16",

+ 37 - 0
virgo.wzfrontend/workark/src/api/chat.js

@@ -0,0 +1,37 @@
+import request from '@/axios'
+/* 
+ * 聊天登录
+ * 
+ * 
+ */
+export function login(data) {
+	return request({
+		url: `/im/user/token/get`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 发送聊天消息
+ * 
+ * 
+ */
+export function sendSystem(data) {
+	return request({
+		url: `/im/message/save`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 上传文件
+ * 
+ * 
+ */
+export function uploadImage(data) {
+	return request({
+		url: `/im/upload/image`,
+		method: 'post',
+		data: data
+	})
+}

+ 200 - 0
virgo.wzfrontend/workark/src/components/common/uniDateformat/date-format.js

@@ -0,0 +1,200 @@
+// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型
+function pad(str, length = 2) {
+	str += ''
+	while (str.length < length) {
+		str = '0' + str
+	}
+	return str.slice(-length)
+}
+
+const parser = {
+	yyyy: (dateObj) => {
+		return pad(dateObj.year, 4)
+	},
+	yy: (dateObj) => {
+		return pad(dateObj.year)
+	},
+	MM: (dateObj) => {
+		return pad(dateObj.month)
+	},
+	M: (dateObj) => {
+		return dateObj.month
+	},
+	dd: (dateObj) => {
+		return pad(dateObj.day)
+	},
+	d: (dateObj) => {
+		return dateObj.day
+	},
+	hh: (dateObj) => {
+		return pad(dateObj.hour)
+	},
+	h: (dateObj) => {
+		return dateObj.hour
+	},
+	mm: (dateObj) => {
+		return pad(dateObj.minute)
+	},
+	m: (dateObj) => {
+		return dateObj.minute
+	},
+	ss: (dateObj) => {
+		return pad(dateObj.second)
+	},
+	s: (dateObj) => {
+		return dateObj.second
+	},
+	SSS: (dateObj) => {
+		return pad(dateObj.millisecond, 3)
+	},
+	S: (dateObj) => {
+		return dateObj.millisecond
+	},
+}
+
+// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12
+function getDate(time) {
+	if (time instanceof Date) {
+		return time
+	}
+	switch (typeof time) {
+		case 'string':
+			{
+				// 2020-12-12T12:12:12.000Z、2020-12-12T12:12:12.000
+				if (time.indexOf('T') > -1) {
+					return new Date(time)
+				}
+				return new Date(time.replace(/-/g, '/'))
+			}
+		default:
+			return new Date(time)
+	}
+}
+
+export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') {
+	if (!date && date !== 0) {
+		return ''
+	}
+	date = getDate(date)
+	const dateObj = {
+		year: date.getFullYear(),
+		month: date.getMonth() + 1,
+		day: date.getDate(),
+		hour: date.getHours(),
+		minute: date.getMinutes(),
+		second: date.getSeconds(),
+		millisecond: date.getMilliseconds()
+	}
+	const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/
+	let flag = true
+	let result = format
+	while (flag) {
+		flag = false
+		result = result.replace(tokenRegExp, function(matched) {
+			flag = true
+			return parser[matched](dateObj)
+		})
+	}
+	return result
+}
+
+export function friendlyDate(time, {
+	locale = 'zh',
+	threshold = [60000, 3600000],
+	format = 'yyyy/MM/dd hh:mm:ss'
+}) {
+	if (time === '-') {
+		return time
+	}
+	if (!time && time !== 0) {
+		return ''
+	}
+	const localeText = {
+		zh: {
+			year: '年',
+			month: '月',
+			day: '天',
+			hour: '小时',
+			minute: '分钟',
+			second: '秒',
+			ago: '前',
+			later: '后',
+			justNow: '刚刚',
+			soon: '刚刚',
+			template: '{num}{unit}{suffix}'
+		},
+		en: {
+			year: 'year',
+			month: 'month',
+			day: 'day',
+			hour: 'hour',
+			minute: 'minute',
+			second: 'second',
+			ago: 'ago',
+			later: 'later',
+			justNow: 'just now',
+			soon: 'soon',
+			template: '{num} {unit} {suffix}'
+		}
+	}
+	const text = localeText[locale] || localeText.zh
+	let date = getDate(time)
+	let ms = date.getTime() - Date.now()
+	let absMs = Math.abs(ms)
+	if (absMs < threshold[0]) {
+		return ms < 0 ? text.justNow : text.soon
+	}
+	if (absMs >= threshold[1]) {
+		return formatDate(date, format)
+	}
+	let num
+	let unit
+	let suffix = text.later
+	if (ms < 0) {
+		suffix = text.ago
+		ms = -ms
+	}
+	const seconds = Math.floor((ms) / 1000)
+	const minutes = Math.floor(seconds / 60)
+	const hours = Math.floor(minutes / 60)
+	const days = Math.floor(hours / 24)
+	const months = Math.floor(days / 30)
+	const years = Math.floor(months / 12)
+	switch (true) {
+		case years > 0:
+			num = years
+			unit = text.year
+			break
+		case months > 0:
+			num = months
+			unit = text.month
+			break
+		case days > 0:
+			num = days
+			unit = text.day
+			break
+		case hours > 0:
+			num = hours
+			unit = text.hour
+			break
+		case minutes > 0:
+			num = minutes
+			unit = text.minute
+			break
+		default:
+			num = seconds
+			unit = text.second
+			break
+	}
+
+	if (locale === 'en') {
+		if (num === 1) {
+			num = 'a'
+		} else {
+			unit += 's'
+		}
+	}
+
+	return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g,
+		suffix)
+}

+ 88 - 0
virgo.wzfrontend/workark/src/components/common/uniDateformat/index.vue

@@ -0,0 +1,88 @@
+<template>
+	<span>{{dateShow}}</span>
+</template>
+
+<script>
+	import {friendlyDate} from './date-format.js'
+	/**
+	 * Dateformat 日期格式化
+	 * @description 日期格式化组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=3279
+	 * @property {Object|String|Number} date 日期对象/日期字符串/时间戳
+	 * @property {String} locale 格式化使用的语言
+	 * 	@value zh 中文
+	 * 	@value en 英文
+	 * @property {Array} threshold 应用不同类型格式化的阈值
+	 * @property {String} format 输出日期字符串时的格式
+	 */
+	export default {
+		name: 'uniDateformat',
+		props: {
+			date: {
+				type: [Object, String, Number],
+				default () {
+					return '-'
+				}
+			},
+			locale: {
+				type: String,
+				default: 'zh',
+			},
+			threshold: {
+				type: Array,
+				default () {
+					return [0, 0]
+				}
+			},
+			format: {
+				type: String,
+				default: 'yyyy/MM/dd hh:mm:ss'
+			},
+			// refreshRate使用不当可能导致性能问题,谨慎使用
+			refreshRate: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		data() {
+			return {
+				refreshMark: 0
+			}
+		},
+		computed: {
+			dateShow() {
+				this.refreshMark
+				return friendlyDate(this.date, {
+					locale: this.locale,
+					threshold: this.threshold,
+					format: this.format
+				})
+			}
+		},
+		watch: {
+			refreshRate: {
+				handler() {
+					this.setAutoRefresh()
+				},
+				immediate: true
+			}
+		},
+		methods: {
+			refresh() {
+				this.refreshMark++
+			},
+			setAutoRefresh() {
+				clearInterval(this.refreshInterval)
+				if (this.refreshRate) {
+					this.refreshInterval = setInterval(() => {
+						this.refresh()
+					}, parseInt(this.refreshRate))
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 43 - 2
virgo.wzfrontend/workark/src/components/work/serve/content/edit.vue

@@ -12,6 +12,13 @@
 					<el-input type="text" v-model="form.price" @input="handleInput($event)" placeholder="请输入价格">
 					</el-input>
 				</el-form-item>
+				<el-form-item label="客服">
+					<el-cascader v-model="customerId" :options="options" :props="{
+						value:'id',
+						label:'name',
+						children:'children'
+					}"></el-cascader>
+				</el-form-item>
 				<el-form-item label="商品介绍" class="hui-textarea">
 					<custom-data ref="customData" :list="customList" :option="option"></custom-data>
 				</el-form-item>
@@ -40,6 +47,12 @@
 		updateServe,
 		getServeById
 	} from '@/api/serve'
+	import {
+		getPartList
+	} from '@/api/organization'
+	import {
+		findParentIds
+	} from '@/uitls'
 	const upload = () => import('@/components/common/upload');
 	const customData = () => import('@/components/common/customData');
 	export default {
@@ -68,10 +81,20 @@
 					label: '介绍内容',
 					value: 'content',
 					type: 'text'
-				}]
+				}],
+				customerId: [],
+				options: []
 			}
 		},
 		mounted() {
+			getPartList(this.$store.getters.organization.id, this.$store.getters.project.id).then(res => {
+				if (res.state) {
+					this.options = res.data;
+					this.returnChildren(this.options);
+					if (this.form.customerId && this.options.length > 0) this.customerId = findParentIds(this
+						.options, -this.form.customerId);
+				}
+			})
 			if (this.isUpdate) {
 				getServeById(this.detailId).then(res => {
 					if (res.state) {
@@ -80,6 +103,8 @@
 						if (this.form.detailedImage) this.detailedImage = JSON.parse(this.form.detailedImage);
 						if (this.form.contract) this.contract = JSON.parse(this.form.contract);
 						if (this.form.intro) this.customList = JSON.parse(this.form.intro);
+						if (this.form.customerId && this.options.length > 0) this.customerId = findParentIds(this
+							.options, -this.form.customerId);
 					}
 				})
 			} else {
@@ -107,6 +132,7 @@
 				postData['detailedImage'] = JSON.stringify(this.$refs.detailedImage.fileList);
 				postData['contract'] = JSON.stringify(this.$refs.contract.fileList);
 				postData['intro'] = JSON.stringify(this.$refs.customData.listData);
+				if (this.customerId.length > 0) postData['customerId'] = -this.customerId[this.customerId.length - 1];
 				if (this.isUpdate) {
 					updateServe(postData).then(this.successFunc)
 				} else {
@@ -119,7 +145,22 @@
 					this.$message.success('操作成功');
 					this.$emit('callback', 'init');
 				}
-			}
+			},
+			returnChildren(data) {
+				data.forEach(item => {
+					if (item.users) {
+						let obj = item.users.map(res => {
+							return {
+								id: -res.id,
+								name: res.name
+							};
+						})
+						item.children = item.children.concat(obj);
+					}
+					if (item.children && item.children.length === 0) item.children = null;
+					if (item.children && item.children.length > 0) this.returnChildren(item.children);
+				});
+			},
 		}
 	}
 </script>

+ 168 - 0
virgo.wzfrontend/workark/src/layout/components/chat.vue

@@ -0,0 +1,168 @@
+<template>
+	<div class="chat-index-box">
+		<div class="chat-index" v-if="chatData.length === 0">
+			<el-empty description="暂无聊天记录"></el-empty>
+		</div>
+		<div class="chat-index" v-else>
+			<div class="message-list">
+				<div :class="nowChat.conversationId === item.conversationId ? 'message-item active': 'message-item'"
+					v-for="(item,index) in chatData" :key="index" @click="linkTo(item)">
+					<el-badge :value="item.unread" class="item" :hidden="!item.unread">
+						<el-image :src="item.userInfo.avatarUrl" class="message-image" fit="fit">
+							<div slot="error" class="image-slot">
+								<i class="el-icon-picture-outline"></i>
+							</div>
+						</el-image>
+					</el-badge>
+					<div class="message-content">
+						<div class="message-title">{{item.userInfo.nickname}}</div>
+						<div class="message-sub-content">
+							{{item.lastMessage.type === 'image'?'[图片]':item.lastMessage.body.text}}
+						</div>
+					</div>
+					<div class="message-date">
+						<uni-dateformat class="visitor-time" :date="item.updatedAt || item.createdAt"
+							:threshold="[60000,3600000 * 24 * 365]">
+						</uni-dateformat>
+					</div>
+				</div>
+			</div>
+			<div class="chat-box" v-if="chatData.length > 0">
+				<chat-box :body="nowChat" :key="nowChat.conversationId"></chat-box>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	import uniDateformat from '@/components/common/uniDateformat'
+	import chatBox from './chatBox.vue';
+	import {
+		mapGetters
+	} from 'vuex';
+	export default {
+		data() {
+			return {
+				chatData: [],
+				nowChat: {}
+			}
+		},
+		mounted() {
+			this.init();
+		},
+		methods: {
+			init() {
+				this.chatData = this.$store.getters.chatList.filter(node => node.conversationId !== 'system');
+				if (this.chatData.length > 0) this.nowChat = this.chatData[0];
+			},
+			linkTo(item) {
+				this.nowChat = item;
+			}
+		},
+		components: {
+			uniDateformat,
+			chatBox
+		},
+		computed: {
+			...mapGetters(['chatList'])
+		},
+		watch: {
+			chatList() {
+				this.chatData = this.$store.getters.chatList.filter(node => node.conversationId !== 'system');
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	.chat-index-box {
+		height: 100%;
+		width: 100%;
+	}
+
+	.chat-index {
+		display: flex;
+		height: 100%;
+		width: 100%;
+
+		.image-slot {
+			width: 100%;
+			height: 100%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			background: #60656e;
+		}
+
+		.el-badge__content {
+			border: none;
+		}
+
+		.chat-box {
+			flex: 1;
+		}
+
+		.message-list {
+			width: 300px;
+			border-right: 1px solid $--border-color-lighter;
+			height: 100%;
+			overflow-y: auto;
+
+			.message-item {
+				display: flex;
+				padding: 12px 15px;
+				position: relative;
+				cursor: pointer;
+
+				&::before {
+					content: '';
+					position: absolute;
+					bottom: 0;
+					right: 0;
+					left: 15px;
+					height: 1px;
+					background: $--border-color-lighter;
+				}
+
+				&:hover,
+				&.active {
+					background: #f3f3f3;
+				}
+			}
+
+			.message-image {
+				width: 50px;
+				height: 50px;
+				border-radius: 8px;
+				overflow: hidden;
+			}
+
+			.message-content {
+				flex: 1;
+				width: 0;
+				margin-left: 10px;
+				display: flex;
+				flex-direction: column;
+				justify-content: center;
+			}
+
+			.message-title {
+				font-size: 16px;
+				font-weight: 600;
+				margin-bottom: 5px;
+			}
+
+			.message-sub-content {
+				font-weight: 300;
+			}
+
+			.message-date {
+				position: absolute;
+				top: 17px;
+				right: 15px;
+				font-size: 12px;
+				font-weight: 300;
+			}
+		}
+	}
+</style>

+ 370 - 0
virgo.wzfrontend/workark/src/layout/components/chatBox.vue

@@ -0,0 +1,370 @@
+<template>
+	<div class="chat-box-data">
+		<div class="chat-loading" v-show="loading">
+			会话获取中...
+		</div>
+		<div ref="chatList" class="chat-information">
+			<div v-for="(item,index) in dataList" :key="index">
+				<div class="chat-item" :class="item.from == `workark${$store.getters.user.userId}` ? 'push':'pull' ">
+					<img :src="item.fromUserInfo.avatarUrl" mode="aspectFill" class="avatar"></img>
+					<div class="content-box">
+						<div class="content" v-if="item.type === 'text'">{{item.body.text}}</div>
+						<el-image :src="item.body.thumbnailUrl" :preview-src-list="[item.body.originalUrl]" fit="fill"
+							:class="returnImageClass(item.body)" v-else>
+						</el-image>
+					</div>
+					<span class="date">
+						{{$dayjs(item.time).format('YYYY-MM-DD HH:mm:ss')}}
+					</span>
+				</div>
+			</div>
+		</div>
+		<div class="chat-send">
+			<div class="file-list">
+				<input type="file" ref="fileadd" style="opacity: 0;width: 0;height: 0;" @change="changeImage"
+					accept="image/*" />
+				<i class="el-icon-picture" @click="uploadImage"></i>
+			</div>
+			<el-input type="textarea" :rows="5" resize="none" placeholder="请输入内容" :maxlength="200"
+				v-model="sendMessage">
+			</el-input>
+			<div class="chat-send-btn">
+				<el-button type="primary" size="medium" @click="send" :loading="sendLoading" :disabled="!sendMessage">
+					发送
+				</el-button>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	import {
+		YeIMUniSDK, // SDK
+		YeIMUniSDKDefines // 预定义常量
+	} from 'yeim-uni-sdk'
+	import {
+		uploadImage
+	} from '@/api/chat.js'
+	import md5 from 'js-md5'
+	import config from "@/config";
+	export default {
+		props: ['body'],
+		data() {
+			return {
+				dataList: [],
+				nextMessageId: null,
+				hasTopData: true,
+				loading: false,
+				sendMessage: '',
+				sendLoading: false
+			}
+		},
+		beforeDestroy() {
+			YeIMUniSDK.getInstance().removeEventListener(YeIMUniSDKDefines.EVENT.MESSAGE_RECEIVED, this.onMessage);
+			document.removeEventListener('keydown', this.kedown);
+		},
+		mounted() {
+			this.$nextTick(() => {
+				// 设置onscroll事件处理函数
+				if (!this.$refs.chatList) return;
+				this.$refs.chatList.onscroll = this.chatScroll;
+				this.$chat.clearConversationUnread(this.body.conversationId);
+				document.addEventListener('keydown', this.kedown);
+				this.mescrollUpFunc('init');
+				YeIMUniSDK.getInstance().addEventListener(YeIMUniSDKDefines.EVENT.MESSAGE_RECEIVED, this
+					.onMessage);
+			})
+		},
+		methods: {
+			kedown(event) {
+				if (event.keyCode === 13) {
+					// 执行回车键按下后的操作
+					this.send();
+				}
+			},
+			chatScroll() {
+				if (!this.hasTopData) return;
+				if (!this.$refs.chatList) return;
+				let scrollHeight = this.$refs.chatList.scrollTop;
+				if (scrollHeight === 0) this.mescrollUpFunc();
+			},
+			returnImageClass(item) {
+				let w = item.thumbnailWidth,
+					h = item.thumbnailHeight;
+				let str = ''
+				if (w > h) str = 'image-width';
+				if (w == h) str = 'image-width-height';
+				if (w < h) str = 'image-height';
+				return str + ' image-box';
+			},
+			mescrollUpFunc(type) {
+				this.loading = true;
+				this.joinHistoryMsg().then(data => {
+					this.loading = false;
+					if (!this.nextMessageId) this.dataList = [];
+					if (data.length === 0) this.hasTopData = false;
+					this.dataList = data.concat(this.dataList);
+					if (type === 'init') {
+						this.$nextTick(() => {
+							this.$refs.chatList.scrollTo(0, 99999);
+						})
+					}
+				})
+			},
+			joinHistoryMsg() {
+				return new Promise((done, fail) => {
+					this.$chat.getHistoryMessageList(this.nextMessageId, this.body.conversationId, res => {
+						this.nextMessageId = res.data.nextMessageId || 'null';
+						done(!res.data ? [] : res.data.list);
+					}, () => {
+						done([]);
+					});
+				})
+			},
+			onMessage(e) {
+				let message = e;
+				if (this.body.conversationId != message.conversationId) return;
+				this.dataList.push(message);
+				this.$nextTick(() => {
+					this.$refs.chatList.scrollTo(0, 99999);
+				})
+				this.$chat.clearConversationUnread(this.body.conversationId);
+			},
+			changeImage(e) {
+				let currentfile = this.$refs.fileadd.files[0];
+				let _self = this;
+				if (currentfile) {
+					const reader = new FileReader();
+					reader.onload = function(e) {
+						const img = new Image();
+						img.onload = () => {
+							let filename = currentfile.name;
+							let suffix = filename.substring(filename.lastIndexOf('.'));
+							let name = md5((new Date()).getTime() + '_' + filename) + '_image' + suffix;
+							const formData = new FormData();
+							formData.append('file', currentfile);
+							formData.append('key', _self.getUploadFilePath(name, 'image'));
+							_self.$loading();
+							uploadImage(formData).then(res => {
+								if (res.state) {
+									_self.sendImage(res.data, img);
+								} else {
+									_self.$loading.close();
+								}
+							})
+						};
+						img.src = e.target.result; // 设置图片源为读取的结果
+					};
+					reader.readAsDataURL(currentfile); // 读取文件内容为DataURL
+				}
+			},
+			getUploadFilePath(filename, dir = 'files') {
+				let date = new Date();
+				let dateDir = date.getFullYear() + '-' + JSON.stringify(date.getMonth() + 1).padStart(2, 0) + '-' + JSON
+					.stringify(date.getDate()).padStart(2, 0);
+				return dir + '/' + dateDir + '/' + filename;
+			},
+			uploadImage() {
+				this.$refs.fileadd.click();
+			},
+			sendImage(data, img) {
+				//创建图片消息
+				let message = YeIMUniSDK.getInstance().createImageMessageFromUrl({
+					toId: this.body.conversationId, //接收者用户ID字符串
+					conversationType: YeIMUniSDKDefines.CONVERSATION_TYPE.PRIVATE, //会话类型:私聊
+					body: {
+						originalUrl: 'https://www.waywish.com/im' + data.url, //原图网络Url
+						originalWidth: img.width, //原图宽度
+						originalHeight: img.height, //原图高度
+						thumbnailUrl: 'https://www.waywish.com/im' + data.thumbnailUrl, //缩略图网络Url
+						thumbnailWidth: data.thumbnailWidth, //缩略图宽度
+						thumbnailHeight: data.thumbnailHeight, //缩略图高度
+					},
+					extra: ""
+				});
+				YeIMUniSDK.getInstance().sendMessage({
+					message: message,
+					success: this.sendSuccess,
+					fail: (err) => {
+						this.$loading.close();
+					}
+				});
+
+			},
+			// 发送信息
+			send() {
+				if (!this.sendMessage) return this.$message.warning('内容不能为空');
+				this.sendLoading = true;
+				this.$chat.sendText(this.body.conversationId, this.sendMessage, this.sendSuccess, (e) => {
+					this.$message.error('发送失败');
+					this.sendLoading = false;
+				});
+			},
+			sendSuccess(res) {
+				this.$loading.close();
+				this.dataList.push(res.data);
+				this.sendMessage = '';
+				this.sendLoading = false;
+				this.$nextTick(() => {
+					this.$refs.chatList.scrollTo(0, 99999);
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.chat-box-data {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		position: relative;
+
+		.file-list {
+			padding: 10px 10px 0 10px;
+
+			.el-icon-picture {
+				font-size: 22px;
+				cursor: pointer;
+			}
+		}
+
+		.chat-item {
+			display: flex;
+			padding: 10px 10px 20px 10px;
+			align-items: flex-start;
+			align-content: flex-start;
+			position: relative;
+
+			.avatar {
+				width: 40px;
+				height: 40px;
+				border-radius: 50%;
+				border: #fff solid 1px;
+			}
+
+			.date {
+				position: absolute;
+				bottom: 5px;
+				font-size: 12px;
+				color: #8e8e8e;
+			}
+
+			.content-box {
+				flex: 1;
+				width: 0;
+				display: flex;
+			}
+
+			.image-box {
+				border-radius: 8px;
+			}
+
+			.image-width {
+				width: 140px;
+				height: 100px;
+			}
+
+			.image-height {
+				width: 100px;
+				height: 140px;
+			}
+
+			.image-width-height {
+				width: 100px;
+				height: 100px;
+			}
+
+			.content {
+				padding: 10px 15px;
+				border-radius: 8px;
+				word-break: break-all;
+				line-height: 21px;
+				position: relative;
+			}
+
+			/* 收到的消息 */
+			&.pull {
+				.content-box {
+					margin-right: 50px;
+				}
+
+				.image-box {
+					margin-left: 10px;
+				}
+
+				.content {
+					margin-left: 10px;
+					background-color: $--background-color-base;
+				}
+
+				.date {
+					left: 60px;
+				}
+			}
+
+			/* 发出的消息 */
+			&.push {
+				/* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
+				flex-direction: row-reverse;
+
+				.content-box {
+					flex-direction: row-reverse;
+					margin-left: 50px;
+				}
+
+				.image-box {
+					margin-right: 10px;
+				}
+
+				.content {
+					margin-right: 10px;
+					background-color: $--color-primary;
+					color: #fff;
+					box-shadow: 0px 1px 12px rgba(3, 3, 3, 0.08);
+				}
+
+				.date {
+					right: 60px;
+				}
+			}
+		}
+
+		.chat-information {
+			flex: 1;
+			height: 0;
+			overflow-y: auto;
+			position: relative;
+		}
+
+		.chat-loading {
+			top: 0;
+			left: 0;
+			width: 100%;
+			text-align: center;
+			position: absolute;
+			background: $--color-primary;
+			height: 30px;
+			color: #fff;
+			line-height: 30px;
+			font-size: 12px;
+			z-index: 99;
+		}
+
+		.chat-send {
+			border-top: 1px solid $--border-color-lighter;
+			position: relative;
+
+			.el-textarea__inner {
+				border: none;
+
+			}
+
+			.chat-send-btn {
+				padding: 10px;
+				text-align: right;
+			}
+		}
+	}
+</style>

+ 2 - 1
virgo.wzfrontend/workark/src/layout/components/login.vue

@@ -172,7 +172,8 @@
 					}
 				})
 			},
-			successLogin(url) {
+			successLogin(url) {
+				this.$chat.connect();
 				this.loginLoading = false;
 				this.$emit('callback', 'init')
 				this.$message.success('登录成功');

+ 56 - 7
virgo.wzfrontend/workark/src/layout/workLayout.vue

@@ -18,7 +18,14 @@
 				<router-view :key="key" />
 			</transition>
 		</div>
-		<el-dialog :close-on-click-modal="true" title="新用户注册福利" :visible.sync="hasCoupon" width="600px"
+		<div class="chat-box">
+			<el-badge :value="badge" :hidden="!badge" class="item">
+				<div class="chat" @click="visible = true">
+					<i class="el-icon-s-comment"></i>
+				</div>
+			</el-badge>
+		</div>
+		<el-dialog :close-on-click-modal="true" title="优惠券" :visible.sync="hasCoupon" width="600px"
 			:append-to-body="true">
 			<div class="hui-flex hui-dialog">
 				<div class="hui-flex-box hui-dialog-content" style="background: #f7f9fc;">
@@ -44,6 +51,10 @@
 				</div>
 			</div>
 		</el-dialog>
+		<el-dialog :close-on-click-modal="false" title="聊天列表" :visible.sync="visible" width="900px"
+			:append-to-body="true">
+			<chat v-if="visible"></chat>
+		</el-dialog>
 	</div>
 </template>
 
@@ -55,16 +66,19 @@
 		getCouponListByUser,
 		receiveCouponByUser
 	} from '@/api/discount'
-	import topNav from './components/topNav'
-	import subMenu from './components/subMenu'
-	import breadCrumb from './components/breadCrumb'
+	const topNav = () => import('./components/topNav');
+	const subMenu = () => import('./components/subMenu');
+	const breadCrumb = () => import('./components/breadCrumb');
+	const chat = () => import('./components/chat');
 	export default {
 		data() {
 			return {
 				menuList: [],
 				isCollapse: false,
 				coupon: [],
-				hasCoupon: false
+				hasCoupon: false,
+				visible: false,
+				badge: 0
 			}
 		},
 		mounted() {
@@ -74,6 +88,17 @@
 		methods: {
 			init() {
 				this.menuList = this.$store.getters.menuData;
+				this.$chat.getConversationList(data => {
+					this.countNumber(data.data)
+				});
+			},
+			countNumber(data) {
+				let badge = 0;
+				let list = data.filter(node => node.conversationId !== 'system');
+				for (let i = 0; i < list.length; i++) {
+					badge += list[i].unread
+				}
+				this.badge = badge;
 			},
 			initCoupon() {
 				getCouponListByUser(this.$store.getters.user.userId).then(res => {
@@ -95,17 +120,21 @@
 		components: {
 			topNav,
 			subMenu,
-			breadCrumb
+			breadCrumb,
+			chat
 		},
 		computed: {
 			key() {
 				return this.$route.path;
 			},
-			...mapGetters(['menuData'])
+			...mapGetters(['menuData', 'chatList'])
 		},
 		watch: {
 			menuData() {
 				this.init();
+			},
+			chatList() {
+				this.countNumber(this.$store.getters.chatList || [])
 			}
 		}
 	}
@@ -156,6 +185,26 @@
 		height: 100%;
 		background: $--background-color-base;
 
+		.chat-box {
+			position: fixed;
+			bottom: 60px;
+			right: 20px;
+			font-size: 26px;
+			cursor: pointer;
+			z-index: 999;
+
+			.chat {
+				width: 50px;
+				height: 50px;
+				background: $--color-primary;
+				color: #fff;
+				border-radius: 50px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+			}
+		}
+
 		.work-layout-main {
 			position: fixed;
 			left: 0;

+ 4 - 0
virgo.wzfrontend/workark/src/main.js

@@ -23,6 +23,10 @@ Vue.prototype.$field = field;
 
 import message from './uitls/message.js';
 Vue.prototype.$msg = message;
+
+import chat from './uitls/chat.js'
+chat.init();
+Vue.prototype.$chat = chat;
 
 Vue.prototype.$confirm = (title, callback) => {
 	return ElementUI.MessageBox.confirm(title, 'WORKARK提示', {

+ 3 - 1
virgo.wzfrontend/workark/src/store/getters.js

@@ -5,6 +5,8 @@ const getters = {
 	organization: state => state.app.organization, //所属组织信息
 	project: state => state.app.project, //项目
 	processSet: state => state.app.processSet, //过程状态管理
-	loginVisible: state => state.app.loginVisible //是否登录
+	loginVisible: state => state.app.loginVisible, //登录弹窗
+	chatList: state => state.app.chatList, //消息列表
+	chatToken: state => state.app.chatToken //消息token
 }
 export default getters;

+ 19 - 1
virgo.wzfrontend/workark/src/store/modules/app.js

@@ -11,7 +11,9 @@ const getDefaultState = () => ({
 		id: -1
 	},
 	processSet: {},
-	loginVisible: 1
+	loginVisible: 1,
+	chatList: [],
+	chatToken: ''
 })
 
 const mutations = {
@@ -38,6 +40,12 @@ const mutations = {
 	},
 	RESET_STATE: (state) => {
 		state = Object.assign(state, getDefaultState())
+	},
+	CHANGE_CHAT_LIST: (state, chatList) => {
+		state.chatList = chatList;
+	},
+	CHANGE_CHAT_TOKEN: (state, chatToken) => {
+		state.chatToken = chatToken;
 	}
 }
 
@@ -81,6 +89,16 @@ const actions = {
 		commit
 	}) {
 		commit('RESET_STATE');
+	},
+	changeChatList({
+		commit
+	}, chatList) {
+		commit('CHANGE_CHAT_LIST', chatList);
+	},
+	changeChatToken({
+		commit
+	}, chatToken) {
+		commit('CHANGE_CHAT_TOKEN', chatToken);
 	}
 }
 

+ 132 - 0
virgo.wzfrontend/workark/src/uitls/chat.js

@@ -0,0 +1,132 @@
+import {
+	YeIMUniSDK, // SDK
+	YeIMUniSDKDefines // 预定义常量
+} from 'yeim-uni-sdk'
+import {
+	login
+} from '@/api/chat.js'
+import md5 from 'js-md5'
+import config from "@/config";
+import store from '@/store'
+let isListenerChatList = false;
+const connect = success => {
+	if (!store.getters.user.userId) return;
+	let code = YeIMUniSDK.getInstance().readyState();
+	if (code !== 3) {
+		if (success) success();
+		return;
+	}
+	YeIMUniSDK.getInstance().connect({
+		userId: `workark${store.getters.user.userId}`,
+		token: store.getters.chatToken,
+		success: (response) => {
+			if (response.code === 200) {
+				$chat.listenerList();
+				if (success) success();
+			}
+		},
+		fail: (err) => {
+			console.log('error');
+			logins();
+		}
+	});
+}
+const logins = () => {
+	let timestamp = (new Date()).getTime() + 86400 * 1000; //1000天后过期
+	let sign = md5(String(`workark${store.getters.user.userId}`) + timestamp + "50abd47112ebe8c5a73f4694c96a49ce");
+	login({
+		userId: `workark${store.getters.user.userId}`,
+		timestamp: timestamp,
+		sign: sign
+	}).then(res => {
+		if (res.state) {
+			store.dispatch('app/changeChatToken', res.data.token);
+			connect();
+		}
+	})
+}
+const $chat = {
+	init() {
+		YeIMUniSDK.init({
+			baseURL: config.baseURL + '/im', // YeIMServer http url (如无特殊需求,服务端启动后仅需修改ip或者域名即可)
+			socketURL: 'wss://www.waywish.com/im/im', // YeIMServer socket url(如无特殊需求,服务端启动后仅需修改ip或者域名即可)
+			/**
+			 * 	日志等级
+			 *  0 普通日志,日志量较多,接入时建议使用
+			 *	1 关键性日志,日志量较少,生产环境时建议使用 
+			 *	2 无日志级别,SDK 将不打印任何日志
+			 */
+			logLevel: 0, // 日志等级, 
+			reConnectInterval: 3000, // 重连时间间隔
+			reConnectTotal: 99, // 最大重连次数,0不限制一直重连 
+			heartInterval: 35000, //心跳时间间隔(默认30s) 
+		});
+	},
+	connect() {
+		if (!store.getters.chatToken) {
+			logins()
+		} else {
+			connect();
+		}
+	},
+	getConversationList(success) {
+		connect(() => {
+			YeIMUniSDK.getInstance().getConversationList({
+				page: 1, //页码
+				limit: 100, //每页数量
+				success: success,
+				fail: (err) => {
+					console.log('error');
+				}
+			});
+		});
+	},
+	getHistoryMessageList(nextMessageId, userId, success, fail) {
+		connect(() => {
+			YeIMUniSDK.getInstance().getHistoryMessageList({
+				nextMessageId: nextMessageId,
+				conversationId: userId,
+				success: success,
+				fail: fail
+			});
+		});
+	},
+	sendText(userId, content, success, fail) {
+		connect(() => {
+			//创建文字消息
+			let message = YeIMUniSDK.getInstance().createTextMessage({
+				toId: userId, //接收者用户ID字符串
+				conversationType: YeIMUniSDKDefines.CONVERSATION_TYPE.PRIVATE, //会话类型:私聊
+				body: {
+					text: content //文本消息内容字符串
+				},
+				extra: ""
+			});
+			//发送消息
+			YeIMUniSDK.getInstance().sendMessage({
+				message: message,
+				success: success,
+				fail: fail
+			});
+		});
+	},
+	disConnect() {
+		YeIMUniSDK.getInstance().disConnect();
+	},
+	listenerList() {
+		if (isListenerChatList) return;
+		connect(() => {
+			isListenerChatList = true;
+			//监听会话列表更新
+			YeIMUniSDK.getInstance().addEventListener(YeIMUniSDKDefines.EVENT.CONVERSATION_LIST_CHANGED, (
+				list) => {
+				store.dispatch('app/changeChatList', list);
+			});
+		})
+	},
+	clearConversationUnread(conversationId) {
+		//清除指定会话未读数,并给对方发送已读回执
+		YeIMUniSDK.getInstance().clearConversationUnread(conversationId);
+	}
+}
+export default $chat;

+ 2 - 1
virgo.wzfrontend/workark/src/views/login/login.vue

@@ -170,7 +170,8 @@
 					}
 				})
 			},
-			successLogin(url) {
+			successLogin(url) {
+				this.$chat.connect();
 				this.loginLoading = false;
 				this.$router.push(url);
 				this.$message.success('登录成功');

+ 22 - 3
virgo.wzfrontend/workark/src/views/website/serveDetail.vue

@@ -23,6 +23,7 @@
 							</div>
 							<div class="button-box">
 								<el-button type="primary" size="small" @click="payItem">购买</el-button>
+								<el-button type="primary" size="small" @click="chatItem">联系客服</el-button>
 							</div>
 						</div>
 						<div class="contract" v-if="detail.contract">
@@ -56,6 +57,11 @@
 				:list="detail.contract ? JSON.parse(detail.contract) : []" type="preview">
 			</pdf-viewer>
 		</el-dialog>
+		<el-dialog title="在线聊天" :visible.sync="chatVisible" width="600px" :append-to-body="true">
+			<chat-box v-if="chatVisible" :body="{
+				conversationId:`workark${detail.customerId}`
+			}"></chat-box>
+		</el-dialog>
 	</div>
 </template>
 
@@ -65,7 +71,8 @@
 	} from '@/api/serve'
 	import Crypto from '@/uitls/crypto'
 	const createOrder = () => import('@/components/website/createOrder');
-	const pdfViewer = () => import('@/components/common/pdfViewer');
+	const pdfViewer = () => import('@/components/common/pdfViewer');
+	const chatBox = () => import('@/layout/components/chatBox.vue');
 	export default {
 		data() {
 			return {
@@ -76,7 +83,8 @@
 				rotatingImages: [],
 				detailedImage: [],
 				dialogVisible: false,
-				dialogType: ''
+				dialogType: '',
+				chatVisible: false
 			};
 		},
 		mounted() {
@@ -136,6 +144,16 @@
 				this.dialogType = 1;
 				this.dialogVisible = true;
 			},
+			chatItem() {
+				if (!this.$store.getters.user.userId) {
+					let index = this.$store.getters.loginVisible;
+					index++
+					this.$store.dispatch('app/changeLoginVisible', index);
+					return;
+				}
+				if (!this.detail.customerId) return this.$message.warning('该产品暂未设置客服');
+				this.chatVisible = true;
+			},
 			lookContract() {
 				this.dialogType = 2;
 				this.dialogVisible = true;
@@ -143,7 +161,8 @@
 		},
 		components: {
 			createOrder,
-			pdfViewer
+			pdfViewer,
+			chatBox
 		}
 	};
 </script>