Commit 620c62bbd99d33af726272e89194d335bf56db66
Merge branch 'f-dev' into 'main'
feat:设备列表新增TBox远程连接 和修改Teambition上的问题和修改设备配置相关 See merge request huang/yun-teng-iot-front!264
Showing
32 changed files
with
1549 additions
and
146 deletions
| ... | ... | @@ -17,6 +17,7 @@ enum EDeviceConfigApi { |
| 17 | 17 | DEVICE_CONFIG_EXPORT = '/deviceProfile/export', |
| 18 | 18 | DEVICE_CONFIG_IMPORT = '/deviceProfile/import', |
| 19 | 19 | SET_DEVICE_ISDEFAULT = '/deviceProfile', |
| 20 | + FRP_API = '/frp/', | |
| 20 | 21 | } |
| 21 | 22 | |
| 22 | 23 | /** |
| ... | ... | @@ -114,3 +115,18 @@ export const setDeviceProfileIsDefaultApi = (id: string, v, params?: {}) => { |
| 114 | 115 | } |
| 115 | 116 | ); |
| 116 | 117 | }; |
| 118 | + | |
| 119 | +/** | |
| 120 | + * Frp内网穿透信息API | |
| 121 | + */ | |
| 122 | +export const frpGetInfoApi = (proxyName: string) => { | |
| 123 | + return defHttp.get({ | |
| 124 | + url: `${EDeviceConfigApi.FRP_API}${proxyName}`, | |
| 125 | + }); | |
| 126 | +}; | |
| 127 | + | |
| 128 | +export const frpPutInfoApi = (proxyName: string, enableRemote: number) => { | |
| 129 | + return defHttp.put({ | |
| 130 | + url: `${EDeviceConfigApi.FRP_API}${proxyName}/${enableRemote}`, | |
| 131 | + }); | |
| 132 | +}; | ... | ... |
| ... | ... | @@ -9,5 +9,7 @@ export { useForm } from './src/hooks/useForm'; |
| 9 | 9 | export { default as ApiSelect } from './src/components/ApiSelect.vue'; |
| 10 | 10 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; |
| 11 | 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; |
| 12 | +export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | |
| 13 | + | |
| 12 | 14 | |
| 13 | 15 | export { BasicForm }; | ... | ... |
| ... | ... | @@ -27,6 +27,7 @@ import { BasicUpload } from '/@/components/Upload'; |
| 27 | 27 | import { StrengthMeter } from '/@/components/StrengthMeter'; |
| 28 | 28 | import { IconPicker } from '/@/components/Icon'; |
| 29 | 29 | import { CountdownInput } from '/@/components/CountDown'; |
| 30 | +import ApiRadioGroup from './components/ApiRadioGroup.vue'; | |
| 30 | 31 | //自定义组件 |
| 31 | 32 | import JAddInput from './jeecg/components/JAddInput.vue'; |
| 32 | 33 | |
| ... | ... | @@ -39,6 +40,7 @@ componentMap.set('InputSearch', Input.Search); |
| 39 | 40 | componentMap.set('InputTextArea', Input.TextArea); |
| 40 | 41 | componentMap.set('InputNumber', InputNumber); |
| 41 | 42 | componentMap.set('AutoComplete', AutoComplete); |
| 43 | +componentMap.set('ApiRadioGroup', ApiRadioGroup); | |
| 42 | 44 | |
| 43 | 45 | componentMap.set('Select', Select); |
| 44 | 46 | componentMap.set('ApiSelect', ApiSelect); | ... | ... |
| 1 | +<!-- | |
| 2 | + * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component | |
| 3 | +--> | |
| 4 | +<template> | |
| 5 | + <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange"> | |
| 6 | + <template v-for="item in getOptions" :key="`${item.value}`"> | |
| 7 | + <RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled"> | |
| 8 | + {{ item.label }} | |
| 9 | + </RadioButton> | |
| 10 | + <Radio v-else :value="item.value" :disabled="item.disabled"> | |
| 11 | + {{ item.label }} | |
| 12 | + </Radio> | |
| 13 | + </template> | |
| 14 | + </RadioGroup> | |
| 15 | +</template> | |
| 16 | +<script lang="ts"> | |
| 17 | + import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue'; | |
| 18 | + import { Radio } from 'ant-design-vue'; | |
| 19 | + import { isFunction } from '/@/utils/is'; | |
| 20 | + import { useRuleFormItem } from '/@/hooks/component/useFormItem'; | |
| 21 | + import { useAttrs } from '/@/hooks/core/useAttrs'; | |
| 22 | + import { propTypes } from '/@/utils/propTypes'; | |
| 23 | + import { get, omit } from 'lodash-es'; | |
| 24 | + import { useI18n } from '/@/hooks/web/useI18n'; | |
| 25 | + type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean }; | |
| 26 | + | |
| 27 | + export default defineComponent({ | |
| 28 | + name: 'ApiRadioGroup', | |
| 29 | + components: { | |
| 30 | + RadioGroup: Radio.Group, | |
| 31 | + RadioButton: Radio.Button, | |
| 32 | + Radio, | |
| 33 | + }, | |
| 34 | + props: { | |
| 35 | + api: { | |
| 36 | + type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>, | |
| 37 | + default: null, | |
| 38 | + }, | |
| 39 | + params: { | |
| 40 | + type: [Object, String] as PropType<Recordable | string>, | |
| 41 | + default: () => ({}), | |
| 42 | + }, | |
| 43 | + value: { | |
| 44 | + type: [String, Number, Boolean] as PropType<string | number | boolean>, | |
| 45 | + }, | |
| 46 | + isBtn: { | |
| 47 | + type: [Boolean] as PropType<boolean>, | |
| 48 | + default: false, | |
| 49 | + }, | |
| 50 | + numberToString: propTypes.bool, | |
| 51 | + resultField: propTypes.string.def(''), | |
| 52 | + labelField: propTypes.string.def('label'), | |
| 53 | + valueField: propTypes.string.def('value'), | |
| 54 | + immediate: propTypes.bool.def(true), | |
| 55 | + }, | |
| 56 | + emits: ['options-change', 'change'], | |
| 57 | + setup(props, { emit }) { | |
| 58 | + const options = ref<OptionsItem[]>([]); | |
| 59 | + const loading = ref(false); | |
| 60 | + const isFirstLoad = ref(true); | |
| 61 | + const emitData = ref<any[]>([]); | |
| 62 | + const attrs = useAttrs(); | |
| 63 | + const { t } = useI18n(); | |
| 64 | + // Embedded in the form, just use the hook binding to perform form verification | |
| 65 | + const [state] = useRuleFormItem(props); | |
| 66 | + | |
| 67 | + // Processing options value | |
| 68 | + const getOptions = computed(() => { | |
| 69 | + const { labelField, valueField, numberToString } = props; | |
| 70 | + | |
| 71 | + return unref(options).reduce((prev, next: Recordable) => { | |
| 72 | + if (next) { | |
| 73 | + const value = next[valueField]; | |
| 74 | + prev.push({ | |
| 75 | + label: next[labelField], | |
| 76 | + value: numberToString ? `${value}` : value, | |
| 77 | + ...omit(next, [labelField, valueField]), | |
| 78 | + }); | |
| 79 | + } | |
| 80 | + return prev; | |
| 81 | + }, [] as OptionsItem[]); | |
| 82 | + }); | |
| 83 | + | |
| 84 | + watchEffect(() => { | |
| 85 | + props.immediate && fetch(); | |
| 86 | + }); | |
| 87 | + | |
| 88 | + watch( | |
| 89 | + () => props.params, | |
| 90 | + () => { | |
| 91 | + !unref(isFirstLoad) && fetch(); | |
| 92 | + }, | |
| 93 | + { deep: true }, | |
| 94 | + ); | |
| 95 | + | |
| 96 | + async function fetch() { | |
| 97 | + const api = props.api; | |
| 98 | + if (!api || !isFunction(api)) return; | |
| 99 | + options.value = []; | |
| 100 | + try { | |
| 101 | + loading.value = true; | |
| 102 | + const res = await api(props.params); | |
| 103 | + if (Array.isArray(res)) { | |
| 104 | + options.value = res; | |
| 105 | + emitChange(); | |
| 106 | + return; | |
| 107 | + } | |
| 108 | + if (props.resultField) { | |
| 109 | + options.value = get(res, props.resultField) || []; | |
| 110 | + } | |
| 111 | + emitChange(); | |
| 112 | + } catch (error) { | |
| 113 | + console.warn(error); | |
| 114 | + } finally { | |
| 115 | + loading.value = false; | |
| 116 | + } | |
| 117 | + } | |
| 118 | + | |
| 119 | + function emitChange() { | |
| 120 | + emit('options-change', unref(getOptions)); | |
| 121 | + } | |
| 122 | + | |
| 123 | + function handleChange(_, ...args) { | |
| 124 | + emitData.value = args; | |
| 125 | + } | |
| 126 | + | |
| 127 | + return { state, getOptions, attrs, loading, t, handleChange, props }; | |
| 128 | + }, | |
| 129 | + }); | |
| 130 | +</script> | ... | ... |
| ... | ... | @@ -77,7 +77,7 @@ |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | 79 | let resStr = ''; |
| 80 | - let dirStr = isBefore ? t('component.time.before') : t('component.time.after'); | |
| 80 | + let dirStr = isBefore ? t('component.time.after') : t('component.time.before'); | |
| 81 | 81 | |
| 82 | 82 | if (diff < ONE_SECONDS) { |
| 83 | 83 | resStr = t('component.time.just'); | ... | ... |
| ... | ... | @@ -477,14 +477,14 @@ |
| 477 | 477 | cursor: pointer; |
| 478 | 478 | position: absolute; |
| 479 | 479 | top: 0.85rem; |
| 480 | - left: 1.1rem; | |
| 480 | + left: 1.1vw; | |
| 481 | 481 | } |
| 482 | 482 | .fold-right { |
| 483 | 483 | z-index: 1; |
| 484 | 484 | cursor: pointer; |
| 485 | 485 | position: absolute; |
| 486 | 486 | top: 0.85rem; |
| 487 | - left: 21.6rem; | |
| 487 | + left: 18.2vw; | |
| 488 | 488 | } |
| 489 | 489 | @prefix-cls: ~'@{namespace}-basic-tree'; |
| 490 | 490 | ... | ... |
| ... | ... | @@ -9,9 +9,7 @@ |
| 9 | 9 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 10 | 10 | import { PlayProtocol } from '../manage/config.data'; |
| 11 | 11 | |
| 12 | - const emit = defineEmits({ | |
| 13 | - success: null, | |
| 14 | - }); | |
| 12 | + const emit = defineEmits(['success','register']); | |
| 15 | 13 | |
| 16 | 14 | const createFlag = ref(false); |
| 17 | 15 | ... | ... |
| ... | ... | @@ -15,6 +15,7 @@ |
| 15 | 15 | import { ref } from 'vue'; |
| 16 | 16 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 17 | 17 | import { Popconfirm } from 'ant-design-vue'; |
| 18 | + import { Authority } from '/@/components/Authority'; | |
| 18 | 19 | |
| 19 | 20 | const enabledBatchDelete = ref(true); |
| 20 | 21 | const [register, { reload, getSelectRowKeys }] = useTable({ | ... | ... |
| ... | ... | @@ -51,23 +51,21 @@ export const step1Schemas: FormSchema[] = [ |
| 51 | 51 | }, |
| 52 | 52 | }, |
| 53 | 53 | { |
| 54 | - field: 'field7', | |
| 55 | - component: 'RadioGroup', | |
| 54 | + field: 'brand', | |
| 55 | + component: 'ApiRadioGroup', | |
| 56 | 56 | label: '选择厂家', |
| 57 | + required: true, | |
| 57 | 58 | colProps: { |
| 58 | 59 | span: 8, |
| 59 | 60 | }, |
| 61 | + defaultValue: 'DIY_', | |
| 60 | 62 | componentProps: { |
| 61 | - options: [ | |
| 62 | - { | |
| 63 | - label: '自定义厂家', | |
| 64 | - value: '1', | |
| 65 | - }, | |
| 66 | - { | |
| 67 | - label: 'TBox边缘网关', | |
| 68 | - value: '2', | |
| 69 | - }, | |
| 70 | - ], | |
| 63 | + api: findDictItemByCode, | |
| 64 | + params: { | |
| 65 | + dictCode: 'DEVICE_BRAND_GATEWAY', | |
| 66 | + }, | |
| 67 | + labelField: 'itemText', | |
| 68 | + valueField: 'itemValue', | |
| 71 | 69 | }, |
| 72 | 70 | ifShow: ({ values }) => isGateWay(values.deviceType), |
| 73 | 71 | }, | ... | ... |
| ... | ... | @@ -22,6 +22,15 @@ |
| 22 | 22 | <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> |
| 23 | 23 | <ChildDevice :fromId="deviceDetail?.tbDeviceId" /> |
| 24 | 24 | </TabPane> |
| 25 | + <!-- 网关设备并且场家是TBox --> | |
| 26 | + <TabPane | |
| 27 | + key="6" | |
| 28 | + tab="TBox" | |
| 29 | + v-if="deviceDetail?.deviceType === 'GATEWAY' && deviceDetail?.brand == 'TBox'" | |
| 30 | + > | |
| 31 | + <TBoxDetail :deviceDetail="deviceDetail" /> | |
| 32 | + </TabPane> | |
| 33 | + <!-- 网关设备并且是TBox --> | |
| 25 | 34 | </Tabs> |
| 26 | 35 | </BasicDrawer> |
| 27 | 36 | </template> |
| ... | ... | @@ -34,6 +43,7 @@ |
| 34 | 43 | import RealTimeData from '../tabs/RealTimeData.vue'; |
| 35 | 44 | import Alarm from '../tabs/Alarm.vue'; |
| 36 | 45 | import ChildDevice from '../tabs/ChildDevice.vue'; |
| 46 | + import TBoxDetail from '../tabs/TBoxDetail.vue'; | |
| 37 | 47 | import CommandIssuance from '../tabs/CommandIssuance.vue'; |
| 38 | 48 | import { getDeviceDetail } from '/@/api/device/deviceManager'; |
| 39 | 49 | export default defineComponent({ |
| ... | ... | @@ -47,6 +57,7 @@ |
| 47 | 57 | Alarm, |
| 48 | 58 | ChildDevice, |
| 49 | 59 | CommandIssuance, |
| 60 | + TBoxDetail, | |
| 50 | 61 | }, |
| 51 | 62 | emits: ['reload', 'register'], |
| 52 | 63 | setup() { | ... | ... |
| ... | ... | @@ -53,12 +53,6 @@ |
| 53 | 53 | <a-button type="primary" class="mr-4" @click="copyTbDeviceId">复制设备ID</a-button> |
| 54 | 54 | <a-button type="primary" class="mr-4" @click="copyDeviceToken">复制访问令牌</a-button> |
| 55 | 55 | <a-button type="primary" class="mr-4" @click="manageDeviceToken">管理设备凭证</a-button> |
| 56 | - <a-button | |
| 57 | - type="primary" | |
| 58 | - v-if="deviceDetail.deviceType == 'GATEWAY'" | |
| 59 | - @click="remoteConnectiondGateway" | |
| 60 | - >远程连接边缘网关</a-button | |
| 61 | - > | |
| 62 | 56 | <ManageDeviceTokenModal @register="registerModal" /> |
| 63 | 57 | </div> |
| 64 | 58 | <div v-if="deviceDetail?.deviceInfo?.address" class="mt-4"> | ... | ... |
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <BasicTable @register="registerTable" :dataSource="tableData"> | |
| 4 | + <template #status> | |
| 5 | + <Switch | |
| 6 | + checked-children="开" | |
| 7 | + un-checked-children="关" | |
| 8 | + v-model:checked="checked" | |
| 9 | + @change="handleChange" | |
| 10 | + :disabled="enableRemoteDisabled" | |
| 11 | + /> | |
| 12 | + </template> | |
| 13 | + </BasicTable> | |
| 14 | + <div style="margin-top: -54vh"> | |
| 15 | + <a-button type="primary" class="mr-4" :disabled="disabled" @click="handleFrpRemote" | |
| 16 | + >远程连接</a-button | |
| 17 | + > | |
| 18 | + </div> | |
| 19 | + </div> | |
| 20 | +</template> | |
| 21 | + | |
| 22 | +<script setup lang="ts"> | |
| 23 | + import { ref, nextTick } from 'vue'; | |
| 24 | + import { BasicTable, useTable, BasicColumn } from '/@/components/Table'; | |
| 25 | + import { Switch } from 'ant-design-vue'; | |
| 26 | + import { h } from 'vue'; | |
| 27 | + import { Tag } from 'ant-design-vue'; | |
| 28 | + import { frpGetInfoApi, frpPutInfoApi } from '/@/api/device/deviceConfigApi'; | |
| 29 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
| 30 | + import { ConsoleSqlOutlined } from '@ant-design/icons-vue'; | |
| 31 | + | |
| 32 | + const props = defineProps({ | |
| 33 | + deviceDetail: { | |
| 34 | + type: Object, | |
| 35 | + required: true, | |
| 36 | + }, | |
| 37 | + }); | |
| 38 | + | |
| 39 | + const { createMessage } = useMessage(); | |
| 40 | + const tableData: any = ref([]); | |
| 41 | + const checked = ref<boolean>(false); | |
| 42 | + const disabled = ref<boolean>(true); | |
| 43 | + const viewDeviceColumn: BasicColumn[] = [ | |
| 44 | + { | |
| 45 | + title: 'Frp连接状态', | |
| 46 | + dataIndex: 'status', | |
| 47 | + width: 80, | |
| 48 | + customRender: ({ record }) => { | |
| 49 | + const status = record.status; | |
| 50 | + const color = status == 1 ? 'green' : 'red'; | |
| 51 | + const text = status == 1 ? '在线' : '离线'; | |
| 52 | + return h(Tag, { color }, () => text); | |
| 53 | + }, | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + title: '远程连接', | |
| 57 | + dataIndex: 'enableRemote', | |
| 58 | + width: 80, | |
| 59 | + slots: { customRender: 'status' }, | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + title: '设备SN', | |
| 63 | + dataIndex: 'proxyName', | |
| 64 | + width: 120, | |
| 65 | + }, | |
| 66 | + ]; | |
| 67 | + const [registerTable] = useTable({ | |
| 68 | + title: 'TBox信息', | |
| 69 | + columns: viewDeviceColumn, | |
| 70 | + showIndexColumn: false, | |
| 71 | + clickToRowSelect: false, | |
| 72 | + showTableSetting: false, | |
| 73 | + bordered: true, | |
| 74 | + }); | |
| 75 | + const enableRemote = ref(0); | |
| 76 | + const proxyName = ref(''); | |
| 77 | + const remotePort = ref(0); | |
| 78 | + const address = ref(''); | |
| 79 | + const enableRemoteDisabled = ref(false); | |
| 80 | + const getTableData = async () => { | |
| 81 | + // const res = await frpGetInfoApi('1000000061664FF'); | |
| 82 | + const res = await frpGetInfoApi(props.deviceDetail.sn); | |
| 83 | + enableRemote.value = res.enableRemote; | |
| 84 | + proxyName.value = res.proxyName; | |
| 85 | + remotePort.value = res.remotePort; | |
| 86 | + address.value = res.address; | |
| 87 | + if (res.enableRemote == 1) { | |
| 88 | + checked.value = true; | |
| 89 | + disabled.value = false; | |
| 90 | + enableRemoteDisabled.value = true; | |
| 91 | + } else { | |
| 92 | + checked.value = false; | |
| 93 | + disabled.value = true; | |
| 94 | + enableRemoteDisabled.value = false; | |
| 95 | + } | |
| 96 | + if (res.status == 0) { | |
| 97 | + enableRemoteDisabled.value = true; | |
| 98 | + } else { | |
| 99 | + enableRemoteDisabled.value = false; | |
| 100 | + } | |
| 101 | + tableData.value.push({ | |
| 102 | + enableRemote: res.enableRemote, | |
| 103 | + proxyName: res.proxyName, | |
| 104 | + status: res.status, | |
| 105 | + }); | |
| 106 | + if (res == '') { | |
| 107 | + enableRemoteDisabled.value = true; | |
| 108 | + disabled.value = true; | |
| 109 | + } | |
| 110 | + }; | |
| 111 | + const handleFrpRemote = async () => { | |
| 112 | + window.open(`${address.value}`); | |
| 113 | + }; | |
| 114 | + nextTick(() => { | |
| 115 | + getTableData(); | |
| 116 | + }); | |
| 117 | + const handleChange = async (e) => { | |
| 118 | + if (e) { | |
| 119 | + disabled.value = false; | |
| 120 | + enableRemote.value = 1; | |
| 121 | + const res = await frpPutInfoApi(proxyName.value, enableRemote.value); | |
| 122 | + if (res.enableRemote == 1) { | |
| 123 | + createMessage.success('开启成功'); | |
| 124 | + checked.value = true; | |
| 125 | + } | |
| 126 | + } else { | |
| 127 | + disabled.value = true; | |
| 128 | + enableRemote.value = 0; | |
| 129 | + const res = await frpPutInfoApi(proxyName.value, enableRemote.value); | |
| 130 | + if (res.enableRemote == 0) { | |
| 131 | + createMessage.success('关闭成功'); | |
| 132 | + checked.value = false; | |
| 133 | + } | |
| 134 | + } | |
| 135 | + }; | |
| 136 | +</script> | |
| 137 | +<style lang="less" scoped></style> | ... | ... |
| ... | ... | @@ -214,45 +214,49 @@ |
| 214 | 214 | if (isUpdate.value == 1) { |
| 215 | 215 | delete postDeviceConfogData.id; |
| 216 | 216 | } |
| 217 | - //1 TODO 待解决OID对象唯一标识不能重复问题验证 | |
| 218 | - let isMappings = false; | |
| 219 | - // let isQuerying = false; | |
| 217 | + let isMappingsKey = ref(false); | |
| 218 | + let isMappingValue = ref(false); | |
| 219 | + let isQueryings = ref(false); | |
| 220 | + let isOIDRepet = ref(false); | |
| 220 | 221 | postDeviceConfogData?.profileData?.transportConfiguration?.communicationConfigs?.forEach( |
| 221 | - (f) => { | |
| 222 | - // if (f.spec == 'TELEMETRY_QUERYING' && f.queryingFrequencyMs == null) { | |
| 223 | - // isQuerying = true; | |
| 224 | - // } else { | |
| 225 | - // isQuerying = false; | |
| 226 | - // } | |
| 227 | - // if (f.spec == 'CLIENT_ATTRIBUTES_QUERYING' && f.queryingFrequencyMs == null) { | |
| 228 | - // isQuerying = true; | |
| 229 | - // } else { | |
| 230 | - // isQuerying = false; | |
| 231 | - // } | |
| 222 | + (f: any) => { | |
| 223 | + if (f.spec == 'TELEMETRY_QUERYING' || f.spec == 'CLIENT_ATTRIBUTES_QUERYING') { | |
| 224 | + if (f.queryingFrequencyMs == null) { | |
| 225 | + isQueryings.value = true; | |
| 226 | + return createMessage.error('请填写查询频率'); | |
| 227 | + } else { | |
| 228 | + isQueryings.value = false; | |
| 229 | + } | |
| 230 | + } | |
| 232 | 231 | if (f.mappings.length == 0) { |
| 233 | - isMappings = true; | |
| 232 | + isMappingsKey.value = true; | |
| 233 | + isMappingValue.value = true; | |
| 234 | 234 | } else { |
| 235 | 235 | f.mappings.forEach((f1) => { |
| 236 | 236 | const findNoneKey = Object.keys(f1).includes(''); |
| 237 | 237 | if (findNoneKey) { |
| 238 | - isMappings = findNoneKey; | |
| 239 | - } else { | |
| 240 | - isMappings = false; | |
| 238 | + isMappingsKey.value = true; | |
| 241 | 239 | } |
| 242 | 240 | }); |
| 243 | 241 | f.mappings.forEach((f2) => { |
| 244 | 242 | const findNoneVal = Object.values(f2).includes(''); |
| 245 | 243 | if (findNoneVal) { |
| 246 | - isMappings = findNoneVal; | |
| 247 | - } else { | |
| 248 | - isMappings = false; | |
| 244 | + isMappingValue.value = true; | |
| 245 | + } | |
| 246 | + }); | |
| 247 | + //新增OID不能重复=====同一层级的OID不能重复 | |
| 248 | + f.mappings.forEach((item, _) => { | |
| 249 | + if (f.mappings.some((citem) => citem !== item && citem.oid === item.oid)) { | |
| 250 | + isOIDRepet.value = true; | |
| 249 | 251 | } |
| 250 | 252 | }); |
| 251 | 253 | } |
| 252 | 254 | } |
| 253 | 255 | ); |
| 254 | - // if (isQuerying) return createMessage.error('请填写Querying frequency,ms'); | |
| 255 | - if (isMappings) return createMessage.error('请填写Date key和OID'); | |
| 256 | + if (isQueryings.value) return createMessage.error('请填写查询频率'); | |
| 257 | + if (isMappingsKey.value) return createMessage.error('请填写Date key和OID'); | |
| 258 | + if (isMappingValue.value) return createMessage.error('请填写Date key和OID'); | |
| 259 | + if (isOIDRepet.value) return createMessage.error('OID不能重复'); | |
| 256 | 260 | deviceConfigAddOrEdit(isEdit.value ? noEditObj : postDeviceConfogData) |
| 257 | 261 | .then((res) => { |
| 258 | 262 | if (!res) return; | ... | ... |
| ... | ... | @@ -32,7 +32,7 @@ export const CoapSchemas: FormSchema[] = [ |
| 32 | 32 | { |
| 33 | 33 | field: 'coapDeviceType', |
| 34 | 34 | component: 'Select', |
| 35 | - label: 'CoAP 设备类型', | |
| 35 | + label: 'CoAP设备类型', | |
| 36 | 36 | defaultValue: 'DEFAULT', |
| 37 | 37 | componentProps: { |
| 38 | 38 | options: [ |
| ... | ... | @@ -45,7 +45,7 @@ export const CoapSchemas: FormSchema[] = [ |
| 45 | 45 | { |
| 46 | 46 | field: 'transportPayloadType', |
| 47 | 47 | component: 'Select', |
| 48 | - label: 'CoAP 设备消息 Payload', | |
| 48 | + label: 'CoAP设备消息Payload', | |
| 49 | 49 | defaultValue: 'JSON', |
| 50 | 50 | componentProps: { |
| 51 | 51 | options: [ |
| ... | ... | @@ -59,7 +59,7 @@ export const CoapSchemas: FormSchema[] = [ |
| 59 | 59 | { |
| 60 | 60 | field: 'powerMode', |
| 61 | 61 | component: 'Select', |
| 62 | - label: 'Power Saving Mode', | |
| 62 | + label: '节能模式', | |
| 63 | 63 | defaultValue: 'DRX', |
| 64 | 64 | componentProps: { |
| 65 | 65 | options: [ |
| ... | ... | @@ -76,11 +76,11 @@ export const CoapSchemas: FormSchema[] = [ |
| 76 | 76 | { |
| 77 | 77 | field: 'psmActivityTimer', |
| 78 | 78 | component: 'InputNumber', |
| 79 | - label: 'PSM Activity Timer', | |
| 79 | + label: 'PSM活动计时器', | |
| 80 | 80 | required: true, |
| 81 | - defaultValue: '10', | |
| 81 | + defaultValue: 10, | |
| 82 | 82 | componentProps: { |
| 83 | - placeholder: '请输入PSM Activity Timer', | |
| 83 | + placeholder: '请输入PSM活动计时器', | |
| 84 | 84 | }, |
| 85 | 85 | colProps: { span: 11 }, |
| 86 | 86 | ifShow: ({ values }) => isPsm(values.powerMode), |
| ... | ... | @@ -104,11 +104,11 @@ export const CoapSchemas: FormSchema[] = [ |
| 104 | 104 | { |
| 105 | 105 | field: 'edrxCycle', |
| 106 | 106 | component: 'InputNumber', |
| 107 | - label: 'eDRX cycle', | |
| 107 | + label: 'eDRX循环', | |
| 108 | 108 | required: true, |
| 109 | - defaultValue: '81', | |
| 109 | + defaultValue: 81, | |
| 110 | 110 | componentProps: { |
| 111 | - placeholder: '请输入PSM Activity Timer', | |
| 111 | + placeholder: '请输入eDRX循环', | |
| 112 | 112 | }, |
| 113 | 113 | colProps: { span: 11 }, |
| 114 | 114 | ifShow: ({ values }) => isDrx(values.powerMode), |
| ... | ... | @@ -132,11 +132,11 @@ export const CoapSchemas: FormSchema[] = [ |
| 132 | 132 | { |
| 133 | 133 | field: 'pagingTransmissionWindow', |
| 134 | 134 | component: 'InputNumber', |
| 135 | - label: 'Paging Transmission Window', | |
| 135 | + label: '寻呼传输窗口', | |
| 136 | 136 | required: true, |
| 137 | - defaultValue: '10', | |
| 137 | + defaultValue: 10, | |
| 138 | 138 | componentProps: { |
| 139 | - placeholder: '请输入Paging Transmission Window', | |
| 139 | + placeholder: '请输入寻呼传输窗口', | |
| 140 | 140 | }, |
| 141 | 141 | colProps: { span: 11 }, |
| 142 | 142 | ifShow: ({ values }) => isDrx(values.powerMode), | ... | ... |
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <BasicModal | |
| 4 | + v-bind="$attrs" | |
| 5 | + width="30rem" | |
| 6 | + :height="heightNum" | |
| 7 | + @register="register" | |
| 8 | + title="Add new server config" | |
| 9 | + @cancel="handleCancel" | |
| 10 | + :showOkBtn="true" | |
| 11 | + @ok="handleSubmit" | |
| 12 | + v-model:visible="visible" | |
| 13 | + > | |
| 14 | + <div style="display: flex; align-items: center; justify-content: center"> | |
| 15 | + <h2>Server type:</h2> | |
| 16 | + <Select | |
| 17 | + v-model:value="selectValue" | |
| 18 | + style="width: 340px" | |
| 19 | + :options="selectOptions" | |
| 20 | + @change="emitChange" | |
| 21 | + /> | |
| 22 | + </div> | |
| 23 | + </BasicModal> | |
| 24 | + </div> | |
| 25 | +</template> | |
| 26 | +<script setup lang="ts"> | |
| 27 | + import { ref } from 'vue'; | |
| 28 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | |
| 29 | + import { Select } from 'ant-design-vue'; | |
| 30 | + import { SelectTypes } from 'ant-design-vue/es/select'; | |
| 31 | + | |
| 32 | + const emit = defineEmits(['register', 'emitSelect']); | |
| 33 | + const [register] = useModalInner((data) => { | |
| 34 | + console.log(data); | |
| 35 | + }); | |
| 36 | + const heightNum = ref(80); | |
| 37 | + const visible = ref(false); | |
| 38 | + const selectValue = ref('LwM2M'); | |
| 39 | + const selectOptions = ref<SelectTypes['options']>([ | |
| 40 | + { | |
| 41 | + label: 'LwM2M Server', | |
| 42 | + value: 'LwM2M', | |
| 43 | + }, | |
| 44 | + { | |
| 45 | + label: 'Bootstrap Server', | |
| 46 | + value: 'Bootstrap', | |
| 47 | + }, | |
| 48 | + ]); | |
| 49 | + const emitChange = (e) => { | |
| 50 | + selectValue.value = e; | |
| 51 | + }; | |
| 52 | + const handleSubmit = () => { | |
| 53 | + emit('emitSelect', selectValue.value); | |
| 54 | + handleCancel(); | |
| 55 | + }; | |
| 56 | + | |
| 57 | + const handleCancel = () => { | |
| 58 | + visible.value = false; | |
| 59 | + }; | |
| 60 | +</script> | |
| 61 | +<style lang="less" scoped></style> | ... | ... |
| ... | ... | @@ -236,7 +236,6 @@ export const deviceSchemas: FormSchema[] = [ |
| 236 | 236 | colProps: { span: 22 }, |
| 237 | 237 | component: 'InputTextArea', |
| 238 | 238 | componentProps: { |
| 239 | - disabled: true, | |
| 240 | 239 | autoSize: { |
| 241 | 240 | maxRows: 50, |
| 242 | 241 | }, |
| ... | ... | @@ -250,45 +249,31 @@ export const deviceSchemas: FormSchema[] = [ |
| 250 | 249 | "keyName": {}, |
| 251 | 250 | "attributeLwm2m": {} |
| 252 | 251 | }, |
| 253 | - "bootstrap": { | |
| 254 | - "servers": { | |
| 255 | - "binding": "UQ", | |
| 256 | - "shortId": 123, | |
| 257 | - "lifetime": 300, | |
| 258 | - "notifIfDisabled": true, | |
| 259 | - "defaultMinPeriod": 1 | |
| 260 | - }, | |
| 261 | - "bootstrapServer": { | |
| 262 | - "bootstrapServerIs": true, | |
| 263 | - "host": "0.0.0.0", | |
| 264 | - "port": 5687, | |
| 265 | - "securityHost": "0.0.0.0", | |
| 266 | - "securityPort": 5688, | |
| 267 | - "serverId": 111, | |
| 268 | - "clientHoldOffTime": 1, | |
| 269 | - "serverPublicKey": "", | |
| 270 | - "bootstrapServerAccountTimeout": 0 | |
| 271 | - }, | |
| 272 | - "lwm2mServer": { | |
| 252 | + "bootstrap": [ | |
| 253 | + { | |
| 254 | + "shortServerId": 123, | |
| 273 | 255 | "bootstrapServerIs": false, |
| 274 | 256 | "host": "0.0.0.0", |
| 275 | 257 | "port": 5685, |
| 276 | - "securityHost": "0.0.0.0", | |
| 277 | - "securityPort": 5686, | |
| 278 | - "serverId": 123, | |
| 279 | 258 | "clientHoldOffTime": 1, |
| 280 | 259 | "serverPublicKey": "", |
| 281 | - "bootstrapServerAccountTimeout": 0 | |
| 260 | + "serverCertificate": "", | |
| 261 | + "bootstrapServerAccountTimeout": 0, | |
| 262 | + "lifetime": 300, | |
| 263 | + "defaultMinPeriod": 1, | |
| 264 | + "notifIfDisabled": true, | |
| 265 | + "binding": "U", | |
| 266 | + "securityMode": "NO_SEC" | |
| 282 | 267 | } |
| 283 | - }, | |
| 268 | + ], | |
| 284 | 269 | "clientLwM2mSettings": { |
| 285 | 270 | "clientOnlyObserveAfterConnect": 1, |
| 286 | 271 | "fwUpdateStrategy": 1, |
| 287 | - "swUpdateStrategy": 2, | |
| 288 | - "swUpdateResource": "coap://localhost:5685", | |
| 272 | + "swUpdateStrategy": 1, | |
| 289 | 273 | "powerMode": "DRX", |
| 290 | 274 | "compositeOperationsSupport": false |
| 291 | 275 | }, |
| 276 | + "bootstrapServerUpdateEnable": false, | |
| 292 | 277 | "type": "LWM2M" |
| 293 | 278 | } |
| 294 | 279 | `, | ... | ... |
| ... | ... | @@ -11,29 +11,44 @@ |
| 11 | 11 | </TabPane> |
| 12 | 12 | <TabPane forceRender key="2" tab="Bootstrap"> |
| 13 | 13 | <div> |
| 14 | - <Checkbox v-model:checked="bootstrapServerUpdateEnable">包括引导服务器更新</Checkbox> | |
| 15 | - <Card | |
| 14 | + <Checkbox | |
| 15 | + @change="handleCheckChange($event)" | |
| 16 | + v-model:checked="bootstrapServerUpdateEnable" | |
| 17 | + >包括引导服务器更新</Checkbox | |
| 18 | + > | |
| 19 | + <CollapseContainer | |
| 16 | 20 | v-for="(item, index) in dynamicBOOTSTRAP.bootstrap" |
| 17 | 21 | :key="item" |
| 18 | - title="LwM2M Server" | |
| 19 | - style="width: 99%; margin-top: 2vh" | |
| 22 | + :title="collapseTitle(item)" | |
| 23 | + class="mt-4" | |
| 20 | 24 | > |
| 21 | - <template #extra> | |
| 22 | - <Button size="small" type="dashed" @click="handleRemove(index)"> | |
| 23 | - <template #icon> | |
| 24 | - <MinusCircleOutlined /> | |
| 25 | - </template> | |
| 25 | + <template #action> | |
| 26 | + <Button | |
| 27 | + style="margin-right: 1vw" | |
| 28 | + size="small" | |
| 29 | + type="text" | |
| 30 | + @click="handleRemove(index)" | |
| 31 | + > | |
| 32 | + <template #icon> <DeleteOutlined /> </template> | |
| 26 | 33 | </Button> |
| 27 | 34 | </template> |
| 28 | - <!-- BootStrapForm表单项 --> | |
| 29 | - <BootStrapForm :ref="dynamicBindRef.BootStrapFormItemRef" :index="index" :item="item" /> | |
| 30 | - </Card> | |
| 35 | + <div style="border: 1px solid #d9d9d9; width: 100%"> | |
| 36 | + <div style="margin: 10px 15px"> | |
| 37 | + <BootStrapForm | |
| 38 | + :ref="dynamicBindRef.BootStrapFormItemRef" | |
| 39 | + :index="index" | |
| 40 | + :item="item" | |
| 41 | + /> | |
| 42 | + </div> | |
| 43 | + </div> | |
| 44 | + </CollapseContainer> | |
| 31 | 45 | <div style="margin-top: 2vh"> |
| 32 | - <Button size="middle" type="dashed" @click="handleAdd"> | |
| 46 | + <Button size="middle" type="text" @click="handleAdd"> | |
| 33 | 47 | <template #icon> |
| 34 | 48 | <PlusCircleOutlined /> |
| 35 | 49 | </template> |
| 36 | - Add LwM2M Server | |
| 50 | + <span v-if="selectCheckStatus">Add server config</span> | |
| 51 | + <span v-else>Add LwM2M Server</span> | |
| 37 | 52 | </Button> |
| 38 | 53 | </div> |
| 39 | 54 | </div> |
| ... | ... | @@ -61,6 +76,7 @@ |
| 61 | 76 | <BasicForm :showResetButton="false" :showSubmitButton="false" @register="registerDevice" /> |
| 62 | 77 | </TabPane> |
| 63 | 78 | </Tabs> |
| 79 | + <ServerConfigModal @register="registerModal" @emitSelect="acceptEmitFunc" /> | |
| 64 | 80 | </div> |
| 65 | 81 | </template> |
| 66 | 82 | |
| ... | ... | @@ -70,8 +86,11 @@ |
| 70 | 86 | import { BasicForm, useForm } from '/@/components/Form'; |
| 71 | 87 | import { modelSchemas, settingsSchemas, deviceSchemas } from './index'; |
| 72 | 88 | import BootStrapForm from './cpns/BootStrapForm.vue'; |
| 73 | - import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons-vue'; | |
| 89 | + import { DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons-vue'; | |
| 74 | 90 | import { Button, Checkbox } from 'ant-design-vue'; |
| 91 | + import { CollapseContainer } from '/@/components/Container'; | |
| 92 | + import { useModal } from '/@/components/Modal'; | |
| 93 | + import ServerConfigModal from './cpns/ServerConfigModal.vue'; | |
| 75 | 94 | |
| 76 | 95 | export default defineComponent({ |
| 77 | 96 | name: 'index', |
| ... | ... | @@ -80,16 +99,21 @@ |
| 80 | 99 | TabPane: Tabs.TabPane, |
| 81 | 100 | BasicForm, |
| 82 | 101 | BootStrapForm, |
| 83 | - MinusCircleOutlined, | |
| 102 | + DeleteOutlined, | |
| 84 | 103 | Card, |
| 85 | 104 | PlusCircleOutlined, |
| 86 | 105 | Button, |
| 87 | 106 | Checkbox, |
| 107 | + CollapseContainer, | |
| 108 | + ServerConfigModal, | |
| 88 | 109 | }, |
| 89 | 110 | setup() { |
| 111 | + const collapseTitle = (item) => { | |
| 112 | + return `LwM2M Server Short server ID: ${item.shortServerId} Security config mode: ${item.securityMode}`; | |
| 113 | + }; | |
| 90 | 114 | const bootstrapServerUpdateEnable = ref(false); |
| 91 | 115 | const dynamicBOOTSTRAP: any = reactive({ |
| 92 | - bootstrap: [{}], | |
| 116 | + bootstrap: [{ securityMode: 'NO_SEC', shortServerId: 1234 }], | |
| 93 | 117 | }); |
| 94 | 118 | const dynamicBindRef: any = { |
| 95 | 119 | BootStrapFormItemRef: ref([]), |
| ... | ... | @@ -140,9 +164,20 @@ |
| 140 | 164 | span: 14, |
| 141 | 165 | }, |
| 142 | 166 | }); |
| 167 | + const [registerModal, { openModal }] = useModal(); | |
| 143 | 168 | |
| 144 | 169 | const handleAdd = () => { |
| 145 | - dynamicBOOTSTRAP.bootstrap.push({}); | |
| 170 | + //TODO 如果是server config 则只弹窗一次 | |
| 171 | + if (selectCheckStatus.value) { | |
| 172 | + openModal(true, { | |
| 173 | + isUpdate: true, | |
| 174 | + }); | |
| 175 | + } else { | |
| 176 | + dynamicBOOTSTRAP.bootstrap.push({ | |
| 177 | + securityMode: 'NO_SEC', | |
| 178 | + shortServerId: 1234, | |
| 179 | + }); | |
| 180 | + } | |
| 146 | 181 | }; |
| 147 | 182 | const handleRemove = (index) => { |
| 148 | 183 | dynamicBOOTSTRAP.bootstrap.splice(index, 1); |
| ... | ... | @@ -240,7 +275,32 @@ |
| 240 | 275 | resetDeviceValue(); |
| 241 | 276 | }); |
| 242 | 277 | }; |
| 243 | - | |
| 278 | + const selectCheckStatus = ref(false); | |
| 279 | + const handleCheckChange = (e) => { | |
| 280 | + selectCheckStatus.value = e.target.checked; | |
| 281 | + if (!selectCheckStatus.value) { | |
| 282 | + const findIndex = dynamicBOOTSTRAP.bootstrap.findIndex((o) => o.type == 'Bootstrap'); | |
| 283 | + if (findIndex !== -1) { | |
| 284 | + dynamicBOOTSTRAP.bootstrap.splice(findIndex, 1); | |
| 285 | + } | |
| 286 | + } | |
| 287 | + }; | |
| 288 | + const acceptEmitFunc = (e) => { | |
| 289 | + switch (e) { | |
| 290 | + case 'LwM2M': | |
| 291 | + dynamicBOOTSTRAP.bootstrap.push({ | |
| 292 | + securityMode: 'NO_SEC', | |
| 293 | + shortServerId: 1234, | |
| 294 | + }); | |
| 295 | + break; | |
| 296 | + case 'Bootstrap': | |
| 297 | + dynamicBOOTSTRAP.bootstrap.push({ | |
| 298 | + securityMode: 'NO_SEC', | |
| 299 | + shortServerId: 1234, | |
| 300 | + }); | |
| 301 | + break; | |
| 302 | + } | |
| 303 | + }; | |
| 244 | 304 | return { |
| 245 | 305 | currentKey, |
| 246 | 306 | currentSize, |
| ... | ... | @@ -256,6 +316,11 @@ |
| 256 | 316 | handleAdd, |
| 257 | 317 | handleRemove, |
| 258 | 318 | bootstrapServerUpdateEnable, |
| 319 | + collapseTitle, | |
| 320 | + handleCheckChange, | |
| 321 | + registerModal, | |
| 322 | + acceptEmitFunc, | |
| 323 | + selectCheckStatus, | |
| 259 | 324 | }; |
| 260 | 325 | }, |
| 261 | 326 | }); | ... | ... |
| ... | ... | @@ -20,7 +20,7 @@ export const MqttSchemas: FormSchema[] = [ |
| 20 | 20 | { |
| 21 | 21 | field: 'deviceTelemetryTopic', |
| 22 | 22 | component: 'Input', |
| 23 | - label: '遥测数据 topic 筛选器', | |
| 23 | + label: '遥测数据主题筛选器', | |
| 24 | 24 | required: true, |
| 25 | 25 | defaultValue: 'v1/devices/me/telemetry', |
| 26 | 26 | componentProps: { |
| ... | ... | @@ -32,7 +32,7 @@ export const MqttSchemas: FormSchema[] = [ |
| 32 | 32 | field: 'deviceAttributesTopic', |
| 33 | 33 | component: 'Input', |
| 34 | 34 | required: true, |
| 35 | - label: 'Attributes topic filter', | |
| 35 | + label: '属性主题过滤器', | |
| 36 | 36 | defaultValue: 'v1/devices/me/attributes', |
| 37 | 37 | componentProps: { |
| 38 | 38 | placeholder: '请输入Attributes topic 筛选器', |
| ... | ... | @@ -64,7 +64,14 @@ export const MqttSchemas: FormSchema[] = [ |
| 64 | 64 | colProps: { span: 23 }, |
| 65 | 65 | defaultValue: false, |
| 66 | 66 | component: 'Checkbox', |
| 67 | - renderComponentContent: `启用后,平台将默认使用Protobuf有效载荷格式。如果解析失败,平台将尝试使用JSON有效负载格式。用于固件更新期间的向后兼容性。例如,固件的初始版本使用Json,而新版本使用Protobuf。在设备组的固件更新过程中,需要同时支持Protobuf和JSON。兼容性模式会导致性能轻微下降,因此建议在所有设备更新后禁用此模式。`, | |
| 67 | + ifShow: ({ values }) => isProtobuf(values.transportPayloadType), | |
| 68 | + renderComponentContent: '启用与其他有效负载格式的兼容性', | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + field: 'desc1', | |
| 72 | + component: 'InputTextArea', | |
| 73 | + label: '', | |
| 74 | + slot: 'desc1', | |
| 68 | 75 | ifShow: ({ values }) => isProtobuf(values.transportPayloadType), |
| 69 | 76 | }, |
| 70 | 77 | { |
| ... | ... | @@ -73,7 +80,16 @@ export const MqttSchemas: FormSchema[] = [ |
| 73 | 80 | colProps: { span: 23 }, |
| 74 | 81 | defaultValue: false, |
| 75 | 82 | component: 'Checkbox', |
| 76 | - renderComponentContent: `启用后,平台将使用Json有效负载格式通过以下主题推送属性和RPC:v1/devices/me/attributes/response/$request_id, v1/devices/me/attributes, v1/devices/me/rpc/request/$request_id,v1/devices/me/rpc/response/$request_id.此设置不会影响使用新(v2)主题发送的属性和rpc订阅:v2/a/res/$request_id, v2/a, v2/r/req/$request_id, v2/r/res/$request_id. Where $request_id是一个整数请求标识符。`, | |
| 83 | + renderComponentContent: '默认下行主题使用Json格式', | |
| 84 | + ifShow: ({ values }) => | |
| 85 | + isProtobuf(values.transportPayloadType) && | |
| 86 | + !!values.useJsonPayloadFormatForDefaultDownlinkTopics, | |
| 87 | + }, | |
| 88 | + { | |
| 89 | + field: 'desc2', | |
| 90 | + component: 'InputTextArea', | |
| 91 | + label: '', | |
| 92 | + slot: 'desc2', | |
| 77 | 93 | ifShow: ({ values }) => |
| 78 | 94 | isProtobuf(values.transportPayloadType) && |
| 79 | 95 | !!values.useJsonPayloadFormatForDefaultDownlinkTopics, | ... | ... |
| ... | ... | @@ -19,6 +19,28 @@ |
| 19 | 19 | </p> |
| 20 | 20 | </div> |
| 21 | 21 | </template> |
| 22 | + <template #desc1> | |
| 23 | + <div style="width: 47rem; margin-left: 2rem"> | |
| 24 | + <p> | |
| 25 | + 启用时,默认情况下,平台将使用Protobuf有效载荷格式。如果解析失败,平台将尝试使用JSON负载格式。 | |
| 26 | + 有助于固件更新期间的向后兼容性。例如,固件的初始版本使用Json,而新版本使用Protobuf。 | |
| 27 | + 在设备组的固件更新过程中,需要同时支持Protobuf和JSON。兼容性模式会导致性能略有下降, | |
| 28 | + 因此建议在更新所有设备后禁用此模式。 | |
| 29 | + </p> | |
| 30 | + </div> | |
| 31 | + </template> | |
| 32 | + <template #desc2> | |
| 33 | + <div style="width: 47rem; margin-left: 2rem"> | |
| 34 | + <p> | |
| 35 | + 启用后,平台将使用Json负载格式通过以下主题推送属性 | |
| 36 | + 和RPC:v1/devices/me/attributes/response/$request\u id、v1/devices/me/attributes、 | |
| 37 | + v1/devices/me/RPC/request/$request\u id、v1/devices/me/RPC/response/$request\u id。 | |
| 38 | + 此设置不会影响使用新(v2)主题发送的属性和rpc订阅: | |
| 39 | + v2/a/res/$request\u id、v2/a、v2/r/req/$request\u id、v2/r/res/$request\u id。 | |
| 40 | + 其中,$request\u id是整数请求标识符。 | |
| 41 | + </p> | |
| 42 | + </div> | |
| 43 | + </template> | |
| 22 | 44 | </BasicForm> |
| 23 | 45 | </div> |
| 24 | 46 | </div> | ... | ... |
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | <div |
| 3 | 3 | v-for="(param, index) in dynamicInput.params" |
| 4 | 4 | :key="index" |
| 5 | - style="display: flex; margin-top: 0.2vh" | |
| 5 | + style="display: flex; margin-top: 0.25vh" | |
| 6 | 6 | > |
| 7 | 7 | <Select |
| 8 | 8 | v-model:value="param.dataType" |
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | @change="emitChange" |
| 18 | 18 | /> |
| 19 | 19 | <a-input |
| 20 | - placeholder="请输入OID" | |
| 20 | + placeholder="请输入OID(不能重复)" | |
| 21 | 21 | v-model:value="param.oid" |
| 22 | 22 | style="width: 38%; margin: 0 0 5px 8px" |
| 23 | 23 | @change="emitChange" |
| ... | ... | @@ -30,7 +30,7 @@ |
| 30 | 30 | /> |
| 31 | 31 | </div> |
| 32 | 32 | <div> |
| 33 | - <a-button type="dashed" style="width: 38%" @click="add"> | |
| 33 | + <a-button type="text" style="width: 28%; margin-top: 0.25vh" @click="add"> | |
| 34 | 34 | <PlusCircleOutlined /> |
| 35 | 35 | Add mapping |
| 36 | 36 | </a-button> | ... | ... |
| ... | ... | @@ -16,10 +16,12 @@ |
| 16 | 16 | > |
| 17 | 17 | <InputNumber |
| 18 | 18 | v-if="item.spec == 'TELEMETRY_QUERYING' || item.spec == 'CLIENT_ATTRIBUTES_QUERYING'" |
| 19 | - v-model:value="item.queryingFrequencyMs" | |
| 20 | - :min="5000" | |
| 21 | - :max="1000000000" | |
| 19 | + v-model:value="queryingFrequencyMs" | |
| 20 | + :min="0" | |
| 21 | + :max="99999999999999999999" | |
| 22 | + style="margin-top: 0.25vh" | |
| 22 | 23 | /> |
| 24 | + <div style="margin-top: 0.65vh"></div> | |
| 23 | 25 | <MappingsForm |
| 24 | 26 | :value="item.mappings" |
| 25 | 27 | @change="handleMappingsChange" |
| ... | ... | @@ -33,7 +35,7 @@ |
| 33 | 35 | style="text-align: center; line-height: 20vh" |
| 34 | 36 | :style="{ lineHeight: dynamicHeight + 'vh' }" |
| 35 | 37 | > |
| 36 | - <Button size="small" type="dashed" @click="handleRemove(item, index)"> | |
| 38 | + <Button size="small" type="default" @click="handleRemove(item, index)"> | |
| 37 | 39 | <template #icon> |
| 38 | 40 | <MinusCircleOutlined /> |
| 39 | 41 | </template> |
| ... | ... | @@ -47,6 +49,7 @@ |
| 47 | 49 | import { Button, InputNumber } from 'ant-design-vue'; |
| 48 | 50 | import { MinusCircleOutlined } from '@ant-design/icons-vue'; |
| 49 | 51 | import MappingsForm from './MappingsForm.vue'; |
| 52 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
| 50 | 53 | |
| 51 | 54 | const props = defineProps({ |
| 52 | 55 | item: { |
| ... | ... | @@ -59,7 +62,8 @@ |
| 59 | 62 | }); |
| 60 | 63 | const emit = defineEmits(['removeItem']); |
| 61 | 64 | const dynamicHeight = ref(25); |
| 62 | - | |
| 65 | + const queryingFrequencyMs = ref(5000); | |
| 66 | + const { createMessage } = useMessage(); | |
| 63 | 67 | const handleMappingsChange = (e) => { |
| 64 | 68 | // eslint-disable-next-line vue/no-mutating-props |
| 65 | 69 | props.item.mappings = e; |
| ... | ... | @@ -70,9 +74,18 @@ |
| 70 | 74 | //设置回显的高度 |
| 71 | 75 | const setFieldsValueFunc = () => { |
| 72 | 76 | dynamicHeight.value = props.item.mappings.length * 3 + props.item.mappings.length + 15; |
| 77 | + queryingFrequencyMs.value = props.item.queryingFrequencyMs; | |
| 73 | 78 | }; |
| 74 | 79 | //获取表单的值 |
| 75 | 80 | const getSnmpFormFunc = () => { |
| 81 | + if ( | |
| 82 | + props.item.spec == 'TELEMETRY_QUERYING' || | |
| 83 | + props.item.spec == 'CLIENT_ATTRIBUTES_QUERYING' | |
| 84 | + ) { | |
| 85 | + if (queryingFrequencyMs.value == null) { | |
| 86 | + return createMessage.error('请填写查询频率'); | |
| 87 | + } | |
| 88 | + } | |
| 76 | 89 | let obj: any = {}; |
| 77 | 90 | obj = { |
| 78 | 91 | ...{ spec: props.item.spec }, |
| ... | ... | @@ -80,7 +93,7 @@ |
| 80 | 93 | ...{ |
| 81 | 94 | queryingFrequencyMs: |
| 82 | 95 | props.item.spec == 'TELEMETRY_QUERYING' || props.item.spec == 'CLIENT_ATTRIBUTES_QUERYING' |
| 83 | - ? props.item.queryingFrequencyMs | |
| 96 | + ? queryingFrequencyMs.value | |
| 84 | 97 | : null, |
| 85 | 98 | }, |
| 86 | 99 | }; | ... | ... |
| ... | ... | @@ -27,22 +27,14 @@ |
| 27 | 27 | :options="selectOptions" |
| 28 | 28 | @change="handleChange(item.spec)" |
| 29 | 29 | allowClear |
| 30 | - > | |
| 31 | - <SelectOption | |
| 32 | - v-for="it in selectOptions" | |
| 33 | - :value="it.value" | |
| 34 | - :key="it.value" | |
| 35 | - :label="it.label" | |
| 36 | - :disabled="it.disabled" | |
| 37 | - /> | |
| 38 | - </Select> | |
| 30 | + /> | |
| 39 | 31 | </SnmpForm> |
| 40 | 32 | </template> |
| 41 | 33 | <div |
| 42 | 34 | v-if="dynamicSNMP.communicationConfigs.length < 4" |
| 43 | 35 | style="margin-left: 0vw; margin-top: 2vh" |
| 44 | 36 | > |
| 45 | - <Button size="middle" type="dashed" @click="handleAdd"> | |
| 37 | + <Button size="middle" type="text" @click="handleAdd"> | |
| 46 | 38 | <template #icon> |
| 47 | 39 | <PlusCircleOutlined /> |
| 48 | 40 | </template> |
| ... | ... | @@ -60,7 +52,7 @@ |
| 60 | 52 | import { BasicForm, useForm } from '/@/components/Form'; |
| 61 | 53 | import { snmpSchemas } from './config'; |
| 62 | 54 | import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
| 63 | - import { Button, Select, SelectOption } from 'ant-design-vue'; | |
| 55 | + import { Button, Select } from 'ant-design-vue'; | |
| 64 | 56 | import SnmpForm from './cpns/SnmpForm.vue'; |
| 65 | 57 | |
| 66 | 58 | interface mappingsI { |
| ... | ... | @@ -77,26 +69,22 @@ |
| 77 | 69 | { |
| 78 | 70 | label: 'Telemetry', |
| 79 | 71 | value: 'TELEMETRY_QUERYING', |
| 80 | - disabled: false, | |
| 81 | 72 | }, |
| 82 | 73 | { |
| 83 | 74 | label: 'Client attributes', |
| 84 | 75 | value: 'CLIENT_ATTRIBUTES_QUERYING', |
| 85 | - disabled: false, | |
| 86 | 76 | }, |
| 87 | 77 | { |
| 88 | 78 | label: 'Shared attributes', |
| 89 | 79 | value: 'SHARED_ATTRIBUTES_SETTING', |
| 90 | - disabled: false, | |
| 91 | 80 | }, |
| 92 | 81 | { |
| 93 | 82 | label: 'RPC request', |
| 94 | 83 | value: 'TO_DEVICE_RPC_REQUEST', |
| 95 | - disabled: false, | |
| 96 | 84 | }, |
| 97 | 85 | ]); |
| 98 | 86 | const selectValue = ref(''); |
| 99 | - //解决新增选择框互斥问题和编辑回显互斥问题 | |
| 87 | + //Select互斥 | |
| 100 | 88 | const handleChange = (value: string) => { |
| 101 | 89 | selectValue.value = value; |
| 102 | 90 | selectOptions.value.forEach((ele: any) => { |
| ... | ... | @@ -154,12 +142,15 @@ |
| 154 | 142 | }); |
| 155 | 143 | }; |
| 156 | 144 | const handleRemoveItem = (item, index) => { |
| 157 | - console.log(item.spec); | |
| 158 | - //2 TODO待解决删除时清空互斥问题 | |
| 145 | + selectOptions.value.forEach((ele: any) => { | |
| 146 | + if (ele.value == item.spec) { | |
| 147 | + ele.disabled = false; | |
| 148 | + } | |
| 149 | + }); | |
| 159 | 150 | dynamicSNMP.communicationConfigs.splice(index, 1); |
| 160 | 151 | }; |
| 152 | + | |
| 161 | 153 | //回显表单值 |
| 162 | - //TODO 采用这种方式动态绑定ref 都会造成回显数据顺序是随机的但不影响回显数据的正确性 | |
| 163 | 154 | const setStepFieldsValueFunc = (v) => { |
| 164 | 155 | setFieldsValue({ |
| 165 | 156 | timeoutMs: v.timeoutMs, |
| ... | ... | @@ -168,14 +159,13 @@ |
| 168 | 159 | dynamicSNMP.communicationConfigs = v.communicationConfigs; |
| 169 | 160 | dynamicSNMP.communicationConfigs.forEach((snmp, index: number) => { |
| 170 | 161 | nextTick(() => { |
| 171 | - //编辑回显互斥赋值 | |
| 172 | 162 | handleChange(snmp.spec); |
| 173 | 163 | unref(dynamicBindRef.SnmpFormItemRef)[index]?.setFieldsValueFunc(); |
| 174 | 164 | }); |
| 175 | 165 | }); |
| 176 | 166 | }; |
| 177 | 167 | |
| 178 | - //获取最终的值 | |
| 168 | + //获取表单值 | |
| 179 | 169 | const getSnmpForm = async () => { |
| 180 | 170 | let value = await validate(); |
| 181 | 171 | if (!value) return; | ... | ... |
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <BasicModal | |
| 4 | + v-bind="$attrs" | |
| 5 | + width="55rem" | |
| 6 | + :height="heightNum" | |
| 7 | + @register="register" | |
| 8 | + title="执行设备及属性" | |
| 9 | + @cancel="handleCancel" | |
| 10 | + :showOkBtn="false" | |
| 11 | + > | |
| 12 | + <div> | |
| 13 | + <BasicTable @register="registerTable" :dataSource="tableData" /> | |
| 14 | + </div> | |
| 15 | + </BasicModal> | |
| 16 | + </div> | |
| 17 | +</template> | |
| 18 | +<script setup lang="ts"> | |
| 19 | + import { ref, nextTick } from 'vue'; | |
| 20 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | |
| 21 | + import { BasicTable, useTable } from '/@/components/Table'; | |
| 22 | + import { viewDeviceColumn } from './config.data'; | |
| 23 | + | |
| 24 | + const heightNum = ref(800); | |
| 25 | + const tableData: any = ref([]); | |
| 26 | + const [registerTable] = useTable({ | |
| 27 | + title: '执行设备及属性', | |
| 28 | + columns: viewDeviceColumn, | |
| 29 | + showIndexColumn: false, | |
| 30 | + clickToRowSelect: false, | |
| 31 | + showTableSetting: false, | |
| 32 | + bordered: true, | |
| 33 | + }); | |
| 34 | + const [register] = useModalInner((data) => { | |
| 35 | + console.log(data); | |
| 36 | + const getTableData = () => { | |
| 37 | + for (let i = 0; i < 100; i++) { | |
| 38 | + tableData.value.push({ | |
| 39 | + tdDevice: `设备${i}`, | |
| 40 | + attr: `CO${i}、Temp${i}`, | |
| 41 | + }); | |
| 42 | + } | |
| 43 | + }; | |
| 44 | + nextTick(() => { | |
| 45 | + getTableData(); | |
| 46 | + }); | |
| 47 | + }); | |
| 48 | + const handleCancel = () => {}; | |
| 49 | +</script> | |
| 50 | +<style></style> | ... | ... |
| 1 | +<template> | |
| 2 | + <BasicDrawer | |
| 3 | + v-bind="$attrs" | |
| 4 | + @register="registerDrawer" | |
| 5 | + showFooter | |
| 6 | + :title="getTitle" | |
| 7 | + width="30%" | |
| 8 | + @ok="handleSubmit" | |
| 9 | + > | |
| 10 | + <BasicForm @register="registerForm" /> | |
| 11 | + </BasicDrawer> | |
| 12 | +</template> | |
| 13 | +<script lang="ts" setup> | |
| 14 | + import { ref, computed, unref, nextTick } from 'vue'; | |
| 15 | + import { BasicForm, useForm } from '/@/components/Form'; | |
| 16 | + import { formSchema } from './config.data'; | |
| 17 | + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; | |
| 18 | + import { createOrEditCameraManage } from '/@/api/camera/cameraManager'; | |
| 19 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
| 20 | + | |
| 21 | + const emit = defineEmits(['success', 'register']); | |
| 22 | + const isUpdate = ref(true); | |
| 23 | + const editId = ref(''); | |
| 24 | + const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({ | |
| 25 | + labelWidth: 120, | |
| 26 | + schemas: formSchema, | |
| 27 | + showActionButtonGroup: false, | |
| 28 | + }); | |
| 29 | + | |
| 30 | + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => { | |
| 31 | + await resetFields(); | |
| 32 | + setDrawerProps({ confirmLoading: false }); | |
| 33 | + isUpdate.value = !!data?.isUpdate; | |
| 34 | + if (unref(isUpdate)) { | |
| 35 | + await nextTick(); | |
| 36 | + editId.value = data.record.id; | |
| 37 | + await setFieldsValue(data.record); | |
| 38 | + } else { | |
| 39 | + editId.value = ''; | |
| 40 | + } | |
| 41 | + }); | |
| 42 | + | |
| 43 | + const getTitle = computed(() => (!unref(isUpdate) ? '新增报表配置' : '编辑报表配置')); | |
| 44 | + | |
| 45 | + async function handleSubmit() { | |
| 46 | + setDrawerProps({ confirmLoading: true }); | |
| 47 | + try { | |
| 48 | + const { createMessage } = useMessage(); | |
| 49 | + const values = await validate(); | |
| 50 | + if (!values) return; | |
| 51 | + let saveMessage = '添加成功'; | |
| 52 | + let updateMessage = '修改成功'; | |
| 53 | + await createOrEditCameraManage(values); | |
| 54 | + closeDrawer(); | |
| 55 | + emit('success'); | |
| 56 | + createMessage.success(unref(isUpdate) ? updateMessage : saveMessage); | |
| 57 | + } finally { | |
| 58 | + setTimeout(() => { | |
| 59 | + setDrawerProps({ confirmLoading: false }); | |
| 60 | + }, 300); | |
| 61 | + } | |
| 62 | + } | |
| 63 | +</script> | ... | ... |
src/views/report/config/config.data.ts
0 → 100644
| 1 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | |
| 2 | +import type { FormSchema as QFormSchema } from '/@/components/Form/index'; | |
| 3 | +import moment from 'moment'; | |
| 4 | +import { getOrganizationList } from '/@/api/system/system'; | |
| 5 | +import { copyTransFun } from '/@/utils/fnUtils'; | |
| 6 | +import { screenLinkPageByDeptIdGetDevice } from '/@/api/ruleengine/ruleengineApi'; | |
| 7 | +import { getAttribute } from '/@/api/ruleengine/ruleengineApi'; | |
| 8 | + | |
| 9 | +export enum TypeEnum { | |
| 10 | + IS_TIMING = 'TIMING', | |
| 11 | + IS_WEEK = 'week', | |
| 12 | + IS_MONTH = 'month', | |
| 13 | +} | |
| 14 | +export const isTiming = (type: string) => { | |
| 15 | + return type === TypeEnum.IS_TIMING; | |
| 16 | +}; | |
| 17 | +export const isWeek = (type: string) => { | |
| 18 | + return type === TypeEnum.IS_WEEK; | |
| 19 | +}; | |
| 20 | + | |
| 21 | +export const isMonth = (type: string) => { | |
| 22 | + return type === TypeEnum.IS_MONTH; | |
| 23 | +}; | |
| 24 | + | |
| 25 | + | |
| 26 | +export enum AggregateDataEnum { | |
| 27 | + MIN = 'MIN', | |
| 28 | + MAX = 'MAX', | |
| 29 | + AVG = 'AVG', | |
| 30 | + SUM = 'SUM', | |
| 31 | + COUNT = 'COUNT', | |
| 32 | + NONE = 'NONE', | |
| 33 | +} | |
| 34 | + | |
| 35 | +// 表格配置 | |
| 36 | +export const columns: BasicColumn[] = [ | |
| 37 | + { | |
| 38 | + title: '配置名称', | |
| 39 | + dataIndex: '1', | |
| 40 | + width: 80, | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + title: '所属组织', | |
| 44 | + dataIndex: '2', | |
| 45 | + width: 120, | |
| 46 | + }, | |
| 47 | + { | |
| 48 | + title: '数据类型', | |
| 49 | + dataIndex: '3', | |
| 50 | + width: 120, | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + title: '配置状态', | |
| 54 | + dataIndex: '4', | |
| 55 | + width: 120, | |
| 56 | + }, | |
| 57 | + { | |
| 58 | + title: '执行方式', | |
| 59 | + dataIndex: '5', | |
| 60 | + width: 160, | |
| 61 | + }, | |
| 62 | + { | |
| 63 | + title: '执行设备', | |
| 64 | + dataIndex: '6', | |
| 65 | + width: 160, | |
| 66 | + slots: { customRender: 'doDeviceSlot' }, | |
| 67 | + }, | |
| 68 | + { | |
| 69 | + title: '创建人', | |
| 70 | + dataIndex: '7', | |
| 71 | + width: 180, | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + title: '创建日期', | |
| 75 | + dataIndex: '8', | |
| 76 | + width: 180, | |
| 77 | + }, | |
| 78 | +]; | |
| 79 | +export const viewDeviceColumn: BasicColumn[] = [ | |
| 80 | + { | |
| 81 | + title: '设备', | |
| 82 | + dataIndex: 'tdDevice', | |
| 83 | + width: 80, | |
| 84 | + }, | |
| 85 | + { | |
| 86 | + title: '属性', | |
| 87 | + dataIndex: 'attr', | |
| 88 | + width: 120, | |
| 89 | + }, | |
| 90 | +]; | |
| 91 | + | |
| 92 | +// 查询配置 | |
| 93 | +export const searchFormSchema: FormSchema[] = [ | |
| 94 | + { | |
| 95 | + field: '1', | |
| 96 | + label: '配置名称', | |
| 97 | + component: 'Input', | |
| 98 | + colProps: { span: 6 }, | |
| 99 | + componentProps: { | |
| 100 | + maxLength: 36, | |
| 101 | + placeholder: '请输入配置名称', | |
| 102 | + }, | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + field: '2', | |
| 106 | + label: '配置状态', | |
| 107 | + component: 'Select', | |
| 108 | + colProps: { span: 6 }, | |
| 109 | + componentProps: { | |
| 110 | + options: [ | |
| 111 | + { | |
| 112 | + label: '启用', | |
| 113 | + value: 1, | |
| 114 | + }, | |
| 115 | + { | |
| 116 | + label: '禁用', | |
| 117 | + value: 0, | |
| 118 | + }, | |
| 119 | + ], | |
| 120 | + placeholder: '请选择配置状态', | |
| 121 | + }, | |
| 122 | + }, | |
| 123 | + { | |
| 124 | + field: '3', | |
| 125 | + label: '创建时间', | |
| 126 | + component: 'RangePicker', | |
| 127 | + componentProps: { | |
| 128 | + showTime: { | |
| 129 | + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], | |
| 130 | + }, | |
| 131 | + }, | |
| 132 | + colProps: { span: 6 }, | |
| 133 | + }, | |
| 134 | +]; | |
| 135 | + | |
| 136 | +// 新增编辑配置 | |
| 137 | +export const formSchema: QFormSchema[] = [ | |
| 138 | + { | |
| 139 | + field: '1', | |
| 140 | + label: '报表名称', | |
| 141 | + colProps: { span: 24 }, | |
| 142 | + helpMessage: ['报表配置', '只对拥有数值型属性', '的设备才能配置'], | |
| 143 | + required: true, | |
| 144 | + component: 'Input', | |
| 145 | + componentProps: { | |
| 146 | + maxLength: 255, | |
| 147 | + placeholder: '请输入报表名称', | |
| 148 | + }, | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + field: 'organizationId', | |
| 152 | + label: '所属组织', | |
| 153 | + colProps: { span: 24 }, | |
| 154 | + component: 'ApiTreeSelect', | |
| 155 | + required: true, | |
| 156 | + componentProps: ({ formActionType }) => { | |
| 157 | + const { updateSchema } = formActionType; | |
| 158 | + return { | |
| 159 | + async onChange(e) { | |
| 160 | + if (e) { | |
| 161 | + //获取该组织下的设备 | |
| 162 | + const { items } = await screenLinkPageByDeptIdGetDevice({ | |
| 163 | + organizationId: e, | |
| 164 | + }); | |
| 165 | + const options = items.map((item) => { | |
| 166 | + return { | |
| 167 | + label: item.name, | |
| 168 | + value: item.tbDeviceId, | |
| 169 | + }; | |
| 170 | + }); | |
| 171 | + updateSchema({ | |
| 172 | + field: 'entityId', | |
| 173 | + componentProps: { | |
| 174 | + options, | |
| 175 | + async onChange(e1) { | |
| 176 | + if (e1) { | |
| 177 | + const res = await getAttribute(e, e1.join(',')); | |
| 178 | + const attr = res.map((item) => { | |
| 179 | + return { | |
| 180 | + label: item, | |
| 181 | + value: item, | |
| 182 | + }; | |
| 183 | + }); | |
| 184 | + updateSchema({ | |
| 185 | + field: 'attr', | |
| 186 | + componentProps: { | |
| 187 | + options: attr, | |
| 188 | + }, | |
| 189 | + }); | |
| 190 | + } | |
| 191 | + }, | |
| 192 | + }, | |
| 193 | + }); | |
| 194 | + } | |
| 195 | + }, | |
| 196 | + maxLength: 250, | |
| 197 | + placeholder: '请选择所属组织', | |
| 198 | + api: async () => { | |
| 199 | + const data = await getOrganizationList(); | |
| 200 | + copyTransFun(data as any as any[]); | |
| 201 | + return data; | |
| 202 | + }, | |
| 203 | + }; | |
| 204 | + }, | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + field: '3', | |
| 208 | + label: '描述', | |
| 209 | + colProps: { span: 24 }, | |
| 210 | + component: 'InputTextArea', | |
| 211 | + componentProps: { | |
| 212 | + maxLength: 255, | |
| 213 | + placeholder: '请输入描述', | |
| 214 | + }, | |
| 215 | + }, | |
| 216 | + { | |
| 217 | + field: 'actionS', | |
| 218 | + component: 'RadioGroup', | |
| 219 | + label: '执行方式', | |
| 220 | + colProps: { | |
| 221 | + span: 24, | |
| 222 | + }, | |
| 223 | + defaultValue: '1', | |
| 224 | + componentProps: { | |
| 225 | + placeholder: '请选择执行方式', | |
| 226 | + options: [ | |
| 227 | + { | |
| 228 | + label: '立即执行', | |
| 229 | + value: '1', | |
| 230 | + }, | |
| 231 | + { | |
| 232 | + label: '定时执行', | |
| 233 | + value: 'TIMING', | |
| 234 | + }, | |
| 235 | + ], | |
| 236 | + }, | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + field: 'timeWeek', | |
| 240 | + component: 'Select', | |
| 241 | + label: '周期', | |
| 242 | + required: true, | |
| 243 | + colProps: { span: 24 }, | |
| 244 | + defaultValue: '1', | |
| 245 | + componentProps: { | |
| 246 | + placeholder: '请选择周期', | |
| 247 | + options: [ | |
| 248 | + { label: '每日', value: 'day' }, | |
| 249 | + { label: '每周', value: 'week' }, | |
| 250 | + { label: '每月', value: 'month' }, | |
| 251 | + ], | |
| 252 | + }, | |
| 253 | + ifShow: ({ values }) => isTiming(values.actionS), | |
| 254 | + }, | |
| 255 | + { | |
| 256 | + field: '5', | |
| 257 | + component: 'Select', | |
| 258 | + label: '每周', | |
| 259 | + required: true, | |
| 260 | + colProps: { span: 24 }, | |
| 261 | + defaultValue: '1', | |
| 262 | + componentProps: { | |
| 263 | + placeholder: '请选择周期', | |
| 264 | + options: [ | |
| 265 | + { label: '星期一', value: '1' }, | |
| 266 | + { label: '星期二', value: '2' }, | |
| 267 | + { label: '星期三', value: '3' }, | |
| 268 | + { label: '星期四', value: '3' }, | |
| 269 | + { label: '星期五', value: '3' }, | |
| 270 | + ], | |
| 271 | + }, | |
| 272 | + ifShow: ({ values }) => isWeek(values.timeWeek), | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + field: '5', | |
| 276 | + component: 'Select', | |
| 277 | + label: '每月', | |
| 278 | + required: true, | |
| 279 | + colProps: { span: 24 }, | |
| 280 | + defaultValue: '1', | |
| 281 | + componentProps: { | |
| 282 | + placeholder: '请选择周期', | |
| 283 | + options: [ | |
| 284 | + { label: '1日', value: '1' }, | |
| 285 | + { label: '2日', value: '7' }, | |
| 286 | + { label: '3日', value: '30' }, | |
| 287 | + ], | |
| 288 | + }, | |
| 289 | + ifShow: ({ values }) => isMonth(values.timeWeek), | |
| 290 | + }, | |
| 291 | + { | |
| 292 | + field: '6', | |
| 293 | + component: 'Select', | |
| 294 | + label: '时间', | |
| 295 | + required: true, | |
| 296 | + colProps: { span: 24 }, | |
| 297 | + defaultValue: '1', | |
| 298 | + componentProps: { | |
| 299 | + placeholder: '请选择时间', | |
| 300 | + options: [ | |
| 301 | + { label: '00点', value: '1' }, | |
| 302 | + { label: '01点', value: '2' }, | |
| 303 | + { label: '02点', value: '3' }, | |
| 304 | + ], | |
| 305 | + }, | |
| 306 | + ifShow: ({ values }) => isTiming(values.actionS), | |
| 307 | + }, | |
| 308 | + { | |
| 309 | + field: 'entityId', | |
| 310 | + label: '设备', | |
| 311 | + required: true, | |
| 312 | + component: 'Select', | |
| 313 | + componentProps: { | |
| 314 | + placeholder: '请选择设备', | |
| 315 | + mode: 'multiple', | |
| 316 | + }, | |
| 317 | + colProps: { span: 24 }, | |
| 318 | + }, | |
| 319 | + { | |
| 320 | + field: '7', | |
| 321 | + component: 'RadioGroup', | |
| 322 | + label: '属性性质', | |
| 323 | + required: true, | |
| 324 | + colProps: { | |
| 325 | + span: 24, | |
| 326 | + }, | |
| 327 | + defaultValue: '1', | |
| 328 | + componentProps: { | |
| 329 | + placeholder: '请选择属性性质', | |
| 330 | + options: [ | |
| 331 | + { | |
| 332 | + label: '共有', | |
| 333 | + value: '1', | |
| 334 | + }, | |
| 335 | + { | |
| 336 | + label: '全部', | |
| 337 | + value: '2', | |
| 338 | + }, | |
| 339 | + ], | |
| 340 | + }, | |
| 341 | + }, | |
| 342 | + { | |
| 343 | + field: 'attr', | |
| 344 | + label: '设备属性', | |
| 345 | + required: true, | |
| 346 | + component: 'Select', | |
| 347 | + componentProps: { | |
| 348 | + placeholder: '请选择设备属性', | |
| 349 | + }, | |
| 350 | + colProps: { span: 24 }, | |
| 351 | + }, | |
| 352 | + { | |
| 353 | + field: '9', | |
| 354 | + label: '数据类型', | |
| 355 | + required: true, | |
| 356 | + component: 'Select', | |
| 357 | + componentProps: { | |
| 358 | + placeholder: '请选择数据类型', | |
| 359 | + options: [ | |
| 360 | + { label: '历史数据', value: '1' }, | |
| 361 | + { label: '同比', value: '2' }, | |
| 362 | + { label: '环比', value: '2' }, | |
| 363 | + ], | |
| 364 | + }, | |
| 365 | + colProps: { span: 24 }, | |
| 366 | + }, | |
| 367 | + { | |
| 368 | + field: '10', | |
| 369 | + label: '聚合条件', | |
| 370 | + required: true, | |
| 371 | + component: 'Select', | |
| 372 | + componentProps: { | |
| 373 | + placeholder: '请选择聚合条件', | |
| 374 | + options: [ | |
| 375 | + { | |
| 376 | + label: '平均值', | |
| 377 | + value: AggregateDataEnum.AVG, | |
| 378 | + }, | |
| 379 | + { | |
| 380 | + label: '总和', | |
| 381 | + value: AggregateDataEnum.SUM, | |
| 382 | + }, | |
| 383 | + { | |
| 384 | + label: '最大值', | |
| 385 | + value: AggregateDataEnum.MAX, | |
| 386 | + }, | |
| 387 | + { | |
| 388 | + label: '最小值', | |
| 389 | + value: AggregateDataEnum.MIN, | |
| 390 | + }, | |
| 391 | + ], | |
| 392 | + }, | |
| 393 | + colProps: { span: 24 }, | |
| 394 | + }, | |
| 395 | + { | |
| 396 | + field: '9', | |
| 397 | + label: '查询周期', | |
| 398 | + required: true, | |
| 399 | + component: 'Select', | |
| 400 | + componentProps: { | |
| 401 | + placeholder: '请选择查询周期', | |
| 402 | + options: [ | |
| 403 | + { label: '1分钟', value: '60000' }, | |
| 404 | + { label: '2分钟', value: '120000' }, | |
| 405 | + { label: '3分钟', value: '180000' }, | |
| 406 | + ], | |
| 407 | + }, | |
| 408 | + colProps: { span: 24 }, | |
| 409 | + }, | |
| 410 | + { | |
| 411 | + field: '10', | |
| 412 | + label: '间隔时间', | |
| 413 | + required: true, | |
| 414 | + component: 'Select', | |
| 415 | + componentProps: { | |
| 416 | + placeholder: '请选择间隔时间', | |
| 417 | + options: [ | |
| 418 | + { label: '1秒', value: '1000' }, | |
| 419 | + { label: '5秒', value: '5000' }, | |
| 420 | + { label: '10秒', value: '10000' }, | |
| 421 | + { label: '15秒', value: '150000' }, | |
| 422 | + ], | |
| 423 | + }, | |
| 424 | + colProps: { span: 24 }, | |
| 425 | + }, | |
| 426 | +]; | ... | ... |
src/views/report/config/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <BasicTable :clickToRowSelect="false" @register="registerTable" :searchInfo="searchInfo"> | |
| 4 | + <template #toolbar> | |
| 5 | + <Authority value="api:yt:report:post"> | |
| 6 | + <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增报表 </a-button> | |
| 7 | + </Authority> | |
| 8 | + <Authority value="api:yt:report:get"> | |
| 9 | + <a-button type="primary" @click="go('/report/export')"> 下载报表 </a-button> | |
| 10 | + </Authority> | |
| 11 | + <Authority value="api:yt:report:delete"> | |
| 12 | + <Popconfirm | |
| 13 | + title="您确定要批量删除数据" | |
| 14 | + ok-text="确定" | |
| 15 | + cancel-text="取消" | |
| 16 | + @confirm="handleDeleteOrBatchDelete(null)" | |
| 17 | + > | |
| 18 | + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button> | |
| 19 | + </Popconfirm> | |
| 20 | + </Authority> | |
| 21 | + </template> | |
| 22 | + <template #doDeviceSlot="{ record }"> | |
| 23 | + <a-button type="text" @click="handleDeviceView(record)"> | |
| 24 | + <span style="color: #377dff">查看设备</span> | |
| 25 | + </a-button> | |
| 26 | + </template> | |
| 27 | + <template #action="{ record }"> | |
| 28 | + <TableAction | |
| 29 | + :actions="[ | |
| 30 | + { | |
| 31 | + label: '编辑', | |
| 32 | + icon: 'clarity:note-edit-line', | |
| 33 | + auth: 'api:yt:report:update', | |
| 34 | + onClick: handleCreateOrEdit.bind(null, record), | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + label: '删除', | |
| 38 | + icon: 'ant-design:delete-outlined', | |
| 39 | + auth: 'api:yt:report:delete', | |
| 40 | + color: 'error', | |
| 41 | + popConfirm: { | |
| 42 | + title: '是否确认删除', | |
| 43 | + confirm: handleDeleteOrBatchDelete.bind(null, record), | |
| 44 | + }, | |
| 45 | + }, | |
| 46 | + ]" | |
| 47 | + /> | |
| 48 | + </template> | |
| 49 | + </BasicTable> | |
| 50 | + <ReportConfigDrawer @register="registerDrawer" @success="handleSuccess" /> | |
| 51 | + <DevicePreviewModal @register="registerModal" /> | |
| 52 | + </div> | |
| 53 | +</template> | |
| 54 | + | |
| 55 | +<script lang="ts" setup> | |
| 56 | + import { reactive, nextTick } from 'vue'; | |
| 57 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
| 58 | + import { useDrawer } from '/@/components/Drawer'; | |
| 59 | + import ReportConfigDrawer from './ReportConfigDrawer.vue'; | |
| 60 | + import DevicePreviewModal from './DevicePreviewModal.vue'; | |
| 61 | + import { cameraPage, deleteCameraManage } from '/@/api/camera/cameraManager'; | |
| 62 | + import { searchFormSchema, columns } from './config.data'; | |
| 63 | + import { Authority } from '/@/components/Authority'; | |
| 64 | + import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | |
| 65 | + import { Popconfirm } from 'ant-design-vue'; | |
| 66 | + import { useModal } from '/@/components/Modal'; | |
| 67 | + import { useGo } from '/@/hooks/web/usePage'; | |
| 68 | + | |
| 69 | + const searchInfo = reactive<Recordable>({}); | |
| 70 | + | |
| 71 | + const [registerTable, { reload, setProps }] = useTable({ | |
| 72 | + title: '报表列表', | |
| 73 | + api: cameraPage, | |
| 74 | + columns, | |
| 75 | + showIndexColumn: false, | |
| 76 | + clickToRowSelect: false, | |
| 77 | + formConfig: { | |
| 78 | + labelWidth: 120, | |
| 79 | + schemas: searchFormSchema, | |
| 80 | + }, | |
| 81 | + useSearchForm: true, | |
| 82 | + showTableSetting: true, | |
| 83 | + bordered: true, | |
| 84 | + rowKey: 'id', | |
| 85 | + actionColumn: { | |
| 86 | + width: 200, | |
| 87 | + title: '操作', | |
| 88 | + dataIndex: 'action', | |
| 89 | + slots: { customRender: 'action' }, | |
| 90 | + fixed: 'right', | |
| 91 | + }, | |
| 92 | + }); | |
| 93 | + | |
| 94 | + // 弹框 | |
| 95 | + const [registerDrawer, { openDrawer }] = useDrawer(); | |
| 96 | + | |
| 97 | + // 刷新 | |
| 98 | + const handleSuccess = () => { | |
| 99 | + reload(); | |
| 100 | + }; | |
| 101 | + | |
| 102 | + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( | |
| 103 | + deleteCameraManage, | |
| 104 | + handleSuccess, | |
| 105 | + setProps | |
| 106 | + ); | |
| 107 | + | |
| 108 | + nextTick(() => { | |
| 109 | + setProps(selectionOptions); | |
| 110 | + }); | |
| 111 | + | |
| 112 | + // 新增或编辑 | |
| 113 | + const handleCreateOrEdit = (record: Recordable | null) => { | |
| 114 | + if (record) { | |
| 115 | + openDrawer(true, { | |
| 116 | + isUpdate: true, | |
| 117 | + record, | |
| 118 | + }); | |
| 119 | + } else { | |
| 120 | + openDrawer(true, { | |
| 121 | + isUpdate: false, | |
| 122 | + }); | |
| 123 | + } | |
| 124 | + }; | |
| 125 | + | |
| 126 | + //查看设备 | |
| 127 | + const [registerModal, { openModal }] = useModal(); | |
| 128 | + const handleDeviceView = (record) => { | |
| 129 | + openModal(true, { | |
| 130 | + isUpdate: true, | |
| 131 | + record, | |
| 132 | + }); | |
| 133 | + }; | |
| 134 | + const go = useGo(); | |
| 135 | +</script> | ... | ... |
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <BasicModal | |
| 4 | + v-bind="$attrs" | |
| 5 | + width="55rem" | |
| 6 | + :height="heightNum" | |
| 7 | + @register="register" | |
| 8 | + title="报表趋势图" | |
| 9 | + @cancel="handleCancel" | |
| 10 | + :showOkBtn="false" | |
| 11 | + > | |
| 12 | + <div ref="chartRef" :style="{ height, width }"></div> | |
| 13 | + </BasicModal> | |
| 14 | + </div> | |
| 15 | +</template> | |
| 16 | +<script setup lang="ts"> | |
| 17 | + import { ref, PropType, Ref, nextTick } from 'vue'; | |
| 18 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | |
| 19 | + import { useECharts } from '/@/hooks/web/useECharts'; | |
| 20 | + | |
| 21 | + defineProps({ | |
| 22 | + width: { | |
| 23 | + type: String as PropType<string>, | |
| 24 | + default: '100%', | |
| 25 | + }, | |
| 26 | + height: { | |
| 27 | + type: String as PropType<string>, | |
| 28 | + default: 'calc(100vh - 378px)', | |
| 29 | + }, | |
| 30 | + }); | |
| 31 | + defineEmits(['register']); | |
| 32 | + const heightNum = ref(800); | |
| 33 | + const chartRef = ref<HTMLDivElement | null>(null); | |
| 34 | + const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>); | |
| 35 | + const [register] = useModalInner((data) => { | |
| 36 | + console.log(data); | |
| 37 | + nextTick(() => { | |
| 38 | + setOptions({ | |
| 39 | + title: { | |
| 40 | + text: '报表趋势图', | |
| 41 | + }, | |
| 42 | + tooltip: { | |
| 43 | + trigger: 'axis', | |
| 44 | + }, | |
| 45 | + legend: { | |
| 46 | + data: ['CO', 'CO2'], | |
| 47 | + }, | |
| 48 | + toolbox: {}, | |
| 49 | + grid: { | |
| 50 | + left: '3%', | |
| 51 | + right: '4%', | |
| 52 | + bottom: '3%', | |
| 53 | + containLabel: true, | |
| 54 | + }, | |
| 55 | + xAxis: { | |
| 56 | + type: 'category', | |
| 57 | + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | |
| 58 | + boundaryGap: false, | |
| 59 | + }, | |
| 60 | + yAxis: { | |
| 61 | + type: 'value', | |
| 62 | + boundaryGap: false, | |
| 63 | + }, | |
| 64 | + series: [ | |
| 65 | + { | |
| 66 | + name: 'CO', | |
| 67 | + type: 'line', | |
| 68 | + stack: 'Total', | |
| 69 | + data: [150, 230, 224, 218, 135, 147, 260], | |
| 70 | + }, | |
| 71 | + { | |
| 72 | + name: 'CO2', | |
| 73 | + type: 'line', | |
| 74 | + stack: 'Total', | |
| 75 | + data: [15, 23, 22, 28, 15, 17, 20], | |
| 76 | + }, | |
| 77 | + ], | |
| 78 | + }); | |
| 79 | + }); | |
| 80 | + }); | |
| 81 | + | |
| 82 | + const handleCancel = () => {}; | |
| 83 | +</script> | |
| 84 | +<style> | |
| 85 | + .video-sty { | |
| 86 | + width: 100%; | |
| 87 | + display: flex; | |
| 88 | + align-items: center; | |
| 89 | + justify-content: center; | |
| 90 | + } | |
| 91 | +</style> | ... | ... |
src/views/report/export/config.data.ts
0 → 100644
| 1 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | |
| 2 | +import moment from 'moment'; | |
| 3 | + | |
| 4 | +// 表格配置 | |
| 5 | +export const columns: BasicColumn[] = [ | |
| 6 | + { | |
| 7 | + title: '配置名称', | |
| 8 | + dataIndex: '1', | |
| 9 | + width: 80, | |
| 10 | + }, | |
| 11 | + { | |
| 12 | + title: '所属组织', | |
| 13 | + dataIndex: '2', | |
| 14 | + width: 120, | |
| 15 | + }, | |
| 16 | + { | |
| 17 | + title: '数据类型', | |
| 18 | + dataIndex: '3', | |
| 19 | + width: 120, | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + title: '执行状态', | |
| 23 | + dataIndex: '4', | |
| 24 | + width: 120, | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + title: '执行日期', | |
| 28 | + dataIndex: '8', | |
| 29 | + width: 180, | |
| 30 | + }, | |
| 31 | +]; | |
| 32 | + | |
| 33 | +// 查询配置 | |
| 34 | +export const searchFormSchema: FormSchema[] = [ | |
| 35 | + { | |
| 36 | + field: '1', | |
| 37 | + label: '配置名称', | |
| 38 | + component: 'Input', | |
| 39 | + colProps: { span: 6 }, | |
| 40 | + componentProps: { | |
| 41 | + maxLength: 36, | |
| 42 | + placeholder: '请输入配置名称', | |
| 43 | + }, | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + field: '2', | |
| 47 | + label: '执行状态', | |
| 48 | + component: 'Select', | |
| 49 | + colProps: { span: 6 }, | |
| 50 | + componentProps: { | |
| 51 | + options: [ | |
| 52 | + { | |
| 53 | + label: '进行中', | |
| 54 | + value: 1, | |
| 55 | + }, | |
| 56 | + { | |
| 57 | + label: '成功', | |
| 58 | + value: 0, | |
| 59 | + }, | |
| 60 | + { | |
| 61 | + label: '失败', | |
| 62 | + value: 0, | |
| 63 | + }, | |
| 64 | + ], | |
| 65 | + placeholder: '请选择执行状态', | |
| 66 | + }, | |
| 67 | + }, | |
| 68 | + { | |
| 69 | + field: '3', | |
| 70 | + label: '执行时间', | |
| 71 | + component: 'RangePicker', | |
| 72 | + componentProps: { | |
| 73 | + showTime: { | |
| 74 | + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], | |
| 75 | + }, | |
| 76 | + }, | |
| 77 | + colProps: { span: 6 }, | |
| 78 | + }, | |
| 79 | +]; | ... | ... |
src/views/report/export/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <BasicTable :clickToRowSelect="false" @register="registerTable" :searchInfo="searchInfo"> | |
| 4 | + <template #toolbar> | |
| 5 | + <Authority value="api:yt:reportExport:export"> | |
| 6 | + <a-button type="primary" @click="exportCharts"> 导出报表 </a-button> | |
| 7 | + </Authority> | |
| 8 | + <Authority value="api:yt:reportExport:delete"> | |
| 9 | + <Popconfirm | |
| 10 | + title="您确定要批量删除数据" | |
| 11 | + ok-text="确定" | |
| 12 | + cancel-text="取消" | |
| 13 | + @confirm="handleDeleteOrBatchDelete(null)" | |
| 14 | + > | |
| 15 | + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button> | |
| 16 | + </Popconfirm> | |
| 17 | + </Authority> | |
| 18 | + </template> | |
| 19 | + <template #action="{ record }"> | |
| 20 | + <TableAction | |
| 21 | + :actions="[ | |
| 22 | + { | |
| 23 | + label: '查看', | |
| 24 | + icon: 'clarity:note-edit-line', | |
| 25 | + auth: 'api:yt:reportExport:get', | |
| 26 | + onClick: handleView.bind(null, record), | |
| 27 | + }, | |
| 28 | + { | |
| 29 | + label: '导出报表', | |
| 30 | + icon: 'clarity:note-edit-line', | |
| 31 | + auth: 'api:yt:reportExport:export', | |
| 32 | + }, | |
| 33 | + { | |
| 34 | + label: '删除', | |
| 35 | + icon: 'ant-design:delete-outlined', | |
| 36 | + auth: 'api:yt:reportExport:delete', | |
| 37 | + color: 'error', | |
| 38 | + popConfirm: { | |
| 39 | + title: '是否确认删除', | |
| 40 | + confirm: handleDeleteOrBatchDelete.bind(null, record), | |
| 41 | + }, | |
| 42 | + }, | |
| 43 | + ]" | |
| 44 | + /> | |
| 45 | + </template> | |
| 46 | + </BasicTable> | |
| 47 | + <ReportPreviewModal @register="registerModal" /> | |
| 48 | + </div> | |
| 49 | +</template> | |
| 50 | + | |
| 51 | +<script lang="ts" setup> | |
| 52 | + import { reactive, nextTick } from 'vue'; | |
| 53 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
| 54 | + import { cameraPage, deleteCameraManage } from '/@/api/camera/cameraManager'; | |
| 55 | + import { searchFormSchema, columns } from './config.data'; | |
| 56 | + import { Authority } from '/@/components/Authority'; | |
| 57 | + import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | |
| 58 | + import { Popconfirm } from 'ant-design-vue'; | |
| 59 | + import { useModal } from '/@/components/Modal'; | |
| 60 | + import ReportPreviewModal from './ReportPreviewModal.vue'; | |
| 61 | + | |
| 62 | + const searchInfo = reactive<Recordable>({}); | |
| 63 | + | |
| 64 | + const [registerTable, { reload, setProps }] = useTable({ | |
| 65 | + title: '报表导出列表', | |
| 66 | + api: cameraPage, | |
| 67 | + columns, | |
| 68 | + showIndexColumn: false, | |
| 69 | + clickToRowSelect: false, | |
| 70 | + formConfig: { | |
| 71 | + labelWidth: 120, | |
| 72 | + schemas: searchFormSchema, | |
| 73 | + }, | |
| 74 | + useSearchForm: true, | |
| 75 | + showTableSetting: true, | |
| 76 | + bordered: true, | |
| 77 | + rowKey: 'id', | |
| 78 | + actionColumn: { | |
| 79 | + width: 200, | |
| 80 | + title: '操作', | |
| 81 | + dataIndex: 'action', | |
| 82 | + slots: { customRender: 'action' }, | |
| 83 | + fixed: 'right', | |
| 84 | + }, | |
| 85 | + }); | |
| 86 | + | |
| 87 | + const handleSuccess = () => { | |
| 88 | + reload(); | |
| 89 | + }; | |
| 90 | + | |
| 91 | + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( | |
| 92 | + deleteCameraManage, | |
| 93 | + handleSuccess, | |
| 94 | + setProps | |
| 95 | + ); | |
| 96 | + | |
| 97 | + nextTick(() => { | |
| 98 | + setProps(selectionOptions); | |
| 99 | + }); | |
| 100 | + | |
| 101 | + const [registerModal, { openModal }] = useModal(); | |
| 102 | + const handleView = (_, record) => { | |
| 103 | + openModal(true, { | |
| 104 | + isUpdate: true, | |
| 105 | + record, | |
| 106 | + }); | |
| 107 | + }; | |
| 108 | + const exportCharts = () => {}; | |
| 109 | +</script> | ... | ... |
| ... | ... | @@ -292,11 +292,15 @@ |
| 292 | 292 | return createMessage.error('请填写属性'); |
| 293 | 293 | } |
| 294 | 294 | } |
| 295 | - await postAddConvertApi(isEdit.value ? noEditObj : allPostForm); | |
| 296 | - createMessage.success('数据流转编辑成功'); | |
| 297 | - emit('success'); | |
| 298 | - defineClearFunc(); | |
| 299 | - closeModal(); | |
| 295 | + const res = await postAddConvertApi(isEdit.value ? noEditObj : allPostForm); | |
| 296 | + if (res) { | |
| 297 | + closeModal(); | |
| 298 | + createMessage.success('数据流转编辑成功'); | |
| 299 | + setTimeout(() => { | |
| 300 | + emit('success'); | |
| 301 | + }, 500); | |
| 302 | + defineClearFunc(); | |
| 303 | + } | |
| 300 | 304 | } |
| 301 | 305 | } catch { |
| 302 | 306 | } finally { | ... | ... |