Showing
10 changed files
with
257 additions
and
40 deletions
| ... | ... | @@ -4,7 +4,9 @@ import { FormSchema as QFormSchema, useComponentRegister } from '/@/components/F |
| 4 | 4 | import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules'; |
| 5 | 5 | import { h } from 'vue'; |
| 6 | 6 | import SnHelpMessage from './SnHelpMessage.vue'; |
| 7 | +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | |
| 7 | 8 | import { OrgTreeSelect } from '../../common/OrgTreeSelect'; |
| 9 | +import { findDictItemByCode } from '/@/api/system/dict'; | |
| 8 | 10 | |
| 9 | 11 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
| 10 | 12 | |
| ... | ... | @@ -42,6 +44,16 @@ export enum MediaType { |
| 42 | 44 | M3U8 = 'm3u8', |
| 43 | 45 | } |
| 44 | 46 | |
| 47 | +const streamingTypeList = [ | |
| 48 | + { label: 'GBT-28181', value: 'GBT-28181' }, | |
| 49 | + { label: '其他', value: 'other' }, | |
| 50 | +]; | |
| 51 | + | |
| 52 | +export enum streamingType { | |
| 53 | + GBT = 'GBT-28181', | |
| 54 | + OTHER = 'other', | |
| 55 | +} | |
| 56 | + | |
| 45 | 57 | // 表格列数据 |
| 46 | 58 | export const columns: BasicColumn[] = [ |
| 47 | 59 | { |
| ... | ... | @@ -148,6 +160,44 @@ export const formSchema: QFormSchema[] = [ |
| 148 | 160 | }, |
| 149 | 161 | }, |
| 150 | 162 | { |
| 163 | + field: 'streamingType', | |
| 164 | + label: '流媒体类型', | |
| 165 | + component: 'Select', | |
| 166 | + ifShow({ values }) { | |
| 167 | + return values.accessMode == AccessMode.Streaming; | |
| 168 | + }, | |
| 169 | + componentProps() { | |
| 170 | + return { | |
| 171 | + placeholder: '请选择流媒体类型', | |
| 172 | + defaultValue: streamingType.OTHER, | |
| 173 | + options: streamingTypeList, | |
| 174 | + }; | |
| 175 | + }, | |
| 176 | + }, | |
| 177 | + { | |
| 178 | + field: 'device', | |
| 179 | + label: '设备选择', | |
| 180 | + ifShow({ values }) { | |
| 181 | + return values.streamingType == streamingType.GBT; | |
| 182 | + }, | |
| 183 | + component: 'ApiSelect', | |
| 184 | + componentProps() { | |
| 185 | + return { | |
| 186 | + api: findDictItemByCode, | |
| 187 | + params: { | |
| 188 | + dictCode: 'device_type', | |
| 189 | + }, | |
| 190 | + valueField: 'itemValue', | |
| 191 | + labelField: 'itemText', | |
| 192 | + placeholder: '请选择设备类型', | |
| 193 | + onChange: (value: DeviceTypeEnum) => { | |
| 194 | + console.log(value, 'value'); | |
| 195 | + }, | |
| 196 | + getPopupContainer: () => document.body, | |
| 197 | + }; | |
| 198 | + }, | |
| 199 | + }, | |
| 200 | + { | |
| 151 | 201 | field: 'brand', |
| 152 | 202 | label: '视频厂家', |
| 153 | 203 | component: 'Input', |
| ... | ... | @@ -193,7 +243,9 @@ export const formSchema: QFormSchema[] = [ |
| 193 | 243 | label: '流媒体配置', |
| 194 | 244 | component: 'Select', |
| 195 | 245 | ifShow({ values }) { |
| 196 | - return values.accessMode === AccessMode.Streaming; | |
| 246 | + return ( | |
| 247 | + values.accessMode === AccessMode.Streaming && values.streamingType != streamingType.GBT | |
| 248 | + ); | |
| 197 | 249 | }, |
| 198 | 250 | slot: 'videoPlatformIdSlot', |
| 199 | 251 | componentProps: { |
| ... | ... | @@ -206,7 +258,9 @@ export const formSchema: QFormSchema[] = [ |
| 206 | 258 | component: 'RadioGroup', |
| 207 | 259 | defaultValue: StreamType.MASTER, |
| 208 | 260 | ifShow({ values }) { |
| 209 | - return values.accessMode === AccessMode.Streaming; | |
| 261 | + return ( | |
| 262 | + values.accessMode === AccessMode.Streaming && values.streamingType != streamingType.GBT | |
| 263 | + ); | |
| 210 | 264 | }, |
| 211 | 265 | componentProps: { |
| 212 | 266 | placeholder: '请选择码流', |
| ... | ... | @@ -224,7 +278,9 @@ export const formSchema: QFormSchema[] = [ |
| 224 | 278 | component: 'RadioGroup', |
| 225 | 279 | defaultValue: PlayProtocol.HTTP, |
| 226 | 280 | ifShow({ values }) { |
| 227 | - return values.accessMode === AccessMode.Streaming; | |
| 281 | + return ( | |
| 282 | + values.accessMode === AccessMode.Streaming && values.streamingType != streamingType.GBT | |
| 283 | + ); | |
| 228 | 284 | }, |
| 229 | 285 | helpMessage: ['平台使用https的hls协议,需联系海康开放平台专家支持。'], |
| 230 | 286 | componentProps: { |
| ... | ... | @@ -242,7 +298,9 @@ export const formSchema: QFormSchema[] = [ |
| 242 | 298 | component: 'Input', |
| 243 | 299 | rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }], |
| 244 | 300 | ifShow({ values }) { |
| 245 | - return values.accessMode === AccessMode.Streaming; | |
| 301 | + return ( | |
| 302 | + values.accessMode === AccessMode.Streaming && values.streamingType != streamingType.GBT | |
| 303 | + ); | |
| 246 | 304 | }, |
| 247 | 305 | componentProps: { |
| 248 | 306 | placeholder: '请输入监控点编号', | ... | ... |
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | @open-gateway-device="handleOpenGatewayDevice" |
| 17 | 17 | /> |
| 18 | 18 | </TabPane> |
| 19 | - <TabPane key="modelOfMatter" tab="物模型数据"> | |
| 19 | + <TabPane v-if="!isTransportType" key="modelOfMatter" tab="物模型数据"> | |
| 20 | 20 | <ModelOfMatter :deviceDetail="deviceDetail" /> |
| 21 | 21 | </TabPane> |
| 22 | 22 | <!-- <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> |
| ... | ... | @@ -28,32 +28,43 @@ |
| 28 | 28 | <!-- <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> |
| 29 | 29 | <CommandIssuance :deviceDetail="deviceDetail" /> |
| 30 | 30 | </TabPane> --> |
| 31 | - <TabPane key="3" tab="告警"><Alarm :id="deviceDetail.id" /></TabPane> | |
| 32 | - <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> | |
| 31 | + <TabPane v-if="!isTransportType" key="3" tab="告警"><Alarm :id="deviceDetail.id" /></TabPane> | |
| 32 | + <TabPane | |
| 33 | + key="4" | |
| 34 | + tab="子设备" | |
| 35 | + v-if="deviceDetail?.deviceType === 'GATEWAY' && !isTransportType" | |
| 36 | + > | |
| 33 | 37 | <ChildDevice |
| 34 | 38 | :fromId="deviceDetail?.tbDeviceId" |
| 35 | 39 | @openTbDeviceDetail="handleOpenTbDeviceDetail" |
| 36 | 40 | /> |
| 37 | 41 | </TabPane> |
| 38 | - <TabPane key="7" tab="命令下发记录"> | |
| 42 | + <TabPane v-if="!isTransportType" key="7" tab="命令下发记录"> | |
| 39 | 43 | <CommandRecord :deviceDetail="deviceDetail" :fromId="deviceDetail?.tbDeviceId" /> |
| 40 | 44 | </TabPane> |
| 41 | 45 | <!-- 网关设备并且场家是TBox --> |
| 42 | 46 | <TabPane |
| 43 | 47 | key="6" |
| 44 | 48 | tab="TBox" |
| 45 | - v-if="deviceDetail?.deviceType === 'GATEWAY' && deviceDetail?.brand == 'TBox'" | |
| 49 | + v-if=" | |
| 50 | + deviceDetail?.deviceType === 'GATEWAY' && | |
| 51 | + deviceDetail?.brand == 'TBox' && | |
| 52 | + !isTransportType | |
| 53 | + " | |
| 46 | 54 | > |
| 47 | 55 | <TBoxDetail :deviceDetail="deviceDetail" /> |
| 48 | 56 | </TabPane> |
| 49 | 57 | <!-- 网关设备并且是TBox --> |
| 50 | 58 | |
| 51 | - <TabPane key="eventManage" tab="事件管理"> | |
| 59 | + <TabPane v-if="!isTransportType" key="eventManage" tab="事件管理"> | |
| 52 | 60 | <EventManage :tbDeviceId="deviceDetail.tbDeviceId" /> |
| 53 | 61 | </TabPane> |
| 54 | - <TabPane key="task" tab="任务"> | |
| 62 | + <TabPane v-if="!isTransportType" key="task" tab="任务"> | |
| 55 | 63 | <Task :tbDeviceId="deviceDetail.tbDeviceId" /> |
| 56 | 64 | </TabPane> |
| 65 | + <TabPane key="videoChannel" tab="视频通道"> | |
| 66 | + <VideoChannel :deviceDetail="deviceDetail" :fromId="deviceDetail?.tbDeviceId" /> | |
| 67 | + </TabPane> | |
| 57 | 68 | </Tabs> |
| 58 | 69 | </BasicDrawer> |
| 59 | 70 | </template> |
| ... | ... | @@ -74,6 +85,7 @@ |
| 74 | 85 | import EventManage from '../tabs/EventManage/index.vue'; |
| 75 | 86 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
| 76 | 87 | import Task from '../tabs/Task.vue'; |
| 88 | + import { VideoChannel } from '../tabs/VideoChannel/index'; | |
| 77 | 89 | |
| 78 | 90 | export default defineComponent({ |
| 79 | 91 | name: 'DeviceModal', |
| ... | ... | @@ -91,6 +103,7 @@ |
| 91 | 103 | CommandRecord, |
| 92 | 104 | EventManage, |
| 93 | 105 | Task, |
| 106 | + VideoChannel, | |
| 94 | 107 | }, |
| 95 | 108 | emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'], |
| 96 | 109 | setup(_props, { emit }) { |
| ... | ... | @@ -99,9 +112,12 @@ |
| 99 | 112 | const deviceDetailRef = ref(); |
| 100 | 113 | const deviceDetail = ref<DeviceRecord>({} as unknown as DeviceRecord); |
| 101 | 114 | const tbDeviceId = ref(''); |
| 115 | + | |
| 116 | + const isTransportType = ref<Boolean>(false); //获取产品是不是GB/T 28181 | |
| 102 | 117 | // 详情回显 |
| 103 | 118 | const [register] = useDrawerInner(async (data) => { |
| 104 | - const { id } = data; | |
| 119 | + const { id, transportType } = data; | |
| 120 | + isTransportType.value = transportType == 'GB/T28181' ? true : false; | |
| 105 | 121 | // 设备详情 |
| 106 | 122 | const res = await getDeviceDetail(id); |
| 107 | 123 | deviceDetail.value = res; |
| ... | ... | @@ -132,6 +148,7 @@ |
| 132 | 148 | tbDeviceId, |
| 133 | 149 | handleOpenTbDeviceDetail, |
| 134 | 150 | handleOpenGatewayDevice, |
| 151 | + isTransportType, | |
| 135 | 152 | }; |
| 136 | 153 | }, |
| 137 | 154 | }); | ... | ... |
| 1 | +import { h } from 'vue'; | |
| 2 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | |
| 3 | +import { Tag } from 'ant-design-vue'; | |
| 4 | + | |
| 5 | +export const configColumns: BasicColumn[] = [ | |
| 6 | + { | |
| 7 | + title: '通道编号', | |
| 8 | + dataIndex: 'channellNumber', | |
| 9 | + }, | |
| 10 | + { | |
| 11 | + title: '设备名称', | |
| 12 | + dataIndex: 'deviceName', | |
| 13 | + }, | |
| 14 | + { | |
| 15 | + title: '通道名称', | |
| 16 | + dataIndex: 'channelName', | |
| 17 | + }, | |
| 18 | + { | |
| 19 | + title: '厂家', | |
| 20 | + dataIndex: 'manufacturer', | |
| 21 | + }, | |
| 22 | + { | |
| 23 | + title: '开启音频', | |
| 24 | + dataIndex: 'turnOnAudio', | |
| 25 | + slots: { customRender: 'turnOnAudio' }, | |
| 26 | + }, | |
| 27 | + { | |
| 28 | + title: '状态', | |
| 29 | + dataIndex: 'state', | |
| 30 | + format: (text) => { | |
| 31 | + return h( | |
| 32 | + Tag, | |
| 33 | + { | |
| 34 | + color: Number(text) === 1 ? 'green' : 'blue', | |
| 35 | + }, | |
| 36 | + () => (Number(text) === 1 ? '在线' : '离线') | |
| 37 | + ); | |
| 38 | + }, | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + title: '操作', | |
| 42 | + dataIndex: 'action', | |
| 43 | + }, | |
| 44 | +]; | |
| 45 | + | |
| 46 | +export const searchFormSchema: FormSchema[] | any = [{}]; | ... | ... |
| 1 | +<template> | |
| 2 | + <BasicTable | |
| 3 | + class="bg-neutral-100 dark:text-gray-300 dark:bg-dark-700 p-4" | |
| 4 | + @register="registerTable" | |
| 5 | + > | |
| 6 | + <template #turnOnAudio="{ record }"> | |
| 7 | + <Switch | |
| 8 | + :checked="record.status === 1" | |
| 9 | + :loading="record.pendingStatus" | |
| 10 | + checkedChildren="开启" | |
| 11 | + unCheckedChildren="关闭" | |
| 12 | + @change="(checked:boolean)=>handleTurnVideo(checked,record)" | |
| 13 | + /> | |
| 14 | + </template> | |
| 15 | + <template #action="{ record }"> | |
| 16 | + <TableAction | |
| 17 | + :actions="[ | |
| 18 | + { | |
| 19 | + label: '播放', | |
| 20 | + auth: 'api:yt:sceneLinkage:get', | |
| 21 | + icon: 'ant-design:playCircle-outlined', | |
| 22 | + onClick: handlePlay.bind(null, record), | |
| 23 | + }, | |
| 24 | + ]" | |
| 25 | + /></template> | |
| 26 | + </BasicTable> | |
| 27 | +</template> | |
| 28 | + | |
| 29 | +<script lang="ts" setup> | |
| 30 | + import { configColumns, searchFormSchema } from './config'; | |
| 31 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
| 32 | + import { Switch } from 'ant-design-vue'; | |
| 33 | + import { DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 34 | + import { watch } from 'vue'; | |
| 35 | + | |
| 36 | + const props = defineProps({ | |
| 37 | + fromId: { | |
| 38 | + type: String, | |
| 39 | + default: '', | |
| 40 | + }, | |
| 41 | + deviceDetail: { | |
| 42 | + type: Object as PropType<DeviceRecord>, | |
| 43 | + required: true, | |
| 44 | + }, | |
| 45 | + }); | |
| 46 | + | |
| 47 | + watch( | |
| 48 | + () => props, | |
| 49 | + () => { | |
| 50 | + console.log(props, 'props'); | |
| 51 | + } | |
| 52 | + ); | |
| 53 | + | |
| 54 | + const [registerTable] = useTable({ | |
| 55 | + // api: deviceCommandRecordGetQuery, | |
| 56 | + columns: configColumns, | |
| 57 | + showTableSetting: true, | |
| 58 | + bordered: true, | |
| 59 | + showIndexColumn: false, | |
| 60 | + formConfig: { | |
| 61 | + labelWidth: 120, | |
| 62 | + schemas: searchFormSchema, | |
| 63 | + }, | |
| 64 | + beforeFetch: (params) => { | |
| 65 | + console.log(params); | |
| 66 | + }, | |
| 67 | + useSearchForm: true, | |
| 68 | + }); | |
| 69 | + | |
| 70 | + const handleTurnVideo = (checked: Boolean, record: Recordable) => { | |
| 71 | + console.log(checked, record, 'record'); | |
| 72 | + }; | |
| 73 | + | |
| 74 | + const handlePlay = (record: Recordable) => { | |
| 75 | + console.log(record); | |
| 76 | + }; | |
| 77 | +</script> | ... | ... |
| ... | ... | @@ -336,10 +336,12 @@ |
| 336 | 336 | } |
| 337 | 337 | |
| 338 | 338 | function handleDetail(record: Recordable) { |
| 339 | - const { id, tbDeviceId } = record; | |
| 339 | + const { id, tbDeviceId, deviceProfile } = record; | |
| 340 | + const { transportType } = deviceProfile || {}; | |
| 340 | 341 | openDrawer(true, { |
| 341 | 342 | id, |
| 342 | 343 | tbDeviceId, |
| 344 | + transportType, | |
| 343 | 345 | }); |
| 344 | 346 | } |
| 345 | 347 | ... | ... |
| ... | ... | @@ -127,11 +127,12 @@ |
| 127 | 127 | } |
| 128 | 128 | }; |
| 129 | 129 | const handleStepNext = (e, data) => { |
| 130 | + const { deviceType } = unref(DevConStRef)?.getFieldsValue() || {}; | |
| 130 | 131 | if (e) { |
| 131 | 132 | current.value++; |
| 132 | 133 | unref(isUpdate) |
| 133 | - ? unref(TransConStRef)?.editOrAddTransportTypeStatus(true) | |
| 134 | - : unref(TransConStRef)?.editOrAddTransportTypeStatus(false); | |
| 134 | + ? unref(TransConStRef)?.editOrAddTransportTypeStatus(true, deviceType) | |
| 135 | + : unref(TransConStRef)?.editOrAddTransportTypeStatus(false, deviceType); | |
| 135 | 136 | } else { |
| 136 | 137 | setTransConfEditFormData(data); |
| 137 | 138 | } | ... | ... |
| ... | ... | @@ -45,19 +45,20 @@ |
| 45 | 45 | ifShowBtn: { type: Boolean, default: true }, |
| 46 | 46 | }); |
| 47 | 47 | |
| 48 | - const [register, { validate, setFieldsValue, resetFields, updateSchema }] = useForm({ | |
| 49 | - labelWidth: 100, | |
| 50 | - schemas: step1Schemas, | |
| 51 | - actionColOptions: { | |
| 52 | - span: 14, | |
| 53 | - }, | |
| 54 | - showResetButton: false, | |
| 55 | - showActionButtonGroup: props.ifShowBtn ? true : false, | |
| 56 | - submitButtonOptions: { | |
| 57 | - text: '下一步', | |
| 58 | - }, | |
| 59 | - submitFunc: customSubmitFunc, | |
| 60 | - }); | |
| 48 | + const [register, { validate, setFieldsValue, resetFields, updateSchema, getFieldsValue }] = | |
| 49 | + useForm({ | |
| 50 | + labelWidth: 100, | |
| 51 | + schemas: step1Schemas, | |
| 52 | + actionColOptions: { | |
| 53 | + span: 14, | |
| 54 | + }, | |
| 55 | + showResetButton: false, | |
| 56 | + showActionButtonGroup: props.ifShowBtn ? true : false, | |
| 57 | + submitButtonOptions: { | |
| 58 | + text: '下一步', | |
| 59 | + }, | |
| 60 | + submitFunc: customSubmitFunc, | |
| 61 | + }); | |
| 61 | 62 | const editOrAddNameStatus = (nameStatus) => |
| 62 | 63 | updateSchema({ |
| 63 | 64 | field: 'name', |
| ... | ... | @@ -129,6 +130,7 @@ |
| 129 | 130 | resetFormData, |
| 130 | 131 | getFormData, |
| 131 | 132 | editOrAddDeviceTypeStatus, |
| 133 | + getFieldsValue, | |
| 132 | 134 | }); |
| 133 | 135 | </script> |
| 134 | 136 | <style lang="less" scoped> | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <div |
| 3 | 3 | class="step2-style" |
| 4 | - :style="[isMqttType == 'DEFAULT' ? { minHeight: 0 + 'px' } : { minHeight: 800 + 'px' }]" | |
| 4 | + :style="[ | |
| 5 | + isMqttType == 'DEFAULT' || isMqttType == 'GB/T28181' | |
| 6 | + ? { minHeight: 0 + 'px' } | |
| 7 | + : { minHeight: 800 + 'px' }, | |
| 8 | + ]" | |
| 5 | 9 | > |
| 6 | 10 | <div |
| 7 | 11 | :style="[ |
| ... | ... | @@ -138,6 +142,7 @@ |
| 138 | 142 | const getSnmpVal = await snmpRef.value?.getFormData(); |
| 139 | 143 | const getTcpVal = await tcpRef.value?.getFormData(); |
| 140 | 144 | step2Data.transportConfiguration = { |
| 145 | + type: isMqttType.value, | |
| 141 | 146 | ...getMqttVal, |
| 142 | 147 | ...getCoapVal, |
| 143 | 148 | ...getLwm2mVal, |
| ... | ... | @@ -148,19 +153,26 @@ |
| 148 | 153 | return step2Data; |
| 149 | 154 | }; |
| 150 | 155 | |
| 151 | - const editOrAddTransportTypeStatus = (status: boolean) => { | |
| 156 | + const editOrAddTransportTypeStatus = (status: boolean, deviceType?: string) => { | |
| 157 | + const options = [ | |
| 158 | + { label: '默认', value: 'DEFAULT' }, | |
| 159 | + { label: 'MQTT', value: 'MQTT' }, | |
| 160 | + { label: 'CoAP', value: 'COAP' }, | |
| 161 | + // { label: 'LWM2M', value: 'LWM2M' }, | |
| 162 | + // { label: 'SNMP', value: 'SNMP' }, | |
| 163 | + { label: 'TCP/UDP', value: 'TCP' }, | |
| 164 | + ]; | |
| 165 | + if (deviceType == 'DIRECT_CONNECTION') { | |
| 166 | + options.push({ label: 'GB/T 28181', value: 'GB/T28181' }); | |
| 167 | + } | |
| 168 | + if (deviceType != 'DIRECT_CONNECTION' && isMqttType.value == 'GB/T28181') { | |
| 169 | + setFieldsValue({ transportType: null }); | |
| 170 | + } | |
| 152 | 171 | updateSchema({ |
| 153 | 172 | field: 'transportType', |
| 154 | 173 | componentProps: { |
| 155 | 174 | disabled: status, |
| 156 | - options: [ | |
| 157 | - { label: '默认', value: 'DEFAULT' }, | |
| 158 | - { label: 'MQTT', value: 'MQTT' }, | |
| 159 | - { label: 'CoAP', value: 'COAP' }, | |
| 160 | - // { label: 'LWM2M', value: 'LWM2M' }, | |
| 161 | - // { label: 'SNMP', value: 'SNMP' }, | |
| 162 | - { label: 'TCP/UDP', value: 'TCP' }, | |
| 163 | - ], | |
| 175 | + options, | |
| 164 | 176 | onChange(e) { |
| 165 | 177 | isMqttType.value = e; |
| 166 | 178 | }, | ... | ... |
| ... | ... | @@ -78,6 +78,6 @@ export const getSendValues = async (option, getDesign, checked) => { |
| 78 | 78 | |
| 79 | 79 | export const CommandTypeEnumLIst = { |
| 80 | 80 | '0': { CUSTOM: 0, name: '自定义' }, |
| 81 | - '1': { CUSTOM: 0, name: '服务' }, | |
| 82 | - '2': { CUSTOM: 0, name: '属性' }, | |
| 81 | + '1': { CUSTOM: 1, name: '服务' }, | |
| 82 | + '2': { CUSTOM: 2, name: '属性' }, | |
| 83 | 83 | }; | ... | ... |