fix: iot 二次确认统一改用 popconfirm 模式(P1)

- 设备详情「配置推送」按钮包 Popconfirm / ElPopconfirm,防误下发
- 场景联动列表 TableAction 启用 / 停用项改用 popConfirm 配置
- 产品详情头部「发布 / 撤销发布 / 同步物模型表结构」三处按钮同步切换
- 移除命令式 Modal.confirm / ElMessageBox.confirm,与 system / iot 现有惯例一致
- 顺带消除 ele 端 ElMessageBox.confirm 取消未 catch 的未处理 promise
pull/345/head
YunaiV 2026-05-21 12:44:12 +08:00
parent 751ba2c782
commit 1afa70bb53
6 changed files with 94 additions and 85 deletions

View File

@ -6,7 +6,7 @@ import { computed, ref, watchEffect } from 'vue';
import { IotDeviceMessageMethodEnum } from '@vben/constants'; import { IotDeviceMessageMethodEnum } from '@vben/constants';
import { Alert, Button, message, Textarea } from 'ant-design-vue'; import { Alert, Button, message, Popconfirm, Textarea } from 'ant-design-vue';
import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device'; import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device';
@ -160,14 +160,13 @@ async function updateDeviceConfig() {
保存 保存
</Button> </Button>
<Button v-else @click="handleEdit"></Button> <Button v-else @click="handleEdit"></Button>
<Button <Popconfirm
v-if="!isEditing" v-if="!isEditing"
:loading="pushLoading" title="确定要推送配置到设备吗?此操作将远程更新设备配置。"
type="primary" @confirm="handleConfigPush"
@click="handleConfigPush"
> >
配置推送 <Button :loading="pushLoading" type="primary"> 配置推送 </Button>
</Button> </Popconfirm>
</div> </div>
</div> </div>
</template> </template>

View File

@ -7,7 +7,13 @@ import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { ProductStatusEnum } from '@vben/constants'; import { ProductStatusEnum } from '@vben/constants';
import { Button, Card, Descriptions, message, Modal } from 'ant-design-vue'; import {
Button,
Card,
Descriptions,
message,
Popconfirm,
} from 'ant-design-vue';
import { import {
syncProductPropertyTable, syncProductPropertyTable,
@ -61,41 +67,23 @@ function openEditForm(row: IotProductApi.Product) {
} }
/** 发布产品 */ /** 发布产品 */
function handlePublish(product: IotProductApi.Product) { async function handlePublish(product: IotProductApi.Product) {
Modal.confirm({ await updateProductStatus(product.id!, ProductStatusEnum.PUBLISHED);
title: '确认发布', message.success('发布成功');
content: `确认要发布产品「${product.name}」吗?`, emit('refresh');
async onOk() {
await updateProductStatus(product.id!, ProductStatusEnum.PUBLISHED);
message.success('发布成功');
emit('refresh');
},
});
} }
/** 撤销发布 */ /** 撤销发布 */
function handleUnpublish(product: IotProductApi.Product) { async function handleUnpublish(product: IotProductApi.Product) {
Modal.confirm({ await updateProductStatus(product.id!, ProductStatusEnum.UNPUBLISHED);
title: '确认撤销发布', message.success('撤销发布成功');
content: `确认要撤销发布产品「${product.name}」吗?`, emit('refresh');
async onOk() {
await updateProductStatus(product.id!, ProductStatusEnum.UNPUBLISHED);
message.success('撤销发布成功');
emit('refresh');
},
});
} }
/** 同步物模型超级表结构 */ /** 同步物模型超级表结构 */
function handleSyncPropertyTable(product: IotProductApi.Product) { async function handleSyncPropertyTable(product: IotProductApi.Product) {
Modal.confirm({ await syncProductPropertyTable(product.id!);
title: '确认同步', message.success('同步成功');
content: `确认要同步产品「${product.name}」的物模型超级表结构吗?`,
async onOk() {
await syncProductPropertyTable(product.id!);
message.success('同步成功');
},
});
} }
</script> </script>
@ -115,32 +103,33 @@ function handleSyncPropertyTable(product: IotProductApi.Product) {
> >
编辑 编辑
</Button> </Button>
<Button <Popconfirm
v-if=" v-if="
product.status === ProductStatusEnum.UNPUBLISHED && product.status === ProductStatusEnum.UNPUBLISHED &&
hasAccessByCodes(['iot:product:update']) hasAccessByCodes(['iot:product:update'])
" "
type="primary" :title="`确认要发布产品「${product.name}」吗?`"
@click="handlePublish(product)" @confirm="handlePublish(product)"
> >
发布 <Button type="primary">发布</Button>
</Button> </Popconfirm>
<Button <Popconfirm
v-if=" v-if="
product.status === ProductStatusEnum.PUBLISHED && product.status === ProductStatusEnum.PUBLISHED &&
hasAccessByCodes(['iot:product:update']) hasAccessByCodes(['iot:product:update'])
" "
danger :title="`确认要撤销发布产品「${product.name}」吗?`"
@click="handleUnpublish(product)" @confirm="handleUnpublish(product)"
> >
撤销发布 <Button danger>撤销发布</Button>
</Button> </Popconfirm>
<Button <Popconfirm
v-if="hasAccessByCodes(['iot:product:update'])" v-if="hasAccessByCodes(['iot:product:update'])"
@click="handleSyncPropertyTable(product)" :title="`确认要同步产品「${product.name}」的物模型超级表结构吗?`"
@confirm="handleSyncPropertyTable(product)"
> >
同步物模型表结构 <Button>同步物模型表结构</Button>
</Button> </Popconfirm>
</div> </div>
</div> </div>

View File

@ -372,7 +372,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
row.status === CommonStatusEnum.ENABLE row.status === CommonStatusEnum.ENABLE
? 'ant-design:stop-outlined' ? 'ant-design:stop-outlined'
: 'ant-design:check-circle-outlined', : 'ant-design:check-circle-outlined',
onClick: handleToggleStatus.bind(null, row), popConfirm: {
title: `确认${row.status === CommonStatusEnum.ENABLE ? '停用' : '启用'}场景规则「${row.name}」吗?`,
confirm: handleToggleStatus.bind(null, row),
},
}, },
{ {
label: $t('common.edit'), label: $t('common.edit'),

View File

@ -6,7 +6,13 @@ import { computed, ref, watchEffect } from 'vue';
import { IotDeviceMessageMethodEnum } from '@vben/constants'; import { IotDeviceMessageMethodEnum } from '@vben/constants';
import { ElAlert, ElButton, ElInput, ElMessage } from 'element-plus'; import {
ElAlert,
ElButton,
ElInput,
ElMessage,
ElPopconfirm,
} from 'element-plus';
import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device'; import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device';
@ -162,14 +168,17 @@ async function updateDeviceConfig() {
保存 保存
</ElButton> </ElButton>
<ElButton v-else @click="handleEdit"></ElButton> <ElButton v-else @click="handleEdit"></ElButton>
<ElButton <ElPopconfirm
v-if="!isEditing" v-if="!isEditing"
:loading="pushLoading" title="确定要推送配置到设备吗?此操作将远程更新设备配置。"
type="primary" @confirm="handleConfigPush"
@click="handleConfigPush"
> >
配置推送 <template #reference>
</ElButton> <ElButton :loading="pushLoading" type="primary">
配置推送
</ElButton>
</template>
</ElPopconfirm>
</div> </div>
</div> </div>
</template> </template>

View File

@ -13,7 +13,7 @@ import {
ElDescriptions, ElDescriptions,
ElDescriptionsItem, ElDescriptionsItem,
ElMessage, ElMessage,
ElMessageBox, ElPopconfirm,
} from 'element-plus'; } from 'element-plus';
import { import {
@ -69,7 +69,6 @@ function openEditForm(row: IotProductApi.Product) {
/** 发布产品 */ /** 发布产品 */
async function handlePublish(product: IotProductApi.Product) { async function handlePublish(product: IotProductApi.Product) {
await ElMessageBox.confirm(`确认要发布产品「${product.name}」吗?`, '确认发布');
await updateProductStatus(product.id!, ProductStatusEnum.PUBLISHED); await updateProductStatus(product.id!, ProductStatusEnum.PUBLISHED);
ElMessage.success('发布成功'); ElMessage.success('发布成功');
emit('refresh'); emit('refresh');
@ -77,10 +76,6 @@ async function handlePublish(product: IotProductApi.Product) {
/** 撤销发布 */ /** 撤销发布 */
async function handleUnpublish(product: IotProductApi.Product) { async function handleUnpublish(product: IotProductApi.Product) {
await ElMessageBox.confirm(
`确认要撤销发布产品「${product.name}」吗?`,
'确认撤销发布',
);
await updateProductStatus(product.id!, ProductStatusEnum.UNPUBLISHED); await updateProductStatus(product.id!, ProductStatusEnum.UNPUBLISHED);
ElMessage.success('撤销发布成功'); ElMessage.success('撤销发布成功');
emit('refresh'); emit('refresh');
@ -88,10 +83,6 @@ async function handleUnpublish(product: IotProductApi.Product) {
/** 同步物模型超级表结构 */ /** 同步物模型超级表结构 */
async function handleSyncPropertyTable(product: IotProductApi.Product) { async function handleSyncPropertyTable(product: IotProductApi.Product) {
await ElMessageBox.confirm(
`确认要同步产品「${product.name}」的物模型超级表结构吗?`,
'确认同步',
);
await syncProductPropertyTable(product.id!); await syncProductPropertyTable(product.id!);
ElMessage.success('同步成功'); ElMessage.success('同步成功');
} }
@ -113,32 +104,39 @@ async function handleSyncPropertyTable(product: IotProductApi.Product) {
> >
编辑 编辑
</ElButton> </ElButton>
<ElButton <ElPopconfirm
v-if=" v-if="
product.status === ProductStatusEnum.UNPUBLISHED && product.status === ProductStatusEnum.UNPUBLISHED &&
hasAccessByCodes(['iot:product:update']) hasAccessByCodes(['iot:product:update'])
" "
type="primary" :title="`确认要发布产品「${product.name}」吗?`"
@click="handlePublish(product)" @confirm="handlePublish(product)"
> >
发布 <template #reference>
</ElButton> <ElButton type="primary">发布</ElButton>
<ElButton </template>
</ElPopconfirm>
<ElPopconfirm
v-if=" v-if="
product.status === ProductStatusEnum.PUBLISHED && product.status === ProductStatusEnum.PUBLISHED &&
hasAccessByCodes(['iot:product:update']) hasAccessByCodes(['iot:product:update'])
" "
type="danger" :title="`确认要撤销发布产品「${product.name}」吗?`"
@click="handleUnpublish(product)" @confirm="handleUnpublish(product)"
> >
撤销发布 <template #reference>
</ElButton> <ElButton type="danger">撤销发布</ElButton>
<ElButton </template>
</ElPopconfirm>
<ElPopconfirm
v-if="hasAccessByCodes(['iot:product:update'])" v-if="hasAccessByCodes(['iot:product:update'])"
@click="handleSyncPropertyTable(product)" :title="`确认要同步产品「${product.name}」的物模型超级表结构吗?`"
@confirm="handleSyncPropertyTable(product)"
> >
同步物模型表结构 <template #reference>
</ElButton> <ElButton>同步物模型表结构</ElButton>
</template>
</ElPopconfirm>
</div> </div>
</div> </div>

View File

@ -15,7 +15,15 @@ import {
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { CronUtils, formatDateTime } from '@vben/utils'; import { CronUtils, formatDateTime } from '@vben/utils';
import { ElCard, ElCol, ElLoading, ElMessage, ElRow, ElTag, ElTooltip } from 'element-plus'; import {
ElCard,
ElCol,
ElLoading,
ElMessage,
ElRow,
ElTag,
ElTooltip,
} from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
@ -368,7 +376,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
row.status === CommonStatusEnum.ENABLE row.status === CommonStatusEnum.ENABLE
? 'ant-design:stop-outlined' ? 'ant-design:stop-outlined'
: 'ant-design:check-circle-outlined', : 'ant-design:check-circle-outlined',
onClick: handleToggleStatus.bind(null, row), popConfirm: {
title: `确认${row.status === CommonStatusEnum.ENABLE ? '停用' : '启用'}场景规则「${row.name}」吗?`,
confirm: handleToggleStatus.bind(null, row),
},
}, },
{ {
label: $t('common.edit'), label: $t('common.edit'),