fix(project): 优化项目与成员页面卡片布局、表单基线对齐与表格统一列宽

This commit is contained in:
xuans 2026-10-17 01:03:50 +08:00
parent b2b52c9bd8
commit c474a13332
4 changed files with 133 additions and 22 deletions

View File

@ -1,3 +1,11 @@
## 2026年10月17日 “项目与成员”页面布局与表格对齐优化
- [✓] 复审通过:优化左侧“我的项目”卡片布局,项目名称与权限标签采用单行水平两端对齐,避免纵向压缩拉伸,保持文字左对齐。
- [✓] 复审通过:优化右侧“授予角色”表单基线对齐,为“项目角色”统一高度基线(22px),解决输入框与下拉选择框上下不对齐的问题。
- [✓] 复审通过:重构成员表格为固定列宽与靠左对齐规范(col-member 38%、col-status 22%、col-roles 40%),采用独立状态徽章与账户 ID 结构,避免列宽扭曲与文字换行混乱。
- [✓] 复审通过:修复底部分页控件“每页 [select] 位成员”横向排布,强制单行水平排列(white-space: nowrap),彻底杜绝“位成员”文字竖向折行。
- [✓] 复审通过:执行前端 356 项单元测试全部 PASS,编译构建产物成功。
## 2026年10月17日 系统设置单项独立保存与光标等待修复验收
- [✓] 复审通过:系统设置重构为单项独立配置与保存,每个选项卡(服务监听、ACME 设置、DNS API 验证、邮件通知)内置独立保存按钮及局部校验逻辑。

View File

@ -1,3 +1,11 @@
## 2026年10月17日 “项目与成员”页面视觉与布局对齐整改
- [✓] 优化左侧项目卡片 flex 布局,项目名称左对齐并单行省略,右侧权限标签紧凑展示不折行。
- [✓] 统一右侧添加成员表单项基线与按钮垂直对齐,解决成员账户与项目角色列高度不一问题。
- [✓] 表格采用 table-layout: fixed 与靠左对齐,统一各列宽度定义,美化成员 ID 与状态标签。
- [✓] 修复分页每页条数区域换行问题,确保“每页 [select] 位成员”横向对齐不折行。
- [✓] 前端测试 356 项全通过并成功部署至第一台服务器(8.137.23.95)。
## 2026年10月17日 系统设置单项独立保存与光标等待修复验收
- [✓] 复审通过:系统设置重构为单项独立配置与保存,每个选项卡(服务监听、ACME 设置、DNS API 验证、邮件通知)内置独立保存按钮及局部校验逻辑。

View File

