iot-ui-vue/src/views/device/Instance/index.vue

702 lines
24 KiB
Vue

<template>
<page-container>
<Search
:columns="columns"
target="device-instance"
@search="handleSearch"
/>
<JTable
ref="instanceRef"
:columns="columns"
:request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
@cancelSelect="cancelSelect"
:params="params"
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">新增</a-button>
<a-dropdown>
<a-button
>批量操作 <AIcon type="DownOutlined"
/></a-button>
<template #overlay>
<a-menu>
<a-menu-item>
<a-button @click="exportVisible = true"
><AIcon
type="ExportOutlined"
/>批量导出设备</a-button
>
</a-menu-item>
<a-menu-item>
<a-button @click="importVisible = true"
><AIcon
type="ImportOutlined"
/>批量导入设备</a-button
>
</a-menu-item>
<a-menu-item>
<a-popconfirm
@confirm="activeAllDevice"
title="确认激活全部设备?"
>
<a-button type="primary" ghost
><AIcon
type="CheckCircleOutlined"
/>激活全部设备</a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item>
<a-button
@click="syncDeviceStatus"
type="primary"
><AIcon
type="SyncOutlined"
/>同步设备状态</a-button
>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
<a-popconfirm
@confirm="delSelectedDevice"
title="已启用的设备无法删除,确认删除选中的禁用状态设备?"
>
<a-button type="primary" danger
><AIcon
type="DeleteOutlined"
/>删除选中设备</a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item
v-if="_selectedRowKeys.length"
title="确认激活选中设备?"
>
<a-popconfirm
@confirm="activeSelectedDevice"
>
<a-button type="primary"
><AIcon
type="CheckOutlined"
/>激活选中设备</a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
<a-popconfirm
@confirm="disabledSelectedDevice"
title="确认禁用选中设备?"
>
<a-button type="primary" danger
><AIcon
type="StopOutlined"
/>禁用选中设备</a-button
>
</a-popconfirm>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<slot name="img">
<img
:src="
getImage('/device/instance/device-card.png')
"
/>
</slot>
</template>
<template #content>
<h3
class="card-item-content-title"
@click.stop="handleView(slotProps.id)"
>
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps.deviceType?.text }}</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">
产品名称
</div>
<div>{{ slotProps.productName }}</div>
</a-col>
</a-row>
</template>
<template #actions="item">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</template>
</a-tooltip>
</template>
</CardBox>
</template>
<template #state="slotProps">
<a-badge
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
</template>
</JTable>
</page-container>
<Import v-if="importVisible" @close="importVisible = false" />
<Export
v-if="exportVisible"
@close="exportVisible = false"
:data="params"
/>
<Process
v-if="operationVisible"
@close="operationVisible = false"
:api="api"
:type="type"
/>
<Save
v-if="visible"
:data="current"
@close="visible = false"
@save="saveBtn"
/>
</template>
<script setup lang="ts">
import {
query,
_delete,
_deploy,
_undeploy,
batchUndeployDevice,
batchDeployDevice,
batchDeleteDevice,
} from '@/api/device/instance';
import type { ActionsType } from '@/components/Table/index.vue';
import { getImage, LocalStore } from '@/utils/comm';
import { message } from 'ant-design-vue';
import Import from './Import/index.vue';
import Export from './Export/index.vue';
import Process from './Process/index.vue';
import Save from './Save/index.vue';
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import {
getProviders,
queryGatewayList,
queryNoPagingPost,
queryOrgThree,
} from '@/api/device/product';
import { queryTree } from '@/api/device/category';
const router = useRouter();
const instanceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
const importVisible = ref<boolean>(false);
const exportVisible = ref<boolean>(false);
const visible = ref<boolean>(false);
const current = ref<Record<string, any>>({});
const operationVisible = ref<boolean>(false);
const api = ref<string>('');
const type = ref<string>('');
const statusMap = new Map();
statusMap.set('online', 'success');
statusMap.set('offline', 'error');
statusMap.set('notActive', 'warning');
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
search: {
type: 'string',
},
},
{
title: '设备名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
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: 'createTime',
key: 'createTime',
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);
});
}),
},
},
{
key: 'accessProvider',
title: '网关类型',
dataIndex: 'accessProvider',
valueType: 'select',
hideInTable: true,
search: {
type: 'select',
options: () =>
new Promise((resolve) => {
getProviders().then((resp: any) => {
resolve(
resp.result.map((item: any) => ({
label: item.name,
value: `accessProvider is ${item.id}`,
})),
);
});
}),
},
},
{
key: 'productId$product-info',
dataIndex: 'productId$product-info',
title: '接入方式',
hideInTable: true,
search: {
type: 'select',
options: () =>
new Promise((resolve) => {
queryGatewayList({}).then((resp: any) => {
resolve(
resp.result.map((item: any) => ({
label: item.name,
value: `accessId is ${item.id}`,
})),
);
});
}),
},
},
{
dataIndex: 'deviceType',
title: '设备类型',
valueType: 'select',
hideInTable: true,
search: {
type: 'select',
options: [
{ label: '直连设备', value: 'device' },
{ label: '网关子设备', value: 'childrenDevice' },
{ label: '网关设备', value: 'gateway' },
],
},
},
{
dataIndex: 'id$dim-assets',
title: '所属组织',
hideInTable: true,
search: {
type: 'treeSelect',
options: () =>
new Promise((resolve) => {
queryOrgThree({}).then((resp: any) => {
const formatValue = (list: any[]) => {
const _list: any[] = [];
list.forEach((item) => {
if (item.children) {
item.children = formatValue(item.children);
}
_list.push({
...item,
id: JSON.stringify({
assetType: 'device',
targets: [
{
type: 'org',
id: item.id,
},
],
}),
});
});
return _list;
};
resolve(formatValue(resp.result));
});
}),
},
},
{
title: '说明',
dataIndex: 'describe',
key: 'describe',
search: {
type: 'string',
},
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 250,
scopedSlots: true,
},
];
const paramsFormat = (
config: Record<string, any>,
_terms: Record<string, any>,
name?: string,
) => {
if (
config?.terms &&
Array.isArray(config.terms) &&
config?.terms.length > 0
) {
(config?.terms || []).map(
(item: Record<string, any>, index: number) => {
if (item?.type) {
_terms[`${name ? `${name}.` : ''}terms[${index}].type`] =
item.type;
}
paramsFormat(
item,
_terms,
`${name ? `${name}.` : ''}terms[${index}]`,
);
},
);
} else if (!config?.terms && Object.keys(config).length > 0) {
Object.keys(config).forEach((key) => {
if (config[key]) {
_terms[`${name ? `${name}.` : ''}${key}`] = config[key];
}
});
}
};
const handleParams = (config: Record<string, any>) => {
const _terms: Record<string, any> = {};
paramsFormat(config, _terms);
if (Object.keys(_terms._value).length && Object.keys(_terms).length) {
const url = new URLSearchParams();
Object.keys(_terms).forEach((key) => {
url.append(key, _terms[key]);
});
return url.toString();
} else {
return '';
}
};
/**
* 新增
*/
const handleAdd = () => {
visible.value = true;
current.value = {};
};
/**
* 查看
*/
const handleView = (id: string) => {
router.push('/iot/device/instance/detail/' + id);
};
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: 'edit',
text: '编辑',
tooltip: {
title: '编辑',
},
icon: 'EditOutlined',
onClick: () => {
visible.value = true;
current.value = data;
},
},
{
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('操作成功!');
instanceRef.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('操作成功!');
instanceRef.value?.reload();
} else {
message.error('操作失败!');
}
},
},
icon: 'DeleteOutlined',
},
];
if (type === 'card')
return actions.filter((i: ActionsType) => i.key !== 'view');
return actions;
};
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
const handleClick = (dt: any) => {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
}
};
const activeAllDevice = () => {
type.value = 'active';
const activeAPI = `${BASE_API_PATH}/device-instance/deploy?:X_Access_Token=${LocalStore.get(
TOKEN_KEY,
)}&${handleParams(params)}`;
api.value = activeAPI;
operationVisible.value = true;
};
const syncDeviceStatus = () => {
type.value = 'sync';
const syncAPI = `${BASE_API_PATH}/device-instance/state/_sync?:X_Access_Token=${LocalStore.get(
TOKEN_KEY,
)}&${handleParams(params)}`;
api.value = syncAPI;
operationVisible.value = true;
};
const delSelectedDevice = async () => {
const resp = await batchDeleteDevice(_selectedRowKeys.value);
if (resp.status === 200) {
message.success('操作成功!');
_selectedRowKeys.value = [];
instanceRef.value?.reload();
}
};
const activeSelectedDevice = async () => {
const resp = await batchDeployDevice(_selectedRowKeys.value);
if (resp.status === 200) {
message.success('操作成功!');
_selectedRowKeys.value = [];
instanceRef.value?.reload();
}
};
const disabledSelectedDevice = async () => {
const resp = await batchUndeployDevice(_selectedRowKeys.value);
if (resp.status === 200) {
message.success('操作成功!');
_selectedRowKeys.value = [];
instanceRef.value?.reload();
}
};
const saveBtn = () => {
visible.value = false;
instanceRef.value?.reload();
};
const handleSearch = (_params: any) => {
console.log(_params);
params.value = _params;
};
</script>