| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030 |
- <template>
- <QueryTableModal
- :open="open"
- :title="title"
- :width="width"
- :z-index="zIndex"
- :loading="loading"
- :filters="filters"
- :filter-schema="visibleFilterSchema"
- :columns="visibleColumns"
- :action-column="actionColumn"
- :data-source="tableData"
- :row-selection="rowSelection"
- :pagination="pagination"
- :total-pages="totalPages"
- :page-size-options="pageSizeOptions"
- :show-table-setting="true"
- :table-setting="{ size: true, setting: true, redo: false, fullScreen: false }"
- :can-fullscreen="true"
- wrap-class-name="trans-record-modal"
- @update:open="(v) => emit('update:open', v)"
- @search="handleSearch"
- @reset="handleReset"
- @prev-page="handlePrevPage"
- @next-page="handleNextPage"
- @page-size-change="handlePageSizeChange"
- @page-change="handlePageChange"
- @sort-change="handleSortChange"
- >
- <template v-if="traceable" #searchActions>
- <div class="trans-record-modal__trace-bar">
- <span class="trans-record-modal__trace-label">追溯方向:</span>
- <RadioGroup
- :value="traceDirection"
- @update:value="(value) => emit('update:traceDirection', value as TraceDirection)"
- >
- <Radio value="forward">向前</Radio>
- <Radio value="backward">向后</Radio>
- </RadioGroup>
- <Button type="primary" :loading="traceLoading" :disabled="!selectedRows.length" @click="handleTrace">
- 追溯
- </Button>
- </div>
- </template>
- </QueryTableModal>
- <TransRecordModal
- v-if="showDetailAction"
- v-model:open="detailModalOpen"
- title="交易记录详情"
- :z-index="detailModalZIndex"
- :base-query="detailBaseQuery"
- @jump="handleDetailModalJump"
- />
- </template>
- <script lang="ts" setup>
- import { computed, nextTick, reactive, ref, watch, h } from 'vue';
- import { Button, Radio } from 'ant-design-vue';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- import {
- getOpenOfficeFileErrorMessage,
- isOpenOfficeFileSuccess,
- openOfficeFileById,
- resolveOfficeFileId,
- } from '@jeesite/core/utils/openOfficeFile';
- import QueryTableModal from '@jeesite/case/views/dm/components/QueryTableModal.vue';
- import componentSetting from '@jeesite/core/settings/componentSetting';
- import {
- COMPARE_OPERATOR_OPTIONS,
- DEFAULT_COMPARE_OPERATOR,
- normalizeCompareOperator,
- } from '@jeesite/core/utils/compareOperator';
- import { maskCertNo } from '@jeesite/core/utils/sensitive';
- import { useTimeSeriesJump } from '@jeesite/otg/hooks/useTimeSeriesJump';
- import { getTransRecord } from '@jeesite/trans/api/transApi';
- import type { TransRecord } from '@jeesite/trans/api/types/dto';
- import type { TransRecordQuery } from '@jeesite/trans/api/types/query';
- import type { CompareOperator } from '@jeesite/core/utils/compareOperator';
- type TraceDirection = 'forward' | 'backward';
- type TransRecordModalQuery = Partial<TransRecordQuery> & Record<string, any>;
- type CompareOperatorOption = { label: string; value: CompareOperator };
- interface TracePayload {
- direction: TraceDirection;
- records: TransRecord[];
- }
- const RadioGroup = Radio.Group;
- const props = withDefaults(
- defineProps<{
- open: boolean;
- title?: string;
- width?: number;
- zIndex?: number;
- baseQuery?: TransRecordModalQuery;
- fetchApi?: (query: TransRecordModalQuery) => Promise<any>;
- hideTechnicalIdColumns?: boolean;
- filterFields?: string[];
- transAmountOperatorOptions?: CompareOperatorOption[];
- traceable?: boolean;
- traceDirection?: TraceDirection;
- traceLoading?: boolean;
- showDetailAction?: boolean;
- }>(),
- {
- title: '交易记录',
- width: 1240,
- zIndex: undefined,
- baseQuery: () => ({}),
- hideTechnicalIdColumns: true,
- filterFields: () => [
- 'personName',
- 'personCardNo',
- 'otherName',
- 'otherCardNo',
- 'debitCreditFlag',
- 'startDate',
- 'endDate',
- 'transAmount',
- ],
- transAmountOperatorOptions: () => [...COMPARE_OPERATOR_OPTIONS],
- traceable: false,
- traceDirection: 'backward',
- traceLoading: false,
- showDetailAction: false,
- },
- );
- const emit = defineEmits<{
- (e: 'update:open', value: boolean): void;
- (e: 'update:traceDirection', value: TraceDirection): void;
- (e: 'trace', payload: TracePayload): void;
- (e: 'jump'): void;
- (e: 'error', message: string): void;
- }>();
- const loading = ref(false);
- const tableData = ref<any[]>([]);
- const selectedRowKeys = ref<Array<string | number>>([]);
- const selectedRows = ref<TransRecord[]>([]);
- const detailModalOpen = ref(false);
- const detailBaseQuery = ref<TransRecordModalQuery>({});
- const { jumping, jumpToBehaviorSpacetime } = useTimeSeriesJump();
- const { createMessage } = useMessage();
- let fetchRequestSequence = 0;
- let activeFetchQueryKey = '';
- let pendingFetchRequested = false;
- let pendingFetchQueryKey = '';
- const filters = reactive({
- personName: '',
- personCardNo: '',
- otherName: '',
- otherCardNo: '',
- debitCreditFlag: '',
- startDate: '',
- endDate: '',
- transAmount: '',
- transAmountSymbol: DEFAULT_COMPARE_OPERATOR,
- orderKey: '',
- sort: '' as '' | 'ASC' | 'DESC',
- });
- const pagination = reactive({
- page: 1,
- limit: componentSetting.table.defaultPageSize,
- total: 0,
- });
- const totalPages = computed(() => {
- if (!pagination.total) return 1;
- return Math.max(1, Math.ceil(pagination.total / pagination.limit));
- });
- const pageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
- label: value,
- value,
- }));
- const compareOperatorOptions = computed(() =>
- props.transAmountOperatorOptions?.length ? props.transAmountOperatorOptions : [...COMPARE_OPERATOR_OPTIONS],
- );
- const filterSchema = [
- { field: 'personName', label: '本方姓名', type: 'input', placeholder: '请输入本方姓名', width: 180 },
- { field: 'personCardNo', label: '本方卡号', type: 'input', placeholder: '请输入本方卡号', width: 180 },
- { field: 'otherName', label: '对方名称', type: 'input', placeholder: '请输入对方名称', width: 180 },
- { field: 'otherCardNo', label: '对方卡号', type: 'input', placeholder: '请输入对方卡号', width: 180 },
- {
- field: 'debitCreditFlag',
- label: '借贷标志',
- type: 'select',
- width: 160,
- options: [
- { label: '全部', value: '' },
- { label: '收入', value: '收入' },
- { label: '支出', value: '支出' },
- ],
- },
- { field: 'startDate', label: '开始日期', type: 'date', width: 170 },
- { field: 'endDate', label: '结束日期', type: 'date', width: 170 },
- {
- field: 'transDateRange',
- label: '交易日期',
- type: 'date-range',
- startField: 'startDate',
- endField: 'endDate',
- width: 320,
- maxWidth: 360,
- rangePlaceholder: ['开始日期', '结束日期'] as [string, string],
- valueFormat: 'YYYY-MM-DD',
- },
- {
- field: 'transAmount',
- label: '交易金额',
- type: 'compare-input',
- operatorField: 'transAmountSymbol',
- options: compareOperatorOptions.value,
- operatorWidth: 72,
- inputWidth: 180,
- inputMaxWidth: 180,
- placeholder: '请输入交易金额',
- },
- ] as const;
- const visibleFilterSchema = computed(() => {
- const visibleFields = new Set(props.filterFields);
- const showDateRange = visibleFields.has('startDate') && visibleFields.has('endDate');
- return filterSchema.filter((item) => {
- if (item.field === 'transDateRange') return showDateRange;
- if (item.field === 'startDate' || item.field === 'endDate')
- return !showDateRange && visibleFields.has(item.field);
- return visibleFields.has(item.field);
- });
- });
- const technicalIdColumnKeys = ['recordId', 'fileId', 'sheetId', 'personLibId'];
- const columns = [
- { title: '本方姓名', dataIndex: 'personName', key: 'personName', width: 120 },
- { title: '本方卡号', dataIndex: 'personCardNo', key: 'personCardNo', width: 180 },
- { title: '对方名称', dataIndex: 'otherName', key: 'otherName', width: 140 },
- { title: '对方卡号', dataIndex: 'otherCardNo', key: 'otherCardNo', width: 180 },
- { title: '本方账号', dataIndex: 'personAccount', key: 'personAccount', width: 180 },
- { title: '对方账号', dataIndex: 'otherAccount', key: 'otherAccount', width: 180 },
- { title: '借贷标志', dataIndex: 'debitCreditFlag', key: 'debitCreditFlag', width: 100 },
- { title: '交易日期时间', dataIndex: 'transDateTime', key: 'transDateTime', width: 180, sorter: true },
- { title: '交易金额', dataIndex: 'transAmount', key: 'transAmount', width: 120, sorter: true },
- { title: '交易余额', dataIndex: 'transBalance', key: 'transBalance', width: 120, sorter: true },
- {
- title: '银行名称',
- dataIndex: 'bankName',
- key: 'bankName',
- width: 160,
- customRender: ({ text }: { text: string }) => h('span', { style: { color: '#1890ff', fontWeight: 500 } }, text),
- },
- { title: '交易类型', dataIndex: 'transType', key: 'transType', width: 120 },
- { title: '交易摘要', dataIndex: 'transSummary', key: 'transSummary', width: 220 },
- { title: '对方余额', dataIndex: 'otherBalance', key: 'otherBalance', width: 120, sorter: true },
- { title: '币种', dataIndex: 'currency', key: 'currency', width: 90 },
- { title: '本方证件号码', dataIndex: 'personCertNo', key: 'personCertNo', width: 180 },
- { title: '对方证件号码', dataIndex: 'otherCertNo', key: 'otherCertNo', width: 180 },
- { title: '交易流水号', dataIndex: 'transSerialNo', key: 'transSerialNo', width: 180 },
- { title: '交易摘要', dataIndex: 'transSummary', key: 'transSummary', width: 220 },
- { title: '交易网点', dataIndex: 'branchName', key: 'branchName', width: 220 },
- { title: '网点代码', dataIndex: 'branchCode', key: 'branchCode', width: 140 },
- { title: '对方开户行', dataIndex: 'otherAccountBank', key: 'otherAccountBank', width: 180 },
- { title: '现金标志', dataIndex: 'cashFlag', key: 'cashFlag', width: 100 },
- { title: '终端号', dataIndex: 'terminalNo', key: 'terminalNo', width: 140 },
- { title: '交易地点', dataIndex: 'transLocation', key: 'transLocation', width: 180 },
- { title: '商户名称', dataIndex: 'merchantName', key: 'merchantName', width: 180 },
- { title: '商户号', dataIndex: 'merchantNo', key: 'merchantNo', width: 160 },
- { title: 'IP地址', dataIndex: 'personIpAddress', key: 'personIpAddress', width: 150 },
- { title: 'MAC地址', dataIndex: 'personMacAddress', key: 'personMacAddress', width: 180 },
- { title: '柜员号', dataIndex: 'transClerkNo', key: 'transClerkNo', width: 140 },
- { title: '交易渠道', dataIndex: 'transChannel', key: 'transChannel', width: 140 },
- { title: '备注', dataIndex: 'remark', key: 'remark', width: 180 },
- ];
- const visibleColumns = computed(() => {
- if (!props.hideTechnicalIdColumns) return columns;
- return columns.filter((column) => !technicalIdColumnKeys.includes(String(column.key || column.dataIndex || '')));
- });
- const normalizeActionRecordId = (value: unknown) => {
- const text = String(value ?? '').trim();
- if (!text || text === '-' || text === 'null' || text === 'undefined') return undefined;
- return text;
- };
- const buildJumpQuery = (record?: TransRecord) => {
- const id = normalizeActionRecordId(record?.id);
- const personName = String(record?.personName || props.baseQuery?.personName || '').trim();
- return {
- hasLocal: 'N',
- tableName: 'trans_record',
- tableNameEn: 'trans_record',
- tableNameCn: '交易记录',
- id,
- personName: personName || undefined,
- personNames: personName ? [personName] : undefined,
- };
- };
- const handleJump = async (record?: TransRecord) => {
- await jumpToBehaviorSpacetime(buildJumpQuery(record), {
- onBeforeNavigate: () => {
- detailModalOpen.value = false;
- detailBaseQuery.value = {};
- emit('jump');
- emit('update:open', false);
- },
- });
- };
- const handleOpenFile = async (record?: TransRecord) => {
- const fileId = resolveOfficeFileId(record);
- if (!fileId) {
- createMessage.warning('未获取到文件ID,暂时无法打开文件');
- return;
- }
- try {
- const response = await openOfficeFileById(fileId);
- if (!isOpenOfficeFileSuccess(response)) {
- createMessage.warning((response as any)?.message || '打开文件失败');
- }
- } catch (error: any) {
- createMessage.error(getOpenOfficeFileErrorMessage(error, '打开文件失败'));
- }
- };
- const handleViewDetail = (record?: TransRecord) => {
- const detailId = normalizeActionRecordId(record?.id);
- if (!detailId) {
- createMessage.warning('未获取到交易记录ID,暂时无法查看详情');
- return;
- }
- detailBaseQuery.value = { id: detailId };
- detailModalOpen.value = true;
- };
- const detailModalZIndex = computed(() => {
- const baseZIndex = Number(props.zIndex);
- return Number.isFinite(baseZIndex) ? baseZIndex + 40 : 1300;
- });
- const handleDetailModalJump = () => {
- detailModalOpen.value = false;
- detailBaseQuery.value = {};
- emit('jump');
- emit('update:open', false);
- };
- const actionColumn = computed(() => ({
- title: '操作',
- dataIndex: 'action',
- key: 'action',
- width: props.showDetailAction ? 220 : 160,
- fixed: 'right',
- align: 'center',
- actions: (record: { raw?: TransRecord }) => {
- const rawRecord = record.raw;
- const query = buildJumpQuery(rawRecord);
- const fileId = resolveOfficeFileId(rawRecord);
- const detailId = normalizeActionRecordId(rawRecord?.id);
- const actions = [
- props.showDetailAction
- ? {
- label: '查看详情',
- disabled: !detailId,
- onClick: () => handleViewDetail(rawRecord),
- }
- : null,
- {
- label: '打开文件',
- disabled: !fileId,
- onClick: () => void handleOpenFile(rawRecord),
- },
- {
- label: '跳转',
- disabled: jumping.value || !query.id,
- onClick: () => void handleJump(rawRecord),
- },
- ];
- return actions.filter(Boolean);
- },
- }));
- const rowSelection = computed(() => {
- if (!props.traceable) return undefined;
- return {
- type: 'checkbox',
- selectedRowKeys: selectedRowKeys.value,
- columnWidth: 50,
- onChange: (keys: Array<string | number>, rows: Array<{ raw?: TransRecord }>) => {
- selectedRowKeys.value = keys;
- selectedRows.value = rows.map((row) => row.raw).filter((item): item is TransRecord => Boolean(item));
- },
- };
- });
- const resolveFiniteNumber = (...values: unknown[]) => {
- for (const value of values) {
- const numeric = Number(value);
- if (Number.isFinite(numeric)) return numeric;
- }
- return undefined;
- };
- const normalizeList = (resp: any): TransRecord[] => {
- const containers = [resp, resp?.data, resp?.page, resp?.result, resp?.data?.result, resp?.data?.data];
- for (const source of containers) {
- if (Array.isArray(source)) return source;
- const candidates = [source?.records, source?.list, source?.rows, source?.data];
- const matched = candidates.find((item) => Array.isArray(item));
- if (Array.isArray(matched)) return matched;
- }
- return [];
- };
- const normalizeTotal = (resp: any, listLen: number) => {
- const total = resolveFiniteNumber(
- resp?.total,
- resp?.count,
- resp?.data?.total,
- resp?.data?.count,
- resp?.page?.total,
- resp?.page?.count,
- resp?.result?.total,
- resp?.result?.count,
- resp?.data?.result?.total,
- resp?.data?.result?.count,
- resp?.data?.data?.total,
- resp?.data?.data?.count,
- );
- return total ?? listLen;
- };
- const normalizeCurrent = (resp: any, fallback: number) => {
- const current = resolveFiniteNumber(
- resp?.current,
- resp?.page,
- resp?.pageNo,
- resp?.pageNum,
- resp?.data?.current,
- resp?.data?.page,
- resp?.data?.pageNo,
- resp?.data?.pageNum,
- resp?.page?.current,
- resp?.page?.page,
- resp?.page?.pageNo,
- resp?.page?.pageNum,
- resp?.result?.current,
- resp?.result?.page,
- resp?.result?.pageNo,
- resp?.result?.pageNum,
- resp?.data?.result?.current,
- resp?.data?.result?.page,
- resp?.data?.result?.pageNo,
- resp?.data?.result?.pageNum,
- resp?.data?.data?.current,
- resp?.data?.data?.page,
- resp?.data?.data?.pageNo,
- resp?.data?.data?.pageNum,
- );
- return current && current > 0 ? current : fallback;
- };
- const normalizeSize = (resp: any, fallback: number) => {
- const size = resolveFiniteNumber(
- resp?.size,
- resp?.limit,
- resp?.pageSize,
- resp?.data?.size,
- resp?.data?.limit,
- resp?.data?.pageSize,
- resp?.page?.size,
- resp?.page?.limit,
- resp?.page?.pageSize,
- resp?.result?.size,
- resp?.result?.limit,
- resp?.result?.pageSize,
- resp?.data?.result?.size,
- resp?.data?.result?.limit,
- resp?.data?.result?.pageSize,
- resp?.data?.data?.size,
- resp?.data?.data?.limit,
- resp?.data?.data?.pageSize,
- );
- return size && size > 0 ? size : fallback;
- };
- const fallbackValue = (value: string | number | null | undefined) => {
- if (value === 0) return '0';
- if (value === null || value === undefined || value === '') return '-';
- return String(value);
- };
- const normalizeQueryId = (value: unknown) => {
- const text = String(value ?? '').trim();
- return text || undefined;
- };
- const normalizeQueryIds = (value: unknown): string[] | undefined => {
- if (Array.isArray(value)) {
- const ids = value.map((item) => String(item ?? '').trim()).filter(Boolean);
- return ids.length ? ids : undefined;
- }
- const text = String(value ?? '').trim();
- if (!text) return undefined;
- const ids = text
- .split(',')
- .map((item) => item.trim())
- .filter(Boolean);
- return ids.length ? ids : undefined;
- };
- const buildQuery = (): TransRecordModalQuery => {
- const baseQuery = { ...(props.baseQuery || {}) };
- delete baseQuery.keyword;
- const transAmountText = String(filters.transAmount || '').trim();
- return {
- ...baseQuery,
- page: pagination.page,
- limit: pagination.limit,
- personName: filters.personName || props.baseQuery?.personName,
- personCardNo: filters.personCardNo || props.baseQuery?.personCardNo,
- otherName: filters.otherName || props.baseQuery?.otherName,
- otherCardNo: filters.otherCardNo || props.baseQuery?.otherCardNo,
- debitCreditFlag: filters.debitCreditFlag || props.baseQuery?.debitCreditFlag,
- startDate: filters.startDate || props.baseQuery?.startDate,
- endDate: filters.endDate || props.baseQuery?.endDate,
- transAmount: transAmountText || props.baseQuery?.transAmount,
- transAmountSymbol: transAmountText
- ? normalizeCompareOperator(filters.transAmountSymbol, DEFAULT_COMPARE_OPERATOR)
- : props.baseQuery?.transAmountSymbol,
- orderKey: filters.orderKey || undefined,
- sort: filters.sort || undefined,
- id: normalizeQueryId(props.baseQuery?.id),
- ids: normalizeQueryIds(props.baseQuery?.ids),
- };
- };
- const normalizeQueryForKey = (value: unknown): unknown => {
- if (Array.isArray(value)) {
- return value.map((item) => normalizeQueryForKey(item)).sort();
- }
- if (value && typeof value === 'object') {
- return Object.keys(value as Record<string, unknown>)
- .sort()
- .reduce<Record<string, unknown>>((result, key) => {
- const item = (value as Record<string, unknown>)[key];
- if (item !== undefined && item !== '') {
- result[key] = normalizeQueryForKey(item);
- }
- return result;
- }, {});
- }
- return value;
- };
- const buildQueryKey = (query: TransRecordModalQuery) => {
- return JSON.stringify(normalizeQueryForKey(query));
- };
- const mapRow = (item: TransRecord, index: number) => ({
- rowKey: item.id ?? `${pagination.page}-${index}`,
- id: (pagination.page - 1) * pagination.limit + index + 1,
- recordId: fallbackValue(item.id),
- fileId: fallbackValue(item.fileId),
- sheetId: fallbackValue(item.sheetId),
- bankName: fallbackValue(item.bankName),
- personName: fallbackValue(item.personName),
- personCertNo: maskCertNo(item.personCertNo),
- personAccount: fallbackValue(item.personAccount),
- personCardNo: fallbackValue(item.personCardNo),
- otherName: fallbackValue(item.otherName),
- otherCertNo: maskCertNo(item.otherCertNo),
- otherAccount: fallbackValue(item.otherAccount),
- otherCardNo: fallbackValue(item.otherCardNo),
- transType: fallbackValue(item.transType),
- debitCreditFlag: fallbackValue(item.debitCreditFlag),
- currency: fallbackValue(item.currency),
- transAmount: fallbackValue(item.transAmount),
- transBalance: fallbackValue(item.transBalance),
- transDateTime: fallbackValue(item.transDateTime),
- transDate: fallbackValue(item.transDate),
- transTime: fallbackValue(item.transTime),
- transSerialNo: fallbackValue(item.transSerialNo),
- otherBalance: fallbackValue(item.otherBalance),
- otherAccountBank: fallbackValue(item.otherAccountBank),
- transSummary: fallbackValue(item.transSummary),
- branchName: fallbackValue(item.branchName),
- branchCode: fallbackValue(item.branchCode),
- cashFlag: fallbackValue(item.cashFlag),
- terminalNo: fallbackValue(item.terminalNo),
- transLocation: fallbackValue(item.transLocation),
- merchantName: fallbackValue(item.merchantName),
- merchantNo: fallbackValue(item.merchantNo),
- personIpAddress: fallbackValue(item.personIpAddress),
- personMacAddress: fallbackValue(item.personMacAddress),
- transClerkNo: fallbackValue(item.transClerkNo),
- transChannel: fallbackValue(item.transChannel),
- remark: fallbackValue(item.remark),
- personLibId: fallbackValue(item.personLibId),
- ext1: fallbackValue(item.ext1),
- festivalName: fallbackValue(item.festivalName),
- raw: item,
- });
- const clearTraceSelection = () => {
- selectedRowKeys.value = [];
- selectedRows.value = [];
- };
- const syncFiltersFromBaseQuery = () => {
- filters.personName = props.baseQuery?.personName || '';
- filters.personCardNo = props.baseQuery?.personCardNo || '';
- filters.otherName = props.baseQuery?.otherName || '';
- filters.otherCardNo = props.baseQuery?.otherCardNo || '';
- filters.debitCreditFlag = props.baseQuery?.debitCreditFlag || '';
- filters.startDate = props.baseQuery?.startDate || '';
- filters.endDate = props.baseQuery?.endDate || '';
- filters.transAmount = props.baseQuery?.transAmount ? String(props.baseQuery.transAmount) : '';
- filters.transAmountSymbol = normalizeCompareOperator(props.baseQuery?.transAmountSymbol, DEFAULT_COMPARE_OPERATOR);
- filters.orderKey = '';
- filters.sort = '';
- };
- const fetchTable = async () => {
- const query = buildQuery();
- const queryKey = buildQueryKey(query);
- if (loading.value) {
- if (queryKey === activeFetchQueryKey || queryKey === pendingFetchQueryKey) return;
- pendingFetchRequested = true;
- pendingFetchQueryKey = queryKey;
- fetchRequestSequence += 1;
- return;
- }
- const requestSequence = ++fetchRequestSequence;
- activeFetchQueryKey = queryKey;
- pendingFetchRequested = false;
- pendingFetchQueryKey = '';
- loading.value = true;
- try {
- const resp = props.fetchApi ? await props.fetchApi(query) : await getTransRecord(query as TransRecordQuery);
- if (requestSequence !== fetchRequestSequence) return;
- const list = normalizeList(resp);
- tableData.value = list.map(mapRow);
- pagination.total = normalizeTotal(resp, list.length);
- pagination.page = normalizeCurrent(resp, pagination.page);
- pagination.limit = normalizeSize(resp, pagination.limit);
- } catch (error) {
- if (requestSequence !== fetchRequestSequence) return;
- tableData.value = [];
- pagination.total = 0;
- const errMsg = error instanceof Error ? error.message : '查询失败,请检查接口或网络';
- console.error('查询交易记录失败', error);
- createMessage.error(errMsg);
- } finally {
- const shouldRunPendingFetch = pendingFetchRequested && props.open;
- if (requestSequence === fetchRequestSequence || shouldRunPendingFetch) {
- loading.value = false;
- }
- if (shouldRunPendingFetch) {
- pendingFetchRequested = false;
- pendingFetchQueryKey = '';
- activeFetchQueryKey = '';
- void fetchTable();
- }
- }
- };
- const handleTrace = () => {
- if (!selectedRows.value.length) return;
- emit('trace', {
- direction: props.traceDirection,
- records: [...selectedRows.value],
- });
- };
- const handleSearch = async () => {
- pagination.page = 1;
- clearTraceSelection();
- await fetchTable();
- };
- const handleReset = async () => {
- syncFiltersFromBaseQuery();
- pagination.page = 1;
- clearTraceSelection();
- await fetchTable();
- };
- const handlePrevPage = async () => {
- if (loading.value || pagination.page <= 1) return;
- pagination.page -= 1;
- clearTraceSelection();
- await fetchTable();
- };
- const handleNextPage = async () => {
- if (loading.value || pagination.page >= totalPages.value) return;
- pagination.page += 1;
- clearTraceSelection();
- await fetchTable();
- };
- const handlePageChange = async (page: number, pageSize: number) => {
- if (loading.value) return;
- if (pageSize !== pagination.limit) {
- await handlePageSizeChange(pageSize);
- return;
- }
- if (page === pagination.page - 1) {
- await handlePrevPage();
- return;
- }
- if (page === pagination.page + 1) {
- await handleNextPage();
- return;
- }
- if (page === pagination.page) return;
- pagination.page = page;
- clearTraceSelection();
- await fetchTable();
- };
- const handlePageSizeChange = async (size: number) => {
- pagination.limit = Number(size);
- pagination.page = 1;
- clearTraceSelection();
- await fetchTable();
- };
- const handleSortChange = async (payload: { orderKey: string; sort: 'ASC' | 'DESC' }) => {
- filters.orderKey = payload.orderKey;
- filters.sort = payload.sort;
- pagination.page = 1;
- clearTraceSelection();
- await fetchTable();
- };
- const initAndFetch = async () => {
- pagination.page = 1;
- syncFiltersFromBaseQuery();
- clearTraceSelection();
- await fetchTable();
- };
- let initFetchScheduled = false;
- const scheduleInitAndFetch = () => {
- if (!props.open || initFetchScheduled) return;
- initFetchScheduled = true;
- nextTick(async () => {
- initFetchScheduled = false;
- if (!props.open) return;
- await initAndFetch();
- });
- };
- watch(
- () => props.open,
- (open) => {
- if (!open) {
- initFetchScheduled = false;
- fetchRequestSequence += 1;
- activeFetchQueryKey = '';
- pendingFetchRequested = false;
- pendingFetchQueryKey = '';
- loading.value = false;
- clearTraceSelection();
- return;
- }
- scheduleInitAndFetch();
- },
- );
- watch(
- () => props.baseQuery,
- () => {
- if (!props.open) return;
- scheduleInitAndFetch();
- },
- { deep: true },
- );
- defineExpose({
- reload: fetchTable,
- });
- </script>
- <style lang="less">
- .trans-record-modal {
- .ant-modal {
- padding-bottom: 0;
- }
- .ant-modal-body > .scroll-container,
- .ant-modal-body > .scroll-container > .scrollbar__wrap,
- .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view,
- .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view > div {
- height: 100%;
- min-height: 0 !important;
- max-height: none !important;
- }
- .ant-modal-body > .scroll-container > .scrollbar__wrap {
- overflow: hidden;
- }
- .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view {
- margin: 0 !important;
- }
- .ant-modal-body > .scroll-container > .scrollbar__bar.is-vertical,
- .ant-modal-body > .scroll-container > .is-vertical {
- display: none !important;
- opacity: 0 !important;
- pointer-events: none;
- }
- .ant-modal-content {
- border-radius: 14px;
- background: #fff;
- box-shadow: 0 18px 46px rgb(15 23 42 / 16%);
- }
- .ant-modal-header {
- padding: 12px 16px;
- background: @modal-header-bg-color;
- border-bottom-color: #e5e7eb;
- }
- .ant-modal-title {
- color: #0f172a;
- font-size: 16px;
- font-weight: 600;
- line-height: 24px;
- letter-spacing: 0.2px;
- }
- .ant-modal-close {
- top: 14px;
- right: 16px;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- width: auto !important;
- height: auto !important;
- border-radius: 8px;
- transition: background-color 0.2s ease;
- .jeesite-basic-modal-close {
- display: flex;
- align-items: center;
- gap: 2px;
- margin-right: 0;
- }
- &:hover {
- background: rgb(37 99 235 / 8%);
- }
- }
- .ant-modal-body {
- padding: 16px;
- }
- .ant-modal-footer {
- padding: 8px 16px 10px;
- border-top-color: #e5e7eb;
- background: #fff;
- }
- .query-table-wrap {
- gap: 12px;
- }
- .query-table-search {
- margin: 0;
- padding: 14px 16px;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- background: #fff;
- box-shadow: inset 0 1px 0 rgb(255 255 255 / 80%);
- .label {
- color: #475569;
- font-size: 12px;
- font-weight: 600;
- }
- .ant-input,
- .ant-input-affix-wrapper,
- .ant-picker,
- .ant-select-selector {
- border-radius: 8px !important;
- }
- .search-actions {
- gap: 12px;
- .ant-btn {
- height: 36px;
- min-width: 76px;
- padding: 0 18px;
- border-radius: 18px;
- font-size: 15px;
- font-weight: 500;
- box-shadow: none;
- }
- .ant-btn-primary {
- border-color: #315efb;
- background: #315efb;
- }
- .ant-btn:not(.ant-btn-primary) {
- color: #1f2937;
- border-color: #d6dbe6;
- background: #fff;
- }
- }
- .search-extra-actions {
- width: 100%;
- margin-left: 0;
- order: -1;
- }
- }
- .query-table-content {
- margin: 0;
- border-color: #dbe3ee;
- border-radius: 12px;
- background: #fff;
- box-shadow: 0 8px 20px rgb(15 23 42 / 4%);
- .ant-table-thead > tr > th {
- background: #f8fafc;
- color: #334155;
- font-weight: 600;
- }
- .ant-table-tbody > tr:nth-child(even) > td {
- background: #fcfdff;
- }
- .ant-table-tbody > tr:hover > td {
- background: #eff6ff;
- }
- }
- .query-table-empty {
- color: #94a3b8;
- background: #fff;
- }
- .pagination-bar {
- min-height: 32px;
- .pagination-total {
- color: #64748b;
- }
- .pagination-size .ant-select-selector,
- .pagination-actions .ant-btn {
- border-radius: 6px;
- }
- .pagination-actions .ant-btn-primary {
- min-width: 36px;
- }
- }
- .trans-record-modal__trace-bar {
- display: flex;
- align-items: center;
- gap: 10px;
- flex-wrap: wrap;
- width: auto;
- }
- .trans-record-modal__trace-label {
- color: #475569;
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
- }
- .trans-record-modal__trace-bar .ant-radio-group {
- display: inline-flex;
- align-items: center;
- gap: 12px;
- }
- .trans-record-modal__trace-bar .ant-radio-wrapper {
- margin-right: 0;
- color: #334155;
- }
- .query-table-search {
- .search-utility-actions {
- width: 100%;
- justify-content: space-between;
- align-items: flex-end;
- }
- .search-extra-actions {
- width: auto;
- margin-left: 0;
- order: -1;
- }
- }
- }
- </style>
|