importList.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. <template>
  2. <div :class="prefixCls" class="case-import-list">
  3. <BasicTable class="import-list-table" @register="registerTable" @fetch-success="handleFetchSuccess">
  4. <template #toolbar>
  5. <div class="table-toolbar">
  6. <div class="table-title-meta">
  7. <span class="table-meta-chip">当前页 {{ currentPageFileCount }} 条</span>
  8. <span class="table-meta-chip is-selected">已选 {{ selectedRowKeysRef.length }} 条</span>
  9. </div>
  10. <Popconfirm title="是否确认删除选中的导入记录?" @confirm="handleDeleteSelected()">
  11. <a-button v-if="selectedRowKeysRef.length > 0" danger class="batch-delete-btn" type="default">
  12. <Icon icon="i-ant-design:delete-outlined" color="error" /> 删除 ({{ selectedRowKeysRef.length }})
  13. </a-button>
  14. </Popconfirm>
  15. </div>
  16. </template>
  17. </BasicTable>
  18. <div class="pagination-bar">
  19. <AnalysisPagination
  20. :total="pagination.total"
  21. :current="pagination.current"
  22. :page-size="pagination.pageSize"
  23. :page-size-options="pageSizeOptions"
  24. :loading="paginationLoading"
  25. @change="handlePaginationChange"
  26. />
  27. </div>
  28. <CleanLogModal v-model:open="logModalOpen" title="导入失败日志" :file-infos="currentLogFileInfos" />
  29. </div>
  30. </template>
  31. <script lang="ts" setup name="ViewsSysEmpUserList">
  32. import { h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
  33. import type { PropType } from 'vue';
  34. import { Popconfirm } from 'ant-design-vue';
  35. import AnalysisPagination from '@jeesite/core/components/AnalysisPagination/src/AnalysisPagination.vue';
  36. import { Icon } from '@jeesite/core/components/Icon';
  37. import { BasicTable, BasicColumn, useTable } from '@jeesite/core/components/Table';
  38. import { useDesign } from '@jeesite/core/hooks/web/useDesign';
  39. import { useGo } from '@jeesite/core/hooks/web/usePage';
  40. import { useMessage } from '@jeesite/core/hooks/web/useMessage';
  41. import { useEmitter } from '@jeesite/core/store/modules/case';
  42. import { PageEnum } from '@jeesite/core/enums/pageEnum';
  43. import type { FormProps } from '@jeesite/core/components/Form';
  44. import type { FileInfo } from '@jeesite/types';
  45. import { importFileList, deletedFileList, preFile } from '@jeesite/case/api/govern/governApi';
  46. import { openLocalFileApi } from '@jeesite/core/api/sys/login';
  47. import { FileStatusEnum } from '@jeesite/case/types/enum';
  48. import componentSetting from '@jeesite/core/settings/componentSetting';
  49. import CleanLogModal from './components/CleanLogModal.vue';
  50. import { resetImportProgressSession } from './importProgressSession';
  51. const props = defineProps({
  52. treeCodes: Array as PropType<string[]>,
  53. treeName: String,
  54. active: {
  55. type: Boolean,
  56. default: false,
  57. },
  58. });
  59. const { prefixCls } = useDesign('case-import-list');
  60. const go = useGo();
  61. const emitter = useEmitter();
  62. const { createMessage } = useMessage();
  63. const CASE_DATA_REFRESH_EVENT = 'case-data:refresh';
  64. const CASE_IMPORT_FILE_REFRESH_EVENT = 'case-import-file:refresh';
  65. const FILE_INFO_STORAGE_KEY = 'case_clean_file_infos';
  66. const IMPORT_FILE_INFO_STORAGE_KEY = 'case_clean_import_file_infos';
  67. const RECLEAN_CONTEXT_STORAGE_KEY = 'case_clean_reclean_context';
  68. const PRE_FILE_EMPTY_MESSAGE = '未识别到可处理文件,可能是文件重复、所有文件不存在,或文件格式不正确';
  69. const ctrlPermi = ref('');
  70. const currentPageFileCount = ref(0);
  71. const logModalOpen = ref(false);
  72. const currentLogFileInfos = ref<FileInfo[]>([]);
  73. const hasLoadedOnActivate = ref(false);
  74. const recleaningFileIds = ref<string[]>([]);
  75. const paginationLoading = ref(false);
  76. const pagination = ref({
  77. current: 1,
  78. pageSize: Number(componentSetting.table.defaultPageSize),
  79. total: 0,
  80. });
  81. const pageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
  82. label: value,
  83. value: Number(value),
  84. }));
  85. const normalizeSearchParams = (params: Recordable = {}) => {
  86. const nextParams: Recordable = { ...params };
  87. Object.keys(nextParams).forEach((key) => {
  88. const value = nextParams[key];
  89. if (value === '' || value === undefined || value === null || (Array.isArray(value) && value.length === 0)) {
  90. delete nextParams[key];
  91. }
  92. });
  93. return nextParams;
  94. };
  95. const handleSearchSubmit = async () => {
  96. const formValues = tableAction.getForm()?.getFieldsValue?.() || {};
  97. await tableAction.reload({
  98. searchInfo: normalizeSearchParams(formValues),
  99. page: 1,
  100. });
  101. };
  102. const searchForm: FormProps<FileInfo> = {
  103. baseColProps: { md: 12, lg: 8, xl: 6 },
  104. labelWidth: 90,
  105. showAdvancedButton: false,
  106. submitFunc: handleSearchSubmit,
  107. schemas: [
  108. {
  109. label: '文件名称:',
  110. field: 'fileName',
  111. component: 'Input',
  112. componentProps: {
  113. placeholder: '请输入文件名称',
  114. },
  115. },
  116. {
  117. label: '上传时间:',
  118. field: 'createDateTime',
  119. component: 'DatePicker',
  120. componentProps: {
  121. format: 'YYYY-MM-DD',
  122. valueFormat: 'YYYY-MM-DD',
  123. placeholder: '请选择上传时间',
  124. },
  125. },
  126. ],
  127. };
  128. const getText = (value: unknown, fallback = '-') => {
  129. const text = String(value ?? '').trim();
  130. return text ? text : fallback;
  131. };
  132. const getNumberText = (value: unknown) => {
  133. const num = Number(value ?? 0);
  134. return Number.isFinite(num) ? String(num) : '0';
  135. };
  136. const fileStatusLabelMap = Object.entries(FileStatusEnum).reduce(
  137. (acc, [key, label]) => {
  138. acc[key] = label;
  139. return acc;
  140. },
  141. {} as Record<string, string>,
  142. );
  143. const getGovernStatusLabel = (status?: string | null) => {
  144. const normalized = String(status || '').trim();
  145. if (!normalized) return '未治理';
  146. return fileStatusLabelMap[normalized] || normalized;
  147. };
  148. const getErrorMessage = (error: any, fallback = '操作失败') =>
  149. error?.response?.data?.message || error?.message || fallback;
  150. const tableColumns: BasicColumn<FileInfo>[] = [
  151. {
  152. title: '序号',
  153. key: 'sort',
  154. width: 76,
  155. align: 'center',
  156. customRender: ({ index }: { index: number }) => h('span', { class: 'table-cell-center' }, String(index + 1)),
  157. },
  158. {
  159. title: '文件名称',
  160. dataIndex: 'fileName',
  161. key: 'fileName',
  162. width: 240,
  163. align: 'center',
  164. ellipsis: true,
  165. customRender: ({ record }: { record: FileInfo }) =>
  166. h(
  167. 'span',
  168. {
  169. class: 'file-name-text',
  170. title: getText(record.fileName || record.sheetName, '未命名文件'),
  171. },
  172. getText(record.fileName || record.sheetName, '未命名文件'),
  173. ),
  174. },
  175. {
  176. title: '导入成功',
  177. dataIndex: 'successNum',
  178. key: 'successNum',
  179. width: 120,
  180. align: 'center',
  181. customRender: ({ value }: { value?: string | number }) =>
  182. h('span', { class: 'table-cell-center' }, getNumberText(value)),
  183. },
  184. {
  185. title: '导入失败',
  186. dataIndex: 'failNum',
  187. key: 'failNum',
  188. width: 120,
  189. align: 'center',
  190. customRender: ({ record }: { record: FileInfo }) =>
  191. h(
  192. 'a',
  193. {
  194. class: 'fail-link',
  195. onClick: (event: MouseEvent) => handleViewFailLog(event, record),
  196. },
  197. getNumberText(record.failNum),
  198. ),
  199. },
  200. {
  201. title: '治理状态',
  202. dataIndex: 'fileStatus',
  203. key: 'fileStatus',
  204. width: 120,
  205. align: 'center',
  206. customRender: ({ value }: { value?: string | null }) =>
  207. h('span', { class: 'govern-status-text' }, getGovernStatusLabel(value)),
  208. },
  209. {
  210. title: '上传时间',
  211. dataIndex: 'createDateTime',
  212. key: 'createDateTime',
  213. width: 180,
  214. align: 'center',
  215. customRender: ({ value }: { value?: string }) => h('span', { class: 'table-cell-center' }, getText(value)),
  216. },
  217. ];
  218. const actionColumn: BasicColumn<FileInfo> = {
  219. title: '操作',
  220. width: 196,
  221. actions: (record: FileInfo) => [
  222. {
  223. label: '重新清洗',
  224. title: '重新清洗',
  225. disabled: recleaningFileIds.value.includes(String(record.id ?? '').trim()),
  226. onClick: handleRecognizeImport.bind(this, record),
  227. },
  228. {
  229. label: '删除',
  230. title: '删除',
  231. color: 'error',
  232. popConfirm: {
  233. title: '是否确认删除文件',
  234. confirm: handleDelete.bind(this, record),
  235. },
  236. },
  237. {
  238. label: '打开',
  239. title: '打开',
  240. onClick: handleOpenFile.bind(this, record),
  241. },
  242. ],
  243. };
  244. const selectedRowKeysRef = ref<string[]>([]);
  245. const rowSelection = {
  246. type: 'checkbox',
  247. columnWidth: 48,
  248. onChange: (selectedRowKeys: string[]) => {
  249. selectedRowKeysRef.value = selectedRowKeys;
  250. },
  251. };
  252. const [registerTable, tableAction] = useTable<FileInfo>({
  253. api: importFileList,
  254. beforeFetch: (params) => {
  255. const nextParams = normalizeSearchParams(params);
  256. nextParams.ctrlPermi = ctrlPermi.value;
  257. return nextParams;
  258. },
  259. afterFetch: (items: FileInfo[]) => {
  260. currentPageFileCount.value = Array.isArray(items) ? items.length : 0;
  261. return items;
  262. },
  263. immediate: false,
  264. columns: tableColumns,
  265. actionColumn,
  266. formConfig: searchForm,
  267. showTableSetting: true,
  268. useSearchForm: true,
  269. showSearchForm: true,
  270. canResize: true,
  271. isCanResizeParent: true,
  272. resizeHeightOffset: 32,
  273. clickToRowSelect: false,
  274. rowKey: 'id',
  275. rowSelection,
  276. defaultRowSelection: rowSelection,
  277. pagination: { position: ['none'] },
  278. });
  279. const { clearSelectedRowKeys, reload, redoHeight } = tableAction;
  280. const handleFetchSuccess = ({ total }: { total?: number }) => {
  281. const tablePagination = tableAction.getPagination();
  282. if (typeof tablePagination === 'boolean') return;
  283. pagination.value.current = Number(tablePagination.current) || 1;
  284. pagination.value.pageSize = Number(tablePagination.pageSize || tablePagination.defaultPageSize) || 1;
  285. pagination.value.total = Number(total ?? tablePagination.total) || 0;
  286. };
  287. const handlePaginationChange = async (page: number, pageSize: number) => {
  288. if (paginationLoading.value || (page === pagination.value.current && pageSize === pagination.value.pageSize))
  289. return;
  290. const nextPage = pageSize === pagination.value.pageSize ? page : 1;
  291. paginationLoading.value = true;
  292. try {
  293. tableAction.setPagination({ current: nextPage, pageSize });
  294. await reload({ page: nextPage });
  295. } finally {
  296. paginationLoading.value = false;
  297. }
  298. };
  299. const syncTableLayout = () => {
  300. if (!props.active) return;
  301. requestAnimationFrame(() => {
  302. redoHeight();
  303. });
  304. };
  305. const handleImportListRefresh = () => {
  306. hasLoadedOnActivate.value = true;
  307. syncTableLayout();
  308. void reload();
  309. };
  310. watch(
  311. () => props.active,
  312. (active) => {
  313. if (!active) return;
  314. if (!hasLoadedOnActivate.value) {
  315. handleImportListRefresh();
  316. return;
  317. }
  318. syncTableLayout();
  319. },
  320. { flush: 'post' },
  321. );
  322. onMounted(() => {
  323. emitter.on(CASE_DATA_REFRESH_EVENT, handleImportListRefresh);
  324. emitter.on(CASE_IMPORT_FILE_REFRESH_EVENT, handleImportListRefresh);
  325. });
  326. onBeforeUnmount(() => {
  327. emitter.off(CASE_DATA_REFRESH_EVENT, handleImportListRefresh, true);
  328. emitter.off(CASE_IMPORT_FILE_REFRESH_EVENT, handleImportListRefresh, true);
  329. });
  330. async function handleDelete(record: Partial<FileInfo>) {
  331. const id = String(record.id ?? '').trim();
  332. if (!id) {
  333. createMessage.warning('未获取到文件ID,暂时无法删除');
  334. return;
  335. }
  336. try {
  337. await deletedFileList({ ids: [id] });
  338. await handleSuccess('文件删除成功');
  339. } catch (error: any) {
  340. createMessage.error(getErrorMessage(error, '文件删除失败'));
  341. }
  342. }
  343. async function handleRecognizeImport(record: Partial<FileInfo>) {
  344. const filePath = String(record.filePath ?? '').trim();
  345. const fileId = String(record.id ?? '').trim();
  346. if (!filePath) {
  347. createMessage.warning('未获取到文件路径,暂时无法重新清洗');
  348. return;
  349. }
  350. if (!fileId) {
  351. createMessage.warning('未获取到文件ID,暂时无法重新清洗');
  352. return;
  353. }
  354. if (recleaningFileIds.value.includes(fileId)) {
  355. return;
  356. }
  357. try {
  358. recleaningFileIds.value = [...recleaningFileIds.value, fileId];
  359. const response = await preFile({
  360. filePath: [filePath],
  361. isDir: false,
  362. reClean: 1,
  363. });
  364. const fileInfos = Array.isArray(response) ? response : ((response as any)?.data ?? []);
  365. if (!Array.isArray(fileInfos) || !fileInfos.length) {
  366. createMessage.warning(PRE_FILE_EMPTY_MESSAGE);
  367. return;
  368. }
  369. sessionStorage.setItem(FILE_INFO_STORAGE_KEY, JSON.stringify(fileInfos));
  370. sessionStorage.removeItem(IMPORT_FILE_INFO_STORAGE_KEY);
  371. sessionStorage.setItem(
  372. RECLEAN_CONTEXT_STORAGE_KEY,
  373. JSON.stringify({
  374. reClean: 1,
  375. fileId,
  376. }),
  377. );
  378. resetImportProgressSession();
  379. await go({
  380. path: PageEnum.BASE_CLEANING,
  381. query: {
  382. focusKey: fileId,
  383. },
  384. });
  385. } catch (error: any) {
  386. createMessage.error(getErrorMessage(error, '进入重新清洗失败'));
  387. } finally {
  388. recleaningFileIds.value = recleaningFileIds.value.filter((id) => id !== fileId);
  389. }
  390. }
  391. async function handleOpenFile(record: Partial<FileInfo>) {
  392. const filePath = String(record.filePath ?? '').trim();
  393. if (!filePath) {
  394. createMessage.warning('未获取到文件路径,暂时无法打开文件');
  395. return;
  396. }
  397. try {
  398. const response = await openLocalFileApi(filePath);
  399. const result = String((response as any)?.result ?? '').toLowerCase();
  400. const code = String((response as any)?.code ?? '').toLowerCase();
  401. const success = result === 'true' || result === 'success' || code === '200' || code === '0';
  402. if (!success) {
  403. createMessage.warning((response as any)?.message || '打开文件失败');
  404. }
  405. } catch (error: any) {
  406. createMessage.error(getErrorMessage(error, '打开文件失败'));
  407. }
  408. }
  409. function handleViewFailLog(event: MouseEvent, record: Partial<FileInfo>) {
  410. event.preventDefault();
  411. event.stopPropagation();
  412. const failCount = Number(record.failNum ?? 0);
  413. if (!Number.isFinite(failCount) || failCount <= 0) {
  414. createMessage.info('当前文件暂无失败记录');
  415. return;
  416. }
  417. const logFileInfo = { ...(record as FileInfo), pid: '0', children: [] as FileInfo[] };
  418. delete (logFileInfo as Recordable).batchId;
  419. currentLogFileInfos.value = [logFileInfo];
  420. logModalOpen.value = true;
  421. }
  422. async function handleDeleteSelected() {
  423. if (!selectedRowKeysRef.value.length) {
  424. createMessage.warning('请先选择需要删除的文件');
  425. return;
  426. }
  427. try {
  428. const ids = selectedRowKeysRef.value.map((item) => String(item)).filter(Boolean);
  429. await deletedFileList({ ids });
  430. await handleSuccess(`已删除 ${ids.length} 条导入记录`);
  431. } catch (error: any) {
  432. createMessage.error(getErrorMessage(error, '批量删除失败'));
  433. }
  434. }
  435. async function handleSuccess(message?: string) {
  436. selectedRowKeysRef.value = [];
  437. clearSelectedRowKeys();
  438. if (message) {
  439. createMessage.success(message);
  440. }
  441. await reload();
  442. }
  443. </script>
  444. <style lang="less">
  445. @prefix-cls: ~'jeesite-case-import-list';
  446. .@{prefix-cls}.case-import-list {
  447. height: 100%;
  448. min-height: 0;
  449. display: flex;
  450. flex-direction: column;
  451. .import-list-table .jeesite-basic-table-header__title {
  452. display: none;
  453. }
  454. .import-list-table .jeesite-basic-table-header__toolbar {
  455. width: 100%;
  456. justify-content: space-between;
  457. margin-top: -2px;
  458. margin-bottom: 8px;
  459. > * {
  460. margin-right: 0;
  461. }
  462. }
  463. .import-list-table .jeesite-basic-table-header__toolbar > .ant-btn {
  464. display: none;
  465. }
  466. .table-toolbar {
  467. display: flex;
  468. align-items: center;
  469. flex: 1 1 auto;
  470. gap: 10px;
  471. min-width: 0;
  472. flex-wrap: wrap;
  473. }
  474. .table-title-meta {
  475. display: flex;
  476. align-items: center;
  477. gap: 8px;
  478. flex-wrap: wrap;
  479. }
  480. .table-meta-chip {
  481. display: inline-flex;
  482. align-items: center;
  483. height: 24px;
  484. padding: 0 10px;
  485. border-radius: 999px;
  486. background: #f3f6fb;
  487. color: #475569;
  488. font-size: 12px;
  489. line-height: 24px;
  490. &.is-selected {
  491. background: #eef4ff;
  492. color: #315efb;
  493. }
  494. }
  495. .batch-delete-btn {
  496. height: 34px;
  497. padding: 0 14px;
  498. border-radius: 8px;
  499. }
  500. .import-list-table,
  501. .import-list-table.jeesite-basic-table {
  502. flex: 1 1 auto;
  503. min-height: 0;
  504. height: 100%;
  505. overflow: hidden;
  506. display: flex;
  507. flex-direction: column;
  508. .jeesite-basic-table {
  509. height: 100%;
  510. display: flex;
  511. flex-direction: column;
  512. }
  513. }
  514. .import-list-table .ant-table-wrapper {
  515. flex: 1 1 auto;
  516. min-height: 0;
  517. display: flex;
  518. flex-direction: column;
  519. padding: 0 6px 14px !important;
  520. box-sizing: border-box;
  521. }
  522. .import-list-table .ant-spin-nested-loading,
  523. .import-list-table .ant-spin-container,
  524. .import-list-table .ant-table,
  525. .import-list-table .ant-table-container {
  526. flex: 1 1 auto;
  527. min-height: 0;
  528. display: flex;
  529. flex-direction: column;
  530. }
  531. .import-list-table .ant-table-body,
  532. .import-list-table .ant-table-content {
  533. flex: 1 1 auto;
  534. min-height: 0;
  535. }
  536. .table-cell-center,
  537. .file-name-text,
  538. .govern-status-text {
  539. display: inline-block;
  540. max-width: 100%;
  541. color: #374151;
  542. font-size: 12px;
  543. line-height: 1.5;
  544. overflow: hidden;
  545. text-overflow: ellipsis;
  546. white-space: nowrap;
  547. vertical-align: middle;
  548. }
  549. .fail-link {
  550. color: #315efb;
  551. font-size: 12px;
  552. line-height: 1.5;
  553. text-decoration: underline;
  554. &:hover {
  555. color: #1d4ed8;
  556. }
  557. }
  558. .ant-form {
  559. padding: 12px 16px 0;
  560. border: 1px solid #e5e7eb;
  561. border-radius: 12px;
  562. background: #fafafa;
  563. margin-bottom: 12px;
  564. .ant-form-item {
  565. margin-bottom: 12px;
  566. }
  567. .ant-input,
  568. .ant-picker,
  569. .ant-input-affix-wrapper {
  570. border-radius: 8px;
  571. }
  572. }
  573. .ant-table-wrapper {
  574. border-radius: 12px;
  575. box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
  576. }
  577. .ant-table-container {
  578. border-radius: 12px;
  579. overflow: hidden;
  580. }
  581. .ant-table-thead > tr > th {
  582. background: #f8fafc;
  583. color: #374151;
  584. font-weight: 600;
  585. border-bottom-color: #e5e7eb;
  586. }
  587. .ant-table-tbody > tr > td {
  588. padding-top: 10px;
  589. padding-bottom: 10px;
  590. color: #4b5563;
  591. border-bottom-color: #eef2f7;
  592. text-align: center;
  593. }
  594. .ant-table-tbody > tr:hover > td {
  595. background: #f8fbff;
  596. }
  597. .jeesite-basic-table-action {
  598. justify-content: center;
  599. gap: 10px;
  600. flex-wrap: nowrap;
  601. .ant-btn-link,
  602. .ant-btn-link.ant-btn-sm {
  603. height: auto;
  604. padding: 0;
  605. color: #374151;
  606. font-size: 12px;
  607. line-height: 1.5;
  608. &:hover {
  609. color: #1d4ed8;
  610. }
  611. }
  612. .ant-btn-link.ant-btn-error,
  613. .ant-btn-link.ant-btn-error.ant-btn-sm {
  614. color: @error-color;
  615. &:hover,
  616. &:focus {
  617. color: lighten(@error-color, 10%);
  618. }
  619. &:active {
  620. color: darken(@error-color, 10%);
  621. }
  622. }
  623. }
  624. .pagination-bar {
  625. display: flex;
  626. align-items: center;
  627. justify-content: flex-end;
  628. flex: 0 0 auto;
  629. min-height: 40px;
  630. padding: 0 6px 6px;
  631. overflow: hidden;
  632. box-sizing: border-box;
  633. }
  634. }
  635. </style>