Commit 45d95317ee1ee1f32ecb0f297ce2b006d9e03d5c

Authored by ww
1 parent f58124e6

feat: 完成任务中心modbus指令生成&&设备详情任务是否允许任务执行

... ... @@ -14,9 +14,9 @@ VITE_GLOB_PUBLIC_PATH = /
14 14 # VITE_PROXY = [["/api","http://101.133.234.90:8080/api"]]
15 15 # 线上测试环境
16 16 # VITE_PROXY = [["/api","http://localhost:8080/api"],["/thingskit-drawio","http://localhost:3000/"]]
17   -VITE_PROXY = [["/api","http://222.180.200.114:48080/api"],["/thingskit-drawio","http://localhost:3000/"],["/large-designer", "http://localhost:5555/large-designer/"]]
  17 +# VITE_PROXY = [["/api","http://222.180.200.114:48080/api"],["/thingskit-drawio","http://localhost:3000/"],["/large-designer", "http://localhost:5555/large-designer/"]]
18 18 # VITE_PROXY = [["/api","http://121.37.251.8:8080/api"],["/thingskit-drawio","http://localhost:3000/"]]
19   -# VITE_PROXY = [["/api","http://192.168.10.125:8080/api"],["/thingskit-drawio","http://192.168.10.125:8080/api"]]
  19 +VITE_PROXY = [["/api","http://192.168.10.114:8080/api"],["/thingskit-drawio","http://192.168.10.125:8080/api"]]
20 20
21 21 # 实时数据的ws地址
22 22 # VITE_WEB_SOCKET = ws://localhost:8080/api/ws/plugins/telemetry?token=
... ...
1   -import { CreateTaskRecordType, GetTaskListParamsType, TaskRecordType } from './model';
  1 +import {
  2 + CreateTaskRecordType,
  3 + GenModbusCommandType,
  4 + GetTaskListParamsType,
  5 + TaskRecordType,
  6 +} from './model';
2 7 import { PaginationResult } from '/#/axios';
3 8 import { defHttp } from '/@/utils/http/axios';
4 9
... ... @@ -9,6 +14,8 @@ enum Api {
9 14 DELETE_TASK = '/task_center',
10 15 UPDATE_TASK = '/task_center/update',
11 16 CANCEL_TASK = '/task_center',
  17 +
  18 + GEN_MODBUS_COMMAND = '/js/modbus',
12 19 }
13 20
14 21 export const getTaskCenterList = (params: GetTaskListParamsType) => {
... ... @@ -45,8 +52,27 @@ export const updateTask = (data: CreateTaskRecordType & Record<'id', string>) =>
45 52 });
46 53 };
47 54
48   -export const cancelTask = (data: Record<'id' | 'tbDeviceId', string>) => {
  55 +/**
  56 + * @description 取消任务
  57 + * @param data
  58 + * @returns
  59 + */
  60 +export const cancelTask = (
  61 + data: Record<'id' | 'tbDeviceId', string> & Record<'allow', boolean>
  62 +) => {
49 63 return defHttp.put({
50   - url: `${Api.CANCEL_TASK}/${data.id}/cancel/${data.tbDeviceId}`,
  64 + url: `${Api.CANCEL_TASK}/${data.id}/update/${data.tbDeviceId}/${data.allow}`,
  65 + });
  66 +};
  67 +
  68 +/**
  69 + * @description 生成modbus指令
  70 + * @param data
  71 + * @returns {string}
  72 + */
  73 +export const genModbusCommand = (data: GenModbusCommandType) => {
  74 + return defHttp.post<string>({
  75 + url: Api.GEN_MODBUS_COMMAND,
  76 + data,
51 77 });
52 78 };
... ...
... ... @@ -3,13 +3,14 @@ import {
3 3 ExecuteTimeTypeEnum,
4 4 PeriodTypeEnum,
5 5 PushWayEnum,
6   -} from '/@/views/task/center/components/CreateModal/config';
  6 +} from '/@/views/task/center/components/DetailModal/config';
