359 lines
8.8 KiB
Vue
359 lines
8.8 KiB
Vue
<template>
|
||
<full-page>
|
||
<view class="claim-page">
|
||
<view v-if="tip" class="tip">{{ tip }}</view>
|
||
<view v-else class="tip">加载中...</view>
|
||
</view>
|
||
|
||
<!-- 海报领取弹窗 -->
|
||
<view v-if="posterVisible" class="poster-mask">
|
||
<view class="poster-dialog">
|
||
<image
|
||
class="poster-image"
|
||
:src="issue?.posterUrl || ''"
|
||
mode="widthFix"
|
||
/>
|
||
<view class="poster-info">
|
||
<view class="poster-title">{{ issue?.activityName || "体验券" }}</view>
|
||
<view class="poster-desc">{{ projectText }}</view>
|
||
<view class="poster-desc">{{ validText }}</view>
|
||
</view>
|
||
<view class="poster-btn" :class="{ disabled: claiming }" @click="acceptCoupon">
|
||
{{ claiming ? "领取中..." : "接受" }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</full-page>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, ref } from "vue";
|
||
import { storeToRefs } from "pinia";
|
||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||
import useAccount from "@/store/account";
|
||
import api from "@/utils/api";
|
||
import { confirm, hideLoading, loading, toast } from "@/utils/widget";
|
||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||
import FullPage from "@/components/full-page.vue";
|
||
|
||
const { account } = storeToRefs(useAccount());
|
||
const { login } = useAccount();
|
||
|
||
const corpId = ref("");
|
||
const issueId = ref("");
|
||
const memberId = ref("");
|
||
const issue = ref(null);
|
||
const tip = ref("");
|
||
const claiming = ref(false);
|
||
const autoPrompted = ref(false);
|
||
const posterVisible = ref(false);
|
||
|
||
const projectText = computed(() => {
|
||
const projects = Array.isArray(issue.value?.projectSnapshot) ? issue.value.projectSnapshot : [];
|
||
if (!projects.length) return "未配置项目";
|
||
return projects.map((p) => `${p.projectName || "项目"}×${p.usageCount || 1}`).join("、");
|
||
});
|
||
|
||
const validText = computed(() => {
|
||
const rule = issue.value?.projectValidRuleSnapshot || {};
|
||
if (issue.value?.projectExpireTime) {
|
||
return `有效期至 ${formatDate(issue.value.projectExpireTime)}`;
|
||
}
|
||
if (rule.type === "fixedDate" && rule.fixedDate) {
|
||
return `有效期至 ${formatDate(rule.fixedDate)}`;
|
||
}
|
||
if (rule.type === "daysAfterClaim") {
|
||
return `领取后 ${rule.days || 0} 天有效`;
|
||
}
|
||
return "有效期以活动规则为准";
|
||
});
|
||
|
||
function formatDate(ts) {
|
||
const d = new Date(Number(ts));
|
||
if (Number.isNaN(d.getTime())) return "-";
|
||
const y = d.getFullYear();
|
||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||
const day = String(d.getDate()).padStart(2, "0");
|
||
return `${y}-${m}-${day}`;
|
||
}
|
||
|
||
function buildClaimContent() {
|
||
const name = issue.value?.activityName || "体验券";
|
||
return `您收到一张【${name}】体验券,包含${projectText.value},是否存入您的权益卡包?`;
|
||
}
|
||
|
||
async function ensureLogin() {
|
||
if (!account.value) await login();
|
||
if (!account.value) {
|
||
tip.value = "请先登录后再领取体验券";
|
||
uni.navigateTo({ url: "/pages/login/login" });
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
async function loadIssueDetail() {
|
||
const res = await api(
|
||
"getExperienceCouponIssueDetail",
|
||
{ corpId: corpId.value, issueId: issueId.value },
|
||
false
|
||
);
|
||
if (!res?.success || !res.data) {
|
||
tip.value = res?.message || "体验券不存在或已失效";
|
||
return false;
|
||
}
|
||
issue.value = res.data;
|
||
if (!memberId.value && res.data.customerId) {
|
||
memberId.value = res.data.customerId;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
async function hasBoundArchive() {
|
||
const openid = account.value?.openid || "";
|
||
if (!openid || !corpId.value) return false;
|
||
|
||
if (memberId.value) {
|
||
try {
|
||
const detail = await api(
|
||
"getCustomerByCustomerId",
|
||
{ corpId: corpId.value, customerId: memberId.value },
|
||
false
|
||
);
|
||
const customer = detail?.data || null;
|
||
if (customer && String(customer.miniAppId || "") === String(openid)) {
|
||
return true;
|
||
}
|
||
} catch (e) {
|
||
console.warn("getCustomerByCustomerId failed", e);
|
||
}
|
||
}
|
||
|
||
try {
|
||
const countRes = await api(
|
||
"getWxAppCustomerCount",
|
||
{ miniAppId: openid, corpId: corpId.value },
|
||
false
|
||
);
|
||
return Number(countRes?.data || 0) > 0;
|
||
} catch (e) {
|
||
console.warn("getWxAppCustomerCount failed", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function goBindArchive() {
|
||
uni.navigateTo({
|
||
url: `/pages/archive/archive-manage?corpId=${encodeURIComponent(corpId.value || "")}`,
|
||
});
|
||
}
|
||
|
||
function isExpiredIssue() {
|
||
const expireAt = Number(issue.value?.projectExpireTime || 0);
|
||
if (expireAt && Date.now() > expireAt) return true;
|
||
const rule = issue.value?.projectValidRuleSnapshot || {};
|
||
if (rule.type === "fixedDate" && rule.fixedDate && Date.now() > Number(rule.fixedDate)) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
async function acceptCoupon() {
|
||
if (claiming.value || !issue.value) return;
|
||
|
||
try {
|
||
await confirm(buildClaimContent(), {
|
||
title: "确认接受",
|
||
confirmText: "确认接受",
|
||
cancelText: "暂不领取",
|
||
});
|
||
} catch {
|
||
return;
|
||
}
|
||
|
||
claiming.value = true;
|
||
loading("领取中...");
|
||
try {
|
||
const res = await api("claimExperienceCoupon", {
|
||
corpId: corpId.value,
|
||
issueId: issueId.value,
|
||
claimUserId: account.value?.openid || "",
|
||
});
|
||
hideLoading();
|
||
if (!res?.success) {
|
||
tip.value = res?.message || "领取失败";
|
||
toast(tip.value);
|
||
return;
|
||
}
|
||
toast("领取成功");
|
||
posterVisible.value = false;
|
||
uni.redirectTo({
|
||
url: `/pages/experience-coupon/my-rights?corpId=${encodeURIComponent(corpId.value)}&customerId=${encodeURIComponent(memberId.value || "")}&name=${encodeURIComponent(issue.value.customerName || "")}`,
|
||
});
|
||
} catch (e) {
|
||
hideLoading();
|
||
tip.value = e?.message || "领取失败";
|
||
toast(tip.value);
|
||
} finally {
|
||
claiming.value = false;
|
||
}
|
||
}
|
||
|
||
async function promptClaim() {
|
||
if (autoPrompted.value) return;
|
||
autoPrompted.value = true;
|
||
|
||
if (!issue.value) return;
|
||
if (issue.value.status === "claimed") {
|
||
tip.value = "该体验券已领取";
|
||
uni.redirectTo({
|
||
url: `/pages/experience-coupon/my-rights?corpId=${encodeURIComponent(corpId.value)}&customerId=${encodeURIComponent(memberId.value || "")}`,
|
||
});
|
||
return;
|
||
}
|
||
if (issue.value.status === "void") {
|
||
tip.value = "该体验券已作废";
|
||
return;
|
||
}
|
||
if (issue.value.status && issue.value.status !== "issued") {
|
||
tip.value = "当前状态不可领取";
|
||
return;
|
||
}
|
||
if (isExpiredIssue()) {
|
||
tip.value = "体验券已过期,无法领取";
|
||
return;
|
||
}
|
||
|
||
const bound = await hasBoundArchive();
|
||
if (!bound) {
|
||
tip.value = "请先绑定档案后再领取体验券";
|
||
try {
|
||
await confirm("请先绑定档案后再领取体验券", {
|
||
title: "提示",
|
||
confirmText: "去绑定",
|
||
cancelText: "取消",
|
||
});
|
||
goBindArchive();
|
||
} catch {
|
||
// ignore
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!issue.value.posterUrl) {
|
||
tip.value = "该体验券暂无海报,无法领取";
|
||
return;
|
||
}
|
||
|
||
tip.value = "";
|
||
posterVisible.value = true;
|
||
}
|
||
|
||
async function bootstrap() {
|
||
loading("加载中...");
|
||
try {
|
||
const okLogin = await ensureLogin();
|
||
if (!okLogin) return;
|
||
const okDetail = await loadIssueDetail();
|
||
if (!okDetail) return;
|
||
await promptClaim();
|
||
} finally {
|
||
hideLoading();
|
||
}
|
||
}
|
||
|
||
onLoad((options = {}) => {
|
||
corpId.value = normalizeCorpId(options.corpId || "");
|
||
issueId.value = options.issueId || options.id || "";
|
||
memberId.value = options.memberId || options.customerId || "";
|
||
if (!corpId.value || !issueId.value) {
|
||
tip.value = "领取参数不完整";
|
||
toast(tip.value);
|
||
return;
|
||
}
|
||
bootstrap();
|
||
});
|
||
|
||
onShow(() => {
|
||
if (issue.value && issue.value.status === "issued" && tip.value.includes("绑定")) {
|
||
autoPrompted.value = false;
|
||
promptClaim();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.claim-page {
|
||
min-height: 100%;
|
||
padding: 30rpx;
|
||
box-sizing: border-box;
|
||
background: #f6fafa;
|
||
}
|
||
|
||
.tip {
|
||
margin-top: 120rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.poster-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1000;
|
||
background: rgba(0, 0, 0, 0.65);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.poster-dialog {
|
||
width: 100%;
|
||
max-width: 620rpx;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.poster-image {
|
||
width: 100%;
|
||
display: block;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.poster-info {
|
||
padding: 24rpx 28rpx 8rpx;
|
||
}
|
||
|
||
.poster-title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #222;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.poster-desc {
|
||
font-size: 24rpx;
|
||
color: #888;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.poster-btn {
|
||
margin: 24rpx 28rpx 32rpx;
|
||
height: 80rpx;
|
||
border-radius: 40rpx;
|
||
background: #065bd6;
|
||
color: #fff;
|
||
font-size: 30rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.poster-btn.disabled {
|
||
opacity: 0.6;
|
||
}
|
||
</style>
|