feat: add customer authentication and scoped RBAC

This commit is contained in:
lingniu
2026-07-16 13:58:28 +08:00
parent 6d6c9ce534
commit a1195fb97d
28 changed files with 1738 additions and 97 deletions

View File

@@ -18,13 +18,23 @@ body { margin: 0; background: var(--v2-bg); color: var(--v2-text); font-family:
button, input, select { font: inherit; }
button, a { -webkit-tap-highlight-color: transparent; }
.v2-auth-screen { display: grid; min-height: 100vh; place-items: center; background: radial-gradient(circle at 50% 10%, #edf4ff 0, #f4f7fb 42%, #eef2f7 100%); padding: 20px; }
.v2-auth-card { display: flex; width: min(390px, 100%); flex-direction: column; align-items: stretch; border: 1px solid var(--v2-border); border-radius: 16px; background: #fff; padding: 34px; box-shadow: 0 22px 70px rgba(21, 32, 51, .12); }
.v2-auth-screen { display: grid; min-height: 100vh; grid-template-columns: minmax(360px, 1.15fr) minmax(420px, .85fr); align-items: center; background: #f3f6fa; padding: clamp(28px, 5vw, 80px); }
.v2-auth-intro { max-width: 640px; padding: 36px 8vw 36px 4vw; }
.v2-auth-intro > img { width: 176px; }
.v2-auth-intro h2 { margin: 54px 0 20px; color: #14223a; font-size: clamp(34px, 4vw, 58px); font-weight: 720; line-height: 1.16; letter-spacing: -.055em; }
.v2-auth-intro p { max-width: 520px; margin: 0; color: #617087; font-size: 16px; line-height: 1.9; }
.v2-auth-intro > div { display: flex; flex-wrap: wrap; gap: 24px; margin-top: 42px; color: #3f5068; font-size: 13px; font-weight: 650; }
.v2-auth-intro > div span::before { display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--v2-blue); content: ""; }
.v2-auth-card { display: flex; width: min(430px, 100%); flex-direction: column; align-items: stretch; border: 1px solid #e0e7f0; border-radius: 18px; background: #fff; padding: 42px; box-shadow: 0 28px 80px rgba(21, 32, 51, .11); }
.v2-auth-loading { grid-column: 1 / -1; justify-self: center; }
.v2-auth-card > strong { margin-top: 12px; text-align: center; }
.v2-auth-logo { display: block; width: 150px; height: auto; margin: 0 auto 18px; }
.v2-auth-card h1 { margin: 0; text-align: center; font-size: 22px; }.v2-auth-card p { margin: 10px 0 24px; color: var(--v2-muted); text-align: center; font-size: 12px; line-height: 1.7; }
.v2-auth-card label { display: flex; flex-direction: column; gap: 7px; color: #58667a; font-size: 12px; font-weight: 600; }.v2-auth-card input { height: 40px; border: 1px solid #d7e0ec; border-radius: 8px; padding: 0 11px; outline: 0; }.v2-auth-card input:focus { border-color: #8bb6fb; box-shadow: 0 0 0 3px rgba(18,104,243,.08); }
.v2-auth-card em { margin-top: 9px; color: var(--v2-red); font-size: 11px; font-style: normal; }.v2-auth-card button { height: 40px; margin-top: 18px; border: 0; border-radius: 8px; background: var(--v2-blue); color: #fff; cursor: pointer; font-weight: 700; }.v2-auth-card button:disabled { opacity: .45; cursor: not-allowed; }
.v2-auth-card h1 { margin: 2px 0 0; text-align: center; font-size: 24px; letter-spacing: -.035em; }.v2-auth-card p { margin: 11px 0 26px; color: var(--v2-muted); text-align: center; font-size: 13px; line-height: 1.7; }
.v2-auth-card label { display: flex; flex-direction: column; gap: 8px; margin-top: 15px; color: #44536a; font-size: 13px; font-weight: 650; }.v2-auth-card input { height: 46px; border: 1px solid #d5deea; border-radius: 9px; padding: 0 13px; outline: 0; font-size: 14px; }.v2-auth-card input:focus { border-color: #7aaafb; box-shadow: 0 0 0 3px rgba(18,104,243,.08); }
.v2-auth-card em { margin-top: 12px; color: var(--v2-red); font-size: 12px; font-style: normal; }.v2-auth-card button { height: 46px; margin-top: 22px; border: 0; border-radius: 9px; background: var(--v2-blue); color: #fff; cursor: pointer; font-size: 14px; font-weight: 700; }.v2-auth-card button:disabled { opacity: .45; cursor: not-allowed; }
.v2-auth-card .v2-auth-mode-switch { height: 36px; margin-top: 8px; background: transparent; color: #68778c; font-size: 12px; font-weight: 600; }
.v2-auth-card .v2-auth-mode-switch:hover { color: var(--v2-blue); }
.v2-auth-card > small { margin-top: 14px; color: #9aa5b5; text-align: center; font-size: 10px; }
.v2-auth-spinner { width: 24px; height: 24px; margin: auto; border: 3px solid #d8e5fa; border-top-color: var(--v2-blue); border-radius: 50%; animation: v2-spin .8s linear infinite; }
.v2-shell { height: 100vh; height: 100dvh; overflow: hidden; background: var(--v2-bg); }
@@ -58,6 +68,13 @@ button, a { -webkit-tap-highlight-color: transparent; }
.v2-help-panel > footer { display: flex; align-items: center; gap: 6px; margin-top: 14px; color: #8996a8; font-size: 10px; }
.v2-help-panel kbd { border: 1px solid #dce4ef; border-radius: 4px; background: #fff; padding: 2px 5px; color: #59677c; font-family: inherit; }
.v2-current-user { display: flex; align-items: center; gap: 6px; margin: 0 5px; color: #59677c; font-size: 11px; }.v2-current-user b { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.v2-current-user small, .v2-role-badge { border-radius: 10px; background: var(--v2-blue-soft); padding: 2px 6px; color: var(--v2-blue); font-size: 9px; }
.v2-topbar-actions .v2-current-user { display: flex; width: auto; padding: 0 7px; }
.v2-password-mobile { display: none !important; }
.v2-password-backdrop { position: fixed; z-index: 1400; inset: 0; display: grid; place-items: center; background: rgba(15,23,42,.28); padding: 16px; }
.v2-password-dialog { width: min(410px,100%); border: 1px solid #e0e7ef; border-radius: 14px; background: #fff; padding: 22px; box-shadow: 0 28px 80px rgba(15,23,42,.2); }
.v2-password-dialog header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }.v2-password-dialog h2 { margin: 0; font-size: 19px; }.v2-password-dialog header p { margin: 6px 0 0; color: #8491a3; font-size: 10px; }.v2-password-dialog header button { width: 30px; height: 30px; border: 0; border-radius: 7px; background: #f2f5f8; color: #66758a; cursor: pointer; }
.v2-password-dialog > label { display: flex; flex-direction: column; gap: 6px; margin-top: 14px; color: #5a6980; font-size: 11px; font-weight: 650; }.v2-password-dialog input { height: 40px; border: 1px solid #d8e1ec; border-radius: 8px; padding: 0 10px; outline: 0; font-size: 13px; }.v2-password-dialog input:focus { border-color: #84b0f7; box-shadow: 0 0 0 3px rgba(18,104,243,.07); }.v2-password-dialog > em { display: block; margin-top: 10px; color: var(--v2-red); font-size: 10px; font-style: normal; }
.v2-password-dialog footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }.v2-password-dialog footer button { height: 36px; border: 1px solid #d7e0eb; border-radius: 7px; background: #fff; padding: 0 14px; color: #5d6d83; cursor: pointer; font-size: 11px; font-weight: 650; }.v2-password-dialog footer button[type="submit"] { border-color: var(--v2-blue); background: var(--v2-blue); color: #fff; }.v2-password-dialog footer button:disabled { opacity: .5; }
.v2-role-notice { margin: 6px 0; border-radius: 6px; background: #f6f8fb; padding: 8px; color: var(--v2-muted); font-size: 8px; line-height: 1.5; }
.v2-content { min-width: 0; min-height: 0; flex: 1; overflow: auto; }
.v2-sidebar.is-collapsed { width: 68px; }
@@ -1359,6 +1376,78 @@ button, a { -webkit-tap-highlight-color: transparent; }
.v2-mileage-evidence { flex-direction: column; line-height: 1.5; }
}
/* Account administration follows the platform's open table/workspace layout. */
.v2-user-admin { min-height: 100%; padding: 22px 24px 34px; }
.v2-user-admin-heading { display: flex; align-items: center; justify-content: space-between; gap: 24px; margin: 0 auto 18px; max-width: 1480px; }
.v2-user-admin-heading h2 { margin: 0; font-size: 22px; letter-spacing: -.035em; }
.v2-user-admin-heading p { margin: 7px 0 0; color: var(--v2-muted); font-size: 12px; }
.v2-user-admin-heading > button, .v2-user-editor form > footer button, .v2-user-editor-empty button { height: 38px; border: 0; border-radius: 8px; background: var(--v2-blue); padding: 0 16px; color: #fff; cursor: pointer; font-size: 12px; font-weight: 700; }
.v2-user-admin-grid { display: grid; max-width: 1480px; min-height: 690px; margin: 0 auto; grid-template-columns: 300px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--v2-border); border-radius: 13px; background: #fff; box-shadow: var(--v2-shadow); }
.v2-user-list { min-width: 0; border-right: 1px solid var(--v2-border); background: #f9fbfd; padding: 12px; }
.v2-user-list-summary { display: flex; align-items: baseline; gap: 7px; padding: 8px 9px 14px; color: #7a8798; font-size: 11px; }
.v2-user-list-summary strong { color: #26364e; font-size: 20px; }
.v2-user-list > button { display: grid; width: 100%; grid-template-columns: 38px minmax(0,1fr) auto; align-items: center; gap: 10px; border: 0; border-radius: 9px; background: transparent; padding: 10px; color: #273750; text-align: left; cursor: pointer; }
.v2-user-list > button:hover { background: #f0f4f9; }
.v2-user-list > button.is-active { background: #eaf2ff; box-shadow: inset 3px 0 var(--v2-blue); }
.v2-user-avatar { display: grid; width: 38px; height: 38px; place-items: center; border-radius: 9px; background: #dfe9f8; color: #3a5e91; font-weight: 800; }
.v2-user-list button > span:nth-child(2) { min-width: 0; }
.v2-user-list button b, .v2-user-list button small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-user-list button b { font-size: 12px; }
.v2-user-list button small { margin-top: 4px; color: #8290a3; font-size: 9px; }
.v2-user-list button i { border-radius: 10px; background: #f0f2f5; padding: 3px 6px; color: #8a96a6; font-size: 8px; font-style: normal; }
.v2-user-list button i.is-enabled { background: #e9f8f2; color: #16805b; }
.v2-user-editor { min-width: 0; }
.v2-user-editor-empty { display: grid; height: 100%; place-content: center; justify-items: center; color: #4a5b72; text-align: center; }
.v2-user-editor-empty strong { font-size: 18px; }.v2-user-editor-empty p { margin: 8px 0 18px; color: #8a96a7; font-size: 12px; }
.v2-user-editor form { height: 100%; padding: 24px 28px 82px; }
.v2-user-editor-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; padding-bottom: 20px; border-bottom: 1px solid #edf1f5; }
.v2-user-editor-title h3 { margin: 0; font-size: 20px; }.v2-user-editor-title p { margin: 6px 0 0; color: #8591a2; font-size: 10px; }
.v2-user-status { display: flex; align-items: center; gap: 8px; color: #45556c; font-size: 11px; font-weight: 650; }
.v2-user-status input { width: 16px; height: 16px; accent-color: var(--v2-blue); }
.v2-user-editor section { padding: 20px 0; border-bottom: 1px solid #edf1f5; }
.v2-user-editor h4 { margin: 0 0 14px; color: #2c3d54; font-size: 13px; }
.v2-user-editor h4 small { margin-left: 7px; color: #91a0b2; font-size: 9px; font-weight: 500; }
.v2-user-fields { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 14px; }
.v2-user-fields label, .v2-vehicle-permission-tools label { display: flex; min-width: 0; flex-direction: column; gap: 6px; color: #637188; font-size: 10px; font-weight: 650; }
.v2-user-fields input, .v2-vehicle-permission-tools input { width: 100%; height: 38px; border: 1px solid #dbe3ed; border-radius: 7px; padding: 0 10px; outline: 0; color: #273750; font-size: 12px; }
.v2-user-fields input:focus, .v2-vehicle-permission-tools input:focus { border-color: #8ab4f7; box-shadow: 0 0 0 3px rgba(18,104,243,.07); }
.v2-user-fields input:disabled { background: #f5f7fa; color: #8390a0; }
.v2-menu-permissions { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 9px; }
.v2-menu-permissions label { display: flex; min-width: 0; align-items: flex-start; gap: 10px; border: 1px solid #e0e7ef; border-radius: 8px; padding: 11px; cursor: pointer; }
.v2-menu-permissions label.is-selected { border-color: #a9c8fb; background: #f3f7ff; }
.v2-menu-permissions input { margin-top: 2px; accent-color: var(--v2-blue); }.v2-menu-permissions span { min-width: 0; }.v2-menu-permissions b, .v2-menu-permissions small { display: block; }.v2-menu-permissions b { color: #33445c; font-size: 11px; }.v2-menu-permissions small { margin-top: 4px; color: #8491a3; font-size: 9px; line-height: 1.5; }
.v2-vehicle-permission-heading { display: flex; align-items: center; justify-content: space-between; }.v2-vehicle-permission-heading h4 { margin-bottom: 14px; }.v2-vehicle-permission-heading > button { border: 0; background: transparent; color: #7d899a; cursor: pointer; font-size: 10px; }
.v2-vehicle-permission-tools { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; }
.v2-bulk-vin { display: flex; gap: 6px; }.v2-bulk-vin button { flex: 0 0 auto; border: 1px solid #cbd8e9; border-radius: 7px; background: #f7f9fc; padding: 0 11px; color: #46607e; cursor: pointer; font-size: 10px; }
.v2-vehicle-candidates { display: grid; max-height: 194px; margin-top: 10px; grid-template-columns: repeat(2,minmax(0,1fr)); overflow: auto; border: 1px solid #e1e7ef; border-radius: 8px; padding: 5px; }
.v2-vehicle-candidates > p { grid-column: 1/-1; margin: 16px; color: #8996a7; font-size: 10px; }
.v2-vehicle-candidates > button { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 8px; border: 0; border-radius: 6px; background: transparent; padding: 8px; text-align: left; cursor: pointer; }.v2-vehicle-candidates > button:hover { background: #f4f7fa; }.v2-vehicle-candidates > button.is-selected { background: #edf4ff; }
.v2-vehicle-candidates b, .v2-vehicle-candidates small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.v2-vehicle-candidates b { color: #34455c; font-size: 10px; }.v2-vehicle-candidates small { margin-top: 3px; color: #8a96a6; font-size: 8px; }.v2-vehicle-candidates i { color: var(--v2-blue); font-size: 9px; font-style: normal; }
.v2-assigned-vins { display: flex; max-height: 118px; flex-wrap: wrap; gap: 6px; overflow: auto; margin-top: 10px; }.v2-assigned-vins button { height: 26px; border: 1px solid #dbe4ef; border-radius: 6px; background: #f7f9fc; padding: 0 7px; color: #52637a; cursor: pointer; font: 9px ui-monospace, SFMono-Regular, Menlo, monospace; }.v2-assigned-vins button span { margin-left: 6px; color: #99a4b3; }
.v2-user-empty { margin: 14px 4px; color: #8b97a7; font-size: 10px; line-height: 1.6; }
.v2-user-feedback { margin: 14px 0 0; color: #16805b; font-size: 11px; }.v2-user-feedback.is-error { color: var(--v2-red); }
.v2-user-editor form > footer { display: flex; justify-content: flex-end; margin-top: 18px; }.v2-user-editor form > footer button:disabled { opacity: .5; }
@media (max-width: 900px) {
.v2-auth-screen { grid-template-columns: 1fr; }
.v2-auth-intro { display: none; }
.v2-auth-card { justify-self: center; }
.v2-user-admin { padding: 12px; }
.v2-user-admin-grid { grid-template-columns: 230px minmax(560px,1fr); overflow-x: auto; }
}
@media (max-width: 680px) {
.v2-auth-screen { min-height: 100dvh; padding: 16px; }
.v2-auth-card { padding: 28px 22px; }
.v2-user-admin { padding: 8px 8px 82px; }
.v2-user-admin-heading { align-items: flex-start; margin-bottom: 10px; }.v2-user-admin-heading h2 { font-size: 17px; }.v2-user-admin-heading p { display: none; }.v2-user-admin-heading > button { height: 34px; flex: 0 0 auto; padding: 0 10px; font-size: 10px; }
.v2-user-admin-grid { display: flex; min-height: 0; overflow: visible; border: 0; box-shadow: none; flex-direction: column; gap: 8px; background: transparent; }
.v2-user-list { display: flex; border: 1px solid var(--v2-border); border-radius: 10px; overflow-x: auto; padding: 6px; background: #fff; }.v2-user-list-summary { flex: 0 0 auto; padding: 8px; }.v2-user-list > button { min-width: 210px; }
.v2-user-editor { border: 1px solid var(--v2-border); border-radius: 10px; overflow: hidden; background: #fff; }.v2-user-editor form { padding: 16px 14px 22px; }.v2-user-fields, .v2-menu-permissions, .v2-vehicle-permission-tools, .v2-vehicle-candidates { grid-template-columns: 1fr; }
.v2-password-mobile { display: grid !important; }
.v2-topbar-actions .v2-current-user { display: none; }
}
@media (max-width: 1280px) {
.v2-access-filter-v3 { grid-template-columns: minmax(210px, 1.3fr) repeat(3, minmax(120px, .75fr)) auto auto; }
.v2-access-filter-v3 > button { min-width: 76px; }