feat: 边缘网关
This commit is contained in:
parent
34d3ff7419
commit
2fee7a95cc
|
@ -33,6 +33,13 @@ export const detail = (id: string) => server.get<DeviceInstance>(`/device-instan
|
|||
*/
|
||||
export const query = (data?: Record<string, any>) => server.post('/device-instance/_query', data)
|
||||
|
||||
/**
|
||||
* 不分页查询设备
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryNoPagingPost = (data?: Record<string, any>) => server.post('/device-instance/_query/no-paging?paging=false', data)
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
* @param id 设备ID
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export const restPassword = (id: string) => server.post(`/edge/operations/${id}/auth-user-password-reset/invoke`)
|
||||
|
||||
export const _control = (deviceId: string) => server.get(`/edge/remote/${deviceId}/url`)
|
||||
|
||||
export const _stopControl = (deviceId: string) => server.get(`/edge/remote/${deviceId}/stop`, {})
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export const query = (data: Record<string, any>) => server.post(`/entity/template/_query`, data)
|
||||
|
||||
export const modify = (id: string, data: Record<string, any>) => server.put(`/entity/template/${id}`, data)
|
||||
|
||||
export const _delete = (id: string) => server.remove(`/entity/template/${id}`)
|
||||
|
||||
export const _start = (data: Record<string, any>) => server.post(`/entity/template/start/_batch`, data)
|
||||
|
||||
export const _stop = (data: Record<string, any>) => server.post(`/entity/template/stop/_batch`, data)
|
||||
|
||||
export const queryDeviceList = (data: Record<string, any>) => server.post(`/device-instance/detail/_query`, data)
|
||||
|
|
@ -1,12 +1,29 @@
|
|||
<template>
|
||||
<j-modal :maskClosable="false" width="800px" :visible="true" title="导入" @ok="handleSave" @cancel="handleCancel">
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="800px"
|
||||
:visible="true"
|
||||
title="导入"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form :layout="'vertical'">
|
||||
<j-row>
|
||||
<j-col span="24">
|
||||
<j-form-item label="产品" required>
|
||||
<j-select showSearch v-model:value="modelRef.product" placeholder="请选择产品">
|
||||
<j-select-option :value="item.id" v-for="item in productList" :key="item.id" :label="item.name">{{ item.name }}</j-select-option>
|
||||
<j-select
|
||||
showSearch
|
||||
v-model:value="modelRef.product"
|
||||
placeholder="请选择产品"
|
||||
>
|
||||
<j-select-option
|
||||
:value="item.id"
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
|
@ -17,7 +34,11 @@
|
|||
</j-col>
|
||||
<j-col span="12">
|
||||
<j-form-item label="文件上传" v-if="modelRef.product">
|
||||
<NormalUpload :product="modelRef.product" v-model="modelRef.upload" :file="modelRef.file" />
|
||||
<NormalUpload
|
||||
:product="modelRef.product"
|
||||
v-model="modelRef.upload"
|
||||
:file="modelRef.file"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
@ -27,16 +48,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product'
|
||||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
})
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
default: undefined,
|
||||
},
|
||||
type: String,
|
||||
});
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
|
||||
const modelRef = reactive({
|
||||
product: undefined,
|
||||
|
@ -44,26 +66,39 @@ const modelRef = reactive({
|
|||
file: {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
queryNoPagingPost({paging: false}).then(resp => {
|
||||
if(resp.status === 200){
|
||||
productList.value = resp.result as Record<string, any>[]
|
||||
queryNoPagingPost({
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
type: 'and'
|
||||
},
|
||||
{
|
||||
column: 'accessProvider',
|
||||
value: props?.type
|
||||
}
|
||||
]
|
||||
}).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp.result as Record<string, any>[];
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
)
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
emit('save')
|
||||
}
|
||||
emit('save');
|
||||
};
|
||||
</script>
|
|
@ -289,7 +289,6 @@ import { queryTree } from '@/api/device/category';
|
|||
import { useMenuStore } from '@/store/menu';
|
||||
import type { ActionsType } from './typings';
|
||||
import dayjs from 'dayjs';
|
||||
import { throttle } from 'lodash-es';
|
||||
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
<template>
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="650px"
|
||||
:visible="true"
|
||||
:title="!!data?.id ? '编辑' : '新增'"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
|
||||
<j-row type="flex">
|
||||
<j-col flex="180px">
|
||||
<j-form-item name="photoUrl">
|
||||
<JProUpload v-model="modelRef.photoUrl" />
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col flex="auto">
|
||||
<j-form-item
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
ID
|
||||
<j-tooltip
|
||||
title="若不填写,系统将自动生成唯一ID"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<j-input
|
||||
v-model:value="modelRef.id"
|
||||
placeholder="请输入ID"
|
||||
:disabled="!!data?.id"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="modelRef.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<j-col :span="22">
|
||||
<j-form-item
|
||||
name="productId"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
>所属产品
|
||||
<j-tooltip title="只能选择“正常”状态的产品">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<j-select
|
||||
showSearch
|
||||
v-model:value="modelRef.productId"
|
||||
:disabled="!!data?.id"
|
||||
placeholder="请选择所属产品"
|
||||
>
|
||||
<j-select-option
|
||||
:value="item.id"
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="2" style="margin-top: 30px">
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:disabled="data.id"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Product:add"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />
|
||||
</PermissionButton>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item
|
||||
label="说明"
|
||||
name="describe"
|
||||
:rules="[
|
||||
{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-textarea
|
||||
v-model:value="modelRef.describe"
|
||||
placeholder="请输入说明"
|
||||
showCount
|
||||
:maxlength="200"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</div>
|
||||
</j-modal>
|
||||
<SaveProduct
|
||||
v-model:visible="visible"
|
||||
v-model:productId="modelRef.productId"
|
||||
:channel="'official-edge-gateway'"
|
||||
@close="onClose"
|
||||
:deviceType="'gateway'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { isExists, update } from '@/api/device/instance';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import SaveProduct from '@/views/media/Device/Save/SaveProduct.vue';
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
productId: undefined,
|
||||
id: undefined,
|
||||
name: '',
|
||||
describe: '',
|
||||
photoUrl: getImage('/device/instance/device-card.png'),
|
||||
});
|
||||
|
||||
const vailId = async (_: Record<string, any>, value: string) => {
|
||||
if (!props?.data?.id && value) {
|
||||
const resp = await isExists(value);
|
||||
if (resp.status === 200 && resp.result) {
|
||||
return Promise.reject('ID重复');
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(newValue) => {
|
||||
queryNoPagingPost({
|
||||
paging: false,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'state',
|
||||
value: 1,
|
||||
type: 'and',
|
||||
},
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'accessProvider',
|
||||
value: 'official-edge-gateway',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp.result as Record<string, any>[];
|
||||
}
|
||||
});
|
||||
Object.assign(modelRef, newValue);
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close');
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (_data: any) => {
|
||||
loading.value = true;
|
||||
const obj = { ..._data };
|
||||
if (!obj.id) {
|
||||
delete obj.id;
|
||||
}
|
||||
const resp = await update(obj).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('save');
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('error', err);
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,438 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="edge-device"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JProTable
|
||||
ref="edgeDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
:gridColumn="3"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-space>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
hasPermission="edge/Device:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
@click="importVisible = true"
|
||||
hasPermission="edge/Device:import"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="ImportOutlined"
|
||||
/></template>
|
||||
导入
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
online: 'success',
|
||||
offline: 'error',
|
||||
notActive: 'warning',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<img
|
||||
:src="getImage('/device/instance/device-card.png')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 600"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<j-row style="margin-top: 20px">
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>{{ slotProps.deviceType?.text }}</div>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
产品名称
|
||||
</div>
|
||||
<Ellipsis style="width: 100%">
|
||||
{{ slotProps.productName }}
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'edge/Device:' + item.key"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
<span>{{
|
||||
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
style="padding: 0 5px"
|
||||
:hasPermission="'edge/Device:' + i.key"
|
||||
>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<Save
|
||||
v-if="visible"
|
||||
:data="current"
|
||||
@close="visible = false"
|
||||
@save="saveBtn"
|
||||
/>
|
||||
<Import @save="onRefresh" @close="importVisible = false" v-if="importVisible" type="official-edge-gateway" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { queryTree } from '@/api/device/category';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { ActionsType } from '@/views/device/Instance/typings';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import dayjs from 'dayjs';
|
||||
import { query, _delete, _deploy, _undeploy } from '@/api/device/instance';
|
||||
import { restPassword } from '@/api/edge/device';
|
||||
import Save from './Save/index.vue';
|
||||
import Import from '@/views/device/Instance/Import/index.vue';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const defaultParams = {
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'productId$product-info',
|
||||
value: 'accessProvider is official-edge-gateway',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const edgeDeviceRef = ref<Record<string, any>>({});
|
||||
const importVisible = ref<boolean>(false);
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
defaultTermType: 'eq',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryNoPagingPost({ paging: false }).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'registryTime',
|
||||
key: 'registryTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'classifiedId',
|
||||
dataIndex: 'classifiedId',
|
||||
title: '产品分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryTree({ paging: false }).then((resp: any) => {
|
||||
resolve(resp.result);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'deviceType',
|
||||
title: '设备类型',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'view',
|
||||
text: '查看',
|
||||
tooltip: {
|
||||
title: '查看',
|
||||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
handleView(data.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
visible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'setting',
|
||||
text: '远程控制',
|
||||
tooltip: {
|
||||
title: '远程控制',
|
||||
},
|
||||
icon: 'ControlOutlined',
|
||||
onClick: () => {
|
||||
message.error('暂未开发');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
text: '重置密码',
|
||||
tooltip: {
|
||||
title: '重置密码',
|
||||
},
|
||||
icon: 'RedoOutlined',
|
||||
popConfirm: {
|
||||
title: '确认重置密码为P@ssw0rd?',
|
||||
onConfirm: async () => {
|
||||
restPassword(data.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeDeviceRef.value?.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state?.value !== 'notActive' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: data.state?.value !== 'notActive' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value !== 'notActive'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${
|
||||
data.state.value !== 'notActive' ? '禁用' : '启用'
|
||||
}?`,
|
||||
onConfirm: async () => {
|
||||
let response = undefined;
|
||||
if (data.state.value !== 'notActive') {
|
||||
response = await _undeploy(data.id);
|
||||
} else {
|
||||
response = await _deploy(data.id);
|
||||
}
|
||||
if (response && response.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeDeviceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
disabled: data.state?.value !== 'notActive',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'notActive'
|
||||
? '已启用的设备不能删除'
|
||||
: '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await _delete(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeDeviceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Instance/Detail', { id });
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
visible.value = true;
|
||||
current.value = {};
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false;
|
||||
edgeDeviceRef.value?.reload();
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
importVisible.value = false
|
||||
edgeDeviceRef.value?.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<j-modal
|
||||
visible
|
||||
title="下发结果"
|
||||
:width="900"
|
||||
@ok="emit('close')"
|
||||
@cancel="emit('close')"
|
||||
>
|
||||
<j-row>
|
||||
<j-col :span="8">
|
||||
<div>成功:{{ count }}</div>
|
||||
<div>
|
||||
失败:{{ countErr }}
|
||||
<j-button @click="_download(errMessage || '', '下发失败原因')" v-if="errMessage.length" type="link"
|
||||
>下载</j-button
|
||||
>
|
||||
</div>
|
||||
</j-col>
|
||||
<j-col :span="8">下发设备数量:{{ list.length || 0 }}</j-col>
|
||||
<j-col :span="8">已下发数量:{{ countErr + count }}</j-col>
|
||||
</j-row>
|
||||
<div v-if="!flag">
|
||||
<j-textarea :rows="20" :value="JSON.stringify(errMessage)" />
|
||||
</div>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import dayjs from 'dayjs';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const count = ref<number>(0);
|
||||
const countErr = ref<number>(0);
|
||||
const flag = ref<boolean>(true);
|
||||
const errMessage = ref<any[]>([]);
|
||||
|
||||
const getData = () => {
|
||||
let dt = 0;
|
||||
let et = 0;
|
||||
const errMessages: any[] = [];
|
||||
const _terms = {
|
||||
deviceId: (props.list || []).map((item: any) => item?.id),
|
||||
params: JSON.stringify({
|
||||
name: props.data.name,
|
||||
targetId: props.data.targetId,
|
||||
targetType: props.data.targetType,
|
||||
category: props.data.category,
|
||||
metadata: props.data?.metadata,
|
||||
}),
|
||||
};
|
||||
const url = new URLSearchParams();
|
||||
Object.keys(_terms).forEach((key) => {
|
||||
if (Array.isArray(_terms[key]) && _terms[key].length) {
|
||||
_terms[key].map((item: string) => {
|
||||
url.append(key, item);
|
||||
});
|
||||
} else {
|
||||
url.append(key, _terms[key]);
|
||||
}
|
||||
});
|
||||
const source = new EventSourcePolyfill(
|
||||
`${BASE_API_PATH}/edge/operations/entity-template-save/invoke/_batch?:X_Access_Token=${LocalStore.get(
|
||||
TOKEN_KEY,
|
||||
)}&${url}`,
|
||||
);
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.successful) {
|
||||
dt += 1;
|
||||
count.value = dt;
|
||||
} else {
|
||||
et += 1;
|
||||
countErr.value = et;
|
||||
flag.value = false;
|
||||
if (errMessages.length <= 5) {
|
||||
errMessages.push({ ...res });
|
||||
errMessage.value = [...errMessages];
|
||||
}
|
||||
}
|
||||
};
|
||||
source.onerror = () => {
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
};
|
||||
|
||||
const _download = (record: Record<string, any>, fileName: string, format?: string) => {
|
||||
// 创建隐藏的可下载链接
|
||||
const ghostLink = document.createElement('a');
|
||||
ghostLink.download = `${fileName ? '' : record?.name}${fileName}_${dayjs(new Date()).format(
|
||||
format || 'YYYY_MM_DD',
|
||||
)}.txt`;
|
||||
ghostLink.style.display = 'none';
|
||||
//字符串内容转成Blob地址
|
||||
const blob = new Blob([JSON.stringify(record)]);
|
||||
ghostLink.href = URL.createObjectURL(blob);
|
||||
//触发点击
|
||||
document.body.appendChild(ghostLink);
|
||||
ghostLink.click();
|
||||
//移除
|
||||
document.body.removeChild(ghostLink);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data.id,
|
||||
(newId) => {
|
||||
if(newId){
|
||||
getData()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<j-modal
|
||||
visible
|
||||
title="下发设备"
|
||||
:width="1000"
|
||||
@ok="onSave"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<div class="alert">
|
||||
<AIcon
|
||||
type="InfoCircleOutlined"
|
||||
style="margin-right: 10px"
|
||||
/>离线设备无法进行设备模板下发
|
||||
</div>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="edge-resource-issue"
|
||||
@search="handleSearch"
|
||||
type="simple"
|
||||
class="search"
|
||||
/>
|
||||
<JProTable
|
||||
ref="edgeResourceIssueRef"
|
||||
:columns="columns"
|
||||
:request="queryDeviceList"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
model="TABLE"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
>
|
||||
<template #state="slotProps">
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #sourceId="slotProps">
|
||||
{{ slotProps.sourceName }}
|
||||
</template>
|
||||
<template #registerTime="slotProps">
|
||||
<span>{{
|
||||
dayjs(slotProps.registerTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
</JProTable>
|
||||
<Result v-if="visible" :data="props.data" :list="_data" @close="onCancel" />
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import { queryDeviceList } from '@/api/edge/resource';
|
||||
import dayjs from 'dayjs';
|
||||
import Result from './Result.vue';
|
||||
|
||||
const defaultParams = {
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'productId$product-info',
|
||||
value: 'accessProvider is official-edge-gateway',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const params = ref({});
|
||||
const edgeResourceIssueRef = ref();
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const _data = ref<any[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
ellipsis: true,
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'registerTime',
|
||||
key: 'registerTime',
|
||||
width: 200,
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const onSelectChange = (keys: string[], _options: any[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
_data.value = _options;
|
||||
};
|
||||
|
||||
const handleSearch = (v: any) => {
|
||||
params.value = v;
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if(_data.value.length){
|
||||
visible.value = true
|
||||
} else {
|
||||
onlyMessage('请选择设备', 'error')
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
padding: 0 0 0 24px;
|
||||
}
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<j-modal visible title="编辑" :width="700" @ok="onSave" @cancel="onCancel">
|
||||
<MonacoEditor
|
||||
style="width: 100%; height: 370px"
|
||||
theme="vs"
|
||||
v-model="monacoValue"
|
||||
language="json"
|
||||
/>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
import { modify } from '@/api/edge/resource';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const monacoValue = ref<string>('{}');
|
||||
|
||||
watchEffect(() => {
|
||||
monacoValue.value = props.data?.metadata || '{}';
|
||||
});
|
||||
|
||||
const onSave = async () => {
|
||||
const resp = await modify(props.data.id, { metadata: unref(monacoValue) });
|
||||
if (resp.status === 200) {
|
||||
emit('save');
|
||||
onlyMessage('操作成功', 'success');
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,383 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="edge-resource"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JProTable
|
||||
ref="edgeResourceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<img
|
||||
:src="getImage('/device/instance/device-card.png')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 600"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<j-row style="margin-top: 20px">
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
通讯协议
|
||||
</div>
|
||||
<Ellipsis>{{
|
||||
options.find(
|
||||
(i) => i.value === slotProps.category,
|
||||
)?.label || slotProps.category
|
||||
}}</Ellipsis>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
所属边缘网关
|
||||
</div>
|
||||
<Ellipsis style="width: 100%">
|
||||
{{ slotProps.sourceName }}
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'edge/Resource:' + item.key"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #sourceId="slotProps">
|
||||
{{ slotProps.sourceName }}
|
||||
</template>
|
||||
<template #category="slotProps">
|
||||
{{
|
||||
options.find((i) => i.value === slotProps.category)
|
||||
?.label || slotProps.category
|
||||
}}
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
<span>{{
|
||||
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
style="padding: 0 5px"
|
||||
:hasPermission="'edge/Resource:' + i.key"
|
||||
>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<Save
|
||||
v-if="visible"
|
||||
:data="current"
|
||||
@close="visible = false"
|
||||
@save="saveBtn"
|
||||
/>
|
||||
<Issue
|
||||
v-if="settingVisible"
|
||||
:data="current"
|
||||
@close="settingVisible = false"
|
||||
/>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/instance';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { ActionsType } from '@/views/device/Instance/typings';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import dayjs from 'dayjs';
|
||||
import { query, _delete, _start, _stop } from '@/api/edge/resource';
|
||||
import Save from './Save/index.vue';
|
||||
import Issue from './Issue/index.vue';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const defaultParams = { sorts: [{ name: 'createTime', order: 'desc' }] };
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('enabled', 'success');
|
||||
statusMap.set('disabled', 'error');
|
||||
|
||||
const options = [
|
||||
{ label: 'UA接入', value: 'OPC_UA' },
|
||||
{ label: 'Modbus TCP接入', value: 'MODBUS_TCP' },
|
||||
{ label: 'S7-200接入', value: 'snap7' },
|
||||
{ label: 'BACnet接入', value: 'BACNetIp' },
|
||||
{ label: 'MODBUS_RTU接入', value: 'MODBUS_RTU' },
|
||||
];
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const edgeResourceRef = ref<Record<string, any>>({});
|
||||
const settingVisible = ref<boolean>(false);
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'category',
|
||||
title: '通信协议',
|
||||
valueType: 'select',
|
||||
scopedSlots: true,
|
||||
key: 'category',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: options,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所属边缘网关',
|
||||
dataIndex: 'sourceId',
|
||||
key: 'sourceId',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryNoPagingPost({
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'productId$product-info',
|
||||
value: 'accessProvider is official-edge-gateway',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
sorts: [
|
||||
{
|
||||
name: 'createTime',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
}).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'disabled' },
|
||||
{ label: '正常', value: 'enabled' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
visible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'setting',
|
||||
text: '下发',
|
||||
disabled: data.state?.value === 'disabled',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value === 'disabled'
|
||||
? '请先启用,再下发'
|
||||
: '下发',
|
||||
},
|
||||
icon: 'DownSquareOutlined',
|
||||
onClick: () => {
|
||||
settingVisible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value !== 'disabled'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${
|
||||
data.state.value !== 'disabled' ? '禁用' : '启用'
|
||||
}?`,
|
||||
onConfirm: async () => {
|
||||
let response = undefined;
|
||||
if (data.state.value !== 'disabled') {
|
||||
response = await _stop([data.id]);
|
||||
} else {
|
||||
response = await _start([data.id]);
|
||||
}
|
||||
if (response && response.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeResourceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
disabled: data.state?.value !== 'disabled',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'disabled'
|
||||
? '请先禁用,再删除。'
|
||||
: '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await _delete(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeResourceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Instance/Detail', { id });
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false;
|
||||
edgeResourceRef.value?.reload();
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
settingVisible.value = false;
|
||||
edgeResourceRef.value?.reload();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -133,6 +133,7 @@ type Emits = {
|
|||
(e: 'update:visible', data: boolean): void;
|
||||
(e: 'update:productId', data: string): void;
|
||||
(e: 'close'): void;
|
||||
(e: 'save', ): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
|
@ -140,6 +141,7 @@ const props = defineProps({
|
|||
visible: { type: Boolean, default: false },
|
||||
productId: { type: String, default: '' },
|
||||
channel: { type: String, default: '' },
|
||||
deviceType: { type: String, default: 'device' }
|
||||
});
|
||||
|
||||
const _vis = computed({
|
||||
|
@ -171,12 +173,12 @@ const handleClick = async (e: any) => {
|
|||
formData.value.accessId = e.id;
|
||||
formData.value.accessName = e.name;
|
||||
formData.value.accessProvider = e.provider;
|
||||
formData.value.messageProtocol = e.provider;
|
||||
formData.value.messageProtocol = e.protocolDetail.id;
|
||||
formData.value.protocolName = e.protocolDetail.name;
|
||||
formData.value.transportProtocol = e.transport;
|
||||
|
||||
const { result } = await DeviceApi.getConfiguration(
|
||||
props.channel,
|
||||
e.protocol,
|
||||
e.transport,
|
||||
);
|
||||
console.log('result: ', result);
|
||||
|
@ -206,7 +208,7 @@ const formData = ref({
|
|||
access_pwd: '',
|
||||
stream_mode: 'UDP',
|
||||
},
|
||||
deviceType: 'device',
|
||||
deviceType: props.deviceType,
|
||||
messageProtocol: '',
|
||||
name: '',
|
||||
protocolName: '',
|
||||
|
|
|
@ -3695,8 +3695,8 @@ jetlinks-store@^0.0.3:
|
|||
|
||||
jetlinks-ui-components@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#30de07a15481f13ea86ebc817baaab3c99034403"
|
||||
integrity sha512-ULgSPU0xY6xUky3beeHVvpHyAHmT6xHsO5eS5m7a3h7AmCoxA3oTWyF20vC+K1zTJBQ7LFCouySqRRz6GimAPg==
|
||||
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#360e87e3cba4d025ec4665943098a88b0d9ff59a"
|
||||
integrity sha512-lYe7kx65XCvZzf7esQRTm/ljlQi5kMtv00yotnSmvMILR6tohx57fA5Ga895i63DrySSmJlqBUsniMtjQVzqqQ==
|
||||
dependencies:
|
||||
"@vueuse/core" "^9.12.0"
|
||||
ant-design-vue "^3.2.15"
|
||||
|
|
Loading…
Reference in New Issue