gengxin
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
const rawCast = require('../../data/cast')
|
||||
const {
|
||||
getSettings,
|
||||
saveSettings,
|
||||
} = require('../../utils/storage')
|
||||
|
||||
const readerDetails = {
|
||||
'tang-shouan': {
|
||||
relationship: '唐明远的父亲、乐乐的爷爷,也是桂香老邻居信得过的唐大夫。',
|
||||
visual: '常穿素色深靛蓝外套,背着旧棕布卫生包。衣着朴素,像每天都会遇见的邻家大夫。',
|
||||
poseCount: 5,
|
||||
},
|
||||
'qin-zhicheng': {
|
||||
relationship: '秦小满的长辈,桂香食堂和饭店里掌勺多年的秦师傅。',
|
||||
visual: '宽头、粗颈、宽肩,前臂厚实;穿中式粗棉厨工褂、白围裙和低矮布帽。',
|
||||
poseCount: 5,
|
||||
},
|
||||
'lin-xiulan': {
|
||||
relationship: '桂香厂老工友,也是饭票、账本和旧物的保管人。',
|
||||
visual: '常穿枣红或深棕工作外套。她不急着训人,更愿意用行动把事情说明白。',
|
||||
poseCount: 5,
|
||||
},
|
||||
'zhao-jianguo': {
|
||||
relationship: '大家叫他赵伯,是桂香厂出了名的劳动骨干和老工友。',
|
||||
visual: '青年时穿结实工装,老年戴深蓝便帽、捧盖碗茶;豪爽有劲,也保留普通人的体面。',
|
||||
poseCount: 5,
|
||||
},
|
||||
'tang-mingyuan': {
|
||||
relationship: '唐守安的儿子、乐乐的父亲,和许多中年人一样忙工作也顾家。',
|
||||
visual: '衣着随年代变化,腰腹和颈围也慢慢有了变化。这是多年生活留下的痕迹,不拿他的身形开玩笑。',
|
||||
poseCount: 4,
|
||||
},
|
||||
'qin-xiaoman': {
|
||||
relationship: '秦师傅的晚辈,如今接手照看桂香饭店。',
|
||||
visual: '穿简洁的现代中式工作装,常带着软尺、纸质座位单和调台记录。',
|
||||
poseCount: 4,
|
||||
},
|
||||
lele: {
|
||||
relationship: '唐明远的儿子、唐守安的孙子,是家里最敢把问题问出来的人。',
|
||||
visual: '圆润、活泼、讨人喜欢。大人从家庭习惯上找原因,不把责任推给孩子。',
|
||||
poseCount: 4,
|
||||
},
|
||||
xiaozhen: {
|
||||
relationship: '在现代生活里陪大家整理健康信息的人,需要时提醒大家联系专业医护。',
|
||||
visual: '灰绿色开衫、白衬衫、低马尾,手里拿着记录夹;总在需要时陪一程,不抢别人的故事。',
|
||||
poseCount: 4,
|
||||
},
|
||||
}
|
||||
|
||||
const cast = rawCast.map((character) => ({
|
||||
...character,
|
||||
relationship: readerDetails[character.id].relationship,
|
||||
readerRole: character.id === 'xiaozhen'
|
||||
? '在故事需要回顾生活习惯时陪大家理一理;遇到拿不准的健康问题,会提醒大家联系专业医护,不替医生作决定。'
|
||||
: character.role,
|
||||
readerVisual: readerDetails[character.id].visual,
|
||||
poseCount: readerDetails[character.id].poseCount,
|
||||
}))
|
||||
|
||||
Page({
|
||||
data: {
|
||||
cast,
|
||||
selected: null,
|
||||
fontScale: 'large',
|
||||
castListEnded: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const settings = getSettings()
|
||||
this.setData({
|
||||
fontScale: settings.fontScale === 'xlarge' ? 'xlarge' : 'large',
|
||||
castListEnded: false,
|
||||
})
|
||||
},
|
||||
|
||||
selectCharacter(event) {
|
||||
const id = event.currentTarget.dataset.id
|
||||
const selected = cast.find((character) => character.id === id)
|
||||
this.setData({ selected })
|
||||
},
|
||||
|
||||
closeCharacter() {
|
||||
this.setData({ selected: null })
|
||||
},
|
||||
|
||||
markListEnd() {
|
||||
if (!this.data.castListEnded) {
|
||||
this.setData({ castListEnded: true })
|
||||
}
|
||||
},
|
||||
|
||||
toggleFont() {
|
||||
const fontScale = this.data.fontScale === 'large' ? 'xlarge' : 'large'
|
||||
const settings = getSettings()
|
||||
settings.fontScale = fontScale
|
||||
saveSettings(settings)
|
||||
this.setData({ fontScale })
|
||||
},
|
||||
|
||||
noop() {},
|
||||
|
||||
goBack() {
|
||||
wx.reLaunch({ url: '/pages/home/home' })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '唐侦探人物画谱:认识桂香故事里的人',
|
||||
path: '/pages/cast/cast',
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '唐侦探人物画谱:认识桂香故事里的人',
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "人物画谱",
|
||||
"pageOrientation": "landscape"
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<view class="safe-shell cast-shell font-{{fontScale}}">
|
||||
<view class="cast-head paper-panel">
|
||||
<button class="quiet-button cast-back" bindtap="goBack" aria-label="返回封面">‹ 封面</button>
|
||||
<view class="cast-heading-copy">
|
||||
<text class="eyebrow">桂香里的熟面孔</text>
|
||||
<text class="cast-title">人物画谱</text>
|
||||
</view>
|
||||
<view class="cast-head-actions">
|
||||
<button
|
||||
class="quiet-button font-button"
|
||||
bindtap="toggleFont"
|
||||
aria-label="{{fontScale === 'large' ? '把人物画谱文字再放大' : '恢复标准大字'}}"
|
||||
>
|
||||
{{fontScale === 'large' ? '再大一点' : '标准大字'}}
|
||||
</button>
|
||||
<view class="capsule-safe-space" aria-hidden="true"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="cast-scroll"
|
||||
scroll-y
|
||||
enhanced
|
||||
show-scrollbar="{{true}}"
|
||||
lower-threshold="24"
|
||||
bindscrolltolower="markListEnd"
|
||||
aria-label="桂香人物列表,可上下滑动"
|
||||
>
|
||||
<view class="cast-intro">
|
||||
<text>点开一位人物,先认清他和家里人的关系,再看看他在桂香里的故事。</text>
|
||||
</view>
|
||||
<view class="cast-grid">
|
||||
<view
|
||||
wx:for="{{cast}}"
|
||||
wx:key="id"
|
||||
class="cast-card paper-panel"
|
||||
aria-role="button"
|
||||
aria-label="{{item.name}},{{item.title}}"
|
||||
hover-class="cast-card-hover"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="selectCharacter"
|
||||
>
|
||||
<view class="cast-image-wrap">
|
||||
<view class="cast-portrait-window poses-{{item.poseCount}}">
|
||||
<image
|
||||
class="cast-image"
|
||||
src="{{item.asset}}"
|
||||
mode="aspectFit"
|
||||
aria-label="{{item.name}}的全身像"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cast-copy">
|
||||
<view class="name-row">
|
||||
<text class="cast-name">{{item.name}}</text>
|
||||
<text class="eras">{{item.eras}}</text>
|
||||
</view>
|
||||
<text class="cast-role-title">{{item.title}}</text>
|
||||
<text class="cast-relationship">{{item.relationship}}</text>
|
||||
<text class="cast-hint">点开看故事</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="list-scroll-guide" aria-live="polite">
|
||||
<text>{{castListEnded ? '人物都看见了' : '向下翻,还有更多人物'}}</text>
|
||||
<text class="scroll-guide-mark" aria-hidden="true">{{castListEnded ? '✓' : '⌄'}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{selected}}" class="modal-backdrop" bindtap="closeCharacter">
|
||||
<view
|
||||
class="character-modal paper-panel"
|
||||
catchtap="noop"
|
||||
aria-role="dialog"
|
||||
aria-label="{{selected.name}}的人物故事"
|
||||
>
|
||||
<button class="modal-close" bindtap="closeCharacter" aria-label="关闭人物故事">关闭</button>
|
||||
<view class="modal-image-wrap">
|
||||
<view class="modal-portrait-window poses-{{selected.poseCount}}">
|
||||
<image
|
||||
class="modal-image"
|
||||
src="{{selected.asset}}"
|
||||
mode="aspectFit"
|
||||
aria-label="{{selected.name}}的全身像"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-copy">
|
||||
<view class="modal-head">
|
||||
<view class="modal-heading">
|
||||
<text class="eyebrow">{{selected.eras}} · {{selected.title}}</text>
|
||||
<text class="modal-name">{{selected.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view
|
||||
class="modal-story-scroll"
|
||||
scroll-y
|
||||
enhanced
|
||||
bounces="{{false}}"
|
||||
show-scrollbar="{{true}}"
|
||||
aria-label="{{selected.name}}的人物故事正文,可上下滑动"
|
||||
>
|
||||
<view class="modal-story">
|
||||
<text class="section-label">他和大家的关系</text>
|
||||
<text class="modal-text">{{selected.relationship}}</text>
|
||||
<text class="section-label">他在桂香里的故事</text>
|
||||
<text class="modal-text">{{selected.readerRole}}</text>
|
||||
<text class="section-label">衣着和模样</text>
|
||||
<text class="modal-text">{{selected.readerVisual}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="modal-footer">
|
||||
<button class="primary-button modal-done" bindtap="closeCharacter">看完了,返回人物谱</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,660 @@
|
||||
.cast-shell {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
gap: 14rpx;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cast-head {
|
||||
display: grid;
|
||||
min-height: 62px;
|
||||
flex: 0 0 auto;
|
||||
grid-template-columns: 110px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.cast-back,
|
||||
.font-button,
|
||||
.modal-close,
|
||||
.modal-done {
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.cast-back {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.cast-heading-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cast-title {
|
||||
overflow: hidden;
|
||||
font-size: 27px;
|
||||
font-weight: 850;
|
||||
letter-spacing: 2px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cast-heading-copy .eyebrow {
|
||||
overflow: hidden;
|
||||
font-size: 15px;
|
||||
letter-spacing: 1px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cast-head-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.font-button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* The WeChat capsule occupies this part of a custom landscape title bar. */
|
||||
.capsule-safe-space {
|
||||
width: 102px;
|
||||
height: 40px;
|
||||
flex: 0 0 102px;
|
||||
}
|
||||
|
||||
.cast-scroll {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cast-intro {
|
||||
padding: 2px 6px 10px;
|
||||
color: #f4e5bd;
|
||||
font-size: 17px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cast-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 0 2px 20px;
|
||||
}
|
||||
|
||||
.cast-card {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 144px;
|
||||
overflow: hidden;
|
||||
grid-template-columns: 132px minmax(0, 1fr);
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cast-card-hover {
|
||||
transform: translateY(2rpx);
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.cast-image-wrap {
|
||||
display: flex;
|
||||
min-height: 144px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #d4c29a;
|
||||
border-right: 5px solid var(--cinnabar);
|
||||
}
|
||||
|
||||
.cast-portrait-window,
|
||||
.modal-portrait-window {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #dfcfaa;
|
||||
border: 1px solid rgba(98, 65, 40, 0.35);
|
||||
}
|
||||
|
||||
.cast-portrait-window {
|
||||
height: 126px;
|
||||
}
|
||||
|
||||
.cast-portrait-window.poses-5 {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
.cast-portrait-window.poses-4 {
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
.cast-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 224px;
|
||||
height: 126px;
|
||||
}
|
||||
|
||||
.cast-copy {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 13px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cast-name {
|
||||
font-size: 23px;
|
||||
font-weight: 850;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.eras {
|
||||
color: var(--muted);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cast-role-title {
|
||||
margin-top: 4px;
|
||||
color: #8d3028;
|
||||
font-size: 17px;
|
||||
font-weight: 750;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cast-relationship {
|
||||
display: -webkit-box;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
color: #554333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.35;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.cast-hint {
|
||||
margin-top: 7px;
|
||||
color: var(--muted);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.list-scroll-guide {
|
||||
display: flex;
|
||||
min-height: 40px;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
color: #f4e5bd;
|
||||
background: rgba(58, 40, 28, 0.94);
|
||||
border: 1px solid rgba(222, 194, 139, 0.55);
|
||||
border-radius: 8px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.scroll-guide-mark {
|
||||
color: #f0c86b;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding:
|
||||
calc(10px + constant(safe-area-inset-top))
|
||||
calc(112px + constant(safe-area-inset-right))
|
||||
calc(10px + constant(safe-area-inset-bottom))
|
||||
calc(10px + constant(safe-area-inset-left));
|
||||
padding:
|
||||
calc(10px + env(safe-area-inset-top))
|
||||
calc(112px + env(safe-area-inset-right))
|
||||
calc(10px + env(safe-area-inset-bottom))
|
||||
calc(10px + env(safe-area-inset-left));
|
||||
background: rgba(19, 13, 9, 0.82);
|
||||
}
|
||||
|
||||
.character-modal {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
grid-template-columns: minmax(240px, 42%) minmax(0, 58%);
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.modal-image-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #d1bf96;
|
||||
border-right: 6rpx solid #7d2a23;
|
||||
}
|
||||
|
||||
.modal-portrait-window {
|
||||
height: 560px;
|
||||
}
|
||||
|
||||
.modal-portrait-window.poses-5 {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.modal-portrait-window.poses-4 {
|
||||
width: 249px;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 996px;
|
||||
height: 560px;
|
||||
}
|
||||
|
||||
.modal-copy {
|
||||
position: relative;
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
grid-template-rows: minmax(76px, auto) minmax(0, 1fr) minmax(68px, auto);
|
||||
}
|
||||
|
||||
.modal-head {
|
||||
display: flex;
|
||||
min-height: 76px;
|
||||
align-items: center;
|
||||
padding: 12rpx 88px 8rpx 22rpx;
|
||||
border-bottom: 2rpx solid rgba(125, 42, 35, 0.22);
|
||||
}
|
||||
|
||||
.modal-heading {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-heading .eyebrow {
|
||||
overflow: hidden;
|
||||
font-size: 18rpx;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.modal-name {
|
||||
margin-top: 2rpx;
|
||||
font-size: 42rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
min-width: 68px;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
color: #6f201b;
|
||||
background: #fff8e5;
|
||||
border: 2rpx solid #9f6d54;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 10px rgba(48, 37, 28, 0.18);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.modal-story-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-story {
|
||||
display: flex;
|
||||
padding: 12rpx 24rpx 28rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
margin-top: 10rpx;
|
||||
color: #8d3028;
|
||||
font-size: 22rpx;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.modal-text {
|
||||
margin-top: 6rpx;
|
||||
color: #4f3e30;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.modal-done {
|
||||
width: 100%;
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
height: 48px;
|
||||
padding: 0 14px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
min-height: 68px;
|
||||
align-items: center;
|
||||
padding: 8px 14px 12px;
|
||||
background: rgba(243, 229, 189, 0.96);
|
||||
border-top: 2rpx solid rgba(125, 42, 35, 0.22);
|
||||
}
|
||||
|
||||
.font-xlarge .cast-title {
|
||||
font-size: 35rpx;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-intro,
|
||||
.font-xlarge .cast-role-title,
|
||||
.font-xlarge .section-label {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-name {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.font-xlarge .eras,
|
||||
.font-xlarge .cast-hint,
|
||||
.font-xlarge .modal-heading .eyebrow {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-relationship,
|
||||
.font-xlarge .list-scroll-guide {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-xlarge .modal-name {
|
||||
font-size: 46rpx;
|
||||
}
|
||||
|
||||
.font-xlarge .modal-text {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
@media (max-height: 620px) {
|
||||
.cast-shell {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cast-scroll {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.cast-head {
|
||||
min-height: 62px;
|
||||
grid-template-columns: 96px minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.cast-back,
|
||||
.font-button {
|
||||
min-height: 48px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.font-button {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.cast-title {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.cast-heading-copy .eyebrow {
|
||||
font-size: 15px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.cast-intro {
|
||||
padding: 0 4px 8px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.cast-grid {
|
||||
gap: 10px;
|
||||
padding: 0 2px 16px;
|
||||
}
|
||||
|
||||
.cast-card {
|
||||
min-height: 144px;
|
||||
grid-template-columns: 132px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.cast-image-wrap {
|
||||
min-height: 144px;
|
||||
}
|
||||
|
||||
.cast-copy {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.cast-name {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.eras {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cast-role-title {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.cast-relationship {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.cast-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.modal-head {
|
||||
min-height: 68px;
|
||||
padding: 8px 82px 6px 14px;
|
||||
}
|
||||
|
||||
.modal-copy {
|
||||
grid-template-rows: minmax(68px, auto) minmax(0, 1fr) minmax(64px, auto);
|
||||
}
|
||||
|
||||
.modal-heading .eyebrow {
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.modal-name {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.modal-story {
|
||||
padding: 7px 16px 16px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
min-height: 64px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.modal-done {
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
margin-top: 7px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.modal-text {
|
||||
margin-top: 4px;
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
padding-right: calc(98px + constant(safe-area-inset-right));
|
||||
padding-left: calc(32px + constant(safe-area-inset-left));
|
||||
padding-right: calc(98px + env(safe-area-inset-right));
|
||||
padding-left: calc(32px + env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
.modal-portrait-window {
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
.modal-portrait-window.poses-5 {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.modal-portrait-window.poses-4 {
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
width: 498px;
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-intro,
|
||||
.font-xlarge .cast-role-title,
|
||||
.font-xlarge .section-label {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-name {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.font-xlarge .eras,
|
||||
.font-xlarge .cast-hint,
|
||||
.font-xlarge .modal-heading .eyebrow {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.font-xlarge .cast-relationship,
|
||||
.font-xlarge .list-scroll-guide {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.font-xlarge .modal-name {
|
||||
font-size: 33px;
|
||||
}
|
||||
|
||||
.font-xlarge .modal-text {
|
||||
font-size: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) and (orientation: landscape) {
|
||||
.cast-head {
|
||||
grid-template-columns: 96px minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.capsule-safe-space {
|
||||
width: 88px;
|
||||
flex-basis: 88px;
|
||||
}
|
||||
|
||||
.font-button {
|
||||
min-width: 84px;
|
||||
padding-right: 9px;
|
||||
padding-left: 9px;
|
||||
}
|
||||
|
||||
.cast-heading-copy .eyebrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cast-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.cast-card {
|
||||
grid-template-columns: 150px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
padding-right: calc(98px + env(safe-area-inset-right));
|
||||
}
|
||||
|
||||
.character-modal {
|
||||
grid-template-columns: minmax(210px, 40%) minmax(0, 60%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
const chapters = require('../../data/chapters')
|
||||
const {
|
||||
getProgress,
|
||||
saveProgress,
|
||||
resetStoryProgress,
|
||||
getSettings,
|
||||
} = require('../../utils/storage')
|
||||
const { chapterRoute } = require('../../utils/chapterRoute')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
chapters: [],
|
||||
fontScale: 'large',
|
||||
catalogListEnded: false,
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const progress = getProgress()
|
||||
const settings = getSettings()
|
||||
const completed = new Set(progress.completedChapters)
|
||||
const lastChapter = Number(progress.lastChapter) || 1
|
||||
const completedHotspots = progress.completedHotspots || {}
|
||||
this.setData({
|
||||
chapters: chapters.map((chapter) => ({
|
||||
...chapter,
|
||||
completed: completed.has(chapter.chapterId),
|
||||
current: chapter.number === lastChapter,
|
||||
readingLabel: completed.has(chapter.chapterId)
|
||||
? '重新翻看这一回'
|
||||
: chapter.number === lastChapter
|
||||
? '上次读到这里'
|
||||
: '翻开这一回',
|
||||
eventCount: Array.isArray(completedHotspots[chapter.chapterId])
|
||||
? completedHotspots[chapter.chapterId].length
|
||||
: 0,
|
||||
})),
|
||||
lastChapter,
|
||||
fontScale: settings.fontScale === 'xlarge' ? 'xlarge' : 'large',
|
||||
catalogListEnded: false,
|
||||
})
|
||||
},
|
||||
|
||||
markListEnd() {
|
||||
if (!this.data.catalogListEnded) {
|
||||
this.setData({ catalogListEnded: true })
|
||||
}
|
||||
},
|
||||
|
||||
openChapter(event) {
|
||||
const chapter = Number(event.currentTarget.dataset.chapter)
|
||||
const progress = getProgress()
|
||||
const chapterMeta = chapters.find((item) => item.number === chapter)
|
||||
const replay = Boolean(
|
||||
chapterMeta
|
||||
&& progress.completedChapters.includes(chapterMeta.chapterId),
|
||||
)
|
||||
progress.lastChapter = chapter
|
||||
saveProgress(progress)
|
||||
wx.navigateTo({
|
||||
url: chapterRoute(chapter, { replay }),
|
||||
})
|
||||
},
|
||||
|
||||
restartStory() {
|
||||
wx.showModal({
|
||||
title: '从第一回重新开始?',
|
||||
content: '关卡进度会清空;已经收藏的“我的桂香岁月”和大字设置会保留。',
|
||||
confirmText: '重新开始',
|
||||
cancelText: '先不清空',
|
||||
confirmColor: '#9f3028',
|
||||
success(result) {
|
||||
if (!result || !result.confirm) return
|
||||
if (!resetStoryProgress()) {
|
||||
wx.showToast({
|
||||
title: '进度暂时没有清空,请稍后再试',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
wx.redirectTo({ url: chapterRoute(1) })
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
goBack() {
|
||||
wx.reLaunch({ url: '/pages/home/home' })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '唐侦探:翻开十五回桂香故事',
|
||||
path: '/pages/catalog/catalog',
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '唐侦探:翻开十五回桂香故事',
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "十五回目录",
|
||||
"pageOrientation": "landscape"
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<view class="safe-shell catalog-shell font-{{fontScale}}">
|
||||
<view class="catalog-head paper-panel">
|
||||
<button class="quiet-button" bindtap="goBack" aria-label="返回封面">‹ 封面</button>
|
||||
<view class="catalog-heading-copy">
|
||||
<text class="eyebrow">桂香里的第十五桌</text>
|
||||
<text class="catalog-title catalog-title-full">第一季 · 十五回目录</text>
|
||||
<text class="catalog-title catalog-title-short">十五回目录</text>
|
||||
</view>
|
||||
<button
|
||||
class="quiet-button restart-story-button"
|
||||
bindtap="restartStory"
|
||||
aria-label="从第一回重新开始,清空关卡进度但保留收藏"
|
||||
>从头再看</button>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="chapter-scroll"
|
||||
scroll-y
|
||||
enhanced
|
||||
show-scrollbar="{{true}}"
|
||||
lower-threshold="24"
|
||||
bindscrolltolower="markListEnd"
|
||||
aria-label="十五回故事目录,可上下滑动"
|
||||
>
|
||||
<view class="chapter-grid">
|
||||
<view
|
||||
wx:for="{{chapters}}"
|
||||
wx:key="chapterId"
|
||||
class="chapter-card paper-panel {{item.completed ? 'completed' : ''}} {{item.current ? 'current' : ''}}"
|
||||
aria-role="button"
|
||||
aria-label="第{{item.number}}回,{{item.title}},{{item.readingLabel}}"
|
||||
hover-class="chapter-card-hover"
|
||||
data-chapter="{{item.number}}"
|
||||
bindtap="openChapter"
|
||||
>
|
||||
<view class="chapter-number">
|
||||
<text>第</text>
|
||||
<text class="number">{{item.number < 10 ? '0' + item.number : item.number}}</text>
|
||||
<text>回</text>
|
||||
</view>
|
||||
<view class="chapter-card-copy">
|
||||
<text class="year">{{item.year}}</text>
|
||||
<text class="chapter-name">{{item.title}}</text>
|
||||
<view class="chapter-badges">
|
||||
<text class="chapter-badge {{item.completed ? 'done-badge' : item.current ? 'current-badge' : 'read-badge'}}">
|
||||
{{item.readingLabel}}
|
||||
</text>
|
||||
<text wx:if="{{item.current && item.eventCount > 0 && !item.completed}}" class="chapter-progress">
|
||||
已看见 {{item.eventCount}} 处线索
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="open-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="scroll-ending">十五回故事,都在这本画册里。</text>
|
||||
</scroll-view>
|
||||
|
||||
<view class="list-scroll-guide" aria-live="polite">
|
||||
<text>{{catalogListEnded ? '已经翻到最后一回' : '向下翻,还有更多故事'}}</text>
|
||||
<text class="scroll-guide-mark" aria-hidden="true">{{catalogListEnded ? '✓' : '⌄'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,364 @@
|
||||
.catalog-shell {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
gap: 12px;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.catalog-head {
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
min-height: 64px;
|
||||
grid-template-columns: 132px minmax(0, 1fr) 132px;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 8px 112px 8px 14px;
|
||||
}
|
||||
|
||||
.restart-story-button {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 48px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
color: #7b2c25;
|
||||
border-color: #a85d4e;
|
||||
}
|
||||
|
||||
.catalog-heading-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.catalog-head > .quiet-button {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 48px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.catalog-title {
|
||||
overflow: hidden;
|
||||
font-size: 25px;
|
||||
font-weight: 850;
|
||||
letter-spacing: 2px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.catalog-title-short {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-heading-copy .eyebrow {
|
||||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chapter-scroll {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.list-scroll-guide {
|
||||
display: flex;
|
||||
min-height: 40px;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
color: #f4e5bd;
|
||||
background: rgba(58, 40, 28, 0.94);
|
||||
border: 1px solid rgba(222, 194, 139, 0.55);
|
||||
border-radius: 8px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.scroll-guide-mark {
|
||||
color: #f0c86b;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.chapter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
padding: 0 2px 22px;
|
||||
}
|
||||
|
||||
.chapter-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 112px;
|
||||
overflow: hidden;
|
||||
grid-template-columns: 68px minmax(0, 1fr) 24px;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 12px 16px 12px 12px;
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.chapter-card.completed {
|
||||
border-color: #50745f;
|
||||
}
|
||||
|
||||
.chapter-card.current {
|
||||
border: 3px solid #a23a2d;
|
||||
box-shadow: 0 10px 25px rgba(94, 32, 24, 0.24);
|
||||
}
|
||||
|
||||
.chapter-card-hover {
|
||||
transform: translateY(2px);
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
display: flex;
|
||||
height: 88px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: #f4e4be;
|
||||
background: #432b1e;
|
||||
border-left: 7px solid var(--cinnabar);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chapter-number .number {
|
||||
font-family: Georgia, serif;
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.chapter-card-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.year {
|
||||
color: #9a332b;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.chapter-name {
|
||||
display: -webkit-box;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
line-height: 1.35;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.chapter-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.chapter-badge {
|
||||
padding: 5px 9px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.done-badge {
|
||||
color: #eff7ef;
|
||||
background: #37624f;
|
||||
}
|
||||
|
||||
.current-badge {
|
||||
color: #fff1cf;
|
||||
background: #943128;
|
||||
}
|
||||
|
||||
.read-badge {
|
||||
color: #725c42;
|
||||
background: #e5d3a9;
|
||||
}
|
||||
|
||||
.chapter-progress {
|
||||
color: #6e5942;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.open-arrow {
|
||||
color: #8e392f;
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.scroll-ending {
|
||||
display: block;
|
||||
padding: 4px 0 18px;
|
||||
color: #d9c69e;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.font-xlarge .catalog-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.font-xlarge .catalog-heading-copy .eyebrow {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-card {
|
||||
min-height: 126px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-number {
|
||||
height: 100px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-number .number {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.font-xlarge .year {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-name {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-badges,
|
||||
.font-xlarge .chapter-progress {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.font-xlarge .scroll-ending {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.font-xlarge .list-scroll-guide {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
@media (max-width: 730px), (max-height: 400px) {
|
||||
.catalog-shell {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.catalog-head {
|
||||
min-height: 58px;
|
||||
grid-template-columns: 96px minmax(0, 1fr) 116px;
|
||||
gap: 9px;
|
||||
padding: 6px 108px 6px 8px;
|
||||
}
|
||||
|
||||
.catalog-title {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.catalog-heading-copy .eyebrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-title-full {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-title-short {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chapter-grid {
|
||||
gap: 9px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.chapter-card {
|
||||
min-height: 102px;
|
||||
grid-template-columns: 58px minmax(0, 1fr) 20px;
|
||||
gap: 10px;
|
||||
padding: 9px 10px 9px 8px;
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
height: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chapter-number .number {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
.year {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.chapter-name {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.chapter-badges,
|
||||
.chapter-progress {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.open-arrow {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.list-scroll-guide {
|
||||
min-height: 36px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.scroll-guide-mark {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.font-xlarge .catalog-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-card {
|
||||
min-height: 116px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-number {
|
||||
height: 94px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-number .number {
|
||||
font-size: 31px;
|
||||
}
|
||||
|
||||
.font-xlarge .year {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-name {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.font-xlarge .chapter-badges,
|
||||
.font-xlarge .chapter-progress {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
const memoryCards = require('../../data/memoryCards')
|
||||
const { getProgress } = require('../../utils/storage')
|
||||
const { getCollectedMemories } = require('../../utils/memoryCollection')
|
||||
const { chapterRoute } = require('../../utils/chapterRoute')
|
||||
const {
|
||||
getHomeLayoutMetrics,
|
||||
getHomeLayoutStyle,
|
||||
hasReadingProgress,
|
||||
} = require('./homeLayout')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
coverOpened: false,
|
||||
completedCount: 0,
|
||||
lastChapter: 1,
|
||||
hasReadingProgress: false,
|
||||
memoryCount: 0,
|
||||
homeLayoutStyle: [
|
||||
'height:390px',
|
||||
'--home-top-inset:8px',
|
||||
'--home-right-inset:10px',
|
||||
'--home-bottom-inset:7px',
|
||||
'--home-left-inset:10px',
|
||||
].join(';'),
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.applyHomeLayout()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const progress = getProgress()
|
||||
this.setData({
|
||||
completedCount: progress.completedChapters.length,
|
||||
lastChapter: progress.lastChapter || 1,
|
||||
hasReadingProgress: hasReadingProgress(progress),
|
||||
memoryCount: getCollectedMemories(progress, memoryCards).length,
|
||||
})
|
||||
this.applyHomeLayout()
|
||||
},
|
||||
|
||||
onResize(resizeInfo) {
|
||||
this.applyHomeLayout(resizeInfo)
|
||||
},
|
||||
|
||||
applyHomeLayout(resizeInfo = null) {
|
||||
let windowInfo = {}
|
||||
try {
|
||||
windowInfo = wx.getWindowInfo
|
||||
? wx.getWindowInfo()
|
||||
: wx.getSystemInfoSync()
|
||||
} catch (error) {
|
||||
windowInfo = {}
|
||||
}
|
||||
|
||||
const resizeSize = resizeInfo && resizeInfo.size
|
||||
? resizeInfo.size
|
||||
: resizeInfo
|
||||
if (resizeSize) {
|
||||
windowInfo = {
|
||||
...windowInfo,
|
||||
windowWidth: resizeSize.windowWidth || windowInfo.windowWidth,
|
||||
windowHeight: resizeSize.windowHeight || windowInfo.windowHeight,
|
||||
}
|
||||
}
|
||||
|
||||
let menuRect = {}
|
||||
try {
|
||||
menuRect = wx.getMenuButtonBoundingClientRect
|
||||
? wx.getMenuButtonBoundingClientRect()
|
||||
: {}
|
||||
} catch (error) {
|
||||
menuRect = {}
|
||||
}
|
||||
|
||||
const metrics = getHomeLayoutMetrics(windowInfo, menuRect)
|
||||
this.setData({
|
||||
homeLayoutStyle: getHomeLayoutStyle(metrics),
|
||||
})
|
||||
},
|
||||
|
||||
startGame() {
|
||||
const chapter = this.data.lastChapter || 1
|
||||
wx.navigateTo({
|
||||
url: chapterRoute(chapter),
|
||||
})
|
||||
},
|
||||
|
||||
revealCover() {
|
||||
if (this.data.coverOpened) return
|
||||
this.setData({ coverOpened: true })
|
||||
},
|
||||
|
||||
keepCoverOpen() {},
|
||||
|
||||
openCatalog() {
|
||||
wx.navigateTo({ url: '/pages/catalog/catalog' })
|
||||
},
|
||||
|
||||
openCast() {
|
||||
wx.navigateTo({ url: '/pages/cast/cast' })
|
||||
},
|
||||
|
||||
openMemories() {
|
||||
wx.navigateTo({ url: '/pages/memories/memories' })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '唐侦探:在一桌饭里,看见被忽略的人',
|
||||
path: '/pages/home/home',
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '唐侦探:在一桌饭里,看见被忽略的人',
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "唐侦探",
|
||||
"pageOrientation": "landscape"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<view class="safe-shell home-shell" style="{{homeLayoutStyle}}">
|
||||
<view
|
||||
class="home-comic-book {{coverOpened ? 'is-open' : 'is-closed'}}"
|
||||
bindtap="revealCover"
|
||||
aria-label="{{coverOpened ? '唐侦探漫画书已经翻开' : '唐侦探漫画封面,轻触翻开'}}"
|
||||
>
|
||||
<view class="home-cover-art" aria-label="桂香饭店人物群像漫画封面">
|
||||
<image
|
||||
class="home-cover-image"
|
||||
src="/assets/comic/s01-c01/S01-C01-P02-missing-table-ensemble-v1.jpg"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view class="home-cover-shade"></view>
|
||||
<view class="home-cover-spine"><text>甄养堂 · 中国健康连环画</text></view>
|
||||
<view class="home-cover-title-block">
|
||||
<text class="home-cover-series">第一季</text>
|
||||
<text class="home-cover-title">唐侦探</text>
|
||||
<text class="home-cover-subtitle">桂香里的第十五桌</text>
|
||||
</view>
|
||||
<view wx:if="{{!coverOpened}}" class="home-cover-touch">
|
||||
<text class="home-cover-touch-mark">›</text>
|
||||
<text>翻开瞧瞧</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
wx:if="{{coverOpened}}"
|
||||
class="home-flyleaf paper-panel"
|
||||
catchtap="keepCoverOpen"
|
||||
aria-label="漫画书扉页"
|
||||
>
|
||||
<view class="home-brand">
|
||||
<view class="seal">甄</view>
|
||||
<view class="home-brand-copy">
|
||||
<text class="home-brand-name">甄养堂</text>
|
||||
<text class="home-brand-kind">一本能看、能点、能带回家聊的中国健康连环画</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="home-flyleaf-title">桂香里的第十五桌</text>
|
||||
<text class="home-season">第一季 · 一桌饭里的三代人</text>
|
||||
<text class="home-quote">“一桌饭,不应该只有坐下的人,还应该有被看见的人。”</text>
|
||||
|
||||
<button
|
||||
class="home-primary"
|
||||
catchtap="startGame"
|
||||
aria-label="{{hasReadingProgress ? '继续阅读上次看到的地方' : '翻开第一回'}}"
|
||||
>
|
||||
{{hasReadingProgress ? '接着上回往下看' : '翻开第一回'}}
|
||||
</button>
|
||||
|
||||
<view class="home-tabs">
|
||||
<button class="home-tab" catchtap="openCatalog" aria-label="打开十五回目录">目录</button>
|
||||
<button class="home-tab" catchtap="openCast" aria-label="打开人物画谱">人物</button>
|
||||
<button class="home-tab home-memory-tab" catchtap="openMemories" aria-label="打开我的桂香岁月">
|
||||
岁月册<text wx:if="{{memoryCount > 0}}"> · {{memoryCount}}页</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<text class="home-tagline">先看画、听故事;翻到背面,再聊聊这回事。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text wx:if="{{coverOpened}}" class="home-disclaimer">这里聊的是日常生活,不替代诊断、处方或个体化治疗建议。</text>
|
||||
</view>
|
||||
@@ -0,0 +1,678 @@
|
||||
.home-shell {
|
||||
--home-top-inset: calc(8px + constant(safe-area-inset-top));
|
||||
--home-right-inset: calc(10px + constant(safe-area-inset-right));
|
||||
--home-bottom-inset: calc(7px + constant(safe-area-inset-bottom));
|
||||
--home-left-inset: calc(10px + constant(safe-area-inset-left));
|
||||
--home-top-inset: calc(8px + env(safe-area-inset-top));
|
||||
--home-right-inset: calc(10px + env(safe-area-inset-right));
|
||||
--home-bottom-inset: calc(7px + env(safe-area-inset-bottom));
|
||||
--home-left-inset: calc(10px + env(safe-area-inset-left));
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
min-height: 0;
|
||||
padding: var(--home-top-inset) var(--home-right-inset)
|
||||
var(--home-bottom-inset) var(--home-left-inset);
|
||||
gap: 6px;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-comic-book {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
color: #33251b;
|
||||
background: #1d130f;
|
||||
border: 3px solid #9e845e;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 15px 34px rgba(0, 0, 0, 0.36);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.home-comic-book.is-open {
|
||||
grid-template-columns: minmax(0, 1.45fr) minmax(280px, 1fr);
|
||||
}
|
||||
|
||||
.home-cover-art {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: #241811;
|
||||
}
|
||||
|
||||
.home-comic-book.is-open .home-cover-art {
|
||||
border-right: 7px solid #6f2b23;
|
||||
box-shadow: 10px 0 24px rgba(32, 19, 12, 0.34);
|
||||
}
|
||||
|
||||
.home-cover-image,
|
||||
.home-cover-shade {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.home-cover-shade {
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(29, 18, 12, 0.68), transparent 24%, transparent 72%, rgba(29, 18, 12, 0.2)),
|
||||
linear-gradient(0deg, rgba(31, 18, 12, 0.82), transparent 53%);
|
||||
box-shadow: inset 0 0 70px rgba(30, 16, 9, 0.3);
|
||||
}
|
||||
|
||||
.home-cover-spine {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 46px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #f0d8a7;
|
||||
background: rgba(74, 35, 26, 0.94);
|
||||
border-right: 2px solid #c59a58;
|
||||
font-family: "Songti SC", "STSong", serif;
|
||||
font-size: 15px;
|
||||
font-weight: 850;
|
||||
letter-spacing: 3px;
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
|
||||
.home-cover-title-block {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 72px;
|
||||
bottom: 54px;
|
||||
display: flex;
|
||||
max-width: 64%;
|
||||
padding: 13px 19px 15px;
|
||||
color: #f7e7c4;
|
||||
background: rgba(43, 27, 18, 0.86);
|
||||
border-left: 7px solid #a53a30;
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.28);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-cover-series {
|
||||
color: #e4c277;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.home-cover-title {
|
||||
margin-top: 3px;
|
||||
font-family: "Songti SC", "STSong", serif;
|
||||
font-size: 43px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 8px;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.home-cover-subtitle {
|
||||
margin-top: 6px;
|
||||
color: #f1d18c;
|
||||
font-family: "Songti SC", "STSong", serif;
|
||||
font-size: 25px;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.home-cover-touch {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
right: 24px;
|
||||
bottom: 22px;
|
||||
display: flex;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 8px 15px;
|
||||
color: #fff0ca;
|
||||
background: rgba(54, 34, 23, 0.9);
|
||||
border: 2px solid #d7b36d;
|
||||
border-radius: 999px;
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
box-shadow: 0 7px 18px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.home-cover-touch-mark {
|
||||
display: flex;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #6c271f;
|
||||
background: #f0d28c;
|
||||
border-radius: 50%;
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.home-flyleaf {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: 16px 20px;
|
||||
background:
|
||||
repeating-linear-gradient(0deg, rgba(92, 61, 35, 0.03) 0, rgba(92, 61, 35, 0.03) 1px, transparent 1px, transparent 5px),
|
||||
#f2e4bf;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-flyleaf-title {
|
||||
display: block;
|
||||
margin-top: 9px;
|
||||
color: #8d2d26;
|
||||
font-family: "Songti SC", "STSong", serif;
|
||||
font-size: 29px;
|
||||
font-weight: 900;
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-quote {
|
||||
position: static;
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
color: #4e3a2b;
|
||||
font-size: 18px;
|
||||
font-weight: 750;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-primary {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-tabs {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.home-flyleaf .home-tab {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 5px 7px;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.home-comic-book button::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.home-book {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
grid-template-columns: minmax(0, 3fr) minmax(285px, 2fr);
|
||||
border: 2px solid #9e845e;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.home-art {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background:
|
||||
repeating-linear-gradient(0deg, rgba(65, 42, 25, 0.04) 0, rgba(65, 42, 25, 0.04) 1px, transparent 1px, transparent 5px),
|
||||
#241811;
|
||||
border-right: 5px solid var(--cinnabar);
|
||||
}
|
||||
|
||||
.home-art-image,
|
||||
.home-art-shade {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.home-art-shade {
|
||||
pointer-events: none;
|
||||
background: linear-gradient(0deg, rgba(35, 23, 16, 0.82), transparent 48%);
|
||||
}
|
||||
|
||||
.home-memory-ribbon {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 6px 10px 6px 7px;
|
||||
color: #4a3225;
|
||||
background: rgba(241, 221, 178, 0.96);
|
||||
border: 2px solid #9e754a;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 5px 14px rgba(25, 15, 8, 0.28);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 850;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.home-memory-ribbon-mark {
|
||||
display: flex;
|
||||
width: 28px;
|
||||
height: 35px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffe9bb;
|
||||
background: #963128;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 78%, 0 100%);
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.home-quote {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 22px;
|
||||
bottom: 18px;
|
||||
left: 22px;
|
||||
color: #f5e5c0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.home-copy {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
justify-content: center;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 18px 24px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.home-brand .seal {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex: none;
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
.home-brand-copy {
|
||||
display: flex;
|
||||
color: #7f2a23;
|
||||
flex-direction: column;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.home-brand-kind {
|
||||
color: #765f45;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.home-title {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-size: 45px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 7px;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.home-subtitle {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: #8d2d26;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.home-season {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #6f5942;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.home-primary {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
padding: 8px 14px;
|
||||
color: #fff0cb;
|
||||
background: var(--cinnabar);
|
||||
border: 3px solid #74231d;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 18px rgba(111, 32, 27, 0.24);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 21px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.home-tabs {
|
||||
display: grid;
|
||||
gap: 9px;
|
||||
margin-top: 10px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.home-tab {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 10px;
|
||||
color: #4b3829;
|
||||
background: #ead9b2;
|
||||
border: 2px solid #997b54;
|
||||
border-radius: 7px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.home-memory-count {
|
||||
display: flex;
|
||||
min-width: 30px;
|
||||
height: 26px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 6px;
|
||||
color: #f9edce;
|
||||
background: #704b35;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.home-tagline {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
color: #594431;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 1.48;
|
||||
}
|
||||
|
||||
.home-disclaimer {
|
||||
display: block;
|
||||
flex: none;
|
||||
color: #d7c49b;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.35;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-height: 620px) {
|
||||
.home-copy {
|
||||
justify-content: flex-start;
|
||||
overflow-y: hidden;
|
||||
padding: 12px 18px;
|
||||
}
|
||||
|
||||
.home-brand .seal {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.home-brand-copy {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.home-brand-kind {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.home-title {
|
||||
margin-top: 7px;
|
||||
font-size: 39px;
|
||||
}
|
||||
|
||||
.home-subtitle {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
.home-season {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.home-primary {
|
||||
min-height: 60px;
|
||||
margin-top: 11px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.home-tabs {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.home-tab {
|
||||
min-height: 48px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.home-tagline {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 16px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.home-disclaimer {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 730px) {
|
||||
.home-brand-kind {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.home-brand {
|
||||
max-width: calc(100% - 104px);
|
||||
}
|
||||
|
||||
.home-title {
|
||||
font-size: 39px;
|
||||
letter-spacing: 5px;
|
||||
}
|
||||
|
||||
.home-subtitle {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.home-tagline {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 16px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.home-memory-ribbon {
|
||||
top: 9px;
|
||||
left: 9px;
|
||||
min-height: 48px;
|
||||
padding: 5px 8px 5px 6px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 430px) {
|
||||
.home-comic-book.is-open {
|
||||
grid-template-columns: minmax(0, 1.3fr) minmax(292px, 1fr);
|
||||
}
|
||||
|
||||
.home-cover-title-block {
|
||||
left: 62px;
|
||||
bottom: 34px;
|
||||
padding: 9px 13px 10px;
|
||||
}
|
||||
|
||||
.home-cover-series {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.home-cover-title {
|
||||
font-size: 34px;
|
||||
letter-spacing: 6px;
|
||||
}
|
||||
|
||||
.home-cover-subtitle {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.home-cover-touch {
|
||||
right: 16px;
|
||||
bottom: 13px;
|
||||
min-height: 48px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.home-flyleaf {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-brand .seal {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-brand-copy {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home-flyleaf-title {
|
||||
margin-top: 5px;
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-season {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-quote {
|
||||
margin-top: 5px;
|
||||
font-size: 15px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-primary {
|
||||
min-height: 50px;
|
||||
margin-top: 6px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-tabs {
|
||||
gap: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-tab {
|
||||
min-height: 48px;
|
||||
padding: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home-flyleaf .home-tagline {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home-copy {
|
||||
justify-content: flex-start;
|
||||
overflow-y: hidden;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.home-brand .seal {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.home-brand-copy {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.home-brand-kind {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.home-title {
|
||||
margin-top: 4px;
|
||||
font-size: 34px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.home-subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.home-season {
|
||||
margin-top: 3px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.home-primary {
|
||||
min-height: 60px;
|
||||
margin-top: 7px;
|
||||
padding: 6px 10px;
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.home-tabs {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.home-tagline {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 15px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.home-disclaimer {
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
function finiteNumber(value, fallback = 0) {
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : fallback
|
||||
}
|
||||
|
||||
function clamp(value, minimum, maximum) {
|
||||
return Math.min(maximum, Math.max(minimum, value))
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页使用自定义导航栏,横屏时既要避开左右刘海与底部 Home
|
||||
* Indicator,也要为微信右上角胶囊留下真实空间。CSS 的 env()
|
||||
* 在部分微信横屏机型上只会返回系统安全区,不包含胶囊,因此这里
|
||||
* 以运行时尺寸为准,CSS env() 只作为脚本尚未执行时的兜底。
|
||||
*/
|
||||
function getHomeLayoutMetrics(windowInfo = {}, menuRect = {}) {
|
||||
const windowWidth = Math.max(320, finiteNumber(windowInfo.windowWidth, 844))
|
||||
const windowHeight = Math.max(240, finiteNumber(windowInfo.windowHeight, 390))
|
||||
const safeArea = windowInfo.safeArea || {}
|
||||
|
||||
const safeLeft = clamp(
|
||||
finiteNumber(safeArea.left, 0),
|
||||
0,
|
||||
windowWidth / 3,
|
||||
)
|
||||
const safeRightEdge = clamp(
|
||||
finiteNumber(safeArea.right, windowWidth),
|
||||
windowWidth * 2 / 3,
|
||||
windowWidth,
|
||||
)
|
||||
const safeRight = clamp(
|
||||
windowWidth - safeRightEdge,
|
||||
0,
|
||||
windowWidth / 3,
|
||||
)
|
||||
const safeTop = Math.max(
|
||||
0,
|
||||
finiteNumber(windowInfo.statusBarHeight, 0),
|
||||
finiteNumber(safeArea.top, 0),
|
||||
)
|
||||
const safeBottomEdge = clamp(
|
||||
finiteNumber(safeArea.bottom, windowHeight),
|
||||
windowHeight * 2 / 3,
|
||||
windowHeight,
|
||||
)
|
||||
const safeBottom = clamp(
|
||||
windowHeight - safeBottomEdge,
|
||||
0,
|
||||
windowHeight / 3,
|
||||
)
|
||||
|
||||
const menuLeft = finiteNumber(menuRect.left, windowWidth)
|
||||
const menuBottom = finiteNumber(menuRect.bottom, 0)
|
||||
const hasMenuRect = (
|
||||
menuLeft > windowWidth / 2
|
||||
&& menuLeft < windowWidth
|
||||
&& menuBottom > 0
|
||||
)
|
||||
const compactHeight = windowHeight <= 620
|
||||
const horizontalPageGap = compactHeight ? 10 : 14
|
||||
const capsuleGap = compactHeight ? 8 : 10
|
||||
|
||||
const leftInset = Math.ceil(safeLeft + horizontalPageGap)
|
||||
const rightInset = Math.ceil(Math.max(
|
||||
safeRight + horizontalPageGap,
|
||||
hasMenuRect
|
||||
? windowWidth - menuLeft + capsuleGap
|
||||
: safeRight + horizontalPageGap,
|
||||
))
|
||||
const topInset = Math.ceil(safeTop + (compactHeight ? 8 : 12))
|
||||
const bottomInset = Math.ceil(safeBottom + (compactHeight ? 7 : 12))
|
||||
|
||||
return {
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
compactHeight,
|
||||
safeLeft,
|
||||
safeRight,
|
||||
safeTop,
|
||||
safeBottom,
|
||||
leftInset,
|
||||
rightInset,
|
||||
topInset,
|
||||
bottomInset,
|
||||
hasMenuRect,
|
||||
}
|
||||
}
|
||||
|
||||
function getHomeLayoutStyle(metrics = {}) {
|
||||
return [
|
||||
`height:${Math.max(240, finiteNumber(metrics.windowHeight, 390))}px`,
|
||||
`--home-top-inset:${Math.max(0, finiteNumber(metrics.topInset, 8))}px`,
|
||||
`--home-right-inset:${Math.max(0, finiteNumber(metrics.rightInset, 10))}px`,
|
||||
`--home-bottom-inset:${Math.max(0, finiteNumber(metrics.bottomInset, 7))}px`,
|
||||
`--home-left-inset:${Math.max(0, finiteNumber(metrics.leftInset, 10))}px`,
|
||||
].join(';')
|
||||
}
|
||||
|
||||
function hasReadingProgress(progress = {}) {
|
||||
const completedChapters = Array.isArray(progress.completedChapters)
|
||||
? progress.completedChapters
|
||||
: []
|
||||
const completedHotspots = progress.completedHotspots
|
||||
&& typeof progress.completedHotspots === 'object'
|
||||
? progress.completedHotspots
|
||||
: {}
|
||||
const hasCompletedHotspot = Object.values(completedHotspots).some(
|
||||
(ids) => Array.isArray(ids) && ids.length > 0,
|
||||
)
|
||||
|
||||
return (
|
||||
completedChapters.length > 0
|
||||
|| finiteNumber(progress.lastChapter, 1) > 1
|
||||
|| hasCompletedHotspot
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHomeLayoutMetrics,
|
||||
getHomeLayoutStyle,
|
||||
hasReadingProgress,
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
const memoryCards = require('../../data/memoryCards')
|
||||
const {
|
||||
getProgress,
|
||||
getSettings,
|
||||
saveSettings,
|
||||
} = require('../../utils/storage')
|
||||
const {
|
||||
getCollectedMemories,
|
||||
} = require('../../utils/memoryCollection')
|
||||
const { chapterRoute } = require('../../utils/chapterRoute')
|
||||
|
||||
function twoDigits(value) {
|
||||
return String(value).padStart(2, '0')
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
memories: [],
|
||||
memoryCount: 0,
|
||||
fontScale: 'large',
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const progress = getProgress()
|
||||
const settings = getSettings()
|
||||
const memories = getCollectedMemories(progress, memoryCards).map(
|
||||
(memory, index) => ({
|
||||
...memory,
|
||||
chapterLabel: `第${twoDigits(memory.chapterNumber)}回`,
|
||||
pageLabel: `第${index + 1}页`,
|
||||
characterInitial: memory.characterName.slice(0, 1),
|
||||
}),
|
||||
)
|
||||
this.setData({
|
||||
memories,
|
||||
memoryCount: memories.length,
|
||||
fontScale: settings.fontScale === 'xlarge' ? 'xlarge' : 'large',
|
||||
})
|
||||
},
|
||||
|
||||
goBack() {
|
||||
wx.reLaunch({ url: '/pages/home/home' })
|
||||
},
|
||||
|
||||
openCatalog() {
|
||||
wx.navigateTo({ url: '/pages/catalog/catalog' })
|
||||
},
|
||||
|
||||
openChapter(event) {
|
||||
const chapter = Number(event.currentTarget.dataset.chapter) || 1
|
||||
wx.navigateTo({
|
||||
url: chapterRoute(chapter),
|
||||
})
|
||||
},
|
||||
|
||||
toggleFont() {
|
||||
const fontScale = this.data.fontScale === 'large' ? 'xlarge' : 'large'
|
||||
const settings = getSettings()
|
||||
settings.fontScale = fontScale
|
||||
saveSettings(settings)
|
||||
this.setData({ fontScale })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '我的桂香岁月:饭桌边,也有值得带回家说的话',
|
||||
path: '/pages/home/home',
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '我的桂香岁月:饭桌边,也有值得带回家说的话',
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<view class="safe-shell memories-shell font-{{fontScale}}">
|
||||
<view class="memories-book paper-panel">
|
||||
<view class="memories-head">
|
||||
<button class="memory-back" bindtap="goBack" aria-label="回到封面">
|
||||
<text class="memory-back-long">‹ 封面</text>
|
||||
<text class="memory-back-short">封面</text>
|
||||
</button>
|
||||
<view class="memories-heading">
|
||||
<text class="memories-title">我的桂香岁月</text>
|
||||
<text class="memories-subtitle">
|
||||
{{memoryCount > 0 ? '收下的故事,慢慢翻、慢慢聊' : '一本等你慢慢装满的故事册'}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="memories-head-actions">
|
||||
<button
|
||||
class="memory-font"
|
||||
bindtap="toggleFont"
|
||||
aria-label="{{fontScale === 'large' ? '把文字再放大一些' : '恢复较大文字'}}"
|
||||
>
|
||||
<text class="memory-font-long">{{fontScale === 'large' ? '再大一点' : '标准大字'}}</text>
|
||||
<text class="memory-font-short">大字</text>
|
||||
</button>
|
||||
<view class="capsule-safe-space" aria-hidden="true"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{memoryCount === 0}}" class="empty-memory">
|
||||
<view class="empty-bookmark">桂香</view>
|
||||
<view class="empty-copy">
|
||||
<text class="empty-title">这本册子,还等着第一段回忆</text>
|
||||
<text class="empty-text">每读完一回,把最后那一页“收进画册”,这里就会多一段人物、一件旧物和一句带回家聊的话。</text>
|
||||
<button class="empty-action" bindtap="openCatalog">去翻一回故事</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
wx:else
|
||||
class="memory-scroll"
|
||||
scroll-x
|
||||
enhanced
|
||||
enable-flex
|
||||
show-scrollbar="{{true}}"
|
||||
aria-label="已收藏的桂香故事页"
|
||||
>
|
||||
<view class="memory-pages">
|
||||
<view
|
||||
wx:for="{{memories}}"
|
||||
wx:key="cardId"
|
||||
class="memory-sheet"
|
||||
aria-label="{{item.pageLabel}},{{item.chapterLabel}},{{item.chapterTitle}}"
|
||||
>
|
||||
<view class="memory-sheet-spine" aria-hidden="true"></view>
|
||||
<view class="memory-left">
|
||||
<text class="memory-page-number">{{item.pageLabel}}</text>
|
||||
<view class="memory-person-seal">{{item.characterInitial}}</view>
|
||||
<text class="memory-person">{{item.characterName}}</text>
|
||||
<text class="memory-era">{{item.eraLine}}</text>
|
||||
<view class="memory-object">
|
||||
<text class="memory-label">这一回留下的物件</text>
|
||||
<text>{{item.eraObject || '桂香饭桌边的一件旧物'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="memory-right" scroll-y enhanced show-scrollbar="{{true}}">
|
||||
<text class="memory-chapter">{{item.chapterLabel}} · {{item.chapterTitle}}</text>
|
||||
<text class="memory-echo">“{{item.tableEcho}}”</text>
|
||||
<view class="memory-note">
|
||||
<text class="memory-label">今天可以这样做</text>
|
||||
<text>{{item.lifeAction}}</text>
|
||||
</view>
|
||||
<view class="memory-note family-note">
|
||||
<text class="memory-label">带回家聊一聊</text>
|
||||
<text>“{{item.familyLine}}”</text>
|
||||
</view>
|
||||
<button
|
||||
class="memory-reread"
|
||||
data-chapter="{{item.chapterNumber}}"
|
||||
bindtap="openChapter"
|
||||
aria-label="再读第{{item.chapterNumber}}回,{{item.chapterTitle}}"
|
||||
>再读这一回</button>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="memory-end-card">
|
||||
<text class="memory-end-title">往后的页,还在饭桌边等你</text>
|
||||
<text>每收下一页,不是为了攒数字,只为记住一个被看见的人。</text>
|
||||
<button class="memory-end-action" bindtap="openCatalog">继续翻故事</button>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,568 @@
|
||||
/* Personal storybook: horizontal pages with elder-friendly reading controls. */
|
||||
.memories-shell {
|
||||
height: 100vh;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.memories-book {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 9px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.memories-head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 66px;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px 8px 12px;
|
||||
border-bottom: 3px solid #9b332a;
|
||||
}
|
||||
|
||||
.memory-back,
|
||||
.memory-font,
|
||||
.empty-action,
|
||||
.memory-reread,
|
||||
.memory-end-action {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 13px;
|
||||
border: 2px solid #997a54;
|
||||
border-radius: 7px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.memory-back,
|
||||
.memory-font {
|
||||
color: #52392a;
|
||||
background: #ead8af;
|
||||
}
|
||||
|
||||
.memories-heading {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 8px;
|
||||
right: 246px;
|
||||
left: 134px;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.memories-title {
|
||||
overflow: hidden;
|
||||
font-size: 29px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 3px;
|
||||
line-height: 1.16;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.memories-subtitle {
|
||||
overflow: hidden;
|
||||
color: #725b43;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.capsule-safe-space {
|
||||
width: 102px;
|
||||
height: 48px;
|
||||
flex: 0 0 102px;
|
||||
}
|
||||
|
||||
.memories-head-actions {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.memory-font {
|
||||
min-width: 112px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.memory-back-short,
|
||||
.memory-font-short {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.memory-back {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 8px;
|
||||
left: 12px;
|
||||
width: 112px;
|
||||
min-width: 112px;
|
||||
}
|
||||
|
||||
.empty-memory {
|
||||
display: grid;
|
||||
width: calc(100% - 48px);
|
||||
max-width: 680px;
|
||||
min-height: 230px;
|
||||
align-self: center;
|
||||
grid-template-columns: 132px minmax(0, 1fr);
|
||||
align-items: stretch;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
background: #f8ecc9;
|
||||
border: 3px double #97784f;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 12px 28px rgba(67, 40, 23, 0.18);
|
||||
}
|
||||
|
||||
.empty-bookmark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #f8e7bb;
|
||||
background: #913128;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 7px;
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
|
||||
.empty-copy {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 22px 28px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 27px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
margin-top: 9px;
|
||||
color: #604b36;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
width: 190px;
|
||||
margin-top: 14px;
|
||||
color: #fff0cc;
|
||||
background: #943128;
|
||||
border-color: #74231d;
|
||||
}
|
||||
|
||||
.memory-scroll {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
padding: 12px 0 10px;
|
||||
}
|
||||
|
||||
.memory-pages {
|
||||
display: inline-flex;
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.memory-sheet {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 710px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
grid-template-columns: 225px minmax(0, 1fr);
|
||||
background:
|
||||
repeating-linear-gradient(0deg, rgba(84, 55, 31, 0.025) 0, rgba(84, 55, 31, 0.025) 1px, transparent 1px, transparent 6px),
|
||||
#f8edcc;
|
||||
border: 3px double #91724b;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 10px 24px rgba(58, 35, 21, 0.2);
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
.memory-sheet-spine {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 219px;
|
||||
width: 7px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(90deg, rgba(81, 50, 27, 0.1), rgba(255, 255, 255, 0.58), rgba(81, 50, 27, 0.12));
|
||||
}
|
||||
|
||||
.memory-left {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
padding: 17px 20px;
|
||||
background: #263a3e;
|
||||
flex-direction: column;
|
||||
color: #f5e5bd;
|
||||
}
|
||||
|
||||
.memory-page-number {
|
||||
align-self: flex-start;
|
||||
color: #d6ba7e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.memory-person-seal {
|
||||
display: flex;
|
||||
width: 74px;
|
||||
height: 74px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 4px;
|
||||
color: #8d2d25;
|
||||
background: #efe0b8;
|
||||
border: 5px double #ad4d3a;
|
||||
border-radius: 50%;
|
||||
font-size: 37px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.memory-person {
|
||||
margin-top: 5px;
|
||||
font-size: 25px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.memory-era {
|
||||
margin-top: 4px;
|
||||
color: #e3cf9f;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 650;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.memory-object {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: auto;
|
||||
padding: 8px 10px;
|
||||
color: #f5e5bd;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
border-left: 4px solid #caa65e;
|
||||
flex-direction: column;
|
||||
font-size: 17px;
|
||||
font-weight: 750;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.memory-right {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 14px 22px 16px 25px;
|
||||
}
|
||||
|
||||
.memory-chapter {
|
||||
display: block;
|
||||
color: #8d2f27;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.memory-echo {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
font-size: 23px;
|
||||
font-weight: 900;
|
||||
line-height: 1.45;
|
||||
border-bottom: 1px solid #c9af7f;
|
||||
}
|
||||
|
||||
.memory-note {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
color: #4f3c2b;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.42;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.memory-label {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
color: #a03b31;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.family-note {
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #35634f;
|
||||
}
|
||||
|
||||
.memory-reread {
|
||||
width: 156px;
|
||||
margin-top: 10px;
|
||||
color: #fff0cc;
|
||||
background: #8d3028;
|
||||
border-color: #70231e;
|
||||
}
|
||||
|
||||
.memory-end-card {
|
||||
display: flex;
|
||||
width: 310px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex: none;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
color: #f2e2b8;
|
||||
background: #3a2a20;
|
||||
border: 3px double #ad8d5e;
|
||||
border-radius: 6px;
|
||||
flex-direction: column;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.memory-end-title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 26px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.memory-end-action {
|
||||
width: 170px;
|
||||
margin-top: 18px;
|
||||
color: #34251c;
|
||||
background: #e9d6a8;
|
||||
}
|
||||
|
||||
.font-xlarge .memory-echo {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.font-xlarge .memory-note,
|
||||
.font-xlarge .memory-object,
|
||||
.font-xlarge .memory-end-card {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
@media (max-width: 730px), (max-height: 400px) {
|
||||
.memories-head {
|
||||
min-height: 58px;
|
||||
padding: 5px 4px;
|
||||
}
|
||||
|
||||
.memories-heading {
|
||||
top: 17px;
|
||||
right: 158px;
|
||||
left: 82px;
|
||||
}
|
||||
|
||||
.memory-back,
|
||||
.memory-font,
|
||||
.empty-action,
|
||||
.memory-reread,
|
||||
.memory-end-action {
|
||||
min-height: 48px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.memories-title {
|
||||
font-size: 18px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.memories-subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.capsule-safe-space {
|
||||
width: 76px;
|
||||
flex-basis: 76px;
|
||||
}
|
||||
|
||||
.memory-font {
|
||||
width: 72px;
|
||||
min-width: 72px;
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.memory-back {
|
||||
top: 5px;
|
||||
left: 4px;
|
||||
width: 74px;
|
||||
min-width: 0;
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.memories-head-actions {
|
||||
top: 5px;
|
||||
right: 4px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.memory-back-long,
|
||||
.memory-font-long {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.memory-back-short,
|
||||
.memory-font-short {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.memory-scroll {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.memory-pages {
|
||||
gap: 12px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.memory-sheet {
|
||||
width: 565px;
|
||||
grid-template-columns: 170px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.memory-sheet-spine {
|
||||
left: 164px;
|
||||
}
|
||||
|
||||
.memory-left {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.memory-page-number {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.memory-person-seal {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
margin-top: 1px;
|
||||
font-size: 29px;
|
||||
}
|
||||
|
||||
.memory-person {
|
||||
margin-top: 2px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.memory-era {
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.memory-object {
|
||||
padding: 6px 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.memory-right {
|
||||
padding: 10px 14px 12px 18px;
|
||||
}
|
||||
|
||||
.memory-chapter {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.memory-echo {
|
||||
margin-top: 4px;
|
||||
padding-bottom: 5px;
|
||||
font-size: 20px;
|
||||
line-height: 1.32;
|
||||
}
|
||||
|
||||
.memory-note {
|
||||
margin-top: 5px;
|
||||
font-size: 15.5px;
|
||||
line-height: 1.32;
|
||||
}
|
||||
|
||||
.memory-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.memory-reread {
|
||||
width: 138px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.font-xlarge .memory-echo {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.font-xlarge .memory-note,
|
||||
.font-xlarge .memory-object,
|
||||
.font-xlarge .memory-end-card {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.empty-memory {
|
||||
width: calc(100% - 24px);
|
||||
max-width: 590px;
|
||||
min-height: 220px;
|
||||
grid-template-columns: 100px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.empty-bookmark {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.empty-copy {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
margin-top: 5px;
|
||||
font-size: 16px;
|
||||
line-height: 1.42;
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
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 = '这回生活观察暂时没有找到。回到封面,还可以慢慢翻一回。'
|
||||
|
||||
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 findReportCard(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: {
|
||||
reportCard: null,
|
||||
chapterLabel: '',
|
||||
invalidCard: false,
|
||||
invalidCardMessage: INVALID_CARD_MESSAGE,
|
||||
},
|
||||
|
||||
onLoad(options = {}) {
|
||||
const reportCard = findReportCard(options.cardId)
|
||||
if (!reportCard) {
|
||||
this.setData({
|
||||
reportCard: null,
|
||||
chapterLabel: '',
|
||||
invalidCard: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
reportCard: { ...reportCard },
|
||||
chapterLabel: `第${twoDigits(reportCard.chapterNumber)}回`,
|
||||
invalidCard: false,
|
||||
})
|
||||
},
|
||||
|
||||
backToChapter() {
|
||||
const reportCard = this.data.reportCard
|
||||
if (!reportCard || !reportCard.chapterNumber) {
|
||||
this.goHome()
|
||||
return
|
||||
}
|
||||
const pages = typeof getCurrentPages === 'function'
|
||||
? getCurrentPages()
|
||||
: []
|
||||
const previous = pages[pages.length - 2]
|
||||
if (
|
||||
previous
|
||||
&& String(previous.route || '').endsWith('/pages/chapter/chapter')
|
||||
&& typeof wx.navigateBack === 'function'
|
||||
) {
|
||||
wx.navigateBack({ delta: 1 })
|
||||
return
|
||||
}
|
||||
wx.navigateTo({
|
||||
url: chapterRoute(reportCard.chapterNumber),
|
||||
})
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const target = { url: '/pages/home/home' }
|
||||
if (typeof wx.reLaunch === 'function') {
|
||||
wx.reLaunch(target)
|
||||
return
|
||||
}
|
||||
wx.redirectTo(target)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<view class="safe-shell report-shell">
|
||||
<view wx:if="{{reportCard}}" class="report-book paper-panel">
|
||||
<view class="report-identity">
|
||||
<text class="report-kicker">桂香生活观察</text>
|
||||
<view class="report-seal" aria-hidden="true">察</view>
|
||||
<text class="report-chapter">{{chapterLabel}}</text>
|
||||
<text class="report-title">{{reportCard.chapterTitle}}</text>
|
||||
<view class="report-person-row">
|
||||
<text class="report-person-label">这回人物</text>
|
||||
<text class="report-person">{{reportCard.characterName}}</text>
|
||||
</view>
|
||||
<view class="report-local-note">
|
||||
<text>这页只留在您的手机里。</text>
|
||||
<text>不会发送答题记录,也不用于诊断。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="report-main">
|
||||
<view class="report-capsule-safe-space" aria-hidden="true"></view>
|
||||
<scroll-view
|
||||
class="report-scroll"
|
||||
scroll-y
|
||||
enhanced
|
||||
bounces="{{false}}"
|
||||
show-scrollbar="{{true}}"
|
||||
aria-label="{{chapterLabel}}桂香生活观察"
|
||||
>
|
||||
<text class="report-heading">这一回,咱们看见了什么</text>
|
||||
<text class="report-disclaimer">本回生活观察,不是个人健康评估,也不替代诊断和个体化治疗</text>
|
||||
|
||||
<view class="report-observation-list">
|
||||
<view class="report-observation-card">
|
||||
<text class="report-observation-index" aria-hidden="true">1</text>
|
||||
<view class="report-observation-copy">
|
||||
<text class="report-observation-label">本回看见</text>
|
||||
<text class="report-observation-value">{{reportCard.observation}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="report-observation-card is-family">
|
||||
<text class="report-observation-index" aria-hidden="true">2</text>
|
||||
<view class="report-observation-copy">
|
||||
<text class="report-observation-label">家里可以</text>
|
||||
<text class="report-observation-value">{{reportCard.familySupport}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="report-observation-card is-today">
|
||||
<text class="report-observation-index" aria-hidden="true">3</text>
|
||||
<view class="report-observation-copy">
|
||||
<text class="report-observation-label">今天试试</text>
|
||||
<text class="report-observation-value">{{reportCard.todayAction}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="report-actions">
|
||||
<button
|
||||
class="report-action report-action-primary"
|
||||
bindtap="backToChapter"
|
||||
aria-label="回到{{chapterLabel}}记忆卡"
|
||||
>回到这一回</button>
|
||||
<button
|
||||
class="report-action report-action-secondary"
|
||||
bindtap="goHome"
|
||||
aria-label="回到唐侦探封面"
|
||||
>回到封面</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="report-invalid paper-panel" aria-live="polite">
|
||||
<view class="report-invalid-seal" aria-hidden="true">桂</view>
|
||||
<text class="report-invalid-title">这回观察,暂时没有翻开</text>
|
||||
<text class="report-invalid-copy">{{invalidCardMessage}}</text>
|
||||
<button
|
||||
class="report-action report-action-primary report-invalid-action"
|
||||
bindtap="goHome"
|
||||
aria-label="回到唐侦探封面"
|
||||
>回到封面</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,28 @@
|
||||
.report-shell{height:100vh;min-height:0;overflow:hidden}
|
||||
.report-book{display:grid;width:100%;height:100%;overflow:hidden;grid-template-columns:minmax(210px,2fr) minmax(0,5fr);border:2px solid #9e845e;border-radius:9px}
|
||||
.report-identity{display:flex;min-height:0;align-items:center;padding:15px 18px;overflow:hidden;color:#f5e5bd;background:#263a3e;border-right:5px solid #9b332a;flex-direction:column}
|
||||
.report-kicker{align-self:flex-start;color:#dfc486;font-size:17px;font-weight:850}
|
||||
.report-seal,.report-invalid-seal{display:flex;align-items:center;justify-content:center;color:#8d2d25;background:#efe0b8;border:5px double #ad4d3a;border-radius:50%;font-weight:900}
|
||||
.report-seal{width:66px;height:66px;flex:none;margin:7px 0 4px;font-size:32px}
|
||||
.report-title{margin-top:3px;font-size:21px;font-weight:900;line-height:1.2;text-align:center}
|
||||
.report-person-label{color:#d7bd7d;font-size:14px;font-weight:850}.report-person{margin-left:8px;font-size:18px;font-weight:900}
|
||||
.report-local-note{display:flex;width:100%;margin-top:auto;padding:6px 8px;color:#eadbb5;background:rgba(19,30,32,.38);border:1px solid rgba(223,196,134,.42);border-radius:6px;flex-direction:column;font-size:14px;font-weight:700;line-height:1.3}
|
||||
.report-main{position:relative;display:grid;min-height:0;padding:46px 17px 12px;gap:8px;grid-template-rows:minmax(0,1fr) auto}
|
||||
.report-capsule-safe-space{position:absolute;top:6px;right:8px;width:104px;height:36px;pointer-events:none}
|
||||
.report-scroll{width:100%;height:100%;min-height:0}
|
||||
.report-heading{display:block;padding-right:108px;color:#493325;font-size:24px;font-weight:900;line-height:1.22}
|
||||
.report-disclaimer{display:block;margin-top:6px;padding:6px 9px;color:#644a37;background:#eadfbe;border-left:5px solid #8f3028;font-size:15px;font-weight:750;line-height:1.3}
|
||||
.report-observation-list{display:grid;gap:6px;margin-top:7px}
|
||||
.report-observation-card{display:grid;align-items:center;gap:9px;padding:6px 9px;background:rgba(255,250,232,.72);border:1px solid rgba(126,86,50,.32);border-radius:7px;grid-template-columns:34px minmax(0,1fr)}
|
||||
.report-observation-card.is-family{border-left:5px solid #8c6f42}.report-observation-card.is-today{border-left:5px solid #47705c}
|
||||
.report-observation-index{padding:5px;color:#fff0ce;background:#8f3028;border-radius:50%;font-size:17px;font-weight:900;text-align:center}
|
||||
.report-observation-copy{display:grid;min-width:0;gap:2px}
|
||||
.report-observation-label{color:#8d2f27;font-size:15px;font-weight:900}
|
||||
.report-observation-value{color:#493325;font-size:19px;font-weight:750;line-height:1.32}
|
||||
.report-actions{display:grid;gap:9px;grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.report-action{display:flex;min-height:52px;align-items:center;justify-content:center;padding:6px 10px;border:3px solid;border-radius:8px;font-size:19px;font-weight:900;line-height:1.2}
|
||||
.report-action-primary{color:#fff0cb;background:#963128;border-color:#74231d}.report-action-secondary{color:#473326;background:#ead9b2;border-color:#997b54}
|
||||
.report-invalid{display:flex;width:min(620px,100%);align-items:center;margin:auto;padding:20px;flex-direction:column}.report-invalid-seal{padding:12px;font-size:28px}.report-invalid-title{font-size:22px;font-weight:900}
|
||||
@media(max-width:700px),(max-height:420px){
|
||||
.report-book{grid-template-columns:minmax(180px,2fr) minmax(0,5fr)}.report-identity{padding:7px 10px}.report-kicker{font-size:14px}.report-seal{width:42px;height:42px;margin:2px 0;border-width:3px;font-size:22px}.report-title{font-size:17px}.report-local-note{padding:3px 5px;font-size:12px;line-height:1.15}
|
||||
.report-main{padding:41px 9px 6px;gap:4px}.report-heading{padding-right:100px;font-size:20px}.report-disclaimer{margin-top:3px;padding:3px 6px;font-size:13px;line-height:1.18}.report-observation-list{gap:3px;margin-top:3px}.report-observation-card{gap:6px;padding:3px 6px;grid-template-columns:28px minmax(0,1fr)}.report-observation-index{font-size:14px}.report-observation-label{font-size:13px}.report-observation-value{font-size:16px;line-height:1.18}.report-actions{gap:5px}.report-action{min-height:48px;padding:4px 6px;font-size:16px}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user