CallRecordModal.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <template>
  2. <QueryTableModal
  3. :open="open"
  4. :title="title"
  5. :width="width"
  6. :z-index="zIndex"
  7. :loading="loading"
  8. :filters="filters"
  9. :filter-schema="visibleFilterSchema"
  10. :columns="visibleColumns"
  11. :action-column="actionColumn"
  12. :data-source="tableData"
  13. :pagination="pagination"
  14. :total-pages="totalPages"
  15. :page-size-options="pageSizeOptions"
  16. :show-table-setting="true"
  17. :table-setting="{ size: true, setting: true, redo: false, fullScreen: false }"
  18. :can-fullscreen="true"
  19. wrap-class-name="call-record-modal"
  20. @update:open="(v) => emit('update:open', v)"
  21. @search="handleSearch"
  22. @reset="handleReset"
  23. @prev-page="handlePrevPage"
  24. @next-page="handleNextPage"
  25. @page-size-change="handlePageSizeChange"
  26. @page-change="handlePageChange"
  27. @sort-change="handleSortChange"
  28. />
  29. </template>
  30. <script lang="ts" setup>
  31. import { computed, nextTick, reactive, ref, watch } from 'vue';
  32. import QueryTableModal from '@/case/views/dm/components/QueryTableModal.vue';
  33. import componentSetting from '@/core/settings/componentSetting';
  34. import { useMessage } from '@/core/hooks/web/useMessage';
  35. import {
  36. downloadOfficeFileById,
  37. getOpenOfficeFileErrorMessage,
  38. resolveOfficeFileId,
  39. } from '@/core/utils/openOfficeFile';
  40. import { maskCertNo } from '@/core/utils/sensitive';
  41. import { useTimeSeriesJump } from '@/otg/hooks/useTimeSeriesJump';
  42. import { getCallRecord } from '@/call/api/callApi';
  43. import type { CallRecord } from '@/call/api/types/dto';
  44. import type { CallRecordQuery } from '@/call/api/types/query';
  45. type CallRecordModalQuery = Partial<CallRecordQuery> & Record<string, any>;
  46. const props = withDefaults(
  47. defineProps<{
  48. open: boolean;
  49. title?: string;
  50. width?: number;
  51. zIndex?: number;
  52. baseQuery?: CallRecordModalQuery;
  53. fetchApi?: (query: CallRecordModalQuery) => Promise<any>;
  54. hideTechnicalIdColumns?: boolean;
  55. filterFields?: string[];
  56. }>(),
  57. {
  58. title: '通信记录',
  59. width: 1240,
  60. baseQuery: () => ({}),
  61. hideTechnicalIdColumns: true,
  62. filterFields: () => [
  63. 'personName',
  64. 'personPhone',
  65. 'otherName',
  66. 'otherPhone',
  67. 'callDirection',
  68. 'startDate',
  69. 'endDate',
  70. ],
  71. },
  72. );
  73. const emit = defineEmits<{
  74. (e: 'update:open', v: boolean): void;
  75. (e: 'jump'): void;
  76. }>();
  77. const loading = ref(false);
  78. const tableData = ref<any[]>([]);
  79. const { jumping, jumpToBehaviorSpacetime } = useTimeSeriesJump();
  80. const { createMessage } = useMessage();
  81. const filters = reactive({
  82. personName: '',
  83. personPhone: '',
  84. otherName: '',
  85. otherPhone: '',
  86. callDirection: '',
  87. startDate: '',
  88. endDate: '',
  89. orderKey: '',
  90. sort: '' as '' | 'ASC' | 'DESC',
  91. });
  92. const pagination = reactive({
  93. page: 1,
  94. limit: componentSetting.table.defaultPageSize,
  95. total: 0,
  96. });
  97. const totalPages = computed(() => {
  98. if (!pagination.total) return 1;
  99. return Math.max(1, Math.ceil(pagination.total / pagination.limit));
  100. });
  101. const pageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
  102. label: value,
  103. value,
  104. }));
  105. const filterSchema = [
  106. { field: 'personName', label: '本方姓名', type: 'input', placeholder: '请输入本方姓名', width: 180 },
  107. { field: 'personPhone', label: '本方号码', type: 'input', placeholder: '请输入本方号码', width: 180 },
  108. { field: 'otherName', label: '对方姓名', type: 'input', placeholder: '请输入对方姓名', width: 180 },
  109. { field: 'otherPhone', label: '对方号码', type: 'input', placeholder: '请输入对方号码', width: 180 },
  110. {
  111. field: 'callDirection',
  112. label: '主被叫',
  113. type: 'select',
  114. width: 160,
  115. options: [
  116. { label: '全部', value: '' },
  117. { label: '主叫', value: '主叫' },
  118. { label: '被叫', value: '被叫' },
  119. ],
  120. },
  121. { field: 'startDate', label: '开始日期', type: 'date', width: 170 },
  122. { field: 'endDate', label: '结束日期', type: 'date', width: 170 },
  123. {
  124. field: 'contactDateRange',
  125. label: '联系日期',
  126. type: 'date-range',
  127. startField: 'startDate',
  128. endField: 'endDate',
  129. width: 320,
  130. maxWidth: 360,
  131. rangePlaceholder: ['开始日期', '结束日期'],
  132. valueFormat: 'YYYY-MM-DD',
  133. },
  134. ] as const;
  135. const visibleFilterSchema = computed(() => {
  136. const visibleFields = new Set(props.filterFields);
  137. const showDateRange = visibleFields.has('startDate') && visibleFields.has('endDate');
  138. return filterSchema.filter((item) => {
  139. if (item.field === 'personName') return false;
  140. if (item.field === 'contactDateRange') return showDateRange;
  141. if (item.field === 'startDate' || item.field === 'endDate')
  142. return !showDateRange && visibleFields.has(item.field);
  143. return visibleFields.has(item.field);
  144. });
  145. });
  146. const technicalIdColumnKeys = ['personLibId'];
  147. const columns = [
  148. { title: '本方姓名', dataIndex: 'personName', key: 'personName', width: 120 },
  149. { title: '本方号码', dataIndex: 'personPhone', key: 'personPhone', width: 140 },
  150. { title: '对方姓名', dataIndex: 'otherName', key: 'otherName', width: 140 },
  151. { title: '对方号码', dataIndex: 'otherPhone', key: 'otherPhone', width: 140 },
  152. { title: '主被叫', dataIndex: 'callDirection', key: 'callDirection', width: 100 },
  153. { title: '通话日期时间', dataIndex: 'callDateTime', key: 'callDateTime', width: 180, sorter: true },
  154. { title: '本方基站名称', dataIndex: 'personStationName', key: 'personStationName', width: 220 },
  155. { title: '本方基站大区号', dataIndex: 'personRegionId', key: 'personRegionId', width: 140 },
  156. { title: '本方基站小区号', dataIndex: 'personCellId', key: 'personCellId', width: 140 },
  157. { title: '通话时长(秒)', dataIndex: 'duration', key: 'duration', width: 120, sorter: true },
  158. { title: '运营商', dataIndex: 'operatorId', key: 'operatorId', width: 120 },
  159. { title: '本方号码归属地', dataIndex: 'personPhoneArea', key: 'personPhoneArea', width: 160 },
  160. { title: '本方证件号码', dataIndex: 'personCertNo', key: 'personCertNo', width: 180 },
  161. { title: '本方基站经度', dataIndex: 'personStationLongitude', key: 'personStationLongitude', width: 140 },
  162. { title: '本方基站纬度', dataIndex: 'personStationLatitude', key: 'personStationLatitude', width: 140 },
  163. { title: '通话类型', dataIndex: 'callType', key: 'callType', width: 100 },
  164. { title: '对方号码归属地', dataIndex: 'otherPhoneArea', key: 'otherPhoneArea', width: 160 },
  165. { title: '对方证件号码', dataIndex: 'otherCertNo', key: 'otherCertNo', width: 180 },
  166. { title: '备注', dataIndex: 'remark', key: 'remark', width: 180 },
  167. ];
  168. const visibleColumns = computed(() => {
  169. if (!props.hideTechnicalIdColumns) return columns;
  170. return columns.filter((column) => !technicalIdColumnKeys.includes(String(column.key || column.dataIndex || '')));
  171. });
  172. const normalizeActionRecordId = (value: unknown) => {
  173. const text = String(value ?? '').trim();
  174. if (!text || text === '-' || text === 'null' || text === 'undefined') return undefined;
  175. return text;
  176. };
  177. const buildJumpQuery = (record?: CallRecord) => {
  178. const id = normalizeActionRecordId(record?.id);
  179. const personName = String(record?.personName || props.baseQuery?.personName || '').trim();
  180. return {
  181. hasLocal: 'N',
  182. tableName: 'call_record',
  183. tableNameEn: 'call_record',
  184. tableNameCn: '通信记录',
  185. id,
  186. personName: personName || undefined,
  187. personNames: personName ? [personName] : undefined,
  188. };
  189. };
  190. const handleJump = async (record?: CallRecord) => {
  191. await jumpToBehaviorSpacetime(buildJumpQuery(record), {
  192. onBeforeNavigate: () => {
  193. emit('jump');
  194. emit('update:open', false);
  195. },
  196. });
  197. };
  198. /** 下载关联的归档表格(Web 化:浏览器另存,不再调起本地应用打开) */
  199. const handleDownloadFile = async (record?: CallRecord) => {
  200. const fileId = resolveOfficeFileId(record);
  201. if (!fileId) {
  202. createMessage.warning('当前记录没有关联的源文件,无法下载');
  203. return;
  204. }
  205. try {
  206. await downloadOfficeFileById(fileId);
  207. } catch (error: any) {
  208. createMessage.error(getOpenOfficeFileErrorMessage(error, '文件下载失败'));
  209. }
  210. };
  211. const actionColumn = computed(() => ({
  212. title: '操作',
  213. dataIndex: 'action',
  214. key: 'action',
  215. width: 160,
  216. fixed: 'right',
  217. align: 'center',
  218. actions: (record: { raw?: CallRecord }) => {
  219. const rawRecord = record.raw;
  220. const query = buildJumpQuery(rawRecord);
  221. return [
  222. {
  223. label: '下载文件',
  224. // 不再按 fileId 置灰:没有来源文件时由 handleDownloadFile 明确提示原因
  225. onClick: () => void handleDownloadFile(rawRecord),
  226. },
  227. {
  228. label: '跳转',
  229. disabled: jumping.value || !query.id,
  230. onClick: () => void handleJump(rawRecord),
  231. },
  232. ];
  233. },
  234. }));
  235. const normalizeList = (resp: any): CallRecord[] => {
  236. if (Array.isArray(resp)) return resp;
  237. if (Array.isArray(resp?.list)) return resp.list;
  238. if (Array.isArray(resp?.data)) return resp.data;
  239. if (Array.isArray(resp?.data?.list)) return resp.data.list;
  240. if (Array.isArray(resp?.records)) return resp.records;
  241. if (Array.isArray(resp?.data?.records)) return resp.data.records;
  242. return [];
  243. };
  244. const normalizeTotal = (resp: any, listLen: number) => {
  245. const total = resp?.total ?? resp?.data?.total ?? resp?.count ?? resp?.data?.count ?? listLen;
  246. return Number.isFinite(Number(total)) ? Number(total) : listLen;
  247. };
  248. const normalizeCurrent = (resp: any, fallback: number) => {
  249. const current = resp?.current ?? resp?.data?.current ?? fallback;
  250. return Number.isFinite(Number(current)) && Number(current) > 0 ? Number(current) : fallback;
  251. };
  252. const normalizeSize = (resp: any, fallback: number) => {
  253. const size = resp?.size ?? resp?.data?.size ?? fallback;
  254. return Number.isFinite(Number(size)) && Number(size) > 0 ? Number(size) : fallback;
  255. };
  256. const toArrayByComma = (value?: string) => {
  257. const raw = String(value || '').trim();
  258. if (!raw) return undefined;
  259. return raw
  260. .split(',')
  261. .map((item) => item.trim())
  262. .filter(Boolean);
  263. };
  264. const toCommaText = (value?: string[]) => {
  265. return Array.isArray(value) && value.length ? value.join(',') : '';
  266. };
  267. const mapDirection = (value?: string) => {
  268. if (!value) return '-';
  269. if (value === 'CALL_IN') return '被叫';
  270. if (value === 'CALL_OUT') return '主叫';
  271. return value;
  272. };
  273. const mapCallType = (value?: string) => {
  274. if (!value) return '-';
  275. if (value === 'VOICE') return '语音';
  276. if (value === 'VIDEO') return '视频';
  277. if (value === 'SMSIN') return '接收';
  278. if (value === 'SMSOUT') return '发送';
  279. return value;
  280. };
  281. const buildQuery = (): CallRecordModalQuery => {
  282. const baseQuery = { ...(props.baseQuery || {}) };
  283. delete baseQuery.keyword;
  284. return {
  285. ...baseQuery,
  286. page: pagination.page,
  287. limit: pagination.limit,
  288. personName: filters.personName || props.baseQuery?.personName,
  289. personPhones: toArrayByComma(filters.personPhone) || props.baseQuery?.personPhones,
  290. otherName: filters.otherName || props.baseQuery?.otherName,
  291. otherPhones: toArrayByComma(filters.otherPhone) || props.baseQuery?.otherPhones,
  292. callDirection: filters.callDirection || props.baseQuery?.callDirection,
  293. callType: props.baseQuery?.callType,
  294. startDate: filters.startDate || props.baseQuery?.startDate,
  295. endDate: filters.endDate || props.baseQuery?.endDate,
  296. startTime: props.baseQuery?.startTime,
  297. endTime: props.baseQuery?.endTime,
  298. monthVal: props.baseQuery?.monthVal,
  299. orderKey: filters.orderKey || undefined,
  300. sort: filters.sort || undefined,
  301. };
  302. };
  303. const mapRow = (item: CallRecord, index: number) => ({
  304. rowKey: item.id ?? `${pagination.page}-${index}`,
  305. id: (pagination.page - 1) * pagination.limit + index + 1,
  306. recordId: item.id ?? '-',
  307. fileId: item.fileId ?? '-',
  308. sheetId: item.sheetId ?? '-',
  309. personName: item.personName || '-',
  310. personPhone: item.personPhone || '-',
  311. operatorId: item.operatorId || '-',
  312. personPhoneArea: item.personPhoneArea || '-',
  313. personCertNo: maskCertNo(item.personCertNo),
  314. personRegionId: item.personRegionId || '-',
  315. personCellId: item.personCellId || '-',
  316. personStationName: item.personStationName || '-',
  317. personStationLongitude: item.personStationLongitude || '-',
  318. personStationLatitude: item.personStationLatitude || '-',
  319. callDateTime: item.callDateTime || (item.callDate && item.callTime ? `${item.callDate} ${item.callTime}` : '-'),
  320. callDate: item.callDate || '-',
  321. callTime: item.callTime || '-',
  322. duration: item.duration ?? 0,
  323. callDirection: mapDirection(item.callDirection),
  324. callType: mapCallType(item.callType),
  325. otherName: item.otherName || '-',
  326. otherPhone: item.otherPhone || '-',
  327. otherPhoneArea: item.otherPhoneArea || '-',
  328. otherCertNo: maskCertNo(item.otherCertNo),
  329. remark: item.remark || '-',
  330. personLibId: item.personLibId || '-',
  331. festivalName: item.festivalName || '-',
  332. raw: item,
  333. });
  334. const syncFiltersFromBaseQuery = () => {
  335. filters.personName = props.baseQuery?.personName || '';
  336. filters.personPhone = toCommaText(props.baseQuery?.personPhones);
  337. filters.otherName = props.baseQuery?.otherName || '';
  338. filters.otherPhone = toCommaText(props.baseQuery?.otherPhones);
  339. filters.callDirection = props.baseQuery?.callDirection || '';
  340. filters.startDate = props.baseQuery?.startDate || '';
  341. filters.endDate = props.baseQuery?.endDate || '';
  342. filters.orderKey = '';
  343. filters.sort = '';
  344. };
  345. const fetchTable = async () => {
  346. loading.value = true;
  347. try {
  348. const query = buildQuery();
  349. const resp = props.fetchApi ? await props.fetchApi(query) : await getCallRecord(query as CallRecordQuery);
  350. const list = normalizeList(resp);
  351. tableData.value = list.map(mapRow);
  352. pagination.total = normalizeTotal(resp, list.length);
  353. pagination.page = normalizeCurrent(resp, pagination.page);
  354. pagination.limit = normalizeSize(resp, pagination.limit);
  355. } catch (error) {
  356. tableData.value = [];
  357. pagination.total = 0;
  358. } finally {
  359. loading.value = false;
  360. }
  361. };
  362. const handleSearch = async () => {
  363. pagination.page = 1;
  364. await fetchTable();
  365. };
  366. const handleReset = async () => {
  367. syncFiltersFromBaseQuery();
  368. pagination.page = 1;
  369. await fetchTable();
  370. };
  371. const handlePrevPage = async () => {
  372. if (loading.value || pagination.page <= 1) return;
  373. pagination.page -= 1;
  374. await fetchTable();
  375. };
  376. const handleNextPage = async () => {
  377. if (loading.value || pagination.page >= totalPages.value) return;
  378. pagination.page += 1;
  379. await fetchTable();
  380. };
  381. const handlePageChange = async (page: number, pageSize: number) => {
  382. if (loading.value) return;
  383. if (pageSize !== pagination.limit) {
  384. await handlePageSizeChange(pageSize);
  385. return;
  386. }
  387. if (page === pagination.page - 1) {
  388. await handlePrevPage();
  389. return;
  390. }
  391. if (page === pagination.page + 1) {
  392. await handleNextPage();
  393. return;
  394. }
  395. if (page === pagination.page) return;
  396. pagination.page = page;
  397. await fetchTable();
  398. };
  399. const handlePageSizeChange = async (size: number) => {
  400. pagination.limit = Number(size);
  401. pagination.page = 1;
  402. await fetchTable();
  403. };
  404. const handleSortChange = async (payload: { orderKey: string; sort: 'ASC' | 'DESC' }) => {
  405. filters.orderKey = payload.orderKey;
  406. filters.sort = payload.sort;
  407. pagination.page = 1;
  408. await fetchTable();
  409. };
  410. const initAndFetch = async () => {
  411. pagination.page = 1;
  412. syncFiltersFromBaseQuery();
  413. await fetchTable();
  414. };
  415. let initFetchScheduled = false;
  416. const scheduleInitAndFetch = () => {
  417. if (!props.open || initFetchScheduled) return;
  418. initFetchScheduled = true;
  419. nextTick(async () => {
  420. initFetchScheduled = false;
  421. if (!props.open) return;
  422. await initAndFetch();
  423. });
  424. };
  425. watch(
  426. () => props.open,
  427. (open) => {
  428. if (!open) {
  429. initFetchScheduled = false;
  430. return;
  431. }
  432. scheduleInitAndFetch();
  433. },
  434. );
  435. watch(
  436. () => props.baseQuery,
  437. () => {
  438. if (!props.open) return;
  439. scheduleInitAndFetch();
  440. },
  441. { deep: true },
  442. );
  443. defineExpose({
  444. reload: fetchTable,
  445. });
  446. </script>
  447. <style lang="less">
  448. .call-record-modal {
  449. .ant-modal {
  450. padding-bottom: 0;
  451. }
  452. .ant-modal-body > .scroll-container,
  453. .ant-modal-body > .scroll-container > .scrollbar__wrap,
  454. .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view,
  455. .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view > div {
  456. height: 100%;
  457. min-height: 0;
  458. }
  459. .ant-modal-body > .scroll-container > .scrollbar__wrap {
  460. overflow: hidden;
  461. }
  462. .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view {
  463. margin: 0 !important;
  464. }
  465. .ant-modal-body > .scroll-container > .scrollbar__bar.is-vertical,
  466. .ant-modal-body > .scroll-container > .is-vertical {
  467. display: none !important;
  468. opacity: 0 !important;
  469. pointer-events: none;
  470. }
  471. .ant-modal-content {
  472. border-radius: 14px;
  473. background: #ffffff;
  474. box-shadow: 0 18px 46px rgba(15, 23, 42, 0.16);
  475. }
  476. .ant-modal-header {
  477. padding: 12px 16px;
  478. background: @modal-header-bg-color;
  479. border-bottom-color: #e5e7eb;
  480. }
  481. .ant-modal-title {
  482. color: #0f172a;
  483. font-size: 16px;
  484. font-weight: 600;
  485. line-height: 24px;
  486. letter-spacing: 0.2px;
  487. }
  488. .ant-modal-close {
  489. top: 14px;
  490. right: 16px;
  491. display: flex;
  492. align-items: center;
  493. justify-content: flex-end;
  494. width: auto !important;
  495. height: auto !important;
  496. border-radius: 8px;
  497. transition: background-color 0.2s ease;
  498. .jeesite-basic-modal-close {
  499. display: flex;
  500. align-items: center;
  501. gap: 2px;
  502. margin-right: 0;
  503. }
  504. &:hover {
  505. background: rgba(37, 99, 235, 0.08);
  506. }
  507. }
  508. .ant-modal-body {
  509. padding: 16px;
  510. }
  511. .ant-modal-footer {
  512. padding: 8px 16px 10px;
  513. border-top-color: #e5e7eb;
  514. background: #fff;
  515. }
  516. .query-table-wrap {
  517. gap: 12px;
  518. }
  519. .query-table-search {
  520. margin: 0;
  521. padding: 14px 16px;
  522. border: 1px solid #e2e8f0;
  523. border-radius: 12px;
  524. background: #ffffff;
  525. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
  526. .label {
  527. color: #475569;
  528. font-size: 12px;
  529. font-weight: 600;
  530. }
  531. .ant-input,
  532. .ant-input-affix-wrapper,
  533. .ant-picker,
  534. .ant-select-selector {
  535. border-radius: 8px !important;
  536. }
  537. .search-actions {
  538. gap: 12px;
  539. .ant-btn {
  540. height: 36px;
  541. min-width: 76px;
  542. padding: 0 18px;
  543. border-radius: 18px;
  544. font-size: 15px;
  545. font-weight: 500;
  546. box-shadow: none;
  547. }
  548. .ant-btn-primary {
  549. border-color: #315efb;
  550. background: #315efb;
  551. }
  552. .ant-btn:not(.ant-btn-primary) {
  553. color: #1f2937;
  554. border-color: #d6dbe6;
  555. background: #ffffff;
  556. }
  557. }
  558. }
  559. .query-table-content {
  560. margin: 0;
  561. border-color: #dbe3ee;
  562. border-radius: 12px;
  563. background: #fff;
  564. box-shadow: 0 8px 20px rgba(15, 23, 42, 0.04);
  565. .ant-table-thead > tr > th {
  566. background: #f8fafc;
  567. color: #334155;
  568. font-weight: 600;
  569. }
  570. .ant-table-tbody > tr:nth-child(even) > td {
  571. background: #fcfdff;
  572. }
  573. .ant-table-tbody > tr:hover > td {
  574. background: #eff6ff;
  575. }
  576. }
  577. .query-table-empty {
  578. color: #94a3b8;
  579. background: #ffffff;
  580. }
  581. .pagination-bar {
  582. .pagination-total {
  583. color: #64748b;
  584. }
  585. .pagination-size .ant-select-selector,
  586. .pagination-actions .ant-btn {
  587. border-radius: 6px;
  588. }
  589. .pagination-actions .ant-btn-primary {
  590. min-width: 36px;
  591. }
  592. }
  593. }
  594. </style>