Commit 9ade13f08435294666d1eed23eb1e97bd59bd196

Authored by xp.Huang
2 parents bcc0a93c 28210ab1

Merge branch 'sqy_dev' into 'main'

feat:设备凭证管理,地图历史数据页面编写

See merge request huang/yun-teng-iot-front!60
... ... @@ -21,9 +21,7 @@
21 21 theme = htmlRoot = null;
22 22 }
23 23 })();
24   - const historyClick = () => {
25   - console.log(1);
26   - };
  24 +
27 25 </script>
28 26 <div id="app">
29 27 <style>
... ... @@ -163,6 +161,7 @@
163 161 </div>
164 162 </div>
165 163 </div>
  164 +
166 165 <script type="module" src="/src/main.ts"></script>
167 166 </body>
168 167 </html>
... ...
... ... @@ -19,6 +19,8 @@ enum DeviceManagerApi {
19 19 DEVICE_PROFILE_URL_ME = '/deviceProfile/me/list',
20 20
21 21 DEVICE_ALARM_URL = '/alarm',
  22 +
  23 + DEVICE_CREDENTIALS = '/device/credentials',
22 24 }
23 25
24 26 export const devicePage = (params: DeviceQueryParam) => {
... ... @@ -108,3 +110,29 @@ export const clearOrAckAlarm = (id: string, flag: boolean) => {
108 110 }
109 111 );
110 112 };
  113 +
  114 +// 获取设备凭证详情
  115 +export const getDeviceToken = (id: string) => {
  116 + return defHttp.get(
  117 + {
  118 + url: `${DeviceManagerApi.DEVICE_URL}/${id}/credentials`,
  119 + },
  120 + {
  121 + joinPrefix: false,
  122 + }
  123 + );
  124 +};
  125 +
  126 +// 保存设备凭证详情
  127 +
  128 +export const saveDeviceToken = (data) => {
  129 + return defHttp.post(
  130 + {
  131 + url: DeviceManagerApi.DEVICE_CREDENTIALS,
  132 + data,
  133 + },
  134 + {
  135 + joinPrefix: false,
  136 + }
  137 + );
  138 +};
... ...
... ... @@ -35,10 +35,12 @@ export const alarmSearchSchemas: FormSchema[] = [
35 35 colProps: { span: 6 },
36 36 },
37 37 {
38   - field: 'time',
  38 + field: 'endTime',
39 39 label: '告警时间范围',
40   - component: 'RangePicker',
41   - componentProps: {},
  40 + component: 'DatePicker',
  41 + componentProps: {
  42 + valueFormat: 'x',
  43 + },
42 44 colProps: { span: 6 },
43 45 },
44 46 ];
... ...
... ... @@ -38,7 +38,6 @@
38 38 formConfig: {
39 39 labelWidth: 120,
40 40 schemas: alarmSearchSchemas,
41   - fieldMapToTime: [['time', ['startTime', 'endTime'], 'x']],
42 41 },
43 42 useSearchForm: true,
44 43 bordered: true,
... ...
... ... @@ -3,6 +3,15 @@ import type { FormSchema } from '/@/components/Table';
3 3 import { getOrganizationList } from '/@/api/system/system';
4 4 import { copyTransFun } from '/@/utils/fnUtils';
5 5 import { getDeviceProfile } from '/@/api/alarm/position';
  6 +
  7 +export enum AggregateDataEnum {
  8 + MIN = 'MIN',
  9 + MAX = 'MAX',
  10 + AVG = 'AVG',
  11 + SUM = 'SUM',
  12 + COUNT = 'COUNT',
  13 + NONE = 'NONE',
  14 +}
