commit
1d9ad91016
@ -75,7 +75,7 @@ async def get_ipc_logo(ns={}):
|
||||
"email": "zhangjian@bjzai.org.cn",
|
||||
"license": "2022001945号-4",
|
||||
"logo": "https://www.opencomputing.cn/idfile?path=logo_ncmatch.png",
|
||||
"mobile": "13601380912",
|
||||
# "mobile": "13601380912",
|
||||
"publicsecurity": "11010502054007",
|
||||
"businesslicense": "京B2-20232313"
|
||||
},
|
||||
@ -106,7 +106,7 @@ async def get_ipc_logo(ns={}):
|
||||
"email": "zhangjian@bjzai.org.cn",
|
||||
"license": "京ICP备13007859号-1",
|
||||
"logo": "https://zgcopc.opencomputing.cn/idfile?path=logo_zgcopc.png",
|
||||
"mobile": "13601380912",
|
||||
# "mobile": "13601380912",
|
||||
"publicsecurity": "11010502054007",
|
||||
"businesslicense": "京ICP备13007859号-1"
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"ignoreDeprecations": "6.0",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
|
||||
28
f/web-kboss/src/api/FinancialSettlementCenter.js
Normal file
28
f/web-kboss/src/api/FinancialSettlementCenter.js
Normal file
@ -0,0 +1,28 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
const get = (url, params = {}) => request({
|
||||
url,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
|
||||
// 汇总查询:accounting_orgid、counterparty_type、period_type、start_date、end_date 等。
|
||||
export const summaryQueryAPI = params => get('/bill/finance_settlement_summary.dspy', params)
|
||||
|
||||
// 结算单预览:对手方和账期参数使用 period_start、period_end。
|
||||
export const settlementPreviewAPI = params => get('/bill/finance_settlement_preview.dspy', params)
|
||||
|
||||
// 截图接口定义为 GET,创建结算单参数通过 query 发送。
|
||||
export const createSettlementStatementAPI = params => get('/bill/finance_settlement_create.dspy', params)
|
||||
|
||||
// 结算单列表:支持对手方、状态、账期和分页筛选。
|
||||
export const settlementListAPI = params => get('/bill/finance_settlement_list.dspy', params)
|
||||
|
||||
// 结算单详情:使用 settlement_id、current_page、page_size。
|
||||
export const settlementDetailsAPI = params => get('/bill/finance_settlement_detail.dspy', params)
|
||||
|
||||
// 截图接口定义为 GET,提交审批参数为 settlement_id、userid、business_name。
|
||||
export const submitApprovalAPI = params => get('/bill/finance_settlement_submit.dspy', params)
|
||||
|
||||
// 截图接口定义为 GET,审批回调参数为 apv_id、status。
|
||||
export const approvalCallbackAPI = params => get('/bill/finance_settlement_apv_callback.dspy', params)
|
||||
@ -563,7 +563,120 @@ export const asyncRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 财务——财务结算中心
|
||||
{
|
||||
path: "/financialSettlementCenter",
|
||||
component: Layout,
|
||||
redirect: "/financialSettlementCenter/processOverview",
|
||||
meta: {
|
||||
title: "财务结算中心",
|
||||
fullPath: "/financialSettlementCenter",
|
||||
noCache: true,
|
||||
icon: "el-icon-s-finance",
|
||||
roles: ["财务"]
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "processOverview",
|
||||
component: () => import('@/views/FinancialSettlementCenter/ProcessOverview.vue'),
|
||||
name: 'FinancialSettlementProcessOverview',
|
||||
meta: {
|
||||
title: "流程总览",
|
||||
fullPath: "/financialSettlementCenter/processOverview",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "summaryQuery",
|
||||
component: () => import('@/views/FinancialSettlementCenter/SummaryQuery.vue'),
|
||||
name: 'FinancialSettlementSummaryQuery',
|
||||
meta: {
|
||||
title: "汇总查询",
|
||||
fullPath: "/financialSettlementCenter/summaryQuery",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "platformRevenue",
|
||||
component: () => import('@/views/FinancialSettlementCenter/PlatformRevenue.vue'),
|
||||
name: 'FinancialSettlementPlatformRevenue',
|
||||
meta: {
|
||||
title: "平台收入",
|
||||
fullPath: "/financialSettlementCenter/platformRevenue",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "settlementStatementPreview",
|
||||
component: () => import('@/views/FinancialSettlementCenter/SettlementStatementPreview.vue'),
|
||||
name: 'FinancialSettlementStatementPreview',
|
||||
meta: {
|
||||
title: "结算单预览",
|
||||
fullPath: "/financialSettlementCenter/settlementStatementPreview",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "createSettlementStatement",
|
||||
component: () => import('@/views/FinancialSettlementCenter/CreateSettlementStatement.vue'),
|
||||
name: 'FinancialCreateSettlementStatement',
|
||||
meta: {
|
||||
title: "创建结算单",
|
||||
fullPath: "/financialSettlementCenter/createSettlementStatement",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "settlementStatementList",
|
||||
component: () => import('@/views/FinancialSettlementCenter/SettlementStatementList.vue'),
|
||||
name: 'FinancialSettlementStatementList',
|
||||
meta: {
|
||||
title: "结算单列表",
|
||||
fullPath: "/financialSettlementCenter/settlementStatementList",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "settlementStatementDetails",
|
||||
component: () => import('@/views/FinancialSettlementCenter/SettlementStatementDetails.vue'),
|
||||
name: 'FinancialSettlementStatementDetails',
|
||||
meta: {
|
||||
title: "结算单详情",
|
||||
fullPath: "/financialSettlementCenter/settlementStatementDetails",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "submitforApproval",
|
||||
component: () => import('@/views/FinancialSettlementCenter/SubmitforApproval.vue'),
|
||||
name: 'FinancialSubmitforApproval',
|
||||
meta: {
|
||||
title: "提交审批",
|
||||
fullPath: "/financialSettlementCenter/submitforApproval",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "approvalCallback",
|
||||
component: () => import('@/views/FinancialSettlementCenter/ApprovalCallback.vue'),
|
||||
name: 'FinancialApprovalCallback',
|
||||
meta: {
|
||||
title: "审批回调",
|
||||
fullPath: "/financialSettlementCenter/approvalCallback",
|
||||
noCache: true,
|
||||
roles: ["财务"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// 运营——模型管理
|
||||
{
|
||||
path: "/modelManagement",
|
||||
|
||||
@ -22,7 +22,7 @@ const COMMON_ROUTE_PATHS = ['/product', '/tokenManagement', '/tokenUsage', '/mod
|
||||
const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/modelInfoConfig', '/operationReport', '/newsLog'];
|
||||
|
||||
// 财务角色需要额外补出来的菜单。
|
||||
const FINANCE_EXTRA_ROUTE_PATHS = ['/financialOverview'];
|
||||
const FINANCE_EXTRA_ROUTE_PATHS = ['/financialOverview', '/financialSettlementCenter'];
|
||||
|
||||
const ADMINISTRATOR_ROUTE_FULL_PATH = '/superAdministrator/roleManagement';
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ export default function getPageTitle(pageTitle) {
|
||||
return `开元云(北京)科技有限公司`
|
||||
}
|
||||
if (domainName.indexOf('ncmatch') > -1) {
|
||||
return `NCMatch`
|
||||
return `数智开物`
|
||||
}
|
||||
return `opencomputing`
|
||||
}
|
||||
|
||||
@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-circle-check"
|
||||
title="审批回调"
|
||||
description="处理审批结果。审批通过后自动结算记账,供应商调用 SettleAccounting,分销商写入账单和账本。"
|
||||
endpoint="GET /bill/finance_settlement_apv_callback.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loadingList" @click="loadList">
|
||||
<i class="el-icon-refresh" />{{ loadingList ? '刷新中...' : '刷新' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<div class="settlement-split-layout">
|
||||
<aside class="settlement-card settlement-selector">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-filter" />
|
||||
<span>审批中的结算单</span>
|
||||
<small class="settlement-card-subtitle">· 2</small>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-for="row in approvalRows"
|
||||
:key="row.id"
|
||||
type="button"
|
||||
class="settlement-selector-item"
|
||||
:class="{ 'is-active': selectedRow && row.id === selectedRow.id }"
|
||||
@click="selectRow(row)"
|
||||
>
|
||||
<span class="settlement-selector-no">{{ row.no }}</span>
|
||||
<span class="settlement-selector-name">{{ row.name }}</span>
|
||||
<span class="settlement-selector-date">{{ row.approvalId }}</span>
|
||||
<span style="display: block; margin-top: 7px;"><SettlementStatusBadge :status="row.status" /></span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<main style="min-width: 0;">
|
||||
<template v-if="selectedRow">
|
||||
<section class="settlement-card" style="margin-bottom: 20px;">
|
||||
<div class="settlement-detail-head">
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px;">
|
||||
<span class="settlement-page-icon" style="width: 40px; height: 40px; font-size: 19px;"><i class="el-icon-document" /></span>
|
||||
<div>
|
||||
<div class="settlement-detail-no">
|
||||
{{ selectedRow.no }}
|
||||
<SettlementStatusBadge :status="selectedRow.status" />
|
||||
</div>
|
||||
<div class="settlement-detail-meta">
|
||||
<i class="el-icon-office-building" /> {{ selectedRow.name }}
|
||||
<span style="margin: 0 7px; color: #cbd5e1;">|</span>
|
||||
<i class="el-icon-date" /> {{ selectedRow.start }} ~ {{ selectedRow.end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-metric-grid settlement-metric-grid--three" style="margin: 0; padding: 0 20px 20px;">
|
||||
<SettlementMetricCard label="销售金额" :value="selectedRow.sales" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="结算金额" :value="selectedRow.settlement" variant="emerald" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入" :value="selectedRow.income" variant="amber" icon="el-icon-s-marketing" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settlement-card" style="margin-bottom: 20px;">
|
||||
<div class="settlement-form-section">
|
||||
<div class="settlement-section-title">
|
||||
<span class="settlement-section-title-icon"><i class="el-icon-s-promotion" /></span>
|
||||
<div>
|
||||
<b>审批回调</b>
|
||||
<span>处理审批结果</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-form-stack">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">审批 ID *</label>
|
||||
<input v-model.trim="form.apv_id" class="settlement-input" type="text" placeholder="请输入审批实例 ID">
|
||||
<small style="display: block; margin-top: 6px; color: #94a3b8; font-size: 11px;">可传 apv_id 或 approval_id</small>
|
||||
</div>
|
||||
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">审批状态 *</label>
|
||||
<div class="settlement-callback-grid">
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.status === 'start' }" @click="form.status = 'start'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-time" />审批中</span>
|
||||
<p>审批进行中</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.status === 'agree' }" @click="form.status = 'agree'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-circle-check" />审批通过</span>
|
||||
<p>自动结算记账</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.status === 'refuse' }" @click="form.status = 'refuse'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-circle-close" />审批拒绝</span>
|
||||
<p>返回待处理状态</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.status === 'terminate' }" @click="form.status = 'terminate'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-remove-outline" />审批撤销</span>
|
||||
<p>终止当前审批流程</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="settlement-alert" style="margin: 0;">
|
||||
<span class="settlement-alert-icon"><i class="el-icon-circle-check" /></span>
|
||||
<div class="settlement-alert-content">
|
||||
<b>审批通过后执行记账</b>
|
||||
<p>供应商结算将调用 SettleAccounting;分销商结算将写入账单和账本。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="result" class="settlement-alert" style="margin: 0;">
|
||||
<span class="settlement-alert-icon"><i class="el-icon-circle-check" /></span>
|
||||
<div class="settlement-alert-content">
|
||||
<b>回调处理成功</b>
|
||||
<p>结算单 ID:{{ result.id || '-' }},处理状态:<SettlementStatusBadge :status="result.status" /></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="submitting" @click="submitCallback">
|
||||
<i class="el-icon-arrow-right" />{{ submitting ? '处理中...' : '提交回调' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-time" />
|
||||
<span>本次会话回调记录</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in history" :key="item.time" class="settlement-history-item">
|
||||
<div class="settlement-history-top">
|
||||
<span class="is-mono is-muted">{{ item.time }}</span>
|
||||
<SettlementStatusBadge :status="item.status" />
|
||||
</div>
|
||||
<div class="settlement-history-meta">
|
||||
<span>结算单:<b>{{ item.no }}</b></span>
|
||||
<span>审批 ID:<b>{{ item.apvId }}</b></span>
|
||||
<span>操作:<b>{{ item.action }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!history.length" class="settlement-history-item" style="color: #94a3b8;">暂无本次会话回调记录</div>
|
||||
</section>
|
||||
</template>
|
||||
<section v-else class="settlement-card settlement-card--padded" style="color: #94a3b8; text-align: center;">
|
||||
{{ loadingList ? '正在加载审批中的结算单...' : '暂无审批中的结算单' }}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { approvalCallbackAPI, settlementListAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getItems,
|
||||
getPayload,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'ApprovalCallback',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadingList: false,
|
||||
submitting: false,
|
||||
approvalRows: [],
|
||||
selectedRow: null,
|
||||
result: null,
|
||||
history: [],
|
||||
form: {
|
||||
apv_id: '',
|
||||
status: 'agree'
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
async loadList() {
|
||||
const accountingOrgid = getAccountingOrgId(this.$route)
|
||||
if (!accountingOrgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loadingList = true
|
||||
try {
|
||||
const payload = getPayload(await settlementListAPI({
|
||||
accounting_orgid: accountingOrgid,
|
||||
current_page: 1,
|
||||
page_size: 100
|
||||
}))
|
||||
this.approvalRows = getItems(payload).map(normalizeSettlement).filter(row => {
|
||||
return row.approvalId && ['approving', 'approved'].includes(row.status)
|
||||
})
|
||||
this.selectedRow = this.approvalRows[0] || null
|
||||
this.form.apv_id = this.selectedRow ? this.selectedRow.approvalId : ''
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '审批中结算单查询失败')
|
||||
} finally {
|
||||
this.loadingList = false
|
||||
}
|
||||
},
|
||||
selectRow(row) {
|
||||
this.selectedRow = row
|
||||
this.form.apv_id = row.approvalId || ''
|
||||
this.result = null
|
||||
},
|
||||
async submitCallback() {
|
||||
if (!this.form.apv_id) {
|
||||
this.$message.warning('请输入审批 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.submitting = true
|
||||
try {
|
||||
const payload = getPayload(await approvalCallbackAPI({
|
||||
apv_id: this.form.apv_id,
|
||||
status: this.form.status
|
||||
}))
|
||||
const item = Array.isArray(payload) ? payload[0] : (getItems(payload)[0] || payload)
|
||||
this.result = normalizeSettlement(item)
|
||||
this.history.unshift({
|
||||
time: new Date().toLocaleString('zh-CN'),
|
||||
no: this.selectedRow ? this.selectedRow.no : '-',
|
||||
apvId: this.form.apv_id,
|
||||
action: this.getActionLabel(),
|
||||
status: this.result.status
|
||||
})
|
||||
this.$message.success('审批回调处理成功')
|
||||
await this.loadList()
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '审批回调处理失败')
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
},
|
||||
getActionLabel() {
|
||||
return {
|
||||
start: '审批中',
|
||||
agree: '审批通过',
|
||||
refuse: '审批拒绝',
|
||||
terminate: '审批撤销'
|
||||
}[this.form.status]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-circle-plus-outline"
|
||||
title="创建结算单"
|
||||
description="创建结算单主表和明细快照。创建后状态为草稿(draft),同一账本、对手方和账期不能重复创建。"
|
||||
endpoint="GET /bill/finance_settlement_create.dspy"
|
||||
/>
|
||||
|
||||
<div class="settlement-form-layout">
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-form-section">
|
||||
<div class="settlement-section-title">
|
||||
<span class="settlement-section-title-icon"><i class="el-icon-circle-plus-outline" /></span>
|
||||
<div>
|
||||
<b>结算单信息</b>
|
||||
<span>填写结算单核心信息</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settlement-form-stack">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">对手方类型 *</label>
|
||||
<div class="settlement-option-grid">
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.counterparty_type === 'supplier' }" @click="form.counterparty_type = 'supplier'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-office-building" />供应商</span>
|
||||
<p>按待结转科目计算结算</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.counterparty_type === 'reseller' }" @click="form.counterparty_type = 'reseller'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-s-shop" />分销商</span>
|
||||
<p>按分销商资金科目计算</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">选择对手方 *</label>
|
||||
<input v-model.trim="form.counterparty_orgid" class="settlement-input" type="text" placeholder="请输入供应商或分销商机构 ID">
|
||||
</div>
|
||||
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期类型 *</label>
|
||||
<div class="settlement-option-grid">
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.period_type === 'day' }" @click="form.period_type = 'day'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-date" />日结</span>
|
||||
<p>按单日账期生成结算单</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-option-card" :class="{ 'is-active': form.period_type === 'month' }" @click="form.period_type = 'month'">
|
||||
<span class="settlement-option-card-head"><i class="el-icon-date" />月结</span>
|
||||
<p>按自然月账期生成结算单</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settlement-inline-grid">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期开始 *</label>
|
||||
<input v-model="form.period_start" class="settlement-input" type="date">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期结束 *</label>
|
||||
<input v-model="form.period_end" class="settlement-input" type="date">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">操作用户 ID</label>
|
||||
<input v-model.trim="form.userid" class="settlement-input" type="text" placeholder="请输入当前操作用户 ID">
|
||||
</div>
|
||||
|
||||
<div class="settlement-actions" style="padding-top: 3px;">
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="creating" @click="createSettlement">
|
||||
<i class="el-icon-circle-plus-outline" />{{ creating ? '创建中...' : '创建结算单' }}
|
||||
</button>
|
||||
<button type="button" class="settlement-btn" @click="goList">
|
||||
查看列表<i class="el-icon-arrow-right" style="margin: 0 0 0 6px;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside>
|
||||
<section class="settlement-card settlement-card--padded" style="margin-bottom: 18px;">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 16px;">
|
||||
<i class="el-icon-info" />
|
||||
<span>创建规则</span>
|
||||
</div>
|
||||
<ul class="settlement-help-list">
|
||||
<li>仅统计已记账账单(bill_state = '1')</li>
|
||||
<li>供应商按待结转科目且贷方计算</li>
|
||||
<li>分销商按分销商存放资金且借方计算</li>
|
||||
<li>平台收入由折扣收入和底价收入构成</li>
|
||||
<li>创建后状态为 draft,需提交审批</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="settlement-card settlement-card--padded">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 15px;">
|
||||
<i :class="result ? 'el-icon-circle-check' : 'el-icon-view'" />
|
||||
<span>{{ result ? '创建成功' : '本次创建预览' }}</span>
|
||||
</div>
|
||||
<div class="settlement-info-grid" style="grid-template-columns: 1fr; padding: 0;">
|
||||
<div>
|
||||
<div class="settlement-info-label">账本机构</div>
|
||||
<div class="settlement-info-value is-mono">{{ accountingOrgid || '-' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">结算对手方</div>
|
||||
<div class="settlement-info-value is-mono">{{ form.counterparty_orgid || '-' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">结算账期</div>
|
||||
<div class="settlement-info-value is-mono">{{ form.period_start }} ~ {{ form.period_end }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">{{ result ? '结算单号' : '预估状态' }}</div>
|
||||
<div v-if="result" class="settlement-info-value is-mono">{{ result.no || result.id }}</div>
|
||||
<SettlementStatusBadge v-else status="draft" />
|
||||
</div>
|
||||
<div v-if="result">
|
||||
<div class="settlement-info-label">创建状态</div>
|
||||
<SettlementStatusBadge :status="result.status" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { createSettlementStatementAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getCurrentUserId,
|
||||
getPayload,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'CreateSettlementStatement',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
creating: false,
|
||||
result: null,
|
||||
accountingOrgid: getAccountingOrgId(this.$route),
|
||||
form: {
|
||||
counterparty_type: 'supplier',
|
||||
counterparty_orgid: '',
|
||||
period_type: 'month',
|
||||
period_start: '2026-06-01',
|
||||
period_end: '2026-06-30',
|
||||
userid: getCurrentUserId()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createSettlement() {
|
||||
this.accountingOrgid = getAccountingOrgId(this.$route)
|
||||
if (!this.accountingOrgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
if (!this.form.counterparty_orgid || !this.form.userid) {
|
||||
this.$message.warning('请填写对手方机构 ID 和操作用户 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.creating = true
|
||||
try {
|
||||
const payload = getPayload(await createSettlementStatementAPI({
|
||||
accounting_orgid: this.accountingOrgid,
|
||||
counterparty_type: this.form.counterparty_type,
|
||||
counterparty_orgid: this.form.counterparty_orgid,
|
||||
period_type: this.form.period_type,
|
||||
period_start: this.form.period_start,
|
||||
period_end: this.form.period_end,
|
||||
userid: this.form.userid
|
||||
}))
|
||||
this.result = normalizeSettlement(payload.settlement || payload)
|
||||
this.$message.success('结算单创建成功')
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '创建结算单失败')
|
||||
} finally {
|
||||
this.creating = false
|
||||
}
|
||||
},
|
||||
goList() {
|
||||
this.$router.push({ name: 'FinancialSettlementStatementList' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-s-marketing"
|
||||
title="平台收入展示"
|
||||
description="展示账期内从供应商和分销商结算中获得的平台收入,包含趋势、占比及对手方贡献排行。"
|
||||
endpoint="GET /bill/finance_settlement_summary.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loading" @click="loadData">
|
||||
<i class="el-icon-refresh" />{{ loading ? '刷新中...' : '刷新' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<section class="settlement-card settlement-card--padded" style="margin-bottom: 24px;">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 18px;">
|
||||
<i class="el-icon-date" />
|
||||
<span>统计区间</span>
|
||||
</div>
|
||||
<div class="settlement-filter-grid settlement-filter-grid--three">
|
||||
<div class="settlement-inline-grid">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">开始日期</label>
|
||||
<input v-model="form.start_date" class="settlement-input" type="date">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">结束日期</label>
|
||||
<input v-model="form.end_date" class="settlement-input" type="date">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">快捷区间</label>
|
||||
<div class="settlement-actions">
|
||||
<button type="button" class="settlement-btn" @click="setRange('2026-01-01', '2026-12-31')">本年</button>
|
||||
<button type="button" class="settlement-btn" @click="setRange('2026-01-01', '2026-06-30')">近半年</button>
|
||||
<button type="button" class="settlement-btn" @click="setRange('2026-04-01', '2026-06-30')">本季度</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settlement-metric-grid">
|
||||
<SettlementMetricCard label="平台总收入" :value="money(totalIncome)" hint="折扣收入 + 底价收入" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="供应商贡献" :value="money(supplierIncome)" :hint="`占比 ${supplierRatio}`" variant="emerald" icon="el-icon-office-building" />
|
||||
<SettlementMetricCard label="分销商贡献" :value="money(resellerIncome)" :hint="`占比 ${resellerRatio}`" variant="violet" icon="el-icon-office-building" />
|
||||
<SettlementMetricCard label="参与对手方" :value="counterpartyCount" suffix="家" hint="有平台收入贡献" variant="amber" icon="el-icon-user" />
|
||||
</div>
|
||||
|
||||
<div class="settlement-chart-grid">
|
||||
<section class="settlement-card settlement-card--padded">
|
||||
<div class="settlement-card-heading" style="justify-content: space-between; margin-bottom: 8px;">
|
||||
<span><i class="el-icon-s-data" /> 月度平台收入趋势</span>
|
||||
<small class="settlement-card-subtitle">按账期聚合</small>
|
||||
</div>
|
||||
<div class="settlement-bar-chart">
|
||||
<div v-for="item in trendBars" :key="item.period" class="settlement-bar-group">
|
||||
<span class="settlement-bar" :style="{ height: item.supplier }" />
|
||||
<span class="settlement-bar settlement-bar--violet" :style="{ height: item.reseller }" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-bar-labels">
|
||||
<span v-for="item in trendBars" :key="`${item.period}-label`">{{ item.period }}</span>
|
||||
</div>
|
||||
<div class="settlement-chart-legend">
|
||||
<span>供应商</span>
|
||||
<span>分销商</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settlement-card settlement-card--padded">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 8px;">
|
||||
<i class="el-icon-pie-chart" />
|
||||
<span>收入构成</span>
|
||||
</div>
|
||||
<div class="settlement-donut-layout">
|
||||
<div class="settlement-donut" :style="{ background: donutBackground }">
|
||||
<div class="settlement-donut-value">
|
||||
<small>总计</small>{{ money(totalIncome) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-chart-legend">
|
||||
<span>供应商 {{ supplierRatio }}</span>
|
||||
<span>分销商 {{ resellerRatio }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-office-building" />
|
||||
<span>对手方贡献排行</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-table-wrap">
|
||||
<table class="settlement-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 72px;">排名</th>
|
||||
<th>对手方名称</th>
|
||||
<th>类型</th>
|
||||
<th class="is-number">平台收入</th>
|
||||
<th class="is-number">占总收入</th>
|
||||
<th>贡献度</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in incomeRanks" :key="row.name">
|
||||
<td><span class="settlement-rank" :class="`settlement-rank--${index + 1}`">{{ index + 1 }}</span></td>
|
||||
<td style="font-weight: 600;">{{ row.name }}</td>
|
||||
<td>
|
||||
<span class="settlement-pill" :class="row.type === '供应商' ? 'settlement-pill--cyan' : 'settlement-pill--violet'">
|
||||
{{ row.type }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="is-number is-amber">{{ row.income }}</td>
|
||||
<td class="is-number is-muted">{{ row.ratio }}</td>
|
||||
<td>
|
||||
<span class="settlement-contribution">
|
||||
<span class="settlement-contribution-bar"><span :style="{ width: row.width }" /></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!loading && !incomeRanks.length">
|
||||
<td colspan="6" style="padding: 42px; color: #94a3b8; text-align: center;">暂无平台收入数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import { summaryQueryAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getItems,
|
||||
getPayload,
|
||||
getSummary,
|
||||
money,
|
||||
normalizeSummaryItem,
|
||||
numberValue,
|
||||
percent
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'PlatformRevenue',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
start_date: '2026-01-01',
|
||||
end_date: '2026-06-30'
|
||||
},
|
||||
supplierItems: [],
|
||||
resellerItems: [],
|
||||
supplierIncome: 0,
|
||||
resellerIncome: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalIncome() {
|
||||
return this.supplierIncome + this.resellerIncome
|
||||
},
|
||||
supplierRatio() {
|
||||
return percent(this.supplierIncome, this.totalIncome)
|
||||
},
|
||||
resellerRatio() {
|
||||
return percent(this.resellerIncome, this.totalIncome)
|
||||
},
|
||||
counterpartyCount() {
|
||||
const names = this.supplierItems.concat(this.resellerItems).map(item => item.id || item.name).filter(Boolean)
|
||||
return Array.from(new Set(names)).length
|
||||
},
|
||||
donutBackground() {
|
||||
const ratio = numberValue(this.totalIncome) ? (this.supplierIncome / this.totalIncome) * 100 : 50
|
||||
return `conic-gradient(#0ea5e9 0 ${ratio}%, #a855f7 ${ratio}% 100%)`
|
||||
},
|
||||
trendBars() {
|
||||
const group = {}
|
||||
this.supplierItems.concat(this.resellerItems).forEach(item => {
|
||||
const period = item.period || '-'
|
||||
if (!group[period]) group[period] = { period, supplier: 0, reseller: 0 }
|
||||
group[period][item.typeKey === 'reseller' ? 'reseller' : 'supplier'] += item.incomeAmount
|
||||
})
|
||||
const items = Object.keys(group).sort().map(key => group[key])
|
||||
const max = Math.max.apply(null, items.map(item => Math.max(item.supplier, item.reseller)).concat([1]))
|
||||
return items.map(item => ({
|
||||
period: item.period,
|
||||
supplier: `${Math.max(5, (item.supplier / max) * 100)}%`,
|
||||
reseller: `${Math.max(5, (item.reseller / max) * 100)}%`
|
||||
}))
|
||||
},
|
||||
incomeRanks() {
|
||||
const groups = {}
|
||||
this.supplierItems.concat(this.resellerItems).forEach(item => {
|
||||
const key = item.id || item.name
|
||||
if (!groups[key]) {
|
||||
groups[key] = {
|
||||
name: item.name || '-',
|
||||
type: item.typeKey === 'reseller' ? '分销商' : '供应商',
|
||||
amount: 0
|
||||
}
|
||||
}
|
||||
groups[key].amount += item.incomeAmount
|
||||
})
|
||||
const rows = Object.keys(groups).map(key => groups[key]).sort((a, b) => b.amount - a.amount)
|
||||
const max = rows.length ? rows[0].amount : 0
|
||||
return rows.slice(0, 10).map(item => ({
|
||||
...item,
|
||||
income: money(item.amount),
|
||||
ratio: percent(item.amount, this.totalIncome),
|
||||
width: max ? `${(item.amount / max) * 100}%` : '0%'
|
||||
}))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
money,
|
||||
buildParams(counterpartyType) {
|
||||
return {
|
||||
accounting_orgid: getAccountingOrgId(this.$route),
|
||||
counterparty_type: counterpartyType,
|
||||
period_type: 'month',
|
||||
start_date: this.form.start_date,
|
||||
end_date: this.form.end_date,
|
||||
counterparty_orgid: '',
|
||||
current_page: 1,
|
||||
page_size: 1000
|
||||
}
|
||||
},
|
||||
async loadData() {
|
||||
const orgid = getAccountingOrgId(this.$route)
|
||||
if (!orgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const [supplierResponse, resellerResponse] = await Promise.all([
|
||||
summaryQueryAPI(this.buildParams('supplier')),
|
||||
summaryQueryAPI(this.buildParams('reseller'))
|
||||
])
|
||||
const supplierPayload = getPayload(supplierResponse)
|
||||
const resellerPayload = getPayload(resellerResponse)
|
||||
this.supplierItems = getItems(supplierPayload).map(item => ({
|
||||
...normalizeSummaryItem(item),
|
||||
typeKey: 'supplier'
|
||||
}))
|
||||
this.resellerItems = getItems(resellerPayload).map(item => ({
|
||||
...normalizeSummaryItem(item),
|
||||
typeKey: 'reseller'
|
||||
}))
|
||||
this.supplierIncome = getSummary(supplierPayload).incomeAmount || this.supplierItems.reduce((sum, item) => sum + item.incomeAmount, 0)
|
||||
this.resellerIncome = getSummary(resellerPayload).incomeAmount || this.resellerItems.reduce((sum, item) => sum + item.incomeAmount, 0)
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '平台收入查询失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
setRange(startDate, endDate) {
|
||||
this.form.start_date = startDate
|
||||
this.form.end_date = endDate
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-s-operation"
|
||||
title="流程总览"
|
||||
description="财务结算全流程闭环:汇总查询、预览核对、创建结算单、提交审批和审批回调。"
|
||||
endpoint="推荐调用流程"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loading" @click="loadData">
|
||||
<i class="el-icon-refresh" />{{ loading ? '刷新中...' : '刷新数据' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<div class="settlement-metric-grid">
|
||||
<SettlementMetricCard label="结算单总数" :value="settlementRows.length" suffix="个" icon="el-icon-tickets" />
|
||||
<SettlementMetricCard label="销售金额合计" :value="money(totals.sales)" variant="emerald" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="结算金额合计" :value="money(totals.settlement)" variant="amber" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入合计" :value="money(totals.income)" variant="violet" icon="el-icon-s-marketing" />
|
||||
</div>
|
||||
|
||||
<section class="settlement-card settlement-card--padded" style="margin-bottom: 24px;">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 22px;">
|
||||
<i class="el-icon-s-operation" />
|
||||
<span>结算流程闭环</span>
|
||||
<small class="settlement-card-subtitle">· 点击任一节点进入对应功能</small>
|
||||
</div>
|
||||
<div class="settlement-flow-grid">
|
||||
<div v-for="step in flowSteps" :key="step.index" class="settlement-flow-node" style="cursor: pointer;" @click="goStep(step.route)">
|
||||
<div class="settlement-flow-icon">
|
||||
<i :class="step.icon" />
|
||||
<span class="settlement-flow-index">{{ step.index }}</span>
|
||||
</div>
|
||||
<div class="settlement-card settlement-card--hover settlement-flow-content">
|
||||
<h3>{{ step.title }}</h3>
|
||||
<strong>{{ step.subtitle }}</strong>
|
||||
<p>{{ step.description }}</p>
|
||||
<code class="settlement-flow-endpoint">{{ step.endpoint }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 18px; margin-top: 21px; padding-top: 17px; color: #64748b; font-size: 12px; border-top: 1px solid #e2e8f0;">
|
||||
<span><i class="el-icon-s-data" style="color: #0ea5e9;" /> 数据查询</span>
|
||||
<span><i class="el-icon-document-add" style="color: #f59e0b;" /> 结算单操作</span>
|
||||
<span><i class="el-icon-circle-check" style="color: #10b981;" /> 审批记账</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settlement-two-column">
|
||||
<section class="settlement-card settlement-card--padded">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 9px;">
|
||||
<i class="el-icon-data-analysis" />
|
||||
<span>状态分布</span>
|
||||
</div>
|
||||
<div v-for="item in statusRows" :key="item.status" class="settlement-status-row">
|
||||
<SettlementStatusBadge :status="item.status" />
|
||||
<div class="settlement-progress-track">
|
||||
<div class="settlement-progress-value" :class="item.color" :style="{ width: item.width }" />
|
||||
</div>
|
||||
<span class="settlement-progress-number">{{ item.value }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settlement-card settlement-card--padded">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-video-play" />
|
||||
<span>快速操作</span>
|
||||
</div>
|
||||
<button v-for="item in quickActions" :key="item.title" type="button" class="settlement-quick-action" @click="goStep(item.route)">
|
||||
<b><i :class="item.icon" style="margin-right: 6px; color: #0284c7;" />{{ item.title }}</b>
|
||||
<span>{{ item.description }}</span>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-document" />
|
||||
<span>最近结算单</span>
|
||||
<small class="settlement-card-subtitle">· {{ settlementRows.length }} 条</small>
|
||||
</div>
|
||||
<button type="button" class="settlement-btn settlement-btn--ghost" @click="goStep('FinancialSettlementStatementList')">
|
||||
查看全部<i class="el-icon-arrow-right" style="margin: 0 0 0 5px;" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="settlement-table-wrap">
|
||||
<table class="settlement-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>结算单号</th>
|
||||
<th>对手方</th>
|
||||
<th>账期</th>
|
||||
<th>类型</th>
|
||||
<th class="is-number">销售金额</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in recentSettlements" :key="row.id" style="cursor: pointer;" @click="goDetail(row)">
|
||||
<td class="is-mono" style="font-weight: 700;">{{ row.no }}</td>
|
||||
<td>{{ row.name }}</td>
|
||||
<td class="is-mono is-muted">{{ row.start }} ~ {{ row.end }}</td>
|
||||
<td><span class="settlement-pill">{{ row.periodType }}</span></td>
|
||||
<td class="is-number">{{ row.sales }}</td>
|
||||
<td><SettlementStatusBadge :status="row.status" /></td>
|
||||
</tr>
|
||||
<tr v-if="!loading && !recentSettlements.length">
|
||||
<td colspan="6" style="padding: 42px; color: #94a3b8; text-align: center;">暂无结算单数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { settlementListAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getItems,
|
||||
getPayload,
|
||||
money,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
import { flowSteps } from './settlementMockData'
|
||||
|
||||
export default {
|
||||
name: 'ProcessOverview',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
flowSteps: flowSteps.map((step, index) => ({
|
||||
...step,
|
||||
route: [
|
||||
'FinancialSettlementSummaryQuery',
|
||||
'FinancialSettlementStatementPreview',
|
||||
'FinancialCreateSettlementStatement',
|
||||
'FinancialSubmitforApproval',
|
||||
'FinancialApprovalCallback'
|
||||
][index]
|
||||
})),
|
||||
settlementRows: [],
|
||||
quickActions: [
|
||||
{ icon: 'el-icon-view', title: '1. 预览结算单', description: '查看账期明细,检查重复', route: 'FinancialSettlementStatementPreview' },
|
||||
{ icon: 'el-icon-circle-plus-outline', title: '2. 创建结算单', description: '生成草稿状态结算单', route: 'FinancialCreateSettlementStatement' },
|
||||
{ icon: 'el-icon-s-promotion', title: '3. 提交审批', description: '送审并等待审批结果', route: 'FinancialSubmitforApproval' },
|
||||
{ icon: 'el-icon-circle-check', title: '4. 审批回调', description: '处理审批结果并完成记账', route: 'FinancialApprovalCallback' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totals() {
|
||||
return this.settlementRows.reduce((result, row) => ({
|
||||
sales: result.sales + row.salesAmount,
|
||||
settlement: result.settlement + row.settlementAmount,
|
||||
income: result.income + row.incomeAmount
|
||||
}), { sales: 0, settlement: 0, income: 0 })
|
||||
},
|
||||
recentSettlements() {
|
||||
return this.settlementRows.slice(0, 5)
|
||||
},
|
||||
statusRows() {
|
||||
const statusList = ['draft', 'approving', 'approved', 'settled', 'rejected', 'failed', 'cancelled']
|
||||
const colorMap = {
|
||||
approving: 'settlement-progress-value--amber',
|
||||
approved: 'settlement-progress-value--emerald',
|
||||
rejected: 'settlement-progress-value--rose',
|
||||
failed: 'settlement-progress-value--rose'
|
||||
}
|
||||
const total = this.settlementRows.length
|
||||
return statusList.map(status => {
|
||||
const count = this.settlementRows.filter(row => row.status === status).length
|
||||
const ratio = total ? (count / total) * 100 : 0
|
||||
return {
|
||||
status,
|
||||
width: `${ratio}%`,
|
||||
value: `${count} (${ratio.toFixed(0)}%)`,
|
||||
color: colorMap[status] || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
money,
|
||||
async loadData() {
|
||||
const accountingOrgid = getAccountingOrgId(this.$route)
|
||||
if (!accountingOrgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const payload = getPayload(await settlementListAPI({
|
||||
accounting_orgid: accountingOrgid,
|
||||
current_page: 1,
|
||||
page_size: 100
|
||||
}))
|
||||
this.settlementRows = getItems(payload).map(normalizeSettlement)
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '结算单概览加载失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
goStep(route) {
|
||||
if (route) this.$router.push({ name: route })
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$router.push({
|
||||
name: 'FinancialSettlementStatementDetails',
|
||||
query: { id: row.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="settlement-metric-card" :class="`settlement-metric-card--${variant}`">
|
||||
<span v-if="icon" class="settlement-metric-icon">
|
||||
<i :class="icon" />
|
||||
</span>
|
||||
<div class="settlement-metric-label">{{ label }}</div>
|
||||
<div class="settlement-metric-value">
|
||||
{{ value }}<span v-if="suffix" class="settlement-metric-suffix">{{ suffix }}</span>
|
||||
</div>
|
||||
<div v-if="hint" class="settlement-metric-hint">{{ hint }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettlementMetricCard',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: value => ['primary', 'emerald', 'amber', 'violet'].includes(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="settlement-page-header">
|
||||
<div class="settlement-page-heading">
|
||||
<span class="settlement-page-icon">
|
||||
<i :class="icon" />
|
||||
</span>
|
||||
<div class="settlement-page-title">
|
||||
<h1>{{ title }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
<span v-if="endpoint" class="settlement-endpoint">{{ endpoint }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.actions" class="settlement-header-actions">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettlementPageHeader',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'el-icon-s-data'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
endpoint: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-document"
|
||||
title="结算单详情"
|
||||
description="查询结算单主表信息和明细快照数据。"
|
||||
endpoint="GET /bill/finance_settlement_detail.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loadingDetail" @click="loadDetail">
|
||||
<i class="el-icon-refresh" />{{ loadingDetail ? '刷新中...' : '刷新' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<div class="settlement-split-layout">
|
||||
<aside class="settlement-card settlement-selector">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-tickets" />
|
||||
<span>结算单</span>
|
||||
<small class="settlement-card-subtitle">· {{ settlementRows.length }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-for="row in settlementRows"
|
||||
:key="row.id"
|
||||
type="button"
|
||||
class="settlement-selector-item"
|
||||
:class="{ 'is-active': selectedRow && row.id === selectedRow.id }"
|
||||
@click="selectRow(row)"
|
||||
>
|
||||
<span class="settlement-selector-no">{{ row.no }}</span>
|
||||
<span class="settlement-selector-name">{{ row.name }}</span>
|
||||
<span class="settlement-selector-date">{{ row.start }} ~ {{ row.end }}</span>
|
||||
<span style="display: block; margin-top: 7px;"><SettlementStatusBadge :status="row.status" /></span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<main style="min-width: 0;">
|
||||
<template v-if="selectedRow">
|
||||
<section class="settlement-card" style="margin-bottom: 20px;">
|
||||
<div class="settlement-detail-head">
|
||||
<div>
|
||||
<div class="settlement-detail-no">
|
||||
<i class="el-icon-document" style="color: #94a3b8; font-size: 17px;" />
|
||||
{{ selectedRow.no }}
|
||||
<SettlementStatusBadge :status="selectedRow.status" />
|
||||
</div>
|
||||
<div class="settlement-detail-meta">创建时间:<span class="is-mono">{{ selectedRow.createdAt }}</span></div>
|
||||
</div>
|
||||
<button v-if="isSubmittable(selectedRow.status)" type="button" class="settlement-btn settlement-btn--primary" @click="goSubmit">
|
||||
<i class="el-icon-s-promotion" />提交审批
|
||||
</button>
|
||||
</div>
|
||||
<div class="settlement-info-grid">
|
||||
<div>
|
||||
<div class="settlement-info-label">对手方类型</div>
|
||||
<div class="settlement-info-value"><i class="el-icon-office-building" style="margin-right: 5px; color: #94a3b8;" />{{ selectedRow.type }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">对手方 ID</div>
|
||||
<div class="settlement-info-value is-mono">{{ selectedRow.orgId }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">对手方名称</div>
|
||||
<div class="settlement-info-value">{{ selectedRow.name }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">账期类型</div>
|
||||
<div class="settlement-info-value"><i class="el-icon-date" style="margin-right: 5px; color: #94a3b8;" />{{ selectedRow.periodType }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">账期开始</div>
|
||||
<div class="settlement-info-value is-mono">{{ selectedRow.start }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settlement-info-label">账期结束</div>
|
||||
<div class="settlement-info-value is-mono">{{ selectedRow.end }}</div>
|
||||
</div>
|
||||
<div v-if="selectedRow.approvalId">
|
||||
<div class="settlement-info-label">审批 ID</div>
|
||||
<div class="settlement-info-value is-mono">{{ selectedRow.approvalId }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settlement-metric-grid settlement-metric-grid--three">
|
||||
<SettlementMetricCard label="销售金额" :value="selectedRow.sales" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="结算金额" :value="selectedRow.settlement" variant="emerald" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入" :value="selectedRow.income" variant="amber" icon="el-icon-s-marketing" />
|
||||
</div>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-document" />
|
||||
<span>结算明细快照</span>
|
||||
<small class="settlement-card-subtitle">· {{ detailTotal }} 笔</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-table-wrap">
|
||||
<table class="settlement-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账单 ID</th>
|
||||
<th>订单 ID</th>
|
||||
<th>账单日期</th>
|
||||
<th>销售模式</th>
|
||||
<th class="is-number">销售金额</th>
|
||||
<th class="is-number">结算金额</th>
|
||||
<th class="is-number">平台收入</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in billRows" :key="row.billId">
|
||||
<td class="is-mono">{{ row.billId }}</td>
|
||||
<td class="is-mono">{{ row.orderId }}</td>
|
||||
<td class="is-mono">{{ row.date }}</td>
|
||||
<td><span class="settlement-pill">{{ row.mode }}</span></td>
|
||||
<td class="is-number">{{ row.sales }}</td>
|
||||
<td class="is-number is-emerald">{{ row.settlement }}</td>
|
||||
<td class="is-number is-amber">{{ row.income }}</td>
|
||||
</tr>
|
||||
<tr v-if="!loadingDetail && !billRows.length">
|
||||
<td colspan="7" style="padding: 42px; color: #94a3b8; text-align: center;">暂无结算明细</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="settlement-table-footer">
|
||||
<span>第 {{ currentPage }} 页 · 每页 {{ pageSize }} 条 · 共 {{ detailTotal }} 条</span>
|
||||
<div class="settlement-pagination">
|
||||
<button type="button" class="settlement-btn" :disabled="currentPage <= 1 || loadingDetail" @click="changePage(-1)"><i class="el-icon-arrow-left" style="margin: 0;" /></button>
|
||||
<span class="settlement-page-number">{{ currentPage }} / {{ totalPages }}</span>
|
||||
<button type="button" class="settlement-btn" :disabled="currentPage >= totalPages || loadingDetail" @click="changePage(1)"><i class="el-icon-arrow-right" style="margin: 0;" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<section v-else class="settlement-card settlement-card--padded" style="color: #94a3b8; text-align: center;">
|
||||
{{ loadingList ? '正在加载结算单...' : '暂无可查看的结算单' }}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { settlementDetailsAPI, settlementListAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getCurrentPage,
|
||||
getItems,
|
||||
getPageSize,
|
||||
getPayload,
|
||||
getTotal,
|
||||
isSubmittable,
|
||||
normalizeBill,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'SettlementStatementDetails',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadingList: false,
|
||||
loadingDetail: false,
|
||||
settlementRows: [],
|
||||
selectedRow: null,
|
||||
billRows: [],
|
||||
detailTotal: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.max(1, Math.ceil(this.detailTotal / this.pageSize))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
isSubmittable,
|
||||
async loadList() {
|
||||
const accountingOrgid = getAccountingOrgId(this.$route)
|
||||
if (!accountingOrgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loadingList = true
|
||||
try {
|
||||
const payload = getPayload(await settlementListAPI({
|
||||
accounting_orgid: accountingOrgid,
|
||||
current_page: 1,
|
||||
page_size: 100
|
||||
}))
|
||||
this.settlementRows = getItems(payload).map(normalizeSettlement)
|
||||
const routeId = this.$route.query.id
|
||||
this.selectedRow = this.settlementRows.find(item => item.id === routeId) || this.settlementRows[0] || null
|
||||
if (this.selectedRow) await this.loadDetail()
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '结算单列表查询失败')
|
||||
} finally {
|
||||
this.loadingList = false
|
||||
}
|
||||
},
|
||||
async loadDetail() {
|
||||
if (!this.selectedRow || !this.selectedRow.id) return
|
||||
this.loadingDetail = true
|
||||
try {
|
||||
const payload = getPayload(await settlementDetailsAPI({
|
||||
settlement_id: this.selectedRow.id,
|
||||
current_page: this.currentPage,
|
||||
page_size: this.pageSize
|
||||
}))
|
||||
const settlement = payload.settlement || payload.header || payload
|
||||
if (settlement && typeof settlement === 'object' && !Array.isArray(settlement)) {
|
||||
this.selectedRow = normalizeSettlement(settlement)
|
||||
}
|
||||
this.billRows = getItems(payload).map(normalizeBill)
|
||||
this.detailTotal = getTotal(payload, this.billRows.length)
|
||||
this.currentPage = getCurrentPage(payload, this.currentPage)
|
||||
this.pageSize = getPageSize(payload, this.pageSize)
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '结算单详情查询失败')
|
||||
} finally {
|
||||
this.loadingDetail = false
|
||||
}
|
||||
},
|
||||
selectRow(row) {
|
||||
if (!row || row.id === (this.selectedRow && this.selectedRow.id)) return
|
||||
this.selectedRow = row
|
||||
this.currentPage = 1
|
||||
this.billRows = []
|
||||
this.$router.replace({
|
||||
name: 'FinancialSettlementStatementDetails',
|
||||
query: { id: row.id }
|
||||
})
|
||||
this.loadDetail()
|
||||
},
|
||||
changePage(offset) {
|
||||
const nextPage = this.currentPage + offset
|
||||
if (nextPage < 1 || nextPage > this.totalPages) return
|
||||
this.currentPage = nextPage
|
||||
this.loadDetail()
|
||||
},
|
||||
goSubmit() {
|
||||
this.$router.push({
|
||||
name: 'FinancialSubmitforApproval',
|
||||
query: { id: this.selectedRow.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-tickets"
|
||||
title="结算单列表"
|
||||
description="分页查询结算单主表数据,支持按对手方、状态、账期等条件筛选。"
|
||||
endpoint="GET /bill/finance_settlement_list.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn" :disabled="loading" @click="resetQuery">
|
||||
<i class="el-icon-refresh" />重置
|
||||
</button>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loading" @click="search">
|
||||
<i class="el-icon-search" />{{ loading ? '查询中...' : '查询' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<section class="settlement-card settlement-card--padded" style="margin-bottom: 24px;">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 18px;">
|
||||
<i class="el-icon-filter" />
|
||||
<span>筛选条件</span>
|
||||
</div>
|
||||
<div class="settlement-filter-grid settlement-filter-grid--six">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">对手方类型</label>
|
||||
<select v-model="form.counterparty_type" class="settlement-select">
|
||||
<option value="">全部</option>
|
||||
<option value="supplier">供应商</option>
|
||||
<option value="reseller">分销商</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">对手方</label>
|
||||
<input v-model.trim="form.counterparty_orgid" class="settlement-input" type="text" placeholder="可选:对手方机构 ID">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">状态</label>
|
||||
<select v-model="form.status" class="settlement-select">
|
||||
<option value="">全部</option>
|
||||
<option v-for="item in statusOptions" :key="item.value" :value="item.value">{{ item.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期类型</label>
|
||||
<select v-model="form.period_type" class="settlement-select">
|
||||
<option value="">全部</option>
|
||||
<option value="day">日结</option>
|
||||
<option value="month">月结</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">开始日期</label>
|
||||
<input v-model="form.start_date" class="settlement-input" type="date">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">结束日期</label>
|
||||
<input v-model="form.end_date" class="settlement-input" type="date">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settlement-metric-grid settlement-metric-grid--three">
|
||||
<SettlementMetricCard label="销售金额合计" :value="money(pageTotals.sales)" :hint="`当前页 ${settlementRows.length} 条`" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="结算金额合计" :value="money(pageTotals.settlement)" variant="emerald" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入合计" :value="money(pageTotals.income)" variant="amber" icon="el-icon-s-marketing" />
|
||||
</div>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-tickets" />
|
||||
<span>结算单列表</span>
|
||||
<small class="settlement-card-subtitle">· 共 {{ total }} 条</small>
|
||||
</div>
|
||||
<button type="button" class="settlement-btn settlement-btn--ghost" disabled>
|
||||
<i class="el-icon-download" />导出
|
||||
</button>
|
||||
</div>
|
||||
<div class="settlement-table-wrap">
|
||||
<table class="settlement-table" style="min-width: 1180px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>结算单号</th>
|
||||
<th>对手方</th>
|
||||
<th>账期</th>
|
||||
<th>账期类型</th>
|
||||
<th class="is-number">销售金额</th>
|
||||
<th class="is-number">结算金额</th>
|
||||
<th class="is-number">平台收入</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th class="is-number">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in settlementRows" :key="row.id">
|
||||
<td class="is-mono" style="font-weight: 700;">{{ row.no }}</td>
|
||||
<td>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span
|
||||
style="display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 7px;"
|
||||
:style="{ color: row.typeKey === 'supplier' ? '#0284c7' : '#7c3aed', background: row.typeKey === 'supplier' ? '#f0f9ff' : '#f5f3ff' }"
|
||||
><i class="el-icon-office-building" /></span>
|
||||
<span>
|
||||
<b style="display: block; color: #334155; font-size: 12px;">{{ row.name }}</b>
|
||||
<small class="is-mono is-muted">{{ row.orgId }}</small>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="is-mono is-muted">{{ row.start }} ~ {{ row.end }}</td>
|
||||
<td><span class="settlement-pill">{{ row.periodType }}</span></td>
|
||||
<td class="is-number">{{ row.sales }}</td>
|
||||
<td class="is-number is-emerald">{{ row.settlement }}</td>
|
||||
<td class="is-number is-amber">{{ row.income }}</td>
|
||||
<td>
|
||||
<SettlementStatusBadge :status="row.status" />
|
||||
<small v-if="row.reason" style="display: block; max-width: 146px; margin-top: 5px; overflow: hidden; color: #e11d48; font-size: 10px; text-overflow: ellipsis; white-space: nowrap;">{{ row.reason }}</small>
|
||||
</td>
|
||||
<td class="is-mono is-muted">{{ row.createdAt }}</td>
|
||||
<td class="is-number">
|
||||
<button type="button" class="settlement-btn settlement-btn--ghost" title="查看详情" @click="openDetail(row)"><i class="el-icon-view" style="margin: 0;" /></button>
|
||||
<button v-if="isSubmittable(row.status)" type="button" class="settlement-btn settlement-btn--ghost" title="提交审批" @click="goSubmit(row)"><i class="el-icon-s-promotion" style="margin: 0;" /></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!loading && !settlementRows.length">
|
||||
<td colspan="10" style="padding: 42px; color: #94a3b8; text-align: center;">暂无结算单数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="settlement-table-footer">
|
||||
<span>第 {{ form.current_page }} 页 · 每页 {{ form.page_size }} 条 · 共 {{ total }} 条</span>
|
||||
<div class="settlement-pagination">
|
||||
<button type="button" class="settlement-btn" :disabled="form.current_page <= 1 || loading" @click="changePage(-1)"><i class="el-icon-arrow-left" style="margin: 0;" /></button>
|
||||
<span class="settlement-page-number">{{ form.current_page }} / {{ totalPages }}</span>
|
||||
<button type="button" class="settlement-btn" :disabled="form.current_page >= totalPages || loading" @click="changePage(1)"><i class="el-icon-arrow-right" style="margin: 0;" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { settlementListAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getCurrentPage,
|
||||
getItems,
|
||||
getPageSize,
|
||||
getPayload,
|
||||
getTotal,
|
||||
isSubmittable,
|
||||
money,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'SettlementStatementList',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
total: 0,
|
||||
settlementRows: [],
|
||||
statusOptions: [
|
||||
{ value: 'draft', label: '草稿' },
|
||||
{ value: 'approving', label: '审批中' },
|
||||
{ value: 'approved', label: '已审批' },
|
||||
{ value: 'rejected', label: '已拒绝' },
|
||||
{ value: 'settled', label: '已结算' },
|
||||
{ value: 'failed', label: '结算失败' },
|
||||
{ value: 'cancelled', label: '已撤销' }
|
||||
],
|
||||
form: {
|
||||
counterparty_type: '',
|
||||
counterparty_orgid: '',
|
||||
status: '',
|
||||
period_type: '',
|
||||
start_date: '2026-06-01',
|
||||
end_date: '2026-06-30',
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.max(1, Math.ceil(this.total / this.form.page_size))
|
||||
},
|
||||
pageTotals() {
|
||||
return this.settlementRows.reduce((totals, row) => ({
|
||||
sales: totals.sales + row.salesAmount,
|
||||
settlement: totals.settlement + row.settlementAmount,
|
||||
income: totals.income + row.incomeAmount
|
||||
}), { sales: 0, settlement: 0, income: 0 })
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
money,
|
||||
isSubmittable,
|
||||
getParams() {
|
||||
return {
|
||||
accounting_orgid: getAccountingOrgId(this.$route),
|
||||
counterparty_type: this.form.counterparty_type || undefined,
|
||||
counterparty_orgid: this.form.counterparty_orgid || undefined,
|
||||
status: this.form.status || undefined,
|
||||
period_type: this.form.period_type || undefined,
|
||||
start_date: this.form.start_date || undefined,
|
||||
end_date: this.form.end_date || undefined,
|
||||
current_page: this.form.current_page,
|
||||
page_size: this.form.page_size
|
||||
}
|
||||
},
|
||||
async loadData() {
|
||||
const params = this.getParams()
|
||||
if (!params.accounting_orgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const payload = getPayload(await settlementListAPI(params))
|
||||
this.settlementRows = getItems(payload).map(normalizeSettlement)
|
||||
this.total = getTotal(payload, this.settlementRows.length)
|
||||
this.form.current_page = getCurrentPage(payload, this.form.current_page)
|
||||
this.form.page_size = getPageSize(payload, this.form.page_size)
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '结算单列表查询失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
search() {
|
||||
this.form.current_page = 1
|
||||
this.loadData()
|
||||
},
|
||||
resetQuery() {
|
||||
this.form = {
|
||||
counterparty_type: '',
|
||||
counterparty_orgid: '',
|
||||
status: '',
|
||||
period_type: '',
|
||||
start_date: '2026-06-01',
|
||||
end_date: '2026-06-30',
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
changePage(offset) {
|
||||
const nextPage = this.form.current_page + offset
|
||||
if (nextPage < 1 || nextPage > this.totalPages) return
|
||||
this.form.current_page = nextPage
|
||||
this.loadData()
|
||||
},
|
||||
openDetail(row) {
|
||||
this.$router.push({
|
||||
name: 'FinancialSettlementStatementDetails',
|
||||
query: { id: row.id }
|
||||
})
|
||||
},
|
||||
goSubmit(row) {
|
||||
this.$router.push({
|
||||
name: 'FinancialSubmitforApproval',
|
||||
query: { id: row.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-view"
|
||||
title="结算单预览"
|
||||
description="创建结算单前预览明细,检查指定账期是否已有结算单,并返回可结算账单明细。"
|
||||
endpoint="GET /bill/finance_settlement_preview.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loading" @click="loadPreview">
|
||||
<i class="el-icon-view" />{{ loading ? '预览中...' : '预览' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<section class="settlement-card settlement-card--padded" style="margin-bottom: 24px;">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 18px;">
|
||||
<i class="el-icon-filter" />
|
||||
<span>预览条件</span>
|
||||
</div>
|
||||
<div class="settlement-filter-grid">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">对手方类型</label>
|
||||
<div class="settlement-choice-group">
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.counterparty_type === 'supplier' }" @click="form.counterparty_type = 'supplier'">供应商</button>
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.counterparty_type === 'reseller' }" @click="form.counterparty_type = 'reseller'">分销商</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">对手方</label>
|
||||
<input v-model.trim="form.counterparty_orgid" class="settlement-input" type="text" placeholder="请输入供应商或分销商机构 ID">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期类型</label>
|
||||
<div class="settlement-choice-group">
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.period_type === 'day' }" @click="form.period_type = 'day'">日结</button>
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.period_type === 'month' }" @click="form.period_type = 'month'">月结</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期开始</label>
|
||||
<input v-model="form.period_start" class="settlement-input" type="date">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期结束</label>
|
||||
<input v-model="form.period_end" class="settlement-input" type="date">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="previewed && existingSettlement" class="settlement-alert settlement-alert--warning">
|
||||
<span class="settlement-alert-icon"><i class="el-icon-warning-outline" /></span>
|
||||
<div class="settlement-alert-content">
|
||||
<b>该账期已存在结算单</b>
|
||||
<p>结算单号:{{ existingSettlement.no }},当前状态:<SettlementStatusBadge :status="existingSettlement.status" /></p>
|
||||
</div>
|
||||
</section>
|
||||
<section v-else-if="previewed" class="settlement-alert">
|
||||
<span class="settlement-alert-icon"><i class="el-icon-circle-check" /></span>
|
||||
<div class="settlement-alert-content">
|
||||
<b>可创建结算单</b>
|
||||
<p>当前账期未存在结算单,可以创建新的结算单。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settlement-metric-grid">
|
||||
<SettlementMetricCard label="销售金额" :value="summary.sales" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="应结算金额" :value="summary.settlement" variant="emerald" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入" :value="summary.income" variant="amber" icon="el-icon-s-marketing" />
|
||||
<SettlementMetricCard label="账单数量" :value="summary.billCount" suffix="笔" variant="violet" icon="el-icon-document" />
|
||||
</div>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-document" />
|
||||
<span>可结算账单明细</span>
|
||||
<small class="settlement-card-subtitle">· {{ billRows.length }} 笔</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-table-wrap">
|
||||
<table class="settlement-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账单 ID</th>
|
||||
<th>订单 ID</th>
|
||||
<th>账单日期</th>
|
||||
<th>销售模式</th>
|
||||
<th class="is-number">销售金额</th>
|
||||
<th class="is-number">结算金额</th>
|
||||
<th class="is-number">平台收入</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in billRows" :key="row.billId">
|
||||
<td class="is-mono">{{ row.billId }}</td>
|
||||
<td class="is-mono">{{ row.orderId }}</td>
|
||||
<td><span class="is-mono"><i class="el-icon-date" style="margin-right: 4px; color: #94a3b8;" />{{ row.date }}</span></td>
|
||||
<td><span class="settlement-pill">{{ row.mode }}</span></td>
|
||||
<td class="is-number">{{ row.sales }}</td>
|
||||
<td class="is-number is-emerald">{{ row.settlement }}</td>
|
||||
<td class="is-number is-amber">{{ row.income }}</td>
|
||||
</tr>
|
||||
<tr v-if="previewed && !loading && !billRows.length">
|
||||
<td colspan="7" style="padding: 42px; color: #94a3b8; text-align: center;">暂无可结算账单</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { settlementPreviewAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getItems,
|
||||
getPayload,
|
||||
getSummary,
|
||||
normalizeBill,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'SettlementStatementPreview',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
previewed: false,
|
||||
existingSettlement: null,
|
||||
billRows: [],
|
||||
summary: {
|
||||
sales: '¥0.00',
|
||||
settlement: '¥0.00',
|
||||
income: '¥0.00',
|
||||
billCount: 0
|
||||
},
|
||||
form: {
|
||||
counterparty_type: 'reseller',
|
||||
counterparty_orgid: '',
|
||||
period_type: 'month',
|
||||
period_start: '2026-06-01',
|
||||
period_end: '2026-06-30',
|
||||
current_page: 1,
|
||||
page_size: 50
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadPreview() {
|
||||
const accountingOrgid = getAccountingOrgId(this.$route)
|
||||
if (!accountingOrgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
if (!this.form.counterparty_orgid) {
|
||||
this.$message.warning('请输入对手方机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const payload = getPayload(await settlementPreviewAPI({
|
||||
accounting_orgid: accountingOrgid,
|
||||
counterparty_type: this.form.counterparty_type,
|
||||
counterparty_orgid: this.form.counterparty_orgid,
|
||||
period_type: this.form.period_type,
|
||||
period_start: this.form.period_start,
|
||||
period_end: this.form.period_end,
|
||||
current_page: this.form.current_page,
|
||||
page_size: this.form.page_size
|
||||
}))
|
||||
this.summary = getSummary(payload)
|
||||
this.billRows = getItems(payload).map(normalizeBill)
|
||||
const existing = payload.existing_settlement || payload.existingSettlement
|
||||
this.existingSettlement = existing ? normalizeSettlement(existing) : null
|
||||
this.previewed = true
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '结算单预览失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<span class="settlement-status" :class="`settlement-status--${status}`">
|
||||
<i :class="statusIcon" />
|
||||
{{ statusLabel }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const STATUS_MAP = {
|
||||
draft: { label: '草稿', icon: 'el-icon-document' },
|
||||
approving: { label: '审批中', icon: 'el-icon-time' },
|
||||
approved: { label: '已审批', icon: 'el-icon-circle-check' },
|
||||
rejected: { label: '已拒绝', icon: 'el-icon-circle-close' },
|
||||
settled: { label: '已结算', icon: 'el-icon-success' },
|
||||
failed: { label: '结算失败', icon: 'el-icon-warning-outline' },
|
||||
cancelled: { label: '已撤销', icon: 'el-icon-remove-outline' }
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SettlementStatusBadge',
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: 'draft'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
statusConfig() {
|
||||
return STATUS_MAP[this.status] || STATUS_MAP.draft
|
||||
},
|
||||
statusLabel() {
|
||||
return this.statusConfig.label
|
||||
},
|
||||
statusIcon() {
|
||||
return this.statusConfig.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-s-promotion"
|
||||
title="提交审批"
|
||||
description="将草稿、审批拒绝或结算失败的结算单提交审批。提交成功后状态更新为审批中(approving)。"
|
||||
endpoint="GET /bill/finance_settlement_submit.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loadingList" @click="loadList">
|
||||
<i class="el-icon-refresh" />{{ loadingList ? '刷新中...' : '刷新' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<div class="settlement-split-layout">
|
||||
<aside class="settlement-card settlement-selector">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-filter" />
|
||||
<span>待提交结算单</span>
|
||||
<small class="settlement-card-subtitle">· 3</small>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-for="row in submitRows"
|
||||
:key="row.id"
|
||||
type="button"
|
||||
class="settlement-selector-item"
|
||||
:class="{ 'is-active': selectedRow && row.id === selectedRow.id }"
|
||||
@click="selectRow(row)"
|
||||
>
|
||||
<span class="settlement-selector-no">{{ row.no }}</span>
|
||||
<span class="settlement-selector-name">{{ row.name }}</span>
|
||||
<span class="settlement-selector-date">{{ row.start }} ~ {{ row.end }}</span>
|
||||
<span style="display: block; margin-top: 7px;"><SettlementStatusBadge :status="row.status" /></span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<main style="min-width: 0;">
|
||||
<template v-if="selectedRow">
|
||||
<section class="settlement-card" style="margin-bottom: 20px;">
|
||||
<div class="settlement-detail-head">
|
||||
<div>
|
||||
<div class="settlement-detail-no">
|
||||
<i class="el-icon-document" style="color: #0ea5e9; font-size: 17px;" />
|
||||
{{ selectedRow.no }}
|
||||
<SettlementStatusBadge :status="selectedRow.status" />
|
||||
</div>
|
||||
<div class="settlement-detail-meta">
|
||||
<i class="el-icon-office-building" /> {{ selectedRow.name }}
|
||||
<span style="margin: 0 7px; color: #cbd5e1;">|</span>
|
||||
<i class="el-icon-date" /> {{ selectedRow.start }} ~ {{ selectedRow.end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-metric-grid settlement-metric-grid--three" style="margin: 0; padding: 0 20px 20px;">
|
||||
<SettlementMetricCard label="销售金额" :value="selectedRow.sales" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="结算金额" :value="selectedRow.settlement" variant="emerald" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入" :value="selectedRow.income" variant="amber" icon="el-icon-s-marketing" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-form-section">
|
||||
<div class="settlement-section-title">
|
||||
<span class="settlement-section-title-icon"><i class="el-icon-s-promotion" /></span>
|
||||
<div>
|
||||
<b>提交审批</b>
|
||||
<span>填写审批信息后提交</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-inline-grid">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">操作用户 ID *</label>
|
||||
<input v-model.trim="form.userid" class="settlement-input" type="text" placeholder="请输入当前操作用户 ID">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">审批业务名</label>
|
||||
<input v-model.trim="form.business_name" class="settlement-input" type="text" placeholder="财务结算">
|
||||
<small style="display: block; margin-top: 6px; color: #94a3b8; font-size: 11px;">需要在 apv_business 表中存在该业务配置</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="settlement-alert settlement-alert--warning" style="margin: 20px 0;">
|
||||
<span class="settlement-alert-icon"><i class="el-icon-warning-outline" /></span>
|
||||
<div class="settlement-alert-content">
|
||||
<b>提交前请确认结算数据</b>
|
||||
<p>提交后结算单状态将变为审批中,等待审批系统回调结果。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="result" class="settlement-alert" style="margin: 0 0 20px;">
|
||||
<span class="settlement-alert-icon"><i class="el-icon-circle-check" /></span>
|
||||
<div class="settlement-alert-content">
|
||||
<b>提交成功</b>
|
||||
<p>审批 ID:{{ result.approvalId || '-' }},当前状态:<SettlementStatusBadge :status="result.status" /></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="submitting" @click="submitApproval">
|
||||
<i class="el-icon-s-promotion" />{{ submitting ? '提交中...' : '提交审批' }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<section v-else class="settlement-card settlement-card--padded" style="color: #94a3b8; text-align: center;">
|
||||
{{ loadingList ? '正在加载待提交结算单...' : '暂无可提交审批的结算单' }}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import SettlementStatusBadge from './SettlementStatusBadge'
|
||||
import { settlementListAPI, submitApprovalAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getCurrentUserId,
|
||||
getItems,
|
||||
getPayload,
|
||||
isSubmittable,
|
||||
normalizeSettlement
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'SubmitforApproval',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard,
|
||||
SettlementStatusBadge
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadingList: false,
|
||||
submitting: false,
|
||||
submitRows: [],
|
||||
selectedRow: null,
|
||||
result: null,
|
||||
form: {
|
||||
userid: getCurrentUserId(),
|
||||
business_name: '财务结算'
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
async loadList() {
|
||||
const accountingOrgid = getAccountingOrgId(this.$route)
|
||||
if (!accountingOrgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loadingList = true
|
||||
try {
|
||||
const payload = getPayload(await settlementListAPI({
|
||||
accounting_orgid: accountingOrgid,
|
||||
current_page: 1,
|
||||
page_size: 100
|
||||
}))
|
||||
this.submitRows = getItems(payload).map(normalizeSettlement).filter(row => isSubmittable(row.status))
|
||||
const routeId = this.$route.query.id
|
||||
this.selectedRow = this.submitRows.find(row => row.id === routeId) || this.submitRows[0] || null
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '待提交结算单查询失败')
|
||||
} finally {
|
||||
this.loadingList = false
|
||||
}
|
||||
},
|
||||
selectRow(row) {
|
||||
this.selectedRow = row
|
||||
this.result = null
|
||||
this.$router.replace({
|
||||
name: 'FinancialSubmitforApproval',
|
||||
query: { id: row.id }
|
||||
})
|
||||
},
|
||||
async submitApproval() {
|
||||
if (!this.selectedRow) return
|
||||
if (!this.form.userid || !this.form.business_name) {
|
||||
this.$message.warning('请填写操作用户 ID 和审批业务名')
|
||||
return
|
||||
}
|
||||
|
||||
this.submitting = true
|
||||
try {
|
||||
const payload = getPayload(await submitApprovalAPI({
|
||||
settlement_id: this.selectedRow.id,
|
||||
userid: this.form.userid,
|
||||
business_name: this.form.business_name
|
||||
}))
|
||||
this.result = normalizeSettlement(payload.settlement || payload)
|
||||
this.result.status = this.result.status === 'draft' ? 'approving' : this.result.status
|
||||
this.$message.success('审批提交成功')
|
||||
await this.loadList()
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '提交审批失败')
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
241
f/web-kboss/src/views/FinancialSettlementCenter/SummaryQuery.vue
Normal file
241
f/web-kboss/src/views/FinancialSettlementCenter/SummaryQuery.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<SettlementPageFrame>
|
||||
<SettlementPageHeader
|
||||
icon="el-icon-s-data"
|
||||
title="汇总查询"
|
||||
description="查询供应商或分销商在指定账期内的销售金额、结算金额、平台收入和账单数量。"
|
||||
endpoint="GET /bill/finance_settlement_summary.dspy"
|
||||
>
|
||||
<template #actions>
|
||||
<button type="button" class="settlement-btn" :disabled="loading" @click="resetQuery">
|
||||
<i class="el-icon-refresh" />重置
|
||||
</button>
|
||||
<button type="button" class="settlement-btn settlement-btn--primary" :disabled="loading" @click="search">
|
||||
<i class="el-icon-search" />{{ loading ? '查询中...' : '查询' }}
|
||||
</button>
|
||||
</template>
|
||||
</SettlementPageHeader>
|
||||
|
||||
<section class="settlement-card settlement-card--padded" style="margin-bottom: 24px;">
|
||||
<div class="settlement-card-heading" style="margin-bottom: 18px;">
|
||||
<i class="el-icon-filter" />
|
||||
<span>查询条件</span>
|
||||
</div>
|
||||
<div class="settlement-filter-grid">
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">对手方类型</label>
|
||||
<div class="settlement-choice-group">
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.counterparty_type === 'supplier' }" @click="setCounterpartyType('supplier')">供应商</button>
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.counterparty_type === 'reseller' }" @click="setCounterpartyType('reseller')">分销商</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">账期类型</label>
|
||||
<div class="settlement-choice-group">
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.period_type === 'day' }" @click="setPeriodType('day')">日结</button>
|
||||
<button type="button" class="settlement-choice" :class="{ 'is-active': form.period_type === 'month' }" @click="setPeriodType('month')">月结</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">开始日期</label>
|
||||
<input v-model="form.start_date" class="settlement-input" type="date">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">结束日期</label>
|
||||
<input v-model="form.end_date" class="settlement-input" type="date">
|
||||
</div>
|
||||
<div class="settlement-field">
|
||||
<label class="settlement-label">指定对手方</label>
|
||||
<input v-model.trim="form.counterparty_orgid" class="settlement-input" type="text" placeholder="可选:对手方机构 ID">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settlement-metric-grid">
|
||||
<SettlementMetricCard label="销售金额合计" :value="summary.sales" hint="汇总销售总额" icon="el-icon-wallet" />
|
||||
<SettlementMetricCard label="结算金额合计" :value="summary.settlement" hint="应结算给对手方" variant="emerald" icon="el-icon-s-order" />
|
||||
<SettlementMetricCard label="平台收入合计" :value="summary.income" hint="折扣收入 + 底价收入" variant="amber" icon="el-icon-s-marketing" />
|
||||
<SettlementMetricCard label="账单数量" :value="summary.billCount" suffix="笔" hint="已记账账单" variant="violet" icon="el-icon-document" />
|
||||
</div>
|
||||
|
||||
<section class="settlement-card">
|
||||
<div class="settlement-card-head">
|
||||
<div class="settlement-card-heading">
|
||||
<i class="el-icon-office-building" />
|
||||
<span>汇总明细</span>
|
||||
<small class="settlement-card-subtitle">· 共 {{ total }} 条</small>
|
||||
</div>
|
||||
<button type="button" class="settlement-btn settlement-btn--ghost" disabled>
|
||||
<i class="el-icon-download" />导出
|
||||
</button>
|
||||
</div>
|
||||
<div class="settlement-table-wrap">
|
||||
<table class="settlement-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账期</th>
|
||||
<th>对手方 ID</th>
|
||||
<th>对手方名称</th>
|
||||
<th class="is-number">销售金额</th>
|
||||
<th class="is-number">结算金额</th>
|
||||
<th class="is-number">平台收入</th>
|
||||
<th class="is-number">账单数</th>
|
||||
<th class="is-number">收入占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in summaryRows" :key="row.id">
|
||||
<td><span class="is-mono"><i class="el-icon-date" style="margin-right: 5px; color: #94a3b8;" />{{ row.period }}</span></td>
|
||||
<td class="is-mono is-muted">{{ row.id }}</td>
|
||||
<td style="font-weight: 600;">{{ row.name }}</td>
|
||||
<td class="is-number">{{ row.sales }}</td>
|
||||
<td class="is-number is-emerald">{{ row.settlement }}</td>
|
||||
<td class="is-number is-amber">{{ row.income }}</td>
|
||||
<td class="is-number is-muted">{{ row.bills }}</td>
|
||||
<td class="is-number">
|
||||
<span class="settlement-contribution">
|
||||
<span class="settlement-contribution-bar"><span :style="{ width: row.ratio }" /></span>
|
||||
<small style="font-size: 11px; color: #64748b;">{{ row.ratio }}</small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!loading && !summaryRows.length">
|
||||
<td colspan="8" style="padding: 42px; color: #94a3b8; text-align: center;">暂无查询数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="settlement-table-footer">
|
||||
<span>第 {{ form.current_page }} 页 · 每页 {{ form.page_size }} 条 · 共 {{ total }} 条</span>
|
||||
<div class="settlement-pagination">
|
||||
<button type="button" class="settlement-btn" :disabled="form.current_page <= 1 || loading" @click="changePage(-1)"><i class="el-icon-arrow-left" style="margin: 0;" /></button>
|
||||
<span class="settlement-page-number">{{ form.current_page }} / {{ totalPages }}</span>
|
||||
<button type="button" class="settlement-btn" :disabled="form.current_page >= totalPages || loading" @click="changePage(1)"><i class="el-icon-arrow-right" style="margin: 0;" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SettlementPageFrame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettlementPageFrame from './SettlementPageFrame'
|
||||
import SettlementPageHeader from './SettlementPageHeader'
|
||||
import SettlementMetricCard from './SettlementMetricCard'
|
||||
import { summaryQueryAPI } from '@/api/FinancialSettlementCenter'
|
||||
import {
|
||||
getAccountingOrgId,
|
||||
getCurrentPage,
|
||||
getItems,
|
||||
getPageSize,
|
||||
getPayload,
|
||||
getSummary,
|
||||
getTotal,
|
||||
normalizeSummaryItem,
|
||||
percent
|
||||
} from './settlementHelpers'
|
||||
|
||||
export default {
|
||||
name: 'SummaryQuery',
|
||||
components: {
|
||||
SettlementPageFrame,
|
||||
SettlementPageHeader,
|
||||
SettlementMetricCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
total: 0,
|
||||
summaryRows: [],
|
||||
summary: {
|
||||
sales: '¥0.00',
|
||||
settlement: '¥0.00',
|
||||
income: '¥0.00',
|
||||
billCount: 0
|
||||
},
|
||||
form: {
|
||||
counterparty_type: 'supplier',
|
||||
period_type: 'month',
|
||||
start_date: '2026-03-01',
|
||||
end_date: '2026-06-30',
|
||||
counterparty_orgid: '',
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.max(1, Math.ceil(this.total / this.form.page_size))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
getParams() {
|
||||
return {
|
||||
accounting_orgid: getAccountingOrgId(this.$route),
|
||||
counterparty_type: this.form.counterparty_type,
|
||||
period_type: this.form.period_type,
|
||||
start_date: this.form.start_date,
|
||||
end_date: this.form.end_date,
|
||||
counterparty_orgid: this.form.counterparty_orgid || undefined,
|
||||
current_page: this.form.current_page,
|
||||
page_size: this.form.page_size
|
||||
}
|
||||
},
|
||||
async loadData() {
|
||||
const params = this.getParams()
|
||||
if (!params.accounting_orgid) {
|
||||
this.$message.warning('未获取到核算机构 ID')
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const payload = getPayload(await summaryQueryAPI(params))
|
||||
this.summary = getSummary(payload)
|
||||
this.summaryRows = getItems(payload).map(normalizeSummaryItem).map(item => ({
|
||||
...item,
|
||||
ratio: percent(item.salesAmount, this.summary.salesAmount)
|
||||
}))
|
||||
this.total = getTotal(payload, this.summaryRows.length)
|
||||
this.form.current_page = getCurrentPage(payload, this.form.current_page)
|
||||
this.form.page_size = getPageSize(payload, this.form.page_size)
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '汇总查询失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
search() {
|
||||
this.form.current_page = 1
|
||||
this.loadData()
|
||||
},
|
||||
resetQuery() {
|
||||
this.form = {
|
||||
counterparty_type: 'supplier',
|
||||
period_type: 'month',
|
||||
start_date: '2026-03-01',
|
||||
end_date: '2026-06-30',
|
||||
counterparty_orgid: '',
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
setCounterpartyType(type) {
|
||||
this.form.counterparty_type = type
|
||||
},
|
||||
setPeriodType(type) {
|
||||
this.form.period_type = type
|
||||
},
|
||||
changePage(offset) {
|
||||
const nextPage = this.form.current_page + offset
|
||||
if (nextPage < 1 || nextPage > this.totalPages) return
|
||||
this.form.current_page = nextPage
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,215 @@
|
||||
const TYPE_LABELS = {
|
||||
supplier: '供应商',
|
||||
reseller: '分销商'
|
||||
}
|
||||
|
||||
const PERIOD_LABELS = {
|
||||
day: '日结',
|
||||
month: '月结'
|
||||
}
|
||||
|
||||
const SALE_MODE_LABELS = {
|
||||
0: '折扣销售',
|
||||
1: '代理销售',
|
||||
2: '底价销售'
|
||||
}
|
||||
|
||||
const STATUS_ALIASES = {
|
||||
start: 'approving',
|
||||
agree: 'approved',
|
||||
refuse: 'rejected',
|
||||
terminate: 'cancelled',
|
||||
approve: 'approved',
|
||||
cancel: 'cancelled'
|
||||
}
|
||||
|
||||
export function getPayload(response) {
|
||||
if (!response) return {}
|
||||
if (response.status === false) {
|
||||
throw new Error(response.msg || response.message || '请求失败')
|
||||
}
|
||||
return response.data !== undefined && response.data !== null ? response.data : response
|
||||
}
|
||||
|
||||
export function getItems(payload) {
|
||||
if (Array.isArray(payload)) return payload
|
||||
if (!payload || typeof payload !== 'object') return []
|
||||
|
||||
const keys = ['items', 'list', 'records', 'rows', 'details', 'detail_items', 'result_list', 'detail_list', 'data_list']
|
||||
for (let index = 0; index < keys.length; index += 1) {
|
||||
const value = payload[keys[index]]
|
||||
if (Array.isArray(value)) return value
|
||||
}
|
||||
if (payload.data && typeof payload.data === 'object') return getItems(payload.data)
|
||||
return []
|
||||
}
|
||||
|
||||
export function getTotal(payload, fallback = 0) {
|
||||
if (!payload || typeof payload !== 'object') return fallback
|
||||
const value = payload.total_count || payload.total || payload.count || payload.totalCount
|
||||
if ((value === undefined || value === null) && payload.data && typeof payload.data === 'object') {
|
||||
return getTotal(payload.data, fallback)
|
||||
}
|
||||
return Number(value === undefined || value === null ? fallback : value)
|
||||
}
|
||||
|
||||
export function getCurrentPage(payload, fallback = 1) {
|
||||
if (!payload || typeof payload !== 'object') return fallback
|
||||
const value = payload.current_page || payload.page || payload.currentPage
|
||||
if ((value === undefined || value === null) && payload.data && typeof payload.data === 'object') {
|
||||
return getCurrentPage(payload.data, fallback)
|
||||
}
|
||||
return Number(value === undefined || value === null ? fallback : value)
|
||||
}
|
||||
|
||||
export function getPageSize(payload, fallback = 20) {
|
||||
if (!payload || typeof payload !== 'object') return fallback
|
||||
const value = payload.page_size || payload.limit || payload.pageSize
|
||||
if ((value === undefined || value === null) && payload.data && typeof payload.data === 'object') {
|
||||
return getPageSize(payload.data, fallback)
|
||||
}
|
||||
return Number(value === undefined || value === null ? fallback : value)
|
||||
}
|
||||
|
||||
export function getAccountingOrgId(route) {
|
||||
return (route && route.query && route.query.orgid) ||
|
||||
sessionStorage.getItem('orgid') ||
|
||||
localStorage.getItem('orgid') ||
|
||||
''
|
||||
}
|
||||
|
||||
export function getCurrentUserId() {
|
||||
return sessionStorage.getItem('userId') ||
|
||||
sessionStorage.getItem('userid') ||
|
||||
sessionStorage.getItem('username') ||
|
||||
''
|
||||
}
|
||||
|
||||
export function numberValue(value) {
|
||||
if (value === undefined || value === null || value === '') return 0
|
||||
if (typeof value === 'number') return Number.isFinite(value) ? value : 0
|
||||
const numeric = Number(String(value).replace(/[^\d.-]/g, ''))
|
||||
return Number.isFinite(numeric) ? numeric : 0
|
||||
}
|
||||
|
||||
export function money(value) {
|
||||
return `¥${numberValue(value).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})}`
|
||||
}
|
||||
|
||||
export function percent(value, total) {
|
||||
const ratio = numberValue(total) ? (numberValue(value) / numberValue(total)) * 100 : 0
|
||||
return `${ratio.toFixed(1)}%`
|
||||
}
|
||||
|
||||
export function normalizeStatus(value) {
|
||||
const status = String(value || 'draft').toLowerCase()
|
||||
return STATUS_ALIASES[status] || status
|
||||
}
|
||||
|
||||
function read(source, keys, fallback = '') {
|
||||
if (!source || typeof source !== 'object') return fallback
|
||||
for (let index = 0; index < keys.length; index += 1) {
|
||||
const value = source[keys[index]]
|
||||
if (value !== undefined && value !== null && value !== '') return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
export function normalizeSettlement(item = {}) {
|
||||
const counterparty = item.counterparty || {}
|
||||
const typeKey = String(read(item, ['counterparty_type', 'type'], read(counterparty, ['type'], ''))).toLowerCase()
|
||||
const periodKey = String(read(item, ['period_type'], '')).toLowerCase()
|
||||
const salesAmount = numberValue(read(item, ['sales_amount', 'sales_total', 'sale_amount']))
|
||||
const settlementAmount = numberValue(read(item, ['settlement_amount', 'settle_amount', 'settle_upstream_amount']))
|
||||
const incomeAmount = numberValue(read(item, ['platform_income_amount', 'profit_amount', 'platform_income']))
|
||||
|
||||
return {
|
||||
id: String(read(item, ['settlement_id', 'id', 'settlementId'])),
|
||||
no: read(item, ['settlement_no', 'settlement_number', 'no'], '-'),
|
||||
typeKey,
|
||||
type: TYPE_LABELS[typeKey] || read(item, ['counterparty_type_name', 'type_name'], read(counterparty, ['type_name'], '-')),
|
||||
orgId: read(item, ['counterparty_orgid', 'counterparty_id', 'orgid'], read(counterparty, ['orgid', 'id'], '-')),
|
||||
name: read(item, ['counterparty_name', 'name'], read(counterparty, ['name'], '-')),
|
||||
periodTypeKey: periodKey,
|
||||
periodType: PERIOD_LABELS[periodKey] || read(item, ['period_type_name'], '-'),
|
||||
start: read(item, ['period_start', 'start_date', 'start'], '-'),
|
||||
end: read(item, ['period_end', 'end_date', 'end'], '-'),
|
||||
salesAmount,
|
||||
settlementAmount,
|
||||
incomeAmount,
|
||||
sales: money(salesAmount),
|
||||
settlement: money(settlementAmount),
|
||||
income: money(incomeAmount),
|
||||
status: normalizeStatus(read(item, ['status', 'settlement_status'], 'draft')),
|
||||
createdAt: read(item, ['created_at', 'create_time', 'createdAt'], '-'),
|
||||
approvalId: read(item, ['approval_id', 'apv_id', 'approvalId'], ''),
|
||||
reason: read(item, ['failure_reason', 'reason', 'fail_reason'], '')
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeSummaryItem(item = {}) {
|
||||
const salesAmount = numberValue(read(item, ['sales_amount', 'sales_total', 'sale_amount']))
|
||||
const settlementAmount = numberValue(read(item, ['settlement_amount', 'settle_amount', 'settle_upstream_amount']))
|
||||
const incomeAmount = numberValue(read(item, ['platform_income_amount', 'profit_amount', 'platform_income']))
|
||||
|
||||
return {
|
||||
period: read(item, ['period', 'period_name', 'settlement_period'], '-'),
|
||||
id: read(item, ['counterparty_orgid', 'counterparty_id', 'orgid'], '-'),
|
||||
name: read(item, ['counterparty_name', 'name'], '-'),
|
||||
typeKey: String(read(item, ['counterparty_type', 'type'], '')).toLowerCase(),
|
||||
salesAmount,
|
||||
settlementAmount,
|
||||
incomeAmount,
|
||||
sales: money(salesAmount),
|
||||
settlement: money(settlementAmount),
|
||||
income: money(incomeAmount),
|
||||
bills: numberValue(read(item, ['bill_count', 'count'])),
|
||||
ratio: '0%'
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeBill(item = {}) {
|
||||
const salesAmount = numberValue(read(item, ['sales_amount', 'sales_total', 'sale_amount']))
|
||||
const settlementAmount = numberValue(read(item, ['settlement_amount', 'settle_amount', 'settle_upstream_amount']))
|
||||
const incomeAmount = numberValue(read(item, ['platform_income_amount', 'profit_amount', 'platform_income']))
|
||||
const mode = read(item, ['sale_mode', 'sales_mode', 'mode'], '')
|
||||
|
||||
return {
|
||||
billId: read(item, ['bill_id', 'id', 'billId'], '-'),
|
||||
orderId: read(item, ['order_id', 'order_no', 'orderId'], '-'),
|
||||
date: read(item, ['bill_date', 'date', 'created_at'], '-'),
|
||||
mode: SALE_MODE_LABELS[mode] || mode || '-',
|
||||
salesAmount,
|
||||
settlementAmount,
|
||||
incomeAmount,
|
||||
sales: money(salesAmount),
|
||||
settlement: money(settlementAmount),
|
||||
income: money(incomeAmount)
|
||||
}
|
||||
}
|
||||
|
||||
export function getSummary(payload = {}) {
|
||||
const nested = payload.data && typeof payload.data === 'object' ? payload.data : {}
|
||||
const summary = payload.summary || payload.total || payload.totals || nested.summary || nested.total || nested.totals || payload
|
||||
const salesAmount = numberValue(read(summary, ['sales_amount', 'sales_total', 'sale_amount']))
|
||||
const settlementAmount = numberValue(read(summary, ['settlement_amount', 'settle_amount', 'settle_upstream_amount']))
|
||||
const incomeAmount = numberValue(read(summary, ['platform_income_amount', 'profit_amount', 'platform_income']))
|
||||
const billCount = numberValue(read(summary, ['bill_count', 'count']))
|
||||
|
||||
return {
|
||||
salesAmount,
|
||||
settlementAmount,
|
||||
incomeAmount,
|
||||
billCount,
|
||||
sales: money(salesAmount),
|
||||
settlement: money(settlementAmount),
|
||||
income: money(incomeAmount)
|
||||
}
|
||||
}
|
||||
|
||||
export function isSubmittable(status) {
|
||||
return ['draft', 'rejected', 'failed'].includes(normalizeStatus(status))
|
||||
}
|
||||
@ -0,0 +1,247 @@
|
||||
export const settlementRows = [
|
||||
{
|
||||
id: 'stl20260601001',
|
||||
no: 'STL202606010001',
|
||||
type: '供应商',
|
||||
typeKey: 'supplier',
|
||||
orgId: 'supplier001',
|
||||
name: '云海科技股份有限公司',
|
||||
periodType: '月结',
|
||||
start: '2026-05-01',
|
||||
end: '2026-05-31',
|
||||
sales: '¥156,800.00',
|
||||
settlement: '¥109,760.00',
|
||||
income: '¥47,040.00',
|
||||
status: 'settled',
|
||||
createdAt: '2026-06-01 09:23:15',
|
||||
approvalId: 'apv20260601001'
|
||||
},
|
||||
{
|
||||
id: 'stl20260602001',
|
||||
no: 'STL202606020002',
|
||||
type: '供应商',
|
||||
typeKey: 'supplier',
|
||||
orgId: 'supplier002',
|
||||
name: '星际云计算服务公司',
|
||||
periodType: '月结',
|
||||
start: '2026-05-01',
|
||||
end: '2026-05-31',
|
||||
sales: '¥234,500.00',
|
||||
settlement: '¥164,150.00',
|
||||
income: '¥70,350.00',
|
||||
status: 'approved',
|
||||
createdAt: '2026-06-02 10:45:30',
|
||||
approvalId: 'apv20260602001'
|
||||
},
|
||||
{
|
||||
id: 'stl20260603001',
|
||||
no: 'STL202606030003',
|
||||
type: '分销商',
|
||||
typeKey: 'reseller',
|
||||
orgId: 'reseller001',
|
||||
name: '东方明珠销售有限公司',
|
||||
periodType: '月结',
|
||||
start: '2026-05-01',
|
||||
end: '2026-05-31',
|
||||
sales: '¥89,500.00',
|
||||
settlement: '¥76,075.00',
|
||||
income: '¥13,425.00',
|
||||
status: 'approving',
|
||||
createdAt: '2026-06-03 14:20:00',
|
||||
approvalId: 'apv20260603001'
|
||||
},
|
||||
{
|
||||
id: 'stl20260604001',
|
||||
no: 'STL202606040004',
|
||||
type: '供应商',
|
||||
typeKey: 'supplier',
|
||||
orgId: 'supplier003',
|
||||
name: '智算数据科技有限公司',
|
||||
periodType: '月结',
|
||||
start: '2026-05-01',
|
||||
end: '2026-05-31',
|
||||
sales: '¥178,900.00',
|
||||
settlement: '¥125,230.00',
|
||||
income: '¥53,670.00',
|
||||
status: 'draft',
|
||||
createdAt: '2026-06-04 11:15:45',
|
||||
approvalId: ''
|
||||
},
|
||||
{
|
||||
id: 'stl20260605001',
|
||||
no: 'STL202606050005',
|
||||
type: '分销商',
|
||||
typeKey: 'reseller',
|
||||
orgId: 'reseller002',
|
||||
name: '粤港澳渠道服务商',
|
||||
periodType: '月结',
|
||||
start: '2026-05-01',
|
||||
end: '2026-05-31',
|
||||
sales: '¥123,400.00',
|
||||
settlement: '¥104,890.00',
|
||||
income: '¥18,510.00',
|
||||
status: 'rejected',
|
||||
createdAt: '2026-06-05 16:30:20',
|
||||
approvalId: 'apv20260605001',
|
||||
reason: '金额与对账单不一致,需重新核对'
|
||||
},
|
||||
{
|
||||
id: 'stl20260606001',
|
||||
no: 'STL202606060006',
|
||||
type: '供应商',
|
||||
typeKey: 'supplier',
|
||||
orgId: 'supplier004',
|
||||
name: '极光网络通信集团',
|
||||
periodType: '月结',
|
||||
start: '2026-05-01',
|
||||
end: '2026-05-31',
|
||||
sales: '¥267,800.00',
|
||||
settlement: '¥187,460.00',
|
||||
income: '¥80,340.00',
|
||||
status: 'failed',
|
||||
createdAt: '2026-06-06 08:50:10',
|
||||
approvalId: 'apv20260606001',
|
||||
reason: '银行账户信息错误,结算转账失败'
|
||||
}
|
||||
]
|
||||
|
||||
export const summaryRows = [
|
||||
{
|
||||
period: '2026-06',
|
||||
id: 'supplier001',
|
||||
name: '云海科技股份有限公司',
|
||||
sales: '¥156,800.00',
|
||||
settlement: '¥109,760.00',
|
||||
income: '¥47,040.00',
|
||||
bills: '42',
|
||||
ratio: '22.6%'
|
||||
},
|
||||
{
|
||||
period: '2026-06',
|
||||
id: 'supplier002',
|
||||
name: '星际云计算服务公司',
|
||||
sales: '¥142,600.00',
|
||||
settlement: '¥99,820.00',
|
||||
income: '¥42,780.00',
|
||||
bills: '36',
|
||||
ratio: '20.6%'
|
||||
},
|
||||
{
|
||||
period: '2026-06',
|
||||
id: 'supplier003',
|
||||
name: '智算数据科技有限公司',
|
||||
sales: '¥98,760.00',
|
||||
settlement: '¥69,132.00',
|
||||
income: '¥29,628.00',
|
||||
bills: '27',
|
||||
ratio: '14.2%'
|
||||
},
|
||||
{
|
||||
period: '2026-06',
|
||||
id: 'reseller001',
|
||||
name: '东方明珠销售有限公司',
|
||||
sales: '¥86,400.00',
|
||||
settlement: '¥73,440.00',
|
||||
income: '¥12,960.00',
|
||||
bills: '19',
|
||||
ratio: '12.5%'
|
||||
}
|
||||
]
|
||||
|
||||
export const billRows = [
|
||||
{
|
||||
billId: 'B202606010001',
|
||||
orderId: 'O202606001',
|
||||
date: '2026-06-01',
|
||||
mode: '标准销售',
|
||||
sales: '¥12,580.00',
|
||||
settlement: '¥8,806.00',
|
||||
income: '¥3,774.00'
|
||||
},
|
||||
{
|
||||
billId: 'B202606010002',
|
||||
orderId: 'O202606002',
|
||||
date: '2026-06-03',
|
||||
mode: '代理销售',
|
||||
sales: '¥8,920.00',
|
||||
settlement: '¥6,244.00',
|
||||
income: '¥2,676.00'
|
||||
},
|
||||
{
|
||||
billId: 'B202606010003',
|
||||
orderId: 'O202606003',
|
||||
date: '2026-06-08',
|
||||
mode: '标准销售',
|
||||
sales: '¥15,600.00',
|
||||
settlement: '¥10,920.00',
|
||||
income: '¥4,680.00'
|
||||
},
|
||||
{
|
||||
billId: 'B202606010004',
|
||||
orderId: 'O202606004',
|
||||
date: '2026-06-12',
|
||||
mode: '项目销售',
|
||||
sales: '¥19,860.00',
|
||||
settlement: '¥13,902.00',
|
||||
income: '¥5,958.00'
|
||||
},
|
||||
{
|
||||
billId: 'B202606010005',
|
||||
orderId: 'O202606005',
|
||||
date: '2026-06-21',
|
||||
mode: '标准销售',
|
||||
sales: '¥11,720.00',
|
||||
settlement: '¥8,204.00',
|
||||
income: '¥3,516.00'
|
||||
}
|
||||
]
|
||||
|
||||
export const flowSteps = [
|
||||
{
|
||||
index: 1,
|
||||
icon: 'el-icon-s-data',
|
||||
title: '汇总查询',
|
||||
subtitle: '数据盘点',
|
||||
description: '查询指定账期内供应商或分销商的销售额、结算金额、平台收入和账单数量。',
|
||||
endpoint: '/bill/finance_settlement_summary.dspy'
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
icon: 'el-icon-view',
|
||||
title: '结算单预览',
|
||||
subtitle: '核对明细',
|
||||
description: '创建前预览结算明细,检查指定账期是否已存在结算单,避免重复创建。',
|
||||
endpoint: '/bill/finance_settlement_preview.dspy'
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
icon: 'el-icon-circle-plus-outline',
|
||||
title: '创建结算单',
|
||||
subtitle: '生成草稿',
|
||||
description: '基于预览数据创建结算单主表和明细快照,结算单初始状态为草稿。',
|
||||
endpoint: '/bill/finance_settlement_create.dspy'
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
icon: 'el-icon-s-promotion',
|
||||
title: '提交审批',
|
||||
subtitle: '送审',
|
||||
description: '将草稿、审批拒绝或结算失败状态的结算单提交至审批流程。',
|
||||
endpoint: '/bill/finance_settlement_submit.dspy'
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
icon: 'el-icon-circle-check',
|
||||
title: '审批回调',
|
||||
subtitle: '记账闭环',
|
||||
description: '接收审批结果,审批通过后自动完成结算记账并更新结算单状态。',
|
||||
endpoint: '/bill/finance_settlement_apv_callback.dspy'
|
||||
}
|
||||
]
|
||||
|
||||
export const incomeRanks = [
|
||||
{ name: '星际云计算服务公司', type: '供应商', income: '¥70,350.00', ratio: '26.3%', width: '100%' },
|
||||
{ name: '极光网络通信集团', type: '供应商', income: '¥62,480.00', ratio: '23.4%', width: '89%' },
|
||||
{ name: '云海科技股份有限公司', type: '供应商', income: '¥47,040.00', ratio: '17.6%', width: '67%' },
|
||||
{ name: '东方明珠销售有限公司', type: '分销商', income: '¥31,860.00', ratio: '11.9%', width: '45%' }
|
||||
]
|
||||
@ -139,7 +139,6 @@ export default Vue.extend({
|
||||
{ label: '训推平台', path: '/homePage/trainPlatform' },
|
||||
{ label: '智能体商店', path: '/homePage/agentStore' },
|
||||
{ label: '供需广场', path: '/ncmatchHome/supplyAndDemandSquare' },
|
||||
{ label: '文档中心', path: '/homePage/new' },
|
||||
],
|
||||
footerAboutUs: [
|
||||
{ label: '关于我们', path: '/homePage/about' },
|
||||
|
||||
@ -359,7 +359,7 @@ export default Vue.extend({
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.dispatchEvent(new Event('kboss-open-login-dialog'))
|
||||
this.$router.push('/login')
|
||||
}
|
||||
},
|
||||
initData() {
|
||||
@ -418,14 +418,14 @@ export default Vue.extend({
|
||||
if (this.loginState) {
|
||||
this.$router.push('/ncmatchHome/favoriteBox')
|
||||
} else {
|
||||
window.dispatchEvent(new Event('kboss-open-login-dialog'))
|
||||
this.$router.push('/login')
|
||||
}
|
||||
},
|
||||
goHistory() {
|
||||
if (this.loginState) {
|
||||
this.$router.push('/ncmatchHome/historyBox')
|
||||
} else {
|
||||
window.dispatchEvent(new Event('kboss-open-login-dialog'))
|
||||
this.$router.push('/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,7 +435,7 @@ export default Vue.extend({
|
||||
<style scoped lang="scss">
|
||||
.jd-homepage {
|
||||
margin: 0;
|
||||
// padding-top: 92px;
|
||||
padding-top: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(180deg, #f0f7ff 0%, #ffffff 46%, #f7faff 74%, #f8fafc 100%);
|
||||
overflow-x: hidden;
|
||||
@ -457,6 +457,7 @@ export default Vue.extend({
|
||||
.public-hero {
|
||||
width: 100%;
|
||||
min-height: 760px;
|
||||
padding-top: 82px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -1069,7 +1070,7 @@ export default Vue.extend({
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 576px) {
|
||||
.jd-homepage {
|
||||
padding-top: 80px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@ -1082,6 +1083,7 @@ export default Vue.extend({
|
||||
|
||||
.public-hero {
|
||||
min-height: auto;
|
||||
padding-top: 72px;
|
||||
}
|
||||
|
||||
.public-hero h1 {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="product-service-page" :class="{ 'single-product-page': isSinglePageMode }">
|
||||
x
|
||||
|
||||
<!-- 产品分类导航 -->
|
||||
<div v-if="!isSinglePageMode" class="category-nav">
|
||||
<div v-for="category in panelData"
|
||||
@ -118,11 +118,12 @@
|
||||
<div class="token-card-top">
|
||||
<span class="token-provider-avatar">
|
||||
<img
|
||||
:src="product.model_logo"
|
||||
alt="模型logo"
|
||||
v-if="getModelLogoUrl(product.model_logo) && !product.logoLoadFailed"
|
||||
:src="getModelLogoUrl(product.model_logo)"
|
||||
:alt="product.display_name || product.model_name || '模型logo'"
|
||||
@error="handleModelLogoError(product)"
|
||||
>
|
||||
|
||||
<template v-else>{{ getProviderInitial(product.provider || product.display_name) }}</template>
|
||||
</span>
|
||||
|
||||
<div class="token-title-group">
|
||||
@ -1594,16 +1595,16 @@ export default {
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
// background: #7c3aed;
|
||||
// border-radius: 8px;
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #2563eb 100%);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
// box-shadow: 0 6px 16px rgba(124, 58, 237, 0.18);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user