7 7 import { TaskTypeEnum } from '/@/views/task/center/config';
8 8
9 9 export interface GetTaskListParamsType {
10 10 page: number;
11 11 pageSize: number;
12 12 state?: string;
  13 + tbDeviceId?: string;
13 14 }
14 15
15 16 export interface CreateTaskRecordType {
... ... @@ -45,4 +46,19 @@ export interface TaskRecordType extends CreateTaskRecordType {
45 46 creator: string;
46 47 enabled: boolean;
47 48 state: number;
  49 + lastExecuteTime?: number;
  50 + tkDeviceTaskCenter?: {
  51 + allowState: number;
  52 + taskCenterId: string;
  53 + tbDeviceId: string;
  54 + };
  55 +}
  56 +
  57 +export interface GenModbusCommandType {
  58 + crc: string;
  59 + deviceCode: string;
  60 + method: string;
  61 + registerAddr: string;
  62 + registerNum?: number;
  63 + registerValues?: number[];
48 64 }
... ...
... ... @@ -6,7 +6,6 @@
6 6 import { onMounted } from 'vue';
7 7 import { computed } from '@vue/reactivity';
8 8 import { onUnmounted } from 'vue';
9   - import { watch } from 'vue';
10 9
11 10 enum EventEnum {
12 11 UPDATE_VALUE = 'update:value',
... ... @@ -64,15 +63,15 @@
64 63 editoreRef.value = new JSONEditor(unref(jsonEditorElRef), unref(getOptions));
65 64 };
66 65
67   - watch(
68   - () => props.value,
69   - (target) => {
70   - unref(editoreRef)?.setText(target || '');
71   - },
72   - {
73   - immediate: true,
74   - }
75   - );
  66 + // watch(
  67 + // () => props.value,
  68 + // (target) => {
  69 + // unref(editoreRef)?.setText(target || '');
  70 + // },
  71 + // {
  72 + // immediate: true,
  73 + // }
  74 + // );
76 75
77 76 const get = (): string => {
78 77 return unref(editoreRef)?.getText() || '';
... ... @@ -84,6 +83,7 @@
84 83
85 84 onMounted(() => {
86 85 initialize();
  86 + unref(editoreRef)?.setText(props.value || '');
87 87 });
88 88
89 89 onUnmounted(() => {
... ...
... ... @@ -11,7 +11,7 @@
11 11 import { getBoundingClientRect } from '/@/utils/domUtils';
12 12 import { TaskRecordType } from '/@/api/task/model';
13 13
14   - defineProps<{
  14 + const props = defineProps<{
15 15 tbDeviceId: string;
16 16 }>();
17 17
... ... @@ -48,6 +48,7 @@
48 48 const { items } = await getTaskCenterList({
49 49 page: pagination.current,
50 50 pageSize: pagination.pageSize,
  51 + tbDeviceId: props.tbDeviceId,
51 52 ...pagination.params,
52 53 });
53 54 dataSource.value = items;
... ...
... ... @@ -13,6 +13,7 @@ import { getDeviceProfile } from '/@/api/alarm/position';
13 13 import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor';
14 14 import { TimeUnitEnum, TimeUnitNameEnum } from '/@/enums/toolEnum';
15 15 import { createPickerSearch } from '/@/utils/pickerSearch';
  16 +import { dateUtil } from '/@/utils/dateUtil';
16 17
17 18 useComponentRegister('DevicePicker', DevicePicker);
18 19 useComponentRegister('PollCommandInput', PollCommandInput);
... ... @@ -64,9 +65,9 @@ export enum ExecuteTimeTypeNameEnum {
64 65 }
65 66
66 67 export enum PeriodTypeEnum {
67   - MONTH,
68   - WEEK,
69   - DAY,
  68 + MONTH = 'MONTH',
  69 + WEEK = 'WEEK',
  70 + DAY = 'DAY',
70 71 }
71 72
72 73 export enum PeriodTypeNameEnum {
... ... @@ -197,7 +198,7 @@ export const formSchemas: FormSchema[] = [
197 198 field: FormFieldsEnum.PUSH_WAY,
198 199 component: 'RadioGroup',
199 200 label: '推送方式',
200   - defaultValue: PushWayEnum.MQTT,
  201 + defaultValue: PushWayEnum.TCP,
201 202 show: false,
202 203 componentProps: {
203 204 options: [
... ... @@ -317,6 +318,7 @@ export const formSchemas: FormSchema[] = [
317 318 componentProps: {
318 319 getPopupContainer: () => document.body,
319 320 valueFormat: 'HH:mm:ss',
  321 + defaultOpenValue: dateUtil('00:00:00', 'HH:mm:ss'),
320 322 },
321 323 },
322 324 {
... ...
... ... @@ -31,7 +31,6 @@
31 31 resetFields();
32 32 if (record && mode === DataActionModeEnum.UPDATE) {
33 33 const res = parseData(record);
34   - console.log({ record, res });
35 34 setFieldsValue({ ...res });
36 35 }
37 36 }
... ...
... ... @@ -6,8 +6,7 @@ import { DeviceCascadePickerValueType } from '../DevicePicker';
6 6 import { TaskTargetEnum } from '../../config';
7 7 import { TimeUnitEnum } from '/@/enums/toolEnum';
8 8
9   -export interface FormValueType
10   - extends Partial<Record<Exclude<FormFieldsEnum, FormFieldsEnum.TRANSPORT_TYPE>, any>> {
  9 +export interface FormValueType extends Partial<Record<FormFieldsEnum, any>> {
11 10 [FormFieldsEnum.EXECUTE_TARGET_DATA]: DeviceCascadePickerValueType;
12 11 }
13 12
... ... @@ -106,7 +105,7 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy
106 105 targetType,
107 106 executeContent: {
108 107 pushContent: {
109   - rpcCommand: pushWay === PushWayEnum.MQTT ? JSON.parse(rpcCommand) : pushWay,
  108 + rpcCommand: pushWay === PushWayEnum.MQTT ? JSON.parse(rpcCommand) : rpcCommand,
110 109 },
111 110 pushWay,
112 111 type: executeContentType,
... ... @@ -138,7 +137,8 @@ export const parseData = (result: TaskRecordType): Required<FormValueType> => {
138 137 return {
139 138 name,
140 139 targetType,
141   - rpcCommand: pushWay === PushWayEnum.MQTT ? JSON.stringify(rpcCommand) : rpcCommand,
  140 + rpcCommand: pushWay === PushWayEnum.MQTT ? JSON.stringify(rpcCommand, null, 2) : rpcCommand,
  141 + transportType: pushWay,
142 142 pushWay,
143 143 executeContentType,
144 144 executeTargetData: {
... ...
src/views/task/center/components/PollCommandInput/GenModbusCommandModal.vue renamed from src/views/task/center/components/PollCommandInput/CreateTCPCommandModal.vue
1 1 <script lang="ts" setup>
2   - import { Button } from 'ant-design-vue';
  2 + import { Button, Spin } from 'ant-design-vue';
3 3 import { BasicForm, useForm } from '/@/components/Form';
4 4 import { BasicModal, useModal } from '/@/components/Modal';
5 5 import { formSchemas } from './config';
6 6 import { ref } from 'vue';
7 7 import { unref } from 'vue';
  8 + import { composeModbusModalData } from './util';
  9 + import { ModbusCommandValueType } from './type';
  10 + import { genModbusCommand } from '/@/api/task';
8 11
9 12 const emit = defineEmits(['update:value']);
10 13
... ... @@ -19,9 +22,29 @@
19 22
20 23 const commandValue = ref('');
21 24
  25 + const loading = ref(false);
22 26 const handleGetValue = async () => {
23   - const value = getFieldsValue();
24   - console.log(value);
  27 + try {
  28 + const value = getFieldsValue();
  29 + const record = composeModbusModalData(value as ModbusCommandValueType);
  30 + loading.value = true;
  31 + const result = await genModbusCommand(record);
  32 + console.log(result);
  33 + commandValue.value = result;
  34 + } catch (error) {
  35 + } finally {
  36 + loading.value = false;
  37 + }
  38 + };
  39 +
  40 + const formatCommand = (command: string, subNumber: number) => {
  41 + const list = command.split('');
  42 + let index = 0;
  43 + const arr: string[][] = [];
  44 + while (index <= list.length) {
  45 + arr.push(list.slice(index, (index += subNumber)));
  46 + }
  47 + return arr.reduce((prev, next) => `${prev} ${next.join('')}`, '');
25 48 };
26 49
27 50 const handleOk = () => {
... ... @@ -30,13 +53,22 @@
30 53 </script>
31 54
32 55 <template>
33   - <BasicModal @register="registerModal" title="配置操作" @ok="handleOk">
  56 + <BasicModal
  57 + @register="registerModal"
  58 + :okButtonProps="{ loading }"
  59 + title="配置操作"
  60 + @ok="handleOk"
  61 + >
34 62 <BasicForm @register="registerForm" class="create-tcp-command-form" />
35 63 <section>
36 64 <Button @click="handleGetValue" type="link" class="!px-0">生成预览</Button>
37   - <div v-if="commandValue">
  65 + <div>
38 66 <div class="text-gray-400">Modbus 指令预览</div>
39   - <code class="bg-dark-50 text-light-50 p-1 w-full block mt-1">{{ commandValue }}</code>
  67 + <Spin :spinning="loading" size="small">
  68 + <div class="bg-dark-50 text-light-50 p-1 w-full block mt-1 min-h-8">{{
  69 + formatCommand(commandValue, 2)
  70 + }}</div>
  71 + </Spin>
40 72 </div>
41 73 </section>
42 74 </BasicModal>
... ...
1 1 <script lang="ts" setup>
2 2 import { InputGroup, InputNumber, Select, Input } from 'ant-design-vue';
3   - import { watch } from 'vue';
4 3 import { unref } from 'vue';
5 4 import { computed } from 'vue';
6 5 import { ref } from 'vue';
... ... @@ -14,13 +13,13 @@
14 13
15 14 const DEC_MAX_VALUE = parseInt('0xffff', 16);
16 15
17   - const props = withDefaults(
  16 + withDefaults(
18 17 defineProps<{
19   - value?: string;
  18 + value?: number | string;
20 19 inputProps?: Recordable;
21 20 }>(),
22 21 {
23   - value: '0',
  22 + value: 0,
24 23 inputProps: () => ({}),
25 24 }
26 25 );
... ... @@ -44,23 +43,24 @@
44 43 return (inputValue.value as number) > DEC_MAX_VALUE ? '0x0000' : formatValue;
45 44 });
46 45
  46 + const toDEC = (value: number | string) => {
  47 + return unref(type) === AddressTypeEnum.DEC
  48 + ? isNaN(value as number)
  49 + ? 0
  50 + : Number(value)
  51 + : parseInt(value, 16);
  52 + };
  53 +
47 54 const handleEmit = () => {
48   - const syncValue = unref(type) === AddressTypeEnum.DEC ? unref(getDecValue) : unref(getHexValue);
  55 + const syncValue = toDEC(unref(inputValue));
49 56 emit('update:value', syncValue);
50 57 };
51 58
52 59 const handleChange = (value: AddressTypeEnum) => {
53 60 const syncValue = value === AddressTypeEnum.DEC ? unref(getHexValue) : unref(getDecValue);
54 61 inputValue.value = syncValue;
55   - emit('update:value', syncValue);
  62 + emit('update:value', toDEC(syncValue));
56 63 };
57   -
58   - watch(
59   - () => props.value,
60   - (targetValue) => {
61   - inputValue.value = targetValue || 0;
62   - }
63   - );
64 64 </script>
65 65
66 66 <template>
... ... @@ -76,10 +76,10 @@
76 76 v-model:value="inputValue"
77 77 :step="1"
78 78 class="flex-1"
79   - @change="handleEmit"
80 79 v-bind="inputProps"
  80 + @change="handleEmit"
81 81 />
82   - <Input v-if="type === AddressTypeEnum.HEX" v-model:value="inputValue" />
  82 + <Input v-if="type === AddressTypeEnum.HEX" v-model:value="inputValue" @change="handleEmit" />
83 83 <div class="text-center h-8 leading-8 px-2 bg-gray-200 cursor-pointer rounded-1 w-20">
84 84 <div v-if="type === AddressTypeEnum.DEC">{{ getDecValue }}</div>
85 85 <div v-if="type === AddressTypeEnum.HEX">{{ getHexValue }}</div>
... ...
... ... @@ -6,10 +6,14 @@ import { createPickerSearch } from '/@/utils/pickerSearch';
6 6 import { ControlGroup } from '../ControlGroup';
7 7
8 8 export enum FormFieldsEnum {
9   - ADDRESS = 'address',
10   - FUNCTION_CODE = 'functionCode',
11   - START_REGISTER_ADDRESS = 'startRegisterAddress',
12   - DATA_VALID = 'dataValid',
  9 + // 设备地址码
  10 + DEVICE_CODE = 'deviceCode',
  11 + // 功能码
  12 + METHOD = 'method',
  13 + // 寄存器地址
  14 + REGISTER_ADDR = 'registerAddr',
  15 + // 数据校验算法
  16 + CRC = 'crc',
13 17 // 线圈个数
14 18 COIL_NUMBER = 'coilNumber',
15 19 // 寄存器个数
... ... @@ -27,7 +31,7 @@ export enum FormFieldsEnum {
27 31 useComponentRegister('RegisterAddressInput', RegisterAddressInput);
28 32 useComponentRegister('ControlGroup', ControlGroup);
29 33
30   -enum FunctionCodeEnum {
  34 +export enum FunctionCodeEnum {
31 35 // 读取线圈状态01
32 36 READ_COIL_STATE_01 = '01',
33 37 // 读取输入状态02
... ... @@ -46,35 +50,45 @@ enum FunctionCodeEnum {
46 50 WRITE_MULTIPLE_KEEP_REGISTER_16 = '16',
47 51 }
48 52
49   -const showCoilNumber = (value: FunctionCodeEnum) =>
  53 +export enum CRCValidTypeEnum {
  54 + CRC_32_HIGH = 'CRC_32_HIGH',
  55 + CRC_32_LOWER = 'CRC_32_LOWER',
  56 + AND_TOTAL_XOR = 'AND_TOTAL_XOR',
  57 + TOTAL_AND_SUM = 'TOTAL_AND_SUM',
  58 + CRC_8 = 'CRC_8',
  59 + CRC_16_HIGH = 'CRC_16_HIGH',
  60 + CRC_16_LOWER = 'CRC_16_LOWER',
  61 +}
  62 +
  63 +export const showCoilNumber = (value: FunctionCodeEnum) =>
50 64 [
51 65 FunctionCodeEnum.READ_COIL_STATE_01,
52 66 FunctionCodeEnum.READ_INPUT_STATE_02,
53 67 FunctionCodeEnum.WRITE_MULTIPLE_COIL_STATE_15,
54 68 ].includes(value);
55 69
56   -const showRegisterNumber = (value: FunctionCodeEnum) =>
  70 +export const showRegisterNumber = (value: FunctionCodeEnum) =>
57 71 [
58 72 FunctionCodeEnum.READ_KEEP_REGISTER_03,
59 73 FunctionCodeEnum.READ_INPUT_REGISTER_04,
60 74 FunctionCodeEnum.WRITE_MULTIPLE_KEEP_REGISTER_16,
61 75 ].includes(value);
62 76
63   -const showCoilValue = (value: FunctionCodeEnum) =>
  77 +export const showCoilValue = (value: FunctionCodeEnum) =>
64 78 [FunctionCodeEnum.WRITE_SINGLE_COIL_REGISTER_05].includes(value);
65 79
66   -const showRegisterValue = (value: FunctionCodeEnum) =>
  80 +export const showRegisterValue = (value: FunctionCodeEnum) =>
67 81 [FunctionCodeEnum.WRITE_SINGLE_KEEP_COIL_REGISTER_06].includes(value);
68 82
69   -const isWriteCoilGroup = (value: FunctionCodeEnum) =>
  83 +export const isWriteCoilGroup = (value: FunctionCodeEnum) =>
70 84 [FunctionCodeEnum.WRITE_MULTIPLE_COIL_STATE_15].includes(value);
71 85
72   -const isWriteRegisterGroup = (value: FunctionCodeEnum) =>
  86 +export const isWriteRegisterGroup = (value: FunctionCodeEnum) =>
73 87 [FunctionCodeEnum.WRITE_MULTIPLE_KEEP_REGISTER_16].includes(value);
74 88
75 89 export const formSchemas: FormSchema[] = [
76 90 {
77   - field: FormFieldsEnum.ADDRESS,
  91 + field: FormFieldsEnum.DEVICE_CODE,
78 92 component: 'ApiSelect',
79 93 label: '从机地址',
80 94 rules: [{ required: true, message: '请选择从机地址' }],
... ... @@ -103,7 +117,7 @@ export const formSchemas: FormSchema[] = [
103 117 },
104 118 },
105 119 {
106   - field: FormFieldsEnum.FUNCTION_CODE,
  120 + field: FormFieldsEnum.METHOD,
107 121 component: 'ApiSelect',
108 122 label: '功能码',
109 123 defaultValue: '01',
... ... @@ -121,18 +135,18 @@ export const formSchemas: FormSchema[] = [
121 135 },
122 136 },
123 137 {
124   - field: FormFieldsEnum.START_REGISTER_ADDRESS,
  138 + field: FormFieldsEnum.REGISTER_ADDR,
125 139 label: '起始寄存器地址',
126 140 component: 'RegisterAddressInput',
127 141 valueField: 'value',
128 142 changeEvent: 'update:value',
129   - defaultValue: '0',
  143 + defaultValue: 0,
130 144 },
131 145 {
132 146 field: FormFieldsEnum.REGISTER_NUMBER,
133 147 label: '寄存器个数',
134 148 component: 'InputNumber',
135   - ifShow: ({ model }) => showRegisterNumber(model[FormFieldsEnum.FUNCTION_CODE]),
  149 + ifShow: ({ model }) => showRegisterNumber(model[FormFieldsEnum.METHOD]),
136 150 defaultValue: 1,
137 151 rules: [{ required: true, message: '请输入寄存器个数' }],
138 152 componentProps: {
... ... @@ -146,7 +160,7 @@ export const formSchemas: FormSchema[] = [
146 160 field: FormFieldsEnum.COIL_NUMBER,
147 161 label: '线圈个数',
148 162 component: 'InputNumber',
149   - ifShow: ({ model }) => showCoilNumber(model[FormFieldsEnum.FUNCTION_CODE]),
  163 + ifShow: ({ model }) => showCoilNumber(model[FormFieldsEnum.METHOD]),
150 164 defaultValue: 1,
151 165 rules: [{ required: true, message: '请输入线圈个数' }],
152 166 componentProps: {
... ... @@ -162,7 +176,7 @@ export const formSchemas: FormSchema[] = [
162 176 component: 'RegisterAddressInput',
163 177 valueField: 'value',
164 178 changeEvent: 'update:value',
165   - ifShow: ({ model }) => showCoilValue(model[FormFieldsEnum.FUNCTION_CODE]),
  179 + ifShow: ({ model }) => showCoilValue(model[FormFieldsEnum.METHOD]),
166 180 defaultValue: '0',
167 181 rules: [{ required: true, message: '请输入线圈值' }],
168 182 componentProps: {
... ... @@ -175,7 +189,7 @@ export const formSchemas: FormSchema[] = [
175 189 component: 'RegisterAddressInput',
176 190 valueField: 'value',
177 191 changeEvent: 'update:value',
178   - ifShow: ({ model }) => showRegisterValue(model[FormFieldsEnum.FUNCTION_CODE]),
  192 + ifShow: ({ model }) => showRegisterValue(model[FormFieldsEnum.METHOD]),
179 193 defaultValue: '0',
180 194 rules: [{ required: true, message: '请输入寄存器值' }],
181 195 componentProps: {
... ... @@ -189,7 +203,7 @@ export const formSchemas: FormSchema[] = [
189 203 colProps: { span: 24 },
190 204 valueField: 'value',
191 205 changeEvent: 'update:value',
192   - ifShow: ({ model }) => isWriteRegisterGroup(model[FormFieldsEnum.FUNCTION_CODE]),
  206 + ifShow: ({ model }) => isWriteRegisterGroup(model[FormFieldsEnum.METHOD]),
193 207 componentProps: ({ formModel }) => {
194 208 const length = formModel[FormFieldsEnum.REGISTER_NUMBER];
195 209 return {
... ... @@ -213,7 +227,7 @@ export const formSchemas: FormSchema[] = [
213 227 colProps: { span: 24 },
214 228 valueField: 'value',
215 229 changeEvent: 'update:value',
216   - ifShow: ({ model }) => isWriteCoilGroup(model[FormFieldsEnum.FUNCTION_CODE]),
  230 + ifShow: ({ model }) => isWriteCoilGroup(model[FormFieldsEnum.METHOD]),
217 231 componentProps: ({ formModel }) => {
218 232 const length = formModel[FormFieldsEnum.COIL_NUMBER];
219 233 return {
... ... @@ -235,11 +249,12 @@ export const formSchemas: FormSchema[] = [
235 249 },
236 250 },
237 251 {
238   - field: FormFieldsEnum.DATA_VALID,
  252 + field: FormFieldsEnum.CRC,
239 253 label: '数据校验',
240 254 component: 'ApiSelect',
241 255 colProps: { span: 24 },
242 256 rules: [{ required: true, message: '请选择数据校验方式' }],
  257 + defaultValue: CRCValidTypeEnum.CRC_16_LOWER,
243 258 componentProps: () => {
244 259 return {
245 260 api: findDictItemByCode,
... ...
... ... @@ -4,11 +4,10 @@
4 4 import { SettingOutlined } from '@ant-design/icons-vue';
5 5 import { ModeEnum } from './index';
6 6 import { computed } from '@vue/reactivity';
7   - import CreateTCPCommandModal from './CreateTCPCommandModal.vue';
  7 + import GenModbusCommandModal from './GenModbusCommandModal.vue';
8 8 import { useModal } from '/@/components/Modal';
9 9 import { ref } from 'vue';
10 10 import { unref } from 'vue';
11   - import JSONEditorType from 'jsoneditor';
12 11
13 12 const props = withDefaults(
14 13 defineProps<{
... ... @@ -54,8 +53,7 @@
54 53 }
55 54 };
56 55
57   - const handleEditorBlur = (_event: Event, instance: JSONEditorType) => {
58   - const value = instance.getText();
  56 + const handleUpdateEditorValue = (value: string) => {
59 57 handleEmit(value);
60 58 };
61 59 </script>
... ... @@ -74,7 +72,11 @@
74 72 <SettingOutlined class="cursor-pointer" @click="handleClick" />
75 73 </template>
76 74 </Input>
77   - <JSONEditor v-if="mode === ModeEnum.JSON" :value="getJSONValue" @blur="handleEditorBlur" />
78   - <CreateTCPCommandModal @register="registerCreateTCPCommandModal" @update:value="handleEmit" />
  75 + <JSONEditor
  76 + v-if="mode === ModeEnum.JSON"
  77 + :value="getJSONValue"
  78 + @update:value="handleUpdateEditorValue"
  79 + />
  80 + <GenModbusCommandModal @register="registerCreateTCPCommandModal" @update:value="handleEmit" />
79 81 </section>
80 82 </template>
... ...
  1 +import { FormFieldsEnum } from './config';
  2 +
  3 +type ValuesItem = { value: any };
  4 +
  5 +export interface ModbusCommandValueType extends Record<FormFieldsEnum, any> {
  6 + [FormFieldsEnum.REGISTER_VALUES]: ValuesItem[];
  7 + [FormFieldsEnum.COIL_VALUES]: ValuesItem[];
  8 +}
... ...
  1 +import { FunctionCodeEnum } from './config';
  2 +import { ModbusCommandValueType } from './type';
  3 +import { GenModbusCommandType } from '/@/api/task/model';
  4 +
  5 +const registerInfo = (record: ModbusCommandValueType): Partial<GenModbusCommandType> => {
  6 + const {
  7 + method,
  8 + registerNumber,
  9 + registerValue,
  10 + registerValues,
  11 + coilNumber,
  12 + coilValue,
  13 + coilValues,
  14 + } = record;
  15 + switch (method) {
  16 + case FunctionCodeEnum.READ_COIL_STATE_01 || FunctionCodeEnum.READ_INPUT_STATE_02:
  17 + return { registerNum: coilNumber };
  18 + case FunctionCodeEnum.READ_KEEP_REGISTER_03 || FunctionCodeEnum.READ_INPUT_REGISTER_04:
  19 + return { registerNum: registerNumber };
  20 + case FunctionCodeEnum.WRITE_SINGLE_COIL_REGISTER_05:
  21 + return { registerValues: [coilValue] };
  22 + case FunctionCodeEnum.WRITE_SINGLE_KEEP_COIL_REGISTER_06:
  23 + return { registerValues: [registerValue] };
  24 + case FunctionCodeEnum.WRITE_MULTIPLE_COIL_STATE_15:
  25 + return {
  26 + registerNum: coilNumber,
  27 + registerValues: coilValues.map((item) => (item.value ? 1 : 0)),
  28 + };
  29 + case FunctionCodeEnum.WRITE_MULTIPLE_KEEP_REGISTER_16:
  30 + return {
  31 + registerNum: registerNumber,
  32 + registerValues: registerValues.map((item) => item.value),
  33 + };
  34 + default:
  35 + return {};
  36 + }
  37 +};
  38 +
  39 +export const composeModbusModalData = (record: ModbusCommandValueType): GenModbusCommandType => {
  40 + const { crc, deviceCode, method, registerAddr } = record;
  41 + return {
  42 + crc,
  43 + deviceCode,
  44 + method,
  45 + registerAddr,
  46 + ...registerInfo(record),
  47 + };
  48 +};
... ...
... ... @@ -14,6 +14,8 @@
14 14 import { computed } from '@vue/reactivity';
15 15 import { unref } from 'vue';
16 16 import { ref } from 'vue';
  17 + import { dateUtil } from '/@/utils/dateUtil';
  18 + import { DEFAULT_DATE_FORMAT } from '/@/views/visual/board/detail/config/util';
17 19
18 20 enum DropMenuEvent {
19 21 DELETE = 'DELETE',
... ... @@ -43,9 +45,9 @@
43 45 });
44 46
45 47 const getCancelState = computed(() => {
46   - const { executeTarget } = unref(getRecord);
47   - const { cancelExecuteDevices } = executeTarget;
48   - return !!(cancelExecuteDevices && cancelExecuteDevices.length);
  48 + return !!(
  49 + unref(getRecord).tkDeviceTaskCenter && unref(getRecord).tkDeviceTaskCenter?.allowState
  50 + );
49 51 });
50 52
51 53 const handleDelete = async () => {
... ... @@ -76,7 +78,11 @@
76 78 try {
77 79 if (!props.tbDeviceId) return;
78 80 loading.value = true;
79   - await cancelTask({ id: unref(getRecord).id, tbDeviceId: props.tbDeviceId });
  81 + await cancelTask({
  82 + id: unref(getRecord).id,
  83 + tbDeviceId: props.tbDeviceId,
  84 + allow: !unref(getCancelState),
  85 + });
80 86 createMessage.success('设置成功');
81 87 props.reload?.();
82 88 } catch (error) {
... ... @@ -85,6 +91,31 @@
85 91 loading.value = false;
86 92 }
87 93 };
  94 +
  95 + const getTwoDateDiff = (date: number, now = dateUtil()) => {
  96 + const unitList = [
  97 + { radix: null, unitName: '年', unit: 'year' },
  98 + { radix: 11, unitName: '月', unit: 'month' },
  99 + { radix: 30, unitName: '日', unit: 'day' },
  100 + { radix: 23, unitName: '时', unit: 'hour' },
  101 + { radix: 59, unitName: '分', unit: 'minute' },
  102 + { radix: 59, unitName: '秒', unit: 'second' },
  103 + ];
  104 +
  105 + for (let i = unitList.length - 1; i >= 0; i--) {
  106 + const { unit, radix, unitName } = unitList[i];
  107 + const lastDate = dateUtil(date);
  108 + const diffValue = now.diff(lastDate, unit as any);
  109 + if (!radix || diffValue <= radix) {
  110 + return { value: diffValue, unit, unitName };
  111 + }
  112 + }
  113 + };
  114 +
  115 + const getLastExecuteTime = computed(() => {
  116 + if (!unref(getRecord).lastExecuteTime) return;
  117 + return getTwoDateDiff(unref(getRecord).lastExecuteTime!);
  118 + });
88 119 </script>
89 120
90 121 <template>
... ... @@ -155,10 +186,15 @@
155 186 <span>运行任务</span>
156 187 </div>
157 188 </Button>
158   - <Tooltip title="最后运行时间:">
  189 + <Tooltip
  190 + v-if="getLastExecuteTime"
  191 + overlay-class-name="task-last-execute-time-tooltip"
  192 + placement="topLeft"
  193 + :title="`最后运行时间: ${dateUtil(getRecord.lastExecuteTime).format(DEFAULT_DATE_FORMAT)}`"
  194 + >
159 195 <div class="text-gray-400 text-xs truncate">
160 196 <span class="mr-2">间隔时间重复</span>
161   - <span>三分钟前</span>
  197 + <span>{{ `${getLastExecuteTime.value}${getLastExecuteTime.unitName}前` }}</span>
162 198 </div>
163 199 </Tooltip>
164 200 </div>
... ... @@ -184,3 +220,9 @@
184 220 </section>
185 221 </Card>
186 222 </template>
  223 +
  224 +<style>
  225 + .task-last-execute-time-tooltip {
  226 + max-width: 300px;
  227 + }
  228 +</style>
... ...
... ... @@ -52,19 +52,6 @@ export const formSchemas: FormSchema[] = [
52 52 getPopupContainer: () => document.body,
53 53 },
54 54 },
55   - // {
56   - // label: '',
57   - // component: 'Select',
58   - // field: FormFieldsEnum.TASK_TYPE,
59   - // defaultValue: TaskTypeEnum.ALL,
60   - // componentProps: {
61   - // options: [
62   - // { label: TaskTypeNameEnum.CUSTOM, value: TaskTypeEnum.CUSTOM },
63   - // { label: TaskTypeNameEnum.MODBUS_RTU, value: TaskTypeEnum.MODBUS_RTU },
64   - // ],
65   - // getPopupContainer: () => document.body,
66   - // },
67   - // },
68 55 {
69 56 label: '状态',
70 57 component: 'Select',
... ...
... ... @@ -5,7 +5,7 @@
5 5 import { formSchemas } from './config';
6 6 import { TaskCard } from './components/TaskCard';
7 7 import { getTaskCenterList } from '/@/api/task';
8   - import { ref } from 'vue';
  8 + import { ref, unref } from 'vue';
9 9 import { onMounted } from 'vue';
10 10 import { DetailModal } from './components/DetailModal';
11 11 import { useModal } from '/@/components/Modal';
... ... @@ -14,6 +14,8 @@
14 14 import { ReloadOutlined } from '@ant-design/icons-vue';
15 15 import { DataActionModeEnum } from '/@/enums/toolEnum';
16 16 import { ModalParamsType } from '/#/utils';
  17 + import { Authority } from '/@/components/Authority';
  18 + import { getBoundingClientRect } from '/@/utils/domUtils';
17 19
18 20 const [registerModal, { openModal }] = useModal();
19 21
... ... @@ -61,7 +63,28 @@
61 63
62 64 const reload = () => getDataSource();
63 65
  66 + const listElRef = ref<Nullable<ComponentElRef>>(null);
  67 +
  68 + const setListHeight = () => {
  69 + const clientHeight = document.documentElement.clientHeight;
  70 + const rect = getBoundingClientRect(unref(listElRef)!.$el!) as DOMRect;
  71 + // margin-top 24 height 24
  72 + const paginationHeight = 24 + 24 + 8;
  73 + // list pading top 8 maring-top 8 extra slot 56
  74 + const listContainerMarginBottom = 8 + 8 + 56;
  75 + const listContainerHeight =
  76 + clientHeight - rect.top - paginationHeight - listContainerMarginBottom;
  77 + const listContainerEl = (unref(listElRef)!.$el as HTMLElement).querySelector(
  78 + '.ant-spin-container'
  79 + ) as HTMLElement;
  80 + listContainerEl &&
  81 + (listContainerEl.style.height = listContainerHeight + 'px') &&
  82 + (listContainerEl.style.overflowY = 'auto') &&
  83 + (listContainerEl.style.overflowX = 'hidden');
  84 + };
  85 +
64 86 onMounted(() => {
  87 + setListHeight();
65 88 getDataSource();
66 89 });
67 90 </script>
... ... @@ -72,11 +95,13 @@
72 95 class="bg-light-50 flex p-4 justify-between items-center x dark:text-gray-300 dark:bg-dark-900"
73 96 >
74 97 <div class="text-2xl">任务</div>
75   - <Button
76   - type="primary"
77   - @click="openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType)"
78   - >创建任务</Button
79   - >
  98 + <Authority>
  99 + <Button
  100 + type="primary"
  101 + @click="openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType)"
  102 + >创建任务
  103 + </Button>
  104 + </Authority>
80 105 </section>
81 106 <section
82 107 class="form-container bg-light-50 px-4 pt-4 mt-4 x dark:text-gray-300 dark:bg-dark-900"
... ... @@ -85,6 +110,7 @@
85 110 </section>
86 111 <section class="bg-light-50 mt-4 p-4 x dark:text-gray-300 dark:bg-dark-900">
87 112 <List
  113 + ref="listElRef"
88 114 :dataSource="dataSource"
89 115 :pagination="pagination"
90 116 :grid="{ gutter: 16, xs: 1, sm: 1, md: 2, lg: 3, xl: 3, xxl: 4, column: 4 }"
... ... @@ -92,7 +118,6 @@
92 118 >
93 119 <template #header>
94 120 <section class="flex justify-end gap-4">
95   - <!-- <CardLayoutButton /> -->
96 121 <Tooltip title="刷新">
97 122 <Button type="primary" @click="getDataSource">
98 123 <ReloadOutlined :spin="loading" />
... ...