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

This commit is contained in:
puhui999
2025-07-26 14:20:07 +08:00
parent 23c5ca5a66
commit 751daf5fbb
5 changed files with 73 additions and 395 deletions

View File

@ -1,7 +1,7 @@
<!-- 设备控制配置组件 --> <!-- 设备控制配置组件 -->
<!-- TODO @puhui999貌似没生效~~~ --> <!-- TODO @puhui999貌似没生效~~~ -->
<template> <template>
<div class="device-control-config"> <div class="flex flex-col gap-16px">
<!-- 产品和设备选择 --> <!-- 产品和设备选择 -->
<ProductDeviceSelector <ProductDeviceSelector
v-model:product-id="action.productId" v-model:product-id="action.productId"
@ -10,7 +10,7 @@
/> />
<!-- 控制参数配置 --> <!-- 控制参数配置 -->
<div v-if="action.productId && action.deviceId" class="control-params"> <div v-if="action.productId && action.deviceId" class="space-y-16px">
<el-form-item label="控制参数" required> <el-form-item label="控制参数" required>
<el-input <el-input
v-model="paramsJson" v-model="paramsJson"
@ -22,14 +22,14 @@
</el-form-item> </el-form-item>
<!-- 参数示例 --> <!-- 参数示例 -->
<div class="params-example"> <div class="mt-12px">
<el-alert title="参数格式示例" type="info" :closable="false" show-icon> <el-alert title="参数格式示例" type="info" :closable="false" show-icon>
<template #default> <template #default>
<div class="example-content"> <div class="space-y-8px">
<p>属性设置示例</p> <p class="m-0 text-14px text-[var(--el-text-color-primary)]">属性设置示例</p>
<pre><code>{ "temperature": 25, "power": true }</code></pre> <pre class="m-0 p-8px bg-[var(--el-fill-color-light)] rounded-4px text-12px text-[var(--el-text-color-regular)] overflow-x-auto"><code>{ "temperature": 25, "power": true }</code></pre>
<p>服务调用示例</p> <p class="m-0 text-14px text-[var(--el-text-color-primary)]">服务调用示例</p>
<pre><code>{ "method": "restart", "params": { "delay": 5 } }</code></pre> <pre class="m-0 p-8px bg-[var(--el-fill-color-light)] rounded-4px text-12px text-[var(--el-text-color-regular)] overflow-x-auto"><code>{ "method": "restart", "params": { "delay": 5 } }</code></pre>
</div> </div>
</template> </template>
</el-alert> </el-alert>
@ -37,7 +37,7 @@
</div> </div>
<!-- 验证结果 --> <!-- 验证结果 -->
<div v-if="validationMessage" class="validation-result"> <div v-if="validationMessage" class="mt-16px">
<el-alert <el-alert
:title="validationMessage" :title="validationMessage"
:type="isValid ? 'success' : 'error'" :type="isValid ? 'success' : 'error'"
@ -140,34 +140,8 @@ watch(
</script> </script>
<style scoped> <style scoped>
.device-control-config { :deep(.example-content code) {
display: flex;
flex-direction: column;
gap: 16px;
}
.control-params {
margin-top: 16px;
}
.params-example {
margin-top: 8px;
}
.example-content pre {
margin: 4px 0;
padding: 8px;
background: var(--el-fill-color-light);
border-radius: 4px;
font-size: 12px;
}
.example-content code {
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
color: var(--el-color-primary); color: var(--el-color-primary);
} }
.validation-result {
margin-top: 8px;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<!-- 场景描述输入组件 --> <!-- 场景描述输入组件 -->
<template> <template>
<div class="description-input"> <div class="relative w-full">
<el-input <el-input
ref="inputRef" ref="inputRef"
v-model="localValue" v-model="localValue"
@ -15,22 +15,22 @@
<!-- 描述模板 --> <!-- 描述模板 -->
<teleport to="body"> <teleport to="body">
<div v-if="showTemplates" ref="templateDropdownRef" class="templates" :style="dropdownStyle"> <div v-if="showTemplates" ref="templateDropdownRef" class="fixed z-1000 bg-white border border-[var(--el-border-color-light)] rounded-6px shadow-[var(--el-box-shadow)] min-w-300px max-w-400px" :style="dropdownStyle">
<div class="templates-header"> <div class="flex items-center justify-between p-12px border-b border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-light)]">
<span class="templates-title">描述模板</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">描述模板</span>
<el-button type="text" size="small" @click="showTemplates = false"> <el-button type="text" size="small" @click="showTemplates = false">
<Icon icon="ep:close" /> <Icon icon="ep:close" />
</el-button> </el-button>
</div> </div>
<div class="templates-list"> <div class="max-h-300px overflow-y-auto">
<div <div
v-for="template in descriptionTemplates" v-for="template in descriptionTemplates"
:key="template.title" :key="template.title"
class="template-item" class="p-12px border-b border-[var(--el-border-color-lighter)] cursor-pointer transition-colors duration-200 hover:bg-[var(--el-fill-color-light)] last:border-b-0"
@click="applyTemplate(template)" @click="applyTemplate(template)"
> >
<div class="template-title">{{ template.title }}</div> <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-4px">{{ template.title }}</div>
<div class="template-content">{{ template.content }}</div> <div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">{{ template.content }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -38,7 +38,7 @@
<!-- TODO @puhui999不用模版哈简单点 --> <!-- TODO @puhui999不用模版哈简单点 -->
<!-- 模板按钮 --> <!-- 模板按钮 -->
<div v-if="!localValue && !showTemplates" class="template-trigger"> <div v-if="!localValue && !showTemplates" class="absolute top-2px right-2px">
<el-button type="text" size="small" @click="toggleTemplates"> <el-button type="text" size="small" @click="toggleTemplates">
<Icon icon="ep:document" class="mr-1" /> <Icon icon="ep:document" class="mr-1" />
使用模板 使用模板
@ -175,75 +175,4 @@ onUnmounted(() => {
}) })
</script> </script>
<style scoped>
.description-input {
position: relative;
width: 100%;
}
.templates {
position: fixed;
z-index: 9999;
background: white;
border: 1px solid var(--el-border-color-light);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 300px;
max-width: 400px;
max-height: 400px;
overflow: hidden;
}
.templates-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-light);
}
.templates-title {
font-size: 12px;
color: var(--el-text-color-secondary);
font-weight: 500;
}
.templates-list {
max-height: 300px;
overflow-y: auto;
}
.template-item {
padding: 12px;
cursor: pointer;
transition: background-color 0.2s;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.template-item:hover {
background: var(--el-fill-color-light);
}
.template-item:last-child {
border-bottom: none;
}
.template-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
margin-bottom: 4px;
}
.template-content {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
}
.template-trigger {
margin-top: 8px;
text-align: right;
}
</style>

