This commit is contained in:
大哥大哥的大哥哥
2026-09-09 14:47:29 +08:00
parent 4d9da40abd
commit 65755c9e96
832 changed files with 412085 additions and 0 deletions
@@ -0,0 +1,112 @@
const memoryCards = require('../../data/memoryCards')
const { chapterRoute } = require('../../utils/chapterRoute')
const MEMORY_CARD_ID_PATTERN = /^S\d{2}-C\d{2}-MC\d{2}$/
const INVALID_CARD_MESSAGE = '这张桂香记忆卡暂时没有找到。回到封面,还可以慢慢翻一回。'
const SHARE_PREVIEW_IMAGE = '/assets/share/guixiang-story-share-preview-v1.jpg'
function memoryCardSharePath(memoryCard) {
const cardId = String(memoryCard && memoryCard.cardId || '').trim()
if (!cardId) return '/pages/home/home'
return `/pages/share/share?cardId=${encodeURIComponent(cardId)}&source=family-memory`
}
function normalizeCardId(value) {
const raw = String(value || '').trim()
if (!raw || raw.length > 64) return ''
let decoded = raw
try {
decoded = decodeURIComponent(raw)
} catch (error) {
return ''
}
return MEMORY_CARD_ID_PATTERN.test(decoded) ? decoded : ''
}
function findMemoryCard(value) {
const cardId = normalizeCardId(value)
if (!cardId) return null
return memoryCards.find((card) => card.cardId === cardId) || null
}
function twoDigits(value) {
return String(value).padStart(2, '0')
}
Page({
data: {
memoryCard: null,
chapterLabel: '',
characterInitial: '',
invalidCard: false,
invalidCardMessage: INVALID_CARD_MESSAGE,
},
onLoad(options = {}) {
const memoryCard = findMemoryCard(options.cardId)
if (!memoryCard) {
this.setData({
memoryCard: null,
chapterLabel: '',
characterInitial: '',
invalidCard: true,
})
return
}
this.setData({
memoryCard: { ...memoryCard },
chapterLabel: `第${twoDigits(memoryCard.chapterNumber)}回`,
characterInitial: String(memoryCard.characterName || '桂').slice(0, 1),
invalidCard: false,
})
},
openChapter() {
const memoryCard = this.data.memoryCard
if (!memoryCard || !memoryCard.chapterNumber) {
this.goHome()
return
}
wx.navigateTo({
url: chapterRoute(memoryCard.chapterNumber),
})
},
goHome() {
const target = { url: '/pages/home/home' }
if (typeof wx.reLaunch === 'function') {
wx.reLaunch(target)
return
}
wx.redirectTo(target)
},
onShareAppMessage() {
const memoryCard = this.data.memoryCard
if (!memoryCard) {
return {
title: '唐侦探:饭桌边,也有值得带回家说的话',
path: '/pages/home/home',
imageUrl: SHARE_PREVIEW_IMAGE,
}
}
return {
title: `带回家的一句话:${memoryCard.familyLine}`,
path: memoryCardSharePath(memoryCard),
imageUrl: SHARE_PREVIEW_IMAGE,
}
},
onShareTimeline() {
const memoryCard = this.data.memoryCard
return memoryCard
? {
title: `桂香记忆:${memoryCard.tableEcho}`,
query: `cardId=${encodeURIComponent(memoryCard.cardId)}&source=family-memory`,
}
: {
title: '唐侦探:饭桌边,也有值得带回家说的话',
query: '',
}
},
})
@@ -0,0 +1,4 @@
{
"navigationStyle": "custom",
"disableScroll": true
}
@@ -0,0 +1,71 @@
<view class="safe-shell share-shell">
<view wx:if="{{memoryCard}}" class="share-card paper-panel">
<view class="share-identity">
<text class="share-kicker">家人发来的桂香记忆</text>
<view class="share-person-seal" aria-hidden="true">{{characterInitial}}</view>
<view class="share-meta-block">
<text class="share-meta-label">人物</text>
<text class="share-person">{{memoryCard.characterName}}</text>
</view>
<view class="share-meta-block">
<text class="share-meta-label">年代</text>
<text class="share-era">{{memoryCard.eraLine}}</text>
</view>
<text class="share-chapter">{{chapterLabel}} · {{memoryCard.chapterTitle}}</text>
</view>
<view class="share-main">
<view class="share-capsule-safe-space" aria-hidden="true"></view>
<scroll-view
class="share-copy"
scroll-y
enhanced
bounces="{{false}}"
show-scrollbar="{{true}}"
aria-label="{{chapterLabel}}桂香记忆卡"
>
<view class="share-section share-echo">
<text class="share-section-label">桌边回声</text>
<text class="share-quote">“{{memoryCard.tableEcho}}”</text>
</view>
<view class="share-section">
<text class="share-section-label">今天行动</text>
<text class="share-section-copy">{{memoryCard.lifeAction}}</text>
</view>
<view class="share-section share-family">
<text class="share-section-label">带回家话语</text>
<text class="share-section-copy">“{{memoryCard.familyLine}}”</text>
</view>
</scroll-view>
<view class="share-actions">
<button
class="share-action share-action-primary"
bindtap="openChapter"
aria-label="一起翻{{chapterLabel}},{{memoryCard.chapterTitle}}"
>一起翻这一回</button>
<button
class="share-action share-action-forward"
open-type="share"
aria-label="把这一页再发给家人"
>再发给家人</button>
<button
class="share-action share-action-secondary"
bindtap="goHome"
aria-label="回到唐侦探封面"
>回到封面</button>
</view>
</view>
</view>
<view wx:else class="share-invalid paper-panel" aria-live="polite">
<view class="share-invalid-seal" aria-hidden="true">桂</view>
<text class="share-invalid-title">这页记忆,暂时没有翻开</text>
<text class="share-invalid-copy">{{invalidCardMessage}}</text>
<button
class="share-action share-action-primary share-invalid-action"
bindtap="goHome"
aria-label="回到唐侦探封面"
>回到封面</button>
</view>
</view>
@@ -0,0 +1,326 @@
.share-shell {
display: flex;
height: 100vh;
min-height: 0;
overflow: hidden;
}
.share-card {
position: relative;
display: grid;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
grid-template-columns: minmax(205px, 2fr) minmax(0, 5fr);
border: 2px solid #9e845e;
border-radius: 9px;
}
.share-identity {
display: flex;
min-width: 0;
min-height: 0;
align-items: center;
padding: 18px 22px;
overflow: hidden;
color: #f5e5bd;
background:
repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.025) 0, rgba(255, 255, 255, 0.025) 1px, transparent 1px, transparent 6px),
#263a3e;
border-right: 5px solid #9b332a;
flex-direction: column;
}
.share-kicker,
.share-meta-label,
.share-section-label,
.share-actions,
.share-invalid-copy {
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
}
.share-kicker {
align-self: flex-start;
color: #dfc486;
font-size: 16px;
font-weight: 850;
letter-spacing: 2px;
}
.share-person-seal,
.share-invalid-seal {
display: flex;
align-items: center;
justify-content: center;
color: #8d2d25;
background: #efe0b8;
border: 5px double #ad4d3a;
border-radius: 50%;
font-weight: 900;
}
.share-person-seal {
width: 78px;
height: 78px;
flex: none;
margin: 9px 0 6px;
font-size: 38px;
}
.share-meta-block {
display: grid;
width: 100%;
min-width: 0;
align-items: start;
gap: 7px;
margin-top: 5px;
grid-template-columns: 42px minmax(0, 1fr);
}
.share-meta-label {
color: #d5b974;
font-size: 15px;
font-weight: 850;
}
.share-person {
font-size: 22px;
font-weight: 900;
line-height: 1.25;
}
.share-era {
color: #f0deae;
font-size: 17px;
font-weight: 750;
line-height: 1.35;
}
.share-chapter {
display: -webkit-box;
width: 100%;
margin-top: auto;
overflow: hidden;
color: #f7e9c7;
font-size: 17px;
font-weight: 850;
line-height: 1.35;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.share-main {
position: relative;
display: grid;
min-width: 0;
min-height: 0;
padding: 48px 20px 16px;
grid-template-rows: minmax(0, 1fr) auto;
gap: 12px;
}
.share-capsule-safe-space {
position: absolute;
top: 6px;
right: 8px;
width: 104px;
height: 36px;
pointer-events: none;
}
.share-copy {
width: 100%;
height: 100%;
min-height: 0;
}
.share-section {
display: grid;
align-items: start;
gap: 12px;
padding: 11px 14px;
grid-template-columns: 92px minmax(0, 1fr);
border-top: 1px solid rgba(126, 86, 50, 0.24);
}
.share-section:first-child {
padding-top: 0;
border-top: 0;
}
.share-section-label {
color: #8d2f27;
font-size: 17px;
font-weight: 900;
}
.share-quote,
.share-section-copy {
display: block;
font-size: 19px;
font-weight: 750;
line-height: 1.5;
}
.share-quote {
color: #493325;
font-size: 21px;
font-weight: 850;
}
.share-family {
background: rgba(167, 114, 54, 0.08);
border-bottom: 1px solid rgba(126, 86, 50, 0.22);
}
.share-actions {
display: grid;
width: 100%;
min-width: 0;
gap: 12px;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.share-action {
display: flex;
width: 100%;
min-width: 0;
max-width: 100%;
min-height: 54px;
align-items: center;
justify-content: center;
padding: 8px 14px;
border: 3px solid;
border-radius: 8px;
font-size: 20px;
font-weight: 900;
line-height: 1.2;
justify-self: stretch;
}
.share-action-primary {
color: #fff0cb;
background: #963128;
border-color: #74231d;
box-shadow: 0 8px 18px rgba(111, 32, 27, 0.2);
}
.share-action-secondary {
color: #473326;
background: #ead9b2;
border-color: #997b54;
}
.share-action-forward {
color: #315545;
background: #dce7d9;
border-color: #64826e;
}
.share-invalid {
display: flex;
width: min(620px, 100%);
min-height: 250px;
align-self: center;
align-items: center;
justify-content: center;
margin: auto;
padding: 24px 34px;
border-radius: 9px;
flex-direction: column;
text-align: center;
}
.share-invalid-seal {
width: 68px;
height: 68px;
flex: none;
font-size: 31px;
}
.share-invalid-title {
margin-top: 9px;
color: #783028;
font-size: 26px;
font-weight: 900;
}
.share-invalid-copy {
margin-top: 7px;
color: #604b36;
font-size: 18px;
font-weight: 700;
line-height: 1.5;
}
.share-invalid-action {
width: 210px;
margin-top: 15px;
}
@media (max-width: 730px), (max-height: 400px) {
.share-card {
grid-template-columns: minmax(190px, 2fr) minmax(0, 5fr);
}
.share-identity {
padding: 10px 15px;
}
.share-kicker {
font-size: 14px;
}
.share-person-seal {
width: 58px;
height: 58px;
margin: 5px 0 2px;
font-size: 29px;
}
.share-person {
font-size: 19px;
}
.share-era,
.share-chapter {
font-size: 15px;
}
.share-main {
padding: 42px 12px 10px;
gap: 8px;
}
.share-section {
gap: 8px;
padding: 7px 8px;
grid-template-columns: 78px minmax(0, 1fr);
}
.share-section-label {
font-size: 15px;
}
.share-quote,
.share-section-copy {
font-size: 16px;
line-height: 1.38;
}
.share-actions {
gap: 8px;
}
.share-action {
min-height: 48px;
padding: 6px 10px;
font-size: 18px;
}
.share-invalid {
min-height: 220px;
padding: 17px 24px;
}
}