6 15 export const formSchema: FormSchema[] = [
7 16 {
8 17 field: 'organizationId',
... ... @@ -82,3 +91,114 @@ export const columns: BasicColumn[] = [
82 91 slots: { customRender: 'deviceState' },
83 92 },
84 93 ];
  94 +
  95 +export const schemas: FormSchema[] = [
  96 + {
  97 + field: 'name',
  98 + label: '最后数据',
  99 + component: 'Select',
  100 + componentProps: {
  101 + options: [
  102 + {
  103 + label: '最近1小时',
  104 + value: '1',
  105 + },
  106 + {
  107 + label: '最近2小时',
  108 + value: '2',
  109 + },
  110 + {
  111 + label: '最近5小时',
  112 + value: '5',
  113 + },
  114 + {
  115 + label: '最近12小时',
  116 + value: '12',
  117 + },
  118 + {
  119 + label: '最近1天',
  120 + value: '1天',
  121 + },
  122 + {
  123 + label: '最近7天',
  124 + value: '7天',
  125 + },
  126 + {
  127 + label: '最近30天',
  128 + value: '30天',
  129 + },
  130 + ],
  131 + },
  132 + colProps: {
  133 + span: 6,
  134 + },
  135 + },
  136 + {
  137 + field: 'name',
  138 + label: '分组间隔',
  139 + component: 'Select',
  140 + componentProps: {
  141 + options: [
  142 + {
  143 + label: '15秒',
  144 + value: '15',
  145 + },
  146 + {
  147 + label: '30秒',
  148 + value: '30',
  149 + },
  150 + {
  151 + label: '1分钟',
  152 + value: '60',
  153 + },
  154 + {
  155 + label: '2分钟',
  156 + value: '120',
  157 + },
  158 + {
  159 + label: '5分钟',
  160 + value: '360',
  161 + },
  162 + {
  163 + label: '10分钟',
  164 + value: '600',
  165 + },
  166 + {
  167 + label: '15分钟',
  168 + value: '900',
  169 + },
  170 + ],
  171 + },
  172 + colProps: {
  173 + span: 6,
  174 + },
  175 + },
  176 + {
  177 + field: 'name',
  178 + label: '数据聚合功能',
  179 + component: 'Select',
  180 + componentProps: {
  181 + options: [
  182 + {
  183 + label: '最小值',
  184 + value: AggregateDataEnum.MIN,
  185 + },
  186 + {
  187 + label: '最大值',
  188 + value: AggregateDataEnum.MAX,
  189 + },
  190 + {
  191 + label: '平均值',
  192 + value: AggregateDataEnum.AVG,
  193 + },
  194 + {
  195 + label: '求和',
  196 + value: AggregateDataEnum.SUM,
  197 + },
  198 + ],
  199 + },
  200 + colProps: {
  201 + span: 6,
  202 + },
  203 + },
  204 +];
... ...
... ... @@ -22,9 +22,13 @@
22 22 : '离线'
23 23 }}
24 24 </Tag>
25   - </template></BasicTable
26   - >
  25 + </template>
  26 + </BasicTable>
27 27 </div>
  28 + <a-button type="primary" @click="handleClick">打开弹窗</a-button>
  29 + <BasicModal @register="registerModal" title="历史数据" width="70%">
  30 + <BasicForm @register="registerForm" />
  31 + </BasicModal>
28 32 </div>
29 33 </template>
30 34 <script lang="ts">
... ... @@ -36,11 +40,16 @@
36 40 import { Tag } from 'ant-design-vue';
37 41 import { DeviceState } from '/@/api/device/model/deviceModel';
38 42 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
  43 + import { useModal, BasicModal } from '/@/components/Modal';
  44 + import { BasicForm, useForm } from '/@/components/Form';
  45 + import { schemas } from './config.data';
