QueryTableModal.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. <template>
  2. <BasicModal
  3. :open="open"
  4. :title="title"
  5. :showOkBtn="false"
  6. :showCancelBtn="false"
  7. :width="width"
  8. :zIndex="zIndex"
  9. :canFullscreen="canFullscreen"
  10. :centered="true"
  11. :wrapClassName="modalWrapClassName"
  12. @update:open="(v) => emit('update:open', v)"
  13. @cancel="emit('update:open', false)"
  14. >
  15. <div ref="wrapRef" class="query-table-wrap">
  16. <div ref="searchRef" class="query-table-search">
  17. <div v-if="switchOptions?.length" class="query-table-switches">
  18. <button
  19. v-for="option in switchOptions"
  20. :key="String(option.value)"
  21. type="button"
  22. class="query-table-switch"
  23. :class="{ active: activeSwitch === option.value, disabled: option.disabled }"
  24. :disabled="option.disabled"
  25. @click="emit('switch-change', option.value)"
  26. >
  27. {{ option.label }}
  28. </button>
  29. </div>
  30. <div
  31. v-for="item in filterSchema"
  32. :key="item.field"
  33. :class="['search-item', { 'search-item--compare-input': item.type === 'compare-input' }]"
  34. >
  35. <span class="label">{{ item.label }}:</span>
  36. <div v-if="item.type === 'compare-input'" class="compare-input-group">
  37. <Select
  38. :value="getFilterValue(item.operatorField)"
  39. :options="item.options ? [...item.options] : []"
  40. popupClassName="query-table-modal-popup"
  41. :getPopupContainer="getModalPopupContainer"
  42. :style="{ width: `${item.operatorWidth || 72}px`, maxWidth: `${item.operatorWidth || 72}px` }"
  43. @update:value="(value) => setFilterValue(item.operatorField, value)"
  44. />
  45. <Input
  46. v-model:value="filters[item.field]"
  47. :placeholder="item.placeholder || ''"
  48. :style="{
  49. width: `${item.inputWidth || 180}px`,
  50. maxWidth: `${item.inputMaxWidth || item.inputWidth || 180}px`,
  51. }"
  52. />
  53. </div>
  54. <Input
  55. v-else-if="item.type === 'input'"
  56. v-model:value="filters[item.field]"
  57. :placeholder="item.placeholder || ''"
  58. :style="{ width: `${item.width || 260}px`, maxWidth: `${item.maxWidth || 300}px` }"
  59. />
  60. <DatePicker
  61. v-else-if="item.type === 'date'"
  62. v-model:value="filters[item.field]"
  63. :placeholder="item.placeholder || ''"
  64. value-format="YYYY-MM-DD"
  65. popupClassName="query-table-modal-popup"
  66. :getPopupContainer="getModalPopupContainer"
  67. :style="{ width: `${item.width || 260}px`, maxWidth: `${item.maxWidth || 300}px` }"
  68. />
  69. <RangePicker
  70. v-else-if="item.type === 'date-range'"
  71. :value="getDateRangeValue(item)"
  72. :placeholder="item.rangePlaceholder || ['开始日期', '结束日期']"
  73. :value-format="item.valueFormat || 'YYYY-MM-DD'"
  74. popupClassName="query-table-modal-popup"
  75. :getPopupContainer="getModalPopupContainer"
  76. :style="{ width: `${item.width || 300}px`, maxWidth: `${item.maxWidth || 360}px` }"
  77. @update:value="(value) => handleDateRangeChange(item, value)"
  78. />
  79. <Select
  80. v-else-if="item.type === 'select'"
  81. v-model:value="filters[item.field]"
  82. :options="item.options ? [...item.options] : []"
  83. popupClassName="query-table-modal-popup"
  84. :getPopupContainer="getModalPopupContainer"
  85. :style="{ width: `${item.width || 260}px`, maxWidth: `${item.maxWidth || 300}px` }"
  86. />
  87. </div>
  88. <div class="search-actions">
  89. <Button type="primary" :loading="loading" @click="emit('search')">查询</Button>
  90. <Button @click="emit('reset')">重置</Button>
  91. </div>
  92. <div v-if="$slots.searchActions || isTableSettingReady" class="search-utility-actions">
  93. <div v-if="$slots.searchActions" class="search-extra-actions">
  94. <slot name="searchActions"></slot>
  95. </div>
  96. <div v-if="isTableSettingReady" class="search-table-setting">
  97. <TableSetting :setting="tableSetting" :getPopupContainer="getTableSettingPopupContainer" />
  98. </div>
  99. </div>
  100. </div>
  101. <div ref="contentRef" class="query-table-content">
  102. <BasicTable
  103. v-if="dataSource?.length"
  104. :columns="columns"
  105. :action-column="actionColumn"
  106. :data-source="dataSource"
  107. :row-selection="tableRowSelection"
  108. :pagination="false"
  109. :show-index-column="showIndexColumn"
  110. :index-column-props="indexColumnProps"
  111. :show-table-setting="false"
  112. :table-setting="tableSetting"
  113. :can-resize="true"
  114. :max-height="tableMaxHeight"
  115. row-key="rowKey"
  116. @register="handleTableRegister"
  117. @change="handleTableChange"
  118. />
  119. <div v-else class="query-table-empty">
  120. <EmptyState variant="compact" :min-height="Math.max(260, tableMaxHeight)" />
  121. </div>
  122. </div>
  123. </div>
  124. <template #footer>
  125. <div ref="paginationRef" class="pagination-bar">
  126. <AnalysisPagination
  127. :total="pagination.total"
  128. :current="pagination.page"
  129. :page-size="pagination.limit"
  130. :page-size-options="pageSizeOptions"
  131. :loading="loading"
  132. @change="handlePaginationChange"
  133. />
  134. </div>
  135. </template>
  136. </BasicModal>
  137. </template>
  138. <script lang="ts" setup>
  139. import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
  140. import type { Dayjs } from 'dayjs';
  141. import { Button, DatePicker, Input, Select } from 'ant-design-vue';
  142. import AnalysisPagination from '@jeesite/core/components/AnalysisPagination/src/AnalysisPagination.vue';
  143. import { EmptyState } from '@jeesite/core/components/EmptyState';
  144. import { BasicTable } from '@jeesite/core/components/Table';
  145. import { BasicModal } from '@jeesite/core/components/Modal';
  146. import TableSetting from '@jeesite/core/components/Table/src/components/settings/index.vue';
  147. import { createTableContext } from '@jeesite/core/components/Table/src/hooks/useTableContext';
  148. import { getModalPopupContainer as resolveModalPopupContainer } from '@jeesite/core/utils';
  149. const RangePicker = DatePicker.RangePicker;
  150. interface FilterItem {
  151. field: string;
  152. label: string;
  153. type: 'input' | 'date' | 'date-range' | 'select' | 'compare-input';
  154. placeholder?: string;
  155. width?: number;
  156. maxWidth?: number;
  157. options?: ReadonlyArray<{ label: string; value: string | number }>;
  158. operatorField?: string;
  159. operatorWidth?: number;
  160. inputWidth?: number;
  161. inputMaxWidth?: number;
  162. startField?: string;
  163. endField?: string;
  164. rangePlaceholder?: [string, string];
  165. valueFormat?: string;
  166. }
  167. interface SwitchOption {
  168. label: string;
  169. value: string | number;
  170. disabled?: boolean;
  171. }
  172. const props = defineProps<{
  173. open: boolean;
  174. title: string;
  175. width?: number;
  176. zIndex?: number;
  177. loading?: boolean;
  178. filters: Record<string, any>;
  179. filterSchema: ReadonlyArray<FilterItem>;
  180. columns: any[];
  181. actionColumn?: any;
  182. dataSource: any[];
  183. rowSelection?: any;
  184. pagination: { page: number; limit: number; total: number };
  185. totalPages: number;
  186. pageSizeOptions: Array<{ label: string | number; value: string | number }>;
  187. showIndexColumn?: boolean;
  188. showTableSetting?: boolean;
  189. tableSetting?: {
  190. size?: boolean;
  191. setting?: boolean;
  192. redo?: boolean;
  193. fullScreen?: boolean;
  194. };
  195. canFullscreen?: boolean;
  196. wrapClassName?: string;
  197. switchOptions?: ReadonlyArray<SwitchOption>;
  198. activeSwitch?: string | number;
  199. }>();
  200. const emit = defineEmits<{
  201. (e: 'update:open', v: boolean): void;
  202. (e: 'search'): void;
  203. (e: 'reset'): void;
  204. (e: 'prev-page'): void;
  205. (e: 'next-page'): void;
  206. (e: 'page-change', page: number, pageSize: number): void;
  207. (e: 'page-size-change', v: number): void;
  208. (e: 'switch-change', v: string | number): void;
  209. (e: 'sort-change', payload: { orderKey: string; sort: 'ASC' | 'DESC' }): void;
  210. }>();
  211. const handlePaginationChange = (page: number, pageSize: number) => {
  212. if (props.loading || (page === props.pagination.page && pageSize === props.pagination.limit)) return;
  213. emit('page-change', page, pageSize);
  214. };
  215. const handleTableChange = (_pagination: any, _filters: any, sorter: any) => {
  216. const currentSorter = Array.isArray(sorter) ? sorter[0] : sorter;
  217. if (!currentSorter?.order) return;
  218. const sort = currentSorter.order === 'ascend' ? 'ASC' : currentSorter.order === 'descend' ? 'DESC' : '';
  219. const orderKey = currentSorter?.column?.key || currentSorter?.field;
  220. if (!sort || !orderKey) return;
  221. emit('sort-change', { orderKey: String(orderKey), sort });
  222. };
  223. const width = props.width ?? 1100;
  224. const zIndex = props.zIndex;
  225. const canFullscreen = props.canFullscreen ?? true;
  226. const showTableSetting = props.showTableSetting ?? false;
  227. const showIndexColumn = ref(props.showIndexColumn ?? false);
  228. const modalWrapClassName = computed(() => ['query-table-modal', props.wrapClassName].filter(Boolean).join(' '));
  229. const wrapRef = ref<HTMLElement | null>(null);
  230. const searchRef = ref<HTMLElement | null>(null);
  231. const contentRef = ref<HTMLElement | null>(null);
  232. const paginationRef = ref<HTMLElement | null>(null);
  233. const tableAction = ref<any>(null);
  234. const tableMaxHeight = ref(420);
  235. const tableRowSelection = ref(props.rowSelection);
  236. const getContentContainer = () => {
  237. const scrollContainer = wrapRef.value?.closest('.scroll-container') as HTMLElement | null;
  238. const modalBody = wrapRef.value?.closest('.ant-modal-body') as HTMLElement | null;
  239. return scrollContainer || modalBody || wrapRef.value?.parentElement || null;
  240. };
  241. const getModalPopupContainer = (triggerNode?: HTMLElement) =>
  242. resolveModalPopupContainer(triggerNode || wrapRef.value || getContentContainer() || undefined);
  243. const getTableSettingPopupContainer = () =>
  244. (wrapRef.value?.closest('.ant-modal-wrap') as HTMLElement | null) ||
  245. (wrapRef.value?.closest('.ant-modal-root') as HTMLElement | null) ||
  246. document.body;
  247. let resizeObserver: ResizeObserver | null = null;
  248. const tableSetting = props.tableSetting ?? {
  249. size: true,
  250. setting: true,
  251. redo: false,
  252. fullScreen: false,
  253. };
  254. const handleTableRegister = (action: any) => {
  255. tableAction.value = action;
  256. scheduleTableHeightUpdate();
  257. };
  258. const isTableSettingReady = computed(() => showTableSetting && !!tableAction.value && !!props.dataSource?.length);
  259. const getTableColumns = (...args: any[]) => tableAction.value?.getColumns?.(...args) || [];
  260. const indexColumnProps = computed(() => ({
  261. width: 70,
  262. customRender: ({ index }: { index: number }) => (props.pagination.page - 1) * props.pagination.limit + index + 1,
  263. }));
  264. const setTableProps = (nextProps: any) => {
  265. const resolvedProps = { ...(nextProps || {}) };
  266. if (Object.prototype.hasOwnProperty.call(resolvedProps, 'showIndexColumn')) {
  267. showIndexColumn.value = resolvedProps.showIndexColumn ?? false;
  268. Reflect.deleteProperty(resolvedProps, 'showIndexColumn');
  269. }
  270. if (Object.prototype.hasOwnProperty.call(resolvedProps, 'rowSelection')) {
  271. tableRowSelection.value = resolvedProps.rowSelection ?? undefined;
  272. }
  273. if (Object.keys(resolvedProps).length) {
  274. tableAction.value?.setProps?.(resolvedProps);
  275. }
  276. };
  277. createTableContext({
  278. wrapRef,
  279. getBindValues: computed(() => ({
  280. columns: getTableColumns({ ignoreIndex: true, ignoreAction: true }),
  281. showIndexColumn: showIndexColumn.value,
  282. rowSelection: tableRowSelection.value,
  283. isTreeTable: false,
  284. })),
  285. getDefaultRowSelection: () => tableAction.value?.getDefaultRowSelection?.(),
  286. getColumns: getTableColumns,
  287. getCacheColumns: () => tableAction.value?.getCacheColumns?.() || [],
  288. setColumns: (nextColumns: any) => tableAction.value?.setColumns?.(nextColumns),
  289. setProps: setTableProps,
  290. } as any);
  291. const getTableExtraHeight = () => {
  292. const tableRoot = wrapRef.value?.querySelector('.query-table-content .jeesite-basic-table') as HTMLElement | null;
  293. if (!tableRoot) return 96;
  294. const toolbarHeight = (tableRoot.querySelector('.jeesite-basic-table-header-container') as HTMLElement | null)
  295. ?.offsetHeight;
  296. const tableHeadHeight = (tableRoot.querySelector('.ant-table-thead') as HTMLElement | null)?.offsetHeight;
  297. const wrapperPaddingHeight = 8;
  298. return (toolbarHeight ?? 0) + (tableHeadHeight ?? 40) + wrapperPaddingHeight;
  299. };
  300. const getWrapGap = () => {
  301. if (!wrapRef.value) return 12;
  302. const wrapStyle = window.getComputedStyle(wrapRef.value);
  303. const gapValue = wrapStyle.rowGap || wrapStyle.gap || '12px';
  304. const gap = Number.parseFloat(gapValue);
  305. return Number.isFinite(gap) ? gap : 12;
  306. };
  307. const getVerticalMargin = (element: HTMLElement | null) => {
  308. if (!element) return 0;
  309. const style = window.getComputedStyle(element);
  310. const marginTop = Number.parseFloat(style.marginTop || '0');
  311. const marginBottom = Number.parseFloat(style.marginBottom || '0');
  312. return (Number.isFinite(marginTop) ? marginTop : 0) + (Number.isFinite(marginBottom) ? marginBottom : 0);
  313. };
  314. const getContentBorderHeight = () => {
  315. if (!contentRef.value) return 2;
  316. return Math.max(0, contentRef.value.offsetHeight - contentRef.value.clientHeight);
  317. };
  318. const getDateRangeValue = (item: FilterItem): [string, string] | undefined => {
  319. if (item.type !== 'date-range' || !item.startField || !item.endField) return undefined;
  320. const startValue = props.filters[item.startField];
  321. const endValue = props.filters[item.endField];
  322. if (!startValue || !endValue) return undefined;
  323. return [String(startValue), String(endValue)];
  324. };
  325. const handleDateRangeChange = (item: FilterItem, value?: [string, string] | [Dayjs, Dayjs] | null) => {
  326. if (item.type !== 'date-range' || !item.startField || !item.endField) return;
  327. const startValue = Array.isArray(value) ? value[0] : undefined;
  328. const endValue = Array.isArray(value) ? value[1] : undefined;
  329. props.filters[item.startField] = typeof startValue === 'string' ? startValue : '';
  330. props.filters[item.endField] = typeof endValue === 'string' ? endValue : '';
  331. };
  332. const getFilterValue = (field?: string) => {
  333. if (!field) return undefined;
  334. return props.filters[field];
  335. };
  336. const setFilterValue = (field: string | undefined, value: unknown) => {
  337. if (!field) return;
  338. props.filters[field] = value;
  339. };
  340. const updateTableMaxHeight = () => {
  341. const contentHeight = getContentContainer()?.clientHeight ?? 0;
  342. if (!contentHeight) return;
  343. const searchHeight = searchRef.value?.offsetHeight ?? 0;
  344. const reservedGap = getWrapGap();
  345. const searchMarginHeight = getVerticalMargin(searchRef.value);
  346. const contentMarginHeight = getVerticalMargin(contentRef.value);
  347. const borderOffset = getContentBorderHeight();
  348. const availableHeight =
  349. contentHeight -
  350. searchHeight -
  351. searchMarginHeight -
  352. reservedGap -
  353. contentMarginHeight -
  354. borderOffset -
  355. getTableExtraHeight();
  356. tableMaxHeight.value = Math.max(260, availableHeight);
  357. };
  358. const scheduleTableHeightUpdate = () => {
  359. nextTick(() => {
  360. requestAnimationFrame(() => {
  361. updateTableMaxHeight();
  362. tableAction.value?.redoHeight?.();
  363. });
  364. });
  365. };
  366. const bindResizeObserver = () => {
  367. if (typeof ResizeObserver === 'undefined') return;
  368. resizeObserver?.disconnect();
  369. resizeObserver = new ResizeObserver(() => {
  370. scheduleTableHeightUpdate();
  371. });
  372. [getContentContainer(), wrapRef.value, searchRef.value, contentRef.value, paginationRef.value].forEach(
  373. (element) => {
  374. if (element) resizeObserver?.observe(element);
  375. },
  376. );
  377. };
  378. watch(
  379. () => props.showIndexColumn,
  380. (value) => {
  381. showIndexColumn.value = value ?? false;
  382. },
  383. );
  384. watch(
  385. () => props.rowSelection,
  386. (value) => {
  387. tableRowSelection.value = value;
  388. },
  389. );
  390. watch(
  391. () => props.open,
  392. (open) => {
  393. if (!open) {
  394. resizeObserver?.disconnect();
  395. return;
  396. }
  397. scheduleTableHeightUpdate();
  398. nextTick(() => {
  399. bindResizeObserver();
  400. scheduleTableHeightUpdate();
  401. });
  402. },
  403. );
  404. watch(
  405. () => props.dataSource?.length,
  406. (length) => {
  407. if (!length) {
  408. tableAction.value = null;
  409. }
  410. if (!props.open) return;
  411. scheduleTableHeightUpdate();
  412. },
  413. { flush: 'post' },
  414. );
  415. onBeforeUnmount(() => {
  416. resizeObserver?.disconnect();
  417. });
  418. </script>
  419. <style lang="less">
  420. .query-table-modal {
  421. .ant-modal {
  422. max-width: calc(100vw - 48px);
  423. }
  424. .ant-modal-body > .scroll-container,
  425. .ant-modal-body > .scroll-container > .scrollbar__wrap,
  426. .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view,
  427. .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view > div {
  428. height: 100%;
  429. min-height: 0 !important;
  430. max-height: none !important;
  431. }
  432. .ant-modal-body > .scroll-container > .scrollbar__wrap {
  433. overflow: hidden;
  434. }
  435. .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view {
  436. margin: 0 !important;
  437. }
  438. .ant-modal-body > .scroll-container > .scrollbar__bar.is-vertical,
  439. .ant-modal-body > .scroll-container > .is-vertical {
  440. display: none !important;
  441. opacity: 0 !important;
  442. pointer-events: none;
  443. }
  444. .ant-modal-close {
  445. display: flex;
  446. align-items: center;
  447. justify-content: flex-end;
  448. width: auto !important;
  449. height: auto !important;
  450. padding: 0;
  451. .jeesite-basic-modal-close {
  452. display: flex;
  453. align-items: center;
  454. gap: 2px;
  455. margin-right: 0;
  456. }
  457. }
  458. .ant-modal-content {
  459. border-radius: 6px;
  460. overflow: hidden;
  461. display: flex;
  462. flex-direction: column;
  463. height: min(720px, calc(100vh - 80px));
  464. max-height: min(720px, calc(100vh - 80px));
  465. }
  466. .query-table-modal-popup,
  467. .ant-select-dropdown,
  468. .ant-picker-dropdown,
  469. .ant-dropdown,
  470. .ant-tooltip {
  471. z-index: 1060;
  472. }
  473. .ant-modal-header {
  474. padding: 12px 16px;
  475. border-bottom: 1px solid #e5e7eb;
  476. flex: 0 0 auto;
  477. background: @modal-header-bg-color;
  478. }
  479. .ant-modal-footer {
  480. padding: 8px 16px 10px;
  481. margin-top: 0;
  482. border-top: 1px solid #e5e7eb;
  483. background: #fff;
  484. }
  485. .ant-modal-body {
  486. flex: 1 1 auto;
  487. min-height: 0;
  488. overflow: hidden;
  489. display: flex;
  490. flex-direction: column;
  491. padding: 16px;
  492. }
  493. }
  494. .fullscreen-modal.query-table-modal {
  495. .ant-modal-content {
  496. max-height: calc(100vh - 16px);
  497. height: calc(100vh - 16px);
  498. }
  499. }
  500. .query-table-wrap {
  501. display: flex;
  502. flex-direction: column;
  503. flex: 1 1 auto;
  504. gap: 12px;
  505. min-height: 0;
  506. min-width: 0;
  507. height: 100%;
  508. }
  509. .query-table-search {
  510. margin: 0;
  511. padding: 14px 16px;
  512. border: 1px solid #e2e8f0;
  513. border-radius: 6px;
  514. background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
  515. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
  516. flex: 0 0 auto;
  517. display: flex;
  518. flex-wrap: wrap;
  519. align-items: center;
  520. gap: 8px 16px;
  521. .query-table-switches {
  522. width: 100%;
  523. display: flex;
  524. align-items: center;
  525. gap: 12px;
  526. margin-bottom: 2px;
  527. }
  528. .query-table-switch {
  529. height: 36px;
  530. padding: 0 18px;
  531. border: 1px solid #dbe3ee;
  532. border-radius: 999px;
  533. background: #fff;
  534. color: #64748b;
  535. font-size: 13px;
  536. font-weight: 600;
  537. cursor: pointer;
  538. transition: all 0.2s ease;
  539. &:hover:not(.disabled) {
  540. color: #2563eb;
  541. border-color: #93c5fd;
  542. }
  543. &.active {
  544. color: #fff;
  545. border-color: #315efb;
  546. background: #315efb;
  547. box-shadow: 0 6px 14px rgba(49, 94, 251, 0.18);
  548. }
  549. &.disabled {
  550. color: #94a3b8;
  551. background: #f8fafc;
  552. cursor: not-allowed;
  553. box-shadow: none;
  554. }
  555. }
  556. .search-item {
  557. display: flex;
  558. align-items: center;
  559. gap: 8px;
  560. .label {
  561. color: #475569;
  562. font-size: 12px;
  563. font-weight: 600;
  564. white-space: nowrap;
  565. }
  566. }
  567. .search-item--compare-input {
  568. .compare-input-group {
  569. display: flex;
  570. align-items: center;
  571. min-width: 0;
  572. .ant-select {
  573. flex: 0 0 auto;
  574. }
  575. .ant-select-selector {
  576. border-top-right-radius: 0 !important;
  577. border-bottom-right-radius: 0 !important;
  578. }
  579. .ant-input,
  580. .ant-input-affix-wrapper {
  581. border-top-left-radius: 0 !important;
  582. border-bottom-left-radius: 0 !important;
  583. margin-left: -1px;
  584. }
  585. }
  586. }
  587. .ant-input,
  588. .ant-input-affix-wrapper,
  589. .ant-picker,
  590. .ant-select-selector {
  591. border-radius: 6px !important;
  592. }
  593. .search-actions {
  594. display: flex;
  595. align-items: center;
  596. gap: 12px;
  597. .ant-btn {
  598. height: 36px;
  599. min-width: 76px;
  600. padding: 0 18px;
  601. border-radius: 18px;
  602. font-size: 15px;
  603. font-weight: 500;
  604. box-shadow: none;
  605. }
  606. .ant-btn-primary {
  607. border-color: #315efb;
  608. background: #315efb;
  609. }
  610. .ant-btn:not(.ant-btn-primary) {
  611. color: #1f2937;
  612. border-color: #d6dbe6;
  613. background: #ffffff;
  614. }
  615. }
  616. .search-utility-actions {
  617. margin-left: auto;
  618. display: flex;
  619. align-items: center;
  620. gap: 10px;
  621. flex-wrap: wrap;
  622. justify-content: flex-end;
  623. }
  624. .search-extra-actions {
  625. display: flex;
  626. align-items: center;
  627. gap: 8px;
  628. flex-wrap: wrap;
  629. }
  630. .search-table-setting {
  631. display: inline-flex;
  632. align-items: center;
  633. padding: 0 2px;
  634. }
  635. }
  636. .query-table-content {
  637. margin: 0;
  638. flex: 1 1 auto;
  639. min-height: 0;
  640. display: flex;
  641. overflow: hidden;
  642. box-sizing: border-box;
  643. border: 0;
  644. border-radius: 0;
  645. background: transparent;
  646. box-shadow: none;
  647. > .jeesite-basic-table,
  648. > .query-table-empty {
  649. flex: 1 1 auto;
  650. min-height: 0;
  651. width: 100%;
  652. }
  653. > .jeesite-basic-table,
  654. > .jeesite-basic-table > .ant-spin-nested-loading,
  655. > .jeesite-basic-table > .ant-spin-nested-loading > .ant-spin-container,
  656. > .jeesite-basic-table .ant-table-wrapper {
  657. height: 100%;
  658. min-height: 0;
  659. }
  660. .ant-table-thead > tr > th {
  661. background: #f8fafc;
  662. color: #334155;
  663. font-weight: 600;
  664. }
  665. .ant-table-tbody > tr:nth-child(even) > td {
  666. background: #fcfdff;
  667. }
  668. .ant-table-tbody > tr:hover > td {
  669. background: #eff6ff;
  670. }
  671. }
  672. .query-table-empty {
  673. display: flex;
  674. align-items: center;
  675. justify-content: center;
  676. background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
  677. }
  678. .pagination-bar {
  679. min-height: 32px;
  680. display: flex;
  681. align-items: center;
  682. justify-content: flex-end;
  683. gap: 12px;
  684. color: #6b7280;
  685. font-size: 12px;
  686. flex-wrap: nowrap;
  687. .pagination-total {
  688. margin-right: 0;
  689. color: #64748b;
  690. white-space: nowrap;
  691. }
  692. .pagination-size {
  693. display: flex;
  694. align-items: center;
  695. gap: 6px;
  696. white-space: nowrap;
  697. .ant-select {
  698. width: 86px;
  699. }
  700. .ant-select-selector {
  701. border-radius: 6px;
  702. }
  703. }
  704. .pagination-actions {
  705. display: flex;
  706. align-items: center;
  707. gap: 6px;
  708. white-space: nowrap;
  709. .page-total {
  710. color: #6b7280;
  711. min-width: 28px;
  712. text-align: center;
  713. }
  714. .ant-btn {
  715. border-radius: 6px;
  716. }
  717. .ant-btn-primary {
  718. min-width: 36px;
  719. }
  720. }
  721. }
  722. </style>