index.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <template>
  2. <div id="index">
  3. <el-row>
  4. <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
  5. <div class="w_1200">
  6. <el-container>
  7. <el-aside width="400px" class="aside">
  8. <el-col :span="24" class="one">
  9. <el-col :span="24" class="one_1">
  10. <el-col :span="4" class="left">
  11. <el-image class="image" :src="user.icon || kf" fit="fill" />
  12. </el-col>
  13. <el-col :span="20" class="right">
  14. {{ user.nick_name || '游客' }}
  15. </el-col>
  16. </el-col>
  17. <el-col :span="24" class="one_2">
  18. <a-input placeholder="查找联系人">
  19. <template #prefix><SearchOutlined /></template>
  20. </a-input>
  21. </el-col>
  22. </el-col>
  23. <el-col :span="24" class="two">
  24. <el-col
  25. :span="24"
  26. class="list"
  27. v-for="(item, index) in chatList"
  28. :key="index"
  29. @click="toView(item)"
  30. >
  31. <div class="line" v-if="item.user == id"></div>
  32. <el-col :span="4" class="left">
  33. <el-image
  34. class="image"
  35. v-if="item.icon && item.icon.length > 0"
  36. :src="item.icon[0].url"
  37. fit="fill"
  38. />
  39. <el-image class="image" v-else :src="kf" fit="fill" />
  40. </el-col>
  41. <el-col :span="20" class="right">
  42. <el-col :span="24" class="right_1">
  43. <div class="name">{{ item.nick_name }}</div>
  44. <div class="time">{{ item.send_time }}</div>
  45. </el-col>
  46. <el-col :span="24" class="right_2">{{ item.content }}</el-col>
  47. </el-col>
  48. </el-col>
  49. </el-col>
  50. </el-aside>
  51. <el-main style="background: #eee; padding: 0">
  52. <Chat></Chat>
  53. </el-main>
  54. </el-container>
  55. </div>
  56. </el-col>
  57. </el-row>
  58. <el-dialog v-model="dialog" title="智能客服">
  59. <el-col :span="24" class="dialog">
  60. <div class="content">
  61. <div class="title">智能客服为您服务</div>
  62. <div class="list">
  63. <el-image class="image" :src="kf" fit="fill" />
  64. <div class="message">Hi,遇到问题随时找智能客服哟~ 有什么需要我帮忙的吗?</div>
  65. </div>
  66. </div>
  67. <div class="foot">
  68. <textarea class="input" placeholder="输入消息"></textarea>
  69. <div class="button">
  70. <div class="send">发 送</div>
  71. </div>
  72. </div>
  73. </el-col>
  74. </el-dialog>
  75. </div>
  76. </template>
  77. <script setup>
  78. // 基础
  79. import moment from 'moment'
  80. import Chat from './parts/chat.vue'
  81. import { SearchOutlined } from '@ant-design/icons-vue'
  82. // 接口
  83. import { ChatStore } from '@/store/api/platform/chat'
  84. import { UsersStore } from '@/store/api/user/user'
  85. const store = UsersStore()
  86. const chatstore = ChatStore()
  87. import { UserStore } from '@/store/user'
  88. const userStore = UserStore()
  89. const user = computed(() => userStore.user)
  90. // 图片引入
  91. import kf from '@/assets/kf.png'
  92. // 路由
  93. const route = useRoute()
  94. // 加载中
  95. const loading = ref(false)
  96. // 是否弹框客服
  97. const dialog = ref(false)
  98. // 聊天者id
  99. const id = ref(route.query.id || '')
  100. // 聊天室
  101. const chatList = ref([])
  102. // 列表
  103. const list = ref([])
  104. let skip = 0
  105. let limit = inject('limit')
  106. // 搜索
  107. const searchForm = ref({})
  108. // 个人信息
  109. const info = ref({})
  110. // 消息
  111. const textarea = ref('')
  112. // 请求
  113. onMounted(async () => {
  114. loading.value = true
  115. await search()
  116. loading.value = false
  117. })
  118. const search = async () => {
  119. searchList({ skip, limit })
  120. if (id.value) {
  121. let res = await store.fetch(id.value)
  122. if (res.errcode == '0') info.value = res.data
  123. searchChat({ skip, limit })
  124. }
  125. }
  126. const searchList = async (query = { skip: 0, limit }) => {
  127. const info = {
  128. skip: query.skip,
  129. limit: query.limit,
  130. ...searchForm.value
  131. }
  132. const res = await chatstore.chat(info)
  133. if (res.errcode == '0') chatList.value = res.data
  134. }
  135. const toView = async (item) => {
  136. id.value = item._id
  137. info.value = item
  138. await searchChat({ skip, limit })
  139. }
  140. const searchChat = async (query = { skip: 0, limit }) => {
  141. const data = {
  142. skip: query.skip,
  143. limit: query.limit,
  144. user: info.value._id,
  145. ...searchForm.value
  146. }
  147. const res = await chatstore.query(data)
  148. if (res.errcode == '0') list.value = res.data
  149. }
  150. const toSend = async () => {
  151. const data = {
  152. sender_id: user.value._id,
  153. receiver_id: id.value,
  154. type: '0',
  155. content: textarea.value,
  156. send_time: moment().format('YYYY-MM-DD HH:mm:ss')
  157. }
  158. const res = await chatstore.create(data)
  159. if (res.errcode === 0) {
  160. textarea.value = ''
  161. searchChat()
  162. }
  163. }
  164. // provide
  165. provide('id', id)
  166. provide('list', list)
  167. provide('info', info)
  168. provide('textarea', textarea)
  169. provide('toSend', toSend)
  170. </script>
  171. <style scoped lang="scss">
  172. .main {
  173. margin: 10px 0;
  174. .aside {
  175. border-radius: 5px 0 0 5px;
  176. background: #33353a;
  177. .one {
  178. .one_1 {
  179. display: flex;
  180. align-items: center;
  181. background: #292b2e;
  182. padding: 10px;
  183. .left {
  184. image {
  185. width: 50px;
  186. height: 50px;
  187. border-radius: 50%;
  188. }
  189. }
  190. .right {
  191. margin: 0 0 0 10px;
  192. white-space: nowrap;
  193. color: #fff;
  194. font-size: 16px;
  195. }
  196. }
  197. .one_2 {
  198. padding: 10px;
  199. border-bottom: 1px solid #404247;
  200. background-color: hsla(0, 0%, 75.3%, 0.2);
  201. :deep(.ant-input-affix-wrapper) {
  202. color: #ccc !important;
  203. background-color: transparent;
  204. }
  205. :deep(.ant-input) {
  206. height: 32px;
  207. position: relative;
  208. caret-color: #fff;
  209. font-size: 14px;
  210. padding: 6px 0;
  211. border: none;
  212. outline: none;
  213. background-color: transparent;
  214. &::placeholder {
  215. color: #ccc !important;
  216. }
  217. }
  218. }
  219. }
  220. .two {
  221. overflow-y: auto;
  222. height: 57vh;
  223. .list {
  224. position: relative;
  225. display: flex;
  226. padding: 10px;
  227. border-bottom: 1px solid #2c2e31;
  228. .right {
  229. margin: 5px 0 0 10px;
  230. .right_1 {
  231. display: flex;
  232. justify-content: space-between;
  233. padding: 0 10px 10px 0;
  234. .name {
  235. color: #fff;
  236. font-size: 14px;
  237. }
  238. .time {
  239. color: #51555e;
  240. vertical-align: top;
  241. word-break: keep-all;
  242. font-size: 10px;
  243. }
  244. }
  245. .right_2 {
  246. color: #999;
  247. }
  248. }
  249. .line {
  250. position: absolute;
  251. background: #ff8f2c;
  252. width: 2px;
  253. left: 0;
  254. top: 0;
  255. height: 85px;
  256. }
  257. }
  258. .list:hover {
  259. background-color: #3a3c42;
  260. }
  261. }
  262. .two::-webkit-scrollbar {
  263. display: none;
  264. }
  265. }
  266. }
  267. :deep(.el-dialog__body) {
  268. padding: 0 !important;
  269. }
  270. .dialog {
  271. .content {
  272. padding: 20px;
  273. .title {
  274. padding-top: 23px;
  275. padding-bottom: 26px;
  276. display: flex;
  277. flex-direction: row;
  278. align-items: center;
  279. justify-content: center;
  280. font-size: 14px;
  281. color: rgba(0, 0, 0, 0.45);
  282. }
  283. .list {
  284. display: flex;
  285. align-items: center;
  286. .image {
  287. width: 60px;
  288. height: 60px;
  289. border-radius: 60px;
  290. margin: 0 10px 0 0;
  291. }
  292. .message {
  293. position: relative;
  294. max-width: 330px;
  295. border-radius: 4px;
  296. font-size: 14px;
  297. line-height: 22px;
  298. box-sizing: border-box;
  299. color: rgba(0, 0, 0, 0.65);
  300. padding: 16px 11px 16px 16px;
  301. background: #ffffff;
  302. box-shadow: 0px 2px 10px 1px rgba(0, 0, 0, 0.12);
  303. }
  304. }
  305. }
  306. .foot {
  307. height: 140px;
  308. background: rgba(0, 0, 0, 0.04);
  309. border: 1px solid rgba(0, 0, 0, 0.15);
  310. padding: 13px 20px;
  311. display: flex;
  312. flex-direction: column;
  313. align-items: stretch;
  314. .input {
  315. flex: 1;
  316. height: 0;
  317. resize: none;
  318. border: none;
  319. outline: none;
  320. background: transparent;
  321. font-size: 14px;
  322. line-height: 22px;
  323. }
  324. .button {
  325. margin-top: 8px;
  326. flex-shrink: 0;
  327. display: flex;
  328. flex-direction: row;
  329. justify-content: flex-end;
  330. .send {
  331. cursor: pointer;
  332. color: #fff;
  333. background: #2f54eb;
  334. border-radius: 4px;
  335. width: 64px;
  336. height: 32px;
  337. font-size: 14px;
  338. display: flex;
  339. align-items: center;
  340. justify-content: center;
  341. }
  342. }
  343. }
  344. }
  345. </style>