CallRecordModal.vue 20 KB

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