39 46 export default defineComponent({
40 47 name: 'BaiduMap',
41 48 components: {
42 49 BasicTable,
43 50 Tag,
  51 + BasicModal,
  52 + BasicForm,
44 53 },
45 54 props: {
46 55 width: {
... ... @@ -151,11 +160,23 @@
151 160 console.log(record);
152 161 };
153 162
  163 + const [registerModal, { openModal }] = useModal();
  164 + const [registerForm] = useForm({
  165 + labelWidth: 120,
  166 + schemas,
  167 + });
  168 + const handleClick = () => {
  169 + openModal(true);
  170 + };
  171 +
154 172 return {
155 173 wrapRef,
156 174 registerTable,
157 175 deviceRowClick,
158 176 DeviceState,
  177 + handleClick,
  178 + registerModal,
  179 + registerForm,
159 180 };
160 181 },
161 182 });
... ...
... ... @@ -268,8 +268,185 @@ export const step2Schemas: FormSchema[] = [
268 268 },
269 269 {
270 270 label: '密码',
  271 + component: 'InputPassword',
  272 + field: 'password',
  273 + ifShow: false,
  274 + },
  275 +];
  276 +
  277 +// 管理凭证的表单配置项
  278 +export const TokenSchemas: FormSchema[] = [
  279 + {
  280 + label: '凭据类型',
  281 + component: 'Select',
  282 + field: 'credentialType',
  283 + required: true,
  284 + componentProps({ formActionType }) {
  285 + const { updateSchema, setFieldsValue } = formActionType;
  286 + return {
  287 + options: [
  288 + {
  289 + value: credentialTypeEnum.ACCESS_TOKEN,
  290 + label: 'Access Token',
  291 + },
  292 + {
  293 + value: credentialTypeEnum.X_509,
  294 + label: 'X.509',
  295 + },
  296 + {
  297 + value: credentialTypeEnum.MQTT_BASIC,
  298 + label: 'MQTT Basic',
  299 + },
  300 + ],
  301 + onChange(value) {
  302 + setFieldsValue({
  303 + publicKey: '',
  304 + credentialsId: '',
  305 + clientId: '',
  306 + username: '',
  307 + password: '',
  308 + });
  309 + if (value === credentialTypeEnum.ACCESS_TOKEN) {
  310 + updateSchema([
  311 + {
  312 + field: 'credentialsId',
  313 + ifShow: true,
  314 + },
  315 + {
  316 + field: 'clientId',
  317 + ifShow: false,
  318 + },
  319 + {
  320 + field: 'username',
  321 + ifShow: false,
  322 + },
  323 + {
  324 + field: 'password',
  325 + ifShow: false,
  326 + },
  327 + {
  328 + field: 'publicKey',
  329 + ifShow: false,
  330 + },
  331 + ]);
  332 + } else if (value === credentialTypeEnum.X_509) {
  333 + updateSchema([
  334 + {
  335 + field: 'publicKey',
  336 + ifShow: true,
  337 + },
  338 + {
  339 + field: 'credentialsId',
  340 + ifShow: false,
  341 + },
  342 + {
  343 + field: 'clientId',
  344 + ifShow: false,
  345 + },
  346 + {
  347 + field: 'username',
  348 + ifShow: false,
  349 + },
  350 + {
  351 + field: 'password',
  352 + ifShow: false,
  353 + },
  354 + ]);
  355 + } else if (value === credentialTypeEnum.MQTT_BASIC) {
  356 + updateSchema([
  357 + {
  358 + field: 'clientId',
  359 + ifShow: true,
  360 + },
  361 + {
  362 + field: 'username',
  363 + ifShow: true,
  364 + },
  365 + {
  366 + field: 'password',
  367 + ifShow: true,
  368 + },
  369 + {
  370 + field: 'publicKey',
  371 + ifShow: false,
  372 + },
  373 + {
  374 + field: 'credentialsId',
  375 + ifShow: false,
  376 + },
  377 + ]);
  378 + } else {
  379 + updateSchema([
  380 + {
  381 + field: 'clientId',
  382 + ifShow: false,
  383 + },
  384 + {
  385 + field: 'username',
  386 + ifShow: false,
  387 + },
  388 + {
  389 + field: 'password',
  390 + ifShow: false,
  391 + },
  392 + {
  393 + field: 'publicKey',
  394 + ifShow: false,
  395 + },
  396 + {
  397 + field: 'credentialsId',
  398 + ifShow: false,
  399 + },
  400 + ]);
  401 + }
  402 + },
  403 + };
  404 + },
  405 + },
  406 + {
  407 + label: '访问令牌',
  408 + component: 'Input',
  409 + field: 'credentialsId',
  410 + required: true,
  411 + ifShow: false,
  412 + },
  413 + {
  414 + label: 'RSA公钥',
271 415 component: 'Input',
  416 + field: 'publicKey',
  417 + required: true,
  418 + ifShow: false,
  419 + },
  420 + {
  421 + label: '客户端ID',
  422 + component: 'Input',
  423 + field: 'clientId',
  424 + required: true,
  425 + ifShow: false,
  426 + },
  427 + {
  428 + label: '用户名',
  429 + component: 'Input',
  430 + field: 'username',
  431 + required: true,
  432 + ifShow: false,
  433 + },
  434 + {
  435 + label: '密码',
  436 + component: 'InputPassword',
272 437 field: 'password',
273 438 ifShow: false,
274 439 },
  440 + {
  441 + label: 'id',
  442 + component: 'Input',
  443 + field: 'id',
  444 + show: false,
  445 + },
  446 + {
  447 + label: 'tbDeviceId',
  448 + component: 'Input',
  449 + field: 'tbDeviceId',
  450 + show: false,
  451 + },
275 452 ];
... ...
... ... @@ -97,10 +97,12 @@ export const alarmSearchSchemas: FormSchema[] = [
97 97 colProps: { span: 6 },
98 98 },
99 99 {
100   - field: 'time',
  100 + field: 'endTime',
101 101 label: '告警时间范围',
102   - component: 'RangePicker',
103   - componentProps: {},
  102 + component: 'DatePicker',
  103 + componentProps: {
  104 + valueFormat: 'x',
  105 + },
104 106 colProps: { span: 6 },
105 107 },
106 108 ];
... ...
... ... @@ -49,11 +49,12 @@
49 49 const activeKey = ref('1');
50 50 const size = ref('small');
51 51 const deviceDetailRef = ref();
52   - const deviceDetail: any = ref({});
53   -
  52 + const deviceDetail = ref<any>({});
  53 + const tbDeviceId = ref('');
