463 lines
11 KiB
Vue
463 lines
11 KiB
Vue
<template>
|
||
<view class="asset-page asset-theme bmt-agree-page">
|
||
<asset-page-shell title="签约协议" />
|
||
|
||
<view v-if="signStatus">
|
||
<view class="bmt-agree-content glass-panel">
|
||
<scroll-view class="bmt-agree-scroll" scroll-y :style="{ height: scrollViewHeight + 'px' }"
|
||
:scroll-with-animation="false" :show-scrollbar="false" @scroll="handleScroll">
|
||
<text class="bmt-agree-title">{{ signData.name }}</text>
|
||
<view class="bmt-agree-desc">
|
||
<text class="bmt-agree-text">{{ signData.content || "暂无协议内容" }}</text>
|
||
<view v-if="!isReachBottom" class="bmt-agree-tip">请阅读到底部后签约</view>
|
||
<view class="bmt-agree-padding"></view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<view class="bmt-agree-bottom">
|
||
<text class="bmt-agree-bottom__hint">{{ isReachBottom ? "已阅读到底部" : "向下滑动阅读签约" }}</text>
|
||
<view class="bmt-agree-bottom__row">
|
||
<view class="bmt-agree-btn bmt-agree-btn--ghost" @click="cancel">取消</view>
|
||
<view class="bmt-agree-btn bmt-agree-btn--primary"
|
||
:class="{ 'is-disabled': !isReachBottom || signing }" @click="toSign">
|
||
{{ signing ? "签约中..." : "确认签约" }}
|
||
</view>
|
||
</view>
|
||
<view class="bmt-agree-copy">
|
||
<image class="bmt-agree-copy__icon" src="https://imgs.agrimedia.cn/copy-icon-sign.png" mode="aspectFit" />
|
||
<text class="bmt-agree-copy__text">易保全在线签约系统</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="bmt-agree-success">
|
||
<image class="bmt-agree-success__image" src="https://imgs.agrimedia.cn/bm-bmt/success.png" mode="aspectFit" />
|
||
<text class="bmt-agree-success__text">恭喜签约完成</text>
|
||
<view class="bmt-agree-success__btn" :class="{ 'is-disabled': countdown > 0 }" @click="compute">
|
||
{{ countdown > 0 ? `完成(${countdown}s)` : "完成" }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||
import { checkBmtSignCondition, startBmtSigning } from "../../api/bmt";
|
||
|
||
const READ_BOTTOM_THRESHOLD = 15;
|
||
const DONE_COUNTDOWN_SECONDS = 5;
|
||
|
||
export default {
|
||
components: {
|
||
AssetPageShell,
|
||
},
|
||
data() {
|
||
return {
|
||
signData: {
|
||
name: "",
|
||
content: "",
|
||
},
|
||
signStatus: true,
|
||
signing: false,
|
||
isReachBottom: false,
|
||
scrollViewHeight: 0,
|
||
scrollTimer: null,
|
||
countdownTimer: null,
|
||
countdown: 0,
|
||
contractId: "",
|
||
};
|
||
},
|
||
onLoad() {
|
||
this.loadAgreement();
|
||
},
|
||
onReady() {
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
this.calcScrollViewHeight();
|
||
}, 100);
|
||
});
|
||
},
|
||
onShow() {
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
this.calcScrollViewHeight();
|
||
}, 300);
|
||
});
|
||
},
|
||
onUnload() {
|
||
if (this.scrollTimer) {
|
||
clearTimeout(this.scrollTimer);
|
||
}
|
||
|
||
this.clearCountdownTimer();
|
||
},
|
||
methods: {
|
||
async loadAgreement() {
|
||
try {
|
||
// 协议内容与签约 id 都取自 go_to_sign_check_bmt
|
||
const data =
|
||
(await checkBmtSignCondition({}, {
|
||
showLoading: true,
|
||
loadingText: "加载中",
|
||
})) || {};
|
||
|
||
this.contractId = String(data.id || "");
|
||
this.signData = {
|
||
name: String(data.name || "签约协议"),
|
||
content: String(data.content || ""),
|
||
};
|
||
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
this.calcScrollViewHeight();
|
||
setTimeout(() => {
|
||
this.calcScrollViewHeight();
|
||
}, 200);
|
||
}, 100);
|
||
});
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: (error && error.message) || "获取签约信息失败,请重试",
|
||
icon: "none",
|
||
});
|
||
}
|
||
},
|
||
calcScrollViewHeight() {
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
const windowHeight = Number(systemInfo.windowHeight || 0);
|
||
const query = uni.createSelectorQuery().in(this);
|
||
|
||
query.select(".bmt-agree-content").boundingClientRect();
|
||
query.select(".bmt-agree-desc").boundingClientRect();
|
||
query.select(".bmt-agree-bottom").boundingClientRect();
|
||
query.exec((res) => {
|
||
const contentRect = res && res[0];
|
||
const descRect = res && res[1];
|
||
const bottomRect = res && res[2];
|
||
|
||
const top = contentRect && contentRect.top ? Number(contentRect.top) : 0;
|
||
const bottomHeight =
|
||
bottomRect && bottomRect.height ? Number(bottomRect.height) : 150;
|
||
const available = windowHeight - top - bottomHeight - 16;
|
||
|
||
this.scrollViewHeight = Math.max(available, 200);
|
||
|
||
// 内容不足以滚动时,直接视为已读到底部,避免短协议无法签约
|
||
const descHeight = descRect && descRect.height ? Number(descRect.height) : 0;
|
||
|
||
if (descHeight && descHeight <= this.scrollViewHeight + READ_BOTTOM_THRESHOLD) {
|
||
this.isReachBottom = true;
|
||
}
|
||
});
|
||
},
|
||
handleScroll(event) {
|
||
if (this.scrollTimer) {
|
||
clearTimeout(this.scrollTimer);
|
||
}
|
||
|
||
this.scrollTimer = setTimeout(() => {
|
||
this.checkScrollBottom(event);
|
||
}, 50);
|
||
},
|
||
checkScrollBottom(event) {
|
||
if (this.isReachBottom) {
|
||
return;
|
||
}
|
||
|
||
const detail = (event && event.detail) || {};
|
||
const scrollTop = Number(detail.scrollTop || 0);
|
||
const scrollHeight = Number(detail.scrollHeight || 0);
|
||
const clientHeight = this.scrollViewHeight;
|
||
const scrollPercent = scrollHeight
|
||
? (scrollTop + clientHeight) / scrollHeight
|
||
: 1;
|
||
|
||
if (
|
||
scrollTop + clientHeight >= scrollHeight - READ_BOTTOM_THRESHOLD ||
|
||
scrollPercent >= 0.95
|
||
) {
|
||
this.isReachBottom = true;
|
||
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
this.calcScrollViewHeight();
|
||
}, 50);
|
||
});
|
||
|
||
uni.showToast({
|
||
title: "已阅读到底部",
|
||
icon: "none",
|
||
duration: 1000,
|
||
});
|
||
}
|
||
},
|
||
cancel() {
|
||
uni.navigateBack({
|
||
delta: 1,
|
||
});
|
||
},
|
||
async toSign() {
|
||
if (!this.isReachBottom || this.signing) {
|
||
return;
|
||
}
|
||
|
||
this.signing = true;
|
||
|
||
try {
|
||
// 签约 id 取自 go_to_sign_check_bmt
|
||
const result =
|
||
(await startBmtSigning(
|
||
{
|
||
id: this.contractId,
|
||
},
|
||
{
|
||
showLoading: true,
|
||
loadingText: "签约中",
|
||
},
|
||
)) || {};
|
||
|
||
console.log("[BMT签约] 协议页 start_signing_bmt_v2 返回:", result.raw);
|
||
|
||
if (Number(result.status) === 0 && result.saasShortLink) {
|
||
this.navigateToExternalUrl(result.saasShortLink);
|
||
return;
|
||
}
|
||
|
||
this.signStatus = false;
|
||
this.startCountdown();
|
||
} catch (error) {
|
||
this.signing = false;
|
||
uni.showToast({
|
||
title: (error && error.message) || "签约失败,请重试",
|
||
icon: "none",
|
||
});
|
||
}
|
||
},
|
||
navigateToExternalUrl(targetUrl) {
|
||
if (!targetUrl) {
|
||
return;
|
||
}
|
||
|
||
if (typeof window !== "undefined" && window.location) {
|
||
window.location.href = targetUrl;
|
||
return;
|
||
}
|
||
|
||
if (
|
||
typeof plus !== "undefined" &&
|
||
plus.webview &&
|
||
plus.webview.currentWebview
|
||
) {
|
||
const currentWebview = plus.webview.currentWebview();
|
||
|
||
if (currentWebview && typeof currentWebview.loadURL === "function") {
|
||
currentWebview.loadURL(targetUrl);
|
||
return;
|
||
}
|
||
}
|
||
|
||
uni.navigateTo({
|
||
url: targetUrl,
|
||
});
|
||
},
|
||
/** 签约完成后固定倒计时,结束后才允许点「完成」 */
|
||
startCountdown() {
|
||
this.clearCountdownTimer();
|
||
this.countdown = DONE_COUNTDOWN_SECONDS;
|
||
|
||
this.countdownTimer = setInterval(() => {
|
||
this.countdown -= 1;
|
||
|
||
if (this.countdown <= 0) {
|
||
this.countdown = 0;
|
||
this.clearCountdownTimer();
|
||
}
|
||
}, 1000);
|
||
},
|
||
clearCountdownTimer() {
|
||
if (this.countdownTimer) {
|
||
clearInterval(this.countdownTimer);
|
||
this.countdownTimer = null;
|
||
}
|
||
},
|
||
compute() {
|
||
// 倒计时结束前不可点击
|
||
if (this.countdown > 0) {
|
||
return;
|
||
}
|
||
|
||
uni.navigateBack({
|
||
delta: 1,
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import "../../styles/tokens.scss";
|
||
@import "../../styles/common.scss";
|
||
|
||
.bmt-agree-page {
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.bmt-agree-content {
|
||
margin: 12rpx 24rpx;
|
||
padding: 28rpx 26rpx;
|
||
}
|
||
|
||
.bmt-agree-scroll {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.bmt-agree-title {
|
||
display: block;
|
||
margin-bottom: 30rpx;
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
color: $asset-text-main;
|
||
}
|
||
|
||
.bmt-agree-desc {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.bmt-agree-text {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
line-height: 1.8;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
color: $asset-text-muted;
|
||
}
|
||
|
||
.bmt-agree-tip {
|
||
margin-top: 24rpx;
|
||
padding-top: 30rpx;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: $asset-text-subtle;
|
||
}
|
||
|
||
.bmt-agree-padding {
|
||
height: 1rpx;
|
||
opacity: 0;
|
||
}
|
||
|
||
.bmt-agree-bottom {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
padding: 22rpx 32rpx calc(22rpx + env(safe-area-inset-bottom));
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
background: #191e32;
|
||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.18);
|
||
z-index: 30;
|
||
}
|
||
|
||
.bmt-agree-bottom__hint {
|
||
display: block;
|
||
margin-bottom: 20rpx;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: $asset-text-subtle;
|
||
}
|
||
|
||
.bmt-agree-bottom__row {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.bmt-agree-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
height: 80rpx;
|
||
border-radius: 40rpx;
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.bmt-agree-btn+.bmt-agree-btn {
|
||
margin-left: 20rpx;
|
||
}
|
||
|
||
.bmt-agree-btn--ghost {
|
||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: $asset-text-main;
|
||
}
|
||
|
||
.bmt-agree-btn--primary {
|
||
background: linear-gradient(135deg, #7e6cff 0%, #6b56f6 100%);
|
||
box-shadow: 0 14rpx 24rpx rgba(93, 79, 225, 0.22);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.bmt-agree-btn--primary.is-disabled {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.bmt-agree-copy {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.bmt-agree-copy__icon {
|
||
width: 112rpx;
|
||
height: 32rpx;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.bmt-agree-copy__text {
|
||
font-size: 24rpx;
|
||
color: $asset-text-subtle;
|
||
}
|
||
|
||
.bmt-agree-success {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding-top: 220rpx;
|
||
}
|
||
|
||
.bmt-agree-success__image {
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
}
|
||
|
||
.bmt-agree-success__text {
|
||
margin-top: 48rpx;
|
||
font-size: 36rpx;
|
||
font-weight: 500;
|
||
color: $asset-text-main;
|
||
}
|
||
|
||
.bmt-agree-success__btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 328rpx;
|
||
height: 80rpx;
|
||
margin-top: 60rpx;
|
||
border-radius: 40rpx;
|
||
background: linear-gradient(135deg, #7e6cff 0%, #6b56f6 100%);
|
||
box-shadow: 0 14rpx 24rpx rgba(93, 79, 225, 0.22);
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.bmt-agree-success__btn.is-disabled {
|
||
opacity: 0.5;
|
||
}
|
||
</style>
|