| | |
| | | <template> |
| | | <div class="service-box flex flex-col pb-40"> |
| | | <iframe height="100%" src="https://cdn.chat20gm.cfd/chat_online/index?channelId=1958101585091772416"></iframe> |
| | | </div> |
| | | <div class="service-box flex flex-col" :class="homesStore.kefu_url ? '' : 'pb-40'"> |
| | | <div> |
| | | <van-nav-bar ref="navEl" :title="$t('onLineService') + ' Jason David'" left-arrow @click-left="onClickLeft" |
| | | fixed /> |
| | | <div class="px-3.5 py-5" v-if="state == 0"> |
| | | <div class="white">{{ $t('OrdersWill') }} <span style="color: #1194F7">{{ dayjs.duration(remainingTime, |
| | | 'seconds').format('mm:ss') }}</span> {{ |
| | | $t('afterCancel') |
| | | }}</div> |
| | | <div class="mt-3"> |
| | | <span class="mr-1" style="color: #8A919E">{{ $t('lumpSum') }}</span> |
| | | <span class="white">{{ payInfo.currency }} {{ payInfo.amount }}</span> |
| | | </div> |
| | | <div class="mt-5"> |
| | | <van-button class="w-full" type="primary" @click="router.back()">{{ $t('toPay') }}</van-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="flex-1" v-if="homesStore.kefu_url"> |
| | | <iframe :src="generateExtranetLink()" width="100%" height="100%" frameborder="0"></iframe> |
| | | </div> |
| | | <div class="localKefu flex-1 flex" v-else> |
| | | <div class="flex flex-col px-10 box-border" ref="boxScrollEl" style="overflow:auto;"> |
| | | <div class="w-full py-4 text-grey text-center pt-30 text-30" @click="onMore" |
| | | :style="{ 'display': finished ? 'none' : 'block' }"> |
| | | {{ $t('historyMessage') }} |
| | | </div> |
| | | <ul class="flex flex-col pt-3"> |
| | | <li v-for="(item, index) in list" :key="index" class="flex flex-col my-3"> |
| | | <!-- <p class="text-center py-2 text-grey text-30" v-if="showTime(index)">{{ |
| | | item.createtime && |
| | | item.createtime.split(' ')[0] |
| | | }}</p> --> |
| | | <template v-if="item.delete_status != -1"> |
| | | <p class="text-center pb-3 text-grey text-30">{{ |
| | | item.createtime |
| | | }}</p> |
| | | <div class="flex" :class="item.send_receive === 'send' ? 'justify-end' : ''"> |
| | | <template v-if="item.send_receive === 'receive'"> |
| | | <img src="@/assets/image/service/responser.png" class="w-20 h-20 mr-5" /> |
| | | <div class="responser px-12 py-8 text-30 left-chatBg"> |
| | | <p class="break-word textColor text-30" style="max-width: 200px;" |
| | | v-if="item.content_type === 'text' || item.type === 'text'"> |
| | | {{ item.content }}</p> |
| | | <img v-else :src="item.content" class="w-200 h-200" @click="onPreview(item.content)" /> |
| | | </div> |
| | | </template> |
| | | <div class="py-8 px-12 rounded-md flex flex-col right-chatBg" v-else> |
| | | <img :src="`${item.content}`" class="w-200 h-200" |
| | | v-if="item.content_type === 'img' || item.type === 'img'" @click="onPreview(item.content)" /> |
| | | <p class="break-word text-white text-30" v-else style="max-width: 200px;">{{ item.content }}</p> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | |
| | | <div |
| | | class="bottom bottomBox flex justify-between items-center w-full fixed bottom-0 borderTop px-4 box-border bgBottom"> |
| | | <van-uploader :max-size="10000 * 1024" @oversize="onOversize" :after-read="afterRead"> |
| | | <img src="@/assets/image/service/photo2.png" class="w-12 h-12" /> |
| | | </van-uploader> |
| | | <input type="text" v-model="message" :placeholder="$t('entryYouMessage')" |
| | | class="flex-1 mx-3 h-full border-none textColor chatBg" maxlength="500" /> |
| | | <img src="@/assets/image/service/send2.png" class="w-12 h-12" @click="throttleSend(message)" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | Uploader, |
| | | showImagePreview |
| | | } from 'vant' |
| | | import { |
| | | _getMsg, |
| | | _getUnreadMsg, |
| | | _sendMsg |
| | | } from '@/service/im.api' |
| | | import { |
| | | _uploadImage |
| | | } from '@/service/upload.api' |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | onUnmounted, |
| | | onBeforeMount |
| | | } from "vue"; |
| | | import { |
| | | useI18n |
| | | } from "vue-i18n"; |
| | | import { |
| | | throttle |
| | | } from '@/utils/index' |
| | | import { |
| | | closeToast, |
| | | showToast, |
| | | showLoadingToast |
| | | } from "vant"; |
| | | import { |
| | | useRoute, |
| | | useRouter |
| | | } from 'vue-router'; |
| | | import { |
| | | getc2cOrder |
| | | } from "@/service/recharge.api.js"; |
| | | import { |
| | | useHomesStore |
| | | } from "@/store/homes.store"; |
| | | import { |
| | | SET_KEFU |
| | | } from "@/store/types.store"; |
| | | import { |
| | | useUserStore |
| | | } from '@/store/user'; |
| | | import dayjs from 'dayjs'; |
| | | import duration from 'dayjs/plugin/duration'; |
| | | dayjs.extend(duration); |
| | | import { Uploader, showImagePreview } from 'vant' |
| | | import { _getMsg, _getUnreadMsg, _sendMsg } from '@/service/im.api' |
| | | import { _uploadImage } from '@/service/upload.api' |
| | | import { ref, onMounted, onUnmounted, onBeforeMount, nextTick } from "vue"; |
| | | import { useI18n } from "vue-i18n"; |
| | | import { throttle } from '@/utils/index' |
| | | import { closeToast, showToast, showLoadingToast } from "vant"; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | import { getc2cOrder } from "@/service/recharge.api.js"; |
| | | import { useHomesStore } from "@/store/homes.store"; |
| | | import { SET_KEFU } from "@/store/types.store"; |
| | | import { useUserStore } from '@/store/user'; |
| | | import dayjs from 'dayjs'; |
| | | import duration from 'dayjs/plugin/duration'; |
| | | dayjs.extend(duration); |
| | | |
| | | const userStore = useUserStore() |
| | | const { |
| | | t |
| | | } = useI18n() |
| | | const homesStore = useHomesStore(); |
| | | const router = useRouter() |
| | | const route = useRoute() |
| | | const list = ref([]) |
| | | const message = ref('') |
| | | const lastMsgId = ref('') |
| | | const interval = ref(null) |
| | | const unread = ref(0) |
| | | const finished = ref(false) |
| | | const isScrollBottom = ref(true) |
| | | const currentScrollTop = ref(0) |
| | | const androidAttrs = ref(null) |
| | | const navEl = ref(null); |
| | | const boxScrollEl = ref(null); |
| | | const navHeight = ref(0); |
| | | const payInfo = ref({}) |
| | | const remainingTime = ref(0) |
| | | const userStore = useUserStore() |
| | | const { t } = useI18n() |
| | | const homesStore = useHomesStore(); |
| | | const router = useRouter() |
| | | const route = useRoute() |
| | | const list = ref([]) |
| | | const message = ref('') |
| | | const lastMsgId = ref('') |
| | | const interval = ref(null) |
| | | const unread = ref(0) |
| | | const finished = ref(false) |
| | | const isScrollBottom = ref(true) |
| | | const currentScrollTop = ref(0) |
| | | const androidAttrs = ref(null) |
| | | const navEl = ref(null); |
| | | const boxScrollEl = ref(null); |
| | | const navHeight = ref(0); |
| | | const payInfo = ref({}) |
| | | const remainingTime = ref(0) |
| | | |
| | | let state = ref(null) |
| | | let orderNo = "" |
| | | let partyId = "" |
| | | onMounted(() => { |
| | | // 美洽客服 |
| | | // const _ll="&id="+_id.value+"&name="+_name.value |
| | | // const _ll="" |
| | | // console.log(_ll) |
| | | // const _uull='https://cdn.chat20gm.cfd/chat_online/index?channelId=1958101585091772416'+_ll; |
| | | // window.location.href=_uull |
| | | let state = ref(null) |
| | | let orderNo = "" |
| | | let partyId = "" |
| | | // onMounted(() => { |
| | | // // 美洽客服 |
| | | // // const _ll="&id="+_id.value+"&name="+_name.value |
| | | // const _ll = "" |
| | | // // console.log(_ll) |
| | | // const _uull = 'https://cdn.chat20gm.cfd/chat_online/index?channelId=1958101585091772416' + _ll; |
| | | // // window.location.href=_uull |
| | | |
| | | }) |
| | | // onMounted_bak(() => { |
| | | // orderNo = "" |
| | | // partyId = "" |
| | | // navHeight.value = navEl.value.$el.getBoundingClientRect().height |
| | | // if (route.query.order_no) { |
| | | // getc2cOrderDetails(route.query.order_no, (data) => { |
| | | // console.log("getc2cOrderDetails = " + JSON.stringify(data)) |
| | | // orderNo = data.orderNo; |
| | | // partyId = data.partyId; |
| | | // fetchList() |
| | | // }) |
| | | // } else { |
| | | // if (!homesStore.kefu_url) { |
| | | // fetchList() |
| | | // } |
| | | // } |
| | | // setInterval(() => { |
| | | // getCountdown() |
| | | // }, 1000) |
| | | // const model = navigator.userAgent; |
| | | // // 判断是否是安卓手机,是则是true |
| | | // androidAttrs.value = model.indexOf('Android') > -1 || model.indexOf('Linux') > -1 |
| | | // window.addEventListener('scroll', handleScroll, true) |
| | | // }) |
| | | // // 绑定滚动事件监听器,使用 nextTick 确保 DOM 渲染完成 |
| | | // nextTick(() => { |
| | | // if (boxScrollEl.value) { |
| | | // boxScrollEl.value.addEventListener('scroll', handleScroll) |
| | | // // 初始化时检查是否在底部 |
| | | // setTimeout(() => { |
| | | // handleScroll() |
| | | // }, 100) |
| | | // } |
| | | // }) |
| | | // }) |
| | | onMounted(() => { |
| | | orderNo = "" |
| | | partyId = "" |
| | | navHeight.value = navEl.value.$el.getBoundingClientRect().height |
| | | if (route.query.order_no) { |
| | | getc2cOrderDetails(route.query.order_no, (data) => { |
| | | console.log("getc2cOrderDetails = " + JSON.stringify(data)) |
| | | orderNo = data.orderNo; |
| | | partyId = data.partyId; |
| | | fetchList() |
| | | }) |
| | | } else { |
| | | if (!homesStore.kefu_url) { |
| | | fetchList() |
| | | } |
| | | } |
| | | setInterval(() => { |
| | | getCountdown() |
| | | }, 1000) |
| | | const model = navigator.userAgent; |
| | | // 判断是否是安卓手机,是则是true |
| | | androidAttrs.value = model.indexOf('Android') > -1 || model.indexOf('Linux') > -1 |
| | | window.addEventListener('scroll', handleScroll, true) |
| | | }) |
| | | |
| | | onBeforeMount(() => { |
| | | homesStore[SET_KEFU]() |
| | | }) |
| | | onBeforeMount(() => { |
| | | homesStore[SET_KEFU]() |
| | | }) |
| | | |
| | | //获取订单详情 |
| | | const getc2cOrderDetails = (orderNo, call) => { |
| | | getc2cOrder({ |
| | | order_no: orderNo |
| | | }).then((res) => { |
| | | payInfo.value = res |
| | | state.value = payInfo.value.state |
| | | remainingTime.value = res.autoCancelTimeRemain |
| | | if (call) { |
| | | call(res) |
| | | } |
| | | }) |
| | | } |
| | | //获取订单详情 |
| | | const getc2cOrderDetails = (orderNo, call) => { |
| | | getc2cOrder({ order_no: orderNo }).then((res) => { |
| | | payInfo.value = res |
| | | state.value = payInfo.value.state |
| | | remainingTime.value = res.autoCancelTimeRemain |
| | | if (call) { call(res) } |
| | | }) |
| | | } |
| | | |
| | | //第三方客服带用户id |
| | | const generateExtranetLink = () => { |
| | | let extranetLink = '' |
| | | if (userStore.userInfo && userStore.userInfo.usercode) { |
| | | const userData = encodeURIComponent(JSON.stringify({ |
| | | name: userStore.userInfo.usercode, |
| | | comment: '' |
| | | })) |
| | | let params = `&clientid=${userStore.userInfo.usercode}&metadata=${userData}`; |
| | | extranetLink = homesStore.kefu_url + params; |
| | | } else { |
| | | extranetLink = homesStore.kefu_url |
| | | } |
| | | // extranetLink = homesStore.kefu_url + params; |
| | | console.log('generateExtranetLink', extranetLink) |
| | | // console.log('extranetLink',extranetLink) |
| | | return extranetLink; |
| | | } |
| | | //第三方客服带用户id |
| | | const generateExtranetLink = () => { |
| | | let extranetLink = '' |
| | | if (userStore.userInfo && userStore.userInfo.usercode) { |
| | | const userData = encodeURIComponent(JSON.stringify({ name: userStore.userInfo.usercode, comment: '' })) |
| | | let params = `&clientid=${userStore.userInfo.usercode}&metadata=${userData}`; |
| | | extranetLink = homesStore.kefu_url + params; |
| | | } else { |
| | | extranetLink = homesStore.kefu_url |
| | | } |
| | | // extranetLink = homesStore.kefu_url + params; |
| | | console.log('generateExtranetLink', extranetLink) |
| | | // console.log('extranetLink',extranetLink) |
| | | return extranetLink; |
| | | } |
| | | |
| | | const throttleSend = throttle((message) => { |
| | | send('text', message) |
| | | }, 500) |
| | | const throttleSend = throttle((message) => { |
| | | send('text', message) |
| | | }, 500) |
| | | |
| | | const onOversize = (file) => { |
| | | showToast(t('fileMaxLimit')); |
| | | } |
| | | const onPreview = (url) => { // 预览 |
| | | showImagePreview([url]) |
| | | } |
| | | const showTime = (index) => { // 时间显示 |
| | | if (index === 0) { |
| | | return true |
| | | } |
| | | if (index === list.value.length - 1) { |
| | | return false |
| | | } |
| | | if (list.value[index].createtime.split(' ')[0] === list.value[index + 1].createtime.split(' ')[1]) { |
| | | return false |
| | | } |
| | | } |
| | | const afterRead = (file) => { // 文件上传 |
| | | showLoadingToast({ |
| | | duration: 0 |
| | | }) |
| | | _uploadImage(file, (percent) => { |
| | | console.log(percent) |
| | | }).then(data => { |
| | | closeToast() |
| | | send('img', data) |
| | | }).catch(() => { |
| | | showToast(t('失败')) |
| | | }) |
| | | } |
| | | const fetchList = async (message_id = '') => { // 获取消息列表 |
| | | console.log("orderNo = " + orderNo); |
| | | console.log("partyId = " + partyId); |
| | | _getMsg({ |
| | | message_id |
| | | }, orderNo, partyId).then(data => { // |
| | | if (!lastMsgId.value) { |
| | | lastMsgId.value = data.length && data[data.length - 1]['id'] |
| | | } |
| | | if (data.length) { |
| | | if (message_id) { // 加载更多 |
| | | lastMsgId.value = data[data.length - 1]['id'] |
| | | list.value = [...data.reverse(), ...list.value] |
| | | } else { |
| | | list.value = [...list.value, ...data.reverse()] |
| | | let hash = {}; |
| | | list.value = list.value.reduce(function(preVal, curVal) { |
| | | hash[curVal.id] ? ' ' : hash[curVal.id] = true && preVal.push(curVal); |
| | | return preVal |
| | | }, []); |
| | | list.value.forEach((item, index) => { |
| | | data.forEach((item2, index2) => { |
| | | if (item.id === item2.id) { |
| | | item.delete_status = item2.delete_status |
| | | } |
| | | }) |
| | | }) |
| | | const onOversize = (file) => { |
| | | showToast(t('fileMaxLimit')); |
| | | } |
| | | const onPreview = (url) => { // 预览 |
| | | showImagePreview([url]) |
| | | } |
| | | const showTime = (index) => { // 时间显示 |
| | | if (index === 0) { |
| | | return true |
| | | } |
| | | if (index === list.value.length - 1) { |
| | | return false |
| | | } |
| | | if (list.value[index].createtime.split(' ')[0] === list.value[index + 1].createtime.split(' ')[1]) { |
| | | return false |
| | | } |
| | | } |
| | | const afterRead = (file) => { // 文件上传 |
| | | showLoadingToast({ duration: 0 }) |
| | | _uploadImage(file, (percent) => { |
| | | console.log(percent) |
| | | }).then(data => { |
| | | closeToast() |
| | | send('img', data) |
| | | }).catch(() => { |
| | | showToast(t('失败')) |
| | | }) |
| | | } |
| | | const fetchList = async (message_id = '') => { // 获取消息列表 |
| | | console.log("orderNo = " + orderNo); |
| | | console.log("partyId = " + partyId); |
| | | // 保存更新前的滚动位置和滚动高度 |
| | | let previousScrollTop = 0 |
| | | let previousScrollHeight = 0 |
| | | if (boxScrollEl.value && !message_id) { |
| | | previousScrollTop = boxScrollEl.value.scrollTop |
| | | previousScrollHeight = boxScrollEl.value.scrollHeight |
| | | } |
| | | |
| | | } |
| | | _getMsg({ message_id }, orderNo, partyId).then(data => { // |
| | | if (!lastMsgId.value) { |
| | | lastMsgId.value = data.length && data[data.length - 1]['id'] |
| | | } |
| | | if (data.length) { |
| | | if (message_id) { // 加载更多 |
| | | lastMsgId.value = data[data.length - 1]['id'] |
| | | list.value = [...data.reverse(), ...list.value] |
| | | } else { |
| | | list.value = [...list.value, ...data.reverse()] |
| | | let hash = {}; |
| | | list.value = list.value.reduce(function (preVal, curVal) { |
| | | hash[curVal.id] ? ' ' : hash[curVal.id] = true && preVal.push(curVal); |
| | | return preVal |
| | | }, []); |
| | | list.value.forEach((item, index) => { |
| | | data.forEach((item2, index2) => { |
| | | if (item.id === item2.id) { |
| | | item.delete_status = item2.delete_status |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | if (isScrollBottom.value) { |
| | | boxScrollEl.value.scrollTop = boxScrollEl.value.scrollHeight - boxScrollEl.value |
| | | .offsetHeight |
| | | } |
| | | currentScrollTop.value = boxScrollEl.value.scrollTop; |
| | | if (data.length < 10) { |
| | | finished.value = true |
| | | } |
| | | } |
| | | if (!message_id) { |
| | | clearIntervalTimer() |
| | | interval.value = setInterval(() => { |
| | | fetchList() |
| | | }, 1000) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const handleScroll = () => { |
| | | if (boxScrollEl.value) { |
| | | if (boxScrollEl.value.scrollTop === currentScrollTop.value) { |
| | | isScrollBottom.value = true |
| | | } else { |
| | | isScrollBottom.value = false |
| | | } |
| | | } |
| | | } |
| | | // 使用 nextTick 确保 DOM 更新完成后再操作滚动 |
| | | setTimeout(() => { |
| | | if (boxScrollEl.value) { |
| | | if (message_id) { |
| | | // 加载更多时,保持滚动位置(通过计算新增内容的高度) |
| | | const newScrollHeight = boxScrollEl.value.scrollHeight |
| | | const heightDiff = newScrollHeight - previousScrollHeight |
| | | boxScrollEl.value.scrollTop = previousScrollTop + heightDiff |
| | | } else { |
| | | // 轮询新消息时,只有用户在底部才滚动到底部 |
| | | if (isScrollBottom.value) { |
| | | boxScrollEl.value.scrollTop = boxScrollEl.value.scrollHeight - boxScrollEl.value.offsetHeight |
| | | } else { |
| | | // 如果用户不在底部,保持当前滚动位置(通过计算新增内容的高度) |
| | | const newScrollHeight = boxScrollEl.value.scrollHeight |
| | | const heightDiff = newScrollHeight - previousScrollHeight |
| | | boxScrollEl.value.scrollTop = previousScrollTop + heightDiff |
| | | } |
| | | } |
| | | currentScrollTop.value = boxScrollEl.value.scrollTop |
| | | } |
| | | }, 0) |
| | | |
| | | const onMore = () => { // 加载更多 |
| | | fetchList(lastMsgId.value) |
| | | } |
| | | const clearIntervalTimer = () => { |
| | | if (interval.value) { |
| | | clearInterval(interval.value) |
| | | interval.value = null |
| | | } |
| | | } |
| | | const fetchUnread = () => { // 获取未读 |
| | | _getUnreadMsg(orderNo, partyId).then(data => { |
| | | unread.value = data |
| | | }) |
| | | } |
| | | const onClickLeft = () => { // 返回 |
| | | router.go(-1); |
| | | } |
| | | const send = (type = 'text', content = '') => { // 发送消息, img 也当消息text |
| | | if (!content) { |
| | | showToast(t('entryMessageContent')) |
| | | return |
| | | } |
| | | _sendMsg(type, content, orderNo, partyId).then(async data => { |
| | | message.value = '' |
| | | isScrollBottom.value = true |
| | | // document.getElementById('bottom').click() |
| | | // await fetchList() |
| | | }) |
| | | } |
| | | const getCountdown = () => { |
| | | if (remainingTime.value > 0) { |
| | | remainingTime.value = remainingTime.value - 1 |
| | | } else { |
| | | remainingTime.value = 0 |
| | | } |
| | | } |
| | | if (data.length < 10) { |
| | | finished.value = true |
| | | } |
| | | } |
| | | if (!message_id) { |
| | | clearIntervalTimer() |
| | | interval.value = setInterval(() => { |
| | | fetchList() |
| | | }, 1000) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onUnmounted(() => { |
| | | clearIntervalTimer() |
| | | }) |
| | | const handleScroll = () => { |
| | | if (boxScrollEl.value) { |
| | | const { scrollTop, scrollHeight, offsetHeight } = boxScrollEl.value |
| | | // 判断是否在底部(允许5px的误差) |
| | | const isAtBottom = scrollHeight - scrollTop - offsetHeight <= 5 |
| | | isScrollBottom.value = isAtBottom |
| | | currentScrollTop.value = scrollTop |
| | | } |
| | | } |
| | | |
| | | const onMore = () => { // 加载更多 |
| | | fetchList(lastMsgId.value) |
| | | } |
| | | const clearIntervalTimer = () => { |
| | | if (interval.value) { |
| | | clearInterval(interval.value) |
| | | interval.value = null |
| | | } |
| | | } |
| | | const fetchUnread = () => { // 获取未读 |
| | | _getUnreadMsg(orderNo, partyId).then(data => { |
| | | unread.value = data |
| | | }) |
| | | } |
| | | const onClickLeft = () => { // 返回 |
| | | router.go(-1); |
| | | } |
| | | const send = (type = 'text', content = '') => { // 发送消息, img 也当消息text |
| | | if (!content) { |
| | | showToast(t('entryMessageContent')) |
| | | return |
| | | } |
| | | _sendMsg(type, content, orderNo, partyId).then(async data => { |
| | | message.value = '' |
| | | isScrollBottom.value = true |
| | | // document.getElementById('bottom').click() |
| | | await fetchList() |
| | | }) |
| | | } |
| | | const getCountdown = () => { |
| | | if (remainingTime.value > 0) { |
| | | remainingTime.value = remainingTime.value - 1 |
| | | } else { |
| | | remainingTime.value = 0 |
| | | } |
| | | } |
| | | |
| | | onUnmounted(() => { |
| | | clearIntervalTimer() |
| | | // 移除滚动事件监听器 |
| | | if (boxScrollEl.value) { |
| | | boxScrollEl.value.removeEventListener('scroll', handleScroll) |
| | | } |
| | | }) |
| | | |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | .service-box { |
| | | font-size: 14px; |
| | | width: 100%; |
| | | height: 100vh; |
| | | box-sizing: border-box; |
| | | background: $mainBgColor; |
| | | overflow: hidden; |
| | | .service-box { |
| | | font-size: 14px; |
| | | width: 100%; |
| | | height: 100vh; |
| | | box-sizing: border-box; |
| | | background: $mainBgColor; |
| | | overflow: hidden; |
| | | |
| | | :deep(.van-hairline--bottom::after) { |
| | | border-color: $mainBgColor; |
| | | } |
| | | } |
| | | :deep(.van-hairline--bottom::after) { |
| | | border-color: $mainBgColor; |
| | | } |
| | | } |
| | | |
| | | .break-word { |
| | | word-wrap: break-word; |
| | | white-space: pre-wrap; |
| | | } |
| | | .break-word { |
| | | word-wrap: break-word; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .max-w-230 { |
| | | max-width: 115px; |
| | | } |
| | | .max-w-230 { |
| | | max-width: 115px; |
| | | } |
| | | |
| | | .responser { |
| | | position: relative; |
| | | .responser { |
| | | position: relative; |
| | | |
| | | &::after { |
| | | content: ''; |
| | | width: 0; |
| | | height: 0; |
| | | border-top: 5px solid transparent; |
| | | border-bottom: 5px solid transparent; |
| | | border-right: 10px solid $input_background; |
| | | position: absolute; |
| | | left: -10px; |
| | | top: 10px; |
| | | } |
| | | } |
| | | &::after { |
| | | content: ''; |
| | | width: 0; |
| | | height: 0; |
| | | border-top: 5px solid transparent; |
| | | border-bottom: 5px solid transparent; |
| | | border-right: 10px solid $input_background; |
| | | position: absolute; |
| | | left: -10px; |
| | | top: 10px; |
| | | } |
| | | } |
| | | |
| | | .borderTop { |
| | | border-top: 1px solid $border_color; |
| | | } |
| | | .borderTop { |
| | | border-top: 1px solid $border_color; |
| | | } |
| | | |
| | | .bottomBox { |
| | | height: 65px; |
| | | } |
| | | .bottomBox { |
| | | height: 65px; |
| | | } |
| | | |
| | | .white { |
| | | color: $text_color; |
| | | } |
| | | .white { |
| | | color: $text_color; |
| | | } |
| | | |
| | | .chatBg { |
| | | background: $input_background; |
| | | height: 36px; |
| | | padding: 8px 18px; |
| | | border-radius: 18px; |
| | | color: $text_color; |
| | | font-size: 14px; |
| | | border: 1px solid $chat-border; |
| | | } |
| | | .chatBg { |
| | | background: $input_background; |
| | | height: 36px; |
| | | padding: 8px 18px; |
| | | border-radius: 18px; |
| | | color: $text_color; |
| | | font-size: 14px; |
| | | border: 1px solid $chat-border; |
| | | } |
| | | |
| | | .right-chatBg { |
| | | position: relative; |
| | | background: $color_main; |
| | | color: $text_color; |
| | | .right-chatBg { |
| | | position: relative; |
| | | background: $color_main; |
| | | color: $text_color; |
| | | |
| | | &::after { |
| | | content: ''; |
| | | width: 0; |
| | | height: 0; |
| | | border-top: 5px solid transparent; |
| | | border-bottom: 5px solid transparent; |
| | | border-left: 10px solid $color_main; |
| | | position: absolute; |
| | | right: -8px; |
| | | top: 14px; |
| | | } |
| | | } |
| | | &::after { |
| | | content: ''; |
| | | width: 0; |
| | | height: 0; |
| | | border-top: 5px solid transparent; |
| | | border-bottom: 5px solid transparent; |
| | | border-left: 10px solid $color_main; |
| | | position: absolute; |
| | | right: -8px; |
| | | top: 14px; |
| | | } |
| | | } |
| | | |
| | | .left-chatBg { |
| | | background: $input_background; |
| | | } |
| | | .left-chatBg { |
| | | background: $input_background; |
| | | } |
| | | |
| | | .localKefu { |
| | | overflow: auto; |
| | | flex-direction: column; |
| | | } |
| | | .localKefu { |
| | | overflow: auto; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .van-nav-bar--fixed { |
| | | position: relative !important; |
| | | } |
| | | </style> |
| | | .van-nav-bar--fixed { |
| | | position: relative !important; |
| | | } |
| | | </style> |