合并:完善店铺收款账户账单字段与加载状态
This commit is contained in:
parent
b008ff602a
commit
015c5de2a9
@ -72,9 +72,9 @@ return [
|
||||
['slug' => 'taobao-wechat', 'title' => '淘系微信账户'],
|
||||
['slug' => 'pinduoduo', 'title' => '拼多多账户'],
|
||||
['slug' => 'jd-wallet', 'title' => '京东网银钱包账户'],
|
||||
['slug' => 'jd-transactions', 'title' => '京东流水'],
|
||||
['slug' => 'douyin', 'title' => '抖音账户'],
|
||||
['slug' => 'dewu', 'title' => '得物账户'],
|
||||
['slug' => 'vipshop', 'title' => '唯品会账户'],
|
||||
],
|
||||
],
|
||||
['slug' => 'marketing-account-balance', 'title' => '店铺营销账户余额'],
|
||||
|
||||
@ -8,6 +8,7 @@ namespace Modules\EcommerceFinance;
|
||||
* 列定义来自「账单抬头.xlsx」的各子表(sheet 名 = 账户类型)。
|
||||
* sheet 名 ↔ tab slug ↔ 列 三者在此一一对应,避免错乱。
|
||||
*
|
||||
* 账户可选配置分组表头 groups,字段列统一由 columns 定义。
|
||||
* 每列: [label, field, kind]
|
||||
* field: 真实列名(如 record_code / income / occurred_at / updated_at),
|
||||
* 或 'payload.xxx' 表示存在 payload JSON 中。
|
||||
@ -18,13 +19,20 @@ namespace Modules\EcommerceFinance;
|
||||
class AccountBillColumns
|
||||
{
|
||||
/**
|
||||
* @return array<string,array{title:string,columns:array<int,array{0:string,1:string,2:string}>}>
|
||||
* @return array<string,array{title:string,groups?:array<int,array{0:string,1:int}>,columns:array<int,array{0:string,1:string,2:string}>}>
|
||||
*/
|
||||
public static function map(): array
|
||||
{
|
||||
return [
|
||||
'taobao-wechat' => [
|
||||
'title' => '淘系微信账户',
|
||||
'groups' => [
|
||||
['基础信息', 7],
|
||||
['账户与交易', 5],
|
||||
['金额', 2],
|
||||
['业务关联', 2],
|
||||
['维护信息', 2],
|
||||
],
|
||||
'columns' => [
|
||||
['记录编码', 'record_code', 'num'],
|
||||
['店铺', 'store_name', 'text'],
|
||||
@ -214,7 +222,7 @@ class AccountBillColumns
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{title:string,columns:array<int,array{label:string,field:string,kind:string}>}
|
||||
* @return array{title:string,groups:array<int,array{label:string,span:int}>,columns:array<int,array{label:string,field:string,kind:string}>}
|
||||
*/
|
||||
public static function for(string $type): ?array
|
||||
{
|
||||
@ -228,8 +236,13 @@ class AccountBillColumns
|
||||
return ['label' => $c[0], 'field' => $c[1], 'kind' => $c[2]];
|
||||
}, $map[$type]['columns']);
|
||||
|
||||
$groups = array_map(function ($group) {
|
||||
return ['label' => $group[0], 'span' => $group[1]];
|
||||
}, $map[$type]['groups'] ?? []);
|
||||
|
||||
return [
|
||||
'title' => $map[$type]['title'],
|
||||
'groups' => $groups,
|
||||
'columns' => $columns,
|
||||
];
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('ec_alipay_bills', function (Blueprint $table) {
|
||||
$table->string('finance_remark', 255)->nullable()->after('store_name');
|
||||
$table->string('order_no', 64)->nullable()->after('finance_remark');
|
||||
$table->decimal('amount', 20, 6)->nullable()->after('order_no');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('ec_alipay_bills', function (Blueprint $table) {
|
||||
$table->dropColumn(['finance_remark', 'order_no', 'amount']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -35,6 +35,14 @@ class AlipayBills extends Controller
|
||||
$query->where('business_type', $type);
|
||||
}
|
||||
|
||||
if ($orderNo = $request->get('order_no')) {
|
||||
$query->where('order_no', 'like', '%' . $orderNo . '%');
|
||||
}
|
||||
|
||||
if ($financeRemark = $request->get('finance_remark')) {
|
||||
$query->where('finance_remark', 'like', '%' . $financeRemark . '%');
|
||||
}
|
||||
|
||||
if ($start = $request->get('start')) {
|
||||
$query->where('occurred_at', '>=', $start);
|
||||
}
|
||||
@ -48,10 +56,12 @@ class AlipayBills extends Controller
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->where('goods_name', 'like', $like)
|
||||
->orWhere('record_code', 'like', $like)
|
||||
->orWhere('order_no', 'like', $like)
|
||||
->orWhere('account_order_no', 'like', $like)
|
||||
->orWhere('business_serial_no', 'like', $like)
|
||||
->orWhere('merchant_order_no', 'like', $like)
|
||||
->orWhere('online_order_no', 'like', $like)
|
||||
->orWhere('finance_remark', 'like', $like)
|
||||
->orWhere('remark', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
@ -15,6 +15,9 @@ class AlipayBill extends Model
|
||||
'company_id',
|
||||
'record_code',
|
||||
'store_name',
|
||||
'finance_remark',
|
||||
'order_no',
|
||||
'amount',
|
||||
'custom_remark',
|
||||
'bill_item',
|
||||
'business_type',
|
||||
@ -43,6 +46,7 @@ class AlipayBill extends Model
|
||||
|
||||
protected $casts = [
|
||||
'occurred_at' => 'datetime',
|
||||
'amount' => 'decimal:6',
|
||||
'income' => 'decimal:6',
|
||||
'expense' => 'decimal:6',
|
||||
'balance' => 'decimal:6',
|
||||
|
||||
@ -54,15 +54,20 @@
|
||||
<!-- 数据表(列由后端配置动态渲染) -->
|
||||
<div class="ab-panel">
|
||||
<div class="ab-phead">
|
||||
<div class="ab-title">{{ columnsTitle }}</div>
|
||||
<div class="ab-title">{{ columnGroups.length ? '账单明细' : columnsTitle }}</div>
|
||||
<div class="ab-count" v-if="!loading">{{ total }} 条</div>
|
||||
</div>
|
||||
|
||||
<div class="ab-scroll">
|
||||
<table class="ab-table" v-if="rows.length && columns.length">
|
||||
<table class="ab-table" :class="{ 'ab-table-grouped': columnGroups.length }" v-if="!loading && !loadError && columns.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(c, i) in columns" :key="i" :class="{ 'ab-num': isNumKind(c.kind) }"
|
||||
<tr class="ab-group" v-if="columnGroups.length">
|
||||
<th v-for="(group, i) in columnGroups" :key="i" :colspan="group.span" scope="colgroup">
|
||||
{{ group.label }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr class="ab-col">
|
||||
<th v-for="(c, i) in columns" :key="i" :class="{ 'ab-num': isNumKind(c.kind) }" scope="col"
|
||||
@click="maybeSort(c)">
|
||||
{{ c.label }}
|
||||
<span class="ab-sort" v-if="sortableField(c.field)">⇅</span>
|
||||
@ -76,11 +81,18 @@
|
||||
<template v-else>{{ cellDisplay(r, c) }}</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!rows.length" class="ab-empty-row">
|
||||
<td :colspan="columns.length" class="ab-empty">没有符合条件的账单记录</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="ab-empty" v-else-if="!loading">没有符合条件的账单记录</div>
|
||||
<div class="ab-empty" v-else>加载中…</div>
|
||||
<div v-else-if="loading" class="ab-empty">加载中…</div>
|
||||
<div v-else-if="loadError" class="ab-empty ab-error">
|
||||
<div>账单加载失败,请重试</div>
|
||||
<button class="ab-btn" @click="fetch">重新加载</button>
|
||||
</div>
|
||||
<div v-else class="ab-empty">暂无账单字段</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
@ -118,9 +130,11 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadError: false,
|
||||
rows: [],
|
||||
columns: [],
|
||||
columnsTitle: '',
|
||||
columnGroups: [],
|
||||
summary: { income: 0, expense: 0, balance: 0, count: 0 },
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
@ -147,6 +161,7 @@ export default {
|
||||
methods: {
|
||||
async fetch() {
|
||||
this.loading = true;
|
||||
this.loadError = false;
|
||||
const params = {
|
||||
bill_type: this.billType,
|
||||
...this.filters,
|
||||
@ -168,12 +183,17 @@ export default {
|
||||
if (body.columns) {
|
||||
this.columns = body.columns.columns || body.columns || [];
|
||||
this.columnsTitle = body.columns.title || '';
|
||||
this.columnGroups = body.columns.groups || [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载账单失败', e);
|
||||
this.rows = [];
|
||||
this.columns = [];
|
||||
this.columnsTitle = '';
|
||||
this.columnGroups = [];
|
||||
this.total = 0;
|
||||
this.summary = { income: 0, expense: 0, balance: 0, count: 0 };
|
||||
this.loadError = true;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@ -295,8 +315,10 @@ export default {
|
||||
|
||||
.ab-scroll { overflow-x: auto; }
|
||||
.ab-table { border-collapse: collapse; width: 100%; font-size: 0.78125rem; }
|
||||
.ab-table thead th { background: #fafbfd; color: #1f2937; font-weight: 600; text-align: left; border-bottom: 1px solid #e6e9f0; padding: 0.5625rem 0.625rem; white-space: nowrap; }
|
||||
.ab-table thead th.ab-num { text-align: right; }
|
||||
.ab-table-grouped { min-width: 2200px; }
|
||||
.ab-table thead .ab-group th { background: #f0f3f9; color: #6b7280; font-size: 0.75rem; font-weight: 600; text-align: center; border-bottom: 1px solid #dfe4ec; padding: 0.625rem; white-space: nowrap; }
|
||||
.ab-table thead .ab-col th { background: #fafbfd; color: #1f2937; font-weight: 600; text-align: left; border-bottom: 1px solid #e6e9f0; padding: 0.5625rem 0.625rem; white-space: nowrap; }
|
||||
.ab-table thead .ab-col th.ab-num { text-align: right; }
|
||||
.ab-sort { color: #9aa3b2; margin-left: 0.1875rem; }
|
||||
.ab-table tbody td { padding: 0.5625rem 0.625rem; border-bottom: 1px solid #f1f3f7; white-space: nowrap; color: #1f2937; }
|
||||
.ab-table tbody tr:hover { background: #f7f9ff; }
|
||||
@ -307,6 +329,8 @@ export default {
|
||||
.ab-tag { display: inline-block; padding: 0.0625rem 0.5rem; border-radius: 1rem; font-size: 0.71875rem; font-weight: 500; background: var(--vip-content-accent-soft); color: var(--vip-content-accent-dark); }
|
||||
|
||||
.ab-empty { padding: 3rem; text-align: center; color: #9aa3b2; }
|
||||
.ab-error { color: #dc2626; }
|
||||
.ab-error .ab-btn { margin-top: 0.75rem; color: #1f2937; }
|
||||
|
||||
.ab-pager { display: flex; align-items: center; gap: 0.625rem; padding: 0.875rem 1rem; border-top: 1px solid #e6e9f0; flex-wrap: wrap; }
|
||||
.ab-page-info { color: #6b7280; font-size: 0.78125rem; }
|
||||
|
||||
@ -82,39 +82,41 @@
|
||||
</div>
|
||||
|
||||
<div class="tb-scroll">
|
||||
<table class="tb-table" v-if="rows.length">
|
||||
<table class="tb-table" v-if="!loading">
|
||||
<thead>
|
||||
<tr class="tb-group">
|
||||
<th colspan="3">标识与店铺</th>
|
||||
<th colspan="4">账单与业务</th>
|
||||
<th colspan="9">账号与流水</th>
|
||||
<th colspan="2">商品</th>
|
||||
<th colspan="7">基础信息</th>
|
||||
<th colspan="8">账户与交易</th>
|
||||
<th colspan="3">金额(元)</th>
|
||||
<th colspan="4">时间</th>
|
||||
<th colspan="6">业务关联</th>
|
||||
<th colspan="4">维护信息</th>
|
||||
</tr>
|
||||
<tr class="tb-col">
|
||||
<th @click="toggleSort('record_code')">记录编码 <span class="tb-sort">⇅</span></th>
|
||||
<th>原始店铺</th>
|
||||
<th>财务备注</th>
|
||||
<th>订单号</th>
|
||||
<th class="tb-num">金额</th>
|
||||
<th>自定义备注</th>
|
||||
<th>账单项目</th>
|
||||
<th>业务类型</th>
|
||||
<th>业务描述</th>
|
||||
<th>备注</th>
|
||||
<th>支付宝账号</th>
|
||||
<th>对方账号</th>
|
||||
<th>账务订单号</th>
|
||||
<th>业务流水号</th>
|
||||
<th>商户订单号</th>
|
||||
<th>业务基础订单号</th>
|
||||
<th>线上单号</th>
|
||||
<th>业务订单号</th>
|
||||
<th>业务账单来源</th>
|
||||
<th>商品名称</th>
|
||||
<th>线上单号对应实际店铺</th>
|
||||
<th>对方账号</th>
|
||||
<th @click="toggleSort('occurred_at')">发生时间 <span class="tb-sort">⇅</span></th>
|
||||
<th class="tb-num" @click="toggleSort('income')">收入 <span class="tb-sort">⇅</span></th>
|
||||
<th class="tb-num" @click="toggleSort('expense')">支出 <span class="tb-sort">⇅</span></th>
|
||||
<th class="tb-num" @click="toggleSort('balance')">余额 <span class="tb-sort">⇅</span></th>
|
||||
<th @click="toggleSort('occurred_at')">发生时间 <span class="tb-sort">⇅</span></th>
|
||||
<th>业务类型</th>
|
||||
<th>备注</th>
|
||||
<th>线上单号</th>
|
||||
<th>线上单号对应实际店铺</th>
|
||||
<th>业务描述</th>
|
||||
<th>业务账单来源</th>
|
||||
<th>业务订单号</th>
|
||||
<th>创建时间</th>
|
||||
<th>修改时间</th>
|
||||
<th>修改人</th>
|
||||
@ -124,39 +126,42 @@
|
||||
<tr v-for="r in rows" :key="r.id">
|
||||
<td class="tb-mono">{{ r.record_code }}</td>
|
||||
<td>{{ r.store_name }}</td>
|
||||
<td class="tb-muted">{{ r.finance_remark || '—' }}</td>
|
||||
<td class="tb-mono">{{ r.order_no || '—' }}</td>
|
||||
<td class="tb-num">{{ r.amount === null || r.amount === undefined ? '—' : money(r.amount) }}</td>
|
||||
<td class="tb-muted">{{ r.custom_remark || '—' }}</td>
|
||||
<td><span class="tb-tag">{{ r.bill_item }}</span></td>
|
||||
<td>
|
||||
<span class="tb-tag" :class="tagClass(r.business_type)">{{ r.business_type }}</span>
|
||||
</td>
|
||||
<td class="tb-muted tb-ellipsis">{{ r.business_description || '—' }}</td>
|
||||
<td class="tb-muted tb-ellipsis">{{ r.remark || '—' }}</td>
|
||||
<td class="tb-mono">{{ r.alipay_account }}</td>
|
||||
<td>{{ r.counterparty_account }}</td>
|
||||
<td class="tb-mono">{{ r.account_order_no }}</td>
|
||||
<td class="tb-mono">{{ r.business_serial_no }}</td>
|
||||
<td class="tb-mono">{{ r.merchant_order_no || '—' }}</td>
|
||||
<td class="tb-mono tb-muted">{{ r.business_base_order_no || '—' }}</td>
|
||||
<td class="tb-mono">{{ r.online_order_no || '—' }}</td>
|
||||
<td class="tb-mono tb-muted">{{ r.business_order_no || '—' }}</td>
|
||||
<td class="tb-muted">{{ r.business_bill_source || '—' }}</td>
|
||||
<td class="tb-ellipsis">{{ r.goods_name }}</td>
|
||||
<td>{{ r.online_real_store || '—' }}</td>
|
||||
<td>{{ r.counterparty_account || '—' }}</td>
|
||||
<td>{{ r.occurred_at || '—' }}</td>
|
||||
<td class="tb-num tb-in" v-if="r.income > 0">+{{ money(r.income) }}</td>
|
||||
<td class="tb-num tb-in" v-else>—</td>
|
||||
<td class="tb-num tb-out" v-if="r.expense > 0">−{{ money(r.expense) }}</td>
|
||||
<td class="tb-num tb-out" v-else>—</td>
|
||||
<td class="tb-num">{{ money(r.balance) }}</td>
|
||||
<td>{{ r.occurred_at }}</td>
|
||||
<td><span class="tb-tag" :class="tagClass(r.business_type)">{{ r.business_type || '—' }}</span></td>
|
||||
<td class="tb-muted tb-ellipsis">{{ r.remark || '—' }}</td>
|
||||
<td class="tb-mono">{{ r.online_order_no || '—' }}</td>
|
||||
<td>{{ r.online_real_store || '—' }}</td>
|
||||
<td class="tb-muted tb-ellipsis">{{ r.business_description || '—' }}</td>
|
||||
<td class="tb-muted">{{ r.business_bill_source || '—' }}</td>
|
||||
<td class="tb-mono tb-muted">{{ r.business_order_no || '—' }}</td>
|
||||
<td class="tb-muted">{{ r.created_at }}</td>
|
||||
<td class="tb-muted">{{ r.updated_at }}</td>
|
||||
<td class="tb-muted">{{ r.modifier || '—' }}</td>
|
||||
</tr>
|
||||
<tr v-if="!rows.length" class="tb-empty-row">
|
||||
<td :colspan="28" class="tb-empty">没有符合条件的账单记录</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="tb-empty" v-else-if="!loading">没有符合条件的账单记录</div>
|
||||
<div class="tb-empty" v-else>加载中…</div>
|
||||
<div v-else class="tb-empty">加载中…</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
|
||||
2
resources/assets/js/views/business/pages.js
vendored
2
resources/assets/js/views/business/pages.js
vendored
@ -16,7 +16,7 @@ import TaobaoAlipayBills from './../../components/business/TaobaoAlipayBills.vue
|
||||
import AccountBills from './../../components/business/AccountBills.vue';
|
||||
|
||||
const GENERIC_TABS = ['taobao-wechat', 'pinduoduo', 'jd-wallet', 'jd-transactions', 'douyin', 'dewu'];
|
||||
const STORE_RECEIVING_TABS = ['taobao-alipay', 'taobao-wechat', 'pinduoduo', 'jd-wallet', 'douyin', 'dewu', 'vipshop'];
|
||||
const STORE_RECEIVING_TABS = ['taobao-alipay', 'taobao-wechat', 'pinduoduo', 'jd-wallet', 'jd-transactions', 'douyin', 'dewu'];
|
||||
|
||||
function pageContext(pathname = window.location.pathname) {
|
||||
const parts = (pathname || '').split('/').filter(Boolean);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user