Commit afde031f90ddbab1e1b958c78a4668a84f509831
Merge branch 'main_dev'
# Conflicts: # src/views/message/template/TemplateDrawer.vue
Showing
79 changed files
with
1186 additions
and
310 deletions
| ... | ... | @@ -53,6 +53,7 @@ export interface DataBoardRecord { |
| 53 | 53 | publicId: string; |
| 54 | 54 | organizationId?: string; |
| 55 | 55 | accessCredentials?: string; |
| 56 | + platform?: string; | |
| 56 | 57 | } |
| 57 | 58 | |
| 58 | 59 | export interface DataBoardList { |
| ... | ... | @@ -137,7 +138,12 @@ export interface MasterDeviceList { |
| 137 | 138 | } |
| 138 | 139 | |
| 139 | 140 | export interface ComponentInfoDetail { |
| 140 | - data: { componentData: DataComponentRecord[]; componentLayout: Layout[] }; | |
| 141 | + data: { | |
| 142 | + componentData: DataComponentRecord[]; | |
| 143 | + componentLayout: Layout[]; | |
| 144 | + phoneModel?: string | object; | |
| 145 | + platform: string; | |
| 146 | + }; | |
| 141 | 147 | } |
| 142 | 148 | |
| 143 | 149 | export interface DeviceAttributeParams { | ... | ... |
| ... | ... | @@ -7,6 +7,9 @@ export interface Specs { |
| 7 | 7 | unit: string; |
| 8 | 8 | unitName: string; |
| 9 | 9 | |
| 10 | + dataType?: string; | |
| 11 | + name?: string; | |
| 12 | + value?: string; | |
| 10 | 13 | step: string; |
| 11 | 14 | length: string; |
| 12 | 15 | boolOpen: string; |
| ... | ... | @@ -20,6 +23,7 @@ export interface Specs { |
| 20 | 23 | export interface DataType { |
| 21 | 24 | type: DataTypeEnum; |
| 22 | 25 | specs?: Partial<Specs> | StructJSON[]; |
| 26 | + specsList?: Specs[]; | |
| 23 | 27 | } |
| 24 | 28 | |
| 25 | 29 | export interface StructJSON { | ... | ... |
| ... | ... | @@ -21,6 +21,7 @@ enum Api { |
| 21 | 21 | SendLoginSmsCode = '/noauth/send_login_code/', |
| 22 | 22 | ResetCode = '/noauth/reset_code/', |
| 23 | 23 | ResetPassword = '/noauth/reset/', |
| 24 | + APP_GET_TOKEN = '/third/login/id/', | |
| 24 | 25 | } |
| 25 | 26 | |
| 26 | 27 | /** |
| ... | ... | @@ -100,3 +101,9 @@ export const getUserToken = (id: string) => { |
| 100 | 101 | url: `/third/login/id/${id}`, |
| 101 | 102 | }); |
| 102 | 103 | }; |
| 104 | + | |
| 105 | +export const doAppLogin = (userId: string) => { | |
| 106 | + return defHttp.get<Record<'refreshToken' | 'token', string>>({ | |
| 107 | + url: `${Api.APP_GET_TOKEN}${userId}`, | |
| 108 | + }); | |
| 109 | +}; | ... | ... |
src/assets/images/sn-step4.png
0 → 100644
197 KB
src/assets/svg/battery.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702374950759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1997" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M144.700101 684.994006l535.580045 0L680.280146 359.237781 144.700101 359.237781 144.700101 684.994006 144.700101 684.994006zM918.373823 440.680675l0-81.442894c0-44.791136-36.649711-81.437777-81.437777-81.437777l-692.235944 0c-44.791136 0-81.437777 36.646642-81.437777 81.437777L63.262324 684.994006c0 44.791136 36.646642 81.442894 81.437777 81.442894l692.235944 0c44.788066 0 81.437777-36.650735 81.437777-81.442894l0-81.437777c22.396079 0 40.7194-18.322297 40.7194-40.7194l0-81.436754C959.093223 459.003995 940.769902 440.680675 918.373823 440.680675L918.373823 440.680675zM877.655446 481.400075l0 81.436754L877.655446 684.994006c0 22.395056-18.323321 40.718377-40.7194 40.718377l-692.235944 0c-22.396079 0-40.7194-18.323321-40.7194-40.718377L103.980701 359.237781c0-22.396079 18.323321-40.7194 40.7194-40.7194l692.235944 0c22.396079 0 40.7194 18.323321 40.7194 40.7194L877.655446 481.400075 877.655446 481.400075zM877.655446 481.400075" fill="#272636" p-id="1998"></path></svg> | |
| \ No newline at end of file | ... | ... |
src/assets/svg/signal.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702374946029" class="icon" viewBox="0 0 1294 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1850" xmlns:xlink="http://www.w3.org/1999/xlink" width="252.734375" height="200"><path d="M0 727.578947l188.631579 0 0 296.421053-188.631579 0 0-296.421053Z" p-id="1851"></path><path d="M269.473684 565.894737l188.631579 0 0 458.105263-188.631579 0 0-458.105263Z" p-id="1852"></path><path d="M565.894737 377.263158l161.684211 0 0 646.736842-161.684211 0 0-646.736842Z" p-id="1853"></path><path d="M835.368421 188.631579l188.631579 0 0 835.368421-188.631579 0 0-835.368421Z" p-id="1854"></path><path d="M1104.842105 0l188.631579 0 0 1024-188.631579 0 0-1024Z" p-id="1855"></path></svg> | |
| \ No newline at end of file | ... | ... |
src/assets/svg/wifi.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702374954963" class="icon" viewBox="0 0 1280 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2277" xmlns:xlink="http://www.w3.org/1999/xlink" width="250" height="200"><path d="M1269.82 309.76C915.48-17.98 364.38-17.86 10.18 309.76c-13.32 12.32-13.58 33.18-0.7 45.96l68.48 67.94c12.28 12.2 32.04 12.46 44.8 0.76 291.84-267.36 742.6-267.42 1034.5 0 12.76 11.7 32.52 11.42 44.8-0.76l68.48-67.94c12.86-12.78 12.6-33.64-0.72-45.96zM640 704c-70.7 0-128 57.3-128 128s57.3 128 128 128 128-57.3 128-128-57.3-128-128-128z m405.34-167.18c-230.52-203.86-580.42-203.64-810.68 0-13.8 12.2-14.24 33.38-1.14 46.3l68.88 67.98c12 11.84 31.32 12.64 44.1 1.6 167.9-145.14 419.48-144.82 586.98 0 12.78 11.04 32.1 10.26 44.1-1.6l68.88-67.98c13.12-12.92 12.66-34.12-1.12-46.3z" p-id="2278"></path></svg> | |
| \ No newline at end of file | ... | ... |
| ... | ... | @@ -42,6 +42,7 @@ import InputGroup from './components/InputGroup.vue'; |
| 42 | 42 | import RegisterAddressInput from '/@/views/task/center/components/PollCommandInput/RegisterAddressInput.vue'; |
| 43 | 43 | import ExtendDesc from '/@/components/Form/src/externalCompns/components/ExtendDesc/index.vue'; |
| 44 | 44 | import DeviceProfileForm from '/@/components/Form/src/externalCompns/components/DeviceProfileForm/index.vue'; |
| 45 | +import EnumList from './externalCompns/components/StructForm/EnumList.vue'; | |
| 45 | 46 | |
| 46 | 47 | const componentMap = new Map<ComponentType, Component>(); |
| 47 | 48 | |
| ... | ... | @@ -90,6 +91,7 @@ componentMap.set('ApiSelectScrollLoad', ApiSelectScrollLoad); |
| 90 | 91 | componentMap.set('InputGroup', InputGroup); |
| 91 | 92 | componentMap.set('RegisterAddressInput', RegisterAddressInput); |
| 92 | 93 | componentMap.set('ExtendDesc', ExtendDesc); |
| 94 | +componentMap.set('EnumList', EnumList); | |
| 93 | 95 | componentMap.set('DeviceProfileForm', DeviceProfileForm); |
| 94 | 96 | |
| 95 | 97 | export function add(compName: ComponentType, component: Component) { | ... | ... |
| 1 | +import { FormSchema } from '/@/components/Table'; | |
| 2 | + | |
| 3 | +export enum FormFieldsEnum { | |
| 4 | + VALUE = 'value', | |
| 5 | + NAME = 'name', | |
| 6 | + DATA_TYPE = 'dataType', | |
| 7 | +} | |
| 8 | + | |
| 9 | +export const getFormSchemas = (): FormSchema[] => { | |
| 10 | + return [ | |
| 11 | + { | |
| 12 | + field: FormFieldsEnum.VALUE, | |
| 13 | + label: '', | |
| 14 | + component: 'InputNumber', | |
| 15 | + rules: [ | |
| 16 | + { required: true, message: `支持整型,取值范围:-2147483648 ~ 2147483647`, type: 'number' }, | |
| 17 | + ], | |
| 18 | + componentProps: () => { | |
| 19 | + return { | |
| 20 | + placeholder: '编号如"0"', | |
| 21 | + min: -2147483648, | |
| 22 | + max: 2147483647, | |
| 23 | + step: 1, | |
| 24 | + precision: 0, | |
| 25 | + }; | |
| 26 | + }, | |
| 27 | + colProps: { | |
| 28 | + span: 11, | |
| 29 | + }, | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + field: 'division', | |
| 33 | + label: '', | |
| 34 | + component: 'Input', | |
| 35 | + slot: 'division', | |
| 36 | + colProps: { | |
| 37 | + span: 1, | |
| 38 | + }, | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + field: FormFieldsEnum.NAME, | |
| 42 | + label: '', | |
| 43 | + component: 'Input', | |
| 44 | + rules: [ | |
| 45 | + { | |
| 46 | + required: true, | |
| 47 | + message: `支持中文、英文大小写、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符`, | |
| 48 | + type: 'string', | |
| 49 | + pattern: /^[a-zA-Z0-9\u4e00-\u9fa5a][\u4e00-\u9fa5a-zA-Z0-9_-]*$/, | |
| 50 | + }, | |
| 51 | + ], | |
| 52 | + componentProps: () => { | |
| 53 | + return { | |
| 54 | + placeholder: '对该枚举项的描述', | |
| 55 | + maxLength: 20, | |
| 56 | + }; | |
| 57 | + }, | |
| 58 | + colProps: { | |
| 59 | + span: 11, | |
| 60 | + }, | |
| 61 | + }, | |
| 62 | + ]; | |
| 63 | +}; | ... | ... |
| 1 | +<script setup lang="ts"> | |
| 2 | + import { Button, Tooltip } from 'ant-design-vue'; | |
| 3 | + import { computed, nextTick, ref, unref, watch } from 'vue'; | |
| 4 | + import { useForm, BasicForm } from '/@/components/Form'; | |
| 5 | + import { Specs } from '/@/api/device/model/modelOfMatterModel'; | |
| 6 | + import { Icon } from '/@/components/Icon'; | |
| 7 | + import { getFormSchemas } from './EnumList.config'; | |
| 8 | + import { FormActionType } from '../../../types/form'; | |
| 9 | + import { buildUUID } from '/@/utils/uuid'; | |
| 10 | + import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 11 | + import { isNullOrUnDef } from '/@/utils/is'; | |
| 12 | + | |
| 13 | + const props = defineProps<{ disabled?: boolean; value?: Specs[] }>(); | |
| 14 | + | |
| 15 | + interface EnumElItemType { | |
| 16 | + uuid: string; | |
| 17 | + formActionType?: FormActionType; | |
| 18 | + dataSource?: Recordable; | |
| 19 | + } | |
| 20 | + | |
| 21 | + const [registerForm] = useForm({ | |
| 22 | + schemas: getFormSchemas(), | |
| 23 | + showActionButtonGroup: false, | |
| 24 | + layout: 'inline', | |
| 25 | + }); | |
| 26 | + | |
| 27 | + const enumsListElRef = ref<EnumElItemType[]>([{ uuid: buildUUID() }]); | |
| 28 | + | |
| 29 | + const setFormActionType = (item: EnumElItemType, el: any) => { | |
| 30 | + item.formActionType = el as unknown as FormActionType; | |
| 31 | + }; | |
| 32 | + | |
| 33 | + const getEnumsLimit = computed(() => unref(enumsListElRef).length >= 100); | |
| 34 | + | |
| 35 | + const hasSameEnum = ref(false); | |
| 36 | + | |
| 37 | + const validateSameEnum = () => { | |
| 38 | + const value = getFieldsValue(); | |
| 39 | + hasSameEnum.value = false; | |
| 40 | + const values = value.map((item) => item.value).filter((value) => !isNullOrUnDef(value)); | |
| 41 | + const names = value.map((item) => item.name).filter((value) => !isNullOrUnDef(value)); | |
| 42 | + | |
| 43 | + if (values.length !== new Set(values).size || names.length !== new Set(names).size) { | |
| 44 | + hasSameEnum.value = true; | |
| 45 | + } | |
| 46 | + }; | |
| 47 | + | |
| 48 | + const validate = async () => { | |
| 49 | + if (unref(hasSameEnum)) throw Error('存在相同的枚举'); | |
| 50 | + for (const enumElItem of unref(enumsListElRef)) { | |
| 51 | + await enumElItem.formActionType?.validate?.(); | |
| 52 | + } | |
| 53 | + validateSameEnum(); | |
| 54 | + }; | |
| 55 | + | |
| 56 | + const getFieldsValue = () => { | |
| 57 | + return unref(enumsListElRef).map( | |
| 58 | + (item) => | |
| 59 | + ({ | |
| 60 | + ...(item.formActionType?.getFieldsValue?.() || {}), | |
| 61 | + dataType: DataTypeEnum.ENUM, | |
| 62 | + } as Specs) | |
| 63 | + ); | |
| 64 | + }; | |
| 65 | + | |
| 66 | + const setFieldsValue = (spaceList: Specs[]) => { | |
| 67 | + enumsListElRef.value = spaceList.map((item) => ({ uuid: buildUUID(), dataSource: item })); | |
| 68 | + | |
| 69 | + nextTick(() => { | |
| 70 | + unref(enumsListElRef).forEach((item) => | |
| 71 | + item.formActionType?.setFieldsValue?.(item.dataSource) | |
| 72 | + ); | |
| 73 | + }); | |
| 74 | + }; | |
| 75 | + | |
| 76 | + const handleDeleteEnums = (item: EnumElItemType) => { | |
| 77 | + const index = unref(enumsListElRef).findIndex((temp) => item.uuid === temp.uuid); | |
| 78 | + | |
| 79 | + if (~index) { | |
| 80 | + enumsListElRef.value.splice(index, 1); | |
| 81 | + validateSameEnum(); | |
| 82 | + } | |
| 83 | + }; | |
| 84 | + | |
| 85 | + const handleAddEnums = () => { | |
| 86 | + unref(enumsListElRef).push({ uuid: buildUUID() }); | |
| 87 | + }; | |
| 88 | + | |
| 89 | + watch( | |
| 90 | + () => props.value, | |
| 91 | + (target) => { | |
| 92 | + setFieldsValue(target || [{} as Specs]); | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + immediate: true, | |
| 96 | + } | |
| 97 | + ); | |
| 98 | + | |
| 99 | + defineExpose({ | |
| 100 | + validate, | |
| 101 | + getFieldsValue, | |
| 102 | + setFieldsValue, | |
| 103 | + }); | |
| 104 | +</script> | |
| 105 | + | |
| 106 | +<template> | |
| 107 | + <section class="w-full"> | |
| 108 | + <header class="flex h-8 items-center"> | |
| 109 | + <div class="w-1/2"> | |
| 110 | + <span>参考值</span> | |
| 111 | + <Tooltip title="支持整型,取值范围:-2147483648 ~ 2147483647"> | |
| 112 | + <Icon icon="ant-design:question-circle-outlined" class="cursor-pointer ml-1" /> | |
| 113 | + </Tooltip> | |
| 114 | + </div> | |
| 115 | + <div class="w-1/2"> | |
| 116 | + <span>参考描述</span> | |
| 117 | + <Tooltip | |
| 118 | + title="支持中文、英文大小写、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符" | |
| 119 | + > | |
| 120 | + <Icon icon="ant-design:question-circle-outlined" class="cursor-pointer ml-1" /> | |
| 121 | + </Tooltip> | |
| 122 | + </div> | |
| 123 | + </header> | |
| 124 | + <main class="w-full"> | |
| 125 | + <section class="w-full flex" v-for="item in enumsListElRef" :key="item.uuid"> | |
| 126 | + <BasicForm | |
| 127 | + :ref="(el) => setFormActionType(item, el)" | |
| 128 | + @register="registerForm" | |
| 129 | + class="enums-form" | |
| 130 | + :disabled="disabled" | |
| 131 | + @field-value-change="validateSameEnum" | |
| 132 | + > | |
| 133 | + <template #division> | |
| 134 | + <div>~</div> | |
| 135 | + </template> | |
| 136 | + </BasicForm> | |
| 137 | + <Button | |
| 138 | + type="link" | |
| 139 | + class="relative -left-6" | |
| 140 | + :disabled="disabled" | |
| 141 | + @click="handleDeleteEnums(item)" | |
| 142 | + > | |
| 143 | + 删除 | |
| 144 | + </Button> | |
| 145 | + </section> | |
| 146 | + </main> | |
| 147 | + <div v-if="hasSameEnum" class="text-red-400">枚举项中存在相同的参数值或参数描述</div> | |
| 148 | + <Tooltip title="枚举项最多创建 100 个"> | |
| 149 | + <Button type="link" @click="handleAddEnums" :disabled="disabled || getEnumsLimit"> | |
| 150 | + +添加枚举项 | |
| 151 | + </Button> | |
| 152 | + </Tooltip> | |
| 153 | + </section> | |
| 154 | +</template> | |
| 155 | + | |
| 156 | +<style scoped lang="less"> | |
| 157 | + .enums-form { | |
| 158 | + @apply w-full; | |
| 159 | + | |
| 160 | + > :deep(.ant-row) { | |
| 161 | + @apply w-full; | |
| 162 | + | |
| 163 | + .ant-input-number { | |
| 164 | + width: 100%; | |
| 165 | + } | |
| 166 | + } | |
| 167 | + } | |
| 168 | +</style> | ... | ... |
| ... | ... | @@ -14,6 +14,8 @@ |
| 14 | 14 | import { DataType, StructJSON } from '/@/api/device/model/modelOfMatterModel'; |
| 15 | 15 | import { isArray } from '/@/utils/is'; |
| 16 | 16 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 17 | + import EnumList from './EnumList.vue'; | |
| 18 | + import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 17 | 19 | |
| 18 | 20 | const modalReceiveRecord = ref<OpenModalParams>({ |
| 19 | 21 | mode: OpenModalMode.CREATE, |
| ... | ... | @@ -26,6 +28,8 @@ |
| 26 | 28 | hiddenAccessMode: boolean; |
| 27 | 29 | }>(); |
| 28 | 30 | |
| 31 | + const enumListRef = ref<InstanceType<typeof EnumList>>(); | |
| 32 | + | |
| 29 | 33 | const emit = defineEmits(['register', 'submit']); |
| 30 | 34 | |
| 31 | 35 | const { createMessage } = useMessage(); |
| ... | ... | @@ -53,13 +57,14 @@ |
| 53 | 57 | modalReceiveRecord.value = record; |
| 54 | 58 | const data = record.record || {}; |
| 55 | 59 | const { dataType = {} } = data! as StructJSON; |
| 56 | - const { specs = {}, type } = dataType as DataType; | |
| 60 | + const { specs = {}, type, specsList } = dataType as DataType; | |
| 57 | 61 | |
| 58 | 62 | if (record.record) { |
| 59 | 63 | const value = { |
| 60 | 64 | type, |
| 61 | 65 | ...data, |
| 62 | 66 | ...(isArray(specs) ? { specs } : { ...specs }), |
| 67 | + enumList: type === DataTypeEnum.ENUM ? specsList : [], | |
| 63 | 68 | }; |
| 64 | 69 | |
| 65 | 70 | setFieldsValue(value); |
| ... | ... | @@ -74,7 +79,8 @@ |
| 74 | 79 | const handleSubmit = async () => { |
| 75 | 80 | try { |
| 76 | 81 | const _value = await validate(); |
| 77 | - let structJSON = transfromToStructJSON(_value); | |
| 82 | + await unref(enumListRef)?.validate?.(); | |
| 83 | + let structJSON = transfromToStructJSON(_value, unref(enumListRef)?.getFieldsValue?.() || []); | |
| 78 | 84 | const value = { |
| 79 | 85 | ...structJSON, |
| 80 | 86 | ...(unref(modalReceiveRecord)?.record?.id |
| ... | ... | @@ -104,7 +110,11 @@ |
| 104 | 110 | destroy-on-close |
| 105 | 111 | :show-ok-btn="!$props.disabled" |
| 106 | 112 | > |
| 107 | - <BasicForm @register="register" :schemas="getFormSchemas" /> | |
| 113 | + <BasicForm @register="register" :schemas="getFormSchemas"> | |
| 114 | + <template #EnumList="{ field, model }"> | |
| 115 | + <EnumList ref="enumListRef" :value="model[field]" :disabled="disabled" /> | |
| 116 | + </template> | |
| 117 | + </BasicForm> | |
| 108 | 118 | </BasicModal> |
| 109 | 119 | </template> |
| 110 | 120 | ... | ... |
| ... | ... | @@ -37,7 +37,7 @@ const validateExcludeComma = (field: string, errorName: string): Rule[] => { |
| 37 | 37 | validator: () => { |
| 38 | 38 | const reg = /[,,]+/; |
| 39 | 39 | if (reg.test(field)) { |
| 40 | - return Promise.reject(errorName); | |
| 40 | + return Promise.reject(`${errorName}不能包含逗号`); | |
| 41 | 41 | } |
| 42 | 42 | return Promise.resolve(); |
| 43 | 43 | }, |
| ... | ... | @@ -64,7 +64,10 @@ export const formSchemas = ({ |
| 64 | 64 | placeholder: '请输入功能名称', |
| 65 | 65 | }, |
| 66 | 66 | dynamicRules: ({ values }) => { |
| 67 | - return validateExcludeComma(values[FormField.FUNCTION_NAME], '功能名称不能包含逗号'); | |
| 67 | + return [ | |
| 68 | + { required: true, message: '请输入功能名称' }, | |
| 69 | + ...validateExcludeComma(values[FormField.FUNCTION_NAME], '功能名称'), | |
| 70 | + ]; | |
| 68 | 71 | }, |
| 69 | 72 | }, |
| 70 | 73 | { |
| ... | ... | @@ -80,7 +83,10 @@ export const formSchemas = ({ |
| 80 | 83 | placeholder: '请输入标识符', |
| 81 | 84 | }, |
| 82 | 85 | dynamicRules: ({ values }) => { |
| 83 | - return validateExcludeComma(values[FormField.IDENTIFIER], '标识符不能包含逗号'); | |
| 86 | + return [ | |
| 87 | + { required: true, message: '请输入标识符' }, | |
| 88 | + ...validateExcludeComma(values[FormField.IDENTIFIER], '标识符'), | |
| 89 | + ]; | |
| 84 | 90 | }, |
| 85 | 91 | }, |
| 86 | 92 | { |
| ... | ... | @@ -105,6 +111,19 @@ export const formSchemas = ({ |
| 105 | 111 | api: async (params: Recordable) => { |
| 106 | 112 | try { |
| 107 | 113 | const record = await findDictItemByCode(params); |
| 114 | + | |
| 115 | + if (isTcp) { | |
| 116 | + // TCP 产品 属性可创建范围 | |
| 117 | + return record.filter((item) => | |
| 118 | + [ | |
| 119 | + DataTypeEnum.BOOL, | |
| 120 | + DataTypeEnum.NUMBER_DOUBLE, | |
| 121 | + DataTypeEnum.NUMBER_INT, | |
| 122 | + DataTypeEnum.STRING, | |
| 123 | + ].includes(item.itemValue as DataTypeEnum) | |
| 124 | + ); | |
| 125 | + } | |
| 126 | + | |
| 108 | 127 | return hasStructForm |
| 109 | 128 | ? record.filter((item) => item.itemValue !== DataTypeEnum.STRUCT) |
| 110 | 129 | : record; |
| ... | ... | @@ -127,6 +146,16 @@ export const formSchemas = ({ |
| 127 | 146 | }, |
| 128 | 147 | }, |
| 129 | 148 | { |
| 149 | + field: FormField.ENUM_LIST, | |
| 150 | + component: 'Input', | |
| 151 | + label: '枚举', | |
| 152 | + ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.ENUM, | |
| 153 | + slot: 'EnumList', | |
| 154 | + colProps: { | |
| 155 | + span: 24, | |
| 156 | + }, | |
| 157 | + }, | |
| 158 | + { | |
| 130 | 159 | field: FormField.VALUE_RANGE, |
| 131 | 160 | label: '取值范围', |
| 132 | 161 | component: 'CustomMinMaxInput', | ... | ... |
| 1 | 1 | import { cloneDeep } from 'lodash-es'; |
| 2 | 2 | import { StructFormValue } from './type'; |
| 3 | -import { DataType, ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 3 | +import { | |
| 4 | + DataType, | |
| 5 | + ModelOfMatterParams, | |
| 6 | + Specs, | |
| 7 | + StructJSON, | |
| 8 | +} from '/@/api/device/model/modelOfMatterModel'; | |
| 4 | 9 | import { isArray } from '/@/utils/is'; |
| 5 | 10 | import { DataTypeEnum } from '/@/enums/objectModelEnum'; |
| 6 | 11 | |
| 7 | -export function transfromToStructJSON(value: StructFormValue): StructJSON { | |
| 12 | +export function transfromToStructJSON(value: StructFormValue, enumList: Specs[] = []): StructJSON { | |
| 8 | 13 | const { |
| 9 | 14 | type, |
| 10 | 15 | valueRange, |
| ... | ... | @@ -55,6 +60,13 @@ export function transfromToStructJSON(value: StructFormValue): StructJSON { |
| 55 | 60 | }; |
| 56 | 61 | break; |
| 57 | 62 | |
| 63 | + case DataTypeEnum.ENUM: | |
| 64 | + dataType = { | |
| 65 | + type, | |
| 66 | + specsList: enumList, | |
| 67 | + }; | |
| 68 | + break; | |
| 69 | + | |
| 58 | 70 | case DataTypeEnum.STRUCT: |
| 59 | 71 | dataType = { |
| 60 | 72 | type, |
| ... | ... | @@ -62,12 +74,13 @@ export function transfromToStructJSON(value: StructFormValue): StructJSON { |
| 62 | 74 | }; |
| 63 | 75 | break; |
| 64 | 76 | } |
| 65 | - return { ...basic, dataType }; | |
| 77 | + return { ...basic, dataType } as StructJSON; | |
| 66 | 78 | } |
| 67 | 79 | |
| 68 | 80 | export const excludeIdInStructJSON = (struct: DataType) => { |
| 69 | 81 | const _value = cloneDeep(struct); |
| 70 | 82 | const { specs } = _value; |
| 83 | + if (!specs) return _value; | |
| 71 | 84 | const list = [specs]; |
| 72 | 85 | |
| 73 | 86 | while (list.length) { |
| ... | ... | @@ -77,10 +90,10 @@ export const excludeIdInStructJSON = (struct: DataType) => { |
| 77 | 90 | if (temp.dataType?.specs) { |
| 78 | 91 | list.push(temp.dataType.specs); |
| 79 | 92 | } |
| 80 | - Reflect.deleteProperty(temp, 'id'); | |
| 93 | + Reflect.has(temp, 'id') && Reflect.deleteProperty(temp, 'id'); | |
| 81 | 94 | }); |
| 82 | 95 | } else { |
| 83 | - Reflect.deleteProperty(item as Recordable, 'id'); | |
| 96 | + Reflect.has(item as Recordable, 'id') && Reflect.deleteProperty(item as Recordable, 'id'); | |
| 84 | 97 | } |
| 85 | 98 | list.shift(); |
| 86 | 99 | } | ... | ... |
| ... | ... | @@ -101,6 +101,26 @@ export const getFormSchemas = ({ |
| 101 | 101 | }; |
| 102 | 102 | }; |
| 103 | 103 | |
| 104 | + const createEnumsSelect = ({ identifier, functionName, dataType }: StructJSON): FormSchema => { | |
| 105 | + const { specsList } = dataType || {}; | |
| 106 | + return { | |
| 107 | + field: identifier, | |
| 108 | + label: functionName!, | |
| 109 | + component: 'Select', | |
| 110 | + rules: [ | |
| 111 | + { | |
| 112 | + required, | |
| 113 | + message: `${functionName}是必填项`, | |
| 114 | + type: 'number', | |
| 115 | + }, | |
| 116 | + ], | |
| 117 | + componentProps: { | |
| 118 | + options: specsList?.map((item) => ({ label: item.name, value: item.value })), | |
| 119 | + placeholder: `请选择${functionName}`, | |
| 120 | + }, | |
| 121 | + }; | |
| 122 | + }; | |
| 123 | + | |
| 104 | 124 | const createStructJson = ({ identifier, functionName, dataType }: StructJSON): FormSchema => { |
| 105 | 125 | return { |
| 106 | 126 | field: identifier, |
| ... | ... | @@ -140,6 +160,7 @@ export const getFormSchemas = ({ |
| 140 | 160 | } |
| 141 | 161 | |
| 142 | 162 | if (type === DataTypeEnum.BOOL) schemas.push(createSelect(item)); |
| 163 | + else if (type === DataTypeEnum.ENUM) schemas.push(createEnumsSelect(item)); | |
| 143 | 164 | else if (type === DataTypeEnum.NUMBER_INT) schemas.push(createInputNumber(item)); |
| 144 | 165 | else if (type === DataTypeEnum.NUMBER_DOUBLE) schemas.push(createInputNumber(item)); |
| 145 | 166 | else if (type === DataTypeEnum.STRING) schemas.push(createInput(item)); | ... | ... |
| ... | ... | @@ -4,12 +4,12 @@ |
| 4 | 4 | <Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)"> |
| 5 | 5 | <PopConfirmButton v-bind="action"> |
| 6 | 6 | <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
| 7 | - <template v-if="action.label">{{ action.label }}</template> | |
| 7 | + <template v-if="action.label"><Label :label="action.label" /></template> | |
| 8 | 8 | </PopConfirmButton> |
| 9 | 9 | </Tooltip> |
| 10 | 10 | <PopConfirmButton v-else v-bind="action"> |
| 11 | 11 | <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
| 12 | - <template v-if="action.label">{{ action.label }}</template> | |
| 12 | + <template v-if="action.label"><Label :label="action.label" /> </template> | |
| 13 | 13 | </PopConfirmButton> |
| 14 | 14 | <Divider |
| 15 | 15 | type="vertical" |
| ... | ... | @@ -36,7 +36,7 @@ |
| 36 | 36 | </div> |
| 37 | 37 | </template> |
| 38 | 38 | <script lang="ts"> |
| 39 | - import { defineComponent, PropType, computed, toRaw, unref } from 'vue'; | |
| 39 | + import { defineComponent, PropType, computed, toRaw, unref, VNode } from 'vue'; | |
| 40 | 40 | // import { MoreOutlined } from '@ant-design/icons-vue'; |
| 41 | 41 | import { Divider, Tooltip, TooltipProps } from 'ant-design-vue'; |
| 42 | 42 | import Icon from '/@/components/Icon/index'; |
| ... | ... | @@ -52,7 +52,14 @@ |
| 52 | 52 | |
| 53 | 53 | export default defineComponent({ |
| 54 | 54 | name: 'TableAction', |
| 55 | - components: { Icon, PopConfirmButton, Divider, Dropdown, Tooltip }, | |
| 55 | + components: { | |
| 56 | + Icon, | |
| 57 | + PopConfirmButton, | |
| 58 | + Divider, | |
| 59 | + Dropdown, | |
| 60 | + Tooltip, | |
| 61 | + Label: (props: { label: VNode | string }) => props.label, | |
| 62 | + }, | |
| 56 | 63 | props: { |
| 57 | 64 | actions: { |
| 58 | 65 | type: Array as PropType<ActionItem[]>, | ... | ... |
| 1 | 1 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; |
| 2 | 2 | import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; |
| 3 | 3 | import { RoleEnum } from '/@/enums/roleEnum'; |
| 4 | +import { VNode } from 'vue'; | |
| 4 | 5 | export interface ActionItem extends ButtonProps { |
| 5 | 6 | onClick?: Fn; |
| 6 | - label?: string; | |
| 7 | + label?: string | VNode; | |
| 7 | 8 | color?: 'success' | 'error' | 'warning'; |
| 8 | 9 | icon?: string; |
| 9 | 10 | popConfirm?: PopConfirm; | ... | ... |
| ... | ... | @@ -2,7 +2,7 @@ import { RouteLocationNormalizedLoaded } from 'vue-router'; |
| 2 | 2 | |
| 3 | 3 | const menuMap = new Map(); |
| 4 | 4 | |
| 5 | -menuMap.set('/visual/board/detail/:boardId/:boardName/:organizationId?', '/visual/board'); | |
| 5 | +menuMap.set('/visual/board/detail/:boardId/:boardName/:platform/:organizationId?', '/visual/board'); | |
| 6 | 6 | menuMap.set('/rule/chain/:id', '/rule/chain'); |
| 7 | 7 | |
| 8 | 8 | export const useMenuActiveFix = (route: RouteLocationNormalizedLoaded) => { | ... | ... |
| ... | ... | @@ -11,7 +11,8 @@ import { getAuthCache } from '/@/utils/auth'; |
| 11 | 11 | const LOGIN_PATH = PageEnum.BASE_LOGIN; |
| 12 | 12 | const ROOT_PATH = RootRoute.path; |
| 13 | 13 | const SHARE_PATH = PageEnum.SHARE_PAGE; |
| 14 | -const whitePathList: string[] = [LOGIN_PATH, SHARE_PATH]; | |
| 14 | +const APP_PATH = PageEnum.APP_PAGE; | |
| 15 | +const whitePathList: string[] = [LOGIN_PATH, SHARE_PATH, APP_PATH]; | |
| 15 | 16 | // const userInfo1 = getAuthCache(USER_INFO_KEY); |
| 16 | 17 | // const userInfo = ref(userInfo1); |
| 17 | 18 | ... | ... |
src/router/routes/appPage.ts
0 → 100644
| 1 | +import { AppRouteRecordRaw } from '../types'; | |
| 2 | +import { PageEnum } from '/@/enums/pageEnum'; | |
| 3 | + | |
| 4 | +export const APP_PAGE_ROUTER: AppRouteRecordRaw = { | |
| 5 | + path: PageEnum.APP_PAGE, | |
| 6 | + name: 'appPage', | |
| 7 | + component: () => import('/@/views/sys/appPage/index.vue'), | |
| 8 | + meta: { | |
| 9 | + title: '公开', | |
| 10 | + hideBreadcrumb: true, | |
| 11 | + hideChildrenInMenu: true, | |
| 12 | + }, | |
| 13 | +}; | ... | ... |
| ... | ... | @@ -5,6 +5,7 @@ import { PageEnum } from '/@/enums/pageEnum'; |
| 5 | 5 | import { t } from '/@/hooks/web/useI18n'; |
| 6 | 6 | import { LAYOUT } from '../constant'; |
| 7 | 7 | import { PUBLIC_PAGE_ROUTER } from './public'; |
| 8 | +import { APP_PAGE_ROUTER } from './appPage'; | |
| 8 | 9 | |
| 9 | 10 | const modules = import.meta.globEager('./modules/**/*.ts'); |
| 10 | 11 | const routeModuleList: AppRouteModule[] = []; |
| ... | ... | @@ -87,4 +88,5 @@ export const basicRoutes = [ |
| 87 | 88 | REDIRECT_ROUTE, |
| 88 | 89 | PAGE_NOT_FOUND_ROUTE, |
| 89 | 90 | PUBLIC_PAGE_ROUTER, |
| 91 | + APP_PAGE_ROUTER, | |
| 90 | 92 | ]; | ... | ... |
| ... | ... | @@ -12,7 +12,16 @@ |
| 12 | 12 | <a-select |
| 13 | 13 | placeholder="请选择流媒体配置" |
| 14 | 14 | v-model:value="model[field]" |
| 15 | - :options="streamConfigOptions.map((item) => ({ value: item.value, label: item.label }))" | |
| 15 | + :options=" | |
| 16 | + streamConfigOptions | |
| 17 | + .filter((item) => item.type === model.videoType) | |
| 18 | + .map((item) => ({ | |
| 19 | + value: item.value, | |
| 20 | + label: item.label, | |
| 21 | + type: item.type, | |
| 22 | + })) | |
| 23 | + " | |
| 24 | + @change="handleChange" | |
| 16 | 25 | > |
| 17 | 26 | <template #dropdownRender="{ menuNode: menu }"> |
| 18 | 27 | <v-nodes :vnodes="menu" /> |
| ... | ... | @@ -31,7 +40,7 @@ |
| 31 | 40 | <script lang="ts"> |
| 32 | 41 | import { defineComponent, ref, computed, unref, nextTick, onMounted } from 'vue'; |
| 33 | 42 | import { BasicForm, useForm } from '/@/components/Form'; |
| 34 | - import { formSchema } from './config.data'; | |
| 43 | + import { AccessMode, formSchema, VideoPlatformEnum } from './config.data'; | |
| 35 | 44 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
| 36 | 45 | import { createOrEditCameraManage } from '/@/api/camera/cameraManager'; |
| 37 | 46 | import { useMessage } from '/@/hooks/web/useMessage'; |
| ... | ... | @@ -70,6 +79,7 @@ |
| 70 | 79 | return { |
| 71 | 80 | label: m.host, |
| 72 | 81 | value: m.id, |
| 82 | + type: m.type, | |
| 73 | 83 | }; |
| 74 | 84 | }); |
| 75 | 85 | }); |
| ... | ... | @@ -96,14 +106,26 @@ |
| 96 | 106 | isUpdate.value = !!data?.isUpdate; |
| 97 | 107 | if (unref(isUpdate)) { |
| 98 | 108 | await nextTick(); |
| 109 | + const { record } = data || {}; | |
| 99 | 110 | editId.value = data.record.id; |
| 111 | + | |
| 100 | 112 | if (data.record.avatar) { |
| 101 | - setFieldsValue({ | |
| 102 | - avatar: [{ uid: buildUUID(), name: 'name', url: data.record.avatar } as FileItem], | |
| 113 | + Object.assign(record, { | |
| 114 | + avatar: [{ uid: buildUUID(), name: 'name', url: record.avatar } as FileItem], | |
| 103 | 115 | }); |
| 104 | 116 | } |
| 105 | - const { ...params } = data.record; | |
| 106 | - await setFieldsValue({ ...params }); | |
| 117 | + | |
| 118 | + if ( | |
| 119 | + record?.accessMode === AccessMode.Streaming && | |
| 120 | + record.videoPlatformDTO?.type === VideoPlatformEnum.FLUORITE | |
| 121 | + ) { | |
| 122 | + Object.assign(record, { | |
| 123 | + articulation: record.streamType, | |
| 124 | + videoFormat: record.playProtocol, | |
| 125 | + }); | |
| 126 | + } | |
| 127 | + | |
| 128 | + setFieldsValue({ ...record, videoType: record.videoPlatformDTO?.type }); | |
| 107 | 129 | } else { |
| 108 | 130 | editId.value = ''; |
| 109 | 131 | } |
| ... | ... | @@ -128,6 +150,15 @@ |
| 128 | 150 | } |
| 129 | 151 | let saveMessage = '添加成功'; |
| 130 | 152 | let updateMessage = '修改成功'; |
| 153 | + | |
| 154 | + if ( | |
| 155 | + values?.accessMode === AccessMode.Streaming && | |
| 156 | + values.videoType === VideoPlatformEnum.FLUORITE | |
| 157 | + ) { | |
| 158 | + values.streamType = values.articulation; | |
| 159 | + values.playProtocol = values.videoFormat; | |
| 160 | + } | |
| 161 | + | |
| 131 | 162 | await createOrEditCameraManage(values); |
| 132 | 163 | closeDrawer(); |
| 133 | 164 | emit('success'); |
| ... | ... | @@ -139,6 +170,11 @@ |
| 139 | 170 | } |
| 140 | 171 | } |
| 141 | 172 | |
| 173 | + const handleChange = (e, options) => { | |
| 174 | + //流媒体配置 | |
| 175 | + setFieldsValue({ videoType: e ? options.type : null }); | |
| 176 | + }; | |
| 177 | + | |
| 142 | 178 | return { |
| 143 | 179 | getTitle, |
| 144 | 180 | registerDrawer, |
| ... | ... | @@ -149,6 +185,7 @@ |
| 149 | 185 | registerSteramingDrawer, |
| 150 | 186 | handleOpenStreamConfig, |
| 151 | 187 | handleSuccess, |
| 188 | + handleChange, | |
| 152 | 189 | }; |
| 153 | 190 | }, |
| 154 | 191 | }); | ... | ... |
| ... | ... | @@ -42,6 +42,8 @@ |
| 42 | 42 | |
| 43 | 43 | const withToken = ref(false); |
| 44 | 44 | |
| 45 | + const videoId = ref<string>(); | |
| 46 | + | |
| 45 | 47 | const fingerprintResult = ref<Nullable<GetResult>>(null); |
| 46 | 48 | |
| 47 | 49 | const options = reactive<VideoJsPlayerOptions>({ |
| ... | ... | @@ -64,6 +66,7 @@ |
| 64 | 66 | const [register] = useModalInner( |
| 65 | 67 | async (data: { record: CameraModel | StreamingManageRecord }) => { |
| 66 | 68 | const { record } = data; |
| 69 | + videoId.value = record.id || ''; | |
| 67 | 70 | const result = await getResult(); |
| 68 | 71 | fingerprintResult.value = result; |
| 69 | 72 | if (record.accessMode === AccessMode.ManuallyEnter) { | ... | ... |
src/views/camera/manage/SnHelpMessage1.vue
0 → 100644
| 1 | +<script lang="ts" setup> | |
| 2 | + import { BasicHelp } from '/@/components/Basic'; | |
| 3 | + import { createImgPreview } from '/@/components/Preview/index'; | |
| 4 | + import snStep4 from '/@/assets/images/sn-step4.png'; | |
| 5 | + const imgList: string[] = [snStep4]; | |
| 6 | + function handlePreview() { | |
| 7 | + createImgPreview({ imageList: imgList, defaultWidth: 1000 }); | |
| 8 | + } | |
| 9 | +</script> | |
| 10 | + | |
| 11 | +<template> | |
| 12 | + <div>监控点编号</div> | |
| 13 | + <BasicHelp | |
| 14 | + placement="top" | |
| 15 | + @click="handlePreview" | |
| 16 | + class="mx-1" | |
| 17 | + text='点击查看如何获取"监控点编号"' | |
| 18 | + /> | |
| 19 | +</template> | ... | ... |
| ... | ... | @@ -306,7 +306,7 @@ |
| 306 | 306 | @on-unmounted="handleCloseFlvPlayUrl(item)" |
| 307 | 307 | /> |
| 308 | 308 | <div |
| 309 | - class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center items-center" | |
| 309 | + class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center" | |
| 310 | 310 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" |
| 311 | 311 | > |
| 312 | 312 | <span>{{ item.name }}</span> | ... | ... |
| ... | ... | @@ -4,10 +4,13 @@ import { FormSchema as QFormSchema, useComponentRegister } from '/@/components/F |
| 4 | 4 | import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules'; |
| 5 | 5 | import { h } from 'vue'; |
| 6 | 6 | import SnHelpMessage from './SnHelpMessage.vue'; |
| 7 | +import SnHelpMessage1 from './SnHelpMessage1.vue'; | |
| 7 | 8 | import { OrgTreeSelect } from '../../common/OrgTreeSelect'; |
| 8 | 9 | import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; |
| 9 | 10 | import { createImgPreview } from '/@/components/Preview'; |
| 10 | 11 | import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; |
| 12 | +import { findDictItemByCode } from '/@/api/system/dict'; | |
| 13 | +import { DictEnum } from '/@/enums/dictEnum'; | |
| 11 | 14 | |
| 12 | 15 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
| 13 | 16 | |
| ... | ... | @@ -45,6 +48,19 @@ export enum MediaType { |
| 45 | 48 | M3U8 = 'm3u8', |
| 46 | 49 | } |
| 47 | 50 | |
| 51 | +export enum FluoriteMideaProtocolEnum { | |
| 52 | + HLS = 2, | |
| 53 | + FLV = 4, | |
| 54 | +} | |
| 55 | + | |
| 56 | +export enum ArticulationEnumType { | |
| 57 | + HIGH_DEFINITION = 1, | |
| 58 | + SMOOTH = 2, | |
| 59 | +} | |
| 60 | +export enum ArticulationEnumNameType { | |
| 61 | + HIGH_DEFINITION = '高清', | |
| 62 | + SMOOTH = '流畅', | |
| 63 | +} | |
| 48 | 64 | // 表格列数据 |
| 49 | 65 | export const columns: BasicColumn[] = [ |
| 50 | 66 | { |
| ... | ... | @@ -100,6 +116,13 @@ export const searchFormSchema: FormSchema[] = [ |
| 100 | 116 | }, |
| 101 | 117 | ]; |
| 102 | 118 | |
| 119 | +export enum VideoPlatformEnum { | |
| 120 | + // 海康 | |
| 121 | + SCI = 0, | |
| 122 | + // 萤石云 | |
| 123 | + FLUORITE = 1, | |
| 124 | +} | |
| 125 | + | |
| 103 | 126 | // 弹框配置项 |
| 104 | 127 | export const formSchema: QFormSchema[] = [ |
| 105 | 128 | { |
| ... | ... | @@ -215,7 +238,25 @@ export const formSchema: QFormSchema[] = [ |
| 215 | 238 | }, |
| 216 | 239 | rules: [{ required: true, message: '视频流是必填项' }, ...CameraVideoUrl], |
| 217 | 240 | }, |
| 218 | - | |
| 241 | + { | |
| 242 | + field: 'videoType', | |
| 243 | + label: '流媒体平台', | |
| 244 | + component: 'ApiRadioGroup', | |
| 245 | + required: true, | |
| 246 | + defaultValue: VideoPlatformEnum.SCI, | |
| 247 | + ifShow: ({ values }) => values.accessMode === AccessMode.Streaming, | |
| 248 | + componentProps: { | |
| 249 | + api: async (params) => { | |
| 250 | + const values = await findDictItemByCode(params); | |
| 251 | + return values.map((item) => ({ label: item.itemText, value: Number(item.itemValue) })); | |
| 252 | + }, | |
| 253 | + params: { | |
| 254 | + dictCode: DictEnum.STREAMING_MEDIA_TYPE, | |
| 255 | + }, | |
| 256 | + getPopupContainer: () => document.body, | |
| 257 | + placeholder: `请选择平台类型`, | |
| 258 | + }, | |
| 259 | + }, | |
| 219 | 260 | { |
| 220 | 261 | field: 'videoPlatformId', |
| 221 | 262 | label: '流媒体配置', |
| ... | ... | @@ -234,7 +275,10 @@ export const formSchema: QFormSchema[] = [ |
| 234 | 275 | component: 'RadioGroup', |
| 235 | 276 | defaultValue: StreamType.MASTER, |
| 236 | 277 | ifShow({ values }) { |
| 237 | - return values.accessMode === AccessMode.Streaming; | |
| 278 | + return ( | |
| 279 | + values.accessMode === AccessMode.Streaming && | |
| 280 | + values.videoType !== VideoPlatformEnum.FLUORITE | |
| 281 | + ); | |
| 238 | 282 | }, |
| 239 | 283 | componentProps: { |
| 240 | 284 | placeholder: '请选择码流', |
| ... | ... | @@ -247,12 +291,53 @@ export const formSchema: QFormSchema[] = [ |
| 247 | 291 | }, |
| 248 | 292 | }, |
| 249 | 293 | { |
| 294 | + field: 'articulation', | |
| 295 | + label: '清晰度', | |
| 296 | + component: 'RadioGroup', | |
| 297 | + defaultValue: ArticulationEnumType.HIGH_DEFINITION, | |
| 298 | + ifShow: ({ model }) => | |
| 299 | + model.accessMode === AccessMode.Streaming && model.videoType === VideoPlatformEnum.FLUORITE, | |
| 300 | + componentProps: () => { | |
| 301 | + return { | |
| 302 | + options: [ | |
| 303 | + { | |
| 304 | + label: ArticulationEnumNameType.HIGH_DEFINITION, | |
| 305 | + value: ArticulationEnumType.HIGH_DEFINITION, | |
| 306 | + }, | |
| 307 | + { | |
| 308 | + label: ArticulationEnumNameType.SMOOTH, | |
| 309 | + value: ArticulationEnumType.SMOOTH, | |
| 310 | + }, | |
| 311 | + ], | |
| 312 | + }; | |
| 313 | + }, | |
| 314 | + }, | |
| 315 | + { | |
| 316 | + field: 'videoFormat', | |
| 317 | + label: '视频格式', | |
| 318 | + component: 'Select', | |
| 319 | + ifShow: ({ model }) => | |
| 320 | + model.accessMode === AccessMode.Streaming && model.videoType === VideoPlatformEnum.FLUORITE, | |
| 321 | + defaultValue: FluoriteMideaProtocolEnum.FLV, | |
| 322 | + required: true, | |
| 323 | + componentProps: { | |
| 324 | + options: [ | |
| 325 | + { label: 'FLV', value: FluoriteMideaProtocolEnum.FLV }, | |
| 326 | + { label: 'HLS', value: FluoriteMideaProtocolEnum.HLS }, | |
| 327 | + ], | |
| 328 | + allowClear: false, | |
| 329 | + }, | |
| 330 | + }, | |
| 331 | + { | |
| 250 | 332 | field: 'playProtocol', |
| 251 | 333 | label: '播放协议', |
| 252 | 334 | component: 'RadioGroup', |
| 253 | 335 | defaultValue: PlayProtocol.HTTP, |
| 254 | 336 | ifShow({ values }) { |
| 255 | - return values.accessMode === AccessMode.Streaming; | |
| 337 | + return ( | |
| 338 | + values.accessMode === AccessMode.Streaming && | |
| 339 | + values.videoType !== VideoPlatformEnum.FLUORITE | |
| 340 | + ); | |
| 256 | 341 | }, |
| 257 | 342 | helpMessage: ['平台使用https的hls协议,需联系海康开放平台专家支持。'], |
| 258 | 343 | componentProps: { |
| ... | ... | @@ -270,7 +355,25 @@ export const formSchema: QFormSchema[] = [ |
| 270 | 355 | component: 'Input', |
| 271 | 356 | rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }], |
| 272 | 357 | ifShow({ values }) { |
| 273 | - return values.accessMode === AccessMode.Streaming; | |
| 358 | + return ( | |
| 359 | + values.accessMode === AccessMode.Streaming && | |
| 360 | + values.videoType !== VideoPlatformEnum.FLUORITE | |
| 361 | + ); | |
| 362 | + }, | |
| 363 | + componentProps: { | |
| 364 | + placeholder: '请输入监控点编号', | |
| 365 | + }, | |
| 366 | + }, | |
| 367 | + { | |
| 368 | + field: 'sn', | |
| 369 | + label: h(SnHelpMessage1) as any, | |
| 370 | + component: 'Input', | |
| 371 | + rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }], | |
| 372 | + ifShow({ values }) { | |
| 373 | + return ( | |
| 374 | + values.accessMode === AccessMode.Streaming && | |
| 375 | + values.videoType === VideoPlatformEnum.FLUORITE | |
| 376 | + ); | |
| 274 | 377 | }, |
| 275 | 378 | componentProps: { |
| 276 | 379 | placeholder: '请输入监控点编号', | ... | ... |
| 1 | 1 | import { PlayProtocol } from '../manage/config.data'; |
| 2 | 2 | import type { StreamingMediaModel } from '/@/api/camera/model/cameraModel'; |
| 3 | +import { findDictItemByCode } from '/@/api/system/dict'; | |
| 4 | +import { DictEnum } from '/@/enums/dictEnum'; | |
| 3 | 5 | import { BasicColumn, FormSchema } from '/@/components/Table'; |
| 4 | 6 | |
| 5 | 7 | export interface DrawerParams { |
| ... | ... | @@ -9,6 +11,7 @@ export interface DrawerParams { |
| 9 | 11 | |
| 10 | 12 | export const streamingMediaTypeMapping = { |
| 11 | 13 | 0: '海康ISC平台', |
| 14 | + 1: '萤石平台', | |
| 12 | 15 | }; |
| 13 | 16 | |
| 14 | 17 | export const streamingMediaSSLMapping = { |
| ... | ... | @@ -37,6 +40,9 @@ export const columnSchema: BasicColumn[] = [ |
| 37 | 40 | title: '用户Key', |
| 38 | 41 | dataIndex: 'appKey', |
| 39 | 42 | width: 80, |
| 43 | + format(text) { | |
| 44 | + return formatSecret(text); | |
| 45 | + }, | |
| 40 | 46 | }, |
| 41 | 47 | { |
| 42 | 48 | title: '用户密钥', |
| ... | ... | @@ -73,26 +79,27 @@ export const formDetailSchema: FormSchema[] = [ |
| 73 | 79 | { |
| 74 | 80 | label: '平台类型', |
| 75 | 81 | field: 'type', |
| 76 | - component: 'Select', | |
| 77 | - rules: [{ required: true, message: '平台类型为必填项', type: 'number' }], | |
| 82 | + component: 'ApiSelect', | |
| 83 | + required: true, | |
| 78 | 84 | componentProps: { |
| 79 | - options: [{ label: '海康ISC平台', value: 0 }], | |
| 80 | - placeholder: '请输入选择平台类型', | |
| 85 | + api: async (params) => { | |
| 86 | + const values = await findDictItemByCode(params); | |
| 87 | + return values.map((item) => ({ label: item.itemText, value: Number(item.itemValue) })); | |
| 88 | + }, | |
| 89 | + params: { | |
| 90 | + dictCode: DictEnum.STREAMING_MEDIA_TYPE, | |
| 91 | + }, | |
| 92 | + getPopupContainer: () => document.body, | |
| 93 | + placeholder: `请选择平台类型`, | |
| 81 | 94 | }, |
| 82 | 95 | }, |
| 83 | 96 | { |
| 84 | 97 | label: '部署环境', |
| 85 | 98 | field: 'ssl', |
| 86 | 99 | component: 'RadioGroup', |
| 100 | + ifShow: false, | |
| 87 | 101 | rules: [{ required: true, message: '流媒体部署环境为必填项', type: 'number' }], |
| 88 | - defaultValue: PlayProtocol.HTTP, | |
| 89 | - componentProps: { | |
| 90 | - defaultValue: PlayProtocol.HTTP, | |
| 91 | - options: [ | |
| 92 | - { label: 'http', value: PlayProtocol.HTTP }, | |
| 93 | - { label: 'https', value: PlayProtocol.HTTPS }, | |
| 94 | - ], | |
| 95 | - }, | |
| 102 | + defaultValue: PlayProtocol.HTTPS, | |
| 96 | 103 | }, |
| 97 | 104 | { |
| 98 | 105 | label: '平台地址', |
| ... | ... | @@ -108,7 +115,7 @@ export const formDetailSchema: FormSchema[] = [ |
| 108 | 115 | { |
| 109 | 116 | label: '用户Key', |
| 110 | 117 | field: 'appKey', |
| 111 | - component: 'Input', | |
| 118 | + component: 'InputPassword', | |
| 112 | 119 | rules: [{ required: true, message: '用户Key为必填项' }], |
| 113 | 120 | componentProps: { |
| 114 | 121 | maxLength: 36, |
| ... | ... | @@ -118,7 +125,7 @@ export const formDetailSchema: FormSchema[] = [ |
| 118 | 125 | { |
| 119 | 126 | label: '用户密钥', |
| 120 | 127 | field: 'appSecret', |
| 121 | - component: 'Input', | |
| 128 | + component: 'InputPassword', | |
| 122 | 129 | rules: [ |
| 123 | 130 | { required: true, message: '用户密钥为必填项' }, |
| 124 | 131 | { required: true, min: 20, message: '用户密钥不能少于20位字符' }, | ... | ... |
| ... | ... | @@ -14,7 +14,9 @@ |
| 14 | 14 | v-model:value="model['templateId']" |
| 15 | 15 | placeholder="请选择模板" |
| 16 | 16 | style="width: 100%" |
| 17 | - :options="selectTemplateOptions" | |
| 17 | + :options=" | |
| 18 | + selectTemplateOptions?.filter((item) => item.platform === model['platform']) || [] | |
| 19 | + " | |
| 18 | 20 | @change="handleTemplateChange" |
| 19 | 21 | v-bind="createPickerSearch()" |
| 20 | 22 | :disabled="templateDisabled" |
| ... | ... | @@ -140,6 +142,7 @@ |
| 140 | 142 | }); |
| 141 | 143 | |
| 142 | 144 | const selectTemplateOptions: Ref<any[]> = ref([]); |
| 145 | + | |
| 143 | 146 | const getTemplate = async (params: queryPageParams) => { |
| 144 | 147 | const { items } = await getPage({ ...params, isTemplate: 1 }); |
| 145 | 148 | selectTemplateOptions.value = items.map((item) => ({ |
| ... | ... | @@ -152,11 +155,7 @@ |
| 152 | 155 | |
| 153 | 156 | const handleTemplateChange = async (_, option) => { |
| 154 | 157 | const { productAndDevice } = option; |
| 155 | - // if (!productAndDevice) return; | |
| 156 | - // selectOptions.value = productAndDevice?.map((item) => ({ | |
| 157 | - // label: item.profileName || item.name, | |
| 158 | - // value: item.profileId, | |
| 159 | - // })); | |
| 158 | + setFieldsValue({ platform: option?.platform }); | |
| 160 | 159 | await nextTick(); |
| 161 | 160 | // 赋值 |
| 162 | 161 | selectDeviceProfileRef.value?.setFieldsValue( | ... | ... |
| ... | ... | @@ -101,10 +101,6 @@ export const formSchema: FormSchema[] = [ |
| 101 | 101 | onPreview: (fileList: FileItem) => { |
| 102 | 102 | createImgPreview({ imageList: [fileList.url!] }); |
| 103 | 103 | }, |
| 104 | - // showUploadList: { | |
| 105 | - // showDownloadIcon: true, | |
| 106 | - // showRemoveIcon: true, | |
| 107 | - // }, | |
| 108 | 104 | }; |
| 109 | 105 | }, |
| 110 | 106 | }, | ... | ... |
| ... | ... | @@ -348,7 +348,7 @@ export const step1Schemas: FormSchema[] = [ |
| 348 | 348 | popconfirmTitle: () => |
| 349 | 349 | updateOrgHelpMessage.map((text) => h('div', { style: { maxWidth: '240px' } }, text)), |
| 350 | 350 | }), |
| 351 | - componentProps: ({ formModel }) => { | |
| 351 | + componentProps: ({ formModel, formActionType }) => { | |
| 352 | 352 | return { |
| 353 | 353 | component: 'OrgTreeSelect', |
| 354 | 354 | defaultLockStatus: !!formModel?.isUpdate, |
| ... | ... | @@ -358,6 +358,17 @@ export const step1Schemas: FormSchema[] = [ |
| 358 | 358 | organizationId: formModel?.sensorOrganizationId, |
| 359 | 359 | }, |
| 360 | 360 | }, |
| 361 | + onOptionsChange: (options: Recordable[]) => { | |
| 362 | + if (!formModel?.organizationId && formModel?.deviceType === DeviceTypeEnum.SENSOR) { | |
| 363 | + const firstItem = options?.[0]; | |
| 364 | + | |
| 365 | + if (firstItem && firstItem?.id) { | |
| 366 | + const { setFieldsValue, clearValidate } = formActionType; | |
| 367 | + setFieldsValue({ organizationId: firstItem.id }); | |
| 368 | + clearValidate('organizationId'); | |
| 369 | + } | |
| 370 | + } | |
| 371 | + }, | |
| 361 | 372 | }, |
| 362 | 373 | }; |
| 363 | 374 | }, | ... | ... |
| ... | ... | @@ -70,6 +70,7 @@ export const columns: BasicColumn[] = [ |
| 70 | 70 | title: '状态', |
| 71 | 71 | dataIndex: 'deviceState', |
| 72 | 72 | width: 110, |
| 73 | + className: 'device-status', | |
| 73 | 74 | slots: { customRender: 'deviceState' }, |
| 74 | 75 | }, |
| 75 | 76 | { |
| ... | ... | @@ -237,4 +238,17 @@ export const searchFormSchema: FormSchema[] = [ |
| 237 | 238 | placeholder: '请选择', |
| 238 | 239 | }, |
| 239 | 240 | }, |
| 241 | + { | |
| 242 | + field: 'alarmStatus', | |
| 243 | + label: '是否告警', | |
| 244 | + component: 'Select', | |
| 245 | + colProps: { span: 6 }, | |
| 246 | + componentProps: { | |
| 247 | + options: [ | |
| 248 | + { label: '是', value: 1 }, | |
| 249 | + { label: '否', value: 0 }, | |
| 250 | + ], | |
| 251 | + placeholder: '请选择设备是否存在告警', | |
| 252 | + }, | |
| 253 | + }, | |
| 240 | 254 | ]; | ... | ... |
| ... | ... | @@ -19,7 +19,20 @@ |
| 19 | 19 | <Tabs.TabPane key="modelOfMatter" tab="物模型数据"> |
| 20 | 20 | <ModelOfMatter :deviceDetail="deviceDetail" /> |
| 21 | 21 | </Tabs.TabPane> |
| 22 | - <Tabs.TabPane key="3" tab="告警"> | |
| 22 | + <Tabs.TabPane key="3"> | |
| 23 | + <template #tab> | |
| 24 | + <Badge :offset="[2, -5]" style="color: inherit"> | |
| 25 | + <span>告警</span> | |
| 26 | + <template #count> | |
| 27 | + <div | |
| 28 | + :style="{ visibility: deviceDetail.alarmStatus ? 'visible' : 'hidden' }" | |
| 29 | + class="w-3.5 h-3.5 !flex justify-center items-center rounded-1 border-red-400 border" | |
| 30 | + > | |
| 31 | + <Icon icon="mdi:bell-warning" color="#f46161" :size="12" class="!mr-0" /> | |
| 32 | + </div> | |
| 33 | + </template> | |
| 34 | + </Badge> | |
| 35 | + </template> | |
| 23 | 36 | <AlarmLog :device-id="deviceDetail.id" class="bg-gray-100" /> |
| 24 | 37 | </Tabs.TabPane> |
| 25 | 38 | <Tabs.TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> |
| ... | ... | @@ -51,7 +64,7 @@ |
| 51 | 64 | <script lang="ts" setup> |
| 52 | 65 | import { ref, computed } from 'vue'; |
| 53 | 66 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
| 54 | - import { Tabs } from 'ant-design-vue'; | |
| 67 | + import { Tabs, Badge } from 'ant-design-vue'; | |
| 55 | 68 | import Detail from '../tabs/Detail.vue'; |
| 56 | 69 | import ChildDevice from '../tabs/ChildDevice.vue'; |
| 57 | 70 | import TBoxDetail from '../tabs/TBoxDetail.vue'; |
| ... | ... | @@ -62,6 +75,7 @@ |
| 62 | 75 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
| 63 | 76 | import Task from '../tabs/Task.vue'; |
| 64 | 77 | import AlarmLog from '/@/views/alarm/log/index.vue'; |
| 78 | + import { Icon } from '/@/components/Icon'; | |
| 65 | 79 | |
| 66 | 80 | const emit = defineEmits(['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail']); |
| 67 | 81 | ... | ... |
| ... | ... | @@ -27,16 +27,26 @@ export interface SocketInfoDataSourceItemType extends BaseAdditionalInfo { |
| 27 | 27 | expand?: boolean; |
| 28 | 28 | showHistoryDataButton?: boolean; |
| 29 | 29 | rawValue?: any; |
| 30 | + enum?: Record<string, string>; | |
| 30 | 31 | } |
| 31 | 32 | |
| 32 | 33 | export function buildTableDataSourceByObjectModel( |
| 33 | 34 | models: DeviceModelOfMatterAttrs[] |
| 34 | 35 | ): SocketInfoDataSourceItemType[] { |
| 35 | 36 | function getAdditionalInfoByDataType(dataType?: DataType) { |
| 36 | - const { specs } = dataType || {}; | |
| 37 | + const { specs, specsList, type } = dataType || {}; | |
| 37 | 38 | if (isArray(specs)) return {}; |
| 38 | 39 | const { unit, boolClose, boolOpen, unitName } = (specs as Partial<Specs>) || {}; |
| 39 | - return { unit, boolClose, boolOpen, unitName }; | |
| 40 | + const result = { unit, boolClose, boolOpen, unitName }; | |
| 41 | + if (type == DataTypeEnum.ENUM && specsList && specsList.length) { | |
| 42 | + Reflect.set( | |
| 43 | + result, | |
| 44 | + 'enum', | |
| 45 | + specsList.reduce((prev, next) => ({ ...prev, [next.value!]: next.name }), {}) | |
| 46 | + ); | |
| 47 | + } | |
| 48 | + | |
| 49 | + return result; | |
| 40 | 50 | } |
| 41 | 51 | |
| 42 | 52 | return models.map((item) => { |
| ... | ... | @@ -72,7 +82,6 @@ export function buildTableDataSourceByObjectModel( |
| 72 | 82 | } else { |
| 73 | 83 | Object.assign(res, getAdditionalInfoByDataType(dataType)); |
| 74 | 84 | } |
| 75 | - | |
| 76 | 85 | return res; |
| 77 | 86 | }); |
| 78 | 87 | } | ... | ... |
| ... | ... | @@ -284,13 +284,15 @@ |
| 284 | 284 | }); |
| 285 | 285 | |
| 286 | 286 | const formatValue = (item: SocketInfoDataSourceItemType) => { |
| 287 | - return item.type === DataTypeEnum.BOOL | |
| 288 | - ? !isNullOrUnDef(item.value) | |
| 289 | - ? !!Number(item.value) | |
| 290 | - ? item.boolOpen | |
| 291 | - : item.boolClose | |
| 292 | - : '--' | |
| 293 | - : (item.value as string) || '--'; | |
| 287 | + if (isNullOrUnDef(item)) return '--'; | |
| 288 | + switch (item.type) { | |
| 289 | + case DataTypeEnum.BOOL: | |
| 290 | + return !!Number(item.value) ? item.boolOpen : item.boolClose; | |
| 291 | + case DataTypeEnum.ENUM: | |
| 292 | + return item.enum?.[item.value as string]; | |
| 293 | + default: | |
| 294 | + return item.value || '--'; | |
| 295 | + } | |
| 294 | 296 | }; |
| 295 | 297 | |
| 296 | 298 | const [register, { openModal: openSendCommandModal }] = useModal(); | ... | ... |
| 1 | +import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 1 | 2 | import { FormSchema } from '/@/components/Form'; |
| 3 | +import { validateTCPCustomCommand } from '/@/components/Form/src/externalCompns/components/ThingsModelForm'; | |
| 4 | +import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 2 | 5 | |
| 3 | 6 | const InsertString = (t, c, n) => { |
| 4 | 7 | const r: string | number[] = []; |
| ... | ... | @@ -127,21 +130,33 @@ const SingleToHexBatch = (t) => { |
| 127 | 130 | return r.join('\r\n'); |
| 128 | 131 | }; |
| 129 | 132 | |
| 130 | -const formSchemasConfig = (schemas, actionType): FormSchema[] => { | |
| 131 | - const { identifier, functionName } = schemas; | |
| 133 | +const formSchemasConfig = (schemas: StructJSON, actionType: string): FormSchema[] => { | |
| 134 | + const { identifier, functionName, dataType } = schemas; | |
| 135 | + | |
| 136 | + if (dataType?.type === DataTypeEnum.STRING) { | |
| 137 | + return [ | |
| 138 | + { | |
| 139 | + field: identifier, | |
| 140 | + label: functionName!, | |
| 141 | + component: 'Input', | |
| 142 | + rules: [{ required: true, validator: validateTCPCustomCommand }], | |
| 143 | + componentProps: { | |
| 144 | + placeholder: `请输入${functionName}`, | |
| 145 | + }, | |
| 146 | + }, | |
| 147 | + ]; | |
| 148 | + } | |
| 149 | + | |
| 132 | 150 | if (actionType == '06') { |
| 133 | 151 | return [ |
| 134 | 152 | { |
| 135 | 153 | field: identifier, |
| 136 | - label: functionName, | |
| 154 | + label: functionName!, | |
| 137 | 155 | component: 'InputNumber', |
| 138 | 156 | rules: [{ required: true, message: '请输入正数' }], |
| 139 | 157 | componentProps: { |
| 140 | 158 | min: 0, |
| 141 | - formatter: (e) => { | |
| 142 | - const value = `${e}`.replace('-', '').replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3'); | |
| 143 | - return value; | |
| 144 | - }, | |
| 159 | + precision: 2, | |
| 145 | 160 | placeholder: `请输入正数`, |
| 146 | 161 | }, |
| 147 | 162 | }, |
| ... | ... | @@ -150,7 +165,7 @@ const formSchemasConfig = (schemas, actionType): FormSchema[] => { |
| 150 | 165 | return [ |
| 151 | 166 | { |
| 152 | 167 | field: identifier, |
| 153 | - label: functionName, | |
| 168 | + label: functionName!, | |
| 154 | 169 | component: 'InputNumber', |
| 155 | 170 | rules: [{ required: true, message: '请输入值' }], |
| 156 | 171 | componentProps: { |
| ... | ... | @@ -165,13 +180,12 @@ const formSchemasConfig = (schemas, actionType): FormSchema[] => { |
| 165 | 180 | return [ |
| 166 | 181 | { |
| 167 | 182 | field: identifier, |
| 168 | - label: functionName, | |
| 183 | + label: functionName!, | |
| 169 | 184 | component: 'InputNumber', |
| 170 | 185 | rules: [{ required: true, message: '请输入值' }], |
| 171 | 186 | componentProps: { |
| 172 | 187 | placeholder: `请输入数字`, |
| 173 | - formatter: (e) => | |
| 174 | - `${e}`.replace(/\B(?=(\d{3})+(?!\d))/g, '').replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3'), | |
| 188 | + precision: 2, | |
| 175 | 189 | }, |
| 176 | 190 | }, |
| 177 | 191 | ]; | ... | ... |
| ... | ... | @@ -35,6 +35,7 @@ |
| 35 | 35 | const zoomFactorValue = ref<number>(1); //缩放因子 |
| 36 | 36 | const isShowMultiply = ref<Boolean>(false); // 只有tcp --> int和double类型才相乘缩放因子 |
| 37 | 37 | const deviceTransportType = ref<string>(); |
| 38 | + const objectDataType = ref<DataTypeEnum>(); | |
| 38 | 39 | |
| 39 | 40 | const [register] = useModalInner(async (params: ModalParamsType<DeviceModelOfMatterAttrs>) => { |
| 40 | 41 | const { record } = params; |
| ... | ... | @@ -48,6 +49,7 @@ |
| 48 | 49 | zoomFactorValue.value = zoomFactor ? Number(zoomFactor) : 1; |
| 49 | 50 | isShowMultiply.value = type == 'INT' || type == 'DOUBLE' ? true : false; |
| 50 | 51 | deviceTransportType.value = transportType; |
| 52 | + objectDataType.value = type; | |
| 51 | 53 | |
| 52 | 54 | let schemas = [{ dataType: dataType, identifier, functionName: name } as StructJSON]; |
| 53 | 55 | |
| ... | ... | @@ -136,7 +138,12 @@ |
| 136 | 138 | |
| 137 | 139 | const sendValue = ref({}); |
| 138 | 140 | //判断tcp类型 标识符是自定义还是ModBus |
| 139 | - if (unref(isShowModBUS)) { | |
| 141 | + if (unref(objectDataType) === DataTypeEnum.STRING) { | |
| 142 | + const flag = await validate(); | |
| 143 | + if (!flag) return; | |
| 144 | + const value = getFieldsValue()[unref(formField)]; | |
| 145 | + sendValue.value = value; | |
| 146 | + } else if (unref(isShowModBUS)) { | |
| 140 | 147 | if (!unref(isShowActionType)) { |
| 141 | 148 | createMessage.warning('当前物模型扩展描述没有填写'); |
| 142 | 149 | return; | ... | ... |
| ... | ... | @@ -12,16 +12,9 @@ export interface BasicCreateFormParams { |
| 12 | 12 | |
| 13 | 13 | useComponentRegister('JSONEditor', JSONEditor); |
| 14 | 14 | |
| 15 | -const validateDouble = ( | |
| 16 | - value: number, | |
| 17 | - type: DataTypeEnum, | |
| 18 | - min?: number | string, | |
| 19 | - max?: number | string | |
| 20 | -) => { | |
| 21 | - min = | |
| 22 | - Number(min) || type === DataTypeEnum.NUMBER_INT ? Number.MIN_SAFE_INTEGER : Number.MIN_VALUE; | |
| 23 | - max = | |
| 24 | - Number(max) || type === DataTypeEnum.NUMBER_INT ? Number.MAX_SAFE_INTEGER : Number.MAX_VALUE; | |
| 15 | +const validateDouble = (value: number, min?: number | string, max?: number | string) => { | |
| 16 | + min = Number(min) || Number.MIN_SAFE_INTEGER; | |
| 17 | + max = Number(max) || Number.MAX_SAFE_INTEGER; | |
| 25 | 18 | |
| 26 | 19 | return { |
| 27 | 20 | flag: value < min || value > max, |
| ... | ... | @@ -47,7 +40,7 @@ export const useGenDynamicForm = () => { |
| 47 | 40 | type: 'number', |
| 48 | 41 | trigger: 'change', |
| 49 | 42 | validator: (_rule, value) => { |
| 50 | - const { flag, message } = validateDouble(value, type, min, max); | |
| 43 | + const { flag, message } = validateDouble(value, min, max); | |
| 51 | 44 | if (flag) { |
| 52 | 45 | return Promise.reject(`${functionName}${message}`); |
| 53 | 46 | } |
| ... | ... | @@ -56,8 +49,8 @@ export const useGenDynamicForm = () => { |
| 56 | 49 | }, |
| 57 | 50 | ], |
| 58 | 51 | componentProps: { |
| 59 | - max: max ?? type === DataTypeEnum.NUMBER_INT ? Number.MAX_SAFE_INTEGER : Number.MAX_VALUE, | |
| 60 | - min: min ?? type === DataTypeEnum.NUMBER_INT ? Number.MIN_SAFE_INTEGER : Number.MIN_VALUE, | |
| 52 | + max: max ?? Number.MAX_SAFE_INTEGER, | |
| 53 | + min: min ?? Number.MIN_SAFE_INTEGER, | |
| 61 | 54 | step, |
| 62 | 55 | placeholder: `请输入${functionName}`, |
| 63 | 56 | precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2, |
| ... | ... | @@ -81,7 +74,7 @@ export const useGenDynamicForm = () => { |
| 81 | 74 | type: 'string', |
| 82 | 75 | trigger: 'change', |
| 83 | 76 | validator: (_rule, value) => { |
| 84 | - if (value.length > length) { | |
| 77 | + if (value?.length > length) { | |
| 85 | 78 | return Promise.reject(`${functionName}数据长度应该小于${length}`); |
| 86 | 79 | } |
| 87 | 80 | return Promise.resolve(value); |
| ... | ... | @@ -117,6 +110,24 @@ export const useGenDynamicForm = () => { |
| 117 | 110 | }; |
| 118 | 111 | }; |
| 119 | 112 | |
| 113 | + const createEnumSelect = ({ | |
| 114 | + identifier, | |
| 115 | + functionName, | |
| 116 | + dataType, | |
| 117 | + }: BasicCreateFormParams): FormSchema => { | |
| 118 | + const { specsList } = dataType; | |
| 119 | + return { | |
| 120 | + field: identifier, | |
| 121 | + label: functionName, | |
| 122 | + component: 'Select', | |
| 123 | + componentProps: { | |
| 124 | + options: specsList?.map((item) => ({ label: item.name, value: item.value })), | |
| 125 | + placeholder: `请选择${functionName}`, | |
| 126 | + getPopupContainer: () => document.body, | |
| 127 | + }, | |
| 128 | + }; | |
| 129 | + }; | |
| 130 | + | |
| 120 | 131 | const createInputJson = ({ identifier, functionName }: BasicCreateFormParams): FormSchema => { |
| 121 | 132 | return { |
| 122 | 133 | field: identifier, |
| ... | ... | @@ -144,6 +155,7 @@ export const useGenDynamicForm = () => { |
| 144 | 155 | [DataTypeEnum.NUMBER_INT]: createInputNumber, |
| 145 | 156 | [DataTypeEnum.STRING]: createInput, |
| 146 | 157 | [DataTypeEnum.STRUCT]: createInputJson, |
| 158 | + [DataTypeEnum.ENUM]: createEnumSelect, | |
| 147 | 159 | }; |
| 148 | 160 | |
| 149 | 161 | const fieldTypeMap = new Map<string, DataTypeEnum>(); | ... | ... |
| ... | ... | @@ -39,6 +39,8 @@ |
| 39 | 39 | |
| 40 | 40 | const playUrl = ref(''); |
| 41 | 41 | |
| 42 | + const videoId = ref<string>(); | |
| 43 | + | |
| 42 | 44 | const withToken = ref(false); |
| 43 | 45 | |
| 44 | 46 | const fingerprintResult = ref<Nullable<GetResult>>(null); |
| ... | ... | @@ -64,6 +66,7 @@ |
| 64 | 66 | const [register] = useModalInner( |
| 65 | 67 | async (data: { record: CameraModel | StreamingManageRecord }) => { |
| 66 | 68 | const { record } = data; |
| 69 | + videoId.value = record.id || ''; | |
| 67 | 70 | const result = await getResult(); |
| 68 | 71 | fingerprintResult.value = result; |
| 69 | 72 | if (record.accessMode === AccessMode.ManuallyEnter) { | ... | ... |
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | <div> |
| 3 | 3 | <PageWrapper dense contentFullHeight contentClass="flex"> |
| 4 | 4 | <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> |
| 5 | - <BasicTable style="flex: auto" @register="registerTable" class="w-5/6 xl:w-4/5"> | |
| 5 | + <BasicTable style="flex: auto" @register="registerTable" class="w-5/6 xl:w-4/5 device-table"> | |
| 6 | 6 | <template #toolbar> |
| 7 | 7 | <Authority :value="DeviceListAuthEnum.CREATE"> |
| 8 | 8 | <a-button type="primary" @click="handleCreate" v-if="authBtn(role)"> |
| ... | ... | @@ -106,13 +106,17 @@ |
| 106 | 106 | </Tag> |
| 107 | 107 | </template> |
| 108 | 108 | <template #deviceState="{ record }"> |
| 109 | - <!-- <HeartOutlined v-if="!record.isCollect" class="mr-1" style="color: red" /> --> | |
| 110 | - <Tooltip> | |
| 111 | - <template #title> 我的收藏</template> | |
| 112 | - <HeartTwoTone v-if="record.isCollect" class="mr-1" twoToneColor="#3B82F6" /> | |
| 113 | - </Tooltip> | |
| 109 | + <div v-if="record.isCollect"> | |
| 110 | + <div class="absolute top-0 left-0 device-collect"> </div> | |
| 111 | + <Icon | |
| 112 | + icon="ph:star-fill" | |
| 113 | + class="fill-light-50 absolute top-0.5 left-0.5" | |
| 114 | + color="#fff" | |
| 115 | + :size="12" | |
| 116 | + /> | |
| 117 | + </div> | |
| 118 | + | |
| 114 | 119 | <Tag |
| 115 | - :style="{ marginLeft: !record.isCollect ? '17px' : '' }" | |
| 116 | 120 | :color=" |
| 117 | 121 | record.deviceState == DeviceState.INACTIVE |
| 118 | 122 | ? 'warning' |
| ... | ... | @@ -135,7 +139,7 @@ |
| 135 | 139 | <TableAction |
| 136 | 140 | :actions="[ |
| 137 | 141 | { |
| 138 | - label: '详情', | |
| 142 | + label: AlarmDetailActionButton({ hasAlarm: !!record.alarmStatus }), | |
| 139 | 143 | icon: 'ant-design:eye-outlined', |
| 140 | 144 | auth: DeviceListAuthEnum.DETAIL, |
| 141 | 145 | onClick: handleDetail.bind(null, record), |
| ... | ... | @@ -232,7 +236,7 @@ |
| 232 | 236 | </div> |
| 233 | 237 | </template> |
| 234 | 238 | <script lang="ts" setup> |
| 235 | - import { reactive, onMounted, ref } from 'vue'; | |
| 239 | + import { reactive, onMounted, ref, h, CSSProperties } from 'vue'; | |
| 236 | 240 | import { |
| 237 | 241 | DeviceModel, |
| 238 | 242 | DeviceRecord, |
| ... | ... | @@ -241,8 +245,7 @@ |
| 241 | 245 | } from '/@/api/device/model/deviceModel'; |
| 242 | 246 | import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table'; |
| 243 | 247 | import { columns, DeviceListAuthEnum, searchFormSchema } from './config/device.data'; |
| 244 | - import { Tag, Popover, Button, Tooltip } from 'ant-design-vue'; | |
| 245 | - import { HeartTwoTone } from '@ant-design/icons-vue'; | |
| 248 | + import { Tag, Popover, Button, Badge } from 'ant-design-vue'; | |
| 246 | 249 | import { |
| 247 | 250 | deleteDevice, |
| 248 | 251 | devicePage, |
| ... | ... | @@ -278,6 +281,7 @@ |
| 278 | 281 | } from './cpns/modal/BatchUpdateProductModal'; |
| 279 | 282 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 280 | 283 | import { AuthDropDown } from '/@/components/Widget'; |
| 284 | + import Icon from '/@/components/Icon'; | |
| 281 | 285 | |
| 282 | 286 | const { isCustomer } = useAuthDeviceDetail(); |
| 283 | 287 | const { createMessage } = useMessage(); |
| ... | ... | @@ -294,6 +298,32 @@ |
| 294 | 298 | const [registerImportModal, { openModal: openImportModal }] = useModal(); |
| 295 | 299 | const [registerBatchUpdateProductModal, { openModal: openBatchUpdateProductModal }] = useModal(); |
| 296 | 300 | |
| 301 | + const AlarmDetailActionButton = ({ hasAlarm }: { hasAlarm?: boolean }) => | |
| 302 | + h( | |
| 303 | + Badge, | |
| 304 | + { offset: [0, -5] }, | |
| 305 | + { | |
| 306 | + default: () => h('span', { style: { color: '#377dff' } }, '详情'), | |
| 307 | + count: () => | |
| 308 | + h( | |
| 309 | + 'div', | |
| 310 | + { | |
| 311 | + style: { | |
| 312 | + visibility: hasAlarm ? 'visible' : 'hidden', | |
| 313 | + width: '14px', | |
| 314 | + height: '14px', | |
| 315 | + display: 'flex', | |
| 316 | + justifyContent: 'center', | |
| 317 | + alignItems: 'center', | |
| 318 | + border: '1px solid #f46161', | |
| 319 | + borderRadius: '50%', | |
| 320 | + } as CSSProperties, | |
| 321 | + }, | |
| 322 | + h(Icon, { icon: 'mdi:bell-warning', color: '#f46161', size: 12 }) | |
| 323 | + ), | |
| 324 | + } | |
| 325 | + ); | |
| 326 | + | |
| 297 | 327 | const batchUpdateProductFlag = ref(true); |
| 298 | 328 | |
| 299 | 329 | const [ |
| ... | ... | @@ -336,6 +366,7 @@ |
| 336 | 366 | rowKey: 'id', |
| 337 | 367 | searchInfo: searchInfo, |
| 338 | 368 | clickToRowSelect: false, |
| 369 | + rowClassName: (record) => ((record as DeviceRecord).alarmStatus ? 'device-alarm-badge' : ''), | |
| 339 | 370 | actionColumn: { |
| 340 | 371 | width: 200, |
| 341 | 372 | title: '操作', |
| ... | ... | @@ -557,4 +588,25 @@ |
| 557 | 588 | }; |
| 558 | 589 | </script> |
| 559 | 590 | |
| 560 | -<style scoped lang="css"></style> | |
| 591 | +<style scoped lang="less"> | |
| 592 | + .device-table { | |
| 593 | + :deep(.ant-form-item-control-input-content) { | |
| 594 | + & > div > div { | |
| 595 | + width: 100%; | |
| 596 | + } | |
| 597 | + } | |
| 598 | + } | |
| 599 | +</style> | |
| 600 | + | |
| 601 | +<style lang="less"> | |
| 602 | + .device-status { | |
| 603 | + position: relative; | |
| 604 | + | |
| 605 | + .device-collect { | |
| 606 | + width: 0; | |
| 607 | + height: 0; | |
| 608 | + border-top: 30px solid #377dff; | |
| 609 | + border-right: 30px solid transparent; | |
| 610 | + } | |
| 611 | + } | |
| 612 | +</style> | ... | ... |
| ... | ... | @@ -144,7 +144,7 @@ |
| 144 | 144 | <div class="h-full w-full !flex justify-center items-center text-center p-1"> |
| 145 | 145 | <Image |
| 146 | 146 | @click.stop |
| 147 | - wrapper-class-name="!w-32 !h-32 !flex !items-center" | |
| 147 | + wrapper-class-name="!w-32 !h-32 !flex !items-center overflow-hidden" | |
| 148 | 148 | :src="item.image || IMAGE_FALLBACK" |
| 149 | 149 | placeholder |
| 150 | 150 | :fallback="IMAGE_FALLBACK" | ... | ... |
| 1 | -<template> | |
| 2 | - <BasicForm @register="register" /> | |
| 3 | -</template> | |
| 4 | 1 | <script lang="ts" setup> |
| 5 | 2 | import { BasicForm, useForm } from '/@/components/Form'; |
| 6 | 3 | import { DataType, ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; |
| ... | ... | @@ -15,34 +12,38 @@ |
| 15 | 12 | import { formSchemas } from '/@/components/Form/src/externalCompns/components/StructForm/config'; |
| 16 | 13 | import { TransportTypeEnum } from '../../../../components/TransportDescript/const'; |
| 17 | 14 | import { DataTypeEnum } from '/@/enums/objectModelEnum'; |
| 15 | + import { ref, unref } from 'vue'; | |
| 16 | + import EnumList from '/@/components/Form/src/externalCompns/components/StructForm/EnumList.vue'; | |
| 18 | 17 | |
| 19 | 18 | const props = defineProps<{ openModalMode: OpenModelMode; transportType?: string | undefined }>(); |
| 20 | 19 | |
| 21 | - const [register, { validate, resetFields, setFieldsValue, setProps }] = useForm({ | |
| 20 | + const enumListRef = ref<InstanceType<typeof EnumList>>(); | |
| 21 | + | |
| 22 | + const [register, { validate, getFieldsValue, resetFields, setFieldsValue }] = useForm({ | |
| 22 | 23 | labelWidth: 100, |
| 23 | 24 | schemas: formSchemas({ |
| 24 | 25 | hasStructForm: false, |
| 25 | 26 | hiddenAccessMode: false, |
| 26 | 27 | isTcp: props.transportType === TransportTypeEnum.TCP, |
| 27 | 28 | }), |
| 28 | - actionColOptions: { | |
| 29 | - span: 14, | |
| 30 | - }, | |
| 31 | - showResetButton: false, | |
| 32 | - submitOnReset: false, | |
| 33 | 29 | showActionButtonGroup: false, |
| 34 | 30 | }); |
| 35 | 31 | |
| 32 | + const disabled = ref(false); | |
| 36 | 33 | const setDisable = (flag: boolean) => { |
| 37 | - setProps({ disabled: flag }); | |
| 34 | + disabled.value = flag; | |
| 38 | 35 | }; |
| 39 | 36 | |
| 40 | 37 | async function getFormData(): Promise<Partial<ModelOfMatterParams>> { |
| 41 | - const _values = (await validate()) as StructFormValue; | |
| 42 | - if (!_values) return {}; | |
| 38 | + await validate(); | |
| 39 | + await unref(enumListRef)?.validate?.(); | |
| 40 | + | |
| 41 | + const _values = getFieldsValue() as StructFormValue; | |
| 43 | 42 | const { functionName, remark, identifier, accessMode } = _values; |
| 44 | - const structJSON = transfromToStructJSON(_values); | |
| 43 | + const structJSON = transfromToStructJSON(_values, unref(enumListRef)?.getFieldsValue?.()); | |
| 44 | + | |
| 45 | 45 | const dataType = excludeIdInStructJSON(structJSON.dataType!); |
| 46 | + | |
| 46 | 47 | const value = { |
| 47 | 48 | functionName, |
| 48 | 49 | functionType: FunctionType.PROPERTIES, |
| ... | ... | @@ -54,6 +55,7 @@ |
| 54 | 55 | dataType: dataType, |
| 55 | 56 | }, |
| 56 | 57 | } as ModelOfMatterParams; |
| 58 | + | |
| 57 | 59 | return value; |
| 58 | 60 | } |
| 59 | 61 | |
| ... | ... | @@ -63,18 +65,21 @@ |
| 63 | 65 | |
| 64 | 66 | const setFormData = (record: ModelOfMatterParams) => { |
| 65 | 67 | const { functionJson } = record; |
| 66 | - const { dataType = {} } = functionJson!; | |
| 68 | + const { dataType } = functionJson!; | |
| 67 | 69 | |
| 68 | - const { specs } = dataType! as DataType; | |
| 70 | + const { specs } = (dataType! || {}) as DataType; | |
| 69 | 71 | |
| 70 | 72 | const value = { |
| 71 | 73 | ...record, |
| 72 | 74 | ...functionJson, |
| 73 | 75 | ...dataType, |
| 74 | 76 | ...(isArray(specs) ? specs : { ...specs }), |
| 75 | - hasStructForm: (record?.functionJson?.dataType as DataType)?.type === DataTypeEnum.STRUCT, | |
| 77 | + hasStructForm: (dataType as DataType)?.type === DataTypeEnum.STRUCT, | |
| 78 | + enumList: | |
| 79 | + (dataType as DataType)?.type === DataTypeEnum.ENUM | |
| 80 | + ? unref((dataType as DataType).specsList) | |
| 81 | + : [], | |
| 76 | 82 | }; |
| 77 | - | |
| 78 | 83 | setFieldsValue(value); |
| 79 | 84 | }; |
| 80 | 85 | |
| ... | ... | @@ -85,4 +90,13 @@ |
| 85 | 90 | setDisable, |
| 86 | 91 | }); |
| 87 | 92 | </script> |
| 93 | + | |
| 94 | +<template> | |
| 95 | + <BasicForm @register="register" :disabled="disabled"> | |
| 96 | + <template #EnumList="{ field, model }"> | |
| 97 | + <EnumList ref="enumListRef" :value="model[field]" :disabled="disabled" /> | |
| 98 | + </template> | |
| 99 | + </BasicForm> | |
| 100 | +</template> | |
| 101 | + | |
| 88 | 102 | <style lang="less" scoped></style> | ... | ... |
| ... | ... | @@ -185,8 +185,8 @@ export const list = [ |
| 185 | 185 | { |
| 186 | 186 | deviceType: '网关/直连/网关子设备', |
| 187 | 187 | function: '事件上报', |
| 188 | - release: 'v1/devices/event/${deviceId}||${deviceName}/${identifier}', | |
| 189 | - subscribe: 'v1/devices/event/${deviceId}||${deviceName}/${identifier}', | |
| 188 | + release: 'v1/devices/event/${deviceId}或${deviceName}/${identifier}', | |
| 189 | + subscribe: 'v1/devices/event/${deviceId}或${deviceName}/${identifier}', | |
| 190 | 190 | platform: '订阅', |
| 191 | 191 | device: '发布', |
| 192 | 192 | }, | ... | ... |
| ... | ... | @@ -132,10 +132,7 @@ |
| 132 | 132 | }; |
| 133 | 133 | } |
| 134 | 134 | Reflect.set(values, 'config', config); |
| 135 | - if ( | |
| 136 | - Reflect.get(values, 'messageType') === MessageEnum.IS_VOICE && | |
| 137 | - Reflect.get(values, 'voiceSignName') | |
| 138 | - ) { | |
| 135 | + if (Reflect.get(values, 'messageType') === MessageEnum.IS_VOICE) { | |
| 139 | 136 | Reflect.set(values, 'signName', values['voiceSignName']); |
| 140 | 137 | } |
| 141 | 138 | let saveMessage = '添加成功'; | ... | ... |
| ... | ... | @@ -102,36 +102,36 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => { |
| 102 | 102 | }, |
| 103 | 103 | }, |
| 104 | 104 | { |
| 105 | - field: FormFieldsEnum.ALARM_PROFILED, | |
| 105 | + field: FormFieldsEnum.ALARM_LEVEL, | |
| 106 | 106 | label: '', |
| 107 | - component: 'AlarmProfileSelect', | |
| 108 | - rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }], | |
| 107 | + component: 'Select', | |
| 109 | 108 | ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY, |
| 109 | + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.ALARM_LEVEL}` }], | |
| 110 | 110 | componentProps: () => { |
| 111 | 111 | return { |
| 112 | - api: async () => { | |
| 113 | - if (!unref(organizationId)) return []; | |
| 114 | - return await getOrganizationAlarmConfig({ organizationId: unref(organizationId) }); | |
| 115 | - }, | |
| 116 | - labelField: 'name', | |
| 117 | - valueField: 'id', | |
| 118 | - placeholder: `请选择${FormFieldsNameEnum.ALARM_PROFILED}`, | |
| 112 | + options: Object.keys(AlarmLevelEnum).map((value) => ({ | |
| 113 | + label: AlarmLevelNameEnum[value], | |
| 114 | + value, | |
| 115 | + })), | |
| 116 | + placeholder: `请选择${FormFieldsNameEnum.ALARM_LEVEL}`, | |
| 119 | 117 | }; |
| 120 | 118 | }, |
| 121 | 119 | }, |
| 122 | 120 | { |
| 123 | - field: FormFieldsEnum.ALARM_LEVEL, | |
| 121 | + field: FormFieldsEnum.ALARM_PROFILED, | |
| 124 | 122 | label: '', |
| 125 | - component: 'Select', | |
| 123 | + component: 'AlarmProfileSelect', | |
| 124 | + // rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }], | |
| 126 | 125 | ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY, |
| 127 | - rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_LEVEL}` }], | |
| 128 | 126 | componentProps: () => { |
| 129 | 127 | return { |
| 130 | - options: Object.keys(AlarmLevelEnum).map((value) => ({ | |
| 131 | - label: AlarmLevelNameEnum[value], | |
| 132 | - value, | |
| 133 | - })), | |
| 134 | - placeholder: `请选择${FormFieldsNameEnum.ALARM_LEVEL}`, | |
| 128 | + api: async () => { | |
| 129 | + if (!unref(organizationId)) return []; | |
| 130 | + return await getOrganizationAlarmConfig({ organizationId: unref(organizationId) }); | |
| 131 | + }, | |
| 132 | + labelField: 'name', | |
| 133 | + valueField: 'id', | |
| 134 | + placeholder: `请选择${FormFieldsNameEnum.ALARM_PROFILED}(可选项)`, | |
| 135 | 135 | }; |
| 136 | 136 | }, |
| 137 | 137 | }, | ... | ... |
src/views/sys/appPage/config/config.ts
0 → 100644
| 1 | +import { useRouter } from 'vue-router'; | |
| 2 | + | |
| 3 | +export enum ViewTypeEnum { | |
| 4 | + DATA_BOARD = 'DATA_BOARD', | |
| 5 | + LARGE_SCREEN = 'LARGE_SCREEN', | |
| 6 | + SCADA = 'SCADA', | |
| 7 | +} | |
| 8 | + | |
| 9 | +export const goShareUrl = (options: { type: ViewTypeEnum; id: string }, openNew?: false) => { | |
| 10 | + const { type, id } = options; | |
| 11 | + const ROUTER = useRouter(); | |
| 12 | + const { origin } = location; | |
| 13 | + const path = `/share/${type}/${id}`; | |
| 14 | + openNew ? ROUTER.push(path) : open(`${origin}${path}`); | |
| 15 | +}; | ... | ... |
src/views/sys/appPage/hook/index.ts
0 → 100644
src/views/sys/appPage/index.vue
0 → 100644
| 1 | +<script lang="ts" setup> | |
| 2 | + import { onMounted } from 'vue'; | |
| 3 | + import { Spin } from 'ant-design-vue'; | |
| 4 | + import { ref } from 'vue'; | |
| 5 | + import { useRoute } from 'vue-router'; | |
| 6 | + import { useUserStore } from '/@/store/modules/user'; | |
| 7 | + import BoardDetail from '/@/views/visual/board/detail/index.vue'; | |
| 8 | + import { doAppLogin } from '/@/api/sys/user'; | |
| 9 | + | |
| 10 | + const loading = ref(true); | |
| 11 | + const ROUTE = useRoute(); | |
| 12 | + const contentData = ref<any>(); | |
| 13 | + const canLoadComponent = ref(false); | |
| 14 | + | |
| 15 | + const modelVisable = ref(false); | |
| 16 | + | |
| 17 | + const userStore = useUserStore(); | |
| 18 | + | |
| 19 | + const getShareToken = async () => { | |
| 20 | + const { params } = ROUTE; | |
| 21 | + const { userId }: any = params; | |
| 22 | + const { token, refreshToken } = await doAppLogin(userId); | |
| 23 | + userStore.storeToken(token, refreshToken); | |
| 24 | + }; | |
| 25 | + | |
| 26 | + const getContentLoading = ref(false); | |
| 27 | + const getContentData = async () => { | |
| 28 | + try { | |
| 29 | + getContentLoading.value = true; | |
| 30 | + loading.value = false; | |
| 31 | + canLoadComponent.value = true; | |
| 32 | + modelVisable.value = false; | |
| 33 | + } catch (error) { | |
| 34 | + } finally { | |
| 35 | + getContentLoading.value = false; | |
| 36 | + } | |
| 37 | + }; | |
| 38 | + | |
| 39 | + onMounted(async () => { | |
| 40 | + await getShareToken(); | |
| 41 | + await getContentData(); | |
| 42 | + }); | |
| 43 | +</script> | |
| 44 | + | |
| 45 | +<template> | |
| 46 | + <Spin | |
| 47 | + :spinning="loading" | |
| 48 | + tip="正在加载中..." | |
| 49 | + size="large" | |
| 50 | + class="!flex justify-center items-center w-full h-full share-full-loading" | |
| 51 | + > | |
| 52 | + <BoardDetail v-if="canLoadComponent" :value="contentData" /> | |
| 53 | + </Spin> | |
| 54 | +</template> | |
| 55 | + | |
| 56 | +<style lang="less"> | |
| 57 | + .share-page-token-modal { | |
| 58 | + .ant-modal-close { | |
| 59 | + display: none; | |
| 60 | + } | |
| 61 | + } | |
| 62 | +</style> | ... | ... |
src/views/visual/board/components/TextComponent/config.ts
deleted
100644 → 0
| 1 | -import { ComponentInfo, DataComponentRecord, DataSource } from '/@/api/dataBoard/model'; | |
| 2 | -export interface TextComponentLayout { | |
| 3 | - id: string; | |
| 4 | - base?: boolean; | |
| 5 | - showUpdate?: boolean; | |
| 6 | - showIcon?: boolean; | |
| 7 | - showUnit?: boolean; | |
| 8 | -} | |
| 9 | - | |
| 10 | -export interface TextComponentValue { | |
| 11 | - name: string; | |
| 12 | - value: number; | |
| 13 | - icon?: string; | |
| 14 | - unit?: string; | |
| 15 | - updateTime?: string; | |
| 16 | - fontColor?: string; | |
| 17 | - iconColor?: string; | |
| 18 | - deviceName?: string; | |
| 19 | -} | |
| 20 | - | |
| 21 | -type TextComponentDefault = TextComponentLayout; | |
| 22 | - | |
| 23 | -export const TextComponent1Config: TextComponentDefault = { | |
| 24 | - id: 'text-component-1', | |
| 25 | - base: true, | |
| 26 | -}; | |
| 27 | - | |
| 28 | -export const TextComponent3Config: TextComponentDefault = { | |
| 29 | - id: 'text-component-3', | |
| 30 | - base: false, | |
| 31 | - showUpdate: true, | |
| 32 | -}; | |
| 33 | -export const TextComponent4Config: TextComponentDefault = { | |
| 34 | - id: 'text-component-4', | |
| 35 | - base: false, | |
| 36 | - showIcon: true, | |
| 37 | - showUpdate: true, | |
| 38 | - showUnit: true, | |
| 39 | -}; | |
| 40 | -export const TextComponent5Config: TextComponentDefault = { | |
| 41 | - id: 'text-component-5', | |
| 42 | - base: false, | |
| 43 | - showIcon: true, | |
| 44 | - showUnit: true, | |
| 45 | -}; | |
| 46 | - | |
| 47 | -export const TextComponentDefaultConfig: Partial<ComponentInfo> = { | |
| 48 | - fontColor: '#000', | |
| 49 | - unit: '℃', | |
| 50 | - iconColor: '#367BFF', | |
| 51 | - icon: 'shuiwen', | |
| 52 | -}; | |
| 53 | - | |
| 54 | -export const transformTextComponentConfig = ( | |
| 55 | - config: TextComponentDefault, | |
| 56 | - _record: DataComponentRecord, | |
| 57 | - dataSourceRecord: DataSource | |
| 58 | -) => { | |
| 59 | - return { | |
| 60 | - layout: { | |
| 61 | - ...config, | |
| 62 | - } as TextComponentLayout, | |
| 63 | - value: { | |
| 64 | - name: dataSourceRecord.attributeRename || dataSourceRecord.attribute, | |
| 65 | - deviceName: dataSourceRecord.deviceRename || dataSourceRecord.deviceId, | |
| 66 | - value: dataSourceRecord.componentInfo.value, | |
| 67 | - icon: dataSourceRecord.componentInfo.icon, | |
| 68 | - unit: dataSourceRecord.componentInfo.unit, | |
| 69 | - updateTime: dataSourceRecord.componentInfo.updateTime, | |
| 70 | - fontColor: dataSourceRecord.componentInfo.fontColor, | |
| 71 | - iconColor: dataSourceRecord.componentInfo.iconColor, | |
| 72 | - } as TextComponentValue, | |
| 73 | - }; | |
| 74 | -}; |
| ... | ... | @@ -4,7 +4,13 @@ export enum ViewType { |
| 4 | 4 | PRIVATE_VIEW = 'PRIVATE_VIEW', |
| 5 | 5 | PUBLIC_VIEW = 'PUBLIC_VIEW', |
| 6 | 6 | } |
| 7 | +import { Platform } from '../../palette/components/PagerHeader/config'; | |
| 8 | + | |
| 7 | 9 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
| 10 | +export enum PlatformType { | |
| 11 | + PHONE = 'phone', | |
| 12 | + PC = 'pc', | |
| 13 | +} | |
| 8 | 14 | |
| 9 | 15 | export const formSchema: FormSchema[] = [ |
| 10 | 16 | { |
| ... | ... | @@ -24,6 +30,32 @@ export const formSchema: FormSchema[] = [ |
| 24 | 30 | rules: [{ required: true, message: '组织为必填项' }], |
| 25 | 31 | }, |
| 26 | 32 | { |
| 33 | + field: 'platform', | |
| 34 | + label: '平台', | |
| 35 | + required: true, | |
| 36 | + component: 'RadioGroup', | |
| 37 | + defaultValue: PlatformType.PC, | |
| 38 | + componentProps({ formModel }) { | |
| 39 | + return { | |
| 40 | + defaultValue: PlatformType.PC, | |
| 41 | + options: [ | |
| 42 | + { label: 'PC端', value: PlatformType.PC }, | |
| 43 | + { label: '移动端', value: PlatformType.PHONE }, | |
| 44 | + ], | |
| 45 | + onChange(e) { | |
| 46 | + formModel.phoneModel = | |
| 47 | + e?.target?.value === PlatformType.PHONE ? JSON.stringify(Platform[1]) : []; | |
| 48 | + }, | |
| 49 | + }; | |
| 50 | + }, | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + field: 'phoneModel', | |
| 54 | + label: '手机端尺寸', | |
| 55 | + component: 'Input', | |
| 56 | + ifShow: false, | |
| 57 | + }, | |
| 58 | + { | |
| 27 | 59 | field: 'remark', |
| 28 | 60 | label: '备注', |
| 29 | 61 | component: 'InputTextArea', | ... | ... |
| 1 | +import { PlatformType } from './panelDetail'; | |
| 1 | 2 | import { FormSchema } from '/@/components/Form'; |
| 2 | 3 | import { ColEx } from '/@/components/Form/src/types'; |
| 3 | 4 | import { useGridLayout } from '/@/hooks/component/useGridLayout'; |
| ... | ... | @@ -13,4 +14,16 @@ export const formSchema: FormSchema[] = [ |
| 13 | 14 | placeholder: '请输入看板名称', |
| 14 | 15 | }, |
| 15 | 16 | }, |
| 17 | + { | |
| 18 | + field: 'platform', | |
| 19 | + label: '平台', | |
| 20 | + component: 'Select', | |
| 21 | + colProps: useGridLayout(2, 3, 4) as unknown as ColEx, | |
| 22 | + componentProps: { | |
| 23 | + options: [ | |
| 24 | + { label: 'PC端', value: PlatformType.PC }, | |
| 25 | + { label: '移动端', value: PlatformType.PHONE }, | |
| 26 | + ], | |
| 27 | + }, | |
| 28 | + }, | |
| 16 | 29 | ]; | ... | ... |
| ... | ... | @@ -135,12 +135,13 @@ |
| 135 | 135 | |
| 136 | 136 | const handleViewBoard = (record: DataBoardRecord) => { |
| 137 | 137 | const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL); |
| 138 | + const { platform = 'pc' } = record || {}; | |
| 138 | 139 | if (hasDetailPermission) { |
| 139 | 140 | const boardId = encode(record.id); |
| 140 | 141 | const boardName = encode(record.name); |
| 141 | 142 | const organizationId = encode(record!.organizationId!); |
| 142 | 143 | |
| 143 | - router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`); | |
| 144 | + router.push(`/visual/board/detail/${boardId}/${boardName}/${platform}/${organizationId}`); | |
| 144 | 145 | } else createMessage.warning('没有权限'); |
| 145 | 146 | }; |
| 146 | 147 | </script> |
| ... | ... | @@ -170,7 +171,7 @@ |
| 170 | 171 | </Dropdown> |
| 171 | 172 | </template> |
| 172 | 173 | <section @click="handleViewBoard(item)"> |
| 173 | - <div class="flex data-card__info"> | |
| 174 | + <div class="flex data-card__info relative"> | |
| 174 | 175 | <div> |
| 175 | 176 | <div>组件数量</div> |
| 176 | 177 | <Statistic class="text-2xl" :value="item.componentNum"> |
| ... | ... | @@ -179,6 +180,12 @@ |
| 179 | 180 | </template> |
| 180 | 181 | </Statistic> |
| 181 | 182 | </div> |
| 183 | + <div class="absolute" style="right: 1%"> | |
| 184 | + <Icon | |
| 185 | + :icon="item.platform === 'pc' ? 'ri:computer-line' : 'clarity:mobile-phone-solid'" | |
| 186 | + style="font-size: 32px" | |
| 187 | + /> | |
| 188 | + </div> | |
| 182 | 189 | </div> |
| 183 | 190 | <div class="flex justify-between mt-4 text-sm" style="color: #999"> |
| 184 | 191 | <div class="flex min-w-20 mr-3"> | ... | ... |
| ... | ... | @@ -107,6 +107,9 @@ export const formSchemas = (): FormSchema[] => { |
| 107 | 107 | [DataSourceField.DEVICE_ID]: null, |
| 108 | 108 | }); |
| 109 | 109 | }, |
| 110 | + apiTreeSelectProps: { | |
| 111 | + params: { organizationId: location?.pathname?.split('/')?.pop() || '' }, | |
| 112 | + }, | |
| 110 | 113 | showCreate: false, |
| 111 | 114 | getPopupContainer: () => document.body, |
| 112 | 115 | }; |
| ... | ... | @@ -157,7 +160,6 @@ export const formSchemas = (): FormSchema[] => { |
| 157 | 160 | }); |
| 158 | 161 | }, |
| 159 | 162 | placeholder: '请选择设备', |
| 160 | - getPopupContainer: () => document.body, | |
| 161 | 163 | ...createPickerSearch(), |
| 162 | 164 | }; |
| 163 | 165 | }, | ... | ... |
| ... | ... | @@ -58,24 +58,24 @@ |
| 58 | 58 | |
| 59 | 59 | const alarmColumns: BasicColumn[] = [ |
| 60 | 60 | { |
| 61 | - title: '状态', | |
| 62 | - dataIndex: 'severity', | |
| 61 | + title: '告警等级', | |
| 62 | + dataIndex: 'status', | |
| 63 | 63 | ellipsis: true, |
| 64 | 64 | width: 80, |
| 65 | - customRender: ({ record }) => { | |
| 66 | - const { severity } = record; | |
| 67 | - const { text, color } = alarmLevel(severity); | |
| 68 | - return h(Tag, { color }, () => text); | |
| 69 | - }, | |
| 65 | + format: (text) => statusType(text), | |
| 70 | 66 | }, |
| 71 | 67 | { title: '设备', dataIndex: 'device', ellipsis: true, width: 120 }, |
| 72 | 68 | { title: '告警场景', dataIndex: 'type', ellipsis: true, width: 80 }, |
| 73 | 69 | { |
| 74 | - title: '状态', | |
| 75 | - dataIndex: 'status', | |
| 70 | + title: '告警级别', | |
| 71 | + dataIndex: 'severity', | |
| 76 | 72 | ellipsis: true, |
| 77 | 73 | width: 80, |
| 78 | - format: (text) => statusType(text), | |
| 74 | + customRender: ({ record }) => { | |
| 75 | + const { severity } = record; | |
| 76 | + const { text, color } = alarmLevel(severity); | |
| 77 | + return h(Tag, { color }, () => text); | |
| 78 | + }, | |
| 79 | 79 | }, |
| 80 | 80 | { |
| 81 | 81 | title: '时间', | ... | ... |
| ... | ... | @@ -4,8 +4,8 @@ |
| 4 | 4 | destroyOnClose |
| 5 | 5 | v-bind="$attrs" |
| 6 | 6 | width="30rem" |
| 7 | - :height="50" | |
| 8 | - :minHeight="50" | |
| 7 | + :height="75" | |
| 8 | + :minHeight="75" | |
| 9 | 9 | @register="register" |
| 10 | 10 | title="操作密码" |
| 11 | 11 | @ok="handleSuccess" |
| ... | ... | @@ -33,6 +33,8 @@ |
| 33 | 33 | const [registerForm, { getFieldsValue, validate }] = useForm({ |
| 34 | 34 | labelWidth: 70, |
| 35 | 35 | schemas: formSchema, |
| 36 | + | |
| 37 | + wrapperCol: { span: 12 }, | |
| 36 | 38 | showActionButtonGroup: false, |
| 37 | 39 | }); |
| 38 | 40 | ... | ... |
| ... | ... | @@ -14,7 +14,7 @@ |
| 14 | 14 | import { useBaiduMapSDK } from '../../../hook/useBaiduMapSDK'; |
| 15 | 15 | import { HistoryModalOkEmitParams, TrackAnimationStatus } from './type'; |
| 16 | 16 | import { useMapTrackPlayback } from './useMapTrackPlayback'; |
| 17 | - import { shallowRef } from 'vue'; | |
| 17 | + // import { shallowRef } from 'vue'; | |
| 18 | 18 | import { formatToDateTime } from '/@/utils/dateUtil'; |
| 19 | 19 | |
| 20 | 20 | const props = defineProps<{ |
| ... | ... | @@ -25,7 +25,7 @@ |
| 25 | 25 | |
| 26 | 26 | const wrapId = `bai-map-${buildUUID()}`; |
| 27 | 27 | |
| 28 | - const mapInstance = shallowRef<Nullable<Recordable>>(null); | |
| 28 | + const mapInstance = ref<Nullable<Recordable>>(null); | |
| 29 | 29 | |
| 30 | 30 | const rangString = ref<Nullable<string>>(null); |
| 31 | 31 | |
| ... | ... | @@ -35,6 +35,9 @@ |
| 35 | 35 | openModal(true, toRaw(props.config.option.dataSource)); |
| 36 | 36 | }; |
| 37 | 37 | |
| 38 | + const { genTrackPlaybackAnimation, playStatus, playFn, continueFn, pauseFn } = | |
| 39 | + useMapTrackPlayback(mapInstance); | |
| 40 | + | |
| 38 | 41 | const getIsWidgetLibSelectMode = computed(() => { |
| 39 | 42 | return !props.config.option.dataSource; |
| 40 | 43 | }); |
| ... | ... | @@ -68,8 +71,8 @@ |
| 68 | 71 | async function initMap() { |
| 69 | 72 | const wrapEl = unref(wrapRef); |
| 70 | 73 | if (!wrapEl) return; |
| 71 | - const BMapGL = (window as any).BMapGL; | |
| 72 | 74 | if (!Reflect.has(window, 'BMapGL')) return; |
| 75 | + const BMapGL = (window as any).BMapGL; | |
| 73 | 76 | mapInstance.value = new BMapGL.Map(wrapId); |
| 74 | 77 | |
| 75 | 78 | // 定位当前城市 |
| ... | ... | @@ -89,13 +92,10 @@ |
| 89 | 92 | } |
| 90 | 93 | |
| 91 | 94 | const { loading } = useBaiduMapSDK(initMap); |
| 92 | - | |
| 93 | - const { genTrackPlaybackAnimation, playStatus, playFn, continueFn, pauseFn } = | |
| 94 | - useMapTrackPlayback(mapInstance); | |
| 95 | 95 | </script> |
| 96 | 96 | |
| 97 | 97 | <template> |
| 98 | - <main class="w-full h-full flex flex-col justify-center items-center"> | |
| 98 | + <main class="w-full h-full flex flex-col p-2 justify-center items-center"> | |
| 99 | 99 | <div class="w-full flex justify-end"> |
| 100 | 100 | <Button |
| 101 | 101 | type="text" |
| ... | ... | @@ -126,9 +126,8 @@ |
| 126 | 126 | :spinning="loading" |
| 127 | 127 | wrapper-class-name="map-spin-wrapper !w-full !h-full !flex justify-center items-center pointer-events-none" |
| 128 | 128 | tip="地图加载中..." |
| 129 | - > | |
| 130 | - <div ref="wrapRef" :id="wrapId" class="w-full h-full no-drag"> </div> | |
| 131 | - </Spin> | |
| 129 | + /> | |
| 130 | + <div ref="wrapRef" :id="wrapId" class="w-full h-full no-drag"> </div> | |
| 132 | 131 | |
| 133 | 132 | <HistoryDataModel @register="register" @ok="handleRenderHistroyData" /> |
| 134 | 133 | </main> | ... | ... |
| ... | ... | @@ -88,6 +88,9 @@ export const formSchemas: FormSchema[] = [ |
| 88 | 88 | return { |
| 89 | 89 | showCreate: false, |
| 90 | 90 | allowClean: true, |
| 91 | + apiTreeSelectProps: { | |
| 92 | + params: { organizationId: location?.pathname?.split('/')?.pop() || '' }, | |
| 93 | + }, | |
| 91 | 94 | onChange() { |
| 92 | 95 | setFieldsValue({ |
| 93 | 96 | [FormFieldEnum.ACCESS_MODE]: null, | ... | ... |
| ... | ... | @@ -68,8 +68,6 @@ |
| 68 | 68 | }; |
| 69 | 69 | |
| 70 | 70 | const instance = unref(basicVideoPlayEl)?.customInit((options) => { |
| 71 | - withToken.value = true; | |
| 72 | - | |
| 73 | 71 | if (unref(withToken)) { |
| 74 | 72 | (options as any).flvjs.config.headers = { |
| 75 | 73 | 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ... | ... |
| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | import { ComponentPropsConfigType } from '../../../index.type'; |
| 6 | 6 | import { useDataFetch } from '../../../hook/socket/useSocket'; |
| 7 | 7 | import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; |
| 8 | + import { isNullOrUnDef } from '/@/utils/is'; | |
| 8 | 9 | |
| 9 | 10 | const props = defineProps<{ |
| 10 | 11 | config: ComponentPropsConfigType; |
| ... | ... | @@ -42,6 +43,7 @@ |
| 42 | 43 | const { data = {} } = message; |
| 43 | 44 | const [latest] = data[attribute] || []; |
| 44 | 45 | const [timespan, value] = latest; |
| 46 | + if (isNullOrUnDef(value)) return; | |
| 45 | 47 | time.value = timespan; |
| 46 | 48 | url.value = `${value}?timespan=${timespan}`; |
| 47 | 49 | }; | ... | ... |
| ... | ... | @@ -23,6 +23,7 @@ |
| 23 | 23 | import { WidgetDataType } from '../../hooks/useDataSource'; |
| 24 | 24 | import { ExtraDataSource } from '../../types'; |
| 25 | 25 | import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config'; |
| 26 | + import { useApp } from '../../hooks/useApp'; | |
| 26 | 27 | |
| 27 | 28 | type DeviceOption = Record<'label' | 'value' | 'organizationId', string>; |
| 28 | 29 | |
| ... | ... | @@ -38,6 +39,8 @@ |
| 38 | 39 | |
| 39 | 40 | const historyData = ref<{ ts: number; value: string; name: string }[]>([]); |
| 40 | 41 | |
| 42 | + const { getIsAppPage } = useApp(); | |
| 43 | + | |
| 41 | 44 | const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } = |
| 42 | 45 | useHistoryData(); |
| 43 | 46 | |
| ... | ... | @@ -294,7 +297,7 @@ |
| 294 | 297 | :destroy-on-close="true" |
| 295 | 298 | :show-ok-btn="false" |
| 296 | 299 | cancel-text="关闭" |
| 297 | - width="70%" | |
| 300 | + :width="getIsAppPage ? '100%' : '75%'" | |
| 298 | 301 | title="历史趋势" |
| 299 | 302 | > |
| 300 | 303 | <section | ... | ... |
| 1 | +export const Platform = [ | |
| 2 | + { key: 'iPhone 8', title: 'iPhone 8', width: 375, height: 667 }, | |
| 3 | + { key: 'iPhone 8 Plus', title: 'iPhone 8 Plus', width: 415, height: 737 }, | |
| 4 | + { key: 'iPhone X/XS', title: 'iPhone X/XS', width: 376, height: 813 }, | |
| 5 | + { key: 'iPad 4', title: 'iPad 4', width: 709, height: 1025 }, | |
| 6 | + { key: 'Galaxy S9', title: 'Galaxy S9', width: 361, height: 741 }, | |
| 7 | + { key: 'Galaxy S10/S10+', title: 'Galaxy S10/S10+', width: 413, height: 870 }, | |
| 8 | + { key: 'Pixel 2', title: 'Pixel 2', width: 413, height: 732 }, | |
| 9 | + { key: 'custom', title: '自定义', width: '', height: '' }, | |
| 10 | +]; | ... | ... |
| ... | ... | @@ -9,6 +9,7 @@ |
| 9 | 9 | |
| 10 | 10 | const ROUTE = useRoute(); |
| 11 | 11 | const ROUTER = useRouter(); |
| 12 | + | |
| 12 | 13 | const getIsSharePage = computed(() => { |
| 13 | 14 | return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId'); |
| 14 | 15 | }); |
| ... | ... | @@ -21,8 +22,6 @@ |
| 21 | 22 | if (unref(getIsSharePage)) return; |
| 22 | 23 | ROUTER.go(-1); |
| 23 | 24 | }; |
| 24 | - | |
| 25 | - // const handleOpenCreatePanel = () => {}; | |
| 26 | 25 | </script> |
| 27 | 26 | |
| 28 | 27 | <template> | ... | ... |
| ... | ... | @@ -19,6 +19,7 @@ |
| 19 | 19 | import { computed } from 'vue'; |
| 20 | 20 | import { useGetComponentConfig } from '../../../packages/hook/useGetComponetConfig'; |
| 21 | 21 | import { isBoolean } from '/@/utils/is'; |
| 22 | + import { useApp } from '../../hooks/useApp'; | |
| 22 | 23 | |
| 23 | 24 | const props = defineProps<{ |
| 24 | 25 | sourceInfo: WidgetDataType; |
| ... | ... | @@ -38,6 +39,8 @@ |
| 38 | 39 | |
| 39 | 40 | const { createMessage } = useMessage(); |
| 40 | 41 | |
| 42 | + const { getIsAppPage } = useApp(); | |
| 43 | + | |
| 41 | 44 | const { boardId } = useBoardId(); |
| 42 | 45 | |
| 43 | 46 | const dropMenuList = ref<AuthDropMenuList[]>([ |
| ... | ... | @@ -171,7 +174,7 @@ |
| 171 | 174 | <AreaChartOutlined v-else class="text-lg" @click="handleOpenTrendModal" /> |
| 172 | 175 | </Tooltip> |
| 173 | 176 | <AuthDropDown |
| 174 | - v-if="!isCustomerUser && dropMenuList.length" | |
| 177 | + v-if="!isCustomerUser && dropMenuList.length && !getIsAppPage" | |
| 175 | 178 | :drop-menu-list="dropMenuList" |
| 176 | 179 | :trigger="['click']" |
| 177 | 180 | > | ... | ... |
| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | import { BasicForm } from '/@/components/Form'; |
| 8 | 8 | import { BasicModal } from '/@/components/Modal'; |
| 9 | 9 | import { nextTick } from 'vue'; |
| 10 | + import { useApp } from '../../hooks/useApp'; | |
| 10 | 11 | const emit = defineEmits(['register', 'getAlarmForm', 'getHistoryForm']); |
| 11 | 12 | // const emit = defineEmits<{ |
| 12 | 13 | // (event: 'getAlarmForm', data: WidgetDataType): void; |
| ... | ... | @@ -15,6 +16,8 @@ |
| 15 | 16 | // const fontId = ref(''); |
| 16 | 17 | const [registerModal, { closeModal }] = useModalInner(async () => {}); |
| 17 | 18 | |
| 19 | + const { getIsAppPage } = useApp(); | |
| 20 | + | |
| 18 | 21 | const [register, method] = useForm({ |
| 19 | 22 | schemas: formSchema(), |
| 20 | 23 | baseColProps: useGridLayout(1) as unknown as ColEx, |
| ... | ... | @@ -53,7 +56,7 @@ |
| 53 | 56 | :destroy-on-close="true" |
| 54 | 57 | :show-ok-btn="true" |
| 55 | 58 | cancel-text="关闭" |
| 56 | - width="40%" | |
| 59 | + :width="getIsAppPage ? '80%' : '40%'" | |
| 57 | 60 | title="历史趋势" |
| 58 | 61 | > |
| 59 | 62 | <section | ... | ... |
src/views/visual/palette/hooks/useApp.ts
0 → 100644
| 1 | +import { computed } from 'vue'; | |
| 2 | +import { useRoute } from 'vue-router'; | |
| 3 | + | |
| 4 | +export const useApp = () => { | |
| 5 | + const ROUTE = useRoute(); | |
| 6 | + const getIsAppPage = computed(() => { | |
| 7 | + return ROUTE.matched.find((item) => item.path === '/appPage/:boardId/:userId'); | |
| 8 | + }); | |
| 9 | + | |
| 10 | + const isPhone = () => { | |
| 11 | + const values = location?.pathname.split('/') || []; | |
| 12 | + return values[values?.length - 2] === 'phone' ? true : false; | |
| 13 | + }; | |
| 14 | + | |
| 15 | + return { getIsAppPage, isPhone }; | |
| 16 | +}; | ... | ... |
| 1 | 1 | import { unref } from 'vue'; |
| 2 | 2 | import { Layout } from 'vue3-grid-layout'; |
| 3 | -import { DEFAULT_MAX_COL, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH } from '..'; | |
| 3 | +import { DEFAULT_MAX_COL, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, PHONE_SIZE } from '..'; | |
| 4 | +import { useApp } from './useApp'; | |
| 4 | 5 | |
| 5 | 6 | interface GapRecord { |
| 6 | 7 | maxGap: number; |
| ... | ... | @@ -8,9 +9,14 @@ interface GapRecord { |
| 8 | 9 | endIndex: Nullable<number>; |
| 9 | 10 | } |
| 10 | 11 | |
| 12 | +const { isPhone } = useApp(); | |
| 13 | + | |
| 11 | 14 | export const useCalcNewWidgetPosition = ( |
| 12 | 15 | layoutInfo: Layout[], |
| 13 | - randomLayout = { width: DEFAULT_WIDGET_WIDTH, height: DEFAULT_WIDGET_HEIGHT } | |
| 16 | + randomLayout = { | |
| 17 | + width: isPhone() ? PHONE_SIZE.DEFAULT_WIDGET_WIDTH : DEFAULT_WIDGET_WIDTH, | |
| 18 | + height: isPhone() ? PHONE_SIZE.DEFAULT_WIDGET_HEIGHT : DEFAULT_WIDGET_HEIGHT, | |
| 19 | + } | |
| 14 | 20 | ) => { |
| 15 | 21 | let maxWidth = 0; |
| 16 | 22 | let maxHeight = 0; | ... | ... |
| ... | ... | @@ -30,6 +30,10 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => { |
| 30 | 30 | const getIsSharePage = computed(() => { |
| 31 | 31 | return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId'); |
| 32 | 32 | }); |
| 33 | + //小程序打开的页面 | |
| 34 | + const getIsAppPage = computed(() => { | |
| 35 | + return ROUTE.matched.find((item) => item.path === '/appPage/:boardId/:userId'); | |
| 36 | + }); | |
| 33 | 37 | |
| 34 | 38 | const getBoardId = computed(() => { |
| 35 | 39 | return decode((ROUTE.params as { boardId: string }).boardId); |
| ... | ... | @@ -131,8 +135,8 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => { |
| 131 | 135 | |
| 132 | 136 | return { |
| 133 | 137 | loading, |
| 134 | - draggable: !unref(getIsSharePage), | |
| 135 | - resizable: !unref(getIsSharePage), | |
| 138 | + draggable: !unref(getIsSharePage) && !unref(getIsAppPage), | |
| 139 | + resizable: !unref(getIsSharePage) && !unref(getIsAppPage), | |
| 136 | 140 | dataSource, |
| 137 | 141 | rawDataSource, |
| 138 | 142 | getDataSource, | ... | ... |
| ... | ... | @@ -11,6 +11,12 @@ export const DEFAULT_WIDGET_HEIGHT = 6; |
| 11 | 11 | export const DEFAULT_MIN_HEIGHT = 5; |
| 12 | 12 | export const DEFAULT_MIN_WIDTH = 3; |
| 13 | 13 | export const DEFAULT_ITEM_MARIGN = 20; |
| 14 | +export const PHONE_SIZE = { | |
| 15 | + DEFAULT_WIDGET_WIDTH: 24, | |
| 16 | + DEFAULT_WIDGET_HEIGHT: 5, | |
| 17 | + DEFAULT_MIN_HEIGHT: 5, | |
| 18 | + DEFAULT_MIN_WIDTH: 24, | |
| 19 | +}; | |
| 14 | 20 | |
| 15 | 21 | import { ViewTypeEnum } from '/@/views/sys/share/config/config'; |
| 16 | 22 | ... | ... |
| ... | ... | @@ -10,6 +10,7 @@ |
| 10 | 10 | DEFAULT_MIN_WIDTH, |
| 11 | 11 | DEFAULT_ITEM_MARIGN, |
| 12 | 12 | VisualComponentPermission, |
| 13 | + PHONE_SIZE, | |
| 13 | 14 | } from './index'; |
| 14 | 15 | import { useDragGridLayout } from './hooks/useDragGridLayout'; |
| 15 | 16 | import { WidgetHeader, WidgetWrapper } from './components/WidgetWrapper'; |
| ... | ... | @@ -19,6 +20,7 @@ |
| 19 | 20 | import { DataSourceBindPanel } from '../dataSourceBindPanel'; |
| 20 | 21 | import { PageHeader } from './components/PagerHeader'; |
| 21 | 22 | import { useShare } from './hooks/useShare'; |
| 23 | + import { useApp } from './hooks/useApp'; | |
| 22 | 24 | import { useRole } from '/@/hooks/business/useRole'; |
| 23 | 25 | import { Authority } from '/@/components/Authority'; |
| 24 | 26 | import { useModal } from '/@/components/Modal'; |
| ... | ... | @@ -33,6 +35,11 @@ |
| 33 | 35 | import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket'; |
| 34 | 36 | import { createAlarmContext } from './hooks/useAlarmTime'; |
| 35 | 37 | import { createHistoryContext } from './hooks/useHistoryForm'; |
| 38 | + import { MoreOutlined, LeftOutlined } from '@ant-design/icons-vue'; | |
| 39 | + import WIFISVG from '/@/assets/svg/wifi.svg'; | |
| 40 | + import SIGNALSVG from '/@/assets/svg/signal.svg'; | |
| 41 | + import BATTERYSVG from '/@/assets/svg/battery.svg'; | |
| 42 | + import { useRoute } from 'vue-router'; | |
| 36 | 43 | |
| 37 | 44 | const props = defineProps<{ |
| 38 | 45 | value?: Recordable; |
| ... | ... | @@ -44,6 +51,8 @@ |
| 44 | 51 | |
| 45 | 52 | const containerRectRef = ref<DOMRect>({} as unknown as DOMRect); |
| 46 | 53 | |
| 54 | + const ROUTE = useRoute(); | |
| 55 | + | |
| 47 | 56 | const { loading, draggable, resizable, dataSource, rawDataSource, setLayoutInfo, getDataSource } = |
| 48 | 57 | useDataSource(getProps); |
| 49 | 58 | |
| ... | ... | @@ -63,6 +72,9 @@ |
| 63 | 72 | } |
| 64 | 73 | }); |
| 65 | 74 | const { getIsSharePage } = useShare(); |
| 75 | + | |
| 76 | + // getIsAppPage 是否是小程序进入的页面 isPhone 是否是创建的手机端 | |
| 77 | + const { getIsAppPage, isPhone } = useApp(); | |
| 66 | 78 | const { isCustomerUser } = useRole(); |
| 67 | 79 | const handleOpenCreatePanel = () => { |
| 68 | 80 | openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType); |
| ... | ... | @@ -157,6 +169,25 @@ |
| 157 | 169 | }, |
| 158 | 170 | { immediate: true } |
| 159 | 171 | ); |
| 172 | + watch( | |
| 173 | + getIsAppPage, | |
| 174 | + (value) => { | |
| 175 | + if (value) { | |
| 176 | + const root = document.querySelector('#app'); | |
| 177 | + (root as HTMLDivElement).style.backgroundColor = | |
| 178 | + unref(getDarkMode) === ThemeEnum.LIGHT ? '#F5F5F5' : '#1b1b1b'; | |
| 179 | + } | |
| 180 | + }, | |
| 181 | + { immediate: true } | |
| 182 | + ); | |
| 183 | + const getDataBoardName = computed(() => { | |
| 184 | + return decodeURIComponent((ROUTE.params as { boardName: string }).boardName || ''); | |
| 185 | + }); | |
| 186 | + | |
| 187 | + const phoneSize = ref({ | |
| 188 | + width: 375, | |
| 189 | + height: 667, | |
| 190 | + }); | |
| 160 | 191 | </script> |
| 161 | 192 | |
| 162 | 193 | <template> |
| ... | ... | @@ -164,7 +195,7 @@ |
| 164 | 195 | ref="containerRefEl" |
| 165 | 196 | class="palette w-full h-full flex-col bg-neutral-100 flex dark:bg-dark-700 dark:text-light-50" |
| 166 | 197 | > |
| 167 | - <PageHeader :widget-number="dataSource.length"> | |
| 198 | + <PageHeader v-if="!getIsAppPage" :widget-number="dataSource.length"> | |
| 168 | 199 | <Authority :value="VisualComponentPermission.CREATE"> |
| 169 | 200 | <Button |
| 170 | 201 | v-if="!getIsSharePage && !isCustomerUser" |
| ... | ... | @@ -177,55 +208,104 @@ |
| 177 | 208 | </PageHeader> |
| 178 | 209 | |
| 179 | 210 | <Spin :spinning="loading"> |
| 180 | - <GridLayout | |
| 181 | - v-model:layout="dataSource" | |
| 182 | - :col-num="DEFAULT_MAX_COL" | |
| 183 | - :row-height="30" | |
| 184 | - :margin="[DEFAULT_ITEM_MARIGN, DEFAULT_ITEM_MARIGN]" | |
| 185 | - :is-draggable="draggable" | |
| 186 | - :is-resizable="resizable" | |
| 187 | - :vertical-compact="true" | |
| 188 | - :use-css-transforms="true" | |
| 189 | - style="width: 100%" | |
| 190 | - > | |
| 191 | - <GridItem | |
| 192 | - v-for="item in dataSource" | |
| 193 | - :key="item.i" | |
| 194 | - :static="item.static" | |
| 195 | - :x="item.x" | |
| 196 | - :y="item.y" | |
| 197 | - :w="item.w" | |
| 198 | - :h="item.h" | |
| 199 | - :i="item.i" | |
| 200 | - :min-h="DEFAULT_MIN_HEIGHT" | |
| 201 | - :min-w="DEFAULT_MIN_WIDTH" | |
| 202 | - :style="{ display: 'flex', flexWrap: 'wrap' }" | |
| 203 | - class="grid-item-layout" | |
| 204 | - @resized="resized" | |
| 205 | - @resize="resize" | |
| 206 | - @moved="moved" | |
| 207 | - @container-resized="containerResized" | |
| 208 | - drag-ignore-from=".no-drag" | |
| 211 | + <div class="w-full h-full flex justify-center items-center mb-3"> | |
| 212 | + <div | |
| 213 | + :style=" | |
| 214 | + !getIsAppPage && isPhone() | |
| 215 | + ? { | |
| 216 | + width: phoneSize.width + 'px', | |
| 217 | + height: phoneSize.height + 'px', | |
| 218 | + border: '2px solid #e5e7eb', | |
| 219 | + } | |
| 220 | + : { width: '100%', height: '100%' } | |
| 221 | + " | |
| 222 | + style="border-radius: 1%" | |
| 209 | 223 | > |
| 210 | - <WidgetWrapper> | |
| 211 | - <template #header> | |
| 212 | - <WidgetHeader | |
| 213 | - :raw-data-source="rawDataSource" | |
| 214 | - :source-info="item" | |
| 215 | - @update="handleUpdateWidget" | |
| 216 | - @open-trend="handleOpenTrend" | |
| 217 | - @open-alarm="handleOpenAlarm" | |
| 218 | - @ok="getDataSource" | |
| 219 | - /> | |
| 220 | - </template> | |
| 221 | - <WidgetDistribute :source-info="item" /> | |
| 222 | - </WidgetWrapper> | |
| 223 | - </GridItem> | |
| 224 | - </GridLayout> | |
| 225 | - <Empty | |
| 226 | - v-if="!dataSource.length" | |
| 227 | - class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" | |
| 228 | - /> | |
| 224 | + <!-- 手机端的模拟样式 --> | |
| 225 | + <div | |
| 226 | + v-if="!getIsAppPage && isPhone()" | |
| 227 | + style="height: 60px; background: white" | |
| 228 | + class="px-1 py-1 relative" | |
| 229 | + > | |
| 230 | + <div class="flex justify-between"> | |
| 231 | + <div>thingskit</div> | |
| 232 | + <div class="flex items-center"> | |
| 233 | + <img :src="WIFISVG" alt="" class="w-3 h-3" /> | |
| 234 | + <img :src="SIGNALSVG" alt="" class="w-3 h-3 mx-1" /> | |
| 235 | + <img :src="BATTERYSVG" alt="" class="w-4 h-4 rotate-45" /> | |
| 236 | + <h1 class="mb-0">18:13</h1> | |
| 237 | + </div> | |
| 238 | + </div> | |
| 239 | + <div class="flex items-center justify-between"> | |
| 240 | + <LeftOutlined class="transform cursor-pointer text-lg" /> | |
| 241 | + <h1 class="font-bold">{{ getDataBoardName }}</h1> | |
| 242 | + <MoreOutlined class="transform rotate-90 cursor-pointer text-lg" /> | |
| 243 | + </div> | |
| 244 | + </div> | |
| 245 | + | |
| 246 | + <div | |
| 247 | + id="appLayoutId" | |
| 248 | + :style=" | |
| 249 | + !getIsAppPage && isPhone() ? { height: `calc(${phoneSize.height}px - 67px)` } : {} | |
| 250 | + " | |
| 251 | + class="overflow-y-scroll" | |
| 252 | + > | |
| 253 | + <GridLayout | |
| 254 | + v-model:layout="dataSource" | |
| 255 | + :col-num="DEFAULT_MAX_COL" | |
| 256 | + :row-height="30" | |
| 257 | + :margin="[DEFAULT_ITEM_MARIGN, DEFAULT_ITEM_MARIGN]" | |
| 258 | + :is-draggable="draggable" | |
| 259 | + :is-resizable="resizable" | |
| 260 | + :vertical-compact="true" | |
| 261 | + :use-css-transforms="true" | |
| 262 | + style="width: 100%" | |
| 263 | + :style="{ | |
| 264 | + '--is-app': getIsAppPage ? 'auto' : 'none', | |
| 265 | + }" | |
| 266 | + > | |
| 267 | + <GridItem | |
| 268 | + v-for="item in dataSource" | |
| 269 | + :key="item.i" | |
| 270 | + :static="item.static" | |
| 271 | + :x="item.x" | |
| 272 | + :y="item.y" | |
| 273 | + :w="item.w" | |
| 274 | + :h="item.h" | |
| 275 | + :i="item.i" | |
| 276 | + :min-h="isPhone() ? PHONE_SIZE.DEFAULT_MIN_HEIGHT : DEFAULT_MIN_HEIGHT" | |
| 277 | + :min-w="isPhone() ? PHONE_SIZE.DEFAULT_MIN_WIDTH : DEFAULT_MIN_WIDTH" | |
| 278 | + :style="{ display: 'flex', flexWrap: 'wrap' }" | |
| 279 | + class="grid-item-layout" | |
| 280 | + @resized="resized" | |
| 281 | + @resize="resize" | |
| 282 | + @moved="moved" | |
| 283 | + @container-resized="containerResized" | |
| 284 | + drag-ignore-from=".no-drag" | |
| 285 | + > | |
| 286 | + <WidgetWrapper> | |
| 287 | + <template #header> | |
| 288 | + <WidgetHeader | |
| 289 | + :raw-data-source="rawDataSource" | |
| 290 | + :source-info="item" | |
| 291 | + @update="handleUpdateWidget" | |
| 292 | + @open-trend="handleOpenTrend" | |
| 293 | + @open-alarm="handleOpenAlarm" | |
| 294 | + @ok="getDataSource" | |
| 295 | + /> | |
| 296 | + </template> | |
| 297 | + <WidgetDistribute :source-info="item" /> | |
| 298 | + </WidgetWrapper> | |
| 299 | + </GridItem> | |
| 300 | + </GridLayout> | |
| 301 | + </div> | |
| 302 | + <Empty | |
| 303 | + v-if="!dataSource.length" | |
| 304 | + :class="isPhone() ? 'absolute' : 'fixed'" | |
| 305 | + class="top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" | |
| 306 | + /> | |
| 307 | + </div> | |
| 308 | + </div> | |
| 229 | 309 | </Spin> |
| 230 | 310 | |
| 231 | 311 | <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" /> |
| ... | ... | @@ -238,4 +318,9 @@ |
| 238 | 318 | </section> |
| 239 | 319 | </template> |
| 240 | 320 | |
| 241 | -<style lang="less" scoped></style> | |
| 321 | +<style lang="less"> | |
| 322 | + .vue-grid-item { | |
| 323 | + pointer-events: painted; | |
| 324 | + touch-action: var(--is-app) !important; | |
| 325 | + } | |
| 326 | +</style> | ... | ... |