feat(接口定义): 完成提取参数和断言规则
This commit is contained in:
parent
595c9572de
commit
7f2fe8acd1
|
@ -220,6 +220,7 @@ public class ApiDefinitionService {
|
|||
createBodyFiles(bodyUploadIds, bodyFiles);
|
||||
|
||||
HashTree hashTree = request.getTestElement().generateHashTree();
|
||||
request.getTestElement().getJmx(hashTree);
|
||||
// 调用执行方法
|
||||
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.DELIMIT.name());
|
||||
return request.getId();
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 24047fea950a74f7848a9fdaa857a22b884c4ce2
|
||||
Subproject commit 419c75bca64b7c5bfbd1194d7f0fd9919f0caa04
|
|
@ -167,7 +167,6 @@ check_owner_comment=The current user does not have permission to manipulate this
|
|||
upload_content_is_null=Imported content is empty
|
||||
test_plan_notification=Test plan notification
|
||||
task_defect_notification=Task defect notification
|
||||
task_notification=Jenkins Task notification
|
||||
task_notification_=Timing task result notification
|
||||
api_definition_url_not_repeating=The interface request address already exists
|
||||
task_notification_jenkins=Jenkins Task notification
|
||||
|
|
|
@ -168,7 +168,6 @@ check_owner_comment=当前用户没有操作此评论的权限
|
|||
upload_content_is_null=导入内容为空
|
||||
test_plan_notification=测试计划通知
|
||||
task_defect_notification=缺陷任务通知
|
||||
task_notification=jenkins任务通知
|
||||
task_notification_=定时任务结果通知
|
||||
api_definition_url_not_repeating=接口请求地址已经存在
|
||||
task_notification_jenkins=jenkins任务通知
|
||||
|
|
|
@ -171,7 +171,5 @@ test_plan_notification=測試計畫通知
|
|||
task_defect_notification=缺陷任務通知
|
||||
task_notification_jenkins=jenkins任務通知
|
||||
task_notification=任務通知
|
||||
|
||||
task_notification=jenkins任務通知
|
||||
task_notification_=定時任務通知
|
||||
api_definition_url_not_repeating=接口請求地址已經存在
|
||||
|
|
|
@ -17,14 +17,12 @@
|
|||
|
||||
<script>
|
||||
|
||||
import {Duration} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionDuration",
|
||||
|
||||
props: {
|
||||
duration: Duration,
|
||||
value: [Number, String],
|
||||
duration:{},
|
||||
edit: Boolean,
|
||||
callback: Function,
|
||||
isReadOnly: {
|
||||
|
@ -42,6 +40,7 @@
|
|||
},
|
||||
remove() {
|
||||
this.duration.value = undefined;
|
||||
|
||||
},
|
||||
change(value) {
|
||||
if (this.validate()) {
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
props: {
|
||||
jsonPath: {
|
||||
type: JSONPath,
|
||||
default: () => {
|
||||
return new JSONPath();
|
||||
}
|
||||
|
|
|
@ -64,7 +64,6 @@ export default {
|
|||
components: {MsDialogFooter, MsJsr233Processor},
|
||||
props: {
|
||||
assertion: {
|
||||
type: AssertionJSR223,
|
||||
default: () => {
|
||||
return new AssertionJSR223();
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ export default {
|
|||
|
||||
props: {
|
||||
regex: {
|
||||
type: Regex,
|
||||
default: () => {
|
||||
return new Regex();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
props: {
|
||||
xPath2: {
|
||||
type: XPath2,
|
||||
default: () => {
|
||||
return new XPath2();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<template>
|
||||
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px">
|
||||
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px" v-loading="loading">
|
||||
<div>
|
||||
<el-button class="ms-left-buttion" size="small" type="danger" plain>{{$t('api_test.definition.request.assertions_rule')}}</el-button>
|
||||
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">移除</el-button>
|
||||
</div>
|
||||
<div class="assertion-add">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4">
|
||||
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
|
||||
:placeholder="$t('api_test.request.assertions.select_type')"
|
||||
size="small">
|
||||
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
||||
<!--
|
||||
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
||||
-->
|
||||
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
|
||||
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
|
||||
<el-option :label="'XPath'" :value="options.XPATH2"/>
|
||||
|
@ -15,8 +21,10 @@
|
|||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
|
||||
:callback="after"/>
|
||||
<!--
|
||||
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
|
||||
:callback="after"/>
|
||||
-->
|
||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
|
||||
:callback="after"/>
|
||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
|
||||
|
@ -77,7 +85,7 @@
|
|||
},
|
||||
|
||||
props: {
|
||||
assertions: Assertions,
|
||||
assertions: {},
|
||||
request: {},
|
||||
scenario: Scenario,
|
||||
isReadOnly: {
|
||||
|
@ -91,12 +99,14 @@
|
|||
options: ASSERTION_TYPE,
|
||||
time: "",
|
||||
type: "",
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
after() {
|
||||
this.type = "";
|
||||
this.reload();
|
||||
},
|
||||
suggestJsonOpen() {
|
||||
if (!this.request.debugRequestResult) {
|
||||
|
@ -105,6 +115,15 @@
|
|||
}
|
||||
this.$refs.jsonpathSuggestList.open();
|
||||
},
|
||||
reload() {
|
||||
this.loading = true
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
remove(){
|
||||
this.$emit('remove', this.assertions);
|
||||
},
|
||||
addJsonpathSuggest(jsonPathList) {
|
||||
jsonPathList.forEach(jsonPath => {
|
||||
let jsonItem = new JSONPath();
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
<script>
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {Assertions} from "../../model/ApiTestModel";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
|
@ -67,7 +66,7 @@ export default {
|
|||
MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex},
|
||||
|
||||
props: {
|
||||
assertions: Assertions,
|
||||
assertions: {},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
|
@ -50,64 +50,65 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {HttpRequest} from "../../model/ApiTestModel";
|
||||
export default {
|
||||
name: "MsApiJsonpathSuggestList",
|
||||
components: {MsDialogFooter},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
dialogFormVisible: false,
|
||||
isCheckAll: false,
|
||||
selectItems: new Set(),
|
||||
jsonPathList: [],
|
||||
};
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {HttpRequest} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiJsonpathSuggestList",
|
||||
components: {MsDialogFooter},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
dialogFormVisible: false,
|
||||
isCheckAll: false,
|
||||
selectItems: new Set(),
|
||||
jsonPathList: [],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
request: HttpRequest,
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.selectItems.clear();
|
||||
},
|
||||
props: {
|
||||
request: HttpRequest,
|
||||
open() {
|
||||
this.getJsonPaths();
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.selectItems.clear();
|
||||
},
|
||||
open() {
|
||||
this.getJsonPaths();
|
||||
},
|
||||
getJsonPaths() {
|
||||
if (this.request.debugRequestResult) {
|
||||
let param = {
|
||||
jsonPath: this.request.debugRequestResult.responseResult.body
|
||||
};
|
||||
this.result = this.$post("/api/getJsonPaths", param).then(response => {
|
||||
this.jsonPathList = response.data.data;
|
||||
this.dialogFormVisible = true;
|
||||
}).catch(() => {
|
||||
this.$warning(this.$t('api_test.request.assertions.json_path_err'));
|
||||
this.dialogFormVisible = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
this.selectItems = new Set(this.jsonPathList);
|
||||
} else {
|
||||
this.selectItems = new Set();
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if (this.selectItems.has(row)) {
|
||||
this.selectItems.delete(row);
|
||||
} else {
|
||||
this.selectItems.add(row);
|
||||
}
|
||||
},
|
||||
commit() {
|
||||
this.$emit("addJsonpathSuggest", this.selectItems);
|
||||
this.dialogFormVisible = false;
|
||||
getJsonPaths() {
|
||||
if (this.request.debugRequestResult) {
|
||||
let param = {
|
||||
jsonPath: this.request.debugRequestResult.responseResult.body
|
||||
};
|
||||
this.result = this.$post("/api/getJsonPaths", param).then(response => {
|
||||
this.jsonPathList = response.data.data;
|
||||
this.dialogFormVisible = true;
|
||||
}).catch(() => {
|
||||
this.$warning(this.$t('api_test.request.assertions.json_path_err'));
|
||||
this.dialogFormVisible = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
this.selectItems = new Set(this.jsonPathList);
|
||||
} else {
|
||||
this.selectItems = new Set();
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if (this.selectItems.has(row)) {
|
||||
this.selectItems.delete(row);
|
||||
} else {
|
||||
this.selectItems.add(row);
|
||||
}
|
||||
},
|
||||
commit() {
|
||||
this.$emit("addJsonpathSuggest", this.selectItems);
|
||||
this.dialogFormVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px">
|
||||
<el-button class="ms-left-buttion" size="small" type="info" plain>提取参数</el-button>
|
||||
<el-button class="ms-left-buttion" size="small" type="info" plain>{{$t('api_test.definition.request.extract_param')}}</el-button>
|
||||
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">移除</el-button>
|
||||
|
||||
<div style="margin: 20px">
|
||||
<div class="extract-description">
|
||||
{{$t('api_test.request.extract.description')}}
|
||||
|
@ -59,7 +61,11 @@
|
|||
methods: {
|
||||
after() {
|
||||
this.type = "";
|
||||
}
|
||||
},
|
||||
remove() {
|
||||
this.$emit('remove', this.extract);
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
}
|
||||
},
|
||||
common: {
|
||||
type: ExtractCommon,
|
||||
default: () => {
|
||||
return new ExtractCommon();
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
components: {MsApiExtractCommon},
|
||||
|
||||
props: {
|
||||
extract: Extract,
|
||||
extract: {},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<!--query 参数-->
|
||||
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters" :disabled="request.rest.length>1">
|
||||
<el-tooltip class="item-tabs" effect="dark" content="地址栏中跟在?后面的参数,如updateapi?id=112" placement="top-start" slot="label">
|
||||
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.query_info')" placement="top-start" slot="label">
|
||||
<span>{{$t('api_test.definition.request.query_param')}}
|
||||
<div class="el-step__icon is-text ms-api-col ms-query" v-if="request.arguments.length>1">
|
||||
<div class="el-step__icon-inner">{{request.arguments.length-1}}</div>
|
||||
|
@ -31,7 +31,7 @@
|
|||
|
||||
<!--REST 参数-->
|
||||
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest" :disabled="request.arguments.length>1">
|
||||
<el-tooltip class="item-tabs" effect="dark" content="地址栏中被斜杠/分隔的参数,如updateapi/{id}" placement="top-start" slot="label">
|
||||
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.rest_info')" placement="top-start" slot="label">
|
||||
<span>
|
||||
{{$t('api_test.definition.request.rest_param')}}
|
||||
<div class="el-step__icon is-text ms-api-col ms-query" v-if="request.rest.length>1">
|
||||
|
@ -49,7 +49,7 @@
|
|||
|
||||
<!-- 认证配置 -->
|
||||
<el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig">
|
||||
<el-tooltip class="item-tabs" effect="dark" content="请求需要进行权限校验" placement="top-start" slot="label">
|
||||
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.auth_config_info')" placement="top-start" slot="label">
|
||||
<span>{{$t('api_test.definition.request.auth_config')}}</span>
|
||||
</el-tooltip>
|
||||
|
||||
|
@ -58,31 +58,30 @@
|
|||
|
||||
</el-tabs>
|
||||
</div>
|
||||
<!--定制脚本-->
|
||||
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData">
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" title="前置脚本" style-type="warning"
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="warning"
|
||||
:jsr223-processor="row"/>
|
||||
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" title="后置脚本" style-type="success"
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="success"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
<ms-api-extract :is-read-only="isReadOnly" v-if="row.label==='JSONPostProcessor'" :extract="row"/>
|
||||
-->
|
||||
</el-col>
|
||||
|
||||
<!--操作按钮-->
|
||||
<el-col :span="3" class="ms-left-cell">
|
||||
<el-button class="ms-left-buttion" size="small" type="warning" @click="addPre" plain>+前置脚本</el-button>
|
||||
<el-button class="ms-left-buttion" size="small" type="warning" @click="addPre" plain>+{{$t('api_test.definition.request.pre_script')}}</el-button>
|
||||
<br/>
|
||||
<el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+后置脚本</el-button>
|
||||
<el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+{{$t('api_test.definition.request.post_script')}}</el-button>
|
||||
<br/>
|
||||
<el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+断言规则</el-button>
|
||||
<el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+{{$t('api_test.definition.request.assertions_rule')}}</el-button>
|
||||
<br/>
|
||||
<el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+提取参数</el-button>
|
||||
<el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+{{$t('api_test.definition.request.extract_param')}}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
@ -99,7 +98,7 @@
|
|||
import {createComponent} from "../jmeter/components";
|
||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||
import MsApiExtract from "../extract/ApiExtract";
|
||||
import {Assertions} from "../../model/ApiTestModel";
|
||||
import {Assertions, Extract} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiHttpRequestForm",
|
||||
|
@ -143,17 +142,8 @@
|
|||
},
|
||||
headerSuggestions: REQUEST_HEADERS,
|
||||
isReloadData: false,
|
||||
assertions: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.request.hashTree.forEach(row => {
|
||||
if (row.type === "Assertions") {
|
||||
row = new Assertions({text: row.text, regex: row.regex, jsonPath: row.jsonPath, jsr223: row.jsr223, xpath2: row.xpath2, duration: row.duration});
|
||||
}
|
||||
})
|
||||
console.log(this.assertions)
|
||||
},
|
||||
methods: {
|
||||
addPre() {
|
||||
let jsr223PreProcessor = createComponent("JSR223PreProcessor");
|
||||
|
@ -166,13 +156,12 @@
|
|||
this.reload();
|
||||
},
|
||||
addAssertions() {
|
||||
//let jsonPathAssertion = createComponent("JSONPathAssertion");
|
||||
let assertions = new Assertions();
|
||||
this.request.hashTree.push(assertions);
|
||||
this.reload();
|
||||
},
|
||||
addExtract() {
|
||||
let jsonPostProcessor = createComponent("JSONPostProcessor");
|
||||
let jsonPostProcessor = new Extract();
|
||||
this.request.hashTree.push(jsonPostProcessor);
|
||||
this.reload();
|
||||
},
|
||||
|
|
|
@ -16,6 +16,15 @@
|
|||
<pre>{{response.responseResult.console}}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
|
||||
<ms-assertion-results :assertions="response.responseResult.assertions"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||
<pre>{{response.responseResult.vars}}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown :commands="modes" :default-command="mode" @command="modeChange"/>
|
||||
|
|
|
@ -901,6 +901,7 @@ export class Duration extends AssertionType {
|
|||
export class Extract extends BaseConfig {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.type = "Extract";
|
||||
this.regex = [];
|
||||
this.json = [];
|
||||
this.xpath = [];
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit eb237fb6bfeba8d99e4db52450ae92f3cdd4ea33
|
||||
Subproject commit 33bbdb3f528c914bf333b2c1839dd6d3bbd9b569
|
|
@ -518,6 +518,13 @@ export default {
|
|||
response_body: "Response body",
|
||||
console: "Console",
|
||||
status_code: "Status code",
|
||||
query_info: "Follow the address bar? The following parameters, such as updateapi?id=112",
|
||||
rest_info: "Slash/separated parameters in the address bar, such as updateapi/{id}",
|
||||
auth_config_info: "Request requires permission verification",
|
||||
pre_script: "Prescript",
|
||||
post_script: "Postscript",
|
||||
extract_param: "Extract parameters",
|
||||
|
||||
}
|
||||
},
|
||||
environment: {
|
||||
|
|
|
@ -519,7 +519,12 @@ export default {
|
|||
response_body: "响应体",
|
||||
console: "控制台",
|
||||
status_code: "状态码",
|
||||
|
||||
query_info: "地址栏中跟在?后面的参数,如updateapi?id=112",
|
||||
rest_info: "地址栏中被斜杠/分隔的参数,如updateapi/{id}",
|
||||
auth_config_info: "请求需要进行权限校验",
|
||||
pre_script: "前置脚本",
|
||||
post_script: "后置脚本",
|
||||
extract_param:"提取参数",
|
||||
}
|
||||
},
|
||||
environment: {
|
||||
|
|
|
@ -519,6 +519,12 @@ export default {
|
|||
response_body: "響應體",
|
||||
console: "控制臺",
|
||||
status_code: "狀態碼",
|
||||
query_info: "地址欄中跟在?後面的參數,如updateapi?id=112",
|
||||
rest_info: "地址欄中被斜杠/分隔的參數,如updateapi/{id}",
|
||||
auth_config_info: "請求需要進行權限校驗",
|
||||
pre_script: "前置腳本",
|
||||
post_script: "後置腳本",
|
||||
extract_param: "提取參數",
|
||||
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue