精简配置表单重复标题说明并完成浏览器验收
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-06 14:48:42 +08:00
parent 042dd589fa
commit b61c4c9479
6 changed files with 47 additions and 52 deletions

View File

@ -75,14 +75,6 @@
<div v-else-if="!loading" class="config-page-body">
<section class="editor-panel" :aria-busy="sectionLoading">
<div class="panel-heading">
<div>
<h4>{{ canWrite ? '填写配置' : '查看配置' }}</h4>
<p class="heading-note">{{ canWrite ? '按分组填写内容,验证并保存后再应用生效。' : '当前页面的脱敏配置内容。' }}</p>
</div>
<span class="selected-section-label">{{ currentSection.label }}</span>
</div>
<p v-if="selectedSection === 'ssl' && status.tlsMode === 'proxy'" class="heading-note" role="note">
当前由反向代理提供 HTTPS,Smail 不自动申请或续期 SSL 证书。下方配置仅用于本地邮件服务证书;外部续期后请重新导入并应用,不能仅更新反代服务器上的证书。
</p>

View File

@ -1,42 +1,26 @@
.config-form {
display: grid;
gap: 24px;
margin-top: 22px;
gap: 20px;
min-width: 0;
}
.config-form:not(:first-child) {
margin-top: 16px;
}
.form-group {
min-width: 0;
}
.form-group + .form-group {
padding-top: 24px;
padding-top: 20px;
border-top: 1px solid var(--pm-border-color);
}
.form-group-heading {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.form-group-heading h5 {
margin: 0;
color: var(--pm-text-primary);
font-size: 15px;
line-height: 1.5;
}
.credential-status {
color: var(--pm-text-secondary);
font-size: 12px;
}
.form-group-description,
.credential-status,
.form-group-note,
.form-empty {
margin: 6px 0 18px;
margin: 0 0 12px;
color: var(--pm-text-secondary);
font-size: 12px;
line-height: 1.7;

View File

@ -1,13 +1,9 @@
<template>
<div class="config-form" :data-form-section="section">
<p v-if="!groups.length" class="form-empty">当前没有可配置的插件。安装插件后可在这里管理启用状态与运行设置。</p>
<section v-for="group in groups" :key="group.id" class="form-group" :aria-labelledby="`form-group-${group.id}`">
<div class="form-group-heading">
<h5 :id="`form-group-${group.id}`">{{ group.title }}</h5>
<span v-if="section === 'plugins'" class="credential-status">{{ group.secretConfigured ? '凭据已配置' : '未配置凭据' }}</span>
</div>
<p class="form-group-description">{{ group.description }}</p>
<p v-if="group.note" class="form-group-description">{{ group.note }}</p>
<section v-for="group in groups" :key="group.id" class="form-group" :aria-label="group.title">
<p v-if="section === 'plugins'" class="credential-status">{{ group.secretConfigured ? '凭据已配置' : '未配置凭据' }}</p>
<p v-if="group.note" class="form-group-note" role="note">{{ group.note }}</p>
<div class="form-grid">
<ConfigField
v-for="field in group.fields"

View File

@ -127,15 +127,6 @@
.panel-heading {
flex-wrap: wrap;
}
.selected-section-label {
padding: 4px 8px;
border-radius: 6px;
background: var(--pm-bg-primary);
color: var(--pm-text-secondary);
font-size: 11px;
line-height: 1.5;
white-space: nowrap;
}
.editor-hint,
.action-hint {
margin: 8px 0 0;
@ -398,9 +389,6 @@
"main";
gap: 16px;
}
.selected-section-label {
white-space: normal;
}
.config-navigation {
gap: 12px;
position: static;

View File

@ -32,6 +32,27 @@ test('五个分区使用分组表单,控件和样式能编译且不再提供 J
}
})
test('分组不显示重复标题和介绍,保留无障碍名称、凭据状态及异常提示', () => {
const template = readFileSync(new URL('ConfigForm/ConfigForm.vue', root), 'utf8')
const css = readFileSync(new URL('ConfigForm/ConfigForm.css', root), 'utf8')
assert.doesNotMatch(template, /<h[1-6]\b|form-group-heading|form-group-description|group\.description/)
assert.doesNotMatch(template, /aria-labelledby/)
assert.match(template, /<section\b[^>]*:aria-label="group.title"/)
assert.match(template, /v-if="section === 'plugins'" class="credential-status"/)
assert.match(template, /group.secretConfigured \? '凭据已配置' : '未配置凭据'/)
assert.match(template, /v-if="group.note" class="form-group-note" role="note"/)
assert.match(template, /v-if="!groups.length" class="form-empty"/)
assert.match(template, /<ConfigField\b/)
for (const binding of ['v-for="field in group.fields"', ':value="getValue(config, field.path)"', ':error="errors[fieldKey(field)]"', ':disabled="disabled"', "emit('change', path, value)"]) {
assert.ok(template.includes(binding))
}
assert.doesNotMatch(css, /form-group-heading|form-group-description/)
const formStyle = css.match(/\.config-form\s*\{([^}]+)\}/)?.[1] || ''
assert.doesNotMatch(formStyle, /margin-top|padding-top/)
assert.match(css, /\.config-form:not\(:first-child\)\s*\{\s*margin-top: 16px;/)
assert.match(css, /\.form-group \+ \.form-group\s*\{[^}]*border-top:/)
})
test('单字段更新完整保留内部数据,锁定字段和污染路径无法写入', () => {
const config = {
generation: 8, schemaVersion: 2, database: { type: 'sqlite', localRef: 'test.db', password: null },

View File

@ -339,6 +339,20 @@ test('配置编辑页移除步骤提示条和专用样式,保留实际操作
assert.match(template, /@click="managePending\('cancel'\)"/)
})
test('配置编辑区移除填写标题、说明与重复标签,保留主标题和必要提示', () => {
const root = new URL('../../frontend/src/views/components/ConfigSettings/', import.meta.url)
const template = readFileSync(new URL('ConfigEditor/ConfigEditor.vue', root), 'utf8')
const css = readFileSync(new URL('ConfigSettings.css', root), 'utf8')
assert.doesNotMatch(template, /'填写配置'|'查看配置'|按分组填写内容|当前页面的脱敏配置内容|panel-heading|selected-section-label/)
assert.doesNotMatch(css, /selected-section-label/)
assert.match(template, /<h3 id="config-page-title">{{ currentSection.label }}/)
assert.match(template, /v-if="selectedSection === 'ssl' && status.tlsMode === 'proxy'"[^>]*role="note"/)
assert.match(template, /v-if="sectionError"[\s\S]*?:title="sectionError"/)
assert.match(template, /<p id="config-sensitive-hint"/)
assert.match(template, /v-if="secretField" class="secret-editor"/)
assert.match(template, /v-if="validationResult"[^>]*role="status"/)
})
test('离开、刷新和正在执行的变更受保护,取消时保留编辑内容', async () => {
let cancel = true
let prompts = 0