拆分域名配置弹窗并修复多域名单屏布局与提示换行

This commit is contained in:
xuans 2026-09-25 01:20:55 +08:00
parent eb9181d04b
commit c654334436
4 changed files with 313 additions and 135 deletions

View File

@ -305,3 +305,62 @@ test("auditStatusRendering", async () => {
assert.doesNotMatch(html, /danger/);
}
});
test("domainModal.compilesAndRendersSanFieldsWithoutLiteralNewline", async () => {
const { readFile } = await import("node:fs/promises");
const { parse, compileScript, compileTemplate } = await import("@vue/compiler-sfc");
const { renderToString } = await import("@vue/server-renderer");
const Vue = await import("vue");
const source = await readFile(new URL("./DomainModal.vue", import.meta.url), "utf8");
const { descriptor, errors } = parse(source);
assert.deepEqual(errors, []);
assert.ok(compileScript(descriptor, { id: "domain-modal" }));
const compiled = compileTemplate({
source: descriptor.template.content,
filename: "DomainModal.vue",
id: "domain-modal",
compilerOptions: { mode: "function" },
});
assert.deepEqual(compiled.errors, []);
const render = new Function("Vue", compiled.code)(Vue);
for (const certificateType of ["single", "wildcard", "san"]) {
const app = Vue.createSSRApp({
render,
setup: () => ({
showForm: true,
formTitle: "test-domain",
closeForm: () => {},
saveDomain: () => {},
form: {
name: "example.test",
certificate_type: certificateType,
sans: "",
acme_environment: "staging",
dns_provider: "aliyun",
auto_renew: true,
server_ids: [],
},
dnsProviders: [],
servers: [],
displayLabel: (_, value) => value,
webServerLabels: {},
formError: "",
saving: false,
}),
});
app.component("HelpTip", {
setup: (_, { slots }) => () => Vue.h("span", { class: "test-help-tip" }, slots.default?.()),
});
const html = await renderToString(app);
assert.match(html, /class="modal-card domain-modal"/);
assert.match(html, /type="submit"/);
assert.match(html, /class="test-help-tip"/);
assert.doesNotMatch(html, /www\.example\.com\\napi/);
assert.equal(html.includes('id="domain-sans"'), certificateType === "san");
if (certificateType === "san") {
assert.match(html, /rows="2"/);
assert.ok(html.includes("www.example.com\napi.example.com"));
}
}
});

View File

@ -2,6 +2,7 @@
import ChangePassword from "../components/ChangePassword/ChangePassword.vue";
import UserManagement from "../UserManagement/UserManagement.vue";
import SettingsModal from "./SettingsModal.vue";
import DomainModal from "./DomainModal.vue";
import { useDashboard } from "./Dashboard.js";
const dashboard = useDashboard();
@ -466,143 +467,10 @@ const {
</div>
</form>
</section>
</div> <div v-if="showForm" class="modal-backdrop" @click.self="closeForm">
<section
class="modal-card"
role="dialog"
aria-modal="true"
:aria-label="formTitle"
>
<div class="modal-heading">
<div>
<p class="eyebrow">域名配置</p>
<h2>{{ formTitle }}</h2>
</div>
<button
class="close-button"
type="button"
aria-label="关闭"
@click="closeForm"
>
×
</button>
</div>
<form @submit.prevent="saveDomain">
<label class="field" for="domain-name"
><span>主域名</span
><input
id="domain-name"
name="name"
v-model="form.name"
type="text"
placeholder="例如 example.com"
autocomplete="off"
/></label>
<label class="field" for="certificate-type"
><span>证书类型</span
><select
id="certificate-type"
name="certificate_type"
v-model="form.certificate_type"
>
<option value="single">单域名</option>
<option value="wildcard">泛域名</option>
<option value="san">SAN 多域名</option>
</select></label
>
<label
v-if="form.certificate_type === 'san'"
class="field"
for="domain-sans"
><span>附加域名 <small>每行一个,也支持逗号分隔</small></span
><textarea
id="domain-sans"
name="sans"
v-model="form.sans"
rows="4"
placeholder="www.example.com\napi.example.com"
></textarea>
</label>
<div class="form-grid">
<label class="field" for="acme-environment"
><span>ACME 环境</span
><select
id="acme-environment"
name="acme_environment"
v-model="form.acme_environment"
>
<option value="staging">测试环境</option>
<option value="production">生产环境</option>
</select></label
>
<label class="field" for="dns-provider"
><span>DNS 验证服务商</span
><select
id="dns-provider"
name="dns_provider"
v-model="form.dns_provider"
>
<option value="">暂不选择</option>
<option
v-for="provider in dnsProviders"
:key="provider.value"
:value="provider.value"
>
{{ provider.label }}
</option>
</select></label
>
</div>
<label class="switch-field" for="auto-renew"
><input
id="auto-renew"
name="auto_renew"
v-model="form.auto_renew"
type="checkbox"
/><span
><strong>启用自动续期</strong
><small>证书剩余有效期达到阈值时自动创建续期任务</small></span
></label
>
<fieldset class="server-picker">
<legend>关联部署服务器</legend>
<label
v-for="server in servers"
:key="server.id"
class="server-option"
><input
v-model="form.server_ids"
type="checkbox"
:value="server.id"
/><span
><strong>{{ server.name }}</strong
><small
>{{ server.address }} ·
{{ displayLabel(webServerLabels, server.web_server) }}</small
></span
></label
>
<p v-if="servers.length === 0" class="muted">
暂无服务器配置,可先保存域名后添加服务器。
</p>
</fieldset>
<p v-if="formError" class="form-error">{{ formError }}</p>
<div class="modal-actions">
<button
class="secondary-button"
type="button"
:disabled="saving"
@click="closeForm"
>
取消</button
><button class="primary-button" type="submit" :disabled="saving">
{{ saving ? "保存中……" : "保存配置" }}
</button>
</div>
</form>
</section>
</div>
<DomainModal v-if="showForm" :state="dashboard" />
<div
v-if="showServerForm"
class="modal-backdrop"

View File

@ -0,0 +1,91 @@
.modal-backdrop.domain-backdrop {
padding: 16px;
}
.domain-modal {
box-sizing: border-box;
width: min(100%, 1060px);
max-height: calc(100dvh - 32px);
padding: 22px 28px;
}
.domain-modal .modal-heading {
margin-bottom: 12px;
padding-bottom: 10px;
}
.domain-modal form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0 18px;
}
.domain-modal .domain-sans-field,
.domain-modal .form-grid,
.domain-modal .switch-field,
.domain-modal .server-picker,
.domain-modal .form-error,
.domain-modal .modal-actions {
grid-column: 1 / -1;
}
.domain-modal .field {
min-width: 0;
margin-bottom: 9px;
gap: 5px;
}
.domain-modal .field input,
.domain-modal .field select,
.domain-modal .field textarea {
min-height: 36px;
padding: 8px 11px;
}
.domain-modal .field textarea {
min-height: 72px;
}
.domain-modal .field > span,
.domain-modal .switch-field > span {
display: flex;
align-items: center;
gap: 6px;
}
.domain-modal .switch-field {
align-items: center;
margin: 4px 0 10px;
}
.domain-modal .server-picker {
min-width: 0;
max-height: 130px;
overflow-y: auto;
margin: 4px 0 12px;
padding: 10px 12px;
}
.domain-modal .server-picker .muted {
margin: 8px 0;
}
.domain-modal .form-error {
margin-bottom: 8px;
}
.domain-modal .modal-actions {
padding-top: 10px;
border-top: 1px solid #dce6f1;
}
@media (max-width: 640px) {
.domain-modal {
padding: 18px;
}
.domain-modal form,
.domain-modal .form-grid {
grid-template-columns: minmax(0, 1fr);
}
}

