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

style: compact member credit entry controls

彭峰 13 часов назад
Родитель
Сommit
d8cbf0c2d1

+ 7 - 7
projects/xiaoshu-admin/src/app/components/member-credit-account.component.html

@@ -6,15 +6,15 @@
 @if(!store){<p>体验资格:{{value.trialStatus==='issued'?'已发放过,不可再次领取':value.trialStatus==='eligible'?'可发放 1 次':'待核验旧系统体验记录'}} · 退回体验额度不恢复领取资格</p>@if(value.canPost&&value.trialStatus==='pending'){<button type="button" (click)="importHistory()" [disabled]="saving()">核验旧体验并补齐历史流水</button>}}
 <div class="accounts">@for(a of value.accounts;track a.account){<article><strong>{{a.label}}</strong><b>{{a.available | number:'1.0-2'}}</b><span>可用 · 余额 {{a.balance}} · 已预留 {{a.reserved}}</span>@if(a.balance<0){<small class="error">历史负余额,需核对</small>}@if(!store){<div class="account-actions">@if(value.canPost){<button type="button" (click)="chooseAccount(a.account)">充值</button>}<button type="button" (click)="chooseAccount(a.account,true)">明细</button></div>}</article>}</div>
 @if(!value.canPost){<p>当前账号没有充值或扣回权限,可查看账户和流水。</p>}
