Commit be4155d1b60f8c27a60318a3d9f85b4caa73fdd7

Authored by xp.Huang
2 parents c5077dc9 07ef1fd2

Merge branch 'ft' into 'main_dev'

fix: 修改Teambition上的问题

See merge request yunteng/thingskit-front!542
... ... @@ -10,6 +10,8 @@ export type SchedueParam = {
10 10 data?: any;
11 11 code?: number;
12 12 jobId?: string;
  13 + startTime?: number;
  14 + endTime?: number;
13 15 };
14 16
15 17 export interface ReportModel {
... ...
... ... @@ -18,6 +18,7 @@
18 18 placeholder="请选择"
19 19 :options="selectOptions"
20 20 @change="handleChange"
  21 + @dropdownVisibleChange="hanldeDropdownVisibleChange"
21 22 allowClear
22 23 />
23 24 </td>
... ... @@ -111,16 +112,20 @@
111 112
112 113 // 减少
113 114 const remove = (item, index: number) => {
114   - if (tableArray.content.length !== 1) {
  115 + if (tableArray.content.length > 1) {
115 116 selectOptions.value.forEach((ele) => {
116 117 if (ele.value == item.key) {
117 118 ele.disabled = false;
118 119 }
119 120 });
120 121 tableArray.content.splice(index, 1);
  122 + } else {
  123 + resetValue();
121 124 }
122 125 };
123 126
  127 + const hanldeDropdownVisibleChange = () => handleChange();
  128 +
124 129 //Select互斥
125 130 const handleChange = () => {
126 131 selectOptions.value.forEach((ele) => {
... ...
... ... @@ -18,6 +18,7 @@
18 18 placeholder="请选择"
19 19 :options="selectOptions"
20 20 @change="handleChange"
  21 + @dropdownVisibleChange="hanldeDropdownVisibleChange"
21 22 allowClear
22 23 />
23 24 </td>
... ... @@ -111,13 +112,15 @@
111 112
112 113 // 减少
113 114 const remove = (item, index: number) => {
114   - if (tableArray.content.length !== 1) {
  115 + if (tableArray.content.length > 1) {
115 116 selectOptions.value.forEach((ele) => {
116 117 if (ele.value == item.key) {
117 118 ele.disabled = false;
118 119 }
119 120 });
120 121 tableArray.content.splice(index, 1);
  122 + } else {
  123 + resetValue();
121 124 }
122 125 };
123 126
... ... @@ -139,6 +142,8 @@
139 142 });
140 143 };
141 144
  145 + const hanldeDropdownVisibleChange = () => handleChange();
  146 +
142 147 //获取数据
143 148 const getValue = () => {
144 149 const assemblyData = tableArray.content.map((it) => {
... ...
... ... @@ -6,6 +6,7 @@
6 6 <ParamsTable ref="paramsCellTableRef" :method="method" />
7 7 <ParamsTest
8 8 @testParamsInterface="handleTestParamsInterface"
  9 + @closeTest="onCloseTest"
9 10 ref="testParamsRequestRef"
10 11 :data="dataMap.mapParamsObj"
11 12 />
... ... @@ -21,6 +22,7 @@
21 22 <BodyTest
22 23 v-if="bodyType !== 'none'"
23 24 @testBodyInterface="handleTestBodyInterface"
  25 + @closeTest="onCloseTest"
24 26 ref="testBodyRequestRef"
25 27 :data="dataMap.mapBodyObj"
26 28 />
... ... @@ -29,6 +31,7 @@
29 31 <HeaderTable ref="editHeaderCellTableRef" :method="method" />
30 32 <HeaderTest
31 33 @testHeaderInterface="handleTestHeaderInterface"
  34 + @closeTest="onCloseTest"
32 35 ref="testHeaderRequestRef"
33 36 :data="dataMap.mapHeaderObj"
34 37 />
... ... @@ -107,6 +110,8 @@
107 110 excuteTestRef.value?.resetValue(true);
108 111 };
109 112
  113 + const onCloseTest = () => excuteTestRef.value?.resetValue(true);
  114 +
110 115 //if-else-if-else分支优化
111 116 const dataForTypeMap = [
112 117 [(type) => type === 'Params', (data) => paramsCellTableRef.value?.setValue(data)],
... ...
1 1 <template>
2 2 <div>
3 3 <div class="mt-8">
4   - <div>
5   - <Button @click="handleTest" type="primary"> 测试接口 </Button>
  4 + <div class="flex">
  5 + <Button @click="handleTest" type="primary"> 打开测试接口 </Button>
  6 + <Button class="ml-2" @click="onCloseTest" type="primary"> 关闭测试接口 </Button>
6 7 </div>
7 8 <div v-if="showTestEditCell" class="mt-8">
8 9 <a-row type="flex" justify="center">
... ... @@ -31,8 +32,9 @@
31 32 import moment from 'moment';
32 33 import { useUtils } from '../../../hooks/useUtils';
33 34 import JsonEditor from '../../SimpleRequest/components/jsonEditor.vue';
  35 + import { useMessage } from '/@/hooks/web/useMessage';
34 36
35   - const emits = defineEmits(['testBodyInterface']);
  37 + const emits = defineEmits(['testBodyInterface', 'closeTest']);
36 38
37 39 const props = defineProps({
38 40 data: {
... ... @@ -40,6 +42,8 @@
40 42 },
41 43 });
42 44
  45 + const { createMessage } = useMessage();
  46 +
43 47 const showTestEditCell = ref(false);
44 48
45 49 const excuteData = ref({});
... ... @@ -111,6 +115,7 @@
111 115 return {
112 116 key,
113 117 value,
  118 + required: it.required,
114 119 };
115 120 });
116 121 };
... ... @@ -120,6 +125,11 @@
120 125 let params: any = {};
121 126 if (props.data?.type === 'x-www-form-urlencoded' || props.data?.type === 'form-data') {
122 127 const getTable = getTestTableKeyValue();
  128 + const hasRequired = getTable?.some((it) => it.required === true && !it.value);
  129 + if (hasRequired) {
  130 + createMessage.error('参数不能为空');
  131 + throw new Error('参数不能为空');
  132 + }
123 133 getTable?.map((it) => (params[it.key!] = it.value!));
124 134 } else if (props.data?.type === 'json') {
125 135 params = jsonEditorRef.value?.getJsonValue();
... ... @@ -141,6 +151,11 @@
141 151 testResult.value = '';
142 152 };
143 153
  154 + const onCloseTest = () => {
  155 + showTestEditCell.value = false;
  156 + emits('closeTest');
  157 + };
  158 +
144 159 defineExpose({
145 160 setValue,
146 161 handleTest,
... ...
... ... @@ -243,7 +243,9 @@
243 243 if (f.key === 'organizationId') organizationId = f.value;
244 244 if (f.key === 'entityId') f.value = null;
245 245 });
246   - getAttributeOptions({ deviceProfileId: e.value });
  246 + if (e.value) {
  247 + getAttributeOptions({ deviceProfileId: e.value });
  248 + }
247 249 if (organizationId !== '') {
248 250 getEntityOptions(organizationId, e.value);
249 251 }
... ...
... ... @@ -5,10 +5,10 @@
5 5 </div>
6 6 <div class="mt-8">
7 7 <a-row type="flex" justify="center">
8   - <a-col :span="2"> 测试结果 </a-col>
9   - <a-col :span="22">
  8 + <a-col :span="24">
10 9 <a-textarea
11   - v-if="ifWebSocket === '2'"
  10 + disabled
  11 + v-if="isWebSocketType === '2'"
12 12 allow-clear
13 13 show-count
14 14 v-model:value="testResult"
... ... @@ -16,6 +16,7 @@
16 16 :rows="8"
17 17 />
18 18 <a-textarea
  19 + disabled
19 20 v-else
20 21 allow-clear
21 22 show-count
... ... @@ -29,14 +30,13 @@
29 30 </div>
30 31 </template>
31 32 <script lang="ts" setup name="testRequest">
32   - import { nextTick, ref, reactive } from 'vue';
  33 + import { nextTick, ref, reactive, onUnmounted } from 'vue';
33 34 import { Button } from 'ant-design-vue';
34 35 import { otherHttp } from '/@/utils/http/axios';
35 36 import { useWebSocket } from '@vueuse/core';
36 37 import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum';
37 38 import { getAuthCache } from '/@/utils/auth';
38   - import { useMessage } from '/@/hooks/web/useMessage';
39   - // import { useGlobSetting } from '/@/hooks/setting';
  39 + import { useUtils } from '../../../hooks/useUtils';
40 40
41 41 const emits = defineEmits(['emitExcute']);
42 42
... ... @@ -48,16 +48,10 @@
48 48
49 49 const showTestFlag = ref(false);
50 50
51   - const ifWebSocket = ref('');
52   -
53   - const { createMessage } = useMessage();
54   -
55 51 const token = getAuthCache(JWT_TOKEN_KEY);
56 52
57 53 const socketUrls = ref('');
58 54
59   - // const { socketUrl } = useGlobSetting();
60   -
61 55 const socketMessage: any = reactive({
62 56 server: ``,
63 57 sendValue: {
... ... @@ -89,6 +83,8 @@
89 83
90 84 const httpResult = ref('');
91 85
  86 + const isWebSocketType = ref('');
  87 +
92 88 //执行测试接口
93 89 const handleExcute = () => {
94 90 emits('emitExcute');
... ... @@ -99,18 +95,23 @@
99 95 await nextTick();
100 96 //获取Params和Header和Body
101 97 const Objects = props.data;
102   - const isWebSocketType = Objects?.method;
  98 + isWebSocketType.value = Objects?.method;
103 99 const apiType = Objects?.apiType;
104 100 const url = Objects?.apiGetUrl;
105 101 const headers = Objects?.Header?.params;
106 102 const params = Objects?.Params?.params;
107 103 const body = Objects?.Body?.params;
108 104 const apiUrl = url?.restfulFormat(params);
109   - ifWebSocket.value = isWebSocketType;
110   - if (isWebSocketType === '2') {
  105 + if (isWebSocketType.value === '2') {
111 106 socketUrls.value = url;
112 107 socketMessage.server = `${socketUrls.value}?token=${token}`;
113   - websocketRequest(params);
  108 + const list = Object.values(params);
  109 + const isEmpty = list.some((it) => it === '' || null || undefined);
  110 + if (!isEmpty) {
  111 + websocketRequest(params);
  112 + } else {
  113 + resetValue(false);
  114 + }
114 115 } else {
115 116 const resp = await otherHttpRequest(apiType, apiUrl?.split('{?')[0], headers, params, body);
116 117 if (!resp) return;
... ... @@ -119,7 +120,7 @@
119 120 };
120 121
121 122 //websocket请求
122   - const websocketRequest = (params) => {
  123 + const websocketRequest = (params, destroy = false) => {
123 124 //websocket请求
124 125 Reflect.deleteProperty(params, 'deviceProfileId');
125 126 Reflect.deleteProperty(params, 'organizationId');
... ... @@ -140,12 +141,17 @@
140 141 console.log('断开连接了');
141 142 close();
142 143 },
143   - onError() {
144   - createMessage.error('webSocket连接超时,请联系管理员');
145   - },
  144 + onError() {},
146 145 });
  146 + if (destroy) close();
147 147 };
148 148
  149 + onUnmounted(() => {
  150 + if (isWebSocketType.value === '2') {
  151 + websocketRequest(null, true);
  152 + }
  153 + });
  154 +
149 155 //TODO: 待优化 项目自带第三方请求
150 156 const otherHttpRequest = async (
151 157 apiType,
... ... @@ -165,8 +171,9 @@
165 171 }
166 172 );
167 173 case 'POST':
  174 + const { convertObj } = useUtils();
168 175 return await otherHttp.post(
169   - { url: apiUrl, data: body, headers, params },
  176 + { url: `${apiUrl}?${convertObj(params)}`, data: body, headers },
170 177 {
171 178 apiUrl: '',
172 179 joinPrefix,
... ... @@ -189,7 +196,7 @@
189 196 }
190 197 httpResult.value = '测试结果为:';
191 198 testResult.value = '测试结果为:';
192   - ifWebSocket.value = '0';
  199 + isWebSocketType.value = '0';
193 200 };
194 201
195 202 const showTest = () => (showTestFlag.value = true);
... ...
1 1 <template>
2 2 <div>
3 3 <div class="mt-8">
4   - <div>
5   - <Button @click="handleTest" type="primary"> 测试接口 </Button>
  4 + <div class="flex">
  5 + <Button @click="handleTest" type="primary"> 打开测试接口 </Button>
  6 + <Button class="ml-2" @click="onCloseTest" type="primary"> 关闭测试接口 </Button>
6 7 </div>
7 8 <div v-if="showTestEditCell" class="mt-8">
8 9 <a-row type="flex" justify="center">
... ... @@ -20,8 +21,9 @@
20 21 import { ref, nextTick } from 'vue';
21 22 import { Button } from 'ant-design-vue';
22 23 import TestHeaderEditCellTable from './testEditHeaderCellTable.vue';
  24 + import { useMessage } from '/@/hooks/web/useMessage';
23 25
24   - const emits = defineEmits(['testHeaderInterface']);
  26 + const emits = defineEmits(['testHeaderInterface', 'closeTest']);
25 27
26 28 const props = defineProps({
27 29 data: {
... ... @@ -29,6 +31,8 @@
29 31 },
30 32 });
31 33
  34 + const { createMessage } = useMessage();
  35 +
32 36 const showTestEditCell = ref(false);
33 37
34 38 const excuteData = ref({});
... ... @@ -59,6 +63,11 @@
59 63 //获取数据
60 64 const getTestValue = () => {
61 65 const getTable = getTestTableKeyValue();
  66 + const hasRequired = getTable?.some((it) => it.required === true && !it.value);
  67 + if (hasRequired) {
  68 + createMessage.error('参数不能为空');
  69 + throw new Error('参数不能为空');
  70 + }
62 71 const params: any = {};
63 72 getTable?.map((it: any) => (params[it.key!] = it.value!));
64 73 excuteData.value = {
... ... @@ -73,6 +82,11 @@
73 82 testResult.value = '';
74 83 };
75 84
  85 + const onCloseTest = () => {
  86 + showTestEditCell.value = false;
  87 + emits('closeTest');
  88 + };
  89 +
76 90 defineExpose({
77 91 setValue,
78 92 handleTest,
... ...
1 1 <template>
2 2 <div>
3 3 <div class="mt-8">
4   - <div>
5   - <Button @click="handleTest" type="primary"> 测试接口 </Button>
  4 + <div class="flex">
  5 + <Button @click="handleTest" type="primary"> 打开测试接口 </Button>
  6 + <Button class="ml-2" @click="onCloseTest" type="primary"> 关闭测试接口 </Button>
6 7 </div>
7 8 <div v-if="showTestEditCell" class="mt-8">
8 9 <a-row type="flex" justify="center">
... ... @@ -22,8 +23,9 @@
22 23 import TestParamsCellTable from './testEditParamsCellTable.vue';
23 24 import moment from 'moment';
24 25 import { useUtils } from '../../../hooks/useUtils';
  26 + import { useMessage } from '/@/hooks/web/useMessage';
25 27
26   - const emits = defineEmits(['testParamsInterface']);
  28 + const emits = defineEmits(['testParamsInterface', 'closeTest']);
27 29
28 30 const props = defineProps({
29 31 data: {
... ... @@ -31,6 +33,8 @@
31 33 },
32 34 });
33 35
  36 + const { createMessage } = useMessage();
  37 +
34 38 const showTestEditCell = ref(false);
35 39
36 40 const excuteData = ref({});
... ... @@ -92,6 +96,7 @@
92 96 return {
93 97 key,
94 98 value,
  99 + required: it.required,
95 100 };
96 101 });
97 102 };
... ... @@ -99,6 +104,11 @@
99 104 //获取数据
100 105 const getTestValue = () => {
101 106 const getTable = getTestTableKeyValue();
  107 + const hasRequired = getTable?.some((it) => it.required === true && !it.value);
  108 + if (hasRequired) {
  109 + createMessage.error('参数不能为空');
  110 + throw new Error('参数不能为空');
  111 + }
102 112 const params: any = {};
103 113 getTable?.map((it) => (params[it.key!] = it.value!));
104 114 if (params['keys']) {
... ... @@ -116,6 +126,11 @@
116 126 testResult.value = '';
117 127 };
118 128
  129 + const onCloseTest = () => {
  130 + showTestEditCell.value = false;
  131 + emits('closeTest');
  132 + };
  133 +
119 134 defineExpose({
120 135 setValue,
121 136 handleTest,
... ...
... ... @@ -235,7 +235,9 @@
235 235 if (f.key === 'organizationId') organizationId = f.value;
236 236 if (f.key === 'entityId') f.value = null;
237 237 });
238   - getAttributeOptions({ deviceProfileId: e.value });
  238 + if (e.value) {
  239 + getAttributeOptions({ deviceProfileId: e.value });
  240 + }
239 241 if (organizationId !== '') {
240 242 getEntityOptions(organizationId, e.value);
241 243 }
... ...
... ... @@ -46,7 +46,7 @@
46 46 import { useMessage } from '/@/hooks/web/useMessage';
47 47 import { useUtils } from './hooks/useUtils';
48 48
49   - const { resetReqHttpType, resetUpdateSchema } = useUtils();
  49 + const { resetReqHttpType } = useUtils();
50 50
51 51 const emits = defineEmits(['success', 'register']);
52 52
... ... @@ -69,10 +69,15 @@
69 69 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
70 70 await resetFields();
71 71 await nextTick();
72   - updateSchema(resetUpdateSchema);
73 72 setFieldsValue(resetReqHttpType);
74 73 const title = `${!data.isUpdate ? '新增' : '修改'}公共接口`;
75 74 setDrawerProps({ title });
  75 + updateSchema({
  76 + field: 'requestHttpTypeAndUrl',
  77 + componentProps: {
  78 + type: '0',
  79 + },
  80 + });
76 81 isUpdate.value = data.isUpdate;
77 82 !isUpdate.value ? (putId.value = '') : (putId.value = data.record.id);
78 83 simpleRequestRef.value?.resetValue() && testSqlRef.value?.resetValue();
... ... @@ -104,6 +109,14 @@
104 109 try {
105 110 const values = await validate();
106 111 if (!values) return;
  112 + const isRequestHttpTypeAndUrlEmpty = values?.requestHttpTypeAndUrl;
  113 + if (
  114 + !Reflect.get(isRequestHttpTypeAndUrlEmpty, 'requestHttpType') ||
  115 + !Reflect.get(isRequestHttpTypeAndUrlEmpty, 'requestUrl')
  116 + ) {
  117 + createMessage.error('请填写请求类型&地址');
  118 + throw Error('请填写请求类型&地址');
  119 + }
107 120 const Objects = simpleRequestRef.value?.getValue(true);
108 121 const requestOriginUrl =
109 122 values['originUrlType'] === 'server_url' ? 'localhost' : values['requestOriginUrl'];
... ...
... ... @@ -36,5 +36,20 @@ export const useUtils = () => {
36 36 type: '0',
37 37 },
38 38 };
39   - return { getMultipleKeys, pushObj, resetReqHttpType, resetUpdateSchema };
  39 + //对象转get params参数
  40 + const convertObj = (data: object) => {
  41 + const _result: any = [];
  42 + for (const key in data) {
  43 + const value = data[key];
  44 + if (value.constructor == Array) {
  45 + value.forEach(function (_value) {
  46 + _result.push(key + '=' + _value);
  47 + });
  48 + } else {
  49 + _result.push(key + '=' + value);
  50 + }
  51 + }
  52 + return _result.join('&');
  53 + };
  54 + return { getMultipleKeys, pushObj, resetReqHttpType, resetUpdateSchema, convertObj };
40 55 };
... ...
... ... @@ -39,7 +39,7 @@
39 39 icon: 'ant-design:node-expand-outlined',
40 40 onClick: handlePublish.bind(null, 'publish', record),
41 41 ifShow: () => {
42   - return record.state === 0;
  42 + return record.state === 0 && record.creator === userId;
43 43 },
44 44 },
45 45 {
... ... @@ -47,7 +47,7 @@
47 47 icon: 'ant-design:node-collapse-outlined',
48 48 onClick: handlePublish.bind(null, 'canelPublish', record),
49 49 ifShow: () => {
50   - return record.state === 1;
  50 + return record.state === 1 && record.creator === userId;
51 51 },
52 52 },
53 53 {
... ... @@ -55,7 +55,7 @@
55 55 icon: 'clarity:note-edit-line',
56 56 onClick: handleCreateOrEdit.bind(null, record),
57 57 ifShow: () => {
58   - return record.state === 0;
  58 + return record.state === 0 && record.creator === userId;
59 59 },
60 60 },
61 61 {
... ... @@ -63,7 +63,7 @@
63 63 icon: 'ant-design:delete-outlined',
64 64 color: 'error',
65 65 ifShow: () => {
66   - return record.state === 0;
  66 + return record.state === 0 && record.creator === userId;
67 67 },
68 68 popConfirm: {
69 69 title: '是否确认删除',
... ... @@ -93,6 +93,12 @@
93 93 import { Popconfirm, Modal } from 'ant-design-vue';
94 94 import { JsonPreview } from '/@/components/CodeEditor';
95 95 import { useMessage } from '/@/hooks/web/useMessage';
  96 + import { USER_INFO_KEY } from '/@/enums/cacheEnum';
  97 + import { getAuthCache } from '/@/utils/auth';
  98 +
  99 + const userInfo = getAuthCache(USER_INFO_KEY) as any;
  100 +
  101 + const userId = ref(userInfo?.userId);
96 102
97 103 const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
98 104 api: getDataViewInterfacePage,
... ...
... ... @@ -52,6 +52,9 @@
52 52 compact: true,
53 53 baseColProps: { span: 8 },
54 54 schemas: searchFormSchema,
  55 + resetFunc: async () => {
  56 + getDataSource({ name: '' });
  57 + },
55 58 submitFunc: async () => {
56 59 getDataSource({ pageSize: pagination.pageSize, page: 1 });
57 60 },
... ...
1 1 <script lang="ts" setup>
2 2 import { Button, Tag } from 'ant-design-vue';
3   - import { h, onMounted, ref, unref } from 'vue';
  3 + import { h, onMounted, ref, unref, Ref } from 'vue';
4 4 import { DeviceRecord } from '/@/api/device/model/deviceModel';
5   - import { ScriptRecord } from '/@/api/scriptmanage/model/scriptModel';
  5 + // import { ScriptRecord } from '/@/api/scriptmanage/model/scriptModel';
6 6 import { getScriptManageMeList } from '/@/api/scriptmanage/scriptManager';
7 7 import { Description, useDescription } from '/@/components/Description';
8 8 import { useModal } from '/@/components/Modal';
9 9 import CoverScriptModal from '/@/views/scriptmanage/converscript/ConverScriptModal.vue';
  10 + import { SelectTypes } from 'ant-design-vue/es/select';
10 11
11 12 const props = defineProps<{
12 13 record: DeviceRecord['profileData']['transportConfiguration'];
13 14 }>();
14 15
15   - const scriptInfo = ref<ScriptRecord>({} as unknown as ScriptRecord);
  16 + // const scriptInfo = ref<ScriptRecord>({} as unknown as ScriptRecord);
  17 + const authScriptIdStr = ref('');
  18 +
  19 + const upScriptIdStr = ref('');
  20 +
  21 + const selectUpOptions: Ref<SelectTypes['options']> = ref([]);
  22 +
  23 + const selectAuthOptions: Ref<SelectTypes['options']> = ref([]);
  24 +
  25 + onMounted(async () => {
  26 + selectUpOptions.value = await getAllScriptType('TRANSPORT_TCP_UP');
  27 + selectAuthOptions.value = await getAllScriptType('TRANSPORT_TCP_AUTH');
  28 + setDescProps({
  29 + data: Object.assign(props.record),
  30 + });
  31 + });
  32 +
  33 + const getAllScriptType = async (type) => {
  34 + const rest = await getScriptManageMeList({ scriptType: type });
  35 + return rest.map((m) => ({ label: m.name, value: m.id }));
  36 + };
  37 +
  38 + const findScriptUpName = (scriptId) => {
  39 + upScriptIdStr.value = scriptId;
  40 + return selectUpOptions.value?.find((it) => it.value === scriptId)?.label;
  41 + };
  42 +
  43 + const findScriptAuthName = (scriptId) => {
  44 + authScriptIdStr.value = scriptId;
  45 + return selectAuthOptions.value?.find((it) => it.value === scriptId)?.label;
  46 + };
16 47
17 48 const [register, { setDescProps }] = useDescription({
18 49 layout: 'vertical',
... ... @@ -25,14 +56,27 @@
25 56 span: 2,
26 57 },
27 58 {
28   - field: 'scriptName',
29   - label: '转换脚本',
  59 + field: 'authScriptId',
  60 + label: '鉴权脚本',
30 61 render: (value: string) => {
31 62 return (
32 63 value &&
33 64 h('div', [
34   - h(Tag, { color: 'blue' }, () => value),
35   - h(Button, { type: 'link', onClick: handleTestScript }, () => '测试脚本'),
  65 + h(Tag, { color: 'blue' }, () => findScriptAuthName(value)),
  66 + h(Button, { type: 'link', onClick: handleTestAuthScript }, () => '测试脚本'),
  67 + ])
  68 + );
  69 + },
  70 + },
  71 + {
  72 + field: 'upScriptId',
  73 + label: '上行脚本',
  74 + render: (value: string) => {
  75 + return (
  76 + value &&
  77 + h('div', [
  78 + h(Tag, { color: 'blue' }, () => findScriptUpName(value)),
  79 + h(Button, { type: 'link', onClick: handleTestUpScript }, () => '测试脚本'),
36 80 ])
37 81 );
38 82 },
... ... @@ -42,29 +86,24 @@
42 86
43 87 const [registerModal, { openModal }] = useModal();
44 88
45   - const handleTestScript = () => {
  89 + const handleTestAuthScript = () => {
46 90 openModal(true, {
47 91 isUpdate: false,
48 92 isTest: true,
49   - record: unref(scriptInfo).id,
  93 + record: unref(authScriptIdStr),
50 94 isText: 'test',
51 95 isTitle: 'test',
52 96 });
53 97 };
54 98
55   - onMounted(() => {
56   - getTransforScriptInfo();
57   - });
58   -
59   - const getTransforScriptInfo = async () => {
60   - try {
61   - const list = await getScriptManageMeList();
62   - const record = list.find((item) => item.id === props.record.scriptId);
63   - scriptInfo.value = record!;
64   - setDescProps({
65   - data: Object.assign(props.record, record, { scriptName: record?.name || '' }),
66   - });
67   - } catch (error) {}
  99 + const handleTestUpScript = () => {
  100 + openModal(true, {
  101 + isUpdate: false,
  102 + isTest: true,
  103 + record: unref(upScriptIdStr),
  104 + isText: 'test',
  105 + isTitle: 'test',
  106 + });
68 107 };
69 108 </script>
70 109
... ...
... ... @@ -41,7 +41,7 @@
41 41 </BasicDrawer>
42 42 </template>
43 43 <script lang="ts" setup>
44   - import { ref, computed, unref, reactive, watch, Ref } from 'vue';
  44 + import { ref, computed, unref, reactive, watch, Ref, nextTick } from 'vue';
45 45 import { BasicForm, useForm } from '/@/components/Form';
46 46 import { formSchema, organizationId } from './config.data';
47 47 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
... ... @@ -198,6 +198,7 @@
198 198 const isViewDetail = ref(false);
199 199
200 200 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  201 + await nextTick();
201 202 await resetFields();
202 203 setDrawerProps({ confirmLoading: false });
203 204 isUpdate.value = !!data?.isUpdate;
... ... @@ -222,6 +223,10 @@
222 223 await setFieldsValue(editResData.data);
223 224 //回显嵌套数据
224 225 await setFieldsValue({
  226 + dateGroupGap:
  227 + editResData.data.queryCondition?.queryMode === 1
  228 + ? editResData.data.queryCondition?.interval
  229 + : null,
225 230 agg: editResData.data.queryCondition?.agg,
226 231 interval: editResData.data.queryCondition?.interval,
227 232 limit: editResData.data.queryCondition?.limit,
... ... @@ -266,6 +271,7 @@
266 271 { label: '最大值', value: AggregateDataEnum.MAX },
267 272 { label: '平均值', value: AggregateDataEnum.AVG },
268 273 { label: '求和', value: AggregateDataEnum.SUM },
  274 + { label: '计数', value: AggregateDataEnum.COUNT },
269 275 { label: '空', value: AggregateDataEnum.NONE },
270 276 ];
271 277 const updateSchemaAgg = (options: {}) => {
... ... @@ -276,8 +282,8 @@
276 282 },
277 283 });
278 284 };
279   - if (editResData.data.dataType == 1) updateSchemaAgg(dataCompareOpions.slice(0, 4));
280   - else updateSchemaAgg(dataCompareOpions.slice(4, 5));
  285 + if (editResData.data.dataType == 1) updateSchemaAgg(dataCompareOpions.slice(0, 5));
  286 + else updateSchemaAgg(dataCompareOpions.slice(5, 6));
281 287 //回显执行方式和查询周期
282 288 const dataQueryOpions = [
283 289 { label: '固定周期', value: QueryWay.LATEST },
... ... @@ -442,7 +448,7 @@
442 448 }
443 449 queryCondition = {
444 450 agg: values.agg,
445   - interval: values.interval,
  451 + interval: values?.queryMode === 'latest' ? values.interval : values.dateGroupGap,
446 452 limit: values.limit,
447 453 ...{
448 454 startTs: startTs.value,
... ...
1 1 import { ref } from 'vue';
2 2 import { BasicColumn, FormSchema } from '/@/components/Table';
3 3 import type { FormSchema as QFormSchema } from '/@/components/Form/index';
4   -import moment from 'moment';
5 4 import { getOrganizationList } from '/@/api/system/system';
6 5 import { copyTransFun } from '/@/utils/fnUtils';
7 6 import { findDictItemByCode } from '/@/api/system/dict';
... ... @@ -12,6 +11,7 @@ import {
12 11 getPacketIntervalByValue,
13 12 intervalOption,
14 13 } from '../../device/localtion/cpns/TimePeriodForm/helper';
  14 +import moment, { Moment } from 'moment';
15 15
16 16 export enum QueryWay {
17 17 LATEST = 'latest',
... ... @@ -433,16 +433,70 @@ export const formSchema: QFormSchema[] = [
433 433 ifShow({ values }) {
434 434 return values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD && !isFixedTime(values.executeWay);
435 435 },
436   - componentProps: {
437   - showTime: {
438   - defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
439   - },
  436 + // componentProps: {
  437 + // showTime: {
  438 + // defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
  439 + // },
  440 + // },
  441 + componentProps({ formActionType }) {
  442 + const { setFieldsValue } = formActionType;
  443 + let dates: Moment[] = [];
  444 + return {
  445 + showTime: {
  446 + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
  447 + },
  448 + onCalendarChange(value: Moment[]) {
  449 + dates = value;
  450 + },
  451 + disabledDate(current: Moment) {
  452 + if (!dates || dates.length === 0 || !current) {
  453 + return false;
  454 + }
  455 + const diffDate = current.diff(dates[0], 'years', true);
  456 + return Math.abs(diffDate) > 1;
  457 + },
  458 + onChange() {
  459 + dates = [];
  460 + setFieldsValue({ dateGroupGap: null });
  461 + },
  462 + getPopupContainer: () => document.body,
  463 + };
440 464 },
441 465 colProps: {
442 466 span: 10,
443 467 },
444 468 },
445 469 {
  470 + field: 'dateGroupGap',
  471 + label: '分组间隔',
  472 + component: 'Select',
  473 + dynamicRules: ({ model }) => {
  474 + return [
  475 + {
  476 + required: model[SchemaFiled.AGG] !== AggregateDataEnum.NONE,
  477 + message: '分组间隔为必填项',
  478 + type: 'number',
  479 + },
  480 + ];
  481 + },
  482 + ifShow({ values }) {
  483 + return values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD && !isFixedTime(values.executeWay);
  484 + },
  485 + componentProps({ formModel, formActionType }) {
  486 + const options =
  487 + formModel[SchemaFiled.WAY] === QueryWay.LATEST
  488 + ? getPacketIntervalByValue(formModel[SchemaFiled.START_TS])
  489 + : getPacketIntervalByRange(formModel[SchemaFiled.DATE_RANGE]);
  490 + if (formModel[SchemaFiled.AGG] !== AggregateDataEnum.NONE) {
  491 + formActionType.setFieldsValue({ [SchemaFiled.LIMIT]: null });
  492 + }
  493 + return {
  494 + options,
  495 + getPopupContainer: () => document.body,
  496 + };
  497 + },
  498 + },
  499 + {
446 500 field: SchemaFiled.START_TS,
447 501 label: '最近时间',
448 502 component: 'Select',
... ...
... ... @@ -128,6 +128,7 @@
128 128 import { findOperation } from './config/formatData';
129 129 import { formatToDateTime } from '/@/utils/dateUtil';
130 130 import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';
  131 + import { isEmpty } from '/@/utils/is';
131 132 import { add } from '/@/components/Form/src/componentMap';
132 133
133 134 add('ObjectModelValidateForm', ObjectModelValidateForm);
... ... @@ -156,7 +157,7 @@
156 157 const id = ref(undefined);
157 158 const tenantId = ref(undefined);
158 159 const isView = ref(true);
159   - const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
  160 + const [registerForm, { resetFields, validate, setFieldsValue, getFieldsValue }] = useForm({
160 161 labelWidth: 120,
161 162 schemas: formSchema,
162 163 showActionButtonGroup: false,
... ... @@ -617,12 +618,25 @@
617 618 // item.updateFieldAlarmConfig(alarmConfigList);
618 619 // });
619 620 // }
  621 +
  622 + const isEmptyThrowError = (obj) => {
  623 + if (isEmpty(obj)) {
  624 + createMessage.error('请选择组织');
  625 + throw Error('请选择组织');
  626 + }
  627 + if (!Reflect.get(obj, 'organizationId')) {
  628 + createMessage.error('请选择组织');
  629 + throw Error('请选择组织');
  630 + }
  631 + };
  632 +
620 633 // 添加触发器
621 634 const addTrigger = () => {
622 635 unref(triggerData).push(Date.now());
623 636 nextTick(() => {
624 637 setFields(skipUnwrap.triggerItemRefs);
625 638 });
  639 + isEmptyThrowError(getFieldsValue());
626 640 };
627 641 // 添加执行条件
628 642 const addCondition = () => {
... ... @@ -630,6 +644,7 @@
630 644 nextTick(() => {
631 645 setFields(skipUnwrap.conditionItemRefs);
632 646 });
  647 + isEmptyThrowError(getFieldsValue());
633 648 };
634 649 // 添加执行动作
635 650 const addAction = () => {
... ... @@ -637,6 +652,7 @@
637 652 nextTick(() => {
638 653 setFields(skipUnwrap.actionItemRefs);
639 654 });
  655 + isEmptyThrowError(getFieldsValue());
640 656 };
641 657
642 658 /**
... ...
1   -<template>
2   - <div>
3   - <a-form
4   - ref="formRef"
5   - :model="scriptForm"
6   - name="basic"
7   - :label-col="{ span: 4 }"
8   - :wrapper-col="{ span: 16 }"
9   - autocomplete="off"
10   - >
11   - <a-form-item
12   - :label="ifAdd ? '名称' : '输入参数(params)'"
13   - :name="ifAdd ? 'name' : 'params'"
14   - :rules="[{ required: true, message: ifAdd ? '请输入脚本名称' : '请输入参数' }]"
15   - >
16   - <a-input
17   - v-if="ifAdd"
18   - :maxlength="36"
19   - @change="handleInputChange"
20   - v-model:value="scriptForm.name"
21   - placeholder="请输入脚本名称"
22   - />
23   - <a-input
24   - @change="handleInputChange"
25   - v-else
26   - v-model:value="scriptForm.params"
27   - placeholder="请输入参数"
28   - />
29   - </a-form-item>
30   - <a-form-item
31   - label="脚本类型"
32   - name="scriptType"
33   - :rules="[{ required: true, message: '请选择脚本类型' }]"
34   - >
35   - <a-space direction="vertical">
36   - <a-radio-group
37   - @change="handleScriptType"
38   - v-model:value="scriptForm.scriptType"
39   - :options="scriptTypeOptions"
40   - />
41   - </a-space>
42   - </a-form-item>
43   - <a-form-item
44   - label="保存原始数据"
45   - name="saveOriginalData"
46   - :rules="[{ required: true, message: '请选择保存原始数据' }]"
47   - >
48   - <a-space direction="vertical">
49   - <a-radio-group v-model:value="scriptForm.saveOriginalData" :options="originalOptions" />
50   - </a-space>
51   - </a-form-item>
52   - <a-form-item label="脚本内容" :name="ifAdd ? 'convertJs' : 'script'">
53   - <Card title="脚本内容" :bodyStyle="{ padding: 0, height: '280px' }">
54   - <template #extra>
55   - <a-button @click="handleFormat" size="small">格式化</a-button>
56   - <Tooltip :title="defaultTitle" class="ml-2">
57   - <QuestionCircleOutlined style="font-size: 1rem" />
58   - </Tooltip>
59   - </template>
60   - <div ref="aceRef" class="overflow-hidden"></div>
61   - </Card>
62   - <Button @click="handleCopy" class="mt-4">
63   - <template #icon>
64   - <CopyOutlined />
65   - </template>
66   - copy
67   - </Button>
68   - </a-form-item>
69   - <a-form-item
70   - :label="ifAdd ? '备注' : '输出参数(output)'"
71   - :name="ifAdd ? 'description' : 'output'"
72   - >
73   - <a-textarea
74   - :rows="3"
75   - v-if="ifAdd"
76   - v-model:value="scriptForm.description"
77   - placeholder="请输入备注"
78   - :maxlength="255"
79   - />
80   - <a-textarea
81   - disabled
82   - :rows="5"
83   - v-else
84   - v-model:value="scriptForm.output"
85   - placeholder="输出参数为服务端返回的内容"
86   - :maxlength="255"
87   - />
88   - </a-form-item>
89   - </a-form>
90   - </div>
91   -</template>
92   -<script setup lang="ts">
93   - import { ref, unref, reactive, onMounted, toRefs, nextTick, computed } from 'vue';
94   - import ace from 'ace-builds';
95   - import { Card, Button, Tooltip } from 'ant-design-vue';
96   - import 'ace-builds/src-noconflict/theme-chrome'; // 默认设置的主题
97   - import 'ace-builds/src-noconflict/theme-terminal'; // 默认设置的主题
98   - import 'ace-builds/src-noconflict/mode-javascript'; // 默认设置的语言模式
99   - import { beautify } from 'ace-builds/src-noconflict/ext-beautify.js';
100   - import { CopyOutlined } from '@ant-design/icons-vue';
101   - import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
102   - import { useMessage } from '/@/hooks/web/useMessage';
103   - import { findDictItemByCode } from '/@/api/system/dict';
104   - import { QuestionCircleOutlined } from '@ant-design/icons-vue';
105   - import { defaultTitle, defaultScriptTypeContent } from './config.data';
106   - import { useAppStore } from '/@/store/modules/app';
107   -
108   - defineEmits(['register']);
109   - const props = defineProps({
110   - ifAdd: { type: Boolean, default: true },
111   - });
112   -
113   - const scriptForm = reactive({
114   - name: '',
115   - description: '',
116   - convertJs: '',
117   - script: '',
118   - params: '',
119   - output: '',
120   - scriptType: 'TRANSPORT_TCP_UP',
121   - saveOriginalData: 'true',
122   - });
123   -
124   - const reportTypeOptions = reactive({
125   - originalOptions: [],
126   - scriptTypeOptions: [],
127   - });
128   -
129   - const { originalOptions, scriptTypeOptions } = toRefs(reportTypeOptions);
130   -
131   - const { createMessage } = useMessage();
132   -
133   - const { clipboardRef, copiedRef } = useCopyToClipboard();
134   -
135   - const aceEditor = ref();
136   -
137   - const aceRef = ref();
138   -
139   - const userStore = useAppStore();
140   -
141   - const getAceClass = computed((): string => userStore.getDarkMode);
142   -
143   - const setDefaultRadio = (p2, p3) => {
144   - scriptForm.saveOriginalData = p2;
145   - scriptForm.scriptType = p3;
146   - };
147   -
148   - const getDictValue = async (dict_type) => {
149   - const res = await findDictItemByCode({
150   - dictCode: dict_type,
151   - });
152   - return res.map((m) => {
153   - return { label: m.itemText, value: m.itemValue };
154   - });
155   - };
156   -
157   - onMounted(async () => {
158   - reportTypeOptions.originalOptions = (await getDictValue('original_data')) as never as any;
159   - reportTypeOptions.scriptTypeOptions = (await getDictValue('script_type')) as never as any;
160   - });
161   -
162   - // 初始化编辑器
163   - const initEditor = () => {
164   - aceEditor.value = ace.edit(aceRef.value, {
165   - maxLines: 12, // 最大行数,超过会自动出现滚动条
166   - minLines: 12, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
167   - fontSize: 14, // 编辑器内字体大小
168   - theme: 'ace/theme/chrome', // 默认设置的主题
169   - mode: 'ace/mode/javascript', // 默认设置的语言模式
170   - tabSize: 2, // 制表符设置为 4 个空格大小
171   - });
172   -
173   - aceEditor.value.setOptions({
174   - enableBasicAutocompletion: true,
175   - enableLiveAutocompletion: true,
176   - theme: getAceClass.value === 'dark' ? 'ace/theme/terminal' : 'ace/theme/chrome',
177   - });
178   - aceEditor.value.setValue('');
179   - beautify(aceEditor.value.session);
180   - switchScriptTypeGetContent('TRANSPORT_TCP_UP');
181   - };
182   -
183   - const handleScriptType = ({ target }) => {
184   - const { value } = target;
185   - switchScriptTypeGetContent(value);
186   - };
187   -
188   - const switchScriptTypeGetContent = (type) => {
189   - aceEditor.value.setValue(defaultScriptTypeContent[type]);
190   - };
191   -
192   - const handleCopy = () => {
193   - const valueRef = aceEditor.value.getValue();
194   - const value = unref(valueRef);
195   - if (!value) {
196   - createMessage.warning('请输入要拷贝的内容!');
197   - return;
198   - }
199   - clipboardRef.value = value;
200   - if (unref(copiedRef)) {
201   - createMessage.success('复制成功!');
202   - }
203   - };
204   -
205   - const formRef = ref();
206   -
207   - const getFormData = async () => {
208   - const value = await formRef.value.validateFields();
209   - if (props.ifAdd) {
210   - scriptForm.convertJs = aceEditor.value.getValue();
211   - if (scriptForm.convertJs == '') {
212   - createMessage.error('请编写脚本内容');
213   - throw '请编写脚本内容';
214   - }
215   - } else {
216   - scriptForm.script = aceEditor.value.getValue();
217   - if (scriptForm.script == '') {
218   - createMessage.error('请编写脚本内容');
219   - throw '请编写脚本内容';
220   - }
221   - }
222   - if (!value) return;
223   - if (scriptForm.params) {
224   - const trimParams = scriptForm.params.replace(/\s*/g, '');
225   - Reflect.set(value, 'params', trimParams);
226   - }
227   - if (scriptForm.convertJs.length > 1000) {
228   - createMessage.error('脚本内容长度不能大于1000');
229   - throw '脚本内容长度不能大于1000';
230   - }
231   - return {
232   - ...value,
233   - ...{ convertJs: props.ifAdd ? scriptForm.convertJs : null },
234   - ...{ script: !props.ifAdd ? scriptForm.script : null },
235   - ...{ saveOriginalData: scriptForm.saveOriginalData === 'false' ? false : true },
236   - };
237   - };
238   -
239   - const handleInputChange = (e) => {
240   - const trimParams = e.target.value.replace(/\s*/g, '');
241   - Reflect.set(scriptForm, 'params', trimParams);
242   - if (scriptForm.scriptType === 'TRANSPORT_TCP_DOWN') {
243   - aceEditor.value.setValue(`out.datas = "${scriptForm.params}";out.deviceName = "sensor";`);
244   - }
245   - };
246   -
247   - const setFormData = (v) => {
248   - if (v) {
249   - for (let i in scriptForm) {
250   - Reflect.set(scriptForm, i, v[i]);
251   - }
252   - nextTick(() => {
253   - setTimeout(() => {
254   - scriptForm.saveOriginalData = v.saveOriginalData === false ? 'false' : 'true';
255   - }, 10);
256   - });
257   - aceEditor.value.setValue(v.convertJs);
258   - handleFormat();
259   - }
260   - };
261   -
262   - const setScriptContentData = (v) => {
263   - aceEditor.value.setValue(v);
264   - handleFormat();
265   - };
266   -
267   - const resetFormData = () => {
268   - for (let i in scriptForm) {
269   - Reflect.set(scriptForm, i, '');
270   - }
271   - };
272   -
273   - const setScriptOutputData = (v) => {
274   - scriptForm.output = v;
275   - };
276   -
277   - const handleFormat = () => {
278   - beautify(aceEditor.value.session);
279   - aceEditor.value.getSession().setUseWrapMode(true);
280   - };
281   -
282   - defineExpose({
283   - initEditor,
284   - getFormData,
285   - resetFormData,
286   - setFormData,
287   - setScriptContentData,
288   - setScriptOutputData,
289   - setDefaultRadio,
290   - });
291   -</script>
292   -<style lang="less" scoped>
293   - @import url('./ConverScriptModal.less');
294   -</style>
  1 +<template>
  2 + <div>
  3 + <a-form
  4 + ref="formRef"
  5 + :model="scriptForm"
  6 + name="basic"
  7 + :label-col="{ span: 4 }"
  8 + :wrapper-col="{ span: 16 }"
  9 + autocomplete="off"
  10 + >
  11 + <a-form-item
  12 + :label="ifAdd ? '名称' : '输入参数(params)'"
  13 + :name="ifAdd ? 'name' : 'params'"
  14 + :rules="[{ required: true, message: ifAdd ? '请输入脚本名称' : '请输入参数' }]"
  15 + >
  16 + <a-input-group compact>
  17 + <a-input
  18 + style="width: calc(100% - 200px)"
  19 + v-if="ifAdd"
  20 + :maxlength="36"
  21 + @change="handleInputChange"
  22 + v-model:value="scriptForm.name"
  23 + placeholder="请输入脚本名称"
  24 + />
  25 + <a-input
  26 + @change="handleInputChange"
  27 + v-else
  28 + v-model:value="scriptForm.params"
  29 + placeholder="请输入参数"
  30 + />
  31 + <a-button @click="onHandleClick(text)" v-show="ifAdd && !view" type="primary">
  32 + 测试
  33 + </a-button>
  34 + </a-input-group>
  35 + </a-form-item>
  36 + <a-form-item
  37 + label="脚本类型"
  38 + name="scriptType"
  39 + :rules="[{ required: true, message: '请选择脚本类型' }]"
  40 + >
  41 + <a-space direction="vertical">
  42 + <a-radio-group
  43 + @change="handleScriptType"
  44 + v-model:value="scriptForm.scriptType"
  45 + :options="scriptTypeOptions"
  46 + />
  47 + </a-space>
  48 + </a-form-item>
  49 + <a-form-item
  50 + label="保存原始数据"
  51 + name="saveOriginalData"
  52 + :rules="[{ required: true, message: '请选择保存原始数据' }]"
  53 + >
  54 + <a-space direction="vertical">
  55 + <a-radio-group v-model:value="scriptForm.saveOriginalData" :options="originalOptions" />
  56 + </a-space>
  57 + </a-form-item>
  58 + <a-form-item label="脚本内容" :name="ifAdd ? 'convertJs' : 'script'">
  59 + <Card title="脚本内容" :bodyStyle="{ padding: 0, height: '280px' }">
  60 + <template #extra>
  61 + <a-button @click="handleFormat" size="small">格式化</a-button>
  62 + <Tooltip v-if="!ifAdd" :title="defaultAuthTitle" class="ml-2">
  63 + <QuestionCircleOutlined style="font-size: 1rem" />
  64 + </Tooltip>
  65 + <Tooltip v-if="!ifAdd" :title="defaultUpTitle" class="ml-2">
  66 + <QuestionCircleOutlined style="font-size: 1rem" />
  67 + </Tooltip>
  68 + </template>
  69 + <div ref="aceRef" class="overflow-hidden"></div>
  70 + </Card>
  71 + <Button @click="handleCopy" class="mt-4">
  72 + <template #icon>
  73 + <CopyOutlined />
  74 + </template>
  75 + copy
  76 + </Button>
  77 + </a-form-item>
  78 + <a-form-item
  79 + :label="ifAdd ? '备注' : '输出参数(output)'"
  80 + :name="ifAdd ? 'description' : 'output'"
  81 + >
  82 + <a-textarea
  83 + :rows="3"
  84 + v-if="ifAdd"
  85 + v-model:value="scriptForm.description"
  86 + placeholder="请输入备注"
  87 + :maxlength="255"
  88 + />
  89 + <a-textarea
  90 + :rows="5"
  91 + v-else
  92 + v-model:value="scriptForm.output"
  93 + placeholder="输出参数为服务端返回的内容"
  94 + :maxlength="255"
  95 + />
  96 + </a-form-item>
  97 + </a-form>
  98 + <TestScriptModal @register="registerModal" @success="handleSuccess" />
  99 + </div>
  100 +</template>
  101 +<script setup lang="ts">
  102 + import { ref, unref, reactive, onMounted, toRefs, nextTick, computed } from 'vue';
  103 + import ace from 'ace-builds';
  104 + import { Card, Button, Tooltip } from 'ant-design-vue';
  105 + import 'ace-builds/src-noconflict/theme-chrome'; // 默认设置的主题
  106 + import 'ace-builds/src-noconflict/theme-terminal'; // 默认设置的主题
  107 + import 'ace-builds/src-noconflict/mode-javascript'; // 默认设置的语言模式
  108 + import { beautify } from 'ace-builds/src-noconflict/ext-beautify.js';
  109 + import { CopyOutlined } from '@ant-design/icons-vue';
  110 + import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  111 + import { useMessage } from '/@/hooks/web/useMessage';
  112 + import { findDictItemByCode } from '/@/api/system/dict';
  113 + import { QuestionCircleOutlined } from '@ant-design/icons-vue';
  114 + import { defaultAuthTitle, defaultUpTitle, defaultScriptTypeContent } from './config.data';
  115 + import { useAppStore } from '/@/store/modules/app';
  116 + import TestScriptModal from './TestScriptModal.vue';
  117 + import { useModal } from '/@/components/Modal';
  118 +
  119 + defineEmits(['register']);
  120 + const props = defineProps({
  121 + ifAdd: { type: Boolean, default: true },
  122 + text: { type: String, default: '' },
  123 + view: { type: Boolean, default: false },
  124 + });
  125 +
  126 + const scriptForm = reactive({
  127 + name: '',
  128 + description: '',
  129 + convertJs: '',
  130 + script: '',
  131 + params: '',
  132 + output: '',
  133 + scriptType: 'TRANSPORT_TCP_UP',
  134 + saveOriginalData: 'true',
  135 + });
  136 +
  137 + const reportTypeOptions = reactive({
  138 + originalOptions: [],
  139 + scriptTypeOptions: [],
  140 + });
  141 +
  142 + const { originalOptions, scriptTypeOptions } = toRefs(reportTypeOptions);
  143 +
  144 + const { createMessage } = useMessage();
  145 +
  146 + const { clipboardRef, copiedRef } = useCopyToClipboard();
  147 +
  148 + const aceEditor = ref();
  149 +
  150 + const aceRef = ref();
  151 +
  152 + const userStore = useAppStore();
  153 +
  154 + const getAceClass = computed((): string => userStore.getDarkMode);
  155 +
  156 + const setDefaultRadio = (p2, p3) => {
  157 + scriptForm.saveOriginalData = p2;
  158 + scriptForm.scriptType = p3;
  159 + };
  160 +
  161 + const getDictValue = async (dict_type) => {
  162 + const res = await findDictItemByCode({
  163 + dictCode: dict_type,
  164 + });
  165 + return res.map((m) => {
  166 + return { label: m.itemText, value: m.itemValue };
  167 + });
  168 + };
  169 +
  170 + onMounted(async () => {
  171 + reportTypeOptions.originalOptions = (await getDictValue('original_data')) as never as any;
  172 + reportTypeOptions.scriptTypeOptions = (await getDictValue('script_type')) as never as any;
  173 + });
  174 +
  175 + // 初始化编辑器
  176 + const initEditor = () => {
  177 + aceEditor.value = ace.edit(aceRef.value, {
  178 + maxLines: 12, // 最大行数,超过会自动出现滚动条
  179 + minLines: 12, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
  180 + fontSize: 14, // 编辑器内字体大小
  181 + theme: 'ace/theme/chrome', // 默认设置的主题
  182 + mode: 'ace/mode/javascript', // 默认设置的语言模式
  183 + tabSize: 2, // 制表符设置为 4 个空格大小
  184 + });
  185 +
  186 + aceEditor.value.setOptions({
  187 + enableBasicAutocompletion: true,
  188 + enableLiveAutocompletion: true,
  189 + theme: getAceClass.value === 'dark' ? 'ace/theme/terminal' : 'ace/theme/chrome',
  190 + });
  191 + aceEditor.value.setValue('');
  192 + beautify(aceEditor.value.session);
  193 + switchScriptTypeGetContent('TRANSPORT_TCP_UP');
  194 + };
  195 +
  196 + const handleScriptType = ({ target }) => {
  197 + const { value } = target;
  198 + switchScriptTypeGetContent(value);
  199 + };
  200 +
  201 + const switchScriptTypeGetContent = (type) => {
  202 + aceEditor.value.setValue(defaultScriptTypeContent[type]);
  203 + };
  204 +
  205 + const handleCopy = () => {
  206 + const valueRef = aceEditor.value.getValue();
  207 + const value = unref(valueRef);
  208 + if (!value) {
  209 + createMessage.warning('请输入要拷贝的内容!');
  210 + return;
  211 + }
  212 + clipboardRef.value = value;
  213 + if (unref(copiedRef)) {
  214 + createMessage.success('复制成功!');
  215 + }
  216 + };
  217 +
  218 + const formRef = ref();
  219 +
  220 + const getFormData = async () => {
  221 + const value = await formRef.value.validateFields();
  222 + if (props.ifAdd) {
  223 + scriptForm.convertJs = aceEditor.value.getValue();
  224 + if (scriptForm.convertJs == '') {
  225 + createMessage.error('请编写脚本内容');
  226 + throw '请编写脚本内容';
  227 + }
  228 + } else {
  229 + scriptForm.script = aceEditor.value.getValue();
  230 + if (scriptForm.script == '') {
  231 + createMessage.error('请编写脚本内容');
  232 + throw '请编写脚本内容';
  233 + }
  234 + }
  235 + if (!value) return;
  236 + if (scriptForm.params) {
  237 + const trimParams = scriptForm.params.replace(/\s*/g, '');
  238 + Reflect.set(value, 'params', trimParams);
  239 + }
  240 + if (scriptForm.convertJs.length > 1000) {
  241 + createMessage.error('脚本内容长度不能大于1000');
  242 + throw '脚本内容长度不能大于1000';
  243 + }
  244 + return {
  245 + ...value,
  246 + ...{ convertJs: props.ifAdd ? scriptForm.convertJs : null },
  247 + ...{ script: !props.ifAdd ? scriptForm.script : null },
  248 + ...{ saveOriginalData: scriptForm.saveOriginalData === 'false' ? false : true },
  249 + };
  250 + };
  251 +
  252 + const handleInputChange = (e) => {
  253 + const trimParams = e.target.value.replace(/\s*/g, '');
  254 + Reflect.set(scriptForm, 'params', trimParams);
  255 + if (scriptForm.scriptType === 'TRANSPORT_TCP_DOWN') {
  256 + aceEditor.value.setValue(`out.datas = "${scriptForm.params}";out.deviceName = "sensor";`);
  257 + }
  258 + };
  259 +
  260 + const getRecordId = ref('');
  261 +
  262 + const setFormData = (v) => {
  263 + if (v) {
  264 + getRecordId.value = v?.id;
  265 + for (let i in scriptForm) {
  266 + Reflect.set(scriptForm, i, v[i]);
  267 + }
  268 + nextTick(() => {
  269 + setTimeout(() => {
  270 + scriptForm.saveOriginalData = v.saveOriginalData === false ? 'false' : 'true';
  271 + }, 10);
  272 + });
  273 + aceEditor.value.setValue(v.convertJs);
  274 + handleFormat();
  275 + }
  276 + };
  277 +
  278 + const setScriptContentData = (v) => {
  279 + aceEditor.value.setValue(v);
  280 + handleFormat();
  281 + };
  282 +
  283 + const resetFormData = () => {
  284 + for (let i in scriptForm) {
  285 + Reflect.set(scriptForm, i, '');
  286 + }
  287 + };
  288 +
  289 + const setScriptOutputData = (v) => {
  290 + scriptForm.output = v;
  291 + };
  292 +
  293 + const handleFormat = () => {
  294 + beautify(aceEditor.value.session);
  295 + aceEditor.value.getSession().setUseWrapMode(true);
  296 + };
  297 +
  298 + const [registerModal, { openModal }] = useModal();
  299 +
  300 + const onHandleClick = (o) => {
  301 + openModal(true, {
  302 + isAuth: '',
  303 + isUpdate: false,
  304 + record: o === 'add' ? null : getRecordId.value,
  305 + isTest: true,
  306 + isText: 'test',
  307 + isTitle: 'test',
  308 + });
  309 + };
  310 +
  311 + defineExpose({
  312 + initEditor,
  313 + getFormData,
  314 + resetFormData,
  315 + setFormData,
  316 + setScriptContentData,
  317 + setScriptOutputData,
  318 + setDefaultRadio,
  319 + });
  320 +</script>
  321 +<style lang="less" scoped>
  322 + @import url('./ConverScriptModal.less');
  323 +</style>
... ...
... ... @@ -10,7 +10,12 @@
10 10 @cancel="handleCancel"
11 11 @ok="handleSubmit"
12 12 >
13   - <ConverScript :ifAdd="isTest ? false : true" ref="converScriptRef" />
  13 + <ConverScript
  14 + :view="isViewDetail"
  15 + :text="isTitle"
  16 + :ifAdd="isTest ? false : true"
  17 + ref="converScriptRef"
  18 + />
14 19 </BasicModal>
15 20 </div>
16 21 </template>
... ...
  1 +<template>
  2 + <div>
  3 + <BasicModal
  4 + destroyOnClose
  5 + v-bind="$attrs"
  6 + width="60rem"
  7 + @register="register"
  8 + :title="getTitle"
  9 + :minHeight="500"
  10 + @cancel="handleCancel"
  11 + @ok="handleSubmit"
  12 + >
  13 + <ConverScript
  14 + :view="isViewDetail"
  15 + :text="isTitle"
  16 + :ifAdd="isTest ? false : true"
  17 + ref="converScriptRef"
  18 + />
  19 + </BasicModal>
  20 + </div>
  21 +</template>
  22 +<script setup lang="ts">
  23 + import { ref, computed, unref, reactive } from 'vue';
  24 + import { BasicModal, useModalInner } from '/@/components/Modal';
  25 + import ConverScript from './ConverScript.vue';
  26 + import {
  27 + createOrEditScriptManage,
  28 + getScriptManageDetail,
  29 + testScriptManage,
  30 + } from '/@/api/scriptmanage/scriptManager';
  31 + import { useMessage } from '/@/hooks/web/useMessage';
  32 +
  33 + const emits = defineEmits(['success', 'register']);
  34 + const { createMessage } = useMessage();
  35 + const converScriptRef = ref<InstanceType<typeof ConverScript>>();
  36 + const getTitle = computed(() => (isUpdate.value ? '编辑转换脚本' : '新增转换脚本'));
  37 + const isUpdate = ref(false);
  38 + const isViewDetail = ref('');
  39 + const isTest = ref(false);
  40 + const isText = ref('');
  41 + const isTitle = ref('');
  42 + const editData = reactive({
  43 + data: {},
  44 + });
  45 + const [register, { setModalProps, closeModal }] = useModalInner(async (data) => {
  46 + setModalProps({ loading: true });
  47 + handleCancel(false);
  48 + isUpdate.value = data.isUpdate;
  49 + isViewDetail.value = data.isView;
  50 + isTest.value = data.isTest;
  51 + isText.value = data.isText;
  52 + isTitle.value = data.isTitle;
  53 + editData.data = data.record;
  54 + setModalProps({ loading: false });
  55 + converScriptRef.value?.initEditor();
  56 + if (!unref(isViewDetail)) {
  57 + const title =
  58 + unref(isTitle) == 'edit'
  59 + ? '编辑转换脚本'
  60 + : unref(isTitle) == 'add'
  61 + ? '新增转换脚本'
  62 + : '测试转换脚本';
  63 + const okText = isText.value == 'test' ? '测试' : '确定';
  64 + if (unref(isTitle) == 'add') {
  65 + converScriptRef.value?.setDefaultRadio('true', 'TRANSPORT_TCP_UP');
  66 + }
  67 + if (unref(isTitle) == 'edit') {
  68 + converScriptRef.value?.setFormData(data.record);
  69 + }
  70 + if (unref(isTitle) == 'test') {
  71 + if (data.record) {
  72 + const res = await getScriptManageDetail(data.record);
  73 + converScriptRef.value?.setFormData(res);
  74 + } else {
  75 + converScriptRef.value?.setDefaultRadio('true', 'TRANSPORT_TCP_UP');
  76 + }
  77 + }
  78 + setModalProps({ title, showOkBtn: true, showCancelBtn: true, okText });
  79 + if (!unref(isUpdate)) {
  80 + }
  81 + } else {
  82 + setModalProps({ showOkBtn: false, showCancelBtn: false, title: '查看转换脚本' });
  83 + const res = await getScriptManageDetail(data.record.id);
  84 + converScriptRef.value?.setFormData(res || {});
  85 + }
  86 + });
  87 +
  88 + const handleSubmit = async () => {
  89 + setModalProps({ confirmLoading: true });
  90 + try {
  91 + const val = await converScriptRef.value?.getFormData();
  92 + const tempObj = {
  93 + ...editData.data,
  94 + ...val,
  95 + };
  96 + const res: any =
  97 + isText.value == 'test'
  98 + ? await testScriptManage(val)
  99 + : await createOrEditScriptManage(tempObj);
  100 + createMessage.success(
  101 + unref(isTitle) == 'edit'
  102 + ? '编辑转换脚本成功'
  103 + : unref(isTitle) == 'add'
  104 + ? '新增转换脚本成功'
  105 + : '测试转换脚本成功'
  106 + );
  107 + if (unref(isTitle) == 'add' || unref(isTitle) == 'edit') {
  108 + setTimeout(() => {
  109 + closeModal();
  110 + }, 10);
  111 + emits('success', {
  112 + res,
  113 + text: isText.value,
  114 + });
  115 + } else {
  116 + if (res) {
  117 + converScriptRef.value?.setScriptOutputData(res?.output || res?.error);
  118 + }
  119 + }
  120 + } finally {
  121 + setModalProps({ confirmLoading: false });
  122 + }
  123 + };
  124 + const handleCancel = (flag) => {
  125 + if (flag) {
  126 + closeModal();
  127 + }
  128 + converScriptRef.value?.resetFormData();
  129 + };
  130 +</script>
  131 +<style lang="less" scoped>
  132 + @import url('./ConverScriptModal.less');
  133 +</style>
... ...
... ... @@ -58,25 +58,30 @@ export const searchFormSchema: FormSchema[] = [
58 58 },
59 59 ];
60 60
61   -export const defaultTitle = h('div', { style: 'background:#404040' }, [
62   - h('h3', { style: 'color:white' }, '示例'),
63   - h('h3', { style: 'color:white' }, '输入参数:'),
64   - h('h3', { style: 'color:white' }, '0103040150008D3BBB'),
65   - h('h3', { style: 'color:white' }, [
66   - h('h3', { style: 'color:white' }, '脚本内容:'),
67   - h(
68   - 'h3',
69   - { style: 'color:white' },
70   - "out.humidity = (parseInt('0x'+params.substr(6, 4))*0.1).toFixed(2);"
71   - ),
72   - h(
73   - 'h3',
74   - { style: 'color:white' },
75   - "out.temperature = (parseInt('0x'+params.substr(10, 4))*0.1).toFixed(2);"
76   - ),
77   - h('h3', { style: 'color:white' }, '输出参数:'),
78   - h('h3', { style: 'color:white' }, "{'humidity':'33.60','temperature':'14.10'}"),
79   - ]),
  61 +export const defaultAuthTitle = h('div', { style: 'background:#404040' }, [
  62 + h('h3', { style: 'color:white' }, '设备鉴权示例'),
  63 + h('h3', { style: 'color:white' }, '输入参数:为16进制字符串'),
  64 + h('h3', { style: 'color:white' }, '输出参数:{"password":"","success":""}'),
  65 + h('h3', { style: 'color:white' }, 'password为设备鉴权信息,success为鉴权成功后响应给设备的内容'),
  66 +]);
  67 +
  68 +export const defaultUpTitle = h('div', { style: 'background:#404040' }, [
  69 + h('h3', { style: 'color:white' }, '上行数据解析示例'),
  70 + h('h3', { style: 'color:white' }, '输入参数:为字符串'),
  71 + h(
  72 + 'h3',
  73 + { style: 'color:white' },
  74 + `输出参数:{"datas":{"source":""},"telemetry":true,"ackMsg":"","deviceName":"","ts":1681701034289}`
  75 + ),
  76 + h(
  77 + 'h3',
  78 + { style: 'color:white' },
  79 + `datas:json对象,属性名为遥测指标或子设备名称
  80 + telemetry: datas内容是否为遥测数据
  81 + ackMsg: 响应给设备的确认消息
  82 + deviceName: 设备名称
  83 + ts: 数据采集时间`
  84 + ),
80 85 ]);
81 86
82 87 // TRANSPORT_TCP_DOWN: 'out.datas = "";out.deviceName = "sensor";',
... ...
... ... @@ -273,6 +273,8 @@
273 273 status: searchInfo.status,
274 274 page: pagination.current,
275 275 pageSize: pagination.pageSize,
  276 + startTime: moment(searchInfo.sendTime.at(-2)).valueOf(),
  277 + endTime: moment(searchInfo.sendTime.at(-1)).valueOf(),
276 278 });
277 279 setTableData(res.items);
278 280 pagination.total = res.total;
... ...