Commit f8e81f7e8d6e35026253b95e4dabbc021ff79875

Authored by gesilong
1 parent d2660418

commit: 设备台账开发(未联调)

  1 +export default {
  2 + ledgerListText: '台账列表',
  3 + createLedgerText: '创建台账',
  4 + importLedgerText: '导入台账',
  5 + deviceName: '设备名称',
  6 + deviceCode: '设备编码',
  7 + deviceType: '设备类型',
  8 + status: '状态',
  9 + description: '负责人',
  10 + operationSuccessText: '操作成功',
  11 + editProductText: '编辑台账',
  12 + updateOrganization: '修改组织',
  13 + batchActionText: '批量操作',
  14 +}
... ...
  1 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  2 +import { findDictItemByCode } from '/@/api/system/dict';
  3 +import { getGatewayDevice, queryDeviceProfileBy } from '/@/api/device/deviceManager';
  4 +import { JSONEditor } from '/@/components/CodeEditor';
  5 +import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  6 +import { h } from 'vue';
  7 +import { TaskTypeEnum } from '/@/views/task/center/config';
  8 +import { createImgPreview } from '/@/components/Preview';
  9 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
  10 +import LockControlGroup from '/@/components/Form/src/components/LockControlGroup.vue';
  11 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  12 +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
  13 +import { HexInput, InputTypeEnum } from '../../profiles/components/ObjectModelForm/HexInput';
  14 +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
  15 +import { getDeviceProfileOtaPackages, getOtaPackageInfo } from '/@/api/ota';
  16 +import { QueryDeviceProfileOtaPackagesType } from '/@/api/ota/model';
  17 +import { OTAPackageType } from '/@/enums/otaEnum';
  18 +import { createPickerSearch } from '/@/utils/pickerSearch';
  19 +import { useI18n } from '/@/hooks/web/useI18n';
  20 +
  21 +useComponentRegister('JSONEditor', JSONEditor);
  22 +useComponentRegister('LockControlGroup', LockControlGroup);
  23 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  24 +useComponentRegister('HexInput', HexInput);
  25 +
  26 +const { t } = useI18n();
  27 +
  28 +export enum TypeEnum {
  29 + IS_GATEWAY = 'GATEWAY',
  30 + SENSOR = 'SENSOR',
  31 +}
  32 +
  33 +export const isGateWay = (type: string) => {
  34 + return type === TypeEnum.IS_GATEWAY;
  35 +};
  36 +
  37 +const updateProductHelpMessage = [
  38 + t('deviceManagement.device.updateProductHelpMessage.text1'),
  39 + t('deviceManagement.device.updateProductHelpMessage.text2'),
  40 +];
  41 +
  42 +export const updateOrgHelpMessage = [
  43 + t('deviceManagement.device.updateOrgHelpMessage.text1'),
  44 + t('deviceManagement.device.updateOrgHelpMessage.text2'),
  45 + t('deviceManagement.device.updateOrgHelpMessage.text3'),
  46 +];
  47 +// 第一步的表单
  48 +export const step1Schemas: FormSchema[] = [
  49 + {
  50 + field: 'icon',
  51 + label: t('business.deviceImageText'),
  52 + component: 'ApiUpload',
  53 + changeEvent: 'update:fileList',
  54 + valueField: 'fileList',
  55 + componentProps: ({ formModel }) => {
  56 + return {
  57 + listType: 'picture-card',
  58 + maxFileLimit: 1,
  59 + accept: '.png,.jpg,.jpeg,.gif',
  60 + api: async (file: File) => {
  61 + try {
  62 + const formData = new FormData();
  63 + formData.set('file', file);
  64 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  65 + return {
  66 + uid: fileStaticUri,
  67 + name: fileName,
  68 + url: fileStaticUri,
  69 + };
  70 + } catch (error) {
  71 + return {};
  72 + }
  73 + },
  74 + onPreview: (fileList) => {
  75 + createImgPreview({ imageList: [fileList.url!] });
  76 + },
  77 + onDelete(url: string) {
  78 + formModel.deleteUrl = url!;
  79 + },
  80 + };
  81 + },
  82 + },
  83 + {
  84 + field: 'deleteUrl',
  85 + label: '',
  86 + component: 'Input',
  87 + show: false,
  88 + },
  89 + {
  90 + field: 'alias',
  91 + label: t('business.aliasText'),
  92 + component: 'Input',
  93 + componentProps: {
  94 + maxLength: 32,
  95 + },
  96 + },
  97 + {
  98 + field: 'name',
  99 + label: t('business.deviceNameText'),
  100 + component: 'Input',
  101 + componentProps: {
  102 + maxLength: 32,
  103 + },
  104 + required: true,
  105 + slot: 'snCode',
  106 + },
  107 + {
  108 + field: 'transportType',
  109 + label: '类型',
  110 + component: 'Input',
  111 + show: false,
  112 + },
  113 + {
  114 + field: 'deviceProfileId',
  115 + label: '',
  116 + component: 'Input',
  117 + show: false,
  118 + },
  119 + {
  120 + field: 'isUpdate',
  121 + label: '编辑模式',
  122 + component: 'Switch',
  123 + show: false,
  124 + },
  125 + {
  126 + field: 'tcpDeviceProtocol',
  127 + label: 'TCP设备协议类型',
  128 + component: 'Input',
  129 + show: false,
  130 + },
  131 +
  132 + {
  133 + field: 'profileId',
  134 + label: t('business.affiliatedProductText'),
  135 + required: true,
  136 + component: 'LockControlGroup',
  137 + helpMessage: updateProductHelpMessage,
  138 + renderComponentContent: () => ({
  139 + popconfirmTitle: () =>
  140 + updateProductHelpMessage.map((text) => h('div', { style: { maxWidth: '200px' } }, text)),
  141 + }),
  142 + componentProps: ({ formActionType, formModel }) => {
  143 + const { setFieldsValue } = formActionType;
  144 + return {
  145 + component: 'ApiSelect',
  146 + defaultLockStatus: !!formModel?.isUpdate,
  147 + componentProps: {
  148 + api: async () => {
  149 + const options = await queryDeviceProfileBy({
  150 + deviceType: formModel?.isUpdate ? formModel?.deviceType : null,
  151 + });
  152 +
  153 + return options;
  154 + },
  155 + labelField: 'name',
  156 + valueField: 'tbProfileId',
  157 + onChange(_value: string, option: DeviceProfileDetail) {
  158 + const { deviceType, transportType, id } = option;
  159 + setFieldsValue({
  160 + deviceType: deviceType,
  161 + transportType,
  162 + deviceProfileId: id,
  163 + gatewayId: null,
  164 + code: null,
  165 + addressCode: null,
  166 + tcpDeviceProtocol: option?.profileData?.transportConfiguration?.protocol,
  167 + });
  168 + },
  169 + onOptionsChange(options: (DeviceProfileDetail & Record<'value', string>)[]) {
  170 + const { profileId } = formModel;
  171 + if (profileId) {
  172 + const selectRecord = options.find((item) => item.value === profileId);
  173 + selectRecord &&
  174 + setFieldsValue({
  175 + transportType: selectRecord!.transportType,
  176 + tcpDeviceProtocol: selectRecord?.profileData?.transportConfiguration?.protocol,
  177 + });
  178 + }
  179 + },
  180 + placeholder: t('deviceManagement.device.productPlaceholderText'),
  181 + ...createPickerSearch(),
  182 + },
  183 + };
  184 + },
  185 + },
  186 + {
  187 + field: 'deviceType',
  188 + label: t('business.deviceTypeText'),
  189 + required: true,
  190 + component: 'ApiSelect',
  191 + dynamicDisabled: true,
  192 + helpMessage: t('deviceManagement.device.deviceTypeHelpText'),
  193 + componentProps: {
  194 + api: findDictItemByCode,
  195 + params: {
  196 + dictCode: 'device_type',
  197 + },
  198 + labelField: 'itemText',
  199 + valueField: 'itemValue',
  200 + },
  201 + },
  202 + {
  203 + field: 'addressType',
  204 + label: '',
  205 + component: 'Input',
  206 + show: false,
  207 + defaultValue: 'HEX',
  208 + },
  209 + {
  210 + field: 'deviceState',
  211 + label: '',
  212 + component: 'Input',
  213 + show: false,
  214 + },
  215 + {
  216 + field: 'addressCode',
  217 + label: t('deviceManagement.device.addressCodeText'),
  218 + dynamicDisabled({ values }) {
  219 + return (
  220 + values.isUpdate &&
  221 + values.deviceType === DeviceTypeEnum.SENSOR &&
  222 + values.deviceState === 'ONLINE' &&
  223 + values.transportType === TransportTypeEnum.TCP
  224 + );
  225 + },
  226 + dynamicRules({ values }) {
  227 + return [
  228 + {
  229 + required:
  230 + values?.transportType === TransportTypeEnum.TCP &&
  231 + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.MODBUS_RTU,
  232 + message: t('deviceManagement.device.addressCodeHelpText'),
  233 + pattern: values?.addressType === 'HEX' ? /^[0-9A-Fa-f]{2}$/ : /^[0-9A-Fa-f]{1,2}$/,
  234 + },
  235 + ];
  236 + },
  237 + helpMessage({ values }) {
  238 + return [
  239 + t('deviceManagement.device.addressCodeHelpText'),
  240 + values.transportType === TransportTypeEnum.TCP &&
  241 + values.deviceType === DeviceTypeEnum.SENSOR
  242 + ? t('deviceManagement.device.tcpAddressCodeHelpText')
  243 + : '',
  244 + ];
  245 + },
  246 + component: 'HexInput',
  247 + changeEvent: 'update:value',
  248 + valueField: 'value',
  249 + componentProps: ({ formModel }) => {
  250 + return {
  251 + type: InputTypeEnum.HEX,
  252 + maxValue: parseInt('FF', 16),
  253 + onHexChange: (e) => {
  254 + formModel.addressType = e;
  255 + },
  256 + };
  257 + },
  258 + ifShow: ({ values }) => {
  259 + return (
  260 + values?.transportType === TransportTypeEnum.TCP &&
  261 + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.MODBUS_RTU
  262 + );
  263 + },
  264 + },
  265 + {
  266 + field: 'code',
  267 + label: t('deviceManagement.device.codeText'),
  268 + dynamicRules({ values }) {
  269 + return [
  270 + {
  271 + required:
  272 + values?.transportType === TransportTypeEnum.TCP &&
  273 + values.deviceType === DeviceTypeEnum.SENSOR,
  274 + },
  275 + ];
  276 + },
  277 + dynamicDisabled({ values }) {
  278 + return (
  279 + values.isUpdate &&
  280 + values.deviceType === DeviceTypeEnum.SENSOR &&
  281 + values.deviceState === 'ONLINE' &&
  282 + values.transportType === TransportTypeEnum.TCP
  283 + );
  284 + },
  285 + helpMessage({ values }) {
  286 + return (
  287 + values.transportType === TransportTypeEnum.TCP &&
  288 + values.deviceType === DeviceTypeEnum.SENSOR
  289 + ? ['tcp网关子设备在线时,不能修改设备标识或地址码']
  290 + : false
  291 + ) as any;
  292 + },
  293 + component: 'Input',
  294 + componentProps: () => {
  295 + return {
  296 + maxLength: 255,
  297 + placeholder: t('deviceManagement.device.codePlaceholderText'),
  298 + };
  299 + },
  300 + ifShow: ({ values }) => {
  301 + return (
  302 + values?.transportType === TransportTypeEnum.TCP &&
  303 + values?.tcpDeviceProtocol === TaskTypeEnum.CUSTOM
  304 + );
  305 + },
  306 + },
  307 +
  308 + {
  309 + field: 'brand',
  310 + component: 'ApiRadioGroup',
  311 + label: t('deviceManagement.device.brandText'),
  312 + required: true,
  313 + defaultValue: 'DIY_',
  314 + componentProps: {
  315 + api: findDictItemByCode,
  316 + params: {
  317 + dictCode: 'device_brand_gateway',
  318 + },
  319 + labelField: 'itemText',
  320 + valueField: 'itemValue',
  321 + },
  322 + ifShow: ({ values }) => isGateWay(values.deviceType),
  323 + },
  324 + {
  325 + field: 'gatewayId',
  326 + label: t('enum.deviceType.GATEWAY'),
  327 + required: true,
  328 + component: 'ApiSelect',
  329 + ifShow: ({ values }) => values.deviceType === 'SENSOR',
  330 + componentProps: ({ formModel, formActionType }) => {
  331 + const { transportType, deviceType, gatewayId } = formModel;
  332 + const { setFieldsValue } = formActionType;
  333 + if (!transportType) return {};
  334 + return {
  335 + api: async (params: Recordable) => {
  336 + try {
  337 + const result = await getGatewayDevice(params as any);
  338 + return result.map((item) => ({ ...item, alias: item.alias || item.name }));
  339 + } catch (e) {
  340 + return [];
  341 + }
  342 + },
  343 + params: {
  344 + transportType,
  345 + gatewayId: deviceType === DeviceTypeEnum.SENSOR && gatewayId ? gatewayId : null,
  346 + },
  347 + valueField: 'tbDeviceId',
  348 + labelField: 'alias',
  349 + onChange: async (_value: string, option: DeviceRecord) => {
  350 + setFieldsValue({ sensorOrganizationId: option?.organizationId, organizationId: null });
  351 + },
  352 + onOptionsChange(options: (DeviceRecord & Record<'value', string>)[]) {
  353 + if (formModel?.deviceType === DeviceTypeEnum.SENSOR && formModel?.gatewayId) {
  354 + const result = options.find((item) => item.value === formModel?.gatewayId);
  355 + result && setFieldsValue({ sensorOrganizationId: result?.organizationId });
  356 + }
  357 + },
  358 + ...createPickerSearch(),
  359 + };
  360 + },
  361 + },
  362 + {
  363 + field: 'sensorOrganizationId',
  364 + label: '依据网关设备请求的组织数组',
  365 + component: 'Input',
  366 + ifShow: false,
  367 + },
  368 + {
  369 + field: 'customerId',
  370 + label: '用来判断编辑时禁用组织修改',
  371 + component: 'Input',
  372 + ifShow: false,
  373 + },
  374 + {
  375 + field: 'organizationId',
  376 + label: t('business.affiliatedOrganizationText'),
  377 + component: 'LockControlGroup',
  378 + required: true,
  379 + helpMessage: updateOrgHelpMessage,
  380 + renderComponentContent: () => ({
  381 + popconfirmTitle: () =>
  382 + updateOrgHelpMessage.map((text) => h('div', { style: { maxWidth: '240px' } }, text)),
  383 + }),
  384 + componentProps: ({ formModel, formActionType }) => {
  385 + return {
  386 + component: 'OrgTreeSelect',
  387 + defaultLockStatus: !!formModel?.isUpdate,
  388 + disabled: !!formModel?.customerId,
  389 + componentProps: {
  390 + apiTreeSelectProps: {
  391 + params: {
  392 + organizationId: formModel?.sensorOrganizationId,
  393 + },
  394 + },
  395 + onOptionsChange: (options: Recordable[]) => {
  396 + if (!formModel?.organizationId && formModel?.deviceType === DeviceTypeEnum.SENSOR) {
  397 + const firstItem = options?.[0];
  398 +
  399 + if (firstItem && firstItem?.id) {
  400 + const { setFieldsValue, clearValidate } = formActionType;
  401 + setFieldsValue({ organizationId: firstItem.id });
  402 + clearValidate('organizationId');
  403 + }
  404 + }
  405 + },
  406 + placeholder: t('deviceManagement.device.organizationPlaceholderText'),
  407 + },
  408 + };
  409 + },
  410 + },
  411 + {
  412 + field: 'directorId',
  413 + label: t('deviceManagement.device.directorName'),
  414 + component: 'Input',
  415 + componentProps: {
  416 + maxLength: 255,
  417 + },
  418 + },
  419 + {
  420 + field: 'label',
  421 + label: t('deviceManagement.device.deviceLabelText'),
  422 + component: 'Input',
  423 + componentProps: {
  424 + maxLength: 255,
  425 + },
  426 + },
  427 + {
  428 + field: 'deviceAddress',
  429 + label: t('business.deviceLocationText'),
  430 + component: 'Input',
  431 + slot: 'deviceAddress',
  432 + },
  433 +
  434 + {
  435 + field: 'firmwareId',
  436 + label: t('deviceManagement.product.assignHardwareText'),
  437 + component: 'ApiSearchSelect',
  438 + ifShow: ({ model }) => model?.isUpdate,
  439 + componentProps: ({ formModel }) => {
  440 + return {
  441 + api: async (params: QueryDeviceProfileOtaPackagesType) => {
  442 + if (!params.deviceProfileId) return [];
  443 + const result = await getDeviceProfileOtaPackages(params);
  444 + return result.data.map((item) => ({
  445 + label: `${item.title}(${item.version})`,
  446 + value: item.id.id,
  447 + }));
  448 + },
  449 + params: (textSearch: string) => {
  450 + return {
  451 + textSearch,
  452 + page: 0,
  453 + type: OTAPackageType.FIRMWARE,
  454 + pageSize: 10,
  455 + deviceProfileId: formModel?.profileId,
  456 + };
  457 + },
  458 + queryApi: async (id: string) => {
  459 + const result = await getOtaPackageInfo(id);
  460 + return { label: `${result.title}(${result.version})`, value: result.id.id };
  461 + },
  462 + };
  463 + },
  464 + },
  465 + {
  466 + field: 'softwareId',
  467 + label: t('deviceManagement.product.assignSoftwareText'),
  468 + component: 'ApiSearchSelect',
  469 + ifShow: ({ model }) => model?.isUpdate,
  470 + componentProps: ({ formModel }) => {
  471 + return {
  472 + api: async (params: QueryDeviceProfileOtaPackagesType) => {
  473 + if (!params.deviceProfileId) return [];
  474 + const result = await getDeviceProfileOtaPackages(params);
  475 + return result.data.map((item) => ({
  476 + label: `${item.title}(${item.version})`,
  477 + value: item.id.id,
  478 + }));
  479 + },
  480 + params: (textSearch: string) => {
  481 + return {
  482 + textSearch,
  483 + page: 0,
  484 + type: OTAPackageType.SOFTWARE,
  485 + pageSize: 10,
  486 + deviceProfileId: formModel?.profileId,
  487 + };
  488 + },
  489 + queryApi: async (id: string) => {
  490 + const result = await getOtaPackageInfo(id);
  491 + return { label: `${result.title}(${result.version})`, value: result.id.id };
  492 + },
  493 + };
  494 + },
  495 + },
  496 + {
  497 + field: 'description',
  498 + label: t('common.remarkText'),
  499 + component: 'InputTextArea',
  500 + componentProps: {
  501 + maxLength: 500,
  502 + },
  503 + },
  504 + {
  505 + field: 'id',
  506 + label: 'id',
  507 + component: 'Input',
  508 + show: false,
  509 + componentProps: {
  510 + maxLength: 36,
  511 + placeholder: '请输入id',
  512 + },
  513 + },
  514 + {
  515 + field: 'tenantId',
  516 + label: '租户Code',
  517 + component: 'Input',
  518 + show: false,
  519 + componentProps: {
  520 + maxLength: 36,
  521 + placeholder: '请输入租户Code',
  522 + },
  523 + },
  524 + {
  525 + field: 'tbDeviceId',
  526 + label: 'tbDeviceId',
  527 + component: 'Input',
  528 + show: false,
  529 + componentProps: {
  530 + maxLength: 36,
  531 + placeholder: '请输入tbDeviceId',
  532 + },
  533 + },
  534 +];
  535 +
  536 +export enum credentialTypeEnum {
  537 + ACCESS_TOKEN = 'ACCESS_TOKEN',
  538 + X_509 = 'X509_CERTIFICATE',
  539 + MQTT_BASIC = 'MQTT_BASIC',
  540 +}
  541 +// 第二步的表单
  542 +export const step2Schemas: FormSchema[] = [
  543 + {
  544 + label: '',
  545 + component: 'Checkbox',
  546 + field: 'addAgree',
  547 + slot: 'addAgree',
  548 + },
  549 + {
  550 + label: t('deviceManagement.device.certificateTypeText'),
  551 + component: 'Select',
  552 + field: 'credentialType',
  553 + required: true,
  554 + componentProps({ formActionType }) {
  555 + const { updateSchema, setFieldsValue } = formActionType;
  556 + return {
  557 + options: [
  558 + {
  559 + value: credentialTypeEnum.ACCESS_TOKEN,
  560 + label: 'Access Token',
  561 + },
  562 + {
  563 + value: credentialTypeEnum.X_509,
  564 + label: 'X.509',
  565 + },
  566 + {
  567 + value: credentialTypeEnum.MQTT_BASIC,
  568 + label: 'MQTT Basic',
  569 + },
  570 + ],
  571 + onChange(value) {
  572 + setFieldsValue({
  573 + publicKey: '',
  574 + credentialsId: '',
  575 + clientId: '',
  576 + username: '',
  577 + password: '',
  578 + });
  579 + if (value === credentialTypeEnum.ACCESS_TOKEN) {
  580 + updateSchema([
  581 + {
  582 + field: 'credentialsId',
  583 + ifShow: true,
  584 + },
  585 + {
  586 + field: 'clientId',
  587 + ifShow: false,
  588 + },
  589 + {
  590 + field: 'username',
  591 + ifShow: false,
  592 + },
  593 + {
  594 + field: 'password',
  595 + ifShow: false,
  596 + },
  597 + {
  598 + field: 'publicKey',
  599 + ifShow: false,
  600 + },
  601 + ]);
  602 + } else if (value === credentialTypeEnum.X_509) {
  603 + updateSchema([
  604 + {
  605 + field: 'publicKey',
  606 + ifShow: true,
  607 + },
  608 + {
  609 + field: 'credentialsId',
  610 + ifShow: false,
  611 + },
  612 + {
  613 + field: 'clientId',
  614 + ifShow: false,
  615 + },
  616 + {
  617 + field: 'username',
  618 + ifShow: false,
  619 + },
  620 + {
  621 + field: 'password',
  622 + ifShow: false,
  623 + },
  624 + ]);
  625 + } else if (value === credentialTypeEnum.MQTT_BASIC) {
  626 + updateSchema([
  627 + {
  628 + field: 'clientId',
  629 + ifShow: true,
  630 + },
  631 + {
  632 + field: 'username',
  633 + ifShow: true,
  634 + },
  635 + {
  636 + field: 'password',
  637 + ifShow: true,
  638 + },
  639 + {
  640 + field: 'publicKey',
  641 + ifShow: false,
  642 + },
  643 + {
  644 + field: 'credentialsId',
  645 + ifShow: false,
  646 + },
  647 + ]);
  648 + } else {
  649 + updateSchema([
  650 + {
  651 + field: 'clientId',
  652 + ifShow: false,
  653 + },
  654 + {
  655 + field: 'username',
  656 + ifShow: false,
  657 + },
  658 + {
  659 + field: 'password',
  660 + ifShow: false,
  661 + },
  662 + {
  663 + field: 'publicKey',
  664 + ifShow: false,
  665 + },
  666 + {
  667 + field: 'credentialsId',
  668 + ifShow: false,
  669 + },
  670 + ]);
  671 + }
  672 + },
  673 + };
  674 + },
  675 + ifShow: ({ values }) => values.addAgree,
  676 + },
  677 + {
  678 + label: t('deviceManagement.device.accessTokenText'),
  679 + component: 'Input',
  680 + field: 'credentialsId',
  681 + required: true,
  682 + ifShow: false,
  683 + slot: 'credentialsId',
  684 + componentProps: {
  685 + maxLength: 36,
  686 + },
  687 + },
  688 + {
  689 + label: t('deviceManagement.device.rsaPublicKeyText'),
  690 + component: 'InputTextArea',
  691 + field: 'publicKey',
  692 + required: true,
  693 + ifShow: false,
  694 + componentProps: {
  695 + rows: 8,
  696 + },
  697 + },
  698 + {
  699 + label: t('deviceManagement.device.clientIdText'),
  700 + component: 'Input',
  701 + field: 'clientId',
  702 + ifShow: false,
  703 + slot: 'clientId',
  704 + componentProps: {
  705 + maxLength: 36,
  706 + },
  707 + },
  708 + {
  709 + label: t('deviceManagement.device.usernameText'),
  710 + component: 'Input',
  711 + field: 'username',
  712 + required: true,
  713 + ifShow: false,
  714 + componentProps: {
  715 + maxLength: 255,
  716 + },
  717 + },
  718 + {
  719 + label: t('deviceManagement.device.passwordText'),
  720 + component: 'InputPassword',
  721 + field: 'password',
  722 + componentProps: {
  723 + maxLength: 36,
  724 + },
  725 + ifShow: false,
  726 + },
  727 +];
  728 +
  729 +// 管理凭证的表单配置项
  730 +export const TokenSchemas: FormSchema[] = [
  731 + {
  732 + label: t('deviceManagement.device.certificateTypeText'),
  733 + component: 'Select',
  734 + field: 'credentialType',
  735 + required: true,
  736 + componentProps({ formActionType }) {
  737 + const { updateSchema, setFieldsValue } = formActionType;
  738 + return {
  739 + options: [
  740 + {
  741 + value: credentialTypeEnum.ACCESS_TOKEN,
  742 + label: 'Access Token',
  743 + },
  744 + {
  745 + value: credentialTypeEnum.X_509,
  746 + label: 'X.509',
  747 + },
  748 + {
  749 + value: credentialTypeEnum.MQTT_BASIC,
  750 + label: 'MQTT Basic',
  751 + },
  752 + ],
  753 + onChange(value) {
  754 + setFieldsValue({
  755 + publicKey: '',
  756 + credentialsId: '',
  757 + clientId: '',
  758 + username: '',
  759 + password: '',
  760 + });
  761 + if (value === credentialTypeEnum.ACCESS_TOKEN) {
  762 + updateSchema([
  763 + {
  764 + field: 'credentialsId',
  765 + ifShow: true,
  766 + },
  767 + {
  768 + field: 'clientId',
  769 + ifShow: false,
  770 + },
  771 + {
  772 + field: 'username',
  773 + ifShow: false,
  774 + },
  775 + {
  776 + field: 'password',
  777 + ifShow: false,
  778 + },
  779 + {
  780 + field: 'publicKey',
  781 + ifShow: false,
  782 + },
  783 + ]);
  784 + } else if (value === credentialTypeEnum.X_509) {
  785 + updateSchema([
  786 + {
  787 + field: 'publicKey',
  788 + ifShow: true,
  789 + },
  790 + {
  791 + field: 'credentialsId',
  792 + ifShow: false,
  793 + },
  794 + {
  795 + field: 'clientId',
  796 + ifShow: false,
  797 + },
  798 + {
  799 + field: 'username',
  800 + ifShow: false,
  801 + },
  802 + {
  803 + field: 'password',
  804 + ifShow: false,
  805 + },
  806 + ]);
  807 + } else if (value === credentialTypeEnum.MQTT_BASIC) {
  808 + updateSchema([
  809 + {
  810 + field: 'clientId',
  811 + ifShow: true,
  812 + },
  813 + {
  814 + field: 'username',
  815 + ifShow: true,
  816 + },
  817 + {
  818 + field: 'password',
  819 + ifShow: true,
  820 + },
  821 + {
  822 + field: 'publicKey',
  823 + ifShow: false,
  824 + },
  825 + {
  826 + field: 'credentialsId',
  827 + ifShow: false,
  828 + },
  829 + ]);
  830 + } else {
  831 + updateSchema([
  832 + {
  833 + field: 'clientId',
  834 + ifShow: false,
  835 + },
  836 + {
  837 + field: 'username',
  838 + ifShow: false,
  839 + },
  840 + {
  841 + field: 'password',
  842 + ifShow: false,
  843 + },
  844 + {
  845 + field: 'publicKey',
  846 + ifShow: false,
  847 + },
  848 + {
  849 + field: 'credentialsId',
  850 + ifShow: false,
  851 + },
  852 + ]);
  853 + }
  854 + },
  855 + };
  856 + },
  857 + },
  858 + {
  859 + label: t('deviceManagement.device.accessTokenText'),
  860 + component: 'Input',
  861 + field: 'credentialsId',
  862 + required: true,
  863 + ifShow: false,
  864 + slot: 'credentialsId',
  865 + componentProps: {
  866 + maxLength: 36,
  867 + },
  868 + },
  869 + {
  870 + label: t('deviceManagement.device.rsaPublicKeyText'),
  871 + component: 'InputTextArea',
  872 + field: 'publicKey',
  873 + required: true,
  874 + ifShow: false,
  875 + componentProps: {
  876 + rows: 8,
  877 + },
  878 + },
  879 + {
  880 + label: t('deviceManagement.device.clientIdText'),
  881 + component: 'Input',
  882 + field: 'clientId',
  883 + ifShow: false,
  884 + slot: 'clientId',
  885 + componentProps: {
  886 + maxLength: 36,
  887 + },
  888 + },
  889 + {
  890 + label: t('deviceManagement.device.usernameText'),
  891 + component: 'Input',
  892 + field: 'username',
  893 + required: true,
  894 + ifShow: false,
  895 + componentProps: {
  896 + maxLength: 255,
  897 + },
  898 + },
  899 + {
  900 + label: t('deviceManagement.device.passwordText'),
  901 + component: 'InputPassword',
  902 + field: 'password',
  903 + ifShow: false,
  904 + componentProps: {
  905 + maxLength: 36,
  906 + },
  907 + },
  908 + {
  909 + label: 'id',
  910 + component: 'Input',
  911 + field: 'id',
  912 + show: false,
  913 + componentProps: {
  914 + maxLength: 36,
  915 + placeholder: '请输入id',
  916 + },
  917 + },
  918 + {
  919 + label: 'tbDeviceId',
  920 + component: 'Input',
  921 + field: 'tbDeviceId',
  922 + show: false,
  923 + componentProps: {
  924 + maxLength: 36,
  925 + placeholder: '请输入tbDeviceId',
  926 + },
  927 + dynamicRules: () => {
  928 + return [
  929 + {
  930 + required: false,
  931 + validator: (_, value) => {
  932 + if (String(value).length > 36) {
  933 + return Promise.reject('字数不超过36个字');
  934 + }
  935 + return Promise.resolve();
  936 + },
  937 + },
  938 + ];
  939 + },
  940 + },
  941 +];