-@if(value.canPost){<form (ngSubmit)="store?submit():check()" (input)="resetPreview()" (change)="resetPreview()"><fieldset [disabled]="saving()||loading()">
-@if(!store){<label>业务类型<select name="direction" [(ngModel)]="direction" (ngModelChange)="sourceId='';resetPreview()"><option value="add">门店划拨充值</option><option value="deduct">原单扣回并退回门店</option><option value="adjust">历史差错调整</option></select></label>}
-@if(direction==='deduct'&&!store){<label class="wide">原充值单<select name="source" [(ngModel)]="sourceId" (ngModelChange)="selectSource($event)"><option value="">请选择原充值记录</option>@for(t of refundable();track t.objectId){<option [value]="t.objectId">{{t.createdAt|date:'yyyy-MM-dd HH:mm':'+0800'}} · {{label(t.account)}} · 可退 {{t.unreturned}}</option>}</select></label>}
+@if(value.canPost){<form (ngSubmit)="store?submit():check()" (input)="resetPreview()" (change)="resetPreview()"><fieldset [class.credit-entry-fields]="!store" [disabled]="saving()||loading()">
+@if(!store){<label class="credit-direction">业务类型<select name="direction" [(ngModel)]="direction" (ngModelChange)="sourceId='';resetPreview()"><option value="add">门店划拨充值</option><option value="deduct">原单扣回并退回门店</option><option value="adjust">历史差错调整</option></select></label>}
+@if(direction==='deduct'&&!store){<label class="wide credit-source">原充值单<select name="source" [(ngModel)]="sourceId" (ngModelChange)="selectSource($event)"><option value="">请选择原充值记录</option>@for(t of refundable();track t.objectId){<option [value]="t.objectId">{{t.createdAt|date:'yyyy-MM-dd HH:mm':'+0800'}} · {{label(t.account)}} · 可退 {{t.unreturned}}</option>}</select></label>}
 <label>课时类型<select name="account" [(ngModel)]="account" (ngModelChange)="resetPreview()" [disabled]="direction==='deduct'&&!store">@for(a of value.accounts;track a.account){<option [value]="a.account">{{a.label}}</option>}</select></label>
-<label>数量<input name="amount" type="number" [min]="account==='UserPoint'?0.01:1" [step]="account==='UserPoint'?0.01:1" [(ngModel)]="amount" required /></label>
+<label class="credit-quantity">数量<input name="amount" type="number" [min]="account==='UserPoint'?0.01:1" [step]="account==='UserPoint'?0.01:1" [(ngModel)]="amount" required /></label>
 <label>付款或记账方式<select name="method" [(ngModel)]="method" required><option value="">请选择</option><option>微信</option><option>支付宝</option><option>转账</option><option>线下收款</option><option>运营记账</option></select></label>
 @if(!store&&direction==='add'){<p class="wide">{{account==='UserPoint'?'单独补充陪练时长,用于独立复习服务或历史额度补充。':account==='UserExp'?'体验固定发放 1 次,结课不扣普通陪练余额。':'普通课充值自动配套陪练:30 分钟每次 0.5 小时,60 分钟每次 1 小时。'}}</p>}
-@if(direction==='adjust'){<label>增减方向<select name="adjustSign" [(ngModel)]="adjustSign"><option value="deduct">扣回差错额度</option><option value="add">补充差错额度</option></select></label><label>调整范围<select name="adjustMode" [(ngModel)]="adjustMode"><option value="paired">普通课时配套调整陪练</option><option value="single">仅纠正所选账户</option></select></label><label class="wide">核对依据<input name="evidence" [(ngModel)]="evidence" placeholder="填写原始单据、核对记录等证据" required /></label><p class="wide">独立调整只影响学员,不增加或扣减门店库存。</p>}
-<label class="wide">操作原因<input name="reason" [(ngModel)]="reason" minlength="2" maxlength="500" placeholder="填写本次充值、扣回或调整原因" required /></label>
+@if(direction==='adjust'){<label>增减方向<select name="adjustSign" [(ngModel)]="adjustSign"><option value="deduct">扣回差错额度</option><option value="add">补充差错额度</option></select></label><label class="credit-adjust-scope">调整范围<select name="adjustMode" [(ngModel)]="adjustMode"><option value="paired">普通课时配套调整陪练</option><option value="single">仅纠正所选账户</option></select></label><label class="wide credit-evidence">核对依据<input name="evidence" [(ngModel)]="evidence" placeholder="填写原始单据、核对记录等证据" required /></label><p class="wide">独立调整只影响学员,不增加或扣减门店库存。</p>}
+<label class="wide credit-reason">操作原因<input name="reason" [(ngModel)]="reason" minlength="2" maxlength="500" placeholder="填写本次充值、扣回或调整原因" required /></label>
 <button class="primary" type="submit" [disabled]="saving()||(!store&&!value.storeId&&direction==='add')">{{saving()?'处理中…':store?'确认总部入账':'预览账户变动'}}</button>
 @if(!store&&value.storeId){<a routerLink="/admin/stores">查看门店库存 / 总部入账</a>}
 </fieldset></form>}
@@ -22,7 +22,7 @@
 @if(store){<div class="scroll"><table><thead><tr><th>时间</th><th>业务</th><th>账户</th><th>数量</th><th>学员</th><th>原因</th></tr></thead><tbody>@for(t of value.transactions;track t.objectId){<tr><td>{{t.createdAt|date:'yyyy-MM-dd HH:mm':'+0800'}}</td><td>{{kindLabel(t.kind)}}</td><td>{{label(t.account)}}</td><td>{{t.amount}}</td><td>{{t.studentId||'—'}}</td><td>{{t.reason}}</td></tr>}</tbody></table></div>}
 }
 @if(!store){<section class="ledger-panel"><header class="section-header"><h3>全部课时流水</h3><button type="button" (click)="export()" [disabled]="exporting()">{{exporting()?'正在导出…':'导出 Excel'}}</button></header><p>时间均为北京时间;成套充值按一笔业务展示,可展开查看双方账户明细。</p>
-<div class="form-grid"><label>账户<select [(ngModel)]="filterAccount"><option value="">全部账户</option>@for(a of data()?.accounts||[];track a.account){<option [value]="a.account">{{a.label}}</option>}</select></label><label>业务类型<select [(ngModel)]="filterKind"><option value="">全部业务</option>@for(k of ['transfer','refund','adjust','lesson','recharge','deduction'];track k){<option [value]="k">{{kindLabel(k)}}</option>}</select></label><app-operations-date-filter label="开始日期" [value]="dateFrom" (valueChange)="dateFrom=$event"/><app-operations-date-filter label="结束日期" [value]="dateTo" (valueChange)="dateTo=$event"/><div class="account-actions"><button type="button" (click)="query()">查询</button><button type="button" (click)="query(true)">重置</button></div></div>
+<div class="form-grid"><label>账户<select [(ngModel)]="filterAccount"><option value="">全部账户</option>@for(a of data()?.accounts||[];track a.account){<option [value]="a.account">{{a.label}}</option>}</select></label><label class="credit-direction">业务类型<select [(ngModel)]="filterKind"><option value="">全部业务</option>@for(k of ['transfer','refund','adjust','lesson','recharge','deduction'];track k){<option [value]="k">{{kindLabel(k)}}</option>}</select></label><app-operations-date-filter label="开始日期" [value]="dateFrom" (valueChange)="dateFrom=$event"/><app-operations-date-filter label="结束日期" [value]="dateTo" (valueChange)="dateTo=$event"/><div class="account-actions"><button type="button" (click)="query()">查询</button><button type="button" (click)="query(true)">重置</button></div></div>
 @if(ledgerError()){<p role="alert" class="error">{{ledgerError()}}</p>}@if(ledgerBusy()){<p role="status">正在加载流水…</p>}
 @for(row of ledger()?.items||[];track row.groupId){<details class="ledger-entry"><summary><time>{{row.at|date:'yyyy-MM-dd HH:mm':'+0800'}}</time><strong>{{kindLabel(row.lines[0].kind)}}</strong><span>@for(l of row.lines;track l.account){ {{label(l.account)}} {{l.delta>0?'+':''}}{{l.delta}} }</span><small>{{row.lines[0].reason||'未记录'}}</small></summary><div class="scroll"><table><thead><tr><th>账户</th><th>增减</th><th>调整前</th><th>调整后</th><th>操作人</th><th>来源与单据</th><th>门店</th></tr></thead><tbody>@for(l of row.lines;track l.account){<tr><td>{{label(l.account)}}</td><td>{{l.delta}}</td><td>{{l.before??'未记录'}}</td><td>{{l.after??'未记录'}}</td><td>{{l.operator||'未记录'}}</td><td>{{l.source==='legacy'?'旧系统':'新系统'}}<small>{{l.reference}}</small>@if(l.lessonId){<button type="button" (click)="lessonId=l.lessonId">查看上课记录</button>}</td><td>{{l.storeId||'未记录'}}@if(l.balances){<small>余额 {{l.balances.storeBefore??'—'}} → {{l.balances.storeAfter??'—'}}</small>}</td></tr>}</tbody></table></div><p>原因:{{row.lines[0].reason||'未记录'}}</p></details>}@empty{ @if(!ledgerBusy()&&!ledgerError()){<p>暂无符合条件的流水</p>} }
 <div class="section-header"><span>共 {{ledger()?.total||0}} 笔业务</span><label>每页<select [(ngModel)]="pageSize" (ngModelChange)="query()"><option [ngValue]="20">20 条</option><option [ngValue]="50">50 条</option><option [ngValue]="100">100 条</option></select></label><div class="account-actions"><button type="button" (click)="turn(-1)" [disabled]="page<=1||ledgerBusy()">上一页</button><span>{{page}}</span><button type="button" (click)="turn(1)" [disabled]="page*pageSize>=(ledger()?.total||0)||ledgerBusy()">下一页</button></div></div></section>}

