Commit 522f7be02bbd6225eb25f2e53c8f0fe2602faf25

Authored by chenhang4442024
1 parent cf269d58

feat(project): add new feature for approve

1. 添加扣款单状态
2. 添加对账单
src/views/project/approve/PayPanel.vue
... ... @@ -262,6 +262,8 @@
262 262 return extractedValues.value.join(',');
263 263 } else if (record?.type == 50) {
264 264 return record?.fieldInfos?.checkBillVO?.innerNo;
  265 + }else if (record?.type == 70) {
  266 + return record?.fieldInfos?.deductionUrlFieldVO?.innerNo;
265 267 }
266 268 },
267 269 },
... ... @@ -275,6 +277,8 @@
275 277 return '应付账单申请';
276 278 } else if (record?.type == 50) {
277 279 return '生产科发票申请';
  280 + }else if (record?.type == 70) {
  281 + return `生产科扣款单申请${record?.fieldInfos?.deductionUrlFieldVO?.deductAmount ? `(¥${record?.fieldInfos?.deductionUrlFieldVO?.deductAmount?.toFixed(2)})` : ''}`;
278 282 }
279 283 },
280 284 },
... ... @@ -289,6 +293,8 @@
289 293 data.value = record?.fieldInfos?.producePaymentCheckBillFieldVO;
290 294 } else if (record?.type == 50) {
291 295 data.value = record?.fieldInfos?.checkBillVO;
  296 + } else if (record?.type == 70) {
  297 + data.value = record?.fieldInfos?.deductionUrlFieldVO;
292 298 }
293 299 return data.value?.productionName;
294 300 },
... ... @@ -353,7 +359,7 @@
353 359 function handleProfitModal() {}
354 360  
355 361 async function handleDetail(data) {
356   - if (data.type == 50) {
  362 + if (data.type == 50 ) {
357 363 showInvoice.value = true;
358 364 mockData.value = data.fieldInfos.checkBillVO;
359 365 } else if (data.type == 40) {
... ... @@ -361,6 +367,10 @@
361 367 mockData.value = data.fieldInfos.producePaymentCheckBillFieldVO;
362 368 financePerson.value = mockData.value?.financePerson;
363 369 trackerUser.value = mockData.value?.trackerUser;
  370 + }else if(data.type == 70){
  371 + showInvoice.value = true;
  372 + mockData.value = data.fieldInfos.deductionUrlFieldVO;
  373 + mockData.value.invoiceUrl = mockData.value.deductUrl;
364 374 }
365 375 openModal(true, { data });
366 376 id.value = data.id;
... ...
src/views/project/approve/ReceivePanel.vue
... ... @@ -35,15 +35,15 @@
35 35 </template>
36 36 </BasicTable>
37 37 <BasicModal
38   - :formFooter="!isApproved && role === ROLE.ADMIN"
  38 + :formFooter="(!isApproved && role === ROLE.ADMIN) || showInvoice"
39 39 @register="registerModal"
40 40 title="申请信息"
41 41 okText="通过"
42 42 width="1000px"
43 43 @ok="handleTrue"
44 44 @visible-change="handleShow"
45   - :showCancelBtn="!isApproved && role === ROLE.ADMIN"
46   - :showOkBtn="!isApproved && role === ROLE.ADMIN"
  45 + :showCancelBtn="(!isApproved && role === ROLE.ADMIN) || (showInvoice && role === ROLE.FINANCE)"
  46 + :showOkBtn="(!isApproved && role === ROLE.ADMIN) || (showInvoice && role === ROLE.FINANCE)"
47 47 >
48 48 <!-- <Description
49 49 class="mt-4"
... ... @@ -104,6 +104,9 @@
104 104 </tr>
105 105 </tbody>
106 106 </table>
  107 + <a v-if="showInvoice" @click="openPic(mockData.invoiceUrl)" rel="noopener noreferrer">{{
  108 + mockData.invoiceName
  109 + }}</a>
107 110 <template #appendFooter>
108 111 <a-button
109 112 v-if="!isApproved && (role === ROLE.ADMIN || role === ROLE.FINANCE)"
... ... @@ -111,7 +114,7 @@
111 114 >
112 115 不通过</a-button
113 116 >
114   - <a-button v-if="isApproved && role === ROLE.ADMIN" @click="handleFalse"> 驳回重填</a-button>
  117 + <a-button v-if="(isApproved && role === ROLE.ADMIN) || (isApproved && showInvoice && role === ROLE.FINANCE)" @click="handleFalse"> 驳回重填</a-button>
115 118 <a-button @click="handleExport"> 导出</a-button>
116 119 </template>
117 120 </BasicModal>
... ... @@ -167,7 +170,7 @@
167 170 const projectNo = ref();
168 171 const financePerson = ref('');
169 172 const trackerUser = ref('');
170   -
  173 + const showInvoice = ref(false);
171 174 const mockData = ref();
172 175 const schema: DescItem[] = [
173 176 {
... ... @@ -217,7 +220,12 @@
217 220 width: 150,
218 221 customRender: (column) => {
219 222 const { record } = column || {};
220   - return record?.fieldInfos?.invoiceFieldVO?.invoiceNo;
  223 + if(record?.type === 30){
  224 + return record?.fieldInfos?.invoiceFieldVO?.invoiceNo;
  225 + }else if(record?.type === 60){
  226 + return record?.fieldInfos?.deductionUrlFieldVO?.invoiceNo;
  227 + }
  228 +
221 229 },
222 230 },
223 231 {
... ... @@ -226,11 +234,28 @@
226 234 width: 150,
227 235 customRender: (column) => {
228 236 const { record } = column || {};
  237 + if(record?.type === 30){
229 238 const extractedValues = ref<string[]>(
230 239 record?.fieldInfos?.invoiceFieldVO?.innerNo?.map((item) => item),
231 240 );
232 241 // return record?.invoiceFieldVo?.innerNo;
233 242 return extractedValues?.value?.join(',');
  243 + }else if(record?.type === 60){
  244 + return record?.fieldInfos?.deductionUrlFieldVO?.innerNo;
  245 + }
  246 + },
  247 + },
  248 + {
  249 + title: '审核类型',
  250 + dataIndex: 'productionDepartment',
  251 + width: 150,
  252 + customRender: (column) => {
  253 + const { record } = column || {};
  254 + if (record?.type === 30) {
  255 + return '应收账单申请';
  256 + } else if (record?.type == 60) {
  257 + return `Invoice扣款单申请${record?.fieldInfos?.deductionUrlFieldVO?.deductAmount ? `(¥${record?.fieldInfos?.deductionUrlFieldVO?.deductAmount?.toFixed(2)})` : ''}`;
  258 + }
234 259 },
235 260 },
236 261 ];
... ... @@ -251,12 +276,21 @@
251 276 ]);
252 277 }
253 278  
  279 + const role = computed(() => {
  280 + return userStore.getUserInfo?.roleSmallVO?.code;
  281 + });
  282 +
  283 + // 根据角色动态设置 searchInfo
  284 + const searchInfo = computed(() => {
  285 + if (role.value === ROLE.BUSINESS || role.value === ROLE.TRACKER) {
  286 + return { type: 60 }; // 只显示 invoice 扣款单类型
  287 + }
  288 + return { type: 30 }; // 显示所有类型
  289 + });
  290 +
