feat: 新增设备批量导出-映射设备
This commit is contained in:
parent
875e7c00f7
commit
677f65ff8c
|
@ -25,3 +25,66 @@ export const ID_Rule = [
|
|||
},
|
||||
Max_Length_64[0]
|
||||
]
|
||||
|
||||
export const CreteRuleByType = (type: string) => {
|
||||
switch (type){
|
||||
case 'int':
|
||||
return [
|
||||
{
|
||||
validator: (_: any, value: number) => {
|
||||
const baseNumber = 2147483648
|
||||
if (value < -baseNumber) {
|
||||
return Promise.reject(`最小仅输入-${baseNumber}`);
|
||||
}
|
||||
if (value > baseNumber) {
|
||||
return Promise.reject(`最大可输入${baseNumber}`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
case'long':
|
||||
return [
|
||||
{
|
||||
validator: (_: any, value: number) => {
|
||||
const baseNumber = 340282346638528860000000000000000000000
|
||||
if (value < -baseNumber) {
|
||||
return Promise.reject(`最小仅输入-${baseNumber}`);
|
||||
}
|
||||
if (value > baseNumber) {
|
||||
return Promise.reject(`最大可输入${baseNumber}`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
case'float':
|
||||
return [
|
||||
{
|
||||
validator: (_: any, value: number) => {
|
||||
const baseNumber = 9223372036854775807
|
||||
if (value < -baseNumber) {
|
||||
return Promise.reject(`最小仅输入-${baseNumber}`);
|
||||
}
|
||||
if (value > baseNumber) {
|
||||
return Promise.reject(`最大可输入${baseNumber}`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
// case'double':
|
||||
// return [
|
||||
// {
|
||||
// max: 1.7976931348623157,
|
||||
// message: '最大可输入64位字符'
|
||||
// }
|
||||
// ]
|
||||
case 'string':
|
||||
return [MaxLengthStringFn()];
|
||||
case 'description':
|
||||
return [MaxLengthStringFn(200)]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
:visible="true"
|
||||
width="800px"
|
||||
title="导入"
|
||||
>
|
||||
<div>
|
||||
<!-- 选择产品 -->
|
||||
<div v-if='steps === 0'>
|
||||
<Product
|
||||
v-model:rowKey='importData.productId'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<j-button v-if='steps === 0' @click='cancel' >取消</j-button>
|
||||
<j-button v-if='steps !== 0' @click='prev' >上一步</j-button>
|
||||
<j-button v-if='steps !== 2' @click='next' type='primary'>下一步</j-button>
|
||||
<j-button v-if='steps === 2' @click='save' type='primary'>确认</j-button>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang='ts' setup name='DeviceImport'>
|
||||
import Product from './product.vue'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
|
||||
import { queryList } from '@/api/device/product';
|
||||
|
||||
const emit = defineEmits(['cancel', 'save']);
|
||||
|
||||
const steps = ref(0) // 步骤
|
||||
|
||||
const importData = reactive({
|
||||
productId: undefined,
|
||||
type: undefined,
|
||||
})
|
||||
|
||||
const next = () => {
|
||||
if (steps.value === 0 && !importData.productId) {
|
||||
return onlyMessage('请选择产品', 'error')
|
||||
}
|
||||
if (steps.value === 1 && !importData.type) {
|
||||
return onlyMessage('请选择导入方式', 'error')
|
||||
}
|
||||
steps.value += 1
|
||||
}
|
||||
|
||||
const prev = () => {
|
||||
if (steps.value === 2 && importData.type) {
|
||||
steps.value = 0
|
||||
} else {
|
||||
steps.value -= 1
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
emit('save')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,254 @@
|
|||
<template>
|
||||
<j-advanced-search
|
||||
:columns="columns"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class="scene-search"
|
||||
target="device-import-product"
|
||||
/>
|
||||
<j-divider style='margin: 0' />
|
||||
<j-pro-table
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:params='params'
|
||||
:request='productQuery'
|
||||
:gridColumn='2'
|
||||
:gridColumns='[2,2,2]'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="rowKey === slotProps.id"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{ 1: 'processing', 0: 'error', }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img width='80' height='80' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style='width: calc(100% - 100px)'>
|
||||
<Ellipsis>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
</div>
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>直连设备</div>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</j-pro-table>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='Product'>
|
||||
import { getProviders, queryGatewayList, queryProductList } from '@/api/device/product'
|
||||
import { queryTree } from '@/api/device/category'
|
||||
import { getTreeData_api } from '@/api/system/department'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import { accessConfigTypeFilter } from '@/utils/setting'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:rowKey', data: string): void
|
||||
(e: 'change', data: string): void
|
||||
}
|
||||
|
||||
const params = ref({})
|
||||
const props = defineProps({
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
const firstFind = ref(true)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '网关类型',
|
||||
dataIndex: 'accessProvider',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () => getProviders().then((resp: any) => {
|
||||
return accessConfigTypeFilter(resp.result || [])
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '接入方式',
|
||||
dataIndex: 'accessName',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () => queryGatewayList().then((resp: any) =>
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name, value: item.id
|
||||
}))
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
dataIndex: 'deviceType',
|
||||
width: 150,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
width: '90px',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 0 },
|
||||
{ label: '正常', value: 1 },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
ellipsis: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
dataIndex: 'classifiedId',
|
||||
title: '分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () => {
|
||||
return new Promise((res => {
|
||||
queryTree({ paging: false }).then(resp => {
|
||||
res(resp.result)
|
||||
})
|
||||
}))
|
||||
},
|
||||
componentProps: {
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'id$dim-assets',
|
||||
title: '所属组织',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () => new Promise((resolve) => {
|
||||
getTreeData_api({ paging: false }).then((resp: any) => {
|
||||
const formatValue = (list: any[]) => {
|
||||
return list.map((item: any) => {
|
||||
if (item.children) {
|
||||
item.children = formatValue(item.children);
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
value: JSON.stringify({
|
||||
assetType: 'product',
|
||||
targets: [
|
||||
{
|
||||
type: 'org',
|
||||
id: item.id,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
resolve(formatValue(resp.result) || [])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
}
|
||||
|
||||
const productQuery = async (p: any) => {
|
||||
const sorts: any = [];
|
||||
|
||||
if (props.rowKey) {
|
||||
sorts.push({
|
||||
name: 'id',
|
||||
value: props.rowKey,
|
||||
});
|
||||
}
|
||||
sorts.push({ name: 'createTime', order: 'desc' });
|
||||
p.sorts = sorts
|
||||
const resp = await queryProductList(p)
|
||||
if (resp.success && props.rowKey && firstFind.value) {
|
||||
const productItem = (resp.result as { data: any[]}).data.find((item: any) => item.id === props.rowKey)
|
||||
emit('update:detail', productItem)
|
||||
firstFind.value = false
|
||||
}
|
||||
return {
|
||||
...resp
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = (detail: any) => {
|
||||
emit('update:rowKey', detail.id)
|
||||
emit('change', detail)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -90,7 +90,7 @@
|
|||
<j-form ref="formRef" :model="formData.data" layout="vertical">
|
||||
<j-form-item
|
||||
:name="item.property"
|
||||
v-for="item in metadata.properties"
|
||||
v-for="item in metadata?.properties || []"
|
||||
:key="item"
|
||||
:label="item.name"
|
||||
:rules="[
|
||||
|
|
|
@ -173,6 +173,7 @@ import {
|
|||
import AccessCard from '../AccessCard/index.vue';
|
||||
import { useMenuStore } from 'store/menu'
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import { CreteRuleByType } from 'components/Form/rules'
|
||||
|
||||
const props = defineProps({
|
||||
provider: {
|
||||
|
@ -208,6 +209,7 @@ const config = ref<any>([])
|
|||
const queryPlugin = (params = {}) => {
|
||||
getPluginList({
|
||||
...params,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
paging: false
|
||||
}).then(res => {
|
||||
pluginList.value = []
|
||||
|
@ -219,26 +221,23 @@ const queryPlugin = (params = {}) => {
|
|||
|
||||
const getRules = (item: any) => {
|
||||
let typeName = '输入'
|
||||
let rules: any[] = []
|
||||
|
||||
if (['select', 'date'].includes(item.type?.type || 'string')) {
|
||||
typeName = '选择'
|
||||
}
|
||||
|
||||
const rules = [
|
||||
{
|
||||
required: true,
|
||||
message: `请${typeName}${item.name}`
|
||||
}
|
||||
]
|
||||
|
||||
if (['select', 'date'].includes(item.type?.type || 'string')) {
|
||||
rules.push({
|
||||
max: 64,
|
||||
message: `最多输入64个字符`
|
||||
})
|
||||
if (item.required) {
|
||||
rules.push(
|
||||
{
|
||||
required: true,
|
||||
message: `请${typeName}${item.name}`
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const typeRules = CreteRuleByType(item.type?.type)
|
||||
rules = [...rules, ...typeRules]
|
||||
return rules
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
name='version'
|
||||
:rules='[{ required: true, message: "请上传文件" }]'
|
||||
>
|
||||
<UploadFile v-model:modelValue='modelRef.version' @change='uploadChange' :disabled='data.id' />
|
||||
<UploadFile v-model:modelValue='modelRef.version' @change='uploadChange' />
|
||||
</j-form-item>
|
||||
<div v-if='modelRef.version' class='file-detail'>
|
||||
<div>
|
||||
|
@ -77,7 +77,6 @@ import { FileUploadResult } from '@/views/link/plugin/typings'
|
|||
import { add, vailIdFn } from '@/api/link/plugin'
|
||||
import { message } from 'jetlinks-ui-components'
|
||||
import { TypeMap } from './util'
|
||||
import { start } from '@/api/link/type'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
@change="handleChange"
|
||||
class="upload-box"
|
||||
:before-upload="beforeUpload"
|
||||
:disabled='disabled || loading'
|
||||
:disabled='loading'
|
||||
:maxCount='1'
|
||||
>
|
||||
<div>
|
||||
<j-button :disabled='disabled'>上传文件</j-button>
|
||||
<span class='upload-tip'>格式要求:{文件名}.jar</span>
|
||||
<j-button>上传文件</j-button>
|
||||
<span class='upload-tip'>格式要求:{文件名}.jar/{文件名}.zip</span>
|
||||
</div>
|
||||
</j-upload>
|
||||
|
||||
|
@ -51,9 +51,9 @@ const loading = ref(false);
|
|||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file, fl) => {
|
||||
const arr = file.name.split('.');
|
||||
const isFile = ['jar'].includes(arr[arr.length - 1]); // file.type === 'application/zip' || file.type === 'application/javj-archive'
|
||||
const isFile = ['jar', 'zip'].includes(arr[arr.length - 1]); // file.type === 'application/zip' || file.type === 'application/javj-archive'
|
||||
if (!isFile) {
|
||||
onlyMessage('请上传.jar格式的文件', 'error');
|
||||
onlyMessage('请上传.jar,.zip格式的文件', 'error');
|
||||
loading.value = false;
|
||||
}
|
||||
return isFile;
|
||||
|
|
|
@ -29,8 +29,12 @@
|
|||
:value="slotProps"
|
||||
:showStatus='false'
|
||||
:actions='getActions(slotProps)'
|
||||
:statusNames='{
|
||||
processing: "processing"
|
||||
}'
|
||||
status='processing'
|
||||
>
|
||||
|
||||
<template #img>
|
||||
<img
|
||||
:width="80"
|
||||
|
@ -39,11 +43,16 @@
|
|||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px); margin-bottom: 18px;">
|
||||
<span style="font-size: 16px; font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<div>
|
||||
<div>
|
||||
<j-tag class='plugin-version'>{{ slotProps.version }}</j-tag>
|
||||
</div>
|
||||
<Ellipsis style="width: calc(100% - 100px); margin-bottom: 18px;">
|
||||
<span style="font-size: 16px; font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
</div>
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
|
@ -258,6 +267,8 @@ onMounted(() => {
|
|||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.plugin-version {
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue