Commit 0e2ab47486d2b63b13aaca31016647212e7e0077

Authored by xp.Huang
2 parents 34bb0833 40475988

Merge branch 'fix/DEFECT-2094' into 'v1.4.0_dev'

fix: 修复超级管理员在编辑租户配置时,数据格式不一致问题

See merge request yunteng/thingskit-front!1349
... ... @@ -132,6 +132,7 @@ export type ComponentType =
132 132 | 'ControlGroup'
133 133 | 'JSONEditor'
134 134 | 'OrgTreeSelect'
  135 + | 'CreateSpeed'
135 136 | 'ExtendDesc'
136 137 | 'JavaScriptFunctionEditor'
137 138 | 'JavascriptEditorWithTestModal'
... ...
... ... @@ -59,6 +59,7 @@ export const formSchema: FormSchema[] = [
59 59 field: 'isolatedTbRuleEngine',
60 60 label: '',
61 61 component: 'Checkbox',
  62 + ifShow: false,
62 63 renderComponentContent: () => {
63 64 return h('span', {}, [
64 65 h('span', {}, '隔离板芯容器中的加工'),
... ... @@ -70,6 +71,7 @@ export const formSchema: FormSchema[] = [
70 71 field: 'isolatedTbCore',
71 72 label: '',
72 73 component: 'Checkbox',
  74 + ifShow: false,
73 75 renderComponentContent: () => {
74 76 return h('span', {}, [
75 77 h('span', {}, '在独立的规则引擎中处理'),
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { alarmSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: alarmSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  2 +
  3 +import CreateSpeed from './createSpeed.vue';
  4 +useComponentRegister('CreateSpeed', CreateSpeed);
  5 +
  6 +export const entityFormSchema: FormSchema[] = [
  7 + {
  8 + field: 'maxDevices',
  9 + required: true,
  10 +
  11 + label: '最大设备数',
  12 + component: 'InputNumber',
  13 + defaultValue: '0',
  14 + colProps: { span: 12 },
  15 + componentProps: {
  16 + placeholder: '请输入最大设备数(请输入数字)',
  17 + },
  18 + },
  19 +
  20 + {
  21 + field: 'maxDashboards',
  22 + required: true,
  23 + defaultValue: '0',
  24 + label: '最大仪表板数',
  25 + colProps: { span: 12 },
  26 + component: 'InputNumber',
  27 + componentProps: {
  28 + placeholder: '请输入仪表板的最大数量(请输入数字)',
  29 + },
  30 + },
  31 + {
  32 + field: 'maxAssets',
  33 + required: true,
  34 + defaultValue: '0',
  35 + label: '最大资产数',
  36 + colProps: { span: 12 },
  37 + component: 'InputNumber',
  38 + componentProps: {
  39 + placeholder: '请输入最大资产(请输入数字)',
  40 + },
  41 + },
  42 + {
  43 + field: 'maxUsers',
  44 + required: true,
  45 + defaultValue: '0',
  46 + label: '最大用户数',
  47 + colProps: { span: 12 },
  48 + component: 'InputNumber',
  49 + componentProps: {
  50 + placeholder: '请输入最大用户数(请输入数字)',
  51 + },
  52 + },
  53 + {
  54 + field: 'maxCustomers',
  55 + required: true,
  56 + defaultValue: '0',
  57 +
  58 + label: '最大客户数',
  59 + colProps: { span: 12 },
  60 + component: 'InputNumber',
  61 + componentProps: {
  62 + placeholder: '请输入最大客户数(请输入数字)',
  63 + },
  64 + },
  65 + {
  66 + field: 'maxRuleChains',
  67 + required: true,
  68 + defaultValue: '0',
  69 + label: '最大规则链数',
  70 + colProps: { span: 12 },
  71 + component: 'InputNumber',
  72 + componentProps: {
  73 + placeholder: '请输入最大规则链数(请输入数字)',
  74 + },
  75 + },
  76 +];
  77 +
  78 +export const ruleEngineSchema: FormSchema[] = [
  79 + {
  80 + field: 'maxREExecutions',
  81 + required: true,
  82 + defaultValue: '0',
  83 + label: '最大规则引擎数',
  84 + colProps: { span: 12 },
  85 + component: 'InputNumber',
  86 + componentProps: {
  87 + placeholder: '请输入最大规则引擎数(请输入数字)',
  88 + },
  89 + },
  90 + {
  91 + field: 'maxTransportMessages',
  92 + required: true,
  93 + defaultValue: '0',
  94 +
  95 + label: '最大传输消息数',
  96 + colProps: { span: 12 },
  97 + component: 'InputNumber',
  98 + componentProps: {
  99 + placeholder: '请输入最大传输消息数(请输入数字)',
  100 + },
  101 + },
  102 + {
  103 + field: 'maxJSExecutions',
  104 + required: true,
  105 + defaultValue: '0',
  106 +
  107 + label: '最大JavaScript执行数',
  108 + colProps: { span: 12 },
  109 + component: 'InputNumber',
  110 + componentProps: {
  111 + placeholder: '请输入最大JavaScript执行数(请输入数字)',
  112 + },
  113 + },
  114 + {
  115 + field: 'maxTbelExecutions',
  116 + required: true,
  117 + defaultValue: '0',
  118 + label: '最大TBEL执行数',
  119 + colProps: { span: 12 },
  120 + component: 'InputNumber',
  121 + componentProps: {
  122 + placeholder: '请输入最大TBEL执行数(请输入数字)',
  123 + },
  124 + },
  125 + {
  126 + field: 'maxRuleNodeExecutionsPerMessage',
  127 + required: true,
  128 + defaultValue: '0',
  129 + label: '每条消息的最大规则节点执行数',
  130 + colProps: { span: 12 },
  131 + component: 'InputNumber',
  132 + componentProps: {
  133 + placeholder: '请输入每条消息的最大规则节点执行数(请输入数字)',
  134 + },
  135 + },
  136 + {
  137 + field: 'maxTransportDataPoints',
  138 + required: true,
  139 + defaultValue: '0',
  140 + label: '最大传输数据点数',
  141 + colProps: { span: 12 },
  142 + component: 'InputNumber',
  143 + componentProps: {
  144 + placeholder: '请输入最大传输数据点数(请输入数字)',
  145 + },
  146 + },
  147 +];
  148 +
  149 +export const ttlSchema: FormSchema[] = [
  150 + {
  151 + field: 'maxDPStorageDays',
  152 + required: true,
  153 + defaultValue: '0',
  154 + label: '最大存储点天',
  155 + colProps: { span: 12 },
  156 + component: 'InputNumber',
  157 + componentProps: {
  158 + placeholder: '请输入最大存储点天(请输入数字)',
  159 + },
  160 + },
  161 + {
  162 + field: 'alarmsTtlDays',
  163 + required: true,
  164 + defaultValue: '0',
  165 +
  166 + label: '告警TTL天数',
  167 + colProps: { span: 12 },
  168 + component: 'InputNumber',
  169 + componentProps: {
  170 + placeholder: '请输入告警存储天数(请输入数字)',
  171 + },
  172 + },
  173 + {
  174 + field: 'defaultStorageTtlDays',
  175 + required: true,
  176 + defaultValue: '0',
  177 +
  178 + label: '默认存储TTL天数',
  179 + colProps: { span: 12 },
  180 + component: 'InputNumber',
  181 + componentProps: {
  182 + placeholder: '请输入默认存储天数(请输入数字)',
  183 + },
  184 + },
  185 + {
  186 + field: 'rpcTtlDays',
  187 + required: true,
  188 + defaultValue: '0',
  189 +
  190 + label: 'RPC TTL天数',
  191 + colProps: { span: 12 },
  192 + component: 'InputNumber',
  193 + componentProps: {
  194 + placeholder: '请输入RPC TTL天数(请输入数字)',
  195 + },
  196 + },
  197 + {
  198 + field: 'queueStatsTtlDays',
  199 + required: true,
  200 + defaultValue: '0',
  201 +
  202 + label: '队列统计信息TTL天数',
  203 + colProps: { span: 12 },
  204 + component: 'InputNumber',
  205 + componentProps: {
  206 + placeholder: '请输入队列统计信息TTL天数(请输入数字)',
  207 + },
  208 + },
  209 + {
  210 + field: 'ruleEngineExceptionsTtlDays',
  211 + required: true,
  212 + defaultValue: '0',
  213 +
  214 + label: '规则引擎异常TTL天数',
  215 + colProps: { span: 12 },
  216 + component: 'InputNumber',
  217 + componentProps: {
  218 + placeholder: '请输入规则引擎异常TTL天数(请输入数字)',
  219 + },
  220 + },
  221 +];
  222 +
  223 +export const alarmSchema: FormSchema[] = [
  224 + {
  225 + field: 'smsEnabled',
  226 + label: '启用短信',
  227 + defaultValue: true,
  228 + component: 'Switch',
  229 + componentProps: ({ formActionType }) => {
  230 + const { setFieldsValue } = formActionType;
  231 + return {
  232 + checkedValue: true,
  233 + unCheckedValue: false,
  234 + checkedChildren: '开',
  235 + unCheckedChildren: '关',
  236 + onChange: () => {
  237 + setFieldsValue({
  238 + maxSms: 0,
  239 + });
  240 + },
  241 + };
  242 + },
  243 + },
  244 + {
  245 + field: 'maxSms',
  246 + required: true,
  247 + defaultValue: '0',
  248 + label: '发送的最大短信数',
  249 + colProps: { span: 12 },
  250 + ifShow: ({ model }) => model.smsEnabled,
  251 + component: 'InputNumber',
  252 + componentProps: {
  253 + placeholder: '请输入发送的最大短信数(请输入数字)',
  254 + },
  255 + },
  256 + {
  257 + field: 'maxEmails',
  258 + required: true,
  259 + label: '最大电子邮件发送数',
  260 + colProps: { span: 12 },
  261 + defaultValue: '0',
  262 + component: 'InputNumber',
  263 + componentProps: {
  264 + placeholder: '请输入最大电子邮件发送数(请输入数字)',
  265 + },
  266 + },
  267 + {
  268 + field: 'maxCreatedAlarms',
  269 + required: true,
  270 + defaultValue: '0',
  271 +
  272 + label: '最大创建告警数',
  273 + colProps: { span: 12 },
  274 + component: 'InputNumber',
  275 + componentProps: {
  276 + placeholder: '请输入最大创建告警数(请输入数字)',
  277 + },
  278 + },
  279 +];
  280 +export const otaSchema: FormSchema[] = [
  281 + {
  282 + field: 'maxResourcesInBytes',
  283 + required: true,
  284 + defaultValue: '0',
  285 + label: '资源文件总大小',
  286 + colProps: { span: 12 },
  287 + component: 'InputNumber',
  288 + componentProps: {
  289 + placeholder: '请输入(请输入数字)',
  290 + },
  291 + },
  292 + {
  293 + field: 'maxOtaPackagesInBytes',
  294 + required: true,
  295 + defaultValue: '0',
  296 + label: 'Ota包文件大小',
  297 + colProps: { span: 12 },
  298 + component: 'InputNumber',
  299 + componentProps: {
  300 + placeholder: '请输入(请输入数字)',
  301 + },
  302 + },
  303 + {
  304 + field: 'maxResourceSize',
  305 + required: true,
  306 + defaultValue: '0',
  307 + label: '最大资源文件大小',
  308 + colProps: { span: 12 },
  309 + component: 'InputNumber',
  310 + componentProps: {
  311 + placeholder: '请输入(请输入数字)',
  312 + },
  313 + },
  314 +];
  315 +
  316 +export const wsSchema: FormSchema[] = [
  317 + {
  318 + field: 'maxWsSessionsPerTenant',
  319 + required: true,
  320 + defaultValue: '0',
  321 + label: '租户最大会话数',
  322 + colProps: { span: 12 },
  323 + component: 'InputNumber',
  324 + componentProps: {
  325 + placeholder: '请输入(请输入数字)',
  326 + },
  327 + },
  328 + {
  329 + field: 'maxWsSubscriptionsPerTenant',
  330 + required: true,
  331 + defaultValue: '0',
  332 + label: '租户最大订阅数',
  333 + colProps: { span: 12 },
  334 + component: 'InputNumber',
  335 + componentProps: {
  336 + placeholder: '请输入(请输入数字)',
  337 + },
  338 + },
  339 + {
  340 + field: 'maxWsSessionsPerCustomer',
  341 + required: true,
  342 + defaultValue: '0',
  343 + label: '客户最大会话数',
  344 + colProps: { span: 12 },
  345 + component: 'InputNumber',
  346 + componentProps: {
  347 + placeholder: '请输入(请输入数字)',
  348 + },
  349 + },
  350 + {
  351 + field: 'maxWsSubscriptionsPerCustomer',
  352 + required: true,
  353 + defaultValue: '0',
  354 + label: '客户最大订阅数',
  355 + colProps: { span: 12 },
  356 + component: 'InputNumber',
  357 + componentProps: {
  358 + placeholder: '请输入(请输入数字)',
  359 + },
  360 + },
  361 +
  362 + {
  363 + field: 'maxWsSessionsPerPublicUser',
  364 + required: true,
  365 + defaultValue: '0',
  366 + label: '公共用户最大会话数',
  367 + colProps: { span: 12 },
  368 + component: 'InputNumber',
  369 + componentProps: {
  370 + placeholder: '请输入(请输入数字)',
  371 + },
  372 + },
  373 + {
  374 + field: 'maxWsSubscriptionsPerPublicUser',
  375 + required: true,
  376 + defaultValue: '0',
  377 + label: '公共用户最大订阅数',
  378 + colProps: { span: 12 },
  379 + component: 'InputNumber',
  380 + componentProps: {
  381 + placeholder: '请输入(请输入数字)',
  382 + },
  383 + },
  384 + {
  385 + field: 'maxWsSessionsPerRegularUser',
  386 + required: true,
  387 + defaultValue: '0',
  388 + label: '普通用户最大会话数',
  389 + colProps: { span: 12 },
  390 + component: 'InputNumber',
  391 + componentProps: {
  392 + placeholder: '请输入(请输入数字)',
  393 + },
  394 + },
  395 + {
  396 + field: 'maxWsSubscriptionsPerRegularUser',
  397 + required: true,
  398 + defaultValue: '0',
  399 + label: '普通用户最大订阅数',
  400 + colProps: { span: 12 },
  401 + component: 'InputNumber',
  402 + componentProps: {
  403 + placeholder: '请输入(请输入数字)',
  404 + },
  405 + },
  406 + {
  407 + field: 'wsMsgQueueLimitPerSession',
  408 + required: true,
  409 + defaultValue: '0',
  410 + helpMessage: ' 队列的大小也受系统配置的限制。',
  411 + label: '会话最大消息队列大小',
  412 + colProps: { span: 12 },
  413 + component: 'InputNumber',
  414 + componentProps: {
  415 + placeholder: '请输入(请输入数字)',
  416 + },
  417 + },
  418 +];
  419 +
  420 +export const speedSchema: FormSchema[] = [
  421 + {
  422 + field: 'transportTenantMsgRateLimit',
  423 + label: '传输租户信息',
  424 + colProps: { span: 12 },
  425 + component: 'CreateSpeed',
  426 + defaultValue: '0',
  427 + changeEvent: 'update:value',
  428 + componentProps: {
  429 + placeholder: '请输入(请输入数字)',
  430 + title: '传输租户信息',
  431 + },
  432 + },
  433 + {
  434 + field: 'transportDeviceMsgRateLimit',
  435 + defaultValue: '0',
  436 + label: '传输设备信息',
  437 + colProps: { span: 12 },
  438 + component: 'CreateSpeed',
  439 + componentProps: {
  440 + placeholder: '请输入(请输入数字)',
  441 + title: '传输设备信息',
  442 + },
  443 + },
  444 + {
  445 + field: 'transportTenantTelemetryMsgRateLimit',
  446 + defaultValue: '0',
  447 + label: '传输租户遥测消息',
  448 + colProps: { span: 12 },
  449 + component: 'CreateSpeed',
  450 + componentProps: {
  451 + placeholder: '请输入(请输入数字)',
  452 + title: '传输租户遥测消息',
  453 + },
  454 + },
  455 + {
  456 + field: 'transportDeviceTelemetryMsgRateLimit',
  457 + defaultValue: '0',
  458 + label: '传输设备遥测消息',
  459 + colProps: { span: 12 },
  460 + component: 'CreateSpeed',
  461 + componentProps: {
  462 + placeholder: '请输入(请输入数字)',
  463 + title: '传输设备遥测消息',
  464 + },
  465 + },
  466 + {
  467 + field: 'transportTenantTelemetryDataPointsRateLimit',
  468 + label: '传输租户遥测数据点',
  469 + colProps: { span: 12 },
  470 + component: 'CreateSpeed',
  471 + componentProps: {
  472 + placeholder: '请输入(请输入数字)',
  473 + title: '传输租户遥测数据点',
  474 + },
  475 + },
  476 + {
  477 + field: 'transportDeviceTelemetryDataPointsRateLimit',
  478 + label: '传输设备遥测数据点',
  479 + colProps: { span: 12 },
  480 + component: 'CreateSpeed',
  481 + componentProps: {
  482 + placeholder: '请输入(请输入数字)',
  483 + title: '传输设备遥测数据点',
  484 + },
  485 + },
  486 + {
  487 + field: 'tenantServerRestLimitsConfiguration',
  488 + label: '租户REST请求',
  489 + colProps: { span: 12 },
  490 + component: 'CreateSpeed',
  491 + componentProps: {
  492 + placeholder: '请输入(请输入数字)',
  493 + title: '租户REST请求',
  494 + },
  495 + },
  496 + {
  497 + field: 'customerServerRestLimitsConfiguration',
  498 + label: '客户REST请求',
  499 + colProps: { span: 12 },
  500 + component: 'CreateSpeed',
  501 + componentProps: {
  502 + placeholder: '请输入(请输入数字)',
  503 + title: '客户REST请求',
  504 + },
  505 + },
  506 + {
  507 + field: 'tenantEntityExportRateLimit',
  508 + label: '实体版本创建',
  509 + colProps: { span: 12 },
  510 + component: 'CreateSpeed',
  511 + componentProps: {
  512 + placeholder: '请输入(请输入数字)',
  513 + title: '实体版本创建',
  514 + },
  515 + },
  516 + {
  517 + field: 'tenantEntityImportRateLimit',
  518 + label: '实体版本加载',
  519 + colProps: { span: 12 },
  520 + component: 'CreateSpeed',
  521 + componentProps: {
  522 + placeholder: '请输入(请输入数字)',
  523 + title: '实体版本加载',
  524 + },
  525 + },
  526 + {
  527 + field: 'wsUpdatesPerSessionRateLimit',
  528 + label: '会话WS更新',
  529 + colProps: { span: 12 },
  530 + component: 'CreateSpeed',
  531 + componentProps: {
  532 + placeholder: '请输入(请输入数字)',
  533 + title: '会话WS更新',
  534 + },
  535 + },
  536 + {
  537 + field: 'cassandraQueryTenantRateLimitsConfiguration',
  538 + label: '租户Cassandra查询',
  539 + colProps: { span: 12 },
  540 + component: 'CreateSpeed',
  541 + componentProps: {
  542 + placeholder: '请输入(请输入数字)',
  543 + title: '租户Cassandra查询',
  544 + },
  545 + },
  546 + {
  547 + field: 'tenantNotificationRequestsRateLimit',
  548 + label: '通知请求',
  549 + colProps: { span: 12 },
  550 + component: 'CreateSpeed',
  551 + componentProps: {
  552 + placeholder: '请输入(请输入数字)',
  553 + title: '通知请求',
  554 + },
  555 + },
  556 + {
  557 + field: 'tenantNotificationRequestsPerRuleRateLimit',
  558 + label: '每个通知规则的通知请求',
  559 + colProps: { span: 12 },
  560 + component: 'CreateSpeed',
  561 + componentProps: {
  562 + placeholder: '请输入(请输入数字)',
  563 + title: '每个通知规则的通知请求',
  564 + },
  565 + },
  566 + {
  567 + field: 'edgeEventRateLimits',
  568 + label: 'Edge events',
  569 + colProps: { span: 12 },
  570 + component: 'CreateSpeed',
  571 + componentProps: {
  572 + placeholder: '请输入(请输入数字)',
  573 + title: 'Edge events',
  574 + },
  575 + },
  576 + {
  577 + field: 'edgeEventRateLimitsPerEdge',
  578 + label: 'Edge events per edge',
  579 + colProps: { span: 12 },
  580 + component: 'CreateSpeed',
  581 + componentProps: {
  582 + placeholder: '请输入(请输入数字)',
  583 + title: 'Edge events per edge',
  584 + },
  585 + },
  586 + {
  587 + field: 'edgeUplinkMessagesRateLimits',
  588 + label: 'Edge uplink message',
  589 + colProps: { span: 12 },
  590 + component: 'CreateSpeed',
  591 + componentProps: {
  592 + placeholder: '请输入(请输入数字)',
  593 + title: 'Edge uplink message',
  594 + },
  595 + },
  596 + {
  597 + field: 'edgeUplinkMessagesRateLimitsPerEdge',
  598 + label: 'Edge uplink message per edge',
  599 + colProps: { span: 12 },
  600 + component: 'CreateSpeed',
  601 + componentProps: {
  602 + placeholder: '请输入(请输入数字)',
  603 + title: 'Edge uplink message per edge',
  604 + },
  605 + },
  606 +];
... ...
  1 +<script lang="ts" setup>
  2 + import { Card, Tag } from 'ant-design-vue';
  3 + import CreateSpeedModel from './createSpeedModel.vue';
  4 + import { ref, watch, unref } from 'vue';
  5 + import { useModal } from '/@/components/Modal';
  6 + import { PlusCircleOutlined } from '@ant-design/icons-vue';
  7 + import { generateUUID } from '/@/hooks/web/useGenerateUUID';
  8 +
  9 + const props = withDefaults(
  10 + defineProps<{
  11 + value: any;
  12 + title?: string;
  13 + }>(),
  14 + {
  15 + title: '编辑传输租户消息速率配置',
  16 + }
  17 + );
  18 + const emit = defineEmits(['update:value']);
  19 +
  20 + const speedData = ref<{ value: string | number; second: string | number; uuid?: string }[]>([]);
  21 + const handleSuccess = (values) => {
  22 + if (values.length) speedData.value = values;
  23 + const items = values.map((item) => `${item.value}:${item.second}`).join(',');
  24 + emit('update:value', items);
  25 + };
  26 + const handleOpenCreate = () => {
  27 + openModal(true, {
  28 + title: props.title,
  29 + data: unref(speedData),
  30 + });
  31 + };
  32 +
  33 + const objectArray = (values) => {
  34 + return values?.map((item) => {
  35 + const [value, second] = item.split(':');
  36 + return {
  37 + value: parseInt(value, 10),
  38 + second: parseInt(second || value, 10),
  39 + uuid: generateUUID(),
  40 + };
  41 + });
  42 + };
  43 +
  44 + watch(
  45 + () => props.value,
  46 + (target) => {
  47 + if (target && typeof target === 'string' && target !== '0') {
  48 + const values = target.split(',');
  49 + speedData.value = objectArray(values);
  50 + }
  51 + },
  52 + { immediate: true }
  53 + );
  54 +
  55 + // watch(
  56 + // () => speedData,
  57 + // () => {
  58 + // emit('update:value', unref(speedData));
  59 + // }
  60 + // );
  61 +
  62 + const [registerModal, { openModal }] = useModal();
  63 +</script>
  64 +<template>
  65 + <section class="!flex items-center">
  66 + <div class="flex flex-col justify-start items-start w-4/5">
  67 + <Card class="w-full">
  68 + <div
  69 + class="flex flex-col"
  70 + v-if="speedData && speedData.length >= 1 && speedData?.[0].value && speedData?.[0].second"
  71 + >
  72 + <div v-for="(item, index) in speedData" :key="item.value">
  73 + <Tag color="blue" v-if="index === 0 && item.value && item.second"
  74 + >{{ item.value }}条消息每{{ item.second }}秒</Tag
  75 + >
  76 + <Tag color="blue" v-else v-show="item.value && item.second"
  77 + >但小于{{ item.value }}条消息每{{ item.second }}秒</Tag
  78 + >
  79 + </div>
  80 + </div>
  81 +
  82 + <div v-else>
  83 + <span class="font-bold">未配置</span>
  84 + </div>
  85 + </Card>
  86 + </div>
  87 + <PlusCircleOutlined
  88 + class="ml-2"
  89 + style="color: #377dff; font-size: 24px"
  90 + @click="handleOpenCreate"
  91 + />
  92 + <CreateSpeedModel @register="registerModal" @success="handleSuccess" />
  93 + </section>
  94 +</template>
  95 +
  96 +<style lang="less" scoped>
  97 + :deep(.ant-card-body) {
  98 + padding: 12px;
  99 + max-height: 110px;
  100 + overflow-y: auto;
  101 + }
  102 +</style>
... ...
  1 +<script setup lang="ts">
  2 + import { BasicModal, useModalInner } from '/@/components/Modal';
  3 + import { Button, Form, InputNumber, Card, Tag, Space } from 'ant-design-vue';
  4 + import { DeleteOutlined } from '@ant-design/icons-vue';
  5 + import { ref } from 'vue';
  6 + import { unref } from 'vue';
  7 + import { useMessage } from '/@/hooks/web/useMessage';
  8 + import { generateUUID } from '/@/hooks/web/useGenerateUUID';
  9 + import { reactive } from 'vue';
  10 +
  11 + const emit = defineEmits(['success', 'register']);
  12 +
  13 + // const formdata = ref<any[]>([{ value: '', second: '', uuid: generateUUID() }]);
  14 +
  15 + interface User {
  16 + value: string;
  17 + second: string;
  18 + uuid: any;
  19 + }
  20 +
  21 + const formref = ref();
  22 +
  23 + const formdata = reactive<{ users: User[] }>({
  24 + users: [],
  25 + });
  26 +
  27 + const title = ref<string>('');
  28 +
  29 + const [registerModal, { closeModal }] = useModalInner((data) => {
  30 + const { title: propTitle } = data || {};
  31 + title.value = propTitle;
  32 + formdata.users = [...data.data] || [];
  33 + });
  34 +
  35 + const { createMessage } = useMessage();
  36 +
  37 + const handleOk = async () => {
  38 + await formref.value.validate();
  39 + closeModal();
  40 + emit('success', formdata.users);
  41 + };
  42 +
  43 + const handleCreate = () => {
  44 + if (unref(formdata).users.length >= 10) {
  45 + createMessage.warning('添加的消息速率限制不能超过10条');
  46 + return;
  47 + }
  48 + formdata.users.push({ value: '', second: '', uuid: generateUUID() });
  49 + };
  50 +
  51 + const handleDelete = (index: number) => {
  52 + formdata.users.splice(index, 1);
  53 + };
  54 +</script>
  55 +<template>
  56 + <BasicModal @register="registerModal" :title="title" @ok="handleOk">
  57 + <div class="!w-full">
  58 + <Form
  59 + style="width: 100%"
  60 + ref="formref"
  61 + :model="formdata"
  62 + layout="inline"
  63 + name="basic"
  64 + :label-col="{ style: { width: '100px' } }"
  65 + :wrapper-col="{ span: 16 }"
  66 + autocomplete="off"
  67 + >
  68 + <Space
  69 + class="flex i mt-1 mb-2"
  70 + v-for="(item, index) in formdata.users"
  71 + :key="item.uuid"
  72 + align="baseline"
  73 + >
  74 + <Form.Item
  75 + label="消息数量"
  76 + :rules="{ required: true, message: '消息数量必填' }"
  77 + :name="['users', index as unknown as string, 'value']"
  78 + >
  79 + <InputNumber v-model:value="item.value" :min="0" />
  80 + </Form.Item>
  81 + <Form.Item
  82 + label="每秒"
  83 + :rules="{ required: true, message: '时间比例必填' }"
  84 + :name="['users', index as unknown as string, 'second']"
  85 + >
  86 + <InputNumber v-model:value="item.second" :min="0" />
  87 + </Form.Item>
  88 +
  89 + <DeleteOutlined @click="handleDelete(index)" class="ml-0.5" style="color: red" />
  90 + </Space>
  91 + </Form>
  92 + </div>
  93 + <Button class="my-2" type="primary" @click="handleCreate">添加限制</Button>
  94 + <div class="flex flex-col justify-start items-start w-full">
  95 + <Card title="预览" class="w-full">
  96 + <div class="flex flex-col" v-if="formdata.users && formdata.users.length >= 1">
  97 + <div v-for="(item, index) in formdata.users" :key="item.value">
  98 + <Tag color="blue" v-if="index === 0 && item.value && item.second"
  99 + >{{ item.value }}条消息每{{ item.second }}秒</Tag
  100 + >
  101 + <Tag color="blue" v-else v-show="item.value && item.second"
  102 + >但小于{{ item.value }}条消息每{{ item.second }}秒</Tag
  103 + >
  104 + </div>
  105 + </div>
  106 +
  107 + <div v-if="!formdata.users?.[0]?.value || !formdata.users?.[0]?.second">
  108 + <span class="font-bold">未配置</span>
  109 + </div>
  110 + </Card>
  111 + </div>
  112 + </BasicModal>
  113 +</template>
  114 +
  115 +<style lang="less" scoped>
  116 + :deep(.ant-card-head) {
  117 + height: 40px;
  118 +
  119 + .ant-card-head-wrapper {
  120 + height: 40px;
  121 + }
  122 + }
  123 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { entityFormSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: entityFormSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { otaSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: otaSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { ruleEngineSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: ruleEngineSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { speedSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: speedSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { ttlSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: ttlSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <BasicForm @register="registerForm" />
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent, ref } from 'vue';
  8 + import { BasicForm, useForm } from '/@/components/Form';
  9 + import { wsSchema } from './config';
  10 +
  11 + export default defineComponent({
  12 + name: 'Index',
  13 + components: { BasicForm },
  14 + emits: ['success', 'register', 'funcResetFields'],
  15 + setup() {
  16 + const getValueData: any = ref({});
  17 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  18 + schemas: wsSchema,
  19 + showActionButtonGroup: false,
  20 + });
  21 + const setFieldsValueFunc = (v) => {
  22 + setFieldsValue(v);
  23 + };
  24 + function getAllFields(getV) {
  25 + const values = getFieldsValue();
  26 + getValueData.value = values;
  27 + getV = getValueData.value;
  28 + return getV;
  29 + }
  30 + function funcResetFields() {
  31 + resetFields();
  32 + }
  33 + return {
  34 + setFieldsValueFunc,
  35 + funcResetFields,
  36 + getAllFields,
  37 + registerForm,
  38 + };
  39 + },
  40 + });
  41 +</script>
  42 +<style lang="less" scoped>
  43 + .tenant-class {
  44 + :deep .ant-input-number {
  45 + width: 95% !important;
  46 + }
  47 + }
  48 +</style>
... ...
  1 +<template>
  2 + <div class="tenant-class">
  3 + <CollapseContainer title="队列">
  4 + <BasicForm @register="registerForm" />
  5 + </CollapseContainer>
  6 + </div>
  7 +</template>
  8 +<script lang="ts">
  9 + import { defineComponent, ref } from 'vue';
  10 + import { BasicForm, useForm } from '/@/components/Form';
  11 + import { formSchema } from './config';
  12 + import { CollapseContainer } from '/@/components/Container/index';
  13 +
  14 + export default defineComponent({
  15 + name: 'Index',
  16 + components: { BasicForm, CollapseContainer },
  17 + emits: ['success', 'register', 'funcResetFields'],
  18 + setup() {
  19 + const getValueData: any = ref({});
  20 + const [registerForm, { getFieldsValue, resetFields, setFieldsValue }] = useForm({
  21 + schemas: formSchema,
  22 + showActionButtonGroup: false,
  23 + });
  24 + const setFieldsValueFunc = (v) => {
  25 + setFieldsValue(v);
  26 + };
  27 + function getAllFields(getV) {
  28 + const values = getFieldsValue();
  29 + getValueData.value = values;
  30 + getV = getValueData.value;
  31 + return getV;
  32 + }
  33 + function funcResetFields() {
  34 + resetFields();
  35 + }
  36 + return {
  37 + setFieldsValueFunc,
  38 + funcResetFields,
  39 + getAllFields,
  40 + registerForm,
  41 + };
  42 + },
  43 + });
  44 +</script>
  45 +<style lang="less" scoped>
  46 + .tenant-class {
  47 + :deep .ant-input-number {
  48 + width: 95% !important;
  49 + }
  50 + }
  51 +</style>
... ...
... ... @@ -5,11 +5,33 @@
5 5 @register="registerDrawer"
6 6 showFooter
7 7 :title="getTitle"
  8 + :destroyOnClose="true"
8 9 width="700px"
9 10 @ok="handleSubmit"
10 11 >
11 12 <BasicForm @register="registerForm" />
12   - <CpnsTenantSet ref="getChildData" :parentData="parentSetData" />
  13 + <!-- <CpnsTenantSet ref="getChildData" :parentData="parentSetData" /> -->
  14 + <CollapseContainer title="实体 (0-无限制)" :defaultExpand="false">
  15 + <Entity ref="getEntity" />
  16 + </CollapseContainer>
  17 + <CollapseContainer title="规则引擎 (0-无限制)" :defaultExpand="false">
  18 + <RuleEngine ref="getRuleEngine" />
  19 + </CollapseContainer>
  20 + <CollapseContainer title="TTL (0-无限制)" :defaultExpand="false">
  21 + <Ttl ref="getTtl" />
  22 + </CollapseContainer>
  23 + <CollapseContainer title="告警与通知 (0-无限制)" :defaultExpand="false">
  24 + <Alarm ref="getAlarm" />
  25 + </CollapseContainer>
  26 + <CollapseContainer title="OTA文件(字节)(0-无限制)" :defaultExpand="false">
  27 + <Ota ref="getOta" />
  28 + </CollapseContainer>
  29 + <CollapseContainer title="WS (0-无限制)" :defaultExpand="false">
  30 + <Ws ref="getWs" />
  31 + </CollapseContainer>
  32 + <CollapseContainer title="速率限制" :defaultExpand="false">
  33 + <Speed ref="getSpeed" />
  34 + </CollapseContainer>
13 35 </BasicDrawer>
14 36 </div>
15 37 </template>
... ... @@ -18,13 +40,33 @@
18 40 import { BasicForm, useForm } from '/@/components/Form';
19 41 import { formSchema } from './config';
20 42 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
21   - import CpnsTenantSet from './cpns/index.vue';
  43 + // import CpnsTenantSet from './cpns/index.vue';
  44 + import Entity from './configuration/entity.vue';
  45 + import Ttl from './configuration/ttl.vue';
  46 + import Alarm from './configuration/alarm.vue';
  47 + import Ota from './configuration/ota.vue';
  48 + import Ws from './configuration/ws.vue';
  49 + import Speed from './configuration/speed.vue';
  50 + import RuleEngine from './configuration/ruleEngine.vue';
22 51 import { saveTenantProfileApi } from '/@/api/tenant/tenantApi';
23 52 import { useMessage } from '/@/hooks/web/useMessage';
  53 + import { CollapseContainer } from '/@/components/Container/index';
24 54
25 55 export default defineComponent({
26 56 name: 'ConfigDrawer',
27   - components: { BasicDrawer, BasicForm, CpnsTenantSet },
  57 + components: {
  58 + BasicDrawer,
  59 + BasicForm,
  60 + CollapseContainer,
  61 + // CpnsTenantSet,
  62 + Ws,
  63 + RuleEngine,
  64 + Ttl,
  65 + Alarm,
  66 + Ota,
  67 + Speed,
  68 + Entity,
  69 + },
28 70 emits: ['success', 'register'],
29 71 setup(_, { emit }) {
30 72 const { createMessage } = useMessage();
... ... @@ -34,6 +76,15 @@
34 76 let getValuesFormData: any = reactive({});
35 77 const { proxy } = getCurrentInstance() as any;
36 78 const getChildData = ref(null);
  79 +
  80 + const getEntity = ref(null);
  81 + const getRuleEngine = ref(null);
  82 + const getTtl = ref(null);
  83 + const getAlarm = ref(null);
  84 + const getOta = ref(null);
  85 + const getWs = ref(null);
  86 + const getSpeed = ref(null);
  87 +
37 88 const editGetId: any = ref('');
38 89 const isDefault = ref(false);
39 90 const createTime = ref<string>('');
... ... @@ -41,9 +92,20 @@
41 92 schemas: formSchema,
42 93 showActionButtonGroup: false,
43 94 });
  95 +
  96 + const funcResetFields = () => {
  97 + proxy.$refs.getEntity.funcResetFields();
  98 + proxy.$refs.getRuleEngine.funcResetFields();
  99 + proxy.$refs.getTtl.funcResetFields();
  100 + proxy.$refs.getAlarm.funcResetFields();
  101 + proxy.$refs.getOta.funcResetFields();
  102 + proxy.$refs.getWs.funcResetFields();
  103 + proxy.$refs.getSpeed.funcResetFields();
  104 + };
  105 +
44 106 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
45 107 //清除表单值
46   - proxy.$refs.getChildData.funcResetFields();
  108 + funcResetFields();
47 109 await resetFields();
48 110 setDrawerProps({ confirmLoading: false });
49 111 isUpdate.value = !!data?.isUpdate;
... ... @@ -54,7 +116,13 @@
54 116 //编辑
55 117 if (unref(isUpdate)) {
56 118 parentSetData.value = { ...data.record.profileData.configuration };
57   - proxy.$refs.getChildData.setFieldsValueFunc(parentSetData.value);
  119 + proxy.$refs.getEntity.setFieldsValueFunc(parentSetData.value);
  120 + proxy.$refs.getRuleEngine.setFieldsValueFunc(parentSetData.value);
  121 + proxy.$refs.getTtl.setFieldsValueFunc(parentSetData.value);
  122 + proxy.$refs.getAlarm.setFieldsValueFunc(parentSetData.value);
  123 + proxy.$refs.getOta.setFieldsValueFunc(parentSetData.value);
  124 + proxy.$refs.getWs.setFieldsValueFunc(parentSetData.value);
  125 + proxy.$refs.getSpeed.setFieldsValueFunc(parentSetData.value);
58 126 editGetId.value = data.record.id;
59 127 isDefault.value = data.record.default;
60 128 await setFieldsValue({
... ... @@ -86,9 +154,25 @@
86 154 const getAllFieldsFunc = async (isUpdate?: boolean) => {
87 155 getValuesFormData = await validate();
88 156 if (!getValuesFormData) return;
89   - let getChildValues = proxy.$refs.getChildData.getAllFields();
  157 + let getEntity = proxy.$refs.getEntity.getAllFields();
  158 + let getRuleEngine = proxy.$refs.getRuleEngine.getAllFields();
  159 + let getTtl = proxy.$refs.getTtl.getAllFields();
  160 + let getAlarm = proxy.$refs.getAlarm.getAllFields();
  161 + let getOta = proxy.$refs.getOta.getAllFields();
  162 + let getWs = proxy.$refs.getWs.getAllFields();
  163 + let getSpeed = proxy.$refs.getSpeed.getAllFields();
90 164 let profileData1 = {
91   - configuration: getChildValues,
  165 + configuration: {
  166 + ...getEntity,
  167 + ...getRuleEngine,
  168 + ...getTtl,
  169 + ...getAlarm,
  170 + ...getOta,
  171 + ...getWs,
  172 + ...getSpeed,
  173 + type: 'DEFAULT',
  174 + },
  175 + queueConfiguration: null,
92 176 };
93 177 const id: any = {
94 178 id: unref(isUpdate) ? editGetId.value : '',
... ... @@ -121,6 +205,7 @@
121 205 try {
122 206 if (!unref(isUpdate)) {
123 207 await getAllFieldsFunc();
  208 + // return;
124 209 await saveTenantProfileApi(postAllData);
125 210 createMessage.success('租户配置新增成功');
126 211 closeDrawer();
... ... @@ -147,6 +232,13 @@
147 232 registerForm,
148 233 getTitle,
149 234 handleSubmit,
  235 + getEntity,
  236 + getRuleEngine,
  237 + getTtl,
  238 + getAlarm,
  239 + getOta,
  240 + getWs,
  241 + getSpeed,
150 242 };
151 243 },
152 244 });
... ...