2025-08-17 13:24:36 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
2025-08-19 13:52:25 +08:00
|
|
|
import {h, onMounted, ref} from "vue";
|
2025-08-17 22:39:59 +08:00
|
|
|
import {
|
2025-08-19 17:35:34 +08:00
|
|
|
SearchOutlined,
|
2025-08-19 22:22:44 +08:00
|
|
|
FileSearchOutlined,
|
|
|
|
|
PlusOutlined
|
2025-08-17 22:39:59 +08:00
|
|
|
} from '@ant-design/icons-vue'
|
2025-08-19 22:49:04 +08:00
|
|
|
import request from '../utils/request.ts';
|
2025-08-19 17:35:34 +08:00
|
|
|
import {baseURL} from "../utils/baseurl.ts";
|
2025-08-19 22:22:44 +08:00
|
|
|
import type {TableProps, UploadChangeParam, UploadProps} from "ant-design-vue";
|
|
|
|
|
import { message } from 'ant-design-vue';
|
|
|
|
|
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
|
|
|
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
import 'dayjs/locale/zh-cn';
|
|
|
|
|
import type {UploadFile} from "ant-design-vue/es/upload/interface";
|
2025-08-19 22:49:04 +08:00
|
|
|
import type {Key} from "ant-design-vue/es/_util/type";
|
2025-08-19 22:22:44 +08:00
|
|
|
const [messageApi, contextHolder] = message.useMessage();
|
2025-08-19 17:35:34 +08:00
|
|
|
interface DataType {
|
|
|
|
|
id: string;
|
|
|
|
|
status: string;
|
|
|
|
|
desc: string;
|
|
|
|
|
desc_list: string;
|
|
|
|
|
}
|
2025-08-17 13:24:36 +08:00
|
|
|
const columns = [
|
|
|
|
|
// {
|
2025-08-19 17:35:34 +08:00
|
|
|
// title: '全选',
|
|
|
|
|
// dataIndex: 'status',
|
|
|
|
|
// key: 'status',
|
|
|
|
|
// showSelection: true,
|
2025-08-17 13:24:36 +08:00
|
|
|
// },
|
|
|
|
|
{
|
|
|
|
|
title: '发票号码',
|
|
|
|
|
dataIndex: 'invoiceNumber',
|
|
|
|
|
key: 'invoiceNumber',
|
2025-08-19 17:35:34 +08:00
|
|
|
// showSelection: true,
|
2025-08-17 13:24:36 +08:00
|
|
|
},
|
2025-08-18 17:54:29 +08:00
|
|
|
{
|
|
|
|
|
title: '销方名称',
|
|
|
|
|
dataIndex: 'sellerName',
|
|
|
|
|
key: 'sellerName',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '销方税号',
|
|
|
|
|
dataIndex: 'sellerTaxNumber',
|
|
|
|
|
key: 'sellerTaxNumber',
|
|
|
|
|
},
|
2025-08-17 13:24:36 +08:00
|
|
|
{
|
|
|
|
|
title: '不含税金额',
|
|
|
|
|
dataIndex: 'invoiceAmountPreTax',
|
|
|
|
|
key: 'invoiceAmountPreTax',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '税额',
|
|
|
|
|
dataIndex: 'invoiceTax',
|
|
|
|
|
key: 'invoiceTax',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '价税合计',
|
|
|
|
|
dataIndex: 'totalAmount',
|
|
|
|
|
key: 'totalAmount',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '开票日期',
|
|
|
|
|
dataIndex: 'invoiceDate',
|
|
|
|
|
key: 'invoiceDate',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '查验结果',
|
2025-08-19 13:52:25 +08:00
|
|
|
dataIndex: 'verify_status',
|
|
|
|
|
key: 'verify_status',
|
2025-08-17 13:24:36 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '查验次数',
|
|
|
|
|
dataIndex: 'inspectionAmount',
|
|
|
|
|
key: 'inspectionAmount',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '查验时间',
|
|
|
|
|
dataIndex: 'verify_time',
|
|
|
|
|
key: 'verify_time',
|
2025-08-19 17:35:34 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '操作',
|
|
|
|
|
dataIndex: 'action',
|
|
|
|
|
key: 'action',
|
2025-08-17 13:24:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
]
|
2025-08-17 22:39:59 +08:00
|
|
|
// 数据
|
2025-08-17 13:24:36 +08:00
|
|
|
let data = ref( [])
|
2025-08-17 22:39:59 +08:00
|
|
|
|
|
|
|
|
let searchParams = ref({
|
|
|
|
|
page: 1,
|
2025-08-19 13:52:25 +08:00
|
|
|
pageSize: 100,
|
2025-08-19 17:35:34 +08:00
|
|
|
total: 0,
|
2025-08-19 13:52:25 +08:00
|
|
|
params: {
|
|
|
|
|
verify_status: 'success',
|
|
|
|
|
value: null,
|
2025-08-19 22:22:44 +08:00
|
|
|
verify_time: null,
|
2025-08-17 22:39:59 +08:00
|
|
|
}
|
|
|
|
|
});
|
2025-08-19 17:35:34 +08:00
|
|
|
// 下拉框
|
|
|
|
|
const rowSelection: TableProps['rowSelection'] = {
|
2025-08-19 22:49:04 +08:00
|
|
|
onChange: (selectedRowKeys: Key[], selectedRows: DataType[]) => {
|
2025-08-19 17:35:34 +08:00
|
|
|
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
|
|
|
|
},
|
|
|
|
|
getCheckboxProps: (record: DataType) => ({
|
|
|
|
|
disabled: record.status === 'yes', // Column configuration not to be checked
|
|
|
|
|
name: record.status,
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
// 弹窗
|
|
|
|
|
const openModal = ref(false);
|
|
|
|
|
const modalTitle = ref('')
|
|
|
|
|
const confirmLoading = ref(false);
|
|
|
|
|
const formData = ref({
|
|
|
|
|
id: '',
|
|
|
|
|
status: false,
|
|
|
|
|
desc: '',
|
|
|
|
|
desc_files: '',
|
2025-08-19 22:22:44 +08:00
|
|
|
fileIds: new Array<String>(),
|
2025-08-19 17:35:34 +08:00
|
|
|
});
|
2025-08-18 22:19:28 +08:00
|
|
|
|
2025-08-19 17:35:34 +08:00
|
|
|
const handleOk = () => {
|
|
|
|
|
confirmLoading.value = true;
|
|
|
|
|
request.post("/updateInvoice", {
|
|
|
|
|
invoiceId: formData.value.id,
|
|
|
|
|
status: formData.value.status?'yes':'no',
|
|
|
|
|
desc: formData.value.desc,
|
2025-08-20 15:11:35 +08:00
|
|
|
desc_files: formData.value.fileIds?.join(','),
|
2025-08-19 17:35:34 +08:00
|
|
|
}).then(( res ) => {
|
2025-08-19 22:22:44 +08:00
|
|
|
if(res.status == 200){
|
|
|
|
|
openModal.value = false;
|
|
|
|
|
confirmLoading.value = false;
|
|
|
|
|
messageApi.success("修改成功")
|
2025-08-20 15:11:35 +08:00
|
|
|
getList();//刷新
|
2025-08-19 22:22:44 +08:00
|
|
|
}
|
2025-08-19 17:35:34 +08:00
|
|
|
})
|
|
|
|
|
};
|
2025-08-19 22:49:04 +08:00
|
|
|
const handleOpen = (record:any) => {
|
2025-08-19 22:22:44 +08:00
|
|
|
if (record.status === 'yes') {
|
|
|
|
|
modalTitle.value = "备注"
|
|
|
|
|
}else {
|
|
|
|
|
modalTitle.value = "入账"
|
|
|
|
|
}
|
|
|
|
|
formData.value = Object.assign({}, record);
|
|
|
|
|
formData.value.status = (record.status === 'yes')
|
|
|
|
|
if (record.desc_files) {
|
2025-08-20 15:11:35 +08:00
|
|
|
formData.value.fileIds = record.desc_files.split(',');
|
2025-08-19 22:22:44 +08:00
|
|
|
let paths = record.desc_files.split(",")
|
|
|
|
|
fileList.value = []
|
|
|
|
|
for(let i = 0; i < paths.length; i++) {
|
|
|
|
|
fileList.value.push({
|
|
|
|
|
uid: paths[i],
|
|
|
|
|
name: paths[i].split("/")[2],
|
|
|
|
|
url: baseURL+"/"+paths[i],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-19 17:35:34 +08:00
|
|
|
openModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
// 数据列表
|
2025-08-18 22:19:28 +08:00
|
|
|
const getList = () => {
|
|
|
|
|
request.post("/listInvoice", {
|
2025-08-19 22:22:44 +08:00
|
|
|
verify_status : searchParams.value.params.verify_status,
|
|
|
|
|
value : searchParams.value.params.value,
|
|
|
|
|
verify_time : searchParams.value.params.verify_time,
|
2025-08-18 22:19:28 +08:00
|
|
|
}).then(( res ) => {
|
|
|
|
|
console.log(res)
|
2025-08-19 13:52:25 +08:00
|
|
|
data.value = res.data
|
2025-08-18 22:19:28 +08:00
|
|
|
})
|
|
|
|
|
}
|
2025-08-19 22:22:44 +08:00
|
|
|
const fileList = ref<UploadProps['fileList']>([])
|
|
|
|
|
const previewVisible = ref(false);
|
|
|
|
|
const previewImage = ref('');
|
|
|
|
|
const previewTitle = ref('');
|
|
|
|
|
|
|
|
|
|
function getBase64(file: File) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
|
reader.onload = () => resolve(reader.result);
|
|
|
|
|
reader.onerror = error => reject(error);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
//
|
|
|
|
|
const handleCancelPreview = () => {
|
|
|
|
|
previewVisible.value = false;
|
|
|
|
|
previewTitle.value = '';
|
|
|
|
|
};
|
2025-08-19 22:49:04 +08:00
|
|
|
const handlePreview = async (file: any) => {
|
2025-08-19 22:22:44 +08:00
|
|
|
if (!file.url && !file.preview) {
|
|
|
|
|
file.preview = (await getBase64(file.originFileObj)) as string;
|
|
|
|
|
}
|
|
|
|
|
previewImage.value = file.url || file.preview;
|
|
|
|
|
previewVisible.value = true;
|
|
|
|
|
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFileChange = (info: UploadChangeParam) => {
|
|
|
|
|
if(info.file.status === 'done'){
|
|
|
|
|
info.file.uid = info.file.response.file_path
|
2025-08-20 15:11:35 +08:00
|
|
|
if (!formData.value.fileIds){
|
|
|
|
|
formData.value.fileIds = new Array<String>();
|
|
|
|
|
}
|
2025-08-19 22:22:44 +08:00
|
|
|
formData.value.fileIds.push(info.file.response.file_path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const handleFileRemove = (file: UploadFile ) => {
|
|
|
|
|
let index = formData.value.fileIds.indexOf(file.uid)
|
|
|
|
|
formData.value.fileIds.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
const handleUpload = (options: any) => {
|
|
|
|
|
const { file, onSuccess, onError, onProgress } = options;
|
|
|
|
|
// 创建 FormData 对象
|
|
|
|
|
const formData1 = new FormData();
|
|
|
|
|
formData1.append('file', file);
|
|
|
|
|
// 使用 XMLHttpRequest 进行上传
|
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
|
// 监听上传进度
|
|
|
|
|
xhr.upload.onprogress = (event) => {
|
|
|
|
|
if (event.lengthComputable) {
|
|
|
|
|
onProgress({ percent: (event.loaded / event.total) * 100 }, file);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// 上传成功回调
|
|
|
|
|
xhr.onload = () => {
|
|
|
|
|
if (xhr.status === 200 || xhr.status === 201) {
|
|
|
|
|
console.log(xhr.responseText);
|
|
|
|
|
onSuccess(xhr.responseText, file);
|
|
|
|
|
let res = JSON.parse(xhr.responseText)
|
|
|
|
|
|
|
|
|
|
if(formData.value.desc_files){
|
|
|
|
|
formData.value.desc_files += ",";
|
|
|
|
|
}else{
|
|
|
|
|
formData.value.desc_files = ""
|
|
|
|
|
}
|
|
|
|
|
formData.value.desc_files += res.file_path;
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
onError(new Error(`Upload failed with status ${xhr.status}`));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// 上传错误回调
|
|
|
|
|
xhr.onerror = () => {
|
|
|
|
|
onError(new Error('Network error'));
|
|
|
|
|
};
|
|
|
|
|
// 发送请求到服务器
|
|
|
|
|
xhr.open('POST', baseURL+'/upload');
|
|
|
|
|
xhr.send(formData1);
|
|
|
|
|
};
|
|
|
|
|
dayjs.locale('zh-cn')
|
2025-08-19 13:52:25 +08:00
|
|
|
onMounted(() => {
|
|
|
|
|
getList()
|
|
|
|
|
})
|
2025-08-17 13:24:36 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-08-17 22:39:59 +08:00
|
|
|
<div class="table-operations">
|
|
|
|
|
<a-row :gutter="20" >
|
|
|
|
|
<a-col :span="22" :align="'end'" offset="2">
|
|
|
|
|
<a-form v-model:value="searchParams" layout="inline">
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-form-item label="验证时间">
|
|
|
|
|
<a-range-picker
|
|
|
|
|
v-model:value="searchParams.params.verify_time"
|
|
|
|
|
valueFormat="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
:locale="zhCN.DatePicker"
|
|
|
|
|
></a-range-picker>
|
2025-08-17 22:39:59 +08:00
|
|
|
</a-form-item>
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-form-item label="关键字">
|
|
|
|
|
<a-input v-model:value="searchParams.params.value" allow-clear></a-input>
|
2025-08-17 22:39:59 +08:00
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="验证结果">
|
2025-08-19 13:52:25 +08:00
|
|
|
<a-radio-group v-model:value="searchParams.params.verify_status">
|
|
|
|
|
<a-radio-button value="success">正常</a-radio-button>
|
|
|
|
|
<a-radio-button value="faild">异常</a-radio-button>
|
2025-08-20 11:26:38 +08:00
|
|
|
<a-radio-button value="all">全部</a-radio-button>
|
2025-08-17 22:39:59 +08:00
|
|
|
</a-radio-group>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item>
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-button type="primary" :icon="h(SearchOutlined)" @click="getList">搜索</a-button>
|
2025-08-17 22:39:59 +08:00
|
|
|
</a-form-item>
|
|
|
|
|
</a-form>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
</div>
|
2025-08-19 17:35:34 +08:00
|
|
|
<a-table :columns="columns" :data-source="data" :row-selection="rowSelection" >
|
2025-08-19 13:52:25 +08:00
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
|
<template v-if="column.key === 'verify_status'">
|
|
|
|
|
<a-tag v-if="record.verify_status === 'success'" color="green">正常</a-tag>
|
|
|
|
|
<a-tag v-if="record.verify_status === 'fail'" color="red">异常</a-tag>
|
|
|
|
|
</template>
|
2025-08-19 17:35:34 +08:00
|
|
|
<template v-if="column.key === 'action'" >
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-button type="primary" v-if="record.status ==='yes'" @click="handleOpen(record)">备注</a-button>
|
|
|
|
|
<a-button type="primary" v-if="record.status ==='no'" @click="handleOpen(record)">入账</a-button>
|
2025-08-19 17:35:34 +08:00
|
|
|
</template>
|
|
|
|
|
</template>
|
|
|
|
|
<template #expandedRowRender="{ record }">
|
|
|
|
|
<a-row >
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-col :span="8">
|
|
|
|
|
<a-image-preview-group v-if="record.desc_files">
|
|
|
|
|
<a-image :width="200" v-for="file in record.desc_files.split(',')" :src="baseURL+'/'+file"/>
|
|
|
|
|
</a-image-preview-group>
|
|
|
|
|
</a-col>
|
2025-08-19 17:35:34 +08:00
|
|
|
<a-col :span="16">
|
|
|
|
|
<a-typography-title :level="4" >备注</a-typography-title>
|
|
|
|
|
<a-typography-paragraph>
|
2025-08-20 11:37:37 +08:00
|
|
|
<blockquote>{{ record.desc }}</blockquote>
|
2025-08-19 17:35:34 +08:00
|
|
|
</a-typography-paragraph>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
</template>
|
|
|
|
|
<template #expandColumnTitle>
|
|
|
|
|
<FileSearchOutlined />
|
2025-08-19 13:52:25 +08:00
|
|
|
</template>
|
2025-08-17 13:24:36 +08:00
|
|
|
</a-table>
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-modal v-model:open="openModal" :title="modalTitle" :confirm-loading="confirmLoading" @ok="handleOk">
|
|
|
|
|
<a-form v-model:value="formData" >
|
|
|
|
|
<a-form-item label="是否已报销">
|
|
|
|
|
<a-switch v-model:checked="formData.status"></a-switch>
|
2025-08-19 17:35:34 +08:00
|
|
|
</a-form-item>
|
2025-08-19 22:22:44 +08:00
|
|
|
<a-form-item label="备注">
|
|
|
|
|
<a-textarea v-model:value="formData.desc" :rows="6"></a-textarea>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="附件">
|
|
|
|
|
<div class="clearfix">
|
|
|
|
|
<a-upload
|
|
|
|
|
v-model:file-list="fileList"
|
|
|
|
|
:customRequest="handleUpload"
|
|
|
|
|
@change="handleFileChange"
|
|
|
|
|
@remove="handleFileRemove"
|
|
|
|
|
list-type="picture-card"
|
|
|
|
|
@preview="handlePreview"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<PlusOutlined />
|
|
|
|
|
<div style="margin-top: 8px">上传</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-upload>
|
|
|
|
|
<a-modal :open="previewVisible" :title="previewTitle" :footer="null" @cancel="handleCancelPreview">
|
|
|
|
|
<img alt="example" style="width: 100%" :src="previewImage" />
|
|
|
|
|
</a-modal>
|
|
|
|
|
</div>
|
|
|
|
|
</a-form-item>
|
2025-08-19 17:35:34 +08:00
|
|
|
</a-form>
|
|
|
|
|
</a-modal>
|
2025-08-19 22:22:44 +08:00
|
|
|
<context-holder />
|
2025-08-17 13:24:36 +08:00
|
|
|
</template>
|
|
|
|
|
<style scoped>
|
2025-08-17 22:39:59 +08:00
|
|
|
.table-operations {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
2025-08-17 13:24:36 +08:00
|
|
|
|
2025-08-17 22:39:59 +08:00
|
|
|
.table-operations > button {
|
|
|
|
|
margin-right: 8px;
|
|
|
|
|
}
|
2025-08-17 13:24:36 +08:00
|
|
|
</style>
|