commit
18c7693ed6
|
@ -63,48 +63,14 @@ export const useMallKefuStore = defineStore('mall-kefu', {
|
|||
}
|
||||
},
|
||||
conversationSort() {
|
||||
// TODO @puhui999:1)逻辑上,先按照置顶、再按照最后消息时间;2)感觉写的有一丢丢小复杂,发给大模型,看看有没可能简化哈。
|
||||
this.conversationList.sort((obj1, obj2) => {
|
||||
// 如果 obj1.adminPinned 为 true,obj2.adminPinned 为 false,obj1 应该排在前面
|
||||
if (obj1.adminPinned && !obj2.adminPinned) {
|
||||
return -1
|
||||
// 按置顶属性和最后消息时间排序
|
||||
this.conversationList.sort((a, b) => {
|
||||
// 按照置顶排序,置顶的会在前面
|
||||
if (a.adminPinned !== b.adminPinned) {
|
||||
return a.adminPinned ? -1 : 1
|
||||
}
|
||||
// 如果 obj1.adminPinned 为 false,obj2.adminPinned 为 true,obj2 应该排在前面
|
||||
if (!obj1.adminPinned && obj2.adminPinned) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 和 obj2.adminPinned 都为 true,比较 adminUnreadMessageCount 的值
|
||||
if (obj1.adminPinned && obj2.adminPinned) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 和 obj2.adminPinned 都为 false,比较 adminUnreadMessageCount 的值
|
||||
if (!obj1.adminPinned && !obj2.adminPinned) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 为 true,obj2.adminPinned 为 true,且 b 都大于 0,比较 adminUnreadMessageCount 的值
|
||||
if (
|
||||
obj1.adminPinned &&
|
||||
obj2.adminPinned &&
|
||||
obj1.adminUnreadMessageCount > 0 &&
|
||||
obj2.adminUnreadMessageCount > 0
|
||||
) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 为 false,obj2.adminPinned 为 false,且 b 都大于 0,比较 adminUnreadMessageCount 的值
|
||||
if (
|
||||
!obj1.adminPinned &&
|
||||
!obj2.adminPinned &&
|
||||
obj1.adminUnreadMessageCount > 0 &&
|
||||
obj2.adminUnreadMessageCount > 0
|
||||
) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
return 0
|
||||
// 按照最后消息时间排序,最近的会在前面
|
||||
return (b.lastMessageTime as unknown as number) - (a.lastMessageTime as unknown as number)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { toNumber } from 'lodash-es'
|
||||
import {toNumber} from 'lodash-es'
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<el-aside class="kefu p-5px h-100%" width="260px">
|
||||
<div class="color-[#999] font-bold my-10px"
|
||||
>会话记录({{ kefuStore.getConversationList.length }})
|
||||
<div class="color-[#999] font-bold my-10px">
|
||||
会话记录({{ kefuStore.getConversationList.length }})
|
||||
</div>
|
||||
<div
|
||||
v-for="item in kefuStore.getConversationList"
|
||||
|
@ -78,6 +78,7 @@ import { formatPast } from '@/utils/formatTime'
|
|||
import { KeFuMessageContentTypeEnum } from './tools/constants'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useMallKefuStore } from '@/store/modules/mall/kefu'
|
||||
import { jsonParse } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'KeFuConversationList' })
|
||||
|
||||
|
@ -118,7 +119,7 @@ const getConversationDisplayText = computed(
|
|||
case KeFuMessageContentTypeEnum.VOICE:
|
||||
return '[语音消息]'
|
||||
case KeFuMessageContentTypeEnum.TEXT:
|
||||
return replaceEmoji(lastMessageContent)
|
||||
return replaceEmoji(jsonParse(lastMessageContent).text || lastMessageContent)
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<MessageItem :message="item">
|
||||
<template v-if="KeFuMessageContentTypeEnum.TEXT === item.contentType">
|
||||
<div
|
||||
v-dompurify-html="replaceEmoji(item.content)"
|
||||
v-dompurify-html="replaceEmoji(getMessageContent(item).text || item.content)"
|
||||
class="flex items-center"
|
||||
></div>
|
||||
</template>
|
||||
|
@ -62,8 +62,8 @@
|
|||
<el-image
|
||||
v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
||||
:initial-index="0"
|
||||
:preview-src-list="[item.content]"
|
||||
:src="item.content"
|
||||
:preview-src-list="[getMessageContent(item).picUrl || item.content]"
|
||||
:src="getMessageContent(item).picUrl || item.content"
|
||||
class="w-200px"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
|
@ -75,12 +75,11 @@
|
|||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
||||
:picUrl="getMessageContent(item).picUrl"
|
||||
:price="getMessageContent(item).price"
|
||||
:skuText="getMessageContent(item).introduction"
|
||||
:sales-count="getMessageContent(item).salesCount"
|
||||
:spuId="getMessageContent(item).spuId"
|
||||
:stock="getMessageContent(item).stock"
|
||||
:title="getMessageContent(item).spuName"
|
||||
:titleWidth="400"
|
||||
class="max-w-70%"
|
||||
priceColor="#FF3000"
|
||||
class="max-w-300px"
|
||||
/>
|
||||
</MessageItem>
|
||||
<!-- 订单消息 -->
|
||||
|
@ -245,6 +244,7 @@ const getNewMessageList = async (val: KeFuConversationRespVO) => {
|
|||
total.value = messageList.value.length || 0
|
||||
loadHistory.value = false
|
||||
refreshContent.value = false
|
||||
skipGetMessageList.value = false
|
||||
// 2.2 设置会话相关属性
|
||||
conversation.value = val
|
||||
queryParams.conversationId = val.id
|
||||
|
@ -268,7 +268,7 @@ const handleSendPicture = async (picUrl: string) => {
|
|||
const msg = {
|
||||
conversationId: conversation.value.id,
|
||||
contentType: KeFuMessageContentTypeEnum.IMAGE,
|
||||
content: picUrl
|
||||
content: JSON.stringify({ picUrl })
|
||||
}
|
||||
await sendMessage(msg)
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ const handleSendMessage = async (event: any) => {
|
|||
const msg = {
|
||||
conversationId: conversation.value.id,
|
||||
contentType: KeFuMessageContentTypeEnum.TEXT,
|
||||
content: message.value
|
||||
content: JSON.stringify({ text: message.value })
|
||||
}
|
||||
await sendMessage(msg)
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
|||
|
||||
&-content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
|
@ -24,7 +24,22 @@
|
|||
交易订单
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="kefu-content">
|
||||
<el-main class="kefu-content p-10px!">
|
||||
<div v-if="!isEmpty(conversation)" v-loading="loading">
|
||||
<!-- 基本信息 -->
|
||||
<UserBasicInfo v-if="activeTab === '会员信息'" :user="user" mode="kefu">
|
||||
<template #header>
|
||||
<CardTitle title="基本信息" />
|
||||
</template>
|
||||
</UserBasicInfo>
|
||||
<!-- 账户信息 -->
|
||||
<el-card v-if="activeTab === '会员信息'" class="h-full mt-10px" shadow="never">
|
||||
<template #header>
|
||||
<CardTitle title="账户信息" />
|
||||
</template>
|
||||
<UserAccountInfo :column="1" :user="user" :wallet="wallet" />
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-show="!isEmpty(conversation)">
|
||||
<el-scrollbar ref="scrollbarRef" always @scroll="handleScroll">
|
||||
<!-- 最近浏览 -->
|
||||
|
@ -45,11 +60,17 @@ import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
|||
import { isEmpty } from '@/utils/is'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar/index'
|
||||
import { CardTitle } from '@/components/Card'
|
||||
import UserBasicInfo from '@/views/member/user/detail/UserBasicInfo.vue'
|
||||
import UserAccountInfo from '@/views/member/user/detail/UserAccountInfo.vue'
|
||||
import * as UserApi from '@/api/member/user'
|
||||
import * as WalletApi from '@/api/pay/wallet/balance'
|
||||
|
||||
defineOptions({ name: 'MemberBrowsingHistory' })
|
||||
|
||||
const activeTab = ref('会员信息')
|
||||
const tabActivation = computed(() => (tab: string) => activeTab.value === tab)
|
||||
|
||||
/** tab 切换 */
|
||||
const productBrowsingHistoryRef = ref<InstanceType<typeof ProductBrowsingHistory>>()
|
||||
const orderBrowsingHistoryRef = ref<InstanceType<typeof OrderBrowsingHistory>>()
|
||||
|
@ -63,6 +84,8 @@ const handleClick = async (tab: string) => {
|
|||
const getHistoryList = async () => {
|
||||
switch (activeTab.value) {
|
||||
case '会员信息':
|
||||
await getUserData()
|
||||
await getUserWallet()
|
||||
break
|
||||
case '最近浏览':
|
||||
await productBrowsingHistoryRef.value?.getHistoryList(conversation.value)
|
||||
|
@ -110,6 +133,37 @@ const handleScroll = debounce(() => {
|
|||
loadMore()
|
||||
}
|
||||
}, 200)
|
||||
|
||||
/* 用户钱包相关信息 */
|
||||
const WALLET_INIT_DATA = {
|
||||
balance: 0,
|
||||
totalExpense: 0,
|
||||
totalRecharge: 0
|
||||
} as WalletApi.WalletVO // 钱包初始化数据
|
||||
const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA) // 钱包信息
|
||||
|
||||
/** 查询用户钱包信息 */
|
||||
const getUserWallet = async () => {
|
||||
if (!conversation.value.userId) {
|
||||
wallet.value = WALLET_INIT_DATA
|
||||
return
|
||||
}
|
||||
const params = { userId: conversation.value.userId }
|
||||
wallet.value = (await WalletApi.getWallet(params)) || WALLET_INIT_DATA
|
||||
}
|
||||
|
||||
const loading = ref(true) // 加载中
|
||||
const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
|
||||
|
||||
/** 获得用户 */
|
||||
const getUserData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
user.value = await UserApi.getUser(conversation.value.userId)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<ProductItem
|
||||
v-for="item in list"
|
||||
:spu-id="item.spuId"
|
||||
:key="item.id"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
:skuText="item.introduction"
|
||||
:sales-count="item.salesCount"
|
||||
:spu-id="item.spuId"
|
||||
:stock="item.stock"
|
||||
:title="item.spuName"
|
||||
:titleWidth="400"
|
||||
class="mb-10px"
|
||||
priceColor="#FF3000"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
</div>
|
||||
<div v-for="item in getMessageContent.items" :key="item.id" class="border-bottom">
|
||||
<ProductItem
|
||||
:spu-id="item.spuId"
|
||||
:num="item.count"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
:skuText="item.properties.map((property: any) => property.valueName).join(' ')"
|
||||
:spu-id="item.spuId"
|
||||
:title="item.spuName"
|
||||
/>
|
||||
</div>
|
||||
|
@ -112,14 +112,14 @@ function formatOrderStatus(order: any) {
|
|||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
border: 1px var(--el-border-color) solid;
|
||||
background-color: var(--app-content-bg-color);
|
||||
background-color: rgba(128, 128, 128, 0.5); // 透明色,暗黑模式下也能体现
|
||||
|
||||
.order-card-header {
|
||||
height: 28px;
|
||||
font-weight: bold;
|
||||
|
||||
.order-no {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
|
||||
span {
|
||||
&:hover {
|
||||
|
@ -128,27 +128,30 @@ function formatOrderStatus(order: any) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-state {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.pay-box {
|
||||
padding-top: 10px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
||||
.discounts-title {
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.discounts-money {
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
color: #999;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.pay-color {
|
||||
font-size: 13px;
|
||||
color: var(--left-menu-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,51 +1,27 @@
|
|||
<template>
|
||||
<div @click.stop="openDetail(props.spuId)" style="cursor: pointer;">
|
||||
<div>
|
||||
<slot name="top"></slot>
|
||||
<div class="product-warp" style="cursor: pointer" @click.stop="openDetail(spuId)">
|
||||
<!-- 左侧商品图片-->
|
||||
<div class="product-warp-left mr-24px">
|
||||
<el-image
|
||||
:initial-index="0"
|
||||
:preview-src-list="[picUrl]"
|
||||
:src="picUrl"
|
||||
class="product-warp-left-img"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
:style="[{ borderRadius: radius + 'px', marginBottom: marginBottom + 'px' }]"
|
||||
class="ss-order-card-warp flex items-stretch justify-between bg-white"
|
||||
>
|
||||
<div class="img-box mr-24px">
|
||||
<el-image
|
||||
:initial-index="0"
|
||||
:preview-src-list="[picUrl]"
|
||||
:src="picUrl"
|
||||
class="order-img"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
@click.stop
|
||||
/>
|
||||
<!-- 右侧商品信息 -->
|
||||
<div class="product-warp-right">
|
||||
<div class="description">{{ title }}</div>
|
||||
<div class="my-5px">
|
||||
<span class="mr-20px">库存: {{ stock || 0 }}</span>
|
||||
<span>销量: {{ salesCount || 0 }}</span>
|
||||
</div>
|
||||
<div
|
||||
:style="[{ width: titleWidth ? titleWidth + 'px' : '' }]"
|
||||
class="box-right flex flex-col justify-between"
|
||||
>
|
||||
<div v-if="title" class="title-text ss-line-2">{{ title }}</div>
|
||||
<div v-if="skuString" class="spec-text mt-8px mb-12px">{{ skuString }}</div>
|
||||
<div class="groupon-box">
|
||||
<slot name="groupon"></slot>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-if="price && Number(price) > 0"
|
||||
:style="[{ color: priceColor }]"
|
||||
class="price-text flex items-center"
|
||||
>
|
||||
¥{{ fenToYuan(price) }}
|
||||
</div>
|
||||
<div v-if="num" class="total-text flex items-center">x {{ num }}</div>
|
||||
<slot name="priceSuffix"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-box">
|
||||
<slot name="tool"></slot>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="rightBottom"></slot>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="price">¥{{ fenToYuan(price) }}</span>
|
||||
<el-button size="small" text type="primary">详情</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,7 +33,7 @@ import { fenToYuan } from '@/utils'
|
|||
const { push } = useRouter()
|
||||
|
||||
defineOptions({ name: 'ProductItem' })
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
spuId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
|
@ -70,134 +46,70 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: ''
|
||||
},
|
||||
titleWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
skuText: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
price: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
priceColor: {
|
||||
type: [String],
|
||||
default: ''
|
||||
},
|
||||
num: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
score: {
|
||||
salesCount: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
radius: {
|
||||
type: [String],
|
||||
default: ''
|
||||
},
|
||||
marginBottom: {
|
||||
type: [String],
|
||||
stock: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
/** SKU 展示字符串 */
|
||||
const skuString = computed(() => {
|
||||
if (!props.skuText) {
|
||||
return ''
|
||||
}
|
||||
if (typeof props.skuText === 'object') {
|
||||
return props.skuText.join(',')
|
||||
}
|
||||
return props.skuText
|
||||
})
|
||||
|
||||
/** 查看商品详情 */
|
||||
const openDetail = (spuId: number) => {
|
||||
console.log(props.spuId)
|
||||
push({ name: 'ProductSpuDetail', params: { id: spuId } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ss-order-card-warp {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px var(--el-border-color) solid;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
.img-box {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.order-img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.box-right {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.tool-box {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.spec-text {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.total-text {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: #999999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ss-line {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
.product-warp {
|
||||
width: 100%;
|
||||
background-color: rgba(128, 128, 128, 0.5); // 透明色,暗黑模式下也能体现
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
|
||||
&-1 {
|
||||
-webkit-line-clamp: 1;
|
||||
&-left {
|
||||
width: 70px;
|
||||
|
||||
&-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-2 {
|
||||
-webkit-line-clamp: 2;
|
||||
&-right {
|
||||
flex: 1;
|
||||
|
||||
.description {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1; /* 显示一行 */
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #ff3000;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,6 +16,7 @@ import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
|||
import { getRefreshToken } from '@/utils/auth'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { useMallKefuStore } from '@/store/modules/mall/kefu'
|
||||
import { jsonParse } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'KeFu' })
|
||||
|
||||
|
@ -30,6 +31,7 @@ const server = ref(
|
|||
) // WebSocket 服务地址
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
// TODO puhui999: websocket 连接有点问题收不到消息 🤣
|
||||
const { data, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: true,
|
||||
heartbeat: true
|
||||
|
@ -45,9 +47,9 @@ watchEffect(() => {
|
|||
if (data.value === 'pong') {
|
||||
return
|
||||
}
|
||||
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
console.log(jsonMessage)
|
||||
const type = jsonMessage.type
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
|
@ -57,7 +59,6 @@ watchEffect(() => {
|
|||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
|
||||
const message = JSON.parse(jsonMessage.content)
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
kefuStore.updateConversation(message.conversationId)
|
||||
// 刷新消息列表
|
||||
keFuChatBoxRef.value?.refreshMessageList(message)
|
||||
|
@ -66,7 +67,7 @@ watchEffect(() => {
|
|||
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
|
||||
// 更新会话已读
|
||||
kefuStore.updateConversationStatus(JSON.parse(jsonMessage.content)?.id)
|
||||
kefuStore.updateConversationStatus(jsonParse(jsonMessage.content))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions :column="column">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="svg-icon:member_level" label=" 等级 " />
|
||||
|
@ -50,7 +50,9 @@ import * as UserApi from '@/api/member/user'
|
|||
import * as WalletApi from '@/api/pay/wallet/balance'
|
||||
import { fenToYuan } from '@/utils'
|
||||
|
||||
defineProps<{ user: UserApi.UserVO; wallet: WalletApi.WalletVO }>() // 用户信息
|
||||
withDefaults(defineProps<{ user: UserApi.UserVO; wallet: WalletApi.WalletVO; column?: number }>(), {
|
||||
column: 2
|
||||
}) // 用户信息
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.cell-item {
|
||||
|
|
|
@ -3,80 +3,141 @@
|
|||
<template #header>
|
||||
<slot name="header"></slot>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-row v-if="mode === 'member'">
|
||||
<el-col :span="4">
|
||||
<ElAvatar shape="square" :size="140" :src="user.avatar || undefined" />
|
||||
<ElAvatar :size="140" :src="user.avatar || undefined" shape="square" />
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="用户名" icon="ep:user" />
|
||||
<descriptions-item-label icon="ep:user" label="用户名" />
|
||||
</template>
|
||||
{{ user.name || '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="昵称" icon="ep:user" />
|
||||
<descriptions-item-label icon="ep:user" label="昵称" />
|
||||
</template>
|
||||
{{ user.nickname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">
|
||||
<template #label>
|
||||
<descriptions-item-label label="手机号" icon="ep:phone" />
|
||||
<descriptions-item-label icon="ep:phone" label="手机号" />
|
||||
</template>
|
||||
{{ user.mobile }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="性别" icon="fa:mars-double" />
|
||||
<descriptions-item-label icon="fa:mars-double" label="性别" />
|
||||
</template>
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="user.sex" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="所在地" icon="ep:location" />
|
||||
<descriptions-item-label icon="ep:location" label="所在地" />
|
||||
</template>
|
||||
{{ user.areaName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="注册 IP" icon="ep:position" />
|
||||
<descriptions-item-label icon="ep:position" label="注册 IP" />
|
||||
</template>
|
||||
{{ user.registerIp }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="生日" icon="fa:birthday-cake" />
|
||||
<descriptions-item-label icon="fa:birthday-cake" label="生日" />
|
||||
</template>
|
||||
{{ user.birthday ? formatDate(user.birthday) : '空' }}
|
||||
{{ user.birthday ? formatDate(user.birthday as any) : '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="注册时间" icon="ep:calendar" />
|
||||
<descriptions-item-label icon="ep:calendar" label="注册时间" />
|
||||
</template>
|
||||
{{ user.createTime ? formatDate(user.createTime) : '空' }}
|
||||
{{ user.createTime ? formatDate(user.createTime as any) : '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label label="最后登录时间" icon="ep:calendar" />
|
||||
<descriptions-item-label icon="ep:calendar" label="最后登录时间" />
|
||||
</template>
|
||||
{{ user.loginDate ? formatDate(user.loginDate) : '空' }}
|
||||
{{ user.loginDate ? formatDate(user.loginDate as any) : '空' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template v-if="mode === 'kefu'">
|
||||
<ElAvatar :size="140" :src="user.avatar || undefined" shape="square" />
|
||||
<el-descriptions :column="1">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:user" label="用户名" />
|
||||
</template>
|
||||
{{ user.name || '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:user" label="昵称" />
|
||||
</template>
|
||||
{{ user.nickname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:phone" label="手机号" />
|
||||
</template>
|
||||
{{ user.mobile }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="fa:mars-double" label="性别" />
|
||||
</template>
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="user.sex" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:location" label="所在地" />
|
||||
</template>
|
||||
{{ user.areaName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:position" label="注册 IP" />
|
||||
</template>
|
||||
{{ user.registerIp }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="fa:birthday-cake" label="生日" />
|
||||
</template>
|
||||
{{ user.birthday ? formatDate(user.birthday as any) : '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:calendar" label="注册时间" />
|
||||
</template>
|
||||
{{ user.createTime ? formatDate(user.createTime as any) : '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<descriptions-item-label icon="ep:calendar" label="最后登录时间" />
|
||||
</template>
|
||||
{{ user.loginDate ? formatDate(user.loginDate as any) : '空' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as UserApi from '@/api/member/user'
|
||||
import { DescriptionsItemLabel } from '@/components/Descriptions/index'
|
||||
|
||||
const { user } = defineProps<{ user: UserApi.UserVO }>()
|
||||
withDefaults(defineProps<{ user: UserApi.UserVO; mode?: string }>(), {
|
||||
mode: 'member'
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
Loading…
Reference in New Issue