nx-sk 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/modules/sections/view.jsx +1 -1
- package/src/modules/settings/view.jsx +11 -6
- package/src/web/frontend/App.jsx +13 -4
- package/src/web/frontend/styles.css +5 -0
- package/src/web/public/assets/{files-I6hzWb3h.js → files-DiyZRSVm.js} +1 -1
- package/src/web/public/assets/{index-Dxf_b602.js → index-BcT34EVd.js} +9 -9
- package/src/web/public/assets/index-lmztA3cP.css +1 -0
- package/src/web/public/assets/{view-CL1Ep4Vj.js → view-BOGUtsmZ.js} +1 -1
- package/src/web/public/assets/{view-D_Z9LvYS.js → view-BYPvk3A0.js} +1 -1
- package/src/web/public/assets/view-CUXbAbpt.js +1 -0
- package/src/web/public/assets/{view-cl8ITPVa.js → view-Cl9B8EO5.js} +1 -1
- package/src/web/public/assets/{view-Hu9QKqmx.js → view-DcVKWhos.js} +1 -1
- package/src/web/public/assets/{view-B5VFWIaH.js → view-DiYF5ioY.js} +1 -1
- package/src/web/public/index.html +2 -2
- package/src/web/public/assets/index-Cwk9VSKZ.css +0 -1
- package/src/web/public/assets/view-CKwFgc0C.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式参考 [Keep a Changelog](https://keepachangelog.com/),中文正文。
|
|
4
4
|
|
|
5
|
+
## [0.9.1] - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **设置页保存无效:面板发的 body 键被静默丢掉**。HTTP body 经 `applySpec` 只保留**声明过的** args/flags(有意严格,防止多余字段流进 service),而设置页发的是平铺的 `{exportDir: …}` / `{navGroups: …}` → 全被丢掉 → service 收到空 patch,只报一句“至少要给一项:setting set --key …”(根因离现场很远)。改为走声明过的 `set`(HTTP 形态 `{set: {k: v}}`,`spec.js` 本来就写着)。**面板「默认行为」卡的保存此前同样是坏的**,一并修好。
|
|
10
|
+
- **分组下拉鼠标下滑就消失**:pill 与菜单之间那 4px 空隙被鼠标穿过时判成“离开”,菜单刚展开就消失——补一块透明桥盖住空隙,并在离开后延迟 140ms 再关(斜着从 pill 划到菜单行也不闪)。
|
|
11
|
+
- 面板文案里两处 markdown 的 `**` 直接显示了出来(JSX 不渲染 markdown)——改为 `<b>` / 纯文本。
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **一致性断言:视图写请求的 body 键必须在 action 里声明过**(含反证:喂一段未声明键的合成代码,断言规则确实判红)。扫全部模块 view,把“发了未声明的键 → 静默丢掉 → 下游报一个离根因很远的错”这类问题钉死在测试里。
|
|
16
|
+
|
|
5
17
|
## [0.9.0] - 2026-10-15
|
|
6
18
|
|
|
7
19
|
### Added
|
package/package.json
CHANGED
|
@@ -273,7 +273,7 @@ function EditForm({ modal, onChange, onSave, onSync, onAddField, onRemoveField,
|
|
|
273
273
|
</div>
|
|
274
274
|
{full?.template ? (
|
|
275
275
|
<div className="hint">
|
|
276
|
-
本栏目由模板「{full.template}
|
|
276
|
+
本栏目由模板「{full.template}」创建。模板后来新增的字段<b>不会自动</b>出现在这里,
|
|
277
277
|
点「同步模板字段」补齐(例如求职模板新增的「附件资料」组 / 简历、头像等 file 字段)。
|
|
278
278
|
</div>
|
|
279
279
|
) : null}
|
|
@@ -26,11 +26,16 @@ export default function SettingsView() {
|
|
|
26
26
|
useEffect(() => { load(); }, [load]);
|
|
27
27
|
|
|
28
28
|
const save = guard(async () => {
|
|
29
|
+
// 必须包在 `set` 里发:HTTP 的 body 会经 applySpec **只保留声明过的键**,
|
|
30
|
+
// 平铺的 `{exportDir: …}` 会被静默丢掉 → service 收到空 patch 报「至少要给一项」。
|
|
31
|
+
// `set` 是 setting.set 声明过的 kv flag,HTTP 形态就是 `{set: {k: v}}`。
|
|
29
32
|
const body = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
set: {
|
|
34
|
+
exportDir: form.exportDir,
|
|
35
|
+
exportFormat: form.exportFormat,
|
|
36
|
+
includeSecretsInExport: !!form.includeSecretsInExport,
|
|
37
|
+
defaultSection: form.defaultSection,
|
|
38
|
+
},
|
|
34
39
|
};
|
|
35
40
|
const r = await api('/api/settings', { method: 'PATCH', body });
|
|
36
41
|
toast('设置已保存');
|
|
@@ -60,7 +65,7 @@ export default function SettingsView() {
|
|
|
60
65
|
if (naked) { toast('每个分组都要有组名', 'bad'); return; }
|
|
61
66
|
const empty = groups.find((g) => !g.sections.length);
|
|
62
67
|
if (empty) { toast(`分组「${empty.title}」还没有勾选栏目——要么勾一个,要么删掉这个分组`, 'bad'); return; }
|
|
63
|
-
const r = await api('/api/settings', { method: 'PATCH', body: { navGroups: groups } });
|
|
68
|
+
const r = await api('/api/settings', { method: 'PATCH', body: { set: { navGroups: groups } } });
|
|
64
69
|
toast(groups.length ? `已保存 ${groups.length} 个导航分组` : '已取消分组,栏目恢复平铺');
|
|
65
70
|
setData({ ...data, settings: r.set });
|
|
66
71
|
setGroups((r.set.navGroups || []).map((g) => ({ title: g.title, sections: [...g.sections] })));
|
|
@@ -130,7 +135,7 @@ export default function SettingsView() {
|
|
|
130
135
|
</div>
|
|
131
136
|
<div className="pad stack">
|
|
132
137
|
<div className="hint">
|
|
133
|
-
|
|
138
|
+
把同域栏目收拢成一个头部入口:头部只显示组名,悬浮展开上下列表选具体栏目。
|
|
134
139
|
不勾进任何分组的栏目保持平铺;组顺序 = 头部显示顺序;一个栏目只能属于一个分组。
|
|
135
140
|
</div>
|
|
136
141
|
{groups.map((g, gi) => (
|
package/src/web/frontend/App.jsx
CHANGED
|
@@ -156,9 +156,18 @@ function SectionPane({ sectionId }) {
|
|
|
156
156
|
function GroupTab({ group, currentId, onGo }) {
|
|
157
157
|
const [open, setOpen] = useState(false);
|
|
158
158
|
const ref = useRef(null);
|
|
159
|
+
// 离开时**延迟**再关:鼠标斜着从 pill 划到菜单行会短暂出界,立刻关就「刚展开就消失」
|
|
160
|
+
const closeTimer = useRef(null);
|
|
159
161
|
const active = group.sections.some((s) => s.id === currentId);
|
|
160
162
|
const total = group.sections.reduce((n, s) => n + (s.count || 0), 0);
|
|
161
163
|
|
|
164
|
+
const cancelClose = () => {
|
|
165
|
+
if (closeTimer.current) { clearTimeout(closeTimer.current); closeTimer.current = null; }
|
|
166
|
+
};
|
|
167
|
+
const openNow = () => { cancelClose(); setOpen(true); };
|
|
168
|
+
const closeSoon = () => { cancelClose(); closeTimer.current = setTimeout(() => setOpen(false), 140); };
|
|
169
|
+
useEffect(() => cancelClose, []);
|
|
170
|
+
|
|
162
171
|
useEffect(() => {
|
|
163
172
|
if (!open) return undefined;
|
|
164
173
|
const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
|
|
@@ -175,10 +184,10 @@ function GroupTab({ group, currentId, onGo }) {
|
|
|
175
184
|
<span
|
|
176
185
|
className="tab-group"
|
|
177
186
|
ref={ref}
|
|
178
|
-
onMouseEnter={
|
|
179
|
-
onMouseLeave={
|
|
187
|
+
onMouseEnter={openNow}
|
|
188
|
+
onMouseLeave={closeSoon}
|
|
180
189
|
>
|
|
181
|
-
<button className={`tab ${active ? 'active' : ''}`} aria-expanded={open} onClick={
|
|
190
|
+
<button className={`tab ${active ? 'active' : ''}`} aria-expanded={open} onClick={openNow}>
|
|
182
191
|
{group.title}
|
|
183
192
|
<span className="count">{total}</span>
|
|
184
193
|
<span className="caret" aria-hidden />
|
|
@@ -190,7 +199,7 @@ function GroupTab({ group, currentId, onGo }) {
|
|
|
190
199
|
key={s.id}
|
|
191
200
|
role="menuitem"
|
|
192
201
|
className={`tab-menu-row ${s.id === currentId ? 'current' : ''}`}
|
|
193
|
-
onClick={() => { setOpen(false); onGo(s.id); }}
|
|
202
|
+
onClick={() => { cancelClose(); setOpen(false); onGo(s.id); }}
|
|
194
203
|
>
|
|
195
204
|
<span className="grow nowrap">{s.title}</span>
|
|
196
205
|
{typeof s.count === 'number' ? <span className="count">{s.count}</span> : null}
|
|
@@ -116,6 +116,11 @@ nav.tabs {
|
|
|
116
116
|
border-radius: var(--radius-btn);
|
|
117
117
|
box-shadow: var(--shadow-relief);
|
|
118
118
|
}
|
|
119
|
+
/* 桥:盖住 pill 与菜单之间那几像素的「死区」——鼠标下滑穿过它时不能被判成离开,
|
|
120
|
+
否则菜单刚探出头就消失(hover 菜单的经典坑) */
|
|
121
|
+
.tab-menu::before {
|
|
122
|
+
content: ''; position: absolute; left: 0; right: 0; top: -5px; height: 5px;
|
|
123
|
+
}
|
|
119
124
|
.tab-menu-row {
|
|
120
125
|
display: flex; align-items: center; gap: 10px; width: 100%;
|
|
121
126
|
height: var(--row-h); padding: 0 8px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as l}from"./index-
|
|
1
|
+
import{a as l}from"./index-BcT34EVd.js";let r=null;function o(n=!1){return!n&&r||(r=l("/api/files").then(e=>e).catch(e=>{throw r=null,e})),r}function i(){return r=null,o(!0)}function u(n){return new Promise((e,a)=>{const t=new FileReader;t.onload=()=>e(t.result),t.onerror=()=>a(new Error("读取文件失败")),t.readAsDataURL(n)})}export{u as f,o as l,i as r};
|