54 54 // 详情回显
55 55 const [register] = useDrawerInner(async (data) => {
56 56 const { id } = data;
  57 + // 设备详情
57 58 const res = await getDeviceDetail(id);
58 59 deviceDetail.value = res;
59 60 const { latitude, longitude, address } = res.deviceInfo;
... ... @@ -72,6 +73,7 @@
72 73 closeDrawer,
73 74 deviceDetail,
74 75 deviceDetailRef,
  76 + tbDeviceId,
75 77 };
76 78 },
77 79 });
... ...
... ... @@ -108,7 +108,6 @@
108 108 if (current.value === 0) {
109 109 // 验证
110 110 const valid = await unref(DeviceStep1Ref)?.parentValidate();
111   - console.log('123');
112 111 if (!valid) return;
113 112 stepState.value = unref(DeviceStep1Ref)?.parentGetFieldsValue();
114 113 } else {
... ...
1 1 <template>
2   - <BasicModal @register="registerModal" v-bind="$attrs" title="管理设备凭证">
3   - <BasicForm @register="registerForm" @cancel="handleCancel" @ok="handleOk">
4   - <template #addAgree="{ model, field }">
5   - <Checkbox v-model:checked="model[field]" @change="checkedChange" />
6   - <span class="ml-2">添加协议</span>
7   - </template>
8   - </BasicForm>
  2 + <BasicModal
  3 + @register="registerModal"
  4 + v-bind="$attrs"
  5 + title="管理设备凭证"
  6 + @cancel="handleCancel"
  7 + @ok="handleOk"
  8 + centered
  9 + >
  10 + <BasicForm @register="registerForm" />
9 11 </BasicModal>
10 12 </template>
11 13
12 14 <script lang="ts">
13 15 import { defineComponent } from 'vue';
14 16 import { useModalInner, BasicModal } from '/@/components/Modal';
15   - import { Checkbox } from 'ant-design-vue';
16 17 import { BasicForm, useForm } from '/@/components/Form';
17   - import { step2Schemas } from '../../config/data';
18   -
  18 + import { TokenSchemas, credentialTypeEnum } from '../../config/data';
  19 + import { saveDeviceToken } from '/@/api/device/deviceManager';
  20 + import { useMessage } from '/@/hooks/web/useMessage';
19 21 export default defineComponent({
20 22 components: {
21 23 BasicModal,
22 24 BasicForm,
23   - Checkbox,
24 25 },
25 26 emits: ['register'],
26 27 setup() {
27   - const [registerModal, { closeModal }] = useModalInner(async (data) => {
28   - console.log(data);
29   - });
30 28 const [
31 29 registerForm,
32 30 { getFieldsValue, updateSchema, validate, resetFields, setFieldsValue },
33 31 ] = useForm({
34 32 labelWidth: 100,
35   - schemas: step2Schemas,
36   - actionColOptions: {
37   - span: 14,
38   - },
39   - labelAlign: 'left',
  33 + schemas: TokenSchemas,
  34 + labelAlign: 'right',
40 35 showSubmitButton: false,
41 36 showResetButton: false,
42 37 wrapperCol: {
43 38 span: 12,
44 39 },
45 40 });
46   - const checkedChange = async (e) => {
  41 + const [registerModal, { closeModal }] = useModalInner(async (data) => {
  42 + if (data.credentialsType === credentialTypeEnum.ACCESS_TOKEN) {
  43 + updateSchema([
  44 + {
  45 + field: 'credentialType',
  46 + ifShow: true,
  47 + },
  48 + {
  49 + field: 'credentialsId',
  50 + ifShow: true,
  51 + },
  52 + ]);
  53 + setFieldsValue({
  54 + credentialType: data.credentialsType,
  55 + credentialsId: data.credentialsId,
  56 + id: data.id.id,
  57 + tbDeviceId: data.deviceId.id,
  58 + });
  59 + } else if (data.credentialsType === credentialTypeEnum.X_509) {
  60 + updateSchema([
  61 + {
  62 + field: 'credentialType',
  63 + ifShow: true,
  64 + },
  65 + {
  66 + field: 'publicKey',
  67 + ifShow: true,
  68 + },
  69 + ]);
  70 + setFieldsValue({
  71 + credentialType: data.credentialsType,
  72 + publicKey: data.credentialsValue,
  73 + id: data.id.id,
  74 + tbDeviceId: data.deviceId.id,
  75 + });
  76 + } else {
  77 + updateSchema([
  78 + {
  79 + field: 'credentialType',
  80 + ifShow: true,
  81 + },
  82 + {
  83 + field: 'clientId',
  84 + ifShow: true,
  85 + },
  86 + {
  87 + field: 'username',
  88 + ifShow: true,
  89 + },
  90 + {
  91 + field: 'password',
  92 + ifShow: true,
  93 + },
  94 + ]);
  95 + const credentialsValue = JSON.parse(data.credentialsValue);
  96 + setFieldsValue({
  97 + credentialType: data.credentialsType,
  98 + clientId: credentialsValue?.clientId,
  99 + username: credentialsValue?.userName,
  100 + password: credentialsValue?.password,
  101 + id: data.id.id,
  102 + tbDeviceId: data.deviceId.id,
  103 + });
  104 + }
  105 + });
  106 + const checkedChange = (e) => {
47 107 if (!e.target.checked) {
48   - await updateSchema([
  108 + updateSchema([
49 109 {
50 110 field: 'credentialsId',
51 111 ifShow: false,
... ... @@ -66,17 +126,84 @@
66 126 field: 'publicKey',
67 127 ifShow: false,
68 128 },
  129 + {
  130 + field: 'credentialType',
  131 + ifShow: false,
  132 + },
69 133 ]);
70 134 setFieldsValue({
71 135 credentialType: '',
72 136 });
  137 + } else {
  138 + updateSchema([
  139 + {
  140 + field: 'credentialType',
  141 + ifShow: true,
  142 + },
  143 + ]);
73 144 }
74 145 };
75 146 const handleCancel = () => {
76   - console.log('取消');
  147 + resetFields();
  148 +
  149 + updateSchema([
  150 + {
  151 + field: 'publicKey',
  152 + ifShow: false,
  153 + },
  154 + {
  155 + field: 'credentialsId',
  156 + ifShow: false,
  157 + },
  158 + {
  159 + field: 'clientId',
  160 + ifShow: false,
  161 + },
  162 + {
  163 + field: 'username',
  164 + ifShow: false,
  165 + },
  166 + {
  167 + field: 'password',
  168 + ifShow: false,
  169 + },
  170 + ]);
77 171 };
78   - const handleOk = () => {
79   - console.log('确定');
  172 + const handleOk = async () => {
  173 + // 验证
  174 + const valid = await validate();
  175 + if (!valid) return;
  176 + // 收集表单数据
  177 + const field = getFieldsValue();
  178 + const editData = {
  179 + id: {
  180 + id: field.id,
  181 + },
  182 + deviceId: {
  183 + id: field.tbDeviceId,
  184 + entityType: 'DEVICE',
  185 + },
  186 + credentialsType: field.credentialType,
  187 + credentialsId: field.credentialsId,
  188 + credentialsValue:
  189 + field.credentialType === credentialTypeEnum.MQTT_BASIC
  190 + ? JSON.stringify({
  191 + userName: field.username,
  192 + password: field.password,
  193 + clientId: field.clientId,
  194 + })
  195 + : field.credentialType === credentialTypeEnum.X_509
  196 + ? field.publicKey
  197 + : null,
  198 + };
  199 +
  200 + await saveDeviceToken(editData);
  201 + const { createMessage } = useMessage();
  202 + createMessage.success('修改设备凭证成功');
  203 +
  204 + // 请求
  205 + closeModal();
  206 + handleCancel();
80 207 };
81 208 return {
82 209 registerModal,
... ...
... ... @@ -43,7 +43,6 @@
43 43 formConfig: {
44 44 labelWidth: 120,
45 45 schemas: alarmSearchSchemas,
46   - fieldMapToTime: [['time', ['startTime', 'endTime'], 'x']],
47 46 },
48 47 useSearchForm: true,
49 48 bordered: true,
... ...
... ... @@ -37,6 +37,7 @@
37 37 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
38 38 import { useModal } from '/@/components/Modal';
39 39 import ManageDeviceTokenModal from '../modal/ManageDeviceTokenModal.vue';
  40 + import { getDeviceToken } from '/@/api/device/deviceManager';
40 41 export default defineComponent({
41 42 components: {
42 43 Image,
... ... @@ -90,16 +91,19 @@
90 91 createMessage.success('复制成功~');
91 92 }
92 93 };
93   - const copyDeviceToken = () => {
94   - // TODO:此处需要动态修改
95   - clipboardRef.value = props.deviceDetail.deviceToken;
96   - if (unref(clipboardRef)) {
97   - createMessage.success('复制成功~');
  94 + const copyDeviceToken = async () => {
  95 + const token = await getDeviceToken(props.deviceDetail.tbDeviceId);
  96 + if (token.credentialsType === 'ACCESS_TOKEN') {
  97 + clipboardRef.value = token.credentialsId;
  98 + } else {
  99 + clipboardRef.value = token.credentialsValue;
98 100 }
  101 + createMessage.success('复制成功~');
99 102 };
100 103 const [registerModal, { openModal }] = useModal();
101   - const manageDeviceToken = () => {
102   - openModal(true);
  104 + const manageDeviceToken = async () => {
  105 + const token = await getDeviceToken(props.deviceDetail.tbDeviceId);
  106 + openModal(true, token);
103 107 };
104 108
105 109 return {
... ...
... ... @@ -26,6 +26,7 @@
26 26 const state = reactive({
27 27 server: `ws://101.133.234.90:8080/api/ws/plugins/telemetry?token=${token}`,
28 28 sendValue: JSON.stringify({
  29 + attrSubCmds: [],
29 30 tsSubCmds: [
30 31 {
31 32 entityType: 'DEVICE',
... ... @@ -34,6 +35,13 @@
34 35 cmdId: 1,
35 36 },
36 37 ],
  38 + historyCmds: [],
  39 + entityDataCmds: [],
  40 + entityDataUnsubscribeCmds: [],
  41 + alarmDataCmds: [],
  42 + alarmDataUnsubscribeCmds: [],
  43 + entityCountCmds: [],
  44 + entityCountUnsubscribeCmds: [],
37 45 }),
38 46 recordList: Array<socketDataType>(),
39 47 });
... ... @@ -49,10 +57,12 @@
49 57 const { send } = useWebSocket(state.server, {
50 58 onConnected() {
51 59 send(state.sendValue);
  60 + console.log('建立连接了');
  61 + console.log(props.deviceDetail.tbDeviceId);
52 62 },
53 63 onMessage(_, e) {
54 64 const { data } = JSON.parse(e.data);
55   - console.log('来新消息了');
  65 + console.log('来新消息了', 'data---', data);
56 66 const newArray: any = [];
57 67 for (const key in data) {
58 68 const newData = data[key].flat(1);
... ...