feat:优化上线后的改造
This commit is contained in:
@@ -5,261 +5,267 @@ import {
|
||||
} from "@/uni_modules/hashmall-customer-service/js_sdk/kefu-realtime.js";
|
||||
|
||||
export default {
|
||||
globalData: {
|
||||
launchShown: false, // 内存标记,杀进程就清空
|
||||
},
|
||||
|
||||
onLaunch: function () {
|
||||
console.log("App Launch");
|
||||
const res = uni.getSystemInfoSync();
|
||||
uni.setStorageSync("platform", res.platform);
|
||||
// #ifdef APP-PLUS
|
||||
plus.navigator.closeSplashscreen(); // 立即关闭原生闪屏
|
||||
|
||||
// 获取应用版本信息
|
||||
plus.runtime.getProperty(plus.runtime.appid, function (inf) {
|
||||
console.log("版本信息", inf.versionCode);
|
||||
uni.setStorageSync("version-1", inf.version); // 大版本号
|
||||
uni.setStorageSync("versionCode", inf.versionCode); // 小版本号
|
||||
});
|
||||
let uuid = plus.device.uuid;
|
||||
// #endif
|
||||
},
|
||||
globalData: {
|
||||
launchShown: false, // 内存标记,杀进程就清空
|
||||
},
|
||||
|
||||
onLaunch: function () {
|
||||
console.log("App Launch");
|
||||
const res = uni.getSystemInfoSync();
|
||||
uni.setStorageSync("platform", res.platform);
|
||||
// #ifdef APP-PLUS
|
||||
plus.navigator.closeSplashscreen(); // 立即关闭原生闪屏
|
||||
|
||||
// 获取应用版本信息
|
||||
plus.runtime.getProperty(plus.runtime.appid, function (inf) {
|
||||
console.log("版本信息", inf.versionCode);
|
||||
uni.setStorageSync("version-1", inf.version); // 大版本号
|
||||
uni.setStorageSync("versionCode", inf.versionCode); // 小版本号
|
||||
});
|
||||
let uuid = plus.device.uuid;
|
||||
// #endif
|
||||
},
|
||||
onShow: function () {
|
||||
console.log("App Show");
|
||||
uni.$emit("kefu-app-show");
|
||||
resumeKefuRealtime();
|
||||
// 隐藏原生tabbar
|
||||
/*#ifdef APP-PLUS*/
|
||||
uni.hideTabBar({
|
||||
animation: false,
|
||||
});
|
||||
/*#endif*/
|
||||
},
|
||||
// 隐藏原生tabbar
|
||||
/*#ifdef APP-PLUS*/
|
||||
uni.hideTabBar({
|
||||
animation: false,
|
||||
});
|
||||
/*#endif*/
|
||||
},
|
||||
onHide: function () {
|
||||
console.log("App Hide");
|
||||
uni.$emit("kefu-app-hide");
|
||||
suspendKefuRealtime();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/uni_modules/uview-plus/index.scss";
|
||||
@import "@/uni.scss";
|
||||
@import "@/common.scss";
|
||||
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
min-width: 750rpx;
|
||||
max-width: 750rpx;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: $app-bj;
|
||||
}
|
||||
|
||||
.content-one {
|
||||
display: flex;
|
||||
min-width: 750rpx;
|
||||
max-width: 750rpx;
|
||||
height: calc(100vh - 100rpx);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: $app-bj;
|
||||
}
|
||||
|
||||
.flex-r-lr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-c-lr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-c {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-r-e {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.flex-r {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text-w-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.text-zi {
|
||||
color: #7934f6;
|
||||
}
|
||||
|
||||
.text-zu {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.text-fu {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.text-fu1 {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.text-lu {
|
||||
color: #00a776;
|
||||
}
|
||||
|
||||
.mt-12 {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.mt-24 {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.mb-32 {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.mt-32 {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.mt-48 {
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.mt-88 {
|
||||
margin-top: 88rpx;
|
||||
}
|
||||
|
||||
.text-20 {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.text-24 {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.text-26 {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.text-28 {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.text-30 {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.text-32 {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.text-36 {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.text-38 {
|
||||
font-size: 38rpx;
|
||||
}
|
||||
|
||||
.text-44 {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
|
||||
.text-48 {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.text-52 {
|
||||
font-size: 52rpx;
|
||||
}
|
||||
|
||||
.text-64 {
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
.text-88 {
|
||||
font-size: 88rpx;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.line-h {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.mr-32 {
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
|
||||
.text-overflow1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.text-d {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.text-space {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
/* 保持图片比例 */
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/uni_modules/uview-plus/index.scss";
|
||||
@import "@/uni.scss";
|
||||
@import "@/common.scss";
|
||||
|
||||
page {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
min-width: 750rpx;
|
||||
max-width: 750rpx;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: $app-bj;
|
||||
}
|
||||
|
||||
.content-one {
|
||||
display: flex;
|
||||
min-width: 750rpx;
|
||||
max-width: 750rpx;
|
||||
height: calc(100vh - 100rpx);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: $app-bj;
|
||||
}
|
||||
|
||||
.flex-r-lr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-c-lr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-c {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-r-e {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.flex-r {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text-w-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.text-zi {
|
||||
color: #7934f6;
|
||||
}
|
||||
|
||||
.text-zu {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.text-fu {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.text-fu1 {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.text-lu {
|
||||
color: #00a776;
|
||||
}
|
||||
|
||||
.mt-12 {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.mt-24 {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.mb-32 {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.mt-32 {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.mt-48 {
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.mt-88 {
|
||||
margin-top: 88rpx;
|
||||
}
|
||||
|
||||
.text-20 {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.text-24 {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.text-26 {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.text-28 {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.text-30 {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.text-32 {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.text-36 {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.text-38 {
|
||||
font-size: 38rpx;
|
||||
}
|
||||
|
||||
.text-44 {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
|
||||
.text-48 {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.text-52 {
|
||||
font-size: 52rpx;
|
||||
}
|
||||
|
||||
.text-64 {
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
.text-88 {
|
||||
font-size: 88rpx;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.line-h {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.mr-32 {
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
|
||||
.text-overflow1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.text-d {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.text-space {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
/* 保持图片比例 */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { getStorageFun, TOKEN_NAME } from "@/utils/auth.js";
|
||||
import { Authorization, KEFU_BASE_URL } from "@/utils/config.js";
|
||||
|
||||
function resolveQiaobaoBaseUrl() {
|
||||
let baseUrl = KEFU_BASE_URL;
|
||||
|
||||
// H5 本地联调时,管理后台测试域名可能尚未部署最新桥宝接口。
|
||||
// 只让 localhost/127.0.0.1 的开发页面访问本机 8080,App、小程序及线上 H5 不受影响。
|
||||
// #ifdef H5
|
||||
if (typeof window !== "undefined" && ["localhost", "127.0.0.1"].includes(window.location.hostname)) {
|
||||
baseUrl = `http://${window.location.hostname}:8080`;
|
||||
}
|
||||
// #endif
|
||||
|
||||
return String(baseUrl).replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
/** Stateless Qiaobao chat. It is intentionally separate from formal customer service. */
|
||||
export function chatWithQiaobao(payload) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${resolveQiaobaoBaseUrl()}/qiaobao/chat`,
|
||||
method: "POST",
|
||||
data: payload,
|
||||
timeout: 30000,
|
||||
header: {
|
||||
Authorization,
|
||||
"HSM-AUTH": getStorageFun(TOKEN_NAME) || "",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
success: ({ data, statusCode }) => {
|
||||
let value = data;
|
||||
if (typeof value === "string") {
|
||||
try { value = JSON.parse(value); } catch (error) { /* keep original */ }
|
||||
}
|
||||
if (statusCode >= 200 && statusCode < 300 && value?.bizcode === 100) {
|
||||
resolve(value.data || {});
|
||||
return;
|
||||
}
|
||||
reject(new Error(value?.msg || "桥宝暂时无法回答"));
|
||||
},
|
||||
fail: (error) => reject(error || new Error("网络连接失败")),
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<view v-if="visible" class="qiaobao-assistant" :class="{ 'is-collapsed': collapsed }">
|
||||
<view v-if="collapsed" class="qiaobao-edge-tab"
|
||||
:class="{ 'is-left': collapsedEdge === 'left', 'is-dragging': dockDragging }" :style="dockStyle" role="button"
|
||||
aria-label="桥宝已收起,点击展开" @click.stop="restoreFromDock" @touchstart.stop="handleDockDragStart"
|
||||
aria-label="桥宝已收起,点击展开" @click.stop="restoreFromDock" @tap.stop="restoreFromDock" @touchstart.stop="handleDockDragStart"
|
||||
@touchmove.stop.prevent="handleDockDragMove" @touchend.stop="handleDockDragEnd"
|
||||
@touchcancel.stop="handleDockDragEnd" @mousedown.stop.prevent="handleDockMouseDragStart">
|
||||
<image class="qiaobao-edge-head" src="https://static.tbmall.xin/static/mine/qiaobao-edge-peek.png"
|
||||
@@ -10,7 +10,7 @@
|
||||
</view>
|
||||
|
||||
<view v-else class="qiaobao-float" :class="{ 'is-scratching': action === 'scratch' }" :style="floatStyle"
|
||||
role="button" aria-label="桥宝,点击查看当前页面说明,长按可隐藏" @click.stop="openHelp" @longpress.stop="openHideMenu"
|
||||
role="button" aria-label="桥宝,点击查看当前页面说明,长按可隐藏" @click.stop="openHelp" @tap.stop="openHelp" @longpress.stop="openHideMenu"
|
||||
@contextmenu.stop.prevent="openHideMenu" @touchstart.stop="handleDragStart"
|
||||
@touchmove.stop.prevent="handleDragMove" @touchend.stop="handleDragEnd" @touchcancel.stop="handleDragEnd"
|
||||
@mousedown.stop.prevent="handleMouseDragStart">
|
||||
@@ -28,25 +28,54 @@
|
||||
mode="aspectFit" />
|
||||
</view>
|
||||
<view class="qiaobao-title-wrap">
|
||||
<text class="qiaobao-kicker">桥宝 · 页面向导</text>
|
||||
<text class="qiaobao-title">{{ resolvedTitle }}</text>
|
||||
<text class="qiaobao-kicker">桥宝 · AI 商城助手</text>
|
||||
<view class="qiaobao-online-row">
|
||||
<view class="qiaobao-online-dot" />
|
||||
<text class="qiaobao-title">陪你逛信任桥TB商城</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qiaobao-close" aria-label="关闭" @click="closeHelp">×</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="qiaobao-content" scroll-y>
|
||||
<text class="qiaobao-summary">{{ resolvedSummary }}</text>
|
||||
<view v-if="resolvedTips.length" class="qiaobao-tips">
|
||||
<view v-for="(tip, index) in resolvedTips" :key="index" class="qiaobao-tip">
|
||||
<text class="qiaobao-tip-index">{{ index + 1 }}</text>
|
||||
<text class="qiaobao-tip-text">{{ tip }}</text>
|
||||
<view class="qiaobao-page-context">
|
||||
<text class="qiaobao-context-label">当前页面</text>
|
||||
<text class="qiaobao-context-title">{{ resolvedTitle }}</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="qiaobao-chat" scroll-y :scroll-top="chatScrollTop" :scroll-with-animation="true">
|
||||
<view v-for="(item, index) in chatMessages" :key="`${item.role}-${index}`" class="qiaobao-message"
|
||||
:class="`is-${item.role}`">
|
||||
<image v-if="item.role === 'assistant'" class="qiaobao-message-avatar"
|
||||
src="https://static.tbmall.xin/static/mine/qiaobao-idle.png" mode="aspectFit" />
|
||||
<view class="qiaobao-message-bubble">
|
||||
<text>{{ item.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<slot />
|
||||
<view v-if="chatLoading" class="qiaobao-message is-assistant">
|
||||
<image class="qiaobao-message-avatar" src="https://static.tbmall.xin/static/mine/qiaobao-blink.png"
|
||||
mode="aspectFit" />
|
||||
<view class="qiaobao-message-bubble is-typing"><text>桥宝正在想</text><text class="qiaobao-dots">•••</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qiaobao-chat-spacer" />
|
||||
</scroll-view>
|
||||
|
||||
<button class="qiaobao-confirm" @click="closeHelp">我知道了</button>
|
||||
<text class="qiaobao-hide-hint">长按桥宝,可以选择隐藏</text>
|
||||
<scroll-view v-if="suggestions.length" class="qiaobao-suggestions" scroll-x :show-scrollbar="false">
|
||||
<view class="qiaobao-suggestion-row">
|
||||
<button v-for="suggestion in suggestions" :key="suggestion" class="qiaobao-suggestion"
|
||||
@click="sendSuggestion(suggestion)">{{ suggestion }}</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="qiaobao-compose">
|
||||
<input v-model="chatInput" class="qiaobao-input" maxlength="500" confirm-type="send"
|
||||
placeholder="问桥宝商城、商品或活动问题" :disabled="chatLoading" @confirm="sendMessage" />
|
||||
<button class="qiaobao-send" :disabled="chatLoading || !chatInput" @click="sendMessage">发送</button>
|
||||
</view>
|
||||
<view class="qiaobao-sheet-foot">
|
||||
<text class="qiaobao-hide-hint">长按桥宝可收起到屏幕边缘</text>
|
||||
<text class="qiaobao-human-link" @click="goToCustomerService">人工客服</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -54,17 +83,29 @@
|
||||
|
||||
<script>
|
||||
import {
|
||||
consumeQiaobaoAutoIntro,
|
||||
closeQiaobaoForSession,
|
||||
getQiaobaoChatHistory,
|
||||
getQiaobaoChatSessionId,
|
||||
getCurrentPagePath,
|
||||
getQiaobaoDockEdge,
|
||||
getQiaobaoPosition,
|
||||
hideQiaobaoForPage,
|
||||
hideQiaobaoGlobally,
|
||||
isQiaobaoClosedForSession,
|
||||
isQiaobaoHidden,
|
||||
saveQiaobaoPosition,
|
||||
saveQiaobaoChatHistory,
|
||||
setQiaobaoDockEdge,
|
||||
showQiaobao,
|
||||
} from "@/utils/qiaobao.js";
|
||||
import { resolveQiaobaoPageHelp } from "@/utils/qiaobao-page-help.js";
|
||||
import { getStorageFun, USER_DATA } from "@/utils/auth.js";
|
||||
import { chatWithQiaobao } from "@/api/qiaobao.js";
|
||||
|
||||
const MALL_WELCOME = "你好,我是桥宝。信任桥TB商城为你提供食品饮料、家居日用、个护清洁等日常商品。商城重视商品信息、价格展示、规则透明和消费权益记录。你可以问我商城特色、商品活动、订单入口或商城规则。";
|
||||
const LOGIN_WELCOME = "欢迎回来,我是桥宝。以后逛商城、看活动或不清楚规则时,点我就可以直接问。账户、订单、物流等实时信息请以对应页面为准,需要人工帮助我也会告诉你入口。";
|
||||
const DEFAULT_SUGGESTIONS = ["商城有什么特点", "怎么买更划算", "贡献值是什么", "联系人工客服"];
|
||||
|
||||
const ACTION_IMAGES = {
|
||||
idle: "https://static.tbmall.xin/static/mine/qiaobao-idle.png",
|
||||
@@ -112,6 +153,14 @@ export default {
|
||||
dragJustEnded: false,
|
||||
dragStartPoint: { x: 0, y: 0 },
|
||||
dragOrigin: { x: 0, y: 0 },
|
||||
chatInput: "",
|
||||
chatLoading: false,
|
||||
chatMessages: [],
|
||||
suggestions: [...DEFAULT_SUGGESTIONS],
|
||||
chatScrollTop: 0,
|
||||
chatSessionId: "",
|
||||
chatScope: "guest",
|
||||
autoIntroTimer: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -156,6 +205,7 @@ export default {
|
||||
uni.$on("qiaobao-visibility-change", this.handleVisibilityChange);
|
||||
this.initializeDragPosition();
|
||||
this.refreshVisibility();
|
||||
this.initializeChat();
|
||||
// #ifdef H5
|
||||
window.addEventListener("mousemove", this.handleMouseDragMove);
|
||||
window.addEventListener("mouseup", this.handleMouseDragEnd);
|
||||
@@ -168,8 +218,77 @@ export default {
|
||||
window.removeEventListener("mouseup", this.handleMouseDragEnd);
|
||||
// #endif
|
||||
this.clearTimers();
|
||||
if (this.autoIntroTimer) clearTimeout(this.autoIntroTimer);
|
||||
},
|
||||
methods: {
|
||||
initializeChat() {
|
||||
const userData = getStorageFun(USER_DATA) || {};
|
||||
this.chatScope = userData.userId ? `user-${userData.userId}` : "guest";
|
||||
this.chatSessionId = getQiaobaoChatSessionId();
|
||||
this.chatMessages = getQiaobaoChatHistory(this.chatScope);
|
||||
if (!this.chatMessages.length) {
|
||||
this.chatMessages = [{ role: "assistant", content: MALL_WELCOME }];
|
||||
saveQiaobaoChatHistory(this.chatMessages, this.chatScope);
|
||||
}
|
||||
const reason = consumeQiaobaoAutoIntro(userData.userId);
|
||||
if (reason === "first-login") {
|
||||
this.appendAssistantMessage(LOGIN_WELCOME, false);
|
||||
}
|
||||
},
|
||||
appendAssistantMessage(content, persist = true) {
|
||||
if (!content) return;
|
||||
const last = this.chatMessages[this.chatMessages.length - 1];
|
||||
if (!last || last.role !== "assistant" || last.content !== content) {
|
||||
this.chatMessages.push({ role: "assistant", content });
|
||||
}
|
||||
if (persist) saveQiaobaoChatHistory(this.chatMessages, this.chatScope);
|
||||
this.scrollChatToBottom();
|
||||
},
|
||||
scrollChatToBottom() {
|
||||
this.$nextTick(() => { this.chatScrollTop += 10000; });
|
||||
},
|
||||
sendSuggestion(value) {
|
||||
if (this.chatLoading) return;
|
||||
if (value === "联系人工客服") {
|
||||
this.goToCustomerService();
|
||||
return;
|
||||
}
|
||||
this.chatInput = value;
|
||||
this.sendMessage();
|
||||
},
|
||||
async sendMessage() {
|
||||
const content = String(this.chatInput || "").trim();
|
||||
if (!content || this.chatLoading) return;
|
||||
this.chatInput = "";
|
||||
this.chatMessages.push({ role: "user", content });
|
||||
saveQiaobaoChatHistory(this.chatMessages, this.chatScope);
|
||||
this.chatLoading = true;
|
||||
this.scrollChatToBottom();
|
||||
try {
|
||||
const history = this.chatMessages.slice(-9, -1).map(({ role, content: value }) => ({ role, content: value }));
|
||||
const data = await chatWithQiaobao({
|
||||
message: content,
|
||||
pagePath: this.currentPageKey,
|
||||
pageTitle: this.resolvedTitle,
|
||||
sessionId: this.chatSessionId,
|
||||
history,
|
||||
});
|
||||
this.suggestions = Array.isArray(data.suggestions) && data.suggestions.length ? data.suggestions : [...DEFAULT_SUGGESTIONS];
|
||||
this.appendAssistantMessage(data.message || "桥宝暂时没理解,可以换一种方式问我。", true);
|
||||
} catch (error) {
|
||||
this.appendAssistantMessage("网络有点忙。你可以稍后再问,或进入人工客服咨询商品、订单、物流和售后问题。", true);
|
||||
} finally {
|
||||
this.chatLoading = false;
|
||||
this.scrollChatToBottom();
|
||||
}
|
||||
},
|
||||
goToCustomerService() {
|
||||
this.closeHelp();
|
||||
uni.navigateTo({
|
||||
url: "/pages/rwa_package/setting/kefu",
|
||||
fail: () => uni.navigateTo({ url: "/pages/rwa_package/kefu/kefu" }),
|
||||
});
|
||||
},
|
||||
initializeDragPosition() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.viewportWidth = Number(systemInfo.windowWidth) || 375;
|
||||
@@ -292,7 +411,10 @@ export default {
|
||||
handleDockDragEnd() {
|
||||
if (!this.dockDragging) return;
|
||||
this.dockDragging = false;
|
||||
if (!this.dockDragMoved) return;
|
||||
if (!this.dockDragMoved) {
|
||||
this.restoreFromDock();
|
||||
return;
|
||||
}
|
||||
setQiaobaoDockEdge(this.collapsedEdge);
|
||||
saveQiaobaoPosition(this.position);
|
||||
this.dockDragJustEnded = true;
|
||||
@@ -304,7 +426,14 @@ export default {
|
||||
handleDragEnd() {
|
||||
if (!this.dragging) return;
|
||||
this.dragging = false;
|
||||
if (!this.dragMoved) return;
|
||||
if (!this.dragMoved) {
|
||||
if (!this.longPressTriggered) {
|
||||
this.openHelp();
|
||||
} else {
|
||||
this.longPressTriggered = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.position = this.clampPosition(this.position);
|
||||
saveQiaobaoPosition(this.position);
|
||||
const edgeThreshold = Math.max(22, this.viewportWidth * 0.06);
|
||||
@@ -324,6 +453,12 @@ export default {
|
||||
return this.position.x + this.petWidth / 2 < this.viewportWidth / 2 ? "left" : "right";
|
||||
},
|
||||
refreshVisibility() {
|
||||
if (isQiaobaoClosedForSession()) {
|
||||
this.visible = false;
|
||||
this.panelVisible = false;
|
||||
this.clearTimers();
|
||||
return;
|
||||
}
|
||||
this.visible = true;
|
||||
this.collapsed = isQiaobaoHidden(this.currentPageKey);
|
||||
this.collapsedEdge = getQiaobaoDockEdge();
|
||||
@@ -342,6 +477,12 @@ export default {
|
||||
}
|
||||
},
|
||||
handleVisibilityChange({ scope, pagePath, edge } = {}) {
|
||||
if (scope === "close") {
|
||||
this.visible = false;
|
||||
this.panelVisible = false;
|
||||
this.clearTimers();
|
||||
return;
|
||||
}
|
||||
if (scope === "all") this.collapseToEdge(edge);
|
||||
if (scope === "page" && pagePath === this.currentPageKey) this.collapseToEdge(edge);
|
||||
if (scope === "show" && pagePath === this.currentPageKey) this.expandFromEdge();
|
||||
@@ -385,16 +526,22 @@ export default {
|
||||
this.$emit("close", this.currentPageKey);
|
||||
},
|
||||
openHideMenu() {
|
||||
if (this.dragging || this.dragMoved) return;
|
||||
// longpress is emitted after touchstart, so `dragging` is normally true here.
|
||||
// Only an actual move should cancel the hide action.
|
||||
if (this.dragMoved || this.dockDragging) return;
|
||||
this.dragging = false;
|
||||
this.longPressTriggered = true;
|
||||
this.showBubble = false;
|
||||
const edge = this.getNearestEdge();
|
||||
uni.vibrateShort?.({ type: "light" });
|
||||
uni.showActionSheet({
|
||||
title: "桥宝收起设置",
|
||||
itemList: ["当前页面收起到边缘", "所有页面收起到边缘"],
|
||||
title: "桥宝操作",
|
||||
itemList: ["仅在当前页面隐藏", "在所有页面隐藏", "关闭桥宝"],
|
||||
success: ({ tapIndex }) => {
|
||||
if (tapIndex === 0) hideQiaobaoForPage(this.currentPageKey, edge);
|
||||
if (tapIndex === 1) hideQiaobaoGlobally(edge);
|
||||
this.$emit("hide", tapIndex === 1 ? "all" : "page");
|
||||
if (tapIndex === 2) this.closeQiaobao();
|
||||
if (tapIndex < 2) this.$emit("hide", tapIndex === 1 ? "all" : "page");
|
||||
},
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
@@ -403,6 +550,14 @@ export default {
|
||||
},
|
||||
});
|
||||
},
|
||||
closeQiaobao() {
|
||||
this.panelVisible = false;
|
||||
this.showBubble = false;
|
||||
this.action = "idle";
|
||||
this.clearTimers();
|
||||
closeQiaobaoForSession();
|
||||
this.$emit("hide", "close");
|
||||
},
|
||||
collapseToEdge(edge = getQiaobaoDockEdge()) {
|
||||
this.collapsedEdge = edge === "left" ? "left" : "right";
|
||||
this.collapsed = true;
|
||||
@@ -450,17 +605,16 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
.qiaobao-assistant {
|
||||
position: relative;
|
||||
z-index: 9999;
|
||||
z-index: 980;
|
||||
}
|
||||
|
||||
.qiaobao-edge-tab {
|
||||
position: fixed;
|
||||
right: -18rpx;
|
||||
bottom: calc(150rpx + constant(safe-area-inset-bottom));
|
||||
bottom: calc(150rpx + env(safe-area-inset-bottom));
|
||||
width: 132rpx;
|
||||
height: 126rpx;
|
||||
z-index: 99999;
|
||||
z-index: 981;
|
||||
overflow: visible;
|
||||
animation: qiaobao-edge-in-right .22s cubic-bezier(.22, 1, .36, 1) both;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@@ -492,11 +646,10 @@ export default {
|
||||
.qiaobao-float {
|
||||
position: fixed;
|
||||
right: 20rpx;
|
||||
bottom: calc(132rpx + constant(safe-area-inset-bottom));
|
||||
bottom: calc(132rpx + env(safe-area-inset-bottom));
|
||||
width: 164rpx;
|
||||
height: 176rpx;
|
||||
z-index: 99999;
|
||||
z-index: 981;
|
||||
transform-origin: 50% 90%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: none;
|
||||
@@ -543,11 +696,8 @@ export default {
|
||||
|
||||
.qiaobao-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999999;
|
||||
inset: 0;
|
||||
z-index: 10080;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
background: rgba(20, 16, 29, .52);
|
||||
@@ -556,21 +706,24 @@ export default {
|
||||
|
||||
.qiaobao-sheet {
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
padding: 12rpx 32rpx 32rpx;
|
||||
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
height: min(84vh, 1280rpx);
|
||||
max-height: 84vh;
|
||||
padding: 12rpx 28rpx calc(22rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
border-radius: 36rpx 36rpx 0 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 -16rpx 48rpx rgba(29, 17, 55, .14);
|
||||
animation: qiaobao-sheet-in .24s ease-out both;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qiaobao-handle {
|
||||
width: 72rpx;
|
||||
height: 8rpx;
|
||||
margin: 4rpx auto 22rpx;
|
||||
margin: 4rpx auto 14rpx;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999rpx;
|
||||
background: #ddd8e8;
|
||||
}
|
||||
@@ -578,12 +731,13 @@ export default {
|
||||
.qiaobao-sheet-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 116rpx;
|
||||
min-height: 92rpx;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.qiaobao-avatar-wrap {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -593,35 +747,53 @@ export default {
|
||||
}
|
||||
|
||||
.qiaobao-avatar {
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
width: 82rpx;
|
||||
height: 82rpx;
|
||||
}
|
||||
|
||||
.qiaobao-title-wrap {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
margin-left: 22rpx;
|
||||
margin-left: 18rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.qiaobao-kicker {
|
||||
color: #7b2cff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
font-size: 21rpx;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.qiaobao-online-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 5rpx;
|
||||
}
|
||||
|
||||
.qiaobao-online-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 50%;
|
||||
background: #39ba76;
|
||||
box-shadow: 0 0 0 6rpx rgba(57, 186, 118, .12);
|
||||
}
|
||||
|
||||
.qiaobao-title {
|
||||
margin-top: 8rpx;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #17131f;
|
||||
font-size: 36rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.25;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.qiaobao-close {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
width: 58rpx;
|
||||
height: 58rpx;
|
||||
margin-left: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -633,74 +805,184 @@ export default {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.qiaobao-content {
|
||||
max-height: 46vh;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.qiaobao-summary {
|
||||
display: block;
|
||||
color: #4b4653;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.qiaobao-tips {
|
||||
margin-top: 26rpx;
|
||||
}
|
||||
|
||||
.qiaobao-tip {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 22rpx;
|
||||
}
|
||||
|
||||
.qiaobao-tip-index {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
flex: 0 0 auto;
|
||||
.qiaobao-page-context {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
color: #6b24e8;
|
||||
background: #eee4ff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
margin: 12rpx 0 10rpx;
|
||||
padding: 13rpx 18rpx;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 16rpx;
|
||||
background: #f7f3ff;
|
||||
}
|
||||
|
||||
.qiaobao-tip-text {
|
||||
.qiaobao-context-label {
|
||||
flex: 0 0 auto;
|
||||
color: #8e77b7;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
|
||||
.qiaobao-context-title {
|
||||
min-width: 0;
|
||||
margin-left: 14rpx;
|
||||
overflow: hidden;
|
||||
color: #392857;
|
||||
font-size: 23rpx;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.qiaobao-chat {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
margin-left: 18rpx;
|
||||
color: #302b38;
|
||||
font-size: 27rpx;
|
||||
line-height: 1.55;
|
||||
padding: 10rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qiaobao-confirm {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
margin: 28rpx 0 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 24rpx;
|
||||
.qiaobao-chat-spacer {
|
||||
height: 12rpx;
|
||||
}
|
||||
|
||||
.qiaobao-message {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin: 12rpx 0;
|
||||
padding-right: 54rpx;
|
||||
}
|
||||
|
||||
.qiaobao-message.is-user {
|
||||
justify-content: flex-end;
|
||||
padding: 0 0 0 54rpx;
|
||||
}
|
||||
|
||||
.qiaobao-message-avatar {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
margin-right: 12rpx;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 18rpx;
|
||||
background: #f0e8ff;
|
||||
}
|
||||
|
||||
.qiaobao-message-bubble {
|
||||
max-width: 100%;
|
||||
padding: 17rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10rpx 24rpx 24rpx 24rpx;
|
||||
color: #292431;
|
||||
background: #f4f1f7;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.55;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.qiaobao-message.is-user .qiaobao-message-bubble {
|
||||
border-radius: 24rpx 10rpx 24rpx 24rpx;
|
||||
color: #fff;
|
||||
background: #7433f1;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
.qiaobao-confirm::after {
|
||||
.qiaobao-message-bubble.is-typing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #756c80;
|
||||
}
|
||||
|
||||
.qiaobao-dots {
|
||||
margin-left: 8rpx;
|
||||
color: #7b2cff;
|
||||
letter-spacing: 3rpx;
|
||||
animation: qiaobao-dots 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.qiaobao-suggestions {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
margin: 2rpx 0 12rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qiaobao-suggestion-row {
|
||||
display: inline-flex;
|
||||
gap: 12rpx;
|
||||
padding-right: 12rpx;
|
||||
}
|
||||
|
||||
.qiaobao-suggestion {
|
||||
height: 56rpx;
|
||||
margin: 0;
|
||||
padding: 0 22rpx;
|
||||
border: 1rpx solid #ddcff8;
|
||||
border-radius: 999rpx;
|
||||
color: #6430bd;
|
||||
background: #faf8ff;
|
||||
font-size: 23rpx;
|
||||
line-height: 54rpx;
|
||||
}
|
||||
|
||||
.qiaobao-suggestion::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.qiaobao-compose {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
padding: 10rpx;
|
||||
border: 1rpx solid #e9e4ef;
|
||||
border-radius: 24rpx;
|
||||
background: #f8f7fa;
|
||||
}
|
||||
|
||||
.qiaobao-input {
|
||||
height: 66rpx;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding: 0 14rpx;
|
||||
color: #25202c;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.qiaobao-send {
|
||||
width: 112rpx;
|
||||
height: 62rpx;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 18rpx;
|
||||
color: #fff;
|
||||
background: #7433f1;
|
||||
font-size: 25rpx;
|
||||
font-weight: 650;
|
||||
line-height: 62rpx;
|
||||
}
|
||||
|
||||
.qiaobao-send[disabled] {
|
||||
color: #a9a3af;
|
||||
background: #e7e3eb;
|
||||
}
|
||||
|
||||
.qiaobao-send::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.qiaobao-sheet-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 0 0 auto;
|
||||
padding: 14rpx 4rpx 0;
|
||||
}
|
||||
|
||||
.qiaobao-hide-hint {
|
||||
display: block;
|
||||
margin-top: 18rpx;
|
||||
color: #9a94a3;
|
||||
text-align: center;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.qiaobao-human-link {
|
||||
color: #7034dc;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes qiaobao-bubble-in {
|
||||
@@ -751,6 +1033,18 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes qiaobao-dots {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: .35;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes qiaobao-edge-in-right {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
"name" : "信任桥",
|
||||
"appid" : "__UNI__4910728",
|
||||
"description" : "信任桥优品是一款集线上商城、线下联盟商家的DAO聚合商城;通过“有趣”“有利”“有希望”的卖货玩法,吸引消费者、推广者、商家、投资人参与生态,共同打造共创共享的全球产业生态。",
|
||||
"versionName" : "1.0.3",
|
||||
"versionCode" : 170,
|
||||
"versionName" : "1.0.5",
|
||||
"versionCode" : 172,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
|
||||
+4
-4
@@ -746,19 +746,19 @@
|
||||
"preloadRule": {
|
||||
"pages/home/home": {
|
||||
"network": "all",
|
||||
"packages": ["pages/mine_package", "pages/order_package", "pages/other_package"]
|
||||
"packages": ["pages/other_package"]
|
||||
},
|
||||
"pages/sort/sort": {
|
||||
"network": "all",
|
||||
"packages": ["pages/other_package", "pages/order_package"]
|
||||
"packages": ["pages/other_package"]
|
||||
},
|
||||
"pages/supply_chain/supply_chain": {
|
||||
"network": "all",
|
||||
"packages": ["pages/other_package", "pages/order_package"]
|
||||
"packages": ["pages/order_package"]
|
||||
},
|
||||
"pages/mine/mine": {
|
||||
"network": "all",
|
||||
"packages": ["pages/mine_package", "pages/rwa_package"]
|
||||
"packages": ["pages/mine_package"]
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
|
||||
+115
-10
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<view class="home_warp" :class="platformType ? '' : 'iosplatformType'" @scroll="onScroll">
|
||||
<!-- 变色搜索栏 -->
|
||||
<view class="header_title_warp" :class="{ 'is-scrolled': isScrolled }" :style="mpHeaderStyle">
|
||||
<view class="language_warp" style="flex: 1; justify-content: flex-start;" @click.stop="jumpSearch">
|
||||
<view class="header_title_warp" :class="{ 'is-scrolled': isScrolled }" :style="headerContainerStyle">
|
||||
<view class="language_warp" style="flex: 1; justify-content: flex-start;" :style="languageBoxStyle"
|
||||
@click.stop="jumpSearch">
|
||||
<view :style="isMP ? 'display: flex; align-items: center;margin-top: 0;' : 'margin-top: 0;'">
|
||||
<up-image src="https://static.tbmall.xin/static/home/search_b.png" width="20" height="20"
|
||||
bgColor="#f1f6ff00"></up-image>
|
||||
<up-image src="https://static.tbmall.xin/static/home/search_b.png" width="20" height="20" bgColor="#f1f6ff00"
|
||||
:style="searchIconStyle"></up-image>
|
||||
</view>
|
||||
<text class="search_msg_warp" :style="isMP ? 'line-height: 1;' : ''">搜索</text>
|
||||
<text class="search_msg_warp" :style="searchMsgStyle">搜索</text>
|
||||
</view>
|
||||
<view class="message-entry" @click.stop="jumpNewSearch('list')">
|
||||
<image src="https://static.tbmall.xin/static/chart.png" class="chart_img"></image>
|
||||
<image src="https://static.tbmall.xin/static/chart.png" class="chart_img" :style="chartImgStyle"></image>
|
||||
<text v-if="kefuUnreadCount > 0" class="message-entry-badge">{{ formatKefuBadge(kefuUnreadCount) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -311,7 +312,7 @@ export default {
|
||||
};
|
||||
},
|
||||
onPageScroll(e) {
|
||||
const top = e ? (e.scrollTop || 0) : 0;
|
||||
const top = this.getScrollTop(e);
|
||||
this.scrollTop = top;
|
||||
this.isScrolled = top > 10;
|
||||
},
|
||||
@@ -379,10 +380,59 @@ export default {
|
||||
if (this.kefuUnreadRefreshTimer) clearTimeout(this.kefuUnreadRefreshTimer);
|
||||
this.kefuUnreadRefreshTimer = null;
|
||||
},
|
||||
mounted() { },
|
||||
mounted() {
|
||||
// #ifdef H5
|
||||
window.addEventListener("scroll", this.handleWindowScroll, true);
|
||||
const pageBody = document.querySelector("uni-page-body");
|
||||
if (pageBody) {
|
||||
pageBody.addEventListener("scroll", this.handleWindowScroll, true);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
beforeUnmount() {
|
||||
// #ifdef H5
|
||||
window.removeEventListener("scroll", this.handleWindowScroll, true);
|
||||
const pageBody = document.querySelector("uni-page-body");
|
||||
if (pageBody) {
|
||||
pageBody.removeEventListener("scroll", this.handleWindowScroll, true);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
getScrollTop(e) {
|
||||
let top = 0;
|
||||
if (typeof e === "number") {
|
||||
top = e;
|
||||
} else if (e && typeof e.scrollTop !== "undefined") {
|
||||
top = e.scrollTop;
|
||||
} else if (e && e.detail && typeof e.detail.scrollTop !== "undefined") {
|
||||
top = e.detail.scrollTop;
|
||||
}
|
||||
// #ifdef H5
|
||||
if (!top) {
|
||||
const pageBody = document.querySelector("uni-page-body");
|
||||
const pageWrapper = document.querySelector("uni-page-wrapper");
|
||||
const homeWarp = document.querySelector(".home_warp");
|
||||
top = Math.max(
|
||||
window.pageYOffset || 0,
|
||||
window.scrollY || 0,
|
||||
document.documentElement?.scrollTop || 0,
|
||||
document.body?.scrollTop || 0,
|
||||
pageBody?.scrollTop || 0,
|
||||
pageWrapper?.scrollTop || 0,
|
||||
homeWarp?.scrollTop || 0
|
||||
);
|
||||
}
|
||||
// #endif
|
||||
return top;
|
||||
},
|
||||
handleWindowScroll(e) {
|
||||
const top = this.getScrollTop(e);
|
||||
this.scrollTop = top;
|
||||
this.isScrolled = top > 10;
|
||||
},
|
||||
onScroll(e) {
|
||||
const top = e.detail ? e.detail.scrollTop : (e.target ? e.target.scrollTop : 0);
|
||||
const top = this.getScrollTop(e);
|
||||
this.scrollTop = top;
|
||||
this.isScrolled = top > 10;
|
||||
},
|
||||
@@ -846,6 +896,55 @@ export default {
|
||||
isBlockedDate() {
|
||||
return useTabber(10);
|
||||
},
|
||||
headerContainerStyle() {
|
||||
const base = this.mpHeaderStyle || {};
|
||||
if (this.isScrolled) {
|
||||
return {
|
||||
...base,
|
||||
backgroundColor: '#ffffff',
|
||||
boxShadow: '0 4rpx 16rpx rgba(0, 0, 0, 0.08)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
};
|
||||
},
|
||||
languageBoxStyle() {
|
||||
const base = this.isMP ? { height: this.mpLanguageStyle?.height, boxSizing: 'border-box' } : {};
|
||||
if (this.isScrolled) {
|
||||
return {
|
||||
...base,
|
||||
backgroundColor: '#f4f5f8',
|
||||
borderColor: '#e8e8e8',
|
||||
};
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.35)',
|
||||
borderColor: 'rgba(255, 255, 255, 0.4)',
|
||||
};
|
||||
},
|
||||
searchMsgStyle() {
|
||||
const base = this.isMP ? { lineHeight: '1' } : {};
|
||||
if (this.isScrolled) {
|
||||
return { ...base, color: '#888888' };
|
||||
}
|
||||
return { ...base, color: 'rgba(255, 255, 255, 0.95)' };
|
||||
},
|
||||
searchIconStyle() {
|
||||
if (this.isScrolled) {
|
||||
return { filter: 'brightness(0.3)' };
|
||||
}
|
||||
return { filter: 'none' };
|
||||
},
|
||||
chartImgStyle() {
|
||||
if (this.isScrolled) {
|
||||
return { filter: 'brightness(0.2)' };
|
||||
}
|
||||
return { filter: 'none' };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -856,6 +955,9 @@ export default {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.iosplatformType {
|
||||
min-height: 100vh;
|
||||
@@ -980,10 +1082,13 @@ export default {
|
||||
}
|
||||
|
||||
.home_content_z_warp {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.logo_banner {
|
||||
|
||||
@@ -43,6 +43,21 @@
|
||||
</up-input>
|
||||
</view>
|
||||
|
||||
<!-- 协议勾选 -->
|
||||
<view class="policy_warp">
|
||||
<up-checkbox name="isCheck" usedAlone v-model:checked="isPolicy" shape="circle" activeColor="#7934F6" size="19"
|
||||
iconSize="18">
|
||||
<template #label>
|
||||
<text>已阅读并同意</text>
|
||||
<text class="policy_a_warp" @click="jumpUserAgreement">《服务协议》</text>
|
||||
<text>、</text>
|
||||
<text class="policy_a_warp" @click="jumpPrivacyAgreement">《隐私政策》</text>
|
||||
<text>、</text>
|
||||
<text class="policy_a_warp" @click="jumpProductAgreement">《售后保障协议》</text>
|
||||
</template>
|
||||
</up-checkbox>
|
||||
</view>
|
||||
|
||||
<view class="login_comm_but login_comm_but_yes" @click="loginFun">
|
||||
<text style="margin-left: 10rpx">登录</text>
|
||||
</view>
|
||||
@@ -91,19 +106,6 @@
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="policy_warp">
|
||||
<up-checkbox name="isCheck" usedAlone v-model:checked="isPolicy" shape="circle" activeColor="#7934F6" size="19"
|
||||
iconSize="18">
|
||||
<template #label>
|
||||
<text>已阅读并同意</text>
|
||||
<text class="policy_a_warp" @click="jumpUserAgreement">《服务协议》</text>
|
||||
<text>、</text>
|
||||
<text class="policy_a_warp" @click="jumpPrivacyAgreement">《隐私政策》</text>
|
||||
<text>、</text>
|
||||
<text class="policy_a_warp" @click="jumpProductAgreement">《售后保障协议》</text>
|
||||
</template>
|
||||
</up-checkbox>
|
||||
</view>
|
||||
<up-toast ref="uToastRef"></up-toast>
|
||||
|
||||
<!-- 自定义协议弹窗 -->
|
||||
@@ -606,8 +608,28 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login_warp {
|
||||
height: calc(100vh - 400rpx);
|
||||
padding: 150rpx 50rpx 0;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 120rpx 50rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.policy_warp {
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
position: relative !important;
|
||||
bottom: auto !important;
|
||||
left: auto !important;
|
||||
width: 100% !important;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.policy_a_warp {
|
||||
color: $app-policy-color;
|
||||
}
|
||||
}
|
||||
|
||||
.login_logo_warp {
|
||||
|
||||
@@ -6,26 +6,26 @@
|
||||
</view>
|
||||
<view class="share-btn-view">
|
||||
<!-- #ifndef APP-PLUS || H5 -->
|
||||
<block v-for="(item, index) in btnList" :key="index">
|
||||
<button v-if="index === 0" open-type="share" hover-class="none" class="share-box share-btn-clean"
|
||||
@click="onBtn(index)">
|
||||
<image :src="`https://static.tbmall.xin/static/new/share_${index}.png`" class="share-img">
|
||||
<block v-for="(item, index) in filteredBtnList" :key="index">
|
||||
<button v-if="item.originalIndex === 0" open-type="share" hover-class="none" class="share-box share-btn-clean"
|
||||
@click="onBtn(item.originalIndex)">
|
||||
<image :src="`https://static.tbmall.xin/static/new/share_${item.originalIndex}.png`" class="share-img">
|
||||
</image>
|
||||
<text class="text-24 text-gray">{{ item }}</text>
|
||||
<text class="text-24 text-gray">{{ item.name }}</text>
|
||||
</button>
|
||||
<view v-else class="share-box" @click="onBtn(index)">
|
||||
<image :src="`https://static.tbmall.xin/static/new/share_${index}.png`" class="share-img">
|
||||
<view v-else class="share-box" @click="onBtn(item.originalIndex)">
|
||||
<image :src="`https://static.tbmall.xin/static/new/share_${item.originalIndex}.png`" class="share-img">
|
||||
</image>
|
||||
<text class="text-24 text-gray">{{ item }}</text>
|
||||
<text class="text-24 text-gray">{{ item.name }}</text>
|
||||
</view>
|
||||
</block>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<view class="share-box" @click="onShare(index)" v-for="(item, index) in appBtnList" :key="index">
|
||||
<image :src="`https://cex-pub.s3.ap-southeast-1.amazonaws.com/static/new/share_${index}.png`"
|
||||
<view class="share-box" @click="onShare(item.originalIndex)" v-for="(item, index) in filteredAppBtnList" :key="index">
|
||||
<image :src="`https://cex-pub.s3.ap-southeast-1.amazonaws.com/static/new/share_${item.originalIndex}.png`"
|
||||
class="share-img"></image>
|
||||
<text class="text-24 text-gray">{{ item }}</text>
|
||||
<text class="text-24 text-gray">{{ item.name }}</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
<view class="share-top-image-view">
|
||||
<image :src="tu" class="tu-img"></image>
|
||||
<view class="image-view-bottom">
|
||||
<view class="image-view-bottom-left">
|
||||
<view class="image-view-bottom-left" :style="{ borderRight: price ? '2rpx solid #EBEBEB' : 'none', flex: price ? 'none' : '1' }">
|
||||
<text class="text-24 text-gray text-overflow1">{{ text }}</text>
|
||||
</view>
|
||||
<view class="image-view-bottom-right">
|
||||
<view class="image-view-bottom-right" v-if="price">
|
||||
<text class="text-24 text-zi">¥{{ price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -86,8 +86,39 @@ export default {
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
hideCopyAndQQ: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isInvitePage() {
|
||||
if (this.hideCopyAndQQ) return true;
|
||||
try {
|
||||
const pages = getCurrentPages();
|
||||
const current = pages[pages.length - 1];
|
||||
const route = current?.route || current?.$page?.fullPath || '';
|
||||
return route.includes('invite_friends');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
filteredBtnList() {
|
||||
const items = this.btnList.map((name, originalIndex) => ({ name, originalIndex }));
|
||||
if (this.isInvitePage) {
|
||||
return items.filter(item => item.name !== '微信');
|
||||
}
|
||||
return items;
|
||||
},
|
||||
filteredAppBtnList() {
|
||||
const items = this.appBtnList.map((name, originalIndex) => ({ name, originalIndex }));
|
||||
if (this.isInvitePage) {
|
||||
return items.filter(item => item.name !== '复制链接' && item.name !== 'QQ');
|
||||
}
|
||||
return items;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
@@ -1,166 +1,354 @@
|
||||
<template>
|
||||
<view class="invite_friends_warp">
|
||||
<view class="invite_img">
|
||||
<image :src="inviteImage" class="invite_imgage" v-if="inviteImage"></image>
|
||||
<view class="loading" v-else>
|
||||
<text>加载分享图片中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="invite_but">
|
||||
<!-- 修改为直接分享图片 -->
|
||||
<button
|
||||
class="share-btn"
|
||||
@click="shareImage"
|
||||
>
|
||||
分享好友
|
||||
</button>
|
||||
</view>
|
||||
<up-toast ref="uToastRef"></up-toast>
|
||||
<qiaobao-assistant page-key="pages/other_package/invite_friends/invite_friends" title="我的推广" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSharePoster, generateSharePoster } from "@/api/common.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
inviteImage: null,
|
||||
imageLoading: false,
|
||||
shareImg:''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getSharePoster();
|
||||
},
|
||||
methods: {
|
||||
// 将base64转换为本地临时文件
|
||||
async base64ToTempFile(base64Data) {
|
||||
try {
|
||||
// 移除base64前缀
|
||||
const base64 = base64Data.replace(/^data:image\/\w+;base64,/, '');
|
||||
// 将base64转换为ArrayBuffer
|
||||
const arrayBuffer = uni.base64ToArrayBuffer(base64);
|
||||
// 获取文件系统管理器
|
||||
const fs = uni.getFileSystemManager();
|
||||
// 生成临时文件路径
|
||||
const tempFilePath = `${wx.env.USER_DATA_PATH}/share_${Date.now()}.png`;
|
||||
// 写入文件
|
||||
fs.writeFileSync(tempFilePath, arrayBuffer, 'binary');
|
||||
return tempFilePath;
|
||||
} catch (error) {
|
||||
console.error('base64转换为临时文件失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 查询分享模版
|
||||
async getSharePoster() {
|
||||
this.imageLoading = true;
|
||||
try {
|
||||
const resp = await getSharePoster();
|
||||
if (resp && resp.bizcode === 100) {
|
||||
const data = resp.data[0];
|
||||
// 生成分享图
|
||||
const generateResp = await generateSharePoster({ id: data.id });
|
||||
if (generateResp && generateResp.bizcode === 100) {
|
||||
this.inviteImage = generateResp.data;
|
||||
// 将base64转换为本地临时文件并存储到shareImg
|
||||
this.shareImg = await this.base64ToTempFile(generateResp.data);
|
||||
console.log('图片转换成功:', this.shareImg);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取分享图失败:", error);
|
||||
} finally {
|
||||
this.imageLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 使用wx.showShareImageMenu分享图片到微信好友
|
||||
async shareImage() {
|
||||
// 确保有分享图再执行分享
|
||||
if (!this.shareImg) {
|
||||
this.$refs.uToastRef.show({
|
||||
type: 'warning',
|
||||
message: "分享图正在加载,请稍后重试",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用微信原生API wx.showShareImageMenu分享图片
|
||||
wx.showShareImageMenu({
|
||||
path: this.shareImg, // 本地临时文件路径
|
||||
success: () => {
|
||||
console.log('分享成功');
|
||||
this.$refs.uToastRef.show({
|
||||
type: 'success',
|
||||
message: "分享成功",
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('分享失败:', err);
|
||||
this.$refs.uToastRef.show({
|
||||
type: 'error',
|
||||
message: "分享失败,请稍后重试",
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('分享过程出错:', error);
|
||||
this.$refs.uToastRef.show({
|
||||
type: 'error',
|
||||
message: "分享失败,请稍后重试",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invite_friends_warp {
|
||||
height: calc(100vh - 80rpx);
|
||||
padding: 10rpx 40rpx 40rpx;
|
||||
}
|
||||
|
||||
.invite_img {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 920rpx;
|
||||
}
|
||||
|
||||
.invite_imgage {
|
||||
width: 100%;
|
||||
height: 920rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.invite_but {
|
||||
padding: 20rpx 50rpx;
|
||||
margin-top: 52rpx;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
width: 100%;
|
||||
background: $app-bt-bj;
|
||||
border-radius: 45rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
// 清除默认按钮样式
|
||||
border: none;
|
||||
outline: none;
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
<template>
|
||||
<view class="invite_friends_warp">
|
||||
<view class="invite_img">
|
||||
<image :src="inviteImage" class="invite_imgage" v-if="inviteImage"></image>
|
||||
<view class="loading" v-else>
|
||||
<text>加载分享图片中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="invite_but">
|
||||
<button
|
||||
class="share-btn"
|
||||
@click="openSharePopup"
|
||||
>
|
||||
分享好友
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 分享弹窗 -->
|
||||
<SharePopup
|
||||
ref="share"
|
||||
v-if="shareOpen"
|
||||
:show="shareShow"
|
||||
@close="onClose"
|
||||
@wxShare="onWxShare"
|
||||
@share="onShare"
|
||||
:id="posterId"
|
||||
text="我的推广海报"
|
||||
:tu="inviteImage || shareImg"
|
||||
></SharePopup>
|
||||
|
||||
<up-toast ref="uToastRef"></up-toast>
|
||||
<qiaobao-assistant page-key="pages/other_package/invite_friends/invite_friends" title="我的推广" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSharePoster, generateSharePoster } from "@/api/common.js";
|
||||
import SharePopup from "@/pages/other_package/components/share-popup.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SharePopup
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inviteImage: null,
|
||||
imageLoading: false,
|
||||
shareImg: '',
|
||||
shareShow: false,
|
||||
shareOpen: true,
|
||||
posterId: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getSharePoster();
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: "邀请您体验信任桥TB商城",
|
||||
path: "/pages/home/home",
|
||||
imageUrl: this.inviteImage || this.shareImg,
|
||||
};
|
||||
},
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: "邀请您体验信任桥TB商城",
|
||||
query: "",
|
||||
imageUrl: this.inviteImage || this.shareImg,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 打开分享弹窗 / 微信小程序下直接触发朋友圈(图片分享菜单)效果
|
||||
openSharePopup() {
|
||||
if (!this.inviteImage && !this.shareImg) {
|
||||
this.$refs.uToastRef.show({
|
||||
type: 'warning',
|
||||
message: "分享图正在加载,请稍后重试",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.onWxShare(1);
|
||||
return;
|
||||
// #endif
|
||||
|
||||
this.shareShow = true;
|
||||
},
|
||||
|
||||
// 关闭分享弹窗
|
||||
onClose() {
|
||||
this.shareShow = false;
|
||||
},
|
||||
|
||||
// 微信小程序环境分享
|
||||
onWxShare(index) {
|
||||
console.log('小程序分享', index);
|
||||
if (index === 0) {
|
||||
// 微信好友: 已通过 button open-type="share" 触发,关闭弹窗即可
|
||||
this.onClose();
|
||||
} else if (index === 1) {
|
||||
// 朋友圈: 唤起小程序原生分享菜单或系统提示
|
||||
// #ifdef MP-WEIXIN
|
||||
if (typeof wx !== 'undefined' && wx.showShareImageMenu && this.shareImg) {
|
||||
wx.showShareImageMenu({
|
||||
path: this.shareImg,
|
||||
fail: () => {
|
||||
uni.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ["shareTimeline"]
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ["shareTimeline"]
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
this.onClose();
|
||||
}
|
||||
},
|
||||
|
||||
// App 及 H5 环境分享
|
||||
onShare(index) {
|
||||
console.log('App/H5分享', index);
|
||||
if (index === 0) {
|
||||
// 微信好友
|
||||
// #ifdef APP-PLUS
|
||||
uni.share({
|
||||
provider: 'weixin',
|
||||
scene: 'WXSceneSession',
|
||||
type: 2,
|
||||
imageUrl: this.shareImg || this.inviteImage,
|
||||
success: () => {
|
||||
this.$refs.uToastRef.show({ type: 'success', message: '分享成功' });
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('App分享好友失败:', err);
|
||||
this.savePosterToAlbum();
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
uni.previewImage({ urls: [this.shareImg || this.inviteImage] });
|
||||
this.$refs.uToastRef.show({ type: 'info', message: '请长按图片发送给好友' });
|
||||
// #endif
|
||||
this.onClose();
|
||||
} else if (index === 1) {
|
||||
// 微信朋友圈
|
||||
// #ifdef APP-PLUS
|
||||
uni.share({
|
||||
provider: 'weixin',
|
||||
scene: 'WXSceneTimeline',
|
||||
type: 2,
|
||||
imageUrl: this.shareImg || this.inviteImage,
|
||||
success: () => {
|
||||
this.$refs.uToastRef.show({ type: 'success', message: '分享成功' });
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('App分享朋友圈失败:', err);
|
||||
this.savePosterToAlbum();
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
uni.previewImage({ urls: [this.shareImg || this.inviteImage] });
|
||||
this.$refs.uToastRef.show({ type: 'info', message: '请长按图片分享到朋友圈' });
|
||||
// #endif
|
||||
this.onClose();
|
||||
} else if (index === 2) {
|
||||
// 复制链接
|
||||
uni.setClipboardData({
|
||||
data: 'https://m.tbmall.xin/#/pages/home/home',
|
||||
success: () => {
|
||||
this.$refs.uToastRef.show({ type: 'success', message: '链接已复制到剪贴板' });
|
||||
}
|
||||
});
|
||||
this.onClose();
|
||||
} else if (index === 3) {
|
||||
// 保存图片
|
||||
this.savePosterToAlbum();
|
||||
this.onClose();
|
||||
} else if (index === 4) {
|
||||
// QQ
|
||||
this.$refs.uToastRef.show({ type: 'warning', message: '暂未开放' });
|
||||
this.onClose();
|
||||
}
|
||||
},
|
||||
|
||||
// 保存海报至相册
|
||||
savePosterToAlbum() {
|
||||
const imgPath = this.shareImg || this.inviteImage;
|
||||
if (!imgPath) {
|
||||
this.$refs.uToastRef.show({ type: 'warning', message: '海报图片生成中,请稍后重试' });
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS || MP-WEIXIN
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: imgPath,
|
||||
success: () => {
|
||||
this.$refs.uToastRef.show({ type: 'success', message: '海报已保存至手机相册' });
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('保存至相册失败:', err);
|
||||
if (err && err.errMsg && err.errMsg.includes('auth')) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '需要保存图片到相册权限,请在设置中开启相册权限',
|
||||
success: (res) => {
|
||||
if (res.confirm) uni.openSetting();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.previewImage({ urls: [imgPath] });
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
uni.previewImage({ urls: [imgPath] });
|
||||
this.$refs.uToastRef.show({ type: 'info', message: '请长按图片保存到手机相册' });
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 将base64转换为本地临时文件
|
||||
base64ToTempFile(base64Data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!base64Data) {
|
||||
return reject(new Error('Base64数据为空'));
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const bitmap = new plus.nativeObj.Bitmap('poster_' + Date.now());
|
||||
bitmap.loadBase64Data(
|
||||
base64Data,
|
||||
() => {
|
||||
const tempPath = `_doc/share_${Date.now()}.png`;
|
||||
bitmap.save(
|
||||
tempPath,
|
||||
{ overwrite: true, format: 'png', quality: 100 },
|
||||
(e) => {
|
||||
bitmap.clear();
|
||||
resolve(e.target);
|
||||
},
|
||||
(err) => {
|
||||
bitmap.clear();
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
(err) => {
|
||||
bitmap.clear();
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
const base64 = base64Data.replace(/^data:image\/\w+;base64,/, '');
|
||||
const arrayBuffer = uni.base64ToArrayBuffer(base64);
|
||||
const fs = typeof wx !== 'undefined' && wx.getFileSystemManager ? wx.getFileSystemManager() : uni.getFileSystemManager();
|
||||
const tempFilePath = `${wx.env.USER_DATA_PATH}/share_${Date.now()}.png`;
|
||||
fs.writeFileSync(tempFilePath, arrayBuffer, 'binary');
|
||||
resolve(tempFilePath);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
resolve(base64Data);
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
|
||||
// 查询分享模版
|
||||
async getSharePoster() {
|
||||
this.imageLoading = true;
|
||||
try {
|
||||
const resp = await getSharePoster();
|
||||
if (resp && resp.bizcode === 100) {
|
||||
const data = resp.data[0];
|
||||
if (data) this.posterId = data.id || '';
|
||||
// 生成分享图
|
||||
const generateResp = await generateSharePoster({ id: data.id });
|
||||
if (generateResp && generateResp.bizcode === 100) {
|
||||
this.inviteImage = generateResp.data;
|
||||
// 将base64转换为本地临时文件并存储到shareImg
|
||||
this.shareImg = await this.base64ToTempFile(generateResp.data);
|
||||
console.log('图片转换成功:', this.shareImg);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取分享图失败:", error);
|
||||
} finally {
|
||||
this.imageLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invite_friends_warp {
|
||||
height: calc(100vh - 80rpx);
|
||||
padding: 10rpx 40rpx 40rpx;
|
||||
}
|
||||
|
||||
.invite_img {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 920rpx;
|
||||
}
|
||||
|
||||
.invite_imgage {
|
||||
width: 100%;
|
||||
height: 920rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.invite_but {
|
||||
padding: 20rpx 50rpx;
|
||||
margin-top: 52rpx;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
width: 100%;
|
||||
background: $app-bt-bj;
|
||||
border-radius: 45rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
// 清除默认按钮样式
|
||||
border: none;
|
||||
outline: none;
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+6
-6
@@ -6,14 +6,14 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
// #endif
|
||||
|
||||
// export const BASE_URL = "https://api.tbmall.xin"; //生产
|
||||
// export const MILD_BASE_URL = "https://agent.tbmall.xin/"; //中台生产
|
||||
// export const kefuBaseUrl = "https://admin.tbmall.xin/api";
|
||||
export const BASE_URL = "https://api.tbmall.xin"; //生产
|
||||
export const MILD_BASE_URL = "https://agent.tbmall.xin/"; //中台生产
|
||||
export const kefuBaseUrl = "https://admin.tbmall.xin/api";
|
||||
|
||||
|
||||
export const BASE_URL = baseUrl; // 本地测试
|
||||
export const MILD_BASE_URL = "https://agent.o.tbmall.xin/"; //中台测试
|
||||
export const kefuBaseUrl = "https://admin.o.tbmall.xin/api";
|
||||
// export const BASE_URL = baseUrl; // 本地测试
|
||||
// export const MILD_BASE_URL = "https://agent.o.tbmall.xin/"; //中台测试
|
||||
// export const kefuBaseUrl = "https://admin.o.tbmall.xin/api";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,56 @@ const GLOBAL_HIDDEN_KEY = "tb_qiaobao_hidden_all";
|
||||
const PAGE_HIDDEN_KEY = "tb_qiaobao_hidden_pages";
|
||||
const DOCK_EDGE_KEY = "tb_qiaobao_dock_edge";
|
||||
const POSITION_KEY = "tb_qiaobao_position";
|
||||
const FIRST_OPEN_KEY = "tb_qiaobao_ai_first_open_v1";
|
||||
const FIRST_LOGIN_PREFIX = "tb_qiaobao_ai_first_login_v1_";
|
||||
const CHAT_SESSION_KEY = "tb_qiaobao_ai_session_v1";
|
||||
const CHAT_HISTORY_PREFIX = "tb_qiaobao_ai_history_v1_";
|
||||
|
||||
// Memory-only flag: closing Qiaobao lasts until the App/WebView process restarts.
|
||||
// It is intentionally not written to storage so users can always recover it by
|
||||
// reopening the App.
|
||||
let sessionClosed = false;
|
||||
|
||||
function getChatHistoryKey(scope) {
|
||||
const safeScope = String(scope || "guest").replace(/[^a-zA-Z0-9_-]/g, "_");
|
||||
return `${CHAT_HISTORY_PREFIX}${safeScope}`;
|
||||
}
|
||||
|
||||
export function getQiaobaoChatSessionId() {
|
||||
let value = uni.getStorageSync(CHAT_SESSION_KEY);
|
||||
if (!value) {
|
||||
value = `qb-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
||||
uni.setStorageSync(CHAT_SESSION_KEY, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function getQiaobaoChatHistory(scope = "guest") {
|
||||
const value = uni.getStorageSync(getChatHistoryKey(scope));
|
||||
return Array.isArray(value) ? value.slice(-30) : [];
|
||||
}
|
||||
|
||||
export function saveQiaobaoChatHistory(messages, scope = "guest") {
|
||||
const safe = Array.isArray(messages)
|
||||
? messages.filter((item) => item && ["user", "assistant"].includes(item.role) && item.content).slice(-30)
|
||||
: [];
|
||||
uni.setStorageSync(getChatHistoryKey(scope), safe);
|
||||
}
|
||||
|
||||
export function consumeQiaobaoAutoIntro(userId) {
|
||||
if (uni.getStorageSync(FIRST_OPEN_KEY) !== true) {
|
||||
uni.setStorageSync(FIRST_OPEN_KEY, true);
|
||||
return "first-open";
|
||||
}
|
||||
if (userId !== undefined && userId !== null && String(userId)) {
|
||||
const key = `${FIRST_LOGIN_PREFIX}${String(userId)}`;
|
||||
if (uni.getStorageSync(key) !== true) {
|
||||
uni.setStorageSync(key, true);
|
||||
return "first-login";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function getQiaobaoDockEdge() {
|
||||
return uni.getStorageSync(DOCK_EDGE_KEY) === "left" ? "left" : "right";
|
||||
@@ -44,6 +94,15 @@ export function isQiaobaoHidden(pagePath = getCurrentPagePath()) {
|
||||
return hiddenAll === true || (Array.isArray(hiddenPages) && hiddenPages.includes(pagePath));
|
||||
}
|
||||
|
||||
export function isQiaobaoClosedForSession() {
|
||||
return sessionClosed;
|
||||
}
|
||||
|
||||
export function closeQiaobaoForSession() {
|
||||
sessionClosed = true;
|
||||
uni.$emit("qiaobao-visibility-change", { scope: "close" });
|
||||
}
|
||||
|
||||
export function hideQiaobaoForPage(pagePath = getCurrentPagePath(), edge = "right") {
|
||||
const hiddenPages = uni.getStorageSync(PAGE_HIDDEN_KEY);
|
||||
const nextPages = Array.isArray(hiddenPages) ? [...hiddenPages] : [];
|
||||
|
||||
Reference in New Issue
Block a user