设计完成30%
@@ -0,0 +1,83 @@
|
||||
<!-- 公共组件:全量页面视觉母版;根据页面目录展示认证、表单、列表、详情、时间轴、设置六类布局。 -->
|
||||
<template>
|
||||
<view class="module-page" :class="`module-page--${page.template}`">
|
||||
<!-- 背景层复用已验收的宣纸、淡墨山水资产,保持全量页面视觉一致。 -->
|
||||
<image class="module-page__paper" :src="isAuth ? '/static/assets/foundation/opaque/auth-page-paper.jpg' : '/static/assets/foundation/opaque/page-paper.jpg'" mode="aspectFill" />
|
||||
<image class="module-page__scenery" src="/static/assets/foundation/transparent/auth-ink-scenery.png" mode="aspectFill" />
|
||||
<PageHeader :title="page.title" :root="isRoot" :action="page.number" />
|
||||
|
||||
<view class="module-page__content" :class="{ 'module-page__content--tab': page.tab }">
|
||||
<view class="page-intro">
|
||||
<image class="page-intro__seal" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
|
||||
<view><text class="page-intro__number">{{ page.number }}</text><text class="page-intro__subtitle">{{ page.subtitle }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 表单与认证母版保留真实 input,便于后续接入接口。 -->
|
||||
<view v-if="page.template === 'auth' || page.template === 'form'" class="form-card paper-card">
|
||||
<view v-for="field in page.fields" :key="field" class="form-row"><text>{{ field }}</text><input :placeholder="`请输入${field}`" placeholder-class="form-placeholder" /></view>
|
||||
<text v-if="page.note" class="form-note">{{ page.note }}</text>
|
||||
<view class="primary-action" @click="showPending"><text>{{ page.action }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 列表母版用于家谱、成员、文章、相册、礼仪及记录。 -->
|
||||
<view v-else-if="page.template === 'list'" class="content-stack">
|
||||
<view v-for="item in page.sections" :key="item[0]" class="list-card paper-card" @click="showPending"><view class="list-card__mark">谱</view><view class="list-card__copy"><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view><text class="list-card__arrow">›</text></view>
|
||||
<view class="primary-action primary-action--compact" @click="showPending"><text>{{ page.action }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 详情母版强调一项主题信息与分段资料。 -->
|
||||
<view v-else-if="page.template === 'detail'" class="content-stack">
|
||||
<view class="detail-hero paper-card"><text class="detail-hero__seal">{{ page.number.slice(0, 1) }}</text><text class="detail-hero__title">{{ page.title }}</text><text>{{ page.subtitle }}</text></view>
|
||||
<view v-for="item in page.sections" :key="item[0]" class="detail-card paper-card"><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view>
|
||||
<view class="primary-action" @click="showPending"><text>{{ page.action }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 时间轴母版用于申请、成长和人生事件。 -->
|
||||
<view v-else-if="page.template === 'timeline'" class="timeline paper-card">
|
||||
<view v-for="item in page.sections" :key="item[0]" class="timeline__item"><view class="timeline__dot"></view><view><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view></view>
|
||||
<view class="primary-action primary-action--compact" @click="showPending"><text>{{ page.action }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 设置母版提供清晰的分组入口和状态说明。 -->
|
||||
<view v-else-if="page.template === 'settings'" class="settings paper-card">
|
||||
<view v-for="item in page.sections" :key="item[0]" class="settings__row" @click="showPending"><view><text>{{ item[0] }}</text><text>{{ item[1] }}</text></view><text>›</text></view>
|
||||
<view class="primary-action primary-action--compact" @click="showPending"><text>{{ page.action }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 空/待接入状态使用题签式留白,避免裸页面。 -->
|
||||
<view v-else class="status-card paper-card"><text class="status-card__badge">{{ page.badge }}</text><text class="status-card__lead">{{ page.lead }}</text><text class="status-card__note">{{ page.note }}</text><view class="primary-action" @click="showPending"><text>{{ page.action }}</text></view></view>
|
||||
</view>
|
||||
<AppTabbar v-if="page.tab" :active="page.tab" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import AppTabbar from '@/components/AppTabbar.vue'
|
||||
import { pageCatalog } from '@/data/page-catalog.js'
|
||||
|
||||
const props = defineProps({ pageId: { type: String, required: true } })
|
||||
const page = computed(() => pageCatalog[props.pageId])
|
||||
const isAuth = computed(() => page.value.template === 'auth')
|
||||
const isRoot = computed(() => false)
|
||||
const showPending = () => uni.showToast({ title: '该功能正在完善中', icon: 'none' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.module-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; }
|
||||
.module-page__paper, .module-page__scenery { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.module-page__paper { opacity: .98; }.module-page__scenery { opacity: .32; }
|
||||
.module-page__content { position: relative; z-index: 1; min-height: calc(100vh - 104rpx); padding: 34rpx 28rpx calc(64rpx + env(safe-area-inset-bottom)); box-sizing: border-box; }
|
||||
.module-page__content--tab { padding-bottom: calc(190rpx + env(safe-area-inset-bottom)); }
|
||||
.page-intro { display: flex; align-items: center; min-height: 104rpx; margin: 0 10rpx 26rpx; }
|
||||
.page-intro__seal { width: 74rpx; height: 74rpx; margin-right: 14rpx; }.page-intro__number, .page-intro__subtitle { display: block; }
|
||||
.page-intro__number { color: $brand-red; font-family: STKaiti, KaiTi, serif; font-size: 27rpx; font-weight: 700; letter-spacing: 3rpx; }.page-intro__subtitle { margin-top: 6rpx; color: $ink-muted; font-size: 22rpx; }
|
||||
.paper-card { position: relative; border: 1rpx solid $gold-soft; background: rgba(255, 250, 240, .9); box-shadow: inset 0 0 0 6rpx rgba(255, 255, 255, .48); }
|
||||
.form-card { padding: 16rpx 30rpx 34rpx; }.form-row { display: flex; align-items: center; min-height: 104rpx; border-bottom: 1rpx solid #ead9ba; }.form-row text { width: 142rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 29rpx; }.form-row input { min-width: 0; flex: 1; color: $ink; font-size: 27rpx; }.form-placeholder { color: #b3a895; }.form-note { display: block; margin-top: 22rpx; color: $ink-muted; font-size: 21rpx; text-align: center; }
|
||||
.primary-action { position: relative; display: flex; height: 100rpx; align-items: center; justify-content: center; margin-top: 34rpx; overflow: hidden; background-image: url('/static/assets/foundation/opaque/a01-primary-button.png'); background-position: center; background-repeat: no-repeat; background-size: 100% 100%; color: #fff9ec; font-family: STKaiti, KaiTi, serif; font-size: 34rpx; font-weight: 700; letter-spacing: 6rpx; }.primary-action--compact { height: 82rpx; font-size: 28rpx; }
|
||||
.content-stack { display: flex; flex-direction: column; gap: 20rpx; }.list-card { display: flex; min-height: 132rpx; align-items: center; padding: 16rpx 22rpx; }.list-card__mark { display: flex; width: 74rpx; height: 86rpx; align-items: center; justify-content: center; margin-right: 18rpx; border: 2rpx solid $brand-red; color: $brand-red; font-family: STKaiti, KaiTi, serif; font-size: 29rpx; font-weight: 700; }.list-card__copy { min-width: 0; flex: 1; }.list-card__copy text, .detail-card text, .timeline__item text, .settings__row text { display: block; }.list-card__copy text:first-child { color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 32rpx; font-weight: 700; }.list-card__copy text:last-child { margin-top: 9rpx; color: $ink-muted; font-size: 21rpx; }.list-card__arrow { color: $gold; font-size: 44rpx; }
|
||||
.detail-hero { display: flex; flex-direction: column; align-items: center; padding: 38rpx 30rpx; text-align: center; }.detail-hero__seal { display: flex; width: 80rpx; height: 96rpx; align-items: center; justify-content: center; border: 2rpx solid $brand-red; color: $brand-red; font-family: STKaiti, KaiTi, serif; font-size: 38rpx; font-weight: 700; }.detail-hero__title { margin: 20rpx 0 12rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 40rpx; font-weight: 700; }.detail-hero > text:last-child { color: $ink-muted; font-size: 22rpx; }.detail-card { padding: 28rpx; }.detail-card text:first-child { color: $brand-red; font-family: STKaiti, KaiTi, serif; font-size: 29rpx; font-weight: 700; }.detail-card text:last-child { margin-top: 14rpx; color: $ink-muted; font-size: 25rpx; line-height: 1.8; }
|
||||
.timeline { padding: 12rpx 28rpx 30rpx; }.timeline__item { position: relative; display: flex; gap: 22rpx; min-height: 112rpx; padding: 30rpx 0 0; }.timeline__item:not(:last-of-type)::before { position: absolute; top: 55rpx; left: 9rpx; width: 2rpx; height: 78rpx; background: $gold-soft; content: ''; }.timeline__dot { width: 20rpx; height: 20rpx; margin-top: 8rpx; border-radius: 50%; background: $brand-red; }.timeline__item text:first-child { color: $ink; font-size: 29rpx; font-weight: 700; }.timeline__item text:last-child { margin-top: 9rpx; color: $ink-muted; font-size: 22rpx; }
|
||||
.settings { padding: 0 28rpx 28rpx; }.settings__row { display: flex; min-height: 116rpx; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #ead9ba; }.settings__row text:first-child { color: $ink; font-size: 29rpx; }.settings__row text:last-child { margin-top: 8rpx; color: $ink-muted; font-size: 21rpx; }.settings__row > text { color: $gold; font-size: 42rpx; }.status-card { display: flex; min-height: 540rpx; flex-direction: column; align-items: center; justify-content: center; padding: 40rpx; text-align: center; }.status-card__badge { display: flex; width: 116rpx; height: 136rpx; align-items: center; justify-content: center; border: 2rpx solid $brand-red; color: $brand-red; font-family: STKaiti, KaiTi, serif; font-size: 50rpx; font-weight: 700; }.status-card__lead { margin-top: 30rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 36rpx; font-weight: 700; }.status-card__note { margin-top: 16rpx; color: $ink-muted; font-size: 23rpx; line-height: 1.7; }
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
// 全量视觉交付的本地页面目录:只保存页面内容与状态,样式由 ModulePage 的六类母版统一维护。
|
||||
export const pageCatalog = {
|
||||
a03: { number: 'A-03', title: '手机验证', subtitle: '验证手机号,守护家谱记忆', template: 'auth', fields: ['手机号', '验证码'], action: '验证并继续', note: '验证码服务待接入' },
|
||||
a04: { number: 'A-04', title: '注册账号', subtitle: '创建属于你的家谱账号', template: 'auth', fields: ['手机号', '设置密码', '确认密码'], action: '提交注册', note: '注册服务待接入' },
|
||||
a05: { number: 'A-05', title: '重设密码', subtitle: '通过手机号找回登录凭证', template: 'auth', fields: ['手机号', '验证码', '新密码'], action: '确认重设', note: '密码重设服务待接入' },
|
||||
a06: { number: 'A-06', title: '登录状态', subtitle: '账户安全提醒与登录结果', template: 'status', badge: '安', lead: '当前没有需要处理的登录异常', note: '授权取消、账号受限和登录失败会显示在这里', action: '返回登录' },
|
||||
|
||||
g02: { number: 'G-02', title: '我的家谱', subtitle: '等待第一段家族记忆开始', template: 'status', badge: '谱', lead: '你还没有加入任何家谱', note: '创建自己的家谱,或搜索并申请加入家族', action: '搜索家谱' },
|
||||
g07: { number: 'G-07', title: '搜索结果', subtitle: '为你找到 3 部公开家谱', template: 'list', sections: [['洛阳汤氏家谱', '河南 · 洛阳 · 158 位成员'], ['汝南周氏家谱', '河南 · 周口 · 86 位成员'], ['清河张氏家谱', '河北 · 邢台 · 214 位成员']], action: '申请加入' },
|
||||
g08: { number: 'G-08', title: '申请加入家谱', subtitle: '留下真实姓名,等待管理员审核', template: 'form', fields: ['真实姓名', '与家谱关系', '申请说明'], action: '提交申请', note: '提交后可在我的申请中查看进度' },
|
||||
g09: { number: 'G-09', title: '我的申请', subtitle: '查看加入家谱的审核进度', template: 'timeline', sections: [['洛阳汤氏家谱', '审核中 · 已提交于今天'], ['清河张氏家谱', '已通过 · 可进入家谱']], action: '查看申请说明' },
|
||||
g11: { number: 'G-11', title: '家谱设置', subtitle: '管理名称、公开范围与访问说明', template: 'settings', sections: [['家谱名称', '汤氏家谱'], ['公开范围', '仅成员可见'], ['访问说明', '家族资料,请妥善保存'], ['转让管理权', '功能待接入']], action: '保存设置' },
|
||||
g12: { number: 'G-12', title: '字辈诗', subtitle: '承载世代相传的字序与家训', template: 'detail', sections: [['启宗敦本,继世传芳', '当前使用字辈 · 敦'], ['字辈维护说明', '管理员可补录或调整字辈诗']], action: '维护字辈诗' },
|
||||
|
||||
t02: { number: 'T-02', title: '世系提示', subtitle: '世系图当前状态', template: 'status', badge: '树', lead: '请横屏查看完整世系图', note: '无成员、加载失败和权限不足会在此处提示', action: '返回成员目录' },
|
||||
t04: { number: 'T-04', title: '新增亲属', subtitle: '补全一段真实的家族关系', template: 'form', fields: ['姓名', '与本人关系', '出生日期', '性别'], action: '保存亲属', note: '保存后将在世系图中显示' },
|
||||
t05: { number: 'T-05', title: '编辑成员资料', subtitle: '妥善记录家人的生命信息', template: 'form', fields: ['姓名', '字辈', '出生日期', '人物简介'], action: '保存资料', note: '隐私字段仅向有权限成员展示' },
|
||||
t06: { number: 'T-06', title: '编辑亲属关系', subtitle: '确认成员之间的血缘与婚姻关系', template: 'form', fields: ['关联成员', '关系类型', '生效日期'], action: '保存关系', note: '发现关系冲突时会提示管理员处理' },
|
||||
t07: { number: 'T-07', title: '成员目录', subtitle: '按世系、姓名或字辈查找成员', template: 'list', sections: [['汤文正', '第 18 世 · 家谱管理员'], ['汤文德', '第 19 世 · 已完善资料'], ['汤文清', '第 19 世 · 待补充信息']], action: '搜索成员' },
|
||||
t08: { number: 'T-08', title: '成员状态', subtitle: '隐私、离世与访问权限说明', template: 'settings', sections: [['隐私成员', '仅本人和管理员可见'], ['已故成员', '在世系图中以纪念样式展示'], ['无访问权限', '请联系家谱管理员']], action: '查看隐私说明' },
|
||||
|
||||
f03: { number: 'F-03', title: '动态详情', subtitle: '一段属于家人的共同记忆', template: 'detail', sections: [['端午家宴', '今年端午全家相聚,留下了许多温暖照片。'], ['家人评论', '愿家族和睦兴旺,岁岁平安。']], action: '写下评论' },
|
||||
f04: { number: 'F-04', title: '谱文', subtitle: '收录家族文章、家训与往事', template: 'list', sections: [['家风家训', '代代相传的处世之道'], ['家族往事', '珍贵的口述与文字记录'], ['族谱序言', '一部家谱的起源']], action: '新建谱文' },
|
||||
f05: { number: 'F-05', title: '谱文详情', subtitle: '家族文字档案', template: 'detail', sections: [['家风家训', '孝友传家,勤俭立业;敬祖睦宗,诚实待人。'], ['收录时间', '2024 年 5 月 12 日']], action: '收藏谱文' },
|
||||
f06: { number: 'F-06', title: '编辑谱文', subtitle: '把值得传承的故事写下来', template: 'form', fields: ['文章标题', '文章分类', '正文内容'], action: '保存谱文', note: '草稿和发布功能待接入' },
|
||||
f07: { number: 'F-07', title: '家族相册', subtitle: '让每一张照片都回到家人身边', template: 'list', sections: [['2024 春节团圆', '18 张照片 · 更新于今天'], ['祖居旧影', '32 张照片 · 家族档案'], ['儿童成长', '46 张照片 · 持续更新']], action: '新建相册' },
|
||||
f08: { number: 'F-08', title: '相册详情', subtitle: '2024 春节团圆', template: 'detail', sections: [['相册说明', '一家人围坐团圆,留下新春最温暖的瞬间。'], ['照片墙', '共 18 张 · 点击照片可预览']], action: '添加照片' },
|
||||
f09: { number: 'F-09', title: '上传照片', subtitle: '选择照片并补充拍摄说明', template: 'form', fields: ['选择照片', '照片说明', '所属相册'], action: '开始上传', note: '上传失败时可在此重新尝试' },
|
||||
f10: { number: 'F-10', title: '家族视频', subtitle: '记录会动的家族记忆', template: 'status', badge: '影', lead: '视频服务暂未开放', note: '开放后可在这里浏览家族影像与纪念视频', action: '返回家族首页' },
|
||||
|
||||
r01: { number: 'R-01', title: '人物录', subtitle: '记录家族中值得铭记的人', template: 'list', sections: [['汤文正', '家谱管理员 · 第 18 世'], ['汤淑华', '家族长辈 · 第 17 世'], ['汤文清', '青年代表 · 第 19 世']], action: '新建人物' },
|
||||
r02: { number: 'R-02', title: '人物详情', subtitle: '汤文正', template: 'detail', sections: [['人物小传', '勤于修谱,常年整理族中旧照片与口述资料。'], ['家族印记', '参与维护汤氏家谱与家风家训。']], action: '编辑人物' },
|
||||
r03: { number: 'R-03', title: '贺礼簿', subtitle: '记录每一份家人之间的心意', template: 'list', sections: [['新春贺礼', '2024 年春节 · 12 条记录'], ['寿宴礼单', '汤老先生八十寿辰'], ['添丁祝福', '家族新成员的祝愿']], action: '新增贺礼' },
|
||||
r04: { number: 'R-04', title: '编辑贺礼', subtitle: '如实留下礼仪往来记录', template: 'form', fields: ['贺礼名称', '赠送人', '日期', '备注'], action: '保存贺礼', note: '删除确认功能待接入' },
|
||||
r05: { number: 'R-05', title: '礼仪活动', subtitle: '家族的重要时刻与礼序', template: 'list', sections: [['清明祭祖', '2024 年 4 月 4 日'], ['中秋家宴', '2024 年 9 月 17 日'], ['新春团拜', '2025 年 1 月 29 日']], action: '新建礼仪' },
|
||||
r06: { number: 'R-06', title: '礼仪详情', subtitle: '清明祭祖', template: 'detail', sections: [['活动说明', '缅怀先祖,凝聚家人,共叙家风传承。'], ['参与人员', '已登记 26 位家人']], action: '编辑活动' },
|
||||
r07: { number: 'R-07', title: '编辑礼仪', subtitle: '补全时间、地点与活动说明', template: 'form', fields: ['活动名称', '活动日期', '举办地点', '活动说明'], action: '保存活动', note: '发布提醒功能待接入' },
|
||||
r08: { number: 'R-08', title: '成长日志', subtitle: '珍藏生命成长中的每一个瞬间', template: 'timeline', sections: [['第一次叫爸爸', '2024 年 3 月 · 家人共同记录'], ['入园第一天', '2024 年 9 月 · 留下勇敢的笑脸']], action: '记录成长' },
|
||||
r09: { number: 'R-09', title: '人生事', subtitle: '记录值得回望的人生节点', template: 'timeline', sections: [['大学毕业', '2018 年 6 月 · 新的起点'], ['结为连理', '2022 年 10 月 · 家人见证']], action: '新增人生事' },
|
||||
r10: { number: 'R-10', title: '家族备忘', subtitle: '把重要的事留给未来的自己', template: 'list', sections: [['修谱资料整理', '请在本月底前补充老照片信息'], ['重阳敬老活动', '10 月 11 日上午在祠堂集合']], action: '新增备忘' },
|
||||
r11: { number: 'R-11', title: '功德记录', subtitle: '致谢每一份对家族的守护', template: 'list', sections: [['修缮祠堂', '2024 年春 · 家族共同支持'], ['奖学助学', '2024 年夏 · 鼓励后辈勤学']], action: '新增记录' },
|
||||
|
||||
n02: { number: 'N-02', title: '消息详情', subtitle: '家谱提醒', template: 'detail', sections: [['申请待审核', '汤文清申请加入家谱,请在审核页面查看。'], ['收到时间', '今天 10:28']], action: '标记已读' },
|
||||
|
||||
m02: { number: 'M-02', title: '个人资料', subtitle: '完善你的家谱身份信息', template: 'form', fields: ['头像昵称', '真实姓名', '常住地区', '个人简介'], action: '保存资料', note: '头像上传服务待接入' },
|
||||
m03: { number: 'M-03', title: '账号与安全', subtitle: '保护你的家谱账号', template: 'settings', sections: [['登录密码', '建议定期更新'], ['绑定手机号', '用于登录和安全验证'], ['登录设备', '暂无异常设备']], action: '检查账号安全' },
|
||||
m04: { number: 'M-04', title: '修改密码', subtitle: '设置新的安全密码', template: 'form', fields: ['当前密码', '新密码', '确认新密码'], action: '确认修改', note: '密码服务待接入' },
|
||||
m05: { number: 'M-05', title: '修改手机号', subtitle: '更换用于登录的手机号', template: 'form', fields: ['当前手机号', '新手机号', '验证码'], action: '确认更换', note: '短信验证服务待接入' },
|
||||
m06: { number: 'M-06', title: '帮助中心', subtitle: '常见问题与使用说明', template: 'settings', sections: [['如何创建家谱', '从家谱首页点击新建家谱'], ['如何邀请家人', '通过申请审核或邀请码加入'], ['资料隐私说明', '个人信息按权限展示']], action: '联系家谱助手' },
|
||||
m07: { number: 'M-07', title: '意见反馈', subtitle: '你的建议会让家谱更好', template: 'form', fields: ['反馈类型', '问题描述', '联系方式'], action: '提交反馈', note: '反馈服务待接入' },
|
||||
m08: { number: 'M-08', title: '应用推广', subtitle: '邀请亲友共建家族记忆', template: 'detail', sections: [['邀请家人', '将家谱分享给家人,共同补全家族故事。'], ['推广说明', '邀请功能与奖励规则待接入。']], action: '生成邀请海报' },
|
||||
m09: { number: 'M-09', title: 'VIP 与订单', subtitle: '家谱服务与订阅记录', template: 'status', badge: '礼', lead: '当前没有订阅订单', note: 'VIP 服务开放后会在这里展示权益与订单', action: '查看服务说明' },
|
||||
m10: { number: 'M-10', title: '关于家谱', subtitle: '传承每一段值得珍藏的家族记忆', template: 'settings', sections: [['用户协议', '功能待接入'], ['隐私政策', '功能待接入'], ['当前版本', '1.0.0'], ['退出登录', '仅本地演示']], action: '查看版本说明' }
|
||||
}
|
||||
@@ -1,18 +1,63 @@
|
||||
# A-01 Runtime Visual QA
|
||||
# A02 账号登录 Design QA
|
||||
|
||||
- Source visual truth: `docs/design/screens/A01-启动登录引导-栅格验收-412x915.png`
|
||||
- Target state: `412 × 915`, agreement unchecked.
|
||||
- Static contract: passed on 2026-07-13.
|
||||
- Runtime screenshot: current `412 × 915` capture accepted by the user on 2026-07-13 as the A-01 visual baseline.
|
||||
## Comparison target
|
||||
|
||||
## Evidence
|
||||
- Source visual truth: `docs/design/screens/runtime/2026-07-13/A01-360x800.png` and `docs/design/screens/runtime/2026-07-13/G01-360x800.png` are the user-approved brand references. They are not a user-approved A02 mockup.
|
||||
- Earlier A02 implementation: `docs/design/screens/runtime/2026-07-13/A02-before-360x800.png`.
|
||||
- Current implementation: `docs/design/screens/runtime/2026-07-13/A02-after-360x800.png`.
|
||||
- Full-view comparison evidence: `docs/design/screens/runtime/2026-07-13/A02-before-after-360x800.png` at 360 × 800.
|
||||
- Focused region review: full-view comparison is sufficient for this iteration because the corrected areas are the whole panel proportion, complete frame, button ratio and background treatment; no text or icon asset is too small to identify in the captured images.
|
||||
|
||||
- The accepted `412 × 915` capture preserves a continuous paper-and-ink background, complete button frames, the intended entry hierarchy and a single-line agreement row.
|
||||
- The user explicitly accepted this capture as the current A-01 visual baseline; no additional visual tuning is authorized in this phase.
|
||||
## Comparison history
|
||||
|
||||
## Accepted scope
|
||||
### Iteration 1: Before refactor
|
||||
|
||||
- This result accepts A-01's current visual baseline only.
|
||||
- Existing local behavior remains unchanged: agreement selection enables the A-02 login route; other entries remain local prepared-state prompts.
|
||||
**Findings**
|
||||
|
||||
final result: passed
|
||||
- [P1] The login form was a thin CSS rectangle rather than a complete genealogy panel.
|
||||
Location: `pages/auth/a02-login.vue`, `.login-panel`.
|
||||
Evidence: the left half of `A02-before-after-360x800.png` has a narrow plain outline, unlike A01’s complete buttons and G01’s complete title slips.
|
||||
Fix: introduced `static/assets/modules/auth/opaque/a02-login-panel.png` as the full opaque paper, double-gold-frame and corner-ornament surface.
|
||||
|
||||
- [P1] The A01 primary button skin was vertically compressed.
|
||||
Location: `pages/auth/a02-login.vue`, `.login-submit`.
|
||||
Evidence: the earlier button had a noticeably flatter appearance than the A01 reference.
|
||||
Fix: set the A02 display slot to 128rpx high and preserved the complete skin as the only button surface.
|
||||
|
||||
- [P2] The main task was visually weaker than the scenery below it.
|
||||
Location: the A02 above-the-fold layout.
|
||||
Evidence: the earlier card ended high in the viewport and left more visual weight to the empty mountain background.
|
||||
Fix: aligned the header with A01 and expanded the panel so its lower paper landscape becomes a deliberate task-ending surface.
|
||||
|
||||
### Iteration 2: Current implementation
|
||||
|
||||
**Findings**
|
||||
|
||||
- [P3] The page uses one existing cloud asset at the heading while the new panel supplies additional pale clouds. This is visually coherent at 360 × 800, but user review should decide whether the heading cloud is one layer too many.
|
||||
Location: A02 heading, right side.
|
||||
Evidence: `A02-after-360x800.png`.
|
||||
Follow-up: remove only the heading cloud if the user finds the top-right decoration busy; do not alter the panel frame or input structure.
|
||||
|
||||
## Required fidelity surfaces
|
||||
|
||||
- Fonts and typography: A02 uses the project’s existing STKaiti/KaiTi hierarchy; current capture keeps the title above form labels and retains readable body copy.
|
||||
- Spacing and layout rhythm: the larger panel establishes a clear header → title → tab → form → action cadence; 320 × 568 and 360 × 640 intentionally scroll rather than clip the action.
|
||||
- Colors and visual tokens: cinnabar remains limited to the header, selected Tab, helper action and primary button; antique gold frames and ink-brown body text follow A01/G01.
|
||||
- Image quality and asset fidelity: the panel is a complete opaque PNG with no generated text, placeholder controls, or CSS-drawn frame; the button remains the approved complete A01 asset.
|
||||
- Copy and content: account and SMS flows retain their original Chinese copy and real input controls; no visible workflow was invented.
|
||||
|
||||
## Primary interactions checked
|
||||
|
||||
- Account/password state captured at 320 × 568, 360 × 640, 360 × 800 and 412 × 915.
|
||||
- SMS Tab was clicked through the Chrome debugging session and captured at 360 × 800: `docs/design/screens/runtime/2026-07-13/A02-sms-360x800.png`.
|
||||
- Form submission behavior is covered by the existing A01/A02 contract and was not changed in this visual pass.
|
||||
|
||||
## Open question
|
||||
|
||||
There is no user-approved A02 visual mockup at the same route and state. A01 and G01 establish the visual language, but they cannot prove that this exact A02 composition meets the user’s subjective standard.
|
||||
|
||||
## Final result
|
||||
|
||||
final result: blocked
|
||||
|
||||
Blocker: wait for the user’s visual approval of the captured A02 account-password state before treating it as the final authentication module sample or propagating it to A03–A05.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# A01、G01 与 A02 视觉审视
|
||||
|
||||
> 日期:2026-07-13
|
||||
> 方法:通过用户授权的 Chrome 调试端口捕获真实 360 × 800 页面画面,并逐张检查。
|
||||
> 范围:只审视 A01、G01 两张已验收基准页与 A02 当前登录页;本记录不是用户最终验收。
|
||||
|
||||
## 1. 截图证据
|
||||
|
||||
| 页面 | 状态 | 截图 |
|
||||
| --- | --- | --- |
|
||||
| A01 启动/登录引导 | 用户已验收的认证基准 | `screens/runtime/2026-07-13/A01-360x800.png` |
|
||||
| G01 我的家谱 | 用户已验收的家谱基准 | `screens/runtime/2026-07-13/G01-360x800.png` |
|
||||
| A02 账号登录 | 当前实现,未验收 | `screens/runtime/2026-07-13/A02-before-360x800.png` |
|
||||
|
||||
## 2. A01、G01 共同的可继承规则
|
||||
|
||||
1. **完整资产承担外观。** 朱砂页头、纸纹、主/次按钮、题签框、谱印框、云纹和分区线都有明确的图片资产;文字与交互位于图片之上。
|
||||
2. **一屏只有一个视觉主角。** A01 是“家谱”标题与两枚操作按钮;G01 是当前家谱题签。其余山水、竹影和金线只托住主角。
|
||||
3. **信息层级有节奏。** 大号楷体标题、常规正文、弱化元信息分明;每一段间保留足够纸面,不把内容挤成普通管理表单。
|
||||
4. **朱砂只表达关键动作或当前状态。** A01 的登录、G01 的当前谱印与选中 Tab 使用朱砂;古金负责边框、图标和分隔。
|
||||
5. **同一类元素使用同一套成品。** A01 两枚按钮保持一致比例;G01 的当前题签、列表题签、谱印、快捷图标和底部导航形成连续的组件语言。
|
||||
6. **背景不抢任务。** 纸纹先提供可读底色,淡墨山水只落在空白和底部,不能穿过输入区或元信息。
|
||||
|
||||
## 3. A02 当前状态
|
||||
|
||||
### 可保留项
|
||||
|
||||
- 已有 A01 同源的纸纹、祠堂页头、谱印、淡墨背景和朱砂按钮皮肤。
|
||||
- 账号密码/手机验证码双 Tab、真实 input、忘记密码入口、协议判断与 A05 跳转仍应保留。
|
||||
- 360 × 800 下页面没有横向裁切,主按钮可见。
|
||||
|
||||
### 必须解决的可见问题
|
||||
|
||||
1. **登录面板是普通的细金线矩形。** 它没有完整纸面、双线、角纹或题签结构,和 A01 的完整按钮、G01 的完整谱签不属于同一视觉系统。
|
||||
2. **卡片内容过小且过密。** 标题、双 Tab、标签、占位文本、忘记密码与协议提示都落在一张矮卡内,用户进入后感受到的是“填写控件”,不是“进入家谱”。
|
||||
3. **按钮比例被压扁。** 当前主按钮使用约 4.49:1 的 A01 皮肤,却放入更扁的容器,边角和纹样失去端正的卷轴比例。
|
||||
4. **标题装饰没有成为层级。** 单侧云纹和细小返回符号像零散点缀,无法支撑“登录家谱”作为当前任务标题。
|
||||
5. **底部留白失衡。** 表单卡结束后到山水背景之间有大块无任务留白,而面板内部反而拥挤;需要把留白放在卡片外、把可读空间还给表单。
|
||||
|
||||
## 4. A02 设计决策
|
||||
|
||||
- 继续使用“家祠卷轴 · 庄重留白”,不另起现代渐变、玻璃卡片或圆角后台表单画风。
|
||||
- 认证头部与 A01 使用同一祠堂高度和谱印位置;头部以下先给清楚的“登录家谱”题签标题,再给完整不透明的认证面板资产。
|
||||
- 认证面板只承载 Tab、输入、忘记密码与协议说明;面板自身纸纹、金边和角纹由一张完整不透明 PNG 提供。
|
||||
- 主按钮只能按完整皮肤原比例显示;空间不足时缩小宽度或增加高度,不能纵向压缩图片。
|
||||
- 淡墨山水放到卡片结束后的留白与底部,作为收束,不穿过输入行。
|
||||
|
||||
## 5. 可见性与无障碍风险
|
||||
|
||||
- A02 的弱提示和协议文字在 360 × 800 下偏小,后续实现要保证正文至少 24rpx,主要点击区至少约 44dp。
|
||||
- 截图不能证明键盘焦点、读屏标签、错误提示朗读和真实 Android 字体回退;实施后仍需在 HBuilderX/Android 复核。
|
||||
- 本次只验证了正常态,没有验证输入错误、验证码倒计时或网络失败态;这些状态保持现有功能边界,不在本轮扩展接口。
|
||||
|
||||
## 6. 下一步
|
||||
|
||||
先为 A02 建立完整认证面板和正确比例按钮的资产合同,再做 A02 四尺寸前后对比;用户确认 A02 后,才将它作为 A03–A05 的认证样板。
|
||||
@@ -62,7 +62,7 @@
|
||||
| `docs/design/screens/A01-启动登录引导-效果稿-v3-文字注册入口.png` | A-01 视觉比对依据 | 已归档并由用户确认的 v3 效果稿 | `852 × 1846px` PNG;仅作真实 DOM 页面还原和运行时验收对照,不直接作为页面内容 |
|
||||
| `static/assets/backgrounds/auth-ancestral-header-v1.png`、`auth-rice-paper-v1.jpg`、`auth-ink-scenery-v1.png` | A-01 分层页面背景 | 2026-07-13 内置图像生成 | 当前 A-01 使用;不得复用 G-01 背景 |
|
||||
| `static/assets/icons/auth/login-outline-v1.png`、`static/assets/icons/auth/wechat-licensed-v1.png` | A-01 两个按钮图标 | 登录图标复用项目既有资产;微信图标来源与许可见旧记录 | 当前 A-01 使用;微信图标免费使用须在应用“关于/设置”及应用商店描述中保留 Icons8 署名链接 |
|
||||
| `static/assets/icons/auth/auth-button-corner-v1.png` | 两个入口按钮的四角卷草装饰 | 2026-07-13 根据 412 × 915 视觉基准生成,去绿幕后保存 | `160 × 160px` 透明 PNG;同一张素材镜像至四角 |
|
||||
| `static/assets/foundation/opaque/a01-primary-button.png`、`a01-secondary-button.png` | A01 登录与微信登录的完整按钮皮肤 | 2026-07-13 以运行时视觉基准的比例与画风生成;不裁切设计图 | `1877 × 418px`、`1881 × 419px` 不透明 PNG;分别承担底色、双线边框和四角纹样,DOM 仅叠放图标与文案 |
|
||||
| `static/assets/icons/auth/auth-divider-knot-v1.png` | 标题上下金线的中心对称中结 | 2026-07-13 根据 412 × 915 视觉基准生成,去绿幕后保存 | `160 × 96px` 透明 PNG;不得改用长祥云 |
|
||||
| `static/assets/icons/auth/auth-title-cloud-v1.png` | “家谱”标题左右祥云 | 2026-07-13 根据 412 × 915 视觉基准生成,去绿幕后保存 | `200 × 120px` 透明 PNG;右侧只允许镜像该素材 |
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# A02 账号登录设计记录
|
||||
|
||||
> 状态:实现与运行时截图已完成,等待用户视觉验收。
|
||||
> 路由:`pages/auth/a02-login?agreed=1`
|
||||
> 视觉来源:已验收的 A01 启动/登录引导与 G01 我的家谱。
|
||||
|
||||
## 1. 页面任务
|
||||
|
||||
让已确认协议的用户在“账号密码登录”与“手机验证码登录”之间切换并进入家谱。页面只保留本地字段校验、A05 忘记密码跳转与待接入 toast;不新增真实账号、短信、微信或后端逻辑。
|
||||
|
||||
## 2. 视觉结构
|
||||
|
||||
1. 使用与 A01 同高度的朱砂祠堂头和居中谱印,保证认证入口属于同一品牌场景。
|
||||
2. 头部下方放置一张完整不透明的卷轴认证面板,而不是普通 CSS 矩形卡;标题、双 Tab、输入、辅助链接和按钮文字都是可编辑的 Vue 内容层。
|
||||
3. 面板上部承载任务标题和双 Tab,中部承载两行输入与辅助操作,下部留给原比例朱砂按钮、协议提示与淡墨山水收束。
|
||||
4. 主按钮继续复用 A01 的 `a01-primary-button.png`,显示槽高度为 128rpx;不把约 4.49:1 的皮肤压进旧的 100rpx 矮按钮。
|
||||
5. 背景山水只出现在认证面板之外和面板底部,保证输入、标签与占位文本的对比度。
|
||||
|
||||
## 3. 资产合同
|
||||
|
||||
| 路径 | 属性/原图尺寸 | 页面显示与职责 |
|
||||
| --- | --- | --- |
|
||||
| `static/assets/foundation/opaque/auth-header.png` | opaque / 750×196 | 认证祠堂头,A01 与 A02 共用。 |
|
||||
| `static/assets/foundation/transparent/brand-seal.png` | transparent / 240×288 | 头部谱印。 |
|
||||
| `static/assets/modules/auth/opaque/a02-login-panel.png` | opaque / 940×1672 | 完整卷轴登录面板,承载纸纹、双金线、角纹、云纹与底部山水;A02 容器 666rpx × 1184rpx。 |
|
||||
| `static/assets/foundation/opaque/a01-primary-button.png` | opaque / 1877×418 | A02 唯一主操作按钮皮肤,显示槽 128rpx 高。 |
|
||||
| `static/assets/foundation/transparent/chevron-right.png` | transparent / 96×96 | 返回图标,水平翻转后使用,不再用文字符号或 CSS 圆框。 |
|
||||
|
||||
认证面板由内置 ImageGen 根据 A01、G01 的已验收截图生成;提示词约束为“完整不透明暖宣纸、双古金线、角纹、边缘淡云竹、中央留给真实控件、无文字无按钮无图标”。生成源保留在 Codex 生成目录,项目运行时只引用上表中的最终资产。
|
||||
|
||||
## 4. 交互与可读性
|
||||
|
||||
- 两个 Tab 仍由 `activeTab` 切换;已验证手机验证码 Tab 的真实页面状态。
|
||||
- 账号、密码、手机号、验证码继续使用原生 `input`,不烘焙到图片。
|
||||
- A02 不绘制面板或主按钮的 CSS `border`/`background`;对应完整外观由图片资产承担。
|
||||
- 320 × 568 下可纵向滚动,主按钮位于首屏可达区域;360 × 640、360 × 800、412 × 915 均无横向裁切。
|
||||
|
||||
## 5. 运行时证据
|
||||
|
||||
| 状态 | 截图 |
|
||||
| --- | --- |
|
||||
| 改造前,账号密码 Tab,360×800 | `screens/runtime/2026-07-13/A02-before-360x800.png` |
|
||||
| 改造后,账号密码 Tab,320×568 | `screens/runtime/2026-07-13/A02-after-320x568.png` |
|
||||
| 改造后,账号密码 Tab,360×640 | `screens/runtime/2026-07-13/A02-after-360x640.png` |
|
||||
| 改造后,账号密码 Tab,360×800 | `screens/runtime/2026-07-13/A02-after-360x800.png` |
|
||||
| 改造后,账号密码 Tab,412×915 | `screens/runtime/2026-07-13/A02-after-412x915.png` |
|
||||
| 改造后,手机验证码 Tab,360×800 | `screens/runtime/2026-07-13/A02-sms-360x800.png` |
|
||||
|
||||
## 6. 自动检查
|
||||
|
||||
- `tests/a02-asset-alpha-audit.ps1`:A02 卷轴面板存在、尺寸稳定、比例稳定、四角不透明。
|
||||
- `tests/a01-a02-ui-contract.ps1`:A02 使用完整卷轴面板与完整主按钮;保留 Tab、原生输入、A05 跳转;拒绝 CSS 面板和 CSS 主按钮外观。
|
||||
- `tests/compile-audit.ps1`:Vue 结构可编译。
|
||||
|
||||
用户未确认前,A02 不得在规划中标记为视觉验收完成,也不得作为其他认证页的最终样板直接复制。
|
||||
@@ -1,6 +1,6 @@
|
||||
# P00 页面结构与资产清单
|
||||
|
||||
> 状态:页面与现有资产迁移已完成;A01 完整按钮候选待设计图复核,两页截图回归待办。
|
||||
> 状态:59 条最终编号路由与第一版统一视觉母版已落地;A01/G01 运行时截图回归及全量逐页视觉精修待办。
|
||||
> 目的:让页面、路由和资产都有唯一、可读、可审计的归属;本文件是 P-00 的执行清单。
|
||||
|
||||
## 1. 页面命名规则
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
尚未被全量设计确认、但为了保留现有功能骨架的通用页面,必须在文件名中使用 `skeleton`,不能使用含义不明的 `index.vue`、`list.vue`、`detail.vue`。
|
||||
|
||||
## 2. 当前 15 个路由的迁移映射
|
||||
## 2. 首批 15 个路由的历史迁移映射
|
||||
|
||||
| 当前路由 | 当前可确认用途 | 目标文件 | 处理状态 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -32,10 +32,11 @@
|
||||
| `pages/tree/t03-member-profile` | T03 成员档案 | `pages/tree/t03-member-profile.vue` | 已迁移;未验收 |
|
||||
| `pages/family/f01-family-feed` | F01 家族动态 | `pages/family/f01-family-feed.vue` | 已迁移;未验收 |
|
||||
| `pages/family/f02-publish-feed` | F02 发布动态 | `pages/family/f02-publish-feed.vue` | 已迁移;未验收 |
|
||||
| `pages/skeleton/content-list-skeleton` | 多类型内容列表功能骨架 | `pages/skeleton/content-list-skeleton.vue` | 已迁移;不能计为设计页 |
|
||||
| `pages/notification/n01-message-center` | N01 消息中心 | `pages/notification/n01-message-center.vue` | 已迁移;未验收 |
|
||||
| `pages/profile/m01-profile-home` | M01 我的首页 | `pages/profile/m01-profile-home.vue` | 已迁移;未验收 |
|
||||
|
||||
上述表仅保留首批迁移的历史背景;当前最终 59 路由、文件存在性和中文编号注释的唯一合同是 `tests/full-page-visual-contract.ps1`。临时 `content-list-skeleton` 已由谱文、相册、礼仪、备忘录和字辈诗最终页面替代并移除。
|
||||
|
||||
迁移要求:同一轮更新页面文件、`pages.json`、所有跳转路径、测试和中文文件头注释;未完成全部引用更新时,不得删除旧路径。
|
||||
|
||||
## 3. 最终资产目录
|
||||
@@ -47,14 +48,18 @@ static/assets/
|
||||
page-paper.jpg
|
||||
auth-page-paper.jpg
|
||||
auth-header.png
|
||||
a01-primary-button.png
|
||||
a01-secondary-button.png
|
||||
root-header-cinnabar.jpg
|
||||
transparent/
|
||||
brand-seal.png
|
||||
auth-title-cloud.png
|
||||
auth-divider-knot.png
|
||||
auth-button-corner.png
|
||||
...
|
||||
modules/
|
||||
auth/
|
||||
opaque/
|
||||
a02-login-panel.png
|
||||
genealogy/
|
||||
opaque/
|
||||
transparent/
|
||||
@@ -66,7 +71,7 @@ static/assets/
|
||||
transparent/
|
||||
```
|
||||
|
||||
当前已迁入的资产以第 4 节台账为准。`auth-primary-button.png`、`auth-secondary-button.png` 尚未生成:在它们验收并替换 A01 的旧角标拼接实现前,A01 不能按“完整资产规则”关闭地基验收。
|
||||
当前已迁入的资产以第 4 节台账为准。A01 已使用 `a01-primary-button.png`、`a01-secondary-button.png` 两张完整按钮皮肤替换旧角标拼接实现;是否维持用户验收效果仍以运行时截图回归为准。
|
||||
|
||||
## 4. 当前资产台账
|
||||
|
||||
@@ -81,9 +86,9 @@ static/assets/
|
||||
| `foundation/transparent/auth-ink-scenery.png` | transparent / 750×1334 | 认证页淡墨叠层 | A01、A02 | 保留 |
|
||||
| `foundation/transparent/auth-title-cloud.png` | transparent / 200×120 | A01 标题左右云纹 | A01 | 保留 |
|
||||
| `foundation/transparent/auth-divider-knot.png` | transparent / 160×96 | A01 文案分隔结 | A01 | 保留 |
|
||||
| `foundation/transparent/auth-button-corner.png` | transparent / 160×160 | A01 旧按钮角标;待完整按钮替换 | A01 | 过渡保留 |
|
||||
| `foundation/opaque/a01-primary-button-candidate.png` | opaque / 1983×793 | A01 主按钮完整图片候选;未接入 | 无 | 待设计图复核 |
|
||||
| `foundation/opaque/a01-secondary-button-candidate.png` | opaque / 1983×793 | A01 次按钮完整图片候选;未接入 | 无 | 待设计图复核 |
|
||||
| `foundation/opaque/a01-primary-button.png` | opaque / 1877×418 | A01 朱砂主按钮完整皮肤 | A01 | 保留;待运行截图回归 |
|
||||
| `foundation/opaque/a01-secondary-button.png` | opaque / 1881×419 | A01 宣纸次按钮完整皮肤 | A01 | 保留;待运行截图回归 |
|
||||
| `modules/auth/opaque/a02-login-panel.png` | opaque / 940×1672 | A02 完整卷轴登录面板,含纸纹、双金线、角纹、云纹与底部山水 | A02 | 保留;待用户视觉验收 |
|
||||
| `foundation/transparent/auth-login-outline.png` | transparent / 96×96 | 登录图标 | A01 | 保留 |
|
||||
| `foundation/transparent/auth-wechat.png` | transparent / 96×96 | 微信图标 | A01 | 保留 |
|
||||
| `foundation/transparent/brand-seal.png` | transparent / 240×288 | 品牌谱印 | A01、A02、`PageHeader` | 保留 |
|
||||
@@ -112,7 +117,7 @@ static/assets/
|
||||
| `modules/genealogy/transparent/shortcut-generation-poem.png` | transparent / 96×96 | 字辈诗入口 | G01 | 保留 |
|
||||
| `modules/genealogy/transparent/shortcut-application.png` | transparent / 96×96 | 审核入口 | G01 | 保留 |
|
||||
|
||||
本轮已迁入 35 项在用资产,删除 24 项运行时零引用的旧副本。两张 A01 候选由图像生成工具制作,不是设计图裁切,当前比例约为 2.5:1,尚不符合现有按钮约 3.6:1 的显示比例,严禁直接接入或作为验收通过依据。旧版设计记录中的历史路径不再是资产依据;后续只按本台账引用和维护。
|
||||
本轮已迁入 35 项在用资产,删除 24 项运行时零引用的旧副本。A01 的两张完整按钮皮肤由图像生成工具制作,不是设计图裁切;它们的原始比例均约为 4.49:1,并由 A01 页面作为单张底层皮肤引用。旧版设计记录中的历史路径不再是资产依据;后续只按本台账引用和维护。
|
||||
|
||||
## 5. 图片属性规则
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 701 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 286 KiB |
@@ -0,0 +1,212 @@
|
||||
# 家谱 APP 视觉设计交接手册
|
||||
|
||||
> 更新日期:2026-07-14
|
||||
> 用途:在新电脑或交给新的 GPT 后,按本手册恢复当前工作;它只记录已发生的事实,不把待验收写成已完成。
|
||||
|
||||
## 1. 当前停点:先看这一节
|
||||
|
||||
项目已有 59 条最终编号路由,第一版都可以浏览;这不等于 59 个页面已完成视觉验收。
|
||||
|
||||
- 用户已经验收的视觉基准只有两个:**A01 启动/登录引导**、**G01 我的家谱**。它们的可见效果已冻结。
|
||||
- **A02 账号登录**已经完成一次资产化重做、功能回归和多尺寸截图,但**仍在等待用户审美验收**。在用户确认前,不得把 A02 标记为完成,也不得把它的样式批量传播到 A03--A06。
|
||||
- 其余模块的页面仍属于后续视觉设计任务;其中不少页面目前由 `components/ModulePage.vue` 与 `data/page-catalog.js` 提供第一版可浏览壳。这个壳是过渡方案,不是最终 UI。
|
||||
- 不提交、不推送、不上传 Git;由用户自行处理版本库操作。
|
||||
|
||||
下一位执行者的第一件事不是开始做新页面,而是请用户查看 A02 的前后对比:
|
||||
|
||||
- `docs/design/screens/runtime/2026-07-13/A02-before-after-360x800.png`
|
||||
- `docs/design/screens/runtime/2026-07-13/A02-after-412x915.png`
|
||||
- `docs/design/screens/runtime/2026-07-13/A02-sms-360x800.png`
|
||||
|
||||
用户若要求调整,只修改 A02 并重新截图、复核;用户若明确通过,才进入 A03--A06。
|
||||
|
||||
## 1.1 迁移前必须保留的文件
|
||||
|
||||
本手册和截图工具是本轮新增文件;换电脑前,用户需在自行提交/上传时包含它们,或完整复制当前工作区。我没有执行任何 Git 暂存、提交、推送或上传操作。
|
||||
|
||||
至少要保留:`docs/design/视觉设计交接手册.md`、`docs/交接记录.md`、`scripts/capture-chrome-page.js`、A02 页面/资产/测试、`docs/design/screens/runtime/2026-07-13/` 的运行证据,以及现有计划与设计记录。只复制已提交的旧版本会丢失当前停点。
|
||||
|
||||
## 2. 必读文件与唯一职责
|
||||
|
||||
| 文件 | 负责什么 |
|
||||
| --- | --- |
|
||||
| `AGENTS.md` | 最小修改、保护既有未提交内容、验证后再交付的项目规则。 |
|
||||
| `docs/规划.md` | 59 个页面的范围、模块顺序和“完成”的定义。 |
|
||||
| `docs/design/D1_安卓视觉规范与页面壳.md` | Android 页面、资产、布局与中文注释的硬约束。 |
|
||||
| `docs/design/P00_页面结构与资产清单.md` | 路由、页面文件和资产台账的唯一清单。 |
|
||||
| `docs/superpowers/plans/2026-07-13-product-design-visual-refinement.md` | 已批准的分模块视觉精修计划和验收顺序。 |
|
||||
| `docs/design/2026-07-13-A01-G01-A02-视觉审视.md` | A01/G01 视觉证据与旧 A02 的问题定位。 |
|
||||
| `docs/design/A02_账号登录_设计记录.md` | A02 当前实现、资产、截图和测试记录。 |
|
||||
| `design-qa.md` | A02 的 Design QA;当前 `final result: blocked`,原因是缺少用户的审美确认。 |
|
||||
| `docs/交接记录.md` | 简版当前状态与恢复入口;本手册提供完整操作细节。 |
|
||||
|
||||
发生冲突时,按“用户最新明确指令 > AGENTS.md > 上表中的当前台账/计划 > 历史文档”的顺序处理。不要以旧的草案或已废弃的截图切片方案为依据。
|
||||
|
||||
## 3. 已验收的风格与绝对禁区
|
||||
|
||||
### A01 与 G01 是可观察的品牌真源
|
||||
|
||||
- A01 参考图:`docs/design/screens/A01-启动登录引导-栅格验收-412x915.png`
|
||||
- G01 参考图:`docs/design/screens/G01-我的家谱-紧凑版设计稿-v5-Tabbar安全区.png`
|
||||
|
||||
共同语言是“家祠卷轴”:暖宣纸、朱砂主色、古金框线和元数据、墨褐字、低对比淡墨山水。主信息应先于背景被读到,留白是结构的一部分。
|
||||
|
||||
必须遵守:
|
||||
|
||||
1. 完整按钮、卡片、标题签、页面头图和有边框的装饰面,使用完整的**不透明位图资产**承载底色、纹理和边框。
|
||||
2. 透明 PNG 仅承担 Logo、图标、祥云、纹样等叠加元素;CSS 只承担布局、文字、状态与交互,不能伪造完整框、祥云、按钮或卡片。
|
||||
3. 不能把整张设计截图或局部截图裁进运行页面;运行资产必须是可复用的独立元素。
|
||||
4. 不要修改 A01/G01 已验收的可见布局、比例、配色或装饰。可读其实现和资产,但不能借“整理”改变效果。
|
||||
5. 不要用一个通用卡片母版覆盖所有模块。家谱、世系树、内容档案、人物记录、通知和设置必须在同一视觉语言下有各自的信息密度与层级。
|
||||
|
||||
## 4. A02 的精确状态
|
||||
|
||||
### 实现内容
|
||||
|
||||
- 路由与页面:`pages/auth/a02-login.vue`
|
||||
- 新面板资产:`static/assets/modules/auth/opaque/a02-login-panel.png`
|
||||
- 实际尺寸:940 × 1672
|
||||
- 属性:四角不透明,承载宣纸底、双古金框、角饰与淡纹样
|
||||
- 主按钮继续复用已验收完整资产:`static/assets/foundation/opaque/a01-primary-button.png`
|
||||
- 账号密码与短信验证码 Tab、输入、忘记密码跳转和原有交互均保留;短信 Tab 已经通过真实 Chrome 调试点击截图。
|
||||
|
||||
### 已保存的运行证据
|
||||
|
||||
目录:`docs/design/screens/runtime/2026-07-13/`
|
||||
|
||||
| 文件 | 状态 |
|
||||
| --- | --- |
|
||||
| `A02-before-360x800.png` | 改造前基线。 |
|
||||
| `A02-after-320x568.png` | 小屏账号密码态;允许纵向滚动,不允许横向裁切。 |
|
||||
| `A02-after-360x640.png` | 中屏账号密码态。 |
|
||||
| `A02-after-360x800.png` | 主对比尺寸。 |
|
||||
| `A02-after-412x915.png` | 大屏账号密码态。 |
|
||||
| `A02-sms-360x800.png` | 真实切换后的短信验证码态。 |
|
||||
| `A02-before-after-360x800.png` | 左旧右新的并排对比。 |
|
||||
|
||||
### 通过/未通过的界线
|
||||
|
||||
已通过的是:资产尺寸与不透明角审计、A01/A02 页面契约、编译检查、A02 的多尺寸运行截图和短信 Tab 交互。
|
||||
|
||||
未通过的是:用户对 A02 的最终审美确认。因此 `design-qa.md` 必须保持 `blocked`,直至用户明确确认。用户若觉得顶部祥云偏多,只可先移除 A02 标题右侧的单个祥云;不要无根据改动面板框、输入区或交互。
|
||||
|
||||
## 5. 换电脑后如何恢复截图与审视能力
|
||||
|
||||
### 必备环境
|
||||
|
||||
- Windows、Chrome、HBuilderX(项目当前以 Android `uni-app` 为目标)。
|
||||
- Node.js 22 或更高版本;当前脚本已在 Node 24 环境使用原生 `fetch` 与 `WebSocket`。
|
||||
- 先在 HBuilderX 启动本地 H5 调试页,并确认它能在 `http://localhost:5173` 打开。不要臆测项目的 npm 启动命令。
|
||||
|
||||
用专用 Chrome 调试实例开启远程端口;它与用户日常 Chrome 隔离:
|
||||
|
||||
```powershell
|
||||
$chrome = "$env:ProgramFiles\Google\Chrome\Application\chrome.exe"
|
||||
Start-Process -FilePath $chrome -ArgumentList `
|
||||
'--remote-debugging-port=9222', `
|
||||
"--user-data-dir=$env:TEMP\jiapu-chrome-debug", `
|
||||
'http://localhost:5173/#/pages/auth/a02-login?agreed=1'
|
||||
```
|
||||
|
||||
确认调试端口与应用页面存在:
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod 'http://127.0.0.1:9222/json/list' | Select-Object title, url
|
||||
```
|
||||
|
||||
使用项目内的正式脚本截图。它会导航**专用调试 Chrome**的当前标签页、写出 PNG,并在结束时清除设备尺寸覆盖;不能操作或关闭用户的日常 Chrome。
|
||||
|
||||
```powershell
|
||||
node scripts/capture-chrome-page.js `
|
||||
'http://localhost:5173/#/pages/auth/a02-login?agreed=1' `
|
||||
'.login-page' `
|
||||
'docs/design/screens/runtime/2026-07-14/A02-after-360x800.png' `
|
||||
360 800
|
||||
```
|
||||
|
||||
截图只作为 `docs/design/screens/runtime/<日期>/` 下的设计证据,绝不能放回 `static/assets/` 作为运行资源。每个代表页面至少做 360 × 800;模块代表页还要做 320 × 568、360 × 640、360 × 800、412 × 915。最终 Android/HBuilderX 截图仍需补做,H5 Chrome 截图不能替代它。
|
||||
|
||||
## 6. 后续执行顺序与每轮产物
|
||||
|
||||
### 门禁 0:先处理 A02
|
||||
|
||||
1. 让用户审 A02 前后对比与 412 × 915、短信 Tab 图。
|
||||
2. 若不通过:只在 A02 修正明确问题,先调整对应契约测试,再改页面/资产,重新截全尺寸图。
|
||||
3. 若通过:把 A02 状态同步到 `docs/规划.md`、`docs/交接记录.md`、`docs/design/A02_账号登录_设计记录.md` 与 `design-qa.md`,再开始 A03--A06。
|
||||
|
||||
### 其后的模块顺序
|
||||
|
||||
1. **账号 A03--A06**:以已验收 A02 为认证样板,但每个页面保留各自的任务与状态,不能直接复制表单。
|
||||
2. **家谱 G02--G12**:先做空态/创建、家谱概览与世代字等代表页,再扩展搜索、申请、设置。
|
||||
3. **世系树 T01--T08**:先建立树、成员档案、关系编辑三类代表页。
|
||||
4. **家族内容 F01--F10**:动态、文章、相册、媒体上传分别建立内容档案式层级。
|
||||
5. **人物记录 R01--R11**:人物档案、礼仪活动、成长时间线先定样板。
|
||||
6. **通知 N01--N02**:区分未读、已读、时间和详情层级,不只依赖颜色。
|
||||
7. **我的 M01--M10**:个人身份首页与安全设置先做,后续设置页按统一行式结构展开。
|
||||
|
||||
`ModulePage.vue`/`page-catalog.js` 中属于本轮模块的临时入口,必须在同一轮被真实页面替换并移除相应依赖;不得保留“新旧两套入口”或把通用壳伪称为精修完成。
|
||||
|
||||
### 每个模块的固定工作流
|
||||
|
||||
1. 用 Product Design 对已运行页面截图审视,明确该模块与 A01/G01 的继承点、不同的信息结构与验收尺寸。
|
||||
2. 先补/改最小的页面契约测试并看到失败,再实现页面;只有缺少真实视觉资产时才生成资产。
|
||||
3. 资产生成后记录路径、尺寸、是否不透明、用途和引用页到 P00;生成图必须经过本地检查后进入 `static/assets/`。
|
||||
4. 保留既有可用跳转和返回行为,删除本轮替换后不再使用的临时导入、目录项和样式。
|
||||
5. 运行受影响的测试、编译审计、真实路由截图,再更新设计记录、规划和交接状态。
|
||||
6. 在用户验收之前,状态统一写“实现与截图完成,视觉待用户验收”。
|
||||
|
||||
## 7. 下一位 GPT 需要具备并实际调用的能力
|
||||
|
||||
这不是“只改 CSS”的任务。执行视觉页前应先读取对应技能说明,并在对用户的工作说明中指出正在使用的技能及目的。
|
||||
|
||||
| 能力/技能 | 何时使用 | 项目中的正确做法 |
|
||||
| --- | --- | --- |
|
||||
| `product-design:index`、`product-design:audit` | 审视、评价或设计一个已运行页面/流程时。 | 先拿到真实路由截图,再依据 A01/G01 与页面任务写审视结论;不要靠源码猜好不好看。 |
|
||||
| Product Design 的 `user-context`、`get-context`、`ideate`、`image-to-code`、Design QA 流程 | 需要探索方向、选择视觉方案、将已选样稿落地、或交付前比对时。 | 有明确视觉真源时先忠实落地;没有真源且需要新方向时先给可比较方案,选定后再实现;最后在 `design-qa.md` 诚实记录通过或阻塞。 |
|
||||
| `imagegen` | 现有资产无法承担完整视觉面时。 | 先用 A01/G01 实图作为参考,明确输出是完整不透明面板还是透明叠加件;检查实际尺寸/Alpha 后再放入项目。不能用截图代替资产。 |
|
||||
| `superpowers:brainstorming` 与 `superpowers:writing-plans` | 新模块或会改变页面行为的创作工作开始前。 | 先写清用户目标、交互和验收,不以“尽快”跳过设计决定。 |
|
||||
| `superpowers:test-driven-development` | 修改功能、路由契约、可审计视觉结构前。 | 先让最小契约测试失败,再做实现;视觉资产同样写尺寸/Alpha/引用审计。 |
|
||||
| `superpowers:systematic-debugging` | 截图、路由、构建或测试出现意外时。 | 先复现和定位单一根因,禁止靠删测试、盲改样式掩盖问题。 |
|
||||
| `superpowers:verification-before-completion` | 任何“完成/修好/通过”的表述前。 | 以命令输出、运行截图和用户确认作证据;不能用意图代替验证。 |
|
||||
|
||||
若新电脑没有 Product Design 或 ImageGen 插件,应先安装/启用同等能力;在能力缺失时不能假装完成视觉审计或资产生成。当前环境曾因 Windows 的 `python`/`python3` 只是商店占位符而无法保存 Product Design 的持久 context,这不影响当前项目文件和截图恢复;新环境若有可用 Python,可重新运行该插件的 context 预检。
|
||||
|
||||
## 8. 验证命令
|
||||
|
||||
先看工作区,再运行与改动相匹配的聚焦测试;模块阶段结束前运行全量 PowerShell 审计:
|
||||
|
||||
```powershell
|
||||
git status --short
|
||||
|
||||
Get-ChildItem tests -Filter *.ps1 | Sort-Object Name | ForEach-Object {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $_.FullName
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
git diff --check
|
||||
```
|
||||
|
||||
A02 当前的最小回归集合:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/a02-asset-alpha-audit.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/a01-a02-ui-contract.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/compile-audit.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/full-page-visual-contract.ps1
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`git diff --check` 可能提示仓库既有的 CRLF 行尾警告;只有真正的空白错误或非零退出码才算失败。不要 reset、checkout、清空或覆盖用户已有改动。
|
||||
|
||||
## 9. 文档与资源维护清单
|
||||
|
||||
每次完成一个页面/模块,按事实更新:
|
||||
|
||||
- `docs/规划.md`:用户验收状态与下一个模块。
|
||||
- `docs/design/P00_页面结构与资产清单.md`:新旧资产、尺寸、透明属性、引用位置与去留。
|
||||
- `docs/design/<页面>_设计记录.md`:设计意图、截图、交互与测试。
|
||||
- `docs/design/screens/runtime/<日期>/`:真实运行截图。
|
||||
- `design-qa.md`:修复项、未决项与最终 `passed/blocked` 结果。
|
||||
- `docs/交接记录.md` 与本手册:真实停点、风险、下一步;未获得用户确认就不能写成“已完成”。
|
||||
|
||||
当前工作区已经存在用户和历史任务的未提交改动。后续工作只能触及当前任务需要的文件;若改动与当前设计目标无关,只报告,不清理。
|
||||
@@ -0,0 +1,63 @@
|
||||
# A01 Complete Button Assets Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` or `superpowers:executing-plans` task by task. Steps use checkbox syntax for tracking.
|
||||
|
||||
**Goal:** Replace A01's temporary CSS-and-corner construction with two audited, complete opaque button images that match the approved A01 button proportion.
|
||||
|
||||
**Architecture:** `pages/auth/a01-entry.vue` remains the owner of button text, icons, agreement gating, and navigation. `a01-primary-button.png` and `a01-secondary-button.png` own each button's paper/vermillion surface, double border, and all four corner ornaments; they render as one non-interactive image beneath the DOM content.
|
||||
|
||||
**Tech Stack:** uni-app Vue 3, scoped SCSS, local opaque PNG assets, PowerShell/System.Drawing audits.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Use the approved `docs/design/screens/A01-启动登录引导-栅格验收-412x915.png` only as the visual reference; never crop it into a runtime asset.
|
||||
- Both final assets are opaque, complete button skins at a 4.492:1 native ratio; preserve DOM labels, icons, events, and agreement behaviour.
|
||||
- Do not use CSS, SVG, or four independently placed corner images to render the final visible border or ornament.
|
||||
- Work directly in the current checkout. Do not commit, push, or upload Git changes; the user will do that.
|
||||
- Change only A01 assets, its page, focused audits, and the affected asset ledger/record.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Define the complete-button contracts
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/a01-asset-alpha-audit.ps1`
|
||||
- Modify: `tests/a01-a02-ui-contract.ps1`
|
||||
|
||||
**Interfaces:** Produces assertions for `a01-primary-button.png`, `a01-secondary-button.png`, and a single `.entry-button__skin` image per A01 button.
|
||||
|
||||
- [x] Add a `$buttons` list containing the two final assets. For each, load the bitmap, require fully opaque four corners, require an aspect ratio within `0.01` of `4.492`, and output a pass message. Do not add these assets to the transparent-icon loop.
|
||||
- [x] Require `a01-primary-button.png`, `a01-secondary-button.png`, and `entry-button__skin`; reject `auth-button-corner.png`, `button-corner-asset`, `.entry-button::after`, and the button CSS `background`/`border` construction.
|
||||
- [x] Verify RED with `powershell -ExecutionPolicy Bypass -File tests\a01-asset-alpha-audit.ps1` and `powershell -ExecutionPolicy Bypass -File tests\a01-a02-ui-contract.ps1`. Both failed because the final assets and page structure did not exist.
|
||||
|
||||
### Task 2: Generate and audit complete opaque button skins
|
||||
|
||||
**Files:**
|
||||
- Create: `static/assets/foundation/opaque/a01-primary-button.png`
|
||||
- Create: `static/assets/foundation/opaque/a01-secondary-button.png`
|
||||
|
||||
**Interfaces:** Each asset is a complete opaque PNG skin at 4.492:1. Primary uses muted cinnabar with antique-gold double rules and compact symmetrical corner curls; secondary uses warm rice paper with antique-gold double rules and matching curls. Neither contains copy, icons, a screenshot crop, watermark, or transparency.
|
||||
|
||||
- [x] Generate one source for each skin from the approved A01 visual reference. The sources contain no Chinese text, icon, shadow, or external background in the final crop.
|
||||
- [x] Crop only the generated source's own button surface. The central paper/vermillion field and vertical side rules were extended without scaling the corner artwork; both skins are within `0.01` of `4.492` and contain no cropped A01 reference pixels.
|
||||
- [x] Verify asset GREEN with `powershell -ExecutionPolicy Bypass -File tests\a01-asset-alpha-audit.ps1`; all transparent-asset checks and both opaque-button checks pass.
|
||||
|
||||
### Task 3: Replace the A01 temporary construction and document ownership
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/auth/a01-entry.vue`
|
||||
- Modify: `docs/design/P00_页面结构与资产清单.md`
|
||||
- Modify: `docs/交接记录.md`
|
||||
|
||||
**Interfaces:** `openLogin`, `prepareWechatLogin`, `prepareRegister`, `prepareAgreement`, and `toggleAgreement` remain unchanged. Each `.entry-button` contains one `.entry-button__skin` image below `.entry-button__content`.
|
||||
|
||||
- [x] Delete the eight `button-corner-asset` images and their CSS. Add the primary/secondary `.entry-button__skin` image as the visual layer inside each button. Remove button borders, pseudo-element inner borders, and button CSS backgrounds; keep dimensions, text, icons, event handlers, and `entry-button__content` stacking unchanged.
|
||||
- [x] Replace the two unapproved candidate rows in P00 with the two final opaque assets, their actual dimensions, A01 ownership, page reference, and retained status. Update the handoff record to say that the candidates were replaced locally and that runtime screenshot regression remains pending.
|
||||
- [x] Verify page GREEN with `powershell -ExecutionPolicy Bypass -File tests\a01-a02-ui-contract.ps1`, `powershell -ExecutionPolicy Bypass -File tests\a01-asset-alpha-audit.ps1`, `powershell -ExecutionPolicy Bypass -File tests\a01-design-record-audit.ps1`, `powershell -ExecutionPolicy Bypass -File tests\compile-audit.ps1`, and `git diff --check`. Every command exits `0`.
|
||||
|
||||
### Task 4: Complete local verification
|
||||
|
||||
**Files:** None unless a focused audit exposes a defect.
|
||||
|
||||
- [x] Run `Get-ChildItem -LiteralPath tests -Filter *.ps1 | Sort-Object Name | ForEach-Object { & powershell -ExecutionPolicy Bypass -File $_.FullName; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } }` and `git diff --check`. All scripts pass and diff check exits `0`.
|
||||
- [ ] The user captures unchecked A01 screenshots at `412 × 915` and `320 × 568` in HBuilderX/Android. Do not mark visual acceptance complete until those screenshots confirm the button borders, text placement, and small-screen agreement row.
|
||||
@@ -0,0 +1,42 @@
|
||||
# 59 页面视觉全量交付执行计划
|
||||
|
||||
**Goal:** 在 2026-07-17 前完成 59 个规划页面/状态单元的统一移动端视觉与本地可浏览流程。
|
||||
|
||||
**Architecture:** 以 A01/G01 的宣纸、朱砂、古金、淡墨视觉为唯一系统。新增共享页面壳、六类精致母版与本地页面目录:页面目录只维护内容,母版拥有各自的视觉比例、卡片层级、空状态和操作区;现有页面保留已可用逻辑并接入对应母版。所有页面先覆盖正常/空/待接入可见状态,接口后置。
|
||||
|
||||
**Global Constraints:** 不改 A01/G01 的已验收可见结构;不接真实接口;只在当前工作目录改动;不执行 Git 提交、推送或上传;每页必须有编号语义路径和中文用途注释。
|
||||
|
||||
### Task 1: 建立全量路由红灯合同
|
||||
|
||||
- Create `tests/full-page-visual-contract.ps1`,固定 A01-A06、G01-G12、T01-T08、F01-F10、R01-R11、N01-N02、M01-M10 共 59 条最终路由;检查每条路由对应 Vue 文件、中文页面注释和页面壳引用。
|
||||
- 先运行该测试,预期因缺少 44 条路由/页面失败。
|
||||
|
||||
### Task 2: 建立共享页面壳与本地页面目录
|
||||
|
||||
- Create `components/ModulePage.vue`:负责纸纹、认证/业务页头、分区、信息卡、表单、列表、空状态、主按钮及安全区;按认证、列表详情、表单编辑、成员档案、内容流、个人消息六种精致母版渲染,禁止只替换标题的单一通用卡片。
|
||||
- Create `data/page-catalog.js`:唯一维护每个新增页面的标题、类型、说明、分组和本地操作反馈。
|
||||
- Create `components/ModulePage` 所需的最小本地样式,不引入依赖。
|
||||
|
||||
### Task 3: 补齐账户与家谱模块
|
||||
|
||||
- Create A03-A06、G02、G07-G09、G11-G12 的页面与路由。
|
||||
- Update A02、G03-G06、G10 的样式壳/跳转,保留现有功能逻辑。
|
||||
- 每页使用 `ModulePage` 与页面目录的唯一配置;本地操作只提示待接入或进入已设计页面。
|
||||
|
||||
### Task 4: 补齐世系与家族内容模块
|
||||
|
||||
- Create T02、T04-T08、F03-F10 的页面与路由。
|
||||
- Update T01、T03、F01、F02 的样式壳/跳转,保留当前可用逻辑。
|
||||
- 通过成员、关系、动态、谱文、相册、视频的列表/详情/表单三类母版覆盖全部状态。
|
||||
|
||||
### Task 5: 补齐人物、消息和个人中心模块
|
||||
|
||||
- Create R01-R11、N02、M02-M10 的页面与路由。
|
||||
- Update N01、M01 的样式壳/跳转。
|
||||
- 完成档案、礼仪、礼物、成长记录、消息、设置、帮助、VIP 等页面的本地展示与操作反馈。
|
||||
|
||||
### Task 6: 替换临时入口并完成全量回归
|
||||
|
||||
- Update `pages.json`、现有跳转路径、`docs/规划.md`、`docs/design/P00_页面结构与资产清单.md`,让 59 路由与页面目录一致;确认零引用后移除临时 `content-list-skeleton` 入口。
|
||||
- Run `full-page-visual-contract.ps1`、现有页面/编译审计、`git diff --check`。
|
||||
- 在 320×568、360×640、360×800、412×915 集中检查模块代表页;记录用户接下来逐页精修的起点。
|
||||
@@ -0,0 +1,365 @@
|
||||
# 家谱 App Product Design 视觉精修实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this plan task-by-task. Do not use parallel agents in this shared workspace.
|
||||
|
||||
**Goal:** 在不改变已验收 A01、G01 可见效果和既有业务交互的前提下,把其余 57 个页面从“可浏览的第一版”精修为同一套、可在 Android 小屏上成立的家祠卷轴界面。
|
||||
|
||||
**Architecture:** A01、G01 是唯一的视觉源,不把它们当作可以随意模仿的截图,而是提炼为“完整图片资产承担可见外观,Vue/CSS 只承担布局、文字和状态”的设计系统。每个模块先完成一张可截图验收的代表页,再将已确认的面板、按钮、列表或状态结构复用到同模块;不得用 `ModulePage.vue` 的单一通用卡片替代不同任务的页面设计。
|
||||
|
||||
**Tech Stack:** Vue 3、`<script setup>`、uni-app、SCSS、uni-app 原生 `image/input/textarea/button`、现有 PowerShell 合同审计、Product Design(`index`、`user-context`、`audit`、`image-to-code`)和 ImageGen(仅为缺失的最终图片资产生成)。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 用户已经验收 A01 启动/登录引导与 G01 我的家谱;这两页的可见结果冻结,不能“顺手优化”。
|
||||
- 不新增路由、后端接口、依赖、登录逻辑或 Android 打包范围;只做已存在页面的视觉和必要的页面内交互保持。
|
||||
- 可见的主按钮、次按钮、题签、卡片边框、分隔纹和云纹必须使用真实图片资产;禁止 CSS 色块、`border`、伪元素、SVG 或文字符号拼出最终装饰。
|
||||
- 只使用用户正在使用的 Chrome 进行 Product Design 截图;没有可用的浏览器捕获能力时,不得把静态代码检查说成视觉验收。
|
||||
- 所有新资产先独立检查透明属性、原图尺寸和显示比例;禁止拉伸 A01 两个约 4.49:1 的按钮皮肤。
|
||||
- 所有视觉页面在 H5 的 320×568、360×640、360×800、412×915 预览;模块收尾前在 HBuilderX/Android 至少复核 360×800、412×915。
|
||||
- 不执行 `git add`、`git commit`、`git push`、上传或覆盖未知改动;用户自行处理 Git。
|
||||
|
||||
---
|
||||
|
||||
## 已确认的视觉基准与当前判断
|
||||
|
||||
### A01 提炼出的规则
|
||||
|
||||
- 上方使用完整朱砂祠堂页头,品牌谱印居中;主内容从纸面中段开始,留白有节奏,不让背景压住任务。
|
||||
- 标题是画面主角:大号楷体墨褐字、左右小云纹、上下金线/如意结,副文案只承担解释。
|
||||
- 两个操作都是完整皮肤:朱砂主按钮、宣纸次按钮;按钮字体、图标和边角纹样都保持完整,不能缩成普通表单按钮。
|
||||
- 淡墨山水只压在背景层,底部成为收束,不能侵入输入内容或降低对比度。
|
||||
|
||||
### G01 提炼出的规则
|
||||
|
||||
- 首页先给“当前家谱”一张有分量的双线题签卡,再给功能快捷入口和列表;信息优先级非常清楚。
|
||||
- 金色线条不是装饰噪音:只用于题签边框、分区线、图标和关键元信息,朱砂只标记品牌、当前项与主要操作。
|
||||
- 图标、谱印框、题签框、分区线、底部山水和 Tab 均是成套资产,形成统一密度,不是若干孤立 CSS 卡片。
|
||||
- 正文保持可读,标题/姓名用楷体形成“谱”的气质,元信息用更小但仍清楚的常规字。
|
||||
|
||||
### A02 当前截图的失败点
|
||||
|
||||
- 登录卡片窄且短,文本、标签和输入线过小;用户首先看到的是背景,而不是“我要登录”。
|
||||
- A01 的祠堂头、A02 的普通表单卡和红色按钮像三套不同完成度的设计,标题云纹也没有形成真正的层级。
|
||||
- 红色主按钮虽然替换了皮肤,但当前容器比例不匹配原图,压扁后失去 A01 的端正感。
|
||||
- 这不是“再加几个装饰”能解决的问题;必须先重新确定 A02 的任务层级、卡片占比和资产比例。
|
||||
|
||||
### 本轮选定方向
|
||||
|
||||
**家祠卷轴 · 庄重留白。** 所有页面以温暖宣纸作为底,朱砂用于品牌和唯一主操作,古金用于题签与层级,墨褐用于信息。不同任务采用不同完整面板:认证是“卷轴登录页”,家谱是“题签/谱册”,世系是“关系图谱”,内容是“档案与相册”,记录是“簿册与时间线”,设置是“折页清单”。
|
||||
|
||||
## 文件职责
|
||||
|
||||
| 文件/目录 | 责任 |
|
||||
| --- | --- |
|
||||
| `docs/design/screens/` | 只存设计基准与运行时验收图,绝不作为页面运行时图片。 |
|
||||
| `docs/design/D1_安卓视觉规范与页面壳.md` | 视觉和资产硬规则的唯一规范;本计划实施后补充已确认的组件比例。 |
|
||||
| `docs/design/P00_页面结构与资产清单.md` | 运行时资产的唯一台账;必须消除其中过时的“按钮未生成”描述。 |
|
||||
| `static/assets/foundation/` | 所有模块共享的纸纹、页头、完整按钮、全局导航和透明装饰。 |
|
||||
| `static/assets/modules/<module>/` | 仅归属于认证、家谱、世系、家族、档案或个人模块的完整面板与透明装饰。 |
|
||||
| `components/PageHeader.vue`、`components/AppTabbar.vue`、`components/GenealogyCard.vue` | 已存在稳定公共结构;只有验收后仍被至少两页使用的视觉结构才加入公共组件。 |
|
||||
| `components/ModulePage.vue`、`data/page-catalog.js` | 第一版页面覆盖工具;逐模块被确认页面替代后,只保留尚未替换路由所需内容,不把它升级成全站视觉答案。 |
|
||||
| `tests/*.ps1` | 路由、资产、行为与可见资产锚点的回归;截图是独立的人工/Product Design 验收证据。 |
|
||||
| `docs/交接记录.md` | 下一个 GPT 的唯一施工停点:已验收页、资产路径、证据、未完成模块、命令和禁区。 |
|
||||
|
||||
## Task 1: 建立真实基线与 Product Design 设计审视
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/A01-360x800.png`
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/G01-360x800.png`
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/A02-before-360x800.png`
|
||||
- Create: `docs/design/2026-07-13-A01-G01-A02-视觉审视.md`
|
||||
- Modify: `docs/规划.md`
|
||||
- Modify: `docs/交接记录.md`
|
||||
|
||||
**Consumes:** 冻结图 `docs/design/screens/A01-启动登录引导-栅格验收-412x915.png`、`docs/design/screens/G01-我的家谱-紧凑版设计稿-v5-Tabbar安全区.png`;当前 A02 路由 `/#/pages/auth/a02-login?agreed=1`。
|
||||
|
||||
**Produces:** 可复核的同尺寸“基准/当前”截图和只针对 A02 的审视结论;没有截图不得进入 A02 实施。
|
||||
|
||||
- [ ] **Step 1: 运行 Product Design 上下文预检并读取现有视觉依据。**
|
||||
|
||||
使用 `Product Design:index` → `Product Design:user-context`。若本机 Python 或已保存的 Product Design context 不可用,在审视文档明确写“无持久上下文,依据项目内冻结图与本轮 Chrome 截图”,不要伪造已保存上下文。
|
||||
|
||||
- [ ] **Step 2: 用用户当前 Chrome 捕获并检查 360×800 的 A01、G01、A02。**
|
||||
|
||||
使用 `Product Design:audit` 的捕获顺序:观察已打开页 → 截图 → 保存 → 打开检查 → 记录。每张图必须没有 DevTools 覆盖页面主体、加载态、裁切或错误页。
|
||||
|
||||
- [ ] **Step 3: 写审视结论。**
|
||||
|
||||
`docs/design/2026-07-13-A01-G01-A02-视觉审视.md` 必须包含:视觉源、A01/G01 的 6 条可继承规则、A02 的至少 5 个可见问题、每项问题的解决策略、截图文件名、截图不能验证的限制。不得宣称无障碍完全合规。
|
||||
|
||||
- [ ] **Step 4: 纠正文档事实。**
|
||||
|
||||
在 `docs/规划.md` 和 `docs/交接记录.md` 中明确:只有 A01、G01 用户验收;`ModulePage` 是第一版覆盖而非最终视觉;A02 当前未验收;P00 中“A01 按钮尚未生成”的文字过期。
|
||||
|
||||
- [ ] **Step 5: 检查。**
|
||||
|
||||
运行:`powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/full-page-visual-contract.ps1`。
|
||||
|
||||
预期:`FULL-PAGE-VISUAL-CONTRACT PASS`。再运行 `git diff --check`,预期仅允许换行符警告,不允许空白错误。
|
||||
|
||||
## Task 2: 固化 A02 视觉合同与所需资产,而不是继续堆 CSS
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/design/A02_账号登录_设计记录.md`
|
||||
- Create: `static/assets/modules/auth/opaque/a02-login-panel.png`
|
||||
- Create only if A01 皮肤无法按原比例容纳登录区: `static/assets/modules/auth/opaque/a02-primary-button.png`
|
||||
- Create only if A01 次按钮无法按原比例容纳辅助操作: `static/assets/modules/auth/opaque/a02-secondary-button.png`
|
||||
- Create: `tests/a02-asset-alpha-audit.ps1`
|
||||
- Modify: `docs/design/P00_页面结构与资产清单.md`
|
||||
|
||||
**Consumes:** Task 1 的已接受截图、A01 标题/云纹/祠堂/按钮资产和 D1 规则。
|
||||
|
||||
**Produces:** A02 的单一资产合同:完整登录面板、正确比例的完整按钮以及每个资产的路径、原图尺寸、显示比例、透明属性和引用位置。
|
||||
|
||||
- [ ] **Step 1: 写 A02 设计记录,不写代码。**
|
||||
|
||||
记录必须把 A02 固定为:同 A01 高度的祠堂头;头下为可见主任务标题;完整卷轴/题签面板承载两个 Tab、输入区和辅助操作;底部山水只在表单后留白出现;主按钮按原图比例显示且高度不低于 88rpx。记录 A02 不新增微信、真实短信或后端登录。
|
||||
|
||||
- [ ] **Step 2: 为缺失的可见面板生成真实图片资产。**
|
||||
|
||||
使用 `Product Design:image-to-code` 所依据的 A01/G01 视觉源和 `imagegen`。面板必须是完整不透明 PNG,包含自身纸面纹理、双金线、角纹;不要生成整页图、截图切片、文字或不可替换的表单字段。透明云纹、图标仍使用已有 PNG。
|
||||
|
||||
- [ ] **Step 3: 先写并运行红灯资产审计。**
|
||||
|
||||
`tests/a02-asset-alpha-audit.ps1` 至少验证:资产存在;完整面板四角不是透明;若生成 A02 按钮,四角也不是透明;原图宽高比与页面显示宽高比误差不超过 3%;透明资产仍保留 alpha 像素。先运行脚本,预期失败原因只能是“资产尚不存在”。
|
||||
|
||||
- [ ] **Step 4: 生成并检查资产后转绿。**
|
||||
|
||||
运行:`powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/a02-asset-alpha-audit.ps1`。
|
||||
|
||||
预期:`A02-ASSET-ALPHA-AUDIT PASS`。然后将所有最终路径、用途、尺寸写入 P00,候选图不进入 `static/assets/`。
|
||||
|
||||
## Task 3: 将 A02 做成认证模块的已验收样板
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/auth/a02-login.vue`
|
||||
- Modify: `tests/a01-a02-ui-contract.ps1`
|
||||
- Modify: `docs/design/A02_账号登录_设计记录.md`
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/A02-after-320x568.png`
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/A02-after-360x640.png`
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/A02-after-360x800.png`
|
||||
- Create: `docs/design/screens/runtime/2026-07-13/A02-after-412x915.png`
|
||||
|
||||
**Consumes:** Task 2 的资产合同。保留现有双 Tab、协议回跳判断、忘记密码跳 A05、字段校验与 toast。
|
||||
|
||||
**Produces:** 唯一可用于 A03–A05 的认证表单样板;页面不是 `ModulePage` 的变体。
|
||||
|
||||
- [ ] **Step 1: 先写红灯页面合同。**
|
||||
|
||||
在 `tests/a01-a02-ui-contract.ps1` 增加 A02 锚点:完整面板图片、正确比例按钮图片、`login-panel` 的可访问文字层、两个真实 input、两个真实 Tab、A05 跳转。拒绝 A02 主面板/主按钮用 `border`、`background: $brand-red`、伪元素拼出最终外观。运行该脚本,预期因新资产或新锚点缺失失败。
|
||||
|
||||
- [ ] **Step 2: 最小实现。**
|
||||
|
||||
A02 模板只保留“背景 → 同 A01 祠堂页头 → 页面标题 → 完整面板 → 表单 → 主按钮 → 底部背景”的层级。图片使用绝对底层,输入、Tab、标题和按钮文字使用相对文字层;不要把任何文字烘焙进图片。表单面板宽度跟随安全边距,按钮按资产比例设置高度,不能 `scaleToFill` 压扁。
|
||||
|
||||
- [ ] **Step 3: 检查逻辑和四尺寸。**
|
||||
|
||||
运行:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/a01-a02-ui-contract.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/a02-asset-alpha-audit.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/compile-audit.ps1
|
||||
```
|
||||
|
||||
预期:三个脚本均 PASS。再用 `Product Design:audit` 检查四尺寸截图:320 宽不横向裁切;360×640 能滚动且主按钮可达;360×800 卡片仍为主角;412×915 底部山水不侵入输入区。
|
||||
|
||||
- [ ] **Step 4: 用户视觉门。**
|
||||
|
||||
只在用户确认 A02 后,将 `docs/规划.md` 状态更新为“认证样板已验收”。用户未确认时,记录为“实现完成,视觉待验收”,不得假称完成。
|
||||
|
||||
## Task 4: 完成认证模块 A03–A06
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/auth/a03-mobile-verify.vue`
|
||||
- Modify: `pages/auth/a04-register.vue`
|
||||
- Modify: `pages/auth/a05-reset-password.vue`
|
||||
- Modify: `pages/auth/a06-auth-status.vue`
|
||||
- Create only after A02 布局被两页复用: `components/auth/AuthFormPanel.vue`
|
||||
- Modify: `data/page-catalog.js` and `components/ModulePage.vue` only when the corresponding A03–A06 entry is removed from the temporary motherplate
|
||||
- Modify: `tests/full-page-visual-contract.ps1`
|
||||
- Modify: `docs/design/P00_页面结构与资产清单.md`
|
||||
|
||||
**Consumes:** 已验收的 A02 面板、按钮和文字层级。
|
||||
|
||||
**Produces:** 认证六页同一入口体验:登录、验证码、注册、重设和结果状态各有对应任务,不能仍显示泛化的“功能完善中”。
|
||||
|
||||
- [ ] **Step 1: 分别写四条红灯合同。**
|
||||
|
||||
A03 必须有手机号与验证码;A04 有手机号、密码、确认密码和协议提示;A05 有手机号、验证码、新密码;A06 有状态图标、说明和明确返回登录操作。合同只验证存在的交互和资产锚点,不宣称后端已接入。
|
||||
|
||||
- [ ] **Step 2: 用 A02 的已确认结构分别实现。**
|
||||
|
||||
只在两页以上完全相同的面板结构出现时创建 `AuthFormPanel.vue`;否则页面内实现。每页使用同一头部、卡片比例、输入高度、按钮比例和淡墨留白,但标题、辅助文案、状态图和行动文案按任务不同。
|
||||
|
||||
- [ ] **Step 3: 模块截图与回归。**
|
||||
|
||||
对 A03、A04、A05、A06 至少各保存 360×800 一张截图;运行认证合同、`compile-audit.ps1`、`git diff --check`。A01 不改动且至少复核 412×915。
|
||||
|
||||
## Task 5: 完成家谱模块 G02–G12
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/genealogy/g02-empty-genealogies.vue`, `g03-create-genealogy.vue`, `g04-first-ancestor.vue`, `g05-genealogy-overview.vue`, `g06-search-genealogies.vue`, `g07-search-result.vue`, `g08-join-application.vue`, `g09-my-applications.vue`, `g10-application-review.vue`, `g11-genealogy-settings.vue`, `g12-generation-poems.vue`
|
||||
- Create as needed after Product Design review: `static/assets/modules/genealogy/opaque/*.png`, `static/assets/modules/genealogy/transparent/*.png`
|
||||
- Modify: `components/GenealogyCard.vue`, `components/ModulePage.vue`, `data/page-catalog.js` only when a confirmed G page replaces the temporary motherplate route
|
||||
- Modify: `tests/g01-visual-contract.ps1`, `tests/core-flow-contract.ps1`, `tests/full-page-visual-contract.ps1`
|
||||
- Modify: `docs/design/P00_页面结构与资产清单.md`
|
||||
|
||||
**Consumes:** G01 题签框、谱印、快捷图标、分区线、底部山水和 Tab;G01 页面本身不改。
|
||||
|
||||
**Produces:** G03(创建家谱)作为表单样板、G07(搜索结果)作为列表样板、G09(申请)作为时间线样板、G12(字辈诗)作为详情样板;其他 G 页只能复用已确认样板,不再保留通用 `ModulePage` 视觉。
|
||||
|
||||
- [ ] **Step 1: Product Design 审视 G03、G07、G09、G12 的任务和截图。**
|
||||
|
||||
明确每页首要动作分别为创建、选择、查看进度、阅读/维护;每页只用一个主操作。生成缺失的完整题签/表单/时间线面板资产前,先把显示槽尺寸写入 P00。
|
||||
|
||||
- [ ] **Step 2: 逐样板写红灯合同、实现、截图。**
|
||||
|
||||
每一类样板先有一个页面通过合同和 360×800、412×915 截图,再应用到同类页。按钮统一使用正确比例的完整朱砂或宣纸皮肤;列表和审核卡使用完整题签图,不得继续用圆角 CSS 卡片。
|
||||
|
||||
- [ ] **Step 3: 保持真实跳转。**
|
||||
|
||||
G01→G03/G05/G06/G10/G12、G05→G08/G11/G12 的现有跳转不得回退。修改每条路径时,同轮更新合同;无效旧路径不能保留。
|
||||
|
||||
- [ ] **Step 4: 回归。**
|
||||
|
||||
运行 `tests/g01-visual-contract.ps1`、`tests/core-flow-contract.ps1`、`tests/compile-audit.ps1`、`tests/full-page-visual-contract.ps1`。G01 至少复核 412×915 以证明未被公共组件改坏。
|
||||
|
||||
## Task 6: 完成世系树与成员模块 T01–T08
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/tree/t01-tree-overview.vue` through `pages/tree/t08-member-states.vue`
|
||||
- Create as needed: `static/assets/modules/tree/opaque/*.png`, `static/assets/modules/tree/transparent/*.png`
|
||||
- Modify: `components/ModulePage.vue`, `data/page-catalog.js`, `tests/core-flow-contract.ps1`, `tests/full-page-visual-contract.ps1`, `docs/design/P00_页面结构与资产清单.md`
|
||||
|
||||
**Consumes:** 已验收 G 模块的表单、题签、列表资产比例;世系图自身需要独立的关系节点和小屏状态设计。
|
||||
|
||||
**Produces:** T01 为横向世系主样板,T03 为人物档案样板,T04 为亲属表单样板,T02/T08 为明确状态样板。
|
||||
|
||||
- [ ] **Step 1: 先完成 T01 与 T03 的 Product Design 截图审视。**
|
||||
|
||||
T01 必须在小屏给出清楚的“横向查看/进入成员目录”出口,不能硬塞一张缩小关系图;T03 先突出人物、世代和关系,再给资料和操作。
|
||||
|
||||
- [ ] **Step 2: 建立真实节点、人物题签和状态资产。**
|
||||
|
||||
关系节点、状态徽记、人物题签必须使用图片资产;信息文本保留在 Vue 中。没有至少两个复用点不要抽取组件。
|
||||
|
||||
- [ ] **Step 3: 逐页替换临时母版并测试。**
|
||||
|
||||
每页保留已有编辑/查看跳转;运行 T 模块合同、编译检查和 360×800、412×915 截图验收。
|
||||
|
||||
## Task 7: 完成家族内容模块 F01–F10
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/family/f01-family-feed.vue` through `pages/family/f10-video-list.vue`
|
||||
- Create as needed: `static/assets/modules/family/opaque/*.png`, `static/assets/modules/family/transparent/*.png`
|
||||
- Modify: `components/ModulePage.vue`, `data/page-catalog.js`, `tests/core-flow-contract.ps1`, `tests/full-page-visual-contract.ps1`, `docs/design/P00_页面结构与资产清单.md`
|
||||
|
||||
**Consumes:** G01 底部导航;F01 已有动态流和现有跳转。
|
||||
|
||||
**Produces:** F01 为家族动态样板,F04 为文章档案列表,F07 为相册封面列表,F08 为照片墙,F02/F06/F09 为发布/编辑表单,F10 为未开放状态。
|
||||
|
||||
- [ ] **Step 1: 用 Product Design 先决定“内容不是家谱列表”。**
|
||||
|
||||
动态、文章、相册和视频保持纸本档案感,但各自有内容缩略、时间、作者和操作层级;不要复刻 G01 的家谱题签。
|
||||
|
||||
- [ ] **Step 2: 只生成能反复使用的内容资产。**
|
||||
|
||||
例如文章档案题签、相册封面框、媒体上传框、评论分隔纹;照片和视频先使用现实感本地 mock,不把纯色方块当最终封面。
|
||||
|
||||
- [ ] **Step 3: 保持 F01 的原有入口。**
|
||||
|
||||
F01→F04/F07/R05/R10 的现有导航继续可用;每个进入详情、编辑或上传的路径都要有可返回操作。
|
||||
|
||||
- [ ] **Step 4: 截图和回归。**
|
||||
|
||||
F01、F04、F07、F09 在四尺寸预览;其余页面至少 360×800。运行受影响流程合同、编译检查和空白检查。
|
||||
|
||||
## Task 8: 完成 R、N、M 模块
|
||||
|
||||
**Files:**
|
||||
- Modify: `pages/records/r01-people-list.vue` through `pages/records/r11-merit-records.vue`
|
||||
- Modify: `pages/notification/n01-message-center.vue`, `pages/notification/n02-message-detail.vue`
|
||||
- Modify: `pages/profile/m01-profile-home.vue` through `pages/profile/m10-about-settings.vue`
|
||||
- Create as needed: `static/assets/modules/records/*`, `static/assets/modules/notification/*`, `static/assets/modules/profile/*`
|
||||
- Modify: `components/ModulePage.vue`, `data/page-catalog.js`, `tests/core-flow-contract.ps1`, `tests/full-page-visual-contract.ps1`, `docs/design/P00_页面结构与资产清单.md`
|
||||
|
||||
**Consumes:** 已确认的列表、详情、表单、时间线和设置样板。
|
||||
|
||||
**Produces:** R 采用簿册/时间线,N 采用清楚的通知层级,M 采用个人身份与设置折页;每个模块都有自己的图标和面板密度,不能留在相同母版外观。
|
||||
|
||||
- [ ] **Step 1: R 先做人物录、礼仪活动、成长日志三类代表页。**
|
||||
|
||||
代表页分别确定人物档案、活动簿册和生命时间线资产与布局后,再完成其余 R 页。
|
||||
|
||||
- [ ] **Step 2: N 先做消息中心和详情的已读层级。**
|
||||
|
||||
未读、已读、时间和空状态必须在不靠颜色的情况下可区分;点击后的 N02 跳转保持真实。
|
||||
|
||||
- [ ] **Step 3: M 先做 M01 个人首页和 M03 安全设置。**
|
||||
|
||||
M01 是身份入口,不是普通列表;M03–M10 使用已确认的设置行/表单,而不是单一通用卡。
|
||||
|
||||
- [ ] **Step 4: 每完成一个模块立即替换对应临时母版入口。**
|
||||
|
||||
删除本轮不再使用的 `pageCatalog` 条目、导入和临时样式;运行全部受影响路由与合同,不能留下“新旧两套页面都可走”的兼容入口。
|
||||
|
||||
## Task 9: 全量视觉回归与真实交接
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/规划.md`
|
||||
- Modify: `docs/交接记录.md`
|
||||
- Modify: `docs/design/D1_安卓视觉规范与页面壳.md`
|
||||
- Modify: `docs/design/P00_页面结构与资产清单.md`
|
||||
- Create: `docs/design/视觉设计交接手册.md`
|
||||
- Modify: all affected `tests/*.ps1`
|
||||
|
||||
**Consumes:** 每个模块的已验收截图、资产台账、页面合同和实际执行结果。
|
||||
|
||||
**Produces:** 下一位 GPT 无需猜测即可继续的交接包;文档只写已经发生的事实,未用户验收的页面明确标注为待验收。
|
||||
|
||||
- [ ] **Step 1: 跑全量可自动验证项。**
|
||||
|
||||
```powershell
|
||||
Get-ChildItem tests -Filter *.ps1 | Sort-Object Name | ForEach-Object {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $_.FullName
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
git diff --check
|
||||
```
|
||||
|
||||
预期:每个脚本 PASS;`git diff --check` 没有空白错误。若任何一项失败,按 `superpowers:systematic-debugging` 先复现、定位、修复并重跑,不能用改测试掩盖问题。
|
||||
|
||||
- [ ] **Step 2: 完成四尺寸视觉门。**
|
||||
|
||||
每个路由至少在 360×800 留一张验收截图;每个模块代表页还须有 320×568、360×640、360×800、412×915。将每张图的页面、尺寸、状态、通过/问题写入交接手册。
|
||||
|
||||
- [ ] **Step 3: 写交接手册。**
|
||||
|
||||
`docs/design/视觉设计交接手册.md` 必须包含:
|
||||
|
||||
- 两张冻结视觉源和其禁止改动的可见元素;
|
||||
- 每个模块的代表页、状态、截图路径与未完成项;
|
||||
- 每项最终资产的路径、`opaque/transparent` 属性、原图尺寸、显示槽和引用页面;
|
||||
- 哪些临时 `ModulePage`/`pageCatalog` 条目已经移除,哪些仍待替换;
|
||||
- 路由总数、关键跳转、可运行的检查命令;
|
||||
- 下一位 GPT 的能力清单:先读 `AGENTS.md`、D1、P00、交接;再调用 `Product Design:index`→`user-context`→`audit`,有选择的视觉源时调用 `image-to-code`,缺真实图片资产时调用 `imagegen`,每次改行为前用 TDD,完成前做视觉截图和自动回归;
|
||||
- 禁区:不动 A01/G01、不可把截图当页面资产、不可用 CSS 伪造完整装饰、不可提交/上传、不可把未验收写成已完成。
|
||||
|
||||
- [ ] **Step 4: 用户最终验收。**
|
||||
|
||||
按模块而不是一次塞 59 张截图给用户;只有用户确认的模块才在 `docs/规划.md` 勾选完成。没有用户回复时,状态保持“实现/截图完成,视觉待用户验收”。
|
||||
|
||||
## 执行顺序与停点
|
||||
|
||||
1. 先执行 Task 1–3,只做 A02,直到用户确认它是认证样板。
|
||||
2. 再执行 Task 4,完成 A 模块后更新一次交接记录。
|
||||
3. 按 Task 5 → 6 → 7 → 8 逐模块推进;任何模块出现不合格截图,先在该模块修复,不跳到下一个模块。
|
||||
4. Task 9 最后执行,不能提前把 57 页写成“验收完成”。
|
||||
|
||||
## 实施前自检
|
||||
|
||||
- 计划覆盖 59 个路由:A、G、T、F、R、N、M 均有明确代表页和替换路径。
|
||||
- 计划把 A01、G01 作为唯一视觉源,禁止全局母版继续掩盖页面差异。
|
||||
- 计划把 Product Design 截图审视、真实图片资产、自动合同、四尺寸截图和用户确认串成顺序,避免“代码 PASS 但页面不好看”。
|
||||
- 本计划不包含 Git 提交步骤,因为用户明确自行上传 Git。
|
||||
@@ -0,0 +1,61 @@
|
||||
# 59 页面视觉全量交付设计
|
||||
|
||||
> 状态:用户已于 2026-07-13 确认“先全部完成、再集中修改”。
|
||||
> 交付窗口:2026-07-13 至 2026-07-17;本文件是本窗口内页面视觉范围与完成标准的唯一依据。
|
||||
|
||||
## 目标
|
||||
|
||||
在 7 月 17 日前,让 `docs/规划.md` 所列 59 个页面/状态单元全部拥有已注册路由、统一的 Android 移动端视觉样式、可浏览的本地内容和主要操作反馈;后续再根据集中截图逐页精修。
|
||||
|
||||
## 明确边界
|
||||
|
||||
- 交付的是前端视觉与本地交互:不接真实登录、短信、微信、上传、支付、接口或 Android 打包。
|
||||
- A01 和 G01 是已验收的视觉基准,本窗口不改变其可见结构。
|
||||
- 当前 15 个页面保留已有可用流程并统一视觉;另建 44 个规划缺失页面与路由。
|
||||
- 页面中的数据为本地示例数据;没有后端数据时仍必须呈现正常、空或待接入状态中的一种。
|
||||
- 不把整页设计图或截图裁切后当页面;可见装饰继续复用项目内的纸纹、朱砂页头、山水、谱印、题签、云纹和图标资产。
|
||||
|
||||
## 单一视觉系统
|
||||
|
||||
所有新页面遵循已验收的 A01/G01 语言:暖宣纸底、朱砂主操作、古金边框与分隔、墨褐主文字、淡墨山水底部留白。母版不是低保真的同一张卡片复制:每一套都有独立的页头、内容节奏、信息卡比例、空状态和操作区;页面再按自身内容组合它们。页面只允许以下六类母版,不为每个页面另起画风:
|
||||
|
||||
| 母版 | 适用页面 | 共同结构 |
|
||||
| --- | --- | --- |
|
||||
| 认证页 | A02-A06 | 认证页头、宣纸背景、居中表单/状态卡、主按钮 |
|
||||
| 家谱列表与详情 | G02-G12 | 朱砂页头、题签、信息卡、分区标题、底部导航 |
|
||||
| 表单与编辑 | G03-G04、T04-T06、F02/F06/F09、R04/R07、M02-M05/M07 | 返回页头、宣纸表单卡、分组字段、固定主操作 |
|
||||
| 成员与档案 | T01-T08、R01-R02、R08-R11 | 人物题签、关系/时间内容卡、筛选或操作条 |
|
||||
| 家族内容 | F01-F10 | 内容头图/分类、动态卡或图文列表、发布入口 |
|
||||
| 个人与消息 | N01-N02、M01-M10 | 个人信息题签、设置列表、消息/状态卡 |
|
||||
|
||||
精致完成的最低要求是:每个母版有明确的主视觉层级、两种以上内容卡层级、专属空状态/操作状态和与内容匹配的留白;同一模块中的列表、详情、编辑页面在版式上可识别为同一产品,但不能仅替换标题和文案。
|
||||
|
||||
仅在至少两个页面复用时新增公共组件;组件只负责页面壳、顶部栏、题签/分区标题、纸纹卡片、表单行和空状态,页面自己的业务文案与列表仍留在页面内。
|
||||
|
||||
## 页面覆盖范围
|
||||
|
||||
按规划的七个模块交付:账户 A01-A06(6)、家谱 G01-G12(12)、世系 T01-T08(8)、家族 F01-F10(10)、人物礼仪档案 R01-R11(11)、消息 N01-N02(2)、个人中心 M01-M10(10),合计 59 个单元。组合描述的单元(例如 R01-R02)按规划拆为独立路由并共用同一母版。
|
||||
|
||||
## 执行节奏
|
||||
|
||||
| 时间 | 可见交付 |
|
||||
| --- | --- |
|
||||
| 7 月 13 日 | 建立页面壳/母版与完整 59 路由清单;完成账户、家谱模块样式与已存在页面的统一接入。 |
|
||||
| 7 月 14 日 | 完成世系、家族内容模块样式与页面。 |
|
||||
| 7 月 15 日 | 完成人物礼仪档案、消息、个人中心模块样式与页面。 |
|
||||
| 7 月 16 日 | 补全每页的空/待接入可见状态,统一导航、间距、字体、主按钮与安全区。 |
|
||||
| 7 月 17 日 | 集中四尺寸检查、修正可见溢出/遮挡/断链,并交由用户开始逐页精修。 |
|
||||
|
||||
## 页面完成标准
|
||||
|
||||
一个页面/状态单元只有同时满足以下条件才算本轮完成:
|
||||
|
||||
1. 在 `pages.json` 有编号语义化路由,页面文件存在且带中文用途注释。
|
||||
2. 进入后有完整视觉层级,不出现浏览器默认样式、裸白背景、无主题按钮或无内容占位。
|
||||
3. 主操作、返回或底部导航有本地反馈;未接功能明确显示“功能待接入”,不伪造成功。
|
||||
4. 在 320×568、360×640、360×800、412×915 下允许纵向滚动且不被安全区/固定操作遮挡。
|
||||
5. 新增路由、跳转、组件和本轮资产通过编译、路由检查和 `git diff --check`。
|
||||
|
||||
## 验收方式
|
||||
|
||||
本窗口采用“模块完成即自检、全量完成后集中截图”的方式;不在每个页面之间等待人工截图。用户在 7 月 17 日后按页面提出视觉修改,修改只影响被指出的页面或已确认的公共母版。
|
||||
@@ -1,110 +1,25 @@
|
||||
# 家谱 APP 交接记录
|
||||
|
||||
> 更新时间:2026-07-13
|
||||
> 用途:更换电脑或更换 GPT 后,从本文件恢复项目上下文。
|
||||
> 当前阶段:P-00 地基重整收尾,**不要跳到 A02**。
|
||||
> 更新时间:2026-07-14
|
||||
> 完整恢复手册见:[视觉设计交接手册](design/视觉设计交接手册.md)。本文件只保留当前真实停点,避免与历史草案互相矛盾。
|
||||
|
||||
## 1. 新接手者先读什么
|
||||
## 当前状态
|
||||
|
||||
1. 根目录 `AGENTS.md`:最小修改、先验证、不得覆盖未知改动。
|
||||
2. [总规划](规划.md):59 个页面的范围、当前状态和推进顺序。
|
||||
3. [D1 安卓视觉地基规范](design/D1_安卓视觉规范与页面壳.md):视觉、资产和注释硬规则。
|
||||
4. [P00 页面结构与资产清单](design/P00_页面结构与资产清单.md):页面重命名映射、资产去留和清理进度。
|
||||
5. A01、G01 设计记录与下列冻结锚点:
|
||||
- `docs/design/screens/A01-启动登录引导-栅格验收-412x915.png`
|
||||
- `docs/design/screens/G01-我的家谱-紧凑版设计稿-v5-Tabbar安全区.png`
|
||||
- `pages.json` 已登记 59 条最终编号路由,并由 `tests/full-page-visual-contract.ps1` 约束。
|
||||
- 用户已经验收并冻结可见效果的页面:A01 启动/登录引导、G01 我的家谱。
|
||||
- A02 账号登录已完成资产化重做、运行截图与自动回归;尚待用户审美确认,**不能标记为完成,也不能向 A03--A06 传播**。
|
||||
- 其余页面可浏览,但包含 `ModulePage.vue`/`page-catalog.js` 的临时第一版壳,不能当作最终视觉设计。
|
||||
- 不提交、不推送、不上传 Git;由用户自行处理。
|
||||
|
||||
## 2. 当前事实
|
||||
## 新电脑的第一步
|
||||
|
||||
- 项目:Android `uni-app`,使用 HBuilderX;不做 iOS、H5、PC、后台或官网。
|
||||
- 全量规划是 59 个页面/状态单元;`pages.json` 当前只有 15 个已注册路由。
|
||||
- 用户已通过视觉验收的只有两个页面:A01 启动/登录引导、G01 我的家谱。
|
||||
- A01、G01 是冻结视觉基准:可以抽取和整理内部实现,但不得改变用户已验收的可见效果。
|
||||
- 其余页面现有代码只算功能骨架或本地 UI,不能标记为视觉完成。
|
||||
- 已完成:15 个路由的语义化文件/跳转迁移、35 项在用资产迁入统一目录、24 项运行时零引用旧资产删除、公共页面/组件中文用途注释。
|
||||
- A01 主/次完整按钮候选已生成并保存到项目,但比例尚不符合现有设计,尚未接入页面;当前第一优先级仍是 P-00 收尾:复核/重做候选,验收后替换旧角标拼接,再做 A01、G01 截图回归;不是继续 A02,也不是接接口。
|
||||
1. 阅读 `AGENTS.md`、[总规划](规划.md)、[D1 视觉规范](design/D1_安卓视觉规范与页面壳.md)、[P00 资产台账](design/P00_页面结构与资产清单.md) 与[完整交接手册](design/视觉设计交接手册.md)。
|
||||
2. 保留工作区改动,运行 `git status --short`;不执行 reset、checkout 或覆盖用户文件。
|
||||
3. 先让用户查看 A02 的 `A02-before-after-360x800.png`、`A02-after-412x915.png` 和 `A02-sms-360x800.png`。用户未确认前,只能继续调整 A02。
|
||||
4. 需要自行截图时,用 `scripts/capture-chrome-page.js` 连接专用 Chrome 的调试端口 `9222`;完整启动命令和截图方式见交接手册第 5 节。
|
||||
|
||||
## 3. 用户明确确认的规则
|
||||
## 已知验证与限制
|
||||
|
||||
- 以 A01、G01 已完成样式为项目地基;不能推翻或另起一套画风。
|
||||
- 资产先行:先形成一套可复用、已验收的资产包,再按资产包设计后续页面。
|
||||
- 所有可见装饰使用图片资产;不使用 CSS/SVG 绘制祥云、边框、分隔纹、按钮边角等可见装饰。
|
||||
- 不透明图片承担自身底色、纹理、完整按钮/卡片/页头背景;透明 PNG 只承担 Logo、图标、祥云、纹样、边框等叠加元素。
|
||||
- 禁止把整页设计图或局部截图裁切后拼进页面;禁止按钮由角标、边线和 CSS 底色拼接。
|
||||
- 页面、路由、资产都必须用语义名和页面编号;旧文件、重复版本和未引用资产确认后删除。
|
||||
- 关键 JS、Vue 结构、资产图层与关键样式必须有中文注释;注释说明用途和约束,不堆砌无意义逐行注释。
|
||||
- JS 和页面代码只做已确认的需求:不发散、不预埋猜测功能、不新增无关依赖、不保留无用代码。
|
||||
|
||||
## 4. P-00 应按什么顺序执行
|
||||
|
||||
1. 运行 `git status --short`,保留未知改动;不得使用 `git reset --hard` 或覆盖用户文件。
|
||||
2. 依据 P00 文档核对 15 个当前路由的页面编号和目标语义名。
|
||||
3. 扫描所有 `/static/assets/` 引用,填写每项资产的用途、透明属性、引用位置与去留状态。
|
||||
4. 建立新的 `foundation/opaque`、`foundation/transparent`、`modules/<module>` 资产结构和资产清单。
|
||||
5. 同轮更新 `pages.json`、跳转路径、测试和页面文件名;确认无引用后才删除旧路径和旧资产。
|
||||
6. 为 A01 生成并单独验收两张完整不透明按钮图片,替换现有 CSS 底色加四角 PNG 的过渡实现;不得裁切设计图,也不得改变当前画风。
|
||||
7. 运行最小相关审计、编译检查、A01/G01 四尺寸截图核对和 `git diff --check`;视觉是否通过以用户换电脑后的设计图复核为准。
|
||||
|
||||
## 5. 当前停点(下次从这里继续)
|
||||
|
||||
本次已经完成且通过回归:
|
||||
|
||||
- 15 个已注册路由改为“页面编号-语义名”文件和 URL;旧入口已移除。
|
||||
- 35 项运行时资产进入 `static/assets/foundation` 或 `static/assets/modules/genealogy`,并按 `opaque`/`transparent` 分类。
|
||||
- 24 项经运行时引用扫描确认无引用的旧副本已删除。
|
||||
- 页面和 3 个公共组件均补上中文用途/图层注释。
|
||||
- 已通过 `foundation-structure-audit`、`foundation-asset-audit`、A01/A02、G01、核心流程、编译、清单、SCSS、Vue 入口以及 `git diff --check` 回归。
|
||||
|
||||
当前没有改动 A01、G01 的可见布局。A01 仍使用旧的“CSS 底色 + 四角 PNG”按钮过渡实现,因此不能称为地基最终验收完成。
|
||||
|
||||
为避免下次电脑丢失进度,已把两张未接入候选放进项目:
|
||||
|
||||
| 文件 | 来源与状态 | 下次处理 |
|
||||
| --- | --- | --- |
|
||||
| `static/assets/foundation/opaque/a01-primary-button-candidate.png` | 图像生成工具生成,1983×793,完整朱砂按钮皮肤;未接入 | 比例约 2.5:1,与现有按钮约 3.6:1 不一致;不能裁切设计图或硬拉伸,需按设计图重新生成/确认。 |
|
||||
| `static/assets/foundation/opaque/a01-secondary-button-candidate.png` | 图像生成工具生成,1983×793,完整宣纸按钮皮肤;未接入 | 与主按钮同样先重做或确认比例、角纹、金边,再进入页面。 |
|
||||
|
||||
下次严格按此顺序继续:
|
||||
|
||||
1. 打开 A01 设计图和当前候选,先确认按钮的目标比例、边框粗细、四角纹样和红/宣纸颜色;候选不合格就重新生成,不接入。
|
||||
2. 两张完整不透明按钮资产分别通过图片尺寸、完整四边框、无截图裁切和设计图人工比对后,才替换 A01 的 CSS 背景、边框及 `auth-button-corner.png` 四角节点。
|
||||
3. 更新 A01 资产台账和最小测试;运行页面契约、资产审计、编译检查。
|
||||
4. 在 320 × 568、360 × 640、360 × 800、412 × 915 截图;A01、G01 都须与设计图再次并排验收。用户可撤销此前“通过”,以新的设计图复核为准。
|
||||
5. 用户确认 P-00 后,才开始 A02;后续每个模块先定完整资产包和母版,再批量实现页面,禁止边做边换画风。
|
||||
|
||||
## 6. 现有路由概况
|
||||
|
||||
当前路由为:
|
||||
|
||||
```text
|
||||
pages/auth/a01-entry
|
||||
pages/auth/a02-login
|
||||
pages/genealogy/g01-my-genealogies
|
||||
pages/genealogy/g03-create-genealogy
|
||||
pages/genealogy/g05-genealogy-overview
|
||||
pages/genealogy/g06-search-genealogies
|
||||
pages/genealogy/g10-application-review
|
||||
pages/genealogy/g04-first-ancestor
|
||||
pages/skeleton/content-list-skeleton
|
||||
pages/family/f02-publish-feed
|
||||
pages/tree/t01-tree-overview
|
||||
pages/tree/t03-member-profile
|
||||
pages/family/f01-family-feed
|
||||
pages/profile/m01-profile-home
|
||||
pages/notification/n01-message-center
|
||||
```
|
||||
|
||||
这些路径已完成当前 15 个路由的语义化迁移;逐项状态与资产台账见 P00 文档。不要新增无编号的 `index.vue`、`list.vue`、`detail.vue` 作为最终页面。
|
||||
|
||||
## 7. 运行与验证
|
||||
|
||||
- 现有测试位于 `tests/`;改动前后优先执行受影响页面的审计脚本。
|
||||
- A01 相关:`tests/a01-a02-ui-contract.ps1`、`tests/a01-asset-alpha-audit.ps1`、`tests/a01-design-record-audit.ps1`。
|
||||
- 公共检查:`tests/foundation-structure-audit.ps1`、`tests/foundation-asset-audit.ps1`、`tests/compile-audit.ps1`、`tests/manifest-json.ps1`、`git diff --check`。
|
||||
- 截图验收尺寸:320 × 568、360 × 640、360 × 800、412 × 915;A01、G01 必须至少复核 412 × 915。
|
||||
|
||||
## 8. 已知工作区注意事项
|
||||
|
||||
- 工作区可能带有用户未提交修改。只处理当前任务涉及文件;提交时只暂存本轮文件。
|
||||
- 历史 `docs/superpowers/*a01*reference-asset-rebuild*` 与旧 G01 设计记录中存在已废弃的路径和“截图/切片式”尝试;不能作为当前实施依据。
|
||||
- 当前依据永远以本文件、`docs/规划.md`、D1、P00 和用户最新消息为准;发生冲突时,以用户最新确认优先。
|
||||
- A02 相关的 `a02-asset-alpha-audit`、`a01-a02-ui-contract`、`compile-audit` 和全量页面路由契约均已在本轮通过;后续修改仍要重新运行受影响检查。
|
||||
- `design-qa.md` 当前是 `final result: blocked`,唯一阻塞是用户尚未确认 A02 的审美结果。
|
||||
- 现有运行图来自 H5 Chrome 调试;最终 Android/HBuilderX 真机/模拟器视觉复核仍待补做,不能被 H5 截图替代。
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
> 电脑或 GPT 更换时,先阅读:[项目交接记录](交接记录.md)。
|
||||
|
||||
> 当前阶段:P-00 地基重整(结构与现有资产已迁移;A01 完整按钮候选已生成、待设计图复核与截图回归)
|
||||
> 当前阶段:Product Design 视觉精修已启动;59 页面第一版可浏览,只有 A01、G01 已获用户视觉验收,A02 正在作为认证样板重做。
|
||||
> 适用端:Android `uni-app`(HBuilderX 打包)
|
||||
> 页面总量:59 个规划页面/状态单元;当前已注册 15 个路由,两者不是同一口径。
|
||||
> 页面总量:59 个规划页面/状态单元;当前已注册 59 条最终编号路由。
|
||||
|
||||
## 1. 本轮目标与边界
|
||||
|
||||
- 只做 Android 用户端;不做 iOS、H5、PC 管理端、后台管理端或官网。
|
||||
- 先完成项目地基:页面目录、路由映射、资产包、公共组件、中文注释和验收规则;地基通过前不继续 A02。
|
||||
- 本窗口以“全量覆盖后集中精修”为顺序推进:先让 59 页拥有统一视觉与本地可浏览入口,再按截图集中修改。
|
||||
- 已由用户验收的 A01 启动/登录引导页与 G01 我的家谱页是**冻结视觉基准**。可重整其内部目录和复用方式,但 412 × 915 的可见效果不得改变。
|
||||
- 现有的 15 个路由只是临时功能骨架;它们不等于 59 个页面均已设计或完成。
|
||||
- 59 条最终路由与文件清单由 `tests/full-page-visual-contract.ps1` 单一维护;代码覆盖不等同于用户视觉验收完成。
|
||||
- 不新增业务接口、真实账号联调、Android 打包或设计清单以外的功能。
|
||||
|
||||
## 2. 唯一依据与完成口径
|
||||
@@ -77,7 +77,7 @@
|
||||
### 5.1 账户与首次使用
|
||||
|
||||
- [x] A-01 启动/登录引导:登录、微信登录、注册入口、协议勾选。
|
||||
- [ ] A-02 账号密码登录:账号密码/手机验证码双标签。
|
||||
- [ ] A-02 账号密码登录:账号密码/手机验证码双标签;卷轴认证样板已实现并完成四尺寸截图,等待用户视觉验收。
|
||||
- [ ] A-03 手机验证码登录与微信授权回跳状态。
|
||||
- [ ] A-04 注册与服务协议确认。
|
||||
- [ ] A-05 忘记密码/重设密码。
|
||||
@@ -137,7 +137,7 @@
|
||||
|
||||
## 6. 推进顺序
|
||||
|
||||
1. **P-00 地基重整(当前)**:已完成页面映射、现有资产迁移、公共组件中文注释和运行时零引用旧资源清理;A01 两张完整按钮候选已保存但尚未接入。下次先按设计图复核候选比例和纹样,通过后才替换旧角标拼接实现,再回归 A01、G01。
|
||||
1. **视觉基线审视(进行中)**:已完成页面映射、资产迁移、公共组件中文注释和运行时零引用旧资源清理;A01 两张完整按钮已接入。2026-07-13 已通过 Chrome 调试端口捕获 A01、G01、A02 的 360 × 800 基线图,审视记录见 `design/2026-07-13-A01-G01-A02-视觉审视.md`;下一步先重做 A02 认证样板,不修改冻结的 A01、G01。
|
||||
2. A-02 至 A-06:在冻结的 A01 认证资产包上完成账户模块。
|
||||
3. G-02 至 G-12:在冻结的 G01 列表、题签、导航资产包上完成家谱模块。
|
||||
4. T、F、R、N、M 模块:每个模块先确认母版和完整资产包,再批量实现其页面/状态。
|
||||
|
||||
@@ -2,63 +2,361 @@
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/auth/a01-entry",
|
||||
"style": { "navigationStyle": "custom", "enablePullDownRefresh": false }
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/a02-login",
|
||||
"style": { "navigationStyle": "custom", "enablePullDownRefresh": false }
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/a03-mobile-verify",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/a04-register",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/a05-reset-password",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/a06-auth-status",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g01-my-genealogies",
|
||||
"style": { "navigationStyle": "custom", "enablePullDownRefresh": false }
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g02-empty-genealogies",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g03-create-genealogy",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g05-genealogy-overview",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g06-search-genealogies",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g10-application-review",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g04-first-ancestor",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/skeleton/content-list-skeleton",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"path": "pages/genealogy/g05-genealogy-overview",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f02-publish-feed",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"path": "pages/genealogy/g06-search-genealogies",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g07-search-result",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g08-join-application",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g09-my-applications",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g10-application-review",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g11-genealogy-settings",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/genealogy/g12-generation-poems",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t01-tree-overview",
|
||||
"style": { "navigationStyle": "custom", "disableScroll": true }
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t02-tree-states",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t03-member-profile",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t04-add-relative",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t05-edit-member",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t06-edit-relationship",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t07-member-directory",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tree/t08-member-states",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f01-family-feed",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m01-profile-home",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"path": "pages/family/f02-publish-feed",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f03-feed-detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f04-article-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f05-article-detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f06-article-editor",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f07-album-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f08-album-detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f09-media-upload",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/family/f10-video-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r01-people-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r02-person-detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r03-gift-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r04-gift-editor",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r05-ritual-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r06-ritual-detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r07-ritual-editor",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r08-growth-journal",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r09-life-events",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r10-memo-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/records/r11-merit-records",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/notification/n01-message-center",
|
||||
"style": { "navigationStyle": "custom" }
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/notification/n02-message-detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m01-profile-home",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m02-edit-profile",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m03-security-settings",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m04-change-password",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m05-change-phone",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m06-help-center",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m07-feedback",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m08-promotion",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m09-vip-orders",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/m10-about-settings",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
@@ -67,4 +365,4 @@
|
||||
"navigationBarBackgroundColor": "#F6F0E5",
|
||||
"backgroundColor": "#F6F0E5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,8 @@
|
||||
|
||||
<view class="entry-actions">
|
||||
<view class="entry-button entry-button--primary" hover-class="entry-button--pressed" @click="openLogin">
|
||||
<image class="button-corner-asset button-corner-asset--top-left" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="button-corner-asset button-corner-asset--top-right" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="button-corner-asset button-corner-asset--bottom-left" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="button-corner-asset button-corner-asset--bottom-right" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<!-- 完整不透明按钮皮肤承载底色、边框与角纹,内容组只保留可访问的图标和文案。 -->
|
||||
<image class="entry-button__skin" src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
|
||||
<view class="entry-button__content">
|
||||
<image class="entry-button__icon" src="/static/assets/foundation/transparent/auth-login-outline.png" mode="aspectFit" />
|
||||
<text>登录</text>
|
||||
@@ -43,10 +41,7 @@
|
||||
</view>
|
||||
|
||||
<view class="entry-button entry-button--secondary" hover-class="entry-button--pressed" @click="prepareWechatLogin">
|
||||
<image class="button-corner-asset button-corner-asset--top-left" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="button-corner-asset button-corner-asset--top-right" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="button-corner-asset button-corner-asset--bottom-left" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="button-corner-asset button-corner-asset--bottom-right" src="/static/assets/foundation/transparent/auth-button-corner.png" mode="aspectFit" />
|
||||
<image class="entry-button__skin" src="/static/assets/foundation/opaque/a01-secondary-button.png" mode="scaleToFill" />
|
||||
<view class="entry-button__content">
|
||||
<image class="entry-button__icon entry-button__icon--wechat" src="/static/assets/foundation/transparent/auth-wechat.png" mode="aspectFit" />
|
||||
<text>微信登录</text>
|
||||
@@ -226,47 +221,26 @@ const prepareAgreement = () => {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 118rpx;
|
||||
border: 2rpx solid #d8ad63;
|
||||
border-radius: 6rpx;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 38rpx;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.entry-button::after {
|
||||
.entry-button__skin {
|
||||
position: absolute;
|
||||
inset: 6rpx;
|
||||
border: 1rpx solid rgba(225, 180, 105, 0.92);
|
||||
border-radius: 2rpx;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button-corner-asset {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-corner-asset--top-left { top: 4rpx; left: 4rpx; }
|
||||
.button-corner-asset--top-right { top: 4rpx; right: 4rpx; transform: scaleX(-1); }
|
||||
.button-corner-asset--bottom-left { bottom: 4rpx; left: 4rpx; transform: scaleY(-1); }
|
||||
.button-corner-asset--bottom-right { right: 4rpx; bottom: 4rpx; transform: scale(-1); }
|
||||
|
||||
.entry-button--primary {
|
||||
border-color: #9e170e;
|
||||
background: #b7170d;
|
||||
color: #fffaf0;
|
||||
}
|
||||
|
||||
.entry-button--primary::after { border-color: #edc67c; }
|
||||
|
||||
.entry-button--secondary {
|
||||
margin-top: 51rpx;
|
||||
background: rgba(255, 252, 245, 0.82);
|
||||
color: #60442f;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,49 +12,58 @@
|
||||
</view>
|
||||
|
||||
<view class="login-panel">
|
||||
<view class="page-heading">
|
||||
<view class="back-button" hover-class="back-hover" @click="goBack">‹</view>
|
||||
<view>
|
||||
<text class="page-title">登录家谱</text>
|
||||
<text class="page-subtitle">欢迎回到家族记忆</text>
|
||||
<image class="login-panel__skin" src="/static/assets/modules/auth/opaque/a02-login-panel.png" mode="scaleToFill" />
|
||||
<view class="login-panel__content">
|
||||
<view class="page-heading">
|
||||
<image class="login-title-cloud" src="/static/assets/foundation/transparent/auth-title-cloud.png" mode="aspectFit" />
|
||||
<view class="back-button" hover-class="back-hover" @click="goBack">
|
||||
<image class="back-button__icon" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="page-heading__copy">
|
||||
<text class="page-title">登录家谱</text>
|
||||
<text class="page-subtitle">欢迎回到家族记忆</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-tabs">
|
||||
<view class="login-tab" :class="{ active: activeTab === 'password' }" @click="activeTab = 'password'">
|
||||
<text>账号密码登录</text>
|
||||
<view class="login-tabs">
|
||||
<view class="login-tab" :class="{ active: activeTab === 'password' }" @click="activeTab = 'password'">
|
||||
<text>账号密码登录</text>
|
||||
</view>
|
||||
<view class="login-tab" :class="{ active: activeTab === 'sms' }" @click="activeTab = 'sms'">
|
||||
<text>手机验证码登录</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="login-tab" :class="{ active: activeTab === 'sms' }" @click="activeTab = 'sms'">
|
||||
<text>手机验证码登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab === 'password'" class="form-content">
|
||||
<view class="input-row">
|
||||
<text class="input-label">账号</text>
|
||||
<input v-model.trim="account" class="auth-input" placeholder="请输入账号或手机号" placeholder-class="placeholder" />
|
||||
<view v-if="activeTab === 'password'" class="form-content">
|
||||
<view class="input-row">
|
||||
<text class="input-label">账号</text>
|
||||
<input v-model.trim="account" class="auth-input" placeholder="请输入账号或手机号" placeholder-class="placeholder" />
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<text class="input-label">密码</text>
|
||||
<input v-model="password" class="auth-input" password placeholder="请输入密码" placeholder-class="placeholder" />
|
||||
</view>
|
||||
<view class="secondary-action" hover-class="text-hover" @click="prepareForgotPassword">忘记密码</view>
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<text class="input-label">密码</text>
|
||||
<input v-model="password" class="auth-input" password placeholder="请输入密码" placeholder-class="placeholder" />
|
||||
</view>
|
||||
<view class="secondary-action" hover-class="text-hover" @click="prepareForgotPassword">忘记密码</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="form-content">
|
||||
<view class="input-row">
|
||||
<text class="input-label">手机</text>
|
||||
<input v-model.trim="smsPhone" class="auth-input" type="number" maxlength="11" placeholder="请输入手机号" placeholder-class="placeholder" />
|
||||
<view v-else class="form-content">
|
||||
<view class="input-row">
|
||||
<text class="input-label">手机</text>
|
||||
<input v-model.trim="smsPhone" class="auth-input" type="number" maxlength="11" placeholder="请输入手机号" placeholder-class="placeholder" />
|
||||
</view>
|
||||
<view class="input-row code-row">
|
||||
<text class="input-label">验证码</text>
|
||||
<input v-model.trim="verificationCode" class="auth-input" type="number" maxlength="6" placeholder="请输入验证码" placeholder-class="placeholder" />
|
||||
<view class="get-code" hover-class="text-hover" @click="prepareGetCode">获取验证码</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="input-row code-row">
|
||||
<text class="input-label">验证码</text>
|
||||
<input v-model.trim="verificationCode" class="auth-input" type="number" maxlength="6" placeholder="请输入验证码" placeholder-class="placeholder" />
|
||||
<view class="get-code" hover-class="text-hover" @click="prepareGetCode">获取验证码</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-submit" hover-class="button-hover" @click="submitLogin">登录</view>
|
||||
<text class="agreement-reminder">请先在登录引导页阅读并确认协议</text>
|
||||
<view class="login-submit" hover-class="button-hover" @click="submitLogin">
|
||||
<image class="login-submit__skin" src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
|
||||
<text class="login-submit__content">登录</text>
|
||||
</view>
|
||||
<text class="agreement-reminder">请先在登录引导页阅读并确认协议</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -79,9 +88,7 @@ const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const prepareForgotPassword = () => {
|
||||
uni.showToast({ title: '忘记密码页面准备中', icon: 'none' })
|
||||
}
|
||||
const prepareForgotPassword = () => uni.navigateTo({ url: '/pages/auth/a05-reset-password' })
|
||||
|
||||
const prepareGetCode = () => {
|
||||
uni.showToast({ title: '获取验证码功能待接入', icon: 'none' })
|
||||
@@ -148,7 +155,7 @@ const submitLogin = () => {
|
||||
|
||||
.auth-header {
|
||||
position: relative;
|
||||
height: 196rpx;
|
||||
height: 253rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -159,44 +166,72 @@ const submitLogin = () => {
|
||||
|
||||
.header-seal {
|
||||
position: absolute;
|
||||
top: 34rpx;
|
||||
top: 70rpx;
|
||||
left: 50%;
|
||||
width: 124rpx;
|
||||
height: 124rpx;
|
||||
width: 136rpx;
|
||||
height: 136rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.login-panel {
|
||||
margin: 68rpx 56rpx 0;
|
||||
padding: 46rpx 38rpx 54rpx;
|
||||
border: 2rpx solid #d6ad6c;
|
||||
border-radius: 10rpx;
|
||||
background: rgba(255, 252, 246, 0.78);
|
||||
box-shadow: inset 0 0 0 5rpx rgba(255, 255, 255, 0.55);
|
||||
position: relative;
|
||||
min-height: 1184rpx;
|
||||
margin: 32rpx 42rpx 0;
|
||||
}
|
||||
|
||||
.login-panel__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-panel__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
min-height: 1184rpx;
|
||||
padding: 66rpx 50rpx 76rpx;
|
||||
}
|
||||
|
||||
.page-heading {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-title-cloud {
|
||||
position: absolute;
|
||||
top: -26rpx;
|
||||
right: 2rpx;
|
||||
width: 154rpx;
|
||||
height: 92rpx;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.page-heading__copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-right: 20rpx;
|
||||
border: 1rpx solid #d7ae70;
|
||||
border-radius: 50%;
|
||||
color: #9d5b28;
|
||||
font-size: 52rpx;
|
||||
font-weight: 300;
|
||||
line-height: 40rpx;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.back-hover { opacity: 0.62; }
|
||||
|
||||
.back-button__icon {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.page-title,
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
@@ -205,31 +240,31 @@ const submitLogin = () => {
|
||||
.page-title {
|
||||
color: #3f2c1d;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 46rpx;
|
||||
font-size: 50rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin-top: 8rpx;
|
||||
margin-top: 10rpx;
|
||||
color: #8c7864;
|
||||
font-size: 22rpx;
|
||||
font-size: 24rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.login-tabs {
|
||||
display: flex;
|
||||
margin-top: 54rpx;
|
||||
margin-top: 62rpx;
|
||||
border-bottom: 1rpx solid #dcc29a;
|
||||
}
|
||||
|
||||
.login-tab {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 0 0 24rpx;
|
||||
padding: 0 0 28rpx;
|
||||
color: #89715a;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 29rpx;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -249,27 +284,27 @@ const submitLogin = () => {
|
||||
content: '';
|
||||
}
|
||||
|
||||
.form-content { margin-top: 26rpx; }
|
||||
.form-content { margin-top: 36rpx; }
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 96rpx;
|
||||
min-height: 112rpx;
|
||||
border-bottom: 1rpx solid #e5d4b7;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
width: 92rpx;
|
||||
width: 100rpx;
|
||||
color: #674b35;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 30rpx;
|
||||
font-size: 31rpx;
|
||||
}
|
||||
|
||||
.auth-input {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
height: 104rpx;
|
||||
color: #3f2c1d;
|
||||
font-size: 28rpx;
|
||||
font-size: 29rpx;
|
||||
}
|
||||
|
||||
.placeholder { color: #b3a895; }
|
||||
@@ -285,7 +320,7 @@ const submitLogin = () => {
|
||||
}
|
||||
|
||||
.secondary-action {
|
||||
margin-top: 28rpx;
|
||||
margin-top: 34rpx;
|
||||
color: #a7160c;
|
||||
font-size: 25rpx;
|
||||
text-align: right;
|
||||
@@ -294,15 +329,25 @@ const submitLogin = () => {
|
||||
.text-hover { opacity: 0.64; }
|
||||
|
||||
.login-submit {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100rpx;
|
||||
margin-top: 60rpx;
|
||||
border: 2rpx solid #9e170e;
|
||||
border-radius: 8rpx;
|
||||
background: #b7170d;
|
||||
box-shadow: inset 0 0 0 4rpx #d69b5f;
|
||||
height: 128rpx;
|
||||
margin-top: 76rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-submit__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-submit__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #fffaf0;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 36rpx;
|
||||
@@ -313,9 +358,9 @@ const submitLogin = () => {
|
||||
|
||||
.agreement-reminder {
|
||||
display: block;
|
||||
margin-top: 26rpx;
|
||||
margin-top: 34rpx;
|
||||
color: #9a8773;
|
||||
font-size: 22rpx;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:A-03;用途:手机验证码登录与授权回跳状态。 -->
|
||||
<template><ModulePage page-id="a03" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:A-04;用途:注册账号与服务协议确认。 -->
|
||||
<template><ModulePage page-id="a04" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:A-05;用途:忘记密码与重设密码。 -->
|
||||
<template><ModulePage page-id="a05" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:A-06;用途:账号受限、登录失败与授权取消状态。 -->
|
||||
<template><ModulePage page-id="a06" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -31,7 +31,15 @@ onMounted(async () => {
|
||||
if (!genealogyId.value) { loadError.value = '请先从“我的家谱”选择一个家谱。'; return }
|
||||
try { feeds.value = await appApi.getFeeds(genealogyId.value) } catch (error) { loadError.value = error.message || '家族动态加载失败。' }
|
||||
})
|
||||
const openSection = (type) => uni.navigateTo({ url: `/pages/skeleton/content-list-skeleton?type=${type}&genealogyId=${genealogyId.value}` })
|
||||
const openSection = (type) => {
|
||||
const routes = {
|
||||
article: '/pages/family/f04-article-list',
|
||||
album: '/pages/family/f07-album-list',
|
||||
ceremony: '/pages/records/r05-ritual-list',
|
||||
record: '/pages/records/r10-memo-list'
|
||||
}
|
||||
uni.navigateTo({ url: `${routes[type]}?genealogyId=${genealogyId.value}` })
|
||||
}
|
||||
const publishFeed = () => {
|
||||
if (!genealogyId.value) { uni.showToast({ title: '请先选择家谱', icon: 'none' }); return }
|
||||
uni.navigateTo({ url: `/pages/family/f02-publish-feed?type=feed&genealogyId=${genealogyId.value}` })
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-03;用途:动态详情、评论、点赞与删除确认。 -->
|
||||
<template><ModulePage page-id="f03" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-04;用途:谱文分类与文章列表。 -->
|
||||
<template><ModulePage page-id="f04" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-05;用途:谱文详情。 -->
|
||||
<template><ModulePage page-id="f05" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-06;用途:新建与编辑谱文。 -->
|
||||
<template><ModulePage page-id="f06" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-07;用途:家族相册列表。 -->
|
||||
<template><ModulePage page-id="f07" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-08;用途:相册详情与照片墙。 -->
|
||||
<template><ModulePage page-id="f08" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-09;用途:图片预览、上传与失败状态。 -->
|
||||
<template><ModulePage page-id="f09" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:F-10;用途:家族视频列表、详情与待开放状态。 -->
|
||||
<template><ModulePage page-id="f10" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -217,7 +217,7 @@ const openShortcut = (key) => {
|
||||
const paths = {
|
||||
tree: "/pages/tree/t01-tree-overview",
|
||||
members: `/pages/genealogy/g05-genealogy-overview?id=${currentGenealogy.value.id}`,
|
||||
poem: "/pages/skeleton/content-list-skeleton?type=poem",
|
||||
poem: "/pages/genealogy/g12-generation-poems",
|
||||
applications: "/pages/genealogy/g10-application-review",
|
||||
};
|
||||
uni.navigateTo({ url: paths[key] });
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:G-02;用途:未加入家谱时的空状态。 -->
|
||||
<template><ModulePage page-id="g02" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 页面编号:G-05;用途:家谱总览。 -->
|
||||
<template>
|
||||
<view class="page-shell detail-page">
|
||||
<PageHeader title="家谱总览" action="管理" @action="showSoon" />
|
||||
<PageHeader title="家谱总览" action="管理" @action="toSettings" />
|
||||
<view v-if="genealogy" class="genealogy-hero">
|
||||
<text class="hero-kicker">{{ genealogy.hall || '堂号待补' }} · {{ genealogy.location || '所在地待补' }}</text>
|
||||
<text class="hero-title">{{ genealogy.name }}</text>
|
||||
@@ -16,12 +16,12 @@
|
||||
<view class="action-grid">
|
||||
<view class="action-cell primary-cell" @click="toTree"><text>世系树</text><text>查看家脉关系</text></view>
|
||||
<view class="action-cell" @click="toFirstPerson"><text>录入族人</text><text>从首代开始完善</text></view>
|
||||
<view class="action-cell" @click="showSoon"><text>字辈谱</text><text>传承行辈次序</text></view>
|
||||
<view class="action-cell" @click="toGenerationPoems"><text>字辈谱</text><text>传承行辈次序</text></view>
|
||||
<view class="action-cell" @click="toApplications"><text>入谱审核</text><text>处理加入申请</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="family-summary"><text class="section-title">家族近况</text><view class="summary-row" @click="toFamily"><text>进入家族圈查看动态</text><text>查看</text></view></view>
|
||||
<view class="invite-block"><text class="invite-title">邀亲人,共建家谱</text><text class="invite-copy">族人申请加入后,由创建者审核确认。</text><button class="primary-button" @click="showSoon">邀请亲人</button></view>
|
||||
<view class="invite-block"><text class="invite-title">邀亲人,共建家谱</text><text class="invite-copy">族人申请加入后,由创建者审核确认。</text><button class="primary-button" @click="toJoinApplication">邀请亲人</button></view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
@@ -57,7 +57,9 @@ const toTree = () => uni.navigateTo({ url: `/pages/tree/t01-tree-overview?geneal
|
||||
const toFirstPerson = () => uni.navigateTo({ url: `/pages/genealogy/g04-first-ancestor?genealogyId=${genealogyId.value}` })
|
||||
const toFamily = () => uni.reLaunch({ url: '/pages/family/f01-family-feed' })
|
||||
const toApplications = () => uni.navigateTo({ url: `/pages/genealogy/g10-application-review?genealogyId=${genealogyId.value}` })
|
||||
const showSoon = () => uni.showToast({ title: '该模块将在后续阶段接入', icon: 'none' })
|
||||
const toSettings = () => uni.navigateTo({ url: `/pages/genealogy/g11-genealogy-settings?genealogyId=${genealogyId.value}` })
|
||||
const toGenerationPoems = () => uni.navigateTo({ url: `/pages/genealogy/g12-generation-poems?genealogyId=${genealogyId.value}` })
|
||||
const toJoinApplication = () => uni.navigateTo({ url: `/pages/genealogy/g08-join-application?genealogyId=${genealogyId.value}` })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:G-07;用途:公开家谱搜索结果与简介。 -->
|
||||
<template><ModulePage page-id="g07" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:G-08;用途:申请加入家谱与提交成功状态。 -->
|
||||
<template><ModulePage page-id="g08" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:G-09;用途:我的家谱申请记录。 -->
|
||||
<template><ModulePage page-id="g09" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:G-11;用途:家谱设置、公开范围与访问说明。 -->
|
||||
<template><ModulePage page-id="g11" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:G-12;用途:字辈诗列表、详情与维护。 -->
|
||||
<template><ModulePage page-id="g12" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -22,10 +22,14 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
const read = async (item) => {
|
||||
if (!item.unread) return
|
||||
if (!item.unread) {
|
||||
uni.navigateTo({ url: `/pages/notification/n02-message-detail?id=${item.id}` })
|
||||
return
|
||||
}
|
||||
try {
|
||||
await appApi.markNotificationRead(item.id)
|
||||
item.unread = false
|
||||
uni.navigateTo({ url: `/pages/notification/n02-message-detail?id=${item.id}` })
|
||||
} catch (error) {
|
||||
uni.showToast({ title: error.message || '标记已读失败', icon: 'none' })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:N-02;用途:消息详情、已读与空状态。 -->
|
||||
<template><ModulePage page-id="n02" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 页面编号:M-01;用途:我的首页。 -->
|
||||
<template>
|
||||
<view class="page-shell profile-page">
|
||||
<view class="profile-hero"><view class="profile-seal">{{ profile.name ? profile.name.slice(0, 1) : '谱' }}</view><view class="profile-copy"><text class="profile-name">{{ profile.name || '家谱用户' }}</text><text class="profile-meta">{{ profile.phone || '资料加载中' }} · {{ profile.role || '家谱成员' }}</text></view><text class="profile-edit" @click="showSoon">资料</text></view>
|
||||
<view class="profile-hero"><view class="profile-seal">{{ profile.name ? profile.name.slice(0, 1) : '谱' }}</view><view class="profile-copy"><text class="profile-name">{{ profile.name || '家谱用户' }}</text><text class="profile-meta">{{ profile.phone || '资料加载中' }} · {{ profile.role || '家谱成员' }}</text></view><text class="profile-edit" @click="toProfile">资料</text></view>
|
||||
<text v-if="loadError" class="error-copy">{{ loadError }}</text>
|
||||
<view class="todo-card paper-card" @click="toNotifications"><view><text class="todo-title">待你处理</text><text class="todo-copy">家谱相关提醒与审核通知</text></view><text class="todo-count">{{ unreadCount }}</text></view>
|
||||
<view class="menu-group paper-card"><view v-for="item in accountItems" :key="item" class="menu-row" @click="showSoon"><text>{{ item }}</text><text>查看</text></view></view>
|
||||
<view class="menu-group paper-card"><view v-for="item in serviceItems" :key="item" class="menu-row" @click="showSoon"><text>{{ item }}</text><text>查看</text></view></view>
|
||||
<view class="menu-group paper-card"><view v-for="item in accountItems" :key="item.label" class="menu-row" @click="openItem(item)"><text>{{ item.label }}</text><text>查看</text></view></view>
|
||||
<view class="menu-group paper-card"><view v-for="item in serviceItems" :key="item.label" class="menu-row" @click="openItem(item)"><text>{{ item.label }}</text><text>查看</text></view></view>
|
||||
<AppTabbar active="profile" />
|
||||
</view>
|
||||
</template>
|
||||
@@ -19,14 +19,23 @@ import { notifications } from '@/data/mock.js'
|
||||
const profile = ref({})
|
||||
const loadError = ref('')
|
||||
const unreadCount = computed(() => notifications.filter((item) => item.unread).length)
|
||||
const accountItems = ['我的资料', '我的加入申请', '账号与安全']
|
||||
const serviceItems = ['帮助中心', '意见反馈', 'VIP 服务']
|
||||
const accountItems = [
|
||||
{ label: '我的资料', path: '/pages/profile/m02-edit-profile' },
|
||||
{ label: '我的加入申请', path: '/pages/genealogy/g09-my-applications' },
|
||||
{ label: '账号与安全', path: '/pages/profile/m03-security-settings' }
|
||||
]
|
||||
const serviceItems = [
|
||||
{ label: '帮助中心', path: '/pages/profile/m06-help-center' },
|
||||
{ label: '意见反馈', path: '/pages/profile/m07-feedback' },
|
||||
{ label: 'VIP 服务', path: '/pages/profile/m09-vip-orders' }
|
||||
]
|
||||
|
||||
onMounted(async () => {
|
||||
try { profile.value = await appApi.getProfile() } catch (error) { loadError.value = error.message || '个人资料加载失败。' }
|
||||
})
|
||||
const toNotifications = () => uni.navigateTo({ url: '/pages/notification/n01-message-center' })
|
||||
const showSoon = () => uni.showToast({ title: '该服务将按计划逐步开放', icon: 'none' })
|
||||
const toProfile = () => uni.navigateTo({ url: '/pages/profile/m02-edit-profile' })
|
||||
const openItem = (item) => uni.navigateTo({ url: item.path })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-02;用途:编辑个人资料。 -->
|
||||
<template><ModulePage page-id="m02" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-03;用途:账号与安全设置。 -->
|
||||
<template><ModulePage page-id="m03" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-04;用途:修改密码。 -->
|
||||
<template><ModulePage page-id="m04" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-05;用途:修改手机号。 -->
|
||||
<template><ModulePage page-id="m05" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-06;用途:帮助中心。 -->
|
||||
<template><ModulePage page-id="m06" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-07;用途:意见反馈。 -->
|
||||
<template><ModulePage page-id="m07" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-08;用途:应用推广。 -->
|
||||
<template><ModulePage page-id="m08" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-09;用途:VIP 服务与订单。 -->
|
||||
<template><ModulePage page-id="m09" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:M-10;用途:关于、协议、隐私与退出确认。 -->
|
||||
<template><ModulePage page-id="m10" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-01;用途:人物录列表。 -->
|
||||
<template><ModulePage page-id="r01" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-02;用途:人物录详情与编辑。 -->
|
||||
<template><ModulePage page-id="r02" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-03;用途:贺礼列表。 -->
|
||||
<template><ModulePage page-id="r03" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-04;用途:贺礼新增、详情与删除确认。 -->
|
||||
<template><ModulePage page-id="r04" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-05;用途:礼仪活动列表。 -->
|
||||
<template><ModulePage page-id="r05" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-06;用途:礼仪详情。 -->
|
||||
<template><ModulePage page-id="r06" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-07;用途:新建与编辑礼仪。 -->
|
||||
<template><ModulePage page-id="r07" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-08;用途:成长日志。 -->
|
||||
<template><ModulePage page-id="r08" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-09;用途:人生事。 -->
|
||||
<template><ModulePage page-id="r09" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-10;用途:家族备忘录。 -->
|
||||
<template><ModulePage page-id="r10" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:R-11;用途:功德记录。 -->
|
||||
<template><ModulePage page-id="r11" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -1,43 +0,0 @@
|
||||
<!-- 页面编号:S-01;用途:多类型内容列表功能骨架;不计为任何已完成设计页面。 -->
|
||||
<template>
|
||||
<view class="page-shell content-page">
|
||||
<PageHeader :title="config.title" />
|
||||
<view class="content-intro"><text>{{ config.copy }}</text></view>
|
||||
<view class="content-list"><view v-for="item in items" :key="item.id" class="content-card paper-card"><text class="content-title">{{ item.title || item.articleTitle || item.albumName || item.ceremonyName || '家族记录' }}</text><text class="content-summary">{{ item.summary || item.content || item.description || item.feedContent || '内容待补充' }}</text><text class="content-date">{{ item.date || item.createTime || '' }}</text></view><view v-if="!items.length && !loadError" class="empty-copy">暂无{{ config.title }}</view></view>
|
||||
<text v-if="loadError" class="error-copy">{{ loadError }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { appApi } from '@/utils/api.js'
|
||||
import { genealogyContext } from '@/utils/genealogy-context.js'
|
||||
|
||||
const type = ref('article')
|
||||
const items = ref([])
|
||||
const loadError = ref('')
|
||||
const definitions = { article: { title: '谱文', copy: '记录家族历史、家训与珍贵文字。' }, album: { title: '相册', copy: '珍存家人、故土与仪式的影像。' }, ceremony: { title: '祭祀', copy: '查看家族祭祀安排与纪念活动。' }, record: { title: '族务', copy: '共同记录成长、备忘与家族事务。' } }
|
||||
const config = computed(() => definitions[type.value] || definitions.article)
|
||||
|
||||
onLoad(async (query) => {
|
||||
type.value = definitions[query.type] ? query.type : 'article'
|
||||
const genealogyId = query.genealogyId || genealogyContext.getCurrentGenealogyId()
|
||||
if (!genealogyId) { loadError.value = '请先选择一个家谱。'; return }
|
||||
genealogyContext.setCurrentGenealogyId(genealogyId)
|
||||
try { items.value = await appApi.getContent(type.value, genealogyId) } catch (error) { loadError.value = error.message || '内容加载失败。' }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content-page { padding-bottom: 36rpx; }
|
||||
.content-intro { padding: 28rpx 30rpx; background: #efe3cd; color: $ink-muted; font-size: 24rpx; }
|
||||
.content-list { display: flex; flex-direction: column; gap: 18rpx; margin: 26rpx 28rpx; }
|
||||
.content-card { padding: 28rpx; }
|
||||
.content-title { display: block; color: $ink; font-size: 31rpx; font-weight: 700; }
|
||||
.content-summary { display: block; margin-top: 14rpx; color: $ink-muted; font-size: 25rpx; line-height: 1.65; }
|
||||
.content-date { display: block; margin-top: 16rpx; color: $gold; font-size: 22rpx; }
|
||||
.empty-copy { padding: 80rpx 0; color: $ink-muted; font-size: 26rpx; text-align: center; }
|
||||
.error-copy { display: block; margin: 20rpx 28rpx; color: $brand-red; font-size: 24rpx; }
|
||||
</style>
|
||||
@@ -2,14 +2,14 @@
|
||||
<template>
|
||||
<view class="tree-page">
|
||||
<PageHeader title="世系树" action="树形视图" />
|
||||
<view class="tree-toolbar"><view class="branch-select">当前家谱世系</view><view class="tree-tools"><text @click="showSoon">查找</text><text @click="showSoon">编辑</text></view></view>
|
||||
<view class="tree-toolbar"><view class="branch-select">当前家谱世系</view><view class="tree-tools"><text @click="toDirectory">查找</text><text @click="toRelationship">编辑</text></view></view>
|
||||
<scroll-view class="tree-scroll" scroll-x scroll-y>
|
||||
<view class="tree-canvas">
|
||||
<view v-if="!members.length" class="tree-empty">{{ loadError || '暂未录入族人' }}</view>
|
||||
<view v-for="member in members" :key="member.id" class="member-node" :class="{ selected: selected && selected.id === member.id }" :style="nodeStyle(member)" @click="selected = member"><text class="node-name">{{ member.name }}</text><text class="node-relation">{{ member.relation }}</text><text class="node-years">{{ member.years }}</text></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="selected" class="member-sheet"><view><text class="sheet-name">{{ selected.name }}</text><text class="sheet-meta">第 {{ selected.generation }} 世 · {{ selected.relation }} · {{ selected.branch }}</text></view><view class="sheet-actions"><text @click="toMember">查看资料</text><text @click="showSoon">添加亲属</text></view></view>
|
||||
<view v-if="selected" class="member-sheet"><view><text class="sheet-name">{{ selected.name }}</text><text class="sheet-meta">第 {{ selected.generation }} 世 · {{ selected.relation }} · {{ selected.branch }}</text></view><view class="sheet-actions"><text @click="toMember">查看资料</text><text @click="toAddRelative">添加亲属</text></view></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -40,7 +40,9 @@ const loadTree = async (query) => {
|
||||
onLoad(loadTree)
|
||||
const nodeStyle = (member) => ({ left: `${member.x}%`, top: `${member.y}%` })
|
||||
const toMember = () => uni.navigateTo({ url: `/pages/tree/t03-member-profile?genealogyId=${genealogyId.value}&personId=${selected.value.id}` })
|
||||
const showSoon = () => uni.showToast({ title: '亲属关系编辑将在下一阶段接入', icon: 'none' })
|
||||
const toDirectory = () => uni.navigateTo({ url: `/pages/tree/t07-member-directory?genealogyId=${genealogyId.value}` })
|
||||
const toRelationship = () => uni.navigateTo({ url: `/pages/tree/t06-edit-relationship?genealogyId=${genealogyId.value}` })
|
||||
const toAddRelative = () => uni.navigateTo({ url: `/pages/tree/t04-add-relative?genealogyId=${genealogyId.value}&personId=${selected.value.id}` })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:T-02;用途:世系图小屏、无成员与加载失败状态。 -->
|
||||
<template><ModulePage page-id="t02" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 页面编号:T-03;用途:成员档案详情。 -->
|
||||
<template>
|
||||
<view class="page-shell member-page">
|
||||
<PageHeader title="成员档案" action="编辑" @action="showSoon" />
|
||||
<PageHeader title="成员档案" action="编辑" @action="toEdit" />
|
||||
<view v-if="member" class="member-hero"><view class="member-seal">{{ member.name.slice(0, 1) }}</view><view><text class="member-name">{{ member.name }}</text><text class="member-role">第 {{ member.generation }} 世 · {{ member.relation }} · 家谱成员</text></view></view>
|
||||
<view v-else class="state-copy">{{ loadError || '正在载入成员档案…' }}</view>
|
||||
<template v-if="member">
|
||||
@@ -41,7 +41,7 @@ onLoad(async (query) => {
|
||||
}
|
||||
})
|
||||
|
||||
const showSoon = () => uni.showToast({ title: '资料编辑将在下一阶段接入', icon: 'none' })
|
||||
const toEdit = () => uni.navigateTo({ url: `/pages/tree/t05-edit-member?genealogyId=${genealogyId.value}&personId=${member.value?.id || ''}` })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:T-04;用途:新增直系亲属。 -->
|
||||
<template><ModulePage page-id="t04" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:T-05;用途:编辑成员资料。 -->
|
||||
<template><ModulePage page-id="t05" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:T-06;用途:编辑亲属关系与冲突提示。 -->
|
||||
<template><ModulePage page-id="t06" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:T-07;用途:成员目录与搜索。 -->
|
||||
<template><ModulePage page-id="t07" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- 页面编号:T-08;用途:成员离世、隐私隐藏与无权限状态。 -->
|
||||
<template><ModulePage page-id="t08" /></template>
|
||||
<script setup>import ModulePage from '@/components/ModulePage.vue'</script>
|
||||
@@ -0,0 +1,91 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const [url, selector, output, width = '360', height = '800', action] = process.argv.slice(2)
|
||||
|
||||
if (!url || !selector || !output) {
|
||||
throw new Error('Usage: node scripts/capture-chrome-page.js <url> <selector> <output> [width] [height] [sms-tab]')
|
||||
}
|
||||
|
||||
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||
|
||||
const connect = async () => {
|
||||
const pages = await (await fetch('http://127.0.0.1:9222/json/list')).json()
|
||||
const page = pages.find((item) => item.type === 'page' && item.url.includes('localhost:5173'))
|
||||
if (!page) throw new Error('Chrome debugging has no localhost:5173 page')
|
||||
|
||||
const socket = new WebSocket(page.webSocketDebuggerUrl)
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.addEventListener('open', resolve, { once: true })
|
||||
socket.addEventListener('error', reject, { once: true })
|
||||
})
|
||||
|
||||
let id = 0
|
||||
const pending = new Map()
|
||||
socket.addEventListener('message', (event) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const request = pending.get(message.id)
|
||||
if (!request) return
|
||||
pending.delete(message.id)
|
||||
if (message.error) request.reject(new Error(message.error.message))
|
||||
else request.resolve(message.result)
|
||||
})
|
||||
|
||||
const send = (method, params = {}) => new Promise((resolve, reject) => {
|
||||
id += 1
|
||||
pending.set(id, { resolve, reject })
|
||||
socket.send(JSON.stringify({ id, method, params }))
|
||||
})
|
||||
|
||||
return { socket, send }
|
||||
}
|
||||
|
||||
const capture = async () => {
|
||||
const { socket, send } = await connect()
|
||||
const viewportWidth = Number(width)
|
||||
const viewportHeight = Number(height)
|
||||
|
||||
try {
|
||||
await send('Page.enable')
|
||||
await send('Runtime.enable')
|
||||
await send('Emulation.setDeviceMetricsOverride', {
|
||||
width: viewportWidth,
|
||||
height: viewportHeight,
|
||||
deviceScaleFactor: 1,
|
||||
mobile: true,
|
||||
screenWidth: viewportWidth,
|
||||
screenHeight: viewportHeight
|
||||
})
|
||||
await send('Page.navigate', { url })
|
||||
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
const result = await send('Runtime.evaluate', {
|
||||
expression: `Boolean(document.querySelector(${JSON.stringify(selector)}))`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (result.result?.value) break
|
||||
if (attempt === 29) throw new Error(`Expected page selector did not render: ${selector}`)
|
||||
await sleep(100)
|
||||
}
|
||||
|
||||
if (action === 'sms-tab') {
|
||||
await send('Runtime.evaluate', {
|
||||
expression: "document.querySelectorAll('.login-tab')[1]?.click()"
|
||||
})
|
||||
}
|
||||
|
||||
await sleep(300)
|
||||
const screenshot = await send('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false })
|
||||
fs.mkdirSync(path.dirname(output), { recursive: true })
|
||||
fs.writeFileSync(output, Buffer.from(screenshot.data, 'base64'))
|
||||
process.stdout.write(`CAPTURED ${output}\n`)
|
||||
} finally {
|
||||
try { await send('Emulation.clearDeviceMetricsOverride') } catch (_) {}
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
capture().catch((error) => {
|
||||
process.stderr.write(`${error.stack || error.message}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
Before Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 986 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -89,13 +89,13 @@ foreach ($requiredVisualAnchor in @(
|
||||
'margin-top: 141rpx',
|
||||
'top: 70rpx',
|
||||
'width: 64rpx',
|
||||
'inset: 6rpx',
|
||||
'entry-button__content',
|
||||
'translateX(-20rpx)',
|
||||
'auth-button-corner.png',
|
||||
'auth-divider-knot.png',
|
||||
'auth-title-cloud.png',
|
||||
'button-corner-asset',
|
||||
'a01-primary-button.png',
|
||||
'a01-secondary-button.png',
|
||||
'entry-button__skin',
|
||||
'align-items: center',
|
||||
'white-space: nowrap',
|
||||
'font-size: 20rpx',
|
||||
@@ -104,7 +104,7 @@ foreach ($requiredVisualAnchor in @(
|
||||
)) {
|
||||
Assert-Contains -Content $entry -Expected $requiredVisualAnchor -Message "Missing A-01 calibrated visual anchor: $requiredVisualAnchor"
|
||||
}
|
||||
foreach ($rejectedVisualConstruction in @('link-line', 'button-corner--legacy', 'class="auth-button primary-button"')) {
|
||||
foreach ($rejectedVisualConstruction in @('link-line', 'button-corner--legacy', 'class="auth-button primary-button"', 'auth-button-corner.png', 'button-corner-asset', '.entry-button::after')) {
|
||||
Assert-NotContains -Content $entry -Unexpected $rejectedVisualConstruction -Message "A-01 retains rejected visual construction: $rejectedVisualConstruction"
|
||||
}
|
||||
foreach ($forbiddenClass in @('entryGuideVisual', 'guide-art', 'entry-hit', 'width: 750rpx', 'min-height: 1625rpx')) {
|
||||
@@ -113,12 +113,18 @@ foreach ($forbiddenClass in @('entryGuideVisual', 'guide-art', 'entry-hit', 'wid
|
||||
Assert-NotContains -Content $entry -Unexpected '/static/assets/icons/action/create-cloud-v2.png' -Message 'A-01 must not reuse the mismatched create-cloud asset for its title or divider ornaments'
|
||||
Assert-Contains -Content $entry -Expected 'filter: brightness(0) invert(1)' -Message 'A-01 primary login icon must render in white'
|
||||
Assert-NotContains -Content $entry -Unexpected $dividerGlyph -Message 'A-01 divider must not use a text glyph as ornament'
|
||||
if (([regex]::Matches($entry, 'button-corner-asset button-corner-asset--')).Count -ne 8) {
|
||||
throw 'A-01 must render the generated ornamental corner asset in all four corners of both entry buttons'
|
||||
if (([regex]::Matches($entry, 'class="entry-button__skin"')).Count -ne 2) {
|
||||
throw 'A-01 must render one complete opaque skin image for each entry button'
|
||||
}
|
||||
if (([regex]::Matches($entry, 'auth-divider-knot\.png')).Count -ne 2) {
|
||||
throw 'A-01 must render the dedicated divider-knot asset above and below the subtitle'
|
||||
}
|
||||
if ($entry -match '(?s)\.entry-button\s*\{[^}]*\bborder\s*:') {
|
||||
throw 'A-01 entry buttons must not draw their final border with CSS'
|
||||
}
|
||||
if ($entry -match '(?s)\.entry-button--(?:primary|secondary)\s*\{[^}]*\bbackground\s*:') {
|
||||
throw 'A-01 entry buttons must not draw their final surface with CSS'
|
||||
}
|
||||
if ($entry -notmatch '(?s)\.agreement-row\s*\{[^}]*align-items:\s*center') {
|
||||
throw 'A-01 agreement checkbox and copy must align on one horizontal center line'
|
||||
}
|
||||
@@ -129,9 +135,40 @@ if ($entry -match '(?s)\.auth-page\s*\{[^}]*overflow\s*:\s*hidden') {
|
||||
throw 'A-01 must allow vertical scrolling on small screens'
|
||||
}
|
||||
|
||||
foreach ($requiredLoginCopy in @($accountPasswordLogin, $smsLogin, $forgotPassword, $getCode, $loginPending, $returnToEntry, $forgotPasswordPending, $getCodePending)) {
|
||||
foreach ($requiredLoginCopy in @($accountPasswordLogin, $smsLogin, $forgotPassword, $getCode, $loginPending, $returnToEntry, $getCodePending)) {
|
||||
Assert-Contains -Content $login -Expected $requiredLoginCopy -Message "Missing A-02 copy: $requiredLoginCopy"
|
||||
}
|
||||
Assert-Contains -Content $login -Expected "url: '/pages/auth/a05-reset-password'" -Message 'A-02 forgot-password entry must navigate to A-05'
|
||||
|
||||
foreach ($requiredLoginVisualAnchor in @(
|
||||
'auth-title-cloud.png',
|
||||
'login-title-cloud',
|
||||
'a02-login-panel.png',
|
||||
'login-panel__skin',
|
||||
'a01-primary-button.png',
|
||||
'login-submit__skin',
|
||||
'login-submit__content'
|
||||
)) {
|
||||
Assert-Contains -Content $login -Expected $requiredLoginVisualAnchor -Message "Missing A-02 calibrated visual anchor: $requiredLoginVisualAnchor"
|
||||
}
|
||||
if (([regex]::Matches($login, 'class="login-submit__skin"')).Count -ne 1) {
|
||||
throw 'A-02 must render one complete opaque skin image for its login button'
|
||||
}
|
||||
if (([regex]::Matches($login, 'class="login-panel__skin"')).Count -ne 1) {
|
||||
throw 'A-02 must render one complete opaque skin image for its login panel'
|
||||
}
|
||||
if ($login -match '(?s)\.login-panel\s*\{[^}]*\bborder\s*:') {
|
||||
throw 'A-02 login panel must not draw its final frame with CSS'
|
||||
}
|
||||
if ($login -match '(?s)\.login-panel\s*\{[^}]*\bbackground\s*:') {
|
||||
throw 'A-02 login panel must not draw its final paper surface with CSS'
|
||||
}
|
||||
if ($login -match '(?s)\.login-submit\s*\{[^}]*\bborder\s*:') {
|
||||
throw 'A-02 login button must not draw its final border with CSS'
|
||||
}
|
||||
if ($login -match '(?s)\.login-submit\s*\{[^}]*\bbackground\s*:') {
|
||||
throw 'A-02 login button must not draw its final surface with CSS'
|
||||
}
|
||||
|
||||
if ($login -match '(?s)\.auth-page\s*\{[^}]*overflow\s*:\s*hidden') {
|
||||
throw 'A-02 must allow vertical scrolling on small screens'
|
||||
|
||||
@@ -11,12 +11,15 @@ $backgrounds = @(
|
||||
$icons = @(
|
||||
'static/assets/foundation/transparent/auth-login-outline.png',
|
||||
'static/assets/foundation/transparent/auth-wechat.png',
|
||||
'static/assets/foundation/transparent/auth-button-corner.png',
|
||||
'static/assets/foundation/transparent/auth-divider-knot.png',
|
||||
'static/assets/foundation/transparent/auth-title-cloud.png'
|
||||
)
|
||||
$buttons = @(
|
||||
'static/assets/foundation/opaque/a01-primary-button.png',
|
||||
'static/assets/foundation/opaque/a01-secondary-button.png'
|
||||
)
|
||||
|
||||
foreach ($asset in $backgrounds + $icons) {
|
||||
foreach ($asset in $backgrounds + $icons + $buttons) {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $root $asset))) {
|
||||
throw "Missing A01 asset: $asset"
|
||||
}
|
||||
@@ -41,7 +44,6 @@ foreach ($asset in $icons) {
|
||||
$bitmap.Dispose()
|
||||
|
||||
$expectedSize = switch ($asset) {
|
||||
'static/assets/foundation/transparent/auth-button-corner.png' { @(160, 160) }
|
||||
'static/assets/foundation/transparent/auth-divider-knot.png' { @(160, 96) }
|
||||
'static/assets/foundation/transparent/auth-title-cloud.png' { @(200, 120) }
|
||||
default { @(96, 96) }
|
||||
@@ -52,4 +54,30 @@ foreach ($asset in $icons) {
|
||||
Write-Output "PASS $asset transparent corners and visible artwork"
|
||||
}
|
||||
|
||||
foreach ($asset in $buttons) {
|
||||
$bitmap = [System.Drawing.Bitmap]::FromFile((Join-Path $root $asset))
|
||||
$width = [int]$bitmap.Width
|
||||
$height = [int]$bitmap.Height
|
||||
$corners = @(
|
||||
$bitmap.GetPixel(0, 0).A,
|
||||
$bitmap.GetPixel(($width - 1), 0).A,
|
||||
$bitmap.GetPixel(0, ($height - 1)).A,
|
||||
$bitmap.GetPixel(($width - 1), ($height - 1)).A
|
||||
)
|
||||
$chromaPixels = 0
|
||||
for ($y = 0; $y -lt $height; $y++) {
|
||||
for ($x = 0; $x -lt $width; $x++) {
|
||||
$pixel = $bitmap.GetPixel($x, $y)
|
||||
if ($pixel.G - [Math]::Max($pixel.R, $pixel.B) -gt 80 -and $pixel.G -gt 120) { $chromaPixels++ }
|
||||
}
|
||||
}
|
||||
$bitmap.Dispose()
|
||||
|
||||
$ratio = [double]$width / $height
|
||||
if ([Math]::Abs($ratio - 4.492) -gt 0.01) { throw "$asset must keep the approved 4.492:1 button ratio." }
|
||||
if (($corners | Where-Object { $_ -ne 255 }).Count -ne 0) { throw "$asset must have opaque outer corners." }
|
||||
if ($chromaPixels -ne 0) { throw "$asset retains chroma-key green pixels." }
|
||||
Write-Output "PASS $asset opaque corners and approved button ratio"
|
||||
}
|
||||
|
||||
Write-Output 'A01-ASSET-ALPHA-AUDIT PASS'
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$asset = Join-Path $root 'static/assets/modules/auth/opaque/a02-login-panel.png'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $asset)) {
|
||||
throw 'Missing A02 login-panel asset'
|
||||
}
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$bitmap = [System.Drawing.Bitmap]::FromFile($asset)
|
||||
$width = [int]$bitmap.Width
|
||||
$height = [int]$bitmap.Height
|
||||
$corners = @(
|
||||
$bitmap.GetPixel(0, 0).A,
|
||||
$bitmap.GetPixel(($width - 1), 0).A,
|
||||
$bitmap.GetPixel(0, ($height - 1)).A,
|
||||
$bitmap.GetPixel(($width - 1), ($height - 1)).A
|
||||
)
|
||||
$bitmap.Dispose()
|
||||
|
||||
$ratio = [double]$width / $height
|
||||
if ($width -ne 940 -or $height -ne 1672) { throw 'A02 login-panel asset must remain 940 x 1672px.' }
|
||||
if ([Math]::Abs($ratio - 0.5622) -gt 0.001) { throw 'A02 login-panel asset must retain its approved panel ratio.' }
|
||||
if (($corners | Where-Object { $_ -ne 255 }).Count -ne 0) { throw 'A02 login-panel asset must have opaque outer corners.' }
|
||||
|
||||
Write-Output 'A02-ASSET-ALPHA-AUDIT PASS'
|
||||
@@ -1,7 +1,7 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$requiredFiles = @('utils/config.js', 'utils/session.js', 'utils/genealogy-context.js', 'utils/api.js', 'pages/genealogy/g04-first-ancestor.vue', 'pages/skeleton/content-list-skeleton.vue', 'pages/family/f02-publish-feed.vue')
|
||||
$requiredFiles = @('utils/config.js', 'utils/session.js', 'utils/genealogy-context.js', 'utils/api.js', 'pages/genealogy/g04-first-ancestor.vue', 'pages/family/f04-article-list.vue', 'pages/family/f02-publish-feed.vue')
|
||||
foreach ($file in $requiredFiles) {
|
||||
if (-not (Test-Path (Join-Path $root $file))) {
|
||||
throw "Missing core-flow file: $file"
|
||||
@@ -49,7 +49,7 @@ $pages = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages.json')
|
||||
if ($pages -notmatch 'pages/genealogy/g04-first-ancestor') {
|
||||
throw 'Missing first-person page route'
|
||||
}
|
||||
foreach ($route in @('pages/skeleton/content-list-skeleton', 'pages/family/f02-publish-feed')) {
|
||||
foreach ($route in @('pages/family/f04-article-list', 'pages/family/f02-publish-feed')) {
|
||||
if ($pages -notmatch [regex]::Escape($route)) {
|
||||
throw "Missing content page route: $route"
|
||||
}
|
||||
@@ -91,7 +91,7 @@ foreach ($method in @('markNotificationRead', 'markAllNotificationsRead')) {
|
||||
}
|
||||
|
||||
$family = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages/family/f01-family-feed.vue')
|
||||
foreach ($token in @('genealogyContext', 'openSection', 'pages/skeleton/content-list-skeleton', 'pages/family/f02-publish-feed')) {
|
||||
foreach ($token in @('genealogyContext', 'openSection', 'pages/family/f04-article-list', 'pages/family/f07-album-list', 'pages/records/r05-ritual-list', 'pages/records/r10-memo-list', 'pages/family/f02-publish-feed')) {
|
||||
if ($family -notmatch [regex]::Escape($token)) {
|
||||
throw "Missing family content flow: $token"
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ Test-ExpectedPage -ExpectedPath 'pages/tree/t01-tree-overview' -PageId 'T-01'
|
||||
Test-ExpectedPage -ExpectedPath 'pages/tree/t03-member-profile' -PageId 'T-03'
|
||||
Test-ExpectedPage -ExpectedPath 'pages/family/f01-family-feed' -PageId 'F-01'
|
||||
Test-ExpectedPage -ExpectedPath 'pages/family/f02-publish-feed' -PageId 'F-02'
|
||||
Test-ExpectedPage -ExpectedPath 'pages/skeleton/content-list-skeleton' -PageId 'S-01'
|
||||
Test-ExpectedPage -ExpectedPath 'pages/notification/n01-message-center' -PageId 'N-01'
|
||||
Test-ExpectedPage -ExpectedPath 'pages/profile/m01-profile-home' -PageId 'M-01'
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function ConvertFrom-Utf8Base64 {
|
||||
param([string]$Value)
|
||||
return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Value))
|
||||
}
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$pagesPath = Join-Path $root 'pages.json'
|
||||
$pageHeader = ConvertFrom-Utf8Base64 '6aG16Z2i57yW5Y+377ya'
|
||||
$expectedPages = @(
|
||||
'pages/auth/a01-entry', 'pages/auth/a02-login', 'pages/auth/a03-mobile-verify', 'pages/auth/a04-register', 'pages/auth/a05-reset-password', 'pages/auth/a06-auth-status',
|
||||
'pages/genealogy/g01-my-genealogies', 'pages/genealogy/g02-empty-genealogies', 'pages/genealogy/g03-create-genealogy', 'pages/genealogy/g04-first-ancestor', 'pages/genealogy/g05-genealogy-overview', 'pages/genealogy/g06-search-genealogies', 'pages/genealogy/g07-search-result', 'pages/genealogy/g08-join-application', 'pages/genealogy/g09-my-applications', 'pages/genealogy/g10-application-review', 'pages/genealogy/g11-genealogy-settings', 'pages/genealogy/g12-generation-poems',
|
||||
'pages/tree/t01-tree-overview', 'pages/tree/t02-tree-states', 'pages/tree/t03-member-profile', 'pages/tree/t04-add-relative', 'pages/tree/t05-edit-member', 'pages/tree/t06-edit-relationship', 'pages/tree/t07-member-directory', 'pages/tree/t08-member-states',
|
||||
'pages/family/f01-family-feed', 'pages/family/f02-publish-feed', 'pages/family/f03-feed-detail', 'pages/family/f04-article-list', 'pages/family/f05-article-detail', 'pages/family/f06-article-editor', 'pages/family/f07-album-list', 'pages/family/f08-album-detail', 'pages/family/f09-media-upload', 'pages/family/f10-video-list',
|
||||
'pages/records/r01-people-list', 'pages/records/r02-person-detail', 'pages/records/r03-gift-list', 'pages/records/r04-gift-editor', 'pages/records/r05-ritual-list', 'pages/records/r06-ritual-detail', 'pages/records/r07-ritual-editor', 'pages/records/r08-growth-journal', 'pages/records/r09-life-events', 'pages/records/r10-memo-list', 'pages/records/r11-merit-records',
|
||||
'pages/notification/n01-message-center', 'pages/notification/n02-message-detail',
|
||||
'pages/profile/m01-profile-home', 'pages/profile/m02-edit-profile', 'pages/profile/m03-security-settings', 'pages/profile/m04-change-password', 'pages/profile/m05-change-phone', 'pages/profile/m06-help-center', 'pages/profile/m07-feedback', 'pages/profile/m08-promotion', 'pages/profile/m09-vip-orders', 'pages/profile/m10-about-settings'
|
||||
)
|
||||
|
||||
$pages = Get-Content -LiteralPath $pagesPath -Raw -Encoding utf8 | ConvertFrom-Json
|
||||
$actualPaths = @($pages.pages.path)
|
||||
if ($actualPaths.Count -ne 59) {
|
||||
throw "Expected 59 final visual routes, found $($actualPaths.Count)."
|
||||
}
|
||||
if (Compare-Object -ReferenceObject $expectedPages -DifferenceObject $actualPaths) {
|
||||
throw 'pages.json routes do not match the 59-page visual delivery contract.'
|
||||
}
|
||||
|
||||
foreach ($page in $expectedPages) {
|
||||
$pageFile = Join-Path $root ($page + '.vue')
|
||||
if (-not (Test-Path -LiteralPath $pageFile)) {
|
||||
throw "Missing planned page file: $page.vue"
|
||||
}
|
||||
$content = Get-Content -LiteralPath $pageFile -Raw -Encoding utf8
|
||||
if ($content -notmatch [regex]::Escape($pageHeader)) {
|
||||
throw "Missing Chinese page header: $page.vue"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output 'FULL-PAGE-VISUAL-CONTRACT PASS'
|
||||
@@ -0,0 +1,31 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$componentPath = Join-Path $root 'components/ModulePage.vue'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $componentPath)) {
|
||||
throw 'Missing shared module page component'
|
||||
}
|
||||
|
||||
$component = Get-Content -LiteralPath $componentPath -Raw -Encoding utf8
|
||||
|
||||
foreach ($anchor in @(
|
||||
'a01-primary-button.png',
|
||||
'background-image:',
|
||||
'background-size: 100% 100%',
|
||||
'.primary-action--compact'
|
||||
)) {
|
||||
if ($component -notmatch [regex]::Escape($anchor)) {
|
||||
throw "ModulePage is missing primary-action visual anchor: $anchor"
|
||||
}
|
||||
}
|
||||
|
||||
if ($component -match '(?s)\.primary-action\s*\{[^}]*\bborder\s*:') {
|
||||
throw 'ModulePage primary actions must not redraw the final button border with CSS'
|
||||
}
|
||||
|
||||
if ($component -match '(?s)\.primary-action\s*\{[^}]*\bbackground\s*:\s*\$brand-red') {
|
||||
throw 'ModulePage primary actions must not redraw the final button surface with a plain CSS color'
|
||||
}
|
||||
|
||||
Write-Output 'MODULE-PAGE-VISUAL-CONTRACT PASS'
|
||||
@@ -25,7 +25,6 @@ $requiredFiles = @(
|
||||
'pages/tree/t03-member-profile.vue',
|
||||
'pages/family/f01-family-feed.vue',
|
||||
'pages/family/f02-publish-feed.vue',
|
||||
'pages/skeleton/content-list-skeleton.vue',
|
||||
'pages/profile/m01-profile-home.vue',
|
||||
'pages/notification/n01-message-center.vue',
|
||||
'static/icons/tab-genealogy.png',
|
||||
@@ -53,7 +52,6 @@ $requiredRoutes = @(
|
||||
'pages/tree/t03-member-profile',
|
||||
'pages/family/f01-family-feed',
|
||||
'pages/family/f02-publish-feed',
|
||||
'pages/skeleton/content-list-skeleton',
|
||||
'pages/profile/m01-profile-home',
|
||||
'pages/notification/n01-message-center'
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"hash": "7ee1354b",
|
||||
"configHash": "ce3416a0",
|
||||
"hash": "569c6bd0",
|
||||
"configHash": "dc147105",
|
||||
"lockfileHash": "e3b0c442",
|
||||
"browserHash": "29a8e5f0",
|
||||
"browserHash": "e2dbcede",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
}
|
||||