iot-ui-vue/src/views/device/components/Metadata/Base/Base.vue

581 lines
18 KiB
Vue

<template>
<j-data-table
v-if="!heavyLoad"
ref="tableRef"
:data-source="dataSource"
:columns="columns"
:height="560"
:searchProps="{
placeholder: '请输入搜索名称'
}"
serial
@editStatus="editStatusChange"
@change="(data) => dataSourceCache = data"
>
<template #expand>
<!-- <PermissionButton
type="primary"
v-if="!showSave"
:hasPermission="`${permission}:update`"
key="add"
:disabled="hasOperate('add', type)"
:tooltip="{
placement: hasOperate('add', type) ? 'topRight' : 'top',
title: hasOperate('add', type)
? '当前的存储方式不支持新增'
: '新增',
getPopupContainer: getPopupContainer,
}"
@click="handleAddClick()"
placement="topRight"
>
新增
</PermissionButton> -->
<PermissionButton
type="primary"
:hasPermission="`${permission}:update`"
key="update"
:loading="loading"
:disabled="hasOperate('add', type) || !editStatus"
:tooltip="{
title: hasOperate('add', type)
? '当前的存储方式不支持新增'
: !editStatus ? '暂无改动数据': '保存',
placement: hasOperate('add', type) ? 'topRight' : 'top',
getPopupContainer: getPopupContainer,
}"
@click="handleSaveClick()"
placement="topRight"
>
保存
</PermissionButton>
</template>
<template #valueType="{ data }">
{{ TypeStringMap[data.record.valueType?.type] }}
</template>
<template #inputs="{ data }">
<j-tooltip
v-if="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
title="继承自产品物模型的数据不支持修改"
>
<!-- <ModelButton :disabled="true"/>-->
<j-button :disabled="true" type="link" >
<AIcon type="SettingOutlined" />
配置
</j-button>
</j-tooltip>
<InputParams
v-else
v-model:value="data.record.inputs"
:has-permission="`${permission}:update`"
/>
</template>
<template #output="{ data }">
{{ data.record.output?.type }}
</template>
<template #async="{ data }">
{{ data.record.async ? '是' : '否' }}
</template>
<template #expands="{ data }" v-if="type === 'events'">
{{ levelMap?.[data.record.expands?.level] || '-' }}
</template>
<template v-else-if="type === 'properties'" #expands="{ data }">
{{ data.record.id && !data.record?.expands?.source ? '设备' : sourceMap?.[data.record?.expands?.source] || '' }}
</template>
<template #properties="{ data }">
<j-tooltip
v-if="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
title="继承自产品物模型的数据不支持修改"
>
<!-- <ModelButton :disabled="true"/>-->
<j-button :disabled="true" type="link">
<AIcon type="SettingOutlined" />
配置
</j-button>
</j-tooltip>
<ConfigParams
v-else
v-model:value="data.record.valueType"
:has-permission="`${permission}:update`"
/>
</template>
<template #outInput>
object
</template>
<template #readType="{data}">
<j-tag v-for="item in data.record?.expands?.type || []" :key="item">
{{ expandsType[item] }}
</j-tag>
</template>
<template #other="{ data }">
<!-- <j-tooltip
v-if="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
title="继承自产品物模型的数据不支持修改"
> -->
<!-- <ModelButton :disabled="true"/>-->
<!-- <j-button :disabled="true" type="link" style="padding-left: 0;">
<AIcon type="SettingOutlined" />
配置
</j-button> -->
<!-- </j-tooltip> -->
<OtherSetting
v-model:value="data.record.expands"
:id="data.record.id"
:disabled="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
:record="data.record"
:type="data.record.valueType.type"
:has-permission="`${permission}:update`"
:tooltip="target === 'device' && productNoEdit.id?.includes?.(data.record.id) ? {
title: '继承自产品物模型的数据不支持删除',
} : undefined"
/>
</template>
<template #action="{data}">
<j-space>
<PermissionButton
:has-permission="`${permission}:update`"
type="link"
key="edit"
style="padding: 0"
:disabled="!!operateLimits('add', type)"
@click="copyItem(data.record, data.index)"
:tooltip="{
title: operateLimits('add', type) ? '当前的存储方式不支持复制' : '复制',
getPopupContainer: getPopupContainer,
}"
>
<AIcon type="CopyOutlined" />
</PermissionButton>
<PermissionButton
:has-permission="`${permission}:update`"
type="link"
key="edit"
style="padding: 0"
:disabled="!!operateLimits('add', type)"
@click="handleAddClick(null, data.index)"
:tooltip="{
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
getPopupContainer: getPopupContainer,
}"
>
<AIcon type="PlusSquareOutlined" />
</PermissionButton>
<PermissionButton
:has-permission="true"
type="link"
key="edit"
style="padding: 0"
@click="showDetail(data.record)"
:tooltip="{
title: '详情',
getPopupContainer: getPopupContainer,
}"
>
<AIcon type="FileSearchOutlined" />
</PermissionButton>
<PermissionButton
:has-permission="`${permission}:update`"
type="link"
key="delete"
style="padding: 0"
danger
:pop-confirm="{
placement: 'topRight',
title: showLastDelete ? '这是最后一条数据了,确认删除?' : '确认删除?',
onConfirm: async () => {
await removeItem(data.index);
},
getPopupContainer: getPopupContainer
}"
:disabled="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
:tooltip="{
placement: 'topRight',
getPopupContainer: getPopupContainer,
title: target === 'device' && productNoEdit.id?.includes?.(data.record.id) ? '继承自产品物模型的数据不支持删除' :'删除',
}"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</j-data-table>
<PermissionButton
type="primary"
block
ghost
:hasPermission="`${permission}:update`"
key="add"
:disabled="hasOperate('add', type)"
:tooltip="{
placement: hasOperate('add', type) ? 'topRight' : 'top',
title: hasOperate('add', type)
? '当前的存储方式不支持新增'
: '新增',
getPopupContainer: getPopupContainer,
}"
@click="handleAddClick()"
placement="topRight"
>
<template #icon><AIcon type="PlusOutlined"/></template>
新增行
</PermissionButton>
<PropertiesModal
v-if="type === 'properties' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
<FunctionModal
v-else-if="type === 'functions' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
<EventModal
v-else-if="type === 'events' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
<TagsModal
v-else-if="type === 'tags' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
</template>
<script setup lang="ts" name="MetadataBase">
import type {
MetadataItem,
MetadataType,
ProductItem,
} from '@/views/device/Product/typings';
import type { PropType } from 'vue';
import { TOKEN_KEY } from '@/utils/variable'
import {useRouter, onBeforeRouteUpdate} from 'vue-router'
import { useMetadata, useOperateLimits } from './hooks';
import {TypeStringMap, useColumns} from './columns';
import { levelMap, sourceMap, expandsType, limitsMap } from './utils';
import { Source, OtherSetting, InputParams, ConfigParams } from './components';
import { saveProductVirtualProperty } from '@/api/device/product';
import { saveDeviceVirtualProperty } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { useProductStore } from '@/store/product';
import { asyncUpdateMetadata, updateMetadata } from '../metadata';
import { useMetadataStore } from '@/store/metadata';
import { DeviceInstance } from '@/views/device/Instance/typings';
import { onlyMessage , LocalStore} from '@/utils/comm';
import {omit} from "lodash-es";
import { PropertiesModal, FunctionModal, EventModal, TagsModal } from './DetailModal'
import { Modal } from 'jetlinks-ui-components'
import {EventEmitter} from "@/utils/utils";
import {computed, watch} from "vue";
import {cloneDeep} from "lodash";
import {useSystem} from "store/system";
import {storeToRefs} from "pinia";
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
import { usePermissionStore } from '@/store/permission';
import App from '@/App.vue';
const props = defineProps({
target: {
type: String as PropType<'device' | 'product'>,
default: 'product',
},
type: {
type: String as PropType<MetadataType>,
default: undefined,
},
permission: {
type: [String, Array] as PropType<string | string[]>,
default: undefined,
},
});
const _target = inject<'device' | 'product'>('_metadataType', props.target);
const tableContainer = ref()
const system = useSystem();
const {basicLayout} = storeToRefs(system);
const router = useRouter()
const { data: metadata, noEdit, productNoEdit } = useMetadata(_target, props.type);
const { hasOperate } = useOperateLimits(_target);
const permissionStore = usePermissionStore()
const metadataStore = useMetadataStore()
const instanceStore = useInstanceStore()
const productStore = useProductStore()
const dataSource = ref<MetadataItem[]>(metadata.value || []);
const tableRef = ref();
const loading = ref(false)
const editStatus = ref(false) // 编辑表格的编辑状态
// const columns = computed(() => MetadataMapping.get(props.type!));
const {columns} = useColumns(props.type, _target, noEdit, productNoEdit)
const detailData = reactive({
data: {},
visible:false
})
const heavyLoad = ref<Boolean>(false)
const showSave = ref(metadata.value.length !== 0)
const dataSourceCache = ref<any[]>(metadata.value)
const fullRef = inject(FULL_CODE);
const getPopupContainer = (node: any) => {
const fullDom = tableRef.value?.fullRef?.()
return fullDom || node
}
const showLastDelete = computed(() => {
return dataSourceCache.value.length === 1
})
provide('_dataSource', dataSourceCache)
const showDetail = (data: any) => {
detailData.data = data
detailData.visible = true
}
const cancelDetailModal = () => {
detailData.data = {}
detailData.visible = false
}
const operateLimits = (action: 'add' | 'updata', types: MetadataType) => {
return (
_target === 'device' &&
(instanceStore.detail.features || []).find((item: { id: string; name: string }) => item.id === limitsMap.get(`${types}-${action}`))
);
};
const handleSearch = (searchValue: string) => {
dataSource.value = searchValue
? metadata.value
.filter((item) => item.name!.indexOf(searchValue) > -1)
.sort((a, b) => b?.sortsIndex - a?.sortsIndex)
: metadata.value;
};
const getDataByType = () => {
let _data: any = {
id: undefined,
name: undefined,
expands: {
source: 'device'
},
valueType: {
type: undefined
}
}
if (props.type === 'functions') {
_data = {
id: undefined,
name: undefined,
async: false,
inputs: [],
output: {
type: undefined
}
}
} else if (props.type === 'events') {
_data = {
id: undefined,
name: undefined,
async: false,
valueType: {
type: 'object',
properties: []
},
expands: {
level: 'ordinary'
}
}
} else if (props.type === 'tags') {
_data = {
id: undefined,
name: undefined,
valueType: {
type: undefined
},
expands: {
type: undefined
}
}
}
return _data
}
const handleAddClick = async (_data?: any, index?: number) => {
const newObject = _data || getDataByType()
const _addData = await tableRef.value.addItem(newObject, index)
nextTick(()=>{
if(tableContainer.value.classList.value === 'tableContainer'){
tableContainer.value.classList.remove('tableContainer')
}
})
// if (_addData.length === 1) {
// showLastDelete.value = true
// }
showSave.value = true
};
const copyItem = (record: any, index: number) => {
const copyData = cloneDeep(omit(record, ['_uuid', '_sortIndex']))
copyData.id = `copy_${copyData.id}`
handleAddClick(copyData, index)
}
const removeItem = (index: number) => {
// const data = [...dataSource.value];
// data.splice(index, 1);
// dataSource.value = data
const _data = tableRef.value.removeItem(index)
// if (_data.length === 1) {
// showLastDelete.value = true
// }
if (_data.length === 0) {
showSave.value = false
handleSaveClick()
}
}
const editStatusChange = (status: boolean) => {
console.log('editStatusChange',status)
editStatus.value = status
}
const handleSaveClick = async (next?: Function) => {
let resp = await tableRef.value.getData().finally(() => {
});
if(resp) {
const virtual: any[] = [];
const arr = resp.map((item: any) => {
if(item.expands?.virtualRule) {
const triggerProperties = item.expands.virtualRule.triggerProperties
const rule = omit(item.expands.virtualRule, ['triggerProperties'])
virtual.push({
triggerProperties,
rule,
type: rule.type,
propertyId: item.id
})
}
return {
...item,
expands: {
...omit(item.expands, ['virtualRule'])
}
}
// return item
})
// 保存规则
if(virtual.length) {
let res = undefined
if(_target === 'device') {
res = await saveDeviceVirtualProperty(instanceStore.current.productId, instanceStore.current.id, virtual)
} else {
res = await saveProductVirtualProperty(productStore.current.id, virtual)
}
}
// 保存属性
const updateStore = (metadata: string) => {
if (_target === 'device') {
const detail = instanceStore.current
detail.metadata = metadata
instanceStore.setCurrent(detail)
} else {
const detail = productStore.current || {} as ProductItem
detail.metadata = metadata
productStore.setCurrent(detail)
}
}
const _detail: ProductItem | DeviceInstance = _target === 'device' ? instanceStore.detail : productStore.current
let _data = updateMetadata(props.type!, arr, _detail, updateStore)
loading.value = true
const result = await asyncUpdateMetadata(_target, _data).finally(() => {
loading.value = false
})
if(result.success) {
dataSource.value = resp
tableRef.value.cleanEditStatus()
editStatus.value = false
onlyMessage('操作成功!')
next?.()
}
}
};
const tabsChange = inject('tabsChange')
const parentTabsChange = (next?: Function) => {
if (editStatus.value && permissionStore.hasPermission(`${props.permission}:update`) && LocalStore.get(TOKEN_KEY)) {
const modal = Modal.confirm({
content: '页面改动数据未保存',
okText: '保存',
cancelText: '不保存',
zIndex: 1400,
closable: true,
onOk: () => {
handleSaveClick(next as Function)
},
onCancel: (e: any) => {
if (!e.triggerCancel) { // 取消按钮
modal.destroy();
(next as Function)?.()
} else {// 右上角取消按钮
const paths = router.currentRoute.value.matched
// basicLayout.value.selectedKeys = paths.map(item => item.path)
basicLayout.value.openKeys = paths.map(item => item.path)
}
}
})
} else {
(next as Function)?.()
}
}
EventEmitter.subscribe(['MetadataTabs'], parentTabsChange)
onUnmounted(() => {
EventEmitter.unSubscribe(['MetadataTabs'], parentTabsChange)
})
watch(() => metadata.value, () => {
dataSource.value = metadata.value
}, { immediate: true })
onBeforeRouteUpdate((to, from, next) => { // 设备管理内路由跳转
parentTabsChange(next as Function)
})
onBeforeRouteLeave((to, from, next) => { // 设备管理外路由跳转
parentTabsChange(next as Function)
})
</script>
<style scoped>
.table-header {
display: flex;
justify-content: space-between;
padding-bottom: 16px;
}
</style>