Просмотр исходного кода

refactor(admin): consolidate member identity and quick actions

彭峰 1 месяц назад
Родитель
Сommit
458bbe7257

+ 10 - 3
projects/xiaoshu-admin/src/app/pages/operations-user-detail.component.html

@@ -1,10 +1,17 @@
-<header class="page-header"><div><a routerLink="/admin/members"><lucide-icon [img]="icons.ArrowLeft" [size]="17" />返回会员管理</a><p>MEMBER PROFILE</p><h1>{{ user()?.displayName || '会员详情' }}</h1><span>围绕会员资料、课程、排课、上课和课时记录完成运营</span></div><button type="button" (click)="refreshBusiness()" [disabled]="loading()"><lucide-icon [img]="icons.RefreshCw" [size]="17" />刷新</button></header>
+<header class="page-header"><a routerLink="/admin/members"><lucide-icon [img]="icons.ArrowLeft" [size]="17" />返回会员管理</a><button type="button" (click)="refreshBusiness()" [disabled]="loading()"><lucide-icon [img]="icons.RefreshCw" [size]="17" />刷新</button></header>
 
 @if (loading() && !user()) { <div class="state"><lucide-icon class="spin" [img]="icons.LoaderCircle" [size]="28" />正在加载会员资料…</div> }
 @else if (error() && !user()) { <div class="state error" role="alert">{{ error() }}</div> }
 @else { @if (user(); as detail) {
-<nav class="quick-actions" aria-label="会员常用操作"><button type="button" (click)="selectTab('balances')"><lucide-icon [img]="icons.WalletCards" [size]="17" />{{ detail.permissions.canManage ? '充值课时' : '查看课时' }}</button><button type="button" (click)="selectTab('courses')"><lucide-icon [img]="icons.BookOpen" [size]="17" />{{ detail.permissions.canEdit ? '按分类开课' : '查看课程' }}</button><a class="action-link primary" routerLink="/admin/schedule" [queryParams]="{studentId:detail.userId}"><lucide-icon [img]="icons.CalendarDays" [size]="17" />为该学员排课</a></nav>
-  <section class="profile-summary"><div class="avatar"><lucide-icon [img]="icons.UserRound" [size]="28" /></div><div><h2>{{ detail.displayName }}</h2><p>会员 ID {{ detail.userId || '—' }} · {{ detail.username }}</p></div><div class="profile-facts"><span>{{ detail.learningStatusLabel }}</span><span>{{ detail.parentDisplayName || '未绑定代理' }}</span><span>{{ detail.registeredAt | date:'yyyy-MM-dd' }} 注册</span></div><span class="status" [class.disabled]="detail.status === 'disabled'">{{ detail.statusLabel }}</span></section>
+  <section class="profile-summary" aria-label="会员资料与操作">
+    <div class="avatar"><lucide-icon [img]="icons.UserRound" [size]="28" /></div>
+    <div class="profile-identity">
+      <div class="profile-title"><h1>{{ detail.displayName }}</h1><span class="status" [class.disabled]="detail.status === 'disabled'">{{ detail.statusLabel }}</span></div>
+      <p>会员 ID {{ detail.userId || '—' }}@if (detail.username && detail.username !== detail.displayName) { · 账号 {{ detail.username }} }</p>
+      <div class="profile-facts"><span>{{ detail.learningStatusLabel }}</span><span>{{ detail.parentDisplayName || '未绑定门店' }}</span><span>{{ detail.registeredAt | date:'yyyy-MM-dd' }} 注册</span></div>
+    </div>
+    <nav class="quick-actions" aria-label="会员常用操作"><button type="button" (click)="selectTab('balances')"><lucide-icon [img]="icons.WalletCards" [size]="17" />{{ detail.permissions.canManage ? '充值课时' : '查看课时' }}</button><button type="button" (click)="selectTab('courses')"><lucide-icon [img]="icons.BookOpen" [size]="17" />{{ detail.permissions.canEdit ? '按分类开课' : '查看课程' }}</button><a class="action-link primary" routerLink="/admin/schedule" [queryParams]="{studentId:detail.userId}"><lucide-icon [img]="icons.CalendarDays" [size]="17" />为该学员排课</a></nav>
+  </section>
   @if (detail.writeReady === false) { <p class="notice" role="status">{{ detail.readOnlyReason }}</p> }
   @if (message()) { <p class="notice" role="status">{{ message() }}</p> } @if (error()) { <p class="notice error" role="alert">{{ error() }}</p> }
 

+ 13 - 14
projects/xiaoshu-admin/src/app/pages/operations-user-detail.component.scss

@@ -1,22 +1,21 @@
 @use '../components/member-controls' as member;
 :host { display:block; color:#263733; font-size:14px; line-height:1.5; }
 @include member.controls;
-.page-header { display:flex; align-items:end; justify-content:space-between; gap:18px; margin-bottom:20px; }
+.page-header { display:flex; align-items:center; justify-content:space-between; gap:18px; margin-bottom:16px; }
 .page-header > button { flex-shrink:0; white-space:nowrap; }
-.page-header a { display:inline-flex; align-items:center; gap:6px; margin-bottom:13px; color:#526e63; font-size:13px; text-decoration:none; }
+.page-header a { display:inline-flex; align-items:center; gap:6px; color:#526e63; font-size:13px; text-decoration:none; }
 .page-header a:hover { text-decoration:underline; }
-.page-header p { margin:0 0 6px; color:#258b68; font-size:12px; font-weight:700; letter-spacing:.15em; }
-.page-header h1 { margin:0; font-size:28px; line-height:1.3; }
-.page-header span { display:block; margin-top:8px; color:#697b74; font-size:14px; }
-.quick-actions { display:flex; flex-wrap:wrap; gap:10px; margin-bottom:18px; }
-.profile-summary { display:flex; align-items:center; gap:16px; padding:20px; border-radius:14px; color:#fff; background:linear-gradient(120deg,#163b58,#176953); }
+.profile-summary { display:flex; align-items:center; flex-wrap:wrap; gap:18px; padding:24px; border-radius:14px; color:#fff; background:linear-gradient(120deg,#163b58,#176953); }
 .avatar { display:grid; flex:none; place-items:center; width:50px; height:50px; border-radius:12px; background:#ffffff24; }
-.profile-summary h2 { margin:0 0 5px; font-size:22px; line-height:1.4; }
-.profile-summary p { margin:0; color:#d3e5de; font-size:13px; }
-.profile-facts { display:flex; flex-wrap:wrap; gap:8px; margin-left:16px; }
-.profile-facts span { padding:5px 10px; border:1px solid #ffffff40; border-radius:999px; color:#e0eee8; font-size:12px; }
+.profile-identity { flex:1 1 320px; min-width:0; }
+.profile-title { display:flex; align-items:center; flex-wrap:wrap; gap:12px; margin-bottom:5px; }
+.profile-title h1 { margin:0; font-size:26px; line-height:1.4; overflow-wrap:anywhere; }
+.profile-summary p { margin:0; color:#d3e5de; font-size:13px; overflow-wrap:anywhere; }
+.profile-facts { display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; }
+.profile-facts span { padding:4px 9px; border:1px solid #ffffff40; border-radius:999px; color:#e0eee8; font-size:12px; }
+.quick-actions { display:flex; flex-wrap:wrap; gap:10px; margin-left:auto; }
+.quick-actions > * { white-space:nowrap; }
 .status,.learning-status { display:inline-flex; padding:5px 10px; border-radius:999px; color:#17654e; background:#e6f5ee; font-size:12px; font-weight:600; white-space:nowrap; }
-.profile-summary>.status { margin-left:auto; }
 .status.disabled { color:#923b44; background:#ffe7e9; }
 .notice { margin:14px 0; padding:12px 16px; border-radius:9px; color:#216b51; background:#e9f7f1; }
 .notice.error,.state.error { color:#a53e47; background:#fff0f1; }
@@ -84,5 +83,5 @@
 .date-filters { display:grid; grid-template-columns:minmax(150px,220px) minmax(150px,220px) auto auto; justify-content:start; align-items:end; gap:12px; }
 .spin { animation:spin .8s linear infinite; }
 @keyframes spin { to { transform:rotate(360deg); } }
-@media(max-width:1100px) { .detail-grid,.audit-grid{grid-template-columns:1fr}.profile-editor{grid-row:auto}.risk-panel{grid-column:auto}.profile-facts{margin-left:0} }
-@media(max-width:650px) { .page-header{align-items:start}.page-header h1{font-size:24px}.page-header span{font-size:13px}.profile-summary{flex-wrap:wrap;padding:16px}.profile-facts{width:100%;order:4}.tabs button{flex:0 0 auto}.form-grid,.account-facts{grid-template-columns:1fr}.metric-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.panel{padding:16px}.panel>header{flex-wrap:wrap}.date-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-actions>*{flex:1 1 auto}.pagination{gap:8px} }
+@media(max-width:1100px) { .detail-grid,.audit-grid{grid-template-columns:1fr}.profile-editor{grid-row:auto}.risk-panel{grid-column:auto} }
+@media(max-width:650px) { .profile-summary{padding:18px;gap:14px}.avatar{display:none}.profile-identity{flex-basis:100%}.profile-title h1{font-size:24px}.quick-actions{width:100%;margin-left:0}.tabs button{flex:0 0 auto}.form-grid,.account-facts{grid-template-columns:1fr}.metric-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.panel{padding:16px}.panel>header{flex-wrap:wrap}.date-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-actions>*{flex:1 1 auto}.pagination{gap:8px} }