+ 21 - 0
projects/xiaoshu-admin/src/app/components/member-enrollment.scss

@@ -35,3 +35,24 @@ input[type=checkbox]:disabled { cursor:not-allowed; }
 .account-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.account-actions button{padding:7px 12px}.preview-panel{background:#edf7f2;border:1px solid #bddbcf;border-radius:12px;padding:18px;margin-bottom:24px}.ledger-panel{border-top:1px solid #dfe7e4;margin-top:24px;padding-top:24px}.ledger-entry{border:1px solid #dfe7e4;border-radius:10px;margin:12px 0;padding:14px}.ledger-entry summary{cursor:pointer;display:list-item;color:#294c3f}.ledger-entry summary>*{margin-right:16px}.ledger-entry small{overflow-wrap:anywhere}.ledger-entry .scroll{max-height:none}.ledger-entry time{font-variant-numeric:tabular-nums}.ledger-entry p{margin-bottom:0}
 
 .credit-panel a{color:#1b735d;font-weight:600;text-underline-offset:3px}.credit-panel form,.ledger-panel{scroll-margin-top:90px}
+
+// Keep member credit controls sized to their content instead of stretching across the panel.
+.credit-entry-fields {
+  display:flex; flex-wrap:wrap; gap:12px; align-items:flex-end; padding:14px 0;
+  > label { flex:0 1 180px; gap:5px; }
+  > .credit-direction { flex-basis:220px; }
+  > .credit-quantity { flex-basis:100px; }
+  > .credit-adjust-scope { flex-basis:240px; }
+  > .credit-source { flex-basis:360px; }
+  > .credit-reason, > .credit-evidence { flex-basis:380px; }
+  > p { flex:0 0 100%; margin:0; font-size:13px; }
+  > button { flex:0 0 auto; }
+  > a { display:inline-flex; align-items:center; min-height:40px; font-size:13px; }
+}
+@media(max-width:650px) {
+  .credit-entry-fields {
+    gap:10px;
+    > label { flex:1 1 calc(50% - 5px); }
+    > .credit-source, > .credit-reason, > .credit-evidence { flex-basis:100%; }
+  }
+}