付款 收款 页面

This commit is contained in:
浅念
2025-09-24 19:07:27 +08:00
parent 90df51097f
commit 129ee07273
3 changed files with 1671 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
<template>
<div class="stocking-page">
<div class="stocking-toolbar boxshadow">
<div class="toolbar-left"></div>
<div class="toolbar-right">
<el-input
v-model="keyword"
placeholder="请输入客户名称"
clearable
style="width: 260px"
@keyup.enter="getList"
>
<template #suffix>
<el-icon class="clickable" @click="getList"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<div class="boxshadow">
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
<el-tab-pane label="未认领" name="unclaimed"></el-tab-pane>
<el-tab-pane label="已认领" name="claimed"></el-tab-pane>
</el-tabs>
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="阿里订单号/PI号" prop="flows_name" min-width="140" />
<ElTableColumn label="创建日期" prop="biz_desc" min-width="160" />
<ElTableColumn label="内勤" prop="initiator_name" min-width="120">
<template #default="scope">
<div style="display: flex; align-items: center">
<img class="avatar" :src="scope.row.initiator_avatar" alt="" />
<p>{{ scope.row.initiator_name }}</p>
</div>
</template>
</ElTableColumn>
<ElTableColumn label="付款来源" prop="approver_name" min-width="120" />
<ElTableColumn label="汇款人" prop="status_desc" min-width="120" />
<ElTableColumn label="账户" prop="created_at" min-width="120" />
<ElTableColumn label="账币种户" prop="created_at" min-width="120" />
<ElTableColumn label="金额" prop="created_at" min-width="120" />
<ElTableColumn label="操作" fixed="right" min-width="140" align="center">
<template #default>
<ElRow :gutter="10" justify="center">
<el-link
v-if="hasAuth('receipt_records@show')"
type="primary"
underline="never"
@click="handleView()"
style="margin-right: 8px"
>
<i class="iconfont-sys">&#xe76c;</i> 查看
</el-link>
<el-link
v-if="hasAuth('receipt_records@update')"
type="success"
underline="never"
@click="handleEdit()"
>
<i class="iconfont-sys">&#xe6bf;</i> 修改
</el-link>
<el-link
v-if="hasAuth('receipt_records@destroy')"
type="danger"
underline="never"
@click="handleDelete()"
>
<i class="iconfont-sys">&#xe6bf;</i> 删除
</el-link>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
</div>
</template>
<script setup lang="ts">
import { manageService } from '@/api/adminApi'
import { financeService } from '@/api/financeApi'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { useAuth } from '@/composables/useAuth'
defineOptions({ name: 'Adminclaim' })
const { hasAuth } = useAuth()
const loading = ref(false)
const keyword = ref('')
const tableRef = ref()
const tableData = ref<any[]>([])
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
const activeTab = ref('unclaimed')
// 审批弹出框相关
onMounted(() => {
getList()
})
watch([keyword], () => {
pagination.currentPage = 1
// 不自动请求,保持与截图行为一致,点击搜索或回车触发
})
const getList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
scene: 'todo',
keyword: keyword.value
}
const { records, current, size, total } = await manageService.stockapprovalrecord(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getList()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
getList()
}
const handleView = () => {}
const handleEdit = () => {}
const handleDelete = () => {}
const handleTabChange = (tab: any) => {
activeTab.value = tab.name
pagination.currentPage = 1
getList()
}
</script>
<style scoped lang="scss">
:deep(.el-tabs__nav-wrap:after){
background: #fff;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 6px;
margin-right: 10px;
}
.stocking-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.stocking-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 12px 16px;
background: var(--art-main-bg-color);
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
}
.clickable {
cursor: pointer;
}
// 创建收款记录 - 弹窗样式对齐设计图
.receipt-dialog {
.receipt-form {
.el-form-item {
margin-bottom: 20px;
}
}
.upload-demo {
width: 100%;
display: flex;
align-items: center;
// justify-content: flex-end;
// border: 1px dashed #dcdfe6;
border-radius: 6px;
cursor: pointer;
}
.avatarimg {
width: 60px;
cursor: pointer;
}
.el-button {
width: 200px;
height: 40px;
font-size: 16px;
}
}
:deep(.receipt-dialog .el-dialog__footer) {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
</style>
+903
View File
@@ -0,0 +1,903 @@
<template>
<div class="stocking-page">
<div class="stocking-toolbar boxshadow">
<div class="toolbar-left">
<ElButton
type="primary"
v-ripple
@click="handleCreate"
v-if="hasAuth('payment_records@add')"
>
创建付款记录
</ElButton>
</div>
<div class="toolbar-right">
<el-input
v-model="keyword"
placeholder="请输入客户名称"
clearable
style="width: 260px"
@keyup.enter="getList"
>
<template #suffix>
<el-icon class="clickable" @click="getList"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<div class="boxshadow">
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="创建日期" prop="created_at" min-width="140" />
<ElTableColumn label="付款来源" prop="country_name" min-width="160" />
<ElTableColumn label="汇款人" prop="remitter" min-width="120" />
<ElTableColumn label="账户" prop="account_name" min-width="120" />
<ElTableColumn label="币种" prop="currency_name" min-width="120" />
<ElTableColumn label="金额" prop="amount" min-width="120" />
<ElTableColumn label="操作" fixed="right" min-width="140" align="center">
<template #default="scope">
<ElRow :gutter="10" justify="center">
<el-link
v-if="hasAuth('payment_records@show')"
type="primary"
underline="never"
@click="handleView()"
style="margin-right: 8px"
>
<i class="iconfont-sys">&#xe76c;</i> 查看
</el-link>
<el-link
v-if="hasAuth('payment_records@update')"
type="success"
underline="never"
@click="handleEdit(scope.row)"
>
<i class="iconfont-sys">&#xe6bf;</i> 修改
</el-link>
<el-link
v-if="hasAuth('payment_records@destroy')"
type="danger"
underline="never"
@click="handleDelete(scope.row)"
>
<i class="iconfont-sys">&#xe6bf;</i> 删除
</el-link>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<!-- 新建/编辑付款记录 - 通用弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑付款记录' : '创建付款记录'"
width="900px"
class="receipt-dialog"
>
<el-form
ref="createFormRef"
:model="form"
:rules="createRules"
label-width="120px"
class="receipt-form"
>
<ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="阿里订单号/PI号">
<el-select
v-model="form.payment_apply_id"
placeholder="请选择阿里订单号/PI号"
@change="fetchOrders"
>
<el-option
v-for="item in piOptions"
:key="item.id"
:label="item.pi_no"
:value="item.pi_no"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="备货单">
<div style="display: flex; align-items: center; gap: 8px">
<ElButton
type="primary"
plain
:disabled="!form.payment_apply_id"
@click="openOrderPicker"
>
选择备货单
</ElButton>
<span v-if="Array.isArray(form.stock_id) && form.stock_id.length">
已选 {{ form.stock_id.length }} 条
</span>
</div>
<!-- <div
v-if="selectedOrders.length"
style="margin-top: 8px; display: flex; flex-wrap: wrap; gap: 8px"
>
<el-tag
v-for="it in selectedOrders"
:key="it.stock_orders_id"
closable
@close="removeSelectedOrder(it.stock_orders_id)"
>
{{ it.order_no }}
</el-tag>
</div> -->
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="业务员">
<el-input type="hidden" v-model="form.sales_user_id" placeholder="请填写业务员" />
<el-input v-model="form.sales_user_name" placeholder="请填写业务员" disabled />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="付款公司">
<el-select v-model="form.corp_id" placeholder="请选择付款公司">
<el-option
v-for="item in corpOptions"
:key="item.item_value"
:label="item.item_label"
:value="item.item_value"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="付款来源">
<el-select v-model="form.country_id" placeholder="请选择付款国家">
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addrs"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="汇款人">
<el-input v-model="form.remitter" placeholder="请填写汇款人" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="账户">
<el-select v-model="form.account_id" placeholder="请选择账户">
<el-option
v-for="item in accountOptions"
:key="item.id"
:label="item.short_desc"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="币种">
<el-select
v-model="form.currency_id"
placeholder="请选择币种"
@change="fetchExchangeRate"
>
<el-option
v-for="item in currencyOptions"
:key="item.id"
:label="item.currency_name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="汇率">
<el-input v-model="form.exchange_rate" placeholder="请填写汇率" disabled />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="总金额">
<el-input v-model="form.amount" placeholder="请填写总金额" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="汇款行">
<el-input v-model="form.swift" placeholder="请填写SWIFT号" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="客户付款水单">
<el-upload
class="upload-demo"
:action="actionImageUplode"
:headers="uploadHeaders"
:on-success="handleUploadSuccess"
:show-file-list="false"
:multiple="true"
>
<img :src="imageUrl1" alt="" class="avatarimg" />
<input type="hidden" v-model="form.voucher_file" />
</el-upload>
<div
v-if="Array.isArray(form.voucher_file_list) && form.voucher_file_list.length"
style="margin-top: 8px"
>
<div
v-for="(u, i) in form.voucher_file_list"
:key="i"
style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px"
>
<img
v-if="/\.(png|jpe?g|gif|webp|bmp|svg)(\?|$)/i.test((u || '').split('?')[0])"
:src="u"
style="
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 6px;
border: 1px solid var(--el-border-color);
"
/>
<a
v-else
:href="u"
target="_blank"
rel="noopener"
style="
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 200px;
"
>{{ (u || '').split('?')[0].split('/').pop() }}</a
>
<el-button link type="danger" @click="removeVoucher(i)" style="width: 80px"
>移除</el-button
>
</div>
</div>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="入账资料提交">
<el-upload
class="upload-demo"
:action="actionImageUplode"
:headers="uploadHeaders"
:on-success="handleUploadSuccess1"
:show-file-list="false"
:multiple="true"
>
<img :src="imageUrl1" alt="" class="avatarimg" />
<input type="hidden" v-model="form.submit_files" />
</el-upload>
<div
v-if="Array.isArray(form.submit_files_list) && form.submit_files_list.length"
style="margin-top: 8px"
>
<div
v-for="(u, i) in form.submit_files_list"
:key="i"
style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px"
>
<img
v-if="/\.(png|jpe?g|gif|webp|bmp|svg)(\?|$)/i.test((u || '').split('?')[0])"
:src="u"
style="
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 6px;
border: 1px solid var(--el-border-color);
"
/>
<a
v-else
:href="u"
target="_blank"
rel="noopener"
style="
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 200px;
"
>{{ (u || '').split('?')[0].split('/').pop() }}</a
>
<el-button link type="danger" @click="removeSubmit(i)" style="width: 80px"
>移除</el-button
>
</div>
</div>
</el-form-item>
</ElCol>
</ElRow>
</el-form>
<template #footer>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</template>
</el-dialog>
<!-- 选择备货单弹窗 -->
<el-dialog v-model="orderPicker.visible" title="选择备货单" width="800px" align-center>
<ArtTable :data="orderPicker.data" :loading="orderPicker.loading">
<template #default>
<ElTableColumn width="56" align="center">
<template #header>
<el-checkbox
:indeterminate="isOrderIndeterminate"
:model-value="isAllOrdersSelected"
@change="toggleSelectAllOrders"
/>
</template>
<template #default="scope">
<el-checkbox
:model-value="isOrderSelected(scope.row)"
@change="() => toggleOrder(scope.row)"
/>
</template>
</ElTableColumn>
<ElTableColumn label="备货单号" prop="order_no" min-width="160" />
<ElTableColumn label="业务员" prop="salesperson_snapshot" min-width="160" />
<ElTableColumn label="创建时间" prop="biz_date" min-width="160" />
</template>
</ArtTable>
<template #footer>
<ElButton type="primary" @click="orderPicker.visible = false">关闭</ElButton>
</template>
</el-dialog>
</div>
</template>
<script lang="ts"></script>
<script setup lang="ts">
import { commonService } from '@/api/commonApi'
import { financeService } from '@/api/admin/finance'
import { UserService } from '@/api/usersApi'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { purchaseService } from '@/api/admin/procurement'
import { useAuth } from '@/composables/useAuth'
import imageUrl1 from '@/assets/img/crm/upimg.png'
import { useUploadHeaders } from '@/composables/useUploadHeaders'
const { uploadHeaders } = useUploadHeaders()
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=payment_records`
defineOptions({ name: 'AdminReceiptRecords' })
const { hasAuth } = useAuth()
const loading = ref(false)
const keyword = ref('')
const tableRef = ref()
const tableData = ref<any[]>([])
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
const piOptions = ref<any>()
const orderOptions = ref<any>()
const loadOptions = async () => {
try {
const res = await purchaseService.purchaseorderpredata({ scene: 'pi_no' })
piOptions.value = res || []
} catch {
piOptions.value = []
}
}
const fetchOrders = async (pi_no: string) => {
try {
const res = await purchaseService.purchaseorderpredata({
scene: 'order_no',
pi_no: pi_no
})
orderOptions.value = res || []
// 切换 PI 后,清空已选备货单与业务员信息
form.value.stock_id = []
form.value.sales_user_name = ''
form.value.sales_user_id = ''
} catch {
orderOptions.value = []
}
}
//备货单选择器(多选)
const orderPicker = reactive({ visible: false, loading: false, data: [] as any[] })
// const selectedOrders = computed<any[]>(() => {
// const ids = new Set((form.value.stock_id || []).map((v) => String(v)))
// const source = Array.isArray(orderOptions.value) ? orderOptions.value : []
// return source.filter((it: any) => ids.has(String(it.stock_orders_id)))
// })
const isAllOrdersSelected = computed<boolean>(() => {
const data = Array.isArray(orderPicker.data) ? orderPicker.data : []
if (!data.length) return false
const ids = new Set((form.value.stock_id || []).map((v) => String(v)))
return data.every((it: any) => ids.has(String(it.stock_orders_id)))
})
const isOrderIndeterminate = computed<boolean>(() => {
const data = Array.isArray(orderPicker.data) ? orderPicker.data : []
const selected = (form.value.stock_id || []).length
return selected > 0 && selected < data.length
})
function openOrderPicker() {
if (!form.value.payment_apply_id) return
orderPicker.visible = true
// 如果未加载,则用现有 orderOptions;如需远程可在此拉取
orderPicker.data = Array.isArray(orderOptions.value) ? orderOptions.value : []
}
function isOrderSelected(row: any) {
return (form.value.stock_id || []).some((id) => String(id) === String(row?.stock_orders_id))
}
function toggleSelectAllOrders() {
const data = Array.isArray(orderPicker.data) ? orderPicker.data : []
if (!data.length) return
if (isAllOrdersSelected.value) {
// 取消全选
const dataIds = new Set(data.map((d: any) => String(d.stock_orders_id)))
form.value.stock_id = (form.value.stock_id || []).filter((id) => !dataIds.has(String(id)))
} else {
// 全选
const existed = new Set((form.value.stock_id || []).map((v) => String(v)))
const merge = [...(form.value.stock_id || [])]
data.forEach((d: any) => {
const id = d?.stock_orders_id
if (id != null && !existed.has(String(id))) merge.push(id)
})
form.value.stock_id = merge
if (!form.value.sales_user_id && data.length) {
form.value.sales_user_name = data[0]?.salesperson_snapshot || ''
form.value.sales_user_id = data[0]?.salesperson_id || ''
}
}
if (!(form.value.stock_id || []).length) {
form.value.sales_user_name = ''
form.value.sales_user_id = ''
}
}
function toggleOrder(row: any) {
const ids = Array.isArray(form.value.stock_id) ? [...form.value.stock_id] : []
const id = row?.stock_orders_id
if (id == null) return
const idx = ids.findIndex((v) => String(v) === String(id))
if (idx >= 0) {
ids.splice(idx, 1)
} else {
ids.push(id)
}
form.value.stock_id = ids
// 同步一次业务员(使用第一个所选的业务员信息)
if (!form.value.sales_user_id && ids.length) {
form.value.sales_user_name = row?.salesperson_snapshot || ''
form.value.sales_user_id = row?.salesperson_id || ''
}
if (!ids.length) {
form.value.sales_user_name = ''
form.value.sales_user_id = ''
}
}
function removeSelectedOrder(stock_orders_id: string | number) {
const next = (form.value.stock_id || []).filter((v) => String(v) !== String(stock_orders_id))
form.value.stock_id = next
if (next.length === 0) {
form.value.sales_user_name = ''
form.value.sales_user_id = ''
}
}
//国家
const countryOptions = ref<any[]>([])
const fetchCountryOptions = async () => {
try {
const res = await commonService.getCountrySelect()
res.forEach((item: any) => {
item.cn_addrs = item.cn_addr + '(' + item.en_addr + ')'
})
countryOptions.value = res || []
} catch {
countryOptions.value = []
}
}
//付款公司
const corpOptions = ref<any[]>([])
const fetchCorpOptions = async () => {
try {
const response = await commonService.getDictSelect({ scope: '21' })
corpOptions.value = response.payment_corp
} catch {
corpOptions.value = []
}
}
//账户
const accountOptions = ref<any[]>([])
const fetchAccountOptions = async () => {
try {
const res = await commonService.getAccountSelect()
accountOptions.value = res || []
} catch {
accountOptions.value = []
}
}
//币种
const currencyOptions = ref<any[]>([])
const fetchCurrencyOptions = async () => {
try {
const res = await commonService.getCurrencySelect()
currencyOptions.value = res || []
} catch {
currencyOptions.value = []
}
}
const fetchExchangeRate = async (currency_id: string) => {
try {
const list = Array.isArray(currencyOptions.value) ? currencyOptions.value : []
const picked = list.find((it: any) => String(it?.id ?? '') === String(currency_id))
if (!picked) {
form.value.exchange_rate = ''
return
}
form.value.exchange_rate = picked?.exchange_rate || ''
} catch {
form.value.exchange_rate = ''
}
}
// 审批弹出框相关
onMounted(() => {
getList()
})
watch([keyword], () => {
pagination.currentPage = 1
// 不自动请求,保持与截图行为一致,点击搜索或回车触发
})
const getList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
scene: 'todo',
keyword: keyword.value
}
const { records, current, size, total } = await financeService.getPaymentList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getList()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
getList()
}
// 新建/编辑弹窗通用状态
const dialogVisible = ref(false)
const createLoading = ref(false)
const isEdit = ref(false)
const currentId = ref<string | number | null>(null)
const createFormRef = ref<FormInstance>()
const form = ref({
payment_apply_id: '',
stock_id: [] as (string | number)[],
sales_user_id: '',
sales_user_name: '',
corp_id: '',
country_id: '',
remitter: '',
account_id: '',
currency_id: '',
exchange_rate: '',
amount: '',
swift: '',
voucher_file: [] as string[],
voucher_file_list: [] as string[],
submit_files: [] as string[],
submit_files_list: [] as string[]
})
const createRules: FormRules = {
payment_apply_id: [{ required: true, message: '请选择阿里订单号/PI号', trigger: 'blur' }],
sales_user_id: [{ required: true, message: '请选择业务员', trigger: 'blur' }],
corp_id: [{ required: true, message: '请选择付款公司', trigger: 'blur' }],
country_id: [{ required: true, message: '请选择付款国家', trigger: 'blur' }],
remitter: [{ required: true, message: '请输入汇款人', trigger: 'blur' }],
account_id: [{ required: true, message: '请选择账户', trigger: 'blur' }],
currency_id: [{ required: true, message: '请选择币种', trigger: 'blur' }],
exchange_rate: [{ required: true, message: '请输入汇率', trigger: 'blur' }],
amount: [{ required: true, message: '请输入金额', trigger: 'blur' }],
swift: [{ required: true, message: '请输入汇款行', trigger: 'blur' }]
}
const resetCreateForm = () => {
Object.assign(form.value, {
payment_apply_id: '',
stock_id: [],
sales_user_id: '',
sales_user_name: '',
corp_id: '',
country_id: '',
remitter: '',
account_id: '',
currency_id: '',
exchange_rate: '',
amount: '',
swift: '',
voucher_file: [],
voucher_file_list: [],
submit_files: [],
submit_files_list: []
})
currentId.value = null
createFormRef.value?.clearValidate?.()
}
const handleCreate = async () => {
resetCreateForm()
isEdit.value = false
loadOptions()
fetchAccountOptions()
fetchCurrencyOptions()
fetchCountryOptions()
fetchCorpOptions()
dialogVisible.value = true
}
const handleSubmit = () => {
createFormRef.value?.validate?.(async (valid: boolean) => {
if (!valid) return
try {
createLoading.value = true
// 根据新增/编辑分支处理
const payload: any = { ...form.value }
delete payload.submit_files_list
delete payload.voucher_file_list
payload.voucher_file = payload.voucher_file.join(',')
// 将所选备货单 ID 列表传给后端字段(兼容旧字段)
if (Array.isArray(payload.stock_id)) {
// 若后端仍接收单个 stock_id,可取第一个;否则保留 stock_id
payload.stock_id = payload.stock_id ?? payload.stock_id[0]
}
payload.stock = [
// 库存数组
{
stock_id: payload.stock_id, // 备货单ID
pi_on: payload.payment_apply_id
}
]
delete payload.payment_apply_id
delete payload.stock_id
if (isEdit.value) {
const forms = { ...payload, id: currentId.value }
const { message } = await financeService.updatePayment(forms)
ElMessage.success(message)
} else {
const { message } = await financeService.addPayment(payload)
ElMessage.success(message)
}
dialogVisible.value = false
getList()
} finally {
createLoading.value = false
}
})
}
const handleView = () => {}
const handleEdit = async (row?: any) => {
resetCreateForm()
isEdit.value = true
currentId.value = row?.id ?? null
// 如有需要,这里可按接口字段回填表单
const data = await financeService.getPaymentDetail({ id: row.id })
row = data || row
if (row && typeof row === 'object') {
const stockList = Array.isArray(row.stock) ? row.stock : []
const firstStock = stockList[0] || {}
const piNo = firstStock?.pi_no || row?.payment_apply_id || row?.pi_no || ''
form.value.payment_apply_id = piNo || form.value.payment_apply_id
// 组装已选备货单ID数组
const pickedIds = stockList
.map((s: any) => s?.stock_id || s?.stock_orders_id || s?.id)
.filter((v: any) => v != null)
if (pickedIds.length) {
form.value.stock_id = pickedIds
} else if (row?.stock_id) {
form.value.stock_id = [row.stock_id]
}
form.value.sales_user_id = row.sales_user_id ?? form.value.sales_user_id
form.value.sales_user_name = row.sales_user_name ?? form.value.sales_user_name
form.value.corp_id = row.corp_id ?? form.value.corp_id
form.value.country_id = row.country_id ?? form.value.country_id
form.value.remitter = row.remitter ?? form.value.remitter
form.value.account_id = row.account_id ?? form.value.account_id
form.value.currency_id = row.currency_id ?? form.value.currency_id
form.value.exchange_rate = row.exchange_rate ?? form.value.exchange_rate
form.value.amount = row.amount ?? form.value.amount
form.value.swift = row.swift ?? form.value.swift
// 处理客户付款水单文件
if (row.voucher_file) {
const voucherFiles = Array.isArray(row.voucher_file)
? row.voucher_file
: String(row.voucher_file)
.split(',')
.map((s: string) => s.trim())
.filter(Boolean)
form.value.voucher_file = voucherFiles
// 构造预览URL列表(假设文件路径需要拼接域名)
const baseUrl = import.meta.env.VITE_API_URL || ''
form.value.voucher_file_list = voucherFiles.map((file: string) =>
file.startsWith('http') ? file : `${baseUrl}/${file}`
)
}
// 处理入账资料提交文件
if (row.submit_files) {
const submitFiles = Array.isArray(row.submit_files)
? row.submit_files
: String(row.submit_files)
.split(',')
.map((s: string) => s.trim())
.filter(Boolean)
form.value.submit_files = submitFiles
// 构造预览URL列表
const baseUrl = import.meta.env.VITE_API_URL || ''
form.value.submit_files_list = submitFiles.map((file: string) =>
file.startsWith('http') ? file : `${baseUrl}/${file}`
)
}
}
loadOptions()
fetchAccountOptions()
fetchCurrencyOptions()
fetchCountryOptions()
fetchCorpOptions()
// 拉取当前 PI 的备货单列表用于弹窗展示与勾选回显
try {
const res = await purchaseService.purchaseorderpredata({
scene: 'order_no',
pi_no: form.value.payment_apply_id
})
orderOptions.value = res || []
} catch {
orderOptions.value = []
}
dialogVisible.value = true
}
const handleDelete = (row: { id: any }) => {
ElMessageBox.confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
financeService.deletePayment({ id: row.id }).then((res) => {
ElMessage.success(res.message)
getList()
})
})
}
function handleUploadSuccess(response: any) {
const fileName = response?.data?.file_name || ''
const fileUrl = response?.data?.file_url || response?.data?.full_url || response?.url || ''
const names = Array.isArray(form.value.voucher_file) ? [...form.value.voucher_file] : []
if (fileName) names.push(fileName)
form.value.voucher_file = names
const list = Array.isArray(form.value.voucher_file_list) ? form.value.voucher_file_list : []
list.push(fileUrl)
form.value.voucher_file_list = [...list]
ElMessage.success(response.message)
}
function handleUploadSuccess1(response: any) {
const fileName = response?.data?.file_name || ''
const fileUrl = response?.data?.file_url || response?.data?.full_url || response?.url || ''
const names = Array.isArray(form.value.submit_files) ? [...form.value.submit_files] : []
if (fileName) names.push(fileName)
form.value.submit_files = names
const list = Array.isArray(form.value.submit_files_list) ? form.value.submit_files_list : []
list.push(fileUrl)
form.value.submit_files_list = [...list]
ElMessage.success(response.message)
}
// 预览项移除(同步预览数组与提交字段)
function removeVoucher(index: number) {
const list = Array.isArray(form.value.voucher_file_list)
? [...form.value.voucher_file_list]
: []
if (index < 0 || index >= list.length) return
list.splice(index, 1)
form.value.voucher_file_list = list
const names = Array.isArray(form.value.voucher_file) ? [...form.value.voucher_file] : []
if (index < names.length) names.splice(index, 1)
form.value.voucher_file = names
}
function removeSubmit(index: number) {
const list = Array.isArray(form.value.submit_files_list)
? [...form.value.submit_files_list]
: []
if (index < 0 || index >= list.length) return
list.splice(index, 1)
form.value.submit_files_list = list
const names = Array.isArray(form.value.submit_files) ? [...form.value.submit_files] : []
if (index < names.length) names.splice(index, 1)
form.value.submit_files = names
}
</script>
<style scoped lang="scss">
.avatar {
width: 40px;
height: 40px;
border-radius: 6px;
margin-right: 10px;
}
.stocking-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.stocking-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 12px 16px;
background: var(--art-main-bg-color);
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
}
.clickable {
cursor: pointer;
}
// 创建收款记录 - 弹窗样式对齐设计图
.receipt-dialog {
.receipt-form {
.el-form-item {
margin-bottom: 20px;
}
}
.upload-demo {
width: 100%;
display: flex;
align-items: center;
border-radius: 6px;
cursor: pointer;
}
.avatarimg {
width: 60px;
cursor: pointer;
}
.el-button {
width: 200px;
height: 40px;
font-size: 16px;
}
}
:deep(.receipt-dialog .el-dialog__footer) {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
</style>
+548
View File
@@ -0,0 +1,548 @@
<template>
<div class="stocking-page">
<div class="stocking-toolbar boxshadow">
<div class="toolbar-left">
<ElButton
type="primary"
v-ripple
@click="handleCreate"
v-if="hasAuth('receipt_records@add')"
>
创建收款记录
</ElButton>
</div>
<div class="toolbar-right">
<el-input
v-model="keyword"
placeholder="请输入客户名称"
clearable
style="width: 260px"
@keyup.enter="getList"
>
<template #suffix>
<el-icon class="clickable" @click="getList"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<div class="boxshadow">
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="创建日期" prop="created_at" min-width="140" />
<ElTableColumn label="付款来源" prop="country_name" min-width="160" />
<ElTableColumn label="汇款人" prop="remitter" min-width="120" />
<ElTableColumn label="账户" prop="account_name" min-width="120" />
<ElTableColumn label="币种" prop="currency_name" min-width="120" />
<ElTableColumn label="金额" prop="amount" min-width="120" />
<ElTableColumn label="折让金额" prop="discount_amount" min-width="120" />
<ElTableColumn label="操作" fixed="right" min-width="140" align="center">
<template #default="scope">
<ElRow :gutter="10" justify="center">
<el-link
v-if="hasAuth('receipt_records@show')"
type="primary"
underline="never"
@click="handleView()"
style="margin-right: 8px"
>
<i class="iconfont-sys">&#xe76c;</i> 查看
</el-link>
<el-link
v-if="hasAuth('receipt_records@update')"
type="success"
underline="never"
@click="handleEdit(scope.row)"
>
<i class="iconfont-sys">&#xe6bf;</i> 修改
</el-link>
<el-link
v-if="hasAuth('receipt_records@destroy')"
type="danger"
underline="never"
@click="handleDelete()"
>
<i class="iconfont-sys">&#xe6bf;</i> 删除
</el-link>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<!-- 新建/编辑收款记录 - 通用弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑收款记录' : '创建收款记录'"
width="900px"
class="receipt-dialog"
>
<el-form
ref="createFormRef"
:model="form"
:rules="createRules"
label-width="180px"
class="receipt-form"
>
<ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="付款来源">
<el-select v-model="form.country_id" placeholder="请选择付款国家">
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addrs"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="汇款人">
<el-input v-model="form.remitter" placeholder="请填写汇款人" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="账户">
<el-select v-model="form.account_id" placeholder="请选择账户">
<el-option
v-for="item in accountOptions"
:key="item.id"
:label="item.short_desc"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="币种">
<el-select v-model="form.currency_id" placeholder="请选择币种">
<el-option
v-for="item in currencyOptions"
:key="item.id"
:label="item.currency_name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="金额">
<el-input v-model="form.amount" placeholder="请填写金额" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="到账时间">
<el-date-picker
v-model="form.arrived_at"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
type="date"
placeholder="请选择到账时间"
style="width: 100%"
/>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="折让金额">
<el-input v-model="form.discount_amount" placeholder="请填写折让金额" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="收汇凭证号">
<el-input v-model="form.voucher_no" placeholder="请填写收汇凭证号" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="实际汇率(出纳)">
<el-input v-model="form.exchange_rate" placeholder="请填写实际汇率" />
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="总金额(人民币)实际汇率">
<el-input
v-model="form.total_amount_cny"
placeholder="请填写总金额(人民币)实际汇率"
/>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="是否转回内地">
<el-radio-group v-model="form.is_domestic_return">
<el-radio :label="0">否</el-radio>
<el-radio :label="1">是</el-radio>
</el-radio-group>
</el-form-item>
</ElCol>
<ElCol :sm="24" :md="12" :lg="12">
<el-form-item label="外汇到账回单(出纳)">
<el-upload
class="upload-demo"
:action="actionImageUplode"
:headers="uploadHeaders"
:on-success="handleUploadSuccess1"
:show-file-list="false"
:multiple="true"
>
<img :src="imageUrl1" alt="" class="avatarimg" />
<input type="hidden" v-model="form.fx_receipt_file" />
</el-upload>
<div
v-if="Array.isArray(form.fx_receipt_file_list) && form.fx_receipt_file_list.length"
style="margin-top: 8px"
>
<div
v-for="(u, i) in form.fx_receipt_file_list"
:key="i"
style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px"
>
<img
v-if="/\.(png|jpe?g|gif|webp|bmp|svg)(\?|$)/i.test((u || '').split('?')[0])"
:src="u"
style="
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 6px;
border: 1px solid var(--el-border-color);
"
/>
<a
v-else
:href="u"
target="_blank"
rel="noopener"
style="
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 200px;
"
>{{ (u || '').split('?')[0].split('/').pop() }}</a
>
<el-button link type="danger" @click="removeVoucher(i)" style="width: 80px"
>移除</el-button
>
</div>
</div>
</el-form-item>
</ElCol>
</ElRow>
</el-form>
<template #footer>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { commonService } from '@/api/commonApi'
import { financeService } from '@/api/admin/finance'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { useAuth } from '@/composables/useAuth'
import imageUrl1 from '@/assets/img/crm/upimg.png'
import { useUploadHeaders } from '@/composables/useUploadHeaders'
const { uploadHeaders } = useUploadHeaders()
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=receipt_records`
defineOptions({ name: 'AdminReceiptRecords' })
const { hasAuth } = useAuth()
const loading = ref(false)
const keyword = ref('')
const tableRef = ref()
const tableData = ref<any[]>([])
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
//国家
const countryOptions = ref<any[]>([])
const fetchCountryOptions = async () => {
try {
const res = await commonService.getCountrySelect()
res.forEach((item: any) => {
item.cn_addrs = item.cn_addr + '(' + item.en_addr + ')'
})
countryOptions.value = res || []
} catch {
countryOptions.value = []
}
}
//账户
const accountOptions = ref<any[]>([])
const fetchAccountOptions = async () => {
try {
const res = await commonService.getAccountSelect()
accountOptions.value = res || []
} catch {
accountOptions.value = []
}
}
//币种
const currencyOptions = ref<any[]>([])
const fetchCurrencyOptions = async () => {
try {
const res = await commonService.getCurrencySelect()
currencyOptions.value = res || []
} catch {
currencyOptions.value = []
}
}
// 审批弹出框相关
onMounted(() => {
getList()
})
watch([keyword], () => {
pagination.currentPage = 1
// 不自动请求,保持与截图行为一致,点击搜索或回车触发
})
const getList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
keyword: keyword.value
}
const { records, current, size, total } = await financeService.getReceiptList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getList()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
getList()
}
// 新建/编辑弹窗通用状态
const dialogVisible = ref(false)
const createLoading = ref(false)
const isEdit = ref(false)
const currentId = ref<string | number | null>(null)
const createFormRef = ref<FormInstance>()
const form = ref({
country_id: '',
remitter: '',
account_id: '',
currency_id: '',
amount: '',
arrived_at: new Date().toISOString().split('T')[0],
discount_amount: '',
voucher_no: '',
total_amount_cny: '',
exchange_rate: '',
is_domestic_return: 0,
fx_receipt_file: '',
fx_receipt_file_list: [] as string[]
})
const createRules: FormRules = {
country_id: [{ required: true, message: '请选择付款来源', trigger: 'change' }],
remitter: [{ required: true, message: '请输入汇款人', trigger: 'blur' }],
account_id: [{ required: true, message: '请选择账户', trigger: 'change' }],
currency_id: [{ required: true, message: '请选择币种', trigger: 'change' }]
}
const resetCreateForm = () => {
Object.assign(form.value, {
country_id: '',
remitter: '',
account_id: '',
currency_id: '',
amount: '',
arrived_at: new Date().toISOString().split('T')[0],
discount_amount: '',
voucher_no: '',
total_amount_cny: '',
exchange_rate: '',
is_domestic_return: 0,
fx_receipt_file: '',
fx_receipt_file_list: []
})
currentId.value = null
createFormRef.value?.clearValidate?.()
}
const handleCreate = async () => {
resetCreateForm()
isEdit.value = false
fetchAccountOptions()
fetchCurrencyOptions()
fetchCountryOptions()
dialogVisible.value = true
}
const handleSubmit = () => {
createFormRef.value?.validate?.(async (valid: boolean) => {
if (!valid) return
try {
createLoading.value = true
// TODO: 根据新增/编辑调用对应接口
if (isEdit.value) {
const forms = { ...form.value, id: currentId.value }
const { message } = await financeService.updateReceipt(forms)
ElMessage.success(message)
} else {
const { message } = await financeService.addReceipt(form.value)
ElMessage.success(message)
}
dialogVisible.value = false
getList()
} finally {
createLoading.value = false
}
})
}
const handleView = () => {}
const handleEdit = (row?: any) => {
resetCreateForm()
isEdit.value = true
currentId.value = row?.id ?? null
if (row && typeof row === 'object') {
form.value.country_id = row.country_id ?? form.value.country_id
form.value.remitter = row.remitter ?? form.value.remitter
form.value.account_id = row.account_id ?? form.value.account_id
form.value.currency_id = row.currency_id ?? form.value.currency_id
form.value.amount = row.amount ?? form.value.amount
form.value.arrived_at = row.arrived_at ?? form.value.arrived_at
form.value.discount_amount = row.discount_amount ?? form.value.discount_amount
form.value.voucher_no = row.voucher_no ?? form.value.voucher_no
form.value.total_amount_cny = row.total_amount_cny ?? form.value.total_amount_cny
form.value.exchange_rate = row.exchange_rate ?? form.value.exchange_rate
form.value.is_domestic_return = row.is_domestic_return ?? form.value.is_domestic_return
form.value.fx_receipt_file = row.fx_receipt_file ?? form.value.fx_receipt_file
form.value.fx_receipt_file_list = Array.isArray(row.fx_receipt_file_list)
? row.fx_receipt_file_list
: form.value.fx_receipt_file_list
}
fetchAccountOptions()
fetchCurrencyOptions()
fetchCountryOptions()
dialogVisible.value = true
}
const handleDelete = () => {
ElMessageBox.confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
financeService.deleteReceipt({ id: currentId.value }).then((res) => {
ElMessage.success(res.message)
getList()
})
})
}
function handleUploadSuccess1(response: any) {
const fileName = response?.data?.file_name || ''
const fileUrl = response?.data?.file_url || response?.data?.full_url || response?.url || ''
form.value.fx_receipt_file = [form.value.fx_receipt_file, fileName].filter(Boolean).join(',')
const list = Array.isArray(form.value.fx_receipt_file_list)
? form.value.fx_receipt_file_list
: []
list.push(fileUrl)
form.value.fx_receipt_file_list = [...list]
ElMessage.success(response.message)
}
// 预览项移除(同步预览数组与提交字段)
function removeVoucher(index: number) {
const list = Array.isArray(form.value.fx_receipt_file_list)
? [...form.value.fx_receipt_file_list]
: []
if (index < 0 || index >= list.length) return
list.splice(index, 1)
form.value.fx_receipt_file_list = list
const names = (form.value.fx_receipt_file || '').split(',').filter(Boolean)
if (index < names.length) names.splice(index, 1)
form.value.fx_receipt_file = names.join(',')
}
</script>
<style scoped lang="scss">
.avatar {
width: 40px;
height: 40px;
border-radius: 6px;
margin-right: 10px;
}
.stocking-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.stocking-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 12px 16px;
background: var(--art-main-bg-color);
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
}
.clickable {
cursor: pointer;
}
// 创建收款记录 - 弹窗样式对齐设计图
.receipt-dialog {
.receipt-form {
.el-form-item {
margin-bottom: 20px;
}
}
.upload-demo {
width: 100%;
display: flex;
align-items: center;
// justify-content: flex-end;
// border: 1px dashed #dcdfe6;
border-radius: 6px;
cursor: pointer;
}
.avatarimg {
width: 60px;
cursor: pointer;
}
.el-button {
width: 200px;
height: 40px;
font-size: 16px;
}
}
:deep(.receipt-dialog .el-dialog__footer) {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
</style>