@ -103,9 +103,9 @@
.project-list button {
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 12px 14px;
border: 1px solid #e2e8f0;
@ -143,12 +143,16 @@
}
.project-list button strong {
font-size: 15px;
font-size: 14px;
font-weight: 600;
color: inherit;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.project-list button small {
flex-shrink: 0;
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
@ -156,6 +160,7 @@
color: #64748b;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.project-list button[aria-current="true"] small {
@ -188,10 +193,13 @@
min-width: 0;
}
.project-account-field > span {
display: flex;
.project-account-field > span,
.project-role-field-label {
display: inline-flex;
align-items: center;
gap: 4px;
height: 22px;
line-height: 22px;
}
.project-management input,
@ -286,6 +294,7 @@
.project-member-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
text-align: left;
font-size: 14px;
@ -301,17 +310,77 @@
color: #475569;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
text-align: left;
letter-spacing: 0.02em;
}
.project-member-table th.col-member,
.project-member-table td.col-member {
width: 38%;
text-align: left;
}
.project-member-table th.col-status,
.project-member-table td.col-status {
width: 22%;
text-align: left;
}
.project-member-table th.col-roles,
.project-member-table td.col-roles {
width: 40%;
text-align: left;
}
.project-member-table td {
padding: 14px 16px;
border-bottom: 1px solid #f1f5f9;
color: #334155;
text-align: left;
vertical-align: middle;
line-height: 1.5;
overflow-wrap: anywhere;
}
.project-member-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.project-member-name {
font-size: 14px;
font-weight: 600;
color: #0f172a;
}
.project-member-id {
color: #64748b;
font-size: 12px;
font-family: ui-monospace, monospace;
}
.project-status-badge {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.project-status-badge.active {
color: #16a34a;
background: #f0fdf4;
border: 1px solid #bbf7d0;
}
.project-status-badge:not(.active) {
color: #92400e;
background: #fffbeb;
border: 1px solid #fde68a;
}
.project-member-table tr:last-child td {
border-bottom: none;
}
@ -384,11 +453,18 @@
gap: 12px;
}
.project-member-pagination label {
display: flex;
.project-member-pagination label,
.member-page-size-label {
display: inline-flex;
align-items: center;
gap: 6px;
margin-right: auto;
white-space: nowrap;
}
.member-page-size-label select {
width: auto;
min-width: 68px;
}
.project-empty {
@ -491,4 +567,4 @@
.project-editor {
grid-template-columns: 1fr;
}
}
}

View File

@ -101,7 +101,8 @@ const {
<option v-for="account in accountChoices" :key="account.id" :value="account.id">{{ account.username }}</option>
</datalist>
</label>
<label>项目角色
<label>
<span class="project-role-field-label">项目角色</span>
<select v-model="memberForm.role" :disabled="savingMember || loadingMembers">
<option v-for="role in projectRoles" :key="role.value" :value="role.value">{{ role.label }}</option>
</select>
@ -117,28 +118,46 @@ const {
</div>
<div class="project-member-table" :aria-busy="loadingMembers">
<table>
<thead><tr><th scope="col">成员</th><th scope="col">状态</th><th scope="col">项目角色</th></tr></thead>
<thead>
<tr>
<th scope="col" class="col-member">成员</th>
<th scope="col" class="col-status">状态</th>
<th scope="col" class="col-roles">项目角色</th>
</tr>
</thead>
<tbody>
<tr v-for="member in members" :key="member.user_id">
<td><strong>{{ member.username }}</strong><small>{{ member.user_id }}</small></td>
<td>{{ member.status === "active" ? "正常" : "已停用" }}</td>
<td><div class="project-role-list">
<span v-for="role in member.roles" :key="role" class="project-role">
{{ roleLabel(role) }}
<button type="button" :disabled="savingMember || loadingMembers" :aria-label="`撤销 ${member.username} 的${roleLabel(role)}角色`" @click="requestRevoke(member, role)">撤销</button>
<td class="col-member">
<div class="project-member-info">
<strong class="project-member-name">{{ member.username }}</strong>
<small class="project-member-id" :title="member.user_id">账户 ID:{{ member.user_id }}</small>
</div>
</td>
<td class="col-status">
<span class="project-status-badge" :class="member.status">
{{ member.status === "active" ? "正常" : "已停用" }}
</span>
</div></td>
</td>
<td class="col-roles">
<div class="project-role-list">
<span v-for="role in member.roles" :key="role" class="project-role">
{{ roleLabel(role) }}
<button type="button" :disabled="savingMember || loadingMembers" :aria-label="`撤销 ${member.username} 的${roleLabel(role)}角色`" @click="requestRevoke(member, role)">撤销</button>
</span>
</div>
</td>
</tr>
<tr v-if="!members.length"><td colspan="3" class="project-empty">{{ loadingMembers ? "正在读取成员……" : "本页暂无成员" }}</td></tr>
</tbody>
</table>
</div>
<div class="project-pagination project-member-pagination">
<label>每页
<label class="member-page-size-label">
<span>每页</span>
<select :value="memberPageSize" :disabled="savingMember || loadingMembers" @change="changeMemberPageSize($event.target.value)">
<option v-for="size in pageSizes" :key="size" :value="size">{{ size }}</option>
</select>
位成员
<span>位成员</span>
</label>
<button type="button" :disabled="savingMember || loadingMembers || memberPage === 0" @click="previousMemberPage">上一页</button>
<span>第 {{ memberPage + 1 }} 页</span>