Files
zkxw/web-admin/src/views/oa-approval/receive-accounts/index.vue
T
zlwuandCursor d37bb41948 feat: 收款账户管理页展示不可修改的所属人
列表和查看能看出登记人,编辑时只读,避免把账户转给别人。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 19:08:32 +08:00

620 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- OA 收款账户管理(租户级公共主数据) -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
<section class="panel">
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
<el-form-item label="关键词">
<el-input
v-model="searchForm.keyword"
placeholder="户名 / 账号 / 银行"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="账户类型">
<el-select v-model="searchForm.accountType" placeholder="请选择" clearable>
<el-option
v-for="(label, value) in ACCOUNT_TYPE_LABEL"
:key="value"
:label="label"
:value="value"
/>
</el-select>
</el-form-item>
</div>
<div class="filter-actions-row">
<div class="filter-actions-end">
<el-button class="btn-ghost" @click="handleReset">
<el-icon><RefreshLeft /></el-icon>重置
</el-button>
<el-button class="btn-main" type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>搜索
</el-button>
</div>
</div>
</el-form>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增账户
</el-button>
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</div>
</div>
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="id"
@selection-change="(rows: any[]) => (selectedRows = rows)"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column
prop="accountType"
label="账户类型"
min-width="120"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-tag :type="typeTagType(row.accountType)" size="small">
{{ ACCOUNT_TYPE_LABEL[row.accountType as ReceiveAccountType] || row.accountType }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="accountName"
label="户名"
min-width="140"
align="left"
header-align="left"
show-overflow-tooltip
/>
<el-table-column
label="所属人"
min-width="120"
align="left"
header-align="left"
show-overflow-tooltip
>
<template #default="{ row }">{{ formatDisplayOrDash(row.ownerName) }}</template>
</el-table-column>
<el-table-column
prop="accountNo"
label="账号"
min-width="160"
align="left"
header-align="left"
show-overflow-tooltip
/>
<el-table-column
prop="bankName"
label="银行"
min-width="120"
align="left"
header-align="left"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.accountType === 'alipay' ? '—' : formatDisplayOrDash(row.bankName) }}
</template>
</el-table-column>
<el-table-column
label="开户地 / 支行"
min-width="200"
align="left"
header-align="left"
show-overflow-tooltip
>
<template #default="{ row }">
<template v-if="row.accountType === 'alipay'">—</template>
<template v-else>
<div>{{ regionText(row) }}</div>
<div v-if="row.bankBranch" class="cell-sub">{{ row.bankBranch }}</div>
</template>
</template>
</el-table-column>
<el-table-column
prop="remark"
label="备注"
min-width="120"
align="left"
header-align="left"
show-overflow-tooltip
>
<template #default="{ row }">{{ formatDisplayOrDash(row.remark) }}</template>
</el-table-column>
<el-table-column
label="操作"
min-width="260"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<div style="display: flex; justify-content: center; gap: 8px">
<el-button class="btn-op-item" @click="handleView(row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-button class="btn-op-item" @click="handleEdit(row)">
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button class="btn-op-item" @click="handleDelete(row)">
<el-icon><Delete /></el-icon>删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
v-model:current-page="pager.page"
v-model:page-size="pager.pageSize"
:total="pager.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
background
@size-change="loadData"
@current-change="loadData"
/>
</div>
</div>
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="720px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<div class="form-grid-2">
<el-form-item label="账户类型" prop="accountType" class="form-col-span-2">
<el-select
v-model="formData.accountType"
placeholder="请选择"
:disabled="isEdit"
@change="onTypeChange"
>
<el-option
v-for="(label, value) in ACCOUNT_TYPE_LABEL"
:key="value"
:label="label"
:value="value"
/>
</el-select>
</el-form-item>
<el-form-item label="户名" prop="accountName">
<el-input v-model="formData.accountName" placeholder="请输入户名" maxlength="128" />
</el-form-item>
<el-form-item v-if="isEdit" label="所属人">
<el-input :model-value="editingOwnerName" disabled />
</el-form-item>
<el-form-item label="账号" prop="accountNo">
<el-input v-model="formData.accountNo" placeholder="请输入账号" maxlength="128" />
</el-form-item>
<template v-if="formData.accountType !== 'alipay'">
<el-form-item label="银行" prop="bankName">
<el-input
v-model="formData.bankName"
placeholder="如:中国工商银行"
maxlength="128"
/>
</el-form-item>
<el-form-item label="银行所在省" prop="bankProvince">
<el-input
v-model="formData.bankProvince"
placeholder="如:安徽省"
maxlength="64"
clearable
/>
</el-form-item>
<el-form-item label="银行所在市" prop="bankCity">
<el-input
v-model="formData.bankCity"
placeholder="如:合肥市"
maxlength="64"
clearable
/>
</el-form-item>
<el-form-item label="银行支行" prop="bankBranch" class="form-col-span-2">
<el-input
v-model="formData.bankBranch"
placeholder="请输入开户支行全称"
maxlength="256"
/>
</el-form-item>
</template>
<el-form-item label="备注" class="form-col-span-2">
<el-input
v-model="formData.remark"
type="textarea"
:rows="2"
placeholder="选填"
maxlength="512"
/>
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="dialogVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="handleSubmit">
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="viewVisible"
title="查看收款账户"
width="640px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
>
<div v-if="viewRow" class="view-grid">
<div class="view-item">
<div class="k">账户类型</div>
<div class="v">
<el-tag :type="typeTagType(viewRow.accountType)" size="small">
{{
ACCOUNT_TYPE_LABEL[viewRow.accountType as ReceiveAccountType] ||
viewRow.accountType
}}
</el-tag>
</div>
</div>
<div class="view-item">
<div class="k">户名</div>
<div class="v">{{ formatDisplayOrDash(viewRow.accountName) }}</div>
</div>
<div class="view-item">
<div class="k">所属人</div>
<div class="v">{{ formatDisplayOrDash(viewRow.ownerName) }}</div>
</div>
<div class="view-item">
<div class="k">账号</div>
<div class="v">{{ formatDisplayOrDash(viewRow.accountNo) }}</div>
</div>
<template v-if="viewRow.accountType !== 'alipay'">
<div class="view-item">
<div class="k">银行</div>
<div class="v">{{ formatDisplayOrDash(viewRow.bankName) }}</div>
</div>
<div class="view-item">
<div class="k">银行所在省 / 市</div>
<div class="v">{{ regionText(viewRow) }}</div>
</div>
<div class="view-item full">
<div class="k">银行支行</div>
<div class="v">{{ formatDisplayOrDash(viewRow.bankBranch) }}</div>
</div>
</template>
<div class="view-item full">
<div class="k">备注</div>
<div class="v">{{ formatDisplayOrDash(viewRow.remark) }}</div>
</div>
</div>
<template #footer>
<el-button class="btn-main" type="primary" @click="viewVisible = false">
<el-icon><Check /></el-icon>
关闭
</el-button>
</template>
</el-dialog>
</section>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
Plus,
Close,
Check,
Edit,
Delete,
View,
RefreshLeft,
Search
} from '@element-plus/icons-vue'
import { formatDisplayOrDash } from '@/utils/datetime-display'
import {
fetchBatchDeleteOaReceiveAccounts,
fetchCreateOaReceiveAccount,
fetchDeleteOaReceiveAccount,
fetchOaReceiveAccountList,
fetchUpdateOaReceiveAccount
} from '@/api/oaApproval'
import {
ACCOUNT_TYPE_LABEL,
type ReceiveAccountType
} from '@/utils/oa-receive-account-value'
defineOptions({ name: 'OaApprovalReceiveAccounts' })
const searchForm = reactive({ keyword: '', accountType: '' })
const tableData = ref<any[]>([])
const selectedRows = ref<any[]>([])
const loading = ref(false)
const pager = reactive({ page: 1, pageSize: 10, total: 0 })
const dialogVisible = ref(false)
const dialogTitle = ref('新增收款账户')
const isEdit = ref(false)
const editingOwnerName = ref('')
const editingId = ref<string | null>(null)
const submitLoading = ref(false)
const formRef = ref()
const formData = reactive({
accountType: 'alipay' as ReceiveAccountType,
accountName: '',
accountNo: '',
bankName: '',
bankProvince: '',
bankCity: '',
bankBranch: '',
remark: ''
})
const requireWhenBank = (_rule: any, value: string, callback: (e?: Error) => void) => {
if (formData.accountType === 'alipay') {
callback()
return
}
if (!String(value || '').trim()) {
callback(new Error('请填写'))
return
}
callback()
}
const formRules = computed(() => ({
accountType: [{ required: true, message: '请选择账户类型', trigger: 'change' }],
accountName: [{ required: true, message: '请输入户名', trigger: 'blur' }],
accountNo: [{ required: true, message: '请输入账号', trigger: 'blur' }],
bankName: [{ validator: requireWhenBank, trigger: 'blur' }],
bankProvince: [{ validator: requireWhenBank, trigger: 'blur' }],
bankCity: [{ validator: requireWhenBank, trigger: 'blur' }],
bankBranch: [{ validator: requireWhenBank, trigger: 'blur' }]
}))
const viewVisible = ref(false)
const viewRow = ref<any>(null)
function typeTagType(t: string): 'success' | 'primary' | 'warning' | 'info' {
if (t === 'alipay') return 'success'
if (t === 'personal') return 'primary'
if (t === 'corporate') return 'warning'
return 'info'
}
function regionText(row: any) {
const p = String(row?.bankProvince || '').trim()
const c = String(row?.bankCity || '').trim()
if (p && c) return `${p} / ${c}`
return p || c || '—'
}
async function loadData() {
loading.value = true
try {
const res = await fetchOaReceiveAccountList({
keyword: searchForm.keyword || undefined,
accountType: searchForm.accountType || undefined,
page: pager.page,
pageSize: pager.pageSize
})
tableData.value = res?.list || []
pager.total = Number(res?.total || 0)
} finally {
loading.value = false
}
}
function handleSearch() {
pager.page = 1
loadData()
}
function handleReset() {
searchForm.keyword = ''
searchForm.accountType = ''
pager.page = 1
loadData()
}
function clearBankFields() {
formData.bankName = ''
formData.bankProvince = ''
formData.bankCity = ''
formData.bankBranch = ''
}
function onTypeChange() {
if (formData.accountType === 'alipay') clearBankFields()
formRef.value?.clearValidate?.()
}
function resetForm() {
formData.accountType = 'alipay'
formData.accountName = ''
formData.accountNo = ''
clearBankFields()
formData.remark = ''
editingId.value = null
}
function handleAdd() {
isEdit.value = false
dialogTitle.value = '新增收款账户'
resetForm()
editingOwnerName.value = ''
dialogVisible.value = true
formRef.value?.clearValidate?.()
}
function handleEdit(row: any) {
isEdit.value = true
dialogTitle.value = '编辑收款账户'
editingId.value = row.id
formData.accountType = (row.accountType || 'alipay') as ReceiveAccountType
formData.accountName = row.accountName || ''
formData.accountNo = row.accountNo || ''
formData.bankName = row.bankName || ''
formData.bankProvince = row.bankProvince || ''
formData.bankCity = row.bankCity || ''
formData.bankBranch = row.bankBranch || ''
formData.remark = row.remark || ''
if (formData.accountType === 'alipay') clearBankFields()
editingOwnerName.value = row.ownerName || '—'
dialogVisible.value = true
formRef.value?.clearValidate?.()
}
function handleView(row: any) {
viewRow.value = row
viewVisible.value = true
}
async function handleSubmit() {
try {
await formRef.value?.validate?.()
} catch {
ElMessageBox.alert('请完善必填项后再提交', '提示', { type: 'warning' })
return
}
submitLoading.value = true
try {
const payload: Record<string, unknown> = {
accountType: formData.accountType,
accountName: formData.accountName.trim(),
accountNo: formData.accountNo.trim(),
remark: formData.remark?.trim() || undefined
}
if (formData.accountType === 'alipay') {
payload.bankName = ''
payload.bankProvince = ''
payload.bankCity = ''
payload.bankBranch = ''
} else {
payload.bankName = formData.bankName.trim()
payload.bankProvince = formData.bankProvince.trim()
payload.bankCity = formData.bankCity.trim()
payload.bankBranch = formData.bankBranch.trim()
}
if (isEdit.value && editingId.value) {
await fetchUpdateOaReceiveAccount(editingId.value, payload)
} else {
await fetchCreateOaReceiveAccount(payload)
}
dialogVisible.value = false
await loadData()
} finally {
submitLoading.value = false
}
}
async function handleDelete(row: any) {
await ElMessageBox.confirm(`确定删除收款账户「${row.accountName}」吗?`, '提示', {
type: 'warning'
})
await fetchDeleteOaReceiveAccount(row.id)
await loadData()
}
async function handleBatchDelete() {
if (!selectedRows.value.length) return
await ElMessageBox.confirm(
`确定删除已选 ${selectedRows.value.length} 个收款账户吗?`,
'提示',
{ type: 'warning' }
)
const ids = selectedRows.value.map((r) => r.id).filter(Boolean)
await fetchBatchDeleteOaReceiveAccounts(ids)
selectedRows.value = []
await loadData()
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
.cell-sub {
margin-top: 2px;
font-size: 12px;
color: var(--mint-text-muted, #64748b);
}
.view-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px 20px;
}
.view-item.full {
grid-column: 1 / -1;
}
.view-item .k {
margin-bottom: 4px;
font-size: 12px;
color: var(--mint-text-muted, #64748b);
}
.view-item .v {
font-weight: 500;
word-break: break-all;
}
@media (max-width: 640px) {
.view-grid {
grid-template-columns: 1fr;
}
}
</style>