Commit 3d1616dcfb1eb8f7a642b2da04db705f0b8eacf3

Authored by ww
1 parent 66212522

feat: 完成任务中心立即实行实现

  1 +import { DeviceRecord } from '../device/model/deviceModel';
1 2 import {
2 3 AddDataBoardParams,
3 4 AddDataComponentParams,
... ... @@ -183,7 +184,7 @@ export const getAllDeviceByOrg = (organizationId: string, deviceProfileId?: stri
183 184 * @returns
184 185 */
185 186 export const getMeetTheConditionsDevice = (params: GetMeetTheConditionsDeviceParams) => {
186   - return defHttp.get({
  187 + return defHttp.get<DeviceRecord[]>({
187 188 url: DeviceUrl.GET_DEVICE,
188 189 params,
189 190 });
... ...
... ... @@ -40,6 +40,11 @@ enum DeviceManagerApi {
40 40 DEVICE_PUBLIC = '/customer/public/device',
41 41
42 42 DEVICE_PRIVATE = '/customer/device',
  43 +
  44 + /**
  45 + * @description 通过设备列表获取设备信息
  46 + */
  47 + QUERY_DEVICES = '/device/get/devices',
43 48 }
44 49
45 50 export const devicePage = (params: DeviceQueryParam) => {
... ... @@ -330,3 +335,10 @@ export const privateDevice = (tbDeviceId: string) => {
330 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 2 CreateTaskRecordType,
3 3 GenModbusCommandType,
4 4 GetTaskListParamsType,
  5 + ImmediateExecuteTaskType,
5 6 TaskRecordType,
6 7 } from './model';
7 8 import { PaginationResult } from '/#/axios';
... ... @@ -16,6 +17,8 @@ enum Api {
16 17 CANCEL_TASK = '/task_center',
17 18
18 19 GEN_MODBUS_COMMAND = '/js/modbus',
  20 +
  21 + IMMEDIATE_EXECUTE = '/task_center/immediate/execute',
19 22 }
20 23
21 24 export const getTaskCenterList = (params: GetTaskListParamsType) => {
... ... @@ -76,3 +79,13 @@ export const genModbusCommand = (data: GenModbusCommandType) => {
76 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 62 registerNum?: number;
63 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 124 | 'TransferTableModal'
125 125 | 'ObjectModelValidateForm'
126 126 | 'DevicePicker'
  127 + | 'ProductPicker'
127 128 | 'PollCommandInput'
128 129 | 'RegisterAddressInput'
129 130 | 'ControlGroup';
... ...
... ... @@ -9,13 +9,13 @@ import {
9 9 import { PollCommandInput, ModeEnum } from '../PollCommandInput';
10 10 import { DeviceProfileModel } from '/@/api/device/model/deviceModel';
11 11 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
12   -import { getDeviceProfile } from '/@/api/alarm/position';
13 12 import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor';
14 13 import { TimeUnitEnum, TimeUnitNameEnum } from '/@/enums/toolEnum';
15   -import { createPickerSearch } from '/@/utils/pickerSearch';
16 14 import { dateUtil } from '/@/utils/dateUtil';
  15 +import { ProductPicker, validateProductPicker } from '../ProductPicker';
17 16
18 17 useComponentRegister('DevicePicker', DevicePicker);
  18 +useComponentRegister('ProductPicker', ProductPicker);
19 19 useComponentRegister('PollCommandInput', PollCommandInput);
20 20
21 21 export enum FormFieldsEnum {
... ... @@ -108,27 +108,26 @@ export const formSchemas: FormSchema[] = [
108 108 },
109 109 {
110 110 field: FormFieldsEnum.DEVICE_PROFILE,
111   - component: 'ApiSelect',
  111 + component: 'ProductPicker',
112 112 label: '产品',
113 113 ifShow: ({ model }) => model[FormFieldsEnum.TARGET_TYPE] === TaskTargetEnum.PRODUCTS,
  114 + valueField: 'value',
  115 + changeEvent: 'update:value',
  116 + rules: [validateProductPicker()],
  117 + helpMessage: ['任务可以对目标产品按预设的时间策略执行任务。'],
114 118 componentProps: ({ formActionType }) => {
115 119 const { setFieldsValue } = formActionType;
116 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 124 setFieldsValue({
125   - [FormFieldsEnum.TRANSPORT_TYPE]: isTCP ? PushWayEnum.TCP : PushWayEnum.MQTT,
  125 + [FormFieldsEnum.TRANSPORT_TYPE]: _value ? option.transportType : null,
126 126 [FormFieldsEnum.PUSH_WAY]: isTCP ? PushWayEnum.TCP : PushWayEnum.MQTT,
127 127 ...(isTCP ? {} : { [FormFieldsEnum.EXECUTE_CONTENT_TYPE]: TaskTypeEnum.CUSTOM }),
128 128 });
129 129 }
130 130 },
131   - ...createPickerSearch(),
132 131 getPopupContainer: () => document.body,
133 132 };
134 133 },
... ...
... ... @@ -5,9 +5,11 @@ import { CreateTaskRecordType, TaskRecordType } from '/@/api/task/model';
5 5 import { DeviceCascadePickerValueType } from '../DevicePicker';
6 6 import { TaskTargetEnum } from '../../config';
7 7 import { TimeUnitEnum } from '/@/enums/toolEnum';
  8 +import { ProductCascadePickerValueType } from '../ProductPicker';
8 9
9 10 export interface FormValueType extends Partial<Record<FormFieldsEnum, any>> {
10 11 [FormFieldsEnum.EXECUTE_TARGET_DATA]: DeviceCascadePickerValueType;
  12 + [FormFieldsEnum.DEVICE_PROFILE]: ProductCascadePickerValueType;
11 13 }
12 14
13 15 type CanWrite<T> = {
... ... @@ -95,6 +97,12 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy
95 97
96 98 const { organizationId, deviceType, deviceId, deviceProfileId } = executeTargetData || {};
97 99
  100 + const {
  101 + organizationId: productOrg,
  102 + deviceProfileId: productId,
  103 + deviceType: productDeviceType,
  104 + } = deviceProfile || {};
  105 +
98 106 const { expression } = genCronExpression(time, period);
99 107
100 108 const cron =
... ... @@ -111,10 +119,10 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy
111 119 type: executeContentType,
112 120 },
113 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 127 executeTime: {
120 128 type: executeTimeType,
... ... @@ -147,7 +155,14 @@ export const parseData = (result: TaskRecordType): Required<FormValueType> => {
147 155 deviceType,
148 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 166 executeTimeType,
152 167 period,
153 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 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 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 12 export enum FormFieldsEnum {
5   - RUN_TARGET_TYPE = 'runTargetType',
  13 + EXECUTE_TARGET_TYPE = 'executeTarget',
6 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 19 export enum TargetType {
... ... @@ -33,26 +40,13 @@ export const formSchemas: FormSchema[] = [
33 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 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 46 show: false,
53 47 },
54 48 {
55   - field: FormFieldsEnum.RUN_TARGET_TYPE,
  49 + field: FormFieldsEnum.EXECUTE_TARGET_TYPE,
56 50 component: 'RadioGroup',
57 51 label: '选择目标类型',
58 52 helpMessage: [
... ... @@ -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 65 component: 'ApiSelect',
72 66 label: '制定目标设备',
73   - ifShow: ({ model }) => model[FormFieldsEnum.RUN_TARGET_TYPE] === TargetType.ASSIGN,
  67 + ifShow: ({ model }) => model[FormFieldsEnum.EXECUTE_TARGET_TYPE] === TargetType.ASSIGN,
74 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 73 return {
78 74 api: async () => {
79 75 try {
80 76 if (isDevices) {
  77 + const result = await getDevicesByDeviceIds(data!);
  78 + return result.data;
81 79 } else {
  80 + return await getMeetTheConditionsDevice({
  81 + organizationId,
  82 + deviceProfileId: data![0],
  83 + });
82 84 }
83 85 } catch (error) {
84 86 return [];
85 87 }
86 88 },
  89 + mode: 'multiple',
  90 + labelField: 'name',
  91 + valueField: 'tbDeviceId',
  92 + ...createPickerSearch(),
  93 + placeholder: '请选择设备',
87 94 getPopupContainer: () => document.body,
88 95 };
89 96 },
... ...
1 1 <script lang="ts" setup>
2 2 import { ref } from 'vue';
3   - import { TaskRecordType } from '/@/api/task/model';
  3 + import { ImmediateExecuteTaskType, TaskRecordType } from '/@/api/task/model';
4 4 import { BasicForm, useForm } from '/@/components/Form';
5 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 15 defineEmits(['register']);
10 16
11 17 const dataSource = ref<TaskRecordType>();
12 18
13   - const [registerModal] = useModalInner((record: TaskRecordType) => {
  19 + const [registerModal, { closeModal }] = useModalInner((record: TaskRecordType) => {
  20 + resetFields();
14 21 dataSource.value = record;
15 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 28 schemas: formSchemas,
22 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 65 </script>
25 66
26 67 <template>
27   - <BasicModal @register="registerModal" title="运行任务">
  68 + <BasicModal
  69 + @register="registerModal"
  70 + title="运行任务"
  71 + :okButtonProps="{ loading }"
  72 + @ok="handleOk"
  73 + >
28 74 <BasicForm @register="registerForm">
29 75 <template #taskName>
30 76 <div class="font-semibold">
... ...
... ... @@ -100,7 +100,7 @@
100 100 <section
101 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 104 <Authority :value="PermissionEnum.CREATE">
105 105 <Button
106 106 type="primary"
... ... @@ -143,7 +143,7 @@
143 143 </List>
144 144 </section>
145 145 <DetailModal @register="registerModal" :reload="reload" />
146   - <RunTaskModal @register="registerRunTaskModal" />
  146 + <RunTaskModal :reload="reload" @register="registerRunTaskModal" />
147 147 </PageWrapper>
148 148 </template>
149 149
... ...