feat(iot):增加 product/product 模块的代码评审
parent
179881bd3d
commit
89f75428d6
|
|
@ -1,6 +1,5 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { IotProductCategoryApi } from '#/api/iot/product/category';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
|
|
@ -12,10 +11,6 @@ import { Button } from 'ant-design-vue';
|
|||
import { z } from '#/adapter/form';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
|
||||
/** 产品分类列表缓存 */
|
||||
let categoryList: IotProductCategoryApi.ProductCategory[] = [];
|
||||
getSimpleProductCategoryList().then((data) => (categoryList = data));
|
||||
|
||||
/** 基础表单字段(不含图标、图片、描述) */
|
||||
export function useBasicFormSchema(
|
||||
formApi?: any,
|
||||
|
|
@ -114,6 +109,15 @@ export function useBasicFormSchema(
|
|||
buttonStyle: 'solid',
|
||||
optionType: 'button',
|
||||
},
|
||||
// TODO @AI:枚举值。或者这里不要枚举值?对齐 vue3 + ep 版本
|
||||
defaultValue: 0,
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
componentProps: (values) => ({
|
||||
// 编辑时设备类型不可改
|
||||
disabled: !!values.id,
|
||||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
|
|
@ -124,6 +128,12 @@ export function useBasicFormSchema(
|
|||
options: getDictOptions(DICT_TYPE.IOT_NET_TYPE, 'number'),
|
||||
placeholder: '请选择联网方式',
|
||||
},
|
||||
// 网关子设备走网关联网,不需要联网方式
|
||||
dependencies: {
|
||||
triggerFields: ['deviceType'],
|
||||
// TODO @AI:枚举值。或者这里不要枚举值?(也看看 vben 里,其它是不是也漏了枚举值。)
|
||||
show: (values) => values.deviceType !== 2,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
|
|
@ -134,6 +144,7 @@ export function useBasicFormSchema(
|
|||
options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'string'),
|
||||
placeholder: '请选择协议类型',
|
||||
},
|
||||
defaultValue: 'mqtt',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
|
|
@ -144,6 +155,7 @@ export function useBasicFormSchema(
|
|||
options: getDictOptions(DICT_TYPE.IOT_SERIALIZE_TYPE, 'string'),
|
||||
placeholder: '请选择序列化类型',
|
||||
},
|
||||
defaultValue: 'json',
|
||||
help: 'iot-gateway-server 默认根据接入的协议类型确定数据格式,仅 MQTT、EMQX 协议支持自定义序列化类型',
|
||||
rules: 'required',
|
||||
},
|
||||
|
|
@ -167,11 +179,7 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'icon',
|
||||
label: '产品图标',
|
||||
component: 'IconPicker',
|
||||
componentProps: {
|
||||
placeholder: '请选择产品图标',
|
||||
prefix: 'carbon',
|
||||
},
|
||||
component: 'ImageUpload',
|
||||
},
|
||||
{
|
||||
fieldName: 'picUrl',
|
||||
|
|
@ -204,11 +212,10 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'categoryId',
|
||||
field: 'categoryName',
|
||||
title: '品类',
|
||||
minWidth: 120,
|
||||
formatter: ({ cellValue }) =>
|
||||
categoryList.find((c) => c.id === cellValue)?.name || '未分类',
|
||||
formatter: ({ row }) => row.categoryName || '未分类',
|
||||
},
|
||||
{
|
||||
field: 'deviceType',
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
|||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { ProductStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Card, Descriptions, message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { updateProductStatus } from '#/api/iot/product/product';
|
||||
import {
|
||||
syncProductPropertyTable,
|
||||
updateProductStatus,
|
||||
} from '#/api/iot/product/product';
|
||||
|
||||
import Form from '../../modules/form.vue';
|
||||
|
||||
|
|
@ -26,6 +30,7 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
|
|
@ -44,8 +49,9 @@ async function copyToClipboard(text: string) {
|
|||
|
||||
/** 跳转到设备管理 */
|
||||
function goToDeviceList(productId: number) {
|
||||
// TODO @AI:在检查下,vben 里面有没其他也是这种路由情况的;要尽量使用 name;
|
||||
router.push({
|
||||
path: '/iot/device/device',
|
||||
name: 'IoTDevice',
|
||||
query: { productId: String(productId) },
|
||||
});
|
||||
}
|
||||
|
|
@ -80,6 +86,18 @@ function handleUnpublish(product: IotProductApi.Product) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 同步物模型超级表结构 */
|
||||
function handleSyncPropertyTable(product: IotProductApi.Product) {
|
||||
Modal.confirm({
|
||||
title: '确认同步',
|
||||
content: `确认要同步产品「${product.name}」的物模型超级表结构吗?`,
|
||||
async onOk() {
|
||||
await syncProductPropertyTable(product.id!);
|
||||
message.success('同步成功');
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -92,25 +110,38 @@ function handleUnpublish(product: IotProductApi.Product) {
|
|||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
v-if="hasAccessByCodes(['iot:product:update'])"
|
||||
:disabled="product.status === ProductStatusEnum.PUBLISHED"
|
||||
@click="openEditForm(product)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
v-if="product.status === ProductStatusEnum.UNPUBLISHED"
|
||||
v-if="
|
||||
product.status === ProductStatusEnum.UNPUBLISHED &&
|
||||
hasAccessByCodes(['iot:product:update'])
|
||||
"
|
||||
type="primary"
|
||||
@click="handlePublish(product)"
|
||||
>
|
||||
发布
|
||||
</Button>
|
||||
<Button
|
||||
v-if="product.status === ProductStatusEnum.PUBLISHED"
|
||||
v-if="
|
||||
product.status === ProductStatusEnum.PUBLISHED &&
|
||||
hasAccessByCodes(['iot:product:update'])
|
||||
"
|
||||
danger
|
||||
@click="handleUnpublish(product)"
|
||||
>
|
||||
撤销发布
|
||||
</Button>
|
||||
<Button
|
||||
v-if="hasAccessByCodes(['iot:product:update'])"
|
||||
@click="handleSyncPropertyTable(product)"
|
||||
>
|
||||
同步物模型表结构
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -217,12 +217,14 @@ onMounted(() => {
|
|||
label: $t('ui.actionTitle.create', ['产品']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['iot:product:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.export'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.DOWNLOAD,
|
||||
auth: ['iot:product:export'],
|
||||
onClick: handleExport,
|
||||
},
|
||||
]"
|
||||
|
|
@ -252,17 +254,20 @@ onMounted(() => {
|
|||
{
|
||||
label: $t('common.detail'),
|
||||
type: 'link',
|
||||
auth: ['iot:product:query'],
|
||||
onClick: openProductDetail.bind(null, row.id!),
|
||||
},
|
||||
{
|
||||
label: '物模型',
|
||||
type: 'link',
|
||||
auth: ['iot:thing-model:query'],
|
||||
onClick: openThingModel.bind(null, row.id!),
|
||||
},
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['iot:product:update'],
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
|
|
@ -270,6 +275,7 @@ onMounted(() => {
|
|||
type: 'link',
|
||||
danger: true,
|
||||
icon: ACTION_ICON.DELETE,
|
||||
auth: ['iot:product:delete'],
|
||||
disabled: row.status === ProductStatusEnum.PUBLISHED,
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
|
|
@ -37,6 +38,8 @@ const emit = defineEmits<{
|
|||
thingModel: [productId: number];
|
||||
}>();
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
const loading = ref(false);
|
||||
const list = ref<any[]>([]);
|
||||
const total = ref(0);
|
||||
|
|
@ -160,6 +163,7 @@ onMounted(() => {
|
|||
<!-- 按钮组 -->
|
||||
<div class="action-buttons">
|
||||
<Button
|
||||
v-if="hasAccessByCodes(['iot:product:update'])"
|
||||
size="small"
|
||||
class="action-btn action-btn-edit"
|
||||
@click="emit('edit', item)"
|
||||
|
|
@ -168,6 +172,7 @@ onMounted(() => {
|
|||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
v-if="hasAccessByCodes(['iot:product:query'])"
|
||||
size="small"
|
||||
class="action-btn action-btn-detail"
|
||||
@click="emit('detail', item.id)"
|
||||
|
|
@ -176,6 +181,7 @@ onMounted(() => {
|
|||
详情
|
||||
</Button>
|
||||
<Button
|
||||
v-if="hasAccessByCodes(['iot:thing-model:query'])"
|
||||
size="small"
|
||||
class="action-btn action-btn-model"
|
||||
@click="emit('thingModel', item.id)"
|
||||
|
|
@ -183,29 +189,32 @@ onMounted(() => {
|
|||
<IconifyIcon icon="lucide:git-branch" class="mr-1" />
|
||||
物模型
|
||||
</Button>
|
||||
<Tooltip v-if="item.status === 1" title="已发布的产品不能删除">
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
disabled
|
||||
class="action-btn action-btn-delete !w-8"
|
||||
<template v-if="hasAccessByCodes(['iot:product:delete'])">
|
||||
<!-- TODO @AI:使用枚举 -->
|
||||
<Tooltip v-if="item.status === 1" title="已发布的产品不能删除">
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
disabled
|
||||
class="action-btn action-btn-delete !w-8"
|
||||
>
|
||||
<IconifyIcon icon="lucide:trash-2" class="text-sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Popconfirm
|
||||
v-else
|
||||
:title="`确认删除产品 ${item.name} 吗?`"
|
||||
@confirm="emit('delete', item)"
|
||||
>
|
||||
<IconifyIcon icon="lucide:trash-2" class="text-sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Popconfirm
|
||||
v-else
|
||||
:title="`确认删除产品 ${item.name} 吗?`"
|
||||
@confirm="emit('delete', item)"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
class="action-btn action-btn-delete !w-8"
|
||||
>
|
||||
<IconifyIcon icon="lucide:trash-2" class="text-sm" />
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
class="action-btn action-btn-delete !w-8"
|
||||
>
|
||||
<IconifyIcon icon="lucide:trash-2" class="text-sm" />
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</template>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
|
|
|||
Loading…
Reference in New Issue