fix: 物模型属性开发
This commit is contained in:
parent
76dfb3a797
commit
02153a63da
|
@ -185,3 +185,12 @@ export const getOperator = () => server.get<OperatorItem>('/property-calculate-r
|
|||
* 获取聚合函数列表
|
||||
*/
|
||||
export const getStreamingAggType = () => server.get<Record<string, string>[]>('/dictionary/streaming-agg-type/items')
|
||||
|
||||
export const getMetadataConfig = (params: {
|
||||
deviceId: string;
|
||||
metadata: {
|
||||
type: MetadataType | 'property';
|
||||
id: string;
|
||||
dataType: string;
|
||||
};
|
||||
}) => server.get<Record<any, any>[]>(`/device/product/${params.deviceId}/config-metadata/${params.metadata.type}/${params.metadata.id}/${params.metadata.dataType}`)
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="indicator-box">
|
||||
<template v-if="['int', 'long', 'double', 'float'].includes(type)">
|
||||
<template v-if="value.range">
|
||||
<a-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small"
|
||||
style="width: 100%;"></a-input-number>
|
||||
~
|
||||
<a-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small"
|
||||
style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
<a-input-number v-else v-model:value="value.value" size="small" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'date'">
|
||||
<a-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" />
|
||||
<a-date-picker v-else show-time v-model:value="value.value" size="small" />
|
||||
</template>
|
||||
<template v-else-if="type === 'boolean'">
|
||||
<a-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></a-select>
|
||||
</template>
|
||||
<template v-else-if="type === 'string'">
|
||||
<a-input v-model:value="value.value" size="small" placeholder="请输入"></a-input>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="value.range">
|
||||
<a-input v-model:value="value.value[0]" :max="value.value[1]" size="small" placeholder="请输入"></a-input>
|
||||
~
|
||||
<a-input v-model:value="value.value[1]" :min="value.value[0]" size="small" placeholder="请输入"></a-input>
|
||||
</template>
|
||||
<a-input-number v-else v-model:value="value.value" size="small" placeholder="请输入"></a-input-number>
|
||||
</template>
|
||||
<div v-if="type !== 'boolean' && type !== 'string'">
|
||||
<a-checkbox style="min-width: 60px; margin-left: 5px;" v-model:checked="value.range" @change="changeChecked">
|
||||
范围
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="JIndicators">
|
||||
import { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface';
|
||||
import { Form } from 'ant-design-vue'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Array] as any
|
||||
},
|
||||
enum: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
Form.useInjectFormItemContext()
|
||||
|
||||
const changeChecked = (e: CheckboxChangeEvent) => {
|
||||
if (e.target.checked) {
|
||||
props.value.value = []
|
||||
} else {
|
||||
delete props.value.value
|
||||
}
|
||||
}
|
||||
|
||||
const list = ref<{ label: any; value: any; }[]>([])
|
||||
watch(() => props.enum,
|
||||
() => {
|
||||
const arr = [];
|
||||
if (!!props.enum?.falseText && props.enum?.falseValue !== undefined) {
|
||||
arr.push({ label: props.enum?.falseText, value: props.enum?.falseValue });
|
||||
}
|
||||
if (!!props.enum?.trueText && props.enum?.trueValue !== undefined) {
|
||||
arr.push({ label: props.enum?.trueText, value: props.enum?.trueValue });
|
||||
}
|
||||
list.value = arr
|
||||
},
|
||||
{ immediate: true, deep: true })
|
||||
|
||||
watch(() => props.type,
|
||||
(value) => {
|
||||
if (value === 'boolean') {
|
||||
if (!props.value.value) props.value.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true })
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.indicator-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label) {
|
||||
line-height: 1;
|
||||
|
||||
>label {
|
||||
font-size: 12px;
|
||||
|
||||
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(input) {
|
||||
height: 22px;
|
||||
}
|
||||
</style>
|
|
@ -77,5 +77,8 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
}
|
||||
:deep(input) {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<a-popover placement="left" trigger="click">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">{{ config.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style="max-width: 400px;" class="ant-form-vertical">
|
||||
<a-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
|
||||
<a-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
|
||||
label: e.text,
|
||||
value: e.value,
|
||||
}))" size="small"></a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<a-button type="dashed" block>
|
||||
存储配置<edit-outlined class="item-icon" />
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
<script setup lang="ts" name="ConfigParam">
|
||||
import { PropType } from 'vue';
|
||||
import { EditOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => ([]),
|
||||
required: true
|
||||
},
|
||||
config: {
|
||||
type: Array as PropType<ValueType>,
|
||||
default: () => ({ properties: [] })
|
||||
}
|
||||
})
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: string | undefined): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
// const _value = computed({
|
||||
// get: () => props.value,
|
||||
// set: (val: string | undefined) => {
|
||||
// emit('update:value', val)
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.item-icon {
|
||||
color: rgb(136, 136, 136);
|
||||
font-size: 12px;
|
||||
}
|
||||
:deep(.ant-form-item-label) {
|
||||
>label {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
:deep(input) {
|
||||
height: 22px;
|
||||
}
|
||||
</style>
|
|
@ -157,4 +157,8 @@ const handleAdd = () => {
|
|||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(input) {
|
||||
height: 22px;
|
||||
}
|
||||
</style>
|
|
@ -19,8 +19,8 @@
|
|||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: 'ID只能由数字、字母、下划线、中划线组成',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="_value[index].id" size="small"></a-input>
|
||||
|
@ -169,4 +169,8 @@ const handleAdd = () => {
|
|||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(input) {
|
||||
height: 22px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,202 @@
|
|||
<template>
|
||||
<div class="json-param">
|
||||
<div class="list-item" v-for="(item, index) in _value" :key="`object_${index}`">
|
||||
<div class="item-left">
|
||||
<menu-outlined class="item-drag item-icon" />
|
||||
{{ `#${index + 1}.` }}
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
<a-popover :visible="editIndex === index" placement="top" @visible-change="change" trigger="click">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置参数</div>
|
||||
<close-outlined @click="handleClose" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div>
|
||||
<a-form :model="_value[index]" layout="vertical">
|
||||
<a-form-item label="标识" name="id" :rules="[
|
||||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: 'ID只能由数字、字母、下划线、中划线组成',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="_value[index].id" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" name="name" :rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="指标值" name="value" :rules="[
|
||||
{ required: true, message: '请输入指标值' },
|
||||
{ validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
|
||||
]">
|
||||
<JIndicators v-model:value="_value[index]" :type="type" size="small" :enum="enum"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.name || '配置参数' }}
|
||||
<edit-outlined class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<delete-outlined @click="handleDelete(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<template #icon><plus-outlined class="item-icon" /></template>
|
||||
添加指标
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="MetricsParam">
|
||||
import { PropType } from 'vue'
|
||||
import { MenuOutlined, EditOutlined, DeleteOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import JIndicators from '@/components/JIndicators/index.vue';
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: Record<any, any>[]): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<Record<any, any>[]>,
|
||||
default: () => ([])
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
enum: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const _value = ref<Record<any, any>[]>([])
|
||||
watchEffect(() => {
|
||||
_value.value = props.value
|
||||
})
|
||||
|
||||
watch(_value,
|
||||
() => {
|
||||
emit('update:value', _value.value)
|
||||
},
|
||||
{ deep: true })
|
||||
|
||||
const editIndex = ref<number>(-1)
|
||||
const handleEdit = (index: number) => {
|
||||
editIndex.value = index
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
editIndex.value = -1
|
||||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
_value.value.push({})
|
||||
emit('update:value', _value.value)
|
||||
}
|
||||
|
||||
const validateIndicator = (value: any) => {
|
||||
if (value?.range) {
|
||||
if (!value?.value || !value?.value[0] || !value?.value[1]) {
|
||||
return Promise.reject(new Error('请输入指标值'));
|
||||
}
|
||||
} else {
|
||||
if (value?.value === '' || value?.value === undefined) {
|
||||
return Promise.reject(new Error('请输入指标值'));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const change = (visible: boolean) => {
|
||||
if (!visible) {
|
||||
editIndex.value = -1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.json-param {
|
||||
.list-item {
|
||||
border: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
padding: 3px 6px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
line-height: 26px;
|
||||
font-size: 14px;
|
||||
|
||||
// .item-left {
|
||||
// .item-drag {
|
||||
// cursor: move;
|
||||
// }
|
||||
// }
|
||||
.item-edit {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
color: rgb(136, 136, 136);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label) {
|
||||
line-height: 1;
|
||||
|
||||
>label {
|
||||
font-size: 12px;
|
||||
|
||||
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-explain) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-with-help) {
|
||||
.ant-form-item-explain {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(input) {
|
||||
height: 22px;
|
||||
}
|
||||
</style>
|
|
@ -33,6 +33,11 @@ const props = defineProps({
|
|||
type: String
|
||||
}
|
||||
})
|
||||
interface Emits {
|
||||
(e: 'refresh'): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
|
@ -95,6 +100,7 @@ const save = reactive({
|
|||
detail.metadata = metadata
|
||||
productStore.setCurrent(detail)
|
||||
}
|
||||
emit('refresh')
|
||||
}
|
||||
const _data = updateMetadata(type, [formValue], _detail, updateStore)
|
||||
const result = await asyncUpdateMetadata(props.type, _data)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type"></Edit>
|
||||
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit>
|
||||
</template>
|
||||
<template #level="slotProps">
|
||||
{{ levelMap[slotProps.expands?.level] || '-' }}
|
||||
|
@ -124,7 +124,7 @@ onMounted(() => {
|
|||
|
||||
})
|
||||
|
||||
watch([route.params.id, type], () => {
|
||||
const refreshMetadata = () => {
|
||||
loading.value = true
|
||||
// const res = target === 'product'
|
||||
// ? await productDetail(route.params.id as string)
|
||||
|
@ -133,7 +133,8 @@ watch([route.params.id, type], () => {
|
|||
const item = JSON.parse(result || '{}') as MetadataItem[]
|
||||
data.value = item[type]?.sort((a: any, b: any) => b?.sortsIndex - a?.sortsIndex)
|
||||
loading.value = false
|
||||
}, { immediate: true })
|
||||
}
|
||||
watch([route.params.id, type], refreshMetadata, { immediate: true })
|
||||
|
||||
const metadataStore = useMetadataStore()
|
||||
const handleAddClick = () => {
|
||||
|
|
Loading…
Reference in New Issue