feat:优化上线后的改造

This commit is contained in:
2026-08-26 17:00:58 +08:00
parent d5253e3202
commit 23b30c8f04
11 changed files with 1310 additions and 560 deletions
+6
View File
@@ -49,6 +49,12 @@ export default {
@import "@/uni.scss";
@import "@/common.scss";
page {
width: 100%;
max-width: 100%;
overflow-x: hidden;
}
.container {
padding: 15px;
}
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+37 -15
View File
@@ -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 {
+44 -13
View File
@@ -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: {
@@ -7,52 +7,278 @@
</view>
</view>
<view class="invite_but">
<!-- 修改为直接分享图片 -->
<button
class="share-btn"
@click="shareImage"
@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>
<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:''
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: {
// 将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;
// 打开分享弹窗 / 微信小程序下直接触发朋友圈(图片分享菜单)效果
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
});
},
// 查询分享模版
@@ -62,6 +288,7 @@
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) {
@@ -76,45 +303,6 @@
} 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: "分享失败,请稍后重试",
});
}
}
}
};
+6 -6
View File
@@ -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";
+59
View File
@@ -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] : [];