Showing
13 changed files
with
342 additions
and
51 deletions
| 1 | +import { DeviceRecord } from '../device/model/deviceModel'; | ||
| 1 | import { | 2 | import { |
| 2 | AddDataBoardParams, | 3 | AddDataBoardParams, |
| 3 | AddDataComponentParams, | 4 | AddDataComponentParams, |
| @@ -183,7 +184,7 @@ export const getAllDeviceByOrg = (organizationId: string, deviceProfileId?: stri | @@ -183,7 +184,7 @@ export const getAllDeviceByOrg = (organizationId: string, deviceProfileId?: stri | ||
| 183 | * @returns | 184 | * @returns |
| 184 | */ | 185 | */ |
| 185 | export const getMeetTheConditionsDevice = (params: GetMeetTheConditionsDeviceParams) => { | 186 | export const getMeetTheConditionsDevice = (params: GetMeetTheConditionsDeviceParams) => { |
| 186 | - return defHttp.get({ | 187 | + return defHttp.get<DeviceRecord[]>({ |
| 187 | url: DeviceUrl.GET_DEVICE, | 188 | url: DeviceUrl.GET_DEVICE, |
| 188 | params, | 189 | params, |
| 189 | }); | 190 | }); |
| @@ -40,6 +40,11 @@ enum DeviceManagerApi { | @@ -40,6 +40,11 @@ enum DeviceManagerApi { | ||
| 40 | DEVICE_PUBLIC = '/customer/public/device', | 40 | DEVICE_PUBLIC = '/customer/public/device', |
| 41 | 41 | ||
| 42 | DEVICE_PRIVATE = '/customer/device', | 42 | DEVICE_PRIVATE = '/customer/device', |
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * @description 通过设备列表获取设备信息 | ||
| 46 | + */ | ||
| 47 | + QUERY_DEVICES = '/device/get/devices', | ||
| 43 | } | 48 | } |
| 44 | 49 | ||
| 45 | export const devicePage = (params: DeviceQueryParam) => { | 50 | export const devicePage = (params: DeviceQueryParam) => { |
| @@ -330,3 +335,10 @@ export const privateDevice = (tbDeviceId: string) => { | @@ -330,3 +335,10 @@ export const privateDevice = (tbDeviceId: string) => { | ||
| 330 | { joinPrefix: false } | 335 | { joinPrefix: false } |
| 331 | ); | 336 | ); |
| 332 | }; | 337 | }; |
| 338 | + | ||
| 339 | +export const getDevicesByDeviceIds = (ids: string[]) => { | ||
| 340 | + return defHttp.post<Record<'data', DeviceModel[]>>({ | ||
| 341 | + url: DeviceManagerApi.QUERY_DEVICES, | ||
| 342 | + data: ids, | ||
| 343 | + }); | ||
| 344 | +}; |
| @@ -2,6 +2,7 @@ import { | @@ -2,6 +2,7 @@ import { | ||
| 2 | CreateTaskRecordType, | 2 | CreateTaskRecordType, |
| 3 | GenModbusCommandType, | 3 | GenModbusCommandType, |
| 4 | GetTaskListParamsType, | 4 | GetTaskListParamsType, |
| 5 | + ImmediateExecuteTaskType, | ||
| 5 | TaskRecordType, | 6 | TaskRecordType, |
| 6 | } from './model'; | 7 | } from './model'; |
| 7 | import { PaginationResult } from '/#/axios'; | 8 | import { PaginationResult } from '/#/axios'; |
| @@ -16,6 +17,8 @@ enum Api { | @@ -16,6 +17,8 @@ enum Api { | ||
| 16 | CANCEL_TASK = '/task_center', | 17 | CANCEL_TASK = '/task_center', |
| 17 | 18 | ||
| 18 | GEN_MODBUS_COMMAND = '/js/modbus', | 19 | GEN_MODBUS_COMMAND = '/js/modbus', |
| 20 | + | ||
| 21 | + IMMEDIATE_EXECUTE = '/task_center/immediate/execute', | ||
| 19 | } | 22 | } |
| 20 | 23 | ||
| 21 | export const getTaskCenterList = (params: GetTaskListParamsType) => { | 24 | export const getTaskCenterList = (params: GetTaskListParamsType) => { |
| @@ -76,3 +79,13 @@ export const genModbusCommand = (data: GenModbusCommandType) => { | @@ -76,3 +79,13 @@ export const genModbusCommand = (data: GenModbusCommandType) => { | ||
| 76 | data, | 79 | data, |
| 77 | }); | 80 | }); |
| 78 | }; | 81 | }; |
| 82 | + | ||
| 83 | +export const immediateExecute = (data: ImmediateExecuteTaskType) => { | ||
| 84 | + return defHttp.post<Record<'data', boolean>>( | ||
| 85 | + { | ||
| 86 | + url: Api.IMMEDIATE_EXECUTE, | ||
| 87 | + params: data, | ||
| 88 | + }, | ||
| 89 | + { joinParamsToUrl: true } | ||
| 90 | + ); | ||
| 91 | +}; |
| @@ -62,3 +62,11 @@ export interface GenModbusCommandType { | @@ -62,3 +62,11 @@ export interface GenModbusCommandType { | ||
| 62 | registerNum?: number; | 62 | registerNum?: number; |
| 63 | registerValues?: number[]; | 63 | registerValues?: number[]; |
| 64 | } | 64 | } |
| 65 | + | ||
| 66 | +export interface ImmediateExecuteTaskType { | ||
| 67 | + executeTarget: TaskTargetEnum; | ||
| 68 | + id: string; | ||
| 69 | + cronExpression: string; | ||
| 70 | + targetIds: string[]; | ||
| 71 | + name: string; | ||
| 72 | +} |
| @@ -124,6 +124,7 @@ export type ComponentType = | @@ -124,6 +124,7 @@ export type ComponentType = | ||
| 124 | | 'TransferTableModal' | 124 | | 'TransferTableModal' |
| 125 | | 'ObjectModelValidateForm' | 125 | | 'ObjectModelValidateForm' |
| 126 | | 'DevicePicker' | 126 | | 'DevicePicker' |
| 127 | + | 'ProductPicker' | ||
| 127 | | 'PollCommandInput' | 128 | | 'PollCommandInput' |
| 128 | | 'RegisterAddressInput' | 129 | | 'RegisterAddressInput' |
| 129 | | 'ControlGroup'; | 130 | | 'ControlGroup'; |
| @@ -9,13 +9,13 @@ import { | @@ -9,13 +9,13 @@ import { | ||
| 9 | import { PollCommandInput, ModeEnum } from '../PollCommandInput'; | 9 | import { PollCommandInput, ModeEnum } from '../PollCommandInput'; |
| 10 | import { DeviceProfileModel } from '/@/api/device/model/deviceModel'; | 10 | import { DeviceProfileModel } from '/@/api/device/model/deviceModel'; |
| 11 | import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const'; | 11 | import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const'; |
| 12 | -import { getDeviceProfile } from '/@/api/alarm/position'; | ||
| 13 | import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor'; | 12 | import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor'; |
| 14 | import { TimeUnitEnum, TimeUnitNameEnum } from '/@/enums/toolEnum'; | 13 | import { TimeUnitEnum, TimeUnitNameEnum } from '/@/enums/toolEnum'; |
| 15 | -import { createPickerSearch } from '/@/utils/pickerSearch'; | ||
| 16 | import { dateUtil } from '/@/utils/dateUtil'; | 14 | import { dateUtil } from '/@/utils/dateUtil'; |
| 15 | +import { ProductPicker, validateProductPicker } from '../ProductPicker'; | ||
| 17 | 16 | ||
| 18 | useComponentRegister('DevicePicker', DevicePicker); | 17 | useComponentRegister('DevicePicker', DevicePicker); |
| 18 | +useComponentRegister('ProductPicker', ProductPicker); | ||
| 19 | useComponentRegister('PollCommandInput', PollCommandInput); | 19 | useComponentRegister('PollCommandInput', PollCommandInput); |
| 20 | 20 | ||
| 21 | export enum FormFieldsEnum { | 21 | export enum FormFieldsEnum { |
| @@ -108,27 +108,26 @@ export const formSchemas: FormSchema[] = [ | @@ -108,27 +108,26 @@ export const formSchemas: FormSchema[] = [ | ||
| 108 | }, | 108 | }, |
| 109 | { | 109 | { |
| 110 | field: FormFieldsEnum.DEVICE_PROFILE, | 110 | field: FormFieldsEnum.DEVICE_PROFILE, |
| 111 | - component: 'ApiSelect', | 111 | + component: 'ProductPicker', |
| 112 | label: '产品', | 112 | label: '产品', |
| 113 | ifShow: ({ model }) => model[FormFieldsEnum.TARGET_TYPE] === TaskTargetEnum.PRODUCTS, | 113 | ifShow: ({ model }) => model[FormFieldsEnum.TARGET_TYPE] === TaskTargetEnum.PRODUCTS, |
| 114 | + valueField: 'value', | ||
| 115 | + changeEvent: 'update:value', | ||
| 116 | + rules: [validateProductPicker()], | ||
| 117 | + helpMessage: ['任务可以对目标产品按预设的时间策略执行任务。'], | ||
| 114 | componentProps: ({ formActionType }) => { | 118 | componentProps: ({ formActionType }) => { |
| 115 | const { setFieldsValue } = formActionType; | 119 | const { setFieldsValue } = formActionType; |
| 116 | return { | 120 | return { |
| 117 | - api: getDeviceProfile, | ||
| 118 | - placeholder: '请选择产品', | ||
| 119 | - labelField: 'name', | ||
| 120 | - valueField: 'id', | ||
| 121 | - onChange(value: string, option: DeviceProfileModel) { | ||
| 122 | - if (value) { | ||
| 123 | - const isTCP = option.transportType === TransportTypeEnum.TCP; | 121 | + onChange(key: string, _value: string | string[], option: DeviceProfileModel) { |
| 122 | + if (key === DeviceCascadePickerFieldsEnum.DEVICE_PROFILE) { | ||
| 123 | + const isTCP = (option || {}).transportType === TransportTypeEnum.TCP; | ||
| 124 | setFieldsValue({ | 124 | setFieldsValue({ |
| 125 | - [FormFieldsEnum.TRANSPORT_TYPE]: isTCP ? PushWayEnum.TCP : PushWayEnum.MQTT, | 125 | + [FormFieldsEnum.TRANSPORT_TYPE]: _value ? option.transportType : null, |
| 126 | [FormFieldsEnum.PUSH_WAY]: isTCP ? PushWayEnum.TCP : PushWayEnum.MQTT, | 126 | [FormFieldsEnum.PUSH_WAY]: isTCP ? PushWayEnum.TCP : PushWayEnum.MQTT, |
| 127 | ...(isTCP ? {} : { [FormFieldsEnum.EXECUTE_CONTENT_TYPE]: TaskTypeEnum.CUSTOM }), | 127 | ...(isTCP ? {} : { [FormFieldsEnum.EXECUTE_CONTENT_TYPE]: TaskTypeEnum.CUSTOM }), |
| 128 | }); | 128 | }); |
| 129 | } | 129 | } |
| 130 | }, | 130 | }, |
| 131 | - ...createPickerSearch(), | ||
| 132 | getPopupContainer: () => document.body, | 131 | getPopupContainer: () => document.body, |
| 133 | }; | 132 | }; |
| 134 | }, | 133 | }, |
| @@ -5,9 +5,11 @@ import { CreateTaskRecordType, TaskRecordType } from '/@/api/task/model'; | @@ -5,9 +5,11 @@ import { CreateTaskRecordType, TaskRecordType } from '/@/api/task/model'; | ||
| 5 | import { DeviceCascadePickerValueType } from '../DevicePicker'; | 5 | import { DeviceCascadePickerValueType } from '../DevicePicker'; |
| 6 | import { TaskTargetEnum } from '../../config'; | 6 | import { TaskTargetEnum } from '../../config'; |
| 7 | import { TimeUnitEnum } from '/@/enums/toolEnum'; | 7 | import { TimeUnitEnum } from '/@/enums/toolEnum'; |
| 8 | +import { ProductCascadePickerValueType } from '../ProductPicker'; | ||
| 8 | 9 | ||
| 9 | export interface FormValueType extends Partial<Record<FormFieldsEnum, any>> { | 10 | export interface FormValueType extends Partial<Record<FormFieldsEnum, any>> { |
| 10 | [FormFieldsEnum.EXECUTE_TARGET_DATA]: DeviceCascadePickerValueType; | 11 | [FormFieldsEnum.EXECUTE_TARGET_DATA]: DeviceCascadePickerValueType; |
| 12 | + [FormFieldsEnum.DEVICE_PROFILE]: ProductCascadePickerValueType; | ||
| 11 | } | 13 | } |
| 12 | 14 | ||
| 13 | type CanWrite<T> = { | 15 | type CanWrite<T> = { |
| @@ -95,6 +97,12 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy | @@ -95,6 +97,12 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy | ||
| 95 | 97 | ||
| 96 | const { organizationId, deviceType, deviceId, deviceProfileId } = executeTargetData || {}; | 98 | const { organizationId, deviceType, deviceId, deviceProfileId } = executeTargetData || {}; |
| 97 | 99 | ||
| 100 | + const { | ||
| 101 | + organizationId: productOrg, | ||
| 102 | + deviceProfileId: productId, | ||
| 103 | + deviceType: productDeviceType, | ||
| 104 | + } = deviceProfile || {}; | ||
| 105 | + | ||
| 98 | const { expression } = genCronExpression(time, period); | 106 | const { expression } = genCronExpression(time, period); |
| 99 | 107 | ||
| 100 | const cron = | 108 | const cron = |
| @@ -111,10 +119,10 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy | @@ -111,10 +119,10 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy | ||
| 111 | type: executeContentType, | 119 | type: executeContentType, |
| 112 | }, | 120 | }, |
| 113 | executeTarget: { | 121 | executeTarget: { |
| 114 | - data: targetType === TaskTargetEnum.PRODUCTS ? [deviceProfile] : (deviceId as string[]), | ||
| 115 | - deviceType, | ||
| 116 | - organizationId, | ||
| 117 | - deviceProfileId: targetType === TaskTargetEnum.PRODUCTS ? deviceProfile : deviceProfileId, | 122 | + data: targetType === TaskTargetEnum.PRODUCTS ? [productId] : (deviceId as string[]), |
| 123 | + deviceType: targetType === TaskTargetEnum.PRODUCTS ? productDeviceType : deviceType, | ||
| 124 | + organizationId: targetType === TaskTargetEnum.PRODUCTS ? productOrg : organizationId, | ||
| 125 | + deviceProfileId: targetType === TaskTargetEnum.PRODUCTS ? productId : deviceProfileId, | ||
| 118 | }, | 126 | }, |
| 119 | executeTime: { | 127 | executeTime: { |
| 120 | type: executeTimeType, | 128 | type: executeTimeType, |
| @@ -147,7 +155,14 @@ export const parseData = (result: TaskRecordType): Required<FormValueType> => { | @@ -147,7 +155,14 @@ export const parseData = (result: TaskRecordType): Required<FormValueType> => { | ||
| 147 | deviceType, | 155 | deviceType, |
| 148 | organizationId, | 156 | organizationId, |
| 149 | }, | 157 | }, |
| 150 | - deviceProfile: targetType === TaskTargetEnum.PRODUCTS ? data : null, | 158 | + deviceProfile: |
| 159 | + targetType === TaskTargetEnum.PRODUCTS | ||
| 160 | + ? { | ||
| 161 | + deviceProfileId: data[0], | ||
| 162 | + deviceType, | ||
| 163 | + organizationId, | ||
| 164 | + } | ||
| 165 | + : ({} as unknown as ProductCascadePickerValueType), | ||
| 151 | executeTimeType, | 166 | executeTimeType, |
| 152 | period, | 167 | period, |
| 153 | periodType: executeTimeType === ExecuteTimeTypeEnum.CUSTOM ? periodType : null, | 168 | periodType: executeTimeType === ExecuteTimeTypeEnum.CUSTOM ? periodType : null, |
| 1 | +export { default as ProductPicker } from './index.vue'; | ||
| 2 | + | ||
| 3 | +export enum FormFieldsEnum { | ||
| 4 | + DEVICE_TYPE = 'deviceType', | ||
| 5 | + DEVICE_PROFILE = 'deviceProfileId', | ||
| 6 | + ORGANIZATION = 'organizationId', | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +export type ProductCascadePickerValueType = Record<FormFieldsEnum, string>; | ||
| 10 | + | ||
| 11 | +export { validateProductPicker } from './utils'; |
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { getOrganizationList } from '/@/api/system/system'; | ||
| 3 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
| 4 | + import { copyTransFun } from '/@/utils/fnUtils'; | ||
| 5 | + import { watch } from 'vue'; | ||
| 6 | + import { nextTick } from 'vue'; | ||
| 7 | + import { getDeviceProfile } from '/@/api/alarm/position'; | ||
| 8 | + import { findDictItemByCode } from '/@/api/system/dict'; | ||
| 9 | + import { DictEnum } from '/@/enums/dictEnum'; | ||
| 10 | + import { FormFieldsEnum } from '.'; | ||
| 11 | + import { isObject } from '/@/utils/is'; | ||
| 12 | + import { createPickerSearch } from '/@/utils/pickerSearch'; | ||
| 13 | + | ||
| 14 | + const props = withDefaults( | ||
| 15 | + defineProps<{ | ||
| 16 | + value?: Recordable; | ||
| 17 | + }>(), | ||
| 18 | + { | ||
| 19 | + value: () => ({}), | ||
| 20 | + } | ||
| 21 | + ); | ||
| 22 | + | ||
| 23 | + const emit = defineEmits(['update:value', 'change']); | ||
| 24 | + | ||
| 25 | + const handleChange = (key: string, value: string | string[], option: Recordable) => { | ||
| 26 | + emit('change', key, value, option); | ||
| 27 | + }; | ||
| 28 | + | ||
| 29 | + const handleEmit = async (key: string, value: string | string[], option: Recordable) => { | ||
| 30 | + await nextTick(); | ||
| 31 | + let _value = getFieldsValue(); | ||
| 32 | + _value = { | ||
| 33 | + ..._value, | ||
| 34 | + | ||
| 35 | + ...(key === FormFieldsEnum.DEVICE_TYPE | ||
| 36 | + ? { | ||
| 37 | + [FormFieldsEnum.DEVICE_PROFILE]: null, | ||
| 38 | + } | ||
| 39 | + : {}), | ||
| 40 | + }; | ||
| 41 | + handleChange(key, value, option); | ||
| 42 | + emit('update:value', { ..._value }); | ||
| 43 | + }; | ||
| 44 | + | ||
| 45 | + const [register, { setFieldsValue, getFieldsValue, resetFields }] = useForm({ | ||
| 46 | + schemas: [ | ||
| 47 | + { | ||
| 48 | + field: FormFieldsEnum.DEVICE_TYPE, | ||
| 49 | + component: 'ApiSelect', | ||
| 50 | + label: '', | ||
| 51 | + componentProps: () => { | ||
| 52 | + return { | ||
| 53 | + api: findDictItemByCode, | ||
| 54 | + params: { | ||
| 55 | + dictCode: DictEnum.DEVICE_TYPE, | ||
| 56 | + }, | ||
| 57 | + labelField: 'itemText', | ||
| 58 | + valueField: 'itemValue', | ||
| 59 | + placeholder: '请选择设备类型', | ||
| 60 | + onChange(value: string, option: Recordable) { | ||
| 61 | + handleEmit(FormFieldsEnum.DEVICE_TYPE, value, option); | ||
| 62 | + }, | ||
| 63 | + getPopupContainer: () => document.body, | ||
| 64 | + ...createPickerSearch(), | ||
| 65 | + }; | ||
| 66 | + }, | ||
| 67 | + }, | ||
| 68 | + { | ||
| 69 | + field: FormFieldsEnum.ORGANIZATION, | ||
| 70 | + component: 'ApiTreeSelect', | ||
| 71 | + label: '', | ||
| 72 | + componentProps: () => { | ||
| 73 | + return { | ||
| 74 | + api: async () => { | ||
| 75 | + try { | ||
| 76 | + const data = await getOrganizationList(); | ||
| 77 | + copyTransFun(data as any); | ||
| 78 | + return data; | ||
| 79 | + } catch (error) { | ||
| 80 | + console.log(error); | ||
| 81 | + return []; | ||
| 82 | + } | ||
| 83 | + }, | ||
| 84 | + placeholder: '请选择组织', | ||
| 85 | + labelField: 'name', | ||
| 86 | + valueField: 'id', | ||
| 87 | + childField: 'children', | ||
| 88 | + onChange(value: string, option: Recordable) { | ||
| 89 | + handleEmit(FormFieldsEnum.ORGANIZATION, value, option); | ||
| 90 | + }, | ||
| 91 | + getPopupContainer: () => document.body, | ||
| 92 | + }; | ||
| 93 | + }, | ||
| 94 | + }, | ||
| 95 | + { | ||
| 96 | + field: FormFieldsEnum.DEVICE_PROFILE, | ||
| 97 | + component: 'ApiSelect', | ||
| 98 | + label: '', | ||
| 99 | + componentProps: ({ formModel }) => { | ||
| 100 | + const deviceType = Reflect.get(formModel, FormFieldsEnum.DEVICE_TYPE); | ||
| 101 | + return { | ||
| 102 | + api: async () => { | ||
| 103 | + try { | ||
| 104 | + return await getDeviceProfile(deviceType); | ||
| 105 | + } catch (error) { | ||
| 106 | + return []; | ||
| 107 | + } | ||
| 108 | + }, | ||
| 109 | + placeholder: '请选择产品', | ||
| 110 | + labelField: 'name', | ||
| 111 | + valueField: 'id', | ||
| 112 | + onChange(value: string, options: Recordable) { | ||
| 113 | + handleEmit(FormFieldsEnum.DEVICE_PROFILE, value, options); | ||
| 114 | + }, | ||
| 115 | + getPopupContainer: () => document.body, | ||
| 116 | + ...createPickerSearch(), | ||
| 117 | + }; | ||
| 118 | + }, | ||
| 119 | + }, | ||
| 120 | + ], | ||
| 121 | + showActionButtonGroup: false, | ||
| 122 | + layout: 'inline', | ||
| 123 | + baseColProps: { span: 8 }, | ||
| 124 | + }); | ||
| 125 | + | ||
| 126 | + const setValue = async () => { | ||
| 127 | + await nextTick(); | ||
| 128 | + if (!props.value || (isObject(props.value) && !Object.values(props.value).length)) { | ||
| 129 | + resetFields(); | ||
| 130 | + return; | ||
| 131 | + } | ||
| 132 | + setFieldsValue(props.value); | ||
| 133 | + }; | ||
| 134 | + | ||
| 135 | + watch( | ||
| 136 | + () => props.value, | ||
| 137 | + () => { | ||
| 138 | + setValue(); | ||
| 139 | + }, | ||
| 140 | + { | ||
| 141 | + immediate: true, | ||
| 142 | + } | ||
| 143 | + ); | ||
| 144 | +</script> | ||
| 145 | + | ||
| 146 | +<template> | ||
| 147 | + <BasicForm @register="register" class="device-picker" /> | ||
| 148 | +</template> | ||
| 149 | + | ||
| 150 | +<style lang="less" scoped> | ||
| 151 | + .device-picker { | ||
| 152 | + :deep(.ant-row) { | ||
| 153 | + width: 100%; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + :deep(.ant-form-item-control-input-content) { | ||
| 157 | + div > div { | ||
| 158 | + width: 100%; | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | +</style> |
| 1 | +import { FormFieldsEnum } from '.'; | ||
| 2 | +import { Rule } from '/@/components/Form'; | ||
| 3 | + | ||
| 4 | +export const validateProductPicker = () => { | ||
| 5 | + return { | ||
| 6 | + required: true, | ||
| 7 | + validateTrigger: 'blur', | ||
| 8 | + validator(_rule: Recordable, value: Recordable, _callback: Fn) { | ||
| 9 | + const product = Reflect.get(value || {}, FormFieldsEnum.DEVICE_PROFILE); | ||
| 10 | + const org = Reflect.get(value || {}, FormFieldsEnum.ORGANIZATION); | ||
| 11 | + if (!product) return Promise.reject('请选择产品'); | ||
| 12 | + if (!org) return Promise.reject('请选择组织'); | ||
| 13 | + return Promise.resolve(); | ||
| 14 | + }, | ||
| 15 | + } as Rule; | ||
| 16 | +}; |
| 1 | import { TaskTargetEnum } from '../../config'; | 1 | import { TaskTargetEnum } from '../../config'; |
| 2 | +import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; | ||
| 3 | +import { getDevicesByDeviceIds } from '/@/api/device/deviceManager'; | ||
| 4 | +import { TaskRecordType } from '/@/api/task/model'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 5 | import { FormSchema } from '/@/components/Form'; |
| 6 | +import { createPickerSearch } from '/@/utils/pickerSearch'; | ||
| 7 | + | ||
| 8 | +export interface FormValue extends Record<FormFieldsEnum, any> { | ||
| 9 | + [FormFieldsEnum.TASK_RECORD]: string; | ||
| 10 | +} | ||
| 3 | 11 | ||
| 4 | export enum FormFieldsEnum { | 12 | export enum FormFieldsEnum { |
| 5 | - RUN_TARGET_TYPE = 'runTargetType', | 13 | + EXECUTE_TARGET_TYPE = 'executeTarget', |
| 6 | TASK_TYPE = 'taskType', | 14 | TASK_TYPE = 'taskType', |
| 7 | - ASSIGN_TARGET = 'assignTarget', | ||
| 8 | - TASK_TARGET_TYPE = 'taskTargetType', | ||
| 9 | - TASK_TARGET_VALUE = 'taskTargetValue', | 15 | + TARGET_IDS = 'targetIds', |
| 16 | + TASK_RECORD = 'taskRecord', | ||
| 10 | } | 17 | } |
| 11 | 18 | ||
| 12 | export enum TargetType { | 19 | export enum TargetType { |
| @@ -33,26 +40,13 @@ export const formSchemas: FormSchema[] = [ | @@ -33,26 +40,13 @@ export const formSchemas: FormSchema[] = [ | ||
| 33 | slot: 'taskType', | 40 | slot: 'taskType', |
| 34 | }, | 41 | }, |
| 35 | { | 42 | { |
| 36 | - field: FormFieldsEnum.TASK_TARGET_TYPE, | ||
| 37 | - component: 'Input', | ||
| 38 | - label: '目标类型', | ||
| 39 | - show: false, | ||
| 40 | - }, | ||
| 41 | - { | ||
| 42 | - field: FormFieldsEnum.TASK_TARGET_VALUE, | 43 | + field: FormFieldsEnum.TASK_RECORD, |
| 43 | component: 'Input', | 44 | component: 'Input', |
| 44 | - label: '目标值', | ||
| 45 | - show: false, | ||
| 46 | - }, | ||
| 47 | - { | ||
| 48 | - field: FormFieldsEnum.TASK_TYPE, | ||
| 49 | - component: 'Input', | ||
| 50 | - label: '任务类型', | ||
| 51 | - renderComponentContent: 'taskType', | 45 | + label: '任务详情', |
| 52 | show: false, | 46 | show: false, |
| 53 | }, | 47 | }, |
| 54 | { | 48 | { |
| 55 | - field: FormFieldsEnum.RUN_TARGET_TYPE, | 49 | + field: FormFieldsEnum.EXECUTE_TARGET_TYPE, |
| 56 | component: 'RadioGroup', | 50 | component: 'RadioGroup', |
| 57 | label: '选择目标类型', | 51 | label: '选择目标类型', |
| 58 | helpMessage: [ | 52 | helpMessage: [ |
| @@ -67,23 +61,36 @@ export const formSchemas: FormSchema[] = [ | @@ -67,23 +61,36 @@ export const formSchemas: FormSchema[] = [ | ||
| 67 | }, | 61 | }, |
| 68 | }, | 62 | }, |
| 69 | { | 63 | { |
| 70 | - field: FormFieldsEnum.ASSIGN_TARGET, | 64 | + field: FormFieldsEnum.TARGET_IDS, |
| 71 | component: 'ApiSelect', | 65 | component: 'ApiSelect', |
| 72 | label: '制定目标设备', | 66 | label: '制定目标设备', |
| 73 | - ifShow: ({ model }) => model[FormFieldsEnum.RUN_TARGET_TYPE] === TargetType.ASSIGN, | 67 | + ifShow: ({ model }) => model[FormFieldsEnum.EXECUTE_TARGET_TYPE] === TargetType.ASSIGN, |
| 74 | componentProps: ({ formModel }) => { | 68 | componentProps: ({ formModel }) => { |
| 75 | - const taskTargetType = formModel[FormFieldsEnum.TASK_TARGET_TYPE]; | ||
| 76 | - const isDevices = taskTargetType === TaskTargetEnum.DEVICES; | 69 | + const record = JSON.parse(formModel[FormFieldsEnum.TASK_RECORD]) as TaskRecordType; |
| 70 | + const isDevices = record.targetType === TaskTargetEnum.DEVICES; | ||
| 71 | + const { executeTarget } = record; | ||
| 72 | + const { organizationId, data } = executeTarget; | ||
| 77 | return { | 73 | return { |
| 78 | api: async () => { | 74 | api: async () => { |
| 79 | try { | 75 | try { |
| 80 | if (isDevices) { | 76 | if (isDevices) { |
| 77 | + const result = await getDevicesByDeviceIds(data!); | ||
| 78 | + return result.data; | ||
| 81 | } else { | 79 | } else { |
| 80 | + return await getMeetTheConditionsDevice({ | ||
| 81 | + organizationId, | ||
| 82 | + deviceProfileId: data![0], | ||
| 83 | + }); | ||
| 82 | } | 84 | } |
| 83 | } catch (error) { | 85 | } catch (error) { |
| 84 | return []; | 86 | return []; |
| 85 | } | 87 | } |
| 86 | }, | 88 | }, |
| 89 | + mode: 'multiple', | ||
| 90 | + labelField: 'name', | ||
| 91 | + valueField: 'tbDeviceId', | ||
| 92 | + ...createPickerSearch(), | ||
| 93 | + placeholder: '请选择设备', | ||
| 87 | getPopupContainer: () => document.body, | 94 | getPopupContainer: () => document.body, |
| 88 | }; | 95 | }; |
| 89 | }, | 96 | }, |
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | import { ref } from 'vue'; | 2 | import { ref } from 'vue'; |
| 3 | - import { TaskRecordType } from '/@/api/task/model'; | 3 | + import { ImmediateExecuteTaskType, TaskRecordType } from '/@/api/task/model'; |
| 4 | import { BasicForm, useForm } from '/@/components/Form'; | 4 | import { BasicForm, useForm } from '/@/components/Form'; |
| 5 | import { BasicModal, useModalInner } from '/@/components/Modal'; | 5 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| 6 | - import { TaskTypeNameEnum } from '../../config'; | ||
| 7 | - import { formSchemas } from './config'; | 6 | + import { TaskTargetEnum, TaskTypeNameEnum } from '../../config'; |
| 7 | + import { FormValue, TargetType, formSchemas } from './config'; | ||
| 8 | + import { unref } from 'vue'; | ||
| 9 | + import { immediateExecute } from '/@/api/task'; | ||
| 10 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 11 | + const props = defineProps<{ | ||
| 12 | + reload: Fn; | ||
| 13 | + }>(); | ||
| 8 | 14 | ||
| 9 | defineEmits(['register']); | 15 | defineEmits(['register']); |
| 10 | 16 | ||
| 11 | const dataSource = ref<TaskRecordType>(); | 17 | const dataSource = ref<TaskRecordType>(); |
| 12 | 18 | ||
| 13 | - const [registerModal] = useModalInner((record: TaskRecordType) => { | 19 | + const [registerModal, { closeModal }] = useModalInner((record: TaskRecordType) => { |
| 20 | + resetFields(); | ||
| 14 | dataSource.value = record; | 21 | dataSource.value = record; |
| 15 | if (record) { | 22 | if (record) { |
| 16 | - setFieldsValue(record); | 23 | + setFieldsValue({ taskRecord: JSON.stringify(record) } as FormValue); |
| 17 | } | 24 | } |
| 18 | }); | 25 | }); |
| 19 | 26 | ||
| 20 | - const [registerForm, { setFieldsValue }] = useForm({ | 27 | + const [registerForm, { setFieldsValue, getFieldsValue, resetFields }] = useForm({ |
| 21 | schemas: formSchemas, | 28 | schemas: formSchemas, |
| 22 | showActionButtonGroup: false, | 29 | showActionButtonGroup: false, |
| 23 | }); | 30 | }); |
| 31 | + | ||
| 32 | + const composeData = (record: FormValue): ImmediateExecuteTaskType => { | ||
| 33 | + const { executeTarget, targetIds } = record; | ||
| 34 | + return { | ||
| 35 | + executeTarget: | ||
| 36 | + executeTarget === TargetType.ASSIGN | ||
| 37 | + ? TaskTargetEnum.DEVICES | ||
| 38 | + : unref(dataSource)!.targetType, | ||
| 39 | + id: unref(dataSource)!.id, | ||
| 40 | + targetIds, | ||
| 41 | + cronExpression: unref(dataSource)!.executeTime.cron, | ||
| 42 | + name: unref(dataSource)!.name, | ||
| 43 | + }; | ||
| 44 | + }; | ||
| 45 | + | ||
| 46 | + const loading = ref(false); | ||
| 47 | + const { createMessage } = useMessage(); | ||
| 48 | + const handleOk = async () => { | ||
| 49 | + const record = getFieldsValue() as FormValue; | ||
| 50 | + const value = composeData(record); | ||
| 51 | + try { | ||
| 52 | + loading.value = true; | ||
| 53 | + const { data } = await immediateExecute(value); | ||
| 54 | + data ? createMessage.success('运行成功') : createMessage.error('运行失败'); | ||
| 55 | + if (data) { | ||
| 56 | + closeModal(); | ||
| 57 | + props.reload?.(); | ||
| 58 | + } | ||
| 59 | + } catch (error) { | ||
| 60 | + throw error; | ||
| 61 | + } finally { | ||
| 62 | + loading.value = false; | ||
| 63 | + } | ||
| 64 | + }; | ||
| 24 | </script> | 65 | </script> |
| 25 | 66 | ||
| 26 | <template> | 67 | <template> |
| 27 | - <BasicModal @register="registerModal" title="运行任务"> | 68 | + <BasicModal |
| 69 | + @register="registerModal" | ||
| 70 | + title="运行任务" | ||
| 71 | + :okButtonProps="{ loading }" | ||
| 72 | + @ok="handleOk" | ||
| 73 | + > | ||
| 28 | <BasicForm @register="registerForm"> | 74 | <BasicForm @register="registerForm"> |
| 29 | <template #taskName> | 75 | <template #taskName> |
| 30 | <div class="font-semibold"> | 76 | <div class="font-semibold"> |
| @@ -100,7 +100,7 @@ | @@ -100,7 +100,7 @@ | ||
| 100 | <section | 100 | <section |
| 101 | class="bg-light-50 flex p-4 justify-between items-center x dark:text-gray-300 dark:bg-dark-900" | 101 | class="bg-light-50 flex p-4 justify-between items-center x dark:text-gray-300 dark:bg-dark-900" |
| 102 | > | 102 | > |
| 103 | - <div class="text-2xl">任务</div> | 103 | + <div class="text-2xl">任务中心</div> |
| 104 | <Authority :value="PermissionEnum.CREATE"> | 104 | <Authority :value="PermissionEnum.CREATE"> |
| 105 | <Button | 105 | <Button |
| 106 | type="primary" | 106 | type="primary" |
| @@ -143,7 +143,7 @@ | @@ -143,7 +143,7 @@ | ||
| 143 | </List> | 143 | </List> |
| 144 | </section> | 144 | </section> |
| 145 | <DetailModal @register="registerModal" :reload="reload" /> | 145 | <DetailModal @register="registerModal" :reload="reload" /> |
| 146 | - <RunTaskModal @register="registerRunTaskModal" /> | 146 | + <RunTaskModal :reload="reload" @register="registerRunTaskModal" /> |
| 147 | </PageWrapper> | 147 | </PageWrapper> |
| 148 | </template> | 148 | </template> |
| 149 | 149 |