View File

@ -1,24 +1,24 @@
<!-- 执行器预览组件 --> <!-- 执行器预览组件 -->
<template> <template>
<div class="action-preview"> <div class="w-full">
<div v-if="actions.length === 0" class="empty-preview"> <div v-if="actions.length === 0" class="text-center py-20px">
<el-text type="info" size="small">暂无执行器配置</el-text> <el-text type="info" size="small">暂无执行器配置</el-text>
</div> </div>
<div v-else class="action-list"> <div v-else class="space-y-12px">
<div <div
v-for="(action, index) in actions" v-for="(action, index) in actions"
:key="index" :key="index"
class="action-item" class="p-12px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
> >
<div class="action-header"> <div class="flex items-center gap-8px mb-8px">
<Icon icon="ep:setting" class="action-icon" /> <Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
<span class="action-title">执行器 {{ index + 1 }}</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">执行器 {{ index + 1 }}</span>
<el-tag :type="getActionTypeTag(action.type)" size="small"> <el-tag :type="getActionTypeTag(action.type)" size="small">
{{ getActionTypeName(action.type) }} {{ getActionTypeName(action.type) }}
</el-tag> </el-tag>
</div> </div>
<div class="action-content"> <div class="pl-24px">
<div class="action-summary"> <div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
{{ getActionSummary(action) }} {{ getActionSummary(action) }}
</div> </div>
</div> </div>
@ -73,55 +73,4 @@ const getActionSummary = (action: ActionFormData) => {
} }
</script> </script>
<style scoped>
.action-preview {
width: 100%;
}
.empty-preview {
text-align: center;
padding: 20px 0;
}
.action-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.action-item {
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
background: var(--el-fill-color-blank);
}
.action-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);
}
.action-icon {
color: var(--el-color-success);
font-size: 14px;
}
.action-title {
font-size: 12px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.action-content {
padding: 8px 12px;
}
.action-summary {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
}
</style>

View File

@ -1,15 +1,15 @@
<!-- 执行器配置组件 --> <!-- 执行器配置组件 -->
<!-- todo @puhui999参考触发器配置简化下 --> <!-- todo @puhui999参考触发器配置简化下 -->
<template> <template>
<el-card class="action-section" shadow="never"> <el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
<template #header> <template #header>
<div class="section-header"> <div class="flex items-center justify-between">
<div class="header-left"> <div class="flex items-center gap-8px">
<Icon icon="ep:setting" class="section-icon" /> <Icon icon="ep:setting" class="text-[var(--el-color-primary)] text-18px" />
<span class="section-title">执行器配置</span> <span class="text-16px font-600 text-[var(--el-text-color-primary)]">执行器配置</span>
<el-tag size="small" type="info">{{ actions.length }}/{{ maxActions }}</el-tag> <el-tag size="small" type="info">{{ actions.length }}/{{ maxActions }}</el-tag>
</div> </div>
<div class="header-right"> <div class="flex items-center gap-8px">
<el-button <el-button
type="primary" type="primary"
size="small" size="small"
@ -23,9 +23,9 @@
</div> </div>
</template> </template>
<div class="section-content"> <div class="p-0">
<!-- 空状态 --> <!-- 空状态 -->
<div v-if="actions.length === 0" class="empty-state"> <div v-if="actions.length === 0">
<el-empty description="暂无执行器配置"> <el-empty description="暂无执行器配置">
<el-button type="primary" @click="addAction"> <el-button type="primary" @click="addAction">
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
@ -35,17 +35,17 @@
</div> </div>
<!-- 执行器列表 --> <!-- 执行器列表 -->
<div v-else class="actions-list"> <div v-else class="space-y-16px">
<div v-for="(action, index) in actions" :key="`action-${index}`" class="action-item"> <div v-for="(action, index) in actions" :key="`action-${index}`" class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
<div class="action-header"> <div class="flex items-center justify-between mb-16px">
<div class="action-title"> <div class="flex items-center gap-8px">
<Icon icon="ep:setting" class="action-icon" /> <Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
<span>执行器 {{ index + 1 }}</span> <span>执行器 {{ index + 1 }}</span>
<el-tag :type="getActionTypeTag(action.type)" size="small"> <el-tag :type="getActionTypeTag(action.type)" size="small">
{{ getActionTypeName(action.type) }} {{ getActionTypeName(action.type) }}
</el-tag> </el-tag>
</div> </div>
<div class="action-actions"> <div>
<el-button <el-button
type="danger" type="danger"
size="small" size="small"
@ -59,7 +59,7 @@
</div> </div>
</div> </div>
<div class="action-content"> <div class="space-y-16px">
<!-- 执行类型选择 --> <!-- 执行类型选择 -->
<ActionTypeSelector <ActionTypeSelector
:model-value="action.type" :model-value="action.type"
@ -87,12 +87,12 @@
</div> </div>
<!-- 添加提示 --> <!-- 添加提示 -->
<div v-if="actions.length > 0 && actions.length < maxActions" class="add-more"> <div v-if="actions.length > 0 && actions.length < maxActions" class="text-center py-16px">
<el-button type="primary" plain @click="addAction" class="add-more-btn"> <el-button type="primary" plain @click="addAction">
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
继续添加执行器 继续添加执行器
</el-button> </el-button>
<span class="add-more-text"> 最多可添加 {{ maxActions }} 个执行器 </span> <span class="block mt-8px text-12px text-[var(--el-text-color-secondary)]"> 最多可添加 {{ maxActions }} 个执行器 </span>
</div> </div>
<!-- 验证结果 --> <!-- 验证结果 -->
@ -266,107 +266,4 @@ watch(
) )
</script> </script>
<style scoped>
.action-section {
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.section-icon {
color: var(--el-color-primary);
font-size: 18px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.section-content {
padding: 0;
}
.empty-state {
padding: 40px 0;
text-align: center;
}
.actions-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.action-item {
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
background: var(--el-fill-color-blank);
}
.action-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);
}
.action-title {
display: flex;
align-items: center;
gap: 8px;
}
.action-icon {
color: var(--el-color-success);
font-size: 16px;
}
.action-content {
padding: 16px;
}
.add-more {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
padding: 16px;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
background: var(--el-fill-color-lighter);
}
.add-more-btn {
flex-shrink: 0;
}
.add-more-text {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.validation-result {
margin-top: 16px;
}
</style>

View File

@ -1,14 +1,14 @@
<!-- 预览区域组件 --> <!-- 预览区域组件 -->
<!-- TODO @puhui999是不是不用这个哈 --> <!-- TODO @puhui999是不是不用这个哈 -->
<template> <template>
<el-card class="preview-section" shadow="never"> <el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
<template #header> <template #header>
<div class="section-header"> <div class="flex items-center justify-between">
<div class="header-left"> <div class="flex items-center gap-8px">
<Icon icon="ep:view" class="section-icon" /> <Icon icon="ep:view" class="text-[var(--el-color-primary)] text-18px" />
<span class="section-title">配置预览</span> <span class="text-16px font-600 text-[var(--el-text-color-primary)]">配置预览</span>
</div> </div>
<div class="header-right"> <div class="flex items-center gap-8px">
<el-button type="primary" size="small" @click="handleValidate" :loading="validating"> <el-button type="primary" size="small" @click="handleValidate" :loading="validating">
<Icon icon="ep:check" /> <Icon icon="ep:check" />
验证配置 验证配置
@ -17,49 +17,49 @@
</div> </div>
</template> </template>
<div class="section-content"> <div class="p-0">
<!-- 基础信息预览 --> <!-- 基础信息预览 -->
<div class="preview-group"> <div class="mb-20px">
<div class="group-header"> <div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:info-filled" class="group-icon" /> <Icon icon="ep:info-filled" class="text-[var(--el-color-info)] text-16px" />
<span class="group-title">基础信息</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">基础信息</span>
</div> </div>
<div class="group-content"> <div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<ConfigPreview :form-data="formData" /> <ConfigPreview :form-data="formData" />
</div> </div>
</div> </div>
<!-- 触发器预览 --> <!-- 触发器预览 -->
<div class="preview-group"> <div class="mb-20px">
<div class="group-header"> <div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:lightning" class="group-icon" /> <Icon icon="ep:lightning" class="text-[var(--el-color-warning)] text-16px" />
<span class="group-title">触发器配置</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发器配置</span>
<el-tag size="small" type="primary">{{ formData.triggers.length }}</el-tag> <el-tag size="small" type="primary">{{ formData.triggers.length }}</el-tag>
</div> </div>
<div class="group-content"> <div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<TriggerPreview :triggers="formData.triggers" /> <TriggerPreview :triggers="formData.triggers" />
</div> </div>
</div> </div>
<!-- 执行器预览 --> <!-- 执行器预览 -->
<div class="preview-group"> <div class="mb-20px">
<div class="group-header"> <div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:setting" class="group-icon" /> <Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
<span class="group-title">执行器配置</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">执行器配置</span>
<el-tag size="small" type="success">{{ formData.actions.length }}</el-tag> <el-tag size="small" type="success">{{ formData.actions.length }}</el-tag>
</div> </div>
<div class="group-content"> <div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<ActionPreview :actions="formData.actions" /> <ActionPreview :actions="formData.actions" />
</div> </div>
</div> </div>
<!-- 验证结果 --> <!-- 验证结果 -->
<div class="preview-group"> <div class="mb-20px">
<div class="group-header"> <div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:circle-check" class="group-icon" /> <Icon icon="ep:circle-check" class="text-[var(--el-color-primary)] text-16px" />
<span class="group-title">验证结果</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">验证结果</span>
</div> </div>
<div class="group-content"> <div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<ValidationResult :validation-result="validationResult" /> <ValidationResult :validation-result="validationResult" />
</div> </div>
</div> </div>
@ -105,75 +105,4 @@ const handleValidate = async () => {
} }
</script> </script>
<style scoped>
.preview-section {
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.section-icon {
color: var(--el-color-primary);
font-size: 18px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.section-content {
padding: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.preview-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;
gap: 8px;
padding: 12px 16px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.group-icon {
color: var(--el-color-primary);
font-size: 16px;
}
.group-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.group-content {
padding: 16px;
}
</style>