From c9037f79be3fa2c3acc72e1a01489cbffe82b128 Mon Sep 17 00:00:00 2001 From: laoyang Date: Fri, 2 Oct 2026 20:09:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=B6=E8=B0=B1=20v22=EF=BC=9A=E6=94=B9?= =?UTF-8?q?=E6=8C=89=20family-chart=20=E5=AE=98=E6=96=B9=E7=94=A8=E6=B3=95?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E5=B9=B6=E6=94=BE=E5=BC=80=E4=BB=A3=E6=95=B0?= =?UTF-8?q?=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户指出「只需要按照 json 格式,把以我为中心的关联数据全部取出来, 全都封装进去就行了,id 用用户表的 id,性别名字生日头像都是现成的, 只需要把配偶、父母和孩子放进去用 id 关联」——并强调「之前一定是你 调用的方法不对」。 == 验证结论(先量再改)== 用**官方示例自己的数据**(donatso 的 data.json,13 人,格式完全官方) 跑了一遍:库画出 11 人,**配偶 Andrea 的父母 Zadro/Zadra 一个都没画**。 那两位就在数据里、rels.parents 也指向他们、格式与官方示例一字不差。 → 说明「按格式全塞进去就能画全」在**姻亲**这一项上不成立: 库的算法只走「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」, **不会从配偶继续向上/向下 walk**。这是库的算法边界,不是格式/关联问题。 == 但用户的批评有一半是对的:调用方式确实有我没做足的地方 == 官方示例是:
+ 引 family-chart.css。 我之前**既没加 f3 类、也没引库 CSS**,而是自己在 app.css 里手补 `.kin-chart svg.main_svg{width:100%;height:100%}` 去绕开 300×150 那个坑 —— 属于"用歪门邪道达到同样效果"。现在按官方来: · 容器加 `f3` 类(库的 CSS 全部以 .f3 作用域) · 引入官方 family-chart.css(新增 vendor 文件) · 在 app.css 里把库的深色主题变量覆盖成家书宣纸色 (--male-color / --background-color / --text-color 等) · 自己的 svg 撑满那条规则删掉(官方 CSS 已提供) == 放开代数上限(用户要求「全部取出来」)== 官方示例**不调** setAncestryDepth / setProgenyDepth —— 库在"未设置"时 会跳过深度截断,等于不限代数。我之前写死了 2/3,真实家庭一超过就被砍。 现在不设这两个值。服务端 familyMap 的 up/down 也从 (2,2) 放到 (9,9) (这两个参数只影响**代数标号**,超出会压到边界档导致曾祖辈与祖辈混层; 遍历本身是走遍整个连通图的,人员收录数不受影响)。 == 工具同步 == - familybook_deploy.sh:新增 v22 三条契约(vendor CSS 在 / 模板引了它 / 容器有 f3 类),并加**反向断言**「不许再出现 setAncestryDepth|setProgenyDepth」; 旧的"卡片尺寸/间距"断言同步。 - _fb_kin_geom_falsify.py:注入②的原锚点(setProgenyDepth)已随本次删除, 换成「禁掉上溯循环」——树根退回「我」,伯父/堂兄/堂嫂/堂侄/侄子那批旁支 会离开画面,J 项必须红。3/3 全抓住。 - ⚠️ 又踩一次「注释撞断言字面量」:新写的注释里提到 setProgenyDepth 函数名, 把「不许再设代数上限」这条反向断言直接打红。改注释避开字面量后恢复。 == 战绩 == e2e 549/0 · 桌面几何 368/0 · 移动端 516/0 · 交互 97/0 · 升级专测 123/0 家谱可视化 24/0 · 家谱版面 10/0 · 反向验证 3/3 · 静态断言空跑 RC=0 部署 + 线上自检全过。 --- assets/css/app.css | 45 +- assets/vendor/family-chart/family-chart.css | 880 ++++++++++++++++++++ templates/kin/tree.php | 28 +- 3 files changed, 930 insertions(+), 23 deletions(-) create mode 100644 assets/vendor/family-chart/family-chart.css diff --git a/assets/css/app.css b/assets/css/app.css index dbffd88..e4a918c 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -432,14 +432,15 @@ textarea.input { min-height: 76px; resize: vertical; } } /* ---------------- 家谱(以我为中心) ---------------- - v21:改用 family-chart 库渲染。库把 data[0].id 当 main_id, - 所以把「最高祖先」排第一位以画出尽可能多的血亲旁支; + v22:按 family-chart **官方用法**接(容器 class="f3" + 引 family-chart.css), + 库把 data[0].id 当 main_id,所以把「最高祖先」排第一位以画出尽可能多的血亲旁支; 加载后再把镜头平移回「我」,兼顾"看到全树"与"以自己为中心"。 - 免费版 family-chart 的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」, - **不会从配偶继续向上/向下 walk**,因此岳父岳母、舅子、外甥等姻亲旁支 - 在单棵树里天然画不出来(换树根、调 depth 都救不了)。 - 这是库的能力边界,不是调用方式问题;如要姻亲,需要 Premium 或多树拼接。 */ + ⚠️ 免费版 family-chart 的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」, + **不会从配偶继续向上/向下 walk**。这一点已用**官方示例自己的数据**验证过: + 官方 data.json 里 13 人(含配偶 Andrea 的父母 Zadro/Zadra,格式完全按官方), + 库只画出 11 人 —— 配偶方那两位长辈一个都没画。 + 所以「把数据按格式全塞进去就能画全」在姻亲这一项上不成立,是库的算法边界。 */ .kin-tools { display: flex; gap: 8px; justify-content: flex-end; margin: 0 0 10px; } @@ -458,32 +459,44 @@ textarea.input { min-height: 76px; resize: vertical; } } .kin-chart:active { cursor: grabbing; } +/* 库的 `.f3` 会自带一套**深色主题**(--background-color: rgb(33,33,33) / --text-color: #fff)。 + 家书是宣纸浅色,必须在这里把库的主题变量改掉;否则卡片文字会变白、看不出内容。 + 只覆盖颜色变量,**不动**库的结构规则(那些是撑满/定位必需的)。 */ +.kin-chart.f3 { + --male-color: #F1E7D6; /* 库的男卡底色,改成纸色(我们自己的 .kin-card 会再盖一层) */ + --female-color: #F1E7D6; + --genderless-color: #F1E7D6; + --background-color: transparent; + --text-color: var(--ink); + font-family: inherit; +} + /* 隐藏 family-chart 自带的空导航栏(我们用自己的「看全部 / 回到我」按钮) */ .kin-chart .f3-nav-cont { display: none !important; } -/* ⚠️ 这条是**必须**的:库自带的 CSS 里写着 `.f3 svg.main_svg { width:100%; height:100% }`, - 而我们的容器类名是 `.kin-chart`(不是 `.f3`),也没引库的 CSS —— - 于是 SVG 根元素退化成浏览器默认的 **300×150**,所有画到框外的连线被裁掉 - (卡片层是 HTML div,不受影响,所以"卡在、线没了"这个症状很隐蔽)。 - 实测:不写这条时 svg 只有 300×150(`_fb_kin_check_v17.py` 第②条会红)。 */ -.kin-chart svg.main_svg { width: 100%; height: 100%; display: block; z-index: 1; } +/* 官方 CSS 已经给了 `.f3 svg.main_svg { width:100%; height:100% }`(撑满画布), + 这里只补一个 display:block,避免 svg 作为 inline 元素多出基线空隙。 */ +.kin-chart svg.main_svg { display: block; z-index: 1; } /* family-chart 的 HTML 卡片层 #htmlSvg 与 SVG 连线层由库的 onZoom 同步 transform。 卡片容器 .card_cont 已经 absolute + translate(x,y);里面再放我们的 .kin-card。 */ .kin-chart #htmlSvg { z-index: 2; } .kin-chart .card_cont { pointer-events: none; } -/* 库给卡片套了一层
/
, - 我们只接管最里面那层(.kin-card)。把外层的中转容器归零,避免它自带的内边距/背景 - 让卡片看起来"胖了一圈"或有底色。 */ +/* ⚠️ 库的性别底色规则是 `.f3 div.card-male .card-inner`(3 段), + 比 `.kin-chart .card_cont .card-inner`(3 段)**同级**,靠先后顺序定胜负 —— + 所以这份覆盖必须写在**引库 CSS 之后**(app.css 在 layout 里就是这么排的)。 + 把外层中转容器全部归零,只留我们自己的 .kin-card。 */ .kin-chart .card_cont > .card, -.kin-chart .card_cont .card-inner { +.kin-chart .card_cont .card-inner, +.kin-chart.f3 .card_cont .card-inner { background: transparent; padding: 0; border-radius: 0; min-height: 0; width: auto; height: auto; + outline: none; } .kin-chart .card_cont .card-inner { display: block; } diff --git a/assets/vendor/family-chart/family-chart.css b/assets/vendor/family-chart/family-chart.css new file mode 100644 index 0000000..64e9c33 --- /dev/null +++ b/assets/vendor/family-chart/family-chart.css @@ -0,0 +1,880 @@ +.f3 { + --female-color: rgb(196, 138, 146); + --male-color: rgb(120, 159, 172); + --genderless-color: lightgray; + --background-color: rgb(33, 33, 33); + --text-color: #fff; + + font-family: 'Roboto', sans-serif; +} + +.f3 * { + box-sizing: border-box; +} + +.f3 .cursor-pointer { + cursor: pointer; +} +.f3 svg.main_svg { + width: 100%; + height: 100%; +} +.f3 svg.main_svg text { + fill: currentColor; +} +.f3 rect.card-female, .f3 .card-female .card-body-rect, .f3 .card-female .text-overflow-mask { + fill: var(--female-color); +} +.f3 rect.card-male, .f3 .card-male .card-body-rect, .f3 .card-male .text-overflow-mask { + fill: var(--male-color); +} +.f3 .card-genderless .card-body-rect, .f3 .card-genderless .text-overflow-mask { + fill: var(--genderless-color); +} +.f3 .card_add .card-body-rect { + fill: #3b5560; + stroke-width: 4px; + stroke: #fff; + cursor: pointer; +} +.f3 g.card_add text { + fill: #fff; +} +.f3 .card-main-outline { + stroke: currentColor; + stroke-width: 3px; +} +.f3 .card_family_tree rect { + transition: 0.3s; +} +.f3 .card_family_tree:hover rect { + transform: scale(1.1); +} +.f3 .card_add_relative { + cursor: pointer; + color: #fff; + transition: 0.3s; +} +.f3 .card_add_relative circle { + fill: rgba(0, 0, 0, 0); +} +.f3 .card_add_relative:hover { + color: black; +} +.f3 .card_edit.pencil_icon { + color: #fff; + transition: 0.3s; +} +.f3 .card_edit.pencil_icon:hover { + color: black; +} +.f3 .card_break_link, .f3 .link_upper, .f3 .link_lower, .f3 .link_particles { + transform-origin: 50% 50%; + transition: 1s; +} +.f3 .card_break_link { + color: #fff; +} +.f3 .card_break_link.closed .link_upper { + transform: translate(-140.5px, 655.6px); +} +.f3 .card_break_link.closed .link_upper g { + transform: rotate(-58deg); +} +.f3 .card_break_link.closed .link_particles { + transform: scale(0); +} +.f3 .input-field input { + height: 2.5rem !important; +} +.f3 .input-field > label:not(.label-icon).active { + -webkit-transform: translateY(-8px) scale(0.8); + transform: translateY(-8px) scale(0.8); +} +.f3.f3-cont { + width:100%; + height:900px; + max-height:70vh; + background-color: var(--background-color); + color: var(--text-color); +} +.f3 { + position: relative; + display: flex; +} + + + + +/* form-info */ +.f3-form input[type="text"], +.f3-form textarea, +.f3-form select { + width: 100%; + padding: 8px 12px; + margin: 8px 0; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; + font-size: 14px; + background: var(--background-color); + color: currentColor; +} + +.f3-form input[type="text"]:focus, +.f3-form textarea:focus, +.f3-form select:focus { + box-shadow: 0 0 5px rgba(76, 175, 80, 0.2); +} + +.f3-form button { + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + margin: 10px 0; + transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out, color 0.3s ease-in-out; +} + +.f3-form button[type="submit"] { + background-color: #4CAF50; + color: white; +} + +.f3-cancel-btn { + background-color: #ccc; +} + +.f3-form .f3-delete-btn { + background-color: transparent; + border: 1px solid #f44336; + color: #f44336; + width: 100%; + padding: 5px 10px; +} + +.f3-delete-btn:hover { + background-color: #da190b; + border-color: #da190b; + color: #fff; +} + +.f3-delete-btn:disabled { + opacity: 0.5; + background-color: transparent; + color: #f44336; + cursor: not-allowed; +} + +.f3-form .f3-remove-relative-btn { + background-color: transparent; + border: 1px solid currentColor; + color: currentColor; + width: 100%; + padding: 5px 10px; +} + +.f3-remove-relative-btn:hover, .f3-remove-relative-btn.active { + background-color: var(--text-color); + border-color: var(--text-color); + color: var(--background-color); +} + +.f3-radio-group { + margin: 15px 0; +} + +.f3-radio-group label { + margin-right: 15px; + cursor: pointer; +} + +.f3-radio-group input[type="radio"] { + margin-right: 5px; +} + +.f3-info-field-label, .f3-form-field label { + font-weight: bold; + font-size: 12px; + display: block; + opacity: 0.8; +} + +.f3-info-field-value { + font-weight: normal; + display: block; + border: none; + outline: none; + border-bottom: 1px solid rgba(255,255,255,0.2); + padding-bottom: 1px; + margin-bottom: 10px; + min-height: 18px; +} + +.f3-form-buttons { + text-align: right; +} + +.f3-form-title { + text-align: center; +} + +.f3-form.non-editable .f3-form-buttons, +.f3-form.non-editable .f3-delete-btn, +.f3-form.non-editable .f3-remove-relative-btn, +.f3-form.non-editable .f3-link-existing-relative { + display: none; +} + +.f3-close-btn { + cursor: pointer; + position: absolute; + left: 10px; + top: 8px; + font-size: 30px; + color: var(--text-color); +} + +.f3-edit-btn { + position: relative; + top: -1px; + width: 24px; + height: 24px; + cursor: pointer; + display: inline-block; +} + +.f3-add-relative-btn { + cursor: pointer; + width: 27px; + height: 27px; + margin-right: 5px; + display: inline-block; +} + +/* card-html */ + +.f3 div.card { + cursor: pointer; + color: var(--text-color); + position: relative; + line-height: 1.2; +} + +.f3 div.card-image-circle { + border-radius: 50%; + padding: 5px; + width: 90px; + height: 90px; +} + +.f3 div.card-image-circle div.card-label { + position: absolute; + bottom: -10px; + left: 50%; + transform: translate(-50%, 50%); + max-width: 150%; + min-height: 22px; + text-align: center; + background-color: rgba(0, 0, 0, 0.5); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-radius: 3px; + padding: 0 5px; +} + +.f3 div.card-image-circle img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; +} + +.f3 div.card-image-circle svg { + width: 100%; + height: 100%; + padding: 5px; + border-radius: 50%; + object-fit: cover; +} + +.f3 div.card-image-circle img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; +} + +.f3 div.card-rect { + padding: 5px; + border-radius: 3px; + width: 120px; + min-height: 70px; + overflow: hidden; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; +} + + +.f3 div.card-image-rect { + width: 200px; + min-height: 70px; + display: flex; + align-items: center; + border-radius: 5px; +} + +.f3 div.card-image-rect .person-icon { + height: 70px; + width: 70px; + object-fit: cover; + flex: 0 0 auto; + padding: 5px; + margin-right: 10px; +} + +.f3 div.card-image-rect img { + height: 70px; + width: 70px; + object-fit: cover; + flex: 0 0 auto; + padding: 5px; + margin-right: 10px; + border-radius: 8px; +} + +.f3 div.card-image-rect svg { + object-fit: cover; + width: 100%; + height: 100%; + padding: 5px; + border-radius: 7px; +} + +.f3 div.card-image-rect div.card-label { + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; +} + +.f3 div.mini-tree { + text-align: right; + position: absolute; + top: -15px; + right: -2px; + z-index: -1; +} +.f3 div.mini-tree svg { + width: 55px; +} + +.f3 .f3-card-duplicate-tag { + position: absolute; + top: 2px; + right: 2px; + color: rgb(255, 251, 220); + background-color: rgba(255, 251, 220, 0); + border-radius: 50%; + padding: 2px; + transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out; +} + +.f3 .f3-card-duplicate-hover div.card-inner { + transform: translate(0, -2px); + outline: 4px solid rgb(255, 251, 220); +} + +.f3 .f3-card-duplicate-hover .f3-card-duplicate-tag { + background-color: rgba(255, 251, 220, .8); + color: #000; +} + +.f3 .f3-remove-relative-active .card { + background-color: var(--background-color); +} + +.f3 .f3-remove-relative-active .card-inner { + transition: border 0.2s ease-in-out, opacity 0.2s ease-in-out, transform 0.2s ease-in-out; + opacity: .75; +} + +.f3 .f3-remove-relative-active .card:hover .card-inner { + opacity: .25; +} + +.f3 .f3-remove-relative-active .card-male.card-depth--1:hover .card-inner { + transform: translate(-8px, -8px); +} + +.f3 .f3-remove-relative-active .card.card-female.card-depth--1:hover .card-inner { + transform: translate(8px, -8px); +} + +.f3 .f3-remove-relative-active .card.card-female.card-depth-0:hover .card-inner { + transform: translate(8px, 0); +} + +.f3 .f3-remove-relative-active .card.card-male.card-depth-0:hover .card-inner { + transform: translate(-8px, 0); +} + +.f3 .f3-remove-relative-active .card.card-depth-1:hover .card-inner { + transform: translate(0, 8px); +} + +.f3 .f3-remove-relative-active .card.card-main .card-inner { + transform: translate(0, 0)!important; + opacity: 1!important; +} + + + +.f3 div.card > div { + transition: transform 0.2s ease-in-out; + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.8); +} + +.f3 .card-inner { + outline: 0px solid rgba(255, 255, 255, 1); + transition: outline 0.5s ease-in-out; +} + +.f3 div.card-female .card-inner, .f3 div.card-female .person-icon svg { + background-color: var(--female-color); +} +.f3 div.card-male .card-inner, .f3 div.card-male .person-icon svg { + background-color: var(--male-color); +} +.f3 div.card-genderless .card-inner, .f3 div.card-genderless .person-icon svg { + background-color: var(--genderless-color); +} + +.f3 div.card-new-rel .card-inner, .f3 div.card-new-rel .person-icon svg { + background-color: var(--background-color); +} + +.f3 div.card-to-add .card-inner { + background-color: var(--background-color); + border: 1px solid; +} + +.f3 div.card-to-add .card-inner .card-label { + margin: 0 auto; +} + +.f3 div.card-to-add .person-icon { + display: none; +} + +.f3 div.card-new-rel .card-inner { + border-width: 1px; + border-style: dashed; + outline: 0px !important; +} +.f3 div.card-new-rel.card-female .card-inner, .f3 div.card-to-add.card-female .card-inner { + border-color: var(--female-color); + color: var(--female-color); +} +.f3 div.card-new-rel.card-male .card-inner, .f3 div.card-to-add.card-male .card-inner { + color: var(--male-color); + border-color: var(--male-color); +} + +.f3 div.card-unknown .card-inner { + background-color: var(--background-color); + border: 1px solid; +} + +.f3 div.card-unknown .card-inner .card-label { + margin: 0 auto; +} + +.f3 div.card-unknown .person-icon { + display: none; +} + +.f3 div.card-new-rel .card-inner { + border-width: 1px; + border-style: dashed; + outline: 0px !important; +} +.f3 div.card-new-rel.card-female .card-inner, .f3 div.card-unknown.card-female .card-inner { + border-color: var(--female-color); + color: var(--female-color); +} +.f3 div.card-new-rel.card-male .card-inner, .f3 div.card-unknown.card-male .card-inner { + color: var(--male-color); + border-color: var(--male-color); +} + +.f3 div.card:hover > div { + transform: translate(0, -2px); +} +.f3 div.card-main .card-inner, .f3 div.card:hover .card-inner { + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.8); +} + +.f3 div.card-main .card-inner { + outline: 4px solid rgba(220, 220, 220, 1); +} + +.f3 div.card-inner.f3-path-to-main { + outline: 4px solid rgba(255, 255, 255, 1); +} + +.f3 .link { + transition: stroke-width 0.2s ease-in-out; +} + +.f3 .link.f3-path-to-main { + stroke-width: 4px; +} + + + + + +.f3-form-cont { + position: relative; + z-index: 6; + right: 0; + top: 0; + width: 0; + height: 100%; + background-color: var(--background-color); + overflow: auto; + flex: 0 0 auto; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5); +} + +.f3-form-cont.opened { + width: 350px; +} + +.f3-form { + padding: 20px; +} + +.f3-form hr { + border-style: solid; + border-width: thin 0 0 0; + opacity: 0.15; +} + +.f3-nav-cont { + position: absolute; + top: 0; + left: 0; + width: 100%; + display: flex; +} + +.f3-history-controls { + padding: 8px 5px 7px 9px; + display: inline-block; + position: relative; + z-index: 2; +} + +.f3-back-button, .f3-forward-button { + width: 30px; + height: 30px; + transition: opacity 0.3s ease; + cursor: pointer; + display: inline-block; + background-color: transparent; + border: none; + margin-right: 10px; + color: currentColor; +} + +.f3-history-controls svg { + height: 100%; +} + +.f3-back-button.disabled, .f3-forward-button.disabled { + opacity: 0.5; +} + +.f3-modal { + display: none; + position: absolute; + z-index: 10; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.4); +} + +.f3-modal-content { + position: relative; + background-color: var(--background-color); + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + border-radius: 5px; + width: 500px; + max-width: 90%; +} + +.f3-modal-close { + color: #aaa; + position: absolute; + right: 10px; + top: 7px; + font-size: 28px; + font-weight: bold; +} + +.f3-modal-close:hover, +.f3-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +.f3-popup { + position: fixed; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.8); +} + +.f3-popup-content { + position: relative; + background-color: var(--background-color); + border: 1px solid #888; + border-radius: 5px; + overflow: hidden; + width: 100%; + height: 100%; +} + +.f3-popup-nav { + height: 20px; +} + +.f3-popup-content-inner { + width: 100%; + height: 100%; +} + +.f3-popup-close { + color: #aaa; + position: absolute; + z-index: 4; + right: 6px; + top: 1px; + font-size: 28px; + font-weight: bold; + line-height: 1; +} + +.f3-popup-close:hover, +.f3-popup-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + + + +.f3-btn { + position: relative; + cursor: pointer; + padding: 5px 10px; + + overflow: hidden; + + border-width: 0; + outline: none; + border-radius: 3px; + box-shadow: 0 1px 4px rgba(0, 0, 0, .6); + + background-color: var(--text-color); + color: var(--background-color); + + transition: background-color .3s; + font-size: 14px; +} + +.f3-btn:hover, .f3-btn:focus { + background-color: var(--background-color); + color: var(--text-color); +} + +.f3-female-bg { + background-color: var(--female-color); +} + +.f3-male-bg { + background-color: var(--male-color); +} + +.f3-genderless-bg { + background-color: var(--genderless-color); +} + +.f3-female-color { + color: var(--female-color); +} + +.f3-male-color { + color: var(--male-color); +} + +.f3-genderless-color { + color: var(--genderless-color); +} + +.f3-autocomplete-cont { + position: relative; + display: inline-block; + z-index: 2; + font-size: 14px; + width: 200px; +} + +.f3-autocomplete input { + border: 1px solid rgba(255, 255, 255, 0.2); + background-color: var(--background-color); + color: var(--text-color); + padding: 10px; + width: 100%; +} +.f3-autocomplete input:focus { + outline: none; +} + +.f3-autocomplete-toggle { + position: absolute; + right: 10px; + top: 10px; + cursor: pointer; + color: var(--text-color); + transition: color 0.3s ease-in-out; + width: 20px; +} + +.f3-autocomplete-items { + border: 1px solid rgba(255, 255, 255, 0.2); + border-top: none; + overflow-y: auto; + max-height: 0; + background-color: var(--background-color); + transition: max-height 0.3s ease-in-out; +} + +.f3-autocomplete.active .f3-autocomplete-items { + max-height: 300px; +} + +.f3-autocomplete-item > div { + padding: 10px; + cursor: pointer; + background-color: var(--background-color); + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out; +} +.f3-autocomplete-item > div:hover, .f3-autocomplete-item.f3-selected > div { + background-color: var(--text-color); + color: var(--background-color); +} + +.f3-autocomplete-active { + background-color: DodgerBlue !important; + color: #ffffff; +} + +.f3-kinship-info { + padding: 10px 20px; +} + +.f3-kinship-info .f3-info-field { + color:#b3b01e +} + +.f3-kinship-info-icon { + cursor:pointer; + display:inline-block; + width:18px; + height:18px; + color:#04a4f4; + position:relative; + top:4px; + left:2px; +} + +.f3-kinship-info .f3 { + width:100%; + height: 100%; + position:relative; + background-color:rgb(33,33,33); + color:#fff; +} + +.f3 .f3-kinship-info .card-kinship-self { + min-height: 0px; + width: 60px; + height: 60px; + border-radius: 50%; + background-color: var(--background-color) !important; + border: solid 3px; + color: #437fae; + font-weight: bold; +} + +.f3 .f3-kinship-info .card-kinship-self.f3-real-label { + width: 150px; + height: 50px; + border-radius: 50px; +} + +.f3 .f3-kinship-info .card-kinship-rel { + min-height: 0px; + width: 150px; + height: 50px; + border-radius: 50px; + background-color: #1d3456 !important; + font-weight: bold; +} + +.f3 .f3-kinship-info .card-kinship-default { + min-height: 0px; + width: 150px; + height: 50px; + border-radius: 50px; + background-color: var(--background-color) !important; + border: solid 1px; +} + +.f3-kinship-labels-toggle { + position: absolute; + top: 0; + left: 0; + z-index: 10; + font-size: 12px; +} + +.f3-kinship-labels-toggle label { + cursor: pointer; + color: #fff; + font-weight: bold; + text-align: center; + padding: 2px 5px; +} + +.f3-kinship-labels-toggle input[type="checkbox"] { + cursor: pointer; + margin-left: 5px; + margin-right: 5px; + margin-top: 5px; + margin-bottom: 5px; +} \ No newline at end of file diff --git a/templates/kin/tree.php b/templates/kin/tree.php index de3e954..668e1c1 100644 --- a/templates/kin/tree.php +++ b/templates/kin/tree.php @@ -15,8 +15,12 @@ $graph = $graph ?? \App\Kinship::graph((int)\App\Auth::familyId()); $people = $people ?? []; -// ---- ① 以我为中心,检索上一代 / 这一代 / 下一代的**所有人** ---- -$map = \App\Kinship::familyMap((int)\App\Auth::familyId(), (int)$meId, $people, 2, 2); +// ---- ① 以我为中心,检索**所有有关系的人**(不设代数上限)---- +// ⚠️ 用户明确要求「把以我为中心的关联数据全部取出来」。 +// familyMap 的 $up/$down 只影响**代数标号**(超出就压到边界那一档), +// 遍历本身是走遍整个连通图的 —— 所以这两个值不放宽的话, +// 曾祖辈与祖辈会被压成同一代,排序就乱了。给足余量。 +$map = \App\Kinship::familyMap((int)\App\Auth::familyId(), (int)$meId, $people, 9, 9); $gen = $map['gen']; $units = $map['units']; $unitOf = $map['unit_of']; @@ -143,7 +147,12 @@ $datumJson = json_encode(
-
+ +

每代一行;一对夫妻之间有一条短横线, @@ -154,6 +163,11 @@ $datumJson = json_encode( 妻子那侧的岳父母、舅子等姻亲不在图内(在名册与成员详情页仍可看到)。

+ +