mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +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 WorkflowStatusColor = 'error' | 'info' | 'primary' | 'secondary' | 'success' | 'warning'
|
||||||
type WorkflowActionSegmentState = 'active' | 'complete' | 'failed' | 'pending'
|
|
||||||
|
|
||||||
interface WorkflowActionDisplay {
|
interface WorkflowActionDisplay {
|
||||||
id?: string
|
id?: string
|
||||||
@@ -243,20 +242,22 @@ const finishedActionCount = computed(() => {
|
|||||||
return Math.min(Math.max(count, 0), totalActionCount.value)
|
return Math.min(Math.max(count, 0), totalActionCount.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const actionSegments = computed<WorkflowActionSegmentState[]>(() => {
|
// 全局执行进度(%):优先使用后端下发的整体进度,缺失时按已完成动作数兜底计算
|
||||||
return workflowActions.value.map((action, index) => {
|
const globalProgressPercent = computed(() => {
|
||||||
const actionId = action.id ? String(action.id) : ''
|
const runtimeProgress = Number(props.workflow.execution_state?.runtime?.progress)
|
||||||
const nodeState = actionId ? executionNodes.value[actionId]?.state : undefined
|
if (Number.isFinite(runtimeProgress)) {
|
||||||
|
return Math.min(Math.max(Math.round(runtimeProgress), 0), 100)
|
||||||
|
}
|
||||||
|
|
||||||
if (nodeState === 'failed') return 'failed'
|
if (totalActionCount.value <= 0) return 0
|
||||||
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'
|
|
||||||
|
|
||||||
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(() => {
|
const runningActionName = computed(() => {
|
||||||
@@ -279,21 +280,7 @@ const executionStatus = computed(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalActionCount.value > 0) {
|
return null
|
||||||
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'),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.workflow.state === 'F') {
|
if (props.workflow.state === 'F') {
|
||||||
@@ -308,7 +295,7 @@ const executionStatus = computed(() => {
|
|||||||
return {
|
return {
|
||||||
color: undefined,
|
color: undefined,
|
||||||
icon: 'mdi-history',
|
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>
|
</VBtn>
|
||||||
</div>
|
</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
|
<div
|
||||||
|
v-if="executionStatus"
|
||||||
class="workflow-task-card__execution-status"
|
class="workflow-task-card__execution-status"
|
||||||
:class="executionStatus.color ? `text-${executionStatus.color}` : 'text-medium-emphasis'"
|
:class="executionStatus.color ? `text-${executionStatus.color}` : 'text-medium-emphasis'"
|
||||||
:title="executionStatus.text"
|
:title="executionStatus.text"
|
||||||
@@ -506,6 +457,17 @@ const executionStatus = computed(() => {
|
|||||||
<span>{{ executionStatus.text }}</span>
|
<span>{{ executionStatus.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</VCardText>
|
</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>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
</VHover>
|
</VHover>
|
||||||
@@ -530,7 +492,7 @@ const executionStatus = computed(() => {
|
|||||||
--workflow-card-header-shadow: none;
|
--workflow-card-header-shadow: none;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
min-block-size: 226px;
|
min-block-size: 176px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -613,8 +575,8 @@ const executionStatus = computed(() => {
|
|||||||
min-inline-size: 0;
|
min-inline-size: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
padding: 11px 12px !important;
|
padding: 12px 12px 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-task-card__status-row {
|
.workflow-task-card__status-row {
|
||||||
@@ -625,77 +587,20 @@ const executionStatus = computed(() => {
|
|||||||
gap: 10px;
|
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 {
|
.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 {
|
.workflow-task-card__progress-fill {
|
||||||
display: flex;
|
display: block;
|
||||||
min-inline-size: 0;
|
block-size: 100%;
|
||||||
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;
|
|
||||||
border-radius: var(--app-control-radius);
|
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));
|
background: rgb(var(--workflow-status-rgb));
|
||||||
}
|
transition: inline-size 0.3s ease;
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-task-card__execution-status {
|
.workflow-task-card__execution-status {
|
||||||
@@ -704,8 +609,6 @@ const executionStatus = computed(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
margin-block-start: auto;
|
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 {
|
.workflow-task-card__execution-status .v-icon {
|
||||||
@@ -721,7 +624,7 @@ const executionStatus = computed(() => {
|
|||||||
|
|
||||||
@media (width <= 599.98px) {
|
@media (width <= 599.98px) {
|
||||||
.workflow-task-card {
|
.workflow-task-card {
|
||||||
min-block-size: 222px;
|
min-block-size: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-task-card__header,
|
.workflow-task-card__header,
|
||||||
@@ -730,7 +633,7 @@ const executionStatus = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.workflow-task-card__body {
|
.workflow-task-card__body {
|
||||||
gap: 9px;
|
gap: 7px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ describe('WorkflowTaskCard redesign', () => {
|
|||||||
expect(container.querySelector('.v-chip')).toHaveTextContent(label)
|
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({
|
const { container } = await renderCard({
|
||||||
execution_state: {
|
execution_state: {
|
||||||
nodes: {
|
nodes: {
|
||||||
@@ -116,27 +116,52 @@ describe('WorkflowTaskCard redesign', () => {
|
|||||||
scrape: { state: 'skipped' },
|
scrape: { state: 'skipped' },
|
||||||
transfer: { state: 'running' },
|
transfer: { state: 'running' },
|
||||||
},
|
},
|
||||||
runtime: { finished_actions: 2 },
|
runtime: { finished_actions: 2, progress: 66 },
|
||||||
},
|
},
|
||||||
state: 'R',
|
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.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' })
|
const { container } = await renderCard({ current_action: ',scan,,scrape,scan,unknown,', state: 'P' })
|
||||||
|
|
||||||
expect(screen.getByText('2 / 3')).toBeInTheDocument()
|
const fill = container.querySelector('.workflow-task-card__progress-fill') as HTMLElement
|
||||||
expect(container.querySelectorAll('.workflow-task-card__action-segment--complete')).toHaveLength(2)
|
expect(fill.style.inlineSize).toBe('67%')
|
||||||
expect(container.querySelectorAll('.workflow-task-card__action-segment--pending')).toHaveLength(1)
|
|
||||||
expect(screen.getByText('上次执行尚未完成')).toBeInTheDocument()
|
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 () => {
|
it('shows failure, last-run and never-run execution details at the bottom', async () => {
|
||||||
const lastTime = '2026-08-03 08:00:00'
|
const lastTime = '2026-08-03 08:00:00'
|
||||||
const { rerender } = await renderCard({ result: '目录无访问权限', state: 'F' })
|
const { rerender } = await renderCard({ result: '目录无访问权限', state: 'F' })
|
||||||
|
|||||||
@@ -589,12 +589,17 @@ const isMacOS = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.workflow-edge-panel {
|
.workflow-edge-panel {
|
||||||
|
--workflow-edge-panel-background: rgb(var(--v-theme-surface));
|
||||||
|
--workflow-edge-panel-backdrop-filter: none;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 120;
|
z-index: 120;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 16px;
|
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;
|
gap: 14px;
|
||||||
inline-size: min(360px, calc(100vw - 32px));
|
inline-size: min(360px, calc(100vw - 32px));
|
||||||
inset-block-start: 20px;
|
inset-block-start: 20px;
|
||||||
|
|||||||
@@ -187,10 +187,21 @@ onMounted(() => {
|
|||||||
@use 'sass:color';
|
@use 'sass:color';
|
||||||
|
|
||||||
.workflow-sidebar {
|
.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;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
overflow: hidden;
|
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%);
|
box-shadow: 0 0 15px rgba(0, 0, 0, 8%);
|
||||||
inline-size: 280px;
|
inline-size: 280px;
|
||||||
inset-block: 0;
|
inset-block: 0;
|
||||||
@@ -220,8 +231,8 @@ onMounted(() => {
|
|||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: rgb(var(--v-theme-background));
|
background-color: var(--workflow-sidebar-header-background);
|
||||||
border-block-end: 1px solid rgba(var(--v-theme-on-background), 0.06);
|
border-block-end: 1px solid var(--workflow-sidebar-border-color);
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -281,11 +292,11 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: rgb(var(--v-theme-surface-variant));
|
background-color: var(--workflow-sidebar-card-background);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgb(var(--v-theme-surface-variant));
|
background-color: var(--workflow-sidebar-card-background-hover);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,7 +329,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.component-desc {
|
.component-desc {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #71717a;
|
color: var(--workflow-sidebar-desc-color);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -327,8 +338,8 @@ onMounted(() => {
|
|||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background-color: rgb(var(--v-theme-background));
|
background-color: var(--workflow-sidebar-footer-background);
|
||||||
border-block-start: 1px solid rgba(0, 0, 0, 6%);
|
border-block-start: 1px solid var(--workflow-sidebar-border-color);
|
||||||
|
|
||||||
.drag-btn {
|
.drag-btn {
|
||||||
background-color: rgb(var(--v-theme-primary));
|
background-color: rgb(var(--v-theme-primary));
|
||||||
|
|||||||
@@ -948,16 +948,14 @@ export default {
|
|||||||
timer: 'Timer',
|
timer: 'Timer',
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
actionCount: 'Action Count',
|
actionCount: 'Action Count',
|
||||||
runCount: 'Run Count',
|
runCount: 'Run {count} times',
|
||||||
progress: 'Progress',
|
progress: 'Progress',
|
||||||
actionProgress: 'Action Progress',
|
|
||||||
error: 'Error Message',
|
error: 'Error Message',
|
||||||
manualTrigger: 'Manual',
|
manualTrigger: 'Manual',
|
||||||
lastExecuted: 'Last run {time}',
|
lastExecuted: 'Last run {time}',
|
||||||
neverExecuted: 'Never run',
|
neverExecuted: 'Never run',
|
||||||
executionIncomplete: 'Previous run is incomplete',
|
executionIncomplete: 'Previous run is incomplete',
|
||||||
executingAction: 'Running {name}',
|
executingAction: 'Running {name}',
|
||||||
preparingAction: 'Preparing action {current}',
|
|
||||||
noActions: 'No actions',
|
noActions: 'No actions',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -937,16 +937,14 @@ export default {
|
|||||||
timer: '定时',
|
timer: '定时',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
actionCount: '动作数',
|
actionCount: '动作数',
|
||||||
runCount: '已执行次数',
|
runCount: '已执行 {count} 次',
|
||||||
progress: '进度',
|
progress: '进度',
|
||||||
actionProgress: '动作进度',
|
|
||||||
error: '错误信息',
|
error: '错误信息',
|
||||||
manualTrigger: '手动',
|
manualTrigger: '手动',
|
||||||
lastExecuted: '上次执行 {time}',
|
lastExecuted: '上次执行 {time}',
|
||||||
neverExecuted: '从未执行',
|
neverExecuted: '从未执行',
|
||||||
executionIncomplete: '上次执行尚未完成',
|
executionIncomplete: '上次执行尚未完成',
|
||||||
executingAction: '正在执行 {name}',
|
executingAction: '正在执行 {name}',
|
||||||
preparingAction: '正在准备第 {current} 个动作',
|
|
||||||
noActions: '暂无动作',
|
noActions: '暂无动作',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -937,16 +937,14 @@ export default {
|
|||||||
timer: '定時器',
|
timer: '定時器',
|
||||||
status: '狀態',
|
status: '狀態',
|
||||||
actionCount: '動作數量',
|
actionCount: '動作數量',
|
||||||
runCount: '執行次數',
|
runCount: '已執行 {count} 次',
|
||||||
progress: '進度',
|
progress: '進度',
|
||||||
actionProgress: '動作進度',
|
|
||||||
error: '錯誤訊息',
|
error: '錯誤訊息',
|
||||||
manualTrigger: '手動',
|
manualTrigger: '手動',
|
||||||
lastExecuted: '上次執行 {time}',
|
lastExecuted: '上次執行 {time}',
|
||||||
neverExecuted: '從未執行',
|
neverExecuted: '從未執行',
|
||||||
executionIncomplete: '上次執行尚未完成',
|
executionIncomplete: '上次執行尚未完成',
|
||||||
executingAction: '正在執行 {name}',
|
executingAction: '正在執行 {name}',
|
||||||
preparingAction: '正在準備第 {current} 個動作',
|
|
||||||
noActions: '暫無動作',
|
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-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-card {
|
||||||
--workflow-share-glass-start-opacity: calc(0.18 + var(--glass-tint-density, 0.65) * 0.38);
|
--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 */
|
/* stylelint-disable scss/at-rule-no-unknown */
|
||||||
|
|
||||||
// 透明主题专用样式
|
// 透明主题专用样式
|
||||||
html[data-theme="transparent"] {
|
html[data-theme='transparent'] {
|
||||||
// 定义透明度变量
|
// 定义透明度变量
|
||||||
--transparent-opacity: 0.3;
|
--transparent-opacity: 0.3;
|
||||||
--transparent-opacity-light: 0.2;
|
--transparent-opacity-light: 0.2;
|
||||||
@@ -21,10 +21,13 @@ html[data-theme="transparent"] {
|
|||||||
--app-grouped-list-icon-opacity: 0.88;
|
--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;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 侧边导航栏
|
// 侧边导航栏
|
||||||
.layout-vertical-nav {
|
.layout-vertical-nav {
|
||||||
backdrop-filter: blur(var(--transparent-blur-heavy));
|
backdrop-filter: blur(var(--transparent-blur-heavy));
|
||||||
@@ -37,7 +40,7 @@ html[data-theme="transparent"] {
|
|||||||
backdrop-filter: blur(var(--transparent-blur));
|
backdrop-filter: blur(var(--transparent-blur));
|
||||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡片
|
// 卡片
|
||||||
.v-card:not(.no-blur) {
|
.v-card:not(.no-blur) {
|
||||||
backdrop-filter: blur(var(--transparent-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-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 {
|
.subscribe-files-dialog {
|
||||||
--sfd-accent-opacity: 0.12;
|
--sfd-accent-opacity: 0.12;
|
||||||
@@ -114,7 +135,7 @@ html[data-theme="transparent"] {
|
|||||||
backdrop-filter: blur(var(--transparent-blur));
|
backdrop-filter: blur(var(--transparent-blur));
|
||||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格
|
// 表格
|
||||||
.v-table {
|
.v-table {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@@ -124,7 +145,7 @@ html[data-theme="transparent"] {
|
|||||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页脚
|
// 页脚
|
||||||
.v-footer {
|
.v-footer {
|
||||||
backdrop-filter: blur(var(--transparent-blur));
|
backdrop-filter: blur(var(--transparent-blur));
|
||||||
@@ -136,17 +157,17 @@ html[data-theme="transparent"] {
|
|||||||
backdrop-filter: blur(var(--transparent-blur));
|
backdrop-filter: blur(var(--transparent-blur));
|
||||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面容器
|
// 页面容器
|
||||||
.layout-content-wrapper {
|
.layout-content-wrapper {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无内容区域的背景设为透明
|
// 无内容区域的背景设为透明
|
||||||
.page-content-container {
|
.page-content-container {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对话框和菜单蒙层样式
|
// 对话框和菜单蒙层样式
|
||||||
.v-overlay__scrim {
|
.v-overlay__scrim {
|
||||||
background: rgba(var(--v-overlay-scrim-background), var(--v-overlay-scrim-opacity));
|
background: rgba(var(--v-overlay-scrim-background), var(--v-overlay-scrim-opacity));
|
||||||
@@ -167,7 +188,7 @@ html[data-theme="transparent"] {
|
|||||||
.v-field {
|
.v-field {
|
||||||
background-color: rgba(var(--v-theme-surface), 0);
|
background-color: rgba(var(--v-theme-surface), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 弹出层内容
|
// 弹出层内容
|
||||||
.v-overlay__content {
|
.v-overlay__content {
|
||||||
border-radius: var(--app-surface-radius);
|
border-radius: var(--app-surface-radius);
|
||||||
@@ -206,11 +227,7 @@ html[data-theme="transparent"] {
|
|||||||
.theme-customizer-shadow-slider,
|
.theme-customizer-shadow-slider,
|
||||||
.theme-customizer-shadow-slider__sample {
|
.theme-customizer-shadow-slider__sample {
|
||||||
background:
|
background:
|
||||||
linear-gradient(
|
linear-gradient(180deg, rgba(var(--v-theme-on-surface), 0.02), rgba(var(--v-theme-on-surface), 0.05)),
|
||||||
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));
|
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;
|
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,
|
.global-blur-layer,
|
||||||
.background-image.active {
|
.background-image.active {
|
||||||
backdrop-filter: none;
|
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;
|
--app-grouped-list-backdrop-filter: none;
|
||||||
|
|
||||||
.global-blur-layer {
|
.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;
|
--app-grouped-list-backdrop-filter: none;
|
||||||
|
|
||||||
.background-image.active {
|
.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;
|
--app-grouped-list-backdrop-filter: none;
|
||||||
|
|
||||||
.layout-vertical-nav,
|
.layout-vertical-nav,
|
||||||
|
|||||||
Reference in New Issue
Block a user