feat: 标签编辑和关系编辑

This commit is contained in:
100011797 2023-01-31 15:38:04 +08:00
parent 0626348328
commit f381453bce
10 changed files with 456 additions and 27 deletions

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

@ -120,3 +120,50 @@ export const update = (data: Partial<DeviceInstance>) => data.id ? server.patch(
* @returns
*/
export const getConfigMetadata = (id: string) => server.get(`/device-instance/${id}/config-metadata`)
/**
*
* @param id id
* @returns
*/
export const _disconnect = (id: string) => server.post(`/device-instance/${id}/disconnect`)
/**
*
* @returns
*/
export const queryUserListNoPaging = () => server.post(`/user/_query/no-paging`, {
paging: false,
sorts: [{name: 'name', order: "asc"}]
})
/**
*
* @param id id
* @param data
* @returns
*/
export const saveRelations = (id: string, data: Record<string, any>) => server.patch(`/device/instance/${id}/relations`, data)
/**
*
* @param id id
* @param data
* @returns
*/
export const saveTags = (id: string, data: Record<string, any>) => server.patch(`/device/instance/${id}/tag`, data)
/**
*
* @param deviceId id
* @param id id
* @returns
*/
export const delTags = (deviceId: string, id: string) => server.remove(`/device/instance/${deviceId}/tag/${id}`)
/**
*
* @param deviceId id
* @returns
*/
export const configurationReset = (deviceId: string) => server.put(`/device-instance/${deviceId}/configuration/_reset`)

View File

@ -333,7 +333,7 @@ const JTable = defineComponent<JTableProps>({
total={total.value}
showQuickJumper={false}
showSizeChanger={true}
current={pageIndex.value}
current={pageIndex.value + 1}
pageSize={pageSize.value}
pageSizeOptions={['12', '24', '48', '60', '100']}
showTotal={(num) => {
@ -345,7 +345,7 @@ const JTable = defineComponent<JTableProps>({
handleSearch({
...props.params,
pageSize: size,
pageIndex: pageSize.value === size ? page : 0
pageIndex: pageSize.value === size ? (page ? page - 1 : 0) : 0
})
}}
/>

View File

@ -4,8 +4,12 @@
<div style="font-size: 16px; font-weight: 700">配置</div>
<a-space>
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
<a-button type="link" v-if="instanceStore.detail.current?.value !== 'notActive'"><AIcon type="CheckOutlined" />应用配置<a-tooltip title="修改配置后需重新应用后才能生效。"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
<a-button type="link" v-if="instanceStore.detail.aloneConfiguration"><AIcon type="SyncOutlined" />恢复默认<a-tooltip title="该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
<a-popconfirm title="确认重新应用该配置?" @confirm="deployBtn">
<a-button type="link" v-if="instanceStore.detail.current?.value !== 'notActive'"><AIcon type="CheckOutlined" />应用配置<a-tooltip title="修改配置后需重新应用后才能生效。"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
</a-popconfirm>
<a-popconfirm title="确认恢复默认配置?" @confirm="resetBtn">
<a-button type="link" v-if="instanceStore.detail.aloneConfiguration"><AIcon type="SyncOutlined" />恢复默认<a-tooltip title="该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
</a-popconfirm>
</a-space>
</div>
<a-descriptions bordered size="small" v-for="i in config" :key="i.name">
@ -28,7 +32,8 @@
<script lang="ts" setup>
import { useInstanceStore } from "@/store/instance"
import { ConfigMetadata } from "@/views/device/Product/typings"
import { getConfigMetadata } from '@/api/device/instance'
import { getConfigMetadata, _deploy, configurationReset } from '@/api/device/instance'
import { message } from "ant-design-vue"
const instanceStore = useInstanceStore()
const visible = ref<boolean>(false)
@ -36,11 +41,11 @@ const config = ref<ConfigMetadata[]>([])
watchEffect(() => {
if(instanceStore.current.id){
// getConfigMetadata(instanceStore.current.id).then(resp => {
// if(resp.status === 200){
// config.value = resp?.result as ConfigMetadata[]
// }
// })
getConfigMetadata(instanceStore.current.id).then(resp => {
if(resp.status === 200){
config.value = resp?.result as ConfigMetadata[]
}
})
}
})
@ -53,5 +58,24 @@ const isExit = (property: string) => {
instanceStore.current?.cachedConfiguration[property]
);
}
const deployBtn = async () => {
if(instanceStore.current.id){
const resp = await _deploy(instanceStore.current.id)
if (resp.status === 200) {
message.success('操作成功')
instanceStore.refresh(instanceStore.current.id)
}
}
}
const resetBtn = async () => {
if(instanceStore.current.id){
const resp = await configurationReset(instanceStore.current.id)
if (resp.status === 200) {
message.success('恢复默认配置成功')
instanceStore.refresh(instanceStore.current.id)
}
}
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<a-drawer placement="right" :closable="false" :visible="true">
<template #title>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<span
><AIcon
type="CloseOutlined"
style="margin-right: 5px"
@click="onClose"
/></span
>
<a-button type="primary" @click="saveBtn">保存</a-button>
</div>
</template>
<a-form layout="vertical" ref="formRef" :model="modelRef">
<a-form-item
:name="item.relation"
:label="item.relationName"
v-for="(item, index) in dataSource"
:key="index"
>
<a-select
showSearch
mode="multiple"
v-model:value="modelRef[item.relation]"
:placeholder="`请选择${item.relationName}`"
>
<a-select-option
:value="item.value"
v-for="item in userList"
:key="item.id"
>{{ item.name }}</a-select-option
>
</a-select>
</a-form-item>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import { queryUserListNoPaging, saveRelations } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { message } from 'ant-design-vue';
const emit = defineEmits(['close', 'save']);
const formRef = ref();
const modelRef = reactive({});
const userList = ref<Record<string, any>[]>([]);
const instanceStore = useInstanceStore();
const dataSource = ref<Record<any, any>[]>([]);
watchEffect(() => {
const arr = (instanceStore.current?.relations || [])
dataSource.value = arr as Record<any, any>[];
arr.map((item) => {
modelRef[item.relation] = [
...(item?.related || []).map((i: any) => JSON.stringify(i)),
];
});
});
onMounted(() => {
queryUserListNoPaging().then((resp) => {
if (resp.status === 200) {
userList.value = (resp.result as Record<string, any>[]).map(
(item) => {
return {
...item,
label: item.name,
value: JSON.stringify({
id: item.id,
name: item.name,
}),
};
},
);
}
});
});
const onClose = () => {
emit('close');
formRef.value.resetFields();
};
const saveBtn = () => {
formRef.value.validate().then(async () => {
const values = toRaw(modelRef);
if (Object.keys(values).length > 0) {
const param: any[] = [];
Object.keys(values).forEach((key) => {
const item = dataSource.value.find((i) => i.relation === key);
const items = (values[key] || []).map((i: string) =>
JSON.parse(i),
);
if (item) {
param.push({
relatedType: 'user',
relation: item.relation,
description: '',
related: [...items],
});
}
});
if(param.length && instanceStore.current.id){
const resp = await saveRelations(instanceStore.current.id, param);
if (resp.status === 200) {
message.success('操作成功!');
emit('save');
formRef.value.resetFields();
}
}
}
});
};
</script>

View File

@ -7,17 +7,27 @@
</template>
<a-descriptions-item :span="1" v-for="item in dataSource" :key="item.objectId" :label="item.relationName">{{ item?.related ? (item?.related || []).map(i => i.name).join(',') : '' }}</a-descriptions-item>
</a-descriptions>
<Save v-if="visible" @save="saveBtn" @close="visible = false" />
</div>
</template>
<script lang="ts" setup>
import { useInstanceStore } from "@/store/instance"
import Save from './Save.vue'
const instanceStore = useInstanceStore()
const dataSource = ref<Record<any, any>[]>([])
const visible = ref<boolean>(false);
watchEffect(() => {
const arr = (instanceStore.current?.relations || []).reverse()
dataSource.value = arr as Record<any, any>[]
})
const saveBtn = () => {
visible.value = false
if(instanceStore.current.id){
instanceStore.refresh(instanceStore.current.id)
}
}
</script>

View File

@ -0,0 +1,117 @@
<template>
<a-modal
:width="1000"
:visible="true"
title="编辑标签"
@ok="handleOk"
@cancel="handleCancel"
>
<a-table
rowKey="id"
:columns="columns"
:data-source="dataSource"
bordered
:pagination="false"
>
<template #bodyCell="{ column, text, record }">
<div style="width: 280px">
<template v-if="['key', 'name'].includes(column.dataIndex)">
<span>{{ text }}</span>
</template>
<template v-else>
<ValueItem
v-model:modelValue="record.value"
:itemType="record.type"
:options="
record.type === 'enum'
? (record?.dataType?.elements || []).map(
(item) => {
return {
label: item.text,
value: item.value,
};
},
)
: record.type === 'boolean'
? [
{ label: '是', value: true },
{ label: '否', value: false },
]
: undefined
"
/>
</template>
</div>
</template>
</a-table>
</a-modal>
</template>
<script lang="ts" setup>
import { useInstanceStore } from '@/store/instance';
import { message } from 'ant-design-vue';
import _ from 'lodash';
import { saveTags, delTags } from '@/api/device/instance'
const emit = defineEmits(['close', 'save']);
const columns = [
{
title: 'ID',
dataIndex: 'key',
with: '33%',
},
{
title: '名称',
dataIndex: 'name',
with: '33%',
},
{
title: '值',
dataIndex: 'value',
with: '34%',
},
];
const instanceStore = useInstanceStore();
const dataSource = ref<Record<any, any>[]>([]);
watchEffect(() => {
const arr = instanceStore.current?.tags || [];
dataSource.value = _.cloneDeep(arr);
});
const handleOk = async () => {
if (dataSource.value.length) {
const list = (dataSource.value || [])
.filter((item: any) => item?.key && item?.value)
.map((i: any) => {
const { dataType, ...extra } = i;
return { ...extra };
});
if (list.length) {
//
const resp = await saveTags(instanceStore.current?.id || '', list);
if (resp.status === 200) {
message.success('操作成功!');
}
}
const _list = (dataSource.value || []).filter((item: any) => item?.key && !item?.value);
if (_list.length) {
//
_list.map(async (item: any) => {
if (item.id) {
await delTags(instanceStore.current?.id || '', item.id);
}
});
}
emit('save');
} else {
emit('close');
}
};
const handleCancel = () => {
emit('close');
};
</script>

View File

@ -1,3 +1,34 @@
<template>
tags
</template>
<div style="margin-top: 20px">
<a-descriptions bordered>
<template #title>
标签
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
</template>
<a-descriptions-item :span="1" v-for="item in dataSource" :key="item.key" :label="`${item.name}${item.key})`">{{ item?.value }}</a-descriptions-item>
</a-descriptions>
<Save v-if="visible" @close="visible = false" @save="saveBtn" />
</div>
</template>
<script lang="ts" setup>
import { useInstanceStore } from "@/store/instance"
import Save from './Save.vue'
const instanceStore = useInstanceStore()
const dataSource = ref<Record<any, any>[]>([])
const visible = ref<boolean>(false)
watchEffect(() => {
const arr = (instanceStore.current?.tags || [])
dataSource.value = arr as Record<any, any>[]
})
const saveBtn = () => {
visible.value = false
if(instanceStore.current.id){
instanceStore.refresh(instanceStore.current.id)
}
}
</script>

View File

@ -1,7 +1,40 @@
<template>
<page-container :tabList="list" @back="onBack" :tabActiveKey="instanceStore.active" @tabChange="onTabChange">
<template #subTitle><div>{{instanceStore.current.name}}</div></template>
<component :is="instanceStore.tabActiveKey" />
<template #title>
<div>
<div style="display: flex; align-items: center;">
<div>{{instanceStore.current.name}}</div>
<a-divider type="vertical" />
<a-space>
<a-badge :text="instanceStore.current.state?.text" :status="statusMap.get(instanceStore.current.state?.value)" />
<a-popconfirm title="确认启用设备" @confirm="handleAction" v-if="instanceStore.current.state?.value === 'notActive'">
<a-button type="link">启用设备</a-button>
</a-popconfirm>
<a-popconfirm title="确认断开连接" @confirm="handleDisconnect" v-if="instanceStore.current.state?.value === 'online'">
<a-button type="link">断开连接</a-button>
</a-popconfirm>
<a-tooltip v-if="instanceStore.current?.accessProvider === 'child-device' &&
instanceStore.current?.state?.value === 'offline'" :title="instanceStore.current?.features?.find((item) => item.id === 'selfManageState')
? '该设备的在线状态与父设备(网关设备)保持一致'
: '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'">
<AIcon type="QuestionCircleOutlined" style="font-size: 14px" />
</a-tooltip>
</a-space>
</div>
<div style="padding-top: 10px">
<a-descriptions size="small" :column="4">
<a-descriptions-item label="ID">{{ instanceStore.current.id }}</a-descriptions-item>
<a-descriptions-item label="所属产品">
<a-button style="margin-top: -5px; padding: 0" type="link" @click="jumpProduct">{{ instanceStore.current.productName }}</a-button>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</template>
<template #extra>
<img @click="handleRefresh" :src="getImage('/device/instance/button.png')" style="margin-right: 20px; cursor: pointer;" />
</template>
<component :is="tabs[instanceStore.tabActiveKey]" />
</page-container>
</template>
@ -9,9 +42,18 @@
import { useInstanceStore } from '@/store/instance';
import Info from './Info/index.vue';
import Metadata from '../../components/Metadata/index.vue';
import { _deploy, _disconnect } from '@/api/device/instance'
import { message } from 'ant-design-vue';
import { getImage } from '@/utils/comm';
const route = useRoute();
const instanceStore = useInstanceStore()
const statusMap = new Map();
statusMap.set('online', 'processing');
statusMap.set('offline', 'error');
statusMap.set('notActive', 'warning');
const list = [
{
key: 'Info',
@ -46,4 +88,35 @@ const onBack = () => {
const onTabChange = (e: string) => {
instanceStore.tabActiveKey = e
}
const handleAction = async () => {
if(instanceStore.current.id){
const resp = await _deploy(instanceStore.current.id)
if(resp.status === 200){
message.success('操作成功!')
instanceStore.refresh(instanceStore.current.id)
}
}
}
const handleDisconnect = async () => {
if(instanceStore.current.id){
const resp = await _disconnect(instanceStore.current.id)
if(resp.status === 200){
message.success('操作成功!')
instanceStore.refresh(instanceStore.current.id)
}
}
}
const handleRefresh = async () => {
if(instanceStore.current.id){
await instanceStore.refresh(instanceStore.current.id)
message.success('操作成功')
}
}
const jumpProduct = () => {
message.warn('暂未开发')
}
</script>

View File

@ -101,8 +101,8 @@
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state.value"
:statusText="slotProps.state.text"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
offline: 'error',
@ -126,7 +126,7 @@
<a-row>
<a-col :span="12">
<div class="card-item-content-text">设备类型</div>
<div>{{ slotProps.deviceType.text }}</div>
<div>{{ slotProps.deviceType?.text }}</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">产品名称</div>
@ -151,7 +151,7 @@
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
<span>{{ item?.text }}</span>
</template>
</a-button>
</a-popconfirm>
@ -166,7 +166,7 @@
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
<span>{{ item?.text }}</span>
</template>
</a-button>
</template>
@ -176,8 +176,8 @@
</template>
<template #state="slotProps">
<a-badge
:text="slotProps.state === 1 ? ' 正常' : '禁用'"
:status="statusMap.get(slotProps.state)"
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
@ -228,7 +228,7 @@
:api="api"
:type="type"
/>
<Save v-if="visible" :data="current" />
<Save v-if="visible" :data="current" @close="visible = false" @save="saveBtn" />
</template>
<script setup lang="ts">
@ -263,8 +263,10 @@ const api = ref<string>('');
const type = ref<string>('');
const statusMap = new Map();
statusMap.set(1, 'processing');
statusMap.set(0, 'error');
statusMap.set('online', 'processing');
statusMap.set('offline', 'error');
statusMap.set('notActive', 'warning');
const columns = [
{
title: 'ID',
@ -399,9 +401,9 @@ const getActions = (
},
{
key: 'action',
text: data.state.value !== 'notActive' ? '禁用' : '启用',
text: data.state?.value !== 'notActive' ? '禁用' : '启用',
tooltip: {
title: data.state.value !== 'notActive' ? '禁用' : '启用',
title: data.state?.value !== 'notActive' ? '禁用' : '启用',
},
icon:
data.state.value !== 'notActive'
@ -430,7 +432,7 @@ const getActions = (
{
key: 'delete',
text: '删除',
disabled: data.state.value !== 'notActive',
disabled: data.state?.value !== 'notActive',
tooltip: {
title:
data.state.value !== 'notActive'