review:【antd】【iot】产品管理
This commit is contained in:
@@ -23,7 +23,6 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
show: () => false,
|
show: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 创建时的 ProductKey 字段(带生成按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
@@ -34,7 +33,6 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在创建时显示(没有 id)
|
|
||||||
return !values.id;
|
return !values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -43,6 +41,7 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
.min(1, 'ProductKey 不能为空')
|
.min(1, 'ProductKey 不能为空')
|
||||||
.max(32, 'ProductKey 长度不能超过 32 个字符'),
|
.max(32, 'ProductKey 长度不能超过 32 个字符'),
|
||||||
suffix: () => {
|
suffix: () => {
|
||||||
|
// 创建时的 ProductKey 字段(带生成按钮)
|
||||||
return h(
|
return h(
|
||||||
Button,
|
Button,
|
||||||
{
|
{
|
||||||
@@ -55,19 +54,17 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 编辑时的 ProductKey 字段(禁用,无按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入 ProductKey',
|
placeholder: '请输入 ProductKey',
|
||||||
disabled: true,
|
disabled: true, // 编辑时的 ProductKey 字段(禁用,无按钮)
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在编辑时显示(有 id)
|
|
||||||
return !!values.id;
|
return !!values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -176,7 +173,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
show: () => false,
|
show: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 创建时的 ProductKey 字段(带生成按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
@@ -187,7 +183,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在创建时显示(没有 id)
|
|
||||||
return !values.id;
|
return !values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -208,7 +203,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 编辑时的 ProductKey 字段(禁用,无按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
@@ -220,7 +214,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在编辑时显示(有 id)
|
|
||||||
return !!values.id;
|
return !!values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -341,6 +334,7 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
|
// TODO @haohao:貌似用不上?
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -367,7 +361,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
{ type: 'checkbox', width: 40 },
|
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
@@ -413,7 +406,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'actions' },
|
slots: { default: 'actions' },
|
||||||
},
|
},
|
||||||
@@ -421,6 +414,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查询产品列表 */
|
/** 查询产品列表 */
|
||||||
|
// TODO @haohao:貌似可以删除?
|
||||||
export async function queryProductList({ page }: any, searchParams: any) {
|
export async function queryProductList({ page }: any, searchParams: any) {
|
||||||
return await getProductPage({
|
return await getProductPage({
|
||||||
pageNo: page.currentPage,
|
pageNo: page.currentPage,
|
||||||
@@ -430,6 +424,7 @@ export async function queryProductList({ page }: any, searchParams: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建图片预览状态 */
|
/** 创建图片预览状态 */
|
||||||
|
// TODO @haohao:可能不一定用的上;
|
||||||
export function useImagePreview() {
|
export function useImagePreview() {
|
||||||
const previewVisible = ref(false);
|
const previewVisible = ref(false);
|
||||||
const previewImage = ref('');
|
const previewImage = ref('');
|
||||||
@@ -446,6 +441,7 @@ export function useImagePreview() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:放到对应的 form 里
|
||||||
/** 生成 ProductKey(包含大小写字母和数字) */
|
/** 生成 ProductKey(包含大小写字母和数字) */
|
||||||
export function generateProductKey(): string {
|
export function generateProductKey(): string {
|
||||||
const chars =
|
const chars =
|
||||||
|
|||||||
@@ -26,17 +26,14 @@ import ProductForm from './modules/product-form.vue';
|
|||||||
defineOptions({ name: 'IoTProduct' });
|
defineOptions({ name: 'IoTProduct' });
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const categoryList = ref<any[]>([]);
|
const categoryList = ref<any[]>([]); // TODO @haohao:category 类型
|
||||||
const viewMode = ref<'card' | 'list'>('card');
|
const viewMode = ref<'card' | 'list'>('card');
|
||||||
const cardViewRef = ref();
|
const cardViewRef = ref();
|
||||||
|
|
||||||
// 搜索参数
|
|
||||||
const searchParams = ref({
|
const searchParams = ref({
|
||||||
name: '',
|
name: '',
|
||||||
productKey: '',
|
productKey: '',
|
||||||
});
|
}); // 搜索参数
|
||||||
|
|
||||||
// 图片预览
|
|
||||||
const { previewVisible, previewImage, handlePreviewImage } = useImagePreview();
|
const { previewVisible, previewImage, handlePreviewImage } = useImagePreview();
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
@@ -44,18 +41,19 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载产品分类列表
|
/** 加载产品分类列表 */
|
||||||
async function loadCategories() {
|
async function loadCategories() {
|
||||||
categoryList.value = await getSimpleProductCategoryList();
|
categoryList.value = await getSimpleProductCategoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取分类名称
|
/** 获取分类名称 */
|
||||||
function getCategoryNameByValue(categoryId: number) {
|
function getCategoryNameByValue(categoryId: number) {
|
||||||
const category = categoryList.value.find((c: any) => c.id === categoryId);
|
const category = categoryList.value.find((c: any) => c.id === categoryId);
|
||||||
return category?.name || '未分类';
|
return category?.name || '未分类';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索 */
|
// TODO @haohao:要不要改成 handleRefresh,注释改成“刷新表格”,更加统一。
|
||||||
|
/** 搜索产品 */
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
if (viewMode.value === 'list') {
|
if (viewMode.value === 'list') {
|
||||||
gridApi.formApi.setValues(searchParams.value);
|
gridApi.formApi.setValues(searchParams.value);
|
||||||
@@ -65,14 +63,14 @@ function handleSearch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置 */
|
/** 重置搜索 */
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchParams.value.name = '';
|
searchParams.value.name = '';
|
||||||
searchParams.value.productKey = '';
|
searchParams.value.productKey = '';
|
||||||
handleSearch();
|
handleSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新 */
|
/** 刷新表格 */
|
||||||
function handleRefresh() {
|
function handleRefresh() {
|
||||||
if (viewMode.value === 'list') {
|
if (viewMode.value === 'list') {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
@@ -84,7 +82,7 @@ function handleRefresh() {
|
|||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function handleExport() {
|
async function handleExport() {
|
||||||
const data = await exportProduct(searchParams.value);
|
const data = await exportProduct(searchParams.value);
|
||||||
await downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开产品详情 */
|
/** 打开产品详情 */
|
||||||
@@ -117,12 +115,12 @@ function handleEdit(row: any) {
|
|||||||
/** 删除产品 */
|
/** 删除产品 */
|
||||||
async function handleDelete(row: any) {
|
async function handleDelete(row: any) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: `正在删除 ${row.name}...`,
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteProduct(row.id);
|
await deleteProduct(row.id!);
|
||||||
message.success(`删除 ${row.name} 成功`);
|
message.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
@@ -130,6 +128,7 @@ async function handleDelete(row: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
// TODO @haohao:这个不用,可以删除掉的
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: [],
|
schema: [],
|
||||||
},
|
},
|
||||||
@@ -156,9 +155,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
refresh: true,
|
refresh: true,
|
||||||
search: true,
|
search: true,
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions,
|
} as VxeTableGridOptions, // TODO @haohao:这里有个 <> 泛型
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCategories();
|
loadCategories();
|
||||||
});
|
});
|
||||||
@@ -172,22 +172,24 @@ onMounted(() => {
|
|||||||
<Card :body-style="{ padding: '16px' }" class="mb-4">
|
<Card :body-style="{ padding: '16px' }" class="mb-4">
|
||||||
<!-- 搜索表单 -->
|
<!-- 搜索表单 -->
|
||||||
<div class="mb-3 flex items-center gap-3">
|
<div class="mb-3 flex items-center gap-3">
|
||||||
|
<!-- TODO @haohao:tindwind -->
|
||||||
<Input
|
<Input
|
||||||
v-model:value="searchParams.name"
|
v-model:value="searchParams.name"
|
||||||
placeholder="请输入产品名称"
|
placeholder="请输入产品名称"
|
||||||
allow-clear
|
allow-clear
|
||||||
style="width: 200px"
|
style="width: 220px"
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<span class="text-gray-400">产品名称</span>
|
<span class="text-gray-400">产品名称</span>
|
||||||
</template>
|
</template>
|
||||||
</Input>
|
</Input>
|
||||||
|
<!-- TODO @haohao:tindwind -->
|
||||||
<Input
|
<Input
|
||||||
v-model:value="searchParams.productKey"
|
v-model:value="searchParams.productKey"
|
||||||
placeholder="请输入产品标识"
|
placeholder="请输入产品标识"
|
||||||
allow-clear
|
allow-clear
|
||||||
style="width: 200px"
|
style="width: 220px"
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -203,20 +205,20 @@ onMounted(() => {
|
|||||||
重置
|
重置
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Space :size="12">
|
<Space :size="12">
|
||||||
<Button type="primary" @click="handleCreate">
|
<Button type="primary" @click="handleCreate">
|
||||||
|
<!-- TODO @haohao:按钮使用中立的,ACTION_ICON.ADD -->
|
||||||
<IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
|
||||||
新增产品
|
新增产品
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" @click="handleExport">
|
<Button type="primary" @click="handleExport">
|
||||||
|
<!-- TODO @haohao:按钮使用中立的,ACTION_ICON.EXPORT -->
|
||||||
<IconifyIcon icon="ant-design:download-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:download-outlined" class="mr-1" />
|
||||||
导出
|
导出
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<!-- 视图切换 -->
|
<!-- 视图切换 -->
|
||||||
<Space :size="4">
|
<Space :size="4">
|
||||||
<Button
|
<Button
|
||||||
@@ -236,16 +238,17 @@ onMounted(() => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Grid v-show="viewMode === 'list'">
|
<Grid v-show="viewMode === 'list'">
|
||||||
|
<!-- TODO @haohao:这里貌似可以删除掉 -->
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<div></div>
|
<div></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 产品分类列 -->
|
<!-- 产品分类列 -->
|
||||||
|
<!-- TODO @haohao:这里应该可以拿到 data.ts,参考别的模块;类似 apps/web-antd/src/views/ai/image/manager/data.ts 里,里面查询 category ,和自己渲染-->
|
||||||
<template #category="{ row }">
|
<template #category="{ row }">
|
||||||
<span>{{ getCategoryNameByValue(row.categoryId) }}</span>
|
<span>{{ getCategoryNameByValue(row.categoryId) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 产品图标列 -->
|
<!-- 产品图标列 -->
|
||||||
|
<!-- TODO @haohao:直接用 Image 组件,就 ok 了呀。在 data.ts 里 -->
|
||||||
<template #icon="{ row }">
|
<template #icon="{ row }">
|
||||||
<Button
|
<Button
|
||||||
v-if="row.icon"
|
v-if="row.icon"
|
||||||
@@ -257,7 +260,7 @@ onMounted(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
<span v-else class="text-gray-400">-</span>
|
<span v-else class="text-gray-400">-</span>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- TODO @haohao:直接用 Image 组件,就 ok 了呀。在 data.ts 里 -->
|
||||||
<!-- 产品图片列 -->
|
<!-- 产品图片列 -->
|
||||||
<template #picUrl="{ row }">
|
<template #picUrl="{ row }">
|
||||||
<Button
|
<Button
|
||||||
@@ -270,8 +273,6 @@ onMounted(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
<span v-else class="text-gray-400">-</span>
|
<span v-else class="text-gray-400">-</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 操作列 -->
|
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
:actions="[
|
:actions="[
|
||||||
@@ -320,7 +321,9 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 图片预览 -->
|
<!-- 图片预览 -->
|
||||||
|
<!-- TODO @haohao:tindwind -->
|
||||||
<div style="display: none">
|
<div style="display: none">
|
||||||
|
<!-- TODO @haohao:是不是通过 Image 直接实现预览 -->
|
||||||
<Image.PreviewGroup
|
<Image.PreviewGroup
|
||||||
:preview="{
|
:preview="{
|
||||||
visible: previewVisible,
|
visible: previewVisible,
|
||||||
@@ -333,6 +336,7 @@ onMounted(() => {
|
|||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/** TODO @haohao:貌似这 2 个 css 没啥用? */
|
||||||
:deep(.vxe-toolbar div) {
|
:deep(.vxe-toolbar div) {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!-- IoT 产品选择器,使用弹窗展示 -->
|
<!-- IoT 产品选择器,使用弹窗展示 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @haohao:这个貌似暂时没看到,在哪里用?
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
@@ -28,6 +29,7 @@ interface Props {
|
|||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
title: '产品选择器',
|
title: '产品选择器',
|
||||||
|
// TODO @haohao:handleConfirm 直接放到这里,不用单独声明
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ const queryParams = reactive({
|
|||||||
name: '',
|
name: '',
|
||||||
productKey: '',
|
productKey: '',
|
||||||
});
|
});
|
||||||
|
// TODO @haohao:是不是 form 应该也在 Grid 里;
|
||||||
|
|
||||||
// 配置表格
|
// 配置表格
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
|
// TODO @haohao:detail 挪到 yudao-ui-admin-vben-v5/apps/web-antd/src/views/iot/product/product/detail 下。独立一个,不放在 modules 里。
|
||||||
import { onMounted, provide, ref } from 'vue';
|
import { onMounted, provide, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
@@ -25,8 +26,7 @@ const loading = ref(true);
|
|||||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||||
const activeTab = ref('info');
|
const activeTab = ref('info');
|
||||||
|
|
||||||
// 提供产品信息给子组件
|
provide('product', product); // 提供产品信息给子组件
|
||||||
provide('product', product);
|
|
||||||
|
|
||||||
/** 获取产品详情 */
|
/** 获取产品详情 */
|
||||||
async function getProductData(productId: number) {
|
async function getProductData(productId: number) {
|
||||||
@@ -44,13 +44,8 @@ async function getProductData(productId: number) {
|
|||||||
async function getDeviceCountData(productId: number) {
|
async function getDeviceCountData(productId: number) {
|
||||||
try {
|
try {
|
||||||
return await getDeviceCount(productId);
|
return await getDeviceCount(productId);
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error(
|
message.error('获取设备数量失败');
|
||||||
'Error fetching device count:',
|
|
||||||
error,
|
|
||||||
'productId:',
|
|
||||||
productId,
|
|
||||||
);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +57,6 @@ onMounted(async () => {
|
|||||||
router.back();
|
router.back();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await getProductData(id);
|
await getProductData(id);
|
||||||
|
|
||||||
// 处理 tab 参数
|
// 处理 tab 参数
|
||||||
@@ -70,7 +64,6 @@ onMounted(async () => {
|
|||||||
if (tab) {
|
if (tab) {
|
||||||
activeTab.value = tab as string;
|
activeTab.value = tab as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询设备数量
|
// 查询设备数量
|
||||||
if (product.value.id) {
|
if (product.value.id) {
|
||||||
product.value.deviceCount = await getDeviceCountData(product.value.id);
|
product.value.deviceCount = await getDeviceCountData(product.value.id);
|
||||||
@@ -85,7 +78,6 @@ onMounted(async () => {
|
|||||||
:product="product"
|
:product="product"
|
||||||
@refresh="() => getProductData(id)"
|
@refresh="() => getProductData(id)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||||
<Tabs.TabPane key="info" tab="产品信息">
|
<Tabs.TabPane key="info" tab="产品信息">
|
||||||
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @haohao:放到 detail/modules 里。然后名字就是 header.vue
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
@@ -51,8 +52,9 @@ function openForm(type: string, id?: number) {
|
|||||||
|
|
||||||
/** 发布产品 */
|
/** 发布产品 */
|
||||||
async function confirmPublish(id: number) {
|
async function confirmPublish(id: number) {
|
||||||
|
// TODO @haohao:最好类似;async function handleDeleteBatch() { 的做法:1)有个 confirm;2)有个 loading
|
||||||
try {
|
try {
|
||||||
await updateProductStatus(id, 1);
|
await updateProductStatus(id, 1); // TODO @好好】:1 和 0,最好用枚举;
|
||||||
message.success('发布成功');
|
message.success('发布成功');
|
||||||
emit('refresh');
|
emit('refresh');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -62,6 +64,7 @@ async function confirmPublish(id: number) {
|
|||||||
|
|
||||||
/** 撤销发布 */
|
/** 撤销发布 */
|
||||||
async function confirmUnpublish(id: number) {
|
async function confirmUnpublish(id: number) {
|
||||||
|
// TODO @haohao:最好类似;async function handleDeleteBatch() { 的做法:1)有个 confirm;2)有个 loading
|
||||||
try {
|
try {
|
||||||
await updateProductStatus(id, 0);
|
await updateProductStatus(id, 0);
|
||||||
message.success('撤销发布成功');
|
message.success('撤销发布成功');
|
||||||
@@ -126,6 +129,7 @@ async function confirmUnpublish(id: number) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- 表单弹窗 -->
|
<!-- 表单弹窗 -->
|
||||||
|
<!-- TODO @haohao:弹不出来;另外,应该用 index.vue 里,Form 的声明方式哈。 -->
|
||||||
<ProductForm ref="formRef" @success="emit('refresh')" />
|
<ProductForm ref="formRef" @success="emit('refresh')" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @haohao:放到 detail/modules 里。然后名字就是 info.vue
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
@@ -23,6 +24,7 @@ function formatDate(date?: Date | string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card title="产品信息">
|
<Card title="产品信息">
|
||||||
|
<!-- TODO @haohao:看看是不是用 description 组件 -->
|
||||||
<Descriptions bordered :column="3" size="small">
|
<Descriptions bordered :column="3" size="small">
|
||||||
<Descriptions.Item label="产品名称">
|
<Descriptions.Item label="产品名称">
|
||||||
{{ product.name }}
|
{{ product.name }}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
|
|
||||||
import { getProductPage } from '#/api/iot/product/product';
|
import { getProductPage } from '#/api/iot/product/product';
|
||||||
|
|
||||||
|
// TODO @haohao:应该是 card-view.vue;
|
||||||
|
|
||||||
// TODO @haohao:命名不太对;可以简化下;
|
// TODO @haohao:命名不太对;可以简化下;
|
||||||
defineOptions({ name: 'ProductCardView' });
|
defineOptions({ name: 'ProductCardView' });
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ const queryParams = ref({
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @haohao:注释的优化;
|
||||||
// 获取分类名称
|
// 获取分类名称
|
||||||
function getCategoryName(categoryId: number) {
|
function getCategoryName(categoryId: number) {
|
||||||
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
||||||
@@ -85,11 +88,6 @@ function getDeviceTypeColor(deviceType: number) {
|
|||||||
return colors[deviceType] || 'default';
|
return colors[deviceType] || 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 暴露方法供父组件调用
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
reload: getList,
|
reload: getList,
|
||||||
search: () => {
|
search: () => {
|
||||||
@@ -97,6 +95,11 @@ defineExpose({
|
|||||||
getList();
|
getList();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -113,9 +116,11 @@ defineExpose({
|
|||||||
:lg="6"
|
:lg="6"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
>
|
>
|
||||||
|
<!-- TODO @haohao:卡片之间的上下距离,太宽了。 -->
|
||||||
<Card :body-style="{ padding: '20px' }" class="product-card h-full">
|
<Card :body-style="{ padding: '20px' }" class="product-card h-full">
|
||||||
<!-- 顶部标题区域 -->
|
<!-- 顶部标题区域 -->
|
||||||
<div class="mb-4 flex items-start">
|
<div class="mb-4 flex items-start">
|
||||||
|
<!-- TODO @haohao:图标太大了;看看是不是参考 vue3 + element-plus 搞小点;然后标题居中。 -->
|
||||||
<div class="product-icon">
|
<div class="product-icon">
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
:icon="item.icon || 'ant-design:inbox-outlined'"
|
:icon="item.icon || 'ant-design:inbox-outlined'"
|
||||||
@@ -126,7 +131,6 @@ defineExpose({
|
|||||||
<div class="product-title">{{ item.name }}</div>
|
<div class="product-title">{{ item.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<div class="mb-4 flex items-start">
|
<div class="mb-4 flex items-start">
|
||||||
<div class="info-list flex-1">
|
<div class="info-list flex-1">
|
||||||
@@ -152,6 +156,7 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">产品标识</span>
|
<span class="info-label">产品标识</span>
|
||||||
|
<!-- TODO @haohao:展示 ?有点奇怪,要不小手? -->
|
||||||
<Tooltip :title="item.productKey || item.id" placement="top">
|
<Tooltip :title="item.productKey || item.id" placement="top">
|
||||||
<span class="info-value product-key">
|
<span class="info-value product-key">
|
||||||
{{ item.productKey || item.id }}
|
{{ item.productKey || item.id }}
|
||||||
@@ -159,6 +164,8 @@ defineExpose({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- TODO @haohao:这里是不是有 image?然后默认 icon -->
|
||||||
|
<!-- TODO @haohao:高度太高了。建议和左侧(产品分类 + 产品类型 + 产品标识)高度保持一致 -->
|
||||||
<div class="product-3d-icon">
|
<div class="product-3d-icon">
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
icon="ant-design:box-plot-outlined"
|
icon="ant-design:box-plot-outlined"
|
||||||
@@ -166,7 +173,6 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 按钮组 -->
|
<!-- 按钮组 -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<Button
|
<Button
|
||||||
@@ -174,6 +180,7 @@ defineExpose({
|
|||||||
class="action-btn action-btn-edit"
|
class="action-btn action-btn-edit"
|
||||||
@click="emit('edit', item)"
|
@click="emit('edit', item)"
|
||||||
>
|
>
|
||||||
|
<!-- TODO @haohao:按钮尽量用中立的按钮,方便迁移 ele; -->
|
||||||
<IconifyIcon icon="ant-design:edit-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:edit-outlined" class="mr-1" />
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
@@ -229,13 +236,13 @@ defineExpose({
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<Empty v-else description="暂无产品数据" class="my-20" />
|
<Empty v-else description="暂无产品数据" class="my-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div v-if="list.length > 0" class="mt-6 flex justify-center">
|
<!-- TODO @haohao:放到最右侧好点 -->
|
||||||
|
<div v-if="list.length > 0" class="flex justify-center">
|
||||||
<Pagination
|
<Pagination
|
||||||
v-model:current="queryParams.pageNo"
|
v-model:current="queryParams.pageNo"
|
||||||
v-model:page-size="queryParams.pageSize"
|
v-model:page-size="queryParams.pageSize"
|
||||||
@@ -251,6 +258,7 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
/** TODO @haohao:看看哪些可以 tindwind 掉 */
|
||||||
.product-card-view {
|
.product-card-view {
|
||||||
.product-card {
|
.product-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
useBasicFormSchema,
|
useBasicFormSchema,
|
||||||
} from '../data';
|
} from '../data';
|
||||||
|
|
||||||
|
// TODO @haohao:应该是 form.vue;
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProductForm' });
|
defineOptions({ name: 'IoTProductForm' });
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
@@ -30,10 +32,9 @@ const formData = ref<any>();
|
|||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id ? '编辑产品' : '新增产品';
|
return formData.value?.id ? '编辑产品' : '新增产品';
|
||||||
});
|
});
|
||||||
|
const activeKey = ref<string[]>([]); // 折叠面板的激活 key,默认不展开
|
||||||
|
|
||||||
// 折叠面板的激活key,默认不展开
|
// TODO @haohao:每一行一个;
|
||||||
const activeKey = ref<string[]>([]);
|
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@@ -46,7 +47,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建高级设置表单
|
// TODO @haohao:每一行一个;
|
||||||
const [AdvancedForm, advancedFormApi] = useVbenForm({
|
const [AdvancedForm, advancedFormApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@@ -59,7 +60,7 @@ const [AdvancedForm, advancedFormApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 在 formApi 创建后设置 schema
|
// TODO @haohao:看看是不是可以参考别的 form 模块,优化表单这块的逻辑;从 61 到 156 行。体感有点冗余、以及代码风格,不够统一;
|
||||||
formApi.setState({ schema: useBasicFormSchema(formApi) });
|
formApi.setState({ schema: useBasicFormSchema(formApi) });
|
||||||
advancedFormApi.setState({ schema: useAdvancedFormSchema() });
|
advancedFormApi.setState({ schema: useAdvancedFormSchema() });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user