import { useCallback, useEffect, useState } from 'react'; import { View, Text, Image, Textarea } from '@tarojs/components'; import '../../styles/redeem.css'; import Taro, { useLoad, useRouter } from '@tarojs/taro'; import { STORE_RATING_MAX_COMMENT, STORE_RATING_MAX_IMAGES, STORE_RATING_QUICK_TAGS, type StoreRatingDto, } from '@dukang/shared-types'; import PageShell from '../../components/PageShell'; import SubPageHeader from '../../components/SubPageHeader'; import { request, toast } from '../../lib/api'; import { formatShanghaiDateTime } from '../../lib/datetime'; import { toMoneyNumber } from '../../lib/money'; import { chooseAndUploadRatingImages } from '../../lib/upload-rating-image'; const LAST_REDEEM_RESULT_KEY = 'lastRedeemResult'; const SCORE_LABELS = ['', '较差', '一般', '还行', '很好', '非常好'] as const; type RedeemRecord = { id: string; redeemNo: string; amount: number; storeId: string; storeName: string; createdAt: string; rating?: StoreRatingDto | null; }; function formatAmountYuan(amount: unknown) { const n = toMoneyNumber(amount); if (Math.abs(n - Math.round(n)) < 1e-9) return String(Math.round(n)); return n.toFixed(2).replace(/\.?0+$/, ''); } function formatVisitLine(createdAt?: string | null, amount?: unknown) { const full = formatShanghaiDateTime(createdAt ?? new Date()); if (full === '—') return `核销用餐权益 ${formatAmountYuan(amount)}`; const datePart = full.slice(0, 10); const time = full.slice(11, 16); const today = formatShanghaiDateTime(new Date()).slice(0, 10); const prefix = datePart === today ? '今日' : datePart.slice(5); return `${prefix} ${time} · 核销用餐权益 ${formatAmountYuan(amount)}`; } function readCachedRecord(): RedeemRecord | null { try { const cached = Taro.getStorageSync(LAST_REDEEM_RESULT_KEY); return cached ? (JSON.parse(String(cached)) as RedeemRecord) : null; } catch { return null; } } export default function RedeemSuccessPage() { const router = useRouter(); const [fromHistory, setFromHistory] = useState(false); const [fromStore, setFromStore] = useState(false); const [record, setRecord] = useState(null); const [serviceScore, setServiceScore] = useState(5); const [envScore, setEnvScore] = useState(5); const [tags, setTags] = useState(['菜品好', '环境佳', '服务周到']); const [comment, setComment] = useState(''); const [imageUrls, setImageUrls] = useState([]); const [coverUrl, setCoverUrl] = useState(''); const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); const rated = Boolean(record?.rating); const applyRating = useCallback((rating: StoreRatingDto) => { const nextService = Number(rating.serviceScore || 5); const nextEnv = Number(rating.envScore || 5); setServiceScore( Number.isFinite(nextService) && nextService > 0 ? Math.min(5, Math.round(nextService)) : 5, ); setEnvScore(Number.isFinite(nextEnv) && nextEnv > 0 ? Math.min(5, Math.round(nextEnv)) : 5); setTags(Array.isArray(rating.tags) ? rating.tags : []); setComment(String(rating.comment || '')); setImageUrls(Array.isArray(rating.imageUrls) ? rating.imageUrls : []); }, []); const loadRecord = useCallback( async (id: string) => { try { let data: RedeemRecord | null = null; try { data = await request(`/redeem/records/${id}`); } catch { const records = await request<{ list?: RedeemRecord[] } | RedeemRecord[]>( '/redeem/records?page=1&pageSize=50', ); const list = Array.isArray(records) ? records : records?.list ?? []; data = list.find((item) => String(item.id) === id) ?? null; } if (!data) { toast('核销记录不存在'); return; } setRecord(data); if (data.rating) applyRating(data.rating); } catch (e) { toast(e instanceof Error ? e.message : '加载失败'); } }, [applyRating], ); useLoad((options) => { const id = String(options?.id || router.params.id || '').trim(); const from = String(options?.from || router.params.from || ''); setFromHistory(from === 'history'); setFromStore(from === 'store'); if (id) { void loadRecord(id); return; } const cached = readCachedRecord(); if (cached) setRecord(cached); }); const amount = toMoneyNumber(record?.amount ?? router.params.amount); const storeName = record?.storeName || '门店'; const visitLine = formatVisitLine(record?.createdAt, amount); useEffect(() => { if (!record?.storeId) return; void request<{ coverUrl?: string | null }>(`/stores/${record.storeId}`) .then((store) => { const url = String(store?.coverUrl || '').trim(); if (url) setCoverUrl(url); }) .catch(() => undefined); }, [record?.storeId]); function clearCache() { try { Taro.removeStorageSync(LAST_REDEEM_RESULT_KEY); } catch { /* ignore */ } } function leave() { clearCache(); const pages = Taro.getCurrentPages(); if ((fromHistory || fromStore) && pages.length > 1) { Taro.navigateBack(); return; } Taro.switchTab({ url: '/pages/benefit/index' }); } function toggleTag(tag: string) { if (rated) return; setTags((prev) => (prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag])); } async function addPhotos() { if (rated || uploading) return; if (imageUrls.length >= STORE_RATING_MAX_IMAGES) { toast(`最多上传${STORE_RATING_MAX_IMAGES}张图片`); return; } setUploading(true); try { const urls = await chooseAndUploadRatingImages(imageUrls.length); if (urls.length) setImageUrls((prev) => [...prev, ...urls].slice(0, STORE_RATING_MAX_IMAGES)); } catch (e) { toast(e instanceof Error ? e.message : '上传失败'); } finally { setUploading(false); } } function removePhoto(url: string) { if (rated) return; setImageUrls((prev) => prev.filter((item) => item !== url)); } async function submitRatingAndFinish() { if (!record?.id) { toast('找不到核销记录'); return; } if (rated) { leave(); return; } setLoading(true); try { await request('/redeem/ratings', { method: 'POST', data: { redeemRecordId: record.id, serviceScore, envScore, comment: comment.trim(), tags, imageUrls, }, }); toast('评价已提交', 'success'); setRecord((prev) => prev ? { ...prev, rating: { serviceScore, envScore, comment, tags, imageUrls }, } : prev, ); setTimeout(() => leave(), 400); } catch (e) { toast(e instanceof Error ? e.message : '评价失败'); } finally { setLoading(false); } } return ( {coverUrl ? ( ) : ( )} {storeName} {visitLine} 服务评分 {[1, 2, 3, 4, 5].map((value) => ( { if (!rated) setServiceScore(value); }} > ★ ))} {SCORE_LABELS[serviceScore]} 环境评分 {[1, 2, 3, 4, 5].map((value) => ( { if (!rated) setEnvScore(value); }} > ★ ))} {SCORE_LABELS[envScore]} 快捷标签 {STORE_RATING_QUICK_TAGS.map((tag) => ( toggleTag(tag)} > {tag} ))} 补充说明 {process.env.TARO_ENV === 'h5' ? (