fix(iot): alert 模块对齐后端 VO + 搜索体验对齐 vben 实践(P1)

- alert/config API 删除多余字段 updateTime
- alert/record API 删除多余字段 deviceName / productName / processTime
- alert/record 搜索表单告警级别字段 configLevel → level ,
  对齐后端 IotAlertRecordPageReqVO.level(之前提交后端不会按级别筛选)
- alert/record 搜索表单设备字段改用 ApiSelect + getSimpleDeviceList 全量加载,
  showSearch / filterable 模糊搜索,对齐 vben 项目 13+ 处 ApiSelect 主流实践
- alert/config + alert/record 列表移除空跑 checkbox 列(无批量删除接口)
pull/345/head
YunaiV 2026-05-21 16:39:30 +08:00
parent d2587c17b0
commit d1a2601b6c
35 changed files with 97 additions and 266 deletions

View File

@ -15,7 +15,6 @@ export namespace AlertConfigApi {
receiveUserNames?: string[];
receiveTypes?: number[];
createTime?: Date;
updateTime?: Date;
}
}

View File

@ -10,13 +10,10 @@ export namespace AlertRecordApi {
configName?: string;
configLevel?: number;
deviceId?: number;
deviceName?: string;
productId?: number;
productName?: string;
deviceMessage?: any;
processStatus?: boolean;
processRemark?: string;
processTime?: Date;
createTime?: Date;
}
}

View File

@ -102,7 +102,6 @@ export function getProductByKey(productKey: string) {
});
}
// TODO @AI这个是不是 vue3 + ep 也没做?感觉对齐就好,不用额外做。
/** 同步产品物模型 TDengine 超级表结构 */
export function syncProductPropertyTable(productId: number) {
return requestClient.post(

View File

@ -140,7 +140,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<AlertConfigApi.AlertConfig>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '配置编号',

View File

@ -35,7 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
},
},
{
fieldName: 'configLevel',
fieldName: 'level',
label: '告警级别',
component: 'Select',
componentProps: {
@ -95,7 +95,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<AlertRecordApi.AlertRecord>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '记录编号',

View File

@ -135,8 +135,15 @@ async function updateDeviceConfig() {
/>
<!-- 代码视图 - 只读展示 -->
<div v-if="!isEditing" class="json-viewer-container">
<pre class="json-code"><code>{{ formattedConfig }}</code></pre>
<div
v-if="!isEditing"
class="max-h-[600px] overflow-y-auto rounded border border-[#d9d9d9] bg-[#f5f5f5] p-3 dark:border-[#3a3a3a] dark:bg-[#1a1a1a]"
>
<pre
class="m-0 whitespace-pre-wrap break-words font-mono text-[13px] leading-normal text-[#333] dark:text-gray-300"
>
<code>{{ formattedConfig }}</code>
</pre>
</div>
<!-- 编辑器视图 - 可编辑 -->
@ -144,7 +151,7 @@ async function updateDeviceConfig() {
v-else
v-model:value="configString"
:rows="20"
class="json-editor"
class="font-mono text-[13px]"
placeholder="请输入 JSON 格式的配置信息"
/>
@ -170,29 +177,3 @@ async function updateDeviceConfig() {
</div>
</div>
</template>
<style scoped>
.json-viewer-container {
max-height: 600px;
padding: 12px;
overflow-y: auto;
background-color: #f5f5f5;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.json-code {
margin: 0;
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px;
line-height: 1.5;
color: #333;
overflow-wrap: break-word;
white-space: pre-wrap;
}
.json-editor {
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px;
}
</style>

View File

@ -15,10 +15,10 @@ import { loadBaiduMapSdk } from '#/components/map';
defineOptions({ name: 'DeviceMapCard' });
const router = useRouter();
const mapContainerRef = ref<HTMLElement>();
let mapInstance: any = null;
const loading = ref(true);
const deviceList = ref<IotDeviceApi.Device[]>([]);
const mapContainerRef = ref<HTMLElement>(); //
let mapInstance: any = null; //
const loading = ref(true); //
const deviceList = ref<IotDeviceApi.Device[]>([]); //
/** 是否有数据 */
const hasData = computed(() => deviceList.value.length > 0);

View File

@ -22,10 +22,10 @@ defineOptions({ name: 'MessageTrendCard' });
const messageChartRef = ref();
const { renderEcharts } = useEcharts(messageChartRef);
const loading = ref(false);
const loading = ref(false); //
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
[],
);
); //
/** 时间范围(仅日期,不包含时分秒) */
const dateRange = ref<[string, string]>([

View File

@ -42,7 +42,7 @@ async function handleRefresh() {
emit('success');
}
/** 按任务名搜索(嵌入页面里,单字段搜索做成 toolbar 内联输入框,回车 / 清空触发查询) */
/** 按任务名搜索 */
async function handleSearch() {
await gridApi.query();
}

View File

@ -8,7 +8,6 @@ import { Tabs } from 'ant-design-vue';
import DataRuleList from './rule/index.vue';
import DataSinkList from './sink/index.vue';
// TODO DONE @AI"/** IoT / */"线
const activeTabName = ref('rule');
</script>

View File

@ -1,56 +1,11 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
import { getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'id',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
component: 'Input',
fieldName: 'name',
label: '规则名称',
rules: 'required',
componentProps: {
placeholder: '请输入规则名称',
},
},
{
fieldName: 'status',
label: '规则状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'description',
label: '规则描述',
component: 'Textarea',
componentProps: {
placeholder: '请输入规则描述',
rows: 3,
},
formItemClass: 'col-span-2',
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [

View File

@ -3,9 +3,9 @@
import { onMounted, ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { Form, Select, Tag } from 'ant-design-vue';
import { Form, Select } from 'ant-design-vue';
import { getAlertConfigPage } from '#/api/iot/alert/config';
import { getSimpleAlertConfigList } from '#/api/iot/alert/config';
/** 告警配置组件 */
defineOptions({ name: 'AlertConfig' });
@ -28,18 +28,11 @@ function handleChange(value?: any) {
emit('update:modelValue', value);
}
// TODO DONE @AI antd + vue3+ep simple-list
// TODO @AIantd vue3 + ep
/** 加载告警配置列表 */
async function loadAlertConfigs() {
loading.value = true;
try {
const data = await getAlertConfigPage({
pageNo: 1,
pageSize: 100,
enabled: true, //
});
alertConfigs.value = data.list || [];
alertConfigs.value = (await getSimpleAlertConfigList()) || [];
} finally {
loading.value = false;
}
@ -68,14 +61,7 @@ onMounted(() => {
:key="config.id"
:label="config.name"
:value="config.id"
>
<div class="flex items-center justify-between">
<span>{{ config.name }}</span>
<Tag :color="config.enabled ? 'success' : 'error'">
{{ config.enabled ? '启用' : '禁用' }}
</Tag>
</div>
</Select.Option>
/>
</Select>
</Form.Item>
</div>

View File

@ -86,9 +86,9 @@ const needsTimeInput = computed(() => {
return timeOnlyOperators.includes(condition.value.operator as any);
});
//
/** 是否需要日期输入:当前只支持时间维度的判断 */
const needsDateInput = computed(() => {
return false; //
return false;
});
//

View File

@ -75,7 +75,6 @@ function handleProductChange(productId?: number) {
if (action.value.productId !== productId) {
action.value.deviceId = undefined;
action.value.identifier = undefined; //
// TODO DONE @AI linter
action.value.params = '' as any; //
selectedService.value = null; //
serviceList.value = []; //
@ -98,7 +97,6 @@ function handleProductChange(productId?: number) {
function handleDeviceChange(deviceId?: number) {
//
if (action.value.deviceId !== deviceId) {
// TODO DONE @AI linter
action.value.params = '' as any; //
}
}
@ -152,7 +150,6 @@ async function loadThingModelProperties(productId: number) {
loadingThingModel.value = true;
const tslData = await fetchThingModelTSL(productId);
// TODO DONE @AI linter
if (!tslData?.properties) {
thingModelProperties.value = [];
return;
@ -187,13 +184,11 @@ async function loadServiceList(productId: number) {
loadingServices.value = true;
const tslData = await fetchThingModelTSL(productId);
// TODO DONE @AI linter
if (!tslData?.services) {
serviceList.value = [];
return;
}
// TODO DONE @AI linter
serviceList.value = tslData.services;
} catch (error) {
console.error('加载服务列表失败:', error);

View File

@ -26,7 +26,6 @@ const deviceLoading = ref(false); // 设备加载状态
const deviceList = ref<any[]>([]); //
/** 处理选择变化事件 */
// TODO @AI value
function handleChange(value: any) {
emit('update:modelValue', value as number | undefined);
emit('change', value as number | undefined);

View File

@ -15,7 +15,6 @@ export namespace AlertConfigApi {
receiveUserNames?: string[];
receiveTypes?: number[];
createTime?: Date;
updateTime?: Date;
}
}

View File

@ -10,13 +10,10 @@ export namespace AlertRecordApi {
configName?: string;
configLevel?: number;
deviceId?: number;
deviceName?: string;
productId?: number;
productName?: string;
deviceMessage?: any;
processStatus?: boolean;
processRemark?: string;
processTime?: Date;
createTime?: Date;
}
}

View File

@ -102,7 +102,6 @@ export function getProductByKey(productKey: string) {
});
}
// TODO @AI这个是不是 vue3 + ep 也没做?感觉对齐就好,不用额外做。
/** 同步产品物模型 TDengine 超级表结构 */
export function syncProductPropertyTable(productId: number) {
return requestClient.post(

View File

@ -138,7 +138,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<AlertConfigApi.AlertConfig>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '配置编号',

View File

@ -35,7 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
},
},
{
fieldName: 'configLevel',
fieldName: 'level',
label: '告警级别',
component: 'Select',
componentProps: {
@ -95,7 +95,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<AlertRecordApi.AlertRecord>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '记录编号',

View File

@ -191,8 +191,8 @@ export function useImportFormSchema(): VbenFormSchema[] {
label: '是否覆盖',
component: 'Switch',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
activeText: '是',
inactiveText: '否',
},
rules: z.boolean().default(false),
help: '是否更新已经存在的设备数据',

View File

@ -142,8 +142,15 @@ async function updateDeviceConfig() {
/>
<!-- 代码视图 - 只读展示 -->
<div v-if="!isEditing" class="json-viewer-container">
<pre class="json-code"><code>{{ formattedConfig }}</code></pre>
<div
v-if="!isEditing"
class="max-h-[600px] overflow-y-auto rounded border border-[#d9d9d9] bg-[#f5f5f5] p-3 dark:border-[#3a3a3a] dark:bg-[#1a1a1a]"
>
<pre
class="m-0 whitespace-pre-wrap break-words font-mono text-[13px] leading-normal text-[#333] dark:text-gray-300"
>
<code>{{ formattedConfig }}</code>
</pre>
</div>
<!-- 编辑器视图 - 可编辑 -->
@ -152,7 +159,7 @@ async function updateDeviceConfig() {
v-model="configString"
type="textarea"
:rows="20"
class="json-editor"
class="font-mono text-[13px]"
placeholder="请输入 JSON 格式的配置信息"
/>
@ -182,30 +189,3 @@ async function updateDeviceConfig() {
</div>
</div>
</template>
<style scoped>
/** todo @AI可以改成 unocss 么?如果不行,写下注释,可以的化,就把 antd、ele、vue3 + ep 都改了! **/
.json-viewer-container {
max-height: 600px;
padding: 12px;
overflow-y: auto;
background-color: #f5f5f5;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.json-code {
margin: 0;
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px;
line-height: 1.5;
color: #333;
overflow-wrap: break-word;
white-space: pre-wrap;
}
.json-editor {
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px;
}
</style>

View File

@ -15,10 +15,10 @@ import { loadBaiduMapSdk } from '#/components/map';
defineOptions({ name: 'DeviceMapCard' });
const router = useRouter();
const mapContainerRef = ref<HTMLElement>();
let mapInstance: any = null;
const loading = ref(true);
const deviceList = ref<IotDeviceApi.Device[]>([]);
const mapContainerRef = ref<HTMLElement>(); //
let mapInstance: any = null; //
const loading = ref(true); //
const deviceList = ref<IotDeviceApi.Device[]>([]); //
/** 是否有数据 */
const hasData = computed(() => deviceList.value.length > 0);

View File

@ -22,10 +22,10 @@ defineOptions({ name: 'MessageTrendCard' });
const messageChartRef = ref();
const { renderEcharts } = useEcharts(messageChartRef);
const loading = ref(false);
const loading = ref(false); //
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
[],
);
); //
/** 时间范围(仅日期,不包含时分秒) */
const dateRange = ref<[string, string]>([

View File

@ -42,7 +42,7 @@ async function handleRefresh() {
emit('success');
}
/** 按任务名搜索(嵌入页面里,单字段搜索做成 toolbar 内联输入框,回车 / 清空触发查询) */
/** 按任务名搜索 */
async function handleSearch() {
await gridApi.query();
}

View File

@ -128,7 +128,6 @@ export function useBasicFormSchema(
// 网关子设备走网关联网,不需要联网方式
dependencies: {
triggerFields: ['deviceType'],
// TODO DONE @AI枚举值。或者这里不要枚举值也看看 vben 里,其它是不是也漏了枚举值。)
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY,
},
rules: 'required',
@ -167,8 +166,8 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
label: '动态注册',
component: 'Switch',
componentProps: {
checkedChildren: '开',
unCheckedChildren: '关',
activeText: '开',
inactiveText: '关',
},
defaultValue: false,
help: '设备动态注册无需一一烧录设备证书DeviceSecret每台设备烧录相同的产品证书即 ProductKey 和 ProductSecret ,云端鉴权通过后下发设备证书,您可以根据需要开启或关闭动态注册,保障安全性。',

View File

@ -131,7 +131,7 @@ onMounted(() => {
<span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
产品分类
</span>
<span class="truncate font-medium text-primary">
<span class="truncate font-medium text-foreground">
{{ getCategoryName(item.categoryId) }}
</span>
</div>
@ -210,7 +210,6 @@ onMounted(() => {
物模型
</ElButton>
<template v-if="hasAccessByCodes(['iot:product:delete'])">
<!-- TODO DONE @AI使用枚举 -->
<ElTooltip
v-if="item.status === ProductStatusEnum.PUBLISHED"
content="已发布的产品不能删除"

View File

@ -8,7 +8,6 @@ import { ElTabPane, ElTabs } from 'element-plus';
import DataRuleList from './rule/index.vue';
import DataSinkList from './sink/index.vue';
// TODO DONE @AI"/** IoT / */"线
const activeTabName = ref('rule');
</script>

View File

@ -1,6 +1,4 @@
<script lang="ts" setup>
// TODO DONE @AI function system user indexrule/data
import { onMounted, ref } from 'vue';
import { IconifyIcon } from '@vben/icons';

View File

@ -189,7 +189,7 @@ function handleTypeChange(type: number) {
<ElRadio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS, 'number')"
:key="String(dict.value)"
:label="dict.value"
:value="dict.value"
>
{{ dict.label }}
</ElRadio>

View File

@ -1,56 +1,11 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
import { getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'id',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
component: 'Input',
fieldName: 'name',
label: '规则名称',
rules: 'required',
componentProps: {
placeholder: '请输入规则名称',
},
},
{
fieldName: 'status',
label: '规则状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'description',
label: '规则描述',
component: 'Textarea',
componentProps: {
placeholder: '请输入规则描述',
rows: 3,
},
formItemClass: 'col-span-2',
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [

View File

@ -3,9 +3,9 @@
import { onMounted, ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { ElFormItem, ElOption, ElSelect, ElTag } from 'element-plus';
import { ElFormItem, ElOption, ElSelect } from 'element-plus';
import { getAlertConfigPage } from '#/api/iot/alert/config';
import { getSimpleAlertConfigList } from '#/api/iot/alert/config';
/** 告警配置组件 */
defineOptions({ name: 'AlertConfig' });
@ -31,18 +31,11 @@ function handleChange(value?: any) {
emit('update:modelValue', value);
}
/**
* 加载告警配置列表
*/
/** 加载告警配置列表 */
async function loadAlertConfigs() {
loading.value = true;
try {
const data = await getAlertConfigPage({
pageNo: 1,
pageSize: 100,
enabled: true, //
});
alertConfigs.value = data.list || [];
alertConfigs.value = (await getSimpleAlertConfigList()) || [];
} finally {
loading.value = false;
}
@ -71,14 +64,7 @@ onMounted(() => {
:key="config.id"
:label="config.name"
:value="config.id"
>
<div class="flex items-center justify-between">
<span>{{ config.name }}</span>
<ElTag :type="config.enabled ? 'success' : 'danger'" size="small">
{{ config.enabled ? '启用' : '禁用' }}
</ElTag>
</div>
</ElOption>
/>
</ElSelect>
</ElFormItem>
</div>

View File

@ -123,13 +123,10 @@ function updateAction(index: number, action: RuleSceneApi.Action) {
/**
* 更新告警配置
* @param index 执行器索引
* @param alertConfigId 告警配置ID
* @param alertConfigId 告警配置 ID
*/
function updateActionAlertConfig(index: number, alertConfigId?: number) {
actions.value[index]!.alertConfigId = alertConfigId;
if (actions.value[index]) {
actions.value[index].alertConfigId = alertConfigId;
}
}
/**
@ -137,22 +134,21 @@ function updateActionAlertConfig(index: number, alertConfigId?: number) {
* @param action 执行器对象
* @param type 执行器类型
*/
function onActionTypeChange(action: RuleSceneApi.Action, type: any) {
//
function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
if (isDeviceAction(type)) {
//
action.alertConfigId = undefined;
if (!(action as any).params) {
(action as any).params = '';
if (!action.params) {
action.params = '';
}
// identifier
if (action.identifier && type !== (action as any).type) {
// identifier
if (action.identifier && type !== action.type) {
action.identifier = undefined;
}
} else if (isAlertAction(type)) {
action.productId = undefined;
action.deviceId = undefined;
action.identifier = undefined; //
action.identifier = undefined;
action.params = undefined;
action.alertConfigId = undefined;
}
@ -213,11 +209,11 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: any) {
<span>执行器 {{ index + 1 }}</span>
</div>
<ElTag
:type="getActionTypeTag(action.type as any)"
:type="getActionTypeTag(action.type as number)"
size="small"
class="font-500"
>
{{ getActionTypeLabel(action.type as any) }}
{{ getActionTypeLabel(action.type as number) }}
</ElTag>
</div>
<div class="gap-8px flex items-center">
@ -242,12 +238,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: any) {
<ElFormItem label="执行类型" required>
<ElSelect
v-model="action.type"
@change="
(value: any) => {
updateActionType(index, value);
onActionTypeChange(action, value);
}
"
@change="(value: any) => updateActionType(index, value)"
placeholder="请选择执行类型"
class="w-full"
>
@ -263,7 +254,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: any) {
<!-- 设备控制配置 -->
<DeviceControlConfig
v-if="isDeviceAction(action.type as any)"
v-if="isDeviceAction(action.type as number)"
:model-value="action"
@update:model-value="(value) => updateAction(index, value)"
/>

View File

@ -16,6 +16,7 @@ import { ElButton, ElCard, ElEmpty, ElFormItem, ElTag } from 'element-plus';
import { CronTab } from '#/components/cron-tab';
import DeviceTriggerConfig from '../configs/device-trigger-config.vue';
import TimerConditionGroupConfig from '../configs/timer-condition-group-config.vue';
/** 触发器配置组件 */
defineOptions({ name: 'TriggerSection' });
@ -96,6 +97,18 @@ function updateTriggerCronConfig(index: number, cronExpression?: string) {
triggers.value[index]!.cronExpression = cronExpression;
}
/**
* 更新触发器条件组配置
* @param index 触发器索引
* @param conditionGroups 条件组
*/
function updateTriggerConditionGroups(
index: number,
conditionGroups: RuleSceneApi.TriggerCondition[][],
) {
triggers.value[index]!.conditionGroups = conditionGroups;
}
/**
* 处理触发器类型变化事件
* @param index 触发器索引
@ -163,10 +176,10 @@ onMounted(() => {
</div>
<ElTag
size="small"
:type="getTriggerTagType(triggerItem.type as any)"
:type="getTriggerTagType(triggerItem.type as number)"
class="font-500"
>
{{ getTriggerTypeLabel(triggerItem.type as any) }}
{{ getTriggerTypeLabel(triggerItem.type as number) }}
</ElTag>
</div>
<div class="gap-8px flex items-center">
@ -188,7 +201,7 @@ onMounted(() => {
<div class="p-16px space-y-16px">
<!-- 设备触发配置 -->
<DeviceTriggerConfig
v-if="isDeviceTrigger(triggerItem.type as any)"
v-if="isDeviceTrigger(triggerItem.type as number)"
:model-value="triggerItem"
:index="index"
@update:model-value="
@ -218,7 +231,7 @@ onMounted(() => {
<div
class="p-16px rounded-6px border border-primary bg-background"
>
<ElFormItem label="CRON表达式" required>
<ElFormItem label="CRON 表达式" required>
<CronTab
:model-value="triggerItem.cronExpression || '0 0 12 * * ?'"
@update:model-value="
@ -227,6 +240,14 @@ onMounted(() => {
/>
</ElFormItem>
</div>
<!-- 附加条件组配置 -->
<TimerConditionGroupConfig
:model-value="triggerItem.conditionGroups"
@update:model-value="
(value) => updateTriggerConditionGroups(index, value)
"
/>
</div>
</div>
</div>

View File

@ -371,7 +371,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
:actions="[
{
label: row.status === CommonStatusEnum.ENABLE ? '停用' : '启用',
type: 'link',
type: 'primary',
link: true,
icon:
row.status === CommonStatusEnum.ENABLE
? 'ant-design:stop-outlined'
@ -383,14 +384,15 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
{
label: $t('common.edit'),
type: 'link',
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),