fix: iot
This commit is contained in:
@@ -1,19 +1,10 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { IotProductCategoryApi } from '#/api/iot/product/category';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import {
|
||||
deleteProductCategory,
|
||||
getProductCategoryPage,
|
||||
getSimpleProductCategoryList,
|
||||
} from '#/api/iot/product/category';
|
||||
import { $t } from '#/locales';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
|
||||
/** 新增/修改产品分类的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
@@ -160,35 +151,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 删除分类 */
|
||||
export async function handleDeleteCategory(
|
||||
row: IotProductCategoryApi.ProductCategory,
|
||||
onSuccess?: () => void,
|
||||
) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteProductCategory(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
onSuccess?.();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询分类列表 */
|
||||
export async function queryProductCategoryList({ page }: any, formValues: any) {
|
||||
const data = await getProductCategoryPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
// 转换为树形结构
|
||||
return {
|
||||
...data,
|
||||
list: handleTree(data.list, 'id', 'parentId'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { IotProductCategoryApi } from '#/api/iot/product/category';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteProductCategory,
|
||||
getProductCategoryPage,
|
||||
} from '#/api/iot/product/category';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import {
|
||||
handleDeleteCategory,
|
||||
queryProductCategoryList,
|
||||
useGridColumns,
|
||||
useGridFormSchema,
|
||||
} from './data';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/ProductCategoryForm.vue';
|
||||
|
||||
defineOptions({ name: 'IoTProductCategory' });
|
||||
@@ -39,7 +41,17 @@ function handleEdit(row: IotProductCategoryApi.ProductCategory) {
|
||||
|
||||
/** 删除分类 */
|
||||
async function handleDelete(row: IotProductCategoryApi.ProductCategory) {
|
||||
await handleDeleteCategory(row, handleRefresh);
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteProductCategory(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
@@ -57,7 +69,18 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: queryProductCategoryList,
|
||||
query: async ({ page }, formValues) => {
|
||||
const data = await getProductCategoryPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
// 转换为树形结构
|
||||
return {
|
||||
...data,
|
||||
list: handleTree(data.list, 'id', 'parentId'),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
|
||||
@@ -5,17 +5,10 @@ import { ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
import {
|
||||
deleteProduct,
|
||||
exportProduct,
|
||||
getProductPage,
|
||||
} from '#/api/iot/product/product';
|
||||
import { getProductPage } from '#/api/iot/product/product';
|
||||
|
||||
/** 新增/修改产品的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
@@ -208,38 +201,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
];
|
||||
}
|
||||
|
||||
/** 加载产品分类列表 */
|
||||
export async function loadCategoryList() {
|
||||
return await getSimpleProductCategoryList();
|
||||
}
|
||||
|
||||
/** 获取分类名称 */
|
||||
export function getCategoryName(categoryList: any[], categoryId: number) {
|
||||
const category = categoryList.find((c: any) => c.id === categoryId);
|
||||
return category?.name || '未分类';
|
||||
}
|
||||
|
||||
/** 删除产品 */
|
||||
export async function handleDeleteProduct(row: any, onSuccess?: () => void) {
|
||||
const hideLoading = message.loading({
|
||||
content: `正在删除 ${row.name}...`,
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteProduct(row.id);
|
||||
message.success(`删除 ${row.name} 成功`);
|
||||
onSuccess?.();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出产品 */
|
||||
export async function handleExportProduct(searchParams: any) {
|
||||
const data = await exportProduct(searchParams);
|
||||
downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
||||
}
|
||||
|
||||
/** 查询产品列表 */
|
||||
export async function queryProductList({ page }: any, searchParams: any) {
|
||||
return await getProductPage({
|
||||
|
||||
@@ -6,21 +6,20 @@ import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { Button, Card, Image, Input, Space } from 'ant-design-vue';
|
||||
import { Button, Card, Image, Input, message, Space } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteProduct,
|
||||
exportProduct,
|
||||
getProductPage,
|
||||
} from '#/api/crm/product';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import {
|
||||
getCategoryName,
|
||||
handleDeleteProduct,
|
||||
handleExportProduct,
|
||||
loadCategoryList,
|
||||
queryProductList,
|
||||
useGridColumns,
|
||||
useImagePreview,
|
||||
} from './data';
|
||||
import { useGridColumns, useImagePreview } from './data';
|
||||
// @ts-ignore
|
||||
import ProductCardView from './modules/ProductCardView.vue';
|
||||
import ProductForm from './modules/ProductForm.vue';
|
||||
@@ -47,14 +46,15 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
// 加载产品分类列表
|
||||
const loadCategories = async () => {
|
||||
categoryList.value = await loadCategoryList();
|
||||
};
|
||||
async function loadCategories() {
|
||||
categoryList.value = await getSimpleProductCategoryList();
|
||||
}
|
||||
|
||||
// 获取分类名称
|
||||
const getCategoryNameByValue = (categoryId: number) => {
|
||||
return getCategoryName(categoryList.value, categoryId);
|
||||
};
|
||||
function getCategoryNameByValue(categoryId: number) {
|
||||
const category = categoryList.value.find((c: any) => c.id === categoryId);
|
||||
return category?.name || '未分类';
|
||||
}
|
||||
|
||||
/** 搜索 */
|
||||
function handleSearch() {
|
||||
@@ -84,7 +84,8 @@ function handleRefresh() {
|
||||
|
||||
/** 导出表格 */
|
||||
async function handleExport() {
|
||||
await handleExportProduct(searchParams.value);
|
||||
const data = await exportProduct(searchParams.value);
|
||||
await downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
||||
}
|
||||
|
||||
/** 打开产品详情 */
|
||||
@@ -116,7 +117,17 @@ function handleEdit(row: any) {
|
||||
|
||||
/** 删除产品 */
|
||||
async function handleDelete(row: any) {
|
||||
await handleDeleteProduct(row, handleRefresh);
|
||||
const hideLoading = message.loading({
|
||||
content: `正在删除 ${row.name}...`,
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteProduct(row.id);
|
||||
message.success(`删除 ${row.name} 成功`);
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
@@ -129,7 +140,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: ({ page }) => queryProductList({ page }, searchParams.value),
|
||||
query: async ({ page }) => {
|
||||
return await getProductPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...searchParams.value,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
@@ -336,6 +353,6 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.ant-image-preview-operations {
|
||||
background: rgba(0, 0, 0, 0.7) !important;
|
||||
background: rgb(0 0 0 / 70%) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -48,13 +48,13 @@ const queryParams = ref({
|
||||
});
|
||||
|
||||
// 获取分类名称
|
||||
const getCategoryName = (categoryId: number) => {
|
||||
function getCategoryName(categoryId: number) {
|
||||
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
||||
return category?.name || '未分类';
|
||||
};
|
||||
}
|
||||
|
||||
// 获取产品列表
|
||||
const getList = async () => {
|
||||
async function getList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getProductPage({
|
||||
@@ -66,23 +66,23 @@ const getList = async () => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 处理页码变化
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
function handlePageChange(page: number, pageSize: number) {
|
||||
queryParams.value.pageNo = page;
|
||||
queryParams.value.pageSize = pageSize;
|
||||
getList();
|
||||
};
|
||||
}
|
||||
|
||||
// 获取设备类型颜色
|
||||
const getDeviceTypeColor = (deviceType: number) => {
|
||||
function getDeviceTypeColor(deviceType: number) {
|
||||
const colors: Record<number, string> = {
|
||||
0: 'blue',
|
||||
1: 'green',
|
||||
};
|
||||
return colors[deviceType] || 'default';
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
@@ -131,9 +131,9 @@ defineExpose({
|
||||
<div class="info-list flex-1">
|
||||
<div class="info-item">
|
||||
<span class="info-label">产品分类</span>
|
||||
<span class="info-value text-primary">{{
|
||||
getCategoryName(item.categoryId)
|
||||
}}</span>
|
||||
<span class="info-value text-primary">
|
||||
{{ getCategoryName(item.categoryId) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">产品类型</span>
|
||||
@@ -152,9 +152,9 @@ defineExpose({
|
||||
<div class="info-item">
|
||||
<span class="info-label">产品标识</span>
|
||||
<Tooltip :title="item.productKey || item.id" placement="top">
|
||||
<span class="info-value product-key">{{
|
||||
item.productKey || item.id
|
||||
}}</span>
|
||||
<span class="info-value product-key">
|
||||
{{ item.productKey || item.id }}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,44 +236,44 @@ defineExpose({
|
||||
.product-card-view {
|
||||
.product-card {
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
border-color: #d9d9d9;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 产品图标
|
||||
.product-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// 产品标题
|
||||
.product-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
color: #1f2937;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -290,16 +290,16 @@ defineExpose({
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #1f2937;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
white-space: nowrap;
|
||||
|
||||
&.text-primary {
|
||||
@@ -308,15 +308,15 @@ defineExpose({
|
||||
}
|
||||
|
||||
.product-key {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
display: inline-block;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
color: #374151;
|
||||
white-space: nowrap;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@@ -328,15 +328,15 @@ defineExpose({
|
||||
|
||||
// 3D 图标
|
||||
.product-3d-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
// 按钮组
|
||||
@@ -344,8 +344,8 @@ defineExpose({
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
margin-top: auto;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
@@ -359,8 +359,8 @@ defineExpose({
|
||||
border-color: #1890ff;
|
||||
|
||||
&:hover {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
background: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,8 +369,8 @@ defineExpose({
|
||||
border-color: #52c41a;
|
||||
|
||||
&:hover {
|
||||
background: #52c41a;
|
||||
color: white;
|
||||
background: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,8 +379,8 @@ defineExpose({
|
||||
border-color: #722ed1;
|
||||
|
||||
&:hover {
|
||||
background: #722ed1;
|
||||
color: white;
|
||||
background: #722ed1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProductPage } from '#/api/iot/product/product';
|
||||
@@ -101,24 +102,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
});
|
||||
|
||||
// 打开选择器
|
||||
const open = async () => {
|
||||
async function open() {
|
||||
selectedProducts.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
modalApi.open();
|
||||
gridApi.reload();
|
||||
};
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
function handleSearch() {
|
||||
gridApi.reload();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
function handleReset() {
|
||||
queryParams.name = '';
|
||||
queryParams.productKey = '';
|
||||
gridApi.reload();
|
||||
};
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
async function handleConfirm() {
|
||||
@@ -151,40 +152,40 @@ defineExpose({ open });
|
||||
<template>
|
||||
<Modal class="!w-[900px]">
|
||||
<div class="mb-4">
|
||||
<a-form layout="inline" :model="queryParams">
|
||||
<a-form-item label="产品名称">
|
||||
<a-input
|
||||
<Form layout="inline" :model="queryParams">
|
||||
<Form.Item label="产品名称">
|
||||
<Input
|
||||
v-model:value="queryParams.name"
|
||||
placeholder="请输入产品名称"
|
||||
allow-clear
|
||||
class="!w-[200px]"
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="ProductKey">
|
||||
<a-input
|
||||
</Form.Item>
|
||||
<Form.Item label="ProductKey">
|
||||
<Input
|
||||
v-model:value="queryParams.productKey"
|
||||
placeholder="请输入产品标识"
|
||||
allow-clear
|
||||
class="!w-[200px]"
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<Icon icon="ant-design:search-outlined" />
|
||||
<IconifyIcon icon="ant-design:search-outlined" />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button class="ml-2" @click="handleReset">
|
||||
</Button>
|
||||
<Button class="ml-2" @click="handleReset">
|
||||
<template #icon>
|
||||
<Icon icon="ant-design:reload-outlined" />
|
||||
<IconifyIcon icon="ant-design:reload-outlined" />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<Grid />
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Button, Card, Descriptions, message } from 'ant-design-vue';
|
||||
|
||||
import { updateProductStatus } from '#/api/iot/product/product';
|
||||
|
||||
@@ -27,30 +27,30 @@ const router = useRouter();
|
||||
const formRef = ref();
|
||||
|
||||
/** 复制到剪贴板 */
|
||||
const copyToClipboard = async (text: string) => {
|
||||
async function copyToClipboard(text: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
message.success('复制成功');
|
||||
} catch {
|
||||
message.error('复制失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 跳转到设备管理 */
|
||||
const goToDeviceList = (productId: number) => {
|
||||
function goToDeviceList(productId: number) {
|
||||
router.push({
|
||||
path: '/iot/device/device',
|
||||
query: { productId: String(productId) },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 打开编辑表单 */
|
||||
const openForm = (type: string, id?: number) => {
|
||||
function openForm(type: string, id?: number) {
|
||||
formRef.value?.open(type, id);
|
||||
};
|
||||
}
|
||||
|
||||
/** 发布产品 */
|
||||
const confirmPublish = async (id: number) => {
|
||||
async function confirmPublish(id: number) {
|
||||
try {
|
||||
await updateProductStatus(id, 1);
|
||||
message.success('发布成功');
|
||||
@@ -58,10 +58,10 @@ const confirmPublish = async (id: number) => {
|
||||
} catch {
|
||||
message.error('发布失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 撤销发布 */
|
||||
const confirmUnpublish = async (id: number) => {
|
||||
async function confirmUnpublish(id: number) {
|
||||
try {
|
||||
await updateProductStatus(id, 0);
|
||||
message.success('撤销发布成功');
|
||||
@@ -69,7 +69,7 @@ const confirmUnpublish = async (id: number) => {
|
||||
} catch {
|
||||
message.error('撤销发布失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -79,51 +79,51 @@ const confirmUnpublish = async (id: number) => {
|
||||
<h2 class="text-xl font-bold">{{ product.name }}</h2>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<a-button
|
||||
<Button
|
||||
:disabled="product.status === 1"
|
||||
@click="openForm('update', product.id)"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
</Button>
|
||||
<Button
|
||||
v-if="product.status === 0"
|
||||
type="primary"
|
||||
@click="confirmPublish(product.id!)"
|
||||
>
|
||||
发布
|
||||
</a-button>
|
||||
<a-button
|
||||
</Button>
|
||||
<Button
|
||||
v-if="product.status === 1"
|
||||
danger
|
||||
@click="confirmUnpublish(product.id!)"
|
||||
>
|
||||
撤销发布
|
||||
</a-button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-card class="mt-4">
|
||||
<a-descriptions :column="1">
|
||||
<a-descriptions-item label="ProductKey">
|
||||
<Card class="mt-4">
|
||||
<Descriptions :column="1">
|
||||
<Descriptions.Item label="ProductKey">
|
||||
{{ product.productKey }}
|
||||
<a-button
|
||||
<Button
|
||||
size="small"
|
||||
class="ml-2"
|
||||
@click="copyToClipboard(product.productKey || '')"
|
||||
>
|
||||
复制
|
||||
</a-button>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="设备总数">
|
||||
<span class="ml-5 mr-2">{{
|
||||
product.deviceCount ?? '加载中...'
|
||||
}}</span>
|
||||
<a-button size="small" @click="goToDeviceList(product.id!)">
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备总数">
|
||||
<span class="ml-5 mr-2">
|
||||
{{ product.deviceCount ?? '加载中...' }}
|
||||
</span>
|
||||
<Button size="small" @click="goToDeviceList(product.id!)">
|
||||
前往管理
|
||||
</a-button>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
<!-- 表单弹窗 -->
|
||||
<ProductForm ref="formRef" @success="emit('refresh')" />
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Card, Descriptions } from 'ant-design-vue';
|
||||
|
||||
import { DeviceTypeEnum } from '#/api/iot/product/product';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
@@ -20,33 +22,33 @@ const formatDate = (date?: Date | string) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card title="产品信息">
|
||||
<a-descriptions bordered :column="3">
|
||||
<a-descriptions-item label="产品名称">
|
||||
<Card title="产品信息">
|
||||
<Descriptions bordered :column="3">
|
||||
<Descriptions.Item label="产品名称">
|
||||
{{ product.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="所属分类">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="所属分类">
|
||||
{{ product.categoryName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="设备类型">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备类型">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
|
||||
:value="product.deviceType"
|
||||
/>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="定位类型">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="定位类型">
|
||||
{{ product.locationType ?? '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">
|
||||
{{ formatDate(product.createTime) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="数据格式">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="数据格式">
|
||||
{{ product.codecType || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品状态">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="产品状态">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
v-if="
|
||||
[DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY].includes(
|
||||
product.deviceType!,
|
||||
@@ -55,10 +57,10 @@ const formatDate = (date?: Date | string) => {
|
||||
label="联网方式"
|
||||
>
|
||||
<DictTag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品描述" :span="3">
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="产品描述" :span="3">
|
||||
{{ product.description || '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message, Tabs } from 'ant-design-vue';
|
||||
|
||||
import { getDeviceCount } from '#/api/iot/device/device';
|
||||
import { getProduct } from '#/api/iot/product/product';
|
||||
@@ -29,7 +29,7 @@ const activeTab = ref('info');
|
||||
provide('product', product);
|
||||
|
||||
/** 获取产品详情 */
|
||||
const getProductData = async (productId: number) => {
|
||||
async function getProductData(productId: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
product.value = await getProduct(productId);
|
||||
@@ -38,10 +38,10 @@ const getProductData = async (productId: number) => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 查询设备数量 */
|
||||
const getDeviceCountData = async (productId: number) => {
|
||||
async function getDeviceCountData(productId: number) {
|
||||
try {
|
||||
return await getDeviceCount(productId);
|
||||
} catch (error) {
|
||||
@@ -53,7 +53,7 @@ const getDeviceCountData = async (productId: number) => {
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
@@ -86,16 +86,16 @@ onMounted(async () => {
|
||||
@refresh="() => getProductData(id)"
|
||||
/>
|
||||
|
||||
<a-tabs v-model:active-key="activeTab" class="mt-4">
|
||||
<a-tab-pane key="info" tab="产品信息">
|
||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||
<Tabs.TabPane key="info" tab="产品信息">
|
||||
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="thingModel" tab="物模型(功能定义)">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="thingModel" tab="物模型(功能定义)">
|
||||
<IoTProductThingModel
|
||||
v-if="activeTab === 'thingModel'"
|
||||
:product-id="id"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user