李红攀:V2.6.969,销售出库管理

This commit is contained in:
2026-06-22 17:09:31 +08:00
parent d0ce2f2b55
commit 617d293dc3
7 changed files with 1710 additions and 2 deletions

View File

@@ -0,0 +1,382 @@
<template>
<view class="yd-page-container">
<wd-navbar
title="销售出库详情"
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="handleBack"
/>
<view class="p-24rpx pb-180rpx" :class="{ 'opacity-60': loading }">
<view class="section-card">
<view class="section-title">
基本信息
</view>
<view class="info-row">
<text class="info-label">出库单号</text>
<text class="info-value">{{ detail.no || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">客户</text>
<text class="info-value">{{ detail.customerName || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">出库时间</text>
<text class="info-value">{{ formatDate(detail.outTime) }}</text>
</view>
<view class="info-row">
<text class="info-label">关联订单</text>
<text class="info-value">{{ detail.orderNo || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">销售人员</text>
<text class="info-value">{{ getSaleUserName() }}</text>
</view>
<view class="info-row">
<text class="info-label">状态</text>
<view :class="getStatusClass(detail.status)">
{{ getStatusText(detail.status) }}
</view>
</view>
<view class="info-row">
<text class="info-label">备注</text>
<text class="info-value max-w-[420rpx] text-right">{{ detail.remark || '-' }}</text>
</view>
</view>
<view class="section-card">
<view class="section-title">
金额信息
</view>
<view class="info-row">
<text class="info-label">总数量</text>
<text class="info-value">{{ formatCount(detail.totalCount) }}</text>
</view>
<view class="info-row">
<text class="info-label">应收金额</text>
<text class="info-value text-[#409eff]">{{ formatPrice(detail.totalPrice) }}</text>
</view>
<view class="info-row">
<text class="info-label">已收金额</text>
<text class="info-value text-[#52c41a]">{{ formatPrice(detail.receiptPrice) }}</text>
</view>
<view class="info-row">
<text class="info-label">未收金额</text>
<text class="info-value text-[#f5222d]">{{ formatPrice(unreceivedPrice) }}</text>
</view>
</view>
<view class="section-card">
<view class="section-title">
出库明细
</view>
<view v-if="!detail.items || detail.items.length === 0" class="py-40rpx text-center text-[#999]">
暂无产品数据
</view>
<view
v-for="(item, index) in detail.items"
:key="item.id || index"
class="item-card"
>
<view class="mb-12rpx text-28rpx text-[#333] font-semibold">
{{ item.productName || '-' }}
</view>
<view class="info-row">
<text class="info-label">仓库</text>
<text class="info-value">{{ item.warehouseName || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">单位</text>
<text class="info-value">{{ item.productUnitName || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">数量</text>
<text class="info-value">{{ formatCount(item.count) }}</text>
</view>
<view class="info-row">
<text class="info-label">单价</text>
<text class="info-value">{{ formatPrice(item.productPrice) }}</text>
</view>
<view class="info-row">
<text class="info-label">金额</text>
<text class="info-value text-[#409eff]">{{ formatPrice(item.totalPrice) }}</text>
</view>
<view class="info-row">
<text class="info-label">批次</text>
<text class="info-value">{{ item.batchNo || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">备注</text>
<text class="info-value max-w-[420rpx] text-right">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
<view class="yd-detail-footer">
<wd-button
v-if="canEdit"
type="primary"
plain
@click="handleEdit"
>
编辑
</wd-button>
<wd-button
v-if="canApprove"
type="success"
@click="handleApprove"
>
审核
</wd-button>
<wd-button
v-if="canDelete"
type="error"
@click="handleDelete"
>
删除
</wd-button>
</view>
</view>
</template>
<script lang="ts" setup>
import type { SaleOut } from '@/api/erp/sale-out'
import { computed, onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { getApprovalRecordListByBiz } from '@/api/erp/approval-record'
import { deleteSaleOut, getSaleOut, updateSaleOutStatus } from '@/api/erp/sale-out'
import { getSimpleUserList } from '@/api/system/user'
import { useAccess } from '@/hooks/useAccess'
import { navigateBackPlus } from '@/utils'
const props = defineProps<{
id?: number | string
}>()
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const { hasAccessByCodes } = useAccess()
const toast = useToast()
const loading = ref(false)
const detail = ref<SaleOut>({})
const userList = ref<{ id?: number, nickname?: string }[]>([])
const unreceivedPrice = computed(() => {
const totalPrice = Number(detail.value.totalPrice || 0)
const receiptPrice = Number(detail.value.receiptPrice || 0)
return Math.max(totalPrice - receiptPrice, 0)
})
const canEdit = computed(() => {
return hasAccessByCodes(['erp:sale-out:update']) && detail.value.status === 10 && !detail.value.hasApprovalRecords
})
const canApprove = computed(() => {
return hasAccessByCodes(['erp:sale-out:update-status']) && detail.value.status === 10
})
const canDelete = computed(() => {
return hasAccessByCodes(['erp:sale-out:delete']) && !detail.value.hasApprovalRecords
})
function handleBack() {
navigateBackPlus('/pages-erp/sale-out/index')
}
function getStatusClass(status?: number) {
switch (status) {
case 10:
return 'status-tag bg-[#fffbe6] text-[#faad14]'
case 20:
return 'status-tag bg-[#f6ffed] text-[#52c41a]'
default:
return 'status-tag bg-[#f5f5f5] text-[#999]'
}
}
function getStatusText(status?: number) {
switch (status) {
case 10:
return '未审核'
case 20:
return '已审核'
default:
return '-'
}
}
function formatDate(value?: string | number) {
if (!value)
return '-'
const date = new Date(value)
if (Number.isNaN(date.getTime()))
return String(value).slice(0, 10)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
function formatCount(value?: number) {
if (value === undefined || value === null)
return '0.00'
return Number(value).toFixed(2)
}
function formatPrice(value?: number) {
if (value === undefined || value === null)
return '0.00'
return Number(value).toFixed(2)
}
function getSaleUserName() {
if (detail.value.saleUserName)
return detail.value.saleUserName
const matched = userList.value.find(item => item.id === detail.value.saleUserId)
return matched?.nickname || '-'
}
async function loadUserList() {
try {
userList.value = await getSimpleUserList()
} catch {
userList.value = []
}
}
async function appendApprovalFlag() {
if (!detail.value.id)
return
try {
const approvalList = await getApprovalRecordListByBiz(detail.value.id, 'erp_sale_out')
detail.value.hasApprovalRecords = approvalList.length > 0
} catch {
detail.value.hasApprovalRecords = false
}
}
async function getDetail() {
if (!props.id)
return
loading.value = true
try {
detail.value = await getSaleOut(Number(props.id))
await appendApprovalFlag()
} catch {
toast.error('加载失败')
} finally {
loading.value = false
}
}
function handleEdit() {
uni.navigateTo({ url: `/pages-erp/sale-out/form/index?id=${props.id}` })
}
function handleApprove() {
uni.showModal({
title: '提示',
content: '确定审核该销售出库单吗?',
success: async (res) => {
if (!res.confirm || !props.id)
return
try {
await updateSaleOutStatus(Number(props.id), 20)
toast.success('审核成功')
getDetail()
} catch {
// handled by http
}
},
})
}
function handleDelete() {
uni.showModal({
title: '提示',
content: '确定删除该销售出库单吗?',
success: async (res) => {
if (!res.confirm || !props.id)
return
try {
await deleteSaleOut([Number(props.id)])
toast.success('删除成功')
setTimeout(() => {
handleBack()
}, 500)
} catch {
// handled by http
}
},
})
}
onMounted(async () => {
await loadUserList()
getDetail()
})
</script>
<style lang="scss" scoped>
.section-card {
margin-bottom: 24rpx;
border-radius: 12rpx;
background: #fff;
padding: 24rpx;
}
.section-title {
margin-bottom: 24rpx;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
}
.info-label {
flex-shrink: 0;
color: #999;
font-size: 28rpx;
}
.info-value {
color: #333;
font-size: 28rpx;
}
.status-tag {
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
}
.item-card {
margin-bottom: 16rpx;
border-radius: 8rpx;
background: #fafafa;
padding: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
</style>