mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
feat(workflow): 简化工作流卡片并适配编辑器主题
- 工作流卡片去掉按节点进度,改为底部通栏全局进度条,已执行次数合并到执行状态行 - 组件选择栏与边设置面板改为主题变量契约,适配透明/玻璃主题毛玻璃材质
This commit is contained in:
@@ -158,7 +158,6 @@ async function handleReset(item: Workflow) {
|
||||
}
|
||||
|
||||
type WorkflowStatusColor = 'error' | 'info' | 'primary' | 'secondary' | 'success' | 'warning'
|
||||
type WorkflowActionSegmentState = 'active' | 'complete' | 'failed' | 'pending'
|
||||
|
||||
interface WorkflowActionDisplay {
|
||||
id?: string
|
||||
@@ -243,20 +242,22 @@ const finishedActionCount = computed(() => {
|
||||
return Math.min(Math.max(count, 0), totalActionCount.value)
|
||||
})
|
||||
|
||||
const actionSegments = computed<WorkflowActionSegmentState[]>(() => {
|
||||
return workflowActions.value.map((action, index) => {
|
||||
const actionId = action.id ? String(action.id) : ''
|
||||
const nodeState = actionId ? executionNodes.value[actionId]?.state : undefined
|
||||
// 全局执行进度(%):优先使用后端下发的整体进度,缺失时按已完成动作数兜底计算
|
||||
const globalProgressPercent = computed(() => {
|
||||
const runtimeProgress = Number(props.workflow.execution_state?.runtime?.progress)
|
||||
if (Number.isFinite(runtimeProgress)) {
|
||||
return Math.min(Math.max(Math.round(runtimeProgress), 0), 100)
|
||||
}
|
||||
|
||||
if (nodeState === 'failed') return 'failed'
|
||||
if (nodeState === 'running' || nodeState === 'queued') return 'active'
|
||||
if (nodeState === 'success' || nodeState === 'completed' || nodeState === 'skipped') return 'complete'
|
||||
if (actionId && currentActionIds.value.has(actionId)) return 'complete'
|
||||
if (index < finishedActionCount.value) return 'complete'
|
||||
if (props.workflow.state === 'R' && index === finishedActionCount.value) return 'active'
|
||||
if (totalActionCount.value <= 0) return 0
|
||||
|
||||
return 'pending'
|
||||
})
|
||||
return Math.min(100, Math.round((finishedActionCount.value / totalActionCount.value) * 100))
|
||||
})
|
||||
|
||||
// 已执行次数:大于 0 时追加在执行状态行后展示
|
||||
const runCountText = computed(() => {
|
||||
const count = props.workflow.run_count || 0
|
||||
return count > 0 ? ` · ${t('workflow.task.info.runCount', { count })}` : ''
|
||||
})
|
||||
|
||||
const runningActionName = computed(() => {
|
||||
@@ -279,21 +280,7 @@ const executionStatus = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
if (totalActionCount.value > 0) {
|
||||
return {
|
||||
color: 'primary' as WorkflowStatusColor,
|
||||
icon: 'mdi-pulse',
|
||||
text: t('workflow.task.info.preparingAction', {
|
||||
current: Math.min(finishedActionCount.value + 1, totalActionCount.value),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
color: 'secondary' as WorkflowStatusColor,
|
||||
icon: 'mdi-vector-polyline-remove',
|
||||
text: t('workflow.task.info.noActions'),
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (props.workflow.state === 'F') {
|
||||
@@ -308,7 +295,7 @@ const executionStatus = computed(() => {
|
||||
return {
|
||||
color: undefined,
|
||||
icon: 'mdi-history',
|
||||
text: t('workflow.task.info.lastExecuted', { time: formatDateDifference(props.workflow.last_time) }),
|
||||
text: `${t('workflow.task.info.lastExecuted', { time: formatDateDifference(props.workflow.last_time) })}${runCountText.value}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,44 +447,8 @@ const executionStatus = computed(() => {
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div class="workflow-task-card__metrics">
|
||||
<div class="workflow-task-card__metric">
|
||||
<span>{{ t('workflow.task.info.actionCount') }}</span>
|
||||
<strong>{{ totalActionCount }}</strong>
|
||||
</div>
|
||||
<div class="workflow-task-card__metric">
|
||||
<span>{{ t('workflow.task.info.runCount') }}</span>
|
||||
<strong>{{ workflow.run_count || 0 }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workflow-task-card__progress">
|
||||
<div class="workflow-task-card__progress-label">
|
||||
<span>{{ t('workflow.task.info.actionProgress') }}</span>
|
||||
<strong>{{ finishedActionCount }} / {{ totalActionCount }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="workflow-task-card__action-track"
|
||||
role="progressbar"
|
||||
:aria-label="`${t('workflow.task.info.actionProgress')}: ${finishedActionCount} / ${totalActionCount}`"
|
||||
aria-valuemin="0"
|
||||
:aria-valuemax="Math.max(totalActionCount, 1)"
|
||||
:aria-valuenow="finishedActionCount"
|
||||
>
|
||||
<span
|
||||
v-for="(segment, index) in actionSegments"
|
||||
:key="workflowActions[index]?.id || index"
|
||||
class="workflow-task-card__action-segment"
|
||||
:class="`workflow-task-card__action-segment--${segment}`"
|
||||
/>
|
||||
<span
|
||||
v-if="totalActionCount === 0"
|
||||
class="workflow-task-card__action-segment workflow-task-card__action-segment--empty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="executionStatus"
|
||||
class="workflow-task-card__execution-status"
|
||||
:class="executionStatus.color ? `text-${executionStatus.color}` : 'text-medium-emphasis'"
|
||||
:title="executionStatus.text"
|
||||
@@ -506,6 +457,17 @@ const executionStatus = computed(() => {
|
||||
<span>{{ executionStatus.text }}</span>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<footer
|
||||
class="workflow-task-card__progress"
|
||||
role="progressbar"
|
||||
:aria-label="workflow.name"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
:aria-valuenow="globalProgressPercent"
|
||||
>
|
||||
<span class="workflow-task-card__progress-fill" :style="{ inlineSize: `${globalProgressPercent}%` }" />
|
||||
</footer>
|
||||
</VCard>
|
||||
</div>
|
||||
</VHover>
|
||||
@@ -530,7 +492,7 @@ const executionStatus = computed(() => {
|
||||
--workflow-card-header-shadow: none;
|
||||
|
||||
display: flex;
|
||||
min-block-size: 226px;
|
||||
min-block-size: 176px;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -613,8 +575,8 @@ const executionStatus = computed(() => {
|
||||
min-inline-size: 0;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 11px 12px !important;
|
||||
gap: 8px;
|
||||
padding: 12px 12px 12px !important;
|
||||
}
|
||||
|
||||
.workflow-task-card__status-row {
|
||||
@@ -625,77 +587,20 @@ const executionStatus = computed(() => {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workflow-task-card__metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.workflow-task-card__metric {
|
||||
display: grid;
|
||||
min-inline-size: 0;
|
||||
gap: 1px;
|
||||
padding-inline-end: 10px;
|
||||
}
|
||||
|
||||
.workflow-task-card__metric + .workflow-task-card__metric {
|
||||
padding-inline: 10px 0;
|
||||
border-inline-start: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
|
||||
}
|
||||
|
||||
.workflow-task-card__metric span,
|
||||
.workflow-task-card__progress-label span {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||
}
|
||||
|
||||
.workflow-task-card__metric strong,
|
||||
.workflow-task-card__progress-label strong {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.workflow-task-card__progress {
|
||||
min-inline-size: 0;
|
||||
flex: 0 0 auto;
|
||||
block-size: 5px;
|
||||
inline-size: 100%;
|
||||
overflow: hidden;
|
||||
background: rgba(var(--v-theme-on-surface), 0.1);
|
||||
}
|
||||
|
||||
.workflow-task-card__progress-label {
|
||||
display: flex;
|
||||
min-inline-size: 0;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workflow-task-card__action-track {
|
||||
display: flex;
|
||||
min-inline-size: 0;
|
||||
gap: 4px;
|
||||
margin-block-start: 6px;
|
||||
}
|
||||
|
||||
.workflow-task-card__action-segment {
|
||||
block-size: 6px;
|
||||
min-inline-size: 2px;
|
||||
flex: 1 1 0;
|
||||
.workflow-task-card__progress-fill {
|
||||
display: block;
|
||||
block-size: 100%;
|
||||
border-radius: var(--app-control-radius);
|
||||
background: rgba(var(--v-theme-on-surface), 0.12);
|
||||
}
|
||||
|
||||
.workflow-task-card__action-segment--complete {
|
||||
background: rgb(var(--workflow-status-rgb));
|
||||
}
|
||||
|
||||
.workflow-task-card__action-segment--active {
|
||||
background: rgba(var(--workflow-status-rgb), 0.22);
|
||||
box-shadow: inset 0 0 0 1px rgb(var(--workflow-status-rgb));
|
||||
}
|
||||
|
||||
.workflow-task-card__action-segment--failed {
|
||||
background: rgb(var(--v-theme-error));
|
||||
}
|
||||
|
||||
.workflow-task-card__action-segment--empty {
|
||||
background: rgba(var(--v-theme-on-surface), 0.08);
|
||||
transition: inline-size 0.3s ease;
|
||||
}
|
||||
|
||||
.workflow-task-card__execution-status {
|
||||
@@ -704,8 +609,6 @@ const executionStatus = computed(() => {
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
margin-block-start: auto;
|
||||
padding-block-start: 9px;
|
||||
border-block-start: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
|
||||
}
|
||||
|
||||
.workflow-task-card__execution-status .v-icon {
|
||||
@@ -721,7 +624,7 @@ const executionStatus = computed(() => {
|
||||
|
||||
@media (width <= 599.98px) {
|
||||
.workflow-task-card {
|
||||
min-block-size: 222px;
|
||||
min-block-size: 170px;
|
||||
}
|
||||
|
||||
.workflow-task-card__header,
|
||||
@@ -730,7 +633,7 @@ const executionStatus = computed(() => {
|
||||
}
|
||||
|
||||
.workflow-task-card__body {
|
||||
gap: 9px;
|
||||
gap: 7px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('WorkflowTaskCard redesign', () => {
|
||||
expect(container.querySelector('.v-chip')).toHaveTextContent(label)
|
||||
})
|
||||
|
||||
it('renders structured node states as segmented action progress', async () => {
|
||||
it('renders the global runtime progress on the bottom progress bar', async () => {
|
||||
const { container } = await renderCard({
|
||||
execution_state: {
|
||||
nodes: {
|
||||
@@ -116,27 +116,52 @@ describe('WorkflowTaskCard redesign', () => {
|
||||
scrape: { state: 'skipped' },
|
||||
transfer: { state: 'running' },
|
||||
},
|
||||
runtime: { finished_actions: 2 },
|
||||
runtime: { finished_actions: 2, progress: 66 },
|
||||
},
|
||||
state: 'R',
|
||||
})
|
||||
|
||||
expect(screen.getByText('2 / 3')).toBeInTheDocument()
|
||||
expect(container.querySelectorAll('.workflow-task-card__action-segment--complete')).toHaveLength(2)
|
||||
expect(container.querySelectorAll('.workflow-task-card__action-segment--active')).toHaveLength(1)
|
||||
expect(screen.getByText('正在执行 整理文件')).toBeInTheDocument()
|
||||
expect(screen.getByRole('progressbar', { name: '动作进度: 2 / 3' })).toHaveAttribute('aria-valuenow', '2')
|
||||
const fill = container.querySelector('.workflow-task-card__progress-fill') as HTMLElement
|
||||
expect(fill.style.inlineSize).toBe('66%')
|
||||
expect(screen.getByRole('progressbar', { name: '扫描和刮削' })).toHaveAttribute('aria-valuenow', '66')
|
||||
})
|
||||
|
||||
it('falls back to unique legacy current-action ids and clamps the count', async () => {
|
||||
it('falls back to legacy current-action counts and clamps the progress', async () => {
|
||||
const { container } = await renderCard({ current_action: ',scan,,scrape,scan,unknown,', state: 'P' })
|
||||
|
||||
expect(screen.getByText('2 / 3')).toBeInTheDocument()
|
||||
expect(container.querySelectorAll('.workflow-task-card__action-segment--complete')).toHaveLength(2)
|
||||
expect(container.querySelectorAll('.workflow-task-card__action-segment--pending')).toHaveLength(1)
|
||||
const fill = container.querySelector('.workflow-task-card__progress-fill') as HTMLElement
|
||||
expect(fill.style.inlineSize).toBe('67%')
|
||||
expect(screen.getByText('上次执行尚未完成')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows the run count after the last execution time', async () => {
|
||||
const lastTime = '2026-08-03 08:00:00'
|
||||
const { rerender } = await renderCard({ last_time: lastTime, run_count: 3, state: 'S' })
|
||||
|
||||
expect(screen.getByText(`上次执行 ${formatDateDifference(lastTime)} · 已执行 3 次`)).toBeInTheDocument()
|
||||
|
||||
await rerender({
|
||||
eventTypes: [],
|
||||
workflow: createWorkflow({ last_time: lastTime, run_count: 0, state: 'S' }),
|
||||
})
|
||||
expect(screen.getByText(`上次执行 ${formatDateDifference(lastTime)}`)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('always renders the progress bar at the bottom of the card', async () => {
|
||||
const { container, rerender } = await renderCard({ state: 'W' })
|
||||
|
||||
const idleBar = container.querySelector('.workflow-task-card__progress') as HTMLElement
|
||||
expect(idleBar).toBeInTheDocument()
|
||||
expect(idleBar).toHaveAttribute('aria-valuenow', '0')
|
||||
|
||||
await rerender({
|
||||
eventTypes: [],
|
||||
workflow: createWorkflow({ execution_state: { runtime: { progress: 100 } }, state: 'S' }),
|
||||
})
|
||||
expect(container.querySelector('.workflow-task-card__progress')).toHaveAttribute('aria-valuenow', '100')
|
||||
})
|
||||
|
||||
it('shows failure, last-run and never-run execution details at the bottom', async () => {
|
||||
const lastTime = '2026-08-03 08:00:00'
|
||||
const { rerender } = await renderCard({ result: '目录无访问权限', state: 'F' })
|
||||
|
||||
@@ -589,12 +589,17 @@ const isMacOS = computed(() => {
|
||||
}
|
||||
|
||||
.workflow-edge-panel {
|
||||
--workflow-edge-panel-background: rgb(var(--v-theme-surface));
|
||||
--workflow-edge-panel-backdrop-filter: none;
|
||||
|
||||
position: absolute;
|
||||
z-index: 120;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
-webkit-backdrop-filter: var(--workflow-edge-panel-backdrop-filter);
|
||||
backdrop-filter: var(--workflow-edge-panel-backdrop-filter);
|
||||
background-color: var(--workflow-edge-panel-background);
|
||||
gap: 14px;
|
||||
inline-size: min(360px, calc(100vw - 32px));
|
||||
inset-block-start: 20px;
|
||||
|
||||
@@ -187,10 +187,21 @@ onMounted(() => {
|
||||
@use 'sass:color';
|
||||
|
||||
.workflow-sidebar {
|
||||
--workflow-sidebar-background: rgb(var(--v-theme-background));
|
||||
--workflow-sidebar-backdrop-filter: none;
|
||||
--workflow-sidebar-header-background: rgb(var(--v-theme-background));
|
||||
--workflow-sidebar-footer-background: rgb(var(--v-theme-background));
|
||||
--workflow-sidebar-border-color: rgba(var(--v-theme-on-background), 0.06);
|
||||
--workflow-sidebar-card-background: rgb(var(--v-theme-surface-variant));
|
||||
--workflow-sidebar-card-background-hover: rgb(var(--v-theme-surface-variant));
|
||||
--workflow-sidebar-desc-color: rgba(var(--v-theme-on-background), 0.55);
|
||||
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
-webkit-backdrop-filter: var(--workflow-sidebar-backdrop-filter);
|
||||
backdrop-filter: var(--workflow-sidebar-backdrop-filter);
|
||||
background-color: var(--workflow-sidebar-background);
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 8%);
|
||||
inline-size: 280px;
|
||||
inset-block: 0;
|
||||
@@ -220,8 +231,8 @@ onMounted(() => {
|
||||
.sidebar-header {
|
||||
flex-shrink: 0;
|
||||
padding: 16px;
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
border-block-end: 1px solid rgba(var(--v-theme-on-background), 0.06);
|
||||
background-color: var(--workflow-sidebar-header-background);
|
||||
border-block-end: 1px solid var(--workflow-sidebar-border-color);
|
||||
|
||||
.header-content {
|
||||
position: relative;
|
||||
@@ -281,11 +292,11 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background-color: rgb(var(--v-theme-surface-variant));
|
||||
background-color: var(--workflow-sidebar-card-background);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(var(--v-theme-surface-variant));
|
||||
background-color: var(--workflow-sidebar-card-background-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
@@ -318,7 +329,7 @@ onMounted(() => {
|
||||
|
||||
.component-desc {
|
||||
overflow: hidden;
|
||||
color: #71717a;
|
||||
color: var(--workflow-sidebar-desc-color);
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -327,8 +338,8 @@ onMounted(() => {
|
||||
.sidebar-footer {
|
||||
flex-shrink: 0;
|
||||
padding: 12px;
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
border-block-start: 1px solid rgba(0, 0, 0, 6%);
|
||||
background-color: var(--workflow-sidebar-footer-background);
|
||||
border-block-start: 1px solid var(--workflow-sidebar-border-color);
|
||||
|
||||
.drag-btn {
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
|
||||
@@ -948,16 +948,14 @@ export default {
|
||||
timer: 'Timer',
|
||||
status: 'Status',
|
||||
actionCount: 'Action Count',
|
||||
runCount: 'Run Count',
|
||||
runCount: 'Run {count} times',
|
||||
progress: 'Progress',
|
||||
actionProgress: 'Action Progress',
|
||||
error: 'Error Message',
|
||||
manualTrigger: 'Manual',
|
||||
lastExecuted: 'Last run {time}',
|
||||
neverExecuted: 'Never run',
|
||||
executionIncomplete: 'Previous run is incomplete',
|
||||
executingAction: 'Running {name}',
|
||||
preparingAction: 'Preparing action {current}',
|
||||
noActions: 'No actions',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -937,16 +937,14 @@ export default {
|
||||
timer: '定时',
|
||||
status: '状态',
|
||||
actionCount: '动作数',
|
||||
runCount: '已执行次数',
|
||||
runCount: '已执行 {count} 次',
|
||||
progress: '进度',
|
||||
actionProgress: '动作进度',
|
||||
error: '错误信息',
|
||||
manualTrigger: '手动',
|
||||
lastExecuted: '上次执行 {time}',
|
||||
neverExecuted: '从未执行',
|
||||
executionIncomplete: '上次执行尚未完成',
|
||||
executingAction: '正在执行 {name}',
|
||||
preparingAction: '正在准备第 {current} 个动作',
|
||||
noActions: '暂无动作',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -937,16 +937,14 @@ export default {
|
||||
timer: '定時器',
|
||||
status: '狀態',
|
||||
actionCount: '動作數量',
|
||||
runCount: '執行次數',
|
||||
runCount: '已執行 {count} 次',
|
||||
progress: '進度',
|
||||
actionProgress: '動作進度',
|
||||
error: '錯誤訊息',
|
||||
manualTrigger: '手動',
|
||||
lastExecuted: '上次執行 {time}',
|
||||
neverExecuted: '從未執行',
|
||||
executionIncomplete: '上次執行尚未完成',
|
||||
executingAction: '正在執行 {name}',
|
||||
preparingAction: '正在準備第 {current} 個動作',
|
||||
noActions: '暫無動作',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1008,6 +1008,24 @@ html[data-theme='glass'] {
|
||||
--workflow-card-header-shadow: inset 0 1px 0 var(--glass-highlight), inset 0 -1px 0 rgba(2, 6, 16, 14%);
|
||||
}
|
||||
|
||||
// 工作流编辑器组件选择栏
|
||||
.workflow-sidebar {
|
||||
--workflow-sidebar-background: var(--glass-surface);
|
||||
--workflow-sidebar-backdrop-filter: var(--glass-surface-backdrop-filter);
|
||||
--workflow-sidebar-header-background: transparent;
|
||||
--workflow-sidebar-footer-background: transparent;
|
||||
--workflow-sidebar-border-color: var(--glass-border);
|
||||
--workflow-sidebar-card-background: var(--glass-control);
|
||||
--workflow-sidebar-card-background-hover: var(--glass-button-surface-hover);
|
||||
--workflow-sidebar-desc-color: var(--glass-control-placeholder-color);
|
||||
}
|
||||
|
||||
// 工作流编辑器流程边设置面板
|
||||
.workflow-edge-panel {
|
||||
--workflow-edge-panel-background: var(--glass-surface);
|
||||
--workflow-edge-panel-backdrop-filter: var(--glass-surface-backdrop-filter);
|
||||
}
|
||||
|
||||
// 分享卡保留原随机渐变的色相,以半透明染色、吸光和镜面层组成有色玻璃。
|
||||
.workflow-share-card {
|
||||
--workflow-share-glass-start-opacity: calc(0.18 + var(--glass-tint-density, 0.65) * 0.38);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* stylelint-disable scss/at-rule-no-unknown */
|
||||
|
||||
// 透明主题专用样式
|
||||
html[data-theme="transparent"] {
|
||||
html[data-theme='transparent'] {
|
||||
// 定义透明度变量
|
||||
--transparent-opacity: 0.3;
|
||||
--transparent-opacity-light: 0.2;
|
||||
@@ -21,10 +21,13 @@ html[data-theme="transparent"] {
|
||||
--app-grouped-list-icon-opacity: 0.88;
|
||||
|
||||
// 应用、布局、主内容区域
|
||||
.v-application, .v-layout, .v-main, .layout-page-content {
|
||||
.v-application,
|
||||
.v-layout,
|
||||
.v-main,
|
||||
.layout-page-content {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
// 侧边导航栏
|
||||
.layout-vertical-nav {
|
||||
backdrop-filter: blur(var(--transparent-blur-heavy));
|
||||
@@ -37,7 +40,7 @@ html[data-theme="transparent"] {
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
|
||||
// 卡片
|
||||
.v-card:not(.no-blur) {
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
@@ -67,6 +70,24 @@ html[data-theme="transparent"] {
|
||||
--workflow-card-header-shadow: inset 0 1px 0 rgba(var(--v-theme-on-surface), 0.14);
|
||||
}
|
||||
|
||||
// 工作流编辑器组件选择栏
|
||||
.workflow-sidebar {
|
||||
--workflow-sidebar-background: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy));
|
||||
--workflow-sidebar-backdrop-filter: blur(var(--transparent-blur-heavy));
|
||||
--workflow-sidebar-header-background: transparent;
|
||||
--workflow-sidebar-footer-background: transparent;
|
||||
--workflow-sidebar-border-color: rgba(var(--v-theme-on-surface), 0.08);
|
||||
--workflow-sidebar-card-background: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
--workflow-sidebar-card-background-hover: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy));
|
||||
--workflow-sidebar-desc-color: rgba(var(--v-theme-on-surface), 0.55);
|
||||
}
|
||||
|
||||
// 工作流编辑器流程边设置面板
|
||||
.workflow-edge-panel {
|
||||
--workflow-edge-panel-background: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy));
|
||||
--workflow-edge-panel-backdrop-filter: blur(var(--transparent-blur-heavy));
|
||||
}
|
||||
|
||||
// 订阅文件弹窗使用独立的玻璃表面参数,避免组件内通过全局选择器穿透主题。
|
||||
.subscribe-files-dialog {
|
||||
--sfd-accent-opacity: 0.12;
|
||||
@@ -114,7 +135,7 @@ html[data-theme="transparent"] {
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
|
||||
// 表格
|
||||
.v-table {
|
||||
border-radius: 0;
|
||||
@@ -124,7 +145,7 @@ html[data-theme="transparent"] {
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 页脚
|
||||
.v-footer {
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
@@ -136,17 +157,17 @@ html[data-theme="transparent"] {
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
|
||||
// 页面容器
|
||||
.layout-content-wrapper {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
// 无内容区域的背景设为透明
|
||||
.page-content-container {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
// 对话框和菜单蒙层样式
|
||||
.v-overlay__scrim {
|
||||
background: rgba(var(--v-overlay-scrim-background), var(--v-overlay-scrim-opacity));
|
||||
@@ -167,7 +188,7 @@ html[data-theme="transparent"] {
|
||||
.v-field {
|
||||
background-color: rgba(var(--v-theme-surface), 0);
|
||||
}
|
||||
|
||||
|
||||
// 弹出层内容
|
||||
.v-overlay__content {
|
||||
border-radius: var(--app-surface-radius);
|
||||
@@ -206,11 +227,7 @@ html[data-theme="transparent"] {
|
||||
.theme-customizer-shadow-slider,
|
||||
.theme-customizer-shadow-slider__sample {
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(var(--v-theme-on-surface), 0.02),
|
||||
rgba(var(--v-theme-on-surface), 0.05)
|
||||
),
|
||||
linear-gradient(180deg, rgba(var(--v-theme-on-surface), 0.02), rgba(var(--v-theme-on-surface), 0.05)),
|
||||
rgba(var(--v-theme-surface), var(--transparent-opacity-light));
|
||||
}
|
||||
|
||||
@@ -259,9 +276,9 @@ html[data-theme="transparent"] {
|
||||
),
|
||||
rgba(var(--v-theme-surface), var(--transparent-opacity-heavy)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="transparent"].transparent-background-blur-disabled {
|
||||
html[data-theme='transparent'].transparent-background-blur-disabled {
|
||||
.global-blur-layer,
|
||||
.background-image.active {
|
||||
backdrop-filter: none;
|
||||
@@ -270,7 +287,7 @@ html[data-theme="transparent"].transparent-background-blur-disabled {
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="transparent"].transparent-glass-lightweight {
|
||||
html[data-theme='transparent'].transparent-glass-lightweight {
|
||||
--app-grouped-list-backdrop-filter: none;
|
||||
|
||||
.global-blur-layer {
|
||||
@@ -334,7 +351,7 @@ html[data-theme="transparent"].transparent-glass-lightweight {
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="transparent"].transparent-glass-realtime {
|
||||
html[data-theme='transparent'].transparent-glass-realtime {
|
||||
--app-grouped-list-backdrop-filter: none;
|
||||
|
||||
.background-image.active {
|
||||
@@ -379,7 +396,7 @@ html[data-theme="transparent"].transparent-glass-realtime {
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="transparent"].transparent-blur-disabled {
|
||||
html[data-theme='transparent'].transparent-blur-disabled {
|
||||
--app-grouped-list-backdrop-filter: none;
|
||||
|
||||
.layout-vertical-nav,
|
||||
|
||||
Reference in New Issue
Block a user