fix: 修复物模型-编辑表格

This commit is contained in:
XieYongHong 2023-07-05 15:06:27 +08:00
parent f295104d8e
commit e6b10a75f2
13 changed files with 338 additions and 204 deletions

View File

@ -136,7 +136,7 @@ const matchComponents: IMatcher[] = [
},
{
pattern: /^Select/,
pattern: /^Select|^SelectBoolean/,
styleDir: 'Select'
},
{
@ -306,7 +306,7 @@ function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _
}
const filterName = ['message', 'Notification']
const primitiveNames = ['AIcon','Affix', 'Anchor', 'AnchorLink', 'message', 'Notification', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider', 'ProTable', 'Search', 'AdvancedSearch', 'Ellipsis', 'MonacoEditor', 'ProLayout', 'ScrollTable', 'TableCard', 'Scrollbar', 'CardSelect', 'ColorPicker', 'PopconfirmModal', 'DataTable',
const primitiveNames = ['AIcon','Affix', 'Anchor', 'AnchorLink', 'message', 'Notification', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'SelectBoolean', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider', 'ProTable', 'Search', 'AdvancedSearch', 'Ellipsis', 'MonacoEditor', 'ProLayout', 'ScrollTable', 'TableCard', 'Scrollbar', 'CardSelect', 'ColorPicker', 'PopconfirmModal', 'DataTable',
'DataTableArray',
'DataTableString',
'DataTableInteger',

View File

@ -1,7 +1,7 @@
<template>
<j-data-table
ref="tableRef"
:data-source="dataSource"
v-model:data-source="dataSource"
:columns="columns"
:height="560"
serial
@ -107,7 +107,7 @@
</j-tag>
</template>
<template #other="{ data }">
配置
<OtherSetting v-model:value="dataSource[data.index]" />
</template>
<template #action="{data}">
<j-space>
@ -196,7 +196,7 @@ import type {
} from '@/views/device/Product/typings';
import type { PropType } from 'vue';
import { useMetadata, useOperateLimits } from './hooks';
import MetadataMapping from './columns';
import { useColumns } from './columns';
import { levelMap, sourceMap, expandsType, limitsMap } from './utils';
import Rule from '@/components/Metadata/Rule';
import { Source, OtherSetting } from './components';
@ -238,7 +238,9 @@ const productStore = useProductStore()
const dataSource = ref<MetadataItem[]>(metadata.value || []);
const tableRef = ref();
const columns = computed(() => MetadataMapping.get(props.type!));
// const columns = computed(() => MetadataMapping.get(props.type!));
const {columns} = useColumns(props.type, target, dataSource.value, metadata.value)
const detailData = reactive({
data: {},
@ -325,18 +327,23 @@ const getDataByType = () => {
return _data
}
const handleAddClick = (_data?: any, index?: number) => {
const handleAddClick = async (_data?: any, index?: number) => {
const newObject = _data || getDataByType()
tableRef.value?.addItem?.(newObject, index)
// tableRef.value?.addItem?.(newObject, index)
const data = [...dataSource.value];
if (index !== undefined) {
data.splice(index + 1, 0, newObject);
//
const _data = await tableRef.value.getData()
console.log(_data)
if (_data) {
data.splice(index + 1, 0, newObject);
}
} else {
data.push(newObject);
data.push(newObject);
}
dataSource.value = data
};

View File

@ -3,6 +3,7 @@ import { DataType, Source, InputParams, OtherSetting, OutputParams, ConfigParams
import SelectColumn from './components/Events/SelectColumn.vue';
import AsyncSelect from './components/Function/AsyncSelect.vue';
import { EventLevel } from "@/views/device/data";
import {MetadataType} from "@/views/device/Product/typings";
interface DataTableColumnProps extends ColumnProps {
type?: string,
components?: {
@ -10,10 +11,11 @@ interface DataTableColumnProps extends ColumnProps {
[key: string]: any
}
form?: {
rules: any[]
rules?: any[]
[key: string]: any
},
options?: any[]
doubleClick?: (record: any, index: number, dataIndex: string) => boolean
}
const SourceMap = {
@ -28,198 +30,235 @@ const type = {
report: '上报',
};
const BaseColumns: DataTableColumnProps[] = [
{
title: '标识',
dataIndex: 'id',
type: 'text'
},
{
title: '名称',
dataIndex: 'name',
width: 300,
type: 'text'
},
];
export const useColumns = (type?: MetadataType, target?: 'device' | 'product', dataSource?: any[], noEditor?: any[]) => {
const EventColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '事件级别',
dataIndex: 'expands',
type: 'components',
components: {
name: SelectColumn,
props: {
options: EventLevel
const BaseColumns: DataTableColumnProps[] = [
{
title: '标识',
dataIndex: 'id',
type: 'text',
form: {
rules: [{
validator(_:any,value: any) {
const hasId = dataSource?.find?.((item) => item.id === value)
if (value) {
if (hasId) {
return Promise.reject('标识重复')
}
return Promise.resolve()
}
return Promise.reject('请输入标识')
}
}]
}
}
},
{
title: '输出参数',
dataIndex: 'outInput',
},
{
title: '配置参数',
dataIndex: 'properties',
type: 'components',
components: {
name: ConfigParams,
}
},
{
title: '说明',
dataIndex: 'description',
type: 'text',
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
]);
const FunctionColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '是否异步',
dataIndex: 'async',
type: 'components',
components: {
name: AsyncSelect,
props: {
options: [
{ label: '是', value: true },
{ label: '否', value: false }
]
},
{
title: '名称',
dataIndex: 'name',
width: 300,
type: 'text',
form: {
rules: [{
required: true,
message: '请输入名称'
}]
}
}
},
{
title: '输入参数',
dataIndex: 'inputs',
type: 'components',
components: {
name: InputParams,
}
},
{
title: '输出参数',
dataIndex: 'output',
type: 'components',
components: {
name: OutputParams
}
},
{
title: '说明',
dataIndex: 'description',
type: 'text',
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
// {
// title: '读写类型',
// dataIndex: 'expands',
// render: (text: any) => (text?.type || []).map((item: string | number) => <Tag>{type[item]}</Tag>),
// },
]);
},
];
const PropertyColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '数据类型',
dataIndex: 'valueType',
type: 'components',
components: {
name: DataType
const EventColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '事件级别',
dataIndex: 'expands',
type: 'components',
components: {
name: SelectColumn,
props: {
options: EventLevel
}
}
},
width: 230
},
{
title: '属性来源',
dataIndex: 'expands',
type: 'components',
components: {
name: Source
{
title: '输出参数',
dataIndex: 'outInput',
},
form: {
required: true,
rules: [
{
validator: async (_: Record<string, any>, value: any) => {
if (value.source) {
if(value.source !== 'rule') {
if(value.type?.length) {
return Promise.resolve();
{
title: '配置参数',
dataIndex: 'properties',
type: 'components',
components: {
name: ConfigParams,
}
},
{
title: '说明',
dataIndex: 'description',
type: 'text',
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
]);
const FunctionColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '是否异步',
dataIndex: 'async',
type: 'components',
components: {
name: AsyncSelect,
props: {
options: [
{ label: '是', value: true },
{ label: '否', value: false }
]
}
}
},
{
title: '输入参数',
dataIndex: 'inputs',
type: 'components',
components: {
name: InputParams,
}
},
{
title: '输出参数',
dataIndex: 'output',
type: 'components',
components: {
name: OutputParams
}
},
{
title: '说明',
dataIndex: 'description',
type: 'text',
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
// {
// title: '读写类型',
// dataIndex: 'expands',
// render: (text: any) => (text?.type || []).map((item: string | number) => <Tag>{type[item]}</Tag>),
// },
]);
const PropertyColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '数据类型',
dataIndex: 'valueType',
type: 'components',
components: {
name: DataType
},
width: 230
},
{
title: '属性来源',
dataIndex: 'expands',
type: 'components',
components: {
name: Source
},
doubleClick(){
return target !== 'device'
},
form: {
required: true,
rules: [
{
validator: async (_: Record<string, any>, value: any) => {
if (value.source) {
if(value.source !== 'rule') {
if(value.type?.length) {
return Promise.resolve();
} else {
return Promise.reject('请选择读写类型');
}
} else {
return Promise.reject('请选择读写类型');
if(value.virtualRule?.rule?.script) {
return Promise.resolve();
}else {
return Promise.reject('请配置规则');
}
}
} else {
if(value.virtualRule?.rule?.script) {
return Promise.resolve();
}else {
return Promise.reject('请配置规则');
}
return Promise.reject('请选择属性来源');
}
} else {
return Promise.reject('请选择属性来源');
}
}
},
]
},
]
},
width: 150
},
width: 150
},
{
title: '其它配置',
dataIndex: 'other',
type: 'components',
width: 100,
components: {
name: OtherSetting
{
title: '其它配置',
dataIndex: 'other',
width: 100,
},
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
]);
const TagColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '数据类型',
dataIndex: 'valueType',
type: 'components',
components: {
name: DataType,
{
title: '操作',
dataIndex: 'action',
width: 120
}
},
{
title: '读写类型',
dataIndex: 'readType',
type: 'components',
components: {
name: TagsType
]);
const TagColumns: DataTableColumnProps[] = BaseColumns.concat([
{
title: '数据类型',
dataIndex: 'valueType',
type: 'components',
components: {
name: DataType,
}
},
{
title: '读写类型',
dataIndex: 'readType',
type: 'components',
components: {
name: TagsType
}
},
{
title: '说明',
dataIndex: 'description',
type: 'text',
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
},
{
title: '说明',
dataIndex: 'description',
type: 'text',
},
{
title: '操作',
dataIndex: 'action',
width: 120
}
]);
]);
const MetadataMapping = new Map<string, DataTableColumnProps[]>();
MetadataMapping.set('properties', PropertyColumns);
MetadataMapping.set('events', EventColumns);
MetadataMapping.set('tags', TagColumns);
MetadataMapping.set('functions', FunctionColumns);
const columns = computed(() => {
switch(type) {
case 'properties':
return PropertyColumns;
case 'events':
return EventColumns
case 'tags':
return TagColumns
case 'functions':
return FunctionColumns
}
})
return {columns}
}
export default MetadataMapping;
// const MetadataMapping = new Map<string, DataTableColumnProps[]>();
// MetadataMapping.set('properties', PropertyColumns);
// MetadataMapping.set('events', EventColumns);
// MetadataMapping.set('tags', TagColumns);
// MetadataMapping.set('functions', FunctionColumns);
//
// export default MetadataMapping;

View File

@ -0,0 +1,45 @@
<template>
<j-select-boolean
v-model:value="myValue"
tureTitle="必填"
falseTitle='不必填'
style="width: 100%;"
@change="change"
>
</j-select-boolean>
</template>
<script setup name="ConstraintSelect">
import { set, get } from 'lodash-es'
const props = defineProps({
value: {
type: Object,
default: () => ({})
},
name: {
type: [String, Array],
default: 'required'
}
})
const emit = defineEmits(['update:value'])
const myValue = ref()
const change = () => {
const newData = { ...props.value }
set(newData, name, myValue.value)
emit('update:value', newData)
}
watch(() => JSON.stringify(props.data), () => {
myValue.value = get(props.data, name)
}, { immediate: true })
</script>
<style scoped>
</style>

View File

@ -15,7 +15,18 @@
:columns="[
{ title: '参数标识', dataIndex: 'id', type: 'text', width: 100 },
{ title: '参数名称', dataIndex: 'name', type: 'text', width: 100 },
{ title: '填写约束', dataIndex: 'required', type: 'booleanSelect', width: 100, components: { props: { tureTitle: '必填', falseTitle: '不必填' }} },
{
title: '填写约束',
dataIndex: 'required',
type: 'components',
width: 100,
components: {
name: ConstraintSelect,
props: {
name: ['expands', 'required']
}
}
},
{
title: '数据类型',
type: 'components',
@ -65,16 +76,16 @@
/>
<DataTableFile v-else-if="type === 'file'" v-model:value="_valueType.fileType" @confirm="valueChange"/>
<DataTableDate v-else-if="type === 'date'" v-model:value="_valueType.date" @confirm="valueChange"/>
<!-- <DataTableString
<DataTableString
v-else-if="['string', 'password'].includes(type)"
v-model:value="data.expands.maxLength"
/> -->
v-model:value="_valueType.maxLength"
/>
</div>
</template>
<script setup lang="ts" name="MetadataDataType">
import { getUnit } from '@/api/device/instance';
import { InputParams, ValueObject, OtherConfigInfo } from '../components'
import {InputParams, ValueObject, OtherConfigInfo, ConstraintSelect} from '../components'
import {
DataTableTypeSelect,
DataTableArray,

View File

@ -4,7 +4,7 @@
{{ text }}
</div>
<!-- <OtherConfigInfo :value="formData"></OtherConfigInfo> -->
<DataTableEnum v-if="formData.type === 'enum'" v-model:value="formData" />
<DataTableEnum v-if="formData.type === 'enum'" v-model:value="formData" />
<DataTableBoolean v-else-if="formData.type === 'boolean'" v-model:value="formData" />
<DataTableDouble
v-else-if="['float', 'double'].includes(formData.type)"

View File

@ -1,5 +1,5 @@
<template>
<DataTableTypeSelect v-model:value="type" :filter="['object', 'array']">
<DataTableTypeSelect v-model:value="type" :filter="['object']">
</DataTableTypeSelect>
</template>

View File

@ -22,11 +22,23 @@ import DataTypeObjectChild from '../DataTypeObjectChild.vue'
import {
DataTableObject,
} from 'jetlinks-ui-components';
import { DataType, OtherConfigInfo, ValueObject } from '../index'
import { ConstraintSelect, OtherConfigInfo, ValueObject } from '../index'
const columns = [
{ title: '参数标识', dataIndex: 'id', type: 'text' },
{ title: '参数名称', dataIndex: 'name', type: 'text' },
{
title: '填写约束',
dataIndex: 'required',
type: 'components',
width: 100,
components: {
name: ConstraintSelect,
props: {
name: ['expands', 'required']
}
}
},
{
title: '数据类型',
type: 'components',

View File

@ -14,6 +14,7 @@
:columns="[
{ title: '参数标识', dataIndex: 'id', type: 'text' },
{ title: '参数名称', dataIndex: 'name', type: 'text' },
{
title: '数据类型',
type: 'components',
@ -74,6 +75,7 @@ import {
DataTableDate,
DataTableObject,
} from 'jetlinks-ui-components';
import DataTypeObjectChild from '../DataTypeObjectChild.vue';
import { cloneDeep } from 'lodash-es';

View File

@ -124,6 +124,7 @@ const getConfig = async () => {
config.value = resp.result
if (resp.result.length && !configValue.value) {
activeKey.value = ['store_0']
resp.result.forEach(a => {
if (a.properties) {
a.properties.forEach(b => {
@ -131,6 +132,8 @@ const getConfig = async () => {
})
}
})
} else if (showMetrics.value) {
activeKey.value = ['metrics']
}
}
}
@ -167,7 +170,7 @@ const confirm = () => {
const visibleChange = (e: boolean) => {
if (e) {
configValue.value = omit(props.value?.expands, ['source', 'type', 'metrics'])
configValue.value = omit(props.value?.expands, ['source', 'type', 'metrics', 'required'])
getConfig()
}
}

View File

@ -8,3 +8,4 @@ export { default as ValueObject } from './Events/ValueObject.vue';
export { default as OtherConfigInfo } from './Events/OtherConfigInfo.vue';
export { default as ConfigParams } from './Events/ConfigParams.vue';
export { default as TagsType } from './Tags/Type.vue'
export { default as ConstraintSelect } from './Constraint.vue'

View File

@ -70,7 +70,7 @@
</template>
<script setup lang="ts" name="BaseMetadata">
import type { MetadataItem, MetadataType } from '@/views/device/Product/typings'
import MetadataMapping from './columns'
import { useColumns } from './columns';
import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product'
import { useMetadataStore } from '@/store/metadata'
@ -124,7 +124,8 @@ const pagination = {
defaultPageSize: 10,
size: 'small',
} as TablePaginationConfig
const columns = computed(() => MetadataMapping.get(type)!.concat(actions))
// const columns = computed(() => MetadataMapping.get(type)!.concat(actions))
const { columns } = useColumns(type)
const items = computed(() => JSON.parse((target === 'product' ? productStore.current?.metadata : instanceStore.current.metadata) || '{}'))
const searchValue = ref<string>()
const handleSearch = (searchValue: string) => {

View File

@ -3823,10 +3823,10 @@ jetlinks-store@^0.0.3:
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
jetlinks-ui-components@^1.0.24:
version "1.0.24"
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.24.tgz#003c3763daad1722543e6a8c8df0fc620a8122d1"
integrity sha512-GmtuxQKg6+6V/B/AmKhuDNfUhfdBMvoOg5Nm/q9XSYsMYpODzn+noqqYrNV0QLWFCGNccM2k1fojaAkirRT2yw==
jetlinks-ui-components@^1.0.23:
version "1.0.23"
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.23.tgz#6bef8fe635867a226afbfee3af6e870e46b0bf54"
integrity sha512-TtJJgtvW2aRvfD1BMTOulefPq3BUyfE02UARRF/XMo9GaPzPq7eqDonH4yrvadEd71cMPj2CYvt/A6FdWJu9yA==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"
@ -3834,6 +3834,19 @@ jetlinks-ui-components@^1.0.24:
colorpicker-v3 "^2.10.2"
lodash-es "^4.17.21"
monaco-editor "^0.35.0"
jetlinks-ui-components@^1.0.24:
version "1.0.24"
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.24.tgz#5c66d4533b3da4dcad0615ce3f186817a2d63450"
integrity sha512-QgdfZwGc1yCR01ujxMf6CIATZitGD+CoVupxN2TrVh+sGLO1tOpQuAYjJnSWwmMAFPPGTE5ChZcOqsBE2g64+w==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"
ant-design-vue "^3.2.15"
colorpicker-v3 "^2.10.2"
jetlinks-ui-components "^1.0.23"
lodash-es "^4.17.21"
monaco-editor "^0.35.0"
sortablejs "^1.15.0"
vuedraggable "^4.1.0"