| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914 |
- <template>
- <div ref="pageRef" :class="prefixCls" class="fast-transfer-page">
- <div class="filter-bar">
- <div class="filter-item">
- <span class="filter-label">选择对象:</span>
- <Select
- v-model:value="filters.target"
- class="filter-input"
- :options="targetOptions"
- placeholder="请选择对象"
- allow-clear
- />
- </div>
- <div class="filter-item">
- <span class="filter-label">时间间隔:</span>
- <Input v-model:value="filters.intervalValue" class="filter-micro" :status="intervalValueStatus" />
- <span class="filter-unit">h</span>
- </div>
- <div class="filter-item">
- <span class="filter-label">收入金额:</span>
- <Select v-model:value="filters.amountOp" class="filter-op" :options="amountOpOptions" />
- <Input v-model:value="filters.amountValue" class="filter-mini" :status="amountValueStatus" />
- </div>
- <div class="filter-item">
- <span class="filter-label">交易日期:</span>
- <RangePicker
- v-model:value="filters.dateRange"
- class="filter-date"
- :format="DATE_FORMAT"
- :value-format="DATE_FORMAT"
- :placeholder="['开始日期', '结束日期']"
- />
- </div>
- <div class="filter-actions">
- <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
- <Button @click="handleReset">重置</Button>
- </div>
- </div>
- <div v-if="!hasSearched" class="query-state-panel query-state-panel--page">
- <EmptyState
- variant="query"
- title="请选择对象后查询"
- description="该页面必须先选择对象后,再点击查询展示资金快转数据"
- min-height="100%"
- description-width="min(100%, 420px)"
- />
- </div>
- <div v-else class="page-content">
- <div class="account-strip">
- <div v-if="bankCards.length" class="account-list">
- <div class="account-strip-head">
- <span class="strip-meta">已选 {{ selectedCardNos.length }} / {{ bankCards.length }} 张</span>
- </div>
- <label
- v-for="card in bankCards"
- :key="card.cardNo || card.bankName"
- class="account-item"
- :class="{ 'is-active': isCardSelected(card.cardNo) }"
- >
- <Checkbox
- class="account-checkbox"
- :checked="isCardSelected(card.cardNo)"
- @change="toggleCard(card.cardNo, $event.target.checked)"
- />
- <div class="account-content">
- <span class="account-number">{{ card.cardNo || '-' }}</span>
- <span class="account-bank">{{ card.bankName || '未知银行' }}</span>
- </div>
- </label>
- </div>
- <div v-else class="selection-empty">暂无银行卡数据</div>
- </div>
- <div class="timeline-panel">
- <div class="timeline-header">
- <div class="header-title income">收入</div>
- <div class="header-axis"></div>
- <div class="header-title expense">支出</div>
- </div>
- <div class="timeline-content">
- <template v-if="timelineRows.length">
- <div class="timeline-line"></div>
- <div v-for="item in timelineRows" :key="item.date" class="timeline-row">
- <div class="timeline-side income-side">
- <button
- v-if="item.income"
- type="button"
- class="flow-card income-card flow-card-button"
- @click="handleFlowCardClick(item, 'income')"
- >
- <span class="flow-count">收入{{ item.income.count }}笔</span>
- <span class="flow-amount">金额{{ formatAmount(item.income.amount) }}</span>
- </button>
- </div>
- <div class="center-axis">
- <div class="axis-dot"></div>
- <div class="axis-date">{{ item.date }}</div>
- </div>
- <div class="timeline-side expense-side">
- <button
- v-if="item.expense"
- type="button"
- class="flow-card expense-card flow-card-button"
- @click="handleFlowCardClick(item, 'expense')"
- >
- <span class="flow-count">支出{{ item.expense.count }}笔</span>
- <span class="flow-amount">金额{{ formatAmount(item.expense.amount) }}</span>
- </button>
- </div>
- </div>
- </template>
- <div v-else class="timeline-empty">
- <EmptyState :description="timelineEmptyDescription" />
- </div>
- </div>
- </div>
- </div>
- <TransRecordModal v-model:open="transRecordModalOpen" title="交易记录" :base-query="transRecordBaseQuery" />
- </div>
- </template>
- <script lang="ts" setup name="ViewsFastTransferIndex">
- import { computed, onActivated, onMounted, ref } from 'vue';
- import { Select, Input, Button, DatePicker, Checkbox } from 'ant-design-vue';
- import { EmptyState } from '@jeesite/core/components/EmptyState';
- import { useAnalysisRefreshPrompt } from '@jeesite/core/hooks/web/useAnalysisRefreshPrompt';
- import { useDesign } from '@jeesite/core/hooks/web/useDesign';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- import { useTableSkill } from '@jeesite/core/hooks/web/useTableSkill';
- import {
- COMPARE_OPERATOR_OPTIONS,
- DEFAULT_COMPARE_OPERATOR,
- normalizeCompareOperator,
- type CompareOperator,
- } from '@jeesite/core/utils/compareOperator';
- import { getPersons } from '@jeesite/person/api/personApi';
- import type { PersonBasicInfo } from '@jeesite/person/api/types/dto';
- import { statFirstTransFastFundFlow } from '@jeesite/trans/api/transApi';
- import type {
- TransFastFundFlowBankDTO,
- TransFastFundFlowDTO,
- TransFastFundFlowDayDTO,
- } from '@jeesite/trans/api/types/dto';
- import type { TransFastFundFlowQuery, TransRecordQuery } from '@jeesite/trans/api/types/query';
- import TransRecordModal from '../components/TransRecordModal.vue';
- interface TimelineSide {
- count: number;
- amount: number;
- ids: Array<string>;
- }
- interface TimelineRow {
- date: string;
- income: TimelineSide | null;
- expense: TimelineSide | null;
- }
- type FlowDirection = 'income' | 'expense';
- type SymbolEnum = Extract<CompareOperator, 'GE' | 'LE'>;
- type FastTransferQueryPayload = TransFastFundFlowQuery & {
- SymbolEnum?: SymbolEnum;
- };
- const { prefixCls } = useDesign('fast-transfer');
- const { createMessage } = useMessage();
- const { queryLoading, runWithQueryLoading } = useTableSkill();
- const RangePicker = DatePicker.RangePicker;
- const DATE_FORMAT = 'YYYY-MM-DD';
- type RangeValue = [string, string] | undefined;
- const filters = ref({
- target: '',
- intervalValue: '24',
- amountOp: DEFAULT_COMPARE_OPERATOR,
- amountValue: '10000',
- dateRange: undefined as RangeValue,
- });
- const pageRef = ref<HTMLElement | null>(null);
- const targetOptions = ref<Array<{ label: string; value: string }>>([]);
- const personList = ref<PersonBasicInfo[]>([]);
- const positiveIntegerPattern = /^[1-9]\d*$/;
- const amountPattern = /^(?:0|[1-9]\d*)(?:\.\d{1,2})?$/;
- const fastResult = ref<TransFastFundFlowDTO | null>(null);
- const hasSearched = ref(false);
- const selectedCardNos = ref<string[]>([]);
- const transRecordModalOpen = ref(false);
- const transRecordBaseQuery = ref<Partial<TransRecordQuery>>({});
- const amountOpOptions = COMPARE_OPERATOR_OPTIONS.filter((item) => item.value === 'GE' || item.value === 'LE');
- const normalizedIntervalValue = computed(() => String(filters.value.intervalValue ?? '').trim());
- const normalizedAmountValue = computed(() => String(filters.value.amountValue ?? '').trim());
- const intervalValueStatus = computed<'error' | undefined>(() => {
- if (!normalizedIntervalValue.value) return undefined;
- return positiveIntegerPattern.test(normalizedIntervalValue.value) ? undefined : 'error';
- });
- const amountValueStatus = computed<'error' | undefined>(() => {
- if (!normalizedAmountValue.value) return undefined;
- return normalizePositiveAmountInput(normalizedAmountValue.value) === null ? 'error' : undefined;
- });
- const activePayload = computed<TransFastFundFlowDTO>(() => unwrapFastResult(fastResult.value));
- const bankCards = computed<TransFastFundFlowBankDTO[]>(() => {
- return Array.isArray(activePayload.value.banks) ? activePayload.value.banks.filter((item) => !!item?.cardNo) : [];
- });
- const selectedPerson = computed<PersonBasicInfo | null>(() => {
- if (!filters.value.target) return null;
- return personList.value.find((item) => item.personName === filters.value.target) || null;
- });
- const selectedPersonCardNos = computed<string[]>(() => {
- return (selectedPerson.value?.cardList || []).map((item) => String(item?.personNo || '').trim()).filter(Boolean);
- });
- const timelineRows = computed<TimelineRow[]>(() => {
- const list: TransFastFundFlowDayDTO[] = Array.isArray(activePayload.value.list) ? activePayload.value.list : [];
- if (!list.length) return [];
- if (bankCards.value.length && !selectedCardNos.value.length) return [];
- const grouped = new Map<string, TimelineRow>();
- list.forEach((item) => {
- if (item.cardNo && !selectedCardNos.value.includes(item.cardNo)) {
- return;
- }
- const date = item.transDate || '-';
- const nextRow = grouped.get(date) || {
- date,
- income: null,
- expense: null,
- };
- const targetKey = isIncome(item.debitCreditFlag) ? 'income' : 'expense';
- const target = nextRow[targetKey] || { count: 0, amount: 0, ids: [] as string[] };
- target.count += toNumber(item.transCount);
- target.amount += toNumber(item.totalTransAmount);
- target.ids = Array.from(new Set([...target.ids, ...normalizeRecordIds(item.idList)]));
- nextRow[targetKey] = target;
- grouped.set(date, nextRow);
- });
- return Array.from(grouped.values()).sort((a, b) => a.date.localeCompare(b.date));
- });
- const { syncAnalysisDataRefreshState } = useAnalysisRefreshPrompt({
- refresh: () => fetchFastFundFlow(),
- hasData: () => hasSearched.value && bankCards.value.length > 0 && timelineRows.value.length > 0,
- });
- const timelineEmptyDescription = computed(() => {
- if (!filters.value.target) {
- return '请选择对象后再查询资金快转数据';
- }
- if (bankCards.value.length && !selectedCardNos.value.length) {
- return '请至少勾选一张银行卡查看对应快转时间轴';
- }
- return '当前筛选条件下暂无快转数据';
- });
- const unwrapFastResult = (resp: any): TransFastFundFlowDTO => {
- if (resp?.data && typeof resp.data === 'object') return resp.data as TransFastFundFlowDTO;
- return (resp || {}) as TransFastFundFlowDTO;
- };
- const isIncome = (flag?: string) => {
- const normalized = String(flag || '').trim();
- return normalized.includes('收') || normalized.includes('入') || normalized.includes('贷');
- };
- const toRawText = (value?: number | string | null) => {
- if (value === 0) return '0';
- if (value === null || value === undefined) return '';
- return String(value).trim();
- };
- const toNumber = (value?: number | string | null) => {
- if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
- const normalized = toRawText(value).replace(/,/g, '');
- if (!normalized) return 0;
- const parsed = Number(normalized);
- return Number.isFinite(parsed) ? parsed : 0;
- };
- function normalizeRecordIds(value?: Array<number | string | null>) {
- if (!Array.isArray(value)) return [] as string[];
- return value.map((item) => toRawText(item)).filter((item): item is string => !!item);
- }
- const formatAmount = (value?: number | string | null) => {
- const amount = toNumber(value);
- if (!Number.isFinite(amount)) return toRawText(value) || '-';
- return new Intl.NumberFormat('zh-CN', {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- }).format(amount);
- };
- const isCardSelected = (cardNo?: string) => {
- if (!cardNo) return false;
- return selectedCardNos.value.includes(cardNo);
- };
- const toggleCard = (cardNo?: string, checked = false) => {
- if (!cardNo) return;
- const nextSet = new Set(selectedCardNos.value);
- if (checked) {
- nextSet.add(cardNo);
- } else {
- nextSet.delete(cardNo);
- }
- selectedCardNos.value = Array.from(nextSet);
- };
- const normalizePositiveIntegerInput = (value?: string) => {
- const next = String(value ?? '').trim();
- if (!next) return undefined;
- if (!positiveIntegerPattern.test(next)) return null;
- const parsed = Number(next);
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
- };
- const normalizePositiveAmountInput = (value?: string) => {
- const next = String(value ?? '').trim();
- const amountForCheck = next.replace(/,/g, '');
- if (!amountForCheck) return undefined;
- if (!amountPattern.test(amountForCheck) || /^0(?:\.0{1,2})?$/.test(amountForCheck)) return null;
- return next;
- };
- const toFastTransferSymbolEnum = (operator?: CompareOperator): SymbolEnum =>
- normalizeCompareOperator(operator, DEFAULT_COMPARE_OPERATOR) === 'LE' ? 'LE' : 'GE';
- const buildQuery = (): FastTransferQueryPayload | null => {
- if (!filters.value.target) {
- createMessage.warning('请选择对象后再查询');
- return null;
- }
- const hour = normalizePositiveIntegerInput(filters.value.intervalValue);
- const transAmount = normalizePositiveAmountInput(filters.value.amountValue);
- const amountOp = normalizeCompareOperator(filters.value.amountOp, DEFAULT_COMPARE_OPERATOR);
- if (hour === null || hour === undefined) {
- createMessage.warning('时间间隔请输入大于 0 的整数');
- return null;
- }
- if (transAmount === null) {
- createMessage.warning('收入金额请输入大于 0 的数字,最多两位小数');
- return null;
- }
- if (transAmount !== undefined && amountOp !== 'GE' && amountOp !== 'LE') {
- createMessage.warning('收入金额条件仅支持 >= 或 <=');
- return null;
- }
- const amountSymbol = transAmount !== undefined ? toFastTransferSymbolEnum(amountOp) : undefined;
- return {
- page: 1,
- limit: 1000,
- startDate: filters.value.dateRange?.[0] || undefined,
- endDate: filters.value.dateRange?.[1] || undefined,
- personName: filters.value.target || undefined,
- personNames: filters.value.target ? [filters.value.target] : undefined,
- personCardNo: selectedPersonCardNos.value.length === 1 ? selectedPersonCardNos.value[0] : undefined,
- hour,
- transAmount,
- transAmountSymbol: amountSymbol,
- money: transAmount,
- SymbolEnum: amountSymbol,
- };
- };
- const buildTransRecordBaseQuery = (item: TimelineRow, direction: FlowDirection): Partial<TransRecordQuery> => {
- const ids = item[direction]?.ids || [];
- if (!ids.length) return {};
- return {
- id: ids.length === 1 ? String(ids[0]) : undefined,
- ids,
- };
- };
- const syncSelectedCards = (banks: TransFastFundFlowBankDTO[]) => {
- const nextCardNos = banks.map((item) => item.cardNo).filter((item): item is string => !!item);
- if (!nextCardNos.length) {
- selectedCardNos.value = [];
- return;
- }
- const nextSet = new Set(nextCardNos);
- const retained = selectedCardNos.value.filter((item) => nextSet.has(item));
- selectedCardNos.value = retained.length ? retained : nextCardNos;
- };
- const fetchFastFundFlow = async () => {
- const query = buildQuery();
- if (!query) return;
- await runWithQueryLoading(async () => {
- const resp = await statFirstTransFastFundFlow(query);
- const payload = unwrapFastResult(resp);
- fastResult.value = payload;
- hasSearched.value = true;
- syncSelectedCards(Array.isArray(payload.banks) ? payload.banks : []);
- syncAnalysisDataRefreshState(Array.isArray(payload.banks) && payload.banks.length > 0);
- });
- };
- const handleSearch = async () => {
- await fetchFastFundFlow();
- };
- const handleReset = async () => {
- filters.value = {
- target: '',
- intervalValue: '24',
- amountOp: DEFAULT_COMPARE_OPERATOR,
- amountValue: '10000',
- dateRange: undefined,
- };
- fastResult.value = null;
- hasSearched.value = false;
- selectedCardNos.value = [];
- };
- const handleFlowCardClick = (item: TimelineRow, direction: FlowDirection) => {
- const baseQuery = buildTransRecordBaseQuery(item, direction);
- if (!baseQuery.ids?.length && !baseQuery.id) {
- createMessage.warning('当前节点暂无可下探的交易记录');
- return;
- }
- transRecordBaseQuery.value = baseQuery;
- transRecordModalOpen.value = true;
- };
- const loadTargetOptions = async () => {
- try {
- personList.value = await getPersons();
- targetOptions.value = personList.value
- .filter((item) => item.personName)
- .map((item) => ({
- label: item.personName || '-',
- value: item.personName || '',
- }));
- } catch {
- personList.value = [];
- targetOptions.value = [];
- }
- };
- const resetPageScroll = () => {
- const pageEl = pageRef.value;
- pageEl?.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
- const layoutContentEl = pageEl?.closest('.jeesite-layout-content') as HTMLElement | null;
- layoutContentEl?.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
- };
- onMounted(() => {
- loadTargetOptions();
- resetPageScroll();
- });
- onActivated(() => {
- resetPageScroll();
- });
- </script>
- <style lang="less">
- @import '../styles/trans-analysis-table-ui.less';
- @prefix-cls: ~'jeesite-fast-transfer';
- .@{prefix-cls}.fast-transfer-page {
- .analysis-page-frame();
- .filter-input {
- width: 180px;
- }
- .filter-mini {
- width: 96px;
- }
- .filter-micro {
- width: 86px;
- }
- .filter-unit {
- color: #6b7280;
- font-size: 12px;
- }
- .filter-op {
- width: 64px;
- min-width: 64px;
- }
- .filter-date {
- width: 360px;
- }
- .filter-actions :deep(.ant-btn) {
- min-width: 68px;
- }
- .page-content {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- .query-state-panel {
- .analysis-card-shell();
- flex: 1 1 auto;
- min-height: 420px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .query-state-panel--page {
- margin-top: 2px;
- }
- .account-strip {
- .analysis-card-shell();
- flex: 0 0 auto;
- }
- .account-list {
- display: flex;
- flex-wrap: wrap;
- gap: 10px 28px;
- align-items: flex-start;
- }
- .account-strip-head {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- margin-bottom: 6px;
- }
- .strip-meta {
- color: #6b7280;
- font-size: 12px;
- line-height: 18px;
- }
- .account-item {
- display: inline-flex;
- align-items: flex-start;
- gap: 8px;
- min-width: 150px;
- padding: 6px 10px 7px;
- border: 1px solid transparent;
- border-radius: 8px;
- cursor: pointer;
- user-select: none;
- transition:
- background-color 0.2s ease,
- border-color 0.2s ease,
- box-shadow 0.2s ease;
- }
- .account-item:hover {
- background: rgba(22, 119, 255, 0.04);
- }
- .account-checkbox {
- margin-top: 2px;
- }
- .account-content {
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-width: 0;
- }
- .selection-empty {
- padding: 8px 0;
- color: #9ca3af;
- font-size: 12px;
- line-height: 20px;
- }
- .account-number {
- color: #1f2937;
- font-size: 13px;
- line-height: 20px;
- font-family: Consolas, 'Courier New', monospace;
- transition: color 0.2s ease;
- }
- .account-bank {
- color: #374151;
- font-size: 13px;
- line-height: 20px;
- transition: color 0.2s ease;
- }
- .account-item.is-active {
- border-color: #cfe4ff;
- background: #edf6ff;
- box-shadow: inset 0 0 0 1px rgba(22, 119, 255, 0.04);
- .account-number,
- .account-bank {
- color: #1697ff;
- font-weight: 600;
- }
- :deep(.ant-checkbox-inner) {
- border-color: #1697ff;
- }
- }
- .timeline-panel {
- .analysis-card-shell();
- flex: 1 1 auto;
- min-height: 420px;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- }
- .timeline-header {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 140px minmax(0, 1fr);
- align-items: center;
- padding: 20px 0 10px;
- .header-title {
- text-align: center;
- font-size: 19px;
- font-weight: 600;
- line-height: 28px;
- color: #111827;
- &.income {
- color: #333333;
- }
- &.expense {
- color: #333333;
- }
- }
- .header-axis {
- min-height: 1px;
- }
- }
- .timeline-content {
- position: relative;
- flex: 1 1 auto;
- min-height: 0;
- padding: 18px 0 72px;
- overflow: auto;
- scroll-padding-bottom: 72px;
- }
- .timeline-empty {
- height: 100%;
- min-height: 260px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .timeline-line {
- position: absolute;
- left: 50%;
- top: 22px;
- bottom: 84px;
- width: 1px;
- background: #bdbdbd;
- transform: translateX(-50%);
- }
- .timeline-row {
- position: relative;
- display: grid;
- grid-template-columns: minmax(0, 1fr) 140px minmax(0, 1fr);
- align-items: center;
- min-height: 112px;
- margin-bottom: 18px;
- }
- .timeline-side {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 100%;
- }
- .income-side {
- padding-right: 30px;
- }
- .expense-side {
- padding-left: 30px;
- }
- .center-axis {
- position: relative;
- z-index: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 10px;
- }
- .axis-dot {
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background: #7b7b7b;
- border: 2px solid #efefef;
- box-shadow:
- 0 0 0 2px #d9d9d9,
- 0 2px 8px rgba(15, 23, 42, 0.14);
- }
- .axis-date {
- color: #4b5563;
- font-size: 12px;
- line-height: 18px;
- font-weight: 500;
- white-space: nowrap;
- text-align: center;
- }
- .flow-card {
- display: inline-flex;
- align-items: center;
- justify-content: space-between;
- gap: 28px;
- min-width: 210px;
- max-width: 260px;
- padding: 11px 18px;
- border-radius: 8px;
- font-size: 12px;
- line-height: 18px;
- box-sizing: border-box;
- box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
- .flow-count,
- .flow-amount {
- font-weight: 600;
- }
- }
- .flow-card-button {
- border: none;
- cursor: pointer;
- transition:
- transform 0.2s ease,
- box-shadow 0.2s ease,
- filter 0.2s ease;
- &:hover {
- transform: translateY(-1px);
- filter: brightness(1.01);
- box-shadow: 0 12px 22px rgba(15, 23, 42, 0.08);
- }
- }
- .income-card {
- background: #f79a13;
- .flow-count,
- .flow-amount {
- color: #ffffff;
- }
- }
- .expense-card {
- background: #c7f169;
- .flow-count,
- .flow-amount {
- color: #365200;
- }
- }
- :deep(.ant-select-selector),
- :deep(.ant-input),
- :deep(.ant-picker),
- :deep(.ant-btn) {
- border-radius: 8px;
- }
- @media screen and (max-width: 992px) {
- .filter-actions {
- width: 100%;
- margin-left: 0;
- }
- .filter-input,
- .filter-date {
- width: 100%;
- }
- .account-list {
- gap: 10px 18px;
- }
- .account-strip-head {
- margin-bottom: 8px;
- }
- .timeline-header,
- .timeline-row {
- grid-template-columns: minmax(0, 1fr) 96px minmax(0, 1fr);
- }
- .income-side {
- padding-right: 18px;
- }
- .expense-side {
- padding-left: 18px;
- }
- .timeline-header {
- padding-top: 18px;
- }
- .flow-card {
- min-width: 144px;
- gap: 14px;
- padding: 10px 12px;
- }
- }
- @media screen and (max-width: 640px) {
- .account-strip {
- padding-left: 10px;
- padding-right: 10px;
- }
- .account-strip-head {
- align-items: flex-start;
- flex-direction: column;
- gap: 4px;
- }
- .timeline-header {
- display: none;
- }
- .timeline-row {
- grid-template-columns: 1fr;
- gap: 10px;
- padding-left: 40px;
- margin-bottom: 18px;
- }
- .timeline-line {
- left: 18px;
- top: 4px;
- bottom: 20px;
- }
- .center-axis {
- position: absolute;
- left: 18px;
- top: 12px;
- transform: translateX(-50%);
- gap: 6px;
- }
- .timeline-side {
- justify-content: flex-start;
- }
- .income-side,
- .expense-side {
- padding: 0;
- }
- .flow-card {
- width: 100%;
- max-width: none;
- }
- }
- }
- @import '../styles/trans-analysis-table-ui.less';
- </style>
|