View File

@ -0,0 +1,160 @@
<script setup>
import HelpTip from "../components/HelpTip/HelpTip.vue";
const props = defineProps({ state: { type: Object, required: true } });
const {
showForm,
formTitle,
closeForm,
saveDomain,
form,
dnsProviders,
servers,
displayLabel,
webServerLabels,
formError,
saving,
} = props.state;
</script>
<template>
<div v-if="showForm" class="modal-backdrop domain-backdrop" @click.self="closeForm">
<section
class="modal-card domain-modal"
role="dialog"
aria-modal="true"
:aria-label="formTitle"
>
<div class="modal-heading">
<div>
<p class="eyebrow">域名配置</p>
<h2>{{ formTitle }}</h2>
</div>
<button
class="close-button"
type="button"
aria-label="关闭"
@click="closeForm"
>
×
</button>
</div>
<form @submit.prevent="saveDomain">
<label class="field" for="domain-name"
><span>主域名</span
><input
id="domain-name"
name="name"
v-model="form.name"
type="text"
placeholder="例如 example.com"
autocomplete="off"
/></label>
<label class="field" for="certificate-type"
><span>证书类型</span
><select
id="certificate-type"
name="certificate_type"
v-model="form.certificate_type"
>
<option value="single">单域名</option>
<option value="wildcard">泛域名</option>
<option value="san">SAN 多域名</option>
</select></label
>
<label
v-if="form.certificate_type === 'san'"
class="field domain-sans-field"
for="domain-sans"
><span>附加域名 <HelpTip>每行一个,也支持逗号分隔</HelpTip></span
><textarea
id="domain-sans"
name="sans"
v-model="form.sans"
rows="2"
:placeholder="'www.example.com\napi.example.com'"
></textarea>
</label>
<div class="form-grid">
<label class="field" for="acme-environment"
><span>ACME 环境</span
><select
id="acme-environment"
name="acme_environment"
v-model="form.acme_environment"
>
<option value="staging">测试环境</option>
<option value="production">生产环境</option>
</select></label
>
<label class="field" for="dns-provider"
><span>DNS 验证服务商</span
><select
id="dns-provider"
name="dns_provider"
v-model="form.dns_provider"
>
<option value="">暂不选择</option>
<option
v-for="provider in dnsProviders"
:key="provider.value"
:value="provider.value"
>
{{ provider.label }}
</option>
</select></label
>
</div>
<label class="switch-field" for="auto-renew"
><input
id="auto-renew"
name="auto_renew"
v-model="form.auto_renew"
type="checkbox"
/><span
><strong>启用自动续期</strong
><HelpTip>证书剩余有效期达到阈值时自动创建续期任务</HelpTip></span
></label
>
<fieldset class="server-picker">
<legend>关联部署服务器</legend>
<label
v-for="server in servers"
:key="server.id"
class="server-option"
><input
v-model="form.server_ids"
type="checkbox"
:value="server.id"
/><span
><strong>{{ server.name }}</strong
><small
>{{ server.address }} ·
{{ displayLabel(webServerLabels, server.web_server) }}</small
></span
></label
>
<p v-if="servers.length === 0" class="muted">
暂无服务器配置,可先保存域名后添加服务器。
</p>
</fieldset>
<p v-if="formError" class="form-error">{{ formError }}</p>
<div class="modal-actions">
<button
class="secondary-button"
type="button"
:disabled="saving"
@click="closeForm"
>
取消</button
><button class="primary-button" type="submit" :disabled="saving">
{{ saving ? "保存中……" : "保存配置" }}
</button>
</div>
</form>
</section>
</div>
</template>
<style scoped src="./Dashboard.css"></style>
<style scoped src="./DomainModal.css"></style>