| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 |
- <template>
- <QueryTableModal
- :open="open"
- :title="title"
- :width="1120"
- :loading="loading"
- :filters="filters"
- :filter-schema="filterSchema"
- :columns="columns"
- :action-column="actionColumn"
- :data-source="tableData"
- :pagination="pagination"
- :total-pages="totalPages"
- :page-size-options="pageSizeOptions"
- :show-table-setting="false"
- :table-setting="{ size: true, setting: true, redo: false, fullScreen: false }"
- :can-fullscreen="true"
- wrap-class-name="case-clean-log-modal"
- @update:open="handleOpenChange"
- @search="handleSearch"
- @reset="handleReset"
- @prev-page="handlePrevPage"
- @next-page="handleNextPage"
- @page-size-change="handlePageSizeChange"
- @page-change="handlePageChange"
- @sort-change="handleSortChange"
- />
- <BasicModal
- :open="detailOpen"
- title="日志详情"
- :width="860"
- :showOkBtn="false"
- :showCancelBtn="false"
- wrapClassName="case-clean-log-detail-modal"
- @update:open="handleDetailOpenChange"
- @cancel="handleDetailOpenChange(false)"
- >
- <div class="case-clean-log-detail">
- <div class="case-clean-log-detail__meta">
- <div class="meta-item">
- <span class="label">文件名称</span>
- <span class="value">{{ getDisplayText(detailRecord?.fileName) }}</span>
- </div>
- <div class="meta-item">
- <span class="label">工作表</span>
- <span class="value">{{ getDisplayText(detailRecord?.sheetName) }}</span>
- </div>
- <div class="meta-item">
- <span class="label">记录时间</span>
- <span class="value">{{ getDisplayText(detailRecord?.createDateTime) }}</span>
- </div>
- </div>
- <div class="case-clean-log-detail__content">
- <div class="content-title">日志内容</div>
- <div class="content-body">{{ getDisplayText(detailRecord?.content) }}</div>
- </div>
- </div>
- </BasicModal>
- </template>
- <script lang="ts" setup name="ViewsDataCleanLogModal">
- import { computed, h, nextTick, reactive, ref, watch } from 'vue';
- import componentSetting from '@jeesite/core/settings/componentSetting';
- import { BasicModal } from '@jeesite/core/components/Modal';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- import type { FileInfo } from '@jeesite/types/api';
- import QueryTableModal from '@jeesite/case/views/dm/components/QueryTableModal.vue';
- import type { CleanErrorLogItem } from '@jeesite/case/api/govern/governApi';
- import { cleanErrorLogList } from '@jeesite/case/api/govern/governApi';
- const ALL_SCOPE = '__all__';
- const DEFAULT_SORT_KEY = 'createDateTime';
- const DEFAULT_SORT_ORDER = 'DESC' as const;
- const LOG_FETCH_LIMIT = 500;
- interface ImportSheetScope {
- key: string;
- label: string;
- querySheetId?: string;
- }
- interface ImportFileScope {
- key: string;
- label: string;
- queryFileId?: string;
- sheets: ImportSheetScope[];
- }
- interface LogQueryTarget {
- fileId?: string;
- sheetId?: string;
- }
- interface LogTableRow {
- rowKey: string;
- id: number;
- fileName: string;
- sheetName: string;
- content: string;
- createDateTime: string;
- raw: CleanErrorLogItem;
- }
- const props = withDefaults(
- defineProps<{
- open: boolean;
- title?: string;
- batchId?: string | number;
- fileInfos?: FileInfo[];
- }>(),
- {
- title: '清洗日志',
- batchId: '',
- fileInfos: () => [],
- },
- );
- const emit = defineEmits<{
- (e: 'update:open', value: boolean): void;
- }>();
- const { createMessage } = useMessage();
- const loading = ref(false);
- const sourceLogs = ref<CleanErrorLogItem[]>([]);
- const tableData = ref<LogTableRow[]>([]);
- const detailOpen = ref(false);
- const detailRecord = ref<CleanErrorLogItem | null>(null);
- const filters = reactive({
- fileScopeId: ALL_SCOPE,
- sheetScopeId: ALL_SCOPE,
- orderKey: DEFAULT_SORT_KEY,
- sort: DEFAULT_SORT_ORDER as '' | 'ASC' | 'DESC',
- });
- const pagination = reactive({
- page: 1,
- limit: componentSetting.table.defaultPageSize,
- total: 0,
- });
- const pageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
- label: value,
- value,
- }));
- const normalizeText = (value: unknown) => String(value ?? '').trim();
- const normalizeId = (value: unknown) => normalizeText(value);
- const normalizeBatchId = (value: unknown) => normalizeText(value);
- const isRootItem = (item?: FileInfo | null) => {
- const parentId = normalizeId(item?.pid);
- return !parentId || parentId === '0' || parentId === 'null' || parentId === 'undefined';
- };
- const getDisplayText = (value: unknown, fallback = '-') => {
- const text = normalizeText(value);
- return text || fallback;
- };
- const createFileScope = (key: string, label: string, queryFileId?: string): ImportFileScope => ({
- key,
- label,
- queryFileId,
- sheets: [],
- });
- const findBatchId = (items?: FileInfo[] | null): string | undefined => {
- if (!Array.isArray(items) || !items.length) return undefined;
- for (const item of items) {
- const batchId = normalizeBatchId(item?.batchId);
- if (batchId) return batchId;
- const childBatchId = findBatchId(Array.isArray(item?.children) ? item.children : []);
- if (childBatchId) return childBatchId;
- }
- return undefined;
- };
- const explicitBatchId = computed(() => normalizeBatchId(props.batchId));
- const currentBatchId = computed(() => explicitBatchId.value || findBatchId(props.fileInfos));
- const importedScopes = computed<ImportFileScope[]>(() => {
- const scopes: ImportFileScope[] = [];
- const scopeMap = new Map<string, ImportFileScope>();
- const rootScopeMap = new Map<string, ImportFileScope>();
- const looseSheets: FileInfo[] = [];
- const ensureFileScope = (key: string, label: string, queryFileId?: string) => {
- const normalizedKey = key || `file_${scopeMap.size + 1}`;
- const matched = scopeMap.get(normalizedKey);
- if (matched) {
- if (!matched.queryFileId && queryFileId) matched.queryFileId = queryFileId;
- if (!normalizeText(matched.label) && normalizeText(label)) matched.label = label;
- return matched;
- }
- const nextScope = createFileScope(normalizedKey, label || `文件${scopeMap.size + 1}`, queryFileId);
- scopeMap.set(normalizedKey, nextScope);
- scopes.push(nextScope);
- if (queryFileId) {
- rootScopeMap.set(queryFileId, nextScope);
- }
- return nextScope;
- };
- const ensureSheetScope = (
- scope: ImportFileScope,
- key: string,
- label: string,
- querySheetId?: string,
- fallbackIndex = 0,
- ) => {
- const normalizedKey = key || `${scope.key}_sheet_${fallbackIndex}`;
- const matched = scope.sheets.find((item) => item.key === normalizedKey);
- if (matched) {
- if (!matched.querySheetId && querySheetId) matched.querySheetId = querySheetId;
- if (!normalizeText(matched.label) && normalizeText(label)) matched.label = label;
- return matched;
- }
- const nextSheet = {
- key: normalizedKey,
- label: label || `工作表${scope.sheets.length + 1}`,
- querySheetId,
- };
- scope.sheets.push(nextSheet);
- return nextSheet;
- };
- const registerNestedNode = (item: FileInfo, parentScope?: ImportFileScope, fallbackIndex = 0) => {
- const itemId = normalizeId(item.id);
- const fileLabel = normalizeText(item.fileName || item.sheetName) || `文件${fallbackIndex + 1}`;
- const sheetLabel = normalizeText(item.sheetName || item.fileName) || `工作表${fallbackIndex + 1}`;
- const children = Array.isArray(item.children) ? item.children : [];
- if (!parentScope && (isRootItem(item) || children.length > 0)) {
- const fileScope = ensureFileScope(itemId || `file_${fallbackIndex + 1}`, fileLabel, itemId || undefined);
- children.forEach((child, childIndex) => registerNestedNode(child, fileScope, childIndex));
- return;
- }
- if (parentScope) {
- ensureSheetScope(parentScope, itemId, sheetLabel, itemId || undefined, fallbackIndex);
- children.forEach((child, childIndex) => registerNestedNode(child, parentScope, childIndex));
- return;
- }
- looseSheets.push(item);
- };
- (props.fileInfos || []).forEach((item, index) => registerNestedNode(item, undefined, index));
- looseSheets.forEach((item, index) => {
- const parentId = normalizeId(item.pid);
- const querySheetId = normalizeId(item.id) || undefined;
- const fileLabel = normalizeText(item.fileName) || `文件${index + 1}`;
- const sheetLabel = normalizeText(item.sheetName || item.fileName) || `工作表${index + 1}`;
- const matchedScope =
- (parentId && rootScopeMap.get(parentId)) ||
- ensureFileScope(parentId || fileLabel, fileLabel, parentId || undefined);
- ensureSheetScope(
- matchedScope,
- querySheetId || `${matchedScope.key}_sheet_${index}`,
- sheetLabel,
- querySheetId,
- index,
- );
- });
- return scopes;
- });
- const isSingleFileScope = computed(() => importedScopes.value.length === 1);
- const fileOptions = computed(() => [
- ...(isSingleFileScope.value ? [] : [{ label: '当前任务全部文件', value: ALL_SCOPE }]),
- ...importedScopes.value.map((item) => ({
- label: item.label,
- value: item.key,
- })),
- ]);
- const selectedFileScope = computed(
- () => importedScopes.value.find((item) => item.key === filters.fileScopeId) || null,
- );
- const defaultFileScopeId = computed(() =>
- isSingleFileScope.value ? importedScopes.value[0]?.key || ALL_SCOPE : ALL_SCOPE,
- );
- const sheetOptions = computed(() => {
- if (!selectedFileScope.value) {
- return [{ label: '全部工作表', value: ALL_SCOPE }];
- }
- return [
- { label: '全部工作表', value: ALL_SCOPE },
- ...selectedFileScope.value.sheets.map((item) => ({
- label: item.label,
- value: item.key,
- })),
- ];
- });
- const filterSchema = computed(
- () =>
- [
- {
- field: 'fileScopeId',
- label: '文件',
- type: 'select',
- width: 260,
- maxWidth: 320,
- options: fileOptions.value,
- },
- {
- field: 'sheetScopeId',
- label: '工作表',
- type: 'select',
- width: 220,
- maxWidth: 280,
- options: sheetOptions.value,
- },
- ] as const,
- );
- const renderTableText = (value: unknown, className?: string) => {
- const text = getDisplayText(value);
- return h(
- 'span',
- {
- class: className,
- title: text,
- },
- text,
- );
- };
- const columns = [
- { title: '序号', dataIndex: 'id', key: 'id', width: 68 },
- {
- title: '文件名称',
- dataIndex: 'fileName',
- key: 'fileName',
- width: 220,
- customRender: ({ text }: { text: string }) => renderTableText(text),
- },
- {
- title: '工作表',
- dataIndex: 'sheetName',
- key: 'sheetName',
- width: 180,
- customRender: ({ text }: { text: string }) => renderTableText(text),
- },
- {
- title: '日志内容',
- dataIndex: 'content',
- key: 'content',
- width: 460,
- customRender: ({ text }: { text: string }) => renderTableText(text, 'log-content-cell'),
- },
- {
- title: '记录时间',
- dataIndex: 'createDateTime',
- key: 'createDateTime',
- width: 180,
- sorter: true,
- customRender: ({ text }: { text: string }) => renderTableText(text),
- },
- ];
- const actionColumn = {
- title: '操作',
- dataIndex: 'action',
- key: 'action',
- width: 92,
- fixed: 'right',
- align: 'center',
- actions: (record: LogTableRow) => [
- {
- label: '详情',
- onClick: () => {
- detailRecord.value = record.raw;
- detailOpen.value = true;
- },
- },
- ],
- };
- const totalPages = computed(() => {
- if (!pagination.total) return 1;
- return Math.max(1, Math.ceil(pagination.total / pagination.limit));
- });
- const normalizeList = (response: any): CleanErrorLogItem[] => {
- if (Array.isArray(response)) return response;
- if (Array.isArray(response?.records)) return response.records;
- if (Array.isArray(response?.data?.records)) return response.data.records;
- if (Array.isArray(response?.list)) return response.list;
- if (Array.isArray(response?.data?.list)) return response.data.list;
- return [];
- };
- const createTargetKey = (target: LogQueryTarget) => `${target.fileId || ''}__${target.sheetId || ''}`;
- const buildTargetsByScope = (scope: ImportFileScope, sheetScopeKey?: string) => {
- if (sheetScopeKey && sheetScopeKey !== ALL_SCOPE) {
- const matchedSheet = scope.sheets.find((item) => item.key === sheetScopeKey);
- if (!matchedSheet) return [] as LogQueryTarget[];
- return [
- {
- fileId: scope.queryFileId,
- sheetId: matchedSheet.querySheetId,
- },
- ].filter((item) => item.fileId || item.sheetId);
- }
- if (scope.queryFileId) {
- return [{ fileId: scope.queryFileId }];
- }
- return scope.sheets
- .map((item) => ({
- sheetId: item.querySheetId,
- }))
- .filter((item) => item.sheetId);
- };
- const resolveRequestTargets = () => {
- if (selectedFileScope.value) {
- return buildTargetsByScope(selectedFileScope.value, filters.sheetScopeId);
- }
- const targetMap = new Map<string, LogQueryTarget>();
- importedScopes.value.forEach((scope) => {
- buildTargetsByScope(scope).forEach((target) => {
- targetMap.set(createTargetKey(target), target);
- });
- });
- return targetMap.size ? Array.from(targetMap.values()) : [{}];
- };
- const matchesTarget = (item: CleanErrorLogItem, target: LogQueryTarget) => {
- const fileId = normalizeId(item.fileId);
- const sheetId = normalizeId(item.sheetId);
- if (target.fileId && target.sheetId) {
- return fileId === target.fileId && sheetId === target.sheetId;
- }
- if (target.fileId) {
- return fileId === target.fileId;
- }
- if (target.sheetId) {
- return sheetId === target.sheetId;
- }
- return true;
- };
- const getSortNumber = (value: unknown) => {
- const timeValue = value ? new Date(String(value)).getTime() : Number.NaN;
- return Number.isFinite(timeValue) ? timeValue : 0;
- };
- const compareText = (left: unknown, right: unknown) =>
- getDisplayText(left).localeCompare(getDisplayText(right), 'zh-CN', { numeric: true, sensitivity: 'base' });
- const filteredLogs = computed(() => {
- let logs = sourceLogs.value.slice();
- if (selectedFileScope.value) {
- const targets = buildTargetsByScope(selectedFileScope.value, filters.sheetScopeId);
- if (targets.length) {
- logs = logs.filter((item) => targets.some((target) => matchesTarget(item, target)));
- }
- }
- const sortKey = filters.orderKey || DEFAULT_SORT_KEY;
- const sortOrder = filters.sort || DEFAULT_SORT_ORDER;
- logs.sort((left, right) => {
- let compareResult = 0;
- if (sortKey === 'createDateTime') {
- compareResult = getSortNumber(left.createDateTime) - getSortNumber(right.createDateTime);
- } else {
- compareResult = compareText((left as any)?.[sortKey], (right as any)?.[sortKey]);
- }
- return sortOrder === 'ASC' ? compareResult : -compareResult;
- });
- return logs;
- });
- const buildRowKey = (item: CleanErrorLogItem, absoluteIndex: number) =>
- normalizeId(item.id) ||
- [item.fileId, item.sheetId, item.createDateTime, absoluteIndex].map((value) => normalizeText(value)).join('_');
- const syncVisibleRows = () => {
- pagination.total = filteredLogs.value.length;
- const maxPage = Math.max(1, Math.ceil(Math.max(1, pagination.total) / pagination.limit));
- if (pagination.page > maxPage) {
- pagination.page = maxPage;
- }
- const startIndex = (pagination.page - 1) * pagination.limit;
- tableData.value = filteredLogs.value.slice(startIndex, startIndex + pagination.limit).map((item, index) => ({
- rowKey: buildRowKey(item, startIndex + index),
- id: startIndex + index + 1,
- fileName: getDisplayText(item.fileName),
- sheetName: getDisplayText(item.sheetName),
- content: getDisplayText(item.content),
- createDateTime: getDisplayText(item.createDateTime),
- raw: item,
- }));
- };
- const resetFilters = () => {
- filters.fileScopeId = defaultFileScopeId.value;
- filters.sheetScopeId = ALL_SCOPE;
- filters.orderKey = DEFAULT_SORT_KEY;
- filters.sort = DEFAULT_SORT_ORDER;
- };
- const fetchLogs = async () => {
- loading.value = true;
- detailOpen.value = false;
- try {
- const batchId = currentBatchId.value;
- const responses = batchId
- ? [
- await cleanErrorLogList({
- page: 1,
- limit: LOG_FETCH_LIMIT,
- batchId,
- }),
- ]
- : await Promise.all(
- // The backend only supports a single fileId/sheetId per request, so current-task logs
- // are aggregated client-side from the imported files when batchId is unavailable.
- resolveRequestTargets().map((target) =>
- cleanErrorLogList({
- page: 1,
- limit: LOG_FETCH_LIMIT,
- ...target,
- }),
- ),
- );
- const logMap = new Map<string, CleanErrorLogItem>();
- responses.forEach((response) => {
- normalizeList(response).forEach((item, index) => {
- const key =
- normalizeId(item.id) ||
- [item.fileId, item.sheetId, item.createDateTime, normalizeText(item.content), index].join('_');
- if (!logMap.has(key)) {
- logMap.set(key, item);
- }
- });
- });
- sourceLogs.value = Array.from(logMap.values());
- } catch (error) {
- sourceLogs.value = [];
- createMessage.error('日志加载失败');
- } finally {
- loading.value = false;
- }
- };
- const handleSearch = async () => {
- pagination.page = 1;
- await fetchLogs();
- };
- const handleReset = async () => {
- resetFilters();
- pagination.page = 1;
- await fetchLogs();
- };
- const handlePrevPage = () => {
- if (pagination.page <= 1) return;
- pagination.page -= 1;
- };
- const handleNextPage = () => {
- if (pagination.page >= totalPages.value) return;
- pagination.page += 1;
- };
- const handlePageChange = (page: number, pageSize: number) => {
- if (loading.value) return;
- if (pageSize !== pagination.limit) {
- handlePageSizeChange(pageSize);
- return;
- }
- if (page === pagination.page - 1) {
- handlePrevPage();
- return;
- }
- if (page === pagination.page + 1) {
- handleNextPage();
- return;
- }
- if (page === pagination.page) return;
- pagination.page = page;
- };
- const handlePageSizeChange = (size: number) => {
- pagination.limit = Number(size);
- pagination.page = 1;
- };
- const handleSortChange = (payload: { orderKey: string; sort: 'ASC' | 'DESC' }) => {
- filters.orderKey = payload.orderKey;
- filters.sort = payload.sort;
- pagination.page = 1;
- };
- const handleOpenChange = (value: boolean) => {
- if (!value) {
- handleDetailOpenChange(false);
- }
- emit('update:open', value);
- };
- const handleDetailOpenChange = (value: boolean) => {
- detailOpen.value = value;
- if (!value) {
- detailRecord.value = null;
- }
- };
- let initFetchScheduled = false;
- const scheduleInitFetch = () => {
- if (!props.open || initFetchScheduled) return;
- initFetchScheduled = true;
- nextTick(async () => {
- initFetchScheduled = false;
- if (!props.open) return;
- resetFilters();
- pagination.page = 1;
- await fetchLogs();
- });
- };
- watch(
- () => props.open,
- (open) => {
- if (!open) {
- initFetchScheduled = false;
- detailOpen.value = false;
- return;
- }
- scheduleInitFetch();
- },
- );
- watch(
- () => props.fileInfos,
- () => {
- filters.fileScopeId = defaultFileScopeId.value;
- if (filters.sheetScopeId !== ALL_SCOPE) {
- const hasMatchedSheet = sheetOptions.value.some((item) => item.value === filters.sheetScopeId);
- if (!hasMatchedSheet) {
- filters.sheetScopeId = ALL_SCOPE;
- }
- }
- if (!props.open) return;
- scheduleInitFetch();
- },
- { deep: true },
- );
- watch(
- () => props.batchId,
- () => {
- if (!props.open) return;
- scheduleInitFetch();
- },
- );
- watch(
- () => filters.fileScopeId,
- () => {
- filters.sheetScopeId = ALL_SCOPE;
- },
- );
- watch(
- [filteredLogs, () => pagination.page, () => pagination.limit],
- () => {
- syncVisibleRows();
- },
- { immediate: true },
- );
- defineExpose({
- reload: fetchLogs,
- });
- </script>
- <style lang="less">
- .case-clean-log-modal {
- .ant-modal {
- padding-bottom: 0;
- }
- .ant-modal-content {
- border-radius: 14px;
- background: linear-gradient(180deg, #fdfefe 0%, #ffffff 100%);
- box-shadow: 0 18px 42px rgba(15, 23, 42, 0.14);
- }
- .ant-modal-header {
- padding: 12px 16px;
- background: @modal-header-bg-color;
- border-bottom-color: #e5e7eb;
- }
- .ant-modal-title {
- color: #111827;
- font-size: 16px;
- font-weight: 600;
- line-height: 24px;
- }
- .ant-modal-body {
- padding: 16px;
- }
- .ant-modal-footer {
- padding: 8px 16px 10px;
- border-top-color: #e5e7eb;
- background: #fff;
- }
- .query-table-search {
- background: linear-gradient(180deg, #fafcff 0%, #ffffff 100%);
- border-color: #e2e8f0;
- }
- .query-table-content {
- .ant-table-thead > tr > th {
- background: #f8fafc;
- color: #334155;
- }
- .ant-table-tbody > tr > td {
- vertical-align: top;
- }
- .ant-table-tbody > tr:hover > td {
- background: #f8fbff;
- }
- }
- .log-content-cell {
- display: -webkit-box;
- overflow: hidden;
- white-space: normal;
- word-break: break-word;
- line-height: 1.6;
- color: #7f1d1d;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- }
- }
- .case-clean-log-detail-modal {
- .ant-modal-content {
- border-radius: 14px;
- overflow: hidden;
- }
- .ant-modal-header {
- padding: 12px 16px;
- border-bottom-color: #e5e7eb;
- background: @modal-header-bg-color;
- }
- .ant-modal-title {
- color: #111827;
- font-size: 16px;
- font-weight: 600;
- line-height: 24px;
- }
- .ant-modal-body {
- padding: 16px;
- }
- }
- .case-clean-log-detail {
- display: flex;
- flex-direction: column;
- gap: 16px;
- &__meta {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 12px;
- }
- &__content {
- padding: 16px;
- border: 1px solid #e5e7eb;
- border-radius: 12px;
- background: #f8fafc;
- }
- .meta-item {
- padding: 14px 16px;
- border: 1px solid #e5e7eb;
- border-radius: 12px;
- background: #fff;
- display: flex;
- flex-direction: column;
- gap: 6px;
- min-width: 0;
- }
- .label {
- color: #64748b;
- font-size: 12px;
- font-weight: 500;
- }
- .value {
- color: #111827;
- font-size: 13px;
- line-height: 1.6;
- word-break: break-word;
- }
- .content-title {
- margin-bottom: 8px;
- color: #475569;
- font-size: 12px;
- font-weight: 600;
- }
- .content-body {
- color: #7f1d1d;
- font-size: 13px;
- line-height: 1.8;
- white-space: pre-wrap;
- word-break: break-word;
- }
- @media (max-width: 768px) {
- &__meta {
- grid-template-columns: 1fr;
- }
- }
- }
- </style>
|