更新
This commit is contained in:
1 parent
5718a00e31
commit
1850ac640e
226 files changed
+37772
-12071
No files matched your search
@@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view v-if="loading && !profile" class="state-box">
|
||||
<text class="state-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<block v-else-if="profile">
|
||||
<view class="hero">
|
||||
<view class="hero-top">
|
||||
<text class="hero-name">{{ name }}</text>
|
||||
<u-tag :text="partnerTypeLabel(partnerType)" type="primary" size="mini" />
|
||||
</view>
|
||||
<text class="hero-sub">{{ [profile.contact_person, profile.contact_phone].filter(Boolean).join(' · ') || '未填联系人' }}</text>
|
||||
<view class="hero-grid">
|
||||
<view class="hero-cell">
|
||||
<text class="cell-label">{{ isSupplier ? '未付应付' : '未收应收' }}</text>
|
||||
<text class="cell-value owe">¥{{ moneyText(openAmount) }}</text>
|
||||
</view>
|
||||
<view class="hero-cell">
|
||||
<text class="cell-label">{{ isSupplier ? '累计应付' : '累计应收' }}</text>
|
||||
<text class="cell-value">¥{{ moneyText(totalAmount) }}</text>
|
||||
</view>
|
||||
<view class="hero-cell">
|
||||
<text class="cell-label">账期</text>
|
||||
<text class="cell-value">{{ Number(profile.payment_days) > 0 ? profile.payment_days + ' 天' : '现结' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="block-head">
|
||||
<text class="block-title">基础资料</text>
|
||||
</view>
|
||||
<view class="line">
|
||||
<text class="line-label">等级 / 类型</text>
|
||||
<text class="line-value">{{ join([profile.supplier_level || profile.customer_level, profile.supplier_type || profile.customer_type]) }}</text>
|
||||
</view>
|
||||
<view class="line">
|
||||
<text class="line-label">联系电话</text>
|
||||
<text class="line-value link" @tap="callPhone">{{ dash(profile.contact_phone) }}</text>
|
||||
</view>
|
||||
<view class="line">
|
||||
<text class="line-label">邮箱</text>
|
||||
<text class="line-value">{{ dash(profile.contact_email) }}</text>
|
||||
</view>
|
||||
<view class="line">
|
||||
<text class="line-label">地址</text>
|
||||
<text class="line-value">{{ dash(profile.address) }}</text>
|
||||
</view>
|
||||
<view class="line">
|
||||
<text class="line-label">开票抬头</text>
|
||||
<text class="line-value">{{ join([profile.invoice_title, profile.tax_number]) }}</text>
|
||||
</view>
|
||||
<view class="line">
|
||||
<text class="line-label">收款账户</text>
|
||||
<text class="line-value">{{ join([profile.bank_name, profile.bank_account]) }}</text>
|
||||
</view>
|
||||
<view v-if="Number(profile.credit_limit) > 0" class="line">
|
||||
<text class="line-label">信用额度</text>
|
||||
<text class="line-value">¥{{ moneyText(profile.credit_limit) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="block-head">
|
||||
<text class="block-title">未结账单({{ bills.length }} 张)</text>
|
||||
<text class="block-tip" @tap="goAllBills">全部 {{ billTotal }} 张</text>
|
||||
</view>
|
||||
<view v-if="!bills.length" class="empty-line">
|
||||
<text>没有未结账单</text>
|
||||
</view>
|
||||
<view v-for="b in bills" :key="b.id" class="line tap" @tap="goBill(b)">
|
||||
<view class="line-label grow">
|
||||
<text>{{ b.source_doc_no || `账单#${b.id}` }}</text>
|
||||
<text class="sub">{{ b.bill_date }}{{ b.due_date ? ' · 到期 ' + b.due_date : '' }}</text>
|
||||
</view>
|
||||
<view class="line-value right">
|
||||
<text class="bal">¥{{ moneyText(b.balance) }}</text>
|
||||
<text v-if="b.overdue_days > 0" class="over">逾期 {{ b.overdue_days }} 天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-space"></view>
|
||||
|
||||
<view class="actions">
|
||||
<view class="act" @tap="goDocs">
|
||||
<text>历史单据</text>
|
||||
</view>
|
||||
<view class="act primary" @tap="goFund">
|
||||
<text>{{ isSupplier ? '去付款' : '去收款' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 客商详情:档案 + 未结账单 + 一键收付款。
|
||||
* 余额口径直接取往来账(bill)与对账汇总,和 Web 端「应收应付」页一致,
|
||||
* 端上不另算一套,避免业务在手机上看到的欠款跟财务对不上。
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import {
|
||||
fetchBills,
|
||||
fetchPartnerSummary,
|
||||
getPartner,
|
||||
moneyText,
|
||||
partnerTypeLabel
|
||||
} from '@/api/erp.js'
|
||||
|
||||
const profile = ref(null)
|
||||
const bills = ref([])
|
||||
const billTotal = ref(0)
|
||||
const summary = ref({})
|
||||
const loading = ref(false)
|
||||
const partnerId = ref(0)
|
||||
const partnerType = ref('customer')
|
||||
|
||||
const isSupplier = computed(() => partnerType.value === 'supplier')
|
||||
const name = computed(() => {
|
||||
const p = profile.value || {}
|
||||
return p.supplier_name || p.customer_name || p.name || '未命名客商'
|
||||
})
|
||||
const openAmount = computed(() => {
|
||||
const s = summary.value || {}
|
||||
return Number(isSupplier.value ? s.pay_open : s.recv_open) || 0
|
||||
})
|
||||
const totalAmount = computed(() => {
|
||||
const s = summary.value || {}
|
||||
return Number(isSupplier.value ? s.pay_total : s.recv_total) || 0
|
||||
})
|
||||
|
||||
onLoad(async (query = {}) => {
|
||||
partnerId.value = Number(query.id) || 0
|
||||
partnerType.value = String(query.partner_type) === 'supplier' ? 'supplier' : 'customer'
|
||||
uni.setNavigationBarTitle({ title: partnerTypeLabel(partnerType.value) })
|
||||
await load()
|
||||
})
|
||||
|
||||
async function load() {
|
||||
if (!partnerId.value) {
|
||||
uni.showToast({ title: '缺少客商 ID', icon: 'none' })
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const [p, b, rows] = await Promise.all([
|
||||
getPartner(partnerId.value, partnerType.value),
|
||||
fetchBills({ partnerId: partnerId.value, partnerType: partnerType.value, settleState: 'open', pageSize: 10 }),
|
||||
fetchPartnerSummary(partnerType.value)
|
||||
])
|
||||
profile.value = p
|
||||
bills.value = b.list || []
|
||||
billTotal.value = b.total || 0
|
||||
// 对账汇总按 partner_type:id 归并,这里只取当前这一家
|
||||
summary.value = rows.find((r) => Number(r.partner_id) === partnerId.value) || {}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '客商信息加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goFund() {
|
||||
const fundType = isSupplier.value ? 2 : 1
|
||||
uni.navigateTo({
|
||||
url: `/pages/erp/finance/fund-edit?fund_type=${fundType}&partner_id=${partnerId.value}&partner_name=${encodeURIComponent(name.value)}`
|
||||
})
|
||||
}
|
||||
|
||||
function goDocs() {
|
||||
uni.navigateTo({ url: `/pages/erp/document/index?partner_id=${partnerId.value}` })
|
||||
}
|
||||
|
||||
function goAllBills() {
|
||||
const billType = isSupplier.value ? 2 : 1
|
||||
uni.navigateTo({ url: `/pages/erp/finance/bill?bill_type=${billType}&partner_id=${partnerId.value}` })
|
||||
}
|
||||
|
||||
function goBill(bill) {
|
||||
uni.navigateTo({ url: `/pages/erp/finance/bill-detail?id=${bill.id}` })
|
||||
}
|
||||
|
||||
function callPhone() {
|
||||
const phone = String(profile.value?.contact_phone || '').trim()
|
||||
if (!phone) return
|
||||
uni.makePhoneCall({ phoneNumber: phone, fail: () => {} })
|
||||
}
|
||||
|
||||
function dash(v) {
|
||||
const s = String(v ?? '').trim()
|
||||
return s || '—'
|
||||
}
|
||||
|
||||
function join(arr) {
|
||||
const s = (arr || []).filter(Boolean).join(' / ')
|
||||
return s || '—'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/src/styles/page-common.scss';
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.hero {
|
||||
margin: 0 $page-padding-x 24rpx;
|
||||
padding: 32rpx;
|
||||
border-radius: $radius-lg;
|
||||
background: $hero-gradient;
|
||||
color: #ffffff;
|
||||
box-shadow: $shadow-float;
|
||||
}
|
||||
|
||||
.hero-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.hero-name {
|
||||
flex: 1;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.hero-grid {
|
||||
display: flex;
|
||||
margin-top: 28rpx;
|
||||
padding-top: 22rpx;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
|
||||
.hero-cell {
|
||||
flex: 1;
|
||||
|
||||
.cell-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.owe {
|
||||
color: #fecaca;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@include card;
|
||||
margin: 0 $page-padding-x 24rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
}
|
||||
|
||||
.block-head {
|
||||
@include block-head;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.block-tip {
|
||||
font-size: 22rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid $color-divider;
|
||||
font-size: 26rpx;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.tap:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.line-label {
|
||||
color: $color-text-secondary;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.grow {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sub {
|
||||
display: block;
|
||||
margin-top: 4rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.line-value {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
color: $color-text;
|
||||
word-break: break-all;
|
||||
|
||||
&.link {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.bal {
|
||||
font-size: 27rpx;
|
||||
font-weight: 500;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.over {
|
||||
font-size: 21rpx;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.empty-line {
|
||||
padding: 40rpx 0;
|
||||
text-align: center;
|
||||
font-size: 25rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.footer-space {
|
||||
height: calc(150rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 18rpx $page-padding-x calc(18rpx + env(safe-area-inset-bottom));
|
||||
background: $color-card;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(15, 23, 42, 0.06);
|
||||
@include dialog-responsive;
|
||||
}
|
||||
|
||||
.act {
|
||||
flex: 1;
|
||||
height: 76rpx;
|
||||
line-height: 76rpx;
|
||||
text-align: center;
|
||||
border-radius: $radius-full;
|
||||
background: $color-bg-page;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-secondary;
|
||||
|
||||
&.primary {
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.state-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 140rpx 0;
|
||||
}
|
||||
|
||||
.state-text {
|
||||
font-size: 28rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user