feat: 重构知识库文档导入任务化流程
- 新增上传建单、异步解析、分块处理与异步向量化闭环 - 收口分享页权限、完成态检索过滤与 SSE 局部状态刷新
This commit is contained in:
@@ -1,36 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@easyflow/locales';
|
||||
import { downloadFileFromBlob } from '@easyflow/utils';
|
||||
|
||||
import { Delete, Download, MoreFilled } from '@element-plus/icons-vue';
|
||||
import {
|
||||
CloseBold,
|
||||
Delete,
|
||||
Download,
|
||||
Files,
|
||||
Loading,
|
||||
Opportunity,
|
||||
Select,
|
||||
UploadFilled,
|
||||
} from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElButton,
|
||||
ElDropdown,
|
||||
ElDropdownItem,
|
||||
ElDropdownMenu,
|
||||
ElIcon,
|
||||
ElImage,
|
||||
ElMessage,
|
||||
ElMessageBox,
|
||||
ElProgress,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { buildKnowledgeShareUrl } from '#/api/knowledge-share';
|
||||
import { api, SseClient } from '#/api/request';
|
||||
import documentIcon from '#/assets/ai/knowledge/document.svg';
|
||||
import PageData from '#/components/page/PageData.vue';
|
||||
import { buildKnowledgePath } from '#/views/ai/documentCollection/share-path';
|
||||
|
||||
interface DocumentStatusPayload {
|
||||
completedChunks?: number;
|
||||
documentId?: number | string;
|
||||
failedChunks?: number;
|
||||
knowledgeId?: number | string;
|
||||
lastTaskError?: string;
|
||||
processStatus?: string;
|
||||
progressPercent?: number;
|
||||
taskModifiedAt?: string;
|
||||
totalChunks?: number;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface DocumentTablePermissions {
|
||||
canCreateContent?: boolean;
|
||||
canDeleteContent?: boolean;
|
||||
canDownloadContent?: boolean;
|
||||
}
|
||||
|
||||
type PermissionKey = keyof DocumentTablePermissions;
|
||||
|
||||
const props = defineProps({
|
||||
knowledgeId: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
manageable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
requestClient: {
|
||||
type: Object as any,
|
||||
default: () => api,
|
||||
@@ -39,19 +68,283 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
permissions: {
|
||||
type: Object as () => DocumentTablePermissions,
|
||||
default: () => ({
|
||||
canCreateContent: true,
|
||||
canDeleteContent: true,
|
||||
canDownloadContent: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['viewDoc']);
|
||||
|
||||
const emits = defineEmits(['viewDoc', 'continueProcess']);
|
||||
|
||||
const STREAM_RECONNECT_DELAY = 1500;
|
||||
const STREAM_RELOAD_DELAY = 250;
|
||||
|
||||
const pageDataRef = ref();
|
||||
const taskStatusStreamClient = new SseClient();
|
||||
let reconnectTimer: null | ReturnType<typeof setTimeout> = null;
|
||||
let reloadTimer: null | ReturnType<typeof setTimeout> = null;
|
||||
let disposed = false;
|
||||
|
||||
defineExpose({
|
||||
reload() {
|
||||
pageDataRef.value?.reload?.();
|
||||
},
|
||||
search(searchText: string) {
|
||||
pageDataRef.value.setQuery({
|
||||
pageDataRef.value?.setQuery?.({
|
||||
title: searchText,
|
||||
});
|
||||
},
|
||||
});
|
||||
const pageDataRef = ref();
|
||||
|
||||
const processingStatuses = new Set(['INDEXING', 'PARSING']);
|
||||
|
||||
const isProcessingStatus = (status?: string) =>
|
||||
processingStatuses.has(status || '');
|
||||
|
||||
const resolvedPermissions = computed(() => ({
|
||||
canCreateContent: props.permissions?.canCreateContent ?? true,
|
||||
canDeleteContent: props.permissions?.canDeleteContent ?? true,
|
||||
canDownloadContent: props.permissions?.canDownloadContent ?? true,
|
||||
}));
|
||||
|
||||
const hasPermission = (key: PermissionKey) => Boolean(resolvedPermissions.value[key]);
|
||||
|
||||
const statusMetaMap: Record<
|
||||
string,
|
||||
{
|
||||
icon: Component;
|
||||
toneClass: string;
|
||||
}
|
||||
> = {
|
||||
COMPLETED: {
|
||||
icon: Select,
|
||||
toneClass: 'status-pill--success',
|
||||
},
|
||||
INDEX_FAILED: {
|
||||
icon: CloseBold,
|
||||
toneClass: 'status-pill--danger',
|
||||
},
|
||||
INDEXING: {
|
||||
icon: Loading,
|
||||
toneClass: 'status-pill--warning',
|
||||
},
|
||||
PARSE_FAILED: {
|
||||
icon: CloseBold,
|
||||
toneClass: 'status-pill--danger',
|
||||
},
|
||||
PARSING: {
|
||||
icon: Loading,
|
||||
toneClass: 'status-pill--warning',
|
||||
},
|
||||
READY_FOR_INDEX: {
|
||||
icon: Opportunity,
|
||||
toneClass: 'status-pill--primary',
|
||||
},
|
||||
READY_FOR_SEGMENT: {
|
||||
icon: Files,
|
||||
toneClass: 'status-pill--primary',
|
||||
},
|
||||
UPLOADED: {
|
||||
icon: UploadFilled,
|
||||
toneClass: 'status-pill--info',
|
||||
},
|
||||
};
|
||||
|
||||
const getStatusLabel = (status?: string) =>
|
||||
$t(`documentCollection.taskStatus.${status || 'UPLOADED'}`);
|
||||
|
||||
const getStatusMeta = (status?: string) =>
|
||||
statusMetaMap[status || 'UPLOADED'] || statusMetaMap.UPLOADED;
|
||||
|
||||
const getChunkCount = (row: any) => {
|
||||
const totalChunks = Number(row.totalChunks || 0);
|
||||
if (totalChunks > 0) {
|
||||
return totalChunks;
|
||||
}
|
||||
return Number(row.chunkCount || 0);
|
||||
};
|
||||
|
||||
const getProgressText = (row: any) => {
|
||||
const completed = Number(row.completedChunks || 0);
|
||||
const total = Number(row.totalChunks || 0);
|
||||
if (total <= 0) {
|
||||
return `${Number(row.progressPercent || 0)}%`;
|
||||
}
|
||||
return `${Number(row.progressPercent || 0)}% · ${completed}/${total}`;
|
||||
};
|
||||
|
||||
const clearReconnectTimer = () => {
|
||||
if (!reconnectTimer) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
};
|
||||
|
||||
const clearReloadTimer = () => {
|
||||
if (!reloadTimer) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = null;
|
||||
};
|
||||
|
||||
const scheduleReload = () => {
|
||||
if (reloadTimer) {
|
||||
return;
|
||||
}
|
||||
reloadTimer = setTimeout(() => {
|
||||
reloadTimer = null;
|
||||
pageDataRef.value?.reload?.();
|
||||
}, STREAM_RELOAD_DELAY);
|
||||
};
|
||||
|
||||
const patchDocumentRow = (payload: DocumentStatusPayload) => {
|
||||
if (!payload.documentId) {
|
||||
return false;
|
||||
}
|
||||
const nextPatch: Record<string, any> = {
|
||||
completedChunks: payload.completedChunks,
|
||||
failedChunks: payload.failedChunks,
|
||||
lastTaskError: payload.lastTaskError,
|
||||
processStatus: payload.processStatus,
|
||||
progressPercent: payload.progressPercent,
|
||||
taskModifiedAt: payload.taskModifiedAt,
|
||||
totalChunks: payload.totalChunks,
|
||||
};
|
||||
if (payload.taskModifiedAt) {
|
||||
nextPatch.modified = payload.taskModifiedAt;
|
||||
}
|
||||
return (
|
||||
pageDataRef.value?.patchRowById?.(payload.documentId, nextPatch) ?? false
|
||||
);
|
||||
};
|
||||
|
||||
const buildTaskStreamUrl = () => {
|
||||
const path = buildKnowledgePath(
|
||||
props.endpointPrefix,
|
||||
'/api/v1/document/import/task/stream',
|
||||
);
|
||||
return props.endpointPrefix ? buildKnowledgeShareUrl(path) : path;
|
||||
};
|
||||
|
||||
const scheduleStreamReconnect = () => {
|
||||
if (disposed || reconnectTimer) {
|
||||
return;
|
||||
}
|
||||
reconnectTimer = setTimeout(() => {
|
||||
reconnectTimer = null;
|
||||
openTaskStatusStream();
|
||||
}, STREAM_RECONNECT_DELAY);
|
||||
};
|
||||
|
||||
const handleTaskStatusMessage = (message: {
|
||||
data?: string;
|
||||
event?: string;
|
||||
}) => {
|
||||
if (!message?.data) {
|
||||
return;
|
||||
}
|
||||
if (message.event && message.event !== 'document-status') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = JSON.parse(message.data) as DocumentStatusPayload;
|
||||
if (payload?.type !== 'document-status') {
|
||||
return;
|
||||
}
|
||||
if (String(payload.knowledgeId || '') !== String(props.knowledgeId || '')) {
|
||||
return;
|
||||
}
|
||||
if (patchDocumentRow(payload)) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// 文档状态流约定始终返回 JSON;异常负载直接忽略,避免误触发列表刷新。
|
||||
return;
|
||||
}
|
||||
scheduleReload();
|
||||
};
|
||||
|
||||
const openTaskStatusStream = async () => {
|
||||
if (!props.knowledgeId) {
|
||||
return;
|
||||
}
|
||||
taskStatusStreamClient.abort();
|
||||
clearReconnectTimer();
|
||||
await taskStatusStreamClient.post(
|
||||
buildTaskStreamUrl(),
|
||||
{
|
||||
knowledgeId: props.knowledgeId,
|
||||
},
|
||||
{
|
||||
onMessage: handleTaskStatusMessage,
|
||||
onError: () => {
|
||||
if (!disposed) {
|
||||
scheduleStreamReconnect();
|
||||
}
|
||||
},
|
||||
onFinished: () => {
|
||||
if (!disposed) {
|
||||
scheduleStreamReconnect();
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const ensurePermission = (key: PermissionKey) => {
|
||||
if (hasPermission(key)) {
|
||||
return true;
|
||||
}
|
||||
ElMessage.warning($t('documentCollection.managePermissionHint'));
|
||||
return false;
|
||||
};
|
||||
|
||||
const requestTaskAction = async (
|
||||
path: string,
|
||||
payload: Record<string, any>,
|
||||
successMessage: string,
|
||||
) => {
|
||||
if (!ensurePermission('canCreateContent')) {
|
||||
return;
|
||||
}
|
||||
const res = await props.requestClient.post(
|
||||
buildKnowledgePath(props.endpointPrefix, path),
|
||||
payload,
|
||||
);
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(successMessage);
|
||||
pageDataRef.value?.reload?.();
|
||||
}
|
||||
};
|
||||
|
||||
const handleContinue = (row: any) => {
|
||||
if (!ensurePermission('canCreateContent')) {
|
||||
return;
|
||||
}
|
||||
emits('continueProcess', row);
|
||||
};
|
||||
|
||||
const handleRetryParse = async (row: any) => {
|
||||
await requestTaskAction(
|
||||
'/api/v1/document/import/task/retryParse',
|
||||
{
|
||||
knowledgeId: props.knowledgeId,
|
||||
documentId: row.id,
|
||||
},
|
||||
getStatusLabel('PARSING'),
|
||||
);
|
||||
};
|
||||
|
||||
const handleView = (row: any) => {
|
||||
emits('viewDoc', row.id);
|
||||
};
|
||||
|
||||
const handleDownload = async (row: any) => {
|
||||
const blob = await props.requestClient.download(
|
||||
buildKnowledgePath(
|
||||
@@ -64,30 +357,108 @@ const handleDownload = async (row: any) => {
|
||||
source: blob,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (row: any) => {
|
||||
if (!props.manageable) {
|
||||
ElMessage.warning($t('documentCollection.managePermissionHint'));
|
||||
if (!ensurePermission('canDeleteContent')) {
|
||||
return;
|
||||
}
|
||||
if (processingStatuses.has(row.processStatus)) {
|
||||
ElMessage.warning($t('documentCollection.processingDeleteBlocked'));
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), {
|
||||
confirmButtonText: $t('button.confirm'),
|
||||
cancelButtonText: $t('button.cancel'),
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
props.requestClient
|
||||
.post(
|
||||
buildKnowledgePath(props.endpointPrefix, '/api/v1/document/removeDoc'),
|
||||
{ id: row.id },
|
||||
)
|
||||
.then((res: any) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success($t('message.deleteOkMessage'));
|
||||
pageDataRef.value.setQuery({ id: props.knowledgeId });
|
||||
}
|
||||
});
|
||||
// 删除逻辑
|
||||
}).then(async () => {
|
||||
const res = await props.requestClient.post(
|
||||
buildKnowledgePath(props.endpointPrefix, '/api/v1/document/removeDoc'),
|
||||
{ id: row.id },
|
||||
);
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success($t('message.deleteOkMessage'));
|
||||
pageDataRef.value?.reload?.();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const primaryActionConfigs: Record<
|
||||
string,
|
||||
{
|
||||
handler: (row: any) => void;
|
||||
label: () => string;
|
||||
permission?: PermissionKey;
|
||||
}
|
||||
> = {
|
||||
COMPLETED: {
|
||||
handler: handleView,
|
||||
label: () => $t('button.viewSegmentation'),
|
||||
},
|
||||
INDEX_FAILED: {
|
||||
handler: handleContinue,
|
||||
label: () => $t('button.continueProcess'),
|
||||
permission: 'canCreateContent',
|
||||
},
|
||||
PARSE_FAILED: {
|
||||
handler: handleRetryParse,
|
||||
label: () => $t('button.retryParse'),
|
||||
permission: 'canCreateContent',
|
||||
},
|
||||
READY_FOR_INDEX: {
|
||||
handler: handleContinue,
|
||||
label: () => $t('button.continueProcess'),
|
||||
permission: 'canCreateContent',
|
||||
},
|
||||
READY_FOR_SEGMENT: {
|
||||
handler: handleContinue,
|
||||
label: () => $t('button.continueProcess'),
|
||||
permission: 'canCreateContent',
|
||||
},
|
||||
};
|
||||
|
||||
const getPrimaryActionLabel = (row: any) => {
|
||||
const config = primaryActionConfigs[row.processStatus || ''];
|
||||
if (!config) {
|
||||
return '';
|
||||
}
|
||||
if (config.permission && !hasPermission(config.permission)) {
|
||||
return '';
|
||||
}
|
||||
return config.label();
|
||||
};
|
||||
|
||||
const handlePrimaryAction = (row: any) => {
|
||||
const config = primaryActionConfigs[row.processStatus || ''];
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
if (config.permission && !hasPermission(config.permission)) {
|
||||
return;
|
||||
}
|
||||
config.handler(row);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
disposed = false;
|
||||
openTaskStatusStream();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
disposed = true;
|
||||
clearReconnectTimer();
|
||||
clearReloadTimer();
|
||||
taskStatusStreamClient.abort();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => `${props.endpointPrefix}:${props.knowledgeId}`,
|
||||
() => {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
openTaskStatusStream();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -109,6 +480,9 @@ const handleDelete = (row: any) => {
|
||||
<ElTableColumn
|
||||
prop="fileName"
|
||||
:label="$t('documentCollection.fileName')"
|
||||
min-width="220"
|
||||
align="left"
|
||||
header-align="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="file-name-container">
|
||||
@@ -119,19 +493,23 @@ const handleDelete = (row: any) => {
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
|
||||
<ElTableColumn
|
||||
prop="documentType"
|
||||
:label="$t('documentCollection.documentType')"
|
||||
width="180"
|
||||
/>
|
||||
<ElTableColumn
|
||||
prop="chunkCount"
|
||||
:label="$t('documentCollection.knowledgeCount')"
|
||||
width="180"
|
||||
/>
|
||||
:label="$t('documentCollection.chunkCount')"
|
||||
width="96"
|
||||
align="left"
|
||||
header-align="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ getChunkCount(row) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
|
||||
<ElTableColumn
|
||||
:label="$t('documentCollection.createdModifyTime')"
|
||||
width="200"
|
||||
width="176"
|
||||
align="left"
|
||||
header-align="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="time-container">
|
||||
@@ -140,34 +518,101 @@ const handleDelete = (row: any) => {
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn :label="$t('common.handle')" width="120" align="right">
|
||||
|
||||
<ElTableColumn
|
||||
:label="$t('documentCollection.processStatus')"
|
||||
min-width="156"
|
||||
align="left"
|
||||
header-align="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-3">
|
||||
<ElButton link type="primary" @click="handleView(row)">
|
||||
{{ $t('button.viewSegmentation') }}
|
||||
<div class="status-cell">
|
||||
<div
|
||||
class="status-pill"
|
||||
:class="getStatusMeta(row.processStatus).toneClass"
|
||||
>
|
||||
<span class="status-pill__icon-shell">
|
||||
<ElIcon
|
||||
class="status-pill__icon"
|
||||
:class="
|
||||
isProcessingStatus(row.processStatus)
|
||||
? 'status-pill__icon--spinning'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<component :is="getStatusMeta(row.processStatus).icon" />
|
||||
</ElIcon>
|
||||
</span>
|
||||
<span class="status-pill__label">
|
||||
{{ getStatusLabel(row.processStatus) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="row.processStatus === 'INDEXING'"
|
||||
class="status-progress"
|
||||
>
|
||||
<ElProgress
|
||||
:percentage="Number(row.progressPercent || 0)"
|
||||
:stroke-width="8"
|
||||
/>
|
||||
<span class="status-progress__text">
|
||||
{{ getProgressText(row) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="row.lastTaskError"
|
||||
class="status-error"
|
||||
:title="row.lastTaskError"
|
||||
>
|
||||
{{ row.lastTaskError }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
|
||||
<ElTableColumn
|
||||
:label="$t('common.handle')"
|
||||
width="148"
|
||||
align="left"
|
||||
header-align="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="action-cell">
|
||||
<ElButton
|
||||
v-if="getPrimaryActionLabel(row)"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePrimaryAction(row)"
|
||||
>
|
||||
{{ getPrimaryActionLabel(row) }}
|
||||
</ElButton>
|
||||
|
||||
<ElDropdown>
|
||||
<ElButton link :icon="MoreFilled" />
|
||||
<ElTooltip
|
||||
v-if="hasPermission('canDownloadContent')"
|
||||
:content="$t('button.download')"
|
||||
placement="top"
|
||||
>
|
||||
<ElButton
|
||||
link
|
||||
:icon="Download"
|
||||
:aria-label="$t('button.download')"
|
||||
@click="handleDownload(row)"
|
||||
/>
|
||||
</ElTooltip>
|
||||
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem @click="handleDownload(row)">
|
||||
<ElButton link :icon="Download">
|
||||
{{ $t('button.download') }}
|
||||
</ElButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem
|
||||
v-if="props.manageable"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<ElButton link :icon="Delete" type="danger">
|
||||
{{ $t('button.delete') }}
|
||||
</ElButton>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
<ElTooltip
|
||||
v-if="hasPermission('canDeleteContent')"
|
||||
:content="$t('button.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<ElButton
|
||||
link
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
:aria-label="$t('button.delete')"
|
||||
@click="handleDelete(row)"
|
||||
/>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
@@ -180,21 +625,166 @@ const handleDelete = (row: any) => {
|
||||
.time-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.file-name-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: #1a1a1a;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.status-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.status-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: min(168px, 100%);
|
||||
}
|
||||
|
||||
.status-progress__text {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
max-width: 176px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--el-color-danger);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
min-height: 30px;
|
||||
padding: 4px 12px 4px 8px;
|
||||
border: 1px solid var(--status-pill-border);
|
||||
border-radius: 999px;
|
||||
background: var(--status-pill-bg);
|
||||
color: var(--status-pill-text);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
.status-pill__icon-shell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: var(--status-pill-icon-bg);
|
||||
}
|
||||
|
||||
.status-pill__icon {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--status-pill-icon-color);
|
||||
}
|
||||
|
||||
.status-pill__label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.status-pill__icon--spinning {
|
||||
animation: status-spin 1.15s linear infinite;
|
||||
}
|
||||
|
||||
.status-pill--primary {
|
||||
--status-pill-bg: var(--el-color-primary-light-9);
|
||||
--status-pill-border: var(--el-color-primary-light-7);
|
||||
--status-pill-icon-bg: var(--el-color-primary-light-8);
|
||||
--status-pill-icon-color: var(--el-color-primary);
|
||||
--status-pill-text: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.status-pill--success {
|
||||
--status-pill-bg: var(--el-color-success-light-9);
|
||||
--status-pill-border: var(--el-color-success-light-7);
|
||||
--status-pill-icon-bg: var(--el-color-success-light-8);
|
||||
--status-pill-icon-color: var(--el-color-success);
|
||||
--status-pill-text: var(--el-color-success);
|
||||
}
|
||||
|
||||
.status-pill--warning {
|
||||
--status-pill-bg: var(--el-color-warning-light-9);
|
||||
--status-pill-border: var(--el-color-warning-light-7);
|
||||
--status-pill-icon-bg: var(--el-color-warning-light-8);
|
||||
--status-pill-icon-color: var(--el-color-warning);
|
||||
--status-pill-text: var(--el-color-warning);
|
||||
}
|
||||
|
||||
.status-pill--danger {
|
||||
--status-pill-bg: var(--el-color-danger-light-9);
|
||||
--status-pill-border: var(--el-color-danger-light-7);
|
||||
--status-pill-icon-bg: var(--el-color-danger-light-8);
|
||||
--status-pill-icon-color: var(--el-color-danger);
|
||||
--status-pill-text: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.status-pill--info {
|
||||
--status-pill-bg: var(--el-fill-color-light);
|
||||
--status-pill-border: var(--el-border-color-light);
|
||||
--status-pill-icon-bg: var(--el-fill-color);
|
||||
--status-pill-icon-color: var(--el-text-color-secondary);
|
||||
--status-pill-text: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-cell :deep(.el-button) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-cell :deep(.el-button + .el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@keyframes status-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user