mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 13:05:18 +00:00
update: redis配置适配密码格式
create:加入自动化测试管理模块
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getApiCaseList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/apicase/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getApiCaseDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/apicase/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createApiCase(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/apicase/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateApiCase(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/apicase/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteApiCase(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/apicase/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getEnvironmentList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/environment/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getEnvironmentDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/environment/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createEnvironment(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/environment/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateEnvironment(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/environment/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteEnvironment(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/environment/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getGlobaldataList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/globaldata/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGlobaldataDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/globaldata/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createGlobaldata(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/globaldata/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateGlobaldata(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/globaldata/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteGlobaldata(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/globaldata/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getModuleList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/module/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getModuleDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/module/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createModule(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/module/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateModule(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/module/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteModule(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/module/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getNotificationConfigList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/notificationconfig/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getNotificationConfigDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/notificationconfig/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createNotificationConfig(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/notificationconfig/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateNotificationConfig(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/notificationconfig/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteNotificationConfig(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/notificationconfig/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getProjectList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/project/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getProjectDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/project/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createProject(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/project/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProject(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/project/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteProject(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/project/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getReportList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/report/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getReportDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/report/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createReport(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/report/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateReport(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/report/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteReport(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/report/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getSuiteList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/suite/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getSuiteDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/suite/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createSuite(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/suite/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateSuite(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/suite/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteSuite(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/suite/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getTaskList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/task/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getTaskDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/task/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createTask(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/task/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateTask(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/task/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteTask(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/task/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getUICaseList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uicase/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUICaseDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uicase/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createUICase(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uicase/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUICase(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uicase/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteUICase(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uicase/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getUIElementList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uielement/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUIElementDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uielement/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createUIElement(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uielement/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUIElement(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uielement/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteUIElement(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uielement/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getUIPageList(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uipage/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUIPageDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uipage/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createUIPage(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uipage/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUIPage(body) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uipage/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteUIPage(query) {
|
||||
return request({
|
||||
url: "/api/v1/autotest/uipage/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
<template>
|
||||
<a-modal v-model:open="debugModalState.open"
|
||||
title="接口调试"
|
||||
:width="1200"
|
||||
:footer="null"
|
||||
:bodyStyle="{ padding: '16px', height: 'calc(100vh - 200px)', overflow: 'auto' }"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<!-- 原有的主体布局代码保持不变 -->
|
||||
<div class="debug-container">
|
||||
<!-- 1. 优化请求配置区域 -->
|
||||
<div class="request-section">
|
||||
<a-form layout="vertical">
|
||||
<!-- 优化URL输入区 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="3">
|
||||
<a-form-item label="请求方法">
|
||||
<a-select v-model:value="debugRequest.method"
|
||||
:dropdownMatchSelectWidth="false"
|
||||
@change="handleMethodChange">
|
||||
<a-select-option v-for="method in HTTP_METHODS" :key="method">
|
||||
<span :class="getMethodClass(method)">{{ method }}</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="17">
|
||||
<a-form-item label="请求地址">
|
||||
<a-input-group compact>
|
||||
<a-select v-model:value="protocol" style="width: 100px">
|
||||
<a-select-option value="http">http://</a-select-option>
|
||||
<a-select-option value="https">https://</a-select-option>
|
||||
</a-select>
|
||||
<a-auto-complete
|
||||
v-model:value="debugRequest.url"
|
||||
:options="urlHistory"
|
||||
style="width: calc(100% - 100px)"
|
||||
placeholder="请输入完整的URL地址"
|
||||
@select="handleUrlSelect"
|
||||
/>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-form-item label=" ">
|
||||
<a-space>
|
||||
<a-tooltip title="Ctrl + Enter">
|
||||
<a-button type="primary" @click="sendRequest" :loading="loading">
|
||||
发送请求
|
||||
<template #icon><SendOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-dropdown>
|
||||
<a-button>
|
||||
更多 <DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="save" @click="saveAsFavorite">
|
||||
<StarOutlined /> 保存到收藏
|
||||
</a-menu-item>
|
||||
<a-menu-item key="copy" @click="copyRequest">
|
||||
<CopyOutlined /> 复制请求
|
||||
</a-menu-item>
|
||||
<a-menu-item key="reset" @click="confirmReset">
|
||||
<ReloadOutlined /> 重置
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 2. 优化请求参数配置区 -->
|
||||
<a-tabs v-model:activeKey="activeTab" class="debug-tabs">
|
||||
<!-- Headers面板增加智能提示 -->
|
||||
<a-tab-pane key="headers" tab="Headers">
|
||||
<div class="tab-toolbar">
|
||||
<a-space>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="link">
|
||||
常用Headers <DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="addCommonHeader">
|
||||
<a-menu-item v-for="header in commonHeaders" :key="header.key">
|
||||
{{ header.key }}: {{ header.value }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-button type="link" @click="clearHeaders">清空</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="param-table">
|
||||
<div v-for="(item, index) in headersList" :key="index" class="param-row">
|
||||
<a-input v-model:value="item.key" placeholder="Key" class="param-key" />
|
||||
<a-input v-model:value="item.value" placeholder="Value" class="param-value" />
|
||||
<a-button type="text" @click="removeHeader(index)" danger>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="link" @click="addHeader" class="add-btn">
|
||||
<PlusOutlined /> 添加 Header
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- Query Params -->
|
||||
<a-tab-pane key="params" tab="Params">
|
||||
<div class="tab-toolbar">
|
||||
<a-button type="link" @click="validateParams">验证参数</a-button>
|
||||
</div>
|
||||
<div class="param-table">
|
||||
<div v-for="(item, index) in paramsList" :key="index" class="param-row">
|
||||
<a-input v-model:value="item.key" placeholder="Key" class="param-key" />
|
||||
<a-input v-model:value="item.value" placeholder="Value" class="param-value" />
|
||||
<a-button type="text" @click="removeParam(index)" danger>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="link" @click="addParam" class="add-btn">
|
||||
<PlusOutlined /> 添加参数
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 3. 优化Body展示 -->
|
||||
<a-tab-pane key="body" tab="Body">
|
||||
<div class="body-section">
|
||||
<a-radio-group v-model:value="bodyType" class="body-type" button-style="solid">
|
||||
<a-radio-button value="none">none</a-radio-button>
|
||||
<a-radio-button value="form-data">form-data</a-radio-button>
|
||||
<a-radio-button value="json">JSON</a-radio-button>
|
||||
<a-radio-button value="raw">Raw</a-radio-button>
|
||||
</a-radio-group>
|
||||
|
||||
<div v-if="bodyType === 'form-data'" class="param-table">
|
||||
<div v-for="(item, index) in formDataList" :key="index" class="param-row">
|
||||
<a-input v-model:value="item.key" placeholder="Key" class="param-key" />
|
||||
<a-input v-model:value="item.value" placeholder="Value" class="param-value" />
|
||||
<a-button type="text" @click="removeFormData(index)" danger>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="link" @click="addFormData" class="add-btn">
|
||||
<PlusOutlined /> 添加字段
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="bodyType === 'json'" class="json-editor">
|
||||
<MonacoEditor v-model:value="jsonBody" :options="editorOptions" language="json"
|
||||
@change="formatJSON" />
|
||||
</div>
|
||||
|
||||
<!-- 增加快捷操作栏 -->
|
||||
<div v-if="bodyType === 'json'" class="json-toolbar">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="formatJSON">
|
||||
<FormatPainterOutlined /> 格式化
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="compressJSON">
|
||||
<CompressOutlined /> 压缩
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="validateJSON">
|
||||
<CheckOutlined /> 验证
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- Cookies -->
|
||||
<a-tab-pane key="cookies" tab="Cookies">
|
||||
<div class="param-table">
|
||||
<!-- ...类似headers的配置代码... -->
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-form>
|
||||
|
||||
<!-- 发送按钮区增加快捷键提示 -->
|
||||
<div class="action-bar">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="sendRequest" :loading="loading">
|
||||
发送请求
|
||||
<template #icon>
|
||||
<SendOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button @click="resetForm">
|
||||
重置
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<span class="shortcut-tip">快捷键: Ctrl + Enter</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 优化响应结果区域 -->
|
||||
<a-divider />
|
||||
<div v-if="response" class="response-section">
|
||||
<!-- 优化响应状态展示 -->
|
||||
<div class="response-header">
|
||||
<div :class="['status', getStatusClass(response.status)]">
|
||||
<a-badge :status="getStatusBadge(response.status)" />
|
||||
Status: {{ response.status }} {{ response.statusText }}
|
||||
</div>
|
||||
<div class="info">
|
||||
<a-space>
|
||||
<span>Time: {{ response.time }}ms</span>
|
||||
<a-divider type="vertical" />
|
||||
<span>Size: {{ response.size }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 优化响应内容展示 -->
|
||||
<a-tabs :animated="false">
|
||||
<a-tab-pane key="response" tab="Response">
|
||||
<div class="response-toolbar">
|
||||
<a-space>
|
||||
<a-tooltip title="复制到剪贴板">
|
||||
<a-button type="link" @click="copyResponse">
|
||||
<CopyOutlined /> 复制
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-button type="link" @click="downloadResponse">
|
||||
<DownloadOutlined /> 下载
|
||||
</a-button>
|
||||
<a-button type="link" @click="searchResponse">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-select v-model:value="responseView" style="width: 100px">
|
||||
<a-select-option value="pretty">Pretty</a-select-option>
|
||||
<a-select-option value="raw">Raw</a-select-option>
|
||||
<a-select-option value="preview">Preview</a-select-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</div>
|
||||
<MonacoEditor :value="formatResponse(response.data)" :options="viewerOptions" language="json"
|
||||
class="response-viewer" />
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="headers" tab="Headers">
|
||||
<a-descriptions bordered :column="1">
|
||||
<a-descriptions-item v-for="(value, key) in response.headers" :key="key" :label="key">
|
||||
{{ value }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="cookies" tab="Cookies">
|
||||
<!-- ...Cookie展示代码... -->
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-empty v-else description="等待发送请求..." />
|
||||
</div>
|
||||
|
||||
<!-- 5. 添加辅助功能弹窗 -->
|
||||
<a-drawer
|
||||
v-model:visible="searchDrawer.visible"
|
||||
title="搜索响应内容"
|
||||
placement="right"
|
||||
:width="300"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="搜索内容">
|
||||
<a-input v-model:value="searchDrawer.keyword" allow-clear @pressEnter="handleSearch" />
|
||||
</a-form-item>
|
||||
<a-form-item label="搜索选项">
|
||||
<a-checkbox v-model:checked="searchDrawer.caseSensitive">区分大小写</a-checkbox>
|
||||
<a-checkbox v-model:checked="searchDrawer.useRegex">使用正则</a-checkbox>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button type="primary" @click="handleSearch">搜索</a-button>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { PlusOutlined, DeleteOutlined, SendOutlined, ReloadOutlined, DownOutlined, StarOutlined, CopyOutlined, FormatPainterOutlined, CompressOutlined, CheckOutlined, DownloadOutlined, SearchOutlined } from '@ant-design/icons-vue';
|
||||
import type { DebugModalState } from './types';
|
||||
import axios from 'axios';
|
||||
|
||||
// 常量定义
|
||||
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
|
||||
|
||||
// 状态定义
|
||||
const debugModalState = reactive<DebugModalState>({
|
||||
open: false,
|
||||
loading: false
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const activeTab = ref('headers');
|
||||
const bodyType = ref('none');
|
||||
const jsonBody = ref('');
|
||||
const response = ref(null);
|
||||
|
||||
// Monaco编辑器配置
|
||||
const editorOptions = {
|
||||
minimap: { enabled: false },
|
||||
lineNumbers: 'on',
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
tabSize: 2,
|
||||
fontSize: 14,
|
||||
theme: 'vs-dark'
|
||||
};
|
||||
|
||||
const viewerOptions = {
|
||||
...editorOptions,
|
||||
readOnly: true
|
||||
};
|
||||
|
||||
// Headers管理
|
||||
const headersList = ref([]);
|
||||
const addHeader = () => headersList.value.push({ key: '', value: '' });
|
||||
const removeHeader = (index) => headersList.value.splice(index, 1);
|
||||
|
||||
// Params管理
|
||||
const paramsList = ref([]);
|
||||
const addParam = () => paramsList.value.push({ key: '', value: '' });
|
||||
const removeParam = (index) => paramsList.value.splice(index, 1);
|
||||
|
||||
// FormData管理
|
||||
const formDataList = ref([]);
|
||||
const addFormData = () => formDataList.value.push({ key: '', value: '' });
|
||||
const removeFormData = (index) => formDataList.value.splice(index, 1);
|
||||
|
||||
// 请求数据管理
|
||||
const debugRequest = reactive({
|
||||
method: 'GET',
|
||||
url: '',
|
||||
headers: {},
|
||||
params: {},
|
||||
data: null
|
||||
});
|
||||
|
||||
// JSON格式化
|
||||
const formatJSON = (value) => {
|
||||
try {
|
||||
const formatted = JSON.stringify(JSON.parse(value), null, 2);
|
||||
jsonBody.value = formatted;
|
||||
} catch (e) {
|
||||
// 保持原值
|
||||
}
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
const sendRequest = async () => {
|
||||
if (!debugRequest.url) {
|
||||
message.error('请输入请求地址');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建请求配置
|
||||
const config = {
|
||||
url: debugRequest.url,
|
||||
method: debugRequest.method,
|
||||
headers: {},
|
||||
params: {},
|
||||
data: null
|
||||
};
|
||||
|
||||
// 处理Headers
|
||||
headersList.value.forEach(item => {
|
||||
if (item.key && item.value) {
|
||||
config.headers[item.key] = item.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 处理Params
|
||||
paramsList.value.forEach(item => {
|
||||
if (item.key && item.value) {
|
||||
config.params[item.key] = item.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 处理Body
|
||||
if (bodyType.value === 'json') {
|
||||
try {
|
||||
config.data = JSON.parse(jsonBody.value);
|
||||
} catch (e) {
|
||||
throw new Error('JSON格式错误');
|
||||
}
|
||||
} else if (bodyType.value === 'form-data') {
|
||||
const formData = new FormData();
|
||||
formDataList.value.forEach(item => {
|
||||
if (item.key && item.value) {
|
||||
formData.append(item.key, item.value);
|
||||
}
|
||||
});
|
||||
config.data = formData;
|
||||
}
|
||||
|
||||
// 发送请求并记录时间
|
||||
const startTime = Date.now();
|
||||
const res = await axios(config);
|
||||
const endTime = Date.now();
|
||||
|
||||
// 计算响应大小
|
||||
const size = JSON.stringify(res.data).length;
|
||||
const sizeStr = size > 1024 ? `${(size / 1024).toFixed(2)} KB` : `${size} B`;
|
||||
|
||||
// 设置响应数据
|
||||
response.value = {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers: res.headers,
|
||||
data: res.data,
|
||||
time: endTime - startTime,
|
||||
size: sizeStr
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
response.value = {
|
||||
status: error.response?.status || 500,
|
||||
statusText: error.message,
|
||||
headers: error.response?.headers || {},
|
||||
data: error.response?.data || error.message,
|
||||
time: 0,
|
||||
size: '0 B'
|
||||
};
|
||||
message.error('请求失败: ' + error.message);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
debugRequest.method = 'GET';
|
||||
debugRequest.url = '';
|
||||
headersList.value = [];
|
||||
paramsList.value = [];
|
||||
formDataList.value = [];
|
||||
jsonBody.value = '';
|
||||
bodyType.value = 'none';
|
||||
response.value = null;
|
||||
};
|
||||
|
||||
// 格式化响应数据
|
||||
const formatResponse = (data) => {
|
||||
try {
|
||||
return typeof data === 'object' ? JSON.stringify(data, null, 2) : data;
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
// 快捷键处理
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
sendRequest();
|
||||
}
|
||||
};
|
||||
|
||||
// 新增方法
|
||||
const handleCancel = () => {
|
||||
Modal.confirm({
|
||||
title: '确认关闭',
|
||||
content: '关闭后将丢失未保存的修改,是否继续?',
|
||||
onOk: () => {
|
||||
debugModalState.open = false;
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const addCommonHeaders = () => {
|
||||
headersList.value.push(
|
||||
{ key: 'Content-Type', value: 'application/json' },
|
||||
{ key: 'Accept', value: '*/*' }
|
||||
);
|
||||
};
|
||||
|
||||
const clearHeaders = () => {
|
||||
Modal.confirm({
|
||||
title: '确认清空',
|
||||
content: '是否清空所有Headers?',
|
||||
onOk: () => {
|
||||
headersList.value = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const validateParams = () => {
|
||||
// ...参数验证逻辑
|
||||
};
|
||||
|
||||
const validateJSON = () => {
|
||||
try {
|
||||
JSON.parse(jsonBody.value);
|
||||
message.success('JSON格式正确');
|
||||
} catch (e) {
|
||||
message.error('JSON格式错误:' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const copyResponse = () => {
|
||||
// ...复制响应内容
|
||||
};
|
||||
|
||||
const downloadResponse = () => {
|
||||
// ...下载响应内容
|
||||
};
|
||||
|
||||
const searchResponse = () => {
|
||||
// ...搜索响应内容
|
||||
};
|
||||
|
||||
const protocol = ref('http');
|
||||
const urlHistory = ref<Array<{value: string, label: string}>>([]);
|
||||
const responseView = ref('pretty');
|
||||
const commonHeaders = [
|
||||
{ key: 'Content-Type', value: 'application/json' },
|
||||
{ key: 'Accept', value: '*/*' },
|
||||
{ key: 'Authorization', value: 'Bearer ' }
|
||||
// 更多常用headers...
|
||||
];
|
||||
|
||||
// 搜索抽屉状态
|
||||
const searchDrawer = reactive({
|
||||
visible: false,
|
||||
keyword: '',
|
||||
caseSensitive: false,
|
||||
useRegex: false
|
||||
});
|
||||
|
||||
// 方法扩展
|
||||
const getMethodClass = (method: string) => {
|
||||
const classes = {
|
||||
GET: 'method-get',
|
||||
POST: 'method-post',
|
||||
PUT: 'method-put',
|
||||
DELETE: 'method-delete'
|
||||
};
|
||||
return classes[method] || '';
|
||||
};
|
||||
|
||||
const getStatusClass = (status: number) => {
|
||||
if (status < 300) return 'success';
|
||||
if (status < 400) return 'warning';
|
||||
return 'error';
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: number) => {
|
||||
if (status < 300) return 'success';
|
||||
if (status < 400) return 'warning';
|
||||
return 'error';
|
||||
};
|
||||
|
||||
const handleMethodChange = (method: string) => {
|
||||
// 根据方法自动添加相关headers
|
||||
if (method === 'POST' || method === 'PUT') {
|
||||
addCommonHeader({ key: 'Content-Type' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleUrlSelect = (url: string) => {
|
||||
// 保存到历史记录
|
||||
if (!urlHistory.value.find(item => item.value === url)) {
|
||||
urlHistory.value.unshift({ value: url, label: url });
|
||||
if (urlHistory.value.length > 10) {
|
||||
urlHistory.value.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const confirmReset = () => {
|
||||
Modal.confirm({
|
||||
title: '确认重置',
|
||||
content: '是否清空所有已配置的请求参数?',
|
||||
onOk: resetForm
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
debugModalState,
|
||||
debugRequest,
|
||||
headersList,
|
||||
paramsList,
|
||||
formDataList,
|
||||
jsonBody,
|
||||
bodyType,
|
||||
resetForm
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.debug-container {
|
||||
// ... 原有样式 ...
|
||||
|
||||
// 新增样式
|
||||
.method-get { color: #52c41a; }
|
||||
.method-post { color: #1890ff; }
|
||||
.method-put { color: #faad14; }
|
||||
.method-delete { color: #ff4d4f; }
|
||||
|
||||
.json-toolbar {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
z-index: 1;
|
||||
background: rgba(255,255,255,0.9);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.response-header {
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&.warning { color: #faad14; }
|
||||
&.error { color: #ff4d4f; }
|
||||
}
|
||||
}
|
||||
|
||||
// ...其他原有样式...
|
||||
}
|
||||
</style>
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
url?: string;
|
||||
method?: string;
|
||||
headers?: string;
|
||||
data_type?: string;
|
||||
data?: string;
|
||||
expected_status_code?: number;
|
||||
expected_msg?: string;
|
||||
expected_response?: string;
|
||||
assertions_status_code?: string;
|
||||
assertions_msg?: string;
|
||||
assertions_response?: string;
|
||||
module_id?: moduleTableDataType['id'];
|
||||
project_id? : projectSeletorType['id'];
|
||||
environment_id?: environmentTableDataType['id'];
|
||||
global_data_id?: globalDataTableDataType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
export interface moduleTableDataType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
export interface environmentTableDataType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
base_url?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
export interface globalDataTableDataType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
value?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
// 添加模块弹窗类型定义
|
||||
export type ModuleModalType = 'create' | 'update' | 'view';
|
||||
|
||||
export interface ModuleModalState {
|
||||
open: boolean;
|
||||
title: string;
|
||||
loading: boolean;
|
||||
type: ModuleModalType;
|
||||
id: number | null;
|
||||
}
|
||||
|
||||
// 请求方法类型
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
||||
|
||||
// k-v 格式定义
|
||||
export interface KeyValue {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// 参数类型定义
|
||||
export type ParamsType = 'params' | 'data' | 'json' | 'file';
|
||||
|
||||
// 断言规则定义
|
||||
export type AssertionRule = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'in' | 'not in';
|
||||
|
||||
// 调试窗口状态
|
||||
export interface DebugModalState {
|
||||
open: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
// 调试请求参数
|
||||
export interface DebugRequestType {
|
||||
method: HttpMethod;
|
||||
url: string;
|
||||
headers: Record<string, string>;
|
||||
params: Record<string, string>;
|
||||
body: any;
|
||||
}
|
||||
|
||||
// 调试响应结果
|
||||
export interface DebugResponseType {
|
||||
status: number;
|
||||
statusText: string;
|
||||
headers: Record<string, string>;
|
||||
data: any;
|
||||
time: number;
|
||||
size: string;
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 主体内容区域 -->
|
||||
<div class="main-content">
|
||||
|
||||
|
||||
<div class="environment-list">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称"
|
||||
allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range"
|
||||
value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="环境列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<!-- 使用 a-row 和 a-col 划分左右布局 -->
|
||||
<a-row :gutter="16">
|
||||
<!-- 左侧部门树 -->
|
||||
<a-col :span="4">
|
||||
<a-tree v-model:selectedKeys="selectedProject" v-if="treeData.length"
|
||||
:tree-data="treeData"
|
||||
@select="handleProjectSelect"
|
||||
:showLine="true"
|
||||
:defaultExpandAll="true"
|
||||
/>
|
||||
</a-col>
|
||||
|
||||
<!-- 右侧用户列表 -->
|
||||
<a-col :span="20">
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1
|
||||
}}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消"
|
||||
@confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建环境' : (modalTitle === 'view' ? '查看环境' : '修改环境')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="环境名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="环境地址">{{ detailState.base_url }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定项目" :span="1">
|
||||
{{ projectList.find(p => p.id === detailState.project_id)?.name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="环境名称" :rules="[{ required: true, message: '请输入环境名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入环境名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="环境地址" :rules="[{ required: true, message: '请输入环境地址' }]">
|
||||
<a-input v-model:value="createState.base_url" placeholder="请输入环境地址" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="createState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{
|
||||
project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="环境名称" :rules="[{ required: true, message: '请输入环境名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入环境名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="环境地址" :rules="[{ required: true, message: '请输入环境地址' }]">
|
||||
<a-input v-model:value="updateState.base_url" placeholder="请输入环境地址" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="updateState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{
|
||||
project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList } from '@/api/autotest/project'
|
||||
import { getEnvironmentList, getEnvironmentDetail, createEnvironment, updateEnvironment, deleteEnvironment } from '@/api/autotest/environment'
|
||||
import type { searchDataType, tableDataType, projectSeletorType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '环境名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '环境地址',
|
||||
dataIndex: 'base_url',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '绑定项目',
|
||||
dataIndex: 'project_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const project = projectList.value.find(p => p.id === text);
|
||||
return project ? project.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
name: '',
|
||||
base_url: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
base_url: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
|
||||
const projectList = ref<projectSeletorType[]>([]);
|
||||
|
||||
const handProjectList = () => {
|
||||
tableLoading.value = true;
|
||||
let params = {};
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
projectList.value = result.data.items;
|
||||
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
if (currentProjectId.value) {
|
||||
params['project_id'] = currentProjectId.value;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getEnvironmentList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadingData();
|
||||
handProjectList(); // 新增:获取项目列表
|
||||
});
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteEnvironment({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createEnvironment(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateEnvironment(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 新增项目选择相关变量
|
||||
const selectedProject = ref<string[]>(['all']);
|
||||
const currentProjectId = ref<number | null>(null);
|
||||
|
||||
// 添加树结构数据处理
|
||||
const treeData = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '全部项目',
|
||||
key: 'all',
|
||||
children: projectList.value.map(project => ({
|
||||
title: project.name,
|
||||
key: String(project.id)
|
||||
}))
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
// 修改项目选择处理逻辑
|
||||
const handleProjectSelect = (selectedKeys: string[]) => {
|
||||
if (selectedKeys.length > 0) {
|
||||
const key = selectedKeys[0];
|
||||
currentProjectId.value = key === 'all' ? null : Number(key);
|
||||
selectedProject.value = [key];
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
base_url?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="全局变量列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<!-- 使用 a-row 和 a-col 划分左右布局 -->
|
||||
<a-row :gutter="16">
|
||||
<!-- 左侧部门树 -->
|
||||
<a-col :span="4">
|
||||
<a-tree v-model:selectedKeys="selectedProject" v-if="treeData.length" :tree-data="treeData"
|
||||
@select="handleProjectSelect" :showLine="true" :defaultExpandAll="true" />
|
||||
</a-col>
|
||||
|
||||
<!-- 右侧用户列表 -->
|
||||
<a-col :span="20">
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消"
|
||||
@confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建全局变量' : (modalTitle === 'view' ? '查看全局变量' : '修改全局变量')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="变量名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="变量值">{{ detailState.value }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定项目">{{ projectList.find(p => p.id ===
|
||||
detailState.project_id)?.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="变量名称" :rules="[{ required: true, message: '请输入变量名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入变量名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="value" label="变量值" :rules="[{ required: true, message: '请输入变量值' }]">
|
||||
<a-input v-model:value="createState.value" placeholder="请输入变量值" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="createState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{
|
||||
project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="变量名称" :rules="[{ required: true, message: '请输入变量名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入变量名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="value" label="变量值" :rules="[{ required: true, message: '请输入变量值' }]">
|
||||
<a-input v-model:value="updateState.value" placeholder="请输入变量值" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="updateState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{
|
||||
project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList } from '@/api/autotest/project'
|
||||
import { getGlobaldataList, getGlobaldataDetail, createGlobaldata, updateGlobaldata, deleteGlobaldata } from '@/api/autotest/globaldata'
|
||||
import type { searchDataType, tableDataType, projectSeletorType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '变量名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '变量值',
|
||||
dataIndex: 'value',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '绑定项目',
|
||||
dataIndex: 'project_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const project = projectList.value.find(p => p.id === text);
|
||||
return project ? project.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
name: '',
|
||||
value: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
value: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
|
||||
const projectList = ref<projectSeletorType[]>([]);
|
||||
|
||||
const handProjectList = () => {
|
||||
tableLoading.value = true;
|
||||
let params = {};
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
projectList.value = result.data.items;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
if (currentProjectId.value) {
|
||||
params['project_id'] = currentProjectId.value;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getGlobaldataList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadingData();
|
||||
handProjectList(); // 新增:获取项目列表
|
||||
});
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteGlobaldata({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createGlobaldata(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateGlobaldata(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 新增项目选择相关变量
|
||||
const selectedProject = ref<string[]>(['all']);
|
||||
const currentProjectId = ref<number | null>(null);
|
||||
|
||||
// 添加树结构数据处理
|
||||
const treeData = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '全部项目',
|
||||
key: 'all',
|
||||
children: projectList.value.map(project => ({
|
||||
title: project.name,
|
||||
key: String(project.id)
|
||||
}))
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
// 修改项目选择处理逻辑
|
||||
const handleProjectSelect = (selectedKeys: string[]) => {
|
||||
if (selectedKeys.length > 0) {
|
||||
const key = selectedKeys[0];
|
||||
currentProjectId.value = key === 'all' ? null : Number(key);
|
||||
selectedProject.value = [key];
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
value?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="模块列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建模块' : (modalTitle === 'view' ? '查看模块' : '修改模块')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="模块名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定项目">{{ projectList.find(p => p.id === detailState.project_id)?.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="模块名称" :rules="[{ required: true, message: '请输入模块名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入模块名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="createState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{ project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="模块名称" :rules="[{ required: true, message: '请输入模块名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入模块名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="updateState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{ project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList } from '@/api/autotest/project'
|
||||
import { getModuleList, getModuleDetail, createModule, updateModule, deleteModule } from '@/api/autotest/module'
|
||||
import type { searchDataType, tableDataType, projectSeletorType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '模块名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '绑定项目',
|
||||
dataIndex: 'project_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const project = projectList.value.find(p => p.id === text);
|
||||
return project ? project.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
name: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
|
||||
const projectList = ref<projectSeletorType[]>([]);
|
||||
|
||||
const handProjectList = () => {
|
||||
tableLoading.value = true;
|
||||
let params = {};
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
projectList.value = result.data.items;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getModuleList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadingData();
|
||||
handProjectList(); // 新增:获取项目列表
|
||||
});
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteModule({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createModule(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateModule(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="通知类型" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="通知配置列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<!-- 使用 a-row 和 a-col 划分左右布局 -->
|
||||
<a-row :gutter="16">
|
||||
<!-- 左侧部门树 -->
|
||||
<a-col :span="4">
|
||||
<a-tree v-model:selectedKeys="selectedProject" v-if="treeData.length" :tree-data="treeData"
|
||||
@select="handleProjectSelect" :showLine="true" :defaultExpandAll="true" />
|
||||
</a-col>
|
||||
|
||||
<!-- 右侧用户列表 -->
|
||||
<a-col :span="20">
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消"
|
||||
@confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建通知' : (modalTitle === 'view' ? '查看通知' : '修改通知')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="2" :labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号" :span="1">
|
||||
{{ (pagination.current - 1) * pagination.pageSize + detailState.index + 1 }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="通知类型" :span="1">
|
||||
{{ detailState.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="通知配置" :span="2">
|
||||
<div v-for="(value, key) in detailState.config" :key="key">
|
||||
{{ key }}: {{ value }}
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定项目" :span="1">
|
||||
{{ projectList.find(p => p.id === detailState.project_id)?.name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人" :span="1">
|
||||
{{ detailState.creator ? detailState.creator.name : '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间" :span="1">
|
||||
{{ detailState.created_at }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间" :span="1">
|
||||
{{ detailState.updated_at }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">
|
||||
{{ detailState.description }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="type" label="通知类型" :rules="[{ required: true, message: '请输入通知类型' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入通知类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="config" label="通知配置" :rules="[{ required: true, message: '请输入通知配置' }]">
|
||||
<a-form-item-rest>
|
||||
<a-button type="link" @click="addConfigField('create')"
|
||||
style="padding-left: 0; margin-bottom: 10px;">
|
||||
<PlusOutlined /> 添加配置项
|
||||
</a-button>
|
||||
<div v-for="item in createConfigItems" :key="item.id"
|
||||
style="margin-bottom: 10px; display: flex; gap: 8px;">
|
||||
<a-input v-model:value="item.key" placeholder="键名" style="width: 200px"
|
||||
@change="(e) => updateConfigKey('create', item.id, e.target.value)" />
|
||||
<a-input v-model:value="item.value" placeholder="键值" style="width: 200px" />
|
||||
<a-button type="text" @click="removeConfigField('create', item.id)" danger>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item-rest>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="createState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{
|
||||
project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="type" label="通知类型" :rules="[{ required: true, message: '请输入通知类型' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入通知类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="config" label="通知配置" :rules="[{ required: true, message: '请输入通知配置' }]">
|
||||
<a-form-item-rest>
|
||||
<a-button type="link" @click="addConfigField('update')"
|
||||
style="padding-left: 0; margin-bottom: 10px;">
|
||||
<PlusOutlined /> 添加配置项
|
||||
</a-button>
|
||||
<div v-for="item in updateConfigItems" :key="item.id"
|
||||
style="margin-bottom: 10px; display: flex; gap: 8px;">
|
||||
<a-input v-model:value="item.key" placeholder="键名" style="width: 200px"
|
||||
@change="(e) => updateConfigKey('update', item.id, e.target.value)" />
|
||||
<a-input v-model:value="item.value" placeholder="键值" style="width: 200px" />
|
||||
<a-button type="text" @click="removeConfigField('update', item.id)" danger>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item-rest>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="updateState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{
|
||||
project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList } from '@/api/autotest/project'
|
||||
import { getNotificationConfigList, getNotificationConfigDetail, createNotificationConfig, updateNotificationConfig, deleteNotificationConfig } from '@/api/autotest/notificationconfig'
|
||||
import type { searchDataType, tableDataType, projectSeletorType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '通知类型',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '通知配置',
|
||||
dataIndex: 'config',
|
||||
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const config = text as { [key: string]: string };
|
||||
return Object.entries(config).map(([key, value]) => `${key}: ${value}`).join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '绑定项目',
|
||||
dataIndex: 'project_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const project = projectList.value.find(p => p.id === text);
|
||||
return project ? project.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
|
||||
const initialState = {
|
||||
name: '',
|
||||
config: {},
|
||||
configKeys: {},
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
};
|
||||
|
||||
const createState = reactive<tableDataType>({...initialState});
|
||||
const updateState = reactive<tableDataType>({...initialState});
|
||||
const detailState = reactive<tableDataType>({
|
||||
...initialState,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
creator: {
|
||||
id: undefined,
|
||||
name: '',
|
||||
username: ''
|
||||
}
|
||||
})
|
||||
|
||||
const projectList = ref<projectSeletorType[]>([]);
|
||||
|
||||
const handProjectList = () => {
|
||||
tableLoading.value = true;
|
||||
let params = {};
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
projectList.value = result.data.items;
|
||||
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
if (currentProjectId.value) {
|
||||
params['project_id'] = currentProjectId.value;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getNotificationConfigList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadingData();
|
||||
handProjectList(); // 新增:获取项目列表
|
||||
});
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
const selected = dataSource.value[index];
|
||||
|
||||
// 确保所有必要的属性都被初始化
|
||||
Object.assign(detailState, {
|
||||
id: selected.id,
|
||||
type: selected.name || '',
|
||||
config: selected.config || {},
|
||||
project_id: selected.project_id,
|
||||
description: selected.description || '',
|
||||
created_at: selected.created_at || '',
|
||||
updated_at: selected.updated_at || '',
|
||||
creator: selected.creator || { id: undefined, name: '', username: '' },
|
||||
index: index
|
||||
});
|
||||
|
||||
detailStateLoading.value = false;
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
|
||||
// 重置状态
|
||||
Object.assign(updateState, {
|
||||
id: selected.id,
|
||||
type: selected.name || '',
|
||||
config: {},
|
||||
project_id: selected.project_id,
|
||||
description: selected.description || ''
|
||||
});
|
||||
|
||||
// 初始化配置项数组
|
||||
updateConfigItems.value = Object.entries(selected.config || {}).map(([key, value]) => ({
|
||||
id: `temp_${Date.now()}_${key}`,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
} else if (modalType === 'create') {
|
||||
createConfigItems.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteNotificationConfig({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const isCreate = modalTitle.value === 'create';
|
||||
const form = isCreate ? createForm.value : updateForm.value;
|
||||
const state = isCreate ? createState : updateState;
|
||||
const configHandler = handleConfig(isCreate ? 'create' : 'update');
|
||||
|
||||
form.validate().then(() => {
|
||||
const submitData = cloneDeep(state);
|
||||
submitData.config = configHandler.getConfigForSubmit();
|
||||
delete submitData.configKeys;
|
||||
|
||||
// 移除空值
|
||||
Object.keys(submitData).forEach(key => {
|
||||
if (isEmpty(submitData[key])) {
|
||||
delete submitData[key];
|
||||
}
|
||||
});
|
||||
|
||||
const apiCall = isCreate ? createNotificationConfig : updateNotificationConfig;
|
||||
|
||||
apiCall(submitData).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
if (isCreate) {
|
||||
Object.assign(createState, initialState);
|
||||
}
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error);
|
||||
});
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加配置项
|
||||
const addConfigField = (type: 'create' | 'update') => handleConfig(type).addField();
|
||||
const removeConfigField = (type: 'create' | 'update', key: string) => handleConfig(type).removeField(key);
|
||||
const updateConfigKey = (type: 'create' | 'update', oldKey: string, newKey: string) => handleConfig(type).updateKey(oldKey, newKey);
|
||||
|
||||
// 优化配置项处理逻辑
|
||||
const handleConfig = (type: 'create' | 'update') => {
|
||||
const state = type === 'create' ? createState : updateState;
|
||||
const items = type === 'create' ? createConfigItems : updateConfigItems;
|
||||
|
||||
return {
|
||||
addField: () => {
|
||||
items.value.push({
|
||||
id: `temp_${Date.now().toString()}`,
|
||||
key: '',
|
||||
value: ''
|
||||
});
|
||||
},
|
||||
removeField: (id: string) => {
|
||||
const index = items.value.findIndex(item => item.id === id);
|
||||
if (index > -1) {
|
||||
items.value.splice(index, 1);
|
||||
}
|
||||
},
|
||||
updateKey: (id: string, newKey: string) => {
|
||||
if (!newKey) return;
|
||||
|
||||
// 检查新键是否已存在
|
||||
if (items.value.some(item => item.key === newKey && item.id !== id)) {
|
||||
message.error('键名已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const item = items.value.find(item => item.id === id);
|
||||
if (item) {
|
||||
item.key = newKey;
|
||||
}
|
||||
},
|
||||
getConfigForSubmit: () => {
|
||||
const config: { [key: string]: string } = {};
|
||||
items.value.forEach(item => {
|
||||
if (item.key && item.value) {
|
||||
config[item.key] = item.value;
|
||||
}
|
||||
});
|
||||
return config;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 添加辅助状态接口
|
||||
interface ConfigItem {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// 添加辅助状态
|
||||
const createConfigItems = ref<ConfigItem[]>([]);
|
||||
const updateConfigItems = ref<ConfigItem[]>([]);
|
||||
|
||||
// 新增项目选择相关变量
|
||||
const selectedProject = ref<string[]>(['all']);
|
||||
const currentProjectId = ref<number | null>(null);
|
||||
|
||||
// 添加树结构数据处理
|
||||
const treeData = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '全部项目',
|
||||
key: 'all',
|
||||
children: projectList.value.map(project => ({
|
||||
title: project.name,
|
||||
key: String(project.id)
|
||||
}))
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
// 修改项目选择处理逻辑
|
||||
const handleProjectSelect = (selectedKeys: string[]) => {
|
||||
if (selectedKeys.length > 0) {
|
||||
const key = selectedKeys[0];
|
||||
currentProjectId.value = key === 'all' ? null : Number(key);
|
||||
selectedProject.value = [key];
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
export interface searchDataType {
|
||||
name?: string;
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
config?: { [key: string]: string };
|
||||
project_id?: number;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="项目列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建项目' : (modalTitle === 'view' ? '查看项目' : '修改项目')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="项目名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="名称" :rules="[{ required: true, message: '请输入名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="标题" :rules="[{ required: true, message: '请输入名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList, createProject, updateProject, deleteProject } from '@/api/autotest/project'
|
||||
import type { searchDataType, tableDataType } from './types'
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
name: '',
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 500
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 120
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => loadingData());
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteProject({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createProject(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateProject(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 报告头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="报告列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建测试报告' : (modalTitle === 'view' ? '查看测试报告' : '修改测试报告')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="报告名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务编号">{{ detailState.task_id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="报告摘要">{{ detailState.summary }}</a-descriptions-item>
|
||||
<a-descriptions-item label="详细报告">{{ detailState.details }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="报告名称" :rules="[{ required: true, message: '请输入报告名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入报告名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="任务编号" :rules="[{ required: true, message: '请输入任务编号' }]">
|
||||
<a-input v-model:value="createState.task_id" placeholder="请输入任务编号" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="报告摘要" :rules="[{ required: true, message: '请输入报告摘要' }]">
|
||||
<a-input v-model:value="createState.summary" placeholder="请输入报告摘要" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="详细报告" :rules="[{ required: true, message: '请输入详细报告' }]">
|
||||
<a-input v-model:value="createState.details" placeholder="请输入详细报告" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="报告名称" :rules="[{ required: true, message: '请输入报告名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入报告名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="任务编号" :rules="[{ required: true, message: '请输入任务编号' }]">
|
||||
<a-input v-model:value="updateState.task_id" placeholder="请输入任务编号" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="报告摘要" :rules="[{ required: true, message: '请输入报告摘要' }]">
|
||||
<a-input v-model:value="updateState.summary" placeholder="请输入报告摘要" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="base_url" label="详细报告" :rules="[{ required: true, message: '请输入详细报告' }]">
|
||||
<a-input v-model:value="updateState.details" placeholder="请输入详细报告" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getReportList, getReportDetail, createReport, updateReport, deleteReport } from '@/api/autotest/report'
|
||||
import type { searchDataType, tableDataType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '报告名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '任务编号',
|
||||
dataIndex: 'task_id',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '报告摘要',
|
||||
dataIndex: 'summary',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '详细报告',
|
||||
dataIndex: 'details',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
name: '',
|
||||
task_id: undefined,
|
||||
summary: '',
|
||||
details: '',
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
task_id: undefined,
|
||||
summary: '',
|
||||
details: '',
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getReportList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => loadingData());
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteReport({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createReport(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateReport(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
task_id?: number;
|
||||
summary?: string;
|
||||
details?: string;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="套件列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建套件' : (modalTitle === 'view' ? '查看套件' : '修改套件')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="套件名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定项目">{{ projectList.find(p => p.id === detailState.project_id)?.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="套件名称" :rules="[{ required: true, message: '请输入套件名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入套件名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="createState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{ project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="套件名称" :rules="[{ required: true, message: '请输入套件名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入套件名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="updateState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{ project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList } from '@/api/autotest/project'
|
||||
import { getSuiteList, getSuiteDetail, createSuite, updateSuite, deleteSuite } from '@/api/autotest/suite'
|
||||
import type { searchDataType, tableDataType, projectSeletorType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '套件名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '绑定项目',
|
||||
dataIndex: 'project_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const project = projectList.value.find(p => p.id === text);
|
||||
return project ? project.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
name: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
|
||||
const projectList = ref<projectSeletorType[]>([]);
|
||||
|
||||
const handProjectList = () => {
|
||||
tableLoading.value = true;
|
||||
let params = {};
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
projectList.value = result.data.items;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getSuiteList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadingData();
|
||||
handProjectList(); // 新增:获取项目列表
|
||||
});
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteSuite({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createSuite(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateSuite(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="类型" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="任务列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id" :columns="columns" :data-source="dataSource"
|
||||
:row-selection="rowSelection" :loading="tableLoading" @change="handleTableChange"
|
||||
:scroll="{ x: 400 }" :pagination="pagination" :style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建任务' : (modalTitle === 'view' ? '查看任务' : '修改任务')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="测试类型">{{ detailState.test_type }}</a-descriptions-item>
|
||||
<a-descriptions-item label="执行状态">{{ detailState.status }}</a-descriptions-item>
|
||||
<a-descriptions-item label="执行日志">{{ detailState.logs }}</a-descriptions-item>
|
||||
<a-descriptions-item label="截图路径">{{ detailState.screenshot_path }}</a-descriptions-item>
|
||||
<a-descriptions-item label="实际响应">{{ detailState.actual_response }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定模块">{{ testCaseList.find(t => t.id === detailState.test_case_id)?.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定项目">{{ projectList.find(p => p.id === detailState.project_id)?.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="测试类型" :rules="[{ required: true, message: '请输入测试类型' }]">
|
||||
<a-input v-model:value="createState.test_type" placeholder="请输入任务类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="url" label="执行状态" :rules="[{ required: true, message: '请输入执行状态' }]">
|
||||
<a-input v-model:value="createState.status" placeholder="请输入任务地址" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="method" label="执行日志" :rules="[{ required: true, message: '请输入执行日志' }]">
|
||||
<a-input v-model:value="createState.logs" placeholder="请输入请求方法" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="headers" label="截图路径" :rules="[{ required: true, message: '请输入截图路径' }]">
|
||||
<a-input v-model:value="createState.screenshot_path" placeholder="请输入请求头" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="headers" label="实际响应" :rules="[{ required: true, message: '请输入实际响应' }]">
|
||||
<a-input v-model:value="createState.actual_response" placeholder="请输入请求头" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="module_id" label="绑定用例" :rules="[{ required: true, message: '请选择绑定用例' }]">
|
||||
<a-select v-model:value="createState.test_case_id" placeholder="请选择绑定模块">
|
||||
<a-select-option v-for="testCase in testCaseList" :key="testCase.id" :value="testCase.id">{{ testCase.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="createState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{ project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState" v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="测试类型" :rules="[{ required: true, message: '请输入测试类型' }]">
|
||||
<a-input v-model:value="updateState.test_type" placeholder="请输入任务类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="url" label="执行状态" :rules="[{ required: true, message: '请输入执行状态' }]">
|
||||
<a-input v-model:value="updateState.status" placeholder="请输入任务地址" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="method" label="执行日志" :rules="[{ required: true, message: '请输入执行日志' }]">
|
||||
<a-input v-model:value="updateState.logs" placeholder="请输入请求方法" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="headers" label="截图路径" :rules="[{ required: true, message: '请输入截图路径' }]">
|
||||
<a-input v-model:value="updateState.screenshot_path" placeholder="请输入请求头" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="headers" label="实际响应" :rules="[{ required: true, message: '请输入实际响应' }]">
|
||||
<a-input v-model:value="updateState.actual_response" placeholder="请输入请求头" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="module_id" label="绑定用例" :rules="[{ required: true, message: '请选择绑定用例' }]">
|
||||
<a-select v-model:value="updateState.test_case_id" placeholder="请选择绑定模块">
|
||||
<a-select-option v-for="testCase in testCaseList" :key="testCase.id" :value="testCase.id">{{ testCase.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="project_id" label="绑定项目" :rules="[{ required: true, message: '请选择绑定项目' }]">
|
||||
<a-select v-model:value="updateState.project_id" placeholder="请选择绑定项目">
|
||||
<a-select-option v-for="project in projectList" :key="project.id" :value="project.id">{{ project.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getProjectList } from '@/api/autotest/project'
|
||||
import { getApiCaseList } from '@/api/autotest/apicase'
|
||||
import { getTaskList, getTaskDetail, createTask, updateTask, deleteTask } from '@/api/autotest/task'
|
||||
import type { searchDataType, tableDataType, projectSeletorType, testCaseSeletorType } from './types'
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '测试类型',
|
||||
dataIndex: 'test_type',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '执行状态',
|
||||
dataIndex: 'status',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '执行日志',
|
||||
dataIndex: 'logs',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '截图路径',
|
||||
dataIndex: 'screenshot_path',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '实际响应',
|
||||
dataIndex: 'actual_response',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '绑定用例',
|
||||
dataIndex: 'test_case_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const testCase = testCaseList.value.find(t => t.id === text);
|
||||
return testCase ? testCase.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '绑定项目',
|
||||
dataIndex: 'project_id',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
const project = projectList.value.find(p => p.id === text);
|
||||
return project ? project.name : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDataType>({
|
||||
test_type: '',
|
||||
status: undefined,
|
||||
logs: '',
|
||||
screenshot_path: '',
|
||||
actual_response: '',
|
||||
test_case_id: undefined,
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDataType>({
|
||||
id: undefined,
|
||||
test_type: '',
|
||||
status: undefined,
|
||||
logs: '',
|
||||
screenshot_path: '',
|
||||
actual_response: '',
|
||||
test_case_id: undefined,
|
||||
project_id: undefined,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDataType>({})
|
||||
const projectList = ref<projectSeletorType[]>([]);
|
||||
const testCaseList = ref<testCaseSeletorType[]>([]);
|
||||
|
||||
const handProjectList = () => {
|
||||
tableLoading.value = true;
|
||||
let params = {};
|
||||
getProjectList(params).then(response => {
|
||||
const result = response.data;
|
||||
projectList.value = result.data.items;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getTaskList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => loadingData());
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDataType) => {
|
||||
deleteTask({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createTask(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateTask(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
export interface searchDataType {
|
||||
name?: string
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
test_type?: string;
|
||||
status?: string;
|
||||
logs?: string;
|
||||
screenshot_path?: string;
|
||||
actual_response?: string;
|
||||
test_case_id?: testCaseSeletorType['id'];
|
||||
project_id? : projectSeletorType['id'];
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface projectSeletorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface testCaseSeletorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user