mg-ocr-invoice 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,750 +1,750 @@
1
- <template>
2
- <div class="InvoiceList">
3
- <div class="selectAll">
4
- <div class="left">
5
- 已选 {{ selectedLength }}/{{ selectedLengthCount }}
6
- </div>
7
- <div class="right">
8
- <span>全选</span>
9
- <input
10
- type="checkbox"
11
- @change.self="changeSelectAll"
12
- v-model="selectedAll"
13
- @click.stop
14
- class="checkbox" />
15
- <!-- <Checkbox
16
- @change.self="changeSelectAll"
17
- v-model="selectedAll"
18
- @click.stop
19
- icon-size="22px"></Checkbox> -->
20
- </div>
21
- </div>
22
-
23
- <div class="card">
24
- <ul>
25
- <li v-for="(item, index) in list" :key="index" @click="clickItem(item)">
26
- <div class="li">
27
- <div
28
- class="item-card"
29
- v-if="
30
- item.taskStatus === 'finish' || item.taskStatus === 'repeat'
31
- ">
32
- <div class="details-btn" @click.stop="openDetails(item)">
33
- 详情
34
- </div>
35
- <div
36
- class="title"
37
- :class="{ person: item.invoiceCompanyType === '公司' }">
38
- {{ item.invoiceCompanyType }}
39
- </div>
40
- <div class="storeName">
41
- <div class="name">
42
- <div class="left">{{ item.sellerName }}</div>
43
- <div class="right" v-if="item.taskStatus === 'repeat'">
44
- 已收录
45
- </div>
46
- </div>
47
- <div class="tags">
48
- <span v-if="item.manualModify" class="manual">
49
- 手工录入
50
- </span>
51
- <div v-else-if="item.invoiceExceptionInfo">
52
- <span :class="setClass(item.invoiceExceptionInfo)">{{
53
- const_invoiceExceptionInfo[item.invoiceExceptionInfo]
54
- }}</span>
55
- </div>
56
- <span v-else :class="setClass(item.realStatus)">{{
57
- const_realStatus[item.realStatus]
58
- }}</span>
59
- <span :class="setClass(item.invoiceStatus)">{{
60
- const_invoiceStatus[item.invoiceStatus]
61
- }}</span>
62
- <!-- <span class="error">识别失败</span>
63
- <span class="default">已使用</span>
64
- <span class="success">识别成功</span> -->
65
- </div>
66
- <div class="InvoiceInfo">
67
- <div class="leftinfo">
68
- <div class="item-info">
69
- <span class="label">发票金额</span>
70
- <span class="value price"
71
- >¥{{
72
- item.priceTaxTotalFigure || item.noTaxAmountTotal
73
- }}</span
74
- >
75
- </div>
76
- <div class="item-info">
77
- <span class="label">发票抬头</span>
78
- <span class="value">{{ item.payerName }}</span>
79
- </div>
80
- <div class="item-info">
81
- <span class="label">发票类型</span>
82
- <span class="value">{{ item.description }}</span>
83
- </div>
84
- <div class="item-info">
85
- <span class="label">发票时间</span>
86
- <span class="value">{{ item.invoiceDate }}</span>
87
- </div>
88
- </div>
89
- <div class="rightCheckbox">
90
- <input
91
- @click.stop
92
- type="checkbox"
93
- class="checkbox"
94
- @change="changeItemCheckbox"
95
- v-model="item.selected"
96
- icon-size="22px" />
97
- </div>
98
- </div>
99
- </div>
100
- </div>
101
-
102
- <div
103
- v-else-if="
104
- item.taskStatus === 'ocr_doing' ||
105
- item.taskStatus === 'ocr_success'
106
- "
107
- style="
108
- background-color: #fff;
109
- padding: 21px 0 15px 12px;
110
- position: relative;
111
- width: 100%;
112
- ">
113
- <Loading class="loading" color="#0094ff">识别中...</Loading>
114
- <Skeleton :loading="true" title :row="6"></Skeleton>
115
- </div>
116
- <div class="err-card" v-else>
117
- <div class="left-img" @click="openErrImg(item.fileUrlKey)">
118
- <img :src="item.fileUrlKey" alt="" />
119
- </div>
120
- <div class="right-errText">
121
- <div class="title"><span>识别失败</span></div>
122
- <div class="content">
123
- <div class="recognitionResult">
124
- <p class="errContent">OCR识别失败</p>
125
- <p>可以重新拍照识别,或手动添加信息导入</p>
126
- </div>
127
- <div>
128
- <input
129
- type="checkbox"
130
- class="checkbox"
131
- @click.stop
132
- v-model="item.selected" />
133
- </div>
134
- </div>
135
- </div>
136
- </div>
137
- </div>
138
- </li>
139
- </ul>
140
- </div>
141
- <div class="btn-form">
142
- <div class="operate">
143
- <div class="delete">
144
- <span @click="deleteSelectItem">移除</span>
145
- </div>
146
- <div class="right-btn">
147
- <span class="add" @click="add">继续添加发票</span>
148
- <span class="ok" :class="{ disabledColor: !submitBtn }" @click="ok"
149
- >确定选择</span
150
- >
151
- </div>
152
- </div>
153
- </div>
154
- </div>
155
- <Popup v-model:show="showPopup" position="bottom" closeable>
156
- <div class="boxPopup">
157
- <div class="title">
158
- <span>添加发票</span>
159
- </div>
160
- <div style="padding-bottom: 24px" @click="selectImg(1)">从相册中选择</div>
161
- <div @click="selectImg(2)">拍照上传</div>
162
- </div>
163
- </Popup>
164
- <Overlay
165
- style="display: flex; justify-content: center; align-items: center"
166
- :show="showLoading">
167
- <Loading class="loading" color="#0094ff">上传中...</Loading>
168
- </Overlay>
169
- </template>
170
- <!-- 异常发票, 已使用的, 使用中的, 识别失败 禁止提交 -->
171
- <script setup lang="ts">
172
- import 'vant/lib/index.css'
173
- import { ref, onMounted, computed } from 'vue'
174
- import {
175
- Popup,
176
- Skeleton,
177
- Loading,
178
- showToast,
179
- showConfirmDialog,
180
- Overlay,
181
- } from 'vant'
182
- import '@/utils/disableZoom'
183
- import { selectPhoto, takePhoto } from '@/utils/upload'
184
- import {
185
- __deleteInvoice,
186
- __getUploadInvoiceList,
187
- __uploadInvoice,
188
- } from '@/api/invoice'
189
- import {
190
- const_invoiceStatus,
191
- const_realStatus,
192
- const_taskStatus,
193
- const_invoiceExceptionInfo,
194
- setClass,
195
- VerificationOfTruth,
196
- } from './const'
197
- import { showImagePreview } from 'vant'
198
- const emit = defineEmits(['edit', 'ok'])
199
- const { listId, multiple, catchList } = defineProps({
200
- listId: {
201
- required: false,
202
- type: String,
203
- },
204
- multiple: {
205
- required: true,
206
- type: Boolean,
207
- },
208
- catchList: {},
209
- })
210
- const token: any = ref(sessionStorage.getItem('token'))
211
- const showLoading = ref(false)
212
- const showPopup = ref(false)
213
- const list: any = ref<Array<any>>([])
214
- if (catchList) {
215
- list.value = catchList
216
- }
217
- const batchId = ref('')
218
- const selectId = computed(() => {
219
- return list.value
220
- .filter((item: any) => item.selected)
221
- .map((item: any) => item.taskId)
222
- })
223
-
224
- const getList = async () => {
225
- return new Promise(async (resolve, reject) => {
226
- const data: any = {}
227
- data.batchId = listId
228
- if (!data.batchId) return
229
- try {
230
- const res: any = await __getUploadInvoiceList(data, token.value)
231
- if (res.code === 200) {
232
- list.value = res.data.invoiceList.map((item: any) => {
233
- let data = list.value.find((v: any) => v.taskId === item.taskId) || {}
234
- return {
235
- ...Object.assign(data, item),
236
- }
237
- })
238
- batchId.value = res.data.batchId
239
- if (list.value.length <= 0) {
240
- selectedAll.value = false
241
- } else {
242
- changeItemCheckbox()
243
- }
244
- resolve(list.value)
245
- }
246
- } catch (error) {
247
- console.log(error)
248
- reject(error)
249
- }
250
- })
251
- }
252
- const clickItem = (row: any) => {
253
- console.log(row.invoiceStatus)
254
- if (row.invoiceStatus !== 'unused') {
255
- return showToast('发票已被使用!')
256
- }
257
- row.selected = !row.selected
258
- }
259
-
260
- const openDetails = (row: any) => {
261
- // if (!isEdit(row)) {
262
- // return
263
- // }
264
- emit(
265
- 'edit',
266
- {
267
- taskId: row.taskId,
268
- batchId: batchId.value,
269
- },
270
- row,
271
- list.value
272
- )
273
- }
274
- const selectStatus = (item: any) => {
275
- // 未使用&& (验真通过||无需验真|| 手工录入||验真异常) && 发票推断状态错误值为空 && 发票识别任务已完成
276
- if (item.invoiceStatus === 'unused') {
277
- if (item.manualModify) {
278
- return true
279
- }
280
- if (!item.invoiceExceptionInfo) {
281
- return true
282
- }
283
- return false
284
- } else {
285
- return false
286
- }
287
- // if (item.invoiceStatus==='unused') {
288
- // return true
289
- // } else if (item.manualModify) {
290
- // return true
291
- // } else if (!item.invoiceExceptionInfo) {
292
- // return true
293
- // } else {
294
- // return false
295
- // }
296
- }
297
- const selectedAll: any = ref(false)
298
- const changeSelectAll = (v: any) => {
299
- if (selectedAll.value) {
300
- showToast('存在已使用或异常发票, 已自动过滤')
301
- }
302
- list.value.forEach((item: any) => {
303
- item.selected = selectStatus(item) && selectedAll.value
304
- })
305
- }
306
- const changeItemCheckbox = () => {
307
- const status = list.value
308
- .filter((item: any) => selectStatus(item))
309
- .every((item: any) => item.selected)
310
- selectedAll.value = status
311
- }
312
- const deleteSelectItem = () => {
313
- if (selectId.value.length <= 0) {
314
- showToast({ type: 'text', message: '请先选择发票' })
315
- return
316
- }
317
- showConfirmDialog({
318
- title: '提醒',
319
- message: '请确认是否需要移除选中项,移除后不可恢复!',
320
- })
321
- .then(async () => {
322
- var urlencoded = new URLSearchParams()
323
- urlencoded.append('batchId', batchId.value)
324
- const idsStr = selectId.value.join(',')
325
- urlencoded.append('taskIds', idsStr)
326
- const res: any = await __deleteInvoice(urlencoded, token.value)
327
- if (res.code === 200) {
328
- showToast({ type: 'success', message: '移除成功' })
329
- getList()
330
- }
331
- })
332
- .catch(() => {
333
- // on cancel
334
- })
335
- }
336
- const add = () => {
337
- showPopup.value = true
338
- }
339
-
340
- const selectImg = async (type: number) => {
341
- let fd: FormData
342
- if (type === 1) {
343
- fd = await selectPhoto(multiple)
344
- } else {
345
- fd = await takePhoto()
346
- }
347
- fd.append('batchId', batchId.value)
348
- showLoading.value = true
349
- showPopup.value = false
350
- try {
351
- const res: any = await __uploadInvoice(fd)
352
- if (res.code === 200) {
353
- showToast({
354
- type: 'success',
355
- message: '上传成功',
356
- })
357
- setTimeGetList()
358
- }
359
- } catch (error) {
360
- console.log(error)
361
- setTimeGetList()
362
- }
363
- showLoading.value = false
364
- }
365
- const submitBtn = computed(() => {
366
- const flag = list.value
367
- .filter((item: any) => item.selected)
368
- .every((item: any) => {
369
- return selectStatus(item)
370
- })
371
- return flag && list.value.filter((item: any) => item.selected).length > 0
372
- })
373
- const selectedLength = computed(() => {
374
- return list.value.filter((item: any) => item.selected && selectStatus(item))
375
- .length
376
- })
377
- const selectedLengthCount = computed(() => {
378
- return list.value.filter((item: any) => selectStatus(item)).length
379
- })
380
- const openErrImg = (url: any) => {
381
- showImagePreview([url])
382
- }
383
- const ok = () => {
384
- if (selectId.value.length <= 0) {
385
- showToast({ type: 'text', message: '请先选择发票' })
386
- return
387
- }
388
- if (!submitBtn.value) {
389
- showToast({
390
- type: 'text',
391
- message: '存在异常发票,无法提交, 请先去修改发票金额',
392
- })
393
- return
394
- }
395
- // showDeleteBtn.value = false
396
- const selectData = list.value
397
- .filter((item: any) => item.selected)
398
- .map((item: any) => {
399
- VerificationOfTruth
400
- let data = VerificationOfTruth(item)
401
- let obj: any = { ...item, ...data }
402
- return obj
403
- })
404
- emit('ok', selectData, batchId.value)
405
- }
406
- const timeID: any = ref(null)
407
- const setTimeGetList = () => {
408
- getList()
409
- .then((res: any) => {
410
- const flag = res.some(
411
- (item: any) =>
412
- item.taskStatus === 'ocr_doing' || item.taskStatus === 'ocr_success'
413
- )
414
- if (!flag) {
415
- return
416
- }
417
- setTimeout(setTimeGetList, 2000)
418
- })
419
- .catch((err) => {
420
- setTimeout(setTimeGetList, 2000)
421
- })
422
- }
423
- onMounted(() => {
424
- setTimeGetList()
425
- })
426
- </script>
427
- <style lang="scss" scoped>
428
- .disabledColor {
429
- opacity: 0.6; /* 降低不透明度以表示禁用状态 */
430
- cursor: not-allowed; /* 鼠标指针样式设置为禁用 */
431
- }
432
- .InvoiceList {
433
- padding-bottom: 100px;
434
- position: relative;
435
- width: 100%;
436
- min-height: 100vh;
437
- background-color: #f3f4f6;
438
- .selectAll {
439
- background-color: #f3f4f6;
440
- position: sticky;
441
- top: 0;
442
- z-index: 999;
443
- padding: 12px;
444
- display: flex;
445
- justify-content: space-between;
446
- .right {
447
- display: flex;
448
- gap: 5px;
449
- padding-right: 12px;
450
- }
451
- }
452
- .card {
453
- width: 100%;
454
- ul {
455
- display: flex;
456
- flex-direction: column;
457
- gap: 12px;
458
- .li {
459
- width: 100%;
460
- border-radius: 4px;
461
- padding: 0 12px;
462
- display: flex;
463
- align-items: center;
464
- gap: 12px;
465
- overflow: hidden;
466
-
467
- .item-card {
468
- flex: 1;
469
- position: relative;
470
-
471
- background-color: #fff;
472
- padding-bottom: 20px;
473
- min-width: 100%;
474
- .details-btn {
475
- position: absolute;
476
- bottom: 12px;
477
- right: 9px;
478
- border: 1px solid #dcdee0;
479
- padding: 4px 16px;
480
- font-size: 14px;
481
- color: #333;
482
- border-radius: 2px;
483
- }
484
- div.person {
485
- background-color: #ff8b26;
486
- }
487
- .title {
488
- color: #fff;
489
- display: flex;
490
- width: max-content;
491
- text-align: center;
492
- align-items: center;
493
- font-size: 12px;
494
- border-radius: 4px 0px 4px 0px;
495
- padding: 2px 4px;
496
- background-color: #266fe8;
497
- margin-bottom: 5px;
498
- }
499
-
500
- .storeName {
501
- padding: 0 12px;
502
- .name {
503
- display: flex;
504
- justify-content: space-between;
505
- width: 100%;
506
- align-items: center;
507
- .left {
508
- font-size: 15px;
509
- color: #333333;
510
- font-weight: 700;
511
- }
512
- .right {
513
- font-size: 12px;
514
- color: #ee0a24;
515
- }
516
- }
517
- .tags {
518
- display: flex;
519
- align-items: center;
520
- gap: 7px;
521
- margin-top: 8px;
522
- margin-bottom: 15px;
523
- .manual {
524
- background-color: #facd91;
525
- color: #c77458;
526
- }
527
- span {
528
- padding: 2px 4px;
529
- font-size: 12px;
530
- font-weight: 400;
531
- border-radius: 2px;
532
- &.ok {
533
- background-color: rgba(38, 111, 232, 0.1);
534
- color: #266fe8;
535
- }
536
- &.warning {
537
- background-color: rgba(255, 251, 230, 1);
538
- color: #ff8f26;
539
- }
540
- &.error {
541
- background-color: rgba(255, 242, 240, 1);
542
- color: #ff4d4f;
543
- }
544
- &.default {
545
- background-color: rgba(153, 153, 153, 0.2);
546
- color: #666666;
547
- }
548
- &.success {
549
- background-color: #e6ffdd;
550
- color: #2ba500;
551
-
552
- opacity: 0.68;
553
- }
554
- }
555
- }
556
- .InvoiceInfo {
557
- display: flex;
558
- justify-content: space-between;
559
- .leftinfo {
560
- .item-info {
561
- display: flex;
562
- align-items: center;
563
- gap: 16px;
564
- margin-bottom: 8px;
565
- &:last-child {
566
- margin-bottom: 0;
567
- }
568
- span {
569
- font-size: 14px;
570
- &.label {
571
- color: #999999;
572
- }
573
- &.value {
574
- color: #333333;
575
- }
576
- &.price {
577
- color: #266fe8;
578
- font-weight: 700;
579
- }
580
- }
581
- }
582
- }
583
- .rightCheckbox {
584
- height: max-content;
585
- }
586
- }
587
- }
588
- }
589
-
590
- .err-card {
591
- background-color: #fff;
592
- width: 100%;
593
- display: flex;
594
- gap: 9px;
595
- .left-img {
596
- width: 113px;
597
- height: 79px;
598
- margin: 10px 0;
599
- margin-left: 10px;
600
- flex-shrink: 0;
601
- img {
602
- width: 100%;
603
- height: 100%;
604
- }
605
- }
606
- .right-errText {
607
- .title {
608
- color: #fff;
609
- display: flex;
610
- text-align: right;
611
- align-items: center;
612
- justify-content: flex-end;
613
- margin-bottom: 5px;
614
- span {
615
- background-color: rgba(238, 10, 36, 0.1);
616
- color: #ee0a24;
617
- padding: 2px 4px;
618
- font-size: 12px;
619
- border-radius: 0 4px 0 4px;
620
- }
621
- }
622
- .content {
623
- display: flex;
624
- padding-right: 12px;
625
- align-items: center;
626
- font-size: 12px;
627
- gap: 18px;
628
- .recognitionResult {
629
- gap: 5px;
630
- display: flex;
631
- flex-direction: column;
632
- justify-content: space-between;
633
- p {
634
- color: #666666;
635
- }
636
- }
637
- }
638
- }
639
- }
640
- }
641
- .loading {
642
- position: absolute;
643
- left: 50%;
644
- top: 50%;
645
- transform: translate(-50%, -50%);
646
- z-index: 100;
647
- }
648
- }
649
- }
650
-
651
- .btn-form {
652
- z-index: 999;
653
- position: fixed;
654
- bottom: 0;
655
- left: 0;
656
- background-color: #fff;
657
- height: 90px;
658
- width: 100%;
659
-
660
- .operate {
661
- width: 100%;
662
- height: 100%;
663
- display: flex;
664
- gap: 12px;
665
- padding: 12px;
666
- padding-bottom: 34px;
667
- font-size: 15px;
668
- span {
669
- flex: 1;
670
- display: flex;
671
- align-items: center;
672
- font-weight: 500;
673
- font-size: 15px;
674
- justify-content: center;
675
- }
676
- .delete {
677
- border: 1px solid #e8e8e8;
678
- display: flex;
679
- align-items: center;
680
- padding: 0 44px;
681
- }
682
- .right-btn {
683
- display: flex;
684
- flex: 1;
685
- span {
686
- color: #fff;
687
- }
688
- .add {
689
- background-color: #ff8b26;
690
- border-radius: 4px 0px 0px 4px;
691
- }
692
- .ok {
693
- border-radius: 0px 4px 4px 0px;
694
- background-color: #266fe8;
695
- }
696
- }
697
- }
698
- }
699
- }
700
- .boxPopup {
701
- height: 100%;
702
- display: flex;
703
- flex-direction: column;
704
- // align-items: center;
705
- // justify-content: space-around;
706
- padding: 16px 23px 34px;
707
- div {
708
- width: 100%;
709
- font-size: 14px;
710
- display: flex;
711
- justify-content: center;
712
- align-items: center;
713
- }
714
- .title {
715
- width: 100%;
716
- display: flex;
717
- justify-content: center;
718
- // padding: 24px 0;
719
- font-size: 16px;
720
- font-weight: 700;
721
- padding-bottom: 24px;
722
- .close {
723
- font-size: 22px;
724
- }
725
- }
726
- }
727
-
728
- /* 重置复选框的默认样式 */
729
- .checkbox {
730
- appearance: none; /* 去除默认外观 */
731
- -webkit-appearance: none;
732
- -moz-appearance: none;
733
- display: inline-block;
734
- width: 22px;
735
- height: 22px;
736
- border: 1px solid #c9cacd; /* 边框宽度和颜色 */
737
- border-radius: 50%; /* 使复选框成为圆形 */
738
- outline: none; /* 去除点击时的边框 */
739
- cursor: pointer;
740
- }
741
-
742
- /* 根据复选框的选中状态设置颜色 */
743
- .checkbox:checked {
744
- width: 22px;
745
- height: 22px;
746
- background: url('@/assets/xuanzhong@2x.png') no-repeat center;
747
- background-size: 110%;
748
- border: none;
749
- }
750
- </style>
1
+ <template>
2
+ <div class="InvoiceList">
3
+ <div class="selectAll">
4
+ <div class="left">
5
+ 已选 {{ selectedLength }}/{{ selectedLengthCount }}
6
+ </div>
7
+ <div class="right">
8
+ <span>全选</span>
9
+ <input
10
+ type="checkbox"
11
+ @change.self="changeSelectAll"
12
+ v-model="selectedAll"
13
+ @click.stop
14
+ class="checkbox" />
15
+ <!-- <Checkbox
16
+ @change.self="changeSelectAll"
17
+ v-model="selectedAll"
18
+ @click.stop
19
+ icon-size="22px"></Checkbox> -->
20
+ </div>
21
+ </div>
22
+
23
+ <div class="card">
24
+ <ul>
25
+ <li v-for="(item, index) in list" :key="index" @click="clickItem(item)">
26
+ <div class="li">
27
+ <div
28
+ class="item-card"
29
+ v-if="
30
+ item.taskStatus === 'finish' || item.taskStatus === 'repeat'
31
+ ">
32
+ <div class="details-btn" @click.stop="openDetails(item)">
33
+ 详情
34
+ </div>
35
+ <div
36
+ class="title"
37
+ :class="{ person: item.invoiceCompanyType === '公司' }">
38
+ {{ item.invoiceCompanyType }}
39
+ </div>
40
+ <div class="storeName">
41
+ <div class="name">
42
+ <div class="left">{{ item.sellerName }}</div>
43
+ <div class="right" v-if="item.taskStatus === 'repeat'">
44
+ 已收录
45
+ </div>
46
+ </div>
47
+ <div class="tags">
48
+ <span v-if="item.manualModify" class="manual">
49
+ 手工录入
50
+ </span>
51
+ <div v-else-if="item.invoiceExceptionInfo">
52
+ <span :class="setClass(item.invoiceExceptionInfo)">{{
53
+ const_invoiceExceptionInfo[item.invoiceExceptionInfo]
54
+ }}</span>
55
+ </div>
56
+ <span v-else :class="setClass(item.realStatus)">{{
57
+ const_realStatus[item.realStatus]
58
+ }}</span>
59
+ <span :class="setClass(item.invoiceStatus)">{{
60
+ const_invoiceStatus[item.invoiceStatus]
61
+ }}</span>
62
+ <!-- <span class="error">识别失败</span>
63
+ <span class="default">已使用</span>
64
+ <span class="success">识别成功</span> -->
65
+ </div>
66
+ <div class="InvoiceInfo">
67
+ <div class="leftinfo">
68
+ <div class="item-info">
69
+ <span class="label">发票金额</span>
70
+ <span class="value price"
71
+ >¥{{
72
+ item.priceTaxTotalFigure || item.noTaxAmountTotal
73
+ }}</span
74
+ >
75
+ </div>
76
+ <div class="item-info">
77
+ <span class="label">发票抬头</span>
78
+ <span class="value">{{ item.payerName }}</span>
79
+ </div>
80
+ <div class="item-info">
81
+ <span class="label">发票类型</span>
82
+ <span class="value">{{ item.description }}</span>
83
+ </div>
84
+ <div class="item-info">
85
+ <span class="label">发票时间</span>
86
+ <span class="value">{{ item.invoiceDate }}</span>
87
+ </div>
88
+ </div>
89
+ <div class="rightCheckbox">
90
+ <input
91
+ @click.stop
92
+ type="checkbox"
93
+ class="checkbox"
94
+ @change="changeItemCheckbox"
95
+ v-model="item.selected"
96
+ icon-size="22px" />
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <div
103
+ v-else-if="
104
+ item.taskStatus === 'ocr_doing' ||
105
+ item.taskStatus === 'ocr_success'
106
+ "
107
+ style="
108
+ background-color: #fff;
109
+ padding: 21px 0 15px 12px;
110
+ position: relative;
111
+ width: 100%;
112
+ ">
113
+ <Loading class="loading" color="#0094ff">识别中...</Loading>
114
+ <Skeleton :loading="true" title :row="6"></Skeleton>
115
+ </div>
116
+ <div class="err-card" v-else>
117
+ <div class="left-img" @click="openErrImg(item.fileUrlKey)">
118
+ <img :src="item.fileUrlKey" alt="" />
119
+ </div>
120
+ <div class="right-errText">
121
+ <div class="title"><span>识别失败</span></div>
122
+ <div class="content">
123
+ <div class="recognitionResult">
124
+ <p class="errContent">OCR识别失败</p>
125
+ <p>可以重新拍照识别,或手动添加信息导入</p>
126
+ </div>
127
+ <div>
128
+ <input
129
+ type="checkbox"
130
+ class="checkbox"
131
+ @click.stop
132
+ v-model="item.selected" />
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </li>
139
+ </ul>
140
+ </div>
141
+ <div class="btn-form">
142
+ <div class="operate">
143
+ <div class="delete">
144
+ <span @click="deleteSelectItem">移除</span>
145
+ </div>
146
+ <div class="right-btn">
147
+ <span class="add" @click="add">继续添加发票</span>
148
+ <span class="ok" :class="{ disabledColor: !submitBtn }" @click="ok"
149
+ >确定选择</span
150
+ >
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ <Popup v-model:show="showPopup" position="bottom" closeable>
156
+ <div class="boxPopup">
157
+ <div class="title">
158
+ <span>添加发票</span>
159
+ </div>
160
+ <div style="padding-bottom: 24px" @click="selectImg(1)">从相册中选择</div>
161
+ <div @click="selectImg(2)">拍照上传</div>
162
+ </div>
163
+ </Popup>
164
+ <Overlay
165
+ style="display: flex; justify-content: center; align-items: center"
166
+ :show="showLoading">
167
+ <Loading class="loading" color="#0094ff">上传中...</Loading>
168
+ </Overlay>
169
+ </template>
170
+ <!-- 异常发票, 已使用的, 使用中的, 识别失败 禁止提交 -->
171
+ <script setup lang="ts">
172
+ import 'vant/lib/index.css'
173
+ import { ref, onMounted, computed } from 'vue'
174
+ import {
175
+ Popup,
176
+ Skeleton,
177
+ Loading,
178
+ showToast,
179
+ showConfirmDialog,
180
+ Overlay,
181
+ } from 'vant'
182
+ import '@/utils/disableZoom'
183
+ import { selectPhoto, takePhoto } from '@/utils/upload'
184
+ import {
185
+ __deleteInvoice,
186
+ __getUploadInvoiceList,
187
+ __uploadInvoice,
188
+ } from '@/api/invoice'
189
+ import {
190
+ const_invoiceStatus,
191
+ const_realStatus,
192
+ const_taskStatus,
193
+ const_invoiceExceptionInfo,
194
+ setClass,
195
+ VerificationOfTruth,
196
+ } from './const'
197
+ import { showImagePreview } from 'vant'
198
+ const emit = defineEmits(['edit', 'ok'])
199
+ const { listId, multiple, catchList } = defineProps({
200
+ listId: {
201
+ required: false,
202
+ type: String,
203
+ },
204
+ multiple: {
205
+ required: true,
206
+ type: Boolean,
207
+ },
208
+ catchList: {},
209
+ })
210
+ const token: any = ref(sessionStorage.getItem('token'))
211
+ const showLoading = ref(false)
212
+ const showPopup = ref(false)
213
+ const list: any = ref<Array<any>>([])
214
+ if (catchList) {
215
+ list.value = catchList
216
+ }
217
+ const batchId = ref('')
218
+ const selectId = computed(() => {
219
+ return list.value
220
+ .filter((item: any) => item.selected)
221
+ .map((item: any) => item.taskId)
222
+ })
223
+
224
+ const getList = async () => {
225
+ return new Promise(async (resolve, reject) => {
226
+ const data: any = {}
227
+ data.batchId = listId
228
+ if (!data.batchId) return
229
+ try {
230
+ const res: any = await __getUploadInvoiceList(data, token.value)
231
+ if (res.code === 200) {
232
+ list.value = res.data.invoiceList.map((item: any) => {
233
+ let data = list.value.find((v: any) => v.taskId === item.taskId) || {}
234
+ return {
235
+ ...Object.assign(data, item),
236
+ }
237
+ })
238
+ batchId.value = res.data.batchId
239
+ if (list.value.length <= 0) {
240
+ selectedAll.value = false
241
+ } else {
242
+ changeItemCheckbox()
243
+ }
244
+ resolve(list.value)
245
+ }
246
+ } catch (error) {
247
+ console.log(error)
248
+ reject(error)
249
+ }
250
+ })
251
+ }
252
+ const clickItem = (row: any) => {
253
+ console.log(row.invoiceStatus)
254
+ if (row.invoiceStatus !== 'unused') {
255
+ return showToast('发票已被使用!')
256
+ }
257
+ row.selected = !row.selected
258
+ }
259
+
260
+ const openDetails = (row: any) => {
261
+ // if (!isEdit(row)) {
262
+ // return
263
+ // }
264
+ emit(
265
+ 'edit',
266
+ {
267
+ taskId: row.taskId,
268
+ batchId: batchId.value,
269
+ },
270
+ row,
271
+ list.value
272
+ )
273
+ }
274
+ const selectStatus = (item: any) => {
275
+ // 未使用&& (验真通过||无需验真|| 手工录入||验真异常) && 发票推断状态错误值为空 && 发票识别任务已完成
276
+ if (item.invoiceStatus === 'unused') {
277
+ if (item.manualModify) {
278
+ return true
279
+ }
280
+ if (!item.invoiceExceptionInfo) {
281
+ return true
282
+ }
283
+ return false
284
+ } else {
285
+ return false
286
+ }
287
+ // if (item.invoiceStatus==='unused') {
288
+ // return true
289
+ // } else if (item.manualModify) {
290
+ // return true
291
+ // } else if (!item.invoiceExceptionInfo) {
292
+ // return true
293
+ // } else {
294
+ // return false
295
+ // }
296
+ }
297
+ const selectedAll: any = ref(false)
298
+ const changeSelectAll = (v: any) => {
299
+ if (selectedAll.value) {
300
+ showToast('存在已使用或异常发票, 已自动过滤')
301
+ }
302
+ list.value.forEach((item: any) => {
303
+ item.selected = selectStatus(item) && selectedAll.value
304
+ })
305
+ }
306
+ const changeItemCheckbox = () => {
307
+ const status = list.value
308
+ .filter((item: any) => selectStatus(item))
309
+ .every((item: any) => item.selected)
310
+ selectedAll.value = status
311
+ }
312
+ const deleteSelectItem = () => {
313
+ if (selectId.value.length <= 0) {
314
+ showToast({ type: 'text', message: '请先选择发票' })
315
+ return
316
+ }
317
+ showConfirmDialog({
318
+ title: '提醒',
319
+ message: '请确认是否需要移除选中项,移除后不可恢复!',
320
+ })
321
+ .then(async () => {
322
+ var urlencoded = new URLSearchParams()
323
+ urlencoded.append('batchId', batchId.value)
324
+ const idsStr = selectId.value.join(',')
325
+ urlencoded.append('taskIds', idsStr)
326
+ const res: any = await __deleteInvoice(urlencoded, token.value)
327
+ if (res.code === 200) {
328
+ showToast({ type: 'success', message: '移除成功' })
329
+ getList()
330
+ }
331
+ })
332
+ .catch(() => {
333
+ // on cancel
334
+ })
335
+ }
336
+ const add = () => {
337
+ showPopup.value = true
338
+ }
339
+
340
+ const selectImg = async (type: number) => {
341
+ let fd: FormData
342
+ if (type === 1) {
343
+ fd = await selectPhoto(multiple)
344
+ } else {
345
+ fd = await takePhoto()
346
+ }
347
+ fd.append('batchId', batchId.value)
348
+ showLoading.value = true
349
+ showPopup.value = false
350
+ try {
351
+ const res: any = await __uploadInvoice(fd)
352
+ if (res.code === 200) {
353
+ showToast({
354
+ type: 'success',
355
+ message: '上传成功',
356
+ })
357
+ setTimeGetList()
358
+ }
359
+ } catch (error) {
360
+ console.log(error)
361
+ setTimeGetList()
362
+ }
363
+ showLoading.value = false
364
+ }
365
+ const submitBtn = computed(() => {
366
+ const flag = list.value
367
+ .filter((item: any) => item.selected)
368
+ .every((item: any) => {
369
+ return selectStatus(item)
370
+ })
371
+ return flag && list.value.filter((item: any) => item.selected).length > 0
372
+ })
373
+ const selectedLength = computed(() => {
374
+ return list.value.filter((item: any) => item.selected && selectStatus(item))
375
+ .length
376
+ })
377
+ const selectedLengthCount = computed(() => {
378
+ return list.value.filter((item: any) => selectStatus(item)).length
379
+ })
380
+ const openErrImg = (url: any) => {
381
+ showImagePreview([url])
382
+ }
383
+ const ok = () => {
384
+ if (selectId.value.length <= 0) {
385
+ showToast({ type: 'text', message: '请先选择发票' })
386
+ return
387
+ }
388
+ if (!submitBtn.value) {
389
+ showToast({
390
+ type: 'text',
391
+ message: '存在异常发票,无法提交, 请先去修改发票金额',
392
+ })
393
+ return
394
+ }
395
+ // showDeleteBtn.value = false
396
+ const selectData = list.value
397
+ .filter((item: any) => item.selected)
398
+ .map((item: any) => {
399
+ VerificationOfTruth
400
+ let data = VerificationOfTruth(item)
401
+ let obj: any = { ...item, ...data }
402
+ return obj
403
+ })
404
+ emit('ok', selectData, batchId.value)
405
+ }
406
+ const timeID: any = ref(null)
407
+ const setTimeGetList = () => {
408
+ getList()
409
+ .then((res: any) => {
410
+ const flag = res.some(
411
+ (item: any) =>
412
+ item.taskStatus === 'ocr_doing' || item.taskStatus === 'ocr_success'
413
+ )
414
+ if (!flag) {
415
+ return
416
+ }
417
+ setTimeout(setTimeGetList, 2000)
418
+ })
419
+ .catch((err) => {
420
+ setTimeout(setTimeGetList, 2000)
421
+ })
422
+ }
423
+ onMounted(() => {
424
+ setTimeGetList()
425
+ })
426
+ </script>
427
+ <style lang="scss" scoped>
428
+ .disabledColor {
429
+ opacity: 0.6; /* 降低不透明度以表示禁用状态 */
430
+ cursor: not-allowed; /* 鼠标指针样式设置为禁用 */
431
+ }
432
+ .InvoiceList {
433
+ padding-bottom: 100px;
434
+ position: relative;
435
+ width: 100%;
436
+ min-height: 100vh;
437
+ background-color: #f3f4f6;
438
+ .selectAll {
439
+ background-color: #f3f4f6;
440
+ position: sticky;
441
+ top: 0;
442
+ z-index: 999;
443
+ padding: 12px;
444
+ display: flex;
445
+ justify-content: space-between;
446
+ .right {
447
+ display: flex;
448
+ gap: 5px;
449
+ padding-right: 12px;
450
+ }
451
+ }
452
+ .card {
453
+ width: 100%;
454
+ ul {
455
+ display: flex;
456
+ flex-direction: column;
457
+ gap: 12px;
458
+ .li {
459
+ width: 100%;
460
+ border-radius: 4px;
461
+ padding: 0 12px;
462
+ display: flex;
463
+ align-items: center;
464
+ gap: 12px;
465
+ overflow: hidden;
466
+
467
+ .item-card {
468
+ flex: 1;
469
+ position: relative;
470
+
471
+ background-color: #fff;
472
+ padding-bottom: 20px;
473
+ min-width: 100%;
474
+ .details-btn {
475
+ position: absolute;
476
+ bottom: 12px;
477
+ right: 9px;
478
+ border: 1px solid #dcdee0;
479
+ padding: 4px 16px;
480
+ font-size: 14px;
481
+ color: #333;
482
+ border-radius: 2px;
483
+ }
484
+ div.person {
485
+ background-color: #ff8b26;
486
+ }
487
+ .title {
488
+ color: #fff;
489
+ display: flex;
490
+ width: max-content;
491
+ text-align: center;
492
+ align-items: center;
493
+ font-size: 12px;
494
+ border-radius: 4px 0px 4px 0px;
495
+ padding: 2px 4px;
496
+ background-color: #266fe8;
497
+ margin-bottom: 5px;
498
+ }
499
+
500
+ .storeName {
501
+ padding: 0 12px;
502
+ .name {
503
+ display: flex;
504
+ justify-content: space-between;
505
+ width: 100%;
506
+ align-items: center;
507
+ .left {
508
+ font-size: 15px;
509
+ color: #333333;
510
+ font-weight: 700;
511
+ }
512
+ .right {
513
+ font-size: 12px;
514
+ color: #ee0a24;
515
+ }
516
+ }
517
+ .tags {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 7px;
521
+ margin-top: 8px;
522
+ margin-bottom: 15px;
523
+ .manual {
524
+ background-color: #facd91;
525
+ color: #c77458;
526
+ }
527
+ span {
528
+ padding: 2px 4px;
529
+ font-size: 12px;
530
+ font-weight: 400;
531
+ border-radius: 2px;
532
+ &.ok {
533
+ background-color: rgba(38, 111, 232, 0.1);
534
+ color: #266fe8;
535
+ }
536
+ &.warning {
537
+ background-color: rgba(255, 251, 230, 1);
538
+ color: #ff8f26;
539
+ }
540
+ &.error {
541
+ background-color: rgba(255, 242, 240, 1);
542
+ color: #ff4d4f;
543
+ }
544
+ &.default {
545
+ background-color: rgba(153, 153, 153, 0.2);
546
+ color: #666666;
547
+ }
548
+ &.success {
549
+ background-color: #e6ffdd;
550
+ color: #2ba500;
551
+
552
+ opacity: 0.68;
553
+ }
554
+ }
555
+ }
556
+ .InvoiceInfo {
557
+ display: flex;
558
+ justify-content: space-between;
559
+ .leftinfo {
560
+ .item-info {
561
+ display: flex;
562
+ align-items: center;
563
+ gap: 16px;
564
+ margin-bottom: 8px;
565
+ &:last-child {
566
+ margin-bottom: 0;
567
+ }
568
+ span {
569
+ font-size: 14px;
570
+ &.label {
571
+ color: #999999;
572
+ }
573
+ &.value {
574
+ color: #333333;
575
+ }
576
+ &.price {
577
+ color: #266fe8;
578
+ font-weight: 700;
579
+ }
580
+ }
581
+ }
582
+ }
583
+ .rightCheckbox {
584
+ height: max-content;
585
+ }
586
+ }
587
+ }
588
+ }
589
+
590
+ .err-card {
591
+ background-color: #fff;
592
+ width: 100%;
593
+ display: flex;
594
+ gap: 9px;
595
+ .left-img {
596
+ width: 113px;
597
+ height: 79px;
598
+ margin: 10px 0;
599
+ margin-left: 10px;
600
+ flex-shrink: 0;
601
+ img {
602
+ width: 100%;
603
+ height: 100%;
604
+ }
605
+ }
606
+ .right-errText {
607
+ .title {
608
+ color: #fff;
609
+ display: flex;
610
+ text-align: right;
611
+ align-items: center;
612
+ justify-content: flex-end;
613
+ margin-bottom: 5px;
614
+ span {
615
+ background-color: rgba(238, 10, 36, 0.1);
616
+ color: #ee0a24;
617
+ padding: 2px 4px;
618
+ font-size: 12px;
619
+ border-radius: 0 4px 0 4px;
620
+ }
621
+ }
622
+ .content {
623
+ display: flex;
624
+ padding-right: 12px;
625
+ align-items: center;
626
+ font-size: 12px;
627
+ gap: 18px;
628
+ .recognitionResult {
629
+ gap: 5px;
630
+ display: flex;
631
+ flex-direction: column;
632
+ justify-content: space-between;
633
+ p {
634
+ color: #666666;
635
+ }
636
+ }
637
+ }
638
+ }
639
+ }
640
+ }
641
+ .loading {
642
+ position: absolute;
643
+ left: 50%;
644
+ top: 50%;
645
+ transform: translate(-50%, -50%);
646
+ z-index: 100;
647
+ }
648
+ }
649
+ }
650
+
651
+ .btn-form {
652
+ z-index: 999;
653
+ position: fixed;
654
+ bottom: 0;
655
+ left: 0;
656
+ background-color: #fff;
657
+ height: 90px;
658
+ width: 100%;
659
+
660
+ .operate {
661
+ width: 100%;
662
+ height: 100%;
663
+ display: flex;
664
+ gap: 12px;
665
+ padding: 12px;
666
+ padding-bottom: 34px;
667
+ font-size: 15px;
668
+ span {
669
+ flex: 1;
670
+ display: flex;
671
+ align-items: center;
672
+ font-weight: 500;
673
+ font-size: 15px;
674
+ justify-content: center;
675
+ }
676
+ .delete {
677
+ border: 1px solid #e8e8e8;
678
+ display: flex;
679
+ align-items: center;
680
+ padding: 0 44px;
681
+ }
682
+ .right-btn {
683
+ display: flex;
684
+ flex: 1;
685
+ span {
686
+ color: #fff;
687
+ }
688
+ .add {
689
+ background-color: #ff8b26;
690
+ border-radius: 4px 0px 0px 4px;
691
+ }
692
+ .ok {
693
+ border-radius: 0px 4px 4px 0px;
694
+ background-color: #266fe8;
695
+ }
696
+ }
697
+ }
698
+ }
699
+ }
700
+ .boxPopup {
701
+ height: 100%;
702
+ display: flex;
703
+ flex-direction: column;
704
+ // align-items: center;
705
+ // justify-content: space-around;
706
+ padding: 16px 23px 34px;
707
+ div {
708
+ width: 100%;
709
+ font-size: 14px;
710
+ display: flex;
711
+ justify-content: center;
712
+ align-items: center;
713
+ }
714
+ .title {
715
+ width: 100%;
716
+ display: flex;
717
+ justify-content: center;
718
+ // padding: 24px 0;
719
+ font-size: 16px;
720
+ font-weight: 700;
721
+ padding-bottom: 24px;
722
+ .close {
723
+ font-size: 22px;
724
+ }
725
+ }
726
+ }
727
+
728
+ /* 重置复选框的默认样式 */
729
+ .checkbox {
730
+ appearance: none; /* 去除默认外观 */
731
+ -webkit-appearance: none;
732
+ -moz-appearance: none;
733
+ display: inline-block;
734
+ width: 22px;
735
+ height: 22px;
736
+ border: 1px solid #c9cacd; /* 边框宽度和颜色 */
737
+ border-radius: 50%; /* 使复选框成为圆形 */
738
+ outline: none; /* 去除点击时的边框 */
739
+ cursor: pointer;
740
+ }
741
+
742
+ /* 根据复选框的选中状态设置颜色 */
743
+ .checkbox:checked {
744
+ width: 22px;
745
+ height: 22px;
746
+ background: url('@/assets/xuanzhong@2x.png') no-repeat center;
747
+ background-size: 110%;
748
+ border: none;
749
+ }
750
+ </style>