index.vue 8.8 KB

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