feat: unify project responsive layouts

This commit is contained in:
rain
2026-07-21 20:59:10 +08:00
parent a0d19f4aab
commit 5d99bae3f2
94 changed files with 2324 additions and 404 deletions
+25 -1
View File
@@ -3,7 +3,11 @@
class="app-button"
:class="[
`app-button--${type}`,
{ 'app-button--block': block, 'app-button--disabled': disabled },
{
'app-button--block': block,
'app-button--compact': compact,
'app-button--disabled': disabled,
},
]"
:disabled="disabled"
:aria-label="label"
@@ -24,6 +28,7 @@ const props = defineProps({
label: { type: String, default: "" },
type: { type: String, default: "primary" },
block: { type: Boolean, default: false },
compact: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
});
const emit = defineEmits(["click"]);
@@ -40,6 +45,8 @@ const handleClick = (event) => {
</script>
<style scoped lang="scss">
@use "../styles/adaptive-frame-profiles.scss" as adaptive;
.app-button {
display: inline-grid;
width: 420rpx;
@@ -60,6 +67,23 @@ const handleClick = (event) => {
display: grid;
width: 100%;
}
.app-button--compact {
}
.app-button--compact.app-button--primary {
@include adaptive.adaptive-scroll-button(primary);
}
.app-button--compact.app-button--secondary {
@include adaptive.adaptive-scroll-button(secondary);
}
.app-button--compact .app-button__skin {
display: none;
}
.app-button--compact .app-button__label {
padding: 0;
font-size: 23rpx;
letter-spacing: 0;
white-space: nowrap;
}
.app-button__skin {
width: 100%;
height: 100%;
+29 -11
View File
@@ -15,9 +15,11 @@
@keydown.esc.stop="cancel"
>
<scroll-view class="app-dialog__content" scroll-y>
<text v-if="eyebrow" class="app-dialog__eyebrow">{{ eyebrow }}</text>
<text class="app-dialog__title">{{ title }}</text>
<text v-if="message" class="app-dialog__message">{{ message }}</text>
<view class="app-dialog__copy">
<text v-if="eyebrow" class="app-dialog__eyebrow">{{ eyebrow }}</text>
<text class="app-dialog__title">{{ title }}</text>
<text v-if="message" class="app-dialog__message">{{ message }}</text>
</view>
<slot />
<view
class="app-dialog__actions"
@@ -27,9 +29,14 @@
v-if="showCancel"
type="secondary"
:label="cancelText"
:compact="compactActions && showCancel"
@click="cancel"
/>
<AppButton :label="confirmText" @click="$emit('confirm')" />
<AppButton
:label="confirmText"
:compact="compactActions && showCancel"
@click="$emit('confirm')"
/>
</view>
</scroll-view>
</view>
@@ -48,6 +55,7 @@ const props = defineProps({
confirmText: { type: String, default: "我知道了" },
cancelText: { type: String, default: "取消" },
showCancel: { type: Boolean, default: false },
compactActions: { type: Boolean, default: false },
closeOnMask: { type: Boolean, default: true },
});
const emit = defineEmits(["confirm", "cancel", "close"]);
@@ -75,6 +83,8 @@ const cancel = () => {
</script>
<style scoped lang="scss">
@use "../styles/adaptive-frame-profiles.scss" as adaptive;
.app-dialog-layer {
position: fixed;
z-index: 80;
@@ -89,22 +99,30 @@ const cancel = () => {
.app-dialog {
width: 650rpx;
max-width: 100%;
min-height: 520rpx;
background: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png")
center / contain no-repeat;
@include adaptive.adaptive-auth-dialog;
}
.app-dialog__content {
z-index: 1;
display: flex;
min-height: 520rpx;
max-height: calc(100vh - 80rpx);
max-height: calc(var(--app-viewport-height, 100vh) - 160rpx);
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: 74rpx 66rpx 54rpx;
padding: 16rpx 20rpx 12rpx;
text-align: center;
overflow-y: auto;
}
.app-dialog__copy {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
}
.app-dialog__title,
.app-dialog__message {
display: block;
width: 100%;
}
.app-dialog__eyebrow {
color: #9f170f;
font-size: 22rpx;
@@ -131,7 +149,7 @@ const cancel = () => {
width: 100%;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
margin-top: auto;
margin-top: 26rpx;
}
.app-dialog__actions--single {
display: flex;
+1 -1
View File
@@ -60,7 +60,7 @@ const switchTab = (item) => {
left: 0;
z-index: 20;
display: flex;
height: 112rpx;
min-height: 112rpx;
padding-bottom: env(safe-area-inset-bottom);
box-sizing: content-box;
border-top: 1rpx solid $gold-soft;
+3 -7
View File
@@ -18,6 +18,8 @@ defineProps({
</script>
<style scoped lang="scss">
@use "../styles/adaptive-frame-profiles.scss" as adaptive;
.app-toast {
position: fixed;
z-index: 100;
@@ -29,13 +31,7 @@ defineProps({
min-height: 82rpx;
align-items: center;
justify-content: center;
box-sizing: border-box;
border: 16rpx solid transparent;
border-width: 16rpx 74rpx;
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png");
border-image-slice: 58 280 fill;
border-image-width: 16rpx 74rpx;
border-image-repeat: stretch;
@include adaptive.adaptive-feedback-toast;
transform: translateX(-50%);
pointer-events: none;
}
+9 -7
View File
@@ -249,6 +249,8 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
@use "../styles/adaptive-frame-profiles.scss" as adaptive;
.module-page {
display: flex;
min-height: 100vh;
@@ -267,18 +269,18 @@ onUnmounted(() => {
padding: 16rpx 26rpx 56rpx;
}
.module-lead {
display: flex;
min-height: 56rpx;
align-items: center;
justify-content: center;
margin: 0 12rpx 18rpx;
color: $ink-muted;
font-size: 23rpx;
text-align: center;
background: url("/static/assets/modules/genealogy/transparent/section-divider.png") center / 100% 100% no-repeat;
background: url("/static/assets/modules/genealogy/transparent/section-divider.png") center / 100% auto no-repeat;
}
.module-lead text {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
display: block;
}
.module-panel {
width: 100%;
@@ -304,7 +306,7 @@ onUnmounted(() => {
}
.form-row,
.settings-row {
background: var(--module-field-asset) center / 100% 100% no-repeat;
@include adaptive.adaptive-module-field;
}
.form-row > text {
color: $ink;
@@ -350,7 +352,7 @@ onUnmounted(() => {
.detail-card,
.timeline-row,
.status-card {
background: var(--module-content-asset) center / 100% 100% no-repeat;
@include adaptive.adaptive-module-content;
}
.list-card__copy {
padding: 34rpx 46rpx 28rpx;
+5 -6
View File
@@ -107,7 +107,7 @@ const configs = {
action: "保存资料",
note: "隐私字段只向本人和有权限的家谱管理员展示。",
fields: [
{ key: "name", label: "姓名", placeholder: "汤文远" },
{ key: "name", label: "姓名", placeholder: "请输入首位成员姓名" },
{ key: "generation", label: "字辈", placeholder: "例如:文" },
{ key: "birthDate", label: "出生日期", placeholder: "1940年" },
{ key: "summary", label: "人物简介", placeholder: "简要记录生平" },
@@ -121,7 +121,7 @@ const configs = {
action: "保存关系",
note: "若形成重复父母、代际倒置等情况,页面会先提示冲突。",
fields: [
{ key: "source", label: "当前成员", placeholder: "汤文远" },
{ key: "source", label: "当前成员", placeholder: "请选择当前成员" },
{ key: "target", label: "关联成员", placeholder: "请选择家谱成员" },
{ key: "relation", label: "关系类型", placeholder: "父子、配偶等" },
{ key: "effectiveDate", label: "生效日期", placeholder: "选填" },
@@ -195,6 +195,7 @@ const showConflictHelp = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.member-form-page {
display: flex;
min-height: 100vh;
@@ -205,14 +206,13 @@ const showConflictHelp = () => {
z-index: 3;
}
.member-form-panel {
@include adaptive.adaptive-tree-panel;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(640px, calc((100vw - 16px) * 1.48));
margin: 18rpx auto 0;
padding: 7.5% 8%;
box-sizing: border-box;
background: url("/static/assets/modules/tree/transparent/t01-state-panel.png")
center / 100% 100% no-repeat;
}
.member-form,
.member-form-result {
@@ -240,6 +240,7 @@ const showConflictHelp = () => {
line-height: 1.5;
}
.member-field {
@include adaptive.adaptive-tree-field;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
min-height: 78rpx;
@@ -248,8 +249,6 @@ const showConflictHelp = () => {
margin-top: 12rpx;
padding: 12rpx 22rpx;
box-sizing: border-box;
background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png")
center / 100% 100% no-repeat;
}
.member-field > text {
z-index: 1;
@@ -0,0 +1,162 @@
# G09 Nine-Slice Application Cards Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复 G09“我的申请”列表卡片装饰边框被纵向拉伸的问题,并收紧卡片内部及卡片之间的间距。
**Architecture:** 保留现有 `list-slip-frame.png` 真实素材和卡片 DOM 结构,将 `.application-card` 从整图背景拉伸改为 CSS `border-image` 九宫格切片。视觉合同负责固定切片值、边框宽度和紧凑布局,并禁止旧的 `100% 100%` 背景方案回归。
**Tech Stack:** uni-app、Vue 3、SCSS、PowerShell 静态合同、MuMu Android 模拟器
## Global Constraints
- 仅修改 G09 列表卡片,不修改空数据、加载和错误状态卡片。
- 使用 `/static/assets/modules/genealogy/transparent/list-slip-frame.png`,切片为 `24 32`
- 卡片边框宽度为 `18rpx 24rpx`,卡片最小高度为 `210rpx`,卡片间距为 `12rpx`
- 正文最小高度为 `174rpx`,网格间距为 `8rpx 14rpx`,内距为 `26rpx 28rpx 22rpx 40rpx`
- 保留 `.application-card__action``white-space: nowrap`
- 不修改文案、业务状态、模拟数据、路由和交互。
- 视觉验收只使用 MuMu Android 原生画面,不使用浏览器。
- 不执行 Git 操作,不启用多代理。
---
### Task 1: 固定 G09 九宫格卡片视觉合同
**Files:**
- Modify: `tests/g09-all-states-visual-contract.ps1`
- Test: `tests/g09-all-states-visual-contract.ps1`
**Interfaces:**
- Consumes: `pages/genealogy/g09-my-applications.vue``<style scoped lang="scss">` 的卡片样式。
- Produces: 对九宫格素材、切片值、边框宽度、紧凑尺寸及旧拉伸背景禁用规则的静态合同。
- [ ] **Step 1: 写入九宫格和紧凑间距断言**
在现有 `$rules` 中加入以下规则:
```powershell
'(?s)\.application-card\s*\{[^}]*min-height:\s*210rpx;[^}]*margin-bottom:\s*12rpx;[^}]*border-width:\s*18rpx 24rpx;[^}]*border-style:\s*solid;[^}]*border-color:\s*transparent;[^}]*border-image-source:\s*url\("/static/assets/modules/genealogy/transparent/list-slip-frame\.png"\);[^}]*border-image-slice:\s*24 32;[^}]*border-image-width:\s*18rpx 24rpx;[^}]*border-image-repeat:\s*stretch;[^}]*box-sizing:\s*border-box;',
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*174rpx;[^}]*gap:\s*8rpx 14rpx;[^}]*padding:\s*26rpx 28rpx 22rpx 40rpx;'
```
在循环前加入旧方案禁用断言:
```powershell
if ($page -match '(?s)\.application-card\s*\{[^}]*background:\s*url\("/static/assets/modules/genealogy/transparent/list-slip-frame\.png"\)[^}]*100%\s+100%') {
throw 'G09 application card must not stretch the complete frame asset to 100% 100%'
}
```
- [ ] **Step 2: 运行合同并确认先失败**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-all-states-visual-contract.ps1
```
Expected: FAIL,错误指出缺少 `210rpx``border-image-source` 或相关九宫格规则。
### Task 2: 将列表卡片改为九宫格边框
**Files:**
- Modify: `pages/genealogy/g09-my-applications.vue`
- Test: `tests/g09-all-states-visual-contract.ps1`
- Test: `tests/g09-document-flow-contract.ps1`
**Interfaces:**
- Consumes: Task 1 固定的样式合同及现有 `list-slip-frame.png`
- Produces: 不拉伸四角、保持文档流并缩短内部间隔的 G09 列表卡片。
- [ ] **Step 1: 替换 `.application-card` 样式**
`.application-card` 精确改为:
```scss
.application-card {
display: grid;
width: 100%;
min-height: 210rpx;
margin-bottom: 12rpx;
border-width: 18rpx 24rpx;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png");
border-image-slice: 24 32;
border-image-width: 18rpx 24rpx;
border-image-repeat: stretch;
box-sizing: border-box;
}
```
- [ ] **Step 2: 收紧 `.application-card__body` 布局**
保留现有网格列定义和 `box-sizing`,将尺寸精确改为:
```scss
min-height: 174rpx;
gap: 8rpx 14rpx;
padding: 26rpx 28rpx 22rpx 40rpx;
```
- [ ] **Step 3: 运行 G09 视觉和文档流合同**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-all-states-visual-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-document-flow-contract.ps1
```
Expected:
```text
G09-ALL-STATES-VISUAL-CONTRACT PASS
G09-DOCUMENT-FLOW-CONTRACT PASS
```
如文档流合同仍只接受 `background` 素材引用,将该合同的 G09 列表素材要求改为接受 `border-image-source`;主操作按钮素材要求保持不变。
### Task 3: 回归验证与 MuMu 原生验收
**Files:**
- Verify: `pages/genealogy/g09-my-applications.vue`
- Verify: `tests/g09-all-states-visual-contract.ps1`
- Verify: `tests/g09-document-flow-contract.ps1`
- Verify: `tests/g09-g10-business-state-contract.ps1`
**Interfaces:**
- Consumes: Task 2 的最终样式。
- Produces: 合同输出与 MuMu 原生截图证据,供用户决定当前页面是否通过。
- [ ] **Step 1: 运行三项聚焦合同**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-all-states-visual-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-document-flow-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-g10-business-state-contract.ps1
```
Expected: 三项均输出 `PASS`
- [ ] **Step 2: 在 MuMu 中刷新 G09 并截图**
使用当前已连接的 `emulator-5554`,刷新 `/pages/genealogy/g09-my-applications`,通过 Android `screencap` 保存新截图到 `tmp/G09-nine-slice-native.png`
Expected: 三张列表卡片四角比例正常,边线不显得纵向膨胀;卡片之间和卡片内部更紧凑;“修改后重新提交”保持单行且没有越界。
- [ ] **Step 3: 对照用户上一张截图检查**
只检查以下视觉差异:
```text
1. 金色装饰角不再纵向拉长。
2. 每张卡片的上下留白减少,但文字不压线。
3. 三张卡片之间的距离缩短。
4. 时间、状态、操作文字没有重叠或溢出。
```
若任一项失败,仅调整本计划中 `.application-card``.application-card__body` 的边框/间距值,再重复三项合同和 MuMu 截图;不改其他页面或业务逻辑。
@@ -0,0 +1,265 @@
# G10 All-States Responsive Visual Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复 G10 列表卡框、双操作按钮和审核弹窗在 MuMu 中的拉伸、细条及标题正文连行问题,并覆盖全部审核状态。
**Architecture:** `AppButton` 提供默认关闭的紧凑九宫格变体,`AppDialog` 只在显式请求且存在双操作时消费该变体。G10 使用共享变体替换页面内直接图片按钮,并把列表、结果及状态卡全部迁移到真实素材九宫格边框。
**Tech Stack:** uni-app、Vue 3、SCSS、PowerShell 静态合同、MuMu Android 模拟器
## Global Constraints
- `AppButton.compact``AppDialog.compactActions` 默认值均为 `false`
- 紧凑按钮切片固定为 `56 300 fill`,边框宽度固定为 `10rpx 26rpx`
- G10 卡框切片固定为 `24 32`,边框宽度固定为 `18rpx 24rpx`
- G10 双操作继续左右并排,按钮尺寸为 `148rpx × 68rpx`
- G10 结果条尺寸为 `308rpx × 68rpx`
- 不修改文案、模拟数据、路由、状态枚举、校验和审核逻辑。
- 视觉验收只使用 MuMu Android 原生画面。
- 不执行 Git 操作,不启用多代理。
---
### Task 1: AppButton 紧凑九宫格变体
**Files:**
- Create: `tests/app-button-compact-contract.ps1`
- Modify: `components/AppButton.vue`
- Test: `tests/shared-interaction-accessibility-contract.ps1`
- Test: `tests/shared-component-document-flow-contract.ps1`
**Interfaces:**
- Consumes: `type: "primary" | "secondary"`、现有点击和禁用接口。
- Produces: `compact: Boolean = false`;根类 `app-button--compact`
- [ ] **Step 1: 写入失败合同**
合同读取 `components/AppButton.vue` 并要求以下标记:
```powershell
foreach ($token in @(
'compact: { type: Boolean, default: false }',
"'app-button--compact': compact",
'border-image-slice: 56 300 fill;',
'border-width: 10rpx 26rpx;',
'a01-scroll-primary-v3.png',
'a01-scroll-secondary-v3.png'
)) {
if (-not $button.Contains($token)) { throw "AppButton compact contract missing: $token" }
}
```
并断言 `.app-button--compact .app-button__skin``display: none`
- [ ] **Step 2: 运行合同并确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File tests/app-button-compact-contract.ps1`
Expected: FAIL,指出缺少 `compact`
- [ ] **Step 3: 实现最小变体**
在属性中加入:
```js
compact: { type: Boolean, default: false },
```
在根类对象中加入:
```js
'app-button--compact': compact
```
为主/次紧凑按钮分别设置对应卷轴 `border-image-source`,共同使用:
```scss
border-width: 10rpx 26rpx;
border-style: solid;
border-color: transparent;
border-image-slice: 56 300 fill;
border-image-width: 10rpx 26rpx;
border-image-repeat: stretch;
```
隐藏紧凑模式中的原图片皮肤,保留标签和原有事件。
- [ ] **Step 4: 运行组件合同**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/app-button-compact-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
```
Expected: 三项均输出 `PASS`
### Task 2: AppDialog 标题分行与显式紧凑双操作
**Files:**
- Create: `tests/app-dialog-responsive-content-contract.ps1`
- Modify: `components/AppDialog.vue`
**Interfaces:**
- Consumes: Task 1 的 `AppButton.compact`
- Produces: `compactActions: Boolean = false``.app-dialog__copy` 独立文本容器。
- [ ] **Step 1: 写入失败合同**
合同要求:
```powershell
foreach ($token in @(
'compactActions: { type: Boolean, default: false }',
'class="app-dialog__copy"',
':compact="compactActions && showCancel"'
)) {
if (-not $dialog.Contains($token)) { throw "AppDialog responsive contract missing: $token" }
}
```
并要求 `.app-dialog__copy` 为纵向 flex、宽度 `100%`,标题和说明为 `display: block; width: 100%`
- [ ] **Step 2: 运行合同并确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File tests/app-dialog-responsive-content-contract.ps1`
Expected: FAIL,指出缺少 `compactActions`
- [ ] **Step 3: 实现弹窗布局**
使用 `.app-dialog__copy` 包裹 eyebrow、title 和 message;在属性中加入:
```js
compactActions: { type: Boolean, default: false },
```
两个 `AppButton` 都传入:
```vue
:compact="compactActions && showCancel"
```
单按钮因 `showCancel === false` 保持默认宽卷轴。
- [ ] **Step 4: 运行弹窗及共享合同**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/app-dialog-responsive-content-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
```
Expected: 三项均输出 `PASS`
### Task 3: G10 列表、结果与状态卡迁移
**Files:**
- Modify: `pages/genealogy/g10-application-review.vue`
- Modify: `tests/g10-all-states-visual-contract.ps1`
- Modify: `tests/g10-document-flow-contract.ps1`
- Test: `tests/g08-g10-application-flow-contract.ps1`
- Test: `tests/g09-g10-business-state-contract.ps1`
**Interfaces:**
- Consumes: `AppButton compact``AppDialog compact-actions`
- Produces: G10 九宫格卡框、左右紧凑操作、九宫格结果条和完整状态卡。
- [ ] **Step 1: 将 G10 合同切换到新契约**
删除 `g10-document-flow-contract.ps1` 对以下旧值的要求:
```text
background ... list-slip-frame.png ... 100% 100%
padding: 44rpx 52rpx 29rpx 64rpx
```
在视觉/文档流合同中要求:
```text
border-image-source: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
border-image-slice: 24 32
border-width: 18rpx 24rpx
min-height: 218rpx
margin-bottom: 12rpx
body min-height: 182rpx
gap: 8rpx 0
padding: 28rpx 28rpx 22rpx 40rpx
<AppButton compact type="secondary"
<AppButton compact
<AppDialog compact-actions
```
并禁止 `.application-card``.review-state-card` 继续使用 `100% 100%` 的完整背景拉伸。
- [ ] **Step 2: 运行 G10 合同并确认失败**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g10-all-states-visual-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g10-document-flow-contract.ps1
```
Expected: FAIL,指出缺少九宫格卡框或紧凑按钮。
- [ ] **Step 3: 修改 G10 模板和样式**
- 导入 `AppButton`
- 用两个 `AppButton compact` 替换 `.review-action` 内的直接图片。
-`AppDialog` 增加 `compact-actions`
- `.application-card` 使用 `24 32` 九宫格切片和规格中的 `218rpx` 尺寸。
- `.application-card__body` 使用 `182rpx``8rpx 0``28rpx 28rpx 22rpx 40rpx`
- `.review-result` 根据 approved/rejected 类使用对应卷轴九宫格并移除直接图片。
- `.review-state-card` 使用同一卡框九宫格,保留其文案布局。
- [ ] **Step 4: 运行页面合同**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g10-all-states-visual-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g10-document-flow-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g08-g10-application-flow-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g09-g10-business-state-contract.ps1
```
Expected: 四项均输出 `PASS`
### Task 4: MuMu 全状态验证
**Files:**
- Verify: `components/AppButton.vue`
- Verify: `components/AppDialog.vue`
- Verify: `pages/genealogy/g10-application-review.vue`
- Save screenshots: `tmp/G10-responsive-*.png`
**Interfaces:**
- Consumes: Task 13 的最终实现。
- Produces: 11 个 G10 状态的 MuMu 原生视觉证据。
- [ ] **Step 1: 运行全部聚焦合同**
运行 Task 1–3 的九项唯一合同,任何一项失败即停止视觉验收。
- [ ] **Step 2: 捕获 11 个 MuMu 状态**
依次捕获列表、说明、通过确认、通过完成、拒绝确认、拒绝空校验、拒绝完成、空、加载、错误、无权限。
- [ ] **Step 3: 逐项检查**
```text
1. 卡框四角不拉伸,卡片上下留白收紧。
2. 左右双按钮皮肤完整,文字居中,不重叠、不越界。
3. 标题和说明独立成行。
4. 结果条完整且不可点击。
5. 空/错/无权限卡框不变形。
6. 加载、重试、拒绝校验和审核业务行为不回退。
```
若任一状态不满足,只调整本规格定义的紧凑切片、G10 卡片间距或弹窗文本布局,并重新运行全部聚焦合同与对应 MuMu 状态。
@@ -0,0 +1,152 @@
# 全项目通用提示文案实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 清除全项目输入提示、示例和辅助说明中的具体人物或家族名称,同时完整保留页面模拟数据。
**Architecture:** 新增一个只扫描用户提示上下文的 PowerShell 合同,作为规则唯一验证入口。生产改动限定在 G03、G08 与共享 `TreeMemberForm` 的六处提示文案,不触碰任何 fixture、卡片或详情数据。
**Tech Stack:** uni-app、Vue 3、PowerShell 静态合同、现有页面合同、MuMu、ADB。
## Global Constraints
- 只检查 `placeholder`、带“例如/比如/如:”的示例和输入框附近辅助说明。
- 模拟数据、成员目录、详情卡、申请记录和测试输入值保持不变。
- G08 使用“某某某堂侄”表达关系格式;其他字段优先使用动作型提示。
- 不执行 Git 操作,不启用多代理。
- 最终视觉结论只依据 MuMu 原生画面。
---
### Task 1: 建立提示文案全项目合同
**Files:**
- Create: `tests/generic-guidance-copy-contract.ps1`
**Interfaces:**
- Consumes: `pages/**/*.vue``components/**/*.vue` 中的用户提示文案。
- Produces: 只针对提示上下文禁止具体姓名的项目级合同。
- [ ] **Step 1: 写入失败合同**
创建 `tests/generic-guidance-copy-contract.ps1`
```powershell
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$vueFiles = @(
Get-ChildItem -LiteralPath (Join-Path $root 'pages') -Recurse -Filter '*.vue'
Get-ChildItem -LiteralPath (Join-Path $root 'components') -Recurse -Filter '*.vue'
)
$specificCopy = @('汤正华', '汤正国', '汤文远', '汤氏家谱', '如:汤')
foreach ($file in $vueFiles) {
$lineNumber = 0
foreach ($line in Get-Content -LiteralPath $file.FullName -Encoding utf8) {
$lineNumber += 1
if ($line -notmatch 'placeholder|例如|比如|如:') { continue }
foreach ($token in $specificCopy) {
if ($line.Contains($token)) {
throw "User guidance must not contain specific copy '$token': $($file.FullName):$lineNumber"
}
}
}
}
$g08 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g08-join-application.vue') -Raw -Encoding utf8
foreach ($required in @('placeholder="例如:某某某堂侄"', '请以家谱中一位已知长辈为参照,例如:某某某堂侄')) {
if (-not $g08.Contains($required)) { throw "G08 generic relationship guidance missing: $required" }
}
Write-Output 'GENERIC-GUIDANCE-COPY-CONTRACT PASS'
```
- [ ] **Step 2: 运行合同并确认 RED**
```powershell
& powershell -ExecutionPolicy Bypass -File tests/generic-guidance-copy-contract.ps1
```
Expected: FAIL,首先报告 G03、G08 或 `TreeMemberForm` 仍含具体提示文案。
### Task 2: 替换六处提示并回归
**Files:**
- Modify: `pages/genealogy/g03-create-genealogy.vue`
- Modify: `pages/genealogy/g08-join-application.vue`
- Modify: `components/tree/TreeMemberForm.vue`
**Interfaces:**
- Consumes: Task 1 的提示文案合同。
- Produces: 不含具体姓名的动作型提示与通用关系示例。
- [ ] **Step 1: 做六处精确替换**
`pages/genealogy/g03-create-genealogy.vue`
```vue
placeholder="请输入姓氏"
placeholder="请输入家谱名称"
```
`pages/genealogy/g08-join-application.vue`
```vue
placeholder="例如:某某某堂侄"
```
```text
请以家谱中一位已知长辈为参照,例如:某某某堂侄
```
`components/tree/TreeMemberForm.vue`
```js
{ key: "name", label: "姓名", placeholder: "请输入首位成员姓名" },
{ key: "source", label: "当前成员", placeholder: "请选择当前成员" },
```
- [ ] **Step 2: 运行静态合同**
```powershell
$tests = @(
'tests/generic-guidance-copy-contract.ps1',
'tests/g03-create-flow-contract.ps1',
'tests/g03-visual-states-contract.ps1',
'tests/g08-g10-application-flow-contract.ps1',
'tests/g08-document-flow-contract.ps1',
'tests/tree-member-form-document-flow-contract.ps1'
)
foreach ($test in $tests) { & powershell -ExecutionPolicy Bypass -File $test }
```
Expected: 六项全部输出各自 `PASS`
- [ ] **Step 3: 运行 G08 流程回归**
```powershell
node tests/g08-g10-application-flow-runtime-smoke.js http://localhost:5173
```
Expected: 输出 `G08-G10-APPLICATION-FLOW-RUNTIME-SMOKE PASS`。若 H5 服务未运行,记录为环境阻塞,不用浏览器截图代替 MuMu 视觉验收。
### Task 3: MuMu 原生复核
**Files:**
- Create: `tmp/G08-generic-guidance-native.png`
**Interfaces:**
- Consumes: 已同步到 MuMu 的 G08 最新页面。
- Produces: 通用关系提示的原生证据。
- [ ] **Step 1: 捕获 G08 当前画面**
```powershell
$adb = 'C:\Users\Administrator\Desktop\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe'
& $adb -s emulator-5554 shell screencap -p /sdcard/G08-generic-guidance-native.png
& $adb -s emulator-5554 pull /sdcard/G08-generic-guidance-native.png tmp/G08-generic-guidance-native.png
```
- [ ] **Step 2: 检查并交给用户确认**
使用 `view_image` 原图确认关系输入和下方辅助说明都显示“某某某堂侄”,页面不再出现具体人物姓名。只报告当前默认状态,不把未查看的校验或提交结果状态标记为通过。
@@ -0,0 +1,228 @@
# Responsive Foundation Phase 1 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 建立全项目响应式基础契约,并让 AppDialog、AppButton 与 ModulePage 使用统一九宫格素材档案和内容驱动高度。
**Architecture:** `styles/adaptive-frame-profiles.scss` 是九宫格切片的唯一所有者;组件只调用具名 mixin。`project-responsive-layout-contract.ps1` 根据覆盖清单扫描已迁移文件,根据唯一白名单放行结构性固定尺寸;第一阶段覆盖三个公共内容组件,后续阶段只增加覆盖范围。
**Tech Stack:** uni-app、Vue 3、SCSS、PowerShell、MuMu Android
## Global Constraints
- 内容容器不得使用固定高度表达内容容量。
- 可变内容背景不得使用 `100% 100%` 整图拉伸。
- 固定高度和固定装饰例外只能由 `tests/responsive-layout-allowlist.json` 所有。
- 第一阶段覆盖 `AppDialog.vue``AppButton.vue``ModulePage.vue`;已覆盖文件不得退出。
- AppDialog 短内容自然收紧、长内容自然增高、超过视口才滚动。
- 不修改业务逻辑、路由、文案和数据状态。
- 不执行 Git 操作,不启用多代理。
---
### Task 1: 唯一九宫格素材档案
**Files:**
- Create: `styles/adaptive-frame-profiles.scss`
- Create: `tests/adaptive-frame-profiles-contract.ps1`
**Interfaces:**
- Produces: `adaptive-auth-dialog``adaptive-scroll-button($type)``adaptive-module-content``adaptive-module-field` 四个 SCSS mixin。
- [ ] **Step 1: 写入失败合同**
合同要求档案存在并包含:
```powershell
foreach ($token in @(
'@mixin adaptive-auth-dialog',
'@mixin adaptive-scroll-button($type)',
'@mixin adaptive-module-content',
'@mixin adaptive-module-field',
'a01-scroll-dialog-v3.png',
'border-image-slice: 260 240 360 240 fill;',
'border-image-slice: 56 300 fill;',
'border-image-slice: 105 150;',
'border-image-slice: 70 100 fill;'
))
```
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File tests/adaptive-frame-profiles-contract.ps1`
Expected: FAIL,素材档案不存在。
- [ ] **Step 2: 创建素材档案**
`adaptive-auth-dialog` 使用:
```scss
border-width: 48rpx 34rpx 72rpx;
border-image-slice: 260 240 360 240 fill;
border-image-width: 48rpx 34rpx 72rpx;
```
`adaptive-scroll-button` 使用现有主/次卷轴素材、`10rpx 26rpx` 边框和 `56 300 fill` 切片。
`adaptive-module-content` 使用动态 `var(--module-content-asset)``18rpx 20rpx` 边框和 `105 150` 切片。
`adaptive-module-field` 使用动态 `var(--module-field-asset)``12rpx 16rpx` 边框和 `70 100 fill` 切片。
所有 mixin 使用透明实体边框、`border-image-repeat: stretch``box-sizing: border-box`
- [ ] **Step 3: 验证档案合同**
Expected: `ADAPTIVE-FRAME-PROFILES-CONTRACT PASS`
### Task 2: 全局扫描合同、覆盖清单与例外白名单
**Files:**
- Create: `tests/project-responsive-layout-contract.ps1`
- Create: `tests/responsive-layout-coverage.json`
- Create: `tests/responsive-layout-allowlist.json`
**Interfaces:**
- Consumes: 覆盖文件列表与按文件/选择器/属性登记的例外。
- Produces: 已迁移文件中固定内容高度、可变背景整图拉伸和重复九宫格切片的失败门禁。
- [ ] **Step 1: 创建第一阶段覆盖清单**
```json
{
"covered": [
"components/AppButton.vue",
"components/AppDialog.vue",
"components/ModulePage.vue"
]
}
```
- [ ] **Step 2: 创建唯一白名单**
第一阶段只允许 `ModulePage.vue``.module-lead` 使用固定分隔线背景;不允许 AppDialog、AppButton 或 ModulePage 内容表面使用固定内容高度/整图拉伸。
- [ ] **Step 3: 创建扫描合同并确认失败**
合同解析覆盖文件的样式块,遇到以下情况失败:
```text
height: <数字>rpx(不含 min-height/max-height
background: ... 100% 100%
页面或组件直接出现 border-image-slice(素材档案本身除外)
```
白名单按规范化文件路径、选择器和属性精确匹配。覆盖清单中的路径不存在也失败。
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File tests/project-responsive-layout-contract.ps1`
Expected: FAIL,指出 AppDialog 固定 `520rpx` 或 ModulePage 内容表面整图拉伸。
### Task 3: AppDialog 与 AppButton 消费统一档案
**Files:**
- Modify: `components/AppDialog.vue`
- Modify: `components/AppButton.vue`
- Modify: `tests/app-dialog-responsive-content-contract.ps1`
- Modify: `tests/app-button-compact-contract.ps1`
**Interfaces:**
- Consumes: Task 1 的 `adaptive-auth-dialog``adaptive-scroll-button`
- Produces: 内容驱动的全局弹窗和默认关闭的紧凑按钮。
- [ ] **Step 1: 更新失败合同**
AppDialog 合同禁止 `min-height: 520rpx`、旧 `background ... contain``margin-top: auto`,要求:
```text
@include adaptive-auth-dialog;
max-height: calc(var(--app-viewport-height, 100vh) - 160rpx)
margin-top: 26rpx
```
AppButton 合同要求调用 `adaptive-scroll-button(primary|secondary)`,不再在组件内重复切片值。
- [ ] **Step 2: 运行合同并确认失败**
Expected: AppDialog 因固定高度失败,AppButton 因本地切片重复失败。
- [ ] **Step 3: 修改 AppDialog**
在 scoped style 开头导入档案。`.app-dialog` 删除固定最小高度和旧背景,调用 `adaptive-auth-dialog``.app-dialog__content` 删除固定最小高度,使用:
```scss
max-height: calc(var(--app-viewport-height, 100vh) - 160rpx);
padding: 16rpx 20rpx 12rpx;
overflow-y: auto;
```
操作区改为 `margin-top: 26rpx`,不再使用 `auto`
- [ ] **Step 4: 修改 AppButton**
导入档案;主/次紧凑类分别调用 `@include adaptive-scroll-button(primary)``secondary`;删除组件内重复边框切片声明。
- [ ] **Step 5: 运行共享组件合同**
Run AppDialog、AppButton、共享无障碍、共享文档流和全局响应式合同,Expected: 全部 `PASS`
### Task 4: ModulePage 内容表面迁移
**Files:**
- Modify: `components/ModulePage.vue`
- Modify: `tests/module-page-visual-contract.ps1`
- Test: `tests/module-series-responsive-runtime-smoke.js`
**Interfaces:**
- Consumes: Task 1 的 module-content 与 module-field mixin。
- Produces: F/R/N/M 通用页面的内容驱动卡片和表单表面。
- [ ] **Step 1: 更新失败合同**
要求 ModulePage 导入档案,字段组调用 `adaptive-module-field`,内容组调用 `adaptive-module-content`;禁止内容表面 `100% 100%`
- [ ] **Step 2: 运行合同并确认失败**
Expected: FAIL,指出旧 `var(--module-*-asset) center / 100% 100%`
- [ ] **Step 3: 修改 ModulePage**
- `.form-row, .settings-row` 调用 `adaptive-module-field`
- `.list-card, .detail-card, .timeline-row, .status-card` 调用 `adaptive-module-content`
- `.module-lead` 自己承担 flex 居中,删除子文本的 `height: 100%`
- 保留现有 `min-height` 作为最低视觉基线,内容可继续增高。
- [ ] **Step 4: 验证 ModulePage**
运行视觉合同、响应式 runtime smoke 与全局响应式合同;若 H5 服务未运行,只记录 runtime 环境阻塞,不用浏览器代替 MuMu。
### Task 5: MuMu 三档基础验证
**Files:**
- Verify: `components/AppDialog.vue`
- Verify: `components/AppButton.vue`
- Verify: `components/ModulePage.vue`
- Save screenshots: `tmp/responsive-foundation-*.png`
**Interfaces:**
- Produces: G10 通过/拒绝弹窗和一个 ModulePage 页面在三档 Android 逻辑视口的原生证据。
- [ ] **Step 1: 运行全部聚焦合同**
必须包含素材档案、全局响应式、AppDialog、AppButton、ModulePage、共享无障碍和共享文档流合同。
- [ ] **Step 2: MuMu 验证 G10**
`360×640dp``360×800dp``412×915dp` 依次检查:
```text
通过确认:无固定大空白。
拒绝确认:输入框和按钮不压边。
拒绝错误:错误文字出现后仍可达按钮。
```
- [ ] **Step 3: MuMu 验证 ModulePage**
选择一个 ready 和一个 error 状态,确认内容卡随文字增高、边角不拉伸、按钮可达。
- [ ] **Step 4: 恢复 MuMu 原始尺寸**
每档验证后恢复用户当前 `720×1280` 物理尺寸和原密度,并把 G10 恢复到默认列表。
@@ -0,0 +1,22 @@
# G09 申请卡片九宫格边框设计
## 问题
`list-slip-frame.png` 原始尺寸为 `720×144`,比例为 `5:1`。G09 卡片当前约为 `672×228rpx`,比例约 `2.95:1`,并使用 `background-size: 100% 100%`,导致整张边框素材被纵向拉伸。卡片上、下内距和卡片间距也偏宽。
## 方案
- `.application-card` 停止使用拉伸背景,改用 CSS `border-image` 九宫格切片。
- 使用原素材 `list-slip-frame.png`,切片固定为上/下 `24px`、左/右 `32px`;四角不变,只拉伸中间边线。
- 卡片边框宽度使用上/下 `18rpx`、左/右 `24rpx`
- 卡片最小高度从 `228rpx` 收紧到 `210rpx`,卡片间距从 `18rpx` 收紧到 `12rpx`
- 卡片正文最小高度使用 `174rpx`,网格间距使用 `8rpx 14rpx`,内距使用 `26rpx 28rpx 22rpx 40rpx`
- 保留 `white-space: nowrap`,不修改操作文案、状态、模拟数据和交互。
## 验证
- 合同禁止 `.application-card` 继续使用 `background: ... list-slip-frame.png ... 100% 100%`
- 合同要求 `border-image-source``border-image-slice: 24 32`、边框宽度和收紧后的布局值。
- G09 视觉、文档流和业务状态合同通过。
- MuMu 原生画面中四角比例正常,卡片内容间距更紧凑,“修改后重新提交”保持单行且不溢出。
- 不执行 Git 操作,不启用多代理。
@@ -0,0 +1,94 @@
# G10 全状态响应式视觉调整设计
## 问题与目标
MuMu 原生画面确认 G10“入谱审核”存在三类问题:
1. 列表卡片和状态卡使用 `list-slip-frame.png` 整图 `100% 100%` 拉伸,四角与上下边框纵向变形。
2. 列表双操作和确认弹窗双操作把比例约 `6.2:1``6.8:1` 的卷轴素材放入窄按钮后使用 `aspectFit`,可见皮肤缩成细横条。
3. `AppDialog` 的标题和说明是相邻文本节点,MuMu 中会连成同一行,信息层级不清楚。
目标是在不改变审核业务逻辑的前提下,让列表、弹窗、审核结果及空/错/无权限状态在 MuMu 中保持真实素材比例、清晰层级和完整触控视觉。
## 组件边界
### AppButton
- 新增布尔属性 `compact`,默认值为 `false`
- 只有显式设置 `compact` 时启用紧凑九宫格皮肤;其他已通过页面继续使用当前 `aspectFit` 皮肤,默认视觉不变。
- 紧凑按钮隐藏原 `<image>` 皮肤,在按钮根节点使用对应的真实卷轴素材作为 `border-image`
- 主按钮:`a01-scroll-primary-v3.png`
- 次按钮:`a01-scroll-secondary-v3.png`
- 紧凑切片固定为 `border-image-slice: 56 300 fill`,边框宽度固定为 `10rpx 26rpx`,重复方式为 `stretch`
- 紧凑按钮继续保留原有点击、禁用、按下态、无障碍标签和文字层。
### AppDialog
- 新增布尔属性 `compactActions`,默认值为 `false`
-`compactActions` 为真且 `showCancel` 为真时,取消和确认按钮继续左右并排,并向两个 `AppButton` 传递 `compact`
- 单按钮弹窗不启用紧凑模式,继续显示当前完整宽卷轴按钮。
- 标题与说明放入独立的 `.app-dialog__copy` 容器;标题和说明均为独立块级行、宽度 `100%`,说明保持 `22rpx` 上间距。
- 不修改弹窗遮罩、背景画、关闭规则、焦点恢复和事件名称。
## G10 页面设计
### 列表与状态卡
- `.application-card` 改用 `list-slip-frame.png` 九宫格边框:
- `border-image-slice: 24 32`
- `border-width: 18rpx 24rpx`
- `border-image-width: 18rpx 24rpx`
- `border-image-repeat: stretch`
- 列表卡片最小高度使用 `218rpx`,卡片间距使用 `12rpx`
- `.application-card__body` 最小高度使用 `182rpx`,间距使用 `8rpx 0`,内距使用 `28rpx 28rpx 22rpx 40rpx`
- 关系说明上间距从 `15rpx` 收紧为 `8rpx`
- `.review-state-card` 同样使用 `24 32` 九宫格切片,不再整图拉伸;状态文案、加载分支和重试行为不变。
### 待审核操作
- G10 列表改为复用 `AppButton compact`,拒绝和通过继续左右并排。
- 每个按钮保持 `148rpx × 68rpx` 的可见尺寸,间距保持 `12rpx`
- 拒绝使用 `secondary`,通过使用 `primary`;按钮文字和点击回调保持不变。
- 页面不再直接渲染 `a01-primary-button-v2.png``a01-secondary-button-v2.png``aspectFit` 图片。
### 审核结果
- `review-result` 保持 `308rpx × 68rpx`,使用与紧凑按钮相同的九宫格切片规则。
- 已通过使用 `a01-scroll-primary-v3.png`,已拒绝使用 `a01-scroll-secondary-v3.png`
- 结果状态是静态展示,不改成可点击按钮,不增加隐藏点击区域。
### 审核弹窗
- G10 的 `AppDialog` 显式传入 `compact-actions`
- 审核说明:标题“审核说明”和说明正文分行;单个“我知道了”按钮保持完整宽卷轴。
- 通过确认:标题与说明分行;取消/确认通过左右并排并使用紧凑九宫格皮肤。
- 拒绝确认:标题与说明分行;拒绝原因输入框保持现有结构;取消/确认拒绝左右并排并使用紧凑九宫格皮肤。
- 不改变拒绝原因必填校验、审核状态写入、反馈提示和弹窗关闭行为。
## 状态覆盖
MuMu 原生验收依次覆盖:
1. 待审核列表。
2. 审核说明。
3. 通过确认。
4. 通过完成与反馈提示。
5. 拒绝确认。
6. 空拒绝原因校验。
7. 拒绝完成与反馈提示。
8. 空数据。
9. 加载。
10. 错误与重新查看。
11. 无权限。
## 合同与验证
- 先补充失败合同,再修改组件和页面。
- `AppButton` 合同要求 `compact` 默认关闭,并固定紧凑九宫格素材与切片值。
- `AppDialog` 合同要求 `compactActions` 默认关闭、双按钮显式传递紧凑模式,并固定标题/说明独立布局。
- G10 视觉合同禁止列表卡和状态卡继续使用完整背景 `100% 100%` 拉伸。
- G10 视觉合同固定列表卡、状态卡、操作按钮和结果状态的九宫格规则与尺寸。
- 运行 AppButton、AppDialog、G08G10 流程、G09/G10 业务状态和 G10 全状态视觉合同。
- 每个可见状态只以 MuMu Android 原生截图验收,不使用浏览器截图代替。
- 不修改文案、模拟数据、状态枚举、路由、审核结果和交互流程。
- 不执行 Git 操作,不启用多代理。
@@ -0,0 +1,38 @@
# 全项目通用提示文案设计
## 目标
所有用户可见的输入提示、示例和辅助说明不得使用具体人物姓名或具体家族名称。页面模拟数据继续使用现有姓名,不纳入本次修改。
## 适用范围
纳入检查:
- `placeholder` 属性和表单配置中的 `placeholder` 字段。
- 带“例如”“比如”“如:”的用户示例文案。
- 紧邻输入框、用于说明填写格式的辅助文字。
排除检查:
- 成员目录、人物档案、申请记录、审核记录等模拟数据。
- 自动化测试填写的模拟输入值。
- 已录入数据在详情页或卡片中的展示。
## 本轮替换
- G08 关系输入:`例如:汤正华堂侄``例如:某某某堂侄`
- G08 关系辅助说明:`请以家谱中一位已知长辈为参照,例如:汤正华堂侄``请以家谱中一位已知长辈为参照,例如:某某某堂侄`
- G03 姓氏:`如:汤``请输入姓氏`
- G03 谱名:`如:汤氏家谱``请输入家谱名称`
- `TreeMemberForm` 首位成员姓名:`汤文远``请输入首位成员姓名`
- `TreeMemberForm` 当前成员:`汤文远``请选择当前成员`
其他不含具体姓名的格式示例保留,例如“长子、配偶”“1992年”“春节团圆”“家风家训”。
## 合同与验收
- 新增提示文案合同,只扫描 `.vue` 文件中含 `placeholder``例如``比如``如:` 的行,以及 G08 的关系辅助说明。
- 合同禁止上述提示上下文出现当前模拟人物姓名或具体家族名称,不扫描普通模拟数据行。
- 聚焦合同与受影响页面现有合同通过。
- MuMu 复核 G08 默认表单,关系输入和辅助说明必须显示“某某某堂侄”。
- 不执行 Git 操作,不启用多代理。
@@ -0,0 +1,180 @@
# 全项目响应式布局与装饰素材自适应设计
## 背景
当前项目共有 65 个 Vue 文件。静态扫描发现:
- 27 个文件包含固定 `height`
- 29 个文件包含 `background-size: 100% 100%` 或等价整图拉伸。
这些规则混合了两类完全不同的用途:导航栏、图标和最低触控目标需要稳定尺寸;弹窗、表单、卡片、列表、状态说明等内容容器必须由内容和屏幕决定尺寸。当前缺少统一契约,导致同一装饰素材在不同页面被重复拉伸,短内容产生大块空白,长内容被压缩或溢出。
本设计建立全项目唯一响应式规则,不再针对单页写专用高度。
## 总体原则
### 内容容量
- 内容容器不得使用固定 `height` 表达内容容量。
- 卡片、弹窗、表单面板、状态面板、列表项和正文区域由内容自然撑开。
- 允许使用 `min-height` 保证最小视觉基线或触控目标,但内容增加时必须继续增高。
- 只有受到视口限制的浮层允许 `max-height`;超过上限时在浮层内部滚动,不压缩正文和操作区。
- 页面主区域使用正常文档流;除 Toast、遮罩、必要悬浮反馈外,不用绝对定位补偿尺寸。
### 宽度与重排
- 页面内容区使用 `width: 100%``max-width: 100%``box-sizing: border-box`
- flex/grid 的可变文本列必须使用 `min-width: 0`
- 数据文本默认允许换行,并使用 `overflow-wrap: anywhere` 保护窄屏。
- 只有短、固定语义的操作按钮允许 `white-space: nowrap`;按钮皮肤和文字容量必须同时验证。
- 多列布局在紧凑宽度无法保证文字容量时改为单列或分行,不使用缩小字体规避。
### 高度与安全区
- `PageHeader``AppTabbar`、状态栏占位、图标、头像和媒体缩略图可以保留固定尺寸或 `aspect-ratio`
- 按钮可以保留最低触控高度,不得用固定高度截断多行标签。
- 页面最小高度使用 `min-height: 100vh`;底部操作同时考虑安全区。
- 键盘弹出、状态栏高度变化和短屏不能让主要操作移出可达区域。
## 装饰素材规则
### 可变内容表面
弹窗、内容卡、表单框、状态框、通知卡、人员卡等承载可变内容的装饰素材必须使用九宫格:
- 使用真实 PNG 作为 `border-image-source`
- 切片值和渲染边框宽度由唯一的素材档案维护。
- 四角、卷轴端头和边缘纹样不拉伸,只拉伸中间区域。
- 不允许在这些容器上使用 `background-size: 100% 100%`
### 固定比例素材
Logo、图标、头像、印章、插画、完整按钮图和媒体缩略图可以使用 `aspectFit``widthFix``contain` 或明确的 `aspect-ratio`
完整按钮图只有在按钮槽位比例与素材比例兼容时使用 `aspectFit`;窄按钮使用紧凑九宫格皮肤,不得缩成细条。
### 允许例外
分隔线、纯纹理层、固定比例的非内容装饰和明确允许裁切的背景可以继续使用拉伸或裁切,但必须登记到统一白名单并说明理由。页面不得自行新增例外。
## 单一所有者
### 素材档案
新增 `styles/adaptive-frame-profiles.scss` 作为九宫格素材的唯一所有者。每个档案包含:
- 档案名称。
- PNG 路径。
- `border-image-slice`
- `border-width` / `border-image-width`
- 是否使用 `fill`
首批档案至少覆盖:
- `auth-dialog`
- `genealogy-slip`
- `module-content`
- `module-field`
- `tree-panel`
- `tree-field`
- `notification-card`
- `profile-summary`
- `records-content`
- `records-field`
页面和组件只消费档案,不重复定义切片值。
### 响应式例外白名单
新增 `tests/responsive-layout-allowlist.json`,作为固定高度和整图拉伸例外的唯一所有者。每条记录必须包含:
- 文件路径。
- 选择器。
- 允许的属性。
- 固定尺寸或拉伸成立的具体原因。
旧例外路径不保留在其他测试或文档中;新增例外必须修改该文件和全局合同。
### 全局合同
新增 `tests/project-responsive-layout-contract.ps1` 扫描 `components/**/*.vue``pages/**/*.vue`
- 内容容器出现固定 `height` 且不在白名单时失败。
- 可变内容表面出现 `100% 100%` 整图拉伸且不在白名单时失败。
- 九宫格档案在页面重复定义切片值时失败。
- 需要换行的数据区域出现无理由 `nowrap` 时失败。
- 共享组件的默认响应式契约缺失时失败。
新增 `tests/responsive-layout-coverage.json` 作为分阶段覆盖清单。第一阶段先纳入公共组件;后续每完成一个模块,就把该模块全部 Vue 文件加入清单。已经纳入的文件不得移出,最终阶段要求清单与 `components/**/*.vue``pages/**/*.vue` 的实际文件集合完全一致,并删除所有仅用于迁移过程的覆盖缺口。该清单不是例外白名单,不能登记允许违规的理由。
## 共享组件设计
### AppDialog
- 删除 `.app-dialog``.app-dialog__content` 的固定 `520rpx` 最小高度。
- `a01-scroll-dialog-v3.png` 改为 `auth-dialog` 九宫格档案。
- 弹窗使用 `width: 650rpx; max-width: 100%`,外层保留视口安全间距,避免依赖额外的 CSS 函数兼容性。
- 内容高度由标题、正文、插槽和操作区自然撑开。
- 操作区使用正常间距,不再使用 `margin-top: auto` 制造固定空白。
- 弹窗使用视口最大高度;内容超出时内部滚动。
- 通过确认会自动收紧;拒绝原因出现后弹窗自动增高;极短屏才滚动。
### AppButton
- 保留默认完整宽按钮和显式 `compact` 九宫格变体。
- 使用 `min-height` 保证触控目标,不用固定高度截断标签。
- 紧凑按钮根据容器宽度保持四字操作容量。
### ModulePage
- `module-content``module-field` 表面改为统一九宫格档案。
- 模块页正文、状态卡和表单由内容撑开。
- 分隔线等固定非内容装饰可登记白名单。
### PageHeader 与 AppTabbar
- 作为结构性导航保留固定内容高度和安全区计算。
- 标题区保留 `min-width: 0`,长标题按既定规则缩放或换行,不影响左右操作区。
- 这些固定高度必须进入白名单并说明为导航结构,不允许页面复制其值。
## 模块迁移顺序
全项目迁移拆分为可独立验收的阶段,不在一次修改中混合 65 个页面:
1. **基础层**:素材档案、全局合同、白名单、AppDialog、AppButton、ModulePage。
2. **A/G 模块**:登录注册、家谱列表、创建、搜索、申请、审核、设置、字辈诗。
3. **T 模块**:树图、成员档案、添加/编辑关系、成员目录与状态页。
4. **F 模块**:动态、文章、相册和媒体上传。
5. **R/N/M 模块**:人物档案、消息通知、个人中心和设置页。
每个阶段必须满足:合同先失败、实现后通过、MuMu 关键页面通过,才能进入下一阶段。
## MuMu 响应式验证矩阵
视觉验收只使用 MuMu Android 原生画面。每个阶段至少验证三种 Android 逻辑视口:
- 紧凑屏:`360 × 640dp`
- 常规长屏:`360 × 800dp`
- 宽长屏:`412 × 915dp`
使用 MuMu 的 Android 分辨率/密度配置或 `adb shell wm size``wm density` 临时切换;每轮结束恢复用户当前配置。不得用浏览器截图代替。
每个页面至少覆盖默认、加载、空、错误和主要弹窗/表单校验状态;没有某状态的页面记录为不适用。
## 验收标准
- 短内容容器不出现由固定高度造成的大块空白。
- 长内容容器不拥挤、不压边、不遮挡操作区。
- 装饰四角、卷轴端头、边框纹样不变形。
- 文本放大、长姓名、长关系说明和错误文案不会被截断。
- 主要操作始终可见或可通过页面/浮层内部滚动到达。
- 三种 MuMu 视口均无水平滚动、非预期裁切或点击区域错位。
- 已通过页面的业务逻辑、路由、数据状态和文案不因布局迁移改变。
## 约束
- 不修改业务接口、状态枚举、模拟数据和页面路由。
- 不以缩小字体作为适配方案。
- 不新增页面专用固定高度补丁。
- 不保留旧拉伸规则作为回退路径。
- 不执行 Git 操作,不启用多代理。
+3 -7
View File
@@ -363,6 +363,8 @@ const prepareAgreement = () => showFeedback("协议页面准备中");
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.login-content {
display: flex;
flex: 1;
@@ -675,15 +677,9 @@ const prepareAgreement = () => showFeedback("协议页面准备中");
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
@include adaptive.adaptive-feedback-toast;
width: 590rpx;
min-height: 82rpx;
border: 16rpx solid transparent;
border-width: 16rpx 74rpx;
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png");
border-image-slice: 58 280 fill;
border-image-width: 16rpx 74rpx;
border-image-repeat: stretch;
background: transparent;
transform: translateX(-50%);
}
+3 -7
View File
@@ -218,6 +218,8 @@ const submitRegister = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.register-content {
display: flex;
flex: 1;
@@ -438,15 +440,9 @@ const submitRegister = () => {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
@include adaptive.adaptive-feedback-toast;
width: 590rpx;
min-height: 82rpx;
border: 16rpx solid transparent;
border-width: 16rpx 74rpx;
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png");
border-image-slice: 58 280 fill;
border-image-width: 16rpx 74rpx;
border-image-repeat: stretch;
background: transparent;
transform: translateX(-50%);
}
+3 -7
View File
@@ -252,6 +252,8 @@ const submitReset = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.reset-content {
display: flex;
flex: 1;
@@ -482,15 +484,9 @@ const submitReset = () => {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
@include adaptive.adaptive-feedback-toast;
width: 590rpx;
min-height: 82rpx;
border: 16rpx solid transparent;
border-width: 16rpx 74rpx;
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png");
border-image-slice: 58 280 fill;
border-image-width: 16rpx 74rpx;
border-image-repeat: stretch;
background: transparent;
transform: translateX(-50%);
}
+5 -3
View File
@@ -144,6 +144,8 @@ const openSection = (key) => {
};
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.family-page {
display: flex;
min-height: 100vh;
@@ -173,12 +175,12 @@ const openSection = (key) => {
font-size: 23rpx;
}
.feed-shortcuts {
@include adaptive.adaptive-scroll-button(secondary);
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
width: 100%;
min-height: 48px;
margin-top: 15rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png") center / 100% 100% no-repeat;
}
.feed-shortcut {
width: 100%;
@@ -196,6 +198,7 @@ const openSection = (key) => {
}
.feed-card,
.feed-state-card {
@include adaptive.adaptive-family-letter;
display: flex;
width: 100%;
min-height: 230rpx;
@@ -204,7 +207,6 @@ const openSection = (key) => {
justify-content: center;
margin-top: 17rpx;
box-sizing: border-box;
background: url("/static/assets/modules/family/transparent/f01-family-letter-card.png") center / 100% 100% no-repeat;
}
.feed-card__copy {
padding: 15% 9%;
@@ -259,11 +261,11 @@ const openSection = (key) => {
line-height: 1.45;
}
.feed-action {
@include adaptive.adaptive-scroll-button(primary);
width: 514rpx;
max-width: 100%;
min-height: 76rpx;
margin: 19rpx auto 0;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / 100% 100% no-repeat;
}
.feed-action text {
display: flex;
+4 -2
View File
@@ -80,6 +80,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.publish-page {
display: flex;
min-height: 100vh;
@@ -88,6 +90,7 @@ onUnmounted(() => {
}
.publish-page__header,
.publish-panel {
@include adaptive.adaptive-family-content;
z-index: 1;
}
.publish-panel {
@@ -96,7 +99,6 @@ onUnmounted(() => {
margin: 18rpx auto 0;
padding: 9%;
box-sizing: border-box;
background: url("/static/assets/modules/family/transparent/module-content-frame.png") center / 100% 100% no-repeat;
}
.publish-form > text,
.publish-result > text {
@@ -117,12 +119,12 @@ onUnmounted(() => {
line-height: 1.6;
}
.publish-field {
@include adaptive.adaptive-family-field;
display: flex;
min-height: 250rpx;
margin-top: 24rpx;
padding: 25rpx;
box-sizing: border-box;
background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat;
}
.publish-field textarea {
width: 100%;
+5 -3
View File
@@ -121,11 +121,13 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.feed-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.feed-detail-header, .feed-detail-loading, .feed-detail-content { z-index: 1; }
.feed-detail-loading { min-height: calc(100vh - 100rpx); }
.feed-detail-content { padding: 18rpx 24rpx 72rpx; }
.feed-article-card, .feed-comments-panel, .feed-comment-form, .feed-state-card { width: 100%; box-sizing: border-box; background: url("/static/assets/modules/family/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
.feed-article-card, .feed-comments-panel, .feed-comment-form, .feed-state-card { @include adaptive.adaptive-family-content; width: 100%; }
.feed-article-card { padding: 46rpx 48rpx 42rpx; }
.feed-article-card text, .feed-state-card > text { display: block; }
.feed-article-card__meta, .feed-article-card__author { color: $ink-muted; font-size: 22rpx; }
@@ -135,7 +137,7 @@ onUnmounted(() => {
.feed-comments-panel { margin-top: 18rpx; padding: 38rpx 38rpx 34rpx; }
.feed-comments-heading { display: flex; align-items: center; justify-content: space-between; color: $brand-red; font-size: 24rpx; font-weight: 700; }
.feed-comments-heading text:last-child { color: $ink-muted; font-size: 21rpx; font-weight: 400; }
.feed-comment-card { margin-top: 16rpx; padding: 20rpx 22rpx; background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
.feed-comment-card { @include adaptive.adaptive-family-field; margin-top: 16rpx; padding: 20rpx 22rpx; }
.feed-comment-card > view { display: flex; justify-content: space-between; gap: 18rpx; color: $ink-muted; font-size: 20rpx; }
.feed-comment-card > text { display: block; margin-top: 9rpx; color: $ink; font-size: 24rpx; line-height: 1.55; }
.feed-comments-empty { padding: 34rpx 10rpx 20rpx; text-align: center; }
@@ -143,7 +145,7 @@ onUnmounted(() => {
.feed-comments-empty text:first-child { color: $ink; font-size: 28rpx; font-weight: 700; }
.feed-comment-form { margin-top: 18rpx; padding: 38rpx 40rpx 42rpx; }
.feed-comment-form > text:first-child { color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 30rpx; font-weight: 700; }
.feed-comment-form textarea { width: auto; min-width: 0; min-height: 126rpx; margin-top: 16rpx; padding: 20rpx 22rpx; box-sizing: border-box; color: $ink; font-size: 24rpx; line-height: 1.55; background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
.feed-comment-form textarea { @include adaptive.adaptive-family-field; width: auto; min-width: 0; min-height: 126rpx; margin-top: 16rpx; padding: 20rpx 22rpx; color: $ink; font-size: 24rpx; line-height: 1.55; }
.feed-comment-error { display: block; margin-top: 10rpx; color: $brand-red; font-size: 22rpx; }
.feed-comment-form .app-button { margin-top: 22rpx; }
.feed-state-card { min-height: 340rpx; margin-top: 36rpx; padding: 78rpx 52rpx 50rpx; text-align: center; }
+5 -3
View File
@@ -89,19 +89,21 @@ const resetFilters = () => { keyword.value = ""; activeCategory.value = "全部"
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.article-list-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.article-list-header, .article-list-loading, .article-list-content { z-index: 1; }
.article-list-loading { min-height: calc(100vh - 100rpx); }
.article-list-content { padding: 18rpx 24rpx 72rpx; }
.article-search { min-height: 82rpx; padding: 12rpx 26rpx; box-sizing: border-box; background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
.article-search { @include adaptive.adaptive-family-field; min-height: 82rpx; padding: 12rpx 26rpx; }
.article-search input { width: 100%; min-height: 58rpx; color: $ink; font-size: 24rpx; }
.article-categories { width: 100%; margin-top: 16rpx; }
.article-categories__row { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 2rpx 4rpx 8rpx; }
.article-category { min-height: 58rpx; padding: 0 28rpx; color: $ink-muted; font-size: 23rpx; background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
.article-category { @include adaptive.adaptive-family-field; min-height: 58rpx; padding: 0 28rpx; color: $ink-muted; font-size: 23rpx; }
.article-category text { display: flex; min-height: 58rpx; align-items: center; }
.article-category--active { color: $brand-red; font-weight: 700; }
.article-list { display: flex; flex-direction: column; gap: 16rpx; margin-top: 10rpx; }
.article-card, .article-list-state-card { width: 100%; box-sizing: border-box; background: url("/static/assets/modules/family/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
.article-card, .article-list-state-card { @include adaptive.adaptive-family-content; width: 100%; }
.article-card { min-height: 196rpx; padding: 34rpx 46rpx 30rpx; }
.article-card > text { display: block; }
.article-card__category { color: $brand-red; font-size: 21rpx; font-weight: 700; letter-spacing: 2rpx; }
+3 -1
View File
@@ -91,11 +91,13 @@ onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer); });
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.article-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.article-detail-header, .article-detail-loading, .article-detail-content { z-index: 1; }
.article-detail-loading { min-height: calc(100vh - 100rpx); }
.article-detail-content { padding: 18rpx 24rpx 72rpx; }
.article-paper, .article-state-card { width: 100%; box-sizing: border-box; background: url("/static/assets/modules/family/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
.article-paper, .article-state-card { @include adaptive.adaptive-family-content; width: 100%; }
.article-paper { padding: 50rpx 48rpx 54rpx; }
.article-paper > text { display: block; }
.article-paper__category { color: $brand-red; font-size: 22rpx; font-weight: 700; letter-spacing: 3rpx; }
+4 -7
View File
@@ -220,6 +220,8 @@ onUnload(() => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.article-editor-page {
display: flex;
min-height: 100vh;
@@ -239,13 +241,8 @@ onUnload(() => {
}
.editor-panel,
.editor-result-card {
@include adaptive.adaptive-family-panel;
width: 100%;
border: 20rpx solid transparent;
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
border-image-slice: 72 fill;
border-image-width: 64rpx;
border-image-repeat: stretch;
box-sizing: border-box;
}
.editor-panel__body {
padding: 38rpx 34rpx 42rpx;
@@ -285,10 +282,10 @@ onUnload(() => {
font-weight: 700;
}
.editor-control {
@include adaptive.adaptive-family-field;
display: flex;
min-height: 82rpx;
align-items: center;
background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat;
}
.editor-control input,
.editor-control textarea {
+5 -3
View File
@@ -102,13 +102,15 @@ onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer); });
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.album-list-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.album-list-header, .album-list-loading, .album-list-content { z-index: 1; }
.album-list-loading { min-height: calc(100vh - 100rpx); }
.album-list-content { padding: 18rpx 24rpx 72rpx; }
.album-list-lead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8rpx 18rpx; min-height: 62rpx; padding: 0 20rpx; color: $ink-muted; font-size: 22rpx; background: url("/static/assets/modules/genealogy/transparent/section-divider.png") center / 100% 100% no-repeat; }
.album-list-lead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8rpx 18rpx; min-height: 62rpx; padding: 0 20rpx; color: $ink-muted; font-size: 22rpx; background: url("/static/assets/modules/genealogy/transparent/section-divider.png") center / 100% auto no-repeat; }
.album-list { display: flex; flex-direction: column; gap: 16rpx; margin-top: 16rpx; }
.album-card, .album-state-card { width: 100%; box-sizing: border-box; background: url("/static/assets/modules/family/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
.album-card, .album-state-card { @include adaptive.adaptive-family-content; width: 100%; }
.album-card { display: grid; grid-template-columns: minmax(150rpx, 0.7fr) minmax(0, 1.3fr); min-height: 210rpx; gap: 22rpx; padding: 30rpx 38rpx; }
.album-card__media { width: 100%; aspect-ratio: 4 / 3; align-self: center; }
.album-card__cover { display: block; width: 100%; height: 100%; }
@@ -125,7 +127,7 @@ onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer); });
.album-state-card .app-button { margin-top: 28rpx; }
.album-dialog-field { width: 100%; margin: 24rpx 0; text-align: left; }
.album-dialog-field > text:first-child { display: block; color: $ink; font-size: 23rpx; font-weight: 700; }
.album-dialog-field input { width: 100%; min-height: 76rpx; margin-top: 10rpx; padding: 0 22rpx; box-sizing: border-box; color: $ink; font-size: 24rpx; background: url("/static/assets/modules/family/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
.album-dialog-field input { @include adaptive.adaptive-family-field; width: 100%; min-height: 76rpx; margin-top: 10rpx; padding: 0 22rpx; color: $ink; font-size: 24rpx; }
.album-dialog-field > text:last-child { display: block; margin-top: 8rpx; color: $brand-red; font-size: 21rpx; }
@media (max-width: 340px) { .album-card { grid-template-columns: 130rpx minmax(0, 1fr); gap: 16rpx; padding-right: 30rpx; padding-left: 30rpx; } }
</style>
+3 -6
View File
@@ -158,6 +158,8 @@ onBackPress(() => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.album-detail-page {
display: flex;
min-height: 100vh;
@@ -255,14 +257,9 @@ onBackPress(() => {
}
.album-empty-state,
.album-expired-state {
@include adaptive.adaptive-family-panel;
width: 100%;
min-height: 430rpx;
box-sizing: border-box;
border: 20rpx solid transparent;
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
border-image-slice: 72 fill;
border-image-width: 64rpx;
border-image-repeat: stretch;
}
.album-state-card__body {
padding: 104rpx 40rpx 62rpx;
+4 -10
View File
@@ -342,6 +342,8 @@ onLoad((query) => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.media-upload-page {
display: flex;
min-height: 100vh;
@@ -356,6 +358,7 @@ onLoad((query) => {
padding: 20rpx 24rpx calc(48rpx + env(safe-area-inset-bottom));
}
.media-album-card {
@include adaptive.adaptive-family-panel;
display: grid;
min-height: 154rpx;
box-sizing: border-box;
@@ -363,11 +366,6 @@ onLoad((query) => {
gap: 20rpx;
align-items: center;
padding: 14rpx 20rpx;
border: 20rpx solid transparent;
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
border-image-slice: 72 fill;
border-image-width: 58rpx;
border-image-repeat: stretch;
}
.media-album-card__cover {
width: 132rpx;
@@ -604,15 +602,11 @@ onLoad((query) => {
}
.media-permission-card,
.media-success-card {
@include adaptive.adaptive-family-panel;
min-height: 410rpx;
margin-top: 24rpx;
box-sizing: border-box;
padding: 98rpx 46rpx 58rpx;
border: 20rpx solid transparent;
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
border-image-slice: 72 fill;
border-image-width: 64rpx;
border-image-repeat: stretch;
text-align: center;
}
.media-state-card__eyebrow {
+10 -20
View File
@@ -561,6 +561,8 @@ const openShortcut = (key) => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.genealogy-index {
display: flex;
min-height: 100vh;
@@ -603,11 +605,10 @@ const openShortcut = (key) => {
}
.current-slip {
@include adaptive.adaptive-genealogy-current-slip;
display: block;
min-height: 268rpx;
padding: 30rpx 34rpx 28rpx;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/current-slip-frame.png") center / 100% 100% no-repeat;
}
.current-seal {
@@ -618,7 +619,7 @@ const openShortcut = (key) => {
align-items: center;
justify-content: center;
margin-right: 24rpx;
background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / 100% 100% no-repeat;
background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / contain no-repeat;
color: #fff7e7;
}
.current-seal-title {
@@ -751,12 +752,11 @@ const openShortcut = (key) => {
}
.application-record {
@include adaptive.adaptive-genealogy-list-card;
display: flex;
min-height: 138rpx;
align-items: center;
box-sizing: border-box;
padding: 22rpx 26rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat;
}
.application-record + .application-record {
margin-top: 12rpx;
@@ -875,9 +875,9 @@ const openShortcut = (key) => {
}
.state-panel--error {
@include adaptive.adaptive-genealogy-state-panel;
min-height: 1120rpx;
padding: 0;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png") center / 100% 100% no-repeat;
}
.error-panel__content {
@@ -933,8 +933,8 @@ const openShortcut = (key) => {
}
.empty-panel {
@include adaptive.adaptive-genealogy-state-panel;
min-height: 1120rpx;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png") center / 100% 100% no-repeat;
}
.empty-panel__content {
@@ -954,7 +954,7 @@ const openShortcut = (key) => {
height: 184rpx;
align-items: center;
justify-content: center;
background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / 100% 100% no-repeat;
background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / contain no-repeat;
}
.empty-seal__text {
@@ -1067,15 +1067,10 @@ const openShortcut = (key) => {
background: rgba(34, 20, 12, 0.68);
}
.add-dialog {
@include adaptive.adaptive-g01-add-sheet;
width: 100%;
min-height: 780rpx;
max-height: calc(100vh - 80rpx);
box-sizing: border-box;
border: 1px solid transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png");
border-image-slice: 220 0 1 0 fill;
border-image-width: 118rpx 0 1rpx;
border-image-repeat: stretch;
}
.add-dialog__content {
display: flex;
@@ -1143,16 +1138,11 @@ const openShortcut = (key) => {
background: rgba(34, 20, 12, 0.58);
}
.genealogy-switcher {
@include adaptive.adaptive-g01-switcher;
width: 670rpx;
max-width: 100%;
min-height: 600rpx;
max-height: calc(100vh - 120rpx);
box-sizing: border-box;
border: 1px solid transparent;
border-image-source: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png");
border-image-slice: 300 260 360 260 fill;
border-image-width: 110rpx 48rpx 132rpx 48rpx;
border-image-repeat: stretch;
}
.genealogy-switcher__content {
display: grid;
+4 -4
View File
@@ -24,7 +24,7 @@
<input
v-model="createForm.surname"
maxlength="4"
placeholder="如:汤"
placeholder="请输入姓氏"
placeholder-class="placeholder"
@input="clearFieldError('surname')"
/>
@@ -37,7 +37,7 @@
<input
v-model="createForm.name"
maxlength="24"
placeholder="如:汤氏家谱"
placeholder="请输入家谱名称"
placeholder-class="placeholder"
@input="clearFieldError('name')"
/>
@@ -407,6 +407,7 @@ const enterOverview = () =>
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.flow-page {
display: flex;
min-height: 100vh;
@@ -420,9 +421,8 @@ const enterOverview = () =>
}
.create-flow-panel {
@include adaptive.adaptive-genealogy-state-panel;
min-height: 1000rpx;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.create-flow-panel__content {
+3 -4
View File
@@ -304,6 +304,7 @@ const applyToJoin = () =>
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.overview-page {
display: flex;
min-height: 100vh;
@@ -314,12 +315,11 @@ const applyToJoin = () =>
z-index: 3;
}
.overview-surface {
@include adaptive.adaptive-g05-overview-surface;
z-index: 2;
width: 100%;
min-height: min(483px, calc(100vw * 1.3422));
flex: 0 0 auto;
background: url("/static/assets/modules/genealogy/opaque/g05-overview-surface.png")
center / 100% 100% no-repeat;
}
.overview-surface--state {
display: flex;
@@ -437,13 +437,12 @@ const applyToJoin = () =>
font-weight: 700;
}
.overview-state {
@include adaptive.adaptive-genealogy-state-panel;
display: flex;
width: 100%;
min-height: 0;
align-items: center;
justify-content: center;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
text-align: center;
}
.overview-state__content {
+5 -52
View File
@@ -5,15 +5,6 @@
<view class="search-page__header"><PageHeader title="加入家谱" /></view>
<view class="search-page__content">
<view class="search-title-strip">
<image
class="search-title-strip__seal"
src="/static/assets/foundation/transparent/brand-seal.png"
mode="aspectFit"
/>
<text class="search-title-strip__text">加入家谱</text>
</view>
<view class="mode-tabs">
<view
class="mode-tab"
@@ -439,6 +430,7 @@ const confirmInvite = () =>
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.search-page {
display: flex;
min-height: 100vh;
@@ -454,32 +446,6 @@ const confirmInvite = () =>
padding: 30rpx 24rpx calc(58rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.search-title-strip {
display: grid;
width: 100%;
height: 138rpx;
grid-template-columns: 156rpx 1fr 170rpx;
align-items: center;
background: url("/static/assets/modules/genealogy/opaque/g06-search-title-strip.png")
center / 100% 100% no-repeat;
}
.search-title-strip__seal {
z-index: 1;
width: 72rpx;
height: 84rpx;
margin-left: 52rpx;
}
.search-title-strip__text {
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
color: #f5d596;
font-family: "STKaiti", "KaiTi", serif;
font-size: 39rpx;
font-weight: 700;
letter-spacing: 5rpx;
}
.mode-tabs {
display: flex;
width: 500rpx;
@@ -516,11 +482,10 @@ const confirmInvite = () =>
margin-top: 32rpx;
}
.search-field {
@include adaptive.adaptive-g06-search-field;
display: grid;
width: 430rpx;
min-height: 95rpx;
background: url("/static/assets/modules/genealogy/opaque/g06-search-input-wide.png")
center / 100% 100% no-repeat;
}
.search-input {
z-index: 1;
@@ -547,13 +512,12 @@ const confirmInvite = () =>
font-size: 21rpx;
}
.search-action {
@include adaptive.adaptive-g06-search-action;
display: flex;
width: 200rpx;
min-height: 88rpx;
align-items: center;
justify-content: center;
background: url("/static/assets/modules/genealogy/opaque/g06-search-button.png")
center / 100% 100% no-repeat;
}
.search-action__label {
z-index: 1;
@@ -665,11 +629,10 @@ const confirmInvite = () =>
font-weight: 700;
}
.result-card {
@include adaptive.adaptive-genealogy-list-card;
min-height: 314rpx;
box-sizing: border-box;
padding: 28rpx 30rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.result-card__content {
z-index: 1;
@@ -748,11 +711,10 @@ const confirmInvite = () =>
line-height: 35rpx;
}
.invite-field {
@include adaptive.adaptive-g06-search-field;
display: grid;
flex: 1;
min-height: 95rpx;
background: url("/static/assets/modules/genealogy/opaque/g06-search-input-wide.png")
center / 100% 100% no-repeat;
}
.invite-input {
z-index: 1;
@@ -792,15 +754,6 @@ const confirmInvite = () =>
padding-right: 20rpx;
padding-left: 20rpx;
}
.search-title-strip__seal {
margin-left: 44rpx;
}
.search-title-strip {
grid-template-columns: 138rpx 1fr 148rpx;
}
.search-title-strip__text {
font-size: 37rpx;
}
.search-field {
width: 430rpx;
}
+5 -6
View File
@@ -40,7 +40,7 @@
<text>与家谱关系</text
><input
v-model="form.relation"
placeholder="例如:汤正华堂侄"
placeholder="例如:某某某堂侄"
placeholder-class="join-placeholder"
@input="clearFieldError('relation')"
/>
@@ -49,7 +49,7 @@
fieldErrors.relation
}}</text>
<text class="relation-help"
>请以家谱中一位已知长辈为参照例如汤正华堂侄</text
>请以家谱中一位已知长辈为参照例如某某某堂侄</text
>
<view class="join-field join-field--message">
<text>{{ sourceContract.thirdFieldLabel }}</text
@@ -224,6 +224,7 @@ const completeFlow = () =>
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.join-page {
display: flex;
min-height: 100vh;
@@ -234,14 +235,13 @@ const completeFlow = () =>
z-index: 3;
}
.join-panel {
@include adaptive.adaptive-genealogy-state-panel;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(590px, calc((100vw - 16px) * 1.337));
margin: 22rpx auto 0;
padding: 70rpx 8%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.join-form {
min-width: 0;
@@ -271,11 +271,10 @@ const completeFlow = () =>
line-height: 1.6;
}
.join-field {
@include adaptive.adaptive-genealogy-form-field;
display: grid;
min-height: 92rpx;
margin-top: 18rpx;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.join-form__previous {
display: block;
+10 -9
View File
@@ -218,6 +218,8 @@ const toSearch = () =>
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.application-page {
display: flex;
min-height: 100vh;
@@ -248,20 +250,19 @@ const toSearch = () =>
font-size: 24rpx;
}
.application-card {
@include adaptive.adaptive-genealogy-list-card;
display: grid;
width: 100%;
min-height: 228rpx;
margin-bottom: 18rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
min-height: 210rpx;
margin-bottom: 12rpx;
}
.application-card__body {
display: grid;
min-width: 0;
min-height: 228rpx;
min-height: 174rpx;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12rpx 18rpx;
padding: 42rpx 52rpx 34rpx 64rpx;
gap: 8rpx 14rpx;
padding: 26rpx 28rpx 22rpx 40rpx;
box-sizing: border-box;
}
.application-card__name {
@@ -332,8 +333,10 @@ const toSearch = () =>
color: $brand-red;
font-size: 23rpx;
font-weight: 700;
white-space: nowrap;
}
.application-state-card {
@include adaptive.adaptive-genealogy-list-card;
display: flex;
width: 100%;
min-height: 228rpx;
@@ -342,8 +345,6 @@ const toSearch = () =>
margin-top: 80rpx;
padding: 48rpx 12%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.application-state-card__copy {
display: flex;
+36 -75
View File
@@ -32,18 +32,17 @@
<text class="application-card__time">{{ item.appliedAt }}</text>
<text class="application-card__relation">{{ item.relation }}</text>
<view v-if="item.status === 'PENDING'" class="review-actions">
<view class="review-action" @click="confirmAudit(item, false)">
<image
src="/static/assets/foundation/transparent/a01-secondary-button-v2.png"
mode="aspectFit"
/><text>拒绝申请</text>
</view>
<view class="review-action" @click="confirmAudit(item, true)">
<image
src="/static/assets/foundation/transparent/a01-primary-button-v2.png"
mode="aspectFit"
/><text>通过申请</text>
</view>
<AppButton
compact
type="secondary"
label="拒绝申请"
@click="confirmAudit(item, false)"
/>
<AppButton
compact
label="通过申请"
@click="confirmAudit(item, true)"
/>
</view>
<view
v-else
@@ -54,14 +53,6 @@
: 'review-result--rejected'
"
>
<image
:src="
item.status === 'APPROVED'
? '/static/assets/foundation/transparent/a01-scroll-primary-v3.png'
: '/static/assets/foundation/transparent/a01-scroll-secondary-v3.png'
"
mode="aspectFit"
/>
<text class="application-card__status">{{
item.status === "APPROVED" ? "已通过" : "已拒绝"
}}</text>
@@ -81,16 +72,12 @@
<text>{{ stateCopy }}</text>
</view>
</view>
<view
<AppButton
v-if="reviewState === 'error'"
class="review-page__retry"
label="重新查看"
@click="loadApplications({ genealogyId })"
>
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/><text>重新查看</text>
</view>
/>
</view>
<AppDialog
@@ -117,6 +104,7 @@
: '确认拒绝'
"
:show-cancel="!!confirmation"
compact-actions
@confirm="helpVisible ? closeDialog() : applyAudit()"
@cancel="closeDialog"
@close="closeDialog"
@@ -146,6 +134,7 @@
import { computed, ref } from "vue";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import AppDialog from "@/components/AppDialog.vue";
import AppButton from "@/components/AppButton.vue";
import AppLoading from "@/components/AppLoading.vue";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
@@ -271,6 +260,8 @@ const showHelp = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.review-page {
display: flex;
min-height: 100vh;
@@ -282,7 +273,7 @@ const showHelp = () => {
}
.rejection-field { width: 100%; margin: 20rpx 0; text-align: left; }
.rejection-field > text:first-child { display: block; color: $ink; font-size: 23rpx; font-weight: 700; }
.rejection-field textarea { width: 100%; min-height: 110rpx; margin-top: 10rpx; padding: 18rpx; box-sizing: border-box; color: $ink; font-size: 23rpx; line-height: 1.55; background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png") center / 100% 100% no-repeat; }
.rejection-field textarea { @include adaptive.adaptive-genealogy-form-field; width: 100%; min-height: 110rpx; margin-top: 10rpx; padding: 18rpx; color: $ink; font-size: 23rpx; line-height: 1.55; }
.rejection-field__error { display: block; margin-top: 8rpx; color: $brand-red; font-size: 21rpx; }
.review-content {
z-index: 1;
@@ -308,19 +299,19 @@ const showHelp = () => {
font-size: 24rpx;
}
.application-card {
@include adaptive.adaptive-genealogy-list-card;
width: 100%;
min-height: 228rpx;
margin-bottom: 18rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat;
min-height: 218rpx;
margin-bottom: 12rpx;
}
.application-card__body {
display: grid;
width: 100%;
min-height: 228rpx;
min-height: 182rpx;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-rows: auto auto auto;
gap: 12rpx 0;
padding: 44rpx 52rpx 29rpx 64rpx;
gap: 8rpx 0;
padding: 28rpx 28rpx 22rpx 40rpx;
box-sizing: border-box;
}
.application-card__name {
@@ -347,7 +338,7 @@ const showHelp = () => {
display: block;
grid-column: 1 / span 2;
grid-row: 2;
margin-top: 15rpx;
margin-top: 8rpx;
color: $ink-muted;
font-size: 24rpx;
line-height: 1.35;
@@ -359,33 +350,12 @@ const showHelp = () => {
justify-self: end;
gap: 12rpx;
}
.review-action {
display: grid;
.review-actions .app-button {
width: 148rpx;
min-height: 68rpx;
}
.review-action image {
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.review-action text {
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
color: $ink;
font-family: "Microsoft YaHei", sans-serif;
font-size: 24rpx;
font-weight: 700;
letter-spacing: 1rpx;
}
.review-action:last-child text {
color: #fff9ed;
}
.review-result {
@include adaptive.adaptive-scroll-button(primary);
display: grid;
grid-column: 2 / 4;
grid-row: 3;
@@ -393,11 +363,6 @@ const showHelp = () => {
width: 308rpx;
min-height: 68rpx;
}
.review-result image {
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.application-card__status {
z-index: 1;
display: flex;
@@ -412,14 +377,20 @@ const showHelp = () => {
.review-result--approved .application-card__status {
color: #fff9ed;
}
.review-result--approved {
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png");
}
.review-result--rejected .application-card__status {
color: $ink;
}
.review-result--rejected {
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png");
}
.review-state-card {
@include adaptive.adaptive-genealogy-list-card;
width: 100%;
min-height: 228rpx;
margin-top: 70rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat;
}
.review-state-card__copy {
display: flex;
@@ -446,18 +417,9 @@ const showHelp = () => {
width: 420rpx;
min-height: 82rpx;
margin: 32rpx auto 0;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat;
}
.review-page__retry text {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #fff9ed;
font-size: 26rpx;
font-weight: 700;
}
.review-feedback {
@include adaptive.adaptive-feedback-toast;
position: fixed;
z-index: 40;
top: 140rpx;
@@ -468,7 +430,6 @@ const showHelp = () => {
align-items: center;
justify-content: center;
padding: 0 36rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png") center / 100% 100% no-repeat;
transform: translateX(-50%);
}
.review-feedback text {
+3 -4
View File
@@ -209,6 +209,7 @@ const saveSettings = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.settings-page {
display: flex;
min-height: 100vh;
@@ -219,14 +220,13 @@ const saveSettings = () => {
z-index: 3;
}
.settings-panel {
@include adaptive.adaptive-genealogy-state-panel;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(620px, calc((100vw - 16px) * 1.43));
margin: 18rpx auto 0;
padding: 70rpx 8%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.settings-panel > .app-loading {
grid-area: 1 / 1 / -1 / -1;
@@ -259,11 +259,10 @@ const saveSettings = () => {
line-height: 1.55;
}
.settings-field {
@include adaptive.adaptive-genealogy-form-field;
display: grid;
min-height: 92rpx;
margin-top: 17rpx;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.settings-field > text {
z-index: 1;
+4 -6
View File
@@ -237,6 +237,7 @@ const savePoems = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.poem-page {
display: flex;
min-height: 100vh;
@@ -247,14 +248,13 @@ const savePoems = () => {
z-index: 3;
}
.poem-panel {
@include adaptive.adaptive-genealogy-state-panel;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(650px, calc((100vw - 16px) * 1.5));
margin: 18rpx auto 0;
padding: 76rpx 8%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.poem-panel > .app-loading {
grid-area: 1 / 1 / -1 / -1;
@@ -289,13 +289,12 @@ const savePoems = () => {
margin-top: 18rpx;
}
.poem-row {
@include adaptive.adaptive-genealogy-form-field;
display: grid;
min-height: 72rpx;
margin-top: 10rpx;
grid-template-columns: 48% auto 1fr auto;
align-items: center;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.poem-row__number {
z-index: 1;
@@ -347,11 +346,10 @@ const savePoems = () => {
letter-spacing: 2rpx;
}
.poem-field {
@include adaptive.adaptive-genealogy-form-field;
display: grid;
min-height: 118rpx;
margin-top: 22rpx;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.poem-field > text {
z-index: 1;
+3 -1
View File
@@ -165,6 +165,8 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.notice-page {
display: flex;
min-height: 100vh;
@@ -185,9 +187,9 @@ onUnmounted(() => {
gap: 18rpx;
}
.notice-card {
@include adaptive.adaptive-notification-content;
width: 100%;
min-height: 220rpx;
background: url("/static/assets/modules/notification/transparent/n01-notice-card.png") center / 100% 100% no-repeat;
}
.notice-card__copy {
display: flex;
+3 -1
View File
@@ -93,10 +93,12 @@ onUnmounted(() => toastTimer && clearTimeout(toastTimer));
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.notice-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.page-layer { z-index: 1; }
.notice-content { flex: 1; padding: 28rpx 30rpx 72rpx; }
.paper-panel { background: url("/static/assets/modules/notification/transparent/n01-notice-card.png") center / 100% 100% no-repeat; }
.paper-panel { @include adaptive.adaptive-notification-content; }
.notice-card { min-height: 430rpx; padding: 54rpx 52rpx; box-sizing: border-box; }
.notice-meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8rpx 20rpx; color: $ink-muted; font-size: 22rpx; }
.notice-meta .is-unread { color: $brand-red; font-weight: 700; }
+4 -3
View File
@@ -177,6 +177,8 @@ const openItem = (item) => uni.navigateTo({ url: item.url });
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.profile-page {
display: flex;
min-height: 100vh;
@@ -192,11 +194,10 @@ const openItem = (item) => uni.navigateTo({ url: item.url });
padding: 26rpx 30rpx 190rpx;
}
.profile-hero {
@include adaptive.adaptive-profile-summary;
display: grid;
width: 100%;
min-height: 252rpx;
box-sizing: border-box;
background: url("/static/assets/modules/profile/transparent/m01-profile-summary-card.png") center / 100% 100% no-repeat;
}
.profile-hero__content {
z-index: 1;
@@ -284,10 +285,10 @@ const openItem = (item) => uni.navigateTo({ url: item.url });
letter-spacing: 2rpx;
}
.profile-scroll-notice {
@include adaptive.adaptive-scroll-button(primary);
width: 88%;
min-height: 112rpx;
margin: 22rpx auto 0;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / 100% 100% no-repeat;
}
.profile-scroll-notice__copy {
display: flex;
+3 -1
View File
@@ -55,10 +55,12 @@ onUnmounted(() => clearTimeout(timer));
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.profile-edit-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.page-layer { z-index: 1; }
.page-content { flex: 1; padding: 26rpx 30rpx 70rpx; }
.profile-avatar-card, .form-panel { background: var(--module-field-asset, url("/static/assets/modules/profile/transparent/module-field-frame.png")) center / 100% 100% no-repeat; }
.profile-avatar-card, .form-panel { @include adaptive.adaptive-profile-field; }
.profile-avatar-card { display: grid; grid-template-columns: 92rpx minmax(0,1fr) auto; align-items: center; gap: 20rpx; min-height: 150rpx; padding: 24rpx 34rpx; box-sizing: border-box; }
.avatar-seal { width: 82rpx; height: auto; max-height: 92rpx; aspect-ratio: 82 / 92; }
.avatar-copy { min-width: 0; }
+3 -1
View File
@@ -43,5 +43,7 @@ onUnmounted(() => clearTimeout(timer));
</script>
<style scoped lang="scss">
.security-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.security-summary{min-height:190rpx;padding:42rpx 48rpx;box-sizing:border-box;background:url("/static/assets/modules/profile/transparent/m01-profile-summary-card.png") center/100% 100% no-repeat;text-align:center}.security-summary text{display:block;overflow-wrap:anywhere}.security-summary text:first-child{color:$ink;font-family:STKaiti,KaiTi,serif;font-size:34rpx;font-weight:700}.security-summary text:last-child{margin-top:12rpx;color:$ink-muted;font-size:23rpx;line-height:1.55}.security-list{margin-top:22rpx}.security-row{display:grid;grid-template-columns:56rpx minmax(0,1fr) 34rpx;min-height:110rpx;align-items:center;gap:18rpx;padding:16rpx 26rpx;box-sizing:border-box;background:url("/static/assets/modules/profile/transparent/module-field-frame.png") center/100% 100% no-repeat}.security-row+ .security-row{margin-top:12rpx}.security-row>image{width:50rpx;height:50rpx}.security-row .chevron{width:30rpx;height:30rpx}.security-row view{min-width:0}.security-row text{display:block;overflow-wrap:anywhere}.security-row text:first-child{color:$ink;font-size:25rpx;font-weight:700}.security-row text:last-child{margin-top:6rpx;color:$ink-muted;font-size:21rpx}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.security-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.security-summary{@include adaptive.adaptive-profile-summary;min-height:190rpx;padding:42rpx 48rpx;text-align:center}.security-summary text{display:block;overflow-wrap:anywhere}.security-summary text:first-child{color:$ink;font-family:STKaiti,KaiTi,serif;font-size:34rpx;font-weight:700}.security-summary text:last-child{margin-top:12rpx;color:$ink-muted;font-size:23rpx;line-height:1.55}.security-list{margin-top:22rpx}.security-row{@include adaptive.adaptive-profile-field;display:grid;grid-template-columns:56rpx minmax(0,1fr) 34rpx;min-height:110rpx;align-items:center;gap:18rpx;padding:16rpx 26rpx}.security-row+ .security-row{margin-top:12rpx}.security-row>image{width:50rpx;height:50rpx}.security-row .chevron{width:30rpx;height:30rpx}.security-row view{min-width:0}.security-row text{display:block;overflow-wrap:anywhere}.security-row text:first-child{color:$ink;font-size:25rpx;font-weight:700}.security-row text:last-child{margin-top:6rpx;color:$ink-muted;font-size:21rpx}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}}
</style>
+2 -1
View File
@@ -55,5 +55,6 @@ onUnmounted(() => clearTimeout(timer));
</script>
<style scoped lang="scss">
.password-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.security-tip,.form-panel{background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.security-tip{min-height:170rpx;padding:38rpx 44rpx;box-sizing:border-box;text-align:center}.security-tip text{display:block}.security-tip text:first-child{color:$ink;font-size:32rpx;font-weight:700}.security-tip text:last-child{margin-top:10rpx;color:$ink-muted;font-size:22rpx;line-height:1.55}.form-panel{margin-top:20rpx;padding:22rpx 34rpx 30rpx;box-sizing:border-box}.field-block+.field-block{margin-top:6rpx}.form-row{display:grid;grid-template-columns:142rpx minmax(0,1fr) 74rpx;min-height:92rpx;align-items:center;gap:12rpx;border-bottom:1px solid rgba(181,137,63,.42)}.form-row>text{color:$ink;font-size:23rpx;font-weight:700}.form-row input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:23rpx}.password-toggle{display:flex;min-height:68rpx;align-items:center;justify-content:flex-end;color:$brand-red;font-size:21rpx}.field-error{display:block;padding-top:7rpx;color:#b42318;font-size:20rpx;line-height:1.4;text-align:right}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}.form-row{grid-template-columns:120rpx minmax(0,1fr) 64rpx;gap:8rpx}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.password-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.security-tip,.form-panel{@include adaptive.adaptive-profile-content}.security-tip{min-height:170rpx;padding:38rpx 44rpx;text-align:center}.security-tip text{display:block}.security-tip text:first-child{color:$ink;font-size:32rpx;font-weight:700}.security-tip text:last-child{margin-top:10rpx;color:$ink-muted;font-size:22rpx;line-height:1.55}.form-panel{margin-top:20rpx;padding:22rpx 34rpx 30rpx}.field-block+.field-block{margin-top:6rpx}.form-row{display:grid;grid-template-columns:142rpx minmax(0,1fr) 74rpx;min-height:92rpx;align-items:center;gap:12rpx;border-bottom:1px solid rgba(181,137,63,.42)}.form-row>text{color:$ink;font-size:23rpx;font-weight:700}.form-row input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:23rpx}.password-toggle{display:flex;min-height:68rpx;align-items:center;justify-content:flex-end;color:$brand-red;font-size:21rpx}.field-error{display:block;padding-top:7rpx;color:#b42318;font-size:20rpx;line-height:1.4;text-align:right}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}.form-row{grid-template-columns:120rpx minmax(0,1fr) 64rpx;gap:8rpx}}
</style>
+2 -1
View File
@@ -51,5 +51,6 @@ onUnmounted(() => { clearInterval(countdownTimer); clearTimeout(stateTimer); cle
</script>
<style scoped lang="scss">
.phone-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.current-phone,.form-panel{background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.current-phone{min-height:210rpx;padding:40rpx 48rpx;box-sizing:border-box;text-align:center}.current-phone text{display:block}.current-phone text:first-child{color:$ink-muted;font-size:22rpx}.current-phone text:nth-child(2){margin-top:8rpx;color:$ink;font-size:38rpx;font-weight:700;letter-spacing:3rpx}.current-phone text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.5}.form-panel{margin-top:22rpx;padding:24rpx 34rpx 32rpx;box-sizing:border-box}.form-row{display:grid;grid-template-columns:130rpx minmax(0,1fr);min-height:94rpx;align-items:center;gap:16rpx;border-bottom:1px solid rgba(181,137,63,.42)}.code-row{grid-template-columns:130rpx minmax(0,1fr) auto}.form-row>text{color:$ink;font-size:23rpx;font-weight:700}.form-row input{width:auto;min-width:0;min-height:70rpx;color:$ink;font-size:23rpx}.code-action{display:flex;min-width:126rpx;min-height:70rpx;align-items:center;justify-content:flex-end;color:$brand-red;font-size:21rpx}.field-error{display:block;padding-top:7rpx;color:#b42318;font-size:20rpx;text-align:right}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}.form-row{grid-template-columns:112rpx minmax(0,1fr)}.code-row{grid-template-columns:112rpx minmax(0,1fr);}.code-action{grid-column:2;justify-content:flex-start}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.phone-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.current-phone,.form-panel{@include adaptive.adaptive-profile-content}.current-phone{min-height:210rpx;padding:40rpx 48rpx;text-align:center}.current-phone text{display:block}.current-phone text:first-child{color:$ink-muted;font-size:22rpx}.current-phone text:nth-child(2){margin-top:8rpx;color:$ink;font-size:38rpx;font-weight:700;letter-spacing:3rpx}.current-phone text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.5}.form-panel{margin-top:22rpx;padding:24rpx 34rpx 32rpx}.form-row{display:grid;grid-template-columns:130rpx minmax(0,1fr);min-height:94rpx;align-items:center;gap:16rpx;border-bottom:1px solid rgba(181,137,63,.42)}.code-row{grid-template-columns:130rpx minmax(0,1fr) auto}.form-row>text{color:$ink;font-size:23rpx;font-weight:700}.form-row input{width:auto;min-width:0;min-height:70rpx;color:$ink;font-size:23rpx}.code-action{display:flex;min-width:126rpx;min-height:70rpx;align-items:center;justify-content:flex-end;color:$brand-red;font-size:21rpx}.field-error{display:block;padding-top:7rpx;color:#b42318;font-size:20rpx;text-align:right}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}.form-row{grid-template-columns:112rpx minmax(0,1fr)}.code-row{grid-template-columns:112rpx minmax(0,1fr);}.code-action{grid-column:2;justify-content:flex-start}}
</style>
+2 -1
View File
@@ -44,5 +44,6 @@ const contactSupport = () => uni.navigateTo({ url: "/pages/profile/m07-feedback"
</script>
<style scoped lang="scss">
.help-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:24rpx 28rpx 72rpx}.search-box{display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:86rpx;align-items:center;gap:16rpx;padding:0 30rpx;background:url("/static/assets/modules/profile/transparent/module-field-frame.png") center/100% 100% no-repeat}.search-box input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:23rpx}.search-box text{color:$ink-muted;font-size:20rpx}.category-scroll{width:100%;margin-top:18rpx}.category-row{display:flex;width:max-content;gap:12rpx;padding:2rpx}.category-chip{display:flex;min-width:100rpx;min-height:64rpx;align-items:center;justify-content:center;padding:0 22rpx;box-sizing:border-box;color:$ink-muted;font-size:22rpx}.category-chip.active{background:url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png") center/100% 100% no-repeat;color:$brand-red;font-weight:700}.question-list{display:flex;flex-direction:column;gap:14rpx;margin-top:18rpx}.question-card,.empty-card{background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.question-card{min-height:104rpx;padding:24rpx 34rpx;box-sizing:border-box}.question-heading{display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:58rpx;align-items:center;gap:18rpx}.question-heading text:first-child{color:$ink;font-size:24rpx;font-weight:700;overflow-wrap:anywhere}.question-heading text:last-child{color:$brand-red;font-size:20rpx}.answer{display:block;padding:14rpx 4rpx 6rpx;border-top:1px solid rgba(181,137,63,.32);color:$ink-muted;font-size:22rpx;line-height:1.65;overflow-wrap:anywhere}.empty-card{min-height:220rpx;padding:58rpx 44rpx;box-sizing:border-box;text-align:center}.empty-card text{display:block}.empty-card text:first-child{color:$ink;font-size:29rpx;font-weight:700}.empty-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:22rpx;line-height:1.5}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:20rpx;padding-left:20rpx}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.help-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:24rpx 28rpx 72rpx}.search-box{@include adaptive.adaptive-profile-field;display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:86rpx;align-items:center;gap:16rpx;padding:0 30rpx}.search-box input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:23rpx}.search-box text{color:$ink-muted;font-size:20rpx}.category-scroll{width:100%;margin-top:18rpx}.category-row{display:flex;width:max-content;gap:12rpx;padding:2rpx}.category-chip{display:flex;min-width:100rpx;min-height:64rpx;align-items:center;justify-content:center;padding:0 22rpx;box-sizing:border-box;color:$ink-muted;font-size:22rpx}.category-chip.active{@include adaptive.adaptive-scroll-button(secondary);color:$brand-red;font-weight:700}.question-list{display:flex;flex-direction:column;gap:14rpx;margin-top:18rpx}.question-card,.empty-card{@include adaptive.adaptive-profile-content}.question-card{min-height:104rpx;padding:24rpx 34rpx}.question-heading{display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:58rpx;align-items:center;gap:18rpx}.question-heading text:first-child{color:$ink;font-size:24rpx;font-weight:700;overflow-wrap:anywhere}.question-heading text:last-child{color:$brand-red;font-size:20rpx}.answer{display:block;padding:14rpx 4rpx 6rpx;border-top:1px solid rgba(181,137,63,.32);color:$ink-muted;font-size:22rpx;line-height:1.65;overflow-wrap:anywhere}.empty-card{min-height:220rpx;padding:58rpx 44rpx;text-align:center}.empty-card text{display:block}.empty-card text:first-child{color:$ink;font-size:29rpx;font-weight:700}.empty-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:22rpx;line-height:1.5}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:20rpx;padding-left:20rpx}}
</style>
+2 -1
View File
@@ -48,5 +48,6 @@ onUnmounted(() => clearTimeout(timer));
</script>
<style scoped lang="scss">
.feedback-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:26rpx 30rpx 72rpx}.lead{display:block;color:$ink-muted;font-size:23rpx;line-height:1.5;text-align:center}.type-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14rpx;margin-top:20rpx}.type-chip{display:flex;min-height:76rpx;align-items:center;justify-content:center;padding:10rpx 16rpx;box-sizing:border-box;background:url("/static/assets/modules/profile/transparent/module-field-frame.png") center/100% 100% no-repeat;color:$ink-muted;font-size:22rpx;text-align:center}.type-chip.active{color:$brand-red;font-weight:700;filter:saturate(1.2)}.form-panel{margin-top:20rpx;padding:30rpx 34rpx;background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.form-panel textarea{display:block;width:auto;min-width:0;min-height:210rpx;color:$ink;font-size:24rpx;line-height:1.65}.counter{display:block;color:$ink-muted;font-size:20rpx;text-align:right}.contact-row{display:grid;grid-template-columns:130rpx minmax(0,1fr);min-height:88rpx;align-items:center;gap:16rpx;margin-top:16rpx;border-top:1px solid rgba(181,137,63,.38)}.contact-row text{color:$ink;font-size:23rpx;font-weight:700}.contact-row input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:22rpx;text-align:right}.field-error{display:block;margin-top:7rpx;color:#b42318;font-size:20rpx;line-height:1.4;text-align:right}.privacy-note{display:block;margin-top:15rpx;color:$ink-muted;font-size:20rpx;line-height:1.5;text-align:center}.page-content>.app-button{margin-top:24rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.feedback-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:26rpx 30rpx 72rpx}.lead{display:block;color:$ink-muted;font-size:23rpx;line-height:1.5;text-align:center}.type-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14rpx;margin-top:20rpx}.type-chip{@include adaptive.adaptive-profile-field;display:flex;min-height:76rpx;align-items:center;justify-content:center;padding:10rpx 16rpx;color:$ink-muted;font-size:22rpx;text-align:center}.type-chip.active{color:$brand-red;font-weight:700;filter:saturate(1.2)}.form-panel{@include adaptive.adaptive-profile-content;margin-top:20rpx;padding:30rpx 34rpx}.form-panel textarea{display:block;width:auto;min-width:0;min-height:210rpx;color:$ink;font-size:24rpx;line-height:1.65}.counter{display:block;color:$ink-muted;font-size:20rpx;text-align:right}.contact-row{display:grid;grid-template-columns:130rpx minmax(0,1fr);min-height:88rpx;align-items:center;gap:16rpx;margin-top:16rpx;border-top:1px solid rgba(181,137,63,.38)}.contact-row text{color:$ink;font-size:23rpx;font-weight:700}.contact-row input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:22rpx;text-align:right}.field-error{display:block;margin-top:7rpx;color:#b42318;font-size:20rpx;line-height:1.4;text-align:right}.privacy-note{display:block;margin-top:15rpx;color:$ink-muted;font-size:20rpx;line-height:1.5;text-align:center}.page-content>.app-button{margin-top:24rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}}
</style>
+2 -1
View File
@@ -42,5 +42,6 @@ onUnmounted(() => clearTimeout(timer));
</script>
<style scoped lang="scss">
.promotion-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.invite-hero,.invite-code-card,.steps-card{background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.invite-hero{display:flex;min-height:300rpx;flex-direction:column;align-items:center;justify-content:center;padding:38rpx 48rpx;box-sizing:border-box;text-align:center}.invite-hero image{width:90rpx;height:auto;max-height:102rpx;aspect-ratio:90/102}.invite-hero text{display:block}.invite-hero text:nth-child(2){margin-top:12rpx;color:$ink;font-family:STKaiti,KaiTi,serif;font-size:34rpx;font-weight:700}.invite-hero text:last-child{margin-top:12rpx;color:$ink-muted;font-size:22rpx;line-height:1.55}.invite-code-card{min-height:220rpx;margin-top:20rpx;padding:38rpx 44rpx;box-sizing:border-box;text-align:center}.invite-code-card>text:first-child{display:block;color:$ink-muted;font-size:21rpx}.invite-code{display:block;margin-top:10rpx;color:$brand-red;font-size:42rpx;font-weight:700;letter-spacing:5rpx;overflow-wrap:anywhere}.copy-action{display:flex;min-height:62rpx;align-items:center;justify-content:center;color:$brand-red;font-size:22rpx}.steps-card{display:flex;min-height:260rpx;flex-direction:column;gap:10rpx;margin-top:20rpx;padding:34rpx 44rpx;box-sizing:border-box;color:$ink-muted;font-size:22rpx}.steps-card text:first-child{margin-bottom:4rpx;color:$ink;font-size:27rpx;font-weight:700}.page-content>.app-button{margin-top:26rpx}.poster-seal{width:96rpx;height:auto;max-height:110rpx;aspect-ratio:96/110;margin:18rpx auto 24rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.invite-code{font-size:35rpx;letter-spacing:3rpx}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.promotion-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.invite-hero,.invite-code-card,.steps-card{@include adaptive.adaptive-profile-content}.invite-hero{display:flex;min-height:300rpx;flex-direction:column;align-items:center;justify-content:center;padding:38rpx 48rpx;text-align:center}.invite-hero image{width:90rpx;height:auto;max-height:102rpx;aspect-ratio:90/102}.invite-hero text{display:block}.invite-hero text:nth-child(2){margin-top:12rpx;color:$ink;font-family:STKaiti,KaiTi,serif;font-size:34rpx;font-weight:700}.invite-hero text:last-child{margin-top:12rpx;color:$ink-muted;font-size:22rpx;line-height:1.55}.invite-code-card{min-height:220rpx;margin-top:20rpx;padding:38rpx 44rpx;text-align:center}.invite-code-card>text:first-child{display:block;color:$ink-muted;font-size:21rpx}.invite-code{display:block;margin-top:10rpx;color:$brand-red;font-size:42rpx;font-weight:700;letter-spacing:5rpx;overflow-wrap:anywhere}.copy-action{display:flex;min-height:62rpx;align-items:center;justify-content:center;color:$brand-red;font-size:22rpx}.steps-card{display:flex;min-height:260rpx;flex-direction:column;gap:10rpx;margin-top:20rpx;padding:34rpx 44rpx;color:$ink-muted;font-size:22rpx}.steps-card text:first-child{margin-bottom:4rpx;color:$ink;font-size:27rpx;font-weight:700}.page-content>.app-button{margin-top:26rpx}.poster-seal{width:96rpx;height:auto;max-height:110rpx;aspect-ratio:96/110;margin:18rpx auto 24rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.invite-code{font-size:35rpx;letter-spacing:3rpx}}
</style>
+2 -1
View File
@@ -35,5 +35,6 @@ const openServiceNotice = () => { serviceNoticeVisible.value = true; };
</script>
<style scoped lang="scss">
.orders-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.service-card,.benefit-card,.order-card,.empty-card{background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.service-card{min-height:220rpx;padding:42rpx 48rpx;box-sizing:border-box;text-align:center}.service-card text{display:block}.service-card text:first-child{color:$ink;font-family:STKaiti,KaiTi,serif;font-size:34rpx;font-weight:700}.service-card text:nth-child(2){margin-top:10rpx;color:$brand-red;font-size:23rpx;font-weight:700}.service-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.55}.benefit-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12rpx;margin-top:18rpx}.benefit-card{min-height:150rpx;padding:28rpx 18rpx;box-sizing:border-box;text-align:center}.benefit-card text{display:block;overflow-wrap:anywhere}.benefit-card text:first-child{color:$ink;font-size:23rpx;font-weight:700}.benefit-card text:last-child{margin-top:8rpx;color:$ink-muted;font-size:19rpx;line-height:1.45}.section-heading{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8rpx 20rpx;margin:28rpx 6rpx 14rpx}.section-heading text:first-child{color:$ink;font-size:27rpx;font-weight:700}.section-heading text:last-child{color:$ink-muted;font-size:21rpx}.order-list{display:flex;flex-direction:column;gap:14rpx}.order-card{display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:130rpx;align-items:center;gap:20rpx;padding:26rpx 34rpx;box-sizing:border-box}.order-card text{display:block;overflow-wrap:anywhere}.order-card view:first-child text:first-child{color:$ink;font-size:23rpx;font-weight:700}.order-card text:last-child{margin-top:6rpx;color:$ink-muted;font-size:20rpx}.order-card view:last-child{text-align:right}.order-card view:last-child text:first-child{color:$brand-red;font-size:23rpx;font-weight:700}.empty-card{min-height:190rpx;padding:48rpx 42rpx;box-sizing:border-box;text-align:center}.empty-card text{display:block}.empty-card text:first-child{color:$ink;font-size:28rpx;font-weight:700}.empty-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.55}.page-content>.app-button{margin-top:26rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.benefit-grid{grid-template-columns:1fr}.benefit-card{min-height:100rpx}.order-card{grid-template-columns:1fr}.order-card view:last-child{text-align:left}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.orders-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.service-card,.benefit-card,.order-card,.empty-card{@include adaptive.adaptive-profile-content}.service-card{min-height:220rpx;padding:42rpx 48rpx;text-align:center}.service-card text{display:block}.service-card text:first-child{color:$ink;font-family:STKaiti,KaiTi,serif;font-size:34rpx;font-weight:700}.service-card text:nth-child(2){margin-top:10rpx;color:$brand-red;font-size:23rpx;font-weight:700}.service-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.55}.benefit-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12rpx;margin-top:18rpx}.benefit-card{min-height:150rpx;padding:28rpx 18rpx;text-align:center}.benefit-card text{display:block;overflow-wrap:anywhere}.benefit-card text:first-child{color:$ink;font-size:23rpx;font-weight:700}.benefit-card text:last-child{margin-top:8rpx;color:$ink-muted;font-size:19rpx;line-height:1.45}.section-heading{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8rpx 20rpx;margin:28rpx 6rpx 14rpx}.section-heading text:first-child{color:$ink;font-size:27rpx;font-weight:700}.section-heading text:last-child{color:$ink-muted;font-size:21rpx}.order-list{display:flex;flex-direction:column;gap:14rpx;margin-top:18rpx}.order-card{display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:130rpx;align-items:center;gap:20rpx;padding:26rpx 34rpx}.order-card text{display:block;overflow-wrap:anywhere}.order-card view:first-child text:first-child{color:$ink;font-size:23rpx;font-weight:700}.order-card text:last-child{margin-top:6rpx;color:$ink-muted;font-size:20rpx}.order-card view:last-child{text-align:right}.order-card view:last-child text:first-child{color:$brand-red;font-size:23rpx;font-weight:700}.empty-card{min-height:190rpx;padding:48rpx 42rpx;text-align:center}.empty-card text{display:block}.empty-card text:first-child{color:$ink;font-size:28rpx;font-weight:700}.empty-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.55}.page-content>.app-button{margin-top:26rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.benefit-grid{grid-template-columns:1fr}.benefit-card{min-height:100rpx}.order-card{grid-template-columns:1fr}.order-card view:last-child{text-align:left}}
</style>
+2 -1
View File
@@ -38,5 +38,6 @@ onUnmounted(() => clearTimeout(timer));
</script>
<style scoped lang="scss">
.about-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.brand-card{display:flex;min-height:300rpx;flex-direction:column;align-items:center;justify-content:center;padding:36rpx 44rpx;box-sizing:border-box;background:url("/static/assets/modules/profile/transparent/m01-profile-summary-card.png") center/100% 100% no-repeat;text-align:center}.brand-card image{width:92rpx;height:106rpx}.brand-card text{display:block}.brand-card text:nth-child(2){margin-top:6rpx;color:$ink;font-family:STKaiti,KaiTi,serif;font-size:40rpx;font-weight:700;letter-spacing:4rpx}.brand-card text:nth-child(3){margin-top:8rpx;color:$ink-muted;font-size:22rpx;line-height:1.5}.brand-card text:last-child{margin-top:10rpx;color:$ink-muted;font-size:20rpx}.settings-list{display:flex;flex-direction:column;gap:12rpx;margin-top:22rpx}.settings-row{display:grid;grid-template-columns:minmax(0,1fr) 32rpx;min-height:104rpx;align-items:center;gap:18rpx;padding:18rpx 30rpx;box-sizing:border-box;background:url("/static/assets/modules/profile/transparent/module-field-frame.png") center/100% 100% no-repeat}.settings-row view{min-width:0}.settings-row text{display:block;overflow-wrap:anywhere}.settings-row text:first-child{color:$ink;font-size:24rpx;font-weight:700}.settings-row text:last-child{margin-top:6rpx;color:$ink-muted;font-size:20rpx}.settings-row image{width:30rpx;height:30rpx}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}}
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.about-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.brand-card{@include adaptive.adaptive-profile-summary;display:flex;min-height:300rpx;flex-direction:column;align-items:center;justify-content:center;padding:36rpx 44rpx;text-align:center}.brand-card image{width:92rpx;height:106rpx}.brand-card text{display:block}.brand-card text:nth-child(2){margin-top:6rpx;color:$ink;font-family:STKaiti,KaiTi,serif;font-size:40rpx;font-weight:700;letter-spacing:4rpx}.brand-card text:nth-child(3){margin-top:8rpx;color:$ink-muted;font-size:22rpx;line-height:1.5}.brand-card text:last-child{margin-top:10rpx;color:$ink-muted;font-size:20rpx}.settings-list{display:flex;flex-direction:column;gap:12rpx;margin-top:22rpx}.settings-row{@include adaptive.adaptive-profile-field;display:grid;grid-template-columns:minmax(0,1fr) 32rpx;min-height:104rpx;align-items:center;gap:18rpx;padding:18rpx 30rpx}.settings-row view{min-width:0}.settings-row text{display:block;overflow-wrap:anywhere}.settings-row text:first-child{color:$ink;font-size:24rpx;font-weight:700}.settings-row text:last-child{margin-top:6rpx;color:$ink-muted;font-size:20rpx}.settings-row image{width:30rpx;height:30rpx}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}}
</style>
+4 -3
View File
@@ -163,6 +163,8 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.people-page {
display: flex;
min-height: 100vh;
@@ -178,12 +180,12 @@ onUnmounted(() => {
padding: 22rpx 24rpx 100rpx;
}
.people-search {
@include adaptive.adaptive-records-field;
display: grid;
grid-template-columns: minmax(0, 1fr) 126rpx;
width: 100%;
min-height: 44px;
align-items: center;
background: url("/static/assets/modules/records/transparent/r01-search-input-frame.png") center / 100% 100% no-repeat;
}
.people-search input {
z-index: 1;
@@ -214,14 +216,13 @@ onUnmounted(() => {
margin-top: 12px;
}
.person-card {
@include adaptive.adaptive-records-person;
display: flex;
width: 100%;
height: clamp(92px, 190rpx, 108px);
align-items: center;
margin-top: 12px;
padding: 16% 10%;
box-sizing: border-box;
background: url("/static/assets/modules/records/transparent/r01-person-name-card.png") center / 100% 100% no-repeat;
}
.person-card:first-child {
margin-top: 0;
+5 -3
View File
@@ -141,24 +141,26 @@ onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer); });
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.person-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.person-detail-header,.person-detail-loading,.person-detail-content { z-index: 1; }
.person-detail-loading { min-height: calc(100vh - 100rpx); }
.person-detail-content { padding: 18rpx 24rpx 72rpx; }
.person-identity-card { display: flex; min-height: 190rpx; padding: 30rpx 10%; box-sizing: border-box; background: url("/static/assets/modules/records/transparent/r01-person-name-card.png") center / 100% 100% no-repeat; }
.person-identity-card { @include adaptive.adaptive-records-person; display: flex; min-height: 190rpx; padding: 30rpx 10%; }
.person-identity-card__copy { display: flex; flex: 1; flex-direction: column; justify-content: center; }
.person-identity-card__copy text { display: block; }
.person-identity-card__name { color: $ink; font-family: STKaiti,KaiTi,serif; font-size: 38rpx; font-weight: 700; }
.person-identity-card__meta { margin-top: 8rpx; color: $ink-muted; font-size: 25rpx; font-weight: 600; }
.person-identity-card__hint { margin-top: 8rpx; color: #806a51; font-size: 21rpx; }
.person-archive-card { min-height: 154rpx; margin-top: 14rpx; padding: 32rpx 42rpx; box-sizing: border-box; }
.person-archive-card,.person-long-field,.person-state-card { background: url("/static/assets/modules/records/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
.person-archive-card,.person-long-field,.person-state-card { @include adaptive.adaptive-records-content; }
.person-archive-card text { display: block; }
.person-archive-card text:first-child,.person-long-field__label { color: $brand-red; font-size: 24rpx; font-weight: 700; }
.person-archive-card text:last-child { margin-top: 11rpx; color: $ink; font-size: 24rpx; line-height: 1.55; }
.person-edit-action { margin-top: 20rpx; }
.person-related-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14rpx; margin-top: 18rpx; }
.person-field { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8rpx 24rpx; min-height: 92rpx; margin-top: 12rpx; padding: 18rpx 28rpx; box-sizing: border-box; background: url("/static/assets/modules/records/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
.person-field { @include adaptive.adaptive-records-field; display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8rpx 24rpx; min-height: 92rpx; margin-top: 12rpx; padding: 18rpx 28rpx; }
.person-field__label { color: $ink; font-size: 23rpx; font-weight: 700; }
.person-field input { width: 100%; min-width: 0; min-height: 56rpx; color: $ink; font-size: 23rpx; text-align: right; }
.person-field-error { grid-column: 1 / -1; display: block; color: $brand-red; font-size: 21rpx; text-align: right; }
+3 -2
View File
@@ -114,6 +114,8 @@ const restoreGifts = () => {
};
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.gift-page {
display: flex;
min-height: 100vh;
@@ -137,8 +139,7 @@ const restoreGifts = () => {
.record-card,
.state-card {
box-sizing: border-box;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.record-card {
min-height: 190rpx;
+4 -4
View File
@@ -168,6 +168,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.gift-editor-page {
display: flex;
min-height: 100vh;
@@ -190,8 +192,7 @@ onUnmounted(() => {
.state-card {
box-sizing: border-box;
padding: 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.form-card > text:first-child,
.detail-card > text:first-child,
@@ -211,8 +212,7 @@ onUnmounted(() => {
margin-top: 14rpx;
padding: 14rpx 24rpx;
box-sizing: border-box;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-field;
}
.field-row > text:first-child,
.detail-card > view > text:first-child {
+3 -2
View File
@@ -104,6 +104,8 @@ const restoreRituals = () => {
};
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.ritual-page {
display: flex;
min-height: 100vh;
@@ -127,8 +129,7 @@ const restoreRituals = () => {
.record-card,
.state-card {
box-sizing: border-box;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.record-card {
min-height: 190rpx;
+3 -2
View File
@@ -126,6 +126,8 @@ const backToRituals = () =>
uni.redirectTo({ url: "/pages/records/r05-ritual-list" });
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.ritual-detail-page {
display: flex;
min-height: 100vh;
@@ -151,8 +153,7 @@ const backToRituals = () =>
.state-card {
box-sizing: border-box;
padding: 42rpx 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.detail-card > text {
display: block;
+4 -4
View File
@@ -139,6 +139,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.ritual-editor-page {
display: flex;
min-height: 100vh;
@@ -156,8 +158,7 @@ onUnmounted(() => {
.state-card {
box-sizing: border-box;
padding: 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.form-card > text:first-child,
.state-card > text:first-child {
@@ -175,8 +176,7 @@ onUnmounted(() => {
margin-top: 14rpx;
padding: 14rpx 24rpx;
box-sizing: border-box;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-field;
}
.field-row > text:first-child {
color: $ink;
+5 -5
View File
@@ -140,6 +140,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.timeline-page {
display: flex;
min-height: 100vh;
@@ -171,7 +173,7 @@ onUnmounted(() => {
color: $ink-muted;
font-size: 22rpx;
background: url("/static/assets/modules/genealogy/transparent/section-divider.png")
center/100% 100% no-repeat;
center/100% auto no-repeat;
}
.person-lead text:first-child {
color: $brand-red;
@@ -181,8 +183,7 @@ onUnmounted(() => {
.state-card {
box-sizing: border-box;
padding: 34rpx 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.timeline-card > text,
.state-card > text {
@@ -241,8 +242,7 @@ onUnmounted(() => {
box-sizing: border-box;
color: $ink;
font-size: 23rpx;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-field;
}
.dialog-form text {
display: block;
+5 -5
View File
@@ -147,6 +147,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.timeline-page {
display: flex;
min-height: 100vh;
@@ -178,7 +180,7 @@ onUnmounted(() => {
color: $ink-muted;
font-size: 22rpx;
background: url("/static/assets/modules/genealogy/transparent/section-divider.png")
center/100% 100% no-repeat;
center/100% auto no-repeat;
}
.person-lead text:first-child {
color: $brand-red;
@@ -188,8 +190,7 @@ onUnmounted(() => {
.state-card {
box-sizing: border-box;
padding: 34rpx 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.timeline-card > text,
.state-card > text {
@@ -248,8 +249,7 @@ onUnmounted(() => {
box-sizing: border-box;
color: $ink;
font-size: 22rpx;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-field;
}
.dialog-form text {
display: block;
+4 -4
View File
@@ -143,6 +143,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.memo-page {
display: flex;
min-height: 100vh;
@@ -167,8 +169,7 @@ onUnmounted(() => {
.state-card {
box-sizing: border-box;
padding: 34rpx 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.memo-card > view {
display: flex;
@@ -234,8 +235,7 @@ onUnmounted(() => {
box-sizing: border-box;
color: $ink;
font-size: 23rpx;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-field;
}
.dialog-form text {
display: block;
+4 -4
View File
@@ -149,6 +149,8 @@ onUnmounted(() => {
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.merit-page {
display: flex;
min-height: 100vh;
@@ -174,8 +176,7 @@ onUnmounted(() => {
.state-card {
box-sizing: border-box;
padding: 34rpx 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-content;
}
.merit-summary {
text-align: center;
@@ -254,8 +255,7 @@ onUnmounted(() => {
box-sizing: border-box;
color: $ink;
font-size: 22rpx;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
@include adaptive.adaptive-records-field;
}
.dialog-form text {
display: block;
+7 -5
View File
@@ -134,12 +134,14 @@ const toTree = () => uni.navigateBack();
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.member-page { display: flex; min-height: 100vh; flex-direction: column; padding-bottom: 28rpx; box-sizing: border-box; background: $paper; }
.member-page__header, .member-context, .member-panel { z-index: 2; }
.member-context { width: calc(100% - 32rpx); margin: 18rpx auto 0; padding: 16rpx 24rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.member-context { @include adaptive.adaptive-tree-field; width: calc(100% - 32rpx); margin: 18rpx auto 0; padding: 16rpx 24rpx; }
.member-context text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.45; }
.member-context text:first-child { color: $ink; font-size: 26rpx; font-weight: 700; }
.member-panel { width: calc(100% - 32rpx); min-height: min(620px, calc((100vw - 16px) * 1.42)); margin: 16rpx auto 0; padding: 9% 8%; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t01-state-panel.png") center / 100% 100% no-repeat; }
.member-panel { @include adaptive.adaptive-tree-panel; width: calc(100% - 32rpx); min-height: min(620px, calc((100vw - 16px) * 1.42)); margin: 16rpx auto 0; padding: 9% 8%; }
.member-panel > .app-loading { margin-top: 30%; }
.member-heading { display: flex; align-items: center; gap: 20rpx; }
.member-heading__seal { display: flex; width: 74rpx; height: 74rpx; flex: 0 0 74rpx; align-items: center; justify-content: center; background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / contain no-repeat; }
@@ -147,18 +149,18 @@ const toTree = () => uni.navigateBack();
.member-heading > view:last-child text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.45; }
.member-heading > view:last-child text:last-child { font-size: 23rpx; }
.member-heading > view:last-child text:first-child { color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 36rpx; font-weight: 700; }
.member-status-entry { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 14rpx; margin-top: 18rpx; padding: 18rpx 22rpx; background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat; }
.member-status-entry { @include adaptive.adaptive-genealogy-list-card; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 14rpx; margin-top: 18rpx; padding: 18rpx 22rpx; }
.member-status-entry text { color: $ink-muted; font-size: 22rpx; line-height: 1.4; }
.member-status-entry text:first-child, .member-status-entry text:last-child { color: $brand-red; font-weight: 700; }
.member-section-title { display: block; margin-top: 24rpx; color: $brand-red; font-size: 24rpx; font-weight: 700; }
.member-info-row { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 64rpx; align-items: center; gap: 18rpx; margin-top: 8rpx; padding: 8rpx 18rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.member-info-row { @include adaptive.adaptive-tree-field; display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 64rpx; align-items: center; gap: 18rpx; margin-top: 8rpx; padding: 8rpx 18rpx; }
.member-info-row text { color: $ink-muted; font-size: 23rpx; line-height: 1.45; }
.member-info-row text:last-child { color: $ink; text-align: right; }
.member-relatives { margin-top: 8rpx; font-size: 23rpx; }
.member-relatives > view { display: flex; justify-content: space-between; gap: 20rpx; padding: 12rpx 18rpx; color: $ink; font-size: 23rpx; }
.member-relatives > view text:last-child { color: $brand-red; }
.member-relatives > text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.5; }
.member-profile-action { display: flex; min-height: 76rpx; align-items: center; justify-content: center; margin-top: 22rpx; background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / 100% 100% no-repeat; }
.member-profile-action { @include adaptive.adaptive-scroll-button(primary); display: flex; min-height: 76rpx; align-items: center; justify-content: center; margin-top: 22rpx; }
.member-profile-action text { color: #fff9ed; font-size: 24rpx; font-weight: 700; }
.member-error { margin-top: 30%; text-align: center; }
.member-error > text { display: block; color: $ink-muted; font-size: 24rpx; line-height: 1.55; }
+4 -2
View File
@@ -220,13 +220,15 @@ const discardAndBack = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.add-relative-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.add-relative-page__header { z-index: 3; }
.add-relative-panel { z-index: 2; width: calc(100% - 32rpx); min-height: min(680px, calc((100vw - 16px) * 1.5)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t01-state-panel.png") center / 100% 100% no-repeat; }
.add-relative-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); min-height: min(680px, calc((100vw - 16px) * 1.5)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }
.member-context, .form-field { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.member-context, .form-field { @include adaptive.adaptive-tree-field; display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; }
.member-context text:first-child, .form-field > text:first-child { color: $ink; font-size: 24rpx; font-weight: 700; }
.member-context text:last-child, .form-field > text:last-child, .form-field input, .form-field textarea { min-width: 0; color: $ink; font-size: 24rpx; line-height: 1.45; text-align: right; }
.form-field textarea { width: auto; min-height: 54rpx; text-align: left; }
+4 -2
View File
@@ -169,13 +169,15 @@ const discardAndBack = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.edit-member-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.edit-member-page__header { z-index: 3; }
.edit-member-panel { z-index: 2; width: calc(100% - 32rpx); min-height: min(690px, calc((100vw - 16px) * 1.52)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t01-state-panel.png") center / 100% 100% no-repeat; }
.edit-member-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); min-height: min(690px, calc((100vw - 16px) * 1.52)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }
.member-context, .form-field { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.member-context, .form-field { @include adaptive.adaptive-tree-field; display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; }
.member-context text:first-child, .form-field > text:first-child { color: $ink; font-size: 24rpx; font-weight: 700; }
.member-context text:last-child, .form-field > text:last-child, .form-field input, .form-field textarea { min-width: 0; color: $ink; font-size: 24rpx; line-height: 1.45; text-align: right; }
.form-field textarea { width: auto; min-height: 54rpx; text-align: left; }
+5 -3
View File
@@ -188,17 +188,19 @@ const handleResultAction = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.relationship-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.relationship-page__header { z-index: 3; }
.relationship-panel { z-index: 2; width: calc(100% - 32rpx); min-height: min(650px, calc((100vw - 16px) * 1.46)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t01-state-panel.png") center / 100% 100% no-repeat; }
.relationship-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); min-height: min(650px, calc((100vw - 16px) * 1.46)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }
.form-field { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.form-field { @include adaptive.adaptive-tree-field; display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; }
.form-field text:first-child { color: $ink; font-size: 24rpx; font-weight: 700; }
.form-field text:last-child { min-width: 0; color: $ink; font-size: 24rpx; line-height: 1.45; text-align: right; }
.field-error { display: block; margin: 5rpx 18rpx 0; color: $brand-red; font-size: 22rpx; line-height: 32rpx; }
.relationship-preview { margin-top: 18rpx; padding: 20rpx 22rpx; background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat; }
.relationship-preview { @include adaptive.adaptive-genealogy-list-card; margin-top: 18rpx; padding: 20rpx 22rpx; }
.relationship-preview text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.5; }
.relationship-preview text:first-child { color: $brand-red; font-weight: 700; }
.form-note { text-align: center; }
+5 -8
View File
@@ -152,6 +152,8 @@ const openMember = (item) =>
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.directory-page {
display: flex;
min-height: 100vh;
@@ -184,12 +186,11 @@ const openMember = (item) =>
font-weight: 500;
}
.directory-search {
@include adaptive.adaptive-tree-field;
display: grid;
width: calc(100% - 48rpx);
min-height: 44px;
margin: 20rpx auto 0;
background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png")
center / 100% 100% no-repeat;
}
.directory-search input {
z-index: 1;
@@ -237,15 +238,13 @@ const openMember = (item) =>
font-size: 23rpx;
}
.directory-card {
@include adaptive.adaptive-genealogy-list-card;
display: flex;
width: 100%;
min-height: 196rpx;
align-items: center;
box-sizing: border-box;
margin-bottom: 16rpx;
padding: 18rpx 28rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.directory-card__copy {
z-index: 1;
@@ -280,6 +279,7 @@ const openMember = (item) =>
line-height: 30rpx;
}
.directory-state-card {
@include adaptive.adaptive-genealogy-list-card;
display: flex;
width: 100%;
min-height: 220rpx;
@@ -287,9 +287,6 @@ const openMember = (item) =>
justify-content: center;
margin-bottom: 16rpx;
padding: 48rpx 12%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.directory-state-card {
margin-top: 70rpx;
+5 -3
View File
@@ -114,17 +114,19 @@ const handleAction = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.member-status-page { display: flex; min-height: 100vh; flex-direction: column; padding-bottom: 34rpx; box-sizing: border-box; background: $paper; }
.member-status-page__header, .member-status-context, .status-card, .status-guidance, .status-action { z-index: 2; }
.member-status-context { width: calc(100% - 32rpx); margin: 18rpx auto 0; padding: 16rpx 24rpx; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png") center / 100% 100% no-repeat; }
.member-status-context { @include adaptive.adaptive-tree-field; width: calc(100% - 32rpx); margin: 18rpx auto 0; padding: 16rpx 24rpx; }
.member-status-context text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.45; }
.member-status-context text:first-child { color: $ink; font-size: 26rpx; font-weight: 700; }
.status-card { width: calc(100% - 32rpx); min-height: 330rpx; margin: 18rpx auto 0; padding: 12% 10%; box-sizing: border-box; background: url("/static/assets/modules/tree/transparent/t01-state-panel.png") center / 100% 100% no-repeat; }
.status-card { @include adaptive.adaptive-tree-panel; width: calc(100% - 32rpx); min-height: 330rpx; margin: 18rpx auto 0; padding: 12% 10%; }
.status-card__copy text { display: block; color: $ink-muted; font-size: 24rpx; line-height: 1.55; text-align: center; }
.status-card__copy text:first-child { color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.status-card__copy text:nth-child(2) { margin-top: 12rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 32rpx; font-weight: 700; }
.status-card__copy text:last-child { margin-top: 14rpx; font-size: 24rpx; }
.status-guidance { width: calc(100% - 48rpx); margin: 16rpx auto 0; padding: 24rpx 28rpx; box-sizing: border-box; background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat; }
.status-guidance { @include adaptive.adaptive-genealogy-list-card; width: calc(100% - 48rpx); margin: 16rpx auto 0; padding: 24rpx 28rpx; }
.status-guidance text { display: block; color: $ink-muted; font-size: 24rpx; line-height: 1.6; }
.status-guidance text:first-child { margin-bottom: 8rpx; color: $ink; font-size: 26rpx; font-weight: 700; }
.status-action { display: grid; width: calc(100% - 72rpx); min-height: 76rpx; margin: 22rpx auto 0; }
+265
View File
@@ -0,0 +1,265 @@
@mixin adaptive-auth-dialog {
box-sizing: border-box;
border-width: 48rpx 34rpx 72rpx;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png");
border-image-slice: 260 240 360 240 fill;
border-image-width: 48rpx 34rpx 72rpx;
border-image-repeat: stretch;
}
@mixin adaptive-scroll-button($type) {
box-sizing: border-box;
border-width: 10rpx 26rpx;
border-style: solid;
border-color: transparent;
border-image-slice: 56 300 fill;
border-image-width: 10rpx 26rpx;
border-image-repeat: stretch;
@if $type == secondary {
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png");
} @else {
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png");
}
}
@mixin adaptive-module-content {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: var(--module-content-asset);
border-image-slice: 105 150;
border-image-width: 18rpx 20rpx;
border-image-repeat: stretch;
}
@mixin adaptive-module-field {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: var(--module-field-asset);
border-image-slice: 70 100 fill;
border-image-width: 12rpx 16rpx;
border-image-repeat: stretch;
}
@mixin adaptive-feedback-toast {
box-sizing: border-box;
border-width: 16rpx 74rpx;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png");
border-image-slice: 58 280 fill;
border-image-width: 16rpx 74rpx;
border-image-repeat: stretch;
}
@mixin adaptive-genealogy-current-slip {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/current-slip-frame.png");
border-image-slice: 24 24 60 24 fill;
border-image-width: 12rpx 12rpx 30rpx;
border-image-repeat: stretch;
}
@mixin adaptive-genealogy-list-card {
box-sizing: border-box;
border-width: 18rpx 24rpx;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png");
border-image-slice: 24 32;
border-image-width: 18rpx 24rpx;
border-image-repeat: stretch;
}
@mixin adaptive-genealogy-state-panel {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png");
border-image-slice: 84 70 360 70 fill;
border-image-width: 42rpx 28rpx 180rpx;
border-image-repeat: stretch;
}
@mixin adaptive-genealogy-form-field {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png");
border-image-slice: 70 100 fill;
border-image-width: 12rpx 16rpx;
border-image-repeat: stretch;
}
@mixin adaptive-g05-overview-surface {
@include adaptive-asset-frame(
"/static/assets/modules/genealogy/opaque/g05-overview-surface.png",
120 100 fill,
42rpx 36rpx
);
}
@mixin adaptive-g06-search-field {
@include adaptive-asset-frame(
"/static/assets/modules/genealogy/opaque/g06-search-input-wide.png",
70 100 fill,
12rpx 16rpx
);
}
@mixin adaptive-g06-search-action {
@include adaptive-asset-frame(
"/static/assets/modules/genealogy/opaque/g06-search-button.png",
36 64 fill,
12rpx 20rpx
);
}
@mixin adaptive-g01-add-sheet {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png");
border-image-slice: 220 0 1 0 fill;
border-image-width: 118rpx 0 1rpx;
border-image-repeat: stretch;
}
@mixin adaptive-g01-switcher {
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image-source: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png");
border-image-slice: 300 260 360 260 fill;
border-image-width: 110rpx 48rpx 132rpx 48rpx;
border-image-repeat: stretch;
}
@mixin adaptive-asset-frame($source, $slice, $width, $layout-border: 1px) {
box-sizing: border-box;
border-width: $layout-border;
border-style: solid;
border-color: transparent;
border-image-source: url($source);
border-image-slice: $slice;
border-image-width: $width;
border-image-repeat: stretch;
}
@mixin adaptive-family-content {
@include adaptive-asset-frame(
"/static/assets/modules/family/transparent/module-content-frame.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-family-panel {
@include adaptive-asset-frame(
"/static/assets/modules/family/transparent/module-content-frame.png",
72 fill,
64rpx,
20rpx
);
}
@mixin adaptive-family-field {
@include adaptive-asset-frame(
"/static/assets/modules/family/transparent/module-field-frame.png",
70 100 fill,
12rpx 16rpx
);
}
@mixin adaptive-family-letter {
@include adaptive-asset-frame(
"/static/assets/modules/family/transparent/f01-family-letter-card.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-notification-content {
@include adaptive-asset-frame(
"/static/assets/modules/notification/transparent/n01-notice-card.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-profile-content {
@include adaptive-asset-frame(
"/static/assets/modules/profile/transparent/module-content-frame.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-profile-field {
@include adaptive-asset-frame(
"/static/assets/modules/profile/transparent/module-field-frame.png",
70 100 fill,
12rpx 16rpx
);
}
@mixin adaptive-profile-summary {
@include adaptive-asset-frame(
"/static/assets/modules/profile/transparent/m01-profile-summary-card.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-records-content {
@include adaptive-asset-frame(
"/static/assets/modules/records/transparent/module-content-frame.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-records-field {
@include adaptive-asset-frame(
"/static/assets/modules/records/transparent/module-field-frame.png",
70 100 fill,
12rpx 16rpx
);
}
@mixin adaptive-records-person {
@include adaptive-asset-frame(
"/static/assets/modules/records/transparent/r01-person-name-card.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-tree-panel {
@include adaptive-asset-frame(
"/static/assets/modules/tree/transparent/t01-state-panel.png",
105 150 fill,
18rpx 20rpx
);
}
@mixin adaptive-tree-field {
@include adaptive-asset-frame(
"/static/assets/modules/tree/transparent/t07-search-input-frame.png",
70 100 fill,
12rpx 16rpx
);
}
+4 -4
View File
@@ -35,6 +35,7 @@ $shellPath = Join-Path $root 'components/AuthPageShell.vue'
$legacyLoginPath = Join-Path $root 'pages/auth/a02-login.vue'
$pagesPath = Join-Path $root 'pages.json'
$globalStylesPath = Join-Path $root 'styles/global.scss'
$profilePath = Join-Path $root 'styles/adaptive-frame-profiles.scss'
if (-not (Test-Path -LiteralPath $entryPath)) {
throw 'Missing the single A01 login page.'
@@ -57,6 +58,7 @@ if ($pages.pages.Count -ne 52) {
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
$shell = Get-Content -LiteralPath $shellPath -Raw -Encoding utf8
$globalStyles = Get-Content -LiteralPath $globalStylesPath -Raw -Encoding utf8
$profiles = Get-Content -LiteralPath $profilePath -Raw -Encoding utf8
# A01 owns content in normal document flow. Decorative artwork must not become
# a second full-page coordinate system.
@@ -157,7 +159,7 @@ foreach ($asset in @(
'a02-agreement-unchecked.png',
'a02-agreement-checked.png'
)) {
Assert-Contains -Content ($entry + $shell) -Expected $asset -Message "A01 is missing asset reference: $asset"
Assert-Contains -Content ($entry + $shell + $profiles) -Expected $asset -Message "A01 is missing asset reference: $asset"
}
foreach ($obsoleteVisualAsset in @(
'auth-header.png',
@@ -244,7 +246,5 @@ $feedbackToastRule = [regex]::Match($entry, '(?ms)^\.feedback-toast\s*\{(?<Body>
if (-not $feedbackToastRule.Success) {
throw 'A01 is missing the custom feedback Toast style rule.'
}
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected '/static/assets/foundation/transparent/a01-scroll-toast-v3.png' -Message 'A01 feedback Toast must use the approved v3 nine-slice asset.'
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'border-image-slice:' -Message 'A01 feedback Toast must declare fixed decorative slices.'
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'fill' -Message 'A01 feedback Toast nine-slice must fill the paper center.'
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected '@include adaptive.adaptive-feedback-toast;' -Message 'A01 feedback Toast must consume the approved shared nine-slice profile.'
Write-Output 'A01-LOGIN-MERGE-CONTRACT PASS'
+2 -2
View File
@@ -32,7 +32,7 @@ foreach ($required in @(
'import AuthPageShell from "@/components/AuthPageShell.vue";',
'a01-vnext-divider-v1.png',
'a01-scroll-primary-v3.png',
'a01-scroll-toast-v3.png',
'adaptive.adaptive-feedback-toast',
'a02-agreement-unchecked.png',
'a02-agreement-checked.png',
'v-model.trim="phone"',
@@ -79,7 +79,7 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
foreach ($required in @(
'register-divider',
'mode="aspectFit"',
'border-image-slice: 58 280 fill;',
'adaptive.adaptive-feedback-toast;',
'top: calc(var(--status-bar-height, 0px) + 24rpx);'
)) {
Assert-Contains -Content $register -Expected $required -Message "Missing A-04 visual balance contract: $required"
+2 -2
View File
@@ -33,7 +33,7 @@ foreach ($required in @(
'import AuthPageShell from "@/components/AuthPageShell.vue";',
'a01-vnext-divider-v1.png',
'a01-scroll-primary-v3.png',
'a01-scroll-toast-v3.png',
'adaptive.adaptive-feedback-toast',
'a01-scroll-dialog-v3.png',
'chevron-right.png',
'v-model.trim="phone"',
@@ -52,7 +52,7 @@ foreach ($required in @(
'if (!/^\d{6}$/.test(verificationCode.value))',
'if (!password.value)',
'if (password.value !== confirmPassword.value)',
'border-image-slice: 58 280 fill;',
'adaptive.adaptive-feedback-toast;',
'top: calc(var(--status-bar-height, 0px) + 24rpx);',
'max-height: calc(var(--app-viewport-height) - 40px);'
)) {
@@ -0,0 +1,49 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$profilePath = Join-Path $root 'styles/adaptive-frame-profiles.scss'
if (-not (Test-Path -LiteralPath $profilePath)) {
throw 'Missing adaptive frame profile owner: styles/adaptive-frame-profiles.scss'
}
$profile = Get-Content -LiteralPath $profilePath -Raw -Encoding UTF8
foreach ($token in @(
'@mixin adaptive-auth-dialog',
'@mixin adaptive-scroll-button($type)',
'@mixin adaptive-module-content',
'@mixin adaptive-module-field',
'@mixin adaptive-feedback-toast',
'@mixin adaptive-genealogy-current-slip',
'@mixin adaptive-genealogy-list-card',
'@mixin adaptive-genealogy-state-panel',
'@mixin adaptive-genealogy-form-field',
'@mixin adaptive-g05-overview-surface',
'@mixin adaptive-g06-search-field',
'@mixin adaptive-g06-search-action',
'@mixin adaptive-g01-add-sheet',
'@mixin adaptive-g01-switcher',
'@mixin adaptive-family-content',
'@mixin adaptive-family-panel',
'@mixin adaptive-family-field',
'@mixin adaptive-family-letter',
'@mixin adaptive-notification-content',
'@mixin adaptive-profile-content',
'@mixin adaptive-profile-field',
'@mixin adaptive-profile-summary',
'@mixin adaptive-records-content',
'@mixin adaptive-records-field',
'@mixin adaptive-records-person',
'@mixin adaptive-tree-panel',
'@mixin adaptive-tree-field',
'a01-scroll-dialog-v3.png',
'border-image-slice: 260 240 360 240 fill;',
'border-image-slice: 56 300 fill;',
'border-image-slice: 105 150;',
'border-image-slice: 70 100 fill;'
)) {
if (-not $profile.Contains($token)) {
throw "Adaptive frame profile missing: $token"
}
}
Write-Output 'ADAPTIVE-FRAME-PROFILES-CONTRACT PASS'
+54
View File
@@ -0,0 +1,54 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
function Read-Utf8([string]$path) {
Get-Content -LiteralPath (Join-Path $root $path) -Raw -Encoding UTF8
}
$expected = @{
'components/AppToast.vue' = @('adaptive.adaptive-feedback-toast')
'pages/auth/a01-entry.vue' = @('adaptive.adaptive-feedback-toast')
'pages/auth/a04-register.vue' = @('adaptive.adaptive-feedback-toast')
'pages/auth/a05-reset-password.vue' = @('adaptive.adaptive-feedback-toast')
'pages/genealogy/g01-my-genealogies.vue' = @(
'adaptive.adaptive-genealogy-current-slip',
'adaptive.adaptive-genealogy-list-card',
'adaptive.adaptive-genealogy-state-panel',
'adaptive.adaptive-g01-add-sheet',
'adaptive.adaptive-g01-switcher'
)
'pages/genealogy/g09-my-applications.vue' = @('adaptive.adaptive-genealogy-list-card')
'pages/genealogy/g10-application-review.vue' = @(
'adaptive.adaptive-genealogy-form-field',
'adaptive.adaptive-genealogy-list-card',
'adaptive.adaptive-scroll-button',
'adaptive.adaptive-feedback-toast'
)
}
foreach ($path in $expected.Keys) {
$source = Read-Utf8 $path
if (-not $source.Contains('@use "../../styles/adaptive-frame-profiles.scss" as adaptive;') -and
-not $source.Contains('@use "../styles/adaptive-frame-profiles.scss" as adaptive;')) {
throw "$path must import the adaptive frame profile owner"
}
foreach ($token in $expected[$path]) {
if (-not $source.Contains($token)) { throw "$path missing adaptive surface: $token" }
}
if ($source -match '(?im)border-image-slice\s*:') {
throw "$path must not own border-image slice values"
}
}
$g01 = Read-Utf8 'pages/genealogy/g01-my-genealogies.vue'
$g10 = Read-Utf8 'pages/genealogy/g10-application-review.vue'
foreach ($source in @($g01, $g10)) {
if ($source -match '(?im)background\s*:[^;\r\n]*100%\s+100%[^;\r\n]*;') {
throw 'A/G variable surfaces must not stretch full images to 100% 100%'
}
}
$tabbar = Read-Utf8 'components/AppTabbar.vue'
if ($tabbar -match '(?im)(?<![-\w])height\s*:\s*112rpx;') { throw 'AppTabbar must use a minimum baseline instead of fixed content height' }
Write-Output 'AG-RESPONSIVE-SURFACES-CONTRACT PASS'
+28
View File
@@ -0,0 +1,28 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$button = Get-Content -LiteralPath (Join-Path $root 'components/AppButton.vue') -Raw -Encoding UTF8
foreach ($token in @(
'compact: { type: Boolean, default: false }',
"'app-button--compact': compact",
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
'@include adaptive.adaptive-scroll-button(primary);',
'@include adaptive.adaptive-scroll-button(secondary);',
'a01-scroll-primary-v3.png',
'a01-scroll-secondary-v3.png'
)) {
if (-not $button.Contains($token)) { throw "AppButton compact contract missing: $token" }
}
if ($button.Contains('border-image-slice: 56 300 fill;')) {
throw 'AppButton must consume the shared adaptive frame profile instead of owning slice values'
}
if ($button -notmatch '(?s)\.app-button--compact \.app-button__skin\s*\{[^}]*display:\s*none;') {
throw 'AppButton compact mode must hide the aspectFit image skin'
}
if ($button -notmatch '(?s)\.app-button--compact \.app-button__label\s*\{[^}]*padding:\s*0;[^}]*font-size:\s*23rpx;[^}]*letter-spacing:\s*0;[^}]*white-space:\s*nowrap;') {
throw 'AppButton compact label must preserve four-character action capacity'
}
Write-Output 'APP-BUTTON-COMPACT-CONTRACT PASS'
@@ -0,0 +1,32 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$dialog = Get-Content -LiteralPath (Join-Path $root 'components/AppDialog.vue') -Raw -Encoding UTF8
foreach ($token in @(
'compactActions: { type: Boolean, default: false }',
'class="app-dialog__copy"',
':compact="compactActions && showCancel"',
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
'@include adaptive.adaptive-auth-dialog;',
'max-height: calc(var(--app-viewport-height, 100vh) - 160rpx);',
'margin-top: 26rpx;'
)) {
if (-not $dialog.Contains($token)) { throw "AppDialog responsive contract missing: $token" }
}
foreach ($forbidden in @(
'min-height: 520rpx;',
'center / contain no-repeat',
'margin-top: auto;'
)) {
if ($dialog.Contains($forbidden)) { throw "AppDialog responsive contract forbids: $forbidden" }
}
foreach ($rule in @(
'(?s)\.app-dialog__copy\s*\{[^}]*display:\s*flex;[^}]*width:\s*100%;[^}]*flex-direction:\s*column;',
'(?s)\.app-dialog__title,\s*\.app-dialog__message\s*\{[^}]*display:\s*block;[^}]*width:\s*100%;'
)) {
if ($dialog -notmatch $rule) { throw "AppDialog responsive layout rule missing: $rule" }
}
Write-Output 'APP-DIALOG-RESPONSIVE-CONTENT-CONTRACT PASS'
+3 -3
View File
@@ -3,9 +3,9 @@ $root = Split-Path $PSScriptRoot -Parent
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/genealogy/g01-my-genealogies.vue'), [System.Text.Encoding]::UTF8)
foreach ($expected in @(
'background: url("/static/assets/modules/genealogy/transparent/current-slip-frame.png") center / 100% 100% no-repeat',
'background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat',
'background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png") center / 100% 100% no-repeat',
'@include adaptive.adaptive-genealogy-current-slip',
'@include adaptive.adaptive-genealogy-list-card',
'@include adaptive.adaptive-genealogy-state-panel',
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat',
'grid-template-columns: minmax(0, 1fr) 96rpx'
)) {
+5 -4
View File
@@ -59,6 +59,7 @@ if ($tabbar -notmatch 'font-size:\s*30rpx') { throw 'Tabbar label size does not
if ($tabbar -notmatch 'width:\s*64rpx') { throw 'Tabbar icon size does not match the approved visual weight.' }
$page = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages/genealogy/g01-my-genealogies.vue')
$profiles = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'styles/adaptive-frame-profiles.scss')
if ($page -match "from '@/utils/api\.js'") { throw 'G-01 visual phase must not call the remote API.' }
foreach ($state in @('isLoading', 'hasGenealogies', 'createdGenealogies', 'joinedGenealogies')) {
if ($page -notmatch $state) { throw "G-01 is missing presentation state: $state" }
@@ -127,10 +128,10 @@ if ($page -match '(?s)\.add-dialog__heading\s*\{[^}]*top\s*:') {
if ($page -notmatch '(?s)\.add-dialog__close\s*\{[^}]*grid-column:\s*2;[^}]*grid-row:\s*1\s*/\s*span\s+2;[^}]*margin-right:\s*-22rpx;') {
throw 'G-01 add sheet close control does not follow the heading grid.'
}
if ($page -notmatch [regex]::Escape('/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png')) {
if ($page -notmatch 'adaptive\.adaptive-g01-add-sheet' -or $profiles -notmatch [regex]::Escape('/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png')) {
throw 'G-01 add sheet does not use the approved complete background asset.'
}
if ($page -notmatch '(?s)\.add-dialog\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3\.png"\);[^}]*border-image-slice:\s*220\s+0\s+1\s+0\s+fill;[^}]*border-image-width:\s*118rpx\s+0\s+1rpx;') {
if ($profiles -notmatch '(?s)@mixin\s+adaptive-g01-add-sheet\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3\.png"\);[^}]*border-image-slice:\s*220\s+0\s+1\s+0\s+fill;[^}]*border-image-width:\s*118rpx\s+0\s+1rpx;') {
throw 'G-01 add sheet does not preserve the complete sheet top while stretching only the paper body.'
}
if ($page -notmatch '(?s)\.add-dialog__close-icon\s*\{[^}]*width:\s*80rpx;[^}]*height:\s*80rpx;') {
@@ -155,7 +156,7 @@ if ($page -notmatch '(?s)\.genealogy-switcher-layer\s*\{[^}]*align-items:\s*cent
if ($page -match 'class="genealogy-switcher__skin"') {
throw 'G-01 switcher must not render the complete background as a fixed aspectFit image.'
}
if ($page -notmatch '(?s)\.genealogy-switcher\s*\{[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);[^}]*border-image-source:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\);[^}]*border-image-slice:\s*300\s+260\s+360\s+260\s+fill;[^}]*border-image-width:\s*110rpx\s+48rpx\s+132rpx\s+48rpx;') {
if ($page -notmatch '(?s)\.genealogy-switcher\s*\{[^}]*@include\s+adaptive\.adaptive-g01-switcher;[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);' -or $profiles -notmatch '(?s)@mixin\s+adaptive-g01-switcher\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\);[^}]*border-image-slice:\s*300\s+260\s+360\s+260\s+fill;[^}]*border-image-width:\s*110rpx\s+48rpx\s+132rpx\s+48rpx;') {
throw 'G-01 switcher does not use the approved stretchable complete background.'
}
if ($page -notmatch '(?s)\.genealogy-switcher__content\s*\{[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);[^}]*padding:\s*120rpx\s+58rpx\s+140rpx;') {
@@ -182,7 +183,7 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
foreach ($asset in @('shortcut-tree.png', 'shortcut-members.png', 'shortcut-generation-poem.png', 'shortcut-application.png', 'add.png')) {
if ($page -notmatch [regex]::Escape($asset)) { throw "G-01 does not consume $asset" }
}
if ($page -notmatch 'current-slip-frame\.png') { throw 'G-01 does not use the approved current genealogy frame asset.' }
if ($page -notmatch 'adaptive\.adaptive-genealogy-current-slip' -or $profiles -notmatch 'current-slip-frame\.png') { throw 'G-01 does not consume the approved current genealogy frame profile.' }
foreach ($token in @('current-summary', 'current-meta-item', 'current-seal-frame', 'meta-location.png', 'meta-member.png', 'meta-admin.png', 'create-cloud.png')) {
if ($page -notmatch $token) { throw "G-01 current panel is missing $token" }
}
+3 -1
View File
@@ -17,6 +17,7 @@ $root = Split-Path -Parent $PSScriptRoot
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
$paths = @($pages.pages.path)
$g03 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
$g04 = Join-Path $root 'pages/genealogy/g04-first-ancestor.vue'
$panelPath = Join-Path $root 'static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png'
@@ -36,7 +37,7 @@ foreach ($required in @(
'class="duplicate-reminder-layer"',
'if (isSubmitting.value) return;',
'/pages/genealogy/g05-genealogy-overview?genealogyId=',
'g01-empty-panel-frame.png',
'adaptive.adaptive-genealogy-state-panel',
'a01-scroll-primary-v3.png',
'create-flow-panel',
'flow-primary-action',
@@ -47,6 +48,7 @@ foreach ($required in @(
)) {
Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required"
}
Assert-Contains -Content $profiles -Expected 'g01-empty-panel-frame.png' -Message 'Adaptive genealogy panel profile must own the G03 panel asset'
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=', 'class="flow-header"', 'flow-header__back', 'root-header-cinnabar.jpg')) {
if ($g03 -match [regex]::Escape($forbidden)) { throw "G03 retains forbidden implementation: $forbidden" }
+3 -1
View File
@@ -17,6 +17,7 @@ function Assert-NoCssSurface {
$root = Split-Path -Parent $PSScriptRoot
$g01 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g01-my-genealogies.vue') -Raw -Encoding utf8
$g05 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g05-genealogy-overview.vue') -Raw -Encoding utf8
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
$runtimeSmoke = Get-Content -LiteralPath (Join-Path $root 'tests/g05-overview-runtime-smoke.js') -Raw -Encoding utf8
$asset = Join-Path $root 'static/assets/modules/genealogy/opaque/g05-overview-surface.png'
@@ -34,7 +35,7 @@ foreach ($required in @(
'overview-state--empty',
'overview-state--error',
'overview-state--no-permission',
'g05-overview-surface.png',
'adaptive.adaptive-g05-overview-surface',
'/pages/genealogy/g03-create-genealogy?step=ancestor&genealogyId=',
'/pages/tree/t01-tree-overview?genealogyId=',
'/pages/genealogy/g10-application-review?genealogyId=',
@@ -44,6 +45,7 @@ foreach ($required in @(
)) {
Assert-Contains $g05 $required "Missing G05 overview contract: $required"
}
Assert-Contains $profiles 'g05-overview-surface.png' 'Adaptive G05 surface profile must own the overview asset'
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
if ($g05 -match [regex]::Escape($forbidden)) { throw "G05 retains forbidden implementation: $forbidden" }
}
+7 -2
View File
@@ -13,17 +13,22 @@ if ($positions.Count -ne 0) {
}
foreach ($required in @(
'background: url("/static/assets/modules/genealogy/opaque/g06-search-title-strip.png")',
'background: url("/static/assets/modules/genealogy/opaque/g06-search-input-wide.png")',
'background: url("/static/assets/modules/genealogy/opaque/g06-search-button.png")',
'background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")',
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
)) {
if ($page -notmatch [regex]::Escape($required)) {
if ($styleWithoutComments -notmatch [regex]::Escape($required)) {
throw "G06 document-flow migration is missing asset-backed surface: $required"
}
}
foreach ($forbidden in @('search-title-strip', 'g06-search-title-strip.png')) {
if ($page -match [regex]::Escape($forbidden)) {
throw "G06 must not retain removed title-strip token: $forbidden"
}
}
foreach ($obsoleteClass in @('search-title-strip__skin', 'search-field__skin', 'search-action__skin', 'result-card__skin')) {
if ($page -match "class=`"$obsoleteClass`"") {
throw "G06 must not retain decorative image layer .$obsoleteClass"
+8 -8
View File
@@ -17,8 +17,8 @@ $root = Split-Path -Parent $PSScriptRoot
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
$paths = @($pages.pages.path)
$g06 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g06-search-genealogies.vue') -Raw -Encoding utf8
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
$g07 = Join-Path $root 'pages/genealogy/g07-search-result.vue'
$titleStripPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g06-search-title-strip.png'
$inputSkinPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g06-search-input-wide.png'
$buttonSkinPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g06-search-button.png'
@@ -45,10 +45,8 @@ foreach ($required in @(
'/pages/genealogy/g08-join-application?source=invite&genealogyId=',
'/pages/genealogy/g05-genealogy-overview?mode=public&genealogyId=',
'search-page__header',
'g06-search-input-wide.png',
'g06-search-button.png',
'g06-search-title-strip.png',
'search-title-strip',
'adaptive.adaptive-g06-search-field',
'adaptive.adaptive-g06-search-action',
'section-divider.png',
'root-header-hall.png',
'search-controls',
@@ -59,8 +57,11 @@ foreach ($required in @(
)) {
Assert-Contains -Content $g06 -Expected $required -Message "Missing G06 flow contract: $required"
}
foreach ($assetName in @('g06-search-input-wide.png', 'g06-search-button.png')) {
Assert-Contains $profiles $assetName "Adaptive G06 profile must own: $assetName"
}
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', 'search-title-strip', 'g06-search-title-strip.png')) {
if ($g06 -match [regex]::Escape($forbidden)) { throw "G06 retains forbidden implementation: $forbidden" }
}
@@ -69,7 +70,7 @@ if ($g06 -match [regex]::Escape('g06-search-panel-v2.png')) { throw 'G06 must no
if ($g06 -match [regex]::Escape('search-hero')) { throw 'G06 must replace the floating hero with the selected title-strip hierarchy' }
if ($g06 -match [regex]::Escape('g06-search-input.png')) { throw 'G06 must use the wide selected-design input skin instead of the tall legacy skin' }
foreach ($className in @('search-title-strip', 'search-field', 'search-action')) {
foreach ($className in @('search-field', 'search-action')) {
Assert-NoCssSurface -Content $g06 -ClassName $className
}
@@ -84,7 +85,6 @@ if ($g06 -notmatch '(?s)\.status-retry\s*\{[^}]*width:\s*300rpx;[^}]*height:\s*7
Add-Type -AssemblyName System.Drawing
foreach ($asset in @(
@{ Path = $titleStripPath; Width = 1122; Height = 220; Label = 'search-title-strip' },
@{ Path = $inputSkinPath; Width = 1120; Height = 248; Label = 'search-input' },
@{ Path = $buttonSkinPath; Width = 300; Height = 132; Label = 'search-button' }
)) {
+9 -4
View File
@@ -18,6 +18,7 @@ $root = Split-Path -Parent $PSScriptRoot
$g08 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g08-join-application.vue') -Raw -Encoding utf8
$g09 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g09-my-applications.vue') -Raw -Encoding utf8
$g10 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g10-application-review.vue') -Raw -Encoding utf8
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
foreach ($page in @($g08, $g09, $g10)) {
@@ -31,8 +32,8 @@ foreach ($required in @(
'join-state--form',
'join-state--success',
'join-state--error',
'g01-empty-panel-frame.png',
'g-form-field-frame.png',
'adaptive.adaptive-genealogy-state-panel',
'adaptive.adaptive-genealogy-form-field',
'a01-scroll-primary-v3.png',
'const genealogyPreview',
'query.genealogyId',
@@ -42,6 +43,9 @@ foreach ($required in @(
'/pages/genealogy/g09-my-applications',
'/pages/genealogy/g01-my-genealogies?genealogyId='
)) { Assert-Contains $g08 $required "Missing G08 join contract: $required" }
foreach ($assetName in @('g01-empty-panel-frame.png', 'g-form-field-frame.png')) {
Assert-Contains $profiles $assetName "Adaptive genealogy profile must own: $assetName"
}
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
if ($g08 -match [regex]::Escape($nativeUi)) { throw "G08 must not use native UniApp UI: $nativeUi" }
}
@@ -54,7 +58,7 @@ foreach ($required in @(
'application-state--list',
'application-state--empty',
'application-state--error',
'list-slip-frame.png',
'adaptive.adaptive-genealogy-list-card',
'"PENDING"',
'"APPROVED"',
'"REJECTED"',
@@ -65,6 +69,7 @@ foreach ($required in @(
'<AppDialog',
'/pages/genealogy/g08-join-application?source=search&previous=rejected&genealogyId='
)) { Assert-Contains $g09 $required "Missing G09 application contract: $required" }
Assert-Contains $profiles 'list-slip-frame.png' 'Adaptive genealogy list profile must own the G09 card asset'
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
if ($g09 -match [regex]::Escape($nativeUi)) { throw "G09 must not use native UniApp UI: $nativeUi" }
}
@@ -74,7 +79,7 @@ foreach ($required in @(
'review-state--list',
'review-state--empty',
'review-state--error',
'list-slip-frame.png',
'adaptive.adaptive-genealogy-list-card',
'a01-scroll-primary-v3.png',
'a01-scroll-secondary-v3.png',
'const reviewSamples',
+6
View File
@@ -9,8 +9,14 @@ $rules = @(
'(?s)\.application-card__status\s*\{[^}]*font-size:\s*25rpx;',
'(?s)\.application-card__hint\s*\{[^}]*font-size:\s*23rpx;',
'(?s)\.application-card__action\s*\{[^}]*font-size:\s*23rpx;',
'(?s)\.application-card__action\s*\{[^}]*white-space:\s*nowrap;',
'(?s)\.application-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;[^}]*min-height:\s*210rpx;[^}]*margin-bottom:\s*12rpx;',
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*174rpx;[^}]*gap:\s*8rpx 14rpx;[^}]*padding:\s*26rpx 28rpx 22rpx 40rpx;',
'(?s)\.application-state-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;'
)
if ($page -match '(?s)\.application-card\s*\{[^}]*background:\s*url\("/static/assets/modules/genealogy/transparent/list-slip-frame\.png"\)[^}]*100%\s+100%') {
throw 'G09 application card must not stretch the complete frame asset to 100% 100%'
}
foreach ($rule in $rules) {
if ($page -notmatch $rule) { throw "G09 readable visual rule missing: $rule" }
}
+22 -5
View File
@@ -6,8 +6,12 @@ foreach ($required in @(
'class="review-result"',
'review-result--approved',
'review-result--rejected',
'a01-secondary-button-v2.png',
'a01-primary-button-v2.png',
'<AppButton',
'compact',
'type="secondary"',
'<AppDialog',
'compact-actions',
'class="review-page__retry"',
'a01-scroll-secondary-v3.png',
'a01-scroll-primary-v3.png'
)) {
@@ -19,11 +23,24 @@ $rules = @(
'(?s)\.application-card__phone\s*\{[^}]*font-size:\s*23rpx;',
'(?s)\.application-card__time\s*\{[^}]*font-size:\s*23rpx;',
'(?s)\.application-card__relation\s*\{[^}]*font-size:\s*24rpx;',
'(?s)\.review-action\s*\{[^}]*width:\s*148rpx;[^}]*height:\s*68rpx;',
'(?s)\.review-action text\s*\{[^}]*font-size:\s*24rpx;',
'(?s)\.review-result\s*\{[^}]*width:\s*308rpx;[^}]*height:\s*68rpx;',
'(?s)\.application-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;[^}]*min-height:\s*218rpx;[^}]*margin-bottom:\s*12rpx;',
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*182rpx;[^}]*gap:\s*8rpx 0;[^}]*padding:\s*28rpx 28rpx 22rpx 40rpx;',
'(?s)\.review-actions \.app-button\s*\{[^}]*width:\s*148rpx;[^}]*min-height:\s*68rpx;',
'(?s)\.review-result\s*\{[^}]*@include\s+adaptive\.adaptive-scroll-button\(primary\);[^}]*width:\s*308rpx;[^}]*min-height:\s*68rpx;',
'(?s)\.review-state-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;',
'(?s)\.review-state-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;'
)
if ($page -match 'a01-(?:primary|secondary)-button-v2\.png') {
throw 'G10 must not render the ultra-wide v2 assets through aspectFit inside narrow action buttons'
}
if ($page -match '(?s)class="review-page__retry"[^>]*>\s*<image') {
throw 'G10 retry action must use AppButton instead of detached image and text nodes'
}
foreach ($selector in @('application-card', 'review-state-card')) {
if ($page -match "(?s)\.$selector\s*\{[^}]*background:[^}]*list-slip-frame\.png[^}]*100%\s+100%") {
throw "G10 .$selector must not stretch the complete frame asset to 100% 100%"
}
}
foreach ($rule in $rules) {
if ($page -notmatch $rule) { throw "G10 readable visual rule missing: $rule" }
}
+7 -4
View File
@@ -3,18 +3,21 @@ $root = Split-Path $PSScriptRoot -Parent
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/genealogy/g10-application-review.vue'), [System.Text.Encoding]::UTF8)
foreach ($expected in @(
'background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat',
'@include adaptive.adaptive-genealogy-list-card',
'@include adaptive.adaptive-feedback-toast',
'background: url("/static/assets/modules/genealogy/transparent/section-divider.png") bottom center / 100% 14rpx no-repeat',
'grid-template-columns: auto minmax(0, 1fr) auto',
'padding: 44rpx 52rpx 29rpx 64rpx',
'grid-area: 1 / 1',
'padding: 28rpx 28rpx 22rpx 40rpx',
'<AppButton',
'compact-actions',
'z-index: 1',
'background: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png") center / 100% 100% no-repeat'
'@include adaptive.adaptive-genealogy-form-field'
)) {
if (-not $page.Contains($expected)) { throw "G10 document-flow contract missing: $expected" }
}
if ($page -notmatch '(?s)\.review-feedback\s*\{[^}]*position:\s*fixed;') { throw 'G10 feedback must remain fixed' }
if ([regex]::Matches($page, 'position\s*:').Count -ne 1) { throw 'G10 must keep only its fixed feedback positioned' }
if ($page -match 'class="(?:application-card__skin|review-state-card__skin)"|review-intro[^>]*>.*?section-divider\.png|review-feedback[^>]*>\s*<image') { throw 'G10 decorative frames must be container backgrounds' }
if ($page -match '(?s)\.(?:application-card|review-state-card)\s*\{[^}]*background:[^}]*list-slip-frame\.png[^}]*100%\s+100%') { throw 'G10 card frames must use nine-slice border images instead of complete-image stretching' }
Write-Output 'G10-DOCUMENT-FLOW-CONTRACT PASS'
+7 -3
View File
@@ -17,12 +17,13 @@ function Assert-NoCssSurface {
$root = Split-Path -Parent $PSScriptRoot
$g11 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g11-genealogy-settings.vue') -Raw -Encoding utf8
$g12 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g12-generation-poems.vue') -Raw -Encoding utf8
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
foreach ($page in @($g11, $g12)) {
if ($page -match '<ModulePage') { throw 'G11-G12 must not retain the generic ModulePage shell' }
Assert-Contains $page '<GenealogyPageBackground />' 'G11-G12 must use the accepted shared genealogy background'
Assert-Contains $page 'g01-empty-panel-frame.png' 'G11-G12 forms must reuse the complete genealogy paper panel'
Assert-Contains $page 'adaptive.adaptive-genealogy-state-panel' 'G11-G12 forms must reuse the adaptive genealogy paper panel profile'
if ($page -match "@/utils/api\.js|\bappApi\b") { throw 'G11-G12 design phase must not connect the API layer' }
}
@@ -34,7 +35,7 @@ foreach ($required in @(
'settings-state--no-permission',
'const nameError = ref',
'class="settings-feedback"',
'g-form-field-frame.png',
'adaptive.adaptive-genealogy-form-field',
'a01-scroll-primary-v3.png',
'const genealogyDraft',
'query.genealogyId',
@@ -51,13 +52,16 @@ foreach ($required in @(
'poem-state--no-permission',
'const poemError = ref',
'class="poem-feedback"',
'g-form-field-frame.png',
'adaptive.adaptive-genealogy-form-field',
'a01-scroll-primary-v3.png',
'a01-scroll-secondary-v3.png',
'const poemRows',
'stopMissingOldGeneration',
'query.genealogyId'
)) { Assert-Contains $g12 $required "Missing G12 generation-poem contract: $required" }
foreach ($assetName in @('g01-empty-panel-frame.png', 'g-form-field-frame.png')) {
Assert-Contains $profiles $assetName "Adaptive genealogy profile must own: $assetName"
}
foreach ($page in @($g11, $g12)) {
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
+36
View File
@@ -0,0 +1,36 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$vueFiles = @(
Get-ChildItem -LiteralPath (Join-Path $root 'pages') -Recurse -Filter '*.vue'
Get-ChildItem -LiteralPath (Join-Path $root 'components') -Recurse -Filter '*.vue'
)
$specificCopy = @(
'\u6C64\u6B63\u534E',
'\u6C64\u6B63\u56FD',
'\u6C64\u6587\u8FDC',
'\u6C64\u6C0F\u5BB6\u8C31',
'\u5982\uFF1A\u6C64'
)
foreach ($file in $vueFiles) {
$lineNumber = 0
foreach ($line in Get-Content -LiteralPath $file.FullName -Encoding utf8) {
$lineNumber += 1
if ($line -notmatch 'placeholder|例如|比如|如:') { continue }
foreach ($token in $specificCopy) {
if ($line -match $token) {
throw "User guidance must not contain specific copy '$token': $($file.FullName):$lineNumber"
}
}
}
}
$g08 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g08-join-application.vue') -Raw -Encoding utf8
foreach ($required in @(
'placeholder="\u4F8B\u5982\uFF1A\u67D0\u67D0\u67D0\u5802\u4F84"',
'\u8BF7\u4EE5\u5BB6\u8C31\u4E2D\u4E00\u4F4D\u5DF2\u77E5\u957F\u8F88\u4E3A\u53C2\u7167\uFF0C\u4F8B\u5982\uFF1A\u67D0\u67D0\u67D0\u5802\u4F84'
)) {
if ($g08 -notmatch $required) { throw "G08 generic relationship guidance missing: $required" }
}
Write-Output 'GENERIC-GUIDANCE-COPY-CONTRACT PASS'
@@ -15,14 +15,15 @@ foreach ($component in @('AppButton.vue', 'AppDialog.vue', 'AppToast.vue', 'AppL
}
$button = Read-Utf8 (Join-Path $componentsRoot 'AppButton.vue')
if ($button -notmatch 'a01-scroll-primary-v3\.png') { throw 'AppButton does not own the approved A-series primary scroll asset.' }
if ($button -notmatch 'a01-scroll-secondary-v3\.png') { throw 'AppButton does not own the approved A-series secondary scroll asset.' }
$profiles = Read-Utf8 (Join-Path $root 'styles/adaptive-frame-profiles.scss')
if ($button -notmatch 'adaptive-scroll-button' -or $profiles -notmatch 'a01-scroll-primary-v3\.png') { throw 'AppButton does not consume the approved A-series primary scroll profile.' }
if ($button -notmatch 'adaptive-scroll-button' -or $profiles -notmatch 'a01-scroll-secondary-v3\.png') { throw 'AppButton does not consume the approved A-series secondary scroll profile.' }
$dialog = Read-Utf8 (Join-Path $componentsRoot 'AppDialog.vue')
if ($dialog -notmatch 'a01-scroll-dialog-v3\.png') { throw 'AppDialog does not own the approved A-series dialog asset.' }
if ($dialog -notmatch 'adaptive-auth-dialog' -or $profiles -notmatch 'a01-scroll-dialog-v3\.png') { throw 'AppDialog does not consume the approved A-series dialog profile.' }
$toast = Read-Utf8 (Join-Path $componentsRoot 'AppToast.vue')
if ($toast -notmatch 'a01-scroll-toast-v3\.png') { throw 'AppToast does not own the approved A-series toast asset.' }
if ($toast -notmatch 'adaptive-feedback-toast' -or $profiles -notmatch 'a01-scroll-toast-v3\.png') { throw 'AppToast does not consume the approved A-series toast profile.' }
$header = Read-Utf8 (Join-Path $componentsRoot 'PageHeader.vue')
if ($header -notmatch 'chevron-right\.png') { throw 'PageHeader does not use the approved image arrow.' }
+10 -3
View File
@@ -19,6 +19,9 @@ foreach ($anchor in @(
'<AppToast',
'<ModulePageBackground',
':style="moduleAssetStyle"',
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
'@include adaptive.adaptive-module-field;',
'@include adaptive.adaptive-module-content;',
'const moduleAssetStyle = computed(() => ({',
'"--module-content-asset": `url(${contentAsset.value})`',
'"--module-field-asset": `url(${fieldAsset.value})`',
@@ -31,7 +34,10 @@ foreach ($forbidden in @(
'class="page-intro"',
'g03-create-flow-panel.png',
'g06-search-input-wide.png',
'application-status-card.png'
'application-status-card.png',
'background: var(--module-field-asset) center / 100% 100% no-repeat;',
'background: var(--module-content-asset) center / 100% 100% no-repeat;',
'height: 100%;'
)) {
if ($component -match [regex]::Escape($forbidden)) { throw "ModulePage must not keep obsolete shared surface: $forbidden" }
}
@@ -52,8 +58,9 @@ foreach ($rule in @(
'(?s)\.module-page\s*\{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;',
'(?s)\.module-page__header,\s*\.module-page__content,\s*\.module-page__loading\s*\{[^}]*z-index:\s*1;',
'(?s)\.module-lead\s*\{[^}]*background:\s*url\("/static/assets/modules/genealogy/transparent/section-divider\.png"\) center / 100% 100% no-repeat;',
'(?s)\.form-row,\s*\.settings-row\s*\{[^}]*background:\s*var\(--module-field-asset\) center / 100% 100% no-repeat;',
'(?s)\.list-card,\s*\.detail-card,\s*\.timeline-row,\s*\.status-card\s*\{[^}]*background:\s*var\(--module-content-asset\) center / 100% 100% no-repeat;'
'(?s)\.module-lead\s*\{[^}]*display:\s*flex;[^}]*align-items:\s*center;[^}]*justify-content:\s*center;',
'(?s)\.form-row,\s*\.settings-row\s*\{[^}]*@include\s+adaptive\.adaptive-module-field;',
'(?s)\.list-card,\s*\.detail-card,\s*\.timeline-row,\s*\.status-card\s*\{[^}]*@include\s+adaptive\.adaptive-module-content;'
)) {
if ($component -notmatch $rule) { throw "ModulePage document-flow background rule missing: $rule" }
}
@@ -0,0 +1,72 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$coveragePath = Join-Path $PSScriptRoot 'responsive-layout-coverage.json'
$allowlistPath = Join-Path $PSScriptRoot 'responsive-layout-allowlist.json'
if (-not (Test-Path -LiteralPath $coveragePath)) { throw 'Missing responsive layout coverage owner' }
if (-not (Test-Path -LiteralPath $allowlistPath)) { throw 'Missing responsive layout allowlist owner' }
$coverage = Get-Content -LiteralPath $coveragePath -Raw -Encoding UTF8 | ConvertFrom-Json
$allowlist = Get-Content -LiteralPath $allowlistPath -Raw -Encoding UTF8 | ConvertFrom-Json
$requiredFoundation = @(
'components/AppButton.vue',
'components/AppDialog.vue',
'components/ModulePage.vue'
)
foreach ($required in $requiredFoundation) {
if ($coverage.covered -notcontains $required) {
throw "Responsive coverage cannot remove migrated foundation file: $required"
}
}
function Normalize-Selector([string]$selector) {
return (($selector -replace '\s+', ' ').Trim())
}
function Test-Allowlisted([string]$path, [string]$selector, [string]$property) {
$normalizedSelector = Normalize-Selector $selector
foreach ($entry in $allowlist.exceptions) {
if ($entry.path -eq $path -and
(Normalize-Selector $entry.selector) -eq $normalizedSelector -and
$entry.property -eq $property) {
return $true
}
}
return $false
}
$violations = [System.Collections.Generic.List[string]]::new()
foreach ($relativePath in $coverage.covered) {
$fullPath = Join-Path $root $relativePath
if (-not (Test-Path -LiteralPath $fullPath)) {
$violations.Add("missing covered file: $relativePath")
continue
}
$source = Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8
$styleMatches = [regex]::Matches($source, '(?is)<style\b[^>]*>(?<css>.*?)</style>')
foreach ($styleMatch in $styleMatches) {
$css = $styleMatch.Groups['css'].Value
$ruleMatches = [regex]::Matches($css, '(?ms)(?<selector>[^{}]+)\{(?<body>[^{}]*)\}')
foreach ($rule in $ruleMatches) {
$selector = Normalize-Selector $rule.Groups['selector'].Value
$body = $rule.Groups['body'].Value
$checks = @(
@{ Name = 'fixed-height'; Pattern = '(?im)(?<![-\w])height\s*:\s*\d+(?:\.\d+)?rpx\b' },
@{ Name = 'background-stretch'; Pattern = '(?im)background\s*:[^;\r\n]*100%\s+100%[^;\r\n]*;' },
@{ Name = 'direct-border-image-slice'; Pattern = '(?im)border-image-slice\s*:' }
)
foreach ($check in $checks) {
if ($body -match $check.Pattern -and -not (Test-Allowlisted $relativePath $selector $check.Name)) {
$violations.Add("$relativePath :: $selector :: $($check.Name)")
}
}
}
}
}
if ($violations.Count -gt 0) {
throw ("Responsive layout contract violations:`n - " + ($violations -join "`n - "))
}
Write-Output 'PROJECT-RESPONSIVE-LAYOUT-CONTRACT PASS'
+84
View File
@@ -0,0 +1,84 @@
{
"exceptions": [
{ "path": "components/AppLoading.vue", "selector": ".app-loading--page .app-loading__seal", "property": "fixed-height", "reason": "Fixed-ratio loading artwork." },
{ "path": "components/AppLoading.vue", "selector": ".app-loading--page .app-loading__knot", "property": "fixed-height", "reason": "Fixed-ratio loading ornament." },
{ "path": "components/AppLoading.vue", "selector": ".app-loading--section .app-loading__seal", "property": "fixed-height", "reason": "Fixed-ratio loading artwork." },
{ "path": "components/AppLoading.vue", "selector": ".app-loading--section .app-loading__knot", "property": "fixed-height", "reason": "Fixed-ratio loading ornament." },
{ "path": "components/AppTabbar.vue", "selector": ".tab-icon", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
{ "path": "components/GenealogyCard.vue", "selector": ".surname-seal", "property": "fixed-height", "reason": "Fixed-ratio surname seal." },
{ "path": "components/GenealogyCard.vue", "selector": ".surname-seal-copy", "property": "fixed-height", "reason": "Text overlay follows fixed seal artwork." },
{ "path": "components/GenealogyCard.vue", "selector": ".card-meta-icon", "property": "fixed-height", "reason": "Fixed-ratio metadata icon." },
{ "path": "components/GenealogyCard.vue", "selector": ".card-chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
{ "path": "components/PageHeader.vue", "selector": ".header-hall", "property": "fixed-height", "reason": "Fixed-ratio header artwork." },
{ "path": "components/PageHeader.vue", "selector": ".header-icon-button", "property": "fixed-height", "reason": "Minimum header touch target." },
{ "path": "components/PageHeader.vue", "selector": ".header-logo", "property": "fixed-height", "reason": "Fixed-ratio brand mark." },
{ "path": "components/PageHeader.vue", "selector": ".header-notice-icon", "property": "fixed-height", "reason": "Fixed-ratio notice icon." },
{ "path": "components/PageHeader.vue", "selector": ".notice-dot", "property": "fixed-height", "reason": "Fixed notification indicator." },
{ "path": "components/PageHeader.vue", "selector": ".header-back", "property": "fixed-height", "reason": "Minimum header touch target." },
{ "path": "components/PageHeader.vue", "selector": ".header-back__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
{ "path": "components/PageHeader.vue", "selector": ".page-header--root .header-logo", "property": "fixed-height", "reason": "Fixed-ratio root brand mark." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".title-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".input-icon", "property": "fixed-height", "reason": "Fixed-ratio form icon." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".password-toggle__icon", "property": "fixed-height", "reason": "Fixed-ratio password icon." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".divider-line", "property": "fixed-height", "reason": "One-pixel ornamental rule." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".divider-knot", "property": "fixed-height", "reason": "Fixed-ratio divider ornament." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".wechat-icon", "property": "fixed-height", "reason": "Fixed-ratio provider icon." },
{ "path": "pages/auth/a01-entry.vue", "selector": ".agreement-icon", "property": "fixed-height", "reason": "Fixed-ratio agreement control." },
{ "path": "pages/auth/a04-register.vue", "selector": ".back-button__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
{ "path": "pages/auth/a04-register.vue", "selector": ".register-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
{ "path": "pages/auth/a04-register.vue", "selector": ".agreement-icon", "property": "fixed-height", "reason": "Fixed-ratio agreement control." },
{ "path": "pages/auth/a05-reset-password.vue", "selector": ".back-button__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
{ "path": "pages/auth/a05-reset-password.vue", "selector": ".reset-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
{ "path": "pages/auth/a05-reset-password.vue", "selector": ".success-mark", "property": "fixed-height", "reason": "Fixed-ratio success icon." },
{ "path": "pages/auth/a06-auth-status.vue", "selector": ".back-button__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
{ "path": "pages/auth/a06-auth-status.vue", "selector": ".status-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
{ "path": "pages/auth/a06-auth-status.vue", "selector": ".status-icon", "property": "fixed-height", "reason": "Fixed-ratio status icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".current-seal", "property": "fixed-height", "reason": "Fixed-ratio genealogy seal." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".current-info-divider", "property": "fixed-height", "reason": "One-pixel ornamental rule." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".current-meta-icon", "property": "fixed-height", "reason": "Fixed-ratio metadata icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".shortcut-icon", "property": "fixed-height", "reason": "Fixed-ratio shortcut icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".section-divider", "property": "fixed-height", "reason": "Fixed-ratio section ornament." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".section-heading::before", "property": "fixed-height", "reason": "Fixed heading accent." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".application-record__chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".create-icon", "property": "fixed-height", "reason": "Fixed-ratio action icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".create-action .create-icon", "property": "fixed-height", "reason": "Responsive override for fixed-ratio action icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".create-cloud", "property": "fixed-height", "reason": "Fixed-ratio decorative cloud." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".error-panel__seal", "property": "fixed-height", "reason": "Fixed-ratio state icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".error-panel__divider", "property": "fixed-height", "reason": "Fixed-ratio state ornament." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".empty-seal", "property": "fixed-height", "reason": "Fixed-ratio empty-state seal." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".empty-divider", "property": "fixed-height", "reason": "Fixed-ratio empty-state ornament." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".add-dialog__close", "property": "fixed-height", "reason": "Minimum close touch target." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".add-dialog__close-icon", "property": "fixed-height", "reason": "Fixed-ratio close icon." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".genealogy-switcher__close", "property": "fixed-height", "reason": "Minimum close touch target." },
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".genealogy-switcher__close-icon", "property": "fixed-height", "reason": "Fixed-ratio close icon." },
{ "path": "pages/genealogy/g05-genealogy-overview.vue", "selector": ".overview-state__seal", "property": "fixed-height", "reason": "Fixed-ratio state icon." },
{ "path": "pages/genealogy/g06-search-genealogies.vue", "selector": ".search-divider", "property": "fixed-height", "reason": "Fixed-ratio section ornament." },
{ "path": "pages/genealogy/g06-search-genealogies.vue", "selector": ".search-status__cloud", "property": "fixed-height", "reason": "Fixed-ratio decorative cloud." },
{ "path": "pages/genealogy/g06-search-genealogies.vue", "selector": ".search-status__hall", "property": "fixed-height", "reason": "Fixed-ratio state artwork." },
{ "path": "pages/family/f09-media-upload.vue", "selector": ".media-album-card__cover", "property": "fixed-height", "reason": "Fixed-ratio media thumbnail." },
{ "path": "pages/family/f09-media-upload.vue", "selector": ".media-photo-editor__thumb", "property": "fixed-height", "reason": "Fixed-ratio media thumbnail." },
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-hero__hall", "property": "fixed-height", "reason": "Fixed-ratio header artwork." },
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-hero__cloud", "property": "fixed-height", "reason": "Fixed-ratio decorative cloud." },
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-identity__seal", "property": "fixed-height", "reason": "Fixed-ratio identity seal." },
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-section-heading image", "property": "fixed-height", "reason": "Fixed-ratio heading ornament." },
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-menu__icon", "property": "fixed-height", "reason": "Fixed-ratio menu icon." },
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-menu__chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
{ "path": "pages/profile/m03-security-settings.vue", "selector": ".security-row>image", "property": "fixed-height", "reason": "Fixed-ratio security icon." },
{ "path": "pages/profile/m03-security-settings.vue", "selector": ".security-row .chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
{ "path": "pages/profile/m10-about-settings.vue", "selector": ".brand-card image", "property": "fixed-height", "reason": "Fixed-ratio brand mark." },
{ "path": "pages/profile/m10-about-settings.vue", "selector": ".settings-row image", "property": "fixed-height", "reason": "Fixed-ratio settings icon." },
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".generation-band", "property": "fixed-height", "reason": "Fixed-height generation lane in the scrollable tree canvas." },
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node", "property": "fixed-height", "reason": "Fixed-ratio interactive tree node." },
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node--selected", "property": "fixed-height", "reason": "Selected state preserves the fixed-ratio tree node." },
{ "path": "pages/tree/t03-member-profile.vue", "selector": ".member-heading__seal", "property": "fixed-height", "reason": "Fixed-ratio member seal." }
]
}
+70
View File
@@ -0,0 +1,70 @@
{
"covered": [
"components/AppButton.vue",
"components/AppDialog.vue",
"components/AppLoading.vue",
"components/AppTabbar.vue",
"components/AppToast.vue",
"components/AuthPageShell.vue",
"components/GenealogyCard.vue",
"components/GenealogyPageBackground.vue",
"components/ModulePage.vue",
"components/ModulePageBackground.vue",
"components/PageHeader.vue",
"pages/auth/a01-entry.vue",
"pages/auth/a04-register.vue",
"pages/auth/a05-reset-password.vue",
"pages/auth/a06-auth-status.vue",
"pages/genealogy/g01-my-genealogies.vue",
"pages/genealogy/g03-create-genealogy.vue",
"pages/genealogy/g05-genealogy-overview.vue",
"pages/genealogy/g06-search-genealogies.vue",
"pages/genealogy/g08-join-application.vue",
"pages/genealogy/g09-my-applications.vue",
"pages/genealogy/g10-application-review.vue",
"pages/genealogy/g11-genealogy-settings.vue",
"pages/genealogy/g12-generation-poems.vue",
"App.vue",
"components/tree/TreeMemberForm.vue",
"pages/family/f01-family-feed.vue",
"pages/family/f02-publish-feed.vue",
"pages/family/f03-feed-detail.vue",
"pages/family/f04-article-list.vue",
"pages/family/f05-article-detail.vue",
"pages/family/f06-article-editor.vue",
"pages/family/f07-album-list.vue",
"pages/family/f08-album-detail.vue",
"pages/family/f09-media-upload.vue",
"pages/family/f10-video-list.vue",
"pages/notification/n01-message-center.vue",
"pages/notification/n02-message-detail.vue",
"pages/profile/m01-profile-home.vue",
"pages/profile/m02-edit-profile.vue",
"pages/profile/m03-security-settings.vue",
"pages/profile/m04-change-password.vue",
"pages/profile/m05-change-phone.vue",
"pages/profile/m06-help-center.vue",
"pages/profile/m07-feedback.vue",
"pages/profile/m08-promotion.vue",
"pages/profile/m09-vip-orders.vue",
"pages/profile/m10-about-settings.vue",
"pages/records/r01-people-list.vue",
"pages/records/r02-person-detail.vue",
"pages/records/r03-gift-list.vue",
"pages/records/r04-gift-editor.vue",
"pages/records/r05-ritual-list.vue",
"pages/records/r06-ritual-detail.vue",
"pages/records/r07-ritual-editor.vue",
"pages/records/r08-growth-journal.vue",
"pages/records/r09-life-events.vue",
"pages/records/r10-memo-list.vue",
"pages/records/r11-merit-records.vue",
"pages/tree/t01-tree-overview.vue",
"pages/tree/t03-member-profile.vue",
"pages/tree/t04-add-relative.vue",
"pages/tree/t05-edit-member.vue",
"pages/tree/t06-edit-relationship.vue",
"pages/tree/t07-member-directory.vue",
"pages/tree/t08-member-states.vue"
]
}
@@ -60,7 +60,7 @@ foreach ($selector in @('card-meta', 'card-updated', 'card-role')) {
if ($dialog -match '(?s)\.app-dialog\s*\{[^}]*position\s*:|(?s)\.app-dialog__content\s*\{[^}]*position\s*:') {
throw 'AppDialog internal content must not use positioning'
}
Assert-Match $dialog '(?s)\.app-dialog\s*\{[^}]*background:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\)' 'AppDialog must render its approved dialog asset as the dialog background'
Assert-Match $dialog '(?s)\.app-dialog\s*\{[^}]*@include\s+adaptive\.adaptive-auth-dialog;' 'AppDialog must consume the approved adaptive dialog frame profile'
if ($loading -match '(?im)(?<![-\w])position\s*:') { throw 'AppLoading must stay in its parent document flow' }
if ($toast -match '(?s)\.app-toast__copy\s*\{[^}]*position\s*:') { throw 'AppToast copy must not create an unnecessary positioning context' }
@@ -29,7 +29,7 @@ foreach ($token in @(
}
$dialog = Read-Utf8 'components/AppDialog.vue'
foreach ($token in @('role="dialog"', 'aria-modal="true"', 'tabindex="-1"', '@keydown.esc.stop="cancel"', 'max-height: calc(100vh - 80rpx)', 'overflow-y: auto')) {
foreach ($token in @('role="dialog"', 'aria-modal="true"', 'tabindex="-1"', '@keydown.esc.stop="cancel"', 'max-height: calc(var(--app-viewport-height, 100vh) - 160rpx)', 'overflow-y: auto')) {
if (-not $dialog.Contains($token)) { throw "AppDialog accessibility contract missing: $token" }
}
+8 -3
View File
@@ -6,6 +6,7 @@ function Assert-Contains {
}
$root = Split-Path -Parent $PSScriptRoot
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
$paths = @(
'pages/tree/t03-member-profile.vue',
'pages/tree/t04-add-relative.vue',
@@ -27,7 +28,7 @@ foreach ($path in @('pages/tree/t03-member-profile.vue', 'pages/tree/t07-member-
}
$t03 = $pages['pages/tree/t03-member-profile.vue']
foreach ($required in @('member-state--detail', 'member-state--error', 't01-state-panel.png', 't07-search-input-frame.png', '/pages/tree/t05-edit-member', 'query.personId')) {
foreach ($required in @('member-state--detail', 'member-state--error', 'adaptive.adaptive-tree-panel', 'adaptive.adaptive-tree-field', '/pages/tree/t05-edit-member', 'query.personId')) {
Assert-Contains $t03 $required "Missing T03 contract: $required"
}
@@ -47,15 +48,19 @@ foreach ($entry in $specializedForms.GetEnumerator()) {
}
$t07 = $pages['pages/tree/t07-member-directory.vue']
foreach ($required in @('directory-state--list', 'directory-state--empty', 'directory-state--error', 'list-slip-frame.png', 'directory-card__status', '/pages/tree/t03-member-profile')) {
foreach ($required in @('directory-state--list', 'directory-state--empty', 'directory-state--error', 'adaptive.adaptive-genealogy-list-card', 'directory-card__status', '/pages/tree/t03-member-profile')) {
Assert-Contains $t07 $required "Missing T07 contract: $required"
}
$t08 = $pages['pages/tree/t08-member-states.vue']
foreach ($required in @('member-status--privacy', 'member-status--deceased', 'member-status--forbidden', 't01-state-panel.png', 'query.state')) {
foreach ($required in @('member-status--privacy', 'member-status--deceased', 'member-status--forbidden', 'adaptive.adaptive-tree-panel', 'query.state')) {
Assert-Contains $t08 $required "Missing T08 contract: $required"
}
foreach ($asset in @('t01-state-panel.png', 't07-search-input-frame.png', 'list-slip-frame.png')) {
Assert-Contains $profiles $asset "Adaptive tree profile missing: $asset"
}
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
foreach ($entry in @('t04:', 't05:', 't06:', 't07:', 't08:')) {
if ($catalog -match [regex]::Escape($entry)) { throw "Page catalog must remove migrated entry: $entry" }
@@ -1,15 +1,19 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent
$component = [System.IO.File]::ReadAllText((Join-Path $root 'components/tree/TreeMemberForm.vue'), [System.Text.Encoding]::UTF8)
$profiles = [System.IO.File]::ReadAllText((Join-Path $root 'styles/adaptive-frame-profiles.scss'), [System.Text.Encoding]::UTF8)
foreach ($expected in @(
'grid-template-columns: auto minmax(0, 1fr)',
'min-height: min(640px, calc((100vw - 16px) * 1.48))',
'background: url("/static/assets/modules/tree/transparent/t01-state-panel.png")',
'background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png")'
'@include adaptive.adaptive-tree-panel;',
'@include adaptive.adaptive-tree-field;'
)) {
if (-not $component.Contains($expected)) { throw "TreeMemberForm document-flow contract missing: $expected" }
}
foreach ($asset in @('t01-state-panel.png', 't07-search-input-frame.png')) {
if (-not $profiles.Contains($asset)) { throw "Adaptive tree profile missing: $asset" }
}
if ($component -match '(?m)\bposition\s*:') {
throw 'TreeMemberForm must not retain positioning in ordinary forms, fields, actions, or result content'
+71
View File
@@ -0,0 +1,71 @@
请接手这个 UniApp 项目的页面审核与全项目响应式适配工作。
项目目录:
【替换成新电脑上的 jiapuapp 绝对路径】
开始前必须按顺序执行:
1. 完整阅读项目根目录 AGENTS.md。
2. 阅读:
- docs/superpowers/specs/2026-07-21-project-wide-responsive-layout-design.md
- docs/superpowers/plans/2026-07-21-responsive-foundation-phase-1.md
3. 查看 tests/responsive-layout-coverage.json。
4. 查看 styles/adaptive-frame-profiles.scss。
5. 检查当前代码和测试结果后再继续,不要重新推翻已经通过的设计。
当前进度:
- 已建立全项目统一响应式档案:
styles/adaptive-frame-profiles.scss
- 公共组件以及 A、G、F、N、M、R、T 系列页面已经迁移到统一响应式规则。
- 可变高度的装饰背景使用九宫格/内容驱动高度。
- 固定比例的图标、缩略图、印章等才允许保留固定尺寸,并记录在:
tests/responsive-layout-allowlist.json
- 已清除页面内的 `background-size: 100% 100%` 强制拉伸。
- 页面不能自行定义 `border-image-slice`,必须由统一档案持有。
- 当前 66/66 个 Vue 文件已经纳入响应式扫描。
- 上一轮 22/22 项核心响应式与业务契约通过。
- MuMu 模拟器已在 360×640 和 412×915 逻辑视口抽查过代表页面。
- 入谱审核弹窗、G05、G06、G08、T03、F09、R01、N01、M01 等页面显示正常。
- 当前模拟器默认尺寸应为 720×1280、320 dpi。
- 不要使用浏览器做视觉审核,只看 MuMu 安卓模拟器。
- 不要执行任何 Git add、commit、push、restore、checkout 等操作。
- 不要启动或关闭我的模拟器,除非我明确要求。
- 忽略截图中的“3D研究室”等系统水印。
- 页面文案不得使用“汤正华堂侄”等具体姓名示例,应使用“某某某堂侄”等通用提示;模拟数据本身不用处理。
- 不使用多代理。
接手后先运行并报告:
powershell -ExecutionPolicy Bypass -File tests/adaptive-frame-profiles-contract.ps1
powershell -ExecutionPolicy Bypass -File tests/project-responsive-layout-contract.ps1
powershell -ExecutionPolicy Bypass -File tests/ag-responsive-surfaces-contract.ps1
powershell -ExecutionPolicy Bypass -File tests/compile-audit.ps1
然后核对:
- Vue 文件数量与 responsive-layout-coverage.json 是否完全一致;
- 是否仍有 `100% 100% no-repeat`
- 是否仍有页面直接写 `border-image-slice`
不要一上来修改代码。先告诉我:
1. 你读到了什么;
2. 当前完成到哪里;
3. 测试是否仍然通过;
4. MuMu 设备是否在线;
5. 下一步准备审核哪个页面。
后续原则:
- 整个项目必须自适应,不接受单页打补丁。
- 内容高度由内容决定。
- 弹窗只允许使用视口 max-height 和内部滚动。
- 不能通过压缩字号或随意缩小元素掩盖布局问题。
- 每修改一个页面,都必须在 MuMu 模拟器验证后再让我审核。
【人工验收断点】
- A 系列及 G01~G10 已经由用户在 MuMu 模拟器中逐页确认通过。
- 当前正式验收断点是 G10「入谱审核」。
- G10 的列表、审核说明、确认通过、确认拒绝等状态已经处理并通过。
- G11、G12 虽然已完成响应式代码迁移和契约检查,但尚未经过用户逐页、逐状态正式验收。
- 下一步必须从 G11「家谱设置」开始,在 MuMu 模拟器中逐页审核。
- 不要重复调整已经通过的 G01~G10,除非发现全局改动造成了明确回归。