feat:财税管理-报销单相关

feature-JimuReport-1106-yjl
yujinlong 4 weeks ago
parent c847def6f2
commit 37f83da6ba

@ -10,7 +10,7 @@
/>
<title><%= title %></title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3756681_ddosvr0ugmq.css" />
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_3756681_4lfn8xzlqbu.css" />
</head>
<body>
<script>

@ -64,6 +64,7 @@
"mockjs": "^1.1.0",
"moment": "^2.30.1",
"nprogress": "^0.2.0",
"number-precision": "^1.6.0",
"path-to-regexp": "^6.2.0",
"pinia": "2.0.12",
"qrcode": "^1.5.4",

@ -66,7 +66,11 @@
<template #title>
<span>保存</span>
</template>
<span v-if="save && showBtns.includes('save')" class="ds-action-svg-btn" @click="saveHandle">
<span
v-if="save && showBtns.includes('save')"
class="ds-action-svg-btn"
@click="saveHandle"
>
<SvgIcon size="18" name="save" />
</span>
</a-tooltip>
@ -74,7 +78,11 @@
<template #title>
<span>复制</span>
</template>
<span v-if="copy && showBtns.includes('copy')" class="ds-action-svg-btn" @click="copyHandle">
<span
v-if="copy && showBtns.includes('copy')"
class="ds-action-svg-btn"
@click="copyHandle"
>
<SvgIcon size="18" name="copy" />
</span>
</a-tooltip>
@ -90,7 +98,11 @@
<template #title>
<span>打印</span>
</template>
<span v-if="code && showBtns.includes('print')" class="ds-action-svg-btn" @click="openPrint">
<span
v-if="code && showBtns.includes('print')"
class="ds-action-svg-btn"
@click="openPrint"
>
<SvgIcon size="18" name="print" />
</span>
</a-tooltip>
@ -98,7 +110,11 @@
<template #title>
<span>提交</span>
</template>
<span v-if="submit && showBtns.includes('submit')" class="ds-action-svg-btn" @click="submitHandle">
<span
v-if="submit && showBtns.includes('submit')"
class="ds-action-svg-btn"
@click="submitHandle"
>
<SvgIcon size="18" name="submit1" />
</span>
</a-tooltip>
@ -121,7 +137,11 @@
<template #title>
<span>导入</span>
</template>
<span v-if="code && showBtns.includes('import')" class="ds-action-svg-btn" @click="importHandle">
<span
v-if="code && showBtns.includes('import')"
class="ds-action-svg-btn"
@click="importHandle"
>
<SvgIcon size="18" name="import" />
</span>
</a-tooltip>
@ -129,7 +149,11 @@
<template #title>
<span>导出</span>
</template>
<span v-if="code && showBtns.includes('export')" class="ds-action-svg-btn" @click="exportHandle">
<span
v-if="code && showBtns.includes('export')"
class="ds-action-svg-btn"
@click="exportHandle"
>
<SvgIcon size="18" name="export" />
</span>
</a-tooltip>
@ -151,7 +175,13 @@
</a-tooltip>
<slot></slot>
</div>
<DsPrint :bookingData=bookingData ref="dsPrint" :code="code" :paramJsonStr="paramJsonStr" :cid="cid" />
<DsPrint
:bookingData="bookingData"
ref="dsPrint"
:code="code"
:paramJsonStr="paramJsonStr"
:cid="cid"
/>
</div>
</template>
<script lang="ts">
@ -190,22 +220,22 @@
// idkey
name: {
type: String,
default: null
default: null,
},
//
save: {
type: Function,
default: null
default: null,
},
//
copy: {
type: Function,
default: null
default: null,
},
//
submit: {
type: Function,
default: null
default: null,
},
//
lasttext: {
@ -224,50 +254,54 @@
//
showBtns: {
type: Array,
default: ['save', 'submit', 'copy', 'print', 'next', 'last']
default: ['save', 'submit', 'copy', 'print', 'next', 'last'],
},
//
paramJsonStr: {
type: String,
default: null
default: null,
},
//
create: {
type: Function,
default: null
default: null,
},
//
delete: {
type: Function,
default: null
default: null,
},
//
withdraw: {
type: Function,
default: null
default: null,
},
//
exportFile: {
type: Function,
default: null
default: null,
},
//
importFile: {
type: Function,
default: null
default: null,
},
//
//
bookingData: {
type: Object,
default: () => {
return {}
}
}
},
},
getExtraQuery: {
type: Function,
default: null,
},
},
emits: ['copy'],
components: {
DsPrint,
SvgIcon
SvgIcon,
},
setup(props, context) {
const { t } = useI18n()
@ -290,6 +324,7 @@
if (obj && obj[props.name]) {
const ids = obj[props.name]
const index = ids.indexOf(props.id)
let extraIndex = index
let id = ''
if (v == 'next') {
//
@ -297,17 +332,21 @@
return createMessage.warning('该票为当前页码第一票!')
}
id = ids[index - 1]
extraIndex = index - 1
} else {
//
if (index == 19) {
return createMessage.warning('该票为当前页码最后一票!')
}
id = ids[index + 1]
extraIndex = index + 1
}
const extraQuery = props?.getExtraQuery(extraIndex) || {}
go({
path: route.path,
query: {
id,
...extraQuery,
},
})
closePage(route.path + '?id=' + id)

@ -133,7 +133,7 @@ export interface FormProps {
}
export interface FormSchema {
// Field name
field: string
field?: string
// Event name triggered by internal value change, default change
changeEvent?: string
// Variable name bound to v-model Default value

@ -21,7 +21,7 @@ export function formatTableData(v) {
/* ()
* topath: path
*/
export function closePage(topath) {
export function closePage(topath?) {
const tabStore = useMultipleTabStore()
tabStore.closeTab(router.currentRoute._value, router)
// 有目标路径则跳转

@ -14,7 +14,7 @@ import { unref } from 'vue'
import { useRouter } from 'vue-router'
import { REDIRECT_NAME } from '/@/router/constant'
export type PathAsPageEnum<T> = T extends { path: string } ? T & { path: PageEnum } : T
export type PathAsPageEnum<T> = T extends { path: string } ? T & { path: PageEnum | string } : T
export type RouteLocationRawEx = PathAsPageEnum<RouteLocationRaw>
function handleError(e: Error) {
@ -27,7 +27,7 @@ function handleError(e: Error) {
export function useGo(_router?: Router) {
const { push, replace } = _router || useRouter()
function go(opt: RouteLocationRawEx = PageEnum.BASE_HOME, isReplace = false) {
if (!opt) {
return
}

@ -78,7 +78,9 @@
const { getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting()
function RText(data) {
let Rdata = ''
if (data?.query?.tabName && data.meta.title == '订舱审批') {
const customTabsList = ['订舱审批', '报销单详情']
if (data?.query?.tabName && customTabsList.includes(data.meta.title)) {
// tab
Rdata = data.query.tabName
} else if (data.meta.title == '海运出口-详情') {
@ -169,7 +171,7 @@
getShowRedo,
getShowFold,
tabWidth,
RText
RText,
}
},
})

@ -47,6 +47,7 @@ interface AppState {
agentFilter: any
slotShow: boolean
ids: any
idsData: Recordable
SourceData: any
}
let timeId: TimeoutHandle
@ -80,6 +81,8 @@ export const useAppStore = defineStore({
FlowInstancesDetailsType: '',
// 存储列表id集合上下票使用
ids: {},
// 存储列表数据,上下票使用
idsData: {},
// 业务来源>业务明细 缓存
SourceData: [],
}),
@ -90,6 +93,9 @@ export const useAppStore = defineStore({
getIds(): any {
return this.ids
},
getIdsData(): any {
return this.idsData
},
getslotShow(): any {
return this.slotShow
},
@ -188,6 +194,9 @@ export const useAppStore = defineStore({
setIds(data, key): void {
this.ids[key] = data
},
setIdsData(data, key): void {
this.idsData[key] = data
},
setslotShow(data): void {
this.slotShow = data
},

@ -25,7 +25,7 @@ export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
}
}
}
} catch (err) {}
} catch (err) { }
return result
}
@ -326,3 +326,20 @@ export function stringIsNull(str) {
// 两个 == 可以同时判断 null 和 undefined
return str == null || str === 'null' || str === 'undefined'
}
/**
*
* @param num
* @param fixedDigit = 2
* @param needThousand = true
* @return {string}
*/
export function numberThousandFormat(num: number | string, fixedDigit = 2, needThousand = true) {
const number = Number(num) || 0
if (number) {
if (needThousand) return number.toFixed(fixedDigit).replace(/(\d)(?=(\d{3})+\.)/g, '$1,')
return number.toFixed(fixedDigit)
} else {
return '0.00'
}
}

@ -1,4 +1,3 @@
// @ts-ignore
import { request } from '/@/utils/request'
import { DataResult, PageRequest } from '/@/api/model/baseModel'
enum Api {

@ -1,4 +1,5 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
import { numberThousandFormat } from '/@/utils/commonUtil'
export type BankItem = {
accountId: string
@ -80,6 +81,9 @@ export const columns: BasicColumn[] = [
width: 100,
dataIndex: 'tradeAmount',
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '交易后余额',
@ -87,6 +91,9 @@ export const columns: BasicColumn[] = [
width: 150,
dataIndex: 'afterTransactionBalance',
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '交易货币',
@ -178,6 +185,9 @@ export const columns: BasicColumn[] = [
width: 100,
dataIndex: 'exchangeRate',
sorter: true,
customRender({ text }) {
return numberThousandFormat(text, 2, false)
},
},
{
title: '交易流水号',

@ -1,151 +0,0 @@
import { request } from '/@/utils/request'
// 获取列表 (Auth)
export function PaymentSettlementGetList(parameter) {
return request({
url: '/feeApi/PaymentSettlement/GetList',
method: 'post',
data: parameter,
})
}
export function GetOrgList() {
return request({
url: '/mainApi/Common/GetOrgList',
method: 'get',
})
}
export function InvoiceSettlementGetInvoiceList(parameter) {
return request({
url: '/feeApi/InvoiceSettlement/GetInvoiceList',
method: 'post',
data: parameter,
})
}
export function InvoiceSettlementGetInvoiceDetails(parameter) {
return request({
url: '/feeApi/InvoiceSettlement/GetInvoiceDetails',
method: 'post',
data: parameter,
})
}
export function InvoiceSettlementGetExchanges(parameter) {
return request({
url: '/feeApi/InvoiceSettlement/GetExchanges',
method: 'post',
data: parameter,
})
}
export function GetExchangeRate(params) {
return request({
url: '/feeApi/FeeCurrencyExchange/GetExchangeRate',
method: 'get',
params
})
}
export function InvoiceSettlementSave(parameter) {
return request({
url: '/feeApi/InvoiceSettlement/Save',
method: 'post',
data: parameter,
})
}
export function InvoiceSettlementGet(params) {
return request({
url: '/feeApi/InvoiceSettlement/Get',
method: 'get',
params
})
}
export function ClientCommonGetStlMode(params) {
return request({
url: '/mainApi/ClientCommon/GetStlMode',
method: 'get',
params
})
}
export function GetClientBankList(parameter) {
return request({
url: '/mainApi/ClientBank/GetClientBankList',
method: 'post',
data: parameter,
})
}
export function PaymentSettlementDelete(parameter) {
return request({
url: '/feeApi/PaymentSettlement/Delete',
method: 'post',
data: parameter,
})
}
export function GeneralInvoiceGet(params) {
return request({
url: '/feeApi/GeneralInvoice/Get',
method: 'get',
params
})
}
export function PaymentSettlementSetLock(parameter) {
return request({
url: '/feeApi/PaymentSettlement/SetLock',
method: 'post',
data: parameter,
})
}
export function PaymentSettlementDeleteDetail(parameter) {
return request({
url: '/feeApi/PaymentSettlement/DeleteDetail',
method: 'post',
data: parameter,
})
}
export function PaymentFreeSettlementGetBizList(parameter) {
return request({
url: '/feeApi/PaymentFreeSettlement/GetBizList',
method: 'post',
data: parameter,
})
}
export function PaymentFreeSettlementGetFees(parameter) {
return request({
url: '/feeApi/PaymentFreeSettlement/GetFees',
method: 'post',
data: parameter,
})
}
export function PaymentFreeSettlementSave(parameter) {
return request({
url: '/feeApi/PaymentFreeSettlement/Save',
method: 'post',
data: parameter,
})
}
export function PaymentFreeSettlementGet(parameter) {
return request({
url: '/feeApi/PaymentFreeSettlement/Get',
method: 'get',
params: parameter,
})
}
export function PaymentFreeSettlementGetExchangesAsync(parameter) {
return request({
url: '/feeApi/PaymentFreeSettlement/GetCurrencies',
method: 'post',
data: parameter,
})
}

@ -0,0 +1,103 @@
import { request } from '/@/utils/request'
import { PageRequest, DataResult } from '/@/api/model/baseModel'
enum Api {
ReimbursementList = '/feeApi/Reimbursement/GetReimbursementList',
ReimbursementInfo = '/feeApi/Reimbursement/GetReimbursementInfo',
revokedReimbursement = '/feeApi/Reimbursement/ReimbursementRevoked',
auditReimbursement = '/feeApi/Reimbursement/Audit',
saveReimbursement = '/feeApi/Reimbursement/AddReimbursement',
submitReimbursement = '/feeApi/Reimbursement/SubmitReimbursement',
removeReimbursement = '/feeApi/Reimbursement/Audit',
invoiceList = '/feeApi/InInvoicet/GetInInvoicet',
bankList = '/mainApi/ClientBank/GetClientBankList',
getUser = '/mainApi/User/GetUserInfo',
}
// 获取报销单列表 (Auth)
export function ReimbursementGetList(data: PageRequest) {
return request<DataResult>({
url: Api.ReimbursementList,
method: 'post',
data,
})
}
// 获取报销单详情 (Auth)
export function ReimbursementGetInfo(data: { id: string | number }) {
return request<DataResult>({
url: Api.ReimbursementInfo,
method: 'get',
params: data,
})
}
// 报销单撤销 (Auth)
export function ReimbursementRevoked(data) {
return request<DataResult>({
url: Api.revokedReimbursement,
method: 'post',
data,
})
}
// 报销单审核 (Auth)
export function ReimbursementAudit(data) {
return request<DataResult>({
url: Api.auditReimbursement,
method: 'post',
data,
})
}
// 报销单保存 (Auth)
export function ReimbursementSave(data) {
return request<DataResult>({
url: Api.saveReimbursement,
method: 'post',
data,
})
}
// 报销单提交 (Auth)
export function ReimbursementSubmit(data) {
return request<DataResult>({
url: Api.submitReimbursement,
method: 'post',
data,
})
}
// 批量删除报销单 (Auth)
export function ReimbursementDelete(data: { ids: string[] | number[] }) {
return request<DataResult>({
url: Api.removeReimbursement,
method: 'post',
data,
})
}
// 进项发票列表 (Auth)
export function GetInvoiceList(data: PageRequest) {
return request<DataResult>({
url: Api.invoiceList,
method: 'post',
data
})
}
// 获取当前用户的银行卡列表
export function GetClientBankList(data) {
return request<DataResult>({
url: Api.bankList,
method: 'post',
data
})
}
// 获取当前用户的详细信息
export function GetUserInfo(query: { id: string }) {
return request<DataResult>({
url: Api.getUser,
method: 'get',
params: query
})
}

@ -1,110 +1,117 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
import { GetClientListByCode } from '/@/api/common'
import { numberThousandFormat } from '/@/utils/commonUtil'
export type BillItem = {
id: string
reimbursementId: string
creationTime: string
reimburser: string
reimbursementType: number
department: string | null
voucherNumber: string
payeeAccountNumber: string
bankName: string
payeeName: string
amount: number
userId: string
rejectReason: string | null
note: string | null
[key: string]: any
}
export const statusList: LabelValueOptions = [
{ label: '未提交', value: 0, class: 'icon-jichu_yuanquan', color: '#7A8798' },
{ label: '已提交', value: 1, class: 'icon-yiwancheng2-copy', color: '#257AFA' },
{ label: '审核通过', value: 2, class: 'icon-yiwancheng2', color: '#17A6A3' },
{ label: '审核驳回', value: 3, class: 'icon-weiwancheng-copy', color: '#BA3849' },
{ label: '已撤销', value: 4, class: 'icon-yichexiao', color: '#7A8798' },
]
export const columns: BasicColumn[] = [
{
title: '报销单编号',
dataIndex: 'applicationNO',
dataIndex: 'reimbursementId',
width: 100,
},
{
title: '制单时间',
dataIndex: 'customerName',
dataIndex: 'creationTime',
width: 100,
customRender({ text }) {
return text ?? '-'
},
},
{
title: '报销人',
dataIndex: 'customerName',
dataIndex: 'reimburser',
width: 100,
},
{
title: '状态',
dataIndex: 'isLocked',
dataIndex: 'reimbursementType',
width: 100,
customRender({ text }) {
return statusList.find((el) => el.value === text)?.label || ''
},
},
{
title: '部门',
dataIndex: 'modeText',
dataIndex: 'department',
width: 100,
customRender({ text }) {
return text ?? '-'
},
},
{
title: '凭证号',
dataIndex: 'currency',
dataIndex: 'voucherNumber',
width: 100,
customRender({ text }) {
return text ?? '-'
},
},
{
title: '收款人账号',
dataIndex: 'rmbAmount',
dataIndex: 'payeeAccountNumber',
width: 100,
},
{
title: '开户行',
dataIndex: 'usdAmount',
dataIndex: 'bankName',
width: 100,
},
{
title: '收款人名称',
dataIndex: 'otherAmount',
dataIndex: 'payeeName',
width: 100,
},
{
title: '合计金额',
dataIndex: 'unInvoiceAmount',
dataIndex: 'amount',
width: 100,
customRender({ text }) {
return numberThousandFormat(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
field: 'billType',
label: '',
field: 'reimbursementId',
label: '报销单编号',
colProps: { span: 4 },
component: 'Input',
show: false,
defaultValue: 1,
},
{
field: 'settlementNO',
label: '结算单号',
field: 'reimburser',
label: '报销人',
colProps: { span: 4 },
component: 'Input',
},
{
field: 'customerId',
label: '结算单位',
component: 'ApiSelect',
colProps: { span: 4 },
componentProps: () => {
return {
api: GetClientListByCode,
labelField: 'pinYinCode',
valueField: 'shortName',
showName: 'shortName',
resultField: 'data',
immediate: false,
}
},
},
{
field: 'applicationNO',
label: '申请单号',
colProps: { span: 4 },
component: 'Input',
},
{
field: 'ledgerVoucherNO',
label: '总账凭证号',
field: 'voucherNumber',
label: '凭证号',
colProps: { span: 4 },
component: 'Input',
},
{
field: 'settlementDate',
label: '结算日期',
component: 'DatePicker',
required: false,
dynamicDisabled: false,
colProps: { span: 4 },
componentProps: {
allowClear: true,
},
},
]

@ -0,0 +1,264 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
import moment from 'moment'
import { numberThousandFormat } from '/@/utils/commonUtil'
import { BillItem } from '../columns'
import { GetClientBankList } from '../api'
export type PageType = '' | 'ADD' | 'EDIT' | 'VIEW' | 'AUDIT'
export type BillRowMap = {
[key: string]: any
}
export interface BillDetail extends BillItem {
data: Recordable[]
}
export const invoiceCodeList: LabelValueOptions = [
{ label: '全电发票(铁路电子客票)', value: '51' },
{ label: '全电发票(航空运输电子客票行程单)', value: '61' },
{ label: '全电发票(增值税专用发票)', value: '81' },
{ label: '全电发票(普通发票)', value: '82' },
{ label: '全电纸质发票(增值税专用发票)', value: '85' },
{ label: '全电纸质发票(普通发票)', value: '86' },
{ label: '增值税电子普票发票', value: '026' },
{ label: '增值税电子专用发票', value: '028' },
{ label: '增值税普通发票', value: '007' },
{ label: '增值税专用发票', value: '004' },
{ label: '税控卷票', value: '025' },
]
export const invoiceStatusList: LabelValueOptions = [
{ label: '蓝票', value: '00' },
{ label: '红票', value: '01' },
]
export const columns: BasicColumn[] = [
{
title: '发票号码',
dataIndex: 'invoiceNumber',
width: 200,
},
{
title: '发票类型代码',
dataIndex: 'invoiceTypeCode',
width: 200,
customRender({ text }) {
return invoiceCodeList.find((el) => el.value === text)?.label || ''
},
},
{
title: '开票日期',
dataIndex: 'invoicingDate',
width: 140,
},
{
title: '合计金额',
dataIndex: 'totalAmount',
// sorter: true,
width: 100,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '合计税额',
dataIndex: 'totalTax',
width: 100,
// sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '价税合计',
dataIndex: 'totalWithTax',
width: 100,
// sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '发票状态',
dataIndex: 'invoiceStatus',
width: 100,
// sorter: true,
customRender: ({ text }) => {
return invoiceStatusList.find((el) => el.value === text)?.label || '未知'
},
},
{
title: '开票人',
dataIndex: 'invoicer',
width: 80,
// sorter: true,
},
{
title: '原发票号码',
dataIndex: 'originalInvoiceNumber',
width: 200,
// sorter: true,
},
{
title: '购方开票名称',
dataIndex: 'buyerInvoiceName',
width: 200,
ellipsis: true,
// sorter: true,
},
{
title: '购方开票税号',
dataIndex: 'buyerInvoiceTaxNumber',
width: 200,
// sorter: true,
},
{
title: '销方开票名称',
dataIndex: 'sellerInvoiceName',
width: 150,
// sorter: true,
},
{
title: '销方开票税号',
dataIndex: 'sellerInvoiceTaxNumber',
width: 200,
ellipsis: true,
// sorter: true,
},
{
title: '是否已获取详情',
dataIndex: 'isDetailObtained',
width: 120,
align: 'center',
// sorter: true,
},
{
title: '销方识别号',
dataIndex: 'sellerIdentificationNumber',
width: 200,
// sorter: true,
},
]
export const getDetailForm = (type: PageType): FormSchema[] => {
return [
{
field: 'bankName',
label: '开户行',
colProps: { span: 4 },
rules: [{ required: true }],
dynamicDisabled: ['VIEW', 'AUDIT'].includes(type),
component: 'ApiSelect',
isEdit: ['VIEW', 'AUDIT'].includes(type) ? 1 : 0,
componentProps: ({ formModel }) => {
return {
immediate: true,
api: () => {
return new Promise((resolve) => {
GetClientBankList({
pageCondition: { sortConditions: [] },
queryCondition:
'[{"FieldName":"clientId","FieldValue":"1844556123181551616","ConditionalType":1}]',
}).then((res) => {
const dataResult = res?.data || []
const defaultData = dataResult.find((item) => item.isInvoiceDefault)
if (defaultData) {
formModel.payeeAccountNumber = defaultData.bankAccountNo
formModel.bankName = defaultData.bankName
}
resolve(res?.data || [])
})
})
},
labelField: 'bankName',
valueField: 'bankName',
onChange: (e, obj) => {
if (e && obj) {
formModel.payeeAccountNumber = obj.bankAccountNo
}
if (!e && !obj) {
formModel.payeeAccountNumber = ''
}
},
}
},
},
{
field: 'payeeName',
label: '收款人名称',
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: true,
component: 'Input',
},
{
field: 'payeeAccountNumber',
label: '收款人账号',
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: true,
component: 'Input',
},
{
field: 'reimburser',
label: '报销人',
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: true,
component: 'Input',
},
{
field: 'department',
label: '部门',
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: true,
component: 'Input',
},
{
field: 'amount',
label: '金额',
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: true,
component: 'Input',
},
{
show: ['VIEW', 'AUDIT'].includes(type),
field: 'ledgerAccount',
label: '会计科目',
rules: [{ required: true }],
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: type === 'VIEW',
component: 'Input',
},
{
show: ['VIEW', 'AUDIT'].includes(type),
field: 'voucherNo',
label: '凭证号',
colProps: { span: 4 },
isEdit: 1,
dynamicDisabled: true,
component: 'Input',
},
{
// show: type === 'VIEW',
field: 'reason',
label: '事由',
colProps: { span: 4 },
component: 'InputTextArea',
componentProps: {
disTrans: true,
disabled: ['VIEW', 'AUDIT'].includes(type),
},
},
{
component: 'Divider',
colProps: {
span: 24,
},
},
]
}

@ -0,0 +1,103 @@
<template>
<BasicModal
v-bind="$attrs"
:use-wrapper="true"
title="审核驳回"
width="50%"
@register="registerModal"
@ok="handleSave"
>
<!-- 表单 -->
<BasicForm @register="registerForm" class="reject-form"> </BasicForm>
<!--右下角按钮-->
<template #footer>
<a-button
pre-icon="ant-design:close-outlined"
type="warning"
:loading="loading"
ghost
style="margin-right: 0.8rem"
@click="closeModal"
>
取消
</a-button>
<a-button
pre-icon="ant-design:check-circle-outlined"
type="primary"
:loading="loading"
@click="handleSave(true)"
>
确定
</a-button>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ReimbursementAudit } from '../../api'
import { BasicModal, useModalInner } from '/@/components/Modal'
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index'
import { useMessage } from '/@/hooks/web/useMessage'
const { createMessage } = useMessage()
const formSchema: FormSchema[] = [
{
field: 'reason',
label: '驳回原因',
defaultValue: '',
component: 'InputTextArea',
required: true,
colProps: { span: 24 },
componentProps: {
rows: 4,
disTrans: true,
},
},
]
// Emits
const emit = defineEmits(['success'])
const loading = ref(false)
const [registerForm, { resetFields, validate }] = useForm({
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
})
const rejectRowId = ref('')
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
console.log('data', data)
resetFields()
rejectRowId.value = data.id
})
async function handleSave(exit) {
try {
const values = await validate()
loading.value = true
setModalProps({ confirmLoading: true, loading: true })
const postData = {
result: 2,
remark: values.reason,
ids: [rejectRowId.value],
businessType: 0,
}
const res = await ReimbursementAudit(postData)
loading.value = false
if (res.succeeded) {
createMessage.success(res.message)
emit('success')
}
exit && closeModal()
} finally {
setModalProps({ confirmLoading: false, loading: false })
}
}
</script>
<style lang="less" scoped>
.reject-form {
#form_item_reason {
height: 170px;
}
}
</style>

@ -0,0 +1,228 @@
<template>
<a-modal
width="1500px"
@cancel="closeModal"
:visible="modalVisible"
title="添加发票明细"
:footer="null"
>
<div style="padding-bottom: 25px">
<a-spin :spinning="loading">
<BasicTable class="ds-table" @register="registerTable">
<template #tableTitle>
<div class="table-title-box">
<div class="module-title">发票列表</div>
<div class="bill-import-opt-box">
<div class="bill-import-opt-btn" @click="addBillHandle">
<i class="iconfont icon-icon_tianjia" style="color: #257afa; font-size: 16px"></i>
<span>添加</span>
</div>
</div>
</div>
</template>
<template #footer>
<div class="calc-sum-box">
<span class="calc-sum-label">选中合计</span>
<span class="calc-sum-value">{{ calcSelectedAmount }}</span>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex == 'invoiceNumber'">
<i
class="iconfont icon-fuzhi3"
style="color: #257afa; font-size: 14px; margin-right: 4px; cursor: pointer"
v-if="record.invoiceNumber"
@click="copyHandle(record)"
></i>
<span>{{ record.invoiceNumber || '' }}</span>
</template>
<template v-if="column.dataIndex == 'isDetailObtained'">
<CheckCircleFilled
style="color: #257afa; font-size: 14px"
v-if="record.isDetailObtained"
/>
</template>
<template v-if="column.dataIndex == 'isPaperInvoice'">
<CheckCircleFilled
style="color: #257afa; font-size: 14px"
v-if="record.isPaperInvoice" />
<CloseCircleFilled v-else style="color: #adadad; font-size: 14px"
/></template>
</template>
</BasicTable>
</a-spin>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, defineEmits } from 'vue'
import { BasicTable, useTable } from '/@/components/Table'
import { columns, searchFormSchema } from './columns'
import { GetInvoiceList } from '../../api'
//
import { useMessage } from '/@/hooks/web/useMessage'
import { formatParams } from '/@/hooks/web/common'
import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue'
import { plus } from 'number-precision'
import { numberThousandFormat } from '/@/utils/commonUtil'
type PropsType = {
visible: boolean
hasSelectedRows: Array<Recordable>
}
const props = withDefaults(defineProps<PropsType>(), {
visible: false,
hasSelectedRows: () => [],
})
const emits = defineEmits(['update:visible', 'finishAdd'])
const modalVisible = ref(false)
watch(
() => props.visible,
(val) => {
modalVisible.value = val
},
{ immediate: true },
)
const closeModal = () => {
modalVisible.value = false
emits('update:visible', false)
}
const { createMessage } = useMessage()
const loading = ref(false)
const [registerTable, { getSelectRows, setSelectedRowKeys }] = useTable({
api: async (p) => {
const res: API.DataResult = await GetInvoiceList(p)
return new Promise((resolve) => {
resolve({ data: [...res.data], total: res.count })
})
},
rowSelection: {
type: 'checkbox',
getCheckboxProps: (record) => {
return {
disabled: props.hasSelectedRows.map((el) => el.id).includes(record.id), //
}
},
},
beforeFetch: (p) => {
return formatParams({ ...p, reimbursementType: 0 })
},
columns: columns,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
useAdvancedSearch: true,
},
rowKey: 'id',
scroll: {
// @ts-ignore-line
x: '100%',
y: 500,
},
isTreeTable: false,
pagination: true,
striped: false,
useSearchForm: true,
bordered: false,
showIndexColumn: false,
canResize: false,
showTableSetting: false,
immediate: true,
})
const calcSelectedAmount = computed(() => {
let totalAmount = 0
totalAmount = [...props.hasSelectedRows, ...getSelectRows()].reduce((pre, cur) => {
return plus(pre + Number(cur?.totalAmount || 0))
}, 0)
return numberThousandFormat(totalAmount)
})
const addBillHandle = () => {
if (!getSelectRows().length) {
createMessage.warning('请至少选择一条数据')
return
}
emits('finishAdd', getSelectRows())
setSelectedRowKeys([])
closeModal()
}
const copyHandle = (row) => {
const { clipboardRef, isSuccessRef } = useCopyToClipboard(row.invoiceNumber)
clipboardRef.value = row.invoiceNumber
if (unref(isSuccessRef)) {
createMessage.success('复制成功!')
}
}
</script>
<style lang="less" scoped>
:deep(.ant-table-footer) {
padding: 0px !important;
background: white;
}
:deep(.ant-table-container) {
padding: 0px;
}
.module-title {
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
color: #33383d;
}
.table-title-box {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 16px 0 6px 0;
.bill-import-opt {
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
&-box {
display: flex;
align-items: center;
justify-content: flex-start;
margin-left: 40px;
}
&-btn {
margin-left: 16px;
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
& > span {
margin-left: 2px;
font-size: 12px;
color: #33383d;
font-weight: 400;
letter-spacing: 0px;
line-height: 15.84px;
}
& + &-btn {
margin-left: 20px;
}
}
}
}
.calc-sum-box {
display: flex;
align-items: center;
font-size: 12px;
font-weight: 700;
padding-top: 16px;
.calc-sum-label {
color: #33383d;
}
.calc-sum-value {
color: #257afa;
}
}
</style>

@ -0,0 +1,159 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
import moment from 'moment'
import { numberThousandFormat } from '/@/utils/commonUtil'
export const invoiceCodeList: LabelValueOptions = [
{ label: '全电发票(铁路电子客票)', value: '51' },
{ label: '全电发票(航空运输电子客票行程单)', value: '61' },
{ label: '全电发票(增值税专用发票)', value: '81' },
{ label: '全电发票(普通发票)', value: '82' },
{ label: '全电纸质发票(增值税专用发票)', value: '85' },
{ label: '全电纸质发票(普通发票)', value: '86' },
{ label: '增值税电子普票发票', value: '026' },
{ label: '增值税电子专用发票', value: '028' },
{ label: '增值税普通发票', value: '007' },
{ label: '增值税专用发票', value: '004' },
{ label: '税控卷票', value: '025' },
]
export const invoiceStatusList: LabelValueOptions = [
{ label: '蓝票', value: '00' },
{ label: '红票', value: '01' },
]
export const columns: BasicColumn[] = [
{
title: '发票号码',
dataIndex: 'invoiceNumber',
width: 200,
},
{
title: '发票类型代码',
dataIndex: 'invoiceTypeCode',
width: 200,
customRender({ text }) {
return invoiceCodeList.find((el) => el.value === text)?.label || ''
},
},
{
title: '开票日期',
dataIndex: 'invoicingDate',
width: 140,
},
{
title: '合计金额',
dataIndex: 'totalAmount',
sorter: true,
width: 100,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '合计税额',
dataIndex: 'totalTax',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '价税合计',
dataIndex: 'totalWithTax',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '发票状态',
dataIndex: 'invoiceStatus',
width: 100,
sorter: true,
customRender: ({ text }) => {
return invoiceStatusList.find((el) => el.value === text)?.label || '未知'
},
},
{
title: '开票人',
dataIndex: 'invoicer',
width: 80,
sorter: true,
},
{
title: '原发票号码',
dataIndex: 'originalInvoiceNumber',
width: 200,
// sorter: true,
},
{
title: '购方开票名称',
dataIndex: 'buyerInvoiceName',
width: 200,
ellipsis: true,
// sorter: true,
},
{
title: '购方开票税号',
dataIndex: 'buyerInvoiceTaxNumber',
width: 200,
// sorter: true,
},
{
title: '销方开票名称',
dataIndex: 'sellerInvoiceName',
width: 130,
// sorter: true,
},
{
title: '销方开票税号',
dataIndex: 'sellerInvoiceTaxNumber',
width: 200,
ellipsis: true,
// sorter: true,
},
{
title: '是否已获取详情',
dataIndex: 'isDetailObtained',
width: 120,
align: 'center',
// sorter: true,
},
{
title: '销方识别号',
dataIndex: 'sellerIdentificationNumber',
width: 200,
// sorter: true,
},
]
export const searchFormSchema: FormSchema[] = [
{
field: 'invoiceNumber',
label: '发票号码',
colProps: { span: 4 },
component: 'Input',
},
{
field: 'buyerInvoiceTaxNumber',
label: '购方开票税号',
colProps: { span: 4 },
component: 'Input',
},
{
field: 'buyerInvoiceName',
label: '购方开票名称',
colProps: { span: 4 },
component: 'Input',
},
{
field: 'invoicingDate',
label: '开票时间',
component: 'RangePicker',
colProps: { span: 4 },
componentProps: {
allowClear: true,
},
},
]

@ -0,0 +1,569 @@
<template>
<a-spin :spinning="loadingFlag" tip="加载中...">
<div class="page-wrapper" :style="{ paddingBottom: pageType == 'AUDIT' ? '80px' : '' }">
<div class="top-opt-wrapper">
<!-- 保存按钮组 -->
<ActionBar
:id="route.query.id"
code="sea_freight_export"
name="expenseAccount"
:save="saveHandle"
:submit="submitHandle"
:withdraw="revokeHandle"
:paramJsonStr="'{id:' + route.query.id + '}'"
:showBtns="displayActionList"
:getExtraQuery="getActionExtraQuery"
layout="horizontal"
></ActionBar>
<Divider type="vertical" class="action-divider" />
<span
:class="['status-box', 'iconfont', getStatusInfo('class')]"
:style="{ color: getStatusInfo('color'), fontSize: '16px' }"
>
<span :style="{ color: getStatusInfo('color') }"> {{ getStatusInfo('label') }}</span>
</span>
<span class="reject-reason" v-if="expenseAccountDetail?.rejectReason">
驳回原因{{ expenseAccountDetail.rejectReason }}
</span>
<span v-if="expenseAccountDetail?.reimbursementId" class="bill-No">
报销单编号{{ expenseAccountDetail.reimbursementId }}
</span>
</div>
<div class="page-content-wrapper">
<div class="fill-form-wrapper">
<div class="module-title">报销详情</div>
<BasicForm @register="registerForm" ref="BasicFormRef"> </BasicForm>
</div>
<BasicTable class="ds-table" @register="registerTable">
<template #tableTitle>
<div class="table-title-box">
<div class="module-title">发票引入</div>
<div class="bill-import-opt-box" v-if="canEditable">
<div class="bill-import-opt-btn" @click="addBillHandle">
<i class="iconfont icon-icon_tianjia" style="color: #257afa; font-size: 16px"></i>
<span>添加发票</span>
</div>
<div class="bill-import-opt-btn" @click="removeBillHandle">
<i class="iconfont icon-shanchu" style="color: #257afa; font-size: 16px"></i>
<span>删除</span>
</div>
</div>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex == 'invoiceNumber'">
<i
class="iconfont icon-fuzhi3"
style="color: #257afa; font-size: 14px; margin-right: 4px; cursor: pointer"
v-if="record.invoiceNumber"
@click="copyHandle(record)"
></i>
<span>{{ record.invoiceNumber || '' }}</span>
</template>
<template v-if="column.dataIndex == 'isDetailObtained'">
<CheckCircleFilled
style="color: #257afa; font-size: 14px"
v-if="record.isDetailObtained"
/>
</template>
<template v-if="column.dataIndex == 'isPaperInvoice'">
<CheckCircleFilled
style="color: #257afa; font-size: 14px"
v-if="record.isPaperInvoice" />
<CloseCircleFilled v-else style="color: #adadad; font-size: 14px"
/></template>
<template v-if="column.key === 'action'">
<TableAction :actions="getActionOptList(record)" />
</template>
</template>
<template #footer>
<div class="calc-sum-box">
<span class="calc-sum-label">合计金额</span>
<span class="calc-sum-value">{{ calcSumAmountFormat }}</span>
</div>
</template>
</BasicTable>
<div class="audit-opt-wrapper" v-if="pageType == 'AUDIT'">
<a-button type="danger" @click="toRejectHandle"></a-button>
<a-button type="primary" @click="auditPassHandle"></a-button>
</div>
</div>
<SearchTable
v-model:visible="searchTableVisible"
:hasSelectedRows="billTableData"
@finishAdd="finishSelectedAdd"
></SearchTable>
<RejectModal @success="rejectSuccess" @register="registerModal"></RejectModal>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import { Divider } from 'ant-design-vue'
import { BasicTable, useTable, TableAction, ActionItem } from '/@/components/Table'
import SearchTable from './components/SearchTable.vue'
import RejectModal from './components/RejectModal.vue'
import { BasicForm, useForm } from '/@/components/Form/index'
import {
GetUserInfo,
ReimbursementGetInfo,
ReimbursementSave,
ReimbursementSubmit,
ReimbursementRevoked,
ReimbursementAudit,
} from '../api'
import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'
import { columns, PageType, getDetailForm, BillRowMap, BillDetail } from './columns'
import { statusList } from '../columns'
import { useMessage } from '/@/hooks/web/useMessage'
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue'
import { plus } from 'number-precision'
import { numberThousandFormat } from '/@/utils/commonUtil'
import { useAppStore } from '/@/store/modules/app'
import { useUserStore } from '/@/store/modules/user'
import { useModal } from '/@/components/Modal'
import { useRoute } from 'vue-router'
import { closePage } from '/@/hooks/web/common'
const appStore = useAppStore()
const userStore = useUserStore()
const route = useRoute()
const { createMessage } = useMessage()
const loadingFlag = ref(false)
const pageType = ref<PageType>(route.query.type as PageType)
const userInfo = computed(() => userStore.getUserInfo)
const formValueBind = ref({})
const canEditable = computed(() => {
return ['ADD', 'EDIT'].includes(pageType.value)
})
const needDetail = computed(() => {
return ['VIEW', 'EDIT', 'AUDIT'].includes(pageType.value)
})
const expenseAccountDetail = ref<BillDetail | null>(null)
const billTableData = ref<BillRowMap[]>([])
const BasicFormRef = ref()
const calcSumAmount = computed(() => {
return billTableData.value?.reduce((acc, cur) => {
return plus(acc, cur?.amount || 0)
}, 0)
})
const calcSumAmountFormat = computed(() => {
const totalAmountStr = numberThousandFormat(calcSumAmount.value)
BasicFormRef.value && setFieldsValue({ amount: totalAmountStr })
return totalAmountStr
})
const displayActionList = computed(() => {
const displayMap = {
ADD: ['save', 'print', 'submit'],
EDIT: ['save', 'print', 'submit', 'next', 'last'],
VIEW: ['print', 'next', 'last'],
AUDIT: ['print'],
}
if (pageType.value === 'VIEW') {
return ['print', 'withdraw', 'next', 'last']
}
return displayMap[pageType.value]
})
const searchTableVisible = ref(canEditable.value)
const [registerForm, { setFieldsValue, validate, getFieldsValue }] = useForm({
labelWidth: 100,
schemas: getDetailForm(pageType.value),
showActionButtonGroup: false,
})
const [registerTable, { setTableData, getSelectRows }] = useTable({
title: '',
dataSource: billTableData.value,
columns,
isTreeTable: false,
pagination: false,
rowSelection: canEditable.value ? { type: 'checkbox' } : undefined,
striped: true,
useSearchForm: false,
showTableSetting: false,
bordered: false,
showIndexColumn: false,
maxHeight: pageType.value == 'AUDIT' ? 400 : undefined,
indexColumnProps: {
width: 60,
},
canResize: true,
resizeHeightOffset: 15,
immediate: true,
})
const copyHandle = (row) => {
const { clipboardRef, isSuccessRef } = useCopyToClipboard(row.invoiceNumber)
clipboardRef.value = row.invoiceNumber
if (unref(isSuccessRef)) {
createMessage.success('复制成功!')
}
}
// Query
const getActionExtraQuery = (index) => {
const obj = appStore.getIdsData
if (obj && obj['expenseAccount']) {
const preData = obj['expenseAccount'][index]
const pageType = [0, 2, 4].includes(preData.reimbursementType) ? 'EDIT' : 'VIEW'
return {
type: pageType,
tabName: pageType == 'EDIT' ? '报销单申请' : '报销单详情',
}
}
return {}
}
const pageInit = async () => {
try {
const userRes = await GetUserInfo({ id: userInfo.value.userId as string })
formValueBind.value = {
payeeName: userRes.data.userName,
reimburser: userRes.data.userName,
department: userRes.data.deptName,
}
setFieldsValue({ ...formValueBind.value })
} catch (error) {}
}
const getDetails = async () => {
try {
loadingFlag.value = true
const res = await ReimbursementGetInfo({
id: route.query.id as string,
})
formValueBind.value = {
bankName: res.data.bankName,
payeeName: res.data.payeeName,
payeeAccountNumber: res.data.payeeAccountNumber,
reimburser: res.data.reimburser,
department: res.data.department,
ledgerAccount: res.data.ledgerAccount,
voucherNo: res.data.voucherNo,
reason: res.data.reason,
}
setFieldsValue({ ...formValueBind.value })
expenseAccountDetail.value = res.data
billTableData.value = res.data?.data || []
setTableData(billTableData.value)
} catch (error) {
} finally {
loadingFlag.value = false
}
}
const getStatusInfo = (key) => {
const item = statusList?.find(
(el) => el.value === expenseAccountDetail.value?.reimbursementType,
)
return (item?.[key] as string) || ''
}
const getActionOptList = (record): ActionItem[] => {
return [
{
icon: h('i', {
class: 'iconfont icon-xiazai',
style: { color: '#257afa', fontSize: '14px' },
}),
isCustomIcon: true,
tooltip: '下载',
onClick: handleDownload.bind(null, record),
},
]
}
//
function handleDownload(row) {
const filePath = row?.pdfFilePath
if (!filePath) {
return createMessage.warning('暂未获取到相关文件')
}
window.open(filePath, '_blank')
}
const saveFunc = async (callback?: Function | undefined) => {
await validate()
if (!billTableData.value.length) {
createMessage.warning('请至少引入一条发票')
return
}
loadingFlag.value = true
const saveData = {
...formValueBind.value,
...getFieldsValue(),
amount: calcSumAmount.value,
id: route.query?.id || undefined,
data: billTableData.value,
}
ReimbursementSave({ ...saveData })
.then((res) => {
if (res.succeeded) {
if (callback && pageType.value == 'ADD') callback(res.data)
else {
loadingFlag.value = false
createMessage.success('保存成功')
closePage()
}
}
})
.catch(() => {
loadingFlag.value = false
})
}
//
const saveHandle = async () => {
saveFunc()
}
//
const submitHandle = () => {
const submitFunc = async (id?: string) => {
const saveData = {
id: id || route.query?.id || undefined,
}
ReimbursementSubmit({ ...saveData })
.then((res) => {
loadingFlag.value = false
if (res.succeeded) {
createMessage.success('提交成功!')
closePage()
}
})
.catch((err) => {
loadingFlag.value = false
})
}
if (pageType.value == 'ADD') {
saveFunc(submitFunc)
} else {
if (!billTableData.value.length) {
createMessage.warning('请至少引入一条发票')
return
}
submitFunc()
}
}
//
const revokeHandle = async () => {
try {
loadingFlag.value = true
const res = await ReimbursementRevoked({
id: expenseAccountDetail.value?.id || route.query.id,
})
loadingFlag.value = false
if (res.succeeded) {
createMessage.success('撤回成功!')
closePage()
}
} catch (error) {
loadingFlag.value = false
}
}
//
const auditPassHandle = async () => {
await validate()
/* if (!getFieldsValue().ledgerAccount) {
createMessage.warning('请将会计科目填写完整')
return
} */
try {
loadingFlag.value = true
const res = await ReimbursementAudit({
result: 1,
ids: [expenseAccountDetail.value?.id || route.query.id],
businessType: 0,
ledgerAccount: getFieldsValue().ledgerAccount,
})
loadingFlag.value = false
if (res.succeeded) {
createMessage.success('审核已通过!')
closePage()
}
} catch (error) {
loadingFlag.value = false
}
}
const [registerModal, { openModal }] = useModal()
//
const toRejectHandle = () => {
openModal(true, {
id: expenseAccountDetail.value?.id || route.query.id,
})
}
//
const rejectSuccess = () => {
loadingFlag.value = false
closePage()
}
//
const addBillHandle = () => {
searchTableVisible.value = true
}
//
const removeBillHandle = () => {
if (!getSelectRows().length) {
createMessage.warning('请选择要删除的发票')
return
}
billTableData.value = billTableData.value.filter(
(el) => !getSelectRows().find((item) => item.id === el.id),
)
setTableData(billTableData.value)
}
//
const finishSelectedAdd = (billArr: BillRowMap[]) => {
billTableData.value = [...billTableData.value, ...billArr]
setTableData(billTableData.value)
}
pageInit()
needDetail.value && getDetails()
</script>
<style lang="less" scoped>
.page-wrapper {
background: #ffffff;
.ant-divider-vertical {
height: 1.9em;
background: #cccccc;
}
:deep(.ant-divider-horizontal) {
margin: 16px 0;
}
:deep(.ant-table-title) {
padding: 0;
margin-bottom: 10px;
}
:deep(.ant-table-container) {
padding: 0;
}
.top-opt-wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 8px 16px;
background: #f5f9fc;
position: relative;
.status-box {
margin: 15px;
display: flex;
justify-content: center;
align-items: center;
& > span {
margin-left: 4px;
font-size: 12px;
}
}
.reject-reason {
margin-left: 70px;
color: #ba3849;
font-size: 12px;
}
.bill-No {
position: absolute;
right: 20px;
font-size: 12px;
color: #7a8798;
}
}
.page-content-wrapper {
padding: 0 20px;
.module-title {
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
color: #33383d;
}
.fill-form-wrapper {
margin-top: 14px;
}
.table-title-box {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 16px 0 6px 0;
.bill-import-opt {
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
&-box {
display: flex;
align-items: center;
justify-content: flex-start;
margin-left: 40px;
}
&-btn {
margin-left: 16px;
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
& > span {
margin-left: 2px;
font-size: 12px;
color: #33383d;
font-weight: 400;
letter-spacing: 0px;
line-height: 15.84px;
}
& + &-btn {
margin-left: 20px;
}
}
}
}
.calc-sum-box {
// position: fixed;
// bottom: 10px;
display: flex;
align-items: center;
// margin-left: 20px;
font-size: 12px;
font-weight: 700;
padding-top: 16px;
.calc-sum-label {
color: #33383d;
}
.calc-sum-value {
color: #257afa;
}
}
.audit-opt-wrapper {
position: fixed;
left: 0;
right: 0;
bottom: 0px;
width: 100%;
height: 60px;
background: #ffffff;
box-shadow: 0px -3px 6px #d6d6d6;
padding-right: 40px;
margin-top: auto;
display: flex;
align-items: center;
justify-content: flex-end;
.ant-btn + .ant-btn {
margin-left: 10px;
}
}
}
}
</style>

@ -1,73 +1,82 @@
<template>
<BasicTable @row-dbClick="GoDetailed" class="ds-table" @register="registerTable">
<template #tableTitle>
<div class="ds-h-aciton-btns-fee">
<a-tooltip placement="top" :mouseEnterDelay="0.5">
<template #title>
<span>复制</span>
</template>
<span class="ds-action-svg-btn">
<img src="../../../assets/svg/infoclient/xinjian.svg" class="SvgImg" />
</span>
</a-tooltip>
<a-tooltip placement="top" :mouseEnterDelay="0.5">
<template #title>
<span>删除</span>
</template>
<a-popconfirm
title="确定删除当前选中数据?"
@confirm="FnDel"
ok-text="是"
cancel-text="否"
>
<span class="ds-action-svg-btn">
<img src="../../../assets/svg/infoclient/shanchu.svg" class="SvgImg" />
<div class="ds-fee-settle-main-table">
<BasicTable @row-dbClick="GoDetailed" class="ds-table" @register="registerTable">
<template #tableTitle>
<div class="ds-h-aciton-btns-fee">
<a-tooltip placement="top" :mouseEnterDelay="0.5">
<template #title>
<span>新建</span>
</template>
<span class="ds-action-svg-btn" @click="GoAdd">
<img src="../../../assets/svg/infoclient/xinjian.svg" class="SvgImg" />
</span>
</a-popconfirm>
</a-tooltip>
</div>
</template>
<template v-slot:bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'clarity:note-edit-line',
tooltip: '编辑',
onClick: () => {
GoDetailed(true, record)
},
},
]"
/>
</a-tooltip>
<a-tooltip placement="top" :mouseEnterDelay="0.5">
<template #title>
<span>删除</span>
</template>
<a-popconfirm
title="确定删除当前选中数据?"
@confirm="FnDel"
ok-text="是"
cancel-text="否"
>
<span class="ds-action-svg-btn">
<img src="../../../assets/svg/infoclient/shanchu.svg" class="SvgImg" />
</span>
</a-popconfirm>
</a-tooltip>
</div>
</template>
<template v-if="column.dataIndex == 'invoiceNumber'">
<span>{{ record.invoiceNumber || '' }}</span>
<i
class="iconfont icon-fuzhi3"
style="color: #257afa; font-size: 14px; margin-left: 4px; cursor: pointer"
v-if="record.invoiceNumber"
@click="copyHandle(record)"
></i>
<template v-slot:bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction :actions="getActionOptList(record)" />
</template>
<template v-if="column.dataIndex == 'invoiceNumber'">
<span>{{ record.invoiceNumber || '' }}</span>
<i
class="iconfont icon-fuzhi3"
style="color: #257afa; font-size: 14px; margin-left: 4px; cursor: pointer"
v-if="record.invoiceNumber"
@click="copyHandle(record)"
></i>
</template>
<template v-if="column.dataIndex == 'reimbursementType'">
<span
:class="['status-box', 'iconfont', getStatusInfo(record, 'class')]"
:style="{ color: getStatusInfo(record, 'color'), fontSize: '16px' }"
>
<span :style="{ color: getStatusInfo(record, 'color') }">
{{ getStatusInfo(record, 'label') }}
</span>
</span>
</template>
</template>
</template>
</BasicTable>
</BasicTable>
</div>
</template>
<script setup lang="ts">
import { PaymentSettlementGetList, PaymentSettlementDelete } from './api.js'
import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { ReimbursementGetList, ReimbursementDelete } from './api.js'
import { BasicTable, useTable, TableAction, ActionItem } from '/@/components/Table'
import { formatParams } from '/@/hooks/web/common'
import { columns, searchFormSchema } from './columns'
import { BillItem, columns, searchFormSchema, statusList } from './columns'
import { useMessage } from '/@/hooks/web/useMessage'
const { createMessage } = useMessage()
import { useGo } from '/@/hooks/web/usePage'
import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'
import { useAppStore } from '/@/store/modules/app'
const appStore = useAppStore()
const go = useGo()
const [registerTable, { reload, setLoading, getSelectRows }] = useTable({
api: async (p) => {
const res: API.DataResult = await PaymentSettlementGetList(p)
const res: API.DataResult<BillItem[]> = await ReimbursementGetList(p)
return new Promise((resolve) => {
const ids = res.data.map((item) => {
return item.id
})
appStore.setIdsData(res.data, 'expenseAccount')
appStore.setIds(ids, 'expenseAccount')
resolve({ data: [...res.data], total: res.count })
})
},
@ -80,6 +89,9 @@
schemas: searchFormSchema,
useAdvancedSearch: true,
},
rowSelection: {
type: 'checkbox',
},
isTreeTable: false,
pagination: true,
striped: true,
@ -99,19 +111,40 @@
fixed: 'right',
},
})
const getStatusInfo = (row, key) => {
const item = statusList?.find((el) => el.value === row?.reimbursementType)
return (item?.[key] as string) || ''
}
const getActionOptList = (record: BillItem): ActionItem[] => {
const canEditable = [0, 2, 4].includes(record.reimbursementType)
return [
{
icon: canEditable
? 'clarity:note-edit-line'
: h('i', {
class: 'iconfont icon-chakan',
style: { color: '#257afa', fontSize: '16px' },
}),
isCustomIcon: !canEditable,
tooltip: canEditable ? '编辑' : '查看',
onClick: GoDetailed.bind(null, record),
},
]
}
function FnDel() {
let ids = getSelectRows().map((item) => {
return item.id
})
if (ids.length == 0) {
createMessage.error('请选择数据')
return false
if (getSelectRows().length == 0) {
return createMessage.error('请选择数据')
}
const data = {
ids: [...ids],
if (getSelectRows().some((el) => el.reimbursementType != 0)) {
return createMessage.error('只能删除未提交的数据')
}
const data: { ids: string[] } = {
ids: getSelectRows().map((item) => item.id),
}
setLoading(true)
PaymentSettlementDelete(data)
ReimbursementDelete(data)
.then((res) => {
if (res.succeeded) {
createMessage.success('删除成功')
@ -123,62 +156,68 @@
setLoading(false)
})
}
const copyHandle = (row) => {
const copyHandle = (row: BillItem) => {
const { clipboardRef, isSuccessRef } = useCopyToClipboard(row.invoiceNumber)
clipboardRef.value = row.invoiceNumber
if (unref(isSuccessRef)) {
createMessage.success('复制成功!')
}
}
function GoDetailed(row) {
if (row.modeText == '发票结算') {
go('/feeSettlementDetail?id=' + row.id + '&type=invoice')
}
if (row.modeText == '自由结算') {
go('/feeSettlementDetail?id=' + row.id + '&type=free')
}
function GoAdd() {
go({
path: '/expenseAccountDetail',
query: {
type: 'ADD',
tabName: '报销单申请',
},
})
}
function GoDetailed(row: BillItem) {
const pageType = [0, 2, 4].includes(row.reimbursementType) ? 'EDIT' : 'VIEW'
go({
path: '/expenseAccountDetail',
query: {
type: pageType,
id: row.id,
tabName: pageType == 'EDIT' ? '报销单申请' : '报销单详情',
},
})
}
</script>
<style lang="less" scoped>
.tableTitleBox {
.ant-btn-link {
margin-left: 13px;
color: #000;
font-size: 16px;
}
}
.SvgImg {
width: 18px;
height: 18px;
margin: 6.8px;
}
:deep(.ant-table-title) {
padding: 0 20px !important;
}
.ds-h-aciton-btns-fee {
display: flex;
align-items: center;
height: 38px;
padding-right: 10px;
.ds-action-svg-btn {
display: inline-block;
width: 32px;
height: 32px;
display: flex;
align-items: center;
width: 24px;
height: 24px;
border-radius: 2px;
margin-right: 8px;
cursor: pointer;
.next {
transform: rotate(180deg);
}
}
.ds-action-svg-btn:hover {
box-shadow: 0px 2px 4px #cad1db;
}
.vben-svg-icon {
margin: 6.8px;
}
.status-box {
display: flex;
justify-content: center;
align-items: center;
& > span {
margin-left: 4px;
font-size: 12px;
}
}
</style>

@ -1,4 +1,5 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
import { numberThousandFormat } from '/@/utils/commonUtil'
export const invoiceCodeList: LabelValueOptions = [
{ label: '全电发票(铁路电子客票)', value: '51' },
@ -45,18 +46,27 @@ export const columns: BasicColumn[] = [
dataIndex: 'totalAmount',
sorter: true,
width: 100,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '合计税额',
dataIndex: 'totalTax',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '价税合计',
dataIndex: 'totalWithTax',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '发票状态',
@ -141,6 +151,9 @@ export const columns: BasicColumn[] = [
dataIndex: 'deductionAmount',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '机动车发票类型代码',
@ -203,6 +216,9 @@ export const columns: BasicColumn[] = [
dataIndex: 'amount',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '销方名称',
@ -260,6 +276,9 @@ export const columns: BasicColumn[] = [
dataIndex: 'taxAmount',
width: 100,
sorter: true,
customRender({ text }) {
return numberThousandFormat(text)
},
},
{
title: '备注',

@ -1187,8 +1187,8 @@
path: '/approve-fee',
query: {
id: data.ouT_BS_NO,
btype: 1
}
btype: 1,
},
})
break
case 'WAIT_ORDER_AUDIT':
@ -1225,6 +1225,17 @@
},
})
break
case 'ReimbursementApproval':
//
go({
path: '/expenseAccountDetail',
query: {
id: data.ouT_BS_NO,
type: 'AUDIT',
tabName: '报销单审核',
},
})
break
default:
break
}

Loading…
Cancel
Save