Merge remote-tracking branch 'yudao/feature/iot' into feature/iot
This commit is contained in:
@ -7,12 +7,18 @@
|
||||
label-width="110px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="产品标识" prop="productKey">
|
||||
<el-form-item label="ProductKey" prop="productKey">
|
||||
<el-input
|
||||
v-model="formData.productKey"
|
||||
placeholder="请输入产品标识"
|
||||
placeholder="请输入 ProductKey"
|
||||
:readonly="formType === 'update'"
|
||||
/>
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="generateProductKey" :disabled="formType === 'update'">
|
||||
重新生成
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入产品名称" />
|
||||
@ -145,7 +151,7 @@ const formData = ref({
|
||||
validateType: ValidateTypeEnum.WEAK
|
||||
})
|
||||
const formRules = reactive({
|
||||
productKey: [{ required: true, message: '产品标识不能为空', trigger: 'blur' }],
|
||||
productKey: [{ required: true, message: 'ProductKey 不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
|
||||
categoryId: [{ required: true, message: '产品分类不能为空', trigger: 'change' }],
|
||||
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }],
|
||||
@ -184,7 +190,7 @@ const open = async (type: string, id?: number) => {
|
||||
}
|
||||
} else {
|
||||
// 新增时,生成随机 productKey
|
||||
formData.value.productKey = generateRandomStr(16)
|
||||
generateProductKey()
|
||||
}
|
||||
// 加载分类列表
|
||||
categoryList.value = await ProductCategoryApi.getSimpleProductCategoryList()
|
||||
@ -231,4 +237,9 @@ const resetForm = () => {
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 生成 ProductKey */
|
||||
const generateProductKey = () => {
|
||||
formData.value.productKey = generateRandomStr(16)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
<el-tabs>
|
||||
<el-tab-pane label="基础通信 Topic">
|
||||
<Table
|
||||
:columns="columns1"
|
||||
:data="data1"
|
||||
:span-method="createSpanMethod(data1)"
|
||||
:columns="basicColumn"
|
||||
:data="basicData"
|
||||
:span-method="createSpanMethod(basicData)"
|
||||
align="left"
|
||||
headerAlign="left"
|
||||
border="true"
|
||||
@ -13,9 +13,9 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="物模型通信 Topic">
|
||||
<Table
|
||||
:columns="columns2"
|
||||
:data="data2"
|
||||
:span-method="createSpanMethod(data2)"
|
||||
:columns="functionColumn"
|
||||
:data="functionData"
|
||||
:span-method="createSpanMethod(functionData)"
|
||||
align="left"
|
||||
headerAlign="left"
|
||||
border="true"
|
||||
@ -29,23 +29,18 @@ import { ProductVO } from '@/api/iot/product/product'
|
||||
|
||||
const props = defineProps<{ product: ProductVO }>()
|
||||
|
||||
// 定义列
|
||||
const columns1 = reactive([
|
||||
// TODO 芋艿:不确定未来会不会改,所以先写死
|
||||
|
||||
// 基础通信 Topic 列
|
||||
const basicColumn = reactive([
|
||||
{ label: '功能', field: 'function', width: 150 },
|
||||
{ label: 'Topic 类', field: 'topicClass', width: 800 },
|
||||
{ label: '操作权限', field: 'operationPermission', width: 100 },
|
||||
{ label: '描述', field: 'description' }
|
||||
])
|
||||
|
||||
const columns2 = reactive([
|
||||
{ label: '功能', field: 'function', width: 150 },
|
||||
{ label: 'Topic 类', field: 'topicClass', width: 800 },
|
||||
{ label: '操作权限', field: 'operationPermission', width: 100 },
|
||||
{ label: '描述', field: 'description' }
|
||||
])
|
||||
|
||||
// TODO @haohao:这个,有没可能写到一个枚举里,方便后续维护? /Users/yunai/Java/yudao-ui-admin-vue3/src/views/ai/utils/constants.ts
|
||||
const data1 = computed(() => {
|
||||
// 基础通信 Topic 数据
|
||||
const basicData = computed(() => {
|
||||
if (!props.product || !props.product.productKey) return []
|
||||
return [
|
||||
{
|
||||
@ -147,7 +142,16 @@ const data1 = computed(() => {
|
||||
]
|
||||
})
|
||||
|
||||
const data2 = computed(() => {
|
||||
// 物模型通信 Topic 列
|
||||
const functionColumn = reactive([
|
||||
{ label: '功能', field: 'function', width: 150 },
|
||||
{ label: 'Topic 类', field: 'topicClass', width: 800 },
|
||||
{ label: '操作权限', field: 'operationPermission', width: 100 },
|
||||
{ label: '描述', field: 'description' }
|
||||
])
|
||||
|
||||
// 物模型通信 Topic 数据
|
||||
const functionData = computed(() => {
|
||||
if (!props.product || !props.product.productKey) return []
|
||||
return [
|
||||
{
|
||||
|
||||
@ -37,7 +37,7 @@ const message = useMessage()
|
||||
const id = Number(route.params.id) // 编号
|
||||
const loading = ref(true) // 加载中
|
||||
const product = ref<ProductVO>({} as ProductVO) // 详情
|
||||
const activeTab = ref('info') // 默认激活的标签页
|
||||
const activeTab = ref('info') // 默认为 info 标签页
|
||||
|
||||
provide(IOT_PROVIDE_KEY.PRODUCT, product) // 提供产品信息给产品信息详情页的所有子组件
|
||||
|
||||
@ -69,6 +69,11 @@ onMounted(async () => {
|
||||
return
|
||||
}
|
||||
await getProductData(id)
|
||||
// 处理 tab 参数
|
||||
const { tab } = route.query
|
||||
if (tab) {
|
||||
activeTab.value = tab as string
|
||||
}
|
||||
// 查询设备数量
|
||||
if (product.value.id) {
|
||||
product.value.deviceCount = await getDeviceCount(product.value.id)
|
||||
|
||||
@ -63,52 +63,89 @@
|
||||
|
||||
<!-- 卡片视图 -->
|
||||
<ContentWrap>
|
||||
<div
|
||||
v-if="viewMode === 'card'"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"
|
||||
>
|
||||
<el-card
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="cursor-pointer hover:shadow-lg transition-shadow"
|
||||
@click="openDetail(item.id)"
|
||||
>
|
||||
<div class="flex items-center mb-4">
|
||||
<el-image
|
||||
:src="item.picUrl || '/src/assets/default-product.png'"
|
||||
class="w-12 h-12 mr-4"
|
||||
fit="cover"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-lg">{{ item.name }}</div>
|
||||
<div class="text-gray-500 text-sm">{{ item.productKey }}</div>
|
||||
<el-row v-if="viewMode === 'card'" :gutter="16">
|
||||
<el-col v-for="item in list" :key="item.id" :xs="24" :sm="12" :md="12" :lg="6" class="mb-4">
|
||||
<el-card class="h-full transition-colors" :body-style="{ padding: '0' }">
|
||||
<!-- 内容区域 -->
|
||||
<div class="p-4">
|
||||
<!-- 标题区域 -->
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="mr-2.5 flex items-center">
|
||||
<el-image :src="item.icon || defaultIconUrl" class="w-[35px] h-[35px]" />
|
||||
</div>
|
||||
<div class="text-[16px] font-600">{{ item.name }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 信息区域 -->
|
||||
<div class="flex items-center text-[14px]">
|
||||
<div class="flex-1">
|
||||
<div class="mb-2.5 last:mb-0">
|
||||
<span class="text-[#717c8e] mr-2.5">产品分类</span>
|
||||
<span class="text-[#0070ff]">{{ item.categoryName }}</span>
|
||||
</div>
|
||||
<div class="mb-2.5 last:mb-0">
|
||||
<span class="text-[#717c8e] mr-2.5">产品类型</span>
|
||||
<dict-tag :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE" :value="item.deviceType" />
|
||||
</div>
|
||||
<div class="mb-2.5 last:mb-0">
|
||||
<span class="text-[#717c8e] mr-2.5">产品标识</span>
|
||||
<span class="text-[#0b1d30]">{{ item.productKey }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[100px] h-[100px]">
|
||||
<el-image :src="item.picUrl || defaultPicUrl" class="w-full h-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<el-divider class="!my-3" />
|
||||
|
||||
<!-- 按钮组 -->
|
||||
<div class="flex items-center px-0">
|
||||
<el-button
|
||||
class="flex-1 !px-2 !h-[32px] text-[13px]"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('update', item.id)"
|
||||
v-hasPermi="['iot:product:update']"
|
||||
>
|
||||
<Icon icon="ep:edit-pen" class="mr-1" />
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
class="flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
|
||||
type="warning"
|
||||
plain
|
||||
@click="openDetail(item.id)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-1" />
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
class="flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
|
||||
type="success"
|
||||
plain
|
||||
@click="openObjectModel(item)"
|
||||
>
|
||||
<Icon icon="ep:scale-to-original" class="mr-1" />
|
||||
物模型
|
||||
</el-button>
|
||||
<div class="mx-[10px] h-[20px] w-[1px] bg-[#dcdfe6]"></div>
|
||||
<el-button
|
||||
class="!px-2 !h-[32px] text-[13px]"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleDelete(item.id)"
|
||||
v-hasPermi="['iot:product:delete']"
|
||||
:disabled="item.status === 1"
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm text-gray-500">
|
||||
<span>{{ item.categoryName }}</span>
|
||||
<dict-tag :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE" :value="item.deviceType" />
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click.stop="openForm('update', item.id)"
|
||||
v-hasPermi="['iot:product:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click.stop="handleDelete(item.id)"
|
||||
v-hasPermi="['iot:product:delete']"
|
||||
:disabled="item.status === 1"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<el-table v-else v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
@ -199,14 +236,19 @@ import { ProductApi, ProductVO } from '@/api/iot/product/product'
|
||||
import ProductForm from './ProductForm.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import defaultPicUrl from '@/assets/imgs/iot/device.png'
|
||||
import defaultIconUrl from '@/assets/svgs/iot/cube.svg'
|
||||
|
||||
/** iot 产品列表 */
|
||||
defineOptions({ name: 'IoTProduct' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const activeName = ref('info') // 当前激活的标签页
|
||||
const list = ref<ProductVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
@ -216,7 +258,7 @@ const queryParams = reactive({
|
||||
productKey: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const exportLoading = ref(false) // 导出加载中
|
||||
const viewMode = ref<'card' | 'list'>('card') // 视图模式状态
|
||||
|
||||
/** 查询列表 */
|
||||
@ -250,11 +292,19 @@ const openForm = (type: string, id?: number) => {
|
||||
}
|
||||
|
||||
/** 打开详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'IoTProductDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开物模型 */
|
||||
const openObjectModel = (item: ProductVO) => {
|
||||
push({
|
||||
name: 'IoTProductDetail',
|
||||
params: { id: item.id },
|
||||
query: { tab: 'function' }
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
@ -286,5 +336,10 @@ const handleExport = async () => {
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
// 处理 tab 参数
|
||||
const { tab } = route.query
|
||||
if (tab) {
|
||||
activeName.value = tab as string
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user