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,6 +17,7 @@ enum EDeviceConfigApi { | ||
17 | DEVICE_CONFIG_EXPORT = '/deviceProfile/export', | 17 | DEVICE_CONFIG_EXPORT = '/deviceProfile/export', |
18 | DEVICE_CONFIG_IMPORT = '/deviceProfile/import', | 18 | DEVICE_CONFIG_IMPORT = '/deviceProfile/import', |
19 | SET_DEVICE_ISDEFAULT = '/deviceProfile', | 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,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 | +}; |
@@ -60,7 +60,7 @@ | @@ -60,7 +60,7 @@ | ||
60 | }); | 60 | }); |
61 | const emit = defineEmits(['expand']); | 61 | const emit = defineEmits(['expand']); |
62 | 62 | ||
63 | - const show = ref(true); | 63 | + const show = ref(false); |
64 | 64 | ||
65 | const { prefixCls } = useDesign('collapse-container'); | 65 | const { prefixCls } = useDesign('collapse-container'); |
66 | 66 |
@@ -9,5 +9,7 @@ export { useForm } from './src/hooks/useForm'; | @@ -9,5 +9,7 @@ export { useForm } from './src/hooks/useForm'; | ||
9 | export { default as ApiSelect } from './src/components/ApiSelect.vue'; | 9 | export { default as ApiSelect } from './src/components/ApiSelect.vue'; |
10 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; | 10 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; |
11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; | 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; |
12 | +export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | ||
13 | + | ||
12 | 14 | ||
13 | export { BasicForm }; | 15 | export { BasicForm }; |
@@ -27,6 +27,7 @@ import { BasicUpload } from '/@/components/Upload'; | @@ -27,6 +27,7 @@ import { BasicUpload } from '/@/components/Upload'; | ||
27 | import { StrengthMeter } from '/@/components/StrengthMeter'; | 27 | import { StrengthMeter } from '/@/components/StrengthMeter'; |
28 | import { IconPicker } from '/@/components/Icon'; | 28 | import { IconPicker } from '/@/components/Icon'; |
29 | import { CountdownInput } from '/@/components/CountDown'; | 29 | import { CountdownInput } from '/@/components/CountDown'; |
30 | +import ApiRadioGroup from './components/ApiRadioGroup.vue'; | ||
30 | //自定义组件 | 31 | //自定义组件 |
31 | import JAddInput from './jeecg/components/JAddInput.vue'; | 32 | import JAddInput from './jeecg/components/JAddInput.vue'; |
32 | 33 | ||
@@ -39,6 +40,7 @@ componentMap.set('InputSearch', Input.Search); | @@ -39,6 +40,7 @@ componentMap.set('InputSearch', Input.Search); | ||
39 | componentMap.set('InputTextArea', Input.TextArea); | 40 | componentMap.set('InputTextArea', Input.TextArea); |
40 | componentMap.set('InputNumber', InputNumber); | 41 | componentMap.set('InputNumber', InputNumber); |
41 | componentMap.set('AutoComplete', AutoComplete); | 42 | componentMap.set('AutoComplete', AutoComplete); |
43 | +componentMap.set('ApiRadioGroup', ApiRadioGroup); | ||
42 | 44 | ||
43 | componentMap.set('Select', Select); | 45 | componentMap.set('Select', Select); |
44 | componentMap.set('ApiSelect', ApiSelect); | 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> |
@@ -82,6 +82,7 @@ export interface ColEx { | @@ -82,6 +82,7 @@ export interface ColEx { | ||
82 | 82 | ||
83 | export type ComponentType = | 83 | export type ComponentType = |
84 | | 'Input' | 84 | | 'Input' |
85 | + | 'ApiRadioGroup' | ||
85 | | 'InputGroup' | 86 | | 'InputGroup' |
86 | | 'InputPassword' | 87 | | 'InputPassword' |
87 | | 'InputSearch' | 88 | | 'InputSearch' |
@@ -77,7 +77,7 @@ | @@ -77,7 +77,7 @@ | ||
77 | } | 77 | } |
78 | 78 | ||
79 | let resStr = ''; | 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 | if (diff < ONE_SECONDS) { | 82 | if (diff < ONE_SECONDS) { |
83 | resStr = t('component.time.just'); | 83 | resStr = t('component.time.just'); |
@@ -477,14 +477,14 @@ | @@ -477,14 +477,14 @@ | ||
477 | cursor: pointer; | 477 | cursor: pointer; |
478 | position: absolute; | 478 | position: absolute; |
479 | top: 0.85rem; | 479 | top: 0.85rem; |
480 | - left: 1.1rem; | 480 | + left: 1.1vw; |
481 | } | 481 | } |
482 | .fold-right { | 482 | .fold-right { |
483 | z-index: 1; | 483 | z-index: 1; |
484 | cursor: pointer; | 484 | cursor: pointer; |
485 | position: absolute; | 485 | position: absolute; |
486 | top: 0.85rem; | 486 | top: 0.85rem; |
487 | - left: 21.6rem; | 487 | + left: 18.2vw; |
488 | } | 488 | } |
489 | @prefix-cls: ~'@{namespace}-basic-tree'; | 489 | @prefix-cls: ~'@{namespace}-basic-tree'; |
490 | 490 |
@@ -9,9 +9,7 @@ | @@ -9,9 +9,7 @@ | ||
9 | import { useMessage } from '/@/hooks/web/useMessage'; | 9 | import { useMessage } from '/@/hooks/web/useMessage'; |
10 | import { PlayProtocol } from '../manage/config.data'; | 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 | const createFlag = ref(false); | 14 | const createFlag = ref(false); |
17 | 15 |
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | import { ref } from 'vue'; | 15 | import { ref } from 'vue'; |
16 | import { useMessage } from '/@/hooks/web/useMessage'; | 16 | import { useMessage } from '/@/hooks/web/useMessage'; |
17 | import { Popconfirm } from 'ant-design-vue'; | 17 | import { Popconfirm } from 'ant-design-vue'; |
18 | + import { Authority } from '/@/components/Authority'; | ||
18 | 19 | ||
19 | const enabledBatchDelete = ref(true); | 20 | const enabledBatchDelete = ref(true); |
20 | const [register, { reload, getSelectRowKeys }] = useTable({ | 21 | const [register, { reload, getSelectRowKeys }] = useTable({ |
@@ -51,23 +51,21 @@ export const step1Schemas: FormSchema[] = [ | @@ -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 | label: '选择厂家', | 56 | label: '选择厂家', |
57 | + required: true, | ||
57 | colProps: { | 58 | colProps: { |
58 | span: 8, | 59 | span: 8, |
59 | }, | 60 | }, |
61 | + defaultValue: 'DIY_', | ||
60 | componentProps: { | 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 | ifShow: ({ values }) => isGateWay(values.deviceType), | 70 | ifShow: ({ values }) => isGateWay(values.deviceType), |
73 | }, | 71 | }, |
@@ -22,6 +22,15 @@ | @@ -22,6 +22,15 @@ | ||
22 | <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> | 22 | <TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> |
23 | <ChildDevice :fromId="deviceDetail?.tbDeviceId" /> | 23 | <ChildDevice :fromId="deviceDetail?.tbDeviceId" /> |
24 | </TabPane> | 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 | </Tabs> | 34 | </Tabs> |
26 | </BasicDrawer> | 35 | </BasicDrawer> |
27 | </template> | 36 | </template> |
@@ -34,6 +43,7 @@ | @@ -34,6 +43,7 @@ | ||
34 | import RealTimeData from '../tabs/RealTimeData.vue'; | 43 | import RealTimeData from '../tabs/RealTimeData.vue'; |
35 | import Alarm from '../tabs/Alarm.vue'; | 44 | import Alarm from '../tabs/Alarm.vue'; |
36 | import ChildDevice from '../tabs/ChildDevice.vue'; | 45 | import ChildDevice from '../tabs/ChildDevice.vue'; |
46 | + import TBoxDetail from '../tabs/TBoxDetail.vue'; | ||
37 | import CommandIssuance from '../tabs/CommandIssuance.vue'; | 47 | import CommandIssuance from '../tabs/CommandIssuance.vue'; |
38 | import { getDeviceDetail } from '/@/api/device/deviceManager'; | 48 | import { getDeviceDetail } from '/@/api/device/deviceManager'; |
39 | export default defineComponent({ | 49 | export default defineComponent({ |
@@ -47,6 +57,7 @@ | @@ -47,6 +57,7 @@ | ||
47 | Alarm, | 57 | Alarm, |
48 | ChildDevice, | 58 | ChildDevice, |
49 | CommandIssuance, | 59 | CommandIssuance, |
60 | + TBoxDetail, | ||
50 | }, | 61 | }, |
51 | emits: ['reload', 'register'], | 62 | emits: ['reload', 'register'], |
52 | setup() { | 63 | setup() { |
@@ -53,12 +53,6 @@ | @@ -53,12 +53,6 @@ | ||
53 | <a-button type="primary" class="mr-4" @click="copyTbDeviceId">复制设备ID</a-button> | 53 | <a-button type="primary" class="mr-4" @click="copyTbDeviceId">复制设备ID</a-button> |
54 | <a-button type="primary" class="mr-4" @click="copyDeviceToken">复制访问令牌</a-button> | 54 | <a-button type="primary" class="mr-4" @click="copyDeviceToken">复制访问令牌</a-button> |
55 | <a-button type="primary" class="mr-4" @click="manageDeviceToken">管理设备凭证</a-button> | 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 | <ManageDeviceTokenModal @register="registerModal" /> | 56 | <ManageDeviceTokenModal @register="registerModal" /> |
63 | </div> | 57 | </div> |
64 | <div v-if="deviceDetail?.deviceInfo?.address" class="mt-4"> | 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,45 +214,49 @@ | ||
214 | if (isUpdate.value == 1) { | 214 | if (isUpdate.value == 1) { |
215 | delete postDeviceConfogData.id; | 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 | postDeviceConfogData?.profileData?.transportConfiguration?.communicationConfigs?.forEach( | 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 | if (f.mappings.length == 0) { | 231 | if (f.mappings.length == 0) { |
233 | - isMappings = true; | 232 | + isMappingsKey.value = true; |
233 | + isMappingValue.value = true; | ||
234 | } else { | 234 | } else { |
235 | f.mappings.forEach((f1) => { | 235 | f.mappings.forEach((f1) => { |
236 | const findNoneKey = Object.keys(f1).includes(''); | 236 | const findNoneKey = Object.keys(f1).includes(''); |
237 | if (findNoneKey) { | 237 | if (findNoneKey) { |
238 | - isMappings = findNoneKey; | ||
239 | - } else { | ||
240 | - isMappings = false; | 238 | + isMappingsKey.value = true; |
241 | } | 239 | } |
242 | }); | 240 | }); |
243 | f.mappings.forEach((f2) => { | 241 | f.mappings.forEach((f2) => { |
244 | const findNoneVal = Object.values(f2).includes(''); | 242 | const findNoneVal = Object.values(f2).includes(''); |
245 | if (findNoneVal) { | 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 | deviceConfigAddOrEdit(isEdit.value ? noEditObj : postDeviceConfogData) | 260 | deviceConfigAddOrEdit(isEdit.value ? noEditObj : postDeviceConfogData) |
257 | .then((res) => { | 261 | .then((res) => { |
258 | if (!res) return; | 262 | if (!res) return; |
@@ -32,7 +32,7 @@ export const CoapSchemas: FormSchema[] = [ | @@ -32,7 +32,7 @@ export const CoapSchemas: FormSchema[] = [ | ||
32 | { | 32 | { |
33 | field: 'coapDeviceType', | 33 | field: 'coapDeviceType', |
34 | component: 'Select', | 34 | component: 'Select', |
35 | - label: 'CoAP 设备类型', | 35 | + label: 'CoAP设备类型', |
36 | defaultValue: 'DEFAULT', | 36 | defaultValue: 'DEFAULT', |
37 | componentProps: { | 37 | componentProps: { |
38 | options: [ | 38 | options: [ |
@@ -45,7 +45,7 @@ export const CoapSchemas: FormSchema[] = [ | @@ -45,7 +45,7 @@ export const CoapSchemas: FormSchema[] = [ | ||
45 | { | 45 | { |
46 | field: 'transportPayloadType', | 46 | field: 'transportPayloadType', |
47 | component: 'Select', | 47 | component: 'Select', |
48 | - label: 'CoAP 设备消息 Payload', | 48 | + label: 'CoAP设备消息Payload', |
49 | defaultValue: 'JSON', | 49 | defaultValue: 'JSON', |
50 | componentProps: { | 50 | componentProps: { |
51 | options: [ | 51 | options: [ |
@@ -59,7 +59,7 @@ export const CoapSchemas: FormSchema[] = [ | @@ -59,7 +59,7 @@ export const CoapSchemas: FormSchema[] = [ | ||
59 | { | 59 | { |
60 | field: 'powerMode', | 60 | field: 'powerMode', |
61 | component: 'Select', | 61 | component: 'Select', |
62 | - label: 'Power Saving Mode', | 62 | + label: '节能模式', |
63 | defaultValue: 'DRX', | 63 | defaultValue: 'DRX', |
64 | componentProps: { | 64 | componentProps: { |
65 | options: [ | 65 | options: [ |
@@ -76,11 +76,11 @@ export const CoapSchemas: FormSchema[] = [ | @@ -76,11 +76,11 @@ export const CoapSchemas: FormSchema[] = [ | ||
76 | { | 76 | { |
77 | field: 'psmActivityTimer', | 77 | field: 'psmActivityTimer', |
78 | component: 'InputNumber', | 78 | component: 'InputNumber', |
79 | - label: 'PSM Activity Timer', | 79 | + label: 'PSM活动计时器', |
80 | required: true, | 80 | required: true, |
81 | - defaultValue: '10', | 81 | + defaultValue: 10, |
82 | componentProps: { | 82 | componentProps: { |
83 | - placeholder: '请输入PSM Activity Timer', | 83 | + placeholder: '请输入PSM活动计时器', |
84 | }, | 84 | }, |
85 | colProps: { span: 11 }, | 85 | colProps: { span: 11 }, |
86 | ifShow: ({ values }) => isPsm(values.powerMode), | 86 | ifShow: ({ values }) => isPsm(values.powerMode), |
@@ -104,11 +104,11 @@ export const CoapSchemas: FormSchema[] = [ | @@ -104,11 +104,11 @@ export const CoapSchemas: FormSchema[] = [ | ||
104 | { | 104 | { |
105 | field: 'edrxCycle', | 105 | field: 'edrxCycle', |
106 | component: 'InputNumber', | 106 | component: 'InputNumber', |
107 | - label: 'eDRX cycle', | 107 | + label: 'eDRX循环', |
108 | required: true, | 108 | required: true, |
109 | - defaultValue: '81', | 109 | + defaultValue: 81, |
110 | componentProps: { | 110 | componentProps: { |
111 | - placeholder: '请输入PSM Activity Timer', | 111 | + placeholder: '请输入eDRX循环', |
112 | }, | 112 | }, |
113 | colProps: { span: 11 }, | 113 | colProps: { span: 11 }, |
114 | ifShow: ({ values }) => isDrx(values.powerMode), | 114 | ifShow: ({ values }) => isDrx(values.powerMode), |
@@ -132,11 +132,11 @@ export const CoapSchemas: FormSchema[] = [ | @@ -132,11 +132,11 @@ export const CoapSchemas: FormSchema[] = [ | ||
132 | { | 132 | { |
133 | field: 'pagingTransmissionWindow', | 133 | field: 'pagingTransmissionWindow', |
134 | component: 'InputNumber', | 134 | component: 'InputNumber', |
135 | - label: 'Paging Transmission Window', | 135 | + label: '寻呼传输窗口', |
136 | required: true, | 136 | required: true, |
137 | - defaultValue: '10', | 137 | + defaultValue: 10, |
138 | componentProps: { | 138 | componentProps: { |
139 | - placeholder: '请输入Paging Transmission Window', | 139 | + placeholder: '请输入寻呼传输窗口', |
140 | }, | 140 | }, |
141 | colProps: { span: 11 }, | 141 | colProps: { span: 11 }, |
142 | ifShow: ({ values }) => isDrx(values.powerMode), | 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,7 +236,6 @@ export const deviceSchemas: FormSchema[] = [ | ||
236 | colProps: { span: 22 }, | 236 | colProps: { span: 22 }, |
237 | component: 'InputTextArea', | 237 | component: 'InputTextArea', |
238 | componentProps: { | 238 | componentProps: { |
239 | - disabled: true, | ||
240 | autoSize: { | 239 | autoSize: { |
241 | maxRows: 50, | 240 | maxRows: 50, |
242 | }, | 241 | }, |
@@ -250,45 +249,31 @@ export const deviceSchemas: FormSchema[] = [ | @@ -250,45 +249,31 @@ export const deviceSchemas: FormSchema[] = [ | ||
250 | "keyName": {}, | 249 | "keyName": {}, |
251 | "attributeLwm2m": {} | 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 | "bootstrapServerIs": false, | 255 | "bootstrapServerIs": false, |
274 | "host": "0.0.0.0", | 256 | "host": "0.0.0.0", |
275 | "port": 5685, | 257 | "port": 5685, |
276 | - "securityHost": "0.0.0.0", | ||
277 | - "securityPort": 5686, | ||
278 | - "serverId": 123, | ||
279 | "clientHoldOffTime": 1, | 258 | "clientHoldOffTime": 1, |
280 | "serverPublicKey": "", | 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 | "clientLwM2mSettings": { | 269 | "clientLwM2mSettings": { |
285 | "clientOnlyObserveAfterConnect": 1, | 270 | "clientOnlyObserveAfterConnect": 1, |
286 | "fwUpdateStrategy": 1, | 271 | "fwUpdateStrategy": 1, |
287 | - "swUpdateStrategy": 2, | ||
288 | - "swUpdateResource": "coap://localhost:5685", | 272 | + "swUpdateStrategy": 1, |
289 | "powerMode": "DRX", | 273 | "powerMode": "DRX", |
290 | "compositeOperationsSupport": false | 274 | "compositeOperationsSupport": false |
291 | }, | 275 | }, |
276 | + "bootstrapServerUpdateEnable": false, | ||
292 | "type": "LWM2M" | 277 | "type": "LWM2M" |
293 | } | 278 | } |
294 | `, | 279 | `, |
@@ -11,29 +11,44 @@ | @@ -11,29 +11,44 @@ | ||
11 | </TabPane> | 11 | </TabPane> |
12 | <TabPane forceRender key="2" tab="Bootstrap"> | 12 | <TabPane forceRender key="2" tab="Bootstrap"> |
13 | <div> | 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 | v-for="(item, index) in dynamicBOOTSTRAP.bootstrap" | 20 | v-for="(item, index) in dynamicBOOTSTRAP.bootstrap" |
17 | :key="item" | 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 | </Button> | 33 | </Button> |
27 | </template> | 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 | <div style="margin-top: 2vh"> | 45 | <div style="margin-top: 2vh"> |
32 | - <Button size="middle" type="dashed" @click="handleAdd"> | 46 | + <Button size="middle" type="text" @click="handleAdd"> |
33 | <template #icon> | 47 | <template #icon> |
34 | <PlusCircleOutlined /> | 48 | <PlusCircleOutlined /> |
35 | </template> | 49 | </template> |
36 | - Add LwM2M Server | 50 | + <span v-if="selectCheckStatus">Add server config</span> |
51 | + <span v-else>Add LwM2M Server</span> | ||
37 | </Button> | 52 | </Button> |
38 | </div> | 53 | </div> |
39 | </div> | 54 | </div> |
@@ -61,6 +76,7 @@ | @@ -61,6 +76,7 @@ | ||
61 | <BasicForm :showResetButton="false" :showSubmitButton="false" @register="registerDevice" /> | 76 | <BasicForm :showResetButton="false" :showSubmitButton="false" @register="registerDevice" /> |
62 | </TabPane> | 77 | </TabPane> |
63 | </Tabs> | 78 | </Tabs> |
79 | + <ServerConfigModal @register="registerModal" @emitSelect="acceptEmitFunc" /> | ||
64 | </div> | 80 | </div> |
65 | </template> | 81 | </template> |
66 | 82 | ||
@@ -70,8 +86,11 @@ | @@ -70,8 +86,11 @@ | ||
70 | import { BasicForm, useForm } from '/@/components/Form'; | 86 | import { BasicForm, useForm } from '/@/components/Form'; |
71 | import { modelSchemas, settingsSchemas, deviceSchemas } from './index'; | 87 | import { modelSchemas, settingsSchemas, deviceSchemas } from './index'; |
72 | import BootStrapForm from './cpns/BootStrapForm.vue'; | 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 | import { Button, Checkbox } from 'ant-design-vue'; | 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 | export default defineComponent({ | 95 | export default defineComponent({ |
77 | name: 'index', | 96 | name: 'index', |
@@ -80,16 +99,21 @@ | @@ -80,16 +99,21 @@ | ||
80 | TabPane: Tabs.TabPane, | 99 | TabPane: Tabs.TabPane, |
81 | BasicForm, | 100 | BasicForm, |
82 | BootStrapForm, | 101 | BootStrapForm, |
83 | - MinusCircleOutlined, | 102 | + DeleteOutlined, |
84 | Card, | 103 | Card, |
85 | PlusCircleOutlined, | 104 | PlusCircleOutlined, |
86 | Button, | 105 | Button, |
87 | Checkbox, | 106 | Checkbox, |
107 | + CollapseContainer, | ||
108 | + ServerConfigModal, | ||
88 | }, | 109 | }, |
89 | setup() { | 110 | setup() { |
111 | + const collapseTitle = (item) => { | ||
112 | + return `LwM2M Server Short server ID: ${item.shortServerId} Security config mode: ${item.securityMode}`; | ||
113 | + }; | ||
90 | const bootstrapServerUpdateEnable = ref(false); | 114 | const bootstrapServerUpdateEnable = ref(false); |
91 | const dynamicBOOTSTRAP: any = reactive({ | 115 | const dynamicBOOTSTRAP: any = reactive({ |
92 | - bootstrap: [{}], | 116 | + bootstrap: [{ securityMode: 'NO_SEC', shortServerId: 1234 }], |
93 | }); | 117 | }); |
94 | const dynamicBindRef: any = { | 118 | const dynamicBindRef: any = { |
95 | BootStrapFormItemRef: ref([]), | 119 | BootStrapFormItemRef: ref([]), |
@@ -140,9 +164,20 @@ | @@ -140,9 +164,20 @@ | ||
140 | span: 14, | 164 | span: 14, |
141 | }, | 165 | }, |
142 | }); | 166 | }); |
167 | + const [registerModal, { openModal }] = useModal(); | ||
143 | 168 | ||
144 | const handleAdd = () => { | 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 | const handleRemove = (index) => { | 182 | const handleRemove = (index) => { |
148 | dynamicBOOTSTRAP.bootstrap.splice(index, 1); | 183 | dynamicBOOTSTRAP.bootstrap.splice(index, 1); |
@@ -240,7 +275,32 @@ | @@ -240,7 +275,32 @@ | ||
240 | resetDeviceValue(); | 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 | return { | 304 | return { |
245 | currentKey, | 305 | currentKey, |
246 | currentSize, | 306 | currentSize, |
@@ -256,6 +316,11 @@ | @@ -256,6 +316,11 @@ | ||
256 | handleAdd, | 316 | handleAdd, |
257 | handleRemove, | 317 | handleRemove, |
258 | bootstrapServerUpdateEnable, | 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,7 +20,7 @@ export const MqttSchemas: FormSchema[] = [ | ||
20 | { | 20 | { |
21 | field: 'deviceTelemetryTopic', | 21 | field: 'deviceTelemetryTopic', |
22 | component: 'Input', | 22 | component: 'Input', |
23 | - label: '遥测数据 topic 筛选器', | 23 | + label: '遥测数据主题筛选器', |
24 | required: true, | 24 | required: true, |
25 | defaultValue: 'v1/devices/me/telemetry', | 25 | defaultValue: 'v1/devices/me/telemetry', |
26 | componentProps: { | 26 | componentProps: { |
@@ -32,7 +32,7 @@ export const MqttSchemas: FormSchema[] = [ | @@ -32,7 +32,7 @@ export const MqttSchemas: FormSchema[] = [ | ||
32 | field: 'deviceAttributesTopic', | 32 | field: 'deviceAttributesTopic', |
33 | component: 'Input', | 33 | component: 'Input', |
34 | required: true, | 34 | required: true, |
35 | - label: 'Attributes topic filter', | 35 | + label: '属性主题过滤器', |
36 | defaultValue: 'v1/devices/me/attributes', | 36 | defaultValue: 'v1/devices/me/attributes', |
37 | componentProps: { | 37 | componentProps: { |
38 | placeholder: '请输入Attributes topic 筛选器', | 38 | placeholder: '请输入Attributes topic 筛选器', |
@@ -64,7 +64,14 @@ export const MqttSchemas: FormSchema[] = [ | @@ -64,7 +64,14 @@ export const MqttSchemas: FormSchema[] = [ | ||
64 | colProps: { span: 23 }, | 64 | colProps: { span: 23 }, |
65 | defaultValue: false, | 65 | defaultValue: false, |
66 | component: 'Checkbox', | 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 | ifShow: ({ values }) => isProtobuf(values.transportPayloadType), | 75 | ifShow: ({ values }) => isProtobuf(values.transportPayloadType), |
69 | }, | 76 | }, |
70 | { | 77 | { |
@@ -73,7 +80,16 @@ export const MqttSchemas: FormSchema[] = [ | @@ -73,7 +80,16 @@ export const MqttSchemas: FormSchema[] = [ | ||
73 | colProps: { span: 23 }, | 80 | colProps: { span: 23 }, |
74 | defaultValue: false, | 81 | defaultValue: false, |
75 | component: 'Checkbox', | 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 | ifShow: ({ values }) => | 93 | ifShow: ({ values }) => |
78 | isProtobuf(values.transportPayloadType) && | 94 | isProtobuf(values.transportPayloadType) && |
79 | !!values.useJsonPayloadFormatForDefaultDownlinkTopics, | 95 | !!values.useJsonPayloadFormatForDefaultDownlinkTopics, |
@@ -19,6 +19,28 @@ | @@ -19,6 +19,28 @@ | ||
19 | </p> | 19 | </p> |
20 | </div> | 20 | </div> |
21 | </template> | 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 | </BasicForm> | 44 | </BasicForm> |
23 | </div> | 45 | </div> |
24 | </div> | 46 | </div> |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <div | 2 | <div |
3 | v-for="(param, index) in dynamicInput.params" | 3 | v-for="(param, index) in dynamicInput.params" |
4 | :key="index" | 4 | :key="index" |
5 | - style="display: flex; margin-top: 0.2vh" | 5 | + style="display: flex; margin-top: 0.25vh" |
6 | > | 6 | > |
7 | <Select | 7 | <Select |
8 | v-model:value="param.dataType" | 8 | v-model:value="param.dataType" |
@@ -17,7 +17,7 @@ | @@ -17,7 +17,7 @@ | ||
17 | @change="emitChange" | 17 | @change="emitChange" |
18 | /> | 18 | /> |
19 | <a-input | 19 | <a-input |
20 | - placeholder="请输入OID" | 20 | + placeholder="请输入OID(不能重复)" |
21 | v-model:value="param.oid" | 21 | v-model:value="param.oid" |
22 | style="width: 38%; margin: 0 0 5px 8px" | 22 | style="width: 38%; margin: 0 0 5px 8px" |
23 | @change="emitChange" | 23 | @change="emitChange" |
@@ -30,7 +30,7 @@ | @@ -30,7 +30,7 @@ | ||
30 | /> | 30 | /> |
31 | </div> | 31 | </div> |
32 | <div> | 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 | <PlusCircleOutlined /> | 34 | <PlusCircleOutlined /> |
35 | Add mapping | 35 | Add mapping |
36 | </a-button> | 36 | </a-button> |
@@ -16,10 +16,12 @@ | @@ -16,10 +16,12 @@ | ||
16 | > | 16 | > |
17 | <InputNumber | 17 | <InputNumber |
18 | v-if="item.spec == 'TELEMETRY_QUERYING' || item.spec == 'CLIENT_ATTRIBUTES_QUERYING'" | 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 | <MappingsForm | 25 | <MappingsForm |
24 | :value="item.mappings" | 26 | :value="item.mappings" |
25 | @change="handleMappingsChange" | 27 | @change="handleMappingsChange" |
@@ -33,7 +35,7 @@ | @@ -33,7 +35,7 @@ | ||
33 | style="text-align: center; line-height: 20vh" | 35 | style="text-align: center; line-height: 20vh" |
34 | :style="{ lineHeight: dynamicHeight + 'vh' }" | 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 | <template #icon> | 39 | <template #icon> |
38 | <MinusCircleOutlined /> | 40 | <MinusCircleOutlined /> |
39 | </template> | 41 | </template> |
@@ -47,6 +49,7 @@ | @@ -47,6 +49,7 @@ | ||
47 | import { Button, InputNumber } from 'ant-design-vue'; | 49 | import { Button, InputNumber } from 'ant-design-vue'; |
48 | import { MinusCircleOutlined } from '@ant-design/icons-vue'; | 50 | import { MinusCircleOutlined } from '@ant-design/icons-vue'; |
49 | import MappingsForm from './MappingsForm.vue'; | 51 | import MappingsForm from './MappingsForm.vue'; |
52 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
50 | 53 | ||
51 | const props = defineProps({ | 54 | const props = defineProps({ |
52 | item: { | 55 | item: { |
@@ -59,7 +62,8 @@ | @@ -59,7 +62,8 @@ | ||
59 | }); | 62 | }); |
60 | const emit = defineEmits(['removeItem']); | 63 | const emit = defineEmits(['removeItem']); |
61 | const dynamicHeight = ref(25); | 64 | const dynamicHeight = ref(25); |
62 | - | 65 | + const queryingFrequencyMs = ref(5000); |
66 | + const { createMessage } = useMessage(); | ||
63 | const handleMappingsChange = (e) => { | 67 | const handleMappingsChange = (e) => { |
64 | // eslint-disable-next-line vue/no-mutating-props | 68 | // eslint-disable-next-line vue/no-mutating-props |
65 | props.item.mappings = e; | 69 | props.item.mappings = e; |
@@ -70,9 +74,18 @@ | @@ -70,9 +74,18 @@ | ||
70 | //设置回显的高度 | 74 | //设置回显的高度 |
71 | const setFieldsValueFunc = () => { | 75 | const setFieldsValueFunc = () => { |
72 | dynamicHeight.value = props.item.mappings.length * 3 + props.item.mappings.length + 15; | 76 | dynamicHeight.value = props.item.mappings.length * 3 + props.item.mappings.length + 15; |
77 | + queryingFrequencyMs.value = props.item.queryingFrequencyMs; | ||
73 | }; | 78 | }; |
74 | //获取表单的值 | 79 | //获取表单的值 |
75 | const getSnmpFormFunc = () => { | 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 | let obj: any = {}; | 89 | let obj: any = {}; |
77 | obj = { | 90 | obj = { |
78 | ...{ spec: props.item.spec }, | 91 | ...{ spec: props.item.spec }, |
@@ -80,7 +93,7 @@ | @@ -80,7 +93,7 @@ | ||
80 | ...{ | 93 | ...{ |
81 | queryingFrequencyMs: | 94 | queryingFrequencyMs: |
82 | props.item.spec == 'TELEMETRY_QUERYING' || props.item.spec == 'CLIENT_ATTRIBUTES_QUERYING' | 95 | props.item.spec == 'TELEMETRY_QUERYING' || props.item.spec == 'CLIENT_ATTRIBUTES_QUERYING' |
83 | - ? props.item.queryingFrequencyMs | 96 | + ? queryingFrequencyMs.value |
84 | : null, | 97 | : null, |
85 | }, | 98 | }, |
86 | }; | 99 | }; |
@@ -27,22 +27,14 @@ | @@ -27,22 +27,14 @@ | ||
27 | :options="selectOptions" | 27 | :options="selectOptions" |
28 | @change="handleChange(item.spec)" | 28 | @change="handleChange(item.spec)" |
29 | allowClear | 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 | </SnmpForm> | 31 | </SnmpForm> |
40 | </template> | 32 | </template> |
41 | <div | 33 | <div |
42 | v-if="dynamicSNMP.communicationConfigs.length < 4" | 34 | v-if="dynamicSNMP.communicationConfigs.length < 4" |
43 | style="margin-left: 0vw; margin-top: 2vh" | 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 | <template #icon> | 38 | <template #icon> |
47 | <PlusCircleOutlined /> | 39 | <PlusCircleOutlined /> |
48 | </template> | 40 | </template> |
@@ -60,7 +52,7 @@ | @@ -60,7 +52,7 @@ | ||
60 | import { BasicForm, useForm } from '/@/components/Form'; | 52 | import { BasicForm, useForm } from '/@/components/Form'; |
61 | import { snmpSchemas } from './config'; | 53 | import { snmpSchemas } from './config'; |
62 | import { PlusCircleOutlined } from '@ant-design/icons-vue'; | 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 | import SnmpForm from './cpns/SnmpForm.vue'; | 56 | import SnmpForm from './cpns/SnmpForm.vue'; |
65 | 57 | ||
66 | interface mappingsI { | 58 | interface mappingsI { |
@@ -77,26 +69,22 @@ | @@ -77,26 +69,22 @@ | ||
77 | { | 69 | { |
78 | label: 'Telemetry', | 70 | label: 'Telemetry', |
79 | value: 'TELEMETRY_QUERYING', | 71 | value: 'TELEMETRY_QUERYING', |
80 | - disabled: false, | ||
81 | }, | 72 | }, |
82 | { | 73 | { |
83 | label: 'Client attributes', | 74 | label: 'Client attributes', |
84 | value: 'CLIENT_ATTRIBUTES_QUERYING', | 75 | value: 'CLIENT_ATTRIBUTES_QUERYING', |
85 | - disabled: false, | ||
86 | }, | 76 | }, |
87 | { | 77 | { |
88 | label: 'Shared attributes', | 78 | label: 'Shared attributes', |
89 | value: 'SHARED_ATTRIBUTES_SETTING', | 79 | value: 'SHARED_ATTRIBUTES_SETTING', |
90 | - disabled: false, | ||
91 | }, | 80 | }, |
92 | { | 81 | { |
93 | label: 'RPC request', | 82 | label: 'RPC request', |
94 | value: 'TO_DEVICE_RPC_REQUEST', | 83 | value: 'TO_DEVICE_RPC_REQUEST', |
95 | - disabled: false, | ||
96 | }, | 84 | }, |
97 | ]); | 85 | ]); |
98 | const selectValue = ref(''); | 86 | const selectValue = ref(''); |
99 | - //解决新增选择框互斥问题和编辑回显互斥问题 | 87 | + //Select互斥 |
100 | const handleChange = (value: string) => { | 88 | const handleChange = (value: string) => { |
101 | selectValue.value = value; | 89 | selectValue.value = value; |
102 | selectOptions.value.forEach((ele: any) => { | 90 | selectOptions.value.forEach((ele: any) => { |
@@ -154,12 +142,15 @@ | @@ -154,12 +142,15 @@ | ||
154 | }); | 142 | }); |
155 | }; | 143 | }; |
156 | const handleRemoveItem = (item, index) => { | 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 | dynamicSNMP.communicationConfigs.splice(index, 1); | 150 | dynamicSNMP.communicationConfigs.splice(index, 1); |
160 | }; | 151 | }; |
152 | + | ||
161 | //回显表单值 | 153 | //回显表单值 |
162 | - //TODO 采用这种方式动态绑定ref 都会造成回显数据顺序是随机的但不影响回显数据的正确性 | ||
163 | const setStepFieldsValueFunc = (v) => { | 154 | const setStepFieldsValueFunc = (v) => { |
164 | setFieldsValue({ | 155 | setFieldsValue({ |
165 | timeoutMs: v.timeoutMs, | 156 | timeoutMs: v.timeoutMs, |
@@ -168,14 +159,13 @@ | @@ -168,14 +159,13 @@ | ||
168 | dynamicSNMP.communicationConfigs = v.communicationConfigs; | 159 | dynamicSNMP.communicationConfigs = v.communicationConfigs; |
169 | dynamicSNMP.communicationConfigs.forEach((snmp, index: number) => { | 160 | dynamicSNMP.communicationConfigs.forEach((snmp, index: number) => { |
170 | nextTick(() => { | 161 | nextTick(() => { |
171 | - //编辑回显互斥赋值 | ||
172 | handleChange(snmp.spec); | 162 | handleChange(snmp.spec); |
173 | unref(dynamicBindRef.SnmpFormItemRef)[index]?.setFieldsValueFunc(); | 163 | unref(dynamicBindRef.SnmpFormItemRef)[index]?.setFieldsValueFunc(); |
174 | }); | 164 | }); |
175 | }); | 165 | }); |
176 | }; | 166 | }; |
177 | 167 | ||
178 | - //获取最终的值 | 168 | + //获取表单值 |
179 | const getSnmpForm = async () => { | 169 | const getSnmpForm = async () => { |
180 | let value = await validate(); | 170 | let value = await validate(); |
181 | if (!value) return; | 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,11 +292,15 @@ | ||
292 | return createMessage.error('请填写属性'); | 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 | } catch { | 305 | } catch { |
302 | } finally { | 306 | } finally { |