254 291 const [registerTable, { reload }] = useTable({
255 292 api: props.isApproved ? getApprovedListApi : getWaitListApi,
256   - searchInfo: { type: 30 },
257   - // scroll: {
258   - // scrollToFirstRowOnChange: true,
259   - // },
  293 + searchInfo: searchInfo.value,
260 294 columns,
261 295 useSearchForm: true,
262 296 formConfig: getFormConfig('invoiceNo'),
... ... @@ -265,7 +299,6 @@
265 299 width: 160,
266 300 title: 'Action',
267 301 dataIndex: 'action',
268   - // slots: { customRender: 'action' },
269 302 },
270 303 });
271 304  
... ... @@ -292,10 +325,32 @@
292 325 }
293 326  
294 327 function handleProfitModal() {}
  328 + // 打开图片或 PDF
  329 + function openPic(url: string) {
  330 + const baseUrl = url.split('?')[0]; // 获取问号前的部分
  331 + if (isImageUrl(baseUrl)) {
  332 + window.open('', '', '').document.write(`<!DOCTYPE html>
  333 + <html>
  334 + <body style="display: flex; justify-content: center; align-items: center;">
  335 + <img src='${url}' width="300px" height="auto"/>
  336 + </body>
  337 + </html>`);
  338 + } else if (isPdfUrl(baseUrl)) {
  339 + view(url); // 新标签页打开 PDF
  340 + } else {
  341 + console.log('不支持的文件类型');
  342 + }
  343 + }
