feat: 新增设备批量导出-映射设备
This commit is contained in:
parent
875e7c00f7
commit
677f65ff8c
|
@ -25,3 +25,66 @@ export const ID_Rule = [
|
||||||
},
|
},
|
||||||
Max_Length_64[0]
|
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 ref="formRef" :model="formData.data" layout="vertical">
|
||||||
<j-form-item
|
<j-form-item
|
||||||
:name="item.property"
|
:name="item.property"
|
||||||
v-for="item in metadata.properties"
|
v-for="item in metadata?.properties || []"
|
||||||
:key="item"
|
:key="item"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:rules="[
|
:rules="[
|
||||||
|
|
|
@ -173,6 +173,7 @@ import {
|
||||||
import AccessCard from '../AccessCard/index.vue';
|
import AccessCard from '../AccessCard/index.vue';
|
||||||
import { useMenuStore } from 'store/menu'
|
import { useMenuStore } from 'store/menu'
|
||||||
import { onlyMessage } from '@/utils/comm';
|
import { onlyMessage } from '@/utils/comm';
|
||||||
|
import { CreteRuleByType } from 'components/Form/rules'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
provider: {
|
provider: {
|
||||||
|
@ -208,6 +209,7 @@ const config = ref<any>([])
|
||||||
const queryPlugin = (params = {}) => {
|
const queryPlugin = (params = {}) => {
|
||||||
getPluginList({
|
getPluginList({
|
||||||
...params,
|
...params,
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
paging: false
|
paging: false
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
pluginList.value = []
|
pluginList.value = []
|
||||||
|
@ -219,26 +221,23 @@ const queryPlugin = (params = {}) => {
|
||||||
|
|
||||||
const getRules = (item: any) => {
|
const getRules = (item: any) => {
|
||||||
let typeName = '输入'
|
let typeName = '输入'
|
||||||
|
let rules: any[] = []
|
||||||
|
|
||||||
if (['select', 'date'].includes(item.type?.type || 'string')) {
|
if (['select', 'date'].includes(item.type?.type || 'string')) {
|
||||||
typeName = '选择'
|
typeName = '选择'
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = [
|
if (item.required) {
|
||||||
{
|
rules.push(
|
||||||
required: true,
|
{
|
||||||
message: `请${typeName}${item.name}`
|
required: true,
|
||||||
}
|
message: `请${typeName}${item.name}`
|
||||||
]
|
}
|
||||||
|
)
|
||||||
if (['select', 'date'].includes(item.type?.type || 'string')) {
|
|
||||||
rules.push({
|
|
||||||
max: 64,
|
|
||||||
message: `最多输入64个字符`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typeRules = CreteRuleByType(item.type?.type)
|
||||||
|
rules = [...rules, ...typeRules]
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
name='version'
|
name='version'
|
||||||
:rules='[{ required: true, message: "请上传文件" }]'
|
: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>
|
</j-form-item>
|
||||||
<div v-if='modelRef.version' class='file-detail'>
|
<div v-if='modelRef.version' class='file-detail'>
|
||||||
<div>
|
<div>
|
||||||
|
@ -77,7 +77,6 @@ import { FileUploadResult } from '@/views/link/plugin/typings'
|
||||||
import { add, vailIdFn } from '@/api/link/plugin'
|
import { add, vailIdFn } from '@/api/link/plugin'
|
||||||
import { message } from 'jetlinks-ui-components'
|
import { message } from 'jetlinks-ui-components'
|
||||||
import { TypeMap } from './util'
|
import { TypeMap } from './util'
|
||||||
import { start } from '@/api/link/type'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -9,12 +9,12 @@
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
class="upload-box"
|
class="upload-box"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
:disabled='disabled || loading'
|
:disabled='loading'
|
||||||
:maxCount='1'
|
:maxCount='1'
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<j-button :disabled='disabled'>上传文件</j-button>
|
<j-button>上传文件</j-button>
|
||||||
<span class='upload-tip'>格式要求:{文件名}.jar</span>
|
<span class='upload-tip'>格式要求:{文件名}.jar/{文件名}.zip</span>
|
||||||
</div>
|
</div>
|
||||||
</j-upload>
|
</j-upload>
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@ const loading = ref(false);
|
||||||
|
|
||||||
const beforeUpload: UploadProps['beforeUpload'] = (file, fl) => {
|
const beforeUpload: UploadProps['beforeUpload'] = (file, fl) => {
|
||||||
const arr = file.name.split('.');
|
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) {
|
if (!isFile) {
|
||||||
onlyMessage('请上传.jar格式的文件', 'error');
|
onlyMessage('请上传.jar,.zip格式的文件', 'error');
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
return isFile;
|
return isFile;
|
||||||
|
|
|
@ -29,8 +29,12 @@
|
||||||
:value="slotProps"
|
:value="slotProps"
|
||||||
:showStatus='false'
|
:showStatus='false'
|
||||||
:actions='getActions(slotProps)'
|
:actions='getActions(slotProps)'
|
||||||
|
:statusNames='{
|
||||||
|
processing: "processing"
|
||||||
|
}'
|
||||||
status='processing'
|
status='processing'
|
||||||
>
|
>
|
||||||
|
|
||||||
<template #img>
|
<template #img>
|
||||||
<img
|
<img
|
||||||
:width="80"
|
:width="80"
|
||||||
|
@ -39,11 +43,16 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<Ellipsis style="width: calc(100% - 100px); margin-bottom: 18px;">
|
<div>
|
||||||
<span style="font-size: 16px; font-weight: 600">
|
<div>
|
||||||
{{ slotProps.name }}
|
<j-tag class='plugin-version'>{{ slotProps.version }}</j-tag>
|
||||||
</span>
|
</div>
|
||||||
</Ellipsis>
|
<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-row>
|
||||||
<j-col :span="12">
|
<j-col :span="12">
|
||||||
<div class="card-item-content-text">
|
<div class="card-item-content-text">
|
||||||
|
@ -258,6 +267,8 @@ onMounted(() => {
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang='less'>
|
||||||
|
.plugin-version {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue