555 lines
33 KiB
PHP
555 lines
33 KiB
PHP
@php
|
||
$withdrawalImportPreview = session('withdrawalImportPreview');
|
||
$withdrawalOpenImport = is_array($withdrawalImportPreview) || $errors->hasAny(['import', 'preview_token']);
|
||
$withdrawalFilterQuery = array_filter(
|
||
request()->only(['platform', 'withdrawal_path', 'store_entity', 'keyword', 'balance_status', 'balance_date']),
|
||
static fn ($value): bool => filled($value),
|
||
);
|
||
$withdrawalQuery = array_filter(
|
||
request()->only(['platform', 'withdrawal_path', 'store_entity', 'keyword', 'balance_status', 'balance_date', 'sort', 'direction']),
|
||
static fn ($value): bool => filled($value),
|
||
);
|
||
$withdrawalSortUrl = static function (string $field, string $direction) use ($withdrawalFilterQuery): string {
|
||
$query = http_build_query(array_merge($withdrawalFilterQuery, [
|
||
'sort' => $field,
|
||
'direction' => $direction,
|
||
]));
|
||
|
||
return request()->url() . '?' . $query;
|
||
};
|
||
$withdrawalColumns = [
|
||
['key' => 'platform', 'label' => '店铺平台', 'numeric' => false, 'title' => null],
|
||
['key' => 'erp_store_name', 'label' => 'ERP店铺名称', 'numeric' => false, 'title' => null],
|
||
['key' => 'store_id', 'label' => '平台店铺ID', 'numeric' => false, 'title' => null],
|
||
['key' => 'store_entity', 'label' => '店铺主体', 'numeric' => false, 'title' => null],
|
||
['key' => 'withdrawal_path', 'label' => '提现路径', 'numeric' => false, 'title' => null],
|
||
['key' => 'withdrawal_account', 'label' => '提现账户名称', 'numeric' => false, 'title' => null],
|
||
['key' => 'bank_card', 'label' => '银行卡号', 'numeric' => false, 'title' => null],
|
||
['key' => 'balance', 'label' => $withdrawalBalanceColumnLabel, 'numeric' => true, 'title' => '余额数据日期:' . $withdrawalBalanceDate],
|
||
['key' => 'reserve_standard', 'label' => '预留标准', 'numeric' => true, 'title' => null],
|
||
['key' => 'withdrawal_amount', 'label' => '提现金额', 'numeric' => true, 'title' => null],
|
||
['key' => 'compliance', 'label' => '是否达标', 'numeric' => false, 'title' => null],
|
||
];
|
||
@endphp
|
||
|
||
<section class="biz-card mt-6 withdrawal-management" aria-label="提现配置列表">
|
||
<div class="biz-card-head">
|
||
<div class="withdrawal-management__heading">
|
||
<div class="biz-card-title">
|
||
<span class="material-icons-outlined" aria-hidden="true">account_balance_wallet</span>
|
||
<span>提现配置</span>
|
||
</div>
|
||
<div class="withdrawal-management__summary">
|
||
<span class="biz-badge">余额源 {{ number_format($withdrawalBalanceSummary['source']) }} 条</span>
|
||
<span class="biz-badge biz-badge-yes">已显示余额 {{ number_format($withdrawalBalanceSummary['matched']) }} 条</span>
|
||
@if ($withdrawalBalanceSummary['ambiguous'] > 0)
|
||
<span class="biz-badge biz-badge-no">多条匹配 {{ number_format($withdrawalBalanceSummary['ambiguous']) }} 条</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<div class="withdrawal-management__actions" aria-label="提现管理导入导出">
|
||
<button type="button" class="biz-btn biz-btn-ghost" data-withdrawal-import-open>
|
||
<span class="material-icons-outlined" aria-hidden="true">upload_file</span>
|
||
<span>导入</span>
|
||
</button>
|
||
<a class="biz-btn biz-btn-primary" href="{{ route('business.withdrawal-management.export', $withdrawalQuery) }}">
|
||
<span class="material-icons-outlined" aria-hidden="true">download</span>
|
||
<span>导出</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<form class="biz-filter-bar" method="GET" action="{{ request()->url() }}" aria-label="提现管理筛选">
|
||
@if ($withdrawalSort['field'] !== '')
|
||
<input type="hidden" name="sort" value="{{ $withdrawalSort['field'] }}">
|
||
<input type="hidden" name="direction" value="{{ $withdrawalSort['direction'] }}">
|
||
@endif
|
||
<div class="biz-filter-field">
|
||
<label for="withdrawal-balance-date">余额日期</label>
|
||
<input
|
||
id="withdrawal-balance-date"
|
||
class="biz-filter-input"
|
||
type="date"
|
||
name="balance_date"
|
||
value="{{ $withdrawalFilters['balanceDate'] }}"
|
||
@if ($withdrawalEarliestBalanceDate) min="{{ $withdrawalEarliestBalanceDate }}" @endif
|
||
@if ($withdrawalLatestBalanceDate) max="{{ $withdrawalLatestBalanceDate }}" @endif
|
||
title="不选时显示最新余额"
|
||
>
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="withdrawal-platform">店铺平台</label>
|
||
<select id="withdrawal-platform" class="biz-filter-select" name="platform">
|
||
<option value="">全部</option>
|
||
@foreach ($withdrawalPlatforms as $option)
|
||
<option value="{{ $option }}" @selected($withdrawalFilters['platform'] === $option)>{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="withdrawal-path">提现路径</label>
|
||
<select id="withdrawal-path" class="biz-filter-select" name="withdrawal_path">
|
||
<option value="">全部</option>
|
||
@foreach ($withdrawalPaths as $option)
|
||
<option value="{{ $option }}" @selected($withdrawalFilters['withdrawalPath'] === $option)>{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="withdrawal-store-entity">店铺主体</label>
|
||
<select id="withdrawal-store-entity" class="biz-filter-select" name="store_entity">
|
||
<option value="">全部</option>
|
||
@foreach ($withdrawalStoreEntities as $option)
|
||
<option value="{{ $option }}" @selected($withdrawalFilters['storeEntity'] === $option)>{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="withdrawal-keyword">ERP店铺名称</label>
|
||
<input id="withdrawal-keyword" class="biz-filter-input" type="search" name="keyword" value="{{ $withdrawalFilters['keyword'] }}" placeholder="输入店铺名称">
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="withdrawal-balance-status">余额状态</label>
|
||
<select id="withdrawal-balance-status" class="biz-filter-select" name="balance_status">
|
||
<option value="">全部</option>
|
||
<option value="matched" @selected($withdrawalFilters['balanceStatus'] === 'matched')>已接入</option>
|
||
<option value="unmatched" @selected($withdrawalFilters['balanceStatus'] === 'unmatched')>未匹配</option>
|
||
<option value="ambiguous" @selected($withdrawalFilters['balanceStatus'] === 'ambiguous')>多条匹配</option>
|
||
<option value="not_ready" @selected($withdrawalFilters['balanceStatus'] === 'not_ready')>待同步</option>
|
||
</select>
|
||
</div>
|
||
<button type="submit" class="biz-btn biz-btn-primary">筛选</button>
|
||
@if (request()->hasAny(['platform', 'withdrawal_path', 'store_entity', 'keyword', 'balance_status', 'balance_date']))
|
||
<a class="biz-btn biz-btn-ghost" href="{{ request()->url() }}">重置</a>
|
||
@endif
|
||
</form>
|
||
|
||
<div class="biz-table-wrap">
|
||
<table
|
||
class="biz-table"
|
||
aria-label="提现管理表格"
|
||
data-finance-table-name="withdrawal-management-v4"
|
||
data-finance-table-native
|
||
data-withdrawal-table
|
||
data-column-storage-key="caiwu.withdrawal.columns.v1.{{ auth()->id() ?? 'guest' }}"
|
||
>
|
||
<thead>
|
||
<tr>
|
||
@foreach ($withdrawalColumns as $column)
|
||
<th
|
||
@class(['num' => $column['numeric']])
|
||
data-withdrawal-column="{{ $column['key'] }}"
|
||
@if ($column['title']) title="{{ $column['title'] }}" @endif
|
||
@if ($withdrawalSort['field'] === $column['key']) aria-sort="{{ $withdrawalSort['direction'] === 'asc' ? 'ascending' : 'descending' }}" @endif
|
||
>
|
||
<div class="withdrawal-table-head">
|
||
<span>{{ $column['label'] }}</span>
|
||
<button
|
||
type="button"
|
||
class="withdrawal-table-head__menu"
|
||
data-withdrawal-column-trigger
|
||
data-sort-field="{{ $column['key'] }}"
|
||
data-sort-label="{{ $column['label'] }}"
|
||
data-sort-asc="{{ $withdrawalSortUrl($column['key'], 'asc') }}"
|
||
data-sort-desc="{{ $withdrawalSortUrl($column['key'], 'desc') }}"
|
||
aria-label="设置{{ $column['label'] }}排序和显示字段"
|
||
aria-haspopup="dialog"
|
||
aria-expanded="false"
|
||
title="排序和显示字段"
|
||
>
|
||
<span class="material-icons-outlined" aria-hidden="true">{{ $withdrawalSort['field'] === $column['key'] ? ($withdrawalSort['direction'] === 'asc' ? 'arrow_upward' : 'arrow_downward') : 'unfold_more' }}</span>
|
||
</button>
|
||
</div>
|
||
</th>
|
||
@endforeach
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($withdrawalRows as $row)
|
||
@php
|
||
$bankCard = filled($row->bank_card_last_four)
|
||
? '**** ' . $row->bank_card_last_four
|
||
: '未配置';
|
||
$balanceMatch = $withdrawalBalances[(int) $row->id] ?? ['status' => 'not_ready', 'balance' => null];
|
||
$balanceText = match ($balanceMatch['status']) {
|
||
'matched' => number_format((float) $balanceMatch['balance'], 2),
|
||
'ambiguous' => '多条匹配',
|
||
'unmatched' => '未匹配',
|
||
default => '待同步',
|
||
};
|
||
$balanceTitle = $balanceMatch['status'] === 'matched'
|
||
? '聚水潭店铺:' . $balanceMatch['source_store_name'] . ';余额日期:' . $withdrawalBalanceDate
|
||
: '余额日期:' . $withdrawalBalanceDate;
|
||
$reserveStandard = $withdrawalReserveStandards[(int) $row->id] ?? null;
|
||
$withdrawalAmount = $withdrawalAmounts[(int) $row->id] ?? null;
|
||
$compliance = $withdrawalCompliance[(int) $row->id] ?? null;
|
||
@endphp
|
||
<tr>
|
||
<td data-withdrawal-column="platform">{{ $row->store_platform ?: '未配置' }}</td>
|
||
<td data-withdrawal-column="erp_store_name">{{ $row->erp_store_name ?: '-' }}</td>
|
||
<td data-withdrawal-column="store_id">{{ $row->store_id ?: '-' }}</td>
|
||
<td data-withdrawal-column="store_entity">{{ $row->store_entity_name ?: '-' }}</td>
|
||
<td data-withdrawal-column="withdrawal_path">{{ $row->withdrawal_path ?: '未配置' }}</td>
|
||
<td data-withdrawal-column="withdrawal_account">{{ $row->withdrawal_account_name ?: '未配置' }}</td>
|
||
<td data-withdrawal-column="bank_card">{{ $bankCard }}</td>
|
||
<td class="num" data-withdrawal-column="balance" title="{{ $balanceTitle }}">{{ $balanceText }}</td>
|
||
<td class="num" data-withdrawal-column="reserve_standard">{{ $reserveStandard === null ? '待配置' : number_format($reserveStandard, 2) }}</td>
|
||
<td class="num" data-withdrawal-column="withdrawal_amount">{{ $withdrawalAmount === null ? '待接入' : number_format($withdrawalAmount, 2) }}</td>
|
||
<td data-withdrawal-column="compliance">
|
||
@if ($compliance === 'yes')
|
||
<span class="biz-badge biz-badge-yes">是</span>
|
||
@elseif ($compliance === 'no')
|
||
<span class="biz-badge biz-badge-no">否</span>
|
||
@else
|
||
<span class="biz-badge biz-badge-no">待核验</span>
|
||
@endif
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="11">
|
||
<div class="biz-empty">
|
||
<span class="material-icons-outlined" aria-hidden="true">inbox</span>
|
||
<div>暂无店铺收款账户,请先在店铺收款账户中维护。</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
|
||
<div
|
||
class="withdrawal-column-menu"
|
||
data-withdrawal-column-menu
|
||
data-current-sort="{{ $withdrawalSort['field'] }}"
|
||
data-current-direction="{{ $withdrawalSort['direction'] }}"
|
||
role="dialog"
|
||
aria-label="表格排序和显示字段"
|
||
hidden
|
||
>
|
||
<div class="withdrawal-column-menu__title" data-withdrawal-menu-title>表格设置</div>
|
||
<div class="withdrawal-column-menu__sort" aria-label="排序方式">
|
||
<a href="#" data-withdrawal-sort-asc>
|
||
<span class="material-icons-outlined" aria-hidden="true">arrow_upward</span>
|
||
<span>升序</span>
|
||
</a>
|
||
<a href="#" data-withdrawal-sort-desc>
|
||
<span class="material-icons-outlined" aria-hidden="true">arrow_downward</span>
|
||
<span>降序</span>
|
||
</a>
|
||
</div>
|
||
<fieldset class="withdrawal-column-menu__fields">
|
||
<legend>显示字段</legend>
|
||
@foreach ($withdrawalColumns as $column)
|
||
<label>
|
||
<input type="checkbox" value="{{ $column['key'] }}" data-withdrawal-column-toggle checked>
|
||
<span>{{ $column['label'] }}</span>
|
||
</label>
|
||
@endforeach
|
||
</fieldset>
|
||
<div class="withdrawal-column-menu__actions">
|
||
<button type="button" data-withdrawal-columns-all>全选</button>
|
||
<button type="button" data-withdrawal-columns-reset>恢复默认</button>
|
||
</div>
|
||
<p class="withdrawal-column-menu__status" data-withdrawal-column-status aria-live="polite"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="biz-pagination">
|
||
<span>共 {{ $withdrawalRows->total() }} 条 · 第 {{ $withdrawalRows->currentPage() }} / {{ $withdrawalRows->lastPage() }} 页</span>
|
||
<span>
|
||
@if ($withdrawalRows->onFirstPage())
|
||
<span>‹ 上一页</span>
|
||
@else
|
||
<a href="{{ $withdrawalRows->previousPageUrl() }}">‹ 上一页</a>
|
||
@endif
|
||
@if ($withdrawalRows->hasMorePages())
|
||
<a href="{{ $withdrawalRows->nextPageUrl() }}">下一页 ›</a>
|
||
@else
|
||
<span>下一页 ›</span>
|
||
@endif
|
||
</span>
|
||
</div>
|
||
|
||
<div
|
||
class="biz-modal withdrawal-import-modal {{ $withdrawalOpenImport ? 'is-open' : '' }}"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="withdrawal-import-title"
|
||
aria-hidden="{{ $withdrawalOpenImport ? 'false' : 'true' }}"
|
||
data-withdrawal-import-modal
|
||
data-auto-open="{{ $withdrawalOpenImport ? 'true' : 'false' }}"
|
||
>
|
||
<div class="biz-modal-card">
|
||
<div class="withdrawal-import__head">
|
||
<div class="biz-modal-title" id="withdrawal-import-title">导入提现配置</div>
|
||
<button type="button" class="withdrawal-import__close" title="关闭" aria-label="关闭" data-withdrawal-import-close>
|
||
<span class="material-icons-outlined" aria-hidden="true">close</span>
|
||
</button>
|
||
</div>
|
||
|
||
@if (is_array($withdrawalImportPreview))
|
||
<div class="withdrawal-import__stats" aria-label="导入预览汇总">
|
||
<div><span>文件数据</span><strong>{{ number_format($withdrawalImportPreview['total']) }}</strong></div>
|
||
<div><span>拟更新</span><strong>{{ number_format($withdrawalImportPreview['changed']) }}</strong></div>
|
||
<div><span>无变化</span><strong>{{ number_format($withdrawalImportPreview['unchanged']) }}</strong></div>
|
||
</div>
|
||
|
||
@if ($withdrawalImportPreview['preview'] !== [])
|
||
<div class="withdrawal-import__preview">
|
||
<table class="biz-table">
|
||
<thead><tr><th>店铺平台</th><th>ERP店铺名称</th><th>平台店铺ID</th><th>变更字段</th></tr></thead>
|
||
<tbody>
|
||
@foreach ($withdrawalImportPreview['preview'] as $previewRow)
|
||
<tr>
|
||
<td>{{ $previewRow['platform'] }}</td>
|
||
<td>{{ $previewRow['erp_store_name'] }}</td>
|
||
<td>{{ $previewRow['store_id'] ?: '-' }}</td>
|
||
<td>{{ $previewRow['changes'] }}</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if ($withdrawalImportPreview['changed'] > count($withdrawalImportPreview['preview']))
|
||
<p class="withdrawal-import__status">已显示 20 / {{ number_format($withdrawalImportPreview['changed']) }} 条变更</p>
|
||
@endif
|
||
@else
|
||
<p class="withdrawal-import__empty">文件内容与当前配置一致,没有需要更新的字段。</p>
|
||
@endif
|
||
|
||
<form method="POST" action="{{ route('business.withdrawal-management.import.commit', $withdrawalQuery) }}">
|
||
@csrf
|
||
<input type="hidden" name="preview_token" value="{{ $withdrawalImportPreview['token'] }}">
|
||
<div class="biz-modal-foot">
|
||
<button type="button" class="biz-btn biz-btn-ghost" data-withdrawal-import-close>取消</button>
|
||
<button type="submit" class="biz-btn biz-btn-primary" @disabled($withdrawalImportPreview['changed'] === 0)>
|
||
<span class="material-icons-outlined" aria-hidden="true">save</span>
|
||
<span>确认更新 {{ number_format($withdrawalImportPreview['changed']) }} 家店铺</span>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
@else
|
||
@if ($errors->has('import'))
|
||
<div class="withdrawal-import__error" role="alert">{{ $errors->first('import') }}</div>
|
||
@endif
|
||
<form method="POST" enctype="multipart/form-data" action="{{ route('business.withdrawal-management.import.preview', $withdrawalQuery) }}">
|
||
@csrf
|
||
<label class="biz-field-label" for="withdrawal-import-file">Excel 文件</label>
|
||
<input id="withdrawal-import-file" class="biz-field-input" type="file" name="import" accept=".xlsx,.xls" required>
|
||
<div class="withdrawal-import__template">
|
||
<a class="biz-btn biz-btn-ghost" href="{{ route('business.withdrawal-management.template') }}">
|
||
<span class="material-icons-outlined" aria-hidden="true">download</span>
|
||
<span>下载导入模板</span>
|
||
</a>
|
||
</div>
|
||
<div class="biz-modal-foot">
|
||
<button type="button" class="biz-btn biz-btn-ghost" data-withdrawal-import-close>取消</button>
|
||
<button type="submit" class="biz-btn biz-btn-primary">
|
||
<span class="material-icons-outlined" aria-hidden="true">preview</span>
|
||
<span>上传并预览</span>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<style>
|
||
.withdrawal-management .biz-card-head { align-items:flex-start; gap:1rem; }
|
||
.withdrawal-management .biz-btn .material-icons-outlined { font-size: 1.1rem; }
|
||
.withdrawal-management .biz-btn-primary { border-color: #856632; background: #856632; color: #fff; }
|
||
.withdrawal-management .biz-btn-primary:hover { background: #6f5429; }
|
||
.withdrawal-management__heading { display:flex; min-width:0; flex-direction:column; gap:.55rem; }
|
||
.withdrawal-management__summary { display:flex; flex-wrap:wrap; gap:.35rem; }
|
||
.withdrawal-management__actions { display:flex; flex:0 0 auto; align-items:center; gap:.5rem; margin-left:auto; }
|
||
.withdrawal-management__actions .biz-btn { min-width:5.5rem; justify-content:center; }
|
||
.withdrawal-table-head { display:flex; min-width:max-content; align-items:center; justify-content:space-between; gap:.35rem; }
|
||
.withdrawal-management th.num .withdrawal-table-head { justify-content:flex-end; }
|
||
.withdrawal-table-head__menu { display:inline-flex; width:1.65rem; height:1.65rem; flex:0 0 1.65rem; align-items:center; justify-content:center; border:1px solid transparent; border-radius:4px; color:#856632; }
|
||
.withdrawal-table-head__menu:hover, .withdrawal-table-head__menu:focus-visible { border-color:#b9954f; background:#fff; outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.2); }
|
||
.withdrawal-table-head__menu .material-icons-outlined { font-size:1rem; }
|
||
.withdrawal-column-menu { position:fixed; z-index:80; width:min(18rem,calc(100vw - 1.5rem)); max-height:min(32rem,calc(100vh - 1.5rem)); overflow:auto; padding:.75rem; border:1px solid #d8d2c7; border-radius:8px; background:#fff; color:#171717; box-shadow:0 4px 8px rgba(23,23,23,.1); }
|
||
.withdrawal-column-menu[hidden] { display:none; }
|
||
.withdrawal-column-menu__title { margin:0 0 .5rem; font-size:.8125rem; font-weight:600; }
|
||
.withdrawal-column-menu__sort { display:grid; grid-template-columns:1fr 1fr; gap:.35rem; padding-bottom:.65rem; border-bottom:1px solid #d8d2c7; }
|
||
.withdrawal-column-menu__sort a { display:flex; min-height:2rem; align-items:center; gap:.35rem; padding:0 .55rem; border:1px solid #d8d2c7; border-radius:6px; color:#171717; font-size:.75rem; text-decoration:none; }
|
||
.withdrawal-column-menu__sort a:hover, .withdrawal-column-menu__sort a:focus-visible, .withdrawal-column-menu__sort a.is-active { border-color:#856632; background:#f7f0de; color:#856632; outline:none; }
|
||
.withdrawal-column-menu__sort .material-icons-outlined { font-size:1rem; }
|
||
.withdrawal-column-menu__fields { display:grid; grid-template-columns:1fr 1fr; gap:.25rem .6rem; margin:.65rem 0 0; padding:0; border:0; }
|
||
.withdrawal-column-menu__fields legend { grid-column:1 / -1; width:100%; margin-bottom:.25rem; color:#595959; font-size:.75rem; font-weight:600; }
|
||
.withdrawal-column-menu__fields label { display:flex; min-width:0; min-height:1.75rem; align-items:center; gap:.4rem; font-size:.75rem; cursor:pointer; }
|
||
.withdrawal-column-menu__fields input { flex:0 0 auto; accent-color:#856632; }
|
||
.withdrawal-column-menu__fields span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||
.withdrawal-column-menu__actions { display:flex; gap:.35rem; margin-top:.6rem; padding-top:.6rem; border-top:1px solid #d8d2c7; }
|
||
.withdrawal-column-menu__actions button { min-height:1.8rem; padding:0 .55rem; border:1px solid #d8d2c7; border-radius:5px; background:#fff; color:#856632; font-size:.75rem; }
|
||
.withdrawal-column-menu__actions button:hover, .withdrawal-column-menu__actions button:focus-visible { border-color:#856632; background:#f7f0de; outline:none; }
|
||
.withdrawal-column-menu__status { min-height:1rem; margin:.45rem 0 0; color:#595959; font-size:.7rem; }
|
||
.withdrawal-management [data-withdrawal-column].is-column-hidden { display:none !important; }
|
||
.withdrawal-import-modal .biz-modal-card { max-width: 820px; border: 1px solid #d8d2c7; border-radius: 8px; }
|
||
.withdrawal-import__head { display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; margin-bottom:1rem; }
|
||
.withdrawal-import__head .biz-modal-title { margin:0; }
|
||
.withdrawal-import__close { display:inline-flex; width:2rem; height:2rem; flex:0 0 2rem; align-items:center; justify-content:center; border:1px solid #d8d2c7; border-radius:6px; color:#34312d; }
|
||
.withdrawal-import__close:hover { background:#f3efe6; }
|
||
.withdrawal-import__stats { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); margin:1rem 0; border:1px solid #d8d2c7; }
|
||
.withdrawal-import__stats > div { display:flex; align-items:center; justify-content:space-between; gap:.75rem; padding:.75rem 1rem; }
|
||
.withdrawal-import__stats > div + div { border-left:1px solid #d8d2c7; }
|
||
.withdrawal-import__stats span { color:#6b665e; font-size:.8125rem; }
|
||
.withdrawal-import__stats strong { color:#171717; font-variant-numeric:tabular-nums; }
|
||
.withdrawal-import__preview { max-height:360px; overflow:auto; border:1px solid #e5e0d7; }
|
||
.withdrawal-import__preview .biz-table { min-width:700px; }
|
||
.withdrawal-import__status { margin:.75rem 0 0; color:#6b665e; font-size:.8125rem; }
|
||
.withdrawal-import__template { margin-top:1rem; }
|
||
.withdrawal-import__empty, .withdrawal-import__error { padding:.85rem 1rem; border:1px solid #d8d2c7; color:#595959; }
|
||
.withdrawal-import__error { margin-bottom:1rem; border-color:#d6a4a4; background:#fff7f7; color:#8a1f1f; }
|
||
@media (max-width: 640px) {
|
||
.withdrawal-management .biz-card-head { align-items:stretch; }
|
||
.withdrawal-management__actions { width:100%; margin-left:0; }
|
||
.withdrawal-management__actions .biz-btn { flex:1 1 0; }
|
||
.withdrawal-column-menu__fields { grid-template-columns:1fr; }
|
||
.withdrawal-import-modal { padding:1rem; }
|
||
.withdrawal-import__stats { grid-template-columns:1fr; }
|
||
.withdrawal-import__stats > div + div { border-left:0; border-top:1px solid #d8d2c7; }
|
||
.withdrawal-import-modal .biz-modal-foot { align-items:stretch; flex-direction:column-reverse; }
|
||
.withdrawal-import-modal .biz-btn { justify-content:center; }
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
(() => {
|
||
const modal = document.querySelector('[data-withdrawal-import-modal]');
|
||
if (!modal) return;
|
||
const open = () => {
|
||
modal.classList.add('is-open');
|
||
modal.setAttribute('aria-hidden', 'false');
|
||
modal.querySelector('input[type="file"], button:not([disabled])')?.focus();
|
||
};
|
||
const close = () => {
|
||
modal.classList.remove('is-open');
|
||
modal.setAttribute('aria-hidden', 'true');
|
||
};
|
||
document.querySelectorAll('[data-withdrawal-import-open]').forEach((button) => button.addEventListener('click', open));
|
||
modal.querySelectorAll('[data-withdrawal-import-close]').forEach((button) => button.addEventListener('click', close));
|
||
modal.addEventListener('click', (event) => { if (event.target === modal) close(); });
|
||
document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && modal.classList.contains('is-open')) close(); });
|
||
if (modal.dataset.autoOpen === 'true') open();
|
||
})();
|
||
</script>
|
||
|
||
<script>
|
||
(() => {
|
||
const table = document.querySelector('[data-withdrawal-table]');
|
||
const menu = document.querySelector('[data-withdrawal-column-menu]');
|
||
if (!table || !menu) return;
|
||
|
||
const triggers = [...table.querySelectorAll('[data-withdrawal-column-trigger]')];
|
||
const checks = [...menu.querySelectorAll('[data-withdrawal-column-toggle]')];
|
||
const ascending = menu.querySelector('[data-withdrawal-sort-asc]');
|
||
const descending = menu.querySelector('[data-withdrawal-sort-desc]');
|
||
const title = menu.querySelector('[data-withdrawal-menu-title]');
|
||
const status = menu.querySelector('[data-withdrawal-column-status]');
|
||
const storageKey = table.dataset.columnStorageKey;
|
||
let activeTrigger = null;
|
||
|
||
const checkedKeys = () => checks.filter((check) => check.checked).map((check) => check.value);
|
||
const close = () => {
|
||
menu.hidden = true;
|
||
activeTrigger?.setAttribute('aria-expanded', 'false');
|
||
activeTrigger = null;
|
||
};
|
||
const applyVisibility = (save = true, message = null) => {
|
||
const visible = new Set(checkedKeys());
|
||
table.querySelectorAll('[data-withdrawal-column]').forEach((cell) => {
|
||
cell.classList.toggle('is-column-hidden', !visible.has(cell.dataset.withdrawalColumn));
|
||
});
|
||
table.querySelectorAll('tbody td[colspan]').forEach((cell) => {
|
||
cell.colSpan = Math.max(visible.size, 1);
|
||
});
|
||
status.textContent = message || `已显示 ${visible.size}/${checks.length} 个字段`;
|
||
if (save && storageKey) {
|
||
try { localStorage.setItem(storageKey, JSON.stringify([...visible])); } catch (_) {}
|
||
}
|
||
};
|
||
const position = (trigger) => {
|
||
const triggerRect = trigger.getBoundingClientRect();
|
||
const menuRect = menu.getBoundingClientRect();
|
||
const gap = 6;
|
||
const edge = 12;
|
||
let left = triggerRect.right - menuRect.width;
|
||
let top = triggerRect.bottom + gap;
|
||
left = Math.max(edge, Math.min(left, window.innerWidth - menuRect.width - edge));
|
||
if (top + menuRect.height > window.innerHeight - edge) {
|
||
top = Math.max(edge, triggerRect.top - menuRect.height - gap);
|
||
}
|
||
menu.style.left = `${left}px`;
|
||
menu.style.top = `${top}px`;
|
||
};
|
||
const open = (trigger) => {
|
||
if (activeTrigger === trigger && !menu.hidden) {
|
||
close();
|
||
return;
|
||
}
|
||
activeTrigger?.setAttribute('aria-expanded', 'false');
|
||
activeTrigger = trigger;
|
||
trigger.setAttribute('aria-expanded', 'true');
|
||
title.textContent = `${trigger.dataset.sortLabel} · 排序与字段`;
|
||
ascending.href = trigger.dataset.sortAsc;
|
||
descending.href = trigger.dataset.sortDesc;
|
||
const current = menu.dataset.currentSort === trigger.dataset.sortField;
|
||
ascending.classList.toggle('is-active', current && menu.dataset.currentDirection === 'asc');
|
||
descending.classList.toggle('is-active', current && menu.dataset.currentDirection === 'desc');
|
||
menu.hidden = false;
|
||
position(trigger);
|
||
ascending.focus({ preventScroll: true });
|
||
};
|
||
|
||
if (storageKey) {
|
||
try {
|
||
const saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
|
||
if (Array.isArray(saved) && saved.length > 0) {
|
||
const available = new Set(checks.map((check) => check.value));
|
||
checks.forEach((check) => { check.checked = saved.includes(check.value) || !available.has(check.value); });
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
if (checkedKeys().length === 0) checks.forEach((check) => { check.checked = true; });
|
||
applyVisibility(false);
|
||
|
||
triggers.forEach((trigger) => trigger.addEventListener('click', (event) => {
|
||
event.stopPropagation();
|
||
open(trigger);
|
||
}));
|
||
checks.forEach((check) => check.addEventListener('change', () => {
|
||
if (checkedKeys().length === 0) {
|
||
check.checked = true;
|
||
applyVisibility(false, '至少保留一个显示字段');
|
||
return;
|
||
}
|
||
applyVisibility();
|
||
}));
|
||
menu.querySelector('[data-withdrawal-columns-all]')?.addEventListener('click', () => {
|
||
checks.forEach((check) => { check.checked = true; });
|
||
applyVisibility();
|
||
});
|
||
menu.querySelector('[data-withdrawal-columns-reset]')?.addEventListener('click', () => {
|
||
checks.forEach((check) => { check.checked = true; });
|
||
if (storageKey) {
|
||
try { localStorage.removeItem(storageKey); } catch (_) {}
|
||
}
|
||
applyVisibility(false, '已恢复默认字段');
|
||
});
|
||
menu.addEventListener('click', (event) => event.stopPropagation());
|
||
document.addEventListener('click', close);
|
||
document.addEventListener('keydown', (event) => {
|
||
if (event.key === 'Escape' && !menu.hidden) {
|
||
const trigger = activeTrigger;
|
||
close();
|
||
trigger?.focus();
|
||
}
|
||
});
|
||
window.addEventListener('resize', close, { passive: true });
|
||
table.closest('.biz-table-wrap')?.addEventListener('scroll', close, { passive: true });
|
||
})();
|
||
</script>
|