diff --git a/pages/archive/edit-archive.vue b/pages/archive/edit-archive.vue index e7908fd..9cf07f3 100644 --- a/pages/archive/edit-archive.vue +++ b/pages/archive/edit-archive.vue @@ -194,8 +194,12 @@ async function getResponsiblePerson() { return res && res.data ? res.data : '' } +function shouldBackAfterArchiveBound() { + return ['experienceCoupon', 'appointmentRegistration'].includes(source.value) +} + function backAfterArchiveBound() { - if (source.value === 'experienceCoupon') { + if (shouldBackAfterArchiveBound()) { uni.navigateBack() return; } @@ -228,8 +232,8 @@ async function init() { const res = bindCustomerId.value ? await getExperienceCouponBindArchive() : await getArchives(); if (res.length > 0) { visible.value = true; - } else if (source.value === 'experienceCoupon') { - await toast('体验券发放档案不存在或已绑定'); + } else if (bindCustomerId.value || shouldBackAfterArchiveBound()) { + await toast('指定绑定档案不存在或已绑定'); uni.navigateBack(); return; } diff --git a/pages/experience-coupon/claim.vue b/pages/experience-coupon/claim.vue index 4d4c539..b034eab 100644 --- a/pages/experience-coupon/claim.vue +++ b/pages/experience-coupon/claim.vue @@ -211,14 +211,21 @@ function openArchiveBindPage() { uni.navigateTo({ url: buildArchiveBindUrl() }); } +function toTimestamp(value) { + if (!value) return 0; + const numeric = Number(value); + if (Number.isFinite(numeric)) return numeric; + const parsed = new Date(value).getTime(); + return Number.isNaN(parsed) ? 0 : parsed; +} + +function getIssueExpireAt() { + return toTimestamp(issue.value?.activityEndTime || issue.value?.displayExpireTime); +} + 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; + const expireAt = getIssueExpireAt(); + return Boolean(expireAt && Date.now() > expireAt); } async function acceptCoupon() { diff --git a/pages/experience-coupon/my-coupons.vue b/pages/experience-coupon/my-coupons.vue index 19f8092..d3f1d9e 100644 --- a/pages/experience-coupon/my-coupons.vue +++ b/pages/experience-coupon/my-coupons.vue @@ -9,12 +9,13 @@ > 加载中... - 暂无待领取体验券 + 暂无体验券 体验券 - {{ item.activityName || "体验券" }} + + {{ item.activityName || "体验券" }} + + + + {{ item.displayStatusText }} + + 项目 @@ -43,14 +51,20 @@ 项目有效期 - {{ item.validText }} + {{ item.projectValidText }} - 活动有效期 - {{ item.activityValidText }} + 体验券有效期 + {{ item.couponValidText }} + + + 发送时间 + {{ item.issueTimeText }} - 点击查看海报领取 + + {{ item.actionText }} + @@ -102,9 +116,18 @@ const posterVisible = ref(false); const current = ref(null); const claiming = ref(false); +function toTimestamp(value) { + if (!value) return 0; + const numeric = Number(value); + if (Number.isFinite(numeric)) return numeric; + const parsed = new Date(value).getTime(); + return Number.isNaN(parsed) ? 0 : parsed; +} + function formatDate(ts) { - if (!ts) return ""; - const d = new Date(Number(ts)); + const time = toTimestamp(ts); + if (!time) return ""; + const d = new Date(time); if (Number.isNaN(d.getTime())) return ""; const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); @@ -112,15 +135,26 @@ function formatDate(ts) { return `${y}-${m}-${day}`; } +function formatDateTime(ts) { + const time = toTimestamp(ts); + if (!time) return "-"; + const d = new Date(time); + if (Number.isNaN(d.getTime())) return "-"; + const h = String(d.getHours()).padStart(2, "0"); + const min = String(d.getMinutes()).padStart(2, "0"); + return `${formatDate(time)} ${h}:${min}`; +} + function projectText(projects) { const arr = Array.isArray(projects) ? projects : []; if (!arr.length) return "未配置项目"; return arr.map((p) => `${p.projectName || "项目"}×${p.usageCount || 1}`).join("、"); } -function buildValidText(item) { +function buildProjectValidText(item) { const rule = item.projectValidRuleSnapshot || {}; - if (item.projectExpireTime) return `有效期至 ${formatDate(item.projectExpireTime)}`; + const projectExpireAt = toTimestamp(item.projectExpireTime); + if (projectExpireAt) return `有效期至 ${formatDate(projectExpireAt)}`; if (rule.type === "fixedDate" && rule.fixedDate) { return `有效期至 ${formatDate(rule.fixedDate)}`; } @@ -130,23 +164,20 @@ function buildValidText(item) { return "领取后按活动规则生效"; } -function buildActivityValidText(item) { +function getCouponExpireAt(item) { + return toTimestamp(item.activityEndTime || item.displayExpireTime); +} + +function buildCouponValidText(item) { const start = formatDate(item.activityStartTime) || "-"; - const end = formatDate(item.activityEndTime) || "-"; + const expireAt = getCouponExpireAt(item); + const end = formatDate(expireAt) || "-"; + if (start === "-" && end === "-") return "未配置有效期"; return `${start} 至 ${end}`; } -function getExpireAt(item) { - if (item.projectExpireTime) return Number(item.projectExpireTime); - const rule = item.projectValidRuleSnapshot || {}; - if (rule.type === "fixedDate" && rule.fixedDate) { - return Number(rule.fixedDate); - } - return 0; -} - function isExpired(item) { - const expireAt = getExpireAt(item); + const expireAt = getCouponExpireAt(item); if (!expireAt) return false; return Date.now() > expireAt; } @@ -184,20 +215,33 @@ async function resolveContext(options = {}) { return !!customerId.value; } -async function voidExpired(item) { - try { - await api( - "voidExperienceCouponIssue", - { - corpId: corpId.value, - issueId: item._id, - voidReason: "有效期内未领取,自动失效", - }, - false - ); - } catch (e) { - console.warn("auto void failed", e); +function resolveDisplayStatus(item) { + const status = item.displayStatus || item.status || "issued"; + if (status === "claimed") { + return { + displayStatus: "claimed", + displayStatusText: "已领取", + actionText: "已领取", + cardClass: "coupon-card--claimed", + statusClass: "coupon-status--claimed", + }; } + if (status === "expired" || status === "void" || isExpired(item)) { + return { + displayStatus: "expired", + displayStatusText: "已过期", + actionText: "已过期", + cardClass: "coupon-card--expired", + statusClass: "coupon-status--expired", + }; + } + return { + displayStatus: "issued", + displayStatusText: "待领取", + actionText: "点击查看海报领取", + cardClass: "coupon-card--issued", + statusClass: "coupon-status--issued", + }; } async function loadCoupons(options = {}) { @@ -213,7 +257,6 @@ async function loadCoupons(options = {}) { { corpId: corpId.value, customerId: customerId.value, - status: "issued", page: 1, pageSize: 100, }, @@ -225,20 +268,19 @@ async function loadCoupons(options = {}) { return; } const raw = res.list || res.data?.list || []; - const valid = []; - for (const item of raw) { - if (isExpired(item)) { - voidExpired(item); - continue; - } - valid.push({ - ...item, - projectText: projectText(item.projectSnapshot), - validText: buildValidText(item), - activityValidText: buildActivityValidText(item), - }); - } - list.value = valid; + list.value = raw + .map((item) => { + const status = resolveDisplayStatus(item); + return { + ...item, + ...status, + projectText: projectText(item.projectSnapshot), + projectValidText: buildProjectValidText(item), + couponValidText: buildCouponValidText(item), + issueTimeText: formatDateTime(item.issueTime || item.createTime), + }; + }) + .sort((a, b) => Number(b.issueTime || b.createTime || 0) - Number(a.issueTime || a.createTime || 0)); } finally { loading.value = false; } @@ -255,6 +297,10 @@ async function refreshCoupons() { } function openPoster(item) { + if (item?.displayStatus !== "issued") { + toast(item?.displayStatusText || "当前体验券不可领取"); + return; + } if (!item?.posterUrl) { toast("该体验券暂无海报"); return; @@ -338,6 +384,11 @@ onShow(async () => { box-shadow: 0 8rpx 10rpx 0 rgba(60, 169, 145, 0.06); } +.coupon-card--claimed, +.coupon-card--expired { + opacity: 0.78; +} + .coupon-poster { width: 160rpx; height: 160rpx; @@ -359,11 +410,48 @@ onShow(async () => { flex: 1; } +.coupon-title-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16rpx; + margin-bottom: 8rpx; +} + +.coupon-state-row { + display: flex; + margin-bottom: 8rpx; +} + .coupon-title { + min-width: 0; + flex: 1; font-size: 30rpx; font-weight: 600; color: #222; - margin-bottom: 8rpx; +} + +.coupon-status { + flex-shrink: 0; + padding: 4rpx 12rpx; + border-radius: 999rpx; + font-size: 22rpx; + line-height: 1.4; +} + +.coupon-status--issued { + color: #ff8a00; + background: rgba(255, 138, 0, 0.12); +} + +.coupon-status--claimed { + color: #0f766e; + background: rgba(15, 118, 110, 0.12); +} + +.coupon-status--expired { + color: #909399; + background: #f0f2f5; } .coupon-info-grid { @@ -416,6 +504,10 @@ onShow(async () => { color: #ff8a00; } +.coupon-action.disabled { + color: #a8abb2; +} + .empty { padding: 120rpx 0; text-align: center; diff --git a/pages/login/redirect-page.vue b/pages/login/redirect-page.vue index cc70517..c20f9cd 100644 --- a/pages/login/redirect-page.vue +++ b/pages/login/redirect-page.vue @@ -143,24 +143,47 @@ async function bindTeam({ corpUserId, externalUserId }) { } } +function normalizePlainOptions(options = {}) { + return Object.keys(options || {}).reduce((acc, key) => { + acc[key] = typeof options[key] === 'string' ? safeDecode(options[key]) : options[key]; + return acc; + }, {}) +} + onLoad((options) => { - if (options.q) { - opts.value = JSON.stringify(options) - changeTeam(parseInviteOptions(options)); - } else if (options.type === 'experienceCoupon' && options.issueId && options.corpId) { + const normalizedOptions = normalizePlainOptions(options); + if (normalizedOptions.q) { + opts.value = JSON.stringify(normalizedOptions) + changeTeam(parseInviteOptions(normalizedOptions)); + } else if (normalizedOptions.type === 'experienceCoupon' && normalizedOptions.issueId && normalizedOptions.corpId) { const params = [ - `corpId=${encodeURIComponent(options.corpId || "")}`, - `issueId=${encodeURIComponent(options.issueId || "")}`, - options.memberId ? `memberId=${encodeURIComponent(options.memberId)}` : "", - options.couponId ? `couponId=${encodeURIComponent(options.couponId)}` : "", - options.unionid ? `unionid=${encodeURIComponent(options.unionid)}` : "", - options.externalUserId ? `externalUserId=${encodeURIComponent(options.externalUserId)}` : "", + `corpId=${encodeURIComponent(normalizedOptions.corpId || "")}`, + `issueId=${encodeURIComponent(normalizedOptions.issueId || "")}`, + normalizedOptions.memberId ? `memberId=${encodeURIComponent(normalizedOptions.memberId)}` : "", + normalizedOptions.couponId ? `couponId=${encodeURIComponent(normalizedOptions.couponId)}` : "", + normalizedOptions.unionid ? `unionid=${encodeURIComponent(normalizedOptions.unionid)}` : "", + normalizedOptions.externalUserId ? `externalUserId=${encodeURIComponent(normalizedOptions.externalUserId)}` : "", ].filter(Boolean).join("&"); uni.redirectTo({ url: `/pages/experience-coupon/claim?${params}`, }); - } else if (options.type === 'archive' || (options.teamId && options.corpId)) { - changeTeam(options); + } else if (normalizedOptions.type === 'appointmentRegistration' && normalizedOptions.customerId && normalizedOptions.corpId) { + const params = [ + `corpId=${encodeURIComponent(normalizedOptions.corpId || "")}`, + normalizedOptions.teamId ? `teamId=${encodeURIComponent(normalizedOptions.teamId)}` : "", + `customerId=${encodeURIComponent(normalizedOptions.customerId || "")}`, + normalizedOptions.name ? `name=${encodeURIComponent(normalizedOptions.name)}` : "", + normalizedOptions.corpName ? `corpName=${encodeURIComponent(normalizedOptions.corpName)}` : "", + normalizedOptions.appointmentId ? `appointmentId=${encodeURIComponent(normalizedOptions.appointmentId)}` : "", + normalizedOptions.month ? `month=${encodeURIComponent(normalizedOptions.month)}` : "", + normalizedOptions.externalUserId ? `externalUserId=${encodeURIComponent(normalizedOptions.externalUserId)}` : "", + normalizedOptions.corpUserId ? `corpUserId=${encodeURIComponent(normalizedOptions.corpUserId)}` : "", + ].filter(Boolean).join("&"); + uni.redirectTo({ + url: `/pages/record/appointment-record?${params}`, + }); + } else if (normalizedOptions.type === 'archive' || (normalizedOptions.teamId && normalizedOptions.corpId)) { + changeTeam(normalizedOptions); } }); diff --git a/pages/record/appointment-record.vue b/pages/record/appointment-record.vue index 227b82a..09ec3a6 100644 --- a/pages/record/appointment-record.vue +++ b/pages/record/appointment-record.vue @@ -61,11 +61,19 @@ diff --git a/pages/record/treatment-record.vue b/pages/record/treatment-record.vue index 27253a6..385aff5 100644 --- a/pages/record/treatment-record.vue +++ b/pages/record/treatment-record.vue @@ -47,18 +47,6 @@ {{ item.treatmentDoctorName || item.doctorName || staffText(item.treatmentDoctorUserId) }} - - 【配台】 - {{ assistantText(item) }} - - - 【治疗部位】 - {{ item.treatmentArea }} - - - 【治疗备注】 - {{ item.treatmentRemark }} -