Commit bb5e1357d57affa88d6e522b90ca20c2cd2c26d5

Authored by xp.Huang
1 parent 3f8739fd

Merge branch 'local_dev_ft' into 'main_dev'

feat:大屏设计器新增restful风格测试

See merge request yunteng/thingskit-front!517

(cherry picked from commit ac332261)

9fd79720 wip:大屏设计器测试开发中
4d97259c wip:测试接口开发中
6a7475d9 feat:大屏公共接口管理测试新增支持restful风格的接口测试
2af3fe42 feat:大屏公共接口管理测试新增支持restful风格的接口测试
... ... @@ -13,10 +13,10 @@
13 13 </thead>
14 14 <tbody>
15 15 <tr v-for="(item, index) in tableArray.content" :key="index">
16   - <td>
  16 + <td style="width: 1vw">
17 17 {{ index + 1 }}
18 18 </td>
19   - <td>
  19 + <td style="width: 12vw">
20 20 <Select
21 21 v-model:value="item.key"
22 22 placeholder="请选择"
... ... @@ -25,17 +25,17 @@
25 25 allowClear
26 26 />
27 27 </td>
28   - <td>
  28 + <td style="width: 12vw">
29 29 <a-input
30 30 :disabled="item.editDisabled"
31 31 placeholder="请输入"
32 32 v-model:value="item.value"
33 33 />
34 34 </td>
35   - <td>
  35 + <td style="width: 4vw">
36 36 <a-switch v-model:checked="item.required" />
37 37 </td>
38   - <td>
  38 + <td style="width: 2vw">
