补齐用户信息标题并将数据库选择归入存储分组

This commit is contained in:
xuans 2026-09-20 23:40:24 +08:00
parent c220b7d06d
commit 6c129bb81e
5 changed files with 117 additions and 35 deletions

View File

@ -4,7 +4,7 @@
"version": "0.1.0",
"type": "module",
"scripts": {
"test": "node --test src/App.test.js src/views/SetupWizard/SetupWizard.test.js src/views/SetupWizard/SetupWizardBootstrap.test.js src/views/SetupWizard/DatabaseSetup.test.js src/views/SetupWizard/BootstrapPayload.test.js src/views/Dashboard/Dashboard.test.js src/views/UserManagement/UserManagement.test.js src/views/components/HelpTip/HelpTip.test.js",
"test": "node --test src/App.test.js src/views/SetupWizard/SetupWizard.test.js src/views/SetupWizard/SetupWizardBootstrap.test.js src/views/SetupWizard/DatabaseSetup.test.js src/views/SetupWizard/SetupWizardLayout.test.js src/views/SetupWizard/BootstrapPayload.test.js src/views/Dashboard/Dashboard.test.js src/views/UserManagement/UserManagement.test.js src/views/components/HelpTip/HelpTip.test.js",
"dev": "vite",
"build": "vite build",
"preview": "vite preview"

View File

@ -11,6 +11,14 @@ const emit = defineEmits(["test"]);
<template>
<section class="database-setup" aria-label="数据库存储配置">
<div class="setup-section-heading">
<strong>
数据库存储
<HelpTip>
选择一种存储方式,验证连接和写权限后继续。不会清空已有数据。
</HelpTip>
</strong>
</div>
<div class="database-type-field">
<label>
<span>数据库类型</span>
@ -32,14 +40,6 @@ const emit = defineEmits(["test"]);
<span v-if="verified" role="status">数据库连接及写权限已验证。</span>
</div>
</div>
<div class="setup-section-heading">
<strong>
数据库存储
<HelpTip>
选择一种存储方式,验证连接和写权限后继续。不会清空已有数据。
</HelpTip>
</strong>
</div>
<div v-if="form.driver !== 'sqlite'" class="setup-grid two-columns database-connection-grid">
<label>
<span>数据库主机</span>

View File

@ -131,7 +131,10 @@ const {
<div class="setup-step-grid">
<fieldset v-if="currentStep === 1" class="setup-step-card" :disabled="submitting || completed">
<legend><span>01</span>超级管理员</legend>
<div class="setup-grid two-columns">
<div class="setup-section-heading admin-account-heading">
<strong>用户信息</strong>
</div>
<div class="setup-grid two-columns admin-account-grid">
<label
><span>用户名</span
><input
@ -229,7 +232,7 @@ const {
</HelpTip>
</strong>
</div>
<div class="setup-grid service-access-grid" :class="{ 'has-database': isBootstrap }">
<div class="setup-grid service-access-grid">
<div class="access-target-field">
<span>管理页面访问方式</span>
<div class="target-switch" role="group" aria-label="管理页面访问方式">
@ -288,15 +291,15 @@ const {
type="number"
/>
</label>
<DatabaseSetup
v-if="isBootstrap"
:form="databaseForm"
:testing="databaseTesting"
:verified="databaseVerified"
:error="databaseError"
@test="testDatabase"
/>
</div>
<DatabaseSetup
v-if="isBootstrap"
:form="databaseForm"
:testing="databaseTesting"
:verified="databaseVerified"
:error="databaseError"
@test="testDatabase"
/>
<div class="provider-description reset-preview">
<strong>密码重置地址预览</strong>
<span>{{ resetURLPreview }}</span>

View File

@ -120,10 +120,41 @@
white-space: normal;
}
.access-shell--setup .setup-step-card:has(#admin-username) > .setup-grid:first-of-type {
.access-shell--setup .admin-account-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.access-shell--setup .admin-account-heading {
margin-top: 0;
}
.access-shell--setup .setup-step-card:has(.admin-account-grid) label,
.access-shell--setup .setup-step-card:has(.admin-account-grid) .access-target-field {
margin-top: 2px;
gap: 2px;
}
.access-shell--setup .setup-step-card:has(.admin-account-grid) label > span,
.access-shell--setup .setup-step-card:has(.admin-account-grid) .access-target-field > span {
min-height: 18px;
line-height: 18px;
}
.access-shell--setup .setup-step-card:has(.admin-account-grid) input,
.access-shell--setup .setup-step-card:has(.admin-account-grid) select {
height: 32px;
min-height: 32px;
padding-block: 4px;
}
.access-shell--setup .setup-step-card:has(.admin-account-grid) .setup-grid {
row-gap: 4px;
}
.access-shell--setup .setup-step-card:has(.admin-account-grid) .setup-section-heading {
margin-bottom: 2px;
}
.access-shell--setup .email-input-group {
grid-template-columns: minmax(0, 1fr) 96px;
}
@ -133,17 +164,16 @@
align-items: start;
}
.access-shell--setup .service-access-grid.has-database {
grid-template-columns: minmax(168px, 0.85fr) minmax(160px, 1.1fr) minmax(140px, 0.8fr) minmax(310px, 1.8fr);
}
.access-shell--setup .service-access-grid > .database-setup {
display: contents;
.access-shell--setup .database-setup {
gap: 4px;
margin-top: 6px;
padding-top: 6px;
}
.access-shell--setup .database-type-field {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 420px) auto;
justify-content: start;
align-items: start;
gap: 8px;
min-width: 0;
@ -156,8 +186,8 @@
.access-shell--setup .database-type-field .dns-test-button {
width: 92px;
height: auto;
min-height: 36px;
margin-top: 28px;
min-height: 32px;
margin-top: 22px;
white-space: normal;
overflow-wrap: anywhere;
padding: 6px 10px;
@ -180,8 +210,6 @@
flex-wrap: wrap;
align-items: baseline;
gap: 4px 10px;
padding-top: 8px;
border-top: 1px solid #e4ebf5;
}
.access-shell--setup .database-connection-grid {
@ -334,10 +362,6 @@
}
@media (min-width: 901px) and (max-width: 1279px) {
.access-shell--setup .service-access-grid.has-database {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.access-shell--setup .database-connection-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}

View File

@ -0,0 +1,55 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { baseParse } from "@vue/compiler-dom";
import { parse } from "@vue/compiler-sfc";
function template(name) {
const source = readFileSync(new URL(name, import.meta.url), "utf8");
return baseParse(parse(source).descriptor.template.content);
}
function find(node, predicate) {
if (predicate(node)) return node;
for (const child of node.children || []) {
const result = find(child, predicate);
if (result) return result;
}
}
function hasClass(name) {
return (node) => node.props?.some((prop) => prop.name === "class"
&& prop.value?.content.split(/\s+/).includes(name));
}
test("account heading precedes the two-column account fields", () => {
const root = template("./SetupWizard.vue");
const card = find(root, (node) => node.tag === "fieldset"
&& node.children.some(hasClass("admin-account-grid")));
const heading = card.children.findIndex(hasClass("admin-account-heading"));
const grid = card.children.findIndex(hasClass("admin-account-grid"));
assert.ok(heading >= 0 && heading < grid);
assert.equal(card.children[grid].children.filter((node) => node.tag === "label").length, 4);
assert.ok(find(card.children[heading], (node) => node.tag === "strong"));
});
test("database setup follows service access instead of occupying its grid", () => {
const root = template("./SetupWizard.vue");
const card = find(root, (node) => node.tag === "fieldset"
&& node.children.some(hasClass("service-access-grid")));
const service = card.children.findIndex(hasClass("service-access-grid"));
const database = card.children.findIndex((node) => node.tag === "DatabaseSetup");
assert.ok(service >= 0 && database > service);
assert.equal(find(card.children[service], (node) => node.tag === "DatabaseSetup"), undefined);
});
test("database heading precedes selection, verification and connection fields", () => {
const section = find(template("./DatabaseSetup.vue"), hasClass("database-setup"));
const heading = section.children.findIndex(hasClass("setup-section-heading"));
const selection = section.children.findIndex(hasClass("database-type-field"));
const connection = section.children.findIndex(hasClass("database-connection-grid"));
assert.ok(heading >= 0 && selection > heading && connection > selection);
assert.ok(find(section.children[selection], (node) => node.tag === "select"));
assert.ok(find(section.children[selection], (node) => node.tag === "button"));
assert.ok(find(section.children[heading], (node) => node.tag === "HelpTip"));
});