mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-13 04:18:55 +08:00
refactor(im): 重构聊天面板UI组件并修复多处交互问题
- 将头像组件从 dicebear 外链改为本地首字+底色生成,新增 avatarColor 工具函数 - 重构订单卡片(footPrint.vue)布局,优化商品与订单信息展示结构 - 重构店铺详情卡片(storeDetail.vue),统一信息行与评分行样式 - 移除消息气泡中的昵称时间行,调整气泡背景色与头像内边距 - 修复 face 变量名与全局组件 <face> 冲突导致头像URL被当作标签渲染 - 将 unload 事件监听替换为 pagehide,兼容 Chrome 弃用策略 - 修复搜索关键词未转义正则元字符导致输入特殊字符时抛异常 - 修复 WebSocket onMessage 对无 text 字段的推送消息误调用 JSON.parse - 将 setup 中同步调用全局 mixin 方法移至 onMounted,避免代理未就绪 - 商品/订单图片统一添加 onerror 默认图兜底 - 聊天列表消息预览支持 emoji 图片渲染(textReplaceEmoji) - 搜索框增加 clearable 属性及"没有找到相关好友"空态提示 - 锁定 element-plus 版本为 2.14.1 防止自动升级引入兼容问题 - 日期格式化分隔符由斜杠改为横杠 - 提现设置页新增提现规则说明字段,满足微信审核公示要求
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"babel-plugin-prismjs": "^2.0.1",
|
"babel-plugin-prismjs": "^2.0.1",
|
||||||
"core-js": "^3.49.0",
|
"core-js": "^3.49.0",
|
||||||
"element-plus": "^2.14.1",
|
"element-plus": "2.14.1",
|
||||||
"js-audio-recorder": "^1.0.6",
|
"js-audio-recorder": "^1.0.6",
|
||||||
"js-base64": "^2.5.1",
|
"js-base64": "^2.5.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ function unloadHandler (e) {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setRefreshView(refreshView);
|
setRefreshView(refreshView);
|
||||||
window.addEventListener('beforeunload', e => beforeunloadHandler(e))
|
window.addEventListener('beforeunload', beforeunloadHandler)
|
||||||
window.addEventListener('unload', e => unloadHandler(e))
|
// unload 事件已被 Chrome 弃用(Permissions-Policy 禁止),改用 pagehide
|
||||||
|
window.addEventListener('pagehide', unloadHandler)
|
||||||
});
|
});
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('beforeunload', e => beforeunloadHandler(e))
|
window.removeEventListener('beforeunload', beforeunloadHandler)
|
||||||
window.removeEventListener('unload', e => unloadHandler(e))
|
window.removeEventListener('pagehide', unloadHandler)
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-tabs v-model="activeName" @tab-click="handleClick" type="card" :stretch=true>
|
<el-tabs v-model="activeName" @tab-click="handleClick" :stretch=true>
|
||||||
<el-tab-pane :label="toUser.storeFlag ? '正在咨询' : '他的足迹'" name="history">
|
<el-tab-pane :label="toUser.storeFlag ? '正在咨询' : '他的足迹'" name="history">
|
||||||
<div>
|
<div>
|
||||||
<GoodsLink :goodsDetail="goodsDetail" v-if="toUser.userId === goodsDetail.storeId"
|
<GoodsLink :goodsDetail="goodsDetail" v-if="toUser.userId === goodsDetail.storeId"
|
||||||
@@ -306,4 +306,20 @@ function formatDateToString(date) {
|
|||||||
:deep(.el-tabs__header) {
|
:deep(.el-tabs__header) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 高度链:tabs 撑满面板,pane 高度传递给内部滚动容器 */
|
||||||
|
:deep(.el-tabs) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__content) {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tab-pane) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<aside class="avatar-column">
|
<aside class="avatar-column">
|
||||||
<div class="avatar-box" v-if="item.float == 'right'">
|
<div class="avatar-box" v-if="item.float == 'right'">
|
||||||
<face :text="face" v-if="face"></face>
|
<face :text="userFace" v-if="userFace"></face>
|
||||||
<face-null :text="name" v-else></face-null>
|
<face-null :text="name" v-else></face-null>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-box" v-if="item.float == 'left'">
|
<div class="avatar-box" v-if="item.float == 'left'">
|
||||||
@@ -53,14 +53,10 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<main class="main-column">
|
<main class="main-column">
|
||||||
<div class="talk-content">
|
<div class="talk-content">
|
||||||
<span class="nickname">
|
|
||||||
{{ item.float == "right" ? name : toUser.name }} |
|
|
||||||
{{ unixToDate(item.createTime, "MM月dd日 hh:mm") }}
|
|
||||||
</span>
|
|
||||||
<div class="flex flex-a-c">
|
<div class="flex flex-a-c">
|
||||||
<legacy-el-icon name="el-icon-refresh-left" class="again main-color" @click="againSendMessage(item)" v-if="item.webSocketStatus" />
|
<legacy-el-icon name="el-icon-refresh-left" class="again main-color" @click="againSendMessage(item)" v-if="item.webSocketStatus" />
|
||||||
<!-- 文本消息 -->
|
<!-- 文本消息 -->
|
||||||
<div v-if="item.messageType == 'MESSAGE'" style="background-color: #d0e9ff;color: black;"
|
<div v-if="item.messageType == 'MESSAGE'"
|
||||||
class="text-message" :class="{
|
class="text-message" :class="{
|
||||||
left: item.float == 'left',
|
left: item.float == 'left',
|
||||||
right: item.float == 'right',
|
right: item.float == 'right',
|
||||||
@@ -79,7 +75,7 @@
|
|||||||
}">
|
}">
|
||||||
<div class="base" @click="linkToGoods(item.text.goodsId, item.text.id)">
|
<div class="base" @click="linkToGoods(item.text.goodsId, item.text.id)">
|
||||||
<div>
|
<div>
|
||||||
<img :src="item.text.thumbnail" class="image" />
|
<img :src="item.text.thumbnail" :onerror="defaultAvatar" class="image" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="goods_name">
|
<div class="goods_name">
|
||||||
@@ -106,7 +102,7 @@
|
|||||||
<div class="goods-shared-box">
|
<div class="goods-shared-box">
|
||||||
|
|
||||||
<div class="goods-item" v-for="(order,orderIndex) in item.text.orderItems" :key="orderIndex">
|
<div class="goods-item" v-for="(order,orderIndex) in item.text.orderItems" :key="orderIndex">
|
||||||
<img :src="order.image" style="height: 100px;width: 100px;" />
|
<img :src="order.image" :onerror="defaultAvatar" style="height: 100px;width: 100px;" />
|
||||||
<div>
|
<div>
|
||||||
<span class="orderGoodsName">{{ order.name }}</span>
|
<span class="orderGoodsName">{{ order.name }}</span>
|
||||||
<div class="goods-item-price">
|
<div class="goods-item-price">
|
||||||
@@ -212,6 +208,7 @@ import SocketInstance from "@/im-server/socket-instance";
|
|||||||
import { SvgMentionDown } from "@/core/icons";
|
import { SvgMentionDown } from "@/core/icons";
|
||||||
import { formatTime, parseTime, copyTextToClipboard } from "@/utils/functions";
|
import { formatTime, parseTime, copyTextToClipboard } from "@/utils/functions";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { useAppStore } from "@/stores/app";
|
||||||
import { useTalkStore } from "@/stores/talk";
|
import { useTalkStore } from "@/stores/talk";
|
||||||
import { useNotifyStore } from "@/stores/notify";
|
import { useNotifyStore } from "@/stores/notify";
|
||||||
import { useSettingsStore } from "@/stores/settings";
|
import { useSettingsStore } from "@/stores/settings";
|
||||||
@@ -259,6 +256,7 @@ const emit = defineEmits(["close-talk"]);
|
|||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
const talkStore = useTalkStore();
|
const talkStore = useTalkStore();
|
||||||
const notifyStore = useNotifyStore();
|
const notifyStore = useNotifyStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
@@ -306,13 +304,16 @@ const panelHeader = ref(null);
|
|||||||
|
|
||||||
const talkItems = computed(() => talkStore.talkItems);
|
const talkItems = computed(() => talkStore.talkItems);
|
||||||
const name = computed(() => userStore.name);
|
const name = computed(() => userStore.name);
|
||||||
const face = computed(() => userStore.face);
|
// 不能命名为 face:会遮蔽 <face> 组件解析,头像 URL 会被当作标签名渲染
|
||||||
|
const userFace = computed(() => userStore.face);
|
||||||
const unreadMessage = computed(() => talkStore.unreadMessage);
|
const unreadMessage = computed(() => talkStore.unreadMessage);
|
||||||
const inputEvent = computed(() => notifyStore.inputEvent);
|
const inputEvent = computed(() => notifyStore.inputEvent);
|
||||||
const id = computed(() => userStore.id);
|
const id = computed(() => userStore.id);
|
||||||
const records = computed(() => dialogueStore.records);
|
const records = computed(() => dialogueStore.records);
|
||||||
const index_name = computed(() => dialogueStore.index_name);
|
const index_name = computed(() => dialogueStore.index_name);
|
||||||
const toUser = computed(() => userStore.toUser);
|
const toUser = computed(() => userStore.toUser);
|
||||||
|
// 商品/订单图片加载失败时的默认图(onerror 属性值)
|
||||||
|
const defaultAvatar = computed(() => appStore.defaultAvatar);
|
||||||
|
|
||||||
const sendTime = formatTime;
|
const sendTime = formatTime;
|
||||||
|
|
||||||
@@ -1147,7 +1148,7 @@ function talkPanelScrollBottom() {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
order: 1;
|
order: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding-top: 25px;
|
padding-top: 8px;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
@@ -1168,7 +1169,7 @@ function talkPanelScrollBottom() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
order: 2;
|
order: 2;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding-top: 22px;
|
padding-top: 5px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1206,13 +1207,6 @@ function talkPanelScrollBottom() {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.nickname {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #a7a0a0;
|
|
||||||
margin: -4px 0 4px -8px;
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -1250,8 +1244,8 @@ function talkPanelScrollBottom() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bg-left-color: #f5f5f5;
|
@bg-left-color: #f2f2f2;
|
||||||
@bg-right-color: #ffffff;
|
@bg-right-color: #409eff;
|
||||||
|
|
||||||
.text-message {
|
.text-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="box" :style="{height:toUser.storeFlag ? '858px' : '815px'}" @scroll="scrollBottom">
|
<div class="box" @scroll="scrollBottom">
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<el-tabs v-model="activeName" @tab-click="handleClick" :stretch=true>
|
<el-tabs v-model="activeName" @tab-click="handleClick" type="card" :stretch=true>
|
||||||
<el-tab-pane label="最近浏览" name="goods">
|
<el-tab-pane label="最近浏览" name="goods">
|
||||||
<dl v-if='list.length' class='base-list'>
|
<dl v-if='list.length' class='base-list'>
|
||||||
<dd :key="index" @click="linkToGoods(item.goodsId, item.id)" v-for="(item,index) in list">
|
<dd :key="index" @click="linkToGoods(item.goodsId, item.id)" v-for="(item,index) in list">
|
||||||
<div class="base">
|
<div class="base">
|
||||||
<div>
|
<div>
|
||||||
<img style="width: 60px; height: 60px;margin-left:20px;box-sizing: border-box;" :src="item.thumbnail"
|
<img style="width: 60px; height: 60px;margin-left:20px;box-sizing: border-box;" :src="item.thumbnail"
|
||||||
class="image" />
|
:onerror="defaultAvatar" class="image" />
|
||||||
</div>
|
</div>
|
||||||
<div class="recent_views">
|
<div class="recent_views">
|
||||||
<a class="goods_name">{{ item.goodsName }}</a>
|
<a class="goods_name">{{ item.goodsName }}</a>
|
||||||
@@ -35,24 +35,8 @@
|
|||||||
<dl class='base-order-list' v-if='orderList.length'>
|
<dl class='base-order-list' v-if='orderList.length'>
|
||||||
<dd v-for="(item, index) in orderList" :key="index">
|
<dd v-for="(item, index) in orderList" :key="index">
|
||||||
<div class="order-list">
|
<div class="order-list">
|
||||||
<div class="order-top order-padding">
|
<div class="order-head">
|
||||||
<span class="order-sn" @click="linkToOrders(item.sn)">订单号:{{ item.sn }}</span>
|
<span class="order-sn" :title="item.sn" @click="linkToOrders(item.sn)">订单号:{{ item.sn }}</span>
|
||||||
</div>
|
|
||||||
<div class="order-section order-padding">
|
|
||||||
<div class="order-items" v-for="(order,orderIndex) in item.orderItems" :key="orderIndex">
|
|
||||||
<img :src="order.image" alt="">
|
|
||||||
<span class="order-goods-name" @click="linkToOrders(item.sn)"> {{ order.name }}</span>
|
|
||||||
<span class="price">{{ $filters.unitPrice(order.goodsPrice, "¥") }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- <img :src="item.groupImages" alt=""> -->
|
|
||||||
|
|
||||||
<div class="order-btn ">
|
|
||||||
<el-button class="store-button" v-if="item.btnHide == 1 && toUser.storeFlag"
|
|
||||||
size="small" @click="submitSendOrderMessage(item, index)" plain>发送</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="order-footer order-padding">
|
|
||||||
<span></span>
|
|
||||||
<el-tag size="small" :type="col[item.orderStatus]">{{
|
<el-tag size="small" :type="col[item.orderStatus]">{{
|
||||||
item.orderStatus == 'STAY_PICKED_UP' ? '待自提'
|
item.orderStatus == 'STAY_PICKED_UP' ? '待自提'
|
||||||
: item.orderStatus == 'CANCELLED' ? '已取消' : item.orderStatus == 'UNPAID' ? '未付款' : item.orderStatus
|
: item.orderStatus == 'CANCELLED' ? '已取消' : item.orderStatus == 'UNPAID' ? '未付款' : item.orderStatus
|
||||||
@@ -62,9 +46,18 @@
|
|||||||
''
|
''
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="order-items" v-for="(order,orderIndex) in item.orderItems" :key="orderIndex">
|
||||||
|
<img :src="order.image" :onerror="defaultAvatar" alt="">
|
||||||
|
<div class="order-item-info">
|
||||||
|
<span class="order-goods-name" :title="order.name" @click="linkToOrders(item.sn)">{{ order.name }}</span>
|
||||||
|
<span class="price">{{ $filters.unitPrice(order.goodsPrice, "¥") }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-foot" v-if="item.btnHide == 1 && toUser.storeFlag">
|
||||||
|
<el-button class="store-button" size="small" @click="submitSendOrderMessage(item, index)" plain>发送</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<div v-else class='no-more'>
|
<div v-else class='no-more'>
|
||||||
@@ -80,9 +73,11 @@
|
|||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted, ref } from "vue";
|
||||||
import { useTalkStore } from "@/stores/talk";
|
import { useTalkStore } from "@/stores/talk";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { useAppStore } from "@/stores/app";
|
||||||
|
|
||||||
const talkStore = useTalkStore();
|
const talkStore = useTalkStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const emit = defineEmits(["loadMore", "sendMessage"]);
|
const emit = defineEmits(["loadMore", "sendMessage"]);
|
||||||
|
|
||||||
@@ -118,6 +113,8 @@ const col = ref({
|
|||||||
const talkItems = computed(() => talkStore.talkItems);
|
const talkItems = computed(() => talkStore.talkItems);
|
||||||
const id = computed(() => userStore.id);
|
const id = computed(() => userStore.id);
|
||||||
const toUser = computed(() => userStore.toUser);
|
const toUser = computed(() => userStore.toUser);
|
||||||
|
// 图片加载失败时的默认图(onerror 属性值)
|
||||||
|
const defaultAvatar = computed(() => appStore.defaultAvatar);
|
||||||
|
|
||||||
//跳转订单列表
|
//跳转订单列表
|
||||||
|
|
||||||
@@ -204,7 +201,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
max-width: 362px;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -237,57 +236,51 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-list {
|
.order-list {
|
||||||
padding: 9px;
|
border: 1px solid #eee;
|
||||||
transition: 0.35s;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
padding: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #5a606b;
|
transition: border-color .2s;
|
||||||
box-sizing: border-box;
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #b3d3f9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-sn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.box::-webkit-scrollbar {
|
.box::-webkit-scrollbar {
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
}
|
}
|
||||||
.order-section {
|
|
||||||
margin: 10px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
img {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-footer {
|
.order-foot {
|
||||||
font-size: 12px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-goods-name {
|
|
||||||
width: 200px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
position: absolute;
|
|
||||||
margin-left: 10px;
|
|
||||||
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orderSn {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-btn {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
position: relative;
|
margin-top: 8px;
|
||||||
bottom: 55px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods_name {
|
.goods_name {
|
||||||
@@ -300,7 +293,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
:deep(.el-tabs__header ) {
|
:deep(.el-tabs__header ) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 362px;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@@ -329,10 +322,6 @@ onMounted(() => {
|
|||||||
// width: 350px;
|
// width: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-padding {
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base {
|
.base {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -354,12 +343,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.base:hover,.order-list:hover{
|
.base:hover{
|
||||||
background: #ededed;
|
background: #ededed;
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
>.review{
|
>.review{
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.goods-price{
|
.goods-price{
|
||||||
@@ -371,9 +360,6 @@ onMounted(() => {
|
|||||||
.separate {
|
.separate {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.order-sn{
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
// .el-tabs--card {
|
// .el-tabs--card {
|
||||||
// height: calc(100vh - 110px);
|
// height: calc(100vh - 110px);
|
||||||
// overflow-y: auto;
|
// overflow-y: auto;
|
||||||
@@ -383,17 +369,54 @@ onMounted(() => {
|
|||||||
// overflow-y: auto;
|
// overflow-y: auto;
|
||||||
// }
|
// }
|
||||||
.base-list{
|
.base-list{
|
||||||
padding-bottom: 120px;
|
padding: 10px 10px 12px;
|
||||||
}
|
}
|
||||||
.base-order-list{
|
.base-order-list{
|
||||||
padding-bottom: 100px;
|
padding: 10px 10px 12px;
|
||||||
}
|
|
||||||
.price{
|
|
||||||
color: red;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
.order-items{
|
.order-items{
|
||||||
margin-bottom: 10px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-goods-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #f23030;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
当前浏览
|
当前浏览
|
||||||
<div class="base">
|
<div class="base">
|
||||||
<div>
|
<div>
|
||||||
<img :src="goodsDetail.thumbnail" class="image" />
|
<img :src="goodsDetail.thumbnail" :onerror="defaultAvatar" class="image" />
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-left: 13px">
|
<div style="margin-left: 13px">
|
||||||
<a @click="linkToGoods(goodsDetail.goodsId, goodsDetail.id)"> {{ goodsDetail.goodsName }} </a>
|
<a @click="linkToGoods(goodsDetail.goodsId, goodsDetail.id)"> {{ goodsDetail.goodsName }} </a>
|
||||||
@@ -23,10 +23,12 @@
|
|||||||
import { computed, onMounted, nextTick, ref } from "vue";
|
import { computed, onMounted, nextTick, ref } from "vue";
|
||||||
import SocketInstance from "@/im-server/socket-instance";
|
import SocketInstance from "@/im-server/socket-instance";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { useAppStore } from "@/stores/app";
|
||||||
import { useDialogueStore } from "@/stores/dialogue";
|
import { useDialogueStore } from "@/stores/dialogue";
|
||||||
import { useTalkStore } from "@/stores/talk";
|
import { useTalkStore } from "@/stores/talk";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
const dialogueStore = useDialogueStore();
|
const dialogueStore = useDialogueStore();
|
||||||
const talkStore = useTalkStore();
|
const talkStore = useTalkStore();
|
||||||
|
|
||||||
@@ -44,6 +46,8 @@ const talkItems = computed(() => talkStore.talkItems);
|
|||||||
const id = computed(() => userStore.id);
|
const id = computed(() => userStore.id);
|
||||||
const index_name = computed(() => dialogueStore.index_name);
|
const index_name = computed(() => dialogueStore.index_name);
|
||||||
const toUser = computed(() => userStore.toUser);
|
const toUser = computed(() => userStore.toUser);
|
||||||
|
// 图片加载失败时的默认图(onerror 属性值)
|
||||||
|
const defaultAvatar = computed(() => appStore.defaultAvatar);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log(props.goodsDetail)
|
console.log(props.goodsDetail)
|
||||||
|
|||||||
@@ -1,100 +1,157 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="width: 362px;">
|
<div class="store-card">
|
||||||
<div class="base">
|
<div class="base">
|
||||||
<div>
|
<img :src="storeInfo.storeLogo" :onerror="onError" class="image" alt="" />
|
||||||
<img :src="storeInfo.storeLogo" class="image" />
|
|
||||||
</div>
|
|
||||||
<div class="store-detail">
|
<div class="store-detail">
|
||||||
<div>
|
<div class="store-name">
|
||||||
<div class="div-zoom">
|
<span class="name-text" :title="storeInfo.storeName">{{ storeInfo.storeName }}</span>
|
||||||
<span class="title_left_name"> 店铺名称:</span> <span class="title_right_name">{{ storeInfo.storeName }}</span>
|
<el-tag type="danger" v-if="storeInfo.selfOperated" size="small">自营</el-tag>
|
||||||
<el-tag style="margin-left: 10px;" type="danger" v-if="storeInfo.selfOperated" size="small">自营</el-tag>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="store-actions">
|
||||||
<span class="title_left_name"> 联系方式:</span> <span class="title_right_name">{{ storeInfo.memberName }}</span>
|
<el-button @click="linkToStore(storeInfo.id)" size="small" plain>进入店铺</el-button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<el-button class="store-button" @click="linkToStore(storeInfo.id)" size="small"
|
|
||||||
plain>进入店铺</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info-list">
|
||||||
|
<div class="info-row" v-for="row in infoRows" :key="row.label">
|
||||||
|
<span class="label">{{ row.label }}</span>
|
||||||
|
<span class="value" :title="row.value">{{ row.value || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="separate">店铺评分: <el-rate v-model="storeInfo.serviceScore" disabled show-score text-color="#ff9900"
|
<div class="rate-row" v-for="row in rates" :key="row.label">
|
||||||
score-template="{value}">
|
<span class="rate-label">{{ row.label }}</span>
|
||||||
</el-rate></div>
|
<el-rate :model-value="row.score" disabled show-score text-color="#ff9900" score-template="{value}" />
|
||||||
<div class="separate">服务评分: <el-rate v-model="storeInfo.descriptionScore" disabled show-score text-color="#ff9900"
|
</div>
|
||||||
score-template="{value}">
|
|
||||||
</el-rate></div>
|
|
||||||
<div class="separate">物流评分: <el-rate v-model="storeInfo.deliveryScore" disabled show-score text-color="#ff9900"
|
|
||||||
score-template="{value}">
|
|
||||||
</el-rate></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { defaultAvatar } from "@/assets/images";
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
storeInfo: {
|
storeInfo: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const value = ref(null);
|
const onError = "this.src='" + defaultAvatar + "'";
|
||||||
const colors = ref(['#99A9BF', '#F7BA2A', '#FF9900']);
|
|
||||||
|
const infoRows = computed(() => [
|
||||||
|
{ label: "店铺地址", value: props.storeInfo.storeAddressPath },
|
||||||
|
{ label: "详细地址", value: props.storeInfo.storeAddressDetail },
|
||||||
|
{ label: "店铺简介", value: props.storeInfo.storeDesc },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 与买家端字段口径一致:店铺评分=描述相符 descriptionScore,服务评分=serviceScore
|
||||||
|
const rates = computed(() => [
|
||||||
|
{ label: "店铺评分", score: Number(props.storeInfo.descriptionScore) || 0 },
|
||||||
|
{ label: "服务评分", score: Number(props.storeInfo.serviceScore) || 0 },
|
||||||
|
{ label: "物流评分", score: Number(props.storeInfo.deliveryScore) || 0 },
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.wrapper{
|
.store-card {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-top: 1px solid #f5eeee;
|
display: flex;
|
||||||
}
|
|
||||||
.title_left_name {
|
.image {
|
||||||
font-size: 12px;
|
height: 72px;
|
||||||
color: #999;
|
width: 72px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title_right_name {
|
.store-detail {
|
||||||
font-size: 12px;
|
flex: 1;
|
||||||
color: #343036;
|
min-width: 0;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-button {
|
|
||||||
background-color: white;
|
|
||||||
|
|
||||||
}
|
|
||||||
.store-detail{
|
|
||||||
margin-left: 13px;
|
margin-left: 13px;
|
||||||
display:flex;
|
display: flex;
|
||||||
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.base {
|
|
||||||
padding: 16px;
|
|
||||||
margin-top: 5px;
|
|
||||||
|
|
||||||
|
.store-name {
|
||||||
display: flex;
|
display: flex;
|
||||||
.image {
|
align-items: center;
|
||||||
height: 100px;
|
gap: 8px;
|
||||||
|
|
||||||
width: 100px
|
.name-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #343036;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
padding: 0 16px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #343036;
|
||||||
|
line-height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.separate {
|
.wrapper {
|
||||||
margin-top: 8px;
|
padding: 12px 16px 16px;
|
||||||
// display: inline;
|
border-top: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 12px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
color: #a1a6af;
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-label {
|
||||||
|
width: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #a1a6af;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<img :src="'https://avatars.dicebear.com/api/initials/'+text+'.svg?fontSize=38'" alt=""/>
|
<i class="face-initial" :style="{ backgroundColor: color }">{{ initial }}</i>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
import { computed } from "vue";
|
||||||
text:{
|
import { avatarColor } from "@/utils/functions";
|
||||||
type:null,
|
|
||||||
default:''
|
const props = defineProps({
|
||||||
}
|
text: {
|
||||||
})
|
type: null,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 无头像时用「底色 + 首字」本地生成,替代原来的 dicebear 外链
|
||||||
|
const initial = computed(() => String(props.text || "").slice(0, 1) || "?");
|
||||||
|
const color = computed(() => avatarColor(String(props.text || "")));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="less" scoped>
|
||||||
|
.face-initial {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #fff;
|
||||||
|
font-style: normal;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,20 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<img :src="text || 'https://avatars.dicebear.com/api/initials/'+name+'.svg?fontSize=38'" alt=""/>
|
<span class="face-wrap">
|
||||||
|
<img v-if="text" :src="text" :onerror="onError" alt="" />
|
||||||
|
<i v-else class="face-initial" :style="{ backgroundColor: color }">{{ initial }}</i>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
import { computed } from "vue";
|
||||||
text: {
|
import { avatarColor } from "@/utils/functions";
|
||||||
type: null,
|
import { defaultAvatar } from "@/assets/images";
|
||||||
default: ''
|
|
||||||
},
|
const props = defineProps({
|
||||||
name: {
|
text: {
|
||||||
type: null,
|
type: null,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
});
|
name: {
|
||||||
|
type: null,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 无头像时用「底色 + 首字」本地生成,替代原来的 dicebear 外链
|
||||||
|
const initial = computed(() => String(props.name || "").slice(0, 1) || "?");
|
||||||
|
const color = computed(() => avatarColor(String(props.name || "")));
|
||||||
|
const onError = "this.src='" + defaultAvatar + "'";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="less" scoped>
|
||||||
|
.face-wrap {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
</style>
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.face-initial {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #fff;
|
||||||
|
font-style: normal;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -273,22 +273,17 @@ class WsSocket {
|
|||||||
*/
|
*/
|
||||||
onMessage(evt) {
|
onMessage(evt) {
|
||||||
let result = this.onParse(evt);
|
let result = this.onParse(evt);
|
||||||
if (
|
// 部分推送(在线状态、系统通知等)没有 text 字段,不做商品/群名解析直接透传
|
||||||
this.onParse(evt).text.includes("goodsName") ||
|
const text = result.text;
|
||||||
this.onParse(evt).text.includes("groupName")
|
let params = {
|
||||||
) {
|
...result,
|
||||||
let params = {
|
text:
|
||||||
...this.onParse(evt),
|
typeof text === "string" &&
|
||||||
text: JSON.parse(this.onParse(evt).text),
|
(text.includes("goodsName") || text.includes("groupName"))
|
||||||
};
|
? JSON.parse(text)
|
||||||
this.onCallBacks["event_talk"](params);
|
: text,
|
||||||
} else {
|
};
|
||||||
let params = {
|
this.onCallBacks["event_talk"](params);
|
||||||
...this.onParse(evt),
|
|
||||||
text: this.onParse(evt).text,
|
|
||||||
};
|
|
||||||
this.onCallBacks["event_talk"](params);
|
|
||||||
}
|
|
||||||
// 指定推送消息
|
// 指定推送消息
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -390,6 +390,19 @@ export function hidePhone (phone) {
|
|||||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
|
return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名字生成稳定的头像底色(本地首字头像用,替代 dicebear 外链)
|
||||||
|
*
|
||||||
|
* @param {String} name
|
||||||
|
*/
|
||||||
|
export function avatarColor (name = "") {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < name.length; i++) {
|
||||||
|
hash = (hash * 31 + name.charCodeAt(i)) >>> 0;
|
||||||
|
}
|
||||||
|
return `hsl(${hash % 360}, 55%, 55%)`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 人性化显示时间
|
* 人性化显示时间
|
||||||
*
|
*
|
||||||
@@ -409,15 +422,15 @@ export function beautifyTime (datetime = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (time.getTime() < outTime.getTime()) {
|
if (time.getTime() < outTime.getTime()) {
|
||||||
return parseTime(outTime, "{y}/{m}/{d}");
|
return parseTime(outTime, "{y}-{m}-{d}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time.getFullYear() != outTime.getFullYear()) {
|
if (time.getFullYear() != outTime.getFullYear()) {
|
||||||
return parseTime(outTime, "{y}/{m}/{d}");
|
return parseTime(outTime, "{y}-{m}-{d}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time.getMonth() != outTime.getMonth()) {
|
if (time.getMonth() != outTime.getMonth()) {
|
||||||
return parseTime(outTime, "{m}/{d}");
|
return parseTime(outTime, "{m}-{d}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time.getDate() != outTime.getDate()) {
|
if (time.getDate() != outTime.getDate()) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<el-header height="118px" class="logo-header">
|
<el-header height="118px" class="logo-header">
|
||||||
<div class="user-login" v-popover:usercard>
|
<div class="user-login" v-popover:usercard>
|
||||||
<div class="user-box">
|
<div class="user-box">
|
||||||
<face :text="face"></face>
|
<face :text="userFace"></face>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="user-status">
|
<p class="user-status">
|
||||||
@@ -66,7 +66,8 @@ const userStore = useUserStore();
|
|||||||
const notifyStore = useNotifyStore();
|
const notifyStore = useNotifyStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const face = computed(() => userStore.face);
|
// 不能命名为 face:会遮蔽 <face> 组件解析,头像 URL 会被当作标签名渲染
|
||||||
|
const userFace = computed(() => userStore.face);
|
||||||
const name = computed(() => userStore.name);
|
const name = computed(() => userStore.name);
|
||||||
const id = computed(() => userStore.id);
|
const id = computed(() => userStore.id);
|
||||||
const defaultAvatar = computed(() => appStore.defaultAvatar);
|
const defaultAvatar = computed(() => appStore.defaultAvatar);
|
||||||
@@ -107,8 +108,8 @@ function setApplyNum () {
|
|||||||
<style lang="less" >
|
<style lang="less" >
|
||||||
.main-layout {
|
.main-layout {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 80%;
|
width: 88%;
|
||||||
height: 80%;
|
height: 92%;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -119,8 +120,8 @@ function setApplyNum () {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
&.full-mode {
|
&.full-mode {
|
||||||
width: 1200px;
|
width: 92%;
|
||||||
height: 85%;
|
height: 96%;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<el-header height="60px" class="header">
|
<el-header height="60px" class="header">
|
||||||
<div class="user-login">
|
<div class="user-login">
|
||||||
<div class="user-box">
|
<div class="user-box">
|
||||||
<face :text="face" :name="name" class="user-face"></face>
|
<face :text="userFace" :name="name" class="user-face"></face>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="user-status">
|
<p class="user-status">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</el-header>
|
</el-header>
|
||||||
<el-header height="60px" class="header">
|
<el-header height="60px" class="header">
|
||||||
<div class="from-search">
|
<div class="from-search">
|
||||||
<el-input v-model="input" :prefix-icon="$legacyIcon('el-icon-search')" placeholder="搜索好友" size="small" />
|
<el-input v-model="input" :prefix-icon="$legacyIcon('el-icon-search')" placeholder="搜索好友" size="small" clearable />
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
<!-- <el-header height="118px" class="logo-header">
|
<!-- <el-header height="118px" class="logo-header">
|
||||||
@@ -58,6 +58,10 @@
|
|||||||
暂无聊天消息
|
暂无聊天消息
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p v-show="loadStatus === 1 && talkNum > 0 && userTalkItem.length === 0" class="empty-data">
|
||||||
|
没有找到相关好友
|
||||||
|
</p>
|
||||||
|
|
||||||
<p v-show="loadStatus === 1 && talkNum > 0" class="main-menu">
|
<p v-show="loadStatus === 1 && talkNum > 0" class="main-menu">
|
||||||
<span class="title">对话记录({{ talkNum }})</span>
|
<span class="title">对话记录({{ talkNum }})</span>
|
||||||
|
|
||||||
@@ -108,7 +112,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="talk-message">
|
<div class="talk-message">
|
||||||
<span v-if="item.lastMessageType === 'MESSAGE'">{{ item.lastTalkMessage }}</span>
|
<span v-if="item.lastMessageType === 'MESSAGE'" v-html="textReplaceEmoji(item.lastTalkMessage)"></span>
|
||||||
<span v-if="item.lastMessageType === 'GOODS'">[商品链接]</span>
|
<span v-if="item.lastMessageType === 'GOODS'">[商品链接]</span>
|
||||||
<span v-if="item.lastMessageType === 'ORDER'">[订单链接]</span>
|
<span v-if="item.lastMessageType === 'ORDER'">[订单链接]</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,6 +160,7 @@ import {
|
|||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
@@ -174,6 +179,7 @@ import {
|
|||||||
} from "@/api/chat";
|
} from "@/api/chat";
|
||||||
import { ServeDeleteContact, ServeEditContactRemark } from "@/api/contacts";
|
import { ServeDeleteContact, ServeEditContactRemark } from "@/api/contacts";
|
||||||
import { beautifyTime } from "@/utils/functions";
|
import { beautifyTime } from "@/utils/functions";
|
||||||
|
import { textReplaceEmoji } from "@/utils/emojis";
|
||||||
import { formatTalkItem, findTalkIndex, getCacheIndexName } from "@/utils/talk";
|
import { formatTalkItem, findTalkIndex, getCacheIndexName } from "@/utils/talk";
|
||||||
import { setToken } from "@/utils/auth";
|
import { setToken } from "@/utils/auth";
|
||||||
import { syncImTokenFromRoute } from "@/utils/im-mode";
|
import { syncImTokenFromRoute } from "@/utils/im-mode";
|
||||||
@@ -243,7 +249,9 @@ const unreadNum = computed(() => talkStore.unreadNum);
|
|||||||
const talkNum = computed(() => talkStore.talkNum);
|
const talkNum = computed(() => talkStore.talkNum);
|
||||||
|
|
||||||
// 原 vuex mapState
|
// 原 vuex mapState
|
||||||
const face = computed(() => userStore.face);
|
// 注意:不能命名为 face——会与全局注册的 <face> 组件绑定冲突,
|
||||||
|
// 模板里的 <face> 标签会被解析成这个 computed 的值(头像 base64 字符串)当作标签名渲染
|
||||||
|
const userFace = computed(() => userStore.face);
|
||||||
const name = computed(() => userStore.name);
|
const name = computed(() => userStore.name);
|
||||||
const talks = computed(() => talkStore.items);
|
const talks = computed(() => talkStore.items);
|
||||||
const id = computed(() => userStore.id);
|
const id = computed(() => userStore.id);
|
||||||
@@ -655,10 +663,11 @@ watch(talkItems, (val) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 搜索用户的时候 根据当前用户表进行模糊搜索
|
// 搜索用户的时候 根据当前用户表进行模糊搜索
|
||||||
watch(input, (val, oldVal) => {
|
watch(input, (val) => {
|
||||||
console.log(val, oldVal);
|
|
||||||
if (val) {
|
if (val) {
|
||||||
let str = ["", ...val, ""].join(".*");
|
// 转义正则元字符,避免输入 [ 等字符时构造 RegExp 抛异常
|
||||||
|
const keyword = val.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
let str = ["", ...keyword, ""].join(".*");
|
||||||
let reg = new RegExp(str);
|
let reg = new RegExp(str);
|
||||||
userTalkItem.value = talkItems.value.filter((item) =>
|
userTalkItem.value = talkItems.value.filter((item) =>
|
||||||
reg.test(item.name)
|
reg.test(item.name)
|
||||||
@@ -703,8 +712,10 @@ onBeforeRouteUpdate((to, from, next) => {
|
|||||||
// 原 beforeCreate
|
// 原 beforeCreate
|
||||||
syncImTokenFromRoute(route, setToken);
|
syncImTokenFromRoute(route, setToken);
|
||||||
|
|
||||||
// 原 async created(保持 await 顺序,但用 IIFE 避免 setup 变为异步)
|
// 原 async created。不能在 setup 同步执行:全局 mixin 的方法(initialize/
|
||||||
(async () => {
|
// loadUserSetting)在 finishComponentSetup 的 applyOptions 阶段才挂到公共代理上,
|
||||||
|
// 即 setup() 返回之后。故放到 onMounted 里执行。
|
||||||
|
onMounted(async () => {
|
||||||
await proxy.initialize();
|
await proxy.initialize();
|
||||||
await proxy.loadUserSetting();
|
await proxy.loadUserSetting();
|
||||||
// 初始化商品参数
|
// 初始化商品参数
|
||||||
@@ -714,7 +725,7 @@ syncImTokenFromRoute(route, setToken);
|
|||||||
* 所以创建会话 并请求用户列表
|
* 所以创建会话 并请求用户列表
|
||||||
* 如果没有id说明当前商家登录 直接请求用户列表
|
* 如果没有id说明当前商家登录 直接请求用户列表
|
||||||
*/
|
*/
|
||||||
})();
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
document.title = title;
|
document.title = title;
|
||||||
@@ -747,8 +758,20 @@ onBeforeUnmount(() => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|
||||||
:deep(.el-input .el-input__inner ) {
|
:deep(.el-input__wrapper) {
|
||||||
border-radius: 20px;
|
border-radius: 16px;
|
||||||
|
background-color: #f5f6f7;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 0 1px #409eff inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,6 +1081,12 @@ onBeforeUnmount(() => {
|
|||||||
.talk-message {
|
.talk-message {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #8f959e;
|
color: #8f959e;
|
||||||
|
|
||||||
|
:deep(img) {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-face {
|
.user-face {
|
||||||
|
|||||||
@@ -20,6 +20,18 @@
|
|||||||
<el-input class="label-appkey" v-model="formValidate.wechatAppId" />
|
<el-input class="label-appkey" v-model="formValidate.wechatAppId" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="提现规则说明" prop="withdrawRule">
|
||||||
|
<div style="display: flex; flex-direction: column; width: 500px;">
|
||||||
|
<el-input
|
||||||
|
v-model="formValidate.withdrawRule"
|
||||||
|
type="textarea"
|
||||||
|
:rows="6"
|
||||||
|
placeholder="请输入提现规则说明(需包含可提现额度、每日提现次数、提现时间、到账时间等,以满足微信审核要求)"
|
||||||
|
/>
|
||||||
|
<div class="rule-tip">展示在移动端/小程序提现页面底部,用于满足微信审核对提现规则公示的要求。</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<div class="label-btns">
|
<div class="label-btns">
|
||||||
<el-button type="primary" @click="submit('formValidateRef')">保存</el-button>
|
<el-button type="primary" @click="submit('formValidateRef')">保存</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,6 +44,13 @@ import { ElMessage } from "element-plus";
|
|||||||
import { setSetting } from "@/api/index";
|
import { setSetting } from "@/api/index";
|
||||||
import { handleSubmit } from "./validate";
|
import { handleSubmit } from "./validate";
|
||||||
|
|
||||||
|
const DEFAULT_WITHDRAW_RULE = `1. 提现额度:单笔最低提现金额以页面展示为准,单日累计最高提现 5000 元;
|
||||||
|
2. 提现次数:每个用户每日最多可申请提现 1 次;
|
||||||
|
3. 提现时间:支持全天 24 小时随时发起提现申请;
|
||||||
|
4. 到账时间:提交申请后预计 1~3 个工作日内审核并到账至您的微信零钱;
|
||||||
|
5. 手续费说明:提现免收手续费;
|
||||||
|
6. 如有其他疑问或遇到提现未到账,请联系在线客服协助处理。`;
|
||||||
|
|
||||||
const props = defineProps(["res", "type"]);
|
const props = defineProps(["res", "type"]);
|
||||||
|
|
||||||
// handleSubmit(that, name) 通过 that.$refs[name] 取表单实例
|
// handleSubmit(that, name) 通过 that.$refs[name] 取表单实例
|
||||||
@@ -64,29 +83,49 @@ function setupSetting() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function init() {
|
function init() {
|
||||||
result.value = JSON.parse(props.res);
|
result.value = JSON.parse(props.res || "{}");
|
||||||
Object.keys(result.value).map((item) => {
|
Object.keys(result.value).map((item) => {
|
||||||
result.value[item] += "";
|
result.value[item] += "";
|
||||||
});
|
});
|
||||||
formValidate.value = { ...result.value };
|
formValidate.value = { ...result.value };
|
||||||
|
if (!formValidate.value.withdrawRule) {
|
||||||
|
formValidate.value.withdrawRule = DEFAULT_WITHDRAW_RULE;
|
||||||
|
}
|
||||||
Object.keys(formValidate.value).forEach((item) => {
|
Object.keys(formValidate.value).forEach((item) => {
|
||||||
ruleValidate[item] = [
|
if (item === "minPrice") {
|
||||||
{
|
ruleValidate[item] = [
|
||||||
required: true,
|
{
|
||||||
message: "请填写必填项",
|
required: true,
|
||||||
trigger: "blur",
|
message: "请填写必填项",
|
||||||
},
|
trigger: "blur",
|
||||||
{
|
|
||||||
validator: (rule, value, callback) => {
|
|
||||||
if (value < 0) {
|
|
||||||
callback(new Error("不能输入负数!"));
|
|
||||||
} else {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
trigger: "change",
|
{
|
||||||
},
|
validator: (rule, value, callback) => {
|
||||||
];
|
if (Number(value) < 0) {
|
||||||
|
callback(new Error("不能输入负数!"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "change",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (item === "withdrawRule") {
|
||||||
|
ruleValidate[item] = [
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
ruleValidate[item] = [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请填写必填项",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +135,13 @@ init();
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./style.scss";
|
@import "./style.scss";
|
||||||
:deep(.el-form-item__content){
|
:deep(.el-form-item__content){
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
.rule-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user