perf:【IoT 物联网】场景联动样式 unocss 化

This commit is contained in:
puhui999
2025-07-26 16:33:27 +08:00
parent 751daf5fbb
commit d4555907cc
7 changed files with 88 additions and 525 deletions

View File

@@ -1,6 +1,6 @@
<!-- 告警配置组件 -->
<template>
<div class="alert-config">
<div class="w-full">
<!-- TODO @puhui999触发告警时不用选择配置哈 -->
<el-form-item label="告警配置" required>
<el-select
@@ -18,10 +18,10 @@
:label="config.name"
:value="config.id"
>
<div class="alert-option">
<div class="option-content">
<div class="option-name">{{ config.name }}</div>
<div class="option-desc">{{ config.description }}</div>
<div class="flex items-center justify-between w-full py-4px">
<div class="flex-1">
<div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">{{ config.name }}</div>
<div class="text-12px text-[var(--el-text-color-secondary)]">{{ config.description }}</div>
</div>
<el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
{{ config.enabled ? '启用' : '禁用' }}
@@ -32,32 +32,32 @@
</el-form-item>
<!-- 告警配置详情 -->
<div v-if="selectedConfig" class="alert-details">
<div class="details-header">
<Icon icon="ep:bell" class="details-icon" />
<span class="details-title">{{ selectedConfig.name }}</span>
<div v-if="selectedConfig" class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
<div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:bell" class="text-[var(--el-color-warning)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">{{ selectedConfig.name }}</span>
<el-tag :type="selectedConfig.enabled ? 'success' : 'danger'" size="small">
{{ selectedConfig.enabled ? '启用' : '禁用' }}
</el-tag>
</div>
<div class="details-content">
<div class="detail-item">
<span class="detail-label">描述</span>
<span class="detail-value">{{ selectedConfig.description }}</span>
<div class="space-y-8px">
<div class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">描述</span>
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedConfig.description }}</span>
</div>
<div class="detail-item">
<span class="detail-label">通知方式</span>
<span class="detail-value">{{ getNotifyTypeName(selectedConfig.notifyType) }}</span>
<div class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">通知方式</span>
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ getNotifyTypeName(selectedConfig.notifyType) }}</span>
</div>
<div v-if="selectedConfig.receivers" class="detail-item">
<span class="detail-label">接收人</span>
<span class="detail-value">{{ selectedConfig.receivers.join(', ') }}</span>
<div v-if="selectedConfig.receivers" class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">接收人</span>
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedConfig.receivers.join(', ') }}</span>
</div>
</div>
</div>
<!-- 验证结果 -->
<div v-if="validationMessage" class="validation-result">
<div v-if="validationMessage" class="mt-16px">
<el-alert
:title="validationMessage"
:type="isValid ? 'success' : 'error'"
@@ -202,90 +202,6 @@ onMounted(() => {
</script>
<style scoped>
.alert-config {
width: 100%;
}
.alert-option {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 4px 0;
}
.option-content {
flex: 1;
}
.option-name {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
margin-bottom: 2px;
}
.option-desc {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.alert-details {
margin-top: 12px;
padding: 12px;
background: var(--el-fill-color-light);
border-radius: 6px;
border: 1px solid var(--el-border-color-lighter);
}
.details-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.details-icon {
color: var(--el-color-warning);
font-size: 14px;
}
.details-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.details-content {
display: flex;
flex-direction: column;
gap: 4px;
margin-left: 22px;
}
.detail-item {
display: flex;
align-items: flex-start;
gap: 8px;
}
.detail-label {
font-size: 12px;
color: var(--el-text-color-secondary);
min-width: 60px;
flex-shrink: 0;
}
.detail-value {
font-size: 12px;
color: var(--el-text-color-primary);
flex: 1;
}
.validation-result {
margin-top: 8px;
}
:deep(.el-select-dropdown__item) {
height: auto;
padding: 8px 20px;

View File

@@ -1,7 +1,7 @@
<!-- 单个条件配置组件 -->
<!-- TODO @puhui999这里需要在对下阿里云 IoT不太对它是条件类型然后选择产品设备接着选条件类型对应的比较 -->
<template>
<div class="condition-config">
<div class="flex flex-col gap-16px">
<el-row :gutter="16">
<!-- 属性/事件/服务选择 -->
<el-col :span="8">
@@ -45,18 +45,18 @@
</el-row>
<!-- 条件预览 -->
<div v-if="conditionPreview" class="condition-preview">
<div class="preview-header">
<Icon icon="ep:view" class="preview-icon" />
<span class="preview-title">条件预览</span>
<div v-if="conditionPreview" class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
<div class="flex items-center gap-8px mb-8px">
<Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span>
</div>
<div class="preview-content">
<code class="preview-text">{{ conditionPreview }}</code>
<div class="pl-24px">
<code class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono">{{ conditionPreview }}</code>
</div>
</div>
<!-- 验证结果 -->
<div v-if="validationMessage" class="validation-result">
<div v-if="validationMessage" class="mt-8px">
<el-alert
:title="validationMessage"
:type="isValid ? 'success' : 'error'"
@@ -210,55 +210,6 @@ onMounted(() => {
</script>
<style scoped>
.condition-config {
display: flex;
flex-direction: column;
gap: 16px;
}
.condition-preview {
padding: 12px;
background: var(--el-fill-color-light);
border-radius: 6px;
border: 1px solid var(--el-border-color-lighter);
}
.preview-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.preview-icon {
color: var(--el-color-primary);
font-size: 14px;
}
.preview-title {
font-size: 12px;
font-weight: 500;
color: var(--el-text-color-secondary);
}
.preview-content {
margin-left: 20px;
}
.preview-text {
font-size: 14px;
color: var(--el-text-color-primary);
background: var(--el-fill-color-blank);
padding: 8px 12px;
border-radius: 4px;
display: block;
font-family: inherit;
}
.validation-result {
margin-top: 8px;
}
:deep(.el-form-item) {
margin-bottom: 0;
}

View File

@@ -1,17 +1,17 @@
<!-- 条件组配置组件 -->
<template>
<div class="condition-group-config">
<div class="group-content">
<div class="p-16px">
<div class="space-y-12px">
<!-- 条件列表 -->
<div v-if="group.conditions && group.conditions.length > 0" class="conditions-list">
<div v-if="group.conditions && group.conditions.length > 0" class="space-y-12px">
<div
v-for="(condition, index) in group.conditions"
:key="`condition-${index}`"
class="condition-item"
class="p-12px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-light)]"
>
<div class="condition-header">
<div class="condition-title">
<span>条件 {{ index + 1 }}</span>
<div class="flex items-center justify-between mb-12px">
<div class="flex items-center gap-8px">
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件 {{ index + 1 }}</span>
<el-tag size="small" type="primary">
{{ getConditionTypeName(condition.type) }}
</el-tag>
@@ -28,7 +28,7 @@
</el-button>
</div>
<div class="condition-content">
<div class="p-12px bg-[var(--el-fill-color-blank)] rounded-4px">
<ConditionConfig
:model-value="condition"
@update:model-value="(value) => updateCondition(index, value)"
@@ -41,8 +41,8 @@
<!-- 逻辑连接符 -->
<!-- TODO @puhui999不用这个哈 -->
<div v-if="index < group.conditions!.length - 1" class="logic-connector">
<el-select v-model="group.logicOperator" size="small" style="width: 80px">
<div v-if="index < group.conditions!.length - 1" class="flex justify-center mt-8px">
<el-select v-model="group.logicOperator" size="small" class="w-80px">
<el-option label="" value="AND" />
<el-option label="" value="OR" />
</el-select>
@@ -51,7 +51,7 @@
</div>
<!-- 空状态 -->
<div v-else class="empty-conditions">
<div v-else class="py-40px text-center">
<el-empty description="暂无条件配置" :image-size="80">
<el-button type="primary" @click="addCondition">
<Icon icon="ep:plus" />
@@ -65,19 +65,19 @@
v-if="
group.conditions && group.conditions.length > 0 && group.conditions.length < maxConditions
"
class="add-condition"
class="text-center py-16px"
>
<el-button type="primary" plain @click="addCondition" class="add-condition-btn">
<el-button type="primary" plain @click="addCondition">
<Icon icon="ep:plus" />
继续添加条件
</el-button>
<span class="add-condition-text"> 最多可添加 {{ maxConditions }} 个条件 </span>
<span class="block mt-8px text-12px text-[var(--el-text-color-secondary)]"> 最多可添加 {{ maxConditions }} 个条件 </span>
</div>
</div>
<!-- 验证结果 -->
<!-- TODO @puhui999是不是不用这种提示只要 validator rules 能展示出来就好了呀。。。 -->
<div v-if="validationMessage" class="validation-result">
<div v-if="validationMessage" class="mt-8px">
<el-alert
:title="validationMessage"
:type="isValid ? 'success' : 'error'"
@@ -230,96 +230,4 @@ onMounted(() => {
})
</script>
<style scoped>
.condition-group-config {
padding: 16px;
}
.group-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.conditions-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.condition-item {
position: relative;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
background: var(--el-fill-color-blank);
}
.condition-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.condition-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.condition-content {
padding: 16px;
}
.logic-connector {
display: flex;
justify-content: center;
align-items: center;
padding: 8px 0;
position: relative;
}
.logic-connector::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 1px;
height: 100%;
background: var(--el-border-color);
}
.empty-conditions {
padding: 40px 0;
text-align: center;
}
.add-condition {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
background: var(--el-fill-color-lighter);
}
.add-condition-btn {
flex-shrink: 0;
}
.add-condition-text {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.validation-result {
margin-top: 16px;
}
</style>

View File

@@ -1,6 +1,6 @@
<!-- 设备触发配置组件 -->
<template>
<div class="device-trigger-config">
<div class="flex flex-col gap-16px">
<!-- 产品和设备选择 -->
<ProductDeviceSelector
v-model:product-id="trigger.productId"
@@ -10,26 +10,26 @@
<!-- TODO @puhui999这里有点冗余建议去掉 -->
<!-- 设备状态变更提示 -->
<div v-if="trigger.type === TriggerTypeEnum.DEVICE_STATE_UPDATE" class="state-update-notice">
<div v-if="trigger.type === TriggerTypeEnum.DEVICE_STATE_UPDATE" class="mt-8px">
<el-alert title="设备状态变更触发" type="info" :closable="false" show-icon>
<template #default>
<p>当选中的设备上线或离线时将自动触发场景规则</p>
<p class="notice-tip">无需配置额外的触发条件</p>
<p class="m-0">当选中的设备上线或离线时将自动触发场景规则</p>
<p class="m-0 mt-4px text-12px text-[var(--el-text-color-secondary)]">无需配置额外的触发条件</p>
</template>
</el-alert>
</div>
<!-- 条件组配置 -->
<div v-else-if="needsConditions" class="condition-groups">
<div class="condition-groups-header">
<div class="header-left">
<span class="header-title">触发条件</span>
<div v-else-if="needsConditions" class="space-y-12px">
<div class="flex items-center justify-between mb-12px">
<div class="flex items-center gap-8px">
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发条件</span>
<!-- TODO @puhui999去掉数量限制 -->
<el-tag size="small" type="info">
{{ trigger.conditionGroups?.length || 0 }}/{{ maxConditionGroups }}
</el-tag>
</div>
<div class="header-right">
<div class="flex items-center gap-8px">
<el-button
type="primary"
size="small"
@@ -45,21 +45,21 @@
<!-- 条件组列表 -->
<div
v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0"
class="condition-groups-list"
class="flex flex-col gap-12px"
>
<div
v-for="(group, groupIndex) in trigger.conditionGroups"
:key="`group-${groupIndex}`"
class="condition-group"
class="border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
>
<div class="group-header">
<div class="group-title">
<div class="flex items-center justify-between p-12px px-16px bg-[var(--el-fill-color-light)] border-b border-[var(--el-border-color-lighter)]">
<div class="flex items-center text-14px font-500 text-[var(--el-text-color-primary)]">
<span>条件组 {{ groupIndex + 1 }}</span>
<!-- TODO @puhui999不用条件组之间就是或条件之间就是且 -->
<el-select
v-model="group.logicOperator"
size="small"
style="width: 80px; margin-left: 12px"
class="w-80px ml-12px"
>
<el-option label="且" value="AND" />
<el-option label="或" value="OR" />
@@ -89,7 +89,7 @@
</div>
<!-- 空状态 -->
<div v-else class="empty-conditions">
<div v-else class="py-40px text-center">
<el-empty description="暂无触发条件">
<el-button type="primary" @click="addConditionGroup">
<Icon icon="ep:plus" />
@@ -100,7 +100,7 @@
</div>
<!-- 验证结果 -->
<div v-if="validationMessage" class="validation-result">
<div v-if="validationMessage" class="mt-8px">
<el-alert
:title="validationMessage"
:type="isValid ? 'success' : 'error'"
@@ -262,86 +262,5 @@ watch(
updateValidationResult()
}
)
// TODO @puhui999unocss
// TODO @puhui999unocss - 已完成转换
</script>
<style scoped>
.device-trigger-config {
display: flex;
flex-direction: column;
gap: 16px;
}
.state-update-notice {
margin-top: 8px;
}
.notice-tip {
margin: 4px 0 0 0;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.condition-groups-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.header-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.condition-groups-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.condition-group {
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
background: var(--el-fill-color-blank);
}
.group-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.group-title {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.empty-conditions {
padding: 40px 0;
text-align: center;
}
.validation-result {
margin-top: 8px;
}
</style>

View File

@@ -1,12 +1,12 @@
<!-- 定时触发配置组件 -->
<template>
<div class="timer-trigger-config">
<div class="config-header">
<div class="header-left">
<Icon icon="ep:timer" class="header-icon" />
<span class="header-title">定时触发配置</span>
<div class="flex flex-col gap-16px">
<div class="flex items-center justify-between p-12px px-16px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
<div class="flex items-center gap-8px">
<Icon icon="ep:timer" class="text-[var(--el-color-danger)] text-18px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">定时触发配置</span>
</div>
<div class="header-right">
<div class="flex items-center gap-8px">
<el-button type="text" size="small" @click="showBuilder = !showBuilder">
<Icon :icon="showBuilder ? 'ep:edit' : 'ep:setting'" />
{{ showBuilder ? '手动编辑' : '可视化编辑' }}
@@ -16,12 +16,12 @@
<!-- 可视化编辑器 -->
<!-- TODO @puhui999是不是复用现有的 cron 组件不然有点重复哈维护比较复杂 -->
<div v-if="showBuilder" class="visual-builder">
<div v-if="showBuilder" class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
<CronBuilder v-model="localValue" @validate="handleValidate" />
</div>
<!-- 手动编辑 -->
<div v-else class="manual-editor">
<div v-else class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
<el-form-item label="CRON表达式" required>
<CronInput v-model="localValue" @validate="handleValidate" />
</el-form-item>
@@ -31,7 +31,7 @@
<NextExecutionPreview :cron-expression="localValue" />
<!-- 验证结果 -->
<div v-if="validationMessage" class="validation-result">
<div v-if="validationMessage" class="mt-8px">
<el-alert
:title="validationMessage"
:type="isValid ? 'success' : 'error'"
@@ -85,55 +85,4 @@ onMounted(() => {
})
</script>
<style scoped>
.timer-trigger-config {
display: flex;
flex-direction: column;
gap: 16px;
}
.config-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--el-fill-color-light);
border-radius: 6px;
border: 1px solid var(--el-border-color-lighter);
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.header-icon {
color: var(--el-color-danger);
font-size: 18px;
}
.header-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.visual-builder,
.manual-editor {
padding: 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
background: var(--el-fill-color-blank);
}
.validation-result {
margin-top: 8px;
}
</style>

View File

@@ -1,21 +1,21 @@
<!-- 配置预览组件 -->
<!-- TODO @puhui999应该暂时不用预览哈 -->
<template>
<div class="config-preview">
<div class="preview-items">
<div class="preview-item">
<span class="item-label">场景名称</span>
<span class="item-value">{{ formData.name || '未设置' }}</span>
<div class="w-full">
<div class="space-y-8px">
<div class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">场景名称</span>
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ formData.name || '未设置' }}</span>
</div>
<div class="preview-item">
<span class="item-label">场景状态</span>
<div class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">场景状态</span>
<el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small">
{{ formData.status === 0 ? '启用' : '禁用' }}
</el-tag>
</div>
<div v-if="formData.description" class="preview-item">
<span class="item-label">场景描述</span>
<span class="item-value">{{ formData.description }}</span>
<div v-if="formData.description" class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">场景描述</span>
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ formData.description }}</span>
</div>
</div>
</div>
@@ -34,33 +34,4 @@ interface Props {
defineProps<Props>()
</script>
<style scoped>
.config-preview {
width: 100%;
}
.preview-items {
display: flex;
flex-direction: column;
gap: 8px;
}
.preview-item {
display: flex;
align-items: center;
gap: 8px;
}
.item-label {
font-size: 12px;
color: var(--el-text-color-secondary);
min-width: 80px;
flex-shrink: 0;
}
.item-value {
font-size: 12px;
color: var(--el-text-color-primary);
flex: 1;
}
</style>

View File

@@ -1,24 +1,24 @@
<!-- 触发器预览组件 -->
<template>
<div class="trigger-preview">
<div v-if="triggers.length === 0" class="empty-preview">
<div class="w-full">
<div v-if="triggers.length === 0" class="text-center py-20px">
<el-text type="info" size="small">暂无触发器配置</el-text>
</div>
<div v-else class="trigger-list">
<div v-else class="space-y-12px">
<div
v-for="(trigger, index) in triggers"
:key="index"
class="trigger-item"
class="p-12px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
>
<div class="trigger-header">
<Icon icon="ep:lightning" class="trigger-icon" />
<span class="trigger-title">触发器 {{ index + 1 }}</span>
<div class="flex items-center gap-8px mb-8px">
<Icon icon="ep:lightning" class="text-[var(--el-color-warning)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发器 {{ index + 1 }}</span>
<el-tag :type="getTriggerTypeTag(trigger.type)" size="small">
{{ getTriggerTypeName(trigger.type) }}
</el-tag>
</div>
<div class="trigger-content">
<div class="trigger-summary">
<div class="pl-24px">
<div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
{{ getTriggerSummary(trigger) }}
</div>
</div>
@@ -77,55 +77,4 @@ const getTriggerSummary = (trigger: TriggerFormData) => {
}
</script>
<style scoped>
.trigger-preview {
width: 100%;
}
.empty-preview {
text-align: center;
padding: 20px 0;
}
.trigger-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.trigger-item {
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
background: var(--el-fill-color-blank);
}
.trigger-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.trigger-icon {
color: var(--el-color-warning);
font-size: 14px;
}
.trigger-title {
font-size: 12px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.trigger-content {
padding: 8px 12px;
}
.trigger-summary {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
}
</style>