<template>
|
<view class="sales-account">
|
<!-- 使用通用页面头部组件 -->
|
<PageHeader title="来票台账" @back="goBack" />
|
|
<!-- 搜索区域 -->
|
<view class="search-filter-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<input
|
class="search-text"
|
placeholder="供应商名称/采购合同号"
|
v-model="searchForm.searchText"
|
confirm-type="search"
|
@confirm="handleQuery"
|
/>
|
</view>
|
<view class="filter-button" @click="handleQuery">
|
<up-icon name="search" size="24" color="#999"></up-icon>
|
</view>
|
</view>
|
</view>
|
|
<!-- 列表区域 -->
|
<view class="ledger-list" v-if="total > 0">
|
<view v-for="(item, index) in ledgerList" :key="index">
|
<view class="ledger-item">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="file-text" size="16" color="#ffffff"></up-icon>
|
</view>
|
<text class="item-id">{{ item.purchaseContractNumber }}</text>
|
</view>
|
</view>
|
<up-divider></up-divider>
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">销售合同号</text>
|
<text class="detail-value">{{ item.salesContractNo }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">项目名称</text>
|
<text class="detail-value">{{ item.projectName }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">供应商名称</text>
|
<text class="detail-value">{{ item.supplierName }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">产品大类</text>
|
<text class="detail-value">{{ item.productCategory }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">规格型号</text>
|
<text class="detail-value">{{ item.specificationModel }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">发票号</text>
|
<text class="detail-value">{{ item.invoiceNumber || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">合同金额(元)</text>
|
<text class="detail-value">{{ item.taxInclusiveTotalPrice || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">来票日期</text>
|
<text class="detail-value">{{ item.createdAt || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">来票金额(元)</text>
|
<text class="detail-value highlight">{{ formatAmount(item.ticketsAmount) }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">不含税金额(元)</text>
|
<text class="detail-value highlight">{{ formatAmount(item.unTicketsPrice) }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">增值税</text>
|
<text class="detail-value">{{ item.invoiceAmount }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">录入人</text>
|
<text class="detail-value">{{ item.issUer }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">录入日期</text>
|
<text class="detail-value">{{ item.createdAt }}</text>
|
</view>
|
</view>
|
<view class="action-buttons">
|
<u-button
|
type="primary"
|
size="small"
|
class="action-btn"
|
:disabled="item.issUer !== userStore.nickName"
|
@click="openEdit(item)"
|
>
|
编辑
|
</u-button>
|
<u-button
|
type="error"
|
size="small"
|
plain
|
class="action-btn"
|
:disabled="item.issUer !== userStore.nickName"
|
@click="handleDelete(item)"
|
>
|
删除
|
</u-button>
|
<u-button
|
type="default"
|
size="small"
|
plain
|
class="action-btn"
|
v-if="item.invoiceFileName"
|
@click="openFileActions(item.commonFiles || [])"
|
>
|
查看附件
|
</u-button>
|
<u-button
|
type="primary"
|
size="small"
|
class="action-btn"
|
v-else
|
:disabled="item.issUer !== userStore.nickName"
|
@click="openUpload(item)"
|
>
|
上传
|
</u-button>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view v-else class="no-data">
|
<text>暂无来票台账数据</text>
|
</view>
|
|
<!-- 单行上传弹窗(无表单) -->
|
<u-popup v-model="showUpload" mode="bottom" border-radius="10">
|
<view class="upload-container">
|
<view class="popup-header">
|
<text class="popup-title">上传附件(仅支持 pdf,最大10MB,最多10个)</text>
|
</view>
|
<u-upload
|
ref="uploadRef"
|
accept="file"
|
multiple
|
:max-count="10"
|
:show-progress="true"
|
:before-upload="beforeReadPdf"
|
:action="uploadUrl"
|
:header="{ Authorization: 'Bearer ' + getToken() }"
|
name="file"
|
@on-success="onUploadSuccess"
|
@on-error="onUploadError"
|
/>
|
<view class="uploaded-list" v-if="fileList.length">
|
<view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">
|
<text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>
|
<u-button size="mini" type="error" plain @click="removeUploaded(idx)">移除</u-button>
|
</view>
|
</view>
|
<view class="filter-actions">
|
<u-button @click="showUpload = false" type="default" size="default" style="width: 150px;">取消</u-button>
|
<u-button @click="confirmUpload" type="primary" size="default" style="width: 150px;">确认</u-button>
|
</view>
|
</view>
|
</u-popup>
|
|
<!-- 附件列表选择 -->
|
<u-action-sheet v-model="showFileSheet" :list="fileActions" :cancel-btn="true" @click="onSelectFile" @close="showFileSheet = false" />
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted } from 'vue'
|
import dayjs from 'dayjs'
|
const showToast = (message) => {
|
uni.showToast({
|
title: message,
|
icon: 'none'
|
})
|
}
|
import useUserStore from '@/store/modules/user'
|
import { getToken } from '@/utils/auth'
|
import config from '@/config.js'
|
import {
|
registrationProductPage,
|
commitFile,
|
delInvoiceLedgerByRegProductId
|
} from '@/api/salesManagement/invoiceLedger.js'
|
import {onShow} from "@dcloudio/uni-app";
|
import {productRecordPage} from "@/api/procurementManagement/procurementInvoiceLedger";
|
import {delRegistration} from "@/api/procurementManagement/invoiceEntry";
|
import PageHeader from '@/components/PageHeader.vue';
|
import FooterButtons from '@/components/FooterButtons.vue';
|
|
const userStore = useUserStore()
|
|
// 列表与查询
|
const ledgerList = ref([])
|
const total = ref(0)
|
const page = reactive({ current: -1, size: -1 })
|
const searchForm = reactive({
|
searchText: '',
|
})
|
|
const currentId = ref('')
|
const fileList = ref([]) // 行上传或通用上传列表
|
const uploadRef = ref()
|
const uploadUrl = config.baseUrl + '/invoiceLedger/uploadFile'
|
|
// 行上传弹窗
|
const showUpload = ref(false)
|
|
// 附件查看
|
const showFileSheet = ref(false)
|
const fileActions = ref([])
|
let currentFilesToOpen = []
|
|
const formatAmount = (val) => {
|
if (val === undefined || val === null || val === '') return '0.00'
|
const num = Number(val)
|
if (Number.isNaN(num)) return '0.00'
|
return num.toFixed(2)
|
}
|
const formatDateTime = (val) => {
|
if (!val) return ''
|
return dayjs(val).format('YYYY-MM-DD HH:mm:ss')
|
}
|
|
const goBack = () => {
|
uni.navigateBack()
|
}
|
|
const handleQuery = () => {
|
getList()
|
}
|
|
const getList = async () => {
|
try {
|
uni.showLoading({
|
title: '加载中...'
|
});
|
const res = await productRecordPage({ ...searchForm, ...page })
|
// 兼容不同返回结构
|
const records = res?.data?.records || res?.records || res?.data || []
|
const totalVal = res?.data?.total || res?.total || records.length || 0
|
ledgerList.value = records
|
total.value = totalVal
|
uni.hideLoading();
|
} catch (e) {
|
uni.hideLoading();
|
showToast('获取列表失败')
|
}
|
}
|
|
// 编辑逻辑改为跳转新页面
|
const openEdit = (row) => {
|
try {
|
uni.setStorageSync('invoiceLedgerEditRow', JSON.stringify(row))
|
uni.navigateTo({ url: '/pages/procurementManagement/procurementInvoiceLedger/detail' })
|
} catch (e) {
|
showToast('跳转失败')
|
}
|
}
|
|
// 删除
|
const handleDelete = (row) => {
|
let ids = [];
|
ids.push(row.id);
|
console.log(ids)
|
uni.showModal({
|
title: '删除确认',
|
content: '该发票台账将被删除,是否确认删除?',
|
success: async (res) => {
|
if (res.confirm) {
|
try {
|
uni.showLoading({
|
title: '处理中...'
|
});
|
await delRegistration(ids)
|
uni.hideLoading();
|
showToast('删除成功')
|
await getList()
|
} catch (e) {
|
uni.hideLoading();
|
showToast('删除失败,请重试')
|
}
|
}
|
}
|
})
|
}
|
|
// 行上传
|
const openUpload = (row) => {
|
currentId.value = row.id
|
fileList.value = []
|
showUpload.value = true
|
}
|
const confirmUpload = async () => {
|
try {
|
const payload = { fileList: fileList.value, id: currentId.value }
|
uni.showLoading({
|
title: '提交中...'
|
});
|
await commitFile(payload)
|
uni.hideLoading();
|
showToast('提交成功')
|
showUpload.value = false
|
fileList.value = []
|
currentId.value = ''
|
getList()
|
} catch (e) {
|
uni.hideLoading();
|
showToast('提交失败,请重试')
|
}
|
}
|
|
// 上传相关
|
const beforeReadPdf = (file) => {
|
// 兼容多文件
|
const files = Array.isArray(file) ? file : [file]
|
for (const f of files) {
|
const sizeOk = f.size <= 10 * 1024 * 1024
|
const ext = (f.name || '').split('.').pop()?.toLowerCase()
|
if (ext !== 'pdf') {
|
showToast('仅支持pdf文件')
|
return false
|
}
|
if (!sizeOk) {
|
showToast('上传文件大小不能超过10MB')
|
return false
|
}
|
}
|
return true
|
}
|
|
// uview-plus 的上传成功回调
|
const onUploadSuccess = (res, file) => {
|
try {
|
const data = JSON.parse(res.data || '{}')
|
if (data.code === 200) {
|
fileList.value.push(data.data)
|
showToast('上传成功')
|
} else {
|
showToast('上传失败: ' + (data.msg || '未知错误'))
|
}
|
} catch (err) {
|
showToast('上传失败')
|
}
|
}
|
|
// uview-plus 的上传失败回调
|
const onUploadError = (err) => {
|
showToast('上传失败')
|
}
|
|
const removeUploaded = (index) => {
|
fileList.value.splice(index, 1)
|
}
|
|
const getFileNameFromUrl = (url) => {
|
try {
|
if (!url) return ''
|
return decodeURIComponent(url.split('/').pop())
|
} catch (e) {
|
return url
|
}
|
}
|
|
// 附件查看
|
const openFileActions = (commonFiles) => {
|
currentFilesToOpen = commonFiles || []
|
fileActions.value = (commonFiles || []).map((f, idx) => ({
|
title: getFileNameFromUrl(f.url || ''),
|
index: idx
|
}))
|
showFileSheet.value = true
|
}
|
const onSelectFile = async (action) => {
|
try {
|
const item = currentFilesToOpen[action.index]
|
if (!item || !item.url) return
|
uni.showLoading({
|
title: '下载中...'
|
});
|
uni.downloadFile({
|
url: item.url,
|
success: (res) => {
|
uni.hideLoading();
|
if (res.statusCode === 200) {
|
uni.openDocument({ filePath: res.tempFilePath })
|
} else {
|
showToast('下载失败')
|
}
|
},
|
fail: () => {
|
uni.hideLoading();
|
showToast('下载失败')
|
}
|
})
|
} catch (e) {
|
uni.hideLoading();
|
showToast('打开失败')
|
}
|
}
|
|
onShow(() => {
|
getList()
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.u-divider {
|
margin: 0 !important;
|
}
|
.sales-account {
|
min-height: 100vh;
|
background: #f8f9fa;
|
position: relative;
|
}
|
|
.search-filter-section {
|
padding: 10px 20px;
|
background: #ffffff;
|
}
|
|
.search-bar {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
}
|
|
.search-input {
|
flex: 1;
|
background: #f5f5f5;
|
border-radius: 24px;
|
padding: 10px 16px;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.search-text {
|
flex: 1;
|
font-size: 14px;
|
color: #333;
|
background: transparent;
|
border: none;
|
outline: none;
|
}
|
|
.search-text::placeholder {
|
color: #999;
|
}
|
|
.filter-button {
|
width: 40px;
|
height: 40px;
|
border-radius: 8px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.ledger-list {
|
padding: 20px;
|
}
|
|
.ledger-item {
|
background: #ffffff;
|
border-radius: 12px;
|
margin-bottom: 16px;
|
overflow: hidden;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
padding: 0 16px;
|
}
|
|
.item-header {
|
padding: 16px 0;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.item-left {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.document-icon {
|
width: 24px;
|
height: 24px;
|
background: #2979ff;
|
border-radius: 4px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.item-id {
|
font-size: 14px;
|
color: #333;
|
font-weight: 500;
|
}
|
|
.item-details {
|
padding: 16px 0;
|
}
|
|
.detail-row {
|
display: flex;
|
align-items: flex-end;
|
justify-content: space-between;
|
margin-bottom: 8px;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
}
|
|
.detail-label {
|
font-size: 12px;
|
color: #777777;
|
min-width: 60px;
|
}
|
|
.detail-value {
|
font-size: 12px;
|
color: #000000;
|
text-align: right;
|
flex: 1;
|
margin-left: 16px;
|
}
|
|
.detail-value.highlight {
|
color: #2979ff;
|
font-weight: 500;
|
}
|
|
.no-data {
|
padding: 40px 0;
|
text-align: center;
|
color: #999;
|
}
|
|
.action-buttons {
|
display: flex;
|
gap: 12px;
|
padding: 0 0 16px 0;
|
justify-content: space-between;
|
}
|
|
.action-btn {
|
flex: 1;
|
}
|
|
.filter-popup {
|
padding: 12px 12px 20px;
|
}
|
|
.popup-header {
|
padding: 10px 16px;
|
border-bottom: 1px solid #f5f5f5;
|
}
|
|
.popup-title {
|
font-size: 16px;
|
font-weight: 500;
|
color: #333;
|
}
|
|
.uploaded-list {
|
padding: 8px 16px 0 16px;
|
}
|
|
.uploaded-item {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 8px 0;
|
border-bottom: 1px solid #f5f5f5;
|
}
|
|
.file-name {
|
font-size: 12px;
|
color: #333;
|
margin-right: 8px;
|
flex: 1;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.tip-text {
|
padding: 4px 16px 0 16px;
|
font-size: 12px;
|
color: #888;
|
}
|
|
.filter-actions {
|
display: flex;
|
gap: 12px;
|
padding: 12px 16px 16px;
|
justify-content: center;
|
}
|
|
.footer-btns {
|
position: fixed;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: #fff;
|
display: flex;
|
justify-content: space-around;
|
align-items: center;
|
padding: 0.75rem 0;
|
box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
|
z-index: 1000;
|
}
|
.cancel-btn {
|
font-weight: 400;
|
font-size: 1rem;
|
color: #FFFFFF;
|
width: 6.375rem;
|
background: #C7C9CC;
|
box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
|
border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
|
}
|
.save-btn {
|
font-weight: 400;
|
font-size: 1rem;
|
color: #FFFFFF;
|
width: 14rem;
|
background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
|
box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
|
border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
|
}
|
</style>
|