增加商品客服显示
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<van-popover v-model="showPopover" trigger="click" placement="top-end">
|
||||
<template #reference>
|
||||
<button class="customer-service-btn">
|
||||
<svg t="1776784019841" class="icon" viewBox="0 0 1051 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5796" width="20" height="20">
|
||||
<path
|
||||
d="M55.351351 553.402811v110.924108a83.027027 83.027027 0 0 0 166.054054 0v-110.924108a83.027027 83.027027 0 0 0-166.054054 0z m763.101406 211.552865A137.852541 137.852541 0 0 1 774.918919 664.326919v-110.924108A138.378378 138.378378 0 0 1 912.328649 415.135135C898.131027 214.071351 730.499459 55.351351 525.837838 55.351351 321.148541 55.351351 153.544649 214.071351 139.347027 415.135135A138.461405 138.461405 0 0 1 276.756757 553.402811v110.924108a138.378378 138.378378 0 0 1-276.756757 0v-110.924108a138.378378 138.378378 0 0 1 83.303784-126.865297C91.883243 189.523027 286.72 0 525.837838 0s433.954595 189.523027 442.534054 426.537514A138.461405 138.461405 0 0 1 1051.675676 553.402811v110.924108a138.378378 138.378378 0 0 1-184.790487 130.269405 470.763243 470.763243 0 0 1-188.858811 121.21946A96.809514 96.809514 0 0 1 580.912432 1010.162162h-82.528864c-53.690811 0-97.113946-43.174054-97.113946-96.864865 0-53.607784 43.284757-96.864865 97.141621-96.864865h82.473514c34.954378 0 65.536 18.265946 82.639567 45.803244a415.273514 415.273514 0 0 0 154.900757-97.28zM830.27027 553.402811v110.924108a83.027027 83.027027 0 0 0 166.054054 0v-110.924108a83.027027 83.027027 0 0 0-166.054054 0zM498.438919 954.810811h82.473513c23.302919 0 41.79027-18.487351 41.790271-41.513514 0-23.053838-18.570378-41.513514-41.790271-41.513513h-82.473513c-23.302919 0-41.79027 18.487351-41.79027 41.513513 0 23.053838 18.570378 41.513514 41.79027 41.513514z"
|
||||
fill="#666666"
|
||||
p-id="5797"
|
||||
></path>
|
||||
</svg>
|
||||
<span>客服</span>
|
||||
</button>
|
||||
</template>
|
||||
<div class="customer-group">
|
||||
<p class="head">点击下方客服即可复制客服号码,可添加或添加微信号</p>
|
||||
<div class="customer-type-group" v-for="(cus, index) in list" :key="index" @click="onService(cus)">
|
||||
<img :src="cus.icon_url" alt="" />
|
||||
<div class="info">
|
||||
<h1>{{ cus.title }} <span class="wechat-account" v-copy="cus.value"></span></h1>
|
||||
<p v-html="cus.content"></p>
|
||||
</div>
|
||||
<a class="phone" :href="'tel://' + cus.value" v-if="cus.title === '电话客服'"></a>
|
||||
</div>
|
||||
</div>
|
||||
</van-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCustomerList } from "@/api";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
showPopover: false,
|
||||
actions: [{ name: "选项1" }, { name: "选项2" }, { name: "选项3" }],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
showPopover(val) {
|
||||
console.log("val", val);
|
||||
document.body.style.overflow = val ? "hidden" : "auto";
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.getCustomerList();
|
||||
}, 1000);
|
||||
},
|
||||
methods: {
|
||||
async getCustomerList() {
|
||||
if (this.list.length) return;
|
||||
const res = await getCustomerList();
|
||||
this.list = res.list;
|
||||
this.qrcode = this.$config.qrcode.wechatAccount || res.platform_code;
|
||||
},
|
||||
onService(cus) {
|
||||
this.showPopover = false;
|
||||
navigator.clipboard.writeText(cus.value);
|
||||
this.$toast.success("已复制");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.customer-service-btn {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
background: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
font-size: 0.25rem;
|
||||
gap: 0.14rem;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.van-popover__content {
|
||||
width: 90%;
|
||||
}
|
||||
.customer-group {
|
||||
padding: 0.3rem;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-shadow: 0 -10px 10px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.head {
|
||||
font-size: 0.3rem;
|
||||
color: #aaa;
|
||||
padding: 0 0.4rem;
|
||||
}
|
||||
.customer-type-group {
|
||||
display: flex;
|
||||
background: white;
|
||||
height: 1.67rem;
|
||||
border-radius: 0.27rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
img {
|
||||
width: 1.07rem;
|
||||
height: 1.07rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
width: 90%;
|
||||
height: 1px;
|
||||
background: #f1f1f1;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
&:last-child::after {
|
||||
display: none;
|
||||
}
|
||||
.phone {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.info {
|
||||
flex: 1;
|
||||
font-size: 0.37rem;
|
||||
h1 {
|
||||
font-size: 0.4rem;
|
||||
margin-bottom: 0.2rem;
|
||||
.wechat-account {
|
||||
color: rgb(167, 54, 54);
|
||||
}
|
||||
}
|
||||
p {
|
||||
color: #aaa;
|
||||
font-size: 0.3rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,284 @@
|
||||
// utils/ObserverInteraction.js
|
||||
|
||||
class ObserverInteraction {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
attributes: true, // 观察属性变化
|
||||
childList: false, // 观察子节点变化
|
||||
subtree: false, // 观察后代节点
|
||||
attributeFilter: [], // 观察特定属性
|
||||
attributeOldValue: false, // 记录属性旧值
|
||||
...options,
|
||||
};
|
||||
|
||||
this.observer = null;
|
||||
this.callbacks = new Map();
|
||||
this.targets = new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始观察目标元素
|
||||
* @param {HTMLElement} target - 要观察的DOM元素
|
||||
* @param {Function} callback - 变化时的回调函数
|
||||
* @param {Object} customOptions - 自定义观察选项
|
||||
*/
|
||||
observe(target, callback, customOptions = {}) {
|
||||
if (!target) {
|
||||
console.error("ObserverInteraction: 目标元素不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
const options = { ...this.options, ...customOptions };
|
||||
|
||||
// 如果已存在观察器,先断开
|
||||
if (this.observer) {
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
// 创建新的观察器
|
||||
this.observer = new MutationObserver((mutationsList) => {
|
||||
const changes = this._processMutations(mutationsList);
|
||||
if (changes.length > 0 && callback) {
|
||||
callback(changes, target);
|
||||
}
|
||||
|
||||
// 触发所有注册的回调
|
||||
this.callbacks.forEach((cb, element) => {
|
||||
if (element === target || element.contains(target)) {
|
||||
cb(changes, target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 开始观察
|
||||
this.observer.observe(target, options);
|
||||
this.targets.add(target);
|
||||
|
||||
// 存储回调
|
||||
if (callback) {
|
||||
this.callbacks.set(target, callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听特定属性的变化
|
||||
* @param {HTMLElement} target - 目标元素
|
||||
* @param {string} attributeName - 属性名
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
watchAttribute(target, attributeName, callback) {
|
||||
return this.observe(target, callback, {
|
||||
attributes: true,
|
||||
attributeFilter: [attributeName],
|
||||
attributeOldValue: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听元素大小变化(使用 ResizeObserver)
|
||||
* @param {HTMLElement} target - 目标元素
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
observeResize(target, callback) {
|
||||
if (!window.ResizeObserver) {
|
||||
console.warn("ResizeObserver 不支持当前浏览器");
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
callback({ width, height, target: entry.target });
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(target);
|
||||
|
||||
// 存储以便清理
|
||||
this.resizeObservers = this.resizeObservers || new Map();
|
||||
this.resizeObservers.set(target, resizeObserver);
|
||||
|
||||
return () => this.unobserveResize(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 mutations 数据
|
||||
* @private
|
||||
*/
|
||||
_processMutations(mutationsList) {
|
||||
const changes = [];
|
||||
|
||||
for (const mutation of mutationsList) {
|
||||
const change = {
|
||||
type: mutation.type,
|
||||
target: mutation.target,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
if (mutation.type === "attributes") {
|
||||
change.attributeName = mutation.attributeName;
|
||||
change.oldValue = mutation.oldValue;
|
||||
change.newValue = mutation.target.getAttribute(mutation.attributeName);
|
||||
}
|
||||
|
||||
if (mutation.type === "childList") {
|
||||
change.addedNodes = Array.from(mutation.addedNodes);
|
||||
change.removedNodes = Array.from(mutation.removedNodes);
|
||||
}
|
||||
|
||||
changes.push(change);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册交互事件
|
||||
* @param {HTMLElement} element - 元素
|
||||
* @param {string} eventType - 事件类型
|
||||
* @param {Function} callback - 回调
|
||||
*/
|
||||
on(element, eventType, callback) {
|
||||
if (!element || !callback) return;
|
||||
|
||||
const handler = (event) => {
|
||||
callback(event, element);
|
||||
};
|
||||
|
||||
element.addEventListener(eventType, handler);
|
||||
|
||||
// 存储以便清理
|
||||
this.eventHandlers = this.eventHandlers || new Map();
|
||||
if (!this.eventHandlers.has(element)) {
|
||||
this.eventHandlers.set(element, new Map());
|
||||
}
|
||||
this.eventHandlers.get(element).set(eventType, handler);
|
||||
|
||||
return () => this.off(element, eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param {HTMLElement} element - 元素
|
||||
* @param {string} eventType - 事件类型
|
||||
*/
|
||||
off(element, eventType) {
|
||||
if (!this.eventHandlers?.has(element)) return;
|
||||
|
||||
const handlers = this.eventHandlers.get(element);
|
||||
if (eventType) {
|
||||
const handler = handlers.get(eventType);
|
||||
if (handler) {
|
||||
element.removeEventListener(eventType, handler);
|
||||
handlers.delete(eventType);
|
||||
}
|
||||
} else {
|
||||
handlers.forEach((handler, type) => {
|
||||
element.removeEventListener(type, handler);
|
||||
});
|
||||
this.eventHandlers.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 观察元素可见性(Intersection Observer)
|
||||
* @param {HTMLElement} target - 目标元素
|
||||
* @param {Function} callback - 可见性变化回调
|
||||
* @param {Object} options - IntersectionObserver 配置
|
||||
*/
|
||||
observeVisibility(target, callback, options = {}) {
|
||||
const defaultOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: "0px",
|
||||
};
|
||||
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
callback({
|
||||
isIntersecting: entry.isIntersecting,
|
||||
intersectionRatio: entry.intersectionRatio,
|
||||
boundingClientRect: entry.boundingClientRect,
|
||||
target: entry.target,
|
||||
});
|
||||
});
|
||||
},
|
||||
{ ...defaultOptions, ...options }
|
||||
);
|
||||
|
||||
intersectionObserver.observe(target);
|
||||
|
||||
this.intersectionObservers = this.intersectionObservers || new Map();
|
||||
this.intersectionObservers.set(target, intersectionObserver);
|
||||
|
||||
return () => this.unobserveVisibility(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止观察可见性
|
||||
* @param {HTMLElement} target - 目标元素
|
||||
*/
|
||||
unobserveVisibility(target) {
|
||||
const observer = this.intersectionObservers?.get(target);
|
||||
if (observer) {
|
||||
observer.unobserve(target);
|
||||
this.intersectionObservers.delete(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止观察大小变化
|
||||
* @param {HTMLElement} target - 目标元素
|
||||
*/
|
||||
unobserveResize(target) {
|
||||
const observer = this.resizeObservers?.get(target);
|
||||
if (observer) {
|
||||
observer.unobserve(target);
|
||||
this.resizeObservers.delete(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开所有观察器
|
||||
*/
|
||||
disconnect() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
this.observer = null;
|
||||
}
|
||||
|
||||
// 清理 ResizeObservers
|
||||
this.resizeObservers?.forEach((observer) => {
|
||||
observer.disconnect();
|
||||
});
|
||||
this.resizeObservers?.clear();
|
||||
|
||||
// 清理 IntersectionObservers
|
||||
this.intersectionObservers?.forEach((observer) => {
|
||||
observer.disconnect();
|
||||
});
|
||||
this.intersectionObservers?.clear();
|
||||
|
||||
this.targets.clear();
|
||||
this.callbacks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实例
|
||||
*/
|
||||
destroy() {
|
||||
this.disconnect();
|
||||
|
||||
// 清理所有事件监听
|
||||
this.eventHandlers?.forEach((handlers, element) => {
|
||||
handlers.forEach((handler, type) => {
|
||||
element.removeEventListener(type, handler);
|
||||
});
|
||||
});
|
||||
this.eventHandlers?.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export default ObserverInteraction;
|
||||
Reference in New Issue
Block a user