refactor: 收敛 iot rule/scene API 的重复类型声明(P1)

- antd / ele api/iot/rule/scene,删除外层重复的 4 个 interface
- createSceneRule / updateSceneRule 入参改用 RuleSceneApi.SceneRule
- 业务文件 import 统一改用 RuleSceneApi.SceneRule / Trigger / TriggerCondition / Action
- 清理 2 处 TODO @haohao 残留注释

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pull/345/head
YunaiV 2026-05-21 11:00:30 +08:00
parent df720b2c1a
commit 057ca0bfde
22 changed files with 183 additions and 292 deletions

View File

@ -50,53 +50,6 @@ export namespace RuleSceneApi {
}
}
// TODO @haohao貌似下面的和 RuleSceneApi 重复了。
/** IoT 场景联动规则 */
export interface IotSceneRule {
id?: number;
name?: string;
description?: string;
status?: number;
triggers?: Trigger[];
actions?: Action[];
createTime?: Date;
}
/** IoT 场景联动规则触发器 */
export interface Trigger {
type?: number;
productId?: number;
deviceId?: number;
identifier?: string;
operator?: string;
value?: any;
cronExpression?: string;
// 后端结构List<List<TriggerCondition>>;外层「或」、组内「且」
conditionGroups?: TriggerCondition[][];
}
/** IoT 场景联动规则触发条件 */
export interface TriggerCondition {
productId?: number;
deviceId?: number;
identifier?: string;
operator?: string;
value?: any;
type?: number;
param?: string;
}
/** IoT 场景联动规则动作 */
export interface Action {
type?: number;
productId?: number;
deviceId?: number;
identifier?: string;
value?: any;
alertConfigId?: number;
params?: Record<string, any>;
}
/** 查询场景联动规则分页 */
export function getSceneRulePage(params: PageParam) {
return requestClient.get<PageResult<RuleSceneApi.SceneRule>>(
@ -113,12 +66,12 @@ export function getSceneRule(id: number) {
}
/** 新增场景联动规则 */
export function createSceneRule(data: IotSceneRule) {
export function createSceneRule(data: RuleSceneApi.SceneRule) {
return requestClient.post('/iot/scene-rule/create', data);
}
/** 修改场景联动规则 */
export function updateSceneRule(data: IotSceneRule) {
export function updateSceneRule(data: RuleSceneApi.SceneRule) {
return requestClient.put('/iot/scene-rule/update', data);
}
@ -128,7 +81,6 @@ export function deleteSceneRule(id: number) {
}
/** 批量删除场景联动规则 */
// TODO @haohao貌似用上。
export function deleteSceneRuleList(ids: number[]) {
return requestClient.delete('/iot/scene-rule/delete-list', {
params: { ids: ids.join(',') },

View File

@ -1,6 +1,6 @@
<!-- 单个条件配置组件 -->
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, ref } from 'vue';
@ -25,12 +25,12 @@ import CurrentTimeConditionConfig from './current-time-condition-config.vue';
defineOptions({ name: 'ConditionConfig' });
const props = defineProps<{
modelValue: TriggerCondition;
modelValue: RuleSceneApi.TriggerCondition;
triggerType: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition): void;
}>();
/** 获取设备状态选项 */
@ -84,7 +84,7 @@ function updateConditionField(field: any, value: any) {
* 更新整个条件对象
* @param newCondition 新的条件对象
*/
function updateCondition(newCondition: TriggerCondition) {
function updateCondition(newCondition: RuleSceneApi.TriggerCondition) {
condition.value = newCondition;
emit('update:modelValue', condition.value);
}
@ -163,10 +163,12 @@ function handleOperatorChange() {
<Form.Item label="条件类型" required>
<Select
:value="condition.type"
@change="(value: any) => {
updateConditionField('type', value);
handleConditionTypeChange(value);
}"
@change="
(value: any) => {
updateConditionField('type', value);
handleConditionTypeChange(value);
}
"
placeholder="请选择条件类型"
class="w-full"
>
@ -212,8 +214,7 @@ function handleOperatorChange() {
<!-- 设备状态条件配置 -->
<div
v-if="
condition.type ===
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
"
class="gap-[16px] flex flex-col"
>
@ -224,10 +225,7 @@ function handleOperatorChange() {
<Form.Item label="操作符" required>
<Select
:value="condition.operator"
@change="
(value: any) => updateConditionField('operator', value)
"
@change="(value: any) => updateConditionField('operator', value)"
placeholder="请选择操作符"
class="w-full"
>
@ -247,10 +245,7 @@ function handleOperatorChange() {
<Form.Item label="设备状态" required>
<Select
:value="condition.param"
@change="
(value: any) => updateConditionField('param', value)
"
@change="(value: any) => updateConditionField('param', value)"
placeholder="请选择设备状态"
class="w-full"
>
@ -270,8 +265,7 @@ function handleOperatorChange() {
<!-- 设备属性条件配置 -->
<div
v-else-if="
condition.type ===
IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
"
class="space-y-[16px]"
>
@ -327,8 +321,7 @@ function handleOperatorChange() {
<!-- 当前时间条件配置 -->
<CurrentTimeConditionConfig
v-else-if="
condition.type ===
IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
"
:model-value="condition"
@update:model-value="updateCondition"

View File

@ -1,6 +1,6 @@
<!-- 当前时间条件配置组件 -->
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, watch } from 'vue';
@ -22,11 +22,11 @@ import {
defineOptions({ name: 'CurrentTimeConditionConfig' });
const props = defineProps<{
modelValue: TriggerCondition;
modelValue: RuleSceneApi.TriggerCondition;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition): void;
}>();
const condition = useVModel(props, 'modelValue', emit);

View File

@ -1,6 +1,6 @@
<!-- 设备控制配置组件 -->
<script setup lang="ts">
import type { Action } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import type { ThingModelApi } from '#/api/iot/thingmodel';
import { computed, onMounted, ref, watch } from 'vue';
@ -25,11 +25,11 @@ import ProductSelector from '../selectors/product-selector.vue';
defineOptions({ name: 'DeviceControlConfig' });
const props = defineProps<{
modelValue: Action;
modelValue: RuleSceneApi.Action;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: Action): void;
(e: 'update:modelValue', value: RuleSceneApi.Action): void;
}>();
const action = useVModel(props, 'modelValue', emit);
@ -58,18 +58,12 @@ const paramsValue = computed({
//
const isPropertySetAction = computed(() => {
return (
action.value.type ===
IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET
);
return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET;
});
//
const isServiceInvokeAction = computed(() => {
return (
action.value.type ===
IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
);
return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE;
});
/**
@ -358,7 +352,10 @@ watch(
</Row>
<!-- 服务选择 - 服务调用类型时显示 -->
<div v-if="action.productId && isServiceInvokeAction" class="space-y-[16px]">
<div
v-if="action.productId && isServiceInvokeAction"
class="space-y-[16px]"
>
<Form.Item label="服务" required>
<Select
v-model:value="action.identifier"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Trigger } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, ref } from 'vue';
@ -25,12 +25,12 @@ import PropertySelector from '../selectors/property-selector.vue';
defineOptions({ name: 'MainConditionInnerConfig' });
const props = defineProps<{
modelValue: Trigger;
modelValue: RuleSceneApi.Trigger;
triggerType: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: Trigger): void;
(e: 'update:modelValue', value: RuleSceneApi.Trigger): void;
(e: 'triggerTypeChange', value: number): void;
}>();
@ -325,9 +325,7 @@ function handlePropertyChange(propertyInfo: any) {
<Form.Item label="操作符" required>
<Select
:value="condition.operator"
@change="
(value: any) => updateConditionField('operator', value)
"
@change="(value: any) => updateConditionField('operator', value)"
placeholder="请选择操作符"
class="w-full"
>
@ -347,9 +345,7 @@ function handlePropertyChange(propertyInfo: any) {
<Form.Item label="参数" required>
<Select
:value="condition.value"
@change="
(value: any) => updateConditionField('value', value)
"
@change="(value: any) => updateConditionField('value', value)"
placeholder="请选择操作符"
class="w-full"
>
@ -371,7 +367,9 @@ function handlePropertyChange(propertyInfo: any) {
<p class="mb-1 text-sm text-muted-foreground">
当前触发事件类型{{ getTriggerTypeLabel(triggerType) }}
</p>
<p class="text-xs text-muted-foreground">此触发类型暂不需要配置额外条件</p>
<p class="text-xs text-muted-foreground">
此触发类型暂不需要配置额外条件
</p>
</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, nextTick } from 'vue';
@ -19,12 +19,12 @@ defineOptions({ name: 'SubConditionGroupConfig' });
const props = defineProps<{
maxConditions?: number;
modelValue: TriggerCondition[];
modelValue: RuleSceneApi.TriggerCondition[];
triggerType: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition[]): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition[]): void;
}>();
const subGroup = useVModel(props, 'modelValue', emit);
@ -43,7 +43,7 @@ async function addCondition() {
return;
}
const newCondition: TriggerCondition = {
const newCondition: RuleSceneApi.TriggerCondition = {
type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, //
productId: undefined,
deviceId: undefined,
@ -74,7 +74,10 @@ function removeCondition(index: number) {
* @param index 条件索引
* @param condition 条件对象
*/
function updateCondition(index: number, condition: TriggerCondition) {
function updateCondition(
index: number,
condition: RuleSceneApi.TriggerCondition,
) {
if (subGroup.value) {
subGroup.value[index] = condition;
}
@ -138,7 +141,7 @@ function updateCondition(index: number, condition: TriggerCondition) {
<ConditionConfig
:model-value="condition"
@update:model-value="
(value: TriggerCondition) =>
(value: RuleSceneApi.TriggerCondition) =>
updateCondition(conditionIndex, value)
"
:trigger-type="triggerType"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { nextTick } from 'vue';
@ -12,11 +12,11 @@ import { Button, Tag } from 'ant-design-vue';
import SubConditionGroupConfig from './sub-condition-group-config.vue';
const props = defineProps<{
modelValue?: TriggerCondition[][];
modelValue?: RuleSceneApi.TriggerCondition[][];
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition[][]): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition[][]): void;
}>();
const conditionGroups = useVModel(props, 'modelValue', emit);
@ -46,7 +46,10 @@ function removeConditionGroup(index: number) {
}
/** 更新条件组 */
function updateConditionGroup(index: number, group: TriggerCondition[]) {
function updateConditionGroup(
index: number,
group: RuleSceneApi.TriggerCondition[],
) {
if (conditionGroups.value) {
conditionGroups.value[index] = group;
}
@ -117,9 +120,7 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
<Tag color="warning" class="font-medium">
组内条件为关系
</Tag>
<Tag color="default">
{{ group?.length || 0 }} 个条件
</Tag>
<Tag color="default"> {{ group?.length || 0 }} 个条件 </Tag>
</div>
<Button
danger
@ -138,7 +139,7 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
:trigger-type="IotRuleSceneTriggerTypeEnum.TIMER"
:max-conditions="maxConditionsPerGroup"
@update:model-value="
(value: TriggerCondition[]) =>
(value: RuleSceneApi.TriggerCondition[]) =>
updateConditionGroup(groupIndex, value)
"
/>
@ -150,13 +151,19 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
class="py-[12px] flex items-center justify-center"
>
<div class="gap-[8px] flex items-center">
<div class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"></div>
<div
class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
></div>
<div
class="px-[14px] py-[3px] rounded-full border border-orange-300 bg-orange-100 dark:border-orange-800 dark:bg-orange-950/50"
>
<span class="text-[13px] font-semibold text-orange-600 dark:text-orange-300"></span>
<span
class="text-[13px] font-semibold text-orange-600 dark:text-orange-300"
></span>
</div>
<div class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"></div>
<div
class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
></div>
</div>
</div>
</div>
@ -168,7 +175,10 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
class="p-[24px] rounded-[8px] border-2 border-dashed border-blue-200 bg-blue-50/40 text-center dark:border-blue-900/40 dark:bg-blue-950/10"
>
<div class="gap-[10px] flex flex-col items-center">
<IconifyIcon icon="lucide:plus" class="text-[28px] text-blue-400 dark:text-blue-300" />
<IconifyIcon
icon="lucide:plus"
class="text-[28px] text-blue-400 dark:text-blue-300"
/>
<div class="text-blue-600 dark:text-blue-300">
<p class="text-[13px] font-medium mb-[2px]">暂无附加条件</p>
<p class="text-[12px]">定时触发时将直接执行动作</p>

View File

@ -1,6 +1,6 @@
<!-- 执行器配置组件 -->
<script setup lang="ts">
import type { Action } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import {
getActionTypeLabel,
@ -19,11 +19,11 @@ import DeviceControlConfig from '../configs/device-control-config.vue';
defineOptions({ name: 'ActionSection' });
const props = defineProps<{
actions: Action[];
actions: RuleSceneApi.Action[];
}>();
const emit = defineEmits<{
(e: 'update:actions', value: Action[]): void;
(e: 'update:actions', value: RuleSceneApi.Action[]): void;
}>();
const actions = useVModel(props, 'actions', emit);
@ -66,7 +66,7 @@ function isAlertAction(type: number): boolean {
* 创建默认的执行器数据
* @returns 默认执行器对象
*/
function createDefaultActionData(): Action {
function createDefaultActionData(): RuleSceneApi.Action {
return {
type: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, //
productId: undefined,
@ -100,7 +100,7 @@ function removeAction(index: number) {
*/
function updateActionType(index: number, type: number) {
actions.value[index]!.type = type;
onActionTypeChange(actions.value[index] as Action, type);
onActionTypeChange(actions.value[index] as RuleSceneApi.Action, type);
}
/**
@ -108,7 +108,7 @@ function updateActionType(index: number, type: number) {
* @param index 执行器索引
* @param action 执行器对象
*/
function updateAction(index: number, action: Action) {
function updateAction(index: number, action: RuleSceneApi.Action) {
actions.value[index] = action;
}
@ -126,12 +126,12 @@ function updateActionAlertConfig(index: number, alertConfigId?: number) {
* @param action 执行器对象
* @param type 执行器类型
*/
function onActionTypeChange(action: Action, type: number) {
function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
if (isDeviceAction(type)) {
//
action.alertConfigId = undefined;
if (!action.params) {
action.params = {};
action.params = '';
}
// identifier
if (action.identifier && type !== action.type) {
@ -153,7 +153,9 @@ function onActionTypeChange(action: Action, type: number) {
<div class="flex items-center justify-between">
<div class="gap-[8px] flex items-center">
<IconifyIcon icon="ep:setting" class="text-[18px] text-primary" />
<span class="text-[16px] font-semibold text-foreground"> 执行器配置 </span>
<span class="text-[16px] font-semibold text-foreground">
执行器配置
</span>
<Tag color="default"> {{ actions.length }} 个执行器 </Tag>
</div>
<div class="gap-[8px] flex items-center">
@ -251,10 +253,7 @@ function onActionTypeChange(action: Action, type: number) {
<!-- 告警配置 - 只有恢复告警时才显示 -->
<AlertConfig
v-if="
action.type ===
IotRuleSceneActionTypeEnum.ALERT_RECOVER
"
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER"
:model-value="action.alertConfigId"
@update:model-value="
(value) => updateActionAlertConfig(index, value)
@ -263,10 +262,7 @@ function onActionTypeChange(action: Action, type: number) {
<!-- 触发告警提示 - 触发告警时显示 -->
<div
v-if="
action.type ===
IotRuleSceneActionTypeEnum.ALERT_TRIGGER
"
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER"
class="bg-fill-color-blank rounded-lg border border-border p-4"
>
<div class="mb-2 flex items-center gap-2">

View File

@ -1,6 +1,6 @@
<!-- 基础信息配置组件 -->
<script setup lang="ts">
import type { IotSceneRule } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@ -15,11 +15,11 @@ import { DictTag } from '#/components/dict-tag';
defineOptions({ name: 'BasicInfoSection' });
const props = defineProps<{
modelValue: IotSceneRule;
modelValue: RuleSceneApi.SceneRule;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: IotSceneRule): void;
(e: 'update:modelValue', value: RuleSceneApi.SceneRule): void;
}>();
const formData = useVModel(props, 'modelValue', emit); //

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Trigger, TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { onMounted } from 'vue';
@ -22,11 +22,11 @@ import TimerConditionGroupConfig from '../configs/timer-condition-group-config.v
defineOptions({ name: 'TriggerSection' });
const props = defineProps<{
triggers: Trigger[];
triggers: RuleSceneApi.Trigger[];
}>();
const emit = defineEmits<{
(e: 'update:triggers', value: Trigger[]): void;
(e: 'update:triggers', value: RuleSceneApi.Trigger[]): void;
}>();
const triggers = useVModel(props, 'triggers', emit);
@ -43,7 +43,7 @@ function getTriggerTagColor(
/** 添加触发器 */
function addTrigger() {
const newTrigger: Trigger = {
const newTrigger: RuleSceneApi.Trigger = {
type: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
productId: undefined,
deviceId: undefined,
@ -70,7 +70,10 @@ function updateTriggerType(index: number, type: number) {
}
/** 更新触发器设备配置 */
function updateTriggerDeviceConfig(index: number, newTrigger: Trigger) {
function updateTriggerDeviceConfig(
index: number,
newTrigger: RuleSceneApi.Trigger,
) {
triggers.value[index] = newTrigger;
}
@ -82,7 +85,7 @@ function updateTriggerCronConfig(index: number, cronExpression?: string) {
/** 更新触发器条件组配置 */
function updateTriggerConditionGroups(
index: number,
conditionGroups: TriggerCondition[][],
conditionGroups: RuleSceneApi.TriggerCondition[][],
) {
triggers.value[index]!.conditionGroups = conditionGroups;
}
@ -183,10 +186,7 @@ onMounted(() => {
<!-- 定时触发配置 -->
<div
v-else-if="
triggerItem.type ===
IotRuleSceneTriggerTypeEnum.TIMER
"
v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER"
class="gap-[16px] flex flex-col"
>
<div

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { IotSceneRule, RuleSceneApi } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, nextTick, reactive, ref } from 'vue';
@ -29,7 +29,7 @@ defineOptions({ name: 'IoTRuleSceneForm' });
const emit = defineEmits(['success']);
const formRef = ref();
const formData = ref<IotSceneRule>(buildEmptyFormData());
const formData = ref<RuleSceneApi.SceneRule>(buildEmptyFormData());
const getTitle = computed(() =>
formData.value.id
@ -46,7 +46,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
}
drawerApi.lock();
try {
const data = { ...formData.value } as IotSceneRule;
const data = { ...formData.value } as RuleSceneApi.SceneRule;
await (data.id ? updateSceneRule(data) : createSceneRule(data));
await drawerApi.close();
emit('success');
@ -78,7 +78,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
});
/** 构造空白表单数据 */
function buildEmptyFormData(): IotSceneRule {
function buildEmptyFormData(): RuleSceneApi.SceneRule {
return {
name: '',
description: '',
@ -100,7 +100,7 @@ function buildEmptyFormData(): IotSceneRule {
}
/** 回显时兜底,保证触发器/执行器数组不为空 */
function normalizeFormData(result: any): IotSceneRule {
function normalizeFormData(result: any): RuleSceneApi.SceneRule {
return {
...result,
triggers: result.triggers?.length

View File

@ -51,53 +51,6 @@ export namespace RuleSceneApi {
}
}
// TODO @haohao貌似下面的和 RuleSceneApi 重复了。
/** IoT 场景联动规则 */
export interface IotSceneRule {
id?: number;
name?: string;
description?: string;
status?: number;
triggers?: Trigger[];
actions?: Action[];
createTime?: Date;
}
/** IoT 场景联动规则触发器 */
export interface Trigger {
type?: number;
productId?: number;
deviceId?: number;
identifier?: string;
operator?: string;
value?: any;
cronExpression?: string;
// 后端结构List<List<TriggerCondition>>;外层「或」、组内「且」
conditionGroups?: TriggerCondition[][];
}
/** IoT 场景联动规则触发条件 */
export interface TriggerCondition {
productId?: number;
deviceId?: number;
identifier?: string;
operator?: string;
value?: any;
type?: number;
param?: string;
}
/** IoT 场景联动规则动作 */
export interface Action {
type?: number;
productId?: number;
deviceId?: number;
identifier?: string;
value?: any;
alertConfigId?: number;
params?: Record<string, any>;
}
/** 查询场景联动规则分页 */
export function getSceneRulePage(params: PageParam) {
return requestClient.get<PageResult<RuleSceneApi.SceneRule>>(
@ -114,12 +67,12 @@ export function getSceneRule(id: number) {
}
/** 新增场景联动规则 */
export function createSceneRule(data: IotSceneRule) {
export function createSceneRule(data: RuleSceneApi.SceneRule) {
return requestClient.post('/iot/scene-rule/create', data);
}
/** 修改场景联动规则 */
export function updateSceneRule(data: IotSceneRule) {
export function updateSceneRule(data: RuleSceneApi.SceneRule) {
return requestClient.put('/iot/scene-rule/update', data);
}
@ -129,7 +82,6 @@ export function deleteSceneRule(id: number) {
}
/** 批量删除场景联动规则 */
// TODO @haohao貌似用上。
export function deleteSceneRuleList(ids: number[]) {
return requestClient.delete('/iot/scene-rule/delete-list', {
params: { ids: ids.join(',') },

View File

@ -1,6 +1,6 @@
<!-- 单个条件配置组件 -->
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, ref } from 'vue';
@ -12,13 +12,7 @@ import {
} from '@vben/constants';
import { useVModel } from '@vueuse/core';
import {
ElCol,
ElFormItem,
ElOption,
ElRow,
ElSelect,
} from 'element-plus';
import { ElCol, ElFormItem, ElOption, ElRow, ElSelect } from 'element-plus';
import ValueInput from '../inputs/value-input.vue';
import DeviceSelector from '../selectors/device-selector.vue';
@ -31,12 +25,12 @@ import CurrentTimeConditionConfig from './current-time-condition-config.vue';
defineOptions({ name: 'ConditionConfig' });
const props = defineProps<{
modelValue: TriggerCondition;
modelValue: RuleSceneApi.TriggerCondition;
triggerType: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition): void;
}>();
/** 获取设备状态选项 */
@ -90,7 +84,7 @@ function updateConditionField(field: any, value: any) {
* 更新整个条件对象
* @param newCondition 新的条件对象
*/
function updateCondition(newCondition: TriggerCondition) {
function updateCondition(newCondition: RuleSceneApi.TriggerCondition) {
condition.value = newCondition;
emit('update:modelValue', condition.value);
}
@ -217,8 +211,7 @@ function handleOperatorChange() {
<!-- 设备状态条件配置 -->
<div
v-if="
condition.type ===
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
"
class="gap-16px flex flex-col"
>
@ -271,8 +264,7 @@ function handleOperatorChange() {
<!-- 设备属性条件配置 -->
<div
v-else-if="
condition.type ===
IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
"
class="space-y-16px"
>
@ -328,8 +320,7 @@ function handleOperatorChange() {
<!-- 当前时间条件配置 -->
<CurrentTimeConditionConfig
v-else-if="
condition.type ===
IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
"
:model-value="condition"
@update:model-value="updateCondition"

View File

@ -1,6 +1,6 @@
<!-- 当前时间条件配置组件 -->
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, watch } from 'vue';
@ -23,11 +23,11 @@ import {
defineOptions({ name: 'CurrentTimeConditionConfig' });
const props = defineProps<{
modelValue: TriggerCondition;
modelValue: RuleSceneApi.TriggerCondition;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition): void;
}>();
const condition = useVModel(props, 'modelValue', emit);

View File

@ -1,6 +1,6 @@
<!-- 设备控制配置组件 -->
<script setup lang="ts">
import type { Action } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import type { ThingModelApi } from '#/api/iot/thingmodel';
import { computed, onMounted, ref, watch } from 'vue';
@ -32,11 +32,11 @@ import ProductSelector from '../selectors/product-selector.vue';
defineOptions({ name: 'DeviceControlConfig' });
const props = defineProps<{
modelValue: Action;
modelValue: RuleSceneApi.Action;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: Action): void;
(e: 'update:modelValue', value: RuleSceneApi.Action): void;
}>();
const action = useVModel(props, 'modelValue', emit);

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Trigger } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, ref } from 'vue';
@ -12,13 +12,7 @@ import {
} from '@vben/constants';
import { useVModel } from '@vueuse/core';
import {
ElCol,
ElFormItem,
ElOption,
ElRow,
ElSelect,
} from 'element-plus';
import { ElCol, ElFormItem, ElOption, ElRow, ElSelect } from 'element-plus';
import JsonParamsInput from '../inputs/json-params-input.vue';
import ValueInput from '../inputs/value-input.vue';
@ -31,12 +25,12 @@ import PropertySelector from '../selectors/property-selector.vue';
defineOptions({ name: 'MainConditionInnerConfig' });
const props = defineProps<{
modelValue: Trigger;
modelValue: RuleSceneApi.Trigger;
triggerType: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: Trigger): void;
(e: 'update:modelValue', value: RuleSceneApi.Trigger): void;
(e: 'triggerTypeChange', value: number): void;
}>();

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, nextTick } from 'vue';
@ -19,12 +19,12 @@ defineOptions({ name: 'SubConditionGroupConfig' });
const props = defineProps<{
maxConditions?: number;
modelValue: TriggerCondition[];
modelValue: RuleSceneApi.TriggerCondition[];
triggerType: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition[]): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition[]): void;
}>();
const subGroup = useVModel(props, 'modelValue', emit);
@ -43,7 +43,7 @@ async function addCondition() {
return;
}
const newCondition: TriggerCondition = {
const newCondition: RuleSceneApi.TriggerCondition = {
type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, //
productId: undefined,
deviceId: undefined,
@ -74,7 +74,10 @@ function removeCondition(index: number) {
* @param index 条件索引
* @param condition 条件对象
*/
function updateCondition(index: number, condition: TriggerCondition) {
function updateCondition(
index: number,
condition: RuleSceneApi.TriggerCondition,
) {
if (subGroup.value) {
subGroup.value[index] = condition;
}
@ -138,7 +141,7 @@ function updateCondition(index: number, condition: TriggerCondition) {
<ConditionConfig
:model-value="condition"
@update:model-value="
(value: TriggerCondition) =>
(value: RuleSceneApi.TriggerCondition) =>
updateCondition(conditionIndex, value)
"
:trigger-type="triggerType"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { TriggerCondition } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { nextTick } from 'vue';
@ -12,11 +12,11 @@ import { ElButton, ElTag } from 'element-plus';
import SubConditionGroupConfig from './sub-condition-group-config.vue';
const props = defineProps<{
modelValue?: TriggerCondition[][];
modelValue?: RuleSceneApi.TriggerCondition[][];
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition[][]): void;
(e: 'update:modelValue', value: RuleSceneApi.TriggerCondition[][]): void;
}>();
const conditionGroups = useVModel(props, 'modelValue', emit);
@ -46,7 +46,10 @@ function removeConditionGroup(index: number) {
}
/** 更新条件组 */
function updateConditionGroup(index: number, group: TriggerCondition[]) {
function updateConditionGroup(
index: number,
group: RuleSceneApi.TriggerCondition[],
) {
if (conditionGroups.value) {
conditionGroups.value[index] = group;
}
@ -117,9 +120,7 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
<ElTag type="warning" class="font-medium">
组内条件为关系
</ElTag>
<ElTag type="info">
{{ group?.length || 0 }} 个条件
</ElTag>
<ElTag type="info"> {{ group?.length || 0 }} 个条件 </ElTag>
</div>
<ElButton
link
@ -138,7 +139,7 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
:trigger-type="IotRuleSceneTriggerTypeEnum.TIMER"
:max-conditions="maxConditionsPerGroup"
@update:model-value="
(value: TriggerCondition[]) =>
(value: RuleSceneApi.TriggerCondition[]) =>
updateConditionGroup(groupIndex, value)
"
/>
@ -150,13 +151,19 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
class="py-[12px] flex items-center justify-center"
>
<div class="gap-[8px] flex items-center">
<div class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"></div>
<div
class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
></div>
<div
class="px-[14px] py-[3px] rounded-full border border-orange-300 bg-orange-100 dark:border-orange-800 dark:bg-orange-950/50"
>
<span class="text-[13px] font-semibold text-orange-600 dark:text-orange-300"></span>
<span
class="text-[13px] font-semibold text-orange-600 dark:text-orange-300"
></span>
</div>
<div class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"></div>
<div
class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
></div>
</div>
</div>
</div>
@ -168,7 +175,10 @@ function updateConditionGroup(index: number, group: TriggerCondition[]) {
class="p-[24px] rounded-[8px] border-2 border-dashed border-blue-200 bg-blue-50/40 text-center dark:border-blue-900/40 dark:bg-blue-950/10"
>
<div class="gap-[10px] flex flex-col items-center">
<IconifyIcon icon="lucide:plus" class="text-[28px] text-blue-400 dark:text-blue-300" />
<IconifyIcon
icon="lucide:plus"
class="text-[28px] text-blue-400 dark:text-blue-300"
/>
<div class="text-blue-600 dark:text-blue-300">
<p class="text-[13px] font-medium mb-[2px]">暂无附加条件</p>
<p class="text-[12px]">定时触发时将直接执行动作</p>

View File

@ -1,6 +1,6 @@
<!-- 执行器配置组件 -->
<script setup lang="ts">
import type { Action } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import {
getActionTypeLabel,
@ -27,11 +27,11 @@ import DeviceControlConfig from '../configs/device-control-config.vue';
defineOptions({ name: 'ActionSection' });
const props = defineProps<{
actions: Action[];
actions: RuleSceneApi.Action[];
}>();
const emit = defineEmits<{
(e: 'update:actions', value: Action[]): void;
(e: 'update:actions', value: RuleSceneApi.Action[]): void;
}>();
const actions = useVModel(props, 'actions', emit);
@ -74,7 +74,7 @@ function isAlertAction(type: number): boolean {
* 创建默认的执行器数据
* @returns 默认执行器对象
*/
function createDefaultActionData(): Action {
function createDefaultActionData(): RuleSceneApi.Action {
return {
type: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, //
productId: undefined,
@ -108,7 +108,7 @@ function removeAction(index: number) {
*/
function updateActionType(index: number, type: number) {
actions.value[index]!.type = type;
onActionTypeChange(actions.value[index] as Action, type);
onActionTypeChange(actions.value[index] as RuleSceneApi.Action, type);
}
/**
@ -116,7 +116,7 @@ function updateActionType(index: number, type: number) {
* @param index 执行器索引
* @param action 执行器对象
*/
function updateAction(index: number, action: Action) {
function updateAction(index: number, action: RuleSceneApi.Action) {
actions.value[index] = action;
}
@ -137,7 +137,7 @@ function updateActionAlertConfig(index: number, alertConfigId?: number) {
* @param action 执行器对象
* @param type 执行器类型
*/
function onActionTypeChange(action: Action, type: any) {
function onActionTypeChange(action: RuleSceneApi.Action, type: any) {
//
if (isDeviceAction(type)) {
//
@ -166,7 +166,9 @@ function onActionTypeChange(action: Action, type: any) {
<div class="gap-8px flex items-center">
<IconifyIcon icon="ep:setting" class="text-18px text-primary" />
<span class="text-16px font-600 text-primary"> 执行器配置 </span>
<ElTag size="small" type="info"> {{ actions.length }} 个执行器 </ElTag>
<ElTag size="small" type="info">
{{ actions.length }} 个执行器
</ElTag>
</div>
<div class="gap-8px flex items-center">
<ElButton type="primary" size="small" @click="addAction">
@ -268,10 +270,7 @@ function onActionTypeChange(action: Action, type: any) {
<!-- 告警配置 - 只有恢复告警时才显示 -->
<AlertConfig
v-if="
action.type ===
IotRuleSceneActionTypeEnum.ALERT_RECOVER
"
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER"
:model-value="action.alertConfigId"
@update:model-value="
(value) => updateActionAlertConfig(index, value)
@ -280,10 +279,7 @@ function onActionTypeChange(action: Action, type: any) {
<!-- 触发告警提示 - 触发告警时显示 -->
<div
v-if="
action.type ===
IotRuleSceneActionTypeEnum.ALERT_TRIGGER
"
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER"
class="bg-fill-color-blank rounded-lg border border-border p-4"
>
<div class="mb-2 flex items-center gap-2">

View File

@ -1,6 +1,6 @@
<!-- 基础信息配置组件 -->
<script setup lang="ts">
import type { IotSceneRule } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@ -23,12 +23,12 @@ import { DictTag } from '#/components/dict-tag';
defineOptions({ name: 'BasicInfoSection' });
const props = defineProps<{
modelValue: IotSceneRule;
modelValue: RuleSceneApi.SceneRule;
rules?: any;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: IotSceneRule): void;
(e: 'update:modelValue', value: RuleSceneApi.SceneRule): void;
}>();
const formData = useVModel(props, 'modelValue', emit); //

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Trigger } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { onMounted } from 'vue';
@ -11,13 +11,7 @@ import {
import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import {
ElButton,
ElCard,
ElEmpty,
ElFormItem,
ElTag,
} from 'element-plus';
import { ElButton, ElCard, ElEmpty, ElFormItem, ElTag } from 'element-plus';
import { CronTab } from '#/components/cron-tab';
@ -27,11 +21,11 @@ import DeviceTriggerConfig from '../configs/device-trigger-config.vue';
defineOptions({ name: 'TriggerSection' });
const props = defineProps<{
triggers: Trigger[];
triggers: RuleSceneApi.Trigger[];
}>();
const emit = defineEmits<{
(e: 'update:triggers', value: Trigger[]): void;
(e: 'update:triggers', value: RuleSceneApi.Trigger[]): void;
}>();
const triggers = useVModel(props, 'triggers', emit);
@ -48,7 +42,7 @@ function getTriggerTagType(
/** 添加触发器 */
function addTrigger() {
const newTrigger: Trigger = {
const newTrigger: RuleSceneApi.Trigger = {
type: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
productId: undefined,
deviceId: undefined,
@ -86,7 +80,10 @@ function updateTriggerType(index: number, type: number) {
* @param index 触发器索引
* @param newTrigger 新的触发器对象
*/
function updateTriggerDeviceConfig(index: number, newTrigger: Trigger) {
function updateTriggerDeviceConfig(
index: number,
newTrigger: RuleSceneApi.Trigger,
) {
triggers.value[index] = newTrigger;
}
@ -130,7 +127,9 @@ onMounted(() => {
<div class="gap-8px flex items-center">
<IconifyIcon icon="ep:lightning" class="text-18px text-primary" />
<span class="text-16px font-600 text-primary">触发器配置</span>
<ElTag size="small" type="info"> {{ triggers.length }} 个触发器 </ElTag>
<ElTag size="small" type="info">
{{ triggers.length }} 个触发器
</ElTag>
</div>
<ElButton type="primary" size="small" @click="addTrigger">
<IconifyIcon icon="lucide:plus" />
@ -200,10 +199,7 @@ onMounted(() => {
<!-- 定时触发配置 -->
<div
v-else-if="
triggerItem.type ===
IotRuleSceneTriggerTypeEnum.TIMER
"
v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER"
class="gap-16px flex flex-col"
>
<div

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { IotSceneRule, RuleSceneApi } from '#/api/iot/rule/scene';
import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, nextTick, reactive, ref } from 'vue';
@ -29,7 +29,7 @@ defineOptions({ name: 'IoTRuleSceneForm' });
const emit = defineEmits(['success']);
const formRef = ref();
const formData = ref<IotSceneRule>(buildEmptyFormData());
const formData = ref<RuleSceneApi.SceneRule>(buildEmptyFormData());
const getTitle = computed(() =>
formData.value.id
@ -46,7 +46,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
}
drawerApi.lock();
try {
const data = { ...formData.value } as IotSceneRule;
const data = { ...formData.value } as RuleSceneApi.SceneRule;
await (data.id ? updateSceneRule(data) : createSceneRule(data));
await drawerApi.close();
emit('success');
@ -78,7 +78,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
});
/** 构造空白表单数据 */
function buildEmptyFormData(): IotSceneRule {
function buildEmptyFormData(): RuleSceneApi.SceneRule {
return {
name: '',
description: '',
@ -100,7 +100,7 @@ function buildEmptyFormData(): IotSceneRule {
}
/** 回显时兜底,保证触发器/执行器数组不为空 */
function normalizeFormData(result: any): IotSceneRule {
function normalizeFormData(result: any): RuleSceneApi.SceneRule {
return {
...result,
triggers: result.triggers?.length