2026-08-12 14:27:07 +08:00

312 lines
7.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-content">{{ buildClaimContent() }}</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 bindingTeamId = ref("");
const projectNameText = computed(() => {
const projects = Array.isArray(issue.value?.projectSnapshot) ? issue.value.projectSnapshot : [];
if (!projects.length) return "未配置项目";
return projects.map((p) => p.projectName || "项目").join("、");
});
function buildClaimContent() {
const name = issue.value?.activityName || "体验券";
return `您收到一张【${name}】体验券,包含${projectNameText.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 || !memberId.value) return false;
try {
const detail = await api(
"getCustomerByCustomerId",
{ corpId: corpId.value, customerId: memberId.value },
false
);
const customer = detail?.data || null;
const teamIds = Array.isArray(customer?.teamId) ? customer.teamId : [customer?.teamId];
bindingTeamId.value = teamIds.find(Boolean) || "";
return Boolean(customer && String(customer.miniAppId || "") === String(openid));
} catch (e) {
console.warn("getCustomerByCustomerId failed", e);
return false;
}
}
function goBindArchive() {
const params = [
`corpId=${encodeURIComponent(corpId.value || "")}`,
`teamId=${encodeURIComponent(bindingTeamId.value || "")}`,
].join("&");
uni.navigateTo({
url: `/pages/archive/edit-archive?${params}`,
});
}
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;
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;
}
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();
}
}
function parseOptions(options = {}) {
const href = typeof options.q === "string" ? decodeURIComponent(options.q) : "";
const [, url = ""] = href.split("?");
return url.split("&").reduce((acc, cur) => {
if (!cur) return acc;
const [key, val = ''] = cur.split("=");
if (!key) return acc;
acc[key] = val
return acc;
}, {});
}
onLoad((opts = {}) => {
let options = {};
if (opts.q) {
options = { ...parseOptions(opts) }
} else {
options = { ...opts }
}
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;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.65);
}
.poster-dialog {
width: 100%;
max-width: 620rpx;
overflow: hidden;
border-radius: 20rpx;
background: #fff;
}
.poster-image {
display: block;
width: 100%;
max-height: 700rpx;
background: #f5f7fa;
}
.poster-content {
padding: 28rpx 30rpx 8rpx;
color: #333;
font-size: 28rpx;
line-height: 1.6;
}
.poster-btn {
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
margin: 24rpx 30rpx 30rpx;
border-radius: 40rpx;
background: #065bd6;
color: #fff;
font-size: 30rpx;
}
.poster-btn.disabled {
opacity: 0.6;
}
</style>