... ...
  1 +import { formatToDateTime } from '/@/utils/dateUtil';
  2 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  3 +import { BasicColumn } from '/@/components/Table';
  4 +import { getCustomerList } from '/@/api/device/deviceManager';
  5 +import { DescItem } from '/@/components/Description/index';
  6 +import moment from 'moment';
  7 +import { CSSProperties, h } from 'vue';
  8 +import { Button, Tag, Tooltip } from 'ant-design-vue';
  9 +import { TypeEnum, updateOrgHelpMessage } from './data';
  10 +import { PageEnum } from '/@/enums/pageEnum';
  11 +import { useGo } from '/@/hooks/web/usePage';
  12 +import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail';
  13 +import { findDictItemByCode } from '/@/api/system/dict';
  14 +import { isNullOrUnDef } from '/@/utils/is';
  15 +import { useI18n } from '/@/hooks/web/useI18n';
  16 +import { DeviceStatusEnum } from '/@/enums/deviceEnum';
  17 +import { AlarmStatus } from '/@/enums/alarmEnum';
  18 +const { t } = useI18n();
  19 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  20 +
  21 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  22 +
  23 +// 设备详情的描述
  24 +export const descSchema = (emit: EmitType): DescItem[] => {
  25 + return [
  26 + {
  27 + field: 'createTime',
  28 + label: t('common.createTimeText'),
  29 + },
  30 + {
  31 + field: 'name',
  32 + label: t('business.deviceNameText'),
  33 + render(val, data: Record<'alias' | 'name', string>) {
  34 + return h(Tooltip, { title: data.alias || val }, () =>
  35 + h('span', { style: { cursor: 'pointer' } as CSSProperties }, data.alias || val)
  36 + );
  37 + },
  38 + },
  39 + {
  40 + field: 'label',
  41 + label: t('deviceManagement.device.deviceLabelText'),
  42 + render(val) {
  43 + return h(Tooltip, { title: val }, () =>
  44 + h('span', { style: { cursor: 'pointer' } as CSSProperties }, val)
  45 + );
  46 + },
  47 + },
  48 + {
  49 + field: 'deviceProfile.name',
  50 + label: t('business.productText'),
  51 + render(val) {
  52 + const go = useGo();
  53 + const { isCustomer } = useAuthDeviceDetail();
  54 + return h(
  55 + Button,
  56 + {
  57 + type: 'link',
  58 + style: { padding: 0 },
  59 + onClick: () =>
  60 + !isCustomer
  61 + ? go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(val)))
  62 + : '',
  63 + },
  64 + { default: () => val }
  65 + );
  66 + },
  67 + },
  68 + {
  69 + field: 'gatewayName',
  70 + label: t('business.affiliatedGatewayText'),
  71 + show: (data) => !!data.gatewayName,
  72 + render(val, data) {
  73 + if (TypeEnum.SENSOR !== data.deviceType) return val;
  74 + return h(
  75 + Button,
  76 + { type: 'link', style: { padding: 0 }, onClick: () => emit('open-gateway-device', data) },
  77 + { default: () => data.gatewayAlias || val }
  78 + );
  79 + },
  80 + },
  81 + {
  82 + field: 'deviceType',
  83 + label: t('business.deviceTypeText'),
  84 + render: (text) => {
  85 + return text && t(`enum.deviceType.${text}`);
  86 + },
  87 + },
  88 + {
  89 + field: 'deviceInfo.sip.cameraCode',
  90 + label: t('deviceManagement.device.deviceCameraCodeText'),
  91 + },
  92 + {
  93 + field: 'deviceInfo.sip.hostAddress',
  94 + label: t('deviceManagement.device.hostAddressText'),
  95 + },
  96 + {
  97 + field: 'deviceInfo.sip.manufacturer',
  98 + label: t('deviceManagement.device.brandText'),
  99 + },
  100 + {
  101 + field: 'deviceInfo.sip.streamMode',
  102 + label: t('deviceManagement.device.streamModeText'),
  103 + },
  104 + {
  105 + field: 'description',
  106 + label: t('common.descText'),
  107 + // span: 2,
  108 + render(val) {
  109 + return h(Tooltip, { title: val }, () =>
  110 + h('span', { style: { cursor: 'pointer' } as CSSProperties }, val)
  111 + );
  112 + },
  113 + },
  114 + ];
  115 +};
  116 +
  117 +// 实时数据表格
  118 +export const realTimeDataColumns: BasicColumn[] = [
  119 + {
  120 + title: t('deviceManagement.device.keyText'),
  121 + dataIndex: 'name',
  122 + width: 100,
  123 + },
  124 + {
  125 + title: t('deviceManagement.device.valueText'),
  126 + dataIndex: 'rawValue',
  127 + width: 160,
  128 + format(text) {
  129 + return isNullOrUnDef(text) ? '--' : text;
  130 + },
  131 + },
  132 + {
  133 + title: t('deviceManagement.device.latestUpdateTimeText'),
  134 + dataIndex: 'time',
  135 + width: 120,
  136 + format: (text) => formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss'),
  137 + },
  138 +];
  139 +
  140 +// 告警
  141 +export const alarmSearchSchemas: FormSchema[] = [
  142 + {
  143 + field: 'status',
  144 + label: t('business.deviceStatusText'),
  145 + component: 'Select',
  146 + colProps: { span: 6 },
  147 + componentProps: {
  148 + options: Object.values(AlarmStatus).map((value) => ({
  149 + label: t(`enum.alarmStaus.${value}`),
  150 + value,
  151 + })),
  152 + },
  153 + },
  154 + {
  155 + field: 'alarmType',
  156 + label: t('deviceManagement.device.alarmSceneText'),
  157 + component: 'Input',
  158 + colProps: { span: 6 },
  159 + componentProps: {
  160 + maxLength: 36,
  161 + },
  162 + },
  163 + {
  164 + field: 'alarmTime',
  165 + label: t('deviceManagement.device.alarmTimeText'),
  166 + component: 'RangePicker',
  167 + componentProps: {
  168 + showTime: { defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')] },
  169 + },
  170 + colProps: { span: 7 },
  171 + },
  172 + {
  173 + field: 'severity',
  174 + label: t('deviceManagement.device.alarmSeverityText'),
  175 + component: 'ApiSelect',
  176 + colProps: { span: 6 },
  177 + componentProps: {
  178 + api: findDictItemByCode,
  179 + params: {
  180 + dictCode: 'severity_type',
  181 + },
  182 + labelField: 'itemText',
  183 + valueField: 'itemValue',
  184 + },
  185 + },
  186 +];
  187 +export const alarmColumns: BasicColumn[] = [
  188 + {
  189 + title: t('deviceManagement.device.alarmTimeText'),
  190 + dataIndex: 'createdTime',
  191 + width: 180,
  192 + },
  193 + {
  194 + title: t('deviceManagement.device.alarmDeviceText'),
  195 + dataIndex: 'deviceName',
  196 + width: 120,
  197 + customRender: ({ record }) => {
  198 + const { deviceAlias, deviceName } = record || {};
  199 + return deviceAlias || deviceName;
  200 + },
  201 + },
  202 + {
  203 + title: t('deviceManagement.device.alarmSceneText'),
  204 + dataIndex: 'type',
  205 + width: 160,
  206 + },
  207 + {
  208 + title: t('deviceManagement.device.alarmSeverityText'),
  209 + dataIndex: 'severity',
  210 + width: 90,
  211 + format: (text) => alarmLevel(text),
  212 + },
  213 + {
  214 + title: t('deviceManagement.device.alarmDetailsText'),
  215 + dataIndex: 'details',
  216 + slots: { customRender: 'details' },
  217 + width: 160,
  218 + },
  219 + {
  220 + title: t('deviceManagement.device.statusText'),
  221 + dataIndex: 'status',
  222 + // format: (text) => statusType(text),
  223 + width: 100,
  224 + },
  225 +];
  226 +
  227 +export const alarmSchemasForm: FormSchema[] = [
  228 + {
  229 + field: 'deviceName',
  230 + label: '告警设备',
  231 + component: 'Input',
  232 + componentProps: {
  233 + disabled: true,
  234 + },
  235 + },
  236 +
  237 + {
  238 + field: 'startTs',
  239 + label: '开始时间',
  240 + component: 'Input',
  241 + componentProps: {
  242 + disabled: true,
  243 + },
  244 + },
  245 + {
  246 + field: 'endTs',
  247 + label: '结束时间',
  248 + component: 'Input',
  249 + componentProps: {
  250 + disabled: true,
  251 + },
  252 + },
  253 + {
  254 + field: 'ackTs',
  255 + label: '处理时间',
  256 + component: 'Input',
  257 + componentProps: {
  258 + disabled: true,
  259 + },
  260 + ifShow: ({ values }) => values.status === '激活已确认' || values.status === '清除已确认',
  261 + },
  262 + {
  263 + field: 'clearTs',
  264 + label: '清除时间',
  265 + component: 'Input',
  266 + componentProps: {
  267 + disabled: true,
  268 + },
  269 + ifShow: ({ values }) => values.status === '清除已确认' || values.status === '清除未确认',
  270 + },
  271 + {
  272 + field: 'type',
  273 + label: '告警场景',
  274 + component: 'Input',
  275 + componentProps: {
  276 + disabled: true,
  277 + },
  278 + },
  279 + {
  280 + field: 'severity',
  281 + label: '严重程度',
  282 + component: 'Input',
  283 + componentProps: {
  284 + disabled: true,
  285 + },
  286 + },
  287 + {
  288 + field: 'status',
  289 + label: '状态',
  290 + component: 'Input',
  291 + componentProps: {
  292 + disabled: true,
  293 + },
  294 + },
  295 +];
  296 +// 子设备
  297 +export const childDeviceSchemas: FormSchema[] = [
  298 + {
  299 + field: 'deviceState',
  300 + label: t('business.deviceStatusText'),
  301 + colProps: { span: 6 },
  302 + component: 'Select',
  303 + componentProps: {
  304 + maxLength: 255,
  305 + options: Object.values(DeviceStatusEnum).map((value) => ({
  306 + label: t(`enum.deviceStatus.${value}`),
  307 + value,
  308 + })),
  309 + },
  310 + },
  311 + {
  312 + field: 'name',
  313 + label: t('business.deviceNameText'),
  314 + component: 'Input',
  315 + colProps: { span: 6 },
  316 + componentProps: {
  317 + maxLength: 255,
  318 + },
  319 + },
  320 +];
  321 +
  322 +const deviceStatusColorMapping = {
  323 + [DeviceStatusEnum.INACTIVE]: 'warning',
  324 + [DeviceStatusEnum.OFFLINE]: 'error',
  325 + [DeviceStatusEnum.ONLINE]: 'success',
  326 +};
  327 +export const childDeviceColumns: BasicColumn[] = [
  328 + {
  329 + title: t('deviceManagement.device.nameText'),
  330 + dataIndex: 'tbDeviceName',
  331 + width: 120,
  332 + slots: {
  333 + customRender: 'tbDeviceName',
  334 + },
  335 + },
  336 + {
  337 + title: t('deviceManagement.device.labelText'),
  338 + dataIndex: 'label',
  339 + width: 160,
  340 + },
  341 + {
  342 + title: t('deviceManagement.device.statusText'),
  343 + dataIndex: 'deviceState',
  344 + customRender: ({ text }) => {
  345 + return h(Tag, { color: deviceStatusColorMapping[text] }, () =>
  346 + t(`enum.deviceStatus.${text}`)
  347 + );
  348 + },
  349 + width: 160,
  350 + },
  351 + {
  352 + title: t('deviceManagement.device.latestConnectionTimeText'),
  353 + dataIndex: 'lastOnlineTime',
  354 + format: (text) => formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss'),
  355 + width: 160,
  356 + },
  357 + {
  358 + title: t('common.updateTimeText'),
  359 + dataIndex: 'createdTime',
  360 + format: (text) => formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss'),
  361 + width: 160,
  362 + },
  363 +];
  364 +
  365 +export const alarmLevel = (type: string): string => {
  366 + const { t } = useI18n();
  367 +
  368 + return t(`enum.alarmLevel.${type}`);
  369 +};
  370 +
  371 +export const customerForm: FormSchema[] = [
  372 + {
  373 + field: 'customerId',
  374 + label: t('deviceManagement.device.assignCustomerText'),
  375 + component: 'ApiSelect',
  376 + componentProps: {
  377 + api: getCustomerList,
  378 + immediate: false,
  379 + labelField: 'realName',
  380 + valueField: 'customerId',
  381 + },
  382 + required: true,
  383 + colProps: { span: 12 },
  384 + },
  385 +];
  386 +
  387 +export const orgForm: FormSchema[] = [
  388 + {
  389 + field: 'sensorOrganizationId',
  390 + label: t('deviceManagement.device.organizeArrayBased'),
  391 + component: 'Input',
  392 + ifShow: false,
  393 + },
  394 + {
  395 + field: 'organizationId',
  396 + component: 'OrgTreeSelect',
  397 + helpMessage: updateOrgHelpMessage,
  398 + label: t('deviceManagement.device.chooseOrganization'),
  399 + required: true,
  400 + componentProps: ({ formModel }) => {
  401 + return {
  402 + apiTreeSelectProps: {
  403 + params: {
  404 + organizationId: formModel?.sensorOrganizationId,
  405 + },
  406 + },
  407 + };
  408 + },
  409 + },
  410 +];
... ...
  1 +import { formatToDate } from '/@/utils/dateUtil';
  2 +import { BasicColumn } from '/@/components/Table';
  3 +import { FormSchema } from '/@/components/Table';
  4 +import { DeviceRecord } from '/@/api/device/model/deviceModel';
  5 +// import { deviceProfile } from '/@/api/device/deviceManager';
  6 +import { h } from 'vue';
  7 +import { Tag, Tooltip } from 'ant-design-vue';
  8 +import { handeleCopy } from '../../../device/profiles/step/topic';
  9 +import { useI18n } from '/@/hooks/web/useI18n';
  10 +import { DeviceStatusEnum, DeviceTypeEnum } from '/@/enums/deviceEnum';
  11 +
  12 +const { t } = useI18n();
  13 +import edgefornt from '/@/assets/icons/edgefornt.svg';
  14 +
  15 +export enum DeviceListAuthEnum {
  16 + /**
  17 + * @description 新增
  18 + */
  19 + CREATE = 'api:yt:device:post',
  20 +
  21 + /**
  22 + * @description 删除
  23 + */
  24 + DELETE = 'api:yt:device:delete',
  25 +
  26 + /**
  27 + * @description 编辑
  28 + */
  29 + UPDATE = 'api:yt:device:update',
  30 +
  31 + /**
  32 + * @description 详情
  33 + */
  34 + DETAIL = 'api:yt:device:get',
  35 +
  36 + /**
  37 + * @description 导入
  38 + */
  39 + IMPORT = 'api:yt:device:import',
  40 +
  41 + /**
  42 + * @description 公开
  43 + */
  44 + PUBLIC = 'api:yt:device:public',
  45 +
  46 + /**
  47 + * @description 上下线
  48 + */
  49 + ONLINE = 'api:yt:device:online:record',
  50 +
  51 + /**
  52 + * @description 管理设备凭证
  53 + */
  54 + EQUIPMENT = 'api:yt:device:equipment',
  55 +
  56 + /**
  57 + * @description 分配客户
  58 + */
  59 + ASSIGN = 'api:yt:device:assign',
  60 +
  61 + /**
  62 + * @description 命令下发
  63 + */
  64 + RPC = 'api:yt:device:rpc',
  65 +
  66 + /**
  67 + * @description 更新产品
  68 + */
  69 + UPDATE_PRODUCT = 'api:yt:device:update:product',
  70 +}
  71 +
  72 +// 表格列数据
  73 +export const columns: BasicColumn[] = [
  74 + {
  75 + title: t('business.deviceStatusText'),
  76 + dataIndex: 'deviceState',
  77 + width: 110,
  78 + className: 'device-status',
  79 + slots: { customRender: 'deviceState' },
  80 + },
  81 + {
  82 + title: t('business.deviceImageText'),
  83 + dataIndex: 'deviceInfo.avatar',
  84 + width: 70,
  85 + slots: { customRender: 'img' },
  86 + },
  87 + {
  88 + dataIndex: 'name',
  89 + title: t('deviceManagement.device.aliasNameText'),
  90 + width: 210,
  91 + slots: { customRender: 'name', title: 'deviceTitle' },
  92 + className: 'device-name-edge',
  93 + customRender: ({ record }) => {
  94 + return h('div', { class: 'py-3 px-3.5' }, [
  95 + record.alias &&
  96 + h(
  97 + 'div',
  98 + {
  99 + class: 'cursor-pointer truncate',
  100 + },
  101 + h(
  102 + Tooltip,
  103 + {
  104 + placement: 'topLeft',
  105 + title: `${record.alias}`,
  106 + },
  107 + () => `${record.alias}`
  108 + )
  109 + ),
  110 + h(
  111 + 'div',
  112 + {
  113 + class: 'cursor-pointer text-blue-500 truncate',
  114 + onClick: () => {
  115 + handeleCopy(`${record.name}`);
  116 + },
  117 + },
  118 + h(
  119 + Tooltip,
  120 + {
  121 + placement: 'topLeft',
  122 + title: `${record.name}`,
  123 + },
  124 + () => `${record.name}`
  125 + )
  126 + ),
  127 + record.isEdge
  128 + ? h('div', { class: 'absolute top-0 left-0', fill: '#1890ff' }, [
  129 + h('img', { src: edgefornt, class: 'w-12.5 h-12.5' }),
  130 + h(
  131 + 'span',
  132 + {
  133 + class:
  134 + 'absolute top-0.5 left-0.5 text-light-50 transform -rotate-45 translate-y-0 !text-10px',
  135 + },
  136 + t('common.edgeFlag')
  137 + ),
  138 + ])
  139 + : '',
  140 + ]);
  141 + },
  142 + },
  143 + {
  144 + title: t('business.deviceTypeText'),
  145 + dataIndex: 'deviceType',
  146 + width: 130,
  147 + customRender({ text }) {
  148 + return h(Tag, { color: 'success' }, () => t(`enum.deviceType.${text}`));
  149 + },
  150 + },
  151 + {
  152 + title: t('business.affiliatedProductText'),
  153 + dataIndex: 'deviceProfile.name',
  154 + width: 180,
  155 + slots: { customRender: 'deviceProfile' },
  156 + ellipsis: true,
  157 + },
  158 + {
  159 + title: t('business.affiliatedOrganizationText'),
  160 + dataIndex: 'organizationDTO.name',
  161 + width: 100,
  162 + },
  163 + // {
  164 + // title: '客户',
  165 + // dataIndex: 'customerName',
  166 + // width: 100,
  167 + // },
  168 + {
  169 + title: t('business.publicText'),
  170 + dataIndex: 'public',
  171 + width: 100,
  172 + customRender({ record }: { record: DeviceRecord }) {
  173 + const flag = record?.customerAdditionalInfo?.isPublic;
  174 + return h(Tag, { color: flag ? 'blue' : 'orange' }, () =>
  175 + flag ? t('business.publicText') : t('business.privateText')
  176 + );
  177 + },
  178 + },
  179 + {
  180 + title: t('deviceManagement.device.lastOnlineTimeText'),
  181 + dataIndex: 'lastOnlineTime',
  182 + format: (text) => text && formatToDate(text, 'YYYY-MM-DD HH:mm:ss'),
  183 + width: 160,
  184 + },
  185 + {
  186 + title: t('deviceManagement.device.lastOfflineTimeText'),
  187 + dataIndex: 'lastOfflineTime',
  188 + format: (text) => {
  189 + return text ? formatToDate(text, 'YYYY-MM-DD HH:mm:ss') : '';
  190 + },
  191 + width: 160,
  192 + },
  193 +];
  194 +
  195 +// 查询字段
  196 +export const searchFormSchema: FormSchema[] = [
  197 + {
  198 + field: 'code',
  199 + label: t('equipment.ledger.deviceCode'),
  200 + component: 'Input',
  201 + colProps: { span: 6 },
  202 + componentProps: {
  203 + maxLength: 255,
  204 + },
  205 + },
  206 + {
  207 + field: 'deviceType',
  208 + label: t('equipment.ledger.deviceType'),
  209 + component: 'Select',
  210 + componentProps: {
  211 + options: Object.values(DeviceTypeEnum).map((value) => ({
  212 + label: t(`enum.deviceType.${value}`),
  213 + value,
  214 + })),
  215 + },
  216 + colProps: { span: 6 },
  217 + },
  218 + {
  219 + field: 'deviceState',
  220 + label: t('business.deviceStatusText'),
  221 + component: 'Select',
  222 + componentProps: {
  223 + options: Object.values(DeviceStatusEnum).map((value) => ({
  224 + label: t(`enum.deviceStatus.${value}`),
  225 + value,
  226 + })),
  227 + },
  228 + colProps: { span: 6 },
  229 + }
  230 +];
... ...
  1 +<template>
  2 + <div>
  3 + <PageWrapper dense contentFullHeight contentClass="flex">
  4 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  5 + <BasicTable style="flex: auto" @register="registerTable" class="w-5/6 xl:w-4/5 device-table">
  6 + <template #toolbar>
  7 + <Authority :value="DeviceListAuthEnum.CREATE">
  8 + <a-button type="primary" @click="handleCreate" v-if="authBtn(role)">
  9 + {{ t('equipment.ledger.createLedgerText') }}
  10 + </a-button>
  11 + </Authority>
  12 +
  13 + <Authority :value="DeviceListAuthEnum.IMPORT">
  14 + <Button type="primary" >
  15 + {{ t('equipment.ledger.importLedgerText') }}
  16 + </Button>
  17 + </Authority>
  18 +
  19 + <Authority
  20 + :value="[
  21 + DeviceListAuthEnum.DELETE,
  22 + DeviceListAuthEnum.ASSIGN,
  23 + DeviceListAuthEnum.UPDATE_PRODUCT,
  24 + ]"
  25 + >
  26 + <AuthDropDown
  27 + v-if="authBtn(role)"
  28 + :disabled="isPublicAndPrivateFlag || !isExistOption"
  29 + :dropMenuList="[
  30 + {
  31 + text: t('common.delText'),
  32 + auth: DeviceListAuthEnum.DELETE,
  33 + icon: 'ant-design:delete-outlined',
  34 + event: '',
  35 + disabled: !batchPrivateFlag,
  36 + popconfirm: {
  37 + title: t('common.deleteConfirmText'),
  38 + onConfirm: () => handleDelete(),
  39 + },
  40 + },
  41 + {
  42 + text: t('equipment.ledger.editProductText'),
  43 + auth: DeviceListAuthEnum.UPDATE_PRODUCT,
  44 + icon: 'clarity:note-edit-line',
  45 + event: '',
  46 + disabled: !batchPrivateFlag || batchUpdateProductFlag,
  47 + onClick: handelOpenBatchUpdateProductModal,
  48 + },
  49 + {
  50 + text: t('business.publicText'),
  51 + icon: 'ant-design:wallet-outlined',
  52 + event: '',
  53 + disabled: !batchPrivateFlag,
  54 + onClick: handleBatchPublic.bind(null),
  55 + },
  56 + {
  57 + text: t('business.privateText'),
  58 + icon: 'ant-design:wallet-outlined',
  59 + event: '',
  60 + disabled: batchPrivateFlag,
  61 + onClick: handleBatchPrivate.bind(null),
  62 + },
  63 + {
  64 + text: t('equipment.ledger.updateOrganization'),
  65 + icon: 'ant-design:wallet-outlined',
  66 + event: '',
  67 + disabled: !batchPrivateFlag || batchSensorFlag || diffBatchSensorFlag,
  68 + onClick: handleBatchOrg.bind(null),
  69 + },
  70 + ]"
  71 + >
  72 + <Button type="primary" :disabled="isPublicAndPrivateFlag || !isExistOption">
  73 + {{ t('equipment.ledger.batchActionText') }}
  74 + </Button>
  75 + </AuthDropDown>
  76 + </Authority>
  77 + </template>
  78 + <template #img="{ record }">
  79 + <TableImg
  80 + :size="30"
  81 + :showBadge="false"
  82 + :simpleShow="true"
  83 + :imgList="
  84 + typeof record?.deviceInfo?.avatar !== 'undefined' &&
  85 + record?.deviceInfo?.avatar !== '' &&
  86 + record?.deviceInfo?.avatar != null
  87 + ? [record.deviceInfo.avatar]
  88 + : null
  89 + "
  90 + />
  91 + </template>
  92 + <template #deviceProfile="{ record }">
  93 + <Button
  94 + @click="!isCustomer ? goDeviceProfile(record.deviceProfile.name) : null"
  95 + type="link"
  96 + >
  97 + {{ record.deviceProfile.name }}
  98 + </Button>
  99 + </template>
  100 + <template #deviceState="{ record }">
  101 + <div v-if="record.isCollect">
  102 + <div class="absolute top-0 left-0 device-collect"> </div>
  103 + <Icon
  104 + icon="ph:star-fill"
  105 + class="fill-light-50 absolute top-0.5 left-0.5"
  106 + color="#fff"
  107 + :size="12"
  108 + />
  109 + </div>
  110 +
  111 + <Tag
  112 + :color="
  113 + record.deviceState == DeviceState.INACTIVE
  114 + ? 'warning'
  115 + : record.deviceState == DeviceState.ONLINE
  116 + ? 'success'
  117 + : record.deviceState == DeviceState.ACTIVE
  118 + ? 'success'
  119 + : 'error'
  120 + "
  121 + class="ml-2"
  122 + >
  123 + {{ t(`enum.deviceStatus.${record.deviceState}`) }}
  124 + </Tag>
  125 + </template>
  126 + <template #action="{ record }">
  127 + <TableAction
  128 + :actions="[
  129 + {
  130 + label: AlarmDetailActionButton({ hasAlarm: !!record.alarmStatus }),
  131 + icon: 'ant-design:eye-outlined',
  132 + auth: DeviceListAuthEnum.DETAIL,
  133 + // onClick: handleDetail.bind(null, record),
  134 + },
  135 + {
  136 + label: t('common.editText'),
  137 + auth: DeviceListAuthEnum.UPDATE,
  138 + icon: 'clarity:note-edit-line',
  139 + ifShow: authBtn(role),
  140 + // onClick: handleEdit.bind(null, record),
  141 + },
  142 + ]"
  143 + :dropDownActions="[
  144 + // record.customerId
  145 + // ? {
  146 + // label: t('deviceManagement.device.cancelAssignText'),
  147 + // icon: 'mdi:account-arrow-left',
  148 + // ifShow: authBtn(role) && !record?.customerAdditionalInfo?.isPublic,
  149 + // auth: DeviceListAuthEnum.ASSIGN,
  150 + // popConfirm: {
  151 + // title: t('deviceManagement.device.cancelAssignConfirmText'),
  152 + // confirm: handleCancelDispatchCustomer.bind(null, record),
  153 + // },
  154 + // }
  155 + // : {
  156 + // label: t('deviceManagement.device.assignCustomerText'),
  157 + // icon: 'mdi:account-arrow-right',
  158 + // ifShow: authBtn(role),
  159 + // auth: DeviceListAuthEnum.ASSIGN,
  160 + // onClick: handleDispatchCustomer.bind(null, record),
  161 + // },
  162 + {
  163 + label: record?.customerAdditionalInfo?.isPublic
  164 + ? t('business.privateText')
  165 + : t('business.publicText'),
  166 + auth: DeviceListAuthEnum.PUBLIC,
  167 + icon: record?.customerAdditionalInfo?.isPublic
  168 + ? 'ant-design:lock-outlined'
  169 + : 'ant-design:unlock-outlined',
  170 + // onClick: handlePublicDevice.bind(null, record),
  171 + },
  172 + {
  173 + label: t('deviceManagement.device.onlineRecordText'),
  174 + auth: DeviceListAuthEnum.ONLINE,
  175 + icon: 'ant-design:rise-outlined',
  176 + // onClick: handleUpAndDownRecord.bind(null, record),
  177 + },
  178 + !record.isCollect
  179 + ? {
  180 + label: t('deviceManagement.device.collectText'),
  181 + icon: 'ant-design:heart-outlined',
  182 + // onClick: handelCollect.bind(null, record),
  183 + }
  184 + : {
  185 + label: t('deviceManagement.device.cancelCollectText'),
  186 + icon: 'ant-design:heart-outlined',
  187 + popConfirm: {
  188 + title: t('deviceManagement.device.cancelCollectConfirmText'),
  189 + // confirm: handelCollect.bind(null, record),
  190 + },
  191 + },
  192 + {
  193 + label: t('common.delText'),
  194 + auth: DeviceListAuthEnum.DELETE,
  195 + icon: 'ant-design:delete-outlined',
  196 + ifShow: authBtn(role) && record.customerId === undefined,
  197 + color: 'error',
  198 + popConfirm: {
  199 + title: !!record.isEdge
  200 + ? t('common.edgeDeviceOperationConfirm')
  201 + : t('common.deleteConfirmText'),
  202 + confirm: handleDelete.bind(null, record),
  203 + },
  204 + },
  205 + ]"
  206 + />
  207 + </template>
  208 + </BasicTable>
  209 +<!-- <LedgerDetailDrawer-->
  210 +<!-- @register="registerDetailDrawer"-->
  211 +<!-- @open-tb-device-detail="handleOpenTbDeviceDetail"-->
  212 +<!-- @open-gateway-device-detail="handleOpenGatewayDetail"-->
  213 +<!-- />-->
  214 + </PageWrapper>
  215 + </div>
  216 +</template>
  217 +<script setup lang="ts">
  218 +import {CSSProperties, h, reactive, ref} from "vue";
  219 +import { PageWrapper } from '/@/components/Page';
  220 +const searchInfo = reactive<Recordable>({});
  221 +const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  222 +import {BasicTable, TableAction, TableImg, useTable} from "/@/components/Table";
  223 +import {OrganizationIdTree, useResetOrganizationTree} from "/@/views/common/organizationIdTree";
  224 +import {
  225 + deleteDevice,
  226 + devicePage,
  227 + doBatchPrivateDevice,
  228 + doBatchPublicDevice
  229 +} from "/@/api/device/deviceManager";
  230 +import {
  231 + columns,
  232 + DeviceListAuthEnum,
  233 + searchFormSchema
  234 +} from "/@/views/equipment/ledger/config/ledger.data";
  235 +import {DeviceModel, DeviceRecord, DeviceState} from "/@/api/device/model/deviceModel";
  236 +import {useI18n} from "/@/hooks/web/useI18n";
  237 +import {authBtn} from "/@/enums/roleEnum";
  238 +import {Badge, Button, Tag} from "ant-design-vue";
  239 +import {Authority} from "/@/components/Authority";
  240 +import {AuthDropDown} from "/@/components/Widget";
  241 +import {getAuthCache} from "/@/utils/auth";
  242 +import {USER_INFO_KEY} from "/@/enums/cacheEnum";
  243 +import {useBatchOperation} from "/@/utils/useBatchOperation";
  244 +import {useMessage} from "/@/hooks/web/useMessage";
  245 +import {DataActionModeEnum} from "/@/enums/toolEnum";
  246 +import {
  247 + BatchUpdateProductModalParamsType
  248 +} from "/@/views/device/list/cpns/modal/BatchUpdateProductModal/index";
  249 +import {useModal} from "/@/components/Modal";
  250 +import Icon from "/@/components/Icon";
  251 +// import LedgerDetailDrawer from "./cpns/modal/LedgerDetailDrawer.vue";
  252 +const { t } = useI18n();
  253 +const userInfo: any = getAuthCache(USER_INFO_KEY);
  254 +const role: string = userInfo.roles[0];
  255 +const isPublicAndPrivateFlag = ref(false);
  256 +const batchPrivateFlag = ref(true);
  257 +const batchUpdateProductFlag = ref(true);
  258 +const batchSensorFlag = ref(false);
  259 +const { createMessage } = useMessage();
  260 +const diffBatchSensorFlag = ref(false);
  261 +const [registerOrgModal, { openModal: openOrgodal }] = useModal();
  262 +const [registerBatchUpdateProductModal, { openModal: openBatchUpdateProductModal }] = useModal();
  263 +
  264 +const AlarmDetailActionButton = ({ hasAlarm }: { hasAlarm?: boolean }) =>
  265 + h(
  266 + Badge,
  267 + { offset: [0, -5] },
  268 + {
  269 + default: () => h('span', { style: { color: '#377dff' } }, t('common.detailText')),
  270 + count: () =>
  271 + h(
  272 + 'div',
  273 + {
  274 + style: {
  275 + visibility: hasAlarm ? 'visible' : 'hidden',
  276 + width: '14px',
  277 + height: '14px',
  278 + display: 'flex',
  279 + justifyContent: 'center',
  280 + alignItems: 'center',
  281 + border: '1px solid #f46161',
  282 + borderRadius: '50%',
  283 + } as CSSProperties,
  284 + },
  285 + h(Icon, { icon: 'mdi:bell-warning', color: '#f46161', size: 12 })
  286 + ),
  287 + }
  288 + );
  289 +
  290 +const [
  291 + registerTable,
  292 + {
  293 + reload,
  294 + setLoading,
  295 + setSelectedRowKeys,
  296 + getForm,
  297 + getSelectRowKeys,
  298 + getSelectRows,
  299 + getRowSelection,
  300 + clearSelectedRowKeys,
  301 + },
  302 +] = useTable({
  303 + title: t('equipment.ledger.ledgerListText'),
  304 + api: devicePage,
  305 + columns,
  306 + beforeFetch: (params) => {
  307 + const { deviceProfileId } = params;
  308 + if (!deviceProfileId) return;
  309 + const obj = {
  310 + ...params,
  311 + ...{
  312 + deviceProfileIds: deviceProfileId ? [deviceProfileId] : null,
  313 + },
  314 + };
  315 + delete obj.deviceProfileId;
  316 + return obj;
  317 + },
  318 + formConfig: {
  319 + labelWidth: 140,
  320 + schemas: searchFormSchema,
  321 + resetFunc: resetFn,
  322 + },
  323 + useSearchForm: true,
  324 + showTableSetting: true,
  325 + bordered: true,
  326 + showIndexColumn: false,
  327 + rowKey: 'id',
  328 + searchInfo: searchInfo,
  329 + clickToRowSelect: false,
  330 + rowClassName: (record) => ((record as DeviceRecord).alarmStatus ? 'device-alarm-badge' : ''),
  331 + actionColumn: {
  332 + width: 200,
  333 + title: t('common.actionText'),
  334 + slots: { customRender: 'action' },
  335 + fixed: 'right',
  336 + },
  337 + rowSelection: {
  338 + type: 'checkbox',
  339 + getCheckboxProps: (record: DeviceModel) => {
  340 + return { disabled: !!record.customerId && record.customerName !== 'Public' };
  341 + },
  342 + onSelect(_record, _selected, selectedRows) {
  343 + const [firstItem] = selectedRows as DeviceRecord[];
  344 + const { deviceType } = firstItem || {};
  345 + batchUpdateProductFlag.value =
  346 + !selectedRows.length ||
  347 + !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  348 +
  349 + batchPrivateFlag.value = selectedRows.some((item: DeviceRecord) => !item?.customerId);
  350 + const filterSensor = selectedRows.map((filterItem: DeviceRecord) => filterItem.deviceType);
  351 + batchSensorFlag.value =
  352 + filterSensor.includes('SENSOR') &&
  353 + (filterSensor.includes('DIRECT_CONNECTION') || filterSensor.includes('GATEWAY')); // 网关子和直连设备或者网关设备,则禁用组织修改
  354 + const filterGatewayId = selectedRows
  355 + .filter((filterItem: DeviceRecord) => filterItem.deviceType === 'SENSOR')
  356 + .map((mapItem: DeviceRecord) => mapItem.gatewayId);
  357 + diffBatchSensorFlag.value = [...new Set(filterGatewayId)].length > 1; // 数组长度大于1,说明选择的网关子设备所属网关为多个,则禁用组织修改
  358 + isPublicAndPrivateFlag.value =
  359 + selectedRows.some((item: DeviceRecord) => !item?.customerId) &&
  360 + selectedRows.some((item: DeviceRecord) => item?.customerAdditionalInfo?.isPublic);
  361 + },
  362 + onSelectAll(_selected, selectedRows) {
  363 + const [firstItem] = selectedRows as DeviceRecord[];
  364 + const { deviceType } = firstItem || {};
  365 + batchUpdateProductFlag.value =
  366 + !selectedRows.length ||
  367 + !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  368 +
  369 + batchPrivateFlag.value = selectedRows.some((item) => !item?.customerId);
  370 + const filterSensor = selectedRows.map((filterItem) => filterItem.deviceType);
  371 + batchSensorFlag.value =
  372 + filterSensor.includes('SENSOR') &&
  373 + (filterSensor.includes('DIRECT_CONNECTION') || filterSensor.includes('GATEWAY')); // 网关子和直连设备或者网关设备,则禁用组织修改
  374 + const filterGatewayId = selectedRows
  375 + .filter((filterItem: DeviceRecord) => filterItem.deviceType === 'SENSOR')
  376 + .map((mapItem: DeviceRecord) => mapItem.gatewayId);
  377 + diffBatchSensorFlag.value = [...new Set(filterGatewayId)].length > 1; // 数组长度大于1,说明选择的网关子设备所属网关为多个,则禁用组织修改
  378 + isPublicAndPrivateFlag.value =
  379 + selectedRows.some((item) => !item?.customerId) &&
  380 + selectedRows.some((item) => item?.customerAdditionalInfo?.isPublic);
  381 + },
  382 + },
  383 +});
  384 +
  385 +const { isExistOption } = useBatchOperation(getRowSelection, setSelectedRowKeys);
  386 +function handleCreate() {
  387 + // openModal(true, {
  388 + // isUpdate: false,
  389 + // });
  390 +}
  391 +
  392 +// 批量公开设备
  393 +const handleBatchPublic = async () => {
  394 + setLoading(true);
  395 + try {
  396 + const options = getSelectRows();
  397 + const tbDeviceIdJoinStr = options.map((item) => item.tbDeviceId).join(',');
  398 + const res = await doBatchPublicDevice(tbDeviceIdJoinStr);
  399 + createMessage.success(
  400 + res === '公开成功' ? t('common.publicSuccess') : t('common.publicError')
  401 + );
  402 + } finally {
  403 + handleReload();
  404 + setLoading(false);
  405 + }
  406 +};
  407 +
  408 +function handleReload() {
  409 + setSelectedRowKeys([]);
  410 + handleSuccess();
  411 +}
  412 +
  413 +// 批量私有设备
  414 +const handleBatchPrivate = async () => {
  415 + setLoading(true);
  416 + try {
  417 + const options = getSelectRows();
  418 + const tbDeviceIdJoinStr = options.map((item) => item.tbDeviceId).join(',');
  419 + const res = await doBatchPrivateDevice(tbDeviceIdJoinStr);
  420 + createMessage.success(
  421 + res === '私有成功' ? t('common.privateSuccess') : t('common.privateError')
  422 + );
  423 + } finally {
  424 + handleReload();
  425 + setLoading(false);
  426 + }
  427 +};
  428 +
  429 +const handleBatchOrg = () => {
  430 + const options = getSelectRows();
  431 + openOrgodal(true, options);
  432 +};
  433 +
  434 +const handelOpenBatchUpdateProductModal = () => {
  435 + const rows: DeviceRecord[] = getSelectRows();
  436 + const [firstItem] = rows;
  437 +
  438 + openBatchUpdateProductModal(true, {
  439 + mode: DataActionModeEnum.UPDATE,
  440 + record: {
  441 + sourceDeviceProfileName: firstItem.deviceProfile.name,
  442 + deviceType: firstItem.deviceType,
  443 + deviceIds: rows.map((item) => item.tbDeviceId),
  444 + deviceProfile: firstItem.deviceProfile,
  445 + },
  446 + } as BatchUpdateProductModalParamsType);
  447 +};
  448 +
  449 +const handleDelete = async (record?: DeviceRecord) => {
  450 + let ids: string[] = [];
  451 + if (record) {
  452 + ids.push(record.id);
  453 + } else {
  454 + ids = getSelectRowKeys();
  455 + }
  456 + try {
  457 + setLoading(true);
  458 + await deleteDevice(ids);
  459 + createMessage.success(t('common.deleteSuccessText'));
  460 + handleReload();
  461 + } catch (error) {
  462 + throw error;
  463 + } finally {
  464 + setLoading(false);
  465 + }
  466 +};
  467 +
  468 +function handleSuccess() {
  469 + reload();
  470 +}
  471 +
  472 +function handleSelect(organization) {
  473 + searchInfo.organizationId = organization;
  474 + handleSuccess();
  475 +}
  476 +
  477 +</script>
  478 +
  479 +<style scoped lang="less">
  480 +.device-table {
  481 + :deep(.ant-form-item-control-input-content) {
  482 + & > div > div {
  483 + width: 100%;
  484 + }
  485 + }
  486 +}
  487 +</style>
  488 +
  489 +<style lang="less">
  490 +.device-status {
  491 + position: relative;
  492 +
  493 + .device-collect {
  494 + width: 0;
  495 + height: 0;
  496 + border-top: 30px solid #377dff;
  497 + border-right: 30px solid transparent;
  498 + }
  499 +}
  500 +
  501 +.device-name-edge {
  502 + width: 100%;
  503 + height: 100%;
  504 + padding: 0 !important;
  505 + position: relative;
  506 +}
  507 +</style>
... ...