| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- <header class="page-header detail-header">
- <div>
- <a class="back-link" routerLink="/admin/stores"><lucide-icon [img]="icons.ArrowLeft" [size]="16"/>返回门店管理</a>
- <p>STORE OPERATING PROFILE</p>
- <h1>{{data()?.store?.storeName||'门店经营详情'}}</h1>
- <span>集中查看门店归属会员、陪练、排课、课程履约和陪练工资成本</span>
- </div>
- <div class="header-actions">
- <label class="month-control"><span>经营月份</span><input type="month" [ngModel]="month()" (ngModelChange)="changeMonth($event)" [disabled]="loading()"/></label>
- <button class="ui-button secondary" type="button" (click)="load(true)" [disabled]="loading()"><lucide-icon [img]="icons.RefreshCw" [size]="17" [class.spin]="loading()"/>{{loading()?'读取中…':'刷新实时数据'}}</button>
- </div>
- </header>
- @if(error()){<p class="notice error" role="alert">{{error()}}</p>}
- @if(loading()&&!data()){
- <section class="loading-card" aria-live="polite"><lucide-icon [img]="icons.RefreshCw" [size]="22" class="spin"/><strong>正在归集门店经营数据</strong><span>会员、排课和工资来自不同业务投影,首次读取可能需要数秒</span></section>
- }
- @if(data(); as detail){
- <section class="source-strip" role="status">
- <div><strong>{{detail.store.storeName}}</strong><span>ID {{detail.store.userId}} · {{detail.store.parentStoreName||'总店/未归属'}} · {{detail.store.statusLabel}}</span></div>
- <div class="source-meta"><span>{{detail.payrollSourceStatus.mode==='live'?'工资使用旧系统实时上课记录':'工资使用最近缓存'}}</span><small>更新于 {{detail.refreshedAt|date:'MM-dd HH:mm:ss'}}</small></div>
- </section>
- <section class="store-profile">
- <div class="store-mark"><lucide-icon [img]="icons.Building2" [size]="30"/></div>
- <div><span>门店账号</span><strong>{{detail.store.storeName}}</strong><small>{{detail.store.username}} · 联系人 {{detail.store.contactName||'未设置'}}</small></div>
- <dl><div><dt>联系电话</dt><dd>{{detail.store.mobile||detail.store.mobileMasked||'未登记'}}</dd></div><div><dt>最近登录</dt><dd>{{detail.store.lastLoginAt?(detail.store.lastLoginAt|date:'yyyy-MM-dd HH:mm'):'从未登录'}}</dd></div><div><dt>历史排课</dt><dd>{{detail.store.totalAppointments}} 节</dd></div></dl>
- </section>
- <section class="summary-grid" aria-label="门店经营摘要">
- <article><span>归属会员</span><strong>{{detail.summary.memberCount}}</strong><small>当前门店关系</small></article>
- <article><span>归属陪练</span><strong>{{detail.summary.coachCount}}</strong><small>可参与排课</small></article>
- <article><span>{{detail.month}} 排课</span><strong>{{detail.summary.monthAppointments}}</strong><small>含不同课型</small></article>
- <article><span>已完成排课</span><strong>{{detail.summary.completedAppointments}}</strong><small>状态 30/40</small></article>
- <article><span>有课学员</span><strong>{{detail.summary.activeStudents}}</strong><small>本月去重人数</small></article>
- <article><span>有效上课记录</span><strong>{{detail.summary.lessonCount}}</strong><small>工资计薪来源</small></article>
- <article><span>陪练工时</span><strong>{{detail.summary.totalHours|number:'1.1-1'}}h</strong><small>按有效上课记录</small></article>
- <article class="pay-card"><span>预估陪练工资</span><strong>¥{{detail.summary.estimatedCoachPay|number:'1.2-2'}}</strong><small>与工资结算一致</small></article>
- </section>
- @if(!detail.commissionStatus.available){<p class="commission-note"><lucide-icon [img]="icons.WalletCards" [size]="18"/><span><strong>门店佣金暂不估算</strong>{{detail.commissionStatus.message}}</span></p>}
- <section class="content-card detail-card">
- <nav class="detail-tabs" aria-label="门店经营详情">
- <button type="button" [class.active]="tab()==='members'" (click)="tab.set('members')"><lucide-icon [img]="icons.Users" [size]="17"/>会员 <em>{{detail.members.length}}</em></button>
- <button type="button" [class.active]="tab()==='coaches'" (click)="tab.set('coaches')"><lucide-icon [img]="icons.UserRound" [size]="17"/>陪练 <em>{{detail.coaches.length}}</em></button>
- <button type="button" [class.active]="tab()==='schedule'" (click)="tab.set('schedule')"><lucide-icon [img]="icons.CalendarDays" [size]="17"/>{{detail.month}} 排课 <em>{{detail.appointments.length}}</em></button>
- <button type="button" [class.active]="tab()==='courses'" (click)="tab.set('courses')"><lucide-icon [img]="icons.GraduationCap" [size]="17"/>课程履约 <em>{{detail.courses.length}}</em></button>
- </nav>
- @if(tab()==='members'){
- <div class="section-heading"><div><strong>归属会员</strong><span>按会员所属门店关系归集,陪练和管理员不会混入</span></div><label class="inline-search"><lucide-icon [img]="icons.Search" [size]="16"/><input [ngModel]="memberSearch()" (ngModelChange)="memberSearch.set($event)" placeholder="搜索姓名、昵称、手机号或ID"/></label></div>
- <div class="table-scroll"><table class="data-table"><thead><tr><th>会员</th><th>学习状态</th><th>剩余课时</th><th>最近上课</th><th>下次预约</th><th>账号状态</th><th>操作</th></tr></thead><tbody>
- @for(row of members();track row.objectId){<tr><td><strong>{{row.displayName}}</strong><small>{{row.mobile||row.mobileMasked||row.username}} · ID {{row.userId}}</small></td><td><span class="badge" [class.warn]="row.learningStatus==='trial'||row.learningStatus==='not_started'">{{row.learningStatusLabel}}</span><small>{{row.learningStatusReason}}</small></td><td>30分 {{row.periods30}} · 60分 {{row.periods60}}<small>体验 {{row.trialPeriods}}</small></td><td>{{row.lastLessonAt?(row.lastLessonAt|date:'yyyy-MM-dd'):'暂无'}}</td><td>{{row.nextAppointmentAt?(row.nextAppointmentAt|date:'yyyy-MM-dd HH:mm'):'待排课'}}</td><td><span class="badge" [class.muted]="row.status==='disabled'">{{row.statusLabel}}</span></td><td><a class="detail-link" [routerLink]="['/admin/members',row.objectId]">查看会员</a></td></tr>}@empty{<tr><td colspan="7"><div class="empty">没有符合条件的会员</div></td></tr>}
- </tbody></table></div>
- } @else if(tab()==='coaches'){
- <div class="section-heading"><div><strong>归属陪练</strong><span>本月课次、工时和工资均使用工资结算相同口径</span></div><label class="inline-search"><lucide-icon [img]="icons.Search" [size]="16"/><input [ngModel]="coachSearch()" (ngModelChange)="coachSearch.set($event)" placeholder="搜索陪练姓名、账号、手机号或ID"/></label></div>
- <div class="table-scroll"><table class="data-table"><thead><tr><th>陪练老师</th><th>今日排课</th><th>待完成</th><th>本月有效课次</th><th>本月工时</th><th>预估工资</th><th>状态</th><th>操作</th></tr></thead><tbody>
- @for(row of coaches();track row.objectId){<tr><td><strong>{{row.displayName}}</strong><small>{{row.mobile||row.mobileMasked||row.username||'历史账号未同步'}} · ID {{row.userId}}</small></td><td>{{row.todayAppointments}} 节</td><td>{{row.pendingAppointments}} 节</td><td>{{row.monthLessons}} 节</td><td>{{row.monthHours|number:'1.1-1'}}h</td><td class="money">¥{{row.estimatedPay|number:'1.2-2'}}</td><td><span class="badge" [class.muted]="row.status==='disabled'">{{row.statusLabel}}</span></td><td><a class="detail-link" [routerLink]="['/admin/coaches',row.objectId]">查看陪练</a></td></tr>}@empty{<tr><td colspan="8"><div class="empty">没有符合条件的陪练</div></td></tr>}
- </tbody></table></div>
- } @else if(tab()==='schedule'){
- <div class="section-heading"><div><strong>{{detail.month}} 排课记录</strong><span>优先按学员所属门店归集;学员关系缺失时才使用陪练所属门店</span></div><label class="inline-search"><lucide-icon [img]="icons.Search" [size]="16"/><input [ngModel]="scheduleSearch()" (ngModelChange)="scheduleSearch.set($event)" placeholder="搜索学员、陪练、课程或状态"/></label></div>
- <div class="table-scroll"><table class="data-table schedule-table"><thead><tr><th>上课时间</th><th>学员</th><th>陪练</th><th>课程</th><th>课型</th><th>状态</th><th>来源</th></tr></thead><tbody>
- @for(row of appointments();track row.targetId){<tr><td><strong>{{row.startsAt|date:'yyyy-MM-dd HH:mm'}}</strong><small><lucide-icon [img]="icons.Clock3" [size]="12"/>{{row.endsAt|date:'HH:mm'}} 结束</small></td><td>{{row.studentName||'未识别学员'}}</td><td>{{row.coachName||'待分配'}}</td><td>{{row.courseName||'未命名课程'}}</td><td>{{row.classTypeLabel}}</td><td><span class="status-pill" [attr.data-tone]="statusTone(row)">{{row.statusLabel}}</span></td><td>{{row.sourceLabel}}</td></tr>}@empty{<tr><td colspan="7"><div class="empty">{{detail.month}} 暂无排课记录</div></td></tr>}
- </tbody></table></div>
- } @else {
- <div class="section-heading"><div><strong>{{detail.month}} 课程履约</strong><span>按课程归集排课、完课和参与学员,方便识别履约不足的课程</span></div></div>
- <div class="table-scroll"><table class="data-table"><thead><tr><th>课程</th><th>参与学员</th><th>排课数</th><th>已完成</th><th>完成率</th><th>最近排课</th></tr></thead><tbody>
- @for(row of detail.courses;track row.courseId){<tr><td><strong>{{row.courseName}}</strong><small>ID {{row.courseId||'未识别'}}</small></td><td>{{row.studentCount}} 人</td><td>{{row.appointmentCount}} 节</td><td>{{row.completedCount}} 节</td><td><div class="progress"><span [style.width.%]="row.completionRate"></span></div><small>{{row.completionRate|number:'1.0-0'}}%</small></td><td>{{row.lastAppointmentAt?(row.lastAppointmentAt|date:'yyyy-MM-dd HH:mm'):'暂无'}}</td></tr>}@empty{<tr><td colspan="6"><div class="empty">{{detail.month}} 暂无课程履约数据</div></td></tr>}
- </tbody></table></div>
- }
- </section>
- }
|