295 344  
296 345 async function handleDetail(data) {
  346 + if(data.type == 60){
  347 + showInvoice.value = true;
  348 + mockData.value = data.fieldInfos.deductionUrlFieldVO;
  349 + }else if(data.type == 30){
  350 + showInvoice.value=false;
  351 + mockData.value=data.fieldInfos.invoiceFieldVO;
  352 + }
297 353 openModal(true, { data });
298   - mockData.value = data.fieldInfos.invoiceFieldVO;
299 354 id.value = data.id;
300 355 financePerson.value = mockData.value?.financePerson;
301 356 trackerUser.value = mockData.value?.trackerUser;
... ... @@ -309,6 +364,13 @@
309 364 otherAmount.value = mockData.value.otherAmount?.toFixed(2);
310 365 invoiceNo.value = mockData.value.invoiceNo;
311 366 payee.value = mockData.value.payee;
  367 + const match = mockData.value?.deductUrl?.match(/aliyuncs\.com\/(.*?)\?/);
  368 + if (match && match[1]) {
  369 + // 对提取的部分进行解码
  370 + mockData.value.invoiceName = decodeURIComponent(match[1]);
  371 + } else {
  372 + mockData.value.invoiceName = mockData.value?.deductUrl;
  373 + }
312 374 }
313 375  
314 376 async function handleTrue() {
... ... @@ -329,10 +391,6 @@
329 391 }
330 392 }
331 393  
332   - const role = computed(() => {
333   - return userStore.getUserInfo?.roleSmallVO?.code;
334   - });
335   -
336 394 // 定义MsgModalClose的事件,方便子组件调用
337 395 const handleMsgModalClose = async (data) => {
338 396 if (data) {
... ... @@ -418,6 +476,8 @@
418 476 msgVisible,
419 477 handleMsgModalClose,
420 478 handlePreview,
  479 + showInvoice,
  480 + openPic,
421 481 mockData,
422 482 schema,
423 483 totalPayAmount,
... ...
src/views/project/approve/index.vue
... ... @@ -16,9 +16,9 @@
16 16 v-if="
17 17 role == ROLE.FINANCE ||
18 18 role == ROLE.ADMIN
19   - // ||
20   - // role == ROLE.BUSINESS ||
21   - // role == ROLE.TRACKER
  19 + ||
  20 + role == ROLE.BUSINESS ||
  21 + role == ROLE.TRACKER
22 22 "
23 23 >
24 24 <ReceivePanel />
... ... @@ -60,9 +60,9 @@
60 60 v-if="
61 61 role == ROLE.FINANCE ||
62 62 role == ROLE.ADMIN
63   - // ||
64   - // role == ROLE.BUSINESS ||
65   - // role == ROLE.TRACKER
  63 + ||
  64 + role == ROLE.BUSINESS ||
  65 + role == ROLE.TRACKER
66 66 "
67 67 >
68 68 <ReceivePanel isApproved />
... ...
src/views/project/finance/financeList/finance.data.tsx
... ... @@ -38,8 +38,9 @@ export const searchFormSchema: FormSchema[] = [
38 38 componentProps: {
39 39 options: [
40 40 { label: '未创建', value: -1 },
41   - { label: '未收款', value: 0 },
42   - { label: '已收款', value: 10 },
  41 + { label: '未收款(已创建未提交审核)', value: 0 },
  42 + { label: '待收款(审核通过,未手动确认)', value: 10 },
  43 + { label: '已收款(手动确认)', value: 40 },
43 44 ],
44 45 },
45 46 },
... ... @@ -51,35 +52,13 @@ export const searchFormSchema: FormSchema[] = [
51 52 componentProps: {
52 53 options: [
53 54 { label: '未创建', value: -1 },
54   - { label: '未付款', value: 0 },
55   - { label: '已付款', value: 10 },
56   - ],
57   - },
58   - },
59   - {
60   - field: 'invoiceFinishStatus',
61   - label: 'invoice最终状态',
62   - component: 'Select',
63   - colProps: { span: 8 },
64   - componentProps: {
65   - options: [
66   - { label: '未完成', value: 0 },
67   - { label: '已完成', value: 10 },
68   - ],
69   - },
70   - },
71   - {
72   - field: 'checkFinishStatus',
73   - label: 'checkNo最终状态',
74   - component: 'Select',
75   - colProps: { span: 8 },
76   - componentProps: {
77   - options: [
78   - { label: '未完成', value: 0 },
79   - { label: '已完成', value: 10 },
  55 + { label: '未付款(已创建未提交审核)', value: 0 },
  56 + { label: '待付款(审核通过,未手动确认)', value: 10 },
  57 + { label: '已付款(手动确认)', value: 40 },
80 58 ],
81 59 },
82 60 },
  61 +
83 62 {
84 63 field: 'customerCode',
85 64 label: '客户编码',
... ... @@ -324,7 +303,7 @@ export const columns: BasicColumn[] = [
324 303 width: 80,
325 304 customRender: (column) => {
326 305 const deductUrl = column.record.invoiceDeductUrl;
327   - if (deductUrl == undefined) {
  306 + if (deductUrl == undefined || !deductUrl) {
328 307 return;
329 308 }
330 309 // return <FilePptOutlined style="font-size:25px" onClick={() => window.open(deductUrl[0])} />;
... ... @@ -332,6 +311,20 @@ export const columns: BasicColumn[] = [
332 311 },
333 312 },
334 313 {
  314 + title: '扣款单状态',
  315 + dataIndex: 'invoiceDeductUrlStatus',
  316 + width: 120,
  317 + customRender: (column) => {
  318 + if(column.record.invoiceDeductUrlStatus == 0){
  319 + return '待审核'
  320 + }else if(column.record.invoiceDeductUrlStatus == 10){
  321 + return '已通过'
  322 + }else if(column.record.invoiceDeductUrlStatus == 20){
  323 + return '已驳回'
  324 + }
  325 + },
  326 + },
  327 + {
335 328 title: '实际应收金额$',
336 329 dataIndex: 'invoiceActualReceivableAmount',
337 330 width: 120,
... ... @@ -381,32 +374,23 @@ export const columns: BasicColumn[] = [
381 374 dataIndex: 'invoiceStatus',
382 375 width: 120,
383 376 customRender: (column) => {
  377 + let statusText='';
  378 + let statusColor='blank';
384 379 if (column.record.invoiceStatus == null || column.record.invoiceStatus == undefined) {
385   - return '未创建';
  380 + statusText='未创建';
386 381 } else if (column.record.invoiceStatus == 0) {
387   - return '未收款';
  382 + statusText= '未收款';
388 383 } else if (column.record.invoiceStatus == 10) {
389   - return '已收款';
  384 + statusText= '待收款';
  385 + statusColor='red';
  386 + }else if (column.record.invoiceStatus == 40) {
  387 + statusText= '已收款';
  388 + statusColor='green';
390 389 }
  390 + return <span style={{color:statusColor}}>{statusText}</span>
391 391 },
392 392 },
393   - {
394   - title: '最终状态',
395   - dataIndex: 'invoiceFinishStatus',
396   - width: 120,
397   - customRender: (column) => {
398   - let statusText = '';
399   - let statusColor = '';
400   - if (column.record.invoiceFinishStatus == null || column.record.invoiceFinishStatus == undefined || column.record.invoiceFinishStatus == 0) {
401   - statusText = '未完成';
402   - statusColor = 'red'; // 设置红色
403   - } else if (column.record.checkFinishStatus == 10) {
404   - statusText = '已完成';
405   - statusColor = 'green'; // 设置绿色
406   - }
407   - return <span style={{ color: statusColor }}>{statusText}</span>;
408   - },
409   - },
  393 +
410 394 {
411 395 title: 'Action',
412 396 key: 'action', // 对应 #bodyCell 中的 column.key
... ... @@ -460,13 +444,27 @@ export const columns: BasicColumn[] = [
460 444 width: 120,
461 445 customRender: (column) => {
462 446 const deductUrl = column.record.checkDeductUrl;
463   - if (deductUrl == undefined) {
  447 + if (deductUrl == undefined || !deductUrl) {
464 448 return;
465 449 }
466 450 return <FilePptOutlined style="font-size:25px" />;
467 451 },
468 452 },
469 453 {
  454 + title: '扣款单状态',
  455 + dataIndex: 'checkDeductUrlStatus',
  456 + width: 120,
  457 + customRender: (column) => {
  458 + if(column.record.checkDeductUrlStatus == 0){
  459 + return '待审核'
  460 + }else if(column.record.checkDeductUrlStatus == 10){
  461 + return '已通过'
  462 + }else if(column.record.checkDeductUrlStatus == 20){
  463 + return '已驳回'
  464 + }
  465 + },
  466 + },
  467 + {
470 468 title: '生产科实际应付金额¥',
471 469 dataIndex: 'checkActualPayedAmount',
472 470 width: 180,
... ... @@ -534,32 +532,23 @@ export const columns: BasicColumn[] = [
534 532 dataIndex: 'checkPayStatus',
535 533 width: 120,
536 534 customRender: (column) => {
  535 + let statusText='';
  536 + let statusColor='blank';
537 537 if (column.record.checkPayStatus == null || column.record.checkPayStatus == undefined) {
538   - return '未创建';
  538 + statusText='未创建';
539 539 } else if (column.record.checkPayStatus == 0) {
540   - return '未付款';
  540 + statusText='未付款';
541 541 } else if (column.record.checkPayStatus == 10) {
542   - return '已付款';
  542 + statusText='待付款';
  543 + statusColor='red';
  544 + }else if (column.record.checkPayStatus == 40) {
  545 + statusText='已付款';
  546 + statusColor='green';
543 547 }
  548 + return <span style={{color:statusColor}}>{statusText}</span>
544 549 },
545 550 },
546   - {
547   - title: '最终状态',
548   - dataIndex: 'checkFinishStatus',
549   - width: 120,
550   - customRender: (column) => {
551   - let statusText = '';
552   - let statusColor = '';
553   - if (column.record.checkFinishStatus == null || column.record.checkFinishStatus == undefined || column.record.checkFinishStatus == 0) {
554   - statusText = '未完成';
555   - statusColor = 'red'; // 设置红色
556   - } else if (column.record.checkFinishStatus == 10) {
557   - statusText = '已完成';
558   - statusColor = 'green'; // 设置绿色
559   - }
560   - return <span style={{ color: statusColor }}>{statusText}</span>;
561   - },
562   - },
  551 +
563 552 {
564 553 title: 'Action',
565 554 key: 'action2', // 对应 #bodyCell 中的 column.key
... ...
src/views/project/finance/financeList/index.vue
... ... @@ -171,8 +171,9 @@
171 171 ...(role == ROLE.ADMIN
172 172 ? [
173 173 {
174   - label: '设置invoice最终完成',
  174 + label: '设置为已收款',
175 175 onClick: handleInvoiceSetFinishStatus.bind(null, record),
  176 + //需要设置按钮为红色。
176 177 },
177 178 ]
178 179 : []),
... ... @@ -261,7 +262,7 @@
261 262 ...(role == ROLE.ADMIN
262 263 ? [
263 264 {
264   - label: '设置对账单最终完成',
  265 + label: '设置为已付款',
265 266 onClick: handleCheckSetFinishStatus.bind(null, record),
266 267 },
267 268 ]
... ...
src/views/project/order/Statement.vue 0 → 100644
  1 +<template>
  2 +
  3 + <BasicModal
  4 + v-bind="$attrs"
  5 + destroyOnClose
  6 + @register="register"
  7 + title="LOACL对账单"
  8 + width="1100px"
  9 + @visible-change="handleShow"
  10 + :footer="null"
  11 + :bodyStyle="{ height: '650px', overflow: 'hidden' }"
  12 + >
  13 + <div class="container">
  14 + <div v-if="isShow1 == true" style="margin-top: 30px">
  15 + <RadioGroup v-model:value="selectedCompany" :options="companyOptions" style="display: flex; gap: 200px;"/>
  16 + </div>
  17 + <!-- 代垫费用区域 -->
  18 + <div class="table-container" style="display: flex; flex-wrap: wrap; justify-content: space-between; margin-top: 30px" >
  19 + <div class="table-box" style="flex: 0.5; margin-right: -20px;">
  20 + <h3>代垫费用</h3>
  21 + <div class="input-row" style="display: flex; align-items: center;">
  22 + <a-button type="primary" style="margin-left: 10px;" @click="addAdvanceCost">添加费用</a-button>
  23 + </div>
  24 + <div v-for="(item, index) in advanceCosts" :key="'advance-' + index" class="input-row" style="margin-top: 10px; display: flex;">
  25 + <a-input v-model:value="item.name" placeholder="请输入费用名称" style="width: 200px;" />
  26 + <a-input-number v-model:value="item.value" placeholder="请输入费用金额" style="width: 150px !important; margin-left: 10px;" />
  27 + <a-button type="danger" style="margin-left: 10px;" @click="removeAdvanceCost(index)">删除</a-button>
  28 + </div>
  29 + </div>
  30 + <div class="table-box" style="flex: 1;">
  31 + <h3>费用支出</h3>
  32 + <div class="input-row" style="display: flex; align-items: center;">
  33 + <a-button type="primary" style="margin-left: 10px;" @click="addExpenseCost">添加费用</a-button>
  34 + </div>
  35 + <div v-for="(item, index) in expenseCosts" :key="'expense-' + index" class="input-row" style="margin-top: 10px; display: flex;">
  36 + <a-input v-model:value="item.name" placeholder="请输入费用名称" style="width: 200px;" />
  37 + <a-input-number v-model:value="item.value" placeholder="请输入费用金额" style="width: 150px !important; margin-left: 10px;" />
  38 + <a-button type="danger" style="margin-left: 10px;" @click="removeExpenseCost(index)">删除</a-button>
  39 + </div>
  40 + </div>
  41 + </div>
  42 +
  43 + <div class="bottom">
  44 + <a-button type="primary" @click="handleAccountStatement" className="ml-4" v-if="isShow1 == true"
  45 + >生成</a-button
  46 + >
  47 + <a-button type="primary" @click="handleCancel" className="ml-4" style="margin-left: 6px"
  48 + >取消</a-button
  49 + >
  50 + </div>
  51 + </div>
  52 + </BasicModal>
  53 + </template>
  54 + <script lang="ts">
  55 + import { defineComponent, ref, computed } from 'vue';
  56 + import { BasicModal, useModalInner } from '/@/components/Modal';
  57 + import { RadioGroup } from 'ant-design-vue';
  58 + import { useMessage } from '@/hooks/web/useMessage';
  59 + import axios from 'axios';
  60 +
  61 + export default defineComponent({
  62 + props: {
  63 + role: {
  64 + type: String,
  65 + },
  66 + customerCodes: {
  67 + type: Array<string | number>,
  68 + },
  69 + },
  70 + components: { BasicModal, RadioGroup },
  71 + setup(props) {
  72 + const loading = ref(true);
  73 + const choose = ref();
  74 + const checkedKeys=ref();
  75 + const isShow1 = ref(true);
  76 + const advanceCostValue = ref(0);
  77 + const expenseCostValue = ref(0);
  78 + const advanceCosts = ref<{ name: string; value: number }[]>([]);
  79 + const expenseCosts = ref<{ name: string; value: number }[]>([]);
  80 + const [register, { setModalProps, closeModal }] = useModalInner(async (data)=>{
  81 + checkedKeys.value=data.checkedKeys;
  82 + });
  83 + const { createMessage } = useMessage();
  84 + const { error } = createMessage;
  85 + const selectedCompany = ref();
  86 + const companyOptions = ref([
  87 + { label: '翱特逸格饰品有限公司', value: '翱特逸格饰品有限公司' },
  88 + { label: '吉庆天成饰品有限公司', value: '吉庆天成饰品有限公司' }
  89 + ]);
  90 +
  91 + function addAdvanceCost() {
  92 + advanceCosts.value.push({ name: '', value: advanceCostValue.value });
  93 + }
  94 +
  95 + function addExpenseCost() {
  96 + expenseCosts.value.push({ name: '', value: expenseCostValue.value });
  97 + }
  98 +
  99 + function removeAdvanceCost(index: number) {
  100 + advanceCosts.value.splice(index, 1);
  101 + }
  102 +
  103 + function removeExpenseCost(index: number) {
  104 + expenseCosts.value.splice(index, 1);
  105 + }
  106 +
  107 + function handleCancel() {
  108 + loading.value = true;
  109 + choose.value = '';
  110 + setModalProps({ loading: false, confirmLoading: false });
  111 + isShow1.value = true;
  112 + advanceCosts.value = [];
  113 + expenseCosts.value = [];
  114 + closeModal();
  115 + }
  116 +
  117 + function handleShow(visible: boolean) {
  118 + if (visible) {
  119 + loading.value = true;
  120 + choose.value = '';
  121 + setModalProps({ loading: false, confirmLoading: false });
  122 + isShow1.value = true;
  123 + } else {
  124 + advanceCosts.value = [];
  125 + expenseCosts.value = [];
  126 + }
  127 + }
  128 + async function handleAccountStatement() {
  129 + if (!selectedCompany.value) {
  130 + error('请选择公司'); // 提示用户选择公司
  131 + return; // 结束函数执行
  132 + }
  133 +
  134 + axios
  135 + .post(
  136 + '/basic-api/order/erp/order/accountStatement',
  137 + {
  138 + ids: checkedKeys.value,
  139 + disbursement: advanceCosts.value.map(item => ({ [item.name]: item.value })),
  140 + deduction: expenseCosts.value.map(item => ({ [item.name]: item.value })),
  141 + titleCompany: selectedCompany.value
  142 + },
  143 + {
  144 + responseType: 'blob', // 设置响应类型为 'blob'
  145 + },
  146 + )
  147 + .then((response) => {
  148 + // 创建一个 Blob 对象来保存二进制数据
  149 + const blob = new Blob([response.data], { type: 'application/octet-stream' });
  150 + const getFormattedDate = (): string => {
  151 + const date = new Date();
  152 +
  153 + const year = date.getFullYear();
  154 + const month = String(date.getMonth() + 1).padStart(2, '0');
  155 + const day = String(date.getDate()).padStart(2, '0');
  156 +
  157 + const hours = String(date.getHours()).padStart(2, '0');
  158 + const minutes = String(date.getMinutes()).padStart(2, '0');
  159 + const seconds = String(date.getSeconds()).padStart(2, '0');
  160 +
  161 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  162 + };
  163 + const date = getFormattedDate();
  164 + // 创建一个链接元素用于下载
  165 + const link = document.createElement('a');
  166 + link.href = window.URL.createObjectURL(blob);
  167 + link.download = `${selectedCompany.value}对账单${date}.xlsx`; // 你可以为文件命名
  168 + document.body.appendChild(link);
  169 + link.click(); // 自动点击链接,触发下载
  170 + document.body.removeChild(link); // 下载完成后移除链接
  171 + })
  172 + .catch((error) => {
  173 + console.error(error);
  174 + });
  175 + closeModal();
  176 + }
  177 + return {
  178 + register,
  179 + loading,
  180 + choose,
  181 + handleShow,
  182 + handleCancel,
  183 + advanceCostValue,
  184 + expenseCostValue,
  185 + advanceCosts,
  186 + expenseCosts,
  187 + addAdvanceCost,
  188 + addExpenseCost,
  189 + isShow1,
  190 + selectedCompany,
  191 + companyOptions,
  192 + removeAdvanceCost,
  193 + removeExpenseCost,
  194 + handleAccountStatement,
  195 + };
  196 + },
  197 + });
  198 + </script>
  199 + <style scoped>
  200 + /* .container {
  201 + position: relative;
  202 + }
  203 + .bottom {
  204 + position: absolute;
  205 + bottom: 0;
  206 + } */
  207 + .container {
  208 + display: flex;
  209 + flex-direction: column;
  210 + /* min-height: 20vh; */
  211 + }
  212 +
  213 + .bottom {
  214 + margin-top: 40px;
  215 + /* margin-right: auto; */
  216 + margin-left: 150px;
  217 + }
  218 +
  219 + .showPdf {
  220 + display: flex;
  221 + position: relative;
  222 + height: 30px;
  223 + width: 470px;
  224 + border: 2px solid;
  225 + border-radius: 5px;
  226 + border-color: #d8d8d8;
  227 + }
  228 +
  229 + .FilePptOutlined {
  230 + margin-top: 3px;
  231 + margin-left: 30px;
  232 + }
  233 +
  234 + .EyeOutlined {
  235 + margin-top: 3px;
  236 + margin-left: 290px;
  237 + }
  238 + </style>
... ...
src/views/project/order/index.vue
... ... @@ -118,6 +118,18 @@
118 118 <!-- <a-space wrap :size="[8, 16]" :style="{ marginBottom: '2px', marginLeft: '10px' }"> -->
119 119 <a-space wrap :style="{ marginBottom: '2px', marginTop: '2px' }">
120 120 <a-button
  121 + :style="{backgroundColor: '#ff69b4', borderColor: '#ff69b4', color: 'white',borderRadius: '5px 5px 5px 5px' }"
  122 + shape="default"
  123 + type="primary"
  124 + @click=" handleStatementModal"
  125 + v-if="
  126 + role === ROLE.ADMIN ||
  127 + role === ROLE.BUSINESS ||
  128 + role === ROLE.TRACKER
  129 + "
  130 + >LOACL对账单</a-button
  131 + >
  132 + <a-button
121 133 :style="{ borderRadius: '5px 5px 5px 5px' }"
122 134 type="primary"
123 135 @click="handleProductInvoiceModal"
... ... @@ -231,6 +243,11 @@
231 243 :role="role"
232 244 :customerCodes="selectedCustomCodes"
233 245 />
  246 + <Statement
  247 + @register="statementModalRegister"
  248 + :role="role"
  249 + :customerCodes="selectedCustomCodes"
  250 + />
234 251 <InvoiceCreate @register="invoiceCreateModalRegister" @success="handleFormSuccess" />
235 252 <ServiceProfit @register="serviceProfitModalRegister" />
236 253 <ProductProfit @register="productProfitModalRegister" />
... ... @@ -250,11 +267,12 @@
250 267 import { BasicTable, useTable, TableAction } from '/@/components/Table';
251 268 import { FormOutlined } from '@ant-design/icons-vue';
252 269 import HeaderCell from '/@/components/Table/src/components/HeaderCell.vue';
253   - import { Alert } from 'ant-design-vue';
  270 + import { Alert } from 'ant-design-vue'
254 271  
255 272 import { useDrawer } from '/@/components/Drawer';
256 273 import ProfitAnalysis from './ProfitAnalysis.vue';
257 274 import ProductText from './ProductText.vue';
  275 + import Statement from './Statement.vue';
258 276 import RateModal from './RateModal.vue';
259 277 import ExportModal from './ExportModal.vue';
260 278 import PassCalculate from './PassCalculate.vue';
... ... @@ -291,6 +309,7 @@
291 309 FormDetail,
292 310 ProfitAnalysis,
293 311 ProductText,
  312 + Statement,
294 313 PassCalculate,
295 314 FormOutlined,
296 315 CheckDetail,
... ... @@ -316,6 +335,7 @@
316 335 const [exportModalRegister, { openModal: openExportModal }] = useModal();
317 336 const [productModalRegister, { openModal: openProductModal }] = useModal();
318 337 const [passModalRegister, { openModal: openPassModal }] = useModal();
  338 + const [statementModalRegister, { openModal: openStatementModal }] = useModal();
319 339  
320 340 const tooltipVisible = ref(false);
321 341 const [formDetailRegister, { openDrawer: openFormDetailDrawer }] = useDrawer();
... ... @@ -761,6 +781,20 @@
761 781 data: values,
762 782 });
763 783 }
  784 + //对账单
  785 + function handleStatementModal(record) {
  786 + const form = getForm();
  787 + const values = form.getFieldsValue();
  788 + if (checkedKeys.value.length == 0) {
  789 + error('请选择订单');
  790 + return;
  791 + }
  792 + openStatementModal(true, {
  793 + checkedKeys: checkedKeys.value,
  794 + customers: selectedCustomCodes.value,
  795 + data: values,
  796 + });
  797 + }
764 798  
765 799 function handleRateModal() {
766 800 const form = getForm();
... ... @@ -871,6 +905,7 @@
871 905 rateModalRegister,
872 906 exportModalRegister,
873 907 productModalRegister,
  908 + statementModalRegister,
874 909 passModalRegister,
875 910 historyDetailRegister,
876 911 trackHistoryRegister,
... ... @@ -878,6 +913,7 @@
878 913 handleProfitModal,
879 914 handleInvoiceCreateModal,
880 915 handleProductInvoiceModal,
  916 + handleStatementModal,
881 917 registerTable,
882 918 getFormValues,
883 919 checkedKeys,
... ... @@ -905,6 +941,7 @@
905 941 handleRateModal,
906 942 openExportModal,
907 943 openProductModal,
  944 + openStatementModal,
908 945 openPassModal,
909 946 handleDelete,
910 947 handleServiceProfitModal,
... ...