39 39 <div>
40 40 <Button type="dashed" @click="add(item, index)">
41 41 <template #icon><PlusOutlined /></template
... ... @@ -189,12 +189,11 @@
189 189
190 190 table {
191 191 border-collapse: collapse;
192   - width: 100%;
  192 + width: 35vw;
193 193 &:extend(.table-border-color);
194 194 }
195 195
196 196 table td {
197   - width: 7vw;
198 197 padding: 5px;
199 198 white-space: nowrap;
200 199 &:extend(.table-border-color);
... ...
... ... @@ -7,7 +7,9 @@
7 7 @testInterface="handleTestInterface"
8 8 ref="testParRequestRef"
9 9 :method="method"
10   - :data="dataList"
  10 + :requestOriginUrl="requestOriginUrl"
  11 + :requestUrl="requestUrl"
  12 + :data="dataMap.mapObj"
11 13 />
12 14 </TabPane>
13 15 <TabPane
... ... @@ -17,12 +19,14 @@
17 19 key="Body"
18 20 tab="Body"
19 21 >
20   - <Body ref="bodyRef" :method="method" :paramsType="paramsType" :data="dataList" />
  22 + <Body ref="bodyRef" :method="method" :paramsType="paramsType" :data="dataMap.mapObj" />
21 23 <TestRequest
22 24 @testInterface="handleTestInterface"
23 25 ref="testBodyRequestRef"
24 26 :method="method"
25   - :data="dataList"
  27 + :requestOriginUrl="requestOriginUrl"
  28 + :requestUrl="requestUrl"
  29 + :data="dataMap.mapObj"
26 30 />
27 31 </TabPane>
28 32 <TabPane v-if="method !== '2'" class="tab-pane" forceRender key="Header" tab="Header">
... ... @@ -31,26 +35,34 @@
31 35 @testInterface="handleTestInterface"
32 36 ref="testHeaderRequestRef"
33 37 :method="method"
34   - :data="dataList"
  38 + :requestOriginUrl="requestOriginUrl"
  39 + :requestUrl="requestUrl"
  40 + :data="dataMap.mapObj"
35 41 />
36 42 </TabPane>
37 43 </Tabs>
38 44 </div>
39 45 </template>
40 46 <script lang="ts" setup name="simpleRequest">
41   - import { ref, nextTick } from 'vue';
  47 + import { ref, nextTick, reactive } from 'vue';
42 48 import { Tabs, TabPane } from 'ant-design-vue';
43 49 import { EditCellTable } from '../EditCellTable';
44 50 import Body from './body.vue';
45 51 import { TestRequest } from '../TestRequest/index';
46 52
47   - defineProps({
  53 + const props = defineProps({
48 54 method: {
49 55 type: String,
50 56 },
51 57 paramsType: {
52 58 type: String,
53 59 },
  60 + requestOriginUrl: {
  61 + type: String,
  62 + },
  63 + requestUrl: {
  64 + type: String,
  65 + },
54 66 });
55 67
56 68 const emits = defineEmits(['activeKey']);
... ... @@ -67,7 +79,9 @@
67 79
68 80 const testHeaderRequestRef = ref<InstanceType<typeof TestRequest>>();
69 81
70   - const dataList: any = ref(null);
  82 + const dataMap: any = reactive({
  83 + mapObj: {},
  84 + });
71 85
72 86 const bodyRef = ref<InstanceType<typeof Body>>();
73 87
... ... @@ -78,8 +92,20 @@
78 92 };
79 93
80 94 const handleTestInterface = () => {
81   - const value = getValue(false);
82   - nextTick(() => (dataList.value = value));
  95 + let value = getValue(false);
  96 + const type = value?.requestParamsBodyType;
  97 + if (type === 'none') value = [];
  98 + if (type === 'form-data') value = value['form-data'];
  99 + if (type === 'x-www-form-urlencoded') value = value['x-www-form-urlencoded'];
  100 + nextTick(
  101 + () =>
  102 + (dataMap.mapObj = {
  103 + list: value,
  104 + requestOriginUrl: props.requestOriginUrl,
  105 + requestUrl: props.requestUrl,
  106 + paramsType: props.paramsType,
  107 + })
  108 + );
83 109 };
84 110
85 111 //获取数据
... ...
1 1 <template>
2 2 <div class="table-content">
3   - <table align="center" style="width: 100%" cellspacing="0">
  3 + <table align="center">
4 4 <thead>
5 5 <tr>
6 6 <th></th>
7 7 <th>内置参数</th>
8 8 <th>参数名</th>
  9 + <th>参数值</th>
9 10 </tr>
10 11 </thead>
11 12 <tbody>
12 13 <tr v-for="(item, index) in tableTestArray.content" :key="index">
13   - <td>
  14 + <td style="width: 1vw">
14 15 {{ index + 1 }}
15 16 </td>
16   - <td>
  17 + <td style="width: 12vw">
17 18 <Select
18 19 :disabled="true"
19 20 v-model:value="item.key"
20 21 placeholder="请选择"
21 22 notFoundContent="请选择"
22   - style="width: 14rem"
23 23 :options="selectOptions"
24   - @change="handleChange(item)"
25 24 allowClear
26 25 />
27 26 </td>
28   - <td>
29   - <a-input
30   - v-if="item.key === 'scope'"
31   - placeholder="请输入"
32   - v-model:value="item.value"
33   - style="width: 14rem"
34   - />
  27 + <td style="width: 12vw">
  28 + <a-input v-if="item.key === 'scope'" placeholder="请输入" v-model:value="item.value" />
35 29 <a-tree-select
36 30 v-else-if="item.key === 'organizationId'"
37 31 v-model:value="item.value"
38 32 show-search
39   - style="width: 14rem"
40 33 :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
41 34 placeholder="请选择组织"
42 35 allow-clear
... ... @@ -49,30 +42,44 @@
49 42 v-model:value="item.value"
50 43 placeholder="请选择"
51 44 notFoundContent="请选择"
52   - style="width: 14rem"
53 45 :options="entityOptions"
54 46 allowClear
  47 + @change="handleDeviceChange(item)"
55 48 />
56 49 <Select
57 50 v-else-if="item.key === 'keys'"
58 51 v-model:value="item.value"
59 52 placeholder="请选择"
60 53 notFoundContent="请选择"
61   - style="width: 14rem"
62 54 :options="attributeOptions"
63 55 allowClear
64 56 />
  57 + <a-range-picker
  58 + v-else-if="item.key === 'date'"
  59 + v-model:value="item.value"
  60 + style="width: 12vw"
  61 + :show-time="{ format: 'HH:mm' }"
  62 + format="YYYY-MM-DD HH:mm"
  63 + :placeholder="['开始', '结束']"
  64 + @ok="onRangeOk"
  65 + />
65 66 <Select
66 67 v-else
67 68 v-model:value="item.value"
68 69 placeholder="请选择"
69 70 notFoundContent="请选择"
70   - style="width: 14rem"
71 71 :options="valueOptions"
72 72 allowClear
73 73 @change="handleValueChange(item)"
74 74 />
75 75 </td>
  76 + <td style="width: 12vw">
  77 + <a-input
  78 + :disabled="item.key !== 'scope'"
  79 + placeholder="请输入"
  80 + v-model:value="item.keyValue"
  81 + />
  82 + </td>
76 83 </tr>
77 84 </tbody>
78 85 </table>
... ... @@ -117,6 +124,7 @@
117 124 type defaultItem = {
118 125 key: null | string;
119 126 value: null | string;
  127 + keyValue: null | string;
120 128 editDisabled: boolean;
121 129 };
122 130
... ... @@ -127,6 +135,7 @@
127 135 {
128 136 key: null,
129 137 value: null,
  138 + keyValue: null,
130 139 editDisabled: false,
131 140 },
132 141 ],
... ... @@ -146,6 +155,10 @@
146 155 getApi(list['x-www-form-urlencoded']);
147 156 };
148 157
  158 + const onRangeOk = (value) => {
  159 + console.log('onOk: ', value);
  160 + };
  161 +
149 162 const getApi = (list) => {
150 163 list?.forEach(async (it) => {
151 164 if (it.key === 'deviceProfileId') {
... ... @@ -158,58 +171,15 @@
158 171 });
159 172 };
160 173
161   - //Select互斥
162   - const handleChange = async (e) => {
163   - selectOptions.value.forEach((ele) => {
164   - ele.disabled = false;
165   - tableTestArray.content.forEach((element) => {
166   - if (element.key === e.key && element.key !== 'scope') {
167   - element.value = null;
168   - }
169   - if (element.key === ele.value && element.key !== 'scope') {
170   - ele.disabled = true;
171   - }
172   - });
173   - });
174   - //获取对应项
175   - if (e.key === 'deviceProfileId') {
176   - const { options } = await useApi(e.key);
177   - valueOptions.value = options;
178   - } else if (e.key === 'organizationId') {
179   - const { options } = await useApi(e.key);
180   - treeData.value = options as any;
181   - } else if (e.key === 'entityId') {
182   - const getOrganizationIds = tableTestArray.content
183   - .map((f) => {
184   - if (f.key === 'organizationId') {
185   - return f.value;
186   - }
187   - })
188   - .filter(Boolean);
189   - if (getOrganizationIds.length === 0) return;
190   - getEntityOptions(getOrganizationIds?.at(-1));
191   - } else if (e.key === 'keys') {
192   - const getIds = tableTestArray.content
193   - .map((f) => {
194   - if (f.key === 'deviceProfileId') {
195   - return f.value;
196   - }
197   - })
198   - .filter(Boolean);
199   - if (getIds.length === 0) return;
200   - getAttributeOptions({ deviceProfileId: getIds?.at(-1) });
201   - } else if (e.key === 'date') {
202   - valueOptions.value = [];
203   - }
204   - };
205   -
206 174 const handleOrgnationChange = async (e) => {
  175 + let deviceProfileId = '';
207 176 tableTestArray.content.forEach((f) => {
208 177 if (f.key === 'entityId') {
209 178 f.value = null;
210 179 }
  180 + if (f.key === 'deviceProfileId') deviceProfileId = f.value;
211 181 });
212   - getEntityOptions(e.value);
  182 + getEntityOptions(e.value, deviceProfileId);
213 183 };
214 184
215 185 const getEntityOptions = async (organizationId: string, deviceProfileId?: string) => {
... ... @@ -227,16 +197,26 @@
227 197 };
228 198
229 199 const handleValueChange = (e) => {
  200 + let organizationId = '';
230 201 if (e.key === 'deviceProfileId') {
231 202 tableTestArray.content.forEach((f) => {
232 203 if (f.key === 'keys') {
233 204 f.value = null;
234 205 }
  206 + if (f.key === 'organizationId') organizationId = f.value;
  207 + if (f.key === 'entityId') f.value = null;
235 208 });
236 209 getAttributeOptions({ deviceProfileId: e.value });
  210 + if (organizationId !== '') {
  211 + getEntityOptions(organizationId, e.value);
  212 + }
237 213 }
238 214 };
239 215
  216 + const handleDeviceChange = (e) => {
  217 + console.log(e);
  218 + };
  219 +
240 220 //获取数据
241 221 const getValue = () => {
242 222 return tableTestArray.content;
... ... @@ -248,6 +228,9 @@
248 228 </script>
249 229
250 230 <style scoped lang="less">
  231 + :deep(.ant-select-selector) {
  232 + width: 12vw !important;
  233 + }
251 234 @table-color: #e5e7eb;
252 235
253 236 .table-border-color {
... ... @@ -257,11 +240,11 @@
257 240
258 241 .table-content {
259 242 table {
  243 + width: 31vw;
260 244 &:extend(.table-border-color);
261 245 }
262 246
263 247 table td {
264   - width: 7vw;
265 248 padding: 5px;
266 249 white-space: nowrap;
267 250 &:extend(.table-border-color);
... ...
... ... @@ -35,11 +35,16 @@
35 35 </div>
36 36 </template>
37 37 <script lang="ts" setup name="testRequest">
38   - import { ref, nextTick } from 'vue';
  38 + import { ref, nextTick, reactive } from 'vue';
39 39 import { Button } from 'ant-design-vue';
40 40 import { TestEditCellTable } from '../TestEditCellTable/index';
41   - import { getAllDeviceByOrg } from '/@/api/dataBoard';
42   - import { getDeviceAttributes } from '/@/api/dataBoard';
  41 + import { otherHttp } from '/@/utils/http/axios';
  42 + import moment from 'moment';
  43 + import { useWebSocket } from '@vueuse/core';
  44 + import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum';
  45 + import { getAuthCache } from '/@/utils/auth';
  46 + import { useMessage } from '/@/hooks/web/useMessage';
  47 + import { useGlobSetting } from '/@/hooks/setting';
43 48
44 49 const emits = defineEmits(['testInterface']);
45 50
... ... @@ -48,7 +53,20 @@
48 53 type: String,
49 54 },
50 55 data: {
51   - type: Array,
  56 + type: Object,
  57 + },
  58 + });
  59 +
  60 + const { createMessage } = useMessage();
  61 +
  62 + const token = getAuthCache(JWT_TOKEN_KEY);
  63 +
  64 + const { socketUrl } = useGlobSetting();
  65 +
  66 + const socketMessage = reactive({
  67 + server: `${socketUrl}${token}`,
  68 + sendValue: {
  69 + tsSubCmds: [],
52 70 },
53 71 });
54 72
... ... @@ -66,26 +84,147 @@
66 84 getValue();
67 85 };
68 86
  87 + //获取多个key
  88 + const getMultipleKeys = (list) => {
  89 + let temps = [];
  90 + list?.forEach((it) => {
  91 + const keys = it.key.split(',');
  92 + const temp = keys.map((item) => {
  93 + let obj: { key: string; value: string } = { key: '', value: '' };
  94 + obj.key = item;
  95 + obj.value = item === 'scope' ? it.value : '';
  96 + return obj;
  97 + });
  98 + temps = temp;
  99 + });
  100 + return temps;
  101 + };
  102 +
  103 + //获取测试表格的key value 数组对象形式
  104 + const getTestTableKeyValue = () => {
  105 + return testEditCellTableRef.value?.getValue().map((it) => {
  106 + const value = it.key === 'scope' ? it.keyValue : it.value;
  107 + const key = it.key === 'scope' ? it.value : it.key;
  108 + return {
  109 + key,
  110 + value,
  111 + };
  112 + });
  113 + };
  114 +
  115 + //格式化"http:xxxx/api/xx/{xx}/{xx}/{xx}这种格式"
  116 + String.prototype.restfulFormat = function (replacements) {
  117 + var formatString = function (str, replacements) {
  118 + replacements =
  119 + typeof replacements === 'object' ? replacements : Array.prototype.slice.call(arguments, 1);
  120 + return str.replace(/\{\{|\}\}|\{(\w+)\}/g, function (m, n) {
  121 + if (m == '{{') {
  122 + return '{';
  123 + }
  124 + if (m == '}}') {
  125 + return '}';
  126 + }
  127 + return replacements[n];
  128 + });
  129 + };
  130 + replacements =
  131 + typeof replacements === 'object' ? replacements : Array.prototype.slice.call(arguments, 0);
  132 + return formatString(this, replacements);
  133 + };
  134 +
  135 + //TODO 待优化 项目自带第三方请求
  136 + const otherHttpRequest = async (apiType, params = {}, api, joinPrefix = false) => {
  137 + switch (apiType) {
  138 + case 'get':
  139 + Reflect.deleteProperty(params, 'deviceProfileId');
  140 + Reflect.deleteProperty(params, 'organizationId');
  141 + Reflect.deleteProperty(params, 'entityId');
  142 + Reflect.deleteProperty(params, 'entityType');
  143 + Reflect.deleteProperty(params, 'scope');
  144 + Reflect.deleteProperty(params, 'id');
  145 + Reflect.deleteProperty(params, 'type');
  146 + if (params['date']) {
  147 + Reflect.set(params, 'startTs', moment(params['date'][0]).valueOf());
  148 + Reflect.set(params, 'endTs', moment(params['date'][1]).valueOf());
  149 + Reflect.deleteProperty(params, 'date');
  150 + } else {
  151 + }
  152 + return await otherHttp.get(
  153 + { url: api, params },
  154 + {
  155 + apiUrl: '',
  156 + joinPrefix,
  157 + }
  158 + );
  159 + case 'post':
  160 + return await otherHttp.post(
  161 + { url: api, data: params },
  162 + {
  163 + apiUrl: '',
  164 + joinPrefix,
  165 + }
  166 + );
  167 + case 'put':
  168 + return await otherHttp.put(
  169 + { url: api, data: params },
  170 + {
  171 + apiUrl: '',
  172 + joinPrefix,
  173 + }
  174 + );
  175 + }
  176 + };
  177 +
69 178 const getValue = async () => {
70 179 await nextTick();
71 180 await nextTick(() => {
72   - testEditCellTableRef.value?.setTableArray(props.data);
  181 + const getSingleKey = props.data?.list;
  182 + const getMuteKey = props.data?.list?.filter((it: any) => it.key.split(',').length > 1);
  183 + const getMuteKeys = getMultipleKeys(getMuteKey);
  184 + const mergeKeys = [...getSingleKey, ...getMuteKeys]?.filter(
  185 + (it: any) => it.key.split(',').length === 1
  186 + );
  187 + testEditCellTableRef.value?.setTableArray(mergeKeys);
73 188 });
74 189 };
75 190
  191 + //执行测试接口
76 192 const handleExcute = async () => {
77   - const params = testEditCellTableRef.value?.getValue();
78   - const keys = params?.map((m) => ({ key: m.key, value: m.value }));
79   - keys?.forEach(async (it: any) => {
80   - if (it.key === 'organizationId') {
81   - //获取设备
82   - const data = await getAllDeviceByOrg(it.value!);
83   - testResult.value = JSON.stringify(data);
84   - }
85   - if (it.key === 'deviceProfileId') {
86   - //获取属性
87   - const data = await getDeviceAttributes({ deviceProfileId: it.value! });
88   - testResult.value = JSON.stringify(data);
  193 + await nextTick();
  194 + await nextTick(async () => {
  195 + const getTable = getTestTableKeyValue();
  196 + const apiGetUrl = `${props.data?.requestOriginUrl}${props.data?.requestUrl}`;
  197 + const apiType = props.data?.paramsType.toLowerCase();
  198 + const params: any = {};
  199 + getTable?.map((it) => (params[it.key!] = it.value!));
  200 + if (props.method === '0') {
  201 + //普通请求
  202 + const formatApi = apiGetUrl.restfulFormat(params);
  203 + const rest = await otherHttpRequest(apiType, params, formatApi.split('{?')[0], false);
  204 + testResult.value = JSON.stringify(rest);
  205 + } else if (props.method === '2') {
  206 + //websocket请求
  207 + Reflect.deleteProperty(params, 'deviceProfileId');
  208 + Reflect.deleteProperty(params, 'organizationId');
  209 + Reflect.set(params, 'cmdId', Number(params?.cmdId));
  210 + socketMessage.sendValue.tsSubCmds.push(params);
  211 + const { send, close } = useWebSocket(socketMessage.server, {
  212 + onConnected() {
  213 + send(JSON.stringify(socketMessage.sendValue));
  214 + console.log('建立连接了');
  215 + },
  216 + onMessage(_, e) {
  217 + const { data } = JSON.parse(e.data);
  218 + testResult.value = JSON.stringify(data);
  219 + },
  220 + onDisconnected() {
  221 + console.log('断开连接了');
  222 + close();
  223 + },
  224 + onError() {
  225 + createMessage.error('webSocket连接超时,请联系管理员');
  226 + },
  227 + });
89 228 }
90 229 });
91 230 };
... ... @@ -95,6 +234,7 @@
95 234 showTestEditCell.value = false;
96 235 testResult.value = '';
97 236 };
  237 +
98 238 defineExpose({
99 239 setValue,
100 240 handleTest,
... ...
... ... @@ -124,7 +124,7 @@ export const schemas: FormSchema[] = [
124 124 required: true,
125 125 component: 'Input',
126 126 componentProps: {
127   - maxLength: 64,
  127 + maxLength: 255,
128 128 placeholder: '请输入源地址',
129 129 },
130 130 },
... ... @@ -154,7 +154,7 @@ export const schemas: FormSchema[] = [
154 154 required: true,
155 155 colProps: { span: 18 },
156 156 componentProps: {
157   - maxLength: 64,
  157 + maxLength: 255,
158 158 placeholder: '请输入接口地址',
159 159 },
160 160 ifShow: ({ values }) =>
... ...
... ... @@ -15,6 +15,8 @@
15 15 @activeKey="handleActiveKey"
16 16 :paramsType="model['requestHttpType']"
17 17 :method="model['requestContentType']"
  18 + :requestOriginUrl="model['requestOriginUrl']"
  19 + :requestUrl="model['requestUrl']"
18 20 />
19 21 <SimpleRequest
20 22 ref="simpleRequestRef"
... ... @@ -22,6 +24,8 @@
22 24 v-else-if="model['requestContentType'] === '2'"
23 25 :paramsType="model['requestHttpType']"
24 26 :method="model['requestContentType']"
  27 + :requestOriginUrl="model['requestOriginUrl']"
  28 + :requestUrl="model['requestUrl']"
25 29 />
26 30 </template>
27 31 <template #testSql="{ model }">
... ...
... ... @@ -52,6 +52,10 @@ declare global {
52 52 declare interface WheelEvent {
53 53 path?: EventTarget[];
54 54 }
  55 +
  56 + declare interface String {
  57 + restfulFormat: any;
  58 + }
55 59 interface ImportMetaEnv extends ViteEnv {
56 60 __: unknown;
57 61 }
... ...