This commit is contained in:
2024-08-13 18:35:48 +08:00
parent 4575d12071
commit 29e8758dc9
18 changed files with 767 additions and 583 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "cdtmp",
"private": true,
"version": "0.0.1",
"version": "0.0.2",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -6,7 +6,7 @@ import qs, { stringify } from "qs"
import { h } from "vue"
import { IconFaceFrownFill } from "@arco-design/web-vue/dist/arco-vue-icon"
// createService
// 创建axios实例添加请求和响应拦截器返回该axios实例
function createService() {
// 创建axios实例
const service = axios.create()
@@ -21,7 +21,6 @@ function createService() {
// 实例的response响应拦截器
service.interceptors.response.use(
(res) => {
// 如果发现响应头有文件传输扩展或者响应头为application/json或者直接status=200
if (
(res.headers["content-disposition"] || !/^application\/json/.test(res.headers["content-type"])) &&
res.status === 200
@@ -33,17 +32,15 @@ function createService() {
res.data.message = "服务器内部错误"
res.data.success = false
} else if (res.data.code && res.data.code !== 200) {
// 如果data里面code字段不为200
Message.error({
content: res.data.message,
// 注意奇怪的用法
icon: () => h(IconFaceFrownFill)
})
}
return res.data
},
(error) => {
// 其实基本逻辑是有message写message
// 没用message再去找默认的response的status
const err = (text) => {
Message.error({
content:
@@ -79,6 +76,7 @@ function createService() {
break
default:
err("未知错误!")
break
}
} else {
err("请求超时,服务器无响应!")
@@ -96,7 +94,6 @@ function createService() {
function createRequest(service) {
return function (config) {
const env = import.meta.env
// localStorage获取token信息
const token = tool.local.get(env.VITE_APP_TOKEN_PREFIX)
const configDefault = {
headers: {
@@ -108,12 +105,7 @@ function createRequest(service) {
baseURL: env.VITE_APP_OPEN_PROXY === "true" ? env.VITE_APP_PROXY_PREFIX : env.VITE_APP_BASE_URL,
data: {}
}
// option是configDefault和传入的config合并
const option = Object.assign(configDefault, config)
// lodash的isEmpty函数可以判断对象属性是否为空/数组是否为空->为空则返回true
// qs中stringfy作用是urlencode
// { c: 'b', a: 'd' } -> 'c=b&a=d'
// 如果有params就转为urlencode样子
if (!isEmpty(option.params)) {
option.url = option.url + "?" + stringify(option.params)
option.params = {}
@@ -122,7 +114,5 @@ function createRequest(service) {
}
}
// 上面两个函数一个为增加实例拦截器,一个是传入实例后请求
export const service = createService()
// 返回的是一个函数这个函数传入config然后添加上默认config然后发出实例的请求
export const request = createRequest(service)

View File

@@ -1,8 +1,10 @@
<template>
<!-- addon-before-cancel -->
<component
:is="componentName"
v-model:visible="dataVisible"
:on-before-ok="submit"
:on-before-cancel="beforeCancel"
@cancel="close"
ok-text="保存"
cancel-text="关闭"
@@ -46,7 +48,7 @@ const dataVisible = ref(false)
const form = ref({})
const actionTitle = ref("")
const dataLoading = ref(true)
const emit = defineEmits(["success", "error"])
const emit = defineEmits(["success", "error", "beforeCancel"])
provide("form", toRaw(form))
@@ -134,6 +136,11 @@ const open = () => {
dataVisible.value = true
}
}
// ~~~~addMethod~~~~
const beforeCancel = () => {
emit("beforeCancel")
return true
}
const close = () => {
dataVisible.value = false
formColumns.value = []

View File

@@ -140,7 +140,7 @@
class="ma-crud-table-tr"
:class="
isFunction(options.rowCustomClass)
? options.rowCustomClass(record, rowIndex) ?? []
? (options.rowCustomClass(record, rowIndex) ?? [])
: options.rowCustomClass
"
@contextmenu.prevent="openContextMenu($event, record)"
@@ -215,7 +215,7 @@
<ma-setting ref="crudSettingRef" />
<ma-form ref="crudFormRef" @success="requestSuccess">
<ma-form ref="crudFormRef" @success="requestSuccess" v-bind="$attrs">
<template v-for="slot in Object.keys($slots)" #[slot]="component">
<slot :name="slot" v-bind="component" />
</template>

View File

@@ -11,7 +11,7 @@
>
测试管理平台
</a-typography-title>
<a-typography-title :heading="6" class="version">V0.0.1</a-typography-title>
<a-typography-title :heading="6" class="version">V0.0.2</a-typography-title>
<icon-menu-fold
v-if="!topMenu && appStore.device === 'mobile'"
style="font-size: 22px; cursor: pointer"
@@ -22,9 +22,7 @@
<div class="center-side flex items-center justify-center font-bold text-lg">
<template v-if="title">
<a-typography-title :style="{ margin: 0, fontSize: '1.1rem', fontWeight: 'bold' }" :heading="4">
项目名称{{ $route.query.ident }}-{{ title }}- key的值为{{
route.query.key ? route.query.key : "无key值"
}}
项目名称{{ $route.query.ident }}-{{ title }}
</a-typography-title>
</template>
<Menu v-if="topMenu"></Menu>

View File

@@ -40,6 +40,7 @@ const router = createRouter({
component: () => import("@/views/project/round/index.vue"),
meta: {
requiresAuth: true,
ignoreCache: true,
roles: ["*"],
locale: "轮次信息",
icon: "icon-arrow-right"
@@ -51,6 +52,7 @@ const router = createRouter({
component: () => import("@/views/project/dut/index.vue"),
meta: {
requiresAuth: true,
ignoreCache: true,
roles: ["*"],
locale: "被测件信息",
icon: "icon-arrow-right"
@@ -62,6 +64,7 @@ const router = createRouter({
component: () => import("@/views/project/design-demand/index.vue"),
meta: {
requiresAuth: true,
ignoreCache: true,
roles: ["*"],
locale: "设计需求",
icon: "icon-arrow-right"
@@ -73,6 +76,7 @@ const router = createRouter({
component: () => import("@/views/project/testDemand/index.vue"),
meta: {
requiresAuth: true,
ignoreCache: true,
roles: ["*"],
locale: "测试需求",
icon: "icon-arrow-right"
@@ -84,6 +88,7 @@ const router = createRouter({
component: () => import("@/views/project/case/index.vue"),
meta: {
requiresAuth: true,
ignoreCache: true,
roles: ["*"],
locale: "测试用例",
icon: "icon-arrow-right"
@@ -95,6 +100,7 @@ const router = createRouter({
component: () => import("@/views/project/problem/index.vue"),
meta: {
requiresAuth: true,
ignoreCache: true,
roles: ["*"],
locale: "问题单详情",
icon: "icon-arrow-right"

View File

@@ -23,7 +23,7 @@
</a>
</a-card>
<div class="mt-2">管理平台版本</div>
<a-tag class="mt-2" color="#0fc6c2">TestManagePlant V0.0.1</a-tag>
<a-tag class="mt-2" color="#0fc6c2">TestManagePlant V0.0.2</a-tag>
</div>
</div>
</template>

View File

@@ -0,0 +1,43 @@
<template>
<a-modal v-model:visible="visible" width="80%" draggable :footer="false">
<template #title>维护数据字典 {{ currentRow.name }}</template>
<!-- crud组件 -->
<div class="lg:w-full w-full lg:mt-0">
<ma-crud :options="crudOptions" :columns="columns" ref="crudRef">
<!-- 排序列 -->
<template #sort="{ record }">
<a-input-number
:default-value="record.sort"
mode="button"
@change="changeSort($event, record.id)"
:min="0"
:max="1000"
/>
</template>
<!-- 状态列 -->
<template #status="{ record }">
<a-switch
:checked-value="1"
unchecked-value="2"
@change="changeStatus($event, record.id)"
:default-checked="record.status == 1"
/>
</template>
</ma-crud>
</div>
</a-modal>
</template>
<script setup>
import { ref } from "vue"
import useCrudRef from "./useCrudRef"
import useOpenChangeModal from "./useOpenChangeModal"
const currentRow = ref({ id: undefined, name: undefined }) // 当前选择的行
const { crudRef, crudOptions, columns } = useCrudRef(currentRow)
const { visible, changeSort, changeStatus, open } = useOpenChangeModal(crudRef, currentRow)
// 暴露自己的open方法
defineExpose({ open })
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,120 @@
import { ref, type Ref } from "vue"
import dictApi from "@/api/system/dict"
/**
* 定义crud的ref以及其
*/
export default function useCrudRef(currentRow: Ref<{ id: number | string; name: string }>) {
const crudRef = ref()
// crud选项
const crudOptions = ref({
autoRequest: false,
api: dictApi.getDictItemAll,
showIndex: false,
rowSelection: { showCheckedAll: true },
operationColumn: true,
operationWidth: 160,
operationColumnAlign: "center",
showTools: false,
beforeAdd: (form: any) => {
form.id = currentRow.value?.id
},
add: { show: true, api: dictApi.saveDictItem },
edit: { show: true, api: dictApi.updateDictItemData },
delete: { show: true, api: dictApi.realDeleteItem },
afterDelete(response) {
crudRef.value.tableRef.selectAll(false)
}
})
// crudColumns
const columns = ref([
{ title: "ID", dataIndex: "id", addDisplay: false, editDisplay: false, width: 50, hide: true },
{
title: "字典标签",
align: "center",
dataIndex: "title",
search: true,
width: 150,
commonRules: [{ required: true, message: "字典标签必填" }]
},
{
title: "字段缩写",
dataIndex: "show_title",
align: "center",
search: true
},
{
title: "字典键值",
align: "center",
dataIndex: "key",
addDisplay: false,
editDisplay: false,
search: true,
commonRules: [{ required: true, message: "字典键值必填" }]
},
{
title: "排序",
align: "center",
dataIndex: "sort",
formType: "input-number",
addDefaultValue: 1,
width: 130,
min: 0,
max: 1000
},
{
title: "状态",
align: "center",
dataIndex: "status",
search: true,
addDefaultValue: "1",
formType: "radio",
dict: { name: "data_status", props: { label: "title", value: "key" } },
width: 70
},
{
title: "备注",
align: "center",
dataIndex: "remark",
hide: true,
formType: "textarea"
},
{
title: "更新时间",
align: "center",
dataIndex: "update_datetime",
addDisplay: false,
editDisplay: false,
search: true,
formType: "range",
width: 110
},
{
title: "文档名称",
dataIndex: "doc_name",
align: "center",
search: false,
placeholder: "如果不是标准则不填"
},
{
title: "发布日期",
dataIndex: "publish_date",
align: "center",
search: false,
placeholder: "如果不是标准则不填"
},
{
title: "标准来源",
dataIndex: "source",
align: "center",
search: false,
placeholder: "如果不是标准则不填"
}
])
return {
crudRef,
crudOptions,
columns
}
}

View File

@@ -0,0 +1,63 @@
import { ref, type Ref } from "vue"
import dictApi from "@/api/system/dict"
import { Message } from "@arco-design/web-vue"
import MaCrud from "@/components/ma-crud/index.vue"
/**
* 1.打开该modal方法/改变状态/改变排序
*/
export default function useOpenChangeModal(crudRef: Ref<InstanceType<typeof MaCrud>>, currentRow: Ref<any>) {
// refs
const visible = ref(false)
// 事件处理
const changeSort = async (value: number, id: number): Promise<void> => {
const response = await dictApi.numberOperation({ id, numberName: "sort", numberValue: value })
response.success && Message.success(response.message)
crudRef.value.refresh()
}
const changeStatus = async (status: number | string, id: number): Promise<void> => {
const response = await dictApi.changeItemStatus({ id, status })
if (response.success) {
Message.success(response.message)
}
}
// 定义该open方法并暴露给组件外部
// 打开a-modal事件
const open = (row: Ref<any>) => {
currentRow.value = row
// 调用固定方法请求数据
crudRef.value.requestParams = { id: currentRow.value.id }
crudRef.value.requestData()
visible.value = true
// 判断如果是行数据的code值为standard则不显示文档名称发布来源发布日期,且表单也不显示
// columnService可以动态设置表格列的属性!!!
const columnService = crudRef.value.getColumnService()
if (currentRow.value.code === "standard") {
columnService.get("doc_name").setAttr("hide", false)
columnService.get("publish_date").setAttr("hide", false)
columnService.get("source").setAttr("hide", false)
columnService.get("doc_name").setAttr("addDisplay", true)
columnService.get("publish_date").setAttr("addDisplay", true)
columnService.get("source").setAttr("addDisplay", true)
columnService.get("doc_name").setAttr("editDisplay", true)
columnService.get("publish_date").setAttr("editDisplay", true)
columnService.get("source").setAttr("editDisplay", true)
} else {
columnService.get("doc_name").setAttr("hide", true)
columnService.get("publish_date").setAttr("hide", true)
columnService.get("source").setAttr("hide", true)
columnService.get("doc_name").setAttr("addDisplay", false)
columnService.get("publish_date").setAttr("addDisplay", false)
columnService.get("source").setAttr("addDisplay", false)
columnService.get("doc_name").setAttr("editDisplay", false)
columnService.get("publish_date").setAttr("editDisplay", false)
columnService.get("source").setAttr("editDisplay", false)
}
}
return {
visible,
changeSort,
changeStatus,
open
}
}

View File

@@ -1,197 +0,0 @@
<template>
<a-modal v-model:visible="visible" width="auto" draggable :footer="false">
<template #title>维护数据字典 {{ currentRow.name }}</template>
<!-- crud组件 -->
<div class="lg:w-full w-full lg:mt-0">
<ma-crud :options="crudOptions" :columns="columns" ref="crudRef">
<!-- 排序列 -->
<template #sort="{ record }">
<a-input-number
:default-value="record.sort"
mode="button"
@change="changeSort($event, record.id)"
:min="0"
:max="1000"
/>
</template>
<!-- 状态列 -->
<template #status="{ record }">
<a-switch
:checked-value="1"
unchecked-value="2"
@change="changeStatus($event, record.id)"
:default-checked="record.status == 1"
/>
</template>
</ma-crud>
</div>
</a-modal>
</template>
<script setup>
import { ref } from "vue"
import dictApi from "@/api/system/dict"
import { Message } from "@arco-design/web-vue"
const crudRef = ref()
const visible = ref(false)
const currentRow = ref({ id: undefined, name: undefined })
// 改变dictItem的sort字段
const changeSort = async (value, id) => {
const response = await dictApi.numberOperation({ id, numberName: "sort", numberValue: value })
response.success && Message.success(response.message)
crudRef.value.refresh()
}
// 改变dictItem状态
const changeStatus = async (status, id) => {
const response = await dictApi.changeItemStatus({ id, status })
if (response.success) {
Message.success(response.message)
}
}
// 打开a-modal事件
const open = (row) => {
currentRow.value = row
// 调用固定方法请求数据
crudRef.value.requestParams = { id: currentRow.value.id }
crudRef.value.requestData()
visible.value = true
// 判断如果是行数据的code值为standard则不显示文档名称发布来源发布日期,且表单也不显示
// columnService可以动态设置表格列的属性!!!
const columnService = crudRef.value.getColumnService()
if (currentRow.value.code === "standard") {
columnService.get("doc_name").setAttr("hide", false)
columnService.get("publish_date").setAttr("hide", false)
columnService.get("source").setAttr("hide", false)
columnService.get("doc_name").setAttr("addDisplay", true)
columnService.get("publish_date").setAttr("addDisplay", true)
columnService.get("source").setAttr("addDisplay", true)
columnService.get("doc_name").setAttr("editDisplay", true)
columnService.get("publish_date").setAttr("editDisplay", true)
columnService.get("source").setAttr("editDisplay", true)
} else {
columnService.get("doc_name").setAttr("hide", true)
columnService.get("publish_date").setAttr("hide", true)
columnService.get("source").setAttr("hide", true)
columnService.get("doc_name").setAttr("addDisplay", false)
columnService.get("publish_date").setAttr("addDisplay", false)
columnService.get("source").setAttr("addDisplay", false)
columnService.get("doc_name").setAttr("editDisplay", false)
columnService.get("publish_date").setAttr("editDisplay", false)
columnService.get("source").setAttr("editDisplay", false)
}
}
// crud选项
const crudOptions = ref({
autoRequest: false,
api: dictApi.getDictItemAll,
showIndex: false,
rowSelection: { showCheckedAll: true },
operationColumn: true,
operationWidth: 160,
operationColumnAlign: "center",
showTools: false,
beforeAdd: (form) => {
form.id = currentRow.value?.id
},
add: { show: true, api: dictApi.saveDictItem },
edit: { show: true, api: dictApi.updateDictItemData },
delete: { show: true, api: dictApi.realDeleteItem },
afterDelete(response) {
crudRef.value.tableRef.selectAll(false)
}
})
// crudColumns
const columns = ref([
{ title: "ID", dataIndex: "id", addDisplay: false, editDisplay: false, width: 50, hide: true },
{
title: "字典标签",
align: "center",
dataIndex: "title",
search: true,
width: 150,
commonRules: [{ required: true, message: "字典标签必填" }]
},
{
title: "字段缩写",
dataIndex: "show_title",
align: "center",
search: true
},
{
title: "字典键值",
align: "center",
dataIndex: "key",
addDisplay: false,
editDisplay: false,
search: true,
commonRules: [{ required: true, message: "字典键值必填" }]
},
{
title: "排序",
align: "center",
dataIndex: "sort",
formType: "input-number",
addDefaultValue: 1,
width: 130,
min: 0,
max: 1000
},
{
title: "状态",
align: "center",
dataIndex: "status",
search: true,
addDefaultValue: "1",
formType: "radio",
dict: { name: "data_status", props: { label: "title", value: "key" } },
width: 70
},
{
title: "备注",
align: "center",
dataIndex: "remark",
hide: true,
formType: "textarea"
},
{
title: "更新时间",
align: "center",
dataIndex: "update_datetime",
addDisplay: false,
editDisplay: false,
search: true,
formType: "range",
width: 110
},
{
title: "文档名称",
dataIndex: "doc_name",
align: "center",
search: false,
placeholder: "如果不是标准则不填"
},
{
title: "发布日期",
dataIndex: "publish_date",
align: "center",
search: false,
placeholder: "如果不是标准则不填"
},
{
title: "标准来源",
dataIndex: "source",
align: "center",
search: false,
placeholder: "如果不是标准则不填"
}
])
// 暴露自己的open方法
defineExpose({ open })
</script>
<style lang="less" scoped></style>

View File

@@ -32,88 +32,13 @@
</template>
<script setup lang="jsx">
import { ref } from "vue"
import dictApi from "@/api/system/dict"
import { Message } from "@arco-design/web-vue"
import DataList from "./dataList.vue"
const crudRef = ref()
const datalistRef = ref()
// 打开datalist页面
const openDictList = async (row) => {
datalistRef.value.open(row)
}
// 点击切换status
const changeStatus = async (status, id) => {
const response = await dictApi.changeStatus({ id, status })
if (response.success) {
Message.success(response.message)
}
}
const crudOptions = ref({
api: dictApi.getDictIndex,
showIndex: false,
operationColumnAlign: "center",
rowSelection: { showCheckedAll: true },
searchColNumber: 4,
tablePagination: false,
operationColumn: true,
showTools: false,
afterDelete(response) {
crudRef.value.tableRef.selectAll(false)
}
})
const crudColumns = ref([
{ title: "ID", dataIndex: "id", addDisplay: false, editDisplay: false, width: 50, hide: true },
{
title: "字典备注",
dataIndex: "remark",
search: true,
width: 220,
align: "center",
commonRules: [{ required: true, message: "字典备注必填" }]
},
{
title: "字典标识",
dataIndex: "code",
search: true,
width: 260,
align: "center",
commonRules: [{ required: true, message: "字典标识必填" }]
},
{
title: "状态",
dataIndex: "status",
search: true,
formType: "radio",
align: "center",
dict: { name: "data_status", props: { label: "title", value: "key" } },
addDefaultValue: "1",
width: 180
},
{
title: "字典名称",
dataIndex: "name",
hide: true,
formType: "textarea",
align: "center"
},
{
title: "更新时间",
dataIndex: "update_datetime",
addDisplay: false,
editDisplay: false,
align: "center",
search: true,
formType: "range",
width: 180
}
])
import DataList from "./DataList/index.vue"
import useDictCrud from "./useDictCrud"
import useDataListRef from "./useDataListRef"
// 1.useDictCrud
const { crudRef, changeStatus, crudOptions, crudColumns } = useDictCrud()
// 2.dataList.vue界面打开和ref定义
const { datalistRef, openDictList } = useDataListRef()
defineOptions({
name: "dictmanage"
})

View File

@@ -0,0 +1,15 @@
import { ref } from "vue"
/**
* 该hook仅定义dataList的ref以及调用open函数
*/
export default function useDataListRef() {
const datalistRef = ref()
// 打开datalist页面
const openDictList = async (row: object) => {
datalistRef.value.open(row)
}
return {
datalistRef,
openDictList
}
}

View File

@@ -0,0 +1,89 @@
import { ref } from "vue"
import dictApi from "@/api/system/dict"
import { Message } from "@arco-design/web-vue"
/**
* dict主页面展示配置以及ref定义
*/
export default function useDictCrud() {
// refs
const crudRef = ref()
// 事件函数
const changeStatus = async (status: string, id: number) => {
// 点击切换status
try {
const response = await dictApi.changeStatus({ id, status })
if (response.success) {
Message.success(response.message)
}
} catch (err) {
return
}
}
// crud定义
const crudOptions = ref({
api: dictApi.getDictIndex,
showIndex: false,
operationColumnAlign: "center",
rowSelection: { showCheckedAll: true },
searchColNumber: 4,
tablePagination: false,
operationColumn: true,
showTools: false,
afterDelete() {
crudRef.value.tableRef.selectAll(false)
}
})
const crudColumns = ref([
{ title: "ID", dataIndex: "id", addDisplay: false, editDisplay: false, width: 50, hide: true },
{
title: "字典备注",
dataIndex: "remark",
search: true,
width: 220,
align: "center",
commonRules: [{ required: true, message: "字典备注必填" }]
},
{
title: "字典标识",
dataIndex: "code",
search: true,
width: 260,
align: "center",
commonRules: [{ required: true, message: "字典标识必填" }]
},
{
title: "状态",
dataIndex: "status",
search: true,
hide: true,
formType: "radio",
align: "center",
dict: { name: "data_status", props: { label: "title", value: "key" } },
addDefaultValue: "1",
width: 180
},
{
title: "字典名称",
dataIndex: "name",
hide: true,
formType: "textarea",
align: "center"
},
{
title: "更新时间",
dataIndex: "update_datetime",
addDisplay: false,
editDisplay: false,
align: "center",
search: true,
formType: "range",
width: 180
}
])
return {
crudRef,
changeStatus,
crudOptions,
crudColumns
}
}

View File

@@ -0,0 +1,274 @@
import { ref, getCurrentInstance } from "vue"
import PinYinMatch from "pinyin-match"
import { useTreeDataStore } from "@/store"
import { useRoute } from "vue-router"
import testDemandApi from "@/api/project/testDemand"
import { isEqual, cloneDeep } from "lodash"
interface ITestContent {
subName: string
subDesc: string
}
/**
* 1.配置crud以及全组件使用变量
* 2.另外包含一个测试项是否保留数据的功能含一个ref以及一个事件处理函数
*/
export default function useCrudRef() {
// global
const treeDataStore = useTreeDataStore()
const route = useRoute()
// variable
const roundNumber = (route.query.key as string)!.split("-")[0]
const dutNumber = (route.query.key as string)!.split("-")[1]
const designDemandNumber = (route.query.key as string)!.split("-")[2]
// refs
const projectId = ref(route.query.id)
const crudRef = ref()
// 处理弹窗关闭事件:处理用户数据是否保留
const app = getCurrentInstance()!.appContext.config.globalProperties
let beforeFormContent: ITestContent[] | undefined = undefined
const handleBeforeCancel = () => {
if (!beforeFormContent) {
return
}
const iuEqualValue = isEqual(crudRef.value.getFormData().testContent, beforeFormContent)
!iuEqualValue &&
app.$modal.confirm({
title: "测试项步骤内容你已改动,是否保留您编写的测试项/测试用例步骤数据?",
content: "",
okText: "保留",
cancelText: "恢复原数据",
simple: true,
onOk: () => null,
onCancel: () => {
crudRef.value.refresh()
}
})
}
// 配置
// crud组件
const crudOptions = ref({
api: testDemandApi.getTestDemandList,
add: { show: true, api: testDemandApi.save, text: "新增测试项" },
edit: { show: true, api: testDemandApi.update, text: "修改测试项" },
delete: { show: true, api: testDemandApi.delete },
showTools: false,
beforeOpenAdd: function () {
// 1.新增则将form的content数据变为undifined以便判断
beforeFormContent = undefined
// 2.设置标识
let key_split = (route.query.key as string)!.split("-")
let round_key = key_split[0]
let dut_key = key_split[1]
let design_key = key_split[2]
let td = treeDataStore.treeData
crudRef.value.crudFormRef.actionTitle = `${route.query.ident} > ${td[round_key].title} > ${td[round_key].children[dut_key].title} > ${td[round_key].children[dut_key].children[design_key].title} > 测试项-`
return true
},
beforeOpenEdit: function (record) {
// 1.储存打开前form的content数据到ref中以便后续比较
beforeFormContent = cloneDeep(record.testContent)
// 2.处理标识
let key_split = (route.query.key as string)!.split("-")
let round_key = key_split[0]
let dut_key = key_split[1]
let design_key = key_split[2]
let td = treeDataStore.treeData
crudRef.value.crudFormRef.actionTitle = `${route.query.ident} > ${td[round_key].title} > ${td[round_key].children[dut_key].title} > ${td[round_key].children[dut_key].children[design_key].title} >测试项[${record.name}]-`
return true
},
afterAdd: (res) => {
let id = projectId.value
treeDataStore.updateTestDemandTreeData(res.data, id)
},
afterEdit: (res) => {
let id = projectId.value
treeDataStore.updateTestDemandTreeData(res.data, id)
},
afterDelete: (res, record) => {
let id = projectId.value
if (!record) {
record = { key: route.query.key + "-X" }
}
treeDataStore.updateTestDemandTreeData(record, id)
// 清空选择
crudRef.value.tableRef.selectAll(false)
},
parameters: {
projectId: route.query.id,
round: roundNumber,
dut: dutNumber,
designDemand: designDemandNumber
},
showIndex: false,
rowSelection: { showCheckedAll: true },
searchColNumber: 3,
tablePagination: false,
operationColumn: true,
operationColumnAlign: "center",
formOption: {
width: 1200,
layout: [
{
formType: "grid",
cols: [
{ span: 12, formList: [{ dataIndex: "ident" }] },
{ span: 12, formList: [{ dataIndex: "name" }] }
]
},
{
formType: "grid",
cols: [{ span: 24, formList: [{ dataIndex: "priority" }] }]
},
{
formType: "grid",
cols: [
{ span: 12, formList: [{ dataIndex: "testType" }] },
{ span: 12, formList: [{ dataIndex: "testMethod" }] }
]
}
]
}
})
const crudColumns = ref([
{
title: "ID",
align: "center",
hide: true,
dataIndex: "id"
},
{
title: "测项标识",
width: 150,
dataIndex: "ident",
sortable: { sortDirections: ["ascend"] },
align: "center",
search: true,
validateTrigger: "blur",
placeholder: "请填写测试项的标识,注意标识不能重复",
commonRules: [{ required: true, message: "测试项标识必填" }],
openPrepend: true
},
{
title: "名称",
dataIndex: "name",
width: 120,
align: "center",
search: true,
commonRules: [{ required: true, message: "名称是必填" }],
validateTrigger: "blur"
},
{
title: "优先级",
dataIndex: "priority",
search: true,
formType: "radio",
align: "center",
addDefaultValue: "1",
dict: {
name: "priority",
props: { label: "title", value: "key" },
translation: true,
tagColors: { 1: "red", 2: "blue", 3: "green" }
}
},
{
title: "测试类型",
dataIndex: "testType",
search: true,
align: "center",
formType: "select",
sortable: { sortDirections: ["ascend", "descend"] },
addDefaultValue: "4",
maxLength: 200,
commonRules: [{ required: true, message: "测试类型必选" }],
dict: { name: "testType", translation: true, props: { label: "title", value: "key" } },
extra: "请保证测试类型选择正确",
// 这是arco的属性所以在ma-crud和ma-form可以直接使用arco属性和事件事件+onXXX
filterOption: function (inputValue, selectedOption) {
if (inputValue) {
let matchRes = PinYinMatch.match(selectedOption.label, inputValue)
if (matchRes) {
return true
}
}
}
},
{
title: "测试手段",
align: "center",
dataIndex: "testMethod",
formType: "select",
multiple: true,
dict: { name: "testMethod", props: { label: "title", value: "key" }, translation: true }
},
{
title: "充分性要求",
hide: true,
dataIndex: "adequacy",
formType: "textarea",
maxLength: 256,
commonRules: [{ required: true, message: "充分性描述必填" }],
addDefaultValue:
"测试用例覆盖XX子项名称1、XX子项名称2、XX子项名称3子项要求的全部内容。\n所有用例执行完毕对于未执行的用例说明未执行原因。"
},
{
title: "测试子项",
hide: true,
dataIndex: "testContent",
commonRules: [{ required: true, message: "测试方法是必填的" }],
formType: "children-form",
formList: [
{
title: "子项名称",
dataIndex: "subName",
placeholder: "对应测试项描述标题,和测试方法的标题",
rules: [{ required: true, message: "测试子项名称必填" }],
onChange: (ev) => {
// 取出子项的对象数组
const subItemFormData = crudRef.value.getFormData().testContent
// 取出充分性条件字段字符串
const mapRes = subItemFormData.map((subItem) => subItem.subName)
crudRef.value.getFormData().adequacy = `测试用例覆盖${mapRes.join(
"、"
)}子项要求的全部内容。\n所有用例执行完毕对于未执行的用例说明未执行原因。`
}
},
{
title: "子项描述",
dataIndex: "subDesc",
placeholder: "对应大纲测试项表格的测试项描述",
rules: [{ required: true, message: "测试子项描述必填" }]
},
{
title: "条件",
dataIndex: "condition",
placeholder: "在什么环境和前置条件下"
},
{
title: "操作",
dataIndex: "operation",
placeholder: "通过xxx操作"
},
{
title: "观察",
dataIndex: "observe",
placeholder: "查看xxx内容"
},
{
title: "期望",
dataIndex: "expect",
placeholder: "xxx结果正确"
}
]
}
])
return {
projectId,
crudRef,
crudOptions,
crudColumns,
handleBeforeCancel
}
}

View File

@@ -0,0 +1,91 @@
import { ref, computed, type Ref } from "vue"
import testDemandApi from "@/api/project/testDemand"
import { Message } from "@arco-design/web-vue"
import { useRoute } from "vue-router"
/**
* 关联测试项弹窗,并提供事件处理函数
*/
export default function useRalateDemand(projectId: Ref<string>) {
// global
const route = useRoute()
const roundNumber = (route.query.key as string)!.split("-")[0]
const dutNumber = (route.query.key as string)!.split("-")[1]
const designDemandNumber = (route.query.key as string)!.split("-")[2]
// refs
const visible = ref(false) // 弹窗显示隐藏
const relatedData: Ref<any[]> = ref([]) // 已关联数据
const options = ref<any>([]) // 级联cascade组件options
const cascaderLoading = ref(false) // 级联的加载圈
const computedRelatedData = computed(() => {
const labelResultList: any[] = []
options.value.forEach((item: any) => {
if (item.children) {
item.children.forEach((child: any) => {
if (relatedData.value.includes(child.value)) {
labelResultList.push(child.label)
}
})
}
})
return labelResultList
})
// 点击关联测试项-button
const handleOpenRelationCSX = async () => {
// 请求接口获取数据
cascaderLoading.value = true
visible.value = true
// 点击进入时清除关联
relatedData.value = []
const res = await testDemandApi.getRelatedTestDemand({ id: projectId.value, round: roundNumber })
options.value = res.data
// 找出本设计需求design对应已关联的测试项
const res_exist = await testDemandApi.getExistRelatedTestDemand({
project_id: projectId.value,
roundNumber,
dutNumber,
designDemandNumber
})
relatedData.value = res_exist.data
cascaderLoading.value = false
}
// 点击关联确定按钮
const handleRelatedOk = async () => {
// 获取级联数据
const relationDestItemIds = relatedData.value
if (relationDestItemIds.length > 0) {
const res = await testDemandApi.solveRelatedTestDemand({
data: relationDestItemIds,
project_id: projectId.value,
roundNumber,
dutNumber,
designDemandNumber
})
if (res.code == 200) {
Message.success(res.message)
return true
}
} else {
const res = await testDemandApi.solveRelatedTestDemand({
data: [],
project_id: projectId.value,
roundNumber,
dutNumber,
designDemandNumber
})
if (res.code == 200) {
Message.success(res.message)
return true
}
}
return false
}
return {
visible,
relatedData,
options,
cascaderLoading,
computedRelatedData,
handleOpenRelationCSX,
handleRelatedOk
}
}

View File

@@ -2,7 +2,7 @@
<div class="ma-content-block lg:flex justify-between p-4">
<div class="lg:w-full w-full lg:ml-4 mt-5 lg:mt-0">
<!-- CRUD组件 -->
<ma-crud :options="crudOptions" :columns="crudColumns" ref="crudRef">
<ma-crud :options="crudOptions" :columns="crudColumns" ref="crudRef" @beforeCancel="handleBeforeCancel">
<template #ident="{ record }">
{{ showType(record) }}
</template>
@@ -42,94 +42,17 @@
</template>
<script setup>
import { ref, computed } from "vue"
import { useRoute } from "vue-router"
import testDemandApi from "@/api/project/testDemand"
import { useTreeDataStore } from "@/store"
import { ref } from "vue"
import commonApi from "@/api/common"
import PinYinMatch from "pinyin-match"
import { Message } from "@arco-design/web-vue"
// hooks
import useCrudRef from "./hooks/useCrudRef"
import useRalateDemand from "./hooks/useRalateDemand"
const treeDataStore = useTreeDataStore()
const route = useRoute()
const crudRef = ref()
// 根据传参获取key分别为轮次、设计需求的key
const roundNumber = route.query.key.split("-")[0]
const dutNumber = route.query.key.split("-")[1]
const designDemandNumber = route.query.key.split("-")[2]
const projectId = ref(route.query.id)
// ~~~~~关联相关变量和函数~~~~~
// 定义关联弹窗变量函数
const visible = ref(false)
const relatedData = ref([])
const computedRelatedData = computed(() => {
const labelResultList = []
options.value.forEach((item) => {
if (item.children) {
item.children.forEach((child) => {
if (relatedData.value.includes(child.value)) {
labelResultList.push(child.label)
}
})
}
})
return labelResultList
})
// 定义cascader的加载圈
const cascaderLoading = ref(false)
// 点击关联测试项-button
const handleOpenRelationCSX = async () => {
// 请求接口获取数据
cascaderLoading.value = true
visible.value = true
// 点击进入时清除关联
relatedData.value = []
const res = await testDemandApi.getRelatedTestDemand({ id: projectId.value, round: roundNumber })
options.value = res.data
// 找出本设计需求design对应已关联的测试项
const res_exist = await testDemandApi.getExistRelatedTestDemand({
project_id: projectId.value,
roundNumber,
dutNumber,
designDemandNumber
})
relatedData.value = res_exist.data
cascaderLoading.value = false
}
// 点击关联确定按钮
const handleRelatedOk = async () => {
// 获取级联数据
const relationDestItemIds = relatedData.value
if (relationDestItemIds.length > 0) {
const res = await testDemandApi.solveRelatedTestDemand({
data: relationDestItemIds,
project_id: projectId.value,
roundNumber,
dutNumber,
designDemandNumber
})
if (res.code == 200) {
Message.success(res.message)
return true
}
} else {
const res = await testDemandApi.solveRelatedTestDemand({
data: [],
project_id: projectId.value,
roundNumber,
dutNumber,
designDemandNumber
})
if (res.code == 200) {
Message.success(res.message)
return true
}
}
return false
}
// 级联cascade组件options
const options = ref([])
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
const { projectId, crudRef, crudOptions, crudColumns, handleBeforeCancel } = useCrudRef()
// 关联弹窗、关联的事件处理
const { visible, relatedData, options, cascaderLoading, computedRelatedData, handleOpenRelationCSX, handleRelatedOk } =
useRalateDemand(projectId)
// 标识显示字段
const testTypeDict = ref([])
@@ -147,204 +70,12 @@ const showType = (record) => {
}
}
}
// crud组件
const crudOptions = ref({
api: testDemandApi.getTestDemandList,
add: { show: true, api: testDemandApi.save, text: "新增测试项" },
edit: { show: true, api: testDemandApi.update, text: "修改测试项" },
delete: { show: true, api: testDemandApi.delete },
showTools: false,
beforeOpenAdd: function () {
let key_split = route.query.key.split("-")
let round_key = key_split[0]
let dut_key = key_split[1]
let design_key = key_split[2]
let td = treeDataStore.treeData
crudRef.value.crudFormRef.actionTitle = `${route.query.ident} > ${td[round_key].title} > ${td[round_key].children[dut_key].title} > ${td[round_key].children[dut_key].children[design_key].title} > 测试项-`
return true
},
beforeOpenEdit: function (record) {
let key_split = route.query.key.split("-")
let round_key = key_split[0]
let dut_key = key_split[1]
let design_key = key_split[2]
let td = treeDataStore.treeData
crudRef.value.crudFormRef.actionTitle = `${route.query.ident} > ${td[round_key].title} > ${td[round_key].children[dut_key].title} > ${td[round_key].children[dut_key].children[design_key].title} >测试项[${record.name}]-`
return true
},
afterAdd: (res) => {
let id = projectId.value
treeDataStore.updateTestDemandTreeData(res.data, id)
},
afterEdit: (res) => {
let id = projectId.value
treeDataStore.updateTestDemandTreeData(res.data, id)
},
afterDelete: (res, record) => {
let id = projectId.value
if (!record) {
record = { key: route.query.key + "-X" }
}
treeDataStore.updateTestDemandTreeData(record, id)
// 清空选择
crudRef.value.tableRef.selectAll(false)
},
parameters: {
projectId: route.query.id,
round: roundNumber,
dut: dutNumber,
designDemand: designDemandNumber
},
showIndex: false,
rowSelection: { showCheckedAll: true },
searchColNumber: 3,
tablePagination: false,
operationColumn: true,
operationColumnAlign: "center",
formOption: {
width: 1200
}
})
const crudColumns = ref([
{
title: "ID",
align: "center",
hide: true,
dataIndex: "id"
},
{
title: "测项标识",
width: 150,
dataIndex: "ident",
sortable: { sortDirections: ["ascend"] },
align: "center",
search: true,
validateTrigger: "blur",
placeholder: "请填写测试项的标识,注意标识不能重复",
commonRules: [{ required: true, message: "测试项标识必填" }],
openPrepend: true
},
{
title: "名称",
dataIndex: "name",
width: 120,
align: "center",
search: true,
commonRules: [{ required: true, message: "名称是必填" }],
validateTrigger: "blur"
},
{
title: "优先级",
dataIndex: "priority",
search: true,
formType: "radio",
align: "center",
addDefaultValue: "1",
dict: {
name: "priority",
props: { label: "title", value: "key" },
translation: true,
tagColors: { 1: "red", 2: "blue", 3: "green" }
}
},
{
title: "测试类型",
dataIndex: "testType",
search: true,
align: "center",
formType: "select",
sortable: { sortDirections: ["ascend", "descend"] },
addDefaultValue: "4",
maxLength: 200,
commonRules: [{ required: true, message: "测试类型必选" }],
dict: { name: "testType", translation: true, props: { label: "title", value: "key" } },
extra: "请保证测试类型选择正确",
// 这是arco的属性所以在ma-crud和ma-form可以直接使用arco属性和事件事件+onXXX
filterOption: function (inputValue, selectedOption) {
if (inputValue) {
let matchRes = PinYinMatch.match(selectedOption.label, inputValue)
if (matchRes) {
return true
}
}
}
},
{
title: "测试手段",
align: "center",
dataIndex: "testMethod",
formType: "select",
multiple: true,
dict: { name: "testMethod", props: { label: "title", value: "key" }, translation: true }
},
{
title: "充分性要求",
hide: true,
addDefaultValue: "覆盖需求相关功能",
dataIndex: "adequacy",
formType: "textarea",
maxLength: 256,
commonRules: [{ required: true, message: "充分性描述必填" }],
addDefaultValue:
"测试用例覆盖XX子项名称1、XX子项名称2、XX子项名称3子项要求的全部内容。\n所有用例执行完毕对于未执行的用例说明未执行原因。"
},
{
title: "测试子项",
hide: true,
dataIndex: "testContent",
commonRules: [{ required: true, message: "测试方法是必填的" }],
formType: "children-form",
formList: [
{
title: "子项名称",
dataIndex: "subName",
placeholder: "对应测试项描述标题,和测试方法的标题",
rules: [{ required: true, message: "测试子项名称必填" }],
onChange: (ev) => {
// 取出子项的对象数组
const subItemFormData = crudRef.value.getFormData().testContent
// 取出充分性条件字段字符串
const mapRes = subItemFormData.map((subItem) => subItem.subName)
crudRef.value.getFormData().adequacy = `测试用例覆盖${mapRes.join(
"、"
)}子项要求的全部内容。\n所有用例执行完毕对于未执行的用例说明未执行原因。`
}
},
{
title: "子项描述",
dataIndex: "subDesc",
placeholder: "对应大纲测试项表格的测试项描述",
rules: [{ required: true, message: "测试子项描述必填" }]
},
{
title: "条件",
dataIndex: "condition",
placeholder: "在什么环境和前置条件下"
},
{
title: "操作",
dataIndex: "operation",
placeholder: "通过xxx操作"
},
{
title: "观察",
dataIndex: "observe",
placeholder: "查看xxx内容"
},
{
title: "期望",
dataIndex: "expect",
placeholder: "xxx结果正确"
}
]
}
])
// 暴露给route-view的刷新表格函数
const refreshCrudTable = () => {
crudRef.value.refresh()
}
defineExpose({ refreshCrudTable })
defineOptions({
name: "designDemand"
})

View File

@@ -2,7 +2,7 @@
<div class="ma-content-block lg:flex justify-between p-4">
<div class="lg:w-full w-full lg:ml-4 mt-5 lg:mt-0">
<!-- CRUD组件 -->
<ma-crud :options="crudOptions" :columns="crudColumns" ref="crudRef">
<ma-crud :options="crudOptions" :columns="crudColumns" ref="crudRef" @beforeCancel="handleBeforeCancel">
<template #ident="{ record }">
{{ showType(record) }}
</template>
@@ -13,11 +13,12 @@
</template>
<script setup lang="jsx">
import { ref } from "vue"
import { ref, getCurrentInstance } from "vue"
import { useRoute } from "vue-router"
import caseApi from "@/api/project/case"
import { useTreeDataStore } from "@/store"
import ProblemForm from "@/views/project/case/components/ProblemForm.vue"
import { isEqual, cloneDeep } from "lodash"
const problemFormRef = ref(null)
const title = ref("问题单表单")
const treeDataStore = useTreeDataStore()
@@ -34,7 +35,29 @@ const showType = (record) => {
return "YL-" + record.testType + "-" + record.ident + "-" + key_string.toString().padStart(3, "0")
}
// crud设置
// crud设置以及是否保留step数据事件函数
const app = getCurrentInstance().appContext.config.globalProperties
let beforeFormStep = undefined
const handleBeforeCancel = () => {
if (!beforeFormStep) {
return
}
console.log(beforeFormStep)
crudRef.value.getFormData().testStep
const iuEqualValue = isEqual(crudRef.value.getFormData().testStep, beforeFormStep)
!iuEqualValue &&
app.$modal.confirm({
title: "测试项步骤内容你已改动,是否保留您编写的测试项/测试用例步骤数据?",
content: "",
okText: "保留",
cancelText: "恢复原数据",
simple: true,
onOk: () => null,
onCancel: () => {
crudRef.value.refresh()
}
})
}
const crudOptions = ref({
api: caseApi.getCaseList,
add: { show: true, api: caseApi.save, text: "新增用例" },
@@ -44,6 +67,9 @@ const crudOptions = ref({
isDbClickEdit: false, // 关闭双击编辑
// 处理新增删除后树状图显示
beforeOpenAdd: function () {
// 1.新增则将form的content数据变为undifined以便判断
beforeFormStep = undefined
// 2.标识处理
let key_split = route.query.key.split("-")
let round_key = key_split[0]
let dut_key = key_split[1]
@@ -57,6 +83,9 @@ const crudOptions = ref({
return true
},
beforeOpenEdit: function (record) {
// 1.储存打开前form的content数据到ref中以便后续比较
beforeFormStep = cloneDeep(record.testStep)
// 2.标识处理
let key_split = route.query.key.split("-")
let round_key = key_split[0]
let dut_key = key_split[1]