diff --git a/config/routes.ts b/config/routes.ts index 9881f9c14f2a8b42bc6e481bbc50e2ef7e574d14..647b79f41c9c3c6f9e1b21bde91c1a2066095844 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -12,7 +12,20 @@ { path: "/plan", name: "plan", - component: "./Plan", + routes: [ + { + path: "/plan/create", + name: "create", + hideInMenu: true, + component: "./Plan", + }, + { + path: "/plan/:plan_id?", + hideInMenu: true, + name: "plan", + component: "./Plan", + }, + ] }, { path: "/cases", @@ -20,6 +33,7 @@ routes: [ { path: "/cases/:mod_id?", + hideInMenu: true, component: "./Suite", }, ] diff --git a/src/components/CustomStyled.tsx b/src/components/CustomStyled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e31981797821d1fe4db9512c148118b76521793d --- /dev/null +++ b/src/components/CustomStyled.tsx @@ -0,0 +1,20 @@ +import styled from "styled-components" +import { Table, Form } from "antd" + +export const CustomTable = styled(Table)` + .ant-table-small .ant-table-thead > tr > th { + background-color: transparent; + } + .ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + .ant-table-expanded-row.ant-table-expanded-row-level-1 > td { + padding: 0!important; + } +` + +export const CustomForm = styled(Form)` +.ant-form-item { + margin-bottom: 12px; +} +` \ No newline at end of file diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ddb61faeac2b8658d957a3bdc30b2d62a7c1811b --- /dev/null +++ b/src/components/Loading.tsx @@ -0,0 +1,20 @@ +import React from "react" +import { Row, Spin } from "antd" +import styled from "styled-components" + +const LoadingWrapper = styled(Row)` + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + background: #fff; + padding-top: 300px; +` +const Loading: React.FC = () => ( + + + +) + +export default Loading \ No newline at end of file diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts index 0e122e7cff689a222db48c5df1e788dbd359005c..7a27b2071fd7c0001545ed9380bfe77776ef738b 100644 --- a/src/locales/zh-CN/menu.ts +++ b/src/locales/zh-CN/menu.ts @@ -3,6 +3,8 @@ export default { "menu.outline": "测试大纲", "menu.demand": "测试需求", "menu.plan": "测试方案", + "menu.plan.plan": "测试方案", + "menu.plan.create": "创建方案", "menu.suite": "测试用例", "menu.task": "测试任务", "menu.server": "测试设备", diff --git a/src/pages/Plan/components/ContentTable/Suite.table.tsx b/src/pages/Plan/components/ContentTable/Suite.table.tsx index ae835381461ba98488c49979c6f74e51403dde68..5581b40cc8a6bfad76192b3bc34ee6a9e4681c08 100644 --- a/src/pages/Plan/components/ContentTable/Suite.table.tsx +++ b/src/pages/Plan/components/ContentTable/Suite.table.tsx @@ -1,71 +1,65 @@ import React from "react" -import { Table , Space, Typography, Row, Button } from "antd" -import { queryTableList } from "./services" -import { useRequest } from "ahooks" +import { Table, Space, Typography, Row, Button, TableColumnType } from "antd" +import SuiteSelet from "../SelectModal/Suite.selet" +import { CustomTable } from "@/components/CustomStyled" -const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } - -const TableList: React.FC = () => { - const [pageQuery, setPageQuery] = React.useState(DEFAULT_PAGE_QUERY) - - const { data: dataSource, loading , refresh } = useRequest( - (params = pageQuery) => queryTableList(params), - { refreshDeps: [pageQuery] } - ) - - const addModalRef = React.useRef(null) as any +const TableList: React.FC = (porps) => { + // const { dataSource } = props + const [source, setSource] = React.useState([]) + const ref = React.useRef(null) as any const handleAdd = async () => { - addModalRef.current?.show() + ref.current?.show() } - const handleEdit = async (row: any) => { - addModalRef.current?.show(row) + const handleDelete = (row: any) => { + setSource(source.filter((i: any) => i.id !== row.id)) } - const handleDelete = async (row: any) => { - - } - - const columns = [{ - title: "", - dataIndex: "", - }, { - title: "操作", - render(row: any) { - return ( - - handleEdit(row)}> - 编辑 - + const columns: TableColumnType[] = [ + { + title: "用例名称", + dataIndex: "name", + }, + { + title: "用例名称", + dataIndex: "name", + }, + { + title: "用例名称", + dataIndex: "name", + }, + { + title: "用例名称", + dataIndex: "name", + }, + { + title: "操作", + render(row) { + return ( handleDelete(row)}> - 删除 + + 删除 + - - ) - } - }] + ) + } + }, + ] return ( - 用例和机器 - + 用例 + - + ) } diff --git a/src/pages/Plan/components/ContentTable/Task.table.tsx b/src/pages/Plan/components/ContentTable/Task.table.tsx index 97ef842ef4b9ec2f34cf9a031ab135f4e5bd5e1f..86870c11ad8fdd1a4bfded038b711aaa32237813 100644 --- a/src/pages/Plan/components/ContentTable/Task.table.tsx +++ b/src/pages/Plan/components/ContentTable/Task.table.tsx @@ -1,71 +1,64 @@ import React from "react" -import { Table, Space, Typography, Row, Button } from "antd" -import { queryTableList } from "./services" -import { useRequest } from "ahooks" - -const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } +import { Table, Space, Typography, Row, Button, TableColumnType } from "antd" +import TaskSelect from "../SelectModal/Task.select" +import { CustomTable } from "@/components/CustomStyled" const TableList: React.FC = () => { - const [pageQuery, setPageQuery] = React.useState(DEFAULT_PAGE_QUERY) - - const { data: dataSource, loading, refresh } = useRequest( - (params = pageQuery) => queryTableList(params), - { refreshDeps: [pageQuery] } - ) - - const addModalRef = React.useRef(null) as any + const ref = React.useRef(null) as any + const [source, setSource] = React.useState([]) const handleAdd = async () => { - addModalRef.current?.show() + ref.current?.show() } - const handleEdit = async (row: any) => { - addModalRef.current?.show(row) + const handleDelete = (row: any) => { + setSource(source.filter((i: any) => i.id !== row.id)) } - const handleDelete = async (row: any) => { - - } - - const columns = [{ - title: "", - dataIndex: "", - }, { - title: "操作", - render(row: any) { - return ( - - handleEdit(row)}> - 编辑 - + const columns: TableColumnType[] = [ + { + title: "任务名称", + dataIndex: "name", + }, + { + title: "运行方式", + dataIndex: "name", + }, + { + title: "测试设备", + dataIndex: "name", + }, + { + title: "备注", + dataIndex: "desc", + }, + { + title: "操作", + render(row) { + return ( handleDelete(row)}> - 删除 + + 删除 + - - ) - } - }] + ) + } + }, + ] return ( 测试任务 - + -
+ ) } diff --git a/src/pages/Plan/components/LeftList/index.tsx b/src/pages/Plan/components/LeftList/index.tsx index 57ee602a347f8a17e149ec23026eef0b66356a52..b710f2983a8ed7ca12baba0c7cca23ac4f8afe40 100644 --- a/src/pages/Plan/components/LeftList/index.tsx +++ b/src/pages/Plan/components/LeftList/index.tsx @@ -1,33 +1,88 @@ import React from "react"; import { Button, Input, Row, Space, Tooltip, Typography } from "antd" import StateTag from "@/components/Public/StateTag"; +import { history, useParams } from "umi" +import { usePlanProvider } from "../../hooks"; +import styled from "styled-components" + +type PlanItemProps = { + is_active?: number; +} + +const activeCls = ` + background-color: #E6F7FF; + color: rgba(24,144,255,1); + .ant-typography { + color: rgba(24,144,255,1); + } +` +const PlanItem = styled(Row) ` + cursor: pointer; + height: 32px; + padding: 4px 12px; + border-bottom: 1px solid #D9D9D9; + ${({ is_active }) => is_active ? activeCls : ""} +` type IProps = { [k: string]: any; } const PlanLeftContent: React.FC = () => { + const { plan_id } = useParams() as any + const { planList } = usePlanProvider() return ( - - - - - +
+ + {`测试方案 (${planList.length})`} + + console.log(e)} + // suffix + /> + + + - -
- - - {"测试方案名称测试方案名称测试方案名称测试方案名称测试方案名称测试方案名称"} - - -
- -
+ { + planList.map((plan: any) => ( + history.push(`/plan/${plan.id}`)} + is_active={plan.id === +plan_id ? 1 : 0} + > +
+ + + {plan.title} + + +
+ +
+ )) + }
- +
) } diff --git a/src/pages/Plan/components/SelectModal/Suite.selet.tsx b/src/pages/Plan/components/SelectModal/Suite.selet.tsx index cf65c1c5c20950a77163ad507468d59d851a24ac..67814caa193495b078fe2450b34e5e53233ffa97 100644 --- a/src/pages/Plan/components/SelectModal/Suite.selet.tsx +++ b/src/pages/Plan/components/SelectModal/Suite.selet.tsx @@ -1,8 +1,11 @@ import React from "react" -import { Modal, Form, Input, Space, Button } from "antd" +import { Modal, Form, Input, Space, Button, Typography, Row, Radio, Checkbox, Empty, Spin } from "antd" +import { useRequest } from "umi"; +import { queryModalCases, queryModules } from "@/pages/Suite/services"; +import { ModuleItem, ModulesContainer, FuncModuleIcon } from "@/pages/Suite/components/LeftList/styled" type IProps = { - onOk: () => void; + onOk: (conf: any) => void; onCancel?: () => void; } @@ -13,6 +16,17 @@ type IRefs = { const ReactComponent: React.ForwardRefRenderFunction = (props, ref) => { const { onOk } = props + const [activeModule, setActiveModule] = React.useState(null) + + const { data: modules } = useRequest(() => queryModules(0), { initialData: [] }) + const { data: cases, run, loading: caseLoading } = useRequest( + (params = { mod_id: null }) => queryModalCases(params), { initialData: [] } + ) + + React.useEffect(() => { + run({ mod_id: activeModule }) + }, [activeModule]) + const [visible, setVisible] = React.useState(false) const [loading, setLoading] = React.useState(false) const [source, setSource] = React.useState(undefined) @@ -25,42 +39,138 @@ const ReactComponent: React.ForwardRefRenderFunction = (props, re } })) + const allCaseId = React.useMemo(() => cases.map((x: any) => x.id), [cases]) + + const [indeterminate, setIndeterminate] = React.useState(false) + const [checkAll, setCheckAll] = React.useState(false) + const [selectedKeys, setSelectedKeys] = React.useState([]) + + const onCheckAllChange = ({ target }: any) => { + setCheckAll(target.checked) + setIndeterminate(false) + setSelectedKeys(target.checked ? allCaseId : []) + } + + const onChange = (list: any) => { + setSelectedKeys(list) + setIndeterminate(!!list.length && list.length < allCaseId.length); + setCheckAll(list.length === allCaseId.length); + } + const [form] = Form.useForm() const handleCancel = () => { setVisible(false) setLoading(false) setSource(undefined) + setSelectedKeys([]) + setIndeterminate(false) + setCheckAll(false) + setActiveModule(null) } const handleOk = async () => { if (loading) return setLoading(true) - onOk() + onOk(cases.filter((i: any) => selectedKeys.includes(i.id))) handleCancel() } + const onModuleChange = (mod_id: any) => { + setActiveModule(mod_id) + setSelectedKeys([]) + } + return ( - - -
+ + + {`共${allCaseId.length}条 已选中${selectedKeys.length}条`} + + + + + + } onCancel={handleCancel} - onOk={handleOk} > -
- - - - + + + +
+ onModuleChange(null)} + > + + + 所有用例 + + + + { + modules.map((mod: any) => ( + onModuleChange(mod.id)} + is_child + > + + + + {mod.name} + + + {`(${mod.count || 0})`} + + + + )) + } + +
+
+ + { + cases.length === 0 ? + : + + + 全选 + + + + { + cases.map((i: any) => ( + {i.name} + )) + } + + + + } + +
+
+
) } diff --git a/src/pages/Plan/components/SelectModal/Task.select.tsx b/src/pages/Plan/components/SelectModal/Task.select.tsx index cf65c1c5c20950a77163ad507468d59d851a24ac..458b0da0cffcf14debb6a13f9057a908466834ba 100644 --- a/src/pages/Plan/components/SelectModal/Task.select.tsx +++ b/src/pages/Plan/components/SelectModal/Task.select.tsx @@ -1,8 +1,10 @@ import React from "react" -import { Modal, Form, Input, Space, Button } from "antd" +import { Modal, Form, Input, Space, Button, Checkbox, Spin } from "antd" +import { useRequest } from "umi"; +import { queryTaskList } from "@/pages/Task/services"; type IProps = { - onOk: () => void; + onOk: (conf: any) => void; onCancel?: () => void; } @@ -16,6 +18,9 @@ const ReactComponent: React.ForwardRefRenderFunction = (props, re const [visible, setVisible] = React.useState(false) const [loading, setLoading] = React.useState(false) const [source, setSource] = React.useState(undefined) + const [selectedKeys, setSelectedKeys] = React.useState([]) + + const { data: taskList, loading: taskLoading } = useRequest(queryTaskList, { initialData: [] }) React.useImperativeHandle(ref, () => ({ show(_: any) { @@ -36,31 +41,34 @@ const ReactComponent: React.ForwardRefRenderFunction = (props, re const handleOk = async () => { if (loading) return setLoading(true) - onOk() + onOk([]) handleCancel() } return ( - + } onCancel={handleCancel} - onOk={handleOk} > -
- - - - + + + + { + taskList.map((i: any) => ( + {i.name} + )) + } + + +
) } diff --git a/src/pages/Plan/hooks.ts b/src/pages/Plan/hooks.ts new file mode 100644 index 0000000000000000000000000000000000000000..e842d1198ff22caae573c5ad7ad3d0434b0516b7 --- /dev/null +++ b/src/pages/Plan/hooks.ts @@ -0,0 +1,15 @@ +import React from "react" + +export const Provider = React.createContext( + { + state: { + planList: [], + }, + dispatch: () => null + } +) + +export const usePlanProvider = () => { + const provider = React.useContext(Provider) + return provider +} \ No newline at end of file diff --git a/src/pages/Plan/index.tsx b/src/pages/Plan/index.tsx index b5919347bf0e9c7eb2939b26d4a6cd18371a6131..1e4887637c992b83a9bd610f6b9318faa3acd868 100644 --- a/src/pages/Plan/index.tsx +++ b/src/pages/Plan/index.tsx @@ -1,42 +1,119 @@ import React from "react" -import { Table, Space, Typography, Row, Button, Col } from "antd" -import { queryTableList } from "./services" -import { useRequest } from "ahooks" +import { Table, Space, Typography, Row, Button, Col, Spin, Form, message, Input, Select } from "antd" +import { createTestPlan, queryTestPlanList } from "./services" +import { useRequest } from "umi" import AddModal from "./components/AddModal" import LeftList from "./components/LeftList" import RightContent from "./components/RightContent" import EditContent from "./components/RightContent/EditContent" +import { Provider as PlanPageProvider } from "./hooks" +import Loading from "@/components/Loading" +import { history, request, useParams } from "umi" +import RichTextEditor from "@/components/RichTextEditor" +import SuiteTable from "@/pages/Plan/components/ContentTable/Suite.table" +import TaskTable from "@/pages/Plan/components/ContentTable/Task.table" +import { CustomForm } from "@/components/CustomStyled" /** * * 测试方案 * */ -const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } -const TestPlan: React.FC = () => { - const [pageQuery, setPageQuery] = React.useState(DEFAULT_PAGE_QUERY) +type IProps = { + [k: string]: any; +} + +const TestPlan: React.FC = (props) => { + const [form] = Form.useForm() + const { data: requirement } = useRequest(() => request(`/api/requirement`), { initialData: [] }) + + const handleSave = () => { + form.validateFields() + .then(async (values) => { + const { code, msg } = await createTestPlan(values) + if (code !== 200) { + message.error(msg) + return + } + }) + } - // const { data, loading } = useRequest(queryTableList, {}) - // if (!data) return <> + const requirementList = React.useMemo(() => { + if (requirement) + return requirement.map((i: any) => ({ label: i.title, value: i.id })) + return [] + }, [requirement]) return ( - - - 测试方案 - - -
- - - - {/* */} - - - - +
+ + + 新建方案 + + + 基础信息 + + + + + +