feat(client): A3 客户与行业目录(两级行业、客户组、字段审批)

背景:A3 需把行业 / 客户 / 客户组 / 客户级字段立为资产根节点;用户确认两级行业树与混搭 UI(行业 B / 客户列表 A / 详情 A / 新建 B / 客户组 B / 字段 A)。

改动:
- 新增 ADR-002,行业改为两级树,客户挂末级行业;同步 PRD / 技术架构 / 开发计划。
- 新增 client 模块:industries / customers / customer-groups / customer-fields 接口、真实 PrismaCustomerDirectory、客户状态机与客户转交、客户组数据范围解析、跨客户 404。
- 新增迁移与幂等种子(电商/金融两级行业、2 客户、1 客户组、4 条字段)。
- 前端新增行业/客户/客户组/字段审批页面与路由、导航更名(客户管理 / 行业管理 / 客户组),复用现有 token。
- 补充 shared 契约单测、client 集成用例、A3 浏览器 e2e。

影响面:client 模块与 client 表、shared 契约、web 路由/导航、ScopeContext 数据范围、OpenAPI 与种子;A1 客户转交占位由 client 实现替换。
回滚:回滚本提交;数据库保留新增表或按报告执行 down SQL;CUSTOMER_DIRECTORY 可切回空集实现。
验证:format:check / lint / typecheck / test 全绿(shared 131 / api 90 / web 125,共 346);test:int 5 文件 39 条;A3 e2e 1/1;build 8/8;真实浏览器六页验证通过。
This commit is contained in:
Stev_Wang committed 2026-11-03 21:45:38 +08:00
1 parent 8626996bb1
commit 140dc386c1
54 files changed
+9018 -147

No files matched your search

+526
View File
@@ -0,0 +1,526 @@
/* A3 客户与行业页面(混搭布局:客户域 A / 行业与客户组 B / 字段审批 A) */
.lx-client-page {
display: flex;
flex-direction: column;
gap: 16px;
min-height: 0;
flex: 1;
}
.lx-client-header {
display: flex;
align-items: flex-start;
gap: 16px;
}
.lx-client-header h1 {
margin: 0;
font-size: 20px;
line-height: 28px;
font-weight: 600;
}
.lx-client-header p {
margin: 2px 0 0;
font-size: 12px;
line-height: 18px;
color: var(--lx-text-2, #8f959e);
}
.lx-client-header__actions {
margin-left: auto;
display: flex;
gap: 8px;
flex: none;
}
.lx-client-filters {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.lx-client-split {
display: flex;
gap: 16px;
flex: 1;
min-height: 0;
}
.lx-client-left {
width: 380px;
flex: none;
background: #fff;
border: 1px solid var(--lx-border, #e5e8ef);
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.lx-client-left__head {
height: 48px;
flex: none;
display: flex;
align-items: center;
gap: 8px;
padding: 0 14px;
border-bottom: 1px solid #eef1f6;
}
.lx-client-left__head b {
font-size: 13px;
}
.lx-client-left__head span {
margin-left: auto;
font-size: 12px;
color: #646a73;
}
.lx-client-left__body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 8px;
}
.lx-client-left__foot {
height: 40px;
flex: none;
display: flex;
align-items: center;
padding: 0 14px;
border-top: 1px solid #eef1f6;
font-size: 12px;
color: #646a73;
}
.lx-client-right {
flex: 1;
min-width: 0;
background: #fff;
border: 1px solid var(--lx-border, #e5e8ef);
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.lx-client-right__head {
flex: none;
padding: 16px 18px;
border-bottom: 1px solid #eef1f6;
display: flex;
align-items: flex-start;
gap: 12px;
}
.lx-client-right__head h2 {
margin: 0;
font-size: 16px;
line-height: 24px;
}
.lx-client-right__head p {
margin: 2px 0 0;
font-size: 12px;
color: #646a73;
}
.lx-client-right__head .lx-client-header__actions {
margin-left: auto;
}
.lx-client-right__body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 16px 18px 20px;
}
.lx-client-tree {
display: flex;
flex-direction: column;
gap: 2px;
}
.lx-client-tree__row {
display: flex;
align-items: center;
gap: 6px;
height: 34px;
padding: 0 8px;
border-radius: 6px;
background: transparent;
color: #646a73;
font-size: 13px;
text-align: left;
}
.lx-client-tree__row:hover {
background: #f2f4f7;
color: #1f2329;
}
.lx-client-tree__row.is-active {
background: #e8f1ff;
color: #1677ff;
}
.lx-client-tree__row.is-child {
padding-left: 30px;
height: 30px;
}
.lx-client-tree__name {
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lx-client-tree__count {
font-size: 11px;
color: #8f959e;
}
.lx-client-tree__row.is-active .lx-client-tree__count {
color: #1677ff;
}
.lx-client-table-card {
background: #fff;
border: 1px solid #e5e8ef;
border-radius: 10px;
overflow: hidden;
}
.lx-client-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 48px 24px;
color: #646a73;
font-size: 12px;
}
.lx-client-empty b {
font-size: 13px;
color: #1f2329;
}
.lx-client-grid {
display: grid;
grid-template-columns: 200px minmax(0, 1fr) 280px;
gap: 16px;
align-items: start;
}
.lx-client-anchors {
background: #fff;
border: 1px solid #e5e8ef;
border-radius: 10px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
position: sticky;
top: 0;
}
.lx-client-anchors a {
display: flex;
align-items: center;
height: 32px;
padding: 0 10px;
border-radius: 6px;
font-size: 13px;
color: #646a73;
}
.lx-client-anchors a:hover {
background: #f2f4f7;
color: #1f2329;
}
.lx-client-panel {
background: #fff;
border: 1px solid #e5e8ef;
border-radius: 10px;
padding: 18px 20px;
min-width: 0;
}
.lx-client-side {
display: flex;
flex-direction: column;
gap: 14px;
}
.lx-client-sidecard {
background: #fff;
border: 1px solid #e5e8ef;
border-radius: 10px;
padding: 14px 16px;
}
.lx-client-sidecard h3 {
margin: 0 0 8px;
font-size: 13px;
font-weight: 600;
}
.lx-client-kv {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px 28px;
}
.lx-client-kv__item span {
display: block;
font-size: 12px;
color: #646a73;
}
.lx-client-kv__item b {
display: block;
margin-top: 2px;
font-size: 13px;
font-weight: 500;
}
.lx-client-section {
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid #eef1f6;
}
.lx-client-section:last-child {
border-bottom: 0;
margin-bottom: 0;
padding-bottom: 0;
}
.lx-client-section__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.lx-client-section__head b {
font-size: 14px;
}
.lx-client-hint {
margin-top: 8px;
font-size: 12px;
line-height: 18px;
color: #646a73;
}
.lx-client-tag {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 7px;
border-radius: 4px;
font-size: 11px;
}
.lx-client-tag--active {
background: #e8ffea;
color: #008a24;
}
.lx-client-tag--paused {
background: #fff4e8;
color: #d25f00;
}
.lx-client-tag--ended {
background: #f2f4f7;
color: #646a73;
}
.lx-client-tag--disabled {
background: #f2f4f7;
color: #646a73;
}
.lx-client-banner {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 6px;
font-size: 12px;
}
.lx-client-banner--paused {
background: #fff4e8;
color: #d25f00;
}
.lx-client-banner--ended {
background: #f2f4f7;
color: #646a73;
}
.lx-client-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.lx-client-chip {
height: 28px;
padding: 0 9px;
border-radius: 6px;
background: #fafbfc;
border: 1px solid #eef1f6;
font-size: 12px;
display: inline-flex;
align-items: center;
gap: 5px;
}
.lx-client-newgrid {
display: grid;
grid-template-columns: 260px minmax(0, 1fr);
gap: 16px;
flex: 1;
min-height: 0;
}
.lx-client-stepnav {
background: #fff;
border: 1px solid #e5e8ef;
border-radius: 10px;
padding: 16px;
}
.lx-client-formcard {
background: #fff;
border: 1px solid #e5e8ef;
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.lx-client-formcard__body {
padding: 20px 22px;
flex: 1;
overflow: auto;
}
.lx-client-formcard__foot {
height: 60px;
flex: none;
display: flex;
align-items: center;
gap: 8px;
padding: 0 20px;
border-top: 1px solid #eef1f6;
}
.lx-client-formcard__foot .spacer {
flex: 1;
}
.lx-client-formgrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px 20px;
}
.lx-client-formgrid .is-full {
grid-column: 1 / -1;
}
.lx-client-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.lx-client-field > label {
font-size: 12px;
color: #646a73;
}
.lx-client-field > label i {
color: #f53f3f;
font-style: normal;
}
.lx-client-memberlist {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 12px;
}
.lx-client-memberrow {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
border: 1px solid #eef1f6;
border-radius: 6px;
}
.lx-client-memberrow b {
font-size: 13px;
font-weight: 500;
}
.lx-client-memberrow span {
margin-left: auto;
font-size: 12px;
color: #646a73;
}
.lx-client-assetempty {
border: 1px dashed #d0d5dd;
border-radius: 6px;
background: #fafbfc;
padding: 20px;
font-size: 12px;
line-height: 20px;
color: #646a73;
}
.lx-client-assetempty b {
display: block;
font-size: 13px;
color: #1f2329;
margin-bottom: 4px;
}
@media (max-width: 1280px) {
.lx-client-grid {
grid-template-columns: 180px minmax(0, 1fr);
}
.lx-client-grid .lx-client-side {
display: none;
}
.lx-client-left {
width: 330px;
}
}
.lx-client-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
table-layout: fixed;
}
.lx-client-table th {
height: 40px;
padding: 0 14px;
text-align: left;
background: #fafbfc;
border-bottom: 1px solid #eef1f6;
color: #646a73;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.lx-client-table td {
padding: 12px 14px;
border-bottom: 1px solid #eef1f6;
color: #646a73;
vertical-align: middle;
line-height: 20px;
}
.lx-client-table tbody tr {
cursor: pointer;
}
.lx-client-table tbody tr:hover {
background: #f8fbff;
}
.lx-client-table td b {
font-weight: 500;
color: #1f2329;
}
.lx-client-sub {
display: block;
font-size: 11px;
color: #646a73;
}
.lx-client-muted {
color: #646a73;
font-size: 12px;
}
.lx-client-count {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 7px;
border-radius: 4px;
background: #f2f4f7;
color: #646a73;
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.lx-client-count.is-full {
background: #fff4e8;
color: #d25f00;
}
.lx-client-tablefoot {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
font-size: 12px;
color: #646a73;
border-top: 1px solid #eef1f6;
}
.lx-client-tablefoot .semi-button {
margin-left: auto;
}
.lx-client-back {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #646a73;
margin-bottom: 4px;
}
.lx-client-back:hover {
color: #1677ff;
}