diff --git a/config/routes.ts b/config/routes.ts index 647b79f41c9c3c6f9e1b21bde91c1a2066095844..136e58949fbc0540b820665cc6fbb8f9133cff60 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -57,6 +57,10 @@ name: "server", component: "./Server", }, + { + path: "*", + redirect: "/outline", + }, { component: "./404", }, diff --git a/package.json b/package.json index 22a78844fab84e84b104d2c32189b638c0ee2cba..26cf561f8ab38ae60d4424a311a2e6f3f0e66ccb 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "@antv/l7": "^2.3.7", "@antv/l7-maps": "^2.3.7", "@antv/l7-react": "^2.1.9", + "@types/react-color": "^3.0.6", + "@types/reactcss": "^1.2.6", "@types/styled-components": "^5.1.24", "@umijs/route-utils": "^1.0.36", "@wangeditor/editor": "^0.15.15", @@ -73,11 +75,13 @@ "nzh": "^1.0.3", "omit.js": "^2.0.2", "react": "^17.0.0", + "react-color": "^2.19.3", "react-dev-inspector": "^1.1.1", "react-dom": "^17.0.0", "react-fittext": "^1.0.0", "react-helmet-async": "^1.0.4", "react-router": "^4.3.1", + "reactcss": "^1.2.3", "styled-components": "^5.3.5", "umi": "^3.5.0", "umi-serve": "^1.9.10" diff --git a/src/app.tsx b/src/app.tsx index bbd61b562bb58a1a29d06595fea996e7b07e0690..f02f89cc1b1ccf4525c212a622af5eddfb3d7476 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -6,6 +6,51 @@ import RightContent from '@/components/RightContent'; import Footer from '@/components/Footer'; import { currentUser as queryCurrentUser } from './services/ant-design-pro/api'; import { BookOutlined, LinkOutlined } from '@ant-design/icons'; +import menuItemRender from "@/components/MenuItemRender" +import styled from "styled-components" + +const HeaderWrapper = styled.div` + #logo a h1 { + color: #fff; + } + + .ant-pro-top-nav-header { + background-color: rgba(5,17,54,1); + } + .ant-pro-top-nav-header-menu .ant-menu.ant-menu-horizontal { + height: 43px; + } + + .ant-menu, + .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected {color: #fff;} + .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after { + border-bottom-color: #fff; + } + + .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover { + color: rgb(255 255 255 / 50%); + transition: unset; + border-bottom: 2px solid transparent; + } + + .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover { + &::after { + border-bottom: none; + } + } + + .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-active::after { + border-bottom: none; + } + + .ant-menu-horizontal > .ant-menu-item::after, .ant-menu-horizontal > .ant-menu-submenu::after { + &:hover { + transition: unset; + border-bottom: none; + } + } + +` const isDev = process.env.NODE_ENV === 'development'; const loginPath = '/user/login'; @@ -56,6 +101,9 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => { history.push(loginPath); } }, + headerTheme: "dark", + headerRender: (props, dom) => {dom}, + menuItemRender, contentStyle: { margin: 12, }, diff --git a/src/assets/png/picker.png b/src/assets/png/picker.png new file mode 100644 index 0000000000000000000000000000000000000000..425925256a4251232999622c041bc932b019c30a Binary files /dev/null and b/src/assets/png/picker.png differ diff --git a/src/assets/svg/Delete.svg b/src/assets/svg/Delete.svg new file mode 100644 index 0000000000000000000000000000000000000000..4d36d137ce749a9cb049cde642d096a3fe28f8e4 --- /dev/null +++ b/src/assets/svg/Delete.svg @@ -0,0 +1,14 @@ + + + Icon/Warning + + + + + + + + + + + \ No newline at end of file diff --git "a/src/assets/case/\347\224\250\344\276\213-n.svg" b/src/assets/svg/case-n.svg similarity index 100% rename from "src/assets/case/\347\224\250\344\276\213-n.svg" rename to src/assets/svg/case-n.svg diff --git "a/src/assets/case/\351\234\200\346\261\202-n.svg" b/src/assets/svg/demand-n.svg similarity index 100% rename from "src/assets/case/\351\234\200\346\261\202-n.svg" rename to src/assets/svg/demand-n.svg diff --git "a/src/assets/case/\345\244\247\347\272\262-n.svg" b/src/assets/svg/outline-n.svg similarity index 100% rename from "src/assets/case/\345\244\247\347\272\262-n.svg" rename to src/assets/svg/outline-n.svg diff --git "a/src/assets/case/\346\226\271\346\241\210-n.svg" b/src/assets/svg/plan-n.svg similarity index 100% rename from "src/assets/case/\346\226\271\346\241\210-n.svg" rename to src/assets/svg/plan-n.svg diff --git "a/src/assets/case/\350\256\276\345\244\207-n.svg" b/src/assets/svg/server-n.svg similarity index 100% rename from "src/assets/case/\350\256\276\345\244\207-n.svg" rename to src/assets/svg/server-n.svg diff --git "a/src/assets/case/\344\273\273\345\212\241-n.svg" b/src/assets/svg/task-n.svg similarity index 100% rename from "src/assets/case/\344\273\273\345\212\241-n.svg" rename to src/assets/svg/task-n.svg diff --git a/src/components/ColorPicker/index.tsx b/src/components/ColorPicker/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..91c1b756a5f8ac9250306baed534e0ecd113fa3b --- /dev/null +++ b/src/components/ColorPicker/index.tsx @@ -0,0 +1,184 @@ +import React, { useState, useEffect } from 'react'; +import reactCSS from 'reactcss'; +import { SketchPicker } from 'react-color'; +import { Divider } from 'antd'; +import { CheckOutlined } from '@ant-design/icons'; +import CustomStyle from './style.less'; +import Picker from '@/assets/png/picker.png' + +const colorPicker: React.FC = ({ value, onChange }) => { + + const [displayColorPicker, setDisplayColorPicker] = useState(false) + const [show, setShow] = useState(false) + const [color, setColor] = useState({ r: 255, g: 157, b: 78, a: 1 }) + const colors = [ + { r: 255, g: 157, b: 78, a: 1 }, + { r: 91, g: 216, b: 166, a: 1 }, + { r: 91, g: 143, b: 249, a: 1 }, + { r: 247, g: 102, b: 78, a: 1 }, + { r: 255, g: 134, b: 183, a: 1 }, + { r: 43, g: 158, b: 157, a: 1 }, + { r: 146, g: 112, b: 202, a: 1 }, + { r: 109, g: 200, b: 236, a: 1 }, + { r: 102, g: 119, b: 150, a: 1 }, + { r: 245, g: 188, b: 22, a: 1 }, + { r: 231, g: 107, b: 242, a: 1 }, + ] + + const handleClick = () => { + setDisplayColorPicker(!displayColorPicker) + }; + + const handleClose = () => { + setDisplayColorPicker(false) + }; + + const handleChange = (color: any) => { + const rgb = color.rgb + setColor(rgb) + const rgba = `rgb(${rgb.r},${rgb.g},${rgb.b},${rgb.a})` + if (onChange) { + onChange(rgba); + } + }; + + useEffect(() => { + let rgb = { r: 255, g: 157, b: 78, a: 1 } + if (value) { + const init = value.slice(4, value.length - 1).split(',') + rgb = { + r: Number(init[0]), + g: Number(init[1]), + b: Number(init[2]), + a: Number(init[3]) + } + } + setColor(rgb) + }, [value]); + + const onselect = (item: any) => { + setColor(item) + setShow(false) + const rgba = `rgb(${item.r},${item.g},${item.b},${item.a})` + if (onChange) { + onChange(rgba); + } + } + + const styles = reactCSS({ + 'default': { + color: { + position: 'relative', + width: '22px', + height: '22px', + borderRadius: '2px', + background: `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`, + }, + swatch: { + // padding: '5px', + transform: 'translateY(5px)', + background: '#fff', + borderRadius: '1px', + boxShadow: '0 0 0 1px rgba(0,0,0,.1)', + display: 'inline-block', + cursor: 'pointer', + }, + popover: { + position: 'absolute', + zIndex: '2', + }, + cover: { + position: 'fixed', + top: '0px', + right: '0px', + bottom: '0px', + left: '0px', + }, + warp: { + position: 'absolute', + paddingTop: '16px', + paddingBottom: '6px', + background: '#fff', + boxShadow: '-12px 0 48px 16px rgba(0,0,0,0.03), -9px 0 28px 0 rgba(0,0,0,0.05), -6px 0 16px -8px rgba(0,0,0,0.08)', + borderRadius: '2px', + zIndex: 9999, + marginTop: 5, + } + }, + }); + + return ( +
+
+
+
+ {displayColorPicker ? +
+
+
+
+ { + colors.map((item, index) => { + return
+
onselect(item)} + > + {JSON.stringify(color) == JSON.stringify(item) && } +
+
+ }) + } +
+
setShow(!show)} + > + +
+
+
+
+ {show && } + {show &&
+ +
+ } +
+
: null + } +
+ ) +} + +export default colorPicker \ No newline at end of file diff --git a/src/components/ColorPicker/style.less b/src/components/ColorPicker/style.less new file mode 100644 index 0000000000000000000000000000000000000000..6fd5418ee48b7e0790587cbaafc2e80003abe9fd --- /dev/null +++ b/src/components/ColorPicker/style.less @@ -0,0 +1,38 @@ +.picker{ + box-shadow: none !important; + border: none; +} + +.waves:active{ + opacity: 0.85; + transform: scale(.9, .9); + box-shadow:0 1px 2px 0 rgba(0,0,0,0.2), 0 1px 3px 0 rgba(0,0,0,0.19) +} +.waves:hover{ + opacity: 0.8; + box-shadow:0 1px 2px 0 rgba(0,0,0,0.2), 0 1px 3px 0 rgba(0,0,0,0.19) +} + +.waves:after { + content: ""; + display: block; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + pointer-events: none; + background-image: radial-gradient(circle, #aaa 10%, transparent 10%); + background-repeat: no-repeat; + background-position: 50%; + transform: scale(10, 10); + opacity: 0; + transition: .3s, opacity .5s; +} + +.waves:active:after { + transform: scale(0, 0); + opacity: 0.3; + transition: 0s; +} + diff --git a/src/components/CustomPagination.tsx b/src/components/CustomPagination.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e20e16d3eb3978cc4e893bec2f6766bec6ad1607 --- /dev/null +++ b/src/components/CustomPagination.tsx @@ -0,0 +1,36 @@ +import { Pagination , Row , Col } from 'antd' +import styled from 'styled-components' + +const CommonPagination = styled(Row)` + margin-top:16px; + width : 100%; +` + +interface PaginationProps { + total : number, + pageSize : number , + current : number, + onPageChange : ( page : number , size? : number ) => void, + style?:any, +} + +export default ( props : PaginationProps ) => { + const { total = 0 , pageSize = 10 , current = 1 , onPageChange , ...rest } = props + if ( total === 0 ) return + return ( + + + `共${ total }条`} + // hideOnSinglePage + onChange={ onPageChange } + /> + + + ) +} \ No newline at end of file diff --git a/src/components/MenuItemRender/index.tsx b/src/components/MenuItemRender/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..44cb65d66012b0d0da0659cd890349ddbc76d5a4 --- /dev/null +++ b/src/components/MenuItemRender/index.tsx @@ -0,0 +1,40 @@ +import { IRoute, history, useLocation } from 'umi'; +import { MenuItemProps } from 'antd' +import type { BaseMenuProps } from '@ant-design/pro-layout/lib/components/SiderMenu/BaseMenu'; +import { Icon, ItemWrapper } from './styled' + +import { ReactComponent as Outline } from '@/assets/svg/outline-n.svg' +import { ReactComponent as Demand } from '@/assets/svg/demand-n.svg' +import { ReactComponent as Plan } from '@/assets/svg/plan-n.svg' +import { ReactComponent as Case } from '@/assets/svg/case-n.svg' +import { ReactComponent as Server } from '@/assets/svg/server-n.svg' +import { ReactComponent as Task } from '@/assets/svg/task-n.svg' + +const switchMenuIcon = ({ locale }: IRoute) => { + return new Map([ + [`menu.outline`, ], + ['menu.demand', ], + ['menu.plan', ], + ['menu.suite', ], + ['menu.server', ], + ['menu.task', ], + ]).get(locale) +} + +const menuItemRender = (props: MenuItemProps & IRoute, defaultDom: React.ReactNode, menuProps: BaseMenuProps) => { + const { pathname } = useLocation() + const { path } = props + + const reg = new RegExp(`^${path as string}`).test(pathname) + + return ( + history.push(path as string)} align="middle" > + + {switchMenuIcon(props)} + + {defaultDom} + + ) +} + +export default menuItemRender \ No newline at end of file diff --git a/src/components/MenuItemRender/styled.tsx b/src/components/MenuItemRender/styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8d6bcd9ca4cfc6026a520ab964d475956de1d383 --- /dev/null +++ b/src/components/MenuItemRender/styled.tsx @@ -0,0 +1,28 @@ +import styled from 'styled-components'; +import { Row } from 'antd' + +const activeSvgCls = ` + svg { + path { + fill: #fff!important; + fill-opacity : 1; + } + } +` + +export const Icon = styled.span<{ isActive: boolean }>` + margin-right:8px; + display: flex; + ${({ isActive }) => isActive && activeSvgCls} +` + +export const ItemWrapper = styled(Row)` + &:hover { + svg { + path { + fill: rgb(255 255 255 / 50%)!important; + fill-opacity : 1; + } + } + } +` \ No newline at end of file diff --git a/src/components/Public/DelModal.tsx b/src/components/Public/DelModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5fb0cd7e3ed12f78b6eaf490847b297954567efb --- /dev/null +++ b/src/components/Public/DelModal.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import { Modal, Typography, Row } from "antd" +import { ReactComponent as Delete } from '@/assets/svg/Delete.svg'; +import styled from 'styled-components' +import { isQuestRight,requestCodeMessage, promiseRequest } from "@/utils" + +const ModalComponent = styled(Modal)` + .ant-typography-danger{ + margin-left: 4px; + color: #F5222D; + } + .ant-btn-primary{ + border-color: #FF4E50; + background: #FF4E50; + } + .ant-modal-title{ + font-weight: 700; + } +`; +type IProps = { + onOk: () => void; + deleteQuest: promiseRequest; +} + +type IRefs = { + [k: string]: any +} +const DelModal: React.ForwardRefRenderFunction = (props, ref) => { + const { onOk, deleteQuest } = props + const [visible, setVisible] = React.useState(false) + const [loading, setLoading] = React.useState(false) + const [operateId, setOperateId] = React.useState(undefined) + React.useImperativeHandle(ref, () => ({ + show(_: any) { + setOperateId(_) + setVisible(true) + } + })) + const handleOk = async () => { + if (loading) return + setLoading(true) + const {code,msg} = await deleteQuest(operateId) + if (!isQuestRight(code)) requestCodeMessage(code, msg || '请求异常') + if (isQuestRight(code)) { + hanldeCancel() + onOk() + } + } + const hanldeCancel = () => { + setVisible(false) + setLoading(false) + setOperateId(undefined) + } + return ( + + 该操作不可逆,请谨慎操作 + + ) +} + +export default React.forwardRef(DelModal) \ No newline at end of file diff --git a/src/components/Public/StatusTag.tsx b/src/components/Public/StatusTag.tsx new file mode 100644 index 0000000000000000000000000000000000000000..39e2e55a3d5ade594fc55cba62c0881e42e4a8c8 --- /dev/null +++ b/src/components/Public/StatusTag.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { Tag } from "antd" + +type IProps = { + state: string +} + +const stateWordsMap = new Map([ + ["init", "新建"], + ["analyzing", "分析中"], + ["running", "运行中"], + ["finish", "已完成"], + ["accepted", "已接受"], + ["refused", "已拒绝"], +]) + +const stateColorMap = new Map([ + ["init", "rgba(0,0,0,0.03)"], + ["analyzing", "rgba(250,173,21,0.1)"], + ["running", "rgba(24,144,255,0.1)"], + ["finish", "rgba(34,188,61,0.1)"], + ["accepted", "rgba(250,173,21,0.1)"], + ["refused", "rgba(255,77,79,0.1)"], +]) +const stateWordsColorMap = new Map([ + ["init", "rgba(0,0,0,0.45)"], + ["analyzing", "rgba(228,155,9,1)"], + ["running", "rgba(24,144,255,1)"], + ["finish", "rgba(34,188,61,1)"], + ["accepted", "rgba(228,155,9,1)"], + ["refused", "rgba(255,77,79,1)"], +]) + +const StateTag: React.FC = (props) => { + const { state } = props + return ( + + {stateWordsMap.get(state) || state} + + ) +} + +export default StateTag \ No newline at end of file diff --git a/src/components/RichTextEditor/index.tsx b/src/components/RichTextEditor/index.tsx index 388d0c8dadf43b0924431706898e19bef88d9cda..7cb1a3b7068016f080e8b0137a0eb2c98e127d75 100644 --- a/src/components/RichTextEditor/index.tsx +++ b/src/components/RichTextEditor/index.tsx @@ -27,11 +27,11 @@ const customUpload = async (file: File, insertFn: any) => { } const RichTextEditor: React.FC = (props) => { - const { defaultValue, onEditorChange, config } = props + const { defaultValue, onEditorChange, config = {} } = props const toolbar = React.useRef(null) const editor = React.useRef(null) as any - + React.useEffect(() => { const riceEditor = createEditor({ selector: editor.current, @@ -51,7 +51,7 @@ const RichTextEditor: React.FC = (props) => { selector: toolbar.current as any, config: toolbarConfig }) - // console.log(tb.getConfig().toolbarKeys) + onEditorChange && onEditorChange(vm) }, onChange(vm) { diff --git a/src/components/RichTextEditor/services.ts b/src/components/RichTextEditor/services.ts new file mode 100644 index 0000000000000000000000000000000000000000..e03b04d53ea5dd0cbdd6a6d63bda1fe29fc459e5 --- /dev/null +++ b/src/components/RichTextEditor/services.ts @@ -0,0 +1,11 @@ + +import { request } from "umi" + +export const uploadFile = async ( formData: any) => { + return request(`/api/images`, { + method : 'post', + data: { + formData + } + }) +} \ No newline at end of file diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx index d9e6731172b83b7c59bdd5ba40c78a559fdcff0f..7b7cff7e69c523d573acaaa5e4ba5bb105e8f203 100644 --- a/src/components/RightContent/AvatarDropdown.tsx +++ b/src/components/RightContent/AvatarDropdown.tsx @@ -31,6 +31,7 @@ const loginOut = async () => { }; const AvatarDropdown: React.FC = ({ menu }) => { + return <> const { initialState, setInitialState } = useModel('@@initialState'); const onMenuClick = useCallback( @@ -58,6 +59,7 @@ const AvatarDropdown: React.FC = ({ menu }) => { ); + if (!initialState) { return loading; } diff --git a/src/components/RightContent/index.tsx b/src/components/RightContent/index.tsx index 6e478abb0499373a3de47909093e6f9fc6505078..8901e5f1874b1dab8516ebcf32a179d044d68752 100644 --- a/src/components/RightContent/index.tsx +++ b/src/components/RightContent/index.tsx @@ -30,30 +30,16 @@ const GlobalHeaderRight: React.FC = () => { placeholder="站内搜索" defaultValue="umi ui" options={[ - { - label: umi ui, - value: 'umi ui', - }, - { - label: Ant Design, - value: 'Ant Design', - }, - { - label: Pro Table, - value: 'Pro Table', - }, - { - label: Pro Layout, - value: 'Pro Layout', - }, - ]} // onSearch={value => { - // console.log('input', value); - // }} + + ]} + // onSearch={value => { + // console.log('input', value); + // }} /> { - window.open('https://pro.ant.design/docs/getting-started'); + // window.open('https://pro.ant.design/docs/getting-started'); }} > diff --git a/src/global.less b/src/global.less index d7b6301d735e40dc608be8e8544934664311a780..27883ea2e64a76e2175a63b17116b42b7c770f69 100644 --- a/src/global.less +++ b/src/global.less @@ -64,3 +64,7 @@ body { } } } +.mgContent{ + background-color: #fff; + padding: 0 12px 16px; +} diff --git a/src/pages/Demand/components/AddDemand.tsx b/src/pages/Demand/components/AddDemand.tsx index 9802ba34c545833189a8381d4723a41ee00c8df0..981755d8be9a2ab899d497c7c5a74fde72028bd2 100644 --- a/src/pages/Demand/components/AddDemand.tsx +++ b/src/pages/Demand/components/AddDemand.tsx @@ -1,92 +1,238 @@ import React from "react" -import { Modal, Form, Input, Space, Button, Row, Col, Divider, Select } from "antd" +import { Modal, Form, Input, Space, Button, Row, Col, Spin, Select, Typography } from "antd" import RichTextEditor from "@/components/RichTextEditor" - +import { getDemandDetail, getOutlineList, updateDemand, createDemand, queryTableList } from "../services" +import { requestCodeMessage, isQuestRight, requestNotTableFn, setFormFieldsValue } from "@/utils" +import StatusTag from "@/components/Public/StatusTag" +import { createEditor } from '@wangeditor/editor' +import { isArray, get } from "lodash" type IProps = { onOk: () => void; onCancel?: () => void; } - +type optionType = { + value: React.ReactText; + label: React.ReactText; +} +type promiseRequest = (values: any) => Promise; type IRefs = { [k: string]: any } const ReactComponent: React.ForwardRefRenderFunction = (props, ref) => { const { onOk } = props - const [visible, setVisible] = React.useState(false) - const [loading, setLoading] = React.useState(false) - const [source, setSource] = React.useState(undefined) + const [isLoading, setIsLoading] = React.useState(false) + const [isVeiw, setIsVeiw] = React.useState(false) + const [source, setSource] = React.useState(undefined) + const [selectedOutline, setSelectedOutline] = React.useState() + const [outlineOption, setOutlineOption] = React.useState([]) + const nameRef = React.useRef(null) as any + const textEditorRef = React.useRef(null) as any + const assigneeOption = [ + { value: 'owner', label: 'owner' }, + { value: 'tester', label: 'tester' }, + ] + const promiseQuest = async (questFn: promiseRequest, id?: number) => { + const result = await questFn(id) + const code = get(result, 'code') + const msg = get(result, 'msg') + const data = get(result, 'data') + if (!isQuestRight(code)) requestCodeMessage(code, msg || '请求异常') + return data + } React.useImperativeHandle(ref, () => ({ show(_: any) { - setSource(_) setVisible(true) - form.setFieldsValue(_) + setIsLoading(true) + setIsVeiw(Boolean(_?.id || _?.id === 0)) + Promise.all(_?.id || _?.id === 0 ? [promiseQuest(getOutlineList), promiseQuest(getDemandDetail, _?.id)] : [promiseQuest(getOutlineList)]) + .then((result) => { + setIsLoading(false) + const demandDetail = result[1] + const outlineList = result[0] + setSource(demandDetail) + form.setFieldsValue({ + assignee: demandDetail?.assignee, + outline_id: demandDetail?.outline_id, + title: demandDetail?.title, + }) + const textContent = (demandDetail?.content || '').trim() + const isObject = textContent.indexOf('[') === 0 && textContent.lastIndexOf(']') === textContent.length - 1 + console.log(isObject, 'isObject') + console.log(demandDetail?.content, 'demandDetail?.content') + + textEditorRef.current = isObject ? JSON.parse(demandDetail?.content) : [] + console.log(textEditorRef.current, 'textEditorRef.current') + let arr: optionType[] = [] + if (isArray(outlineList)) { + arr = outlineList.map(item => ({ value: get(item, 'id'), label: get(item, 'title') })) + } + setOutlineOption(arr) + }) + .catch((err: any) => { + setIsLoading(false) + console.log(err) + }) } })) const [form] = Form.useForm() - + const handleEditorChange = (vm: any) => { + textEditorRef.current = vm + } + const handleOutlineChange = (keys: number) => { + setFormFieldsValue(form, { outline_id: keys }) + setSelectedOutline(keys) + } + const handleAssigneeChange = (keys: number) => { + setFormFieldsValue(form, { assignee: keys }) + setSelectedOutline(keys) + } + const handleEdit = () => setIsVeiw(false) const handleCancel = () => { + textEditorRef.current = null + form.resetFields() setVisible(false) - setLoading(false) + setIsLoading(false) setSource(undefined) + setIsVeiw(false) + setOutlineOption([]) + setSelectedOutline(undefined) } const handleOk = async () => { - if (loading) return - setLoading(true) - onOk() - handleCancel() + form.validateFields() // 触发表单验证,返回Promise + .then(async (values: any) => { + if (isLoading) return + setIsLoading(true) + let questType: promiseRequest = createDemand + const parmas = { + ...values, + } + parmas.content = JSON.stringify(textEditorRef.current.children) + if (isVeiw) parmas.title = source?.title + if (source) { + parmas.id = source?.id + questType = updateDemand + } + const result = await questType(parmas) + const code = get(result, 'code') + const msg = get(result, 'msg') + if (!isQuestRight(code)) requestCodeMessage(code, msg || '请求异常') + if (isQuestRight(code)) { + onOk() + handleCancel() + } + }) + .catch(err => console.log(err)) + } return ( - + } bodyStyle={{ padding: 0 }} onCancel={handleCancel} onOk={handleOk} > - - - - -
- -
-
- - -
- - + + } - - - - + option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 + } + > + { + assigneeOption.map((item: optionType) => {item.label}) + } + + + + + + - - -
- -
+ + +
) } -export default React.forwardRef(ReactComponent) \ No newline at end of file +export default React.forwardRef(ReactComponent) diff --git a/src/pages/Demand/index.tsx b/src/pages/Demand/index.tsx index 5727e2745274e79ac520ed3de0423489eaddcc8c..a81dff2f9736706aaaeca8223b40fd8a45e5a3af 100644 --- a/src/pages/Demand/index.tsx +++ b/src/pages/Demand/index.tsx @@ -1,20 +1,25 @@ -import React from "react" -import { Table, Space, Typography, Row, Button } from "antd" -import { queryTableList } from "./services" -import { useRequest } from "ahooks" +import { useState, useRef } from "react" +import { Table, Space, Typography, Row, Button, Layout } from "antd" +import { getDemandList, queryTableList, deleteDemand } from "./services" import AddModal from "./components/AddDemand" - -const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } +import { requestFn, defaultParams } from "@/utils" +import { useUpdateEffect } from 'ahooks' +import CustomPagination from "@/components/CustomPagination" +import StatusTag from "@/components/Public/StatusTag" +import DelModal from "@/components/Public/DelModal" +import gCls from '@/global.less' +import _ from 'lodash' +const DEFAULT_PAGE_QUERY = { page_size: 10, page_num: 1 } const TestDemand: React.FC = () => { - const [pageQuery, setPageQuery] = React.useState(DEFAULT_PAGE_QUERY) - - const { data: dataSource, loading, refresh } = useRequest( - (params = pageQuery) => queryTableList(params), - { refreshDeps: [pageQuery] } - ) + const [questParmas, setQuestParmas] = useState(DEFAULT_PAGE_QUERY) + const { data: tableSource, loading, run, refresh } = requestFn(getDemandList, DEFAULT_PAGE_QUERY) - const addModalRef = React.useRef(null) as any + useUpdateEffect(() => { + run(questParmas) + }, [questParmas]) + const addModalRef = useRef(null) as any + const delModalRef = useRef(null) as any const handleAdd = async () => { addModalRef.current?.show() @@ -24,70 +29,114 @@ const TestDemand: React.FC = () => { addModalRef.current?.show(row) } - const handleDelete = async (row: any) => { + const handleDelete = (id: number) => { + delModalRef.current?.show(id) + } + const delCallback= async () => { + const parmasCopy = _.cloneDeep(questParmas) + const { page_size = 10, page_num = 1 } = parmasCopy + const totalPage: number = Math.ceil((_.get(tableSource, 'total') - 1) / page_size) || 1 + if (totalPage <= page_num) { + parmasCopy.page_num = totalPage + setQuestParmas(parmasCopy) + } else { + await refresh() + } } const columns = [{ title: "标题", - dataIndex: "", - }, { + dataIndex: 'title', + key: 'title', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "阶段", - dataIndex: "", - }, { + dataIndex: 'status', + key: 'status', + ellipsis: true, + render: (row: any) => row ? : '-' + }, + { title: "测试大纲", - dataIndex: "", - }, { + dataIndex: 'outline_title', + key: 'outline_title', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "创建人", - dataIndex: "", - }, { + dataIndex: "owner", + key: 'owner', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "指派人", - dataIndex: "", - }, { + dataIndex: "assignee", + key: 'assignee', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "创建日期", - dataIndex: "", - }, { + dataIndex: "gmt_created", + key: 'gmt_created', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "操作", + width: 100, render(row: any) { return ( handleEdit(row)}> 编辑 - handleDelete(row)}> + handleDelete(row?.id)}> 删除 ) } }] - + return ( - - - 测试需求 + + + {`测试需求 (${tableSource?.total || 0})`} + { + setQuestParmas({ ...questParmas, page_size: size, page_num: page }) } - }} + } /> - + + ) } -export default TestDemand \ No newline at end of file +export default TestDemand diff --git a/src/pages/Demand/services.ts b/src/pages/Demand/services.ts index 22287f79384380df6e4397ec5ad94cb82969c9e3..64a14a5272c8c441258f77eecfb984dfb97cf52a 100644 --- a/src/pages/Demand/services.ts +++ b/src/pages/Demand/services.ts @@ -1,9 +1,57 @@ +import React from "react"; import { request } from "umi" type DemandParams = { - + page_size: number; + page_num: number; } -export const queryTableList = async (data: DemandParams) => { - return request(`/api/xxxxxxx`, { method: "post", data }) -} \ No newline at end of file +export const queryTableList = async (params: DemandParams) => { + return request(`/project/public`, {params }) +} +// 鿴б +export const getDemandList = async ( params : DemandParams ) => { + return request(`/api/requirement`, { params }) +} +// 鿴 +export const getDemandDetail = async ( id : number ) => { + return request(`/api/requirement/${id}`) +} +// ǰ׺ҲԴ +export const getOutlineList = async ( prefix? : string ) => { + return request(`/api/outline/list`, { params: {prefix}}) +} +// ² +export const updateDemand = async ( data:{ + id: number, + title: string, + content ?: string, + assignee: React.ReactText, + outline_id ?: React.ReactText, +} ) => { + return request(`/api/requirement/edit/${data?.id}`, { + method : 'post', + data:{ + ...data, + id: undefined + } + }) +} +// +export const createDemand = async ( data:{ + title: string, + content ?: string, + assignee: React.ReactText, + outline_id ?: React.ReactText, +} ) => { + return request(`/api/requirement/create`, { + method : 'post', + data + }) +} +// ɾ +export const deleteDemand = async( id:number | null) => { + return request(`/api/requirement/${id}` , { + method : 'delete', + }) +} diff --git a/src/pages/Outline/index.tsx b/src/pages/Outline/index.tsx index 3c46f7288e90d6070de954dc4e132bb27fa86017..76f0c14f3618f6ef8bb20f34992f54fb6141f857 100644 --- a/src/pages/Outline/index.tsx +++ b/src/pages/Outline/index.tsx @@ -108,10 +108,10 @@ const TestDemand: React.FC = () => { ]; return ( - + - - 测试大纲 + + 测试大纲({dataSource?.total || 0}) - + } onCancel={handleCancel} @@ -57,6 +127,10 @@ const ReactComponent: React.ForwardRefRenderFunction = (props, re
@@ -68,21 +142,30 @@ const ReactComponent: React.ForwardRefRenderFunction = (props, re + {/* */} - x86_64 - aarch64 - loogarch64 + x86_64 + aarch64 + loogarch64 - + + {/* */} - vm - docker - 物理机 + 不限制 + VM + docker + 物理机 - - + + diff --git a/src/pages/Server/components/CheckboxSearchSelect.tsx b/src/pages/Server/components/CheckboxSearchSelect.tsx new file mode 100644 index 0000000000000000000000000000000000000000..31348fc5df95ee8e87f2f0a7574a96adb002923f --- /dev/null +++ b/src/pages/Server/components/CheckboxSearchSelect.tsx @@ -0,0 +1,170 @@ + +import { Col, Empty, Divider, message, Checkbox, Select, Input, Space } from 'antd' +import React, { useState, useEffect, useRef } from 'react' +import { requestNotTableFn,requestCodeMessage, promiseRequest, isQuestRight } from "@/utils" +import { useUpdateEffect } from 'ahooks'; +import { addLabel } from "../services" +import styles from '../index.less' +import _ from 'lodash' +import classNames from 'classnames'; +import { PlusOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons' +import ColorPicker from '@/components/ColorPicker' +type MultipleSelectProp = { + dataIndex: string; + formReact: any; + questLink: promiseRequest; + placeholder ?: string; + effect ?: any; +} +type itemType = { + name: string; + color: string; +} +const MultipleSelect: React.FC = (props) => { + const { dataIndex, formReact, questLink, placeholder, effect } = props + const [checkedList, setCheckedList] = useState([]) + const [selectOptions, setSelectOptions] = useState([]) + // const [selectVal, setSelectVal] = useState('') + const [isButton, setIsButton] = useState(true) + const [inputVal,setInputVal] = useState('') + const [selColor,setSelColor] = useState('rgb(255,157,78)') + const { data: resultData} = requestNotTableFn(questLink,'',true) + + const lableSelect: any = useRef(null) + + useUpdateEffect(() =>{ + const {data} = resultData || {} + let arr:itemType[] = [] + if(_.isArray(data)) { + data.forEach((item:any) => { + if(_.get(item,'name').trim()) arr.push({name: _.get(item,'name').trim(), color: _.get(item,'color')}) + }) + } + setSelectOptions(arr) + },[resultData]) + useEffect(() => { + let arr = formReact.getFieldsValue()[dataIndex] || [] + arr = arr.map((val: itemType) => val?.name) + setCheckedList(arr) + }, [effect]) + + // const handleSelectSearch = (val: any) => { + // setSelectVal(val) + // } + const handleSelectChange = (value: any) => { + setCheckedList(value); + } + // const handleSelectBlur = () => { + // const lableNames = formReact.getFieldValue('lable') || [] + // if (selectVal) { + // formReact.setFieldsValue({ lable: lableNames.concat([selectVal]) }) + // setSelectVal('') + // lableSelect.current.blur() + // } + // } + const onChange = (list: any) => { + setCheckedList(list) + const newList = _.reduce(list, function(arr:itemType[],name) { + const target = _.find(selectOptions,{ name: name }) + if(target) arr.push(target) + return arr + }, []) + formReact.setFieldsValue({ lable: newList }) + } + const handleAddTag = () => setIsButton(!isButton) + const handleTagInput = (e:any) => setInputVal(e.target.value) + const handleChangeColor = (color:string) => { + setSelColor(color) + } + const handleAddOK = async () => { + const name = inputVal.trim() + if (!name) return message.warning('标签名称不能为空') + if (!selColor) return message.warning('标签颜色不能为空') + if(_.find(selectOptions,{ name: name })) return message.warning('标签名称不能重复') + const addTag = { name: name, color: selColor } + const { code, msg } = await addLabel({ name: name, color: selColor }) || {} + if (!isQuestRight(code)) requestCodeMessage(code, msg || '请求异常') + if (isQuestRight(code)) { + const lableNames = formReact.getFieldValue('lable') || [] + formReact.setFieldsValue({ lable: lableNames.concat([{ name: name, color: selColor }]) }) + setCheckedList(checkedList.concat([name])) + setSelectOptions([addTag,...selectOptions]) + handleAddCancle() + } + } + const handleAddCancle = () => { + setInputVal('') + setSelColor('rgb(255,157,78)') + setIsButton(!isButton) + formReact.resetFields() + } + const optionsList = selectOptions.map(item => _.get(item,'name')) + // console.log(checkedList,'checkedList') + return ( + + + +
+ } + + )} + + /> + ) +} +export default MultipleSelect diff --git a/src/pages/Server/index.less b/src/pages/Server/index.less new file mode 100644 index 0000000000000000000000000000000000000000..6f59d6ee725fec37738d9e22c3502db32c0a1712 --- /dev/null +++ b/src/pages/Server/index.less @@ -0,0 +1,51 @@ +.pers_select{ + :global{ + .cb-row{ + margin-bottom: 4px; + } + } +} +.custom_style_select{ + :global{ + .ant-checkbox-wrapper{ + & > span:last-of-type{ + word-break: break-all; + } + } + } +} +.select_baseline { + :global(.ant-select-selection-search) { + // min-width: 5px; + } +} +.join_baseline{ + width: 100%; +} +.join_base_line{ + cursor: pointer; + color: rgba(0,0,0,0.65); + height : 32px ; + line-height : 32px ; +} +.tag_select{ + :global{ + .ant-checkbox{ + display: none; + } + .ant-checkbox-group-item{ + display: block; + width: 100%; + } + .ant-checkbox-wrapper-checked { + color: #1890FF; + background-color: #E6F7FF; + } + .ant-select-item-option-selected { + color : #1890FF; + font-weight: normal; + } + } + +} + diff --git a/src/pages/Server/index.tsx b/src/pages/Server/index.tsx index 1ff503833c9d724f92c4bf472d45fdac7cfcedcc..89d4ef3b6464f91f9cda391247962cf4247d6efe 100644 --- a/src/pages/Server/index.tsx +++ b/src/pages/Server/index.tsx @@ -1,63 +1,133 @@ -import React from "react" -import { Table, Space, Typography, Row, Button } from "antd" -import { queryTableList } from "./services" -import { useRequest } from "ahooks" +import React, { useState } from "react" +import { Table, Space, Typography, Row, Button, Layout, Badge, Tag } from "antd" +import { queryTableList, deleteDevice, getDeviceList, getDevicePing } from "./services" + +import { requestFn, defaultParams, } from "@/utils" +import { useUpdateEffect } from 'ahooks' +import CustomPagination from "@/components/CustomPagination" import AddModal from "./components/AddServerModal" +import DelModal from "@/components/Public/DelModal" +import gCls from '@/global.less' +import _, { isArray, isString } from "lodash" const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } const TableList: React.FC = () => { - const [pageQuery, setPageQuery] = React.useState(DEFAULT_PAGE_QUERY) + const [questParmas, setQuestParmas] = useState(DEFAULT_PAGE_QUERY) - const { data: dataSource, loading, refresh } = useRequest( - (params = pageQuery) => queryTableList(params), - { refreshDeps: [pageQuery] } - ) + const { data: tableSource, loading, run, refresh } = requestFn(getDeviceList, DEFAULT_PAGE_QUERY) + + useUpdateEffect(() => { + run(questParmas) + }, [questParmas]) const addModalRef = React.useRef(null) as any + const delModalRef = React.useRef(null) as any const handleAdd = async () => { addModalRef.current?.show() } + const handlePing = (id: number) => { + } const handleEdit = async (row: any) => { addModalRef.current?.show(row) } - const handleDelete = async (row: any) => { + const handleDelete = (id: number) => { + delModalRef.current?.show(id) + } + const delCallback = async () => { + const parmasCopy = _.cloneDeep(questParmas) + const { page_size = 10, page_num = 1 } = parmasCopy + const totalPage: number = Math.ceil((_.get(tableSource, 'total') - 1) / page_size) || 1 + if (totalPage <= page_num) { + parmasCopy.page_num = totalPage + setQuestParmas(parmasCopy) + } else { + await refresh() + } + } + const getSatus = (status: boolean) => { + if (status) return { color: 'green', text: '可用' } + return { color: 'red', text: '不可用' } + } + const getType = (type: string) => { + if (type === 'vm') return 'VM' + if (type === 'docker') return 'docker' + if (type === 'physics') return '物理机' + return '-' } const columns = [{ title: "名称", - dataIndex: "", - }, { + dataIndex: 'name', + key: 'name', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "状态", - dataIndex: "", - }, { + dataIndex: 'status', + key: 'status', + ellipsis: true, + render: (row: any) => row ? : '-' + }, + { title: "类型", - dataIndex: "", - }, { + dataIndex: 'type', + key: 'type', + ellipsis: true, + render: (row: any) => getType(row) + }, + { title: "IP", - dataIndex: "", - }, { + dataIndex: "ip", + key: 'ip', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "SN", - dataIndex: "", - }, { + dataIndex: "sn", + key: 'sn', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "标签", - dataIndex: "", - }, { + dataIndex: "label", + key: 'label', + ellipsis: true, + render: (row: any) => { + if (isArray(row)) { + return row.map((item: { name: string, color: string }) => {item?.name}) + } + if (isString(row) && row) return {row} + return '-' + } + }, + { title: "创建人", - dataIndex: "", - }, { + dataIndex: "owner", + key: 'owner', + ellipsis: true, + render: (row: any) => row || '-' + }, + { title: "操作", + width: 120, render(row: any) { return ( + handlePing(row?.id)}> + 校验 + handleEdit(row)}> 编辑 - handleDelete(row)}> + handleDelete(row?.id)}> 删除 @@ -66,30 +136,37 @@ const TableList: React.FC = () => { }] return ( - - - 测试设备 - + + + {`测试设备 (${tableSource?.total || 0})`} +
+ { + setQuestParmas({ ...questParmas, page_size: size, page_num: page }) } - }} + } /> - + + ) } diff --git a/src/pages/Server/services.ts b/src/pages/Server/services.ts index d4b22527fe8540cff818ad501855d57efb7980f0..13b981678fde27f2ade153b411c7a4cfe6807676 100644 --- a/src/pages/Server/services.ts +++ b/src/pages/Server/services.ts @@ -1,5 +1,76 @@ +import React from "react"; import { request } from "umi" -export const queryTableList = async () => { - return request(`/api/xxx`) +type DemandParams = { + page_size: number; + page_num: number; +} + +export const queryTableList = async (params: DemandParams) => { + return request(`/project/public`, {params }) +} +// 查看设备列表 +export const getDeviceList = async ( params : DemandParams ) => { + return request(`/api/device`, { params }) +} +// 查看单个设备详情 +export const getDeviceDetail = async ( id : number ) => { + return request(`/api/device/${id}`) +} +// 根据前缀查找设备标签 +export const getTagList = async ( prefix? : string ) => { + return request(`/api/device/labels`, { params: {prefix}}) +} +// 更新设备 +export const updateDevice = async ( data:{ + name: string, + ip: string, + arch: string, + type : string, + sn?: string, + lable?: any, + id: number +} ) => { + return request(`/api/device/modify/${data?.id}`, { + method : 'post', + data:{ + ...data, + id: undefined + } + }) +} +// 创建设备 +export const createDevice = async ( data:{ + name: string, + ip: string, + arch: string, + type : string, + sn?: string, + lable?: string, +} ) => { + return request(`/api/device/add`, { + method : 'post', + data + }) +} +// 删除设备 +export const deleteDevice = async( id:number | null) => { + return request(`/api/device/${id}` , { + method : 'delete', + }) +} +// 测试设备连通性(未实现) +export const getDevicePing = async ( id : number ) => { + return request(`/api/device/ping/${id}`) +} + +// 创建设备 +export const addLabel = async ( data:{ + name: string, + color: string, +} ) => { + return request(`/api/device/label/add`, { + method : 'post', + data + }) } \ No newline at end of file diff --git a/src/pages/Suite/components/AddModal.tsx b/src/pages/Suite/components/AddModal.tsx index dde56acba6557ac105c8923789c67632e572a0f2..5360c50da046243e7fed68df6e8b6787aac43a84 100644 --- a/src/pages/Suite/components/AddModal.tsx +++ b/src/pages/Suite/components/AddModal.tsx @@ -188,7 +188,7 @@ const ReactComponent: React.ForwardRefRenderFunction = (props, re /* if (!richEditerRef.current) return setLoading(false) - const text = .getText() + const text = richEditerRef.current.getText() const descArr = text.split("\n").filter(Boolean) const base_fields = descArr.reduce((pre: any, cur: any) => { diff --git a/src/pages/Suite/components/Case/Edit.tsx b/src/pages/Suite/components/Case/Edit.tsx index a1bf6aa08a819769cd1196dbb5e1bec779ab069b..74b9d5ffba03740c97b7a29100b74b6879c2883c 100644 --- a/src/pages/Suite/components/Case/Edit.tsx +++ b/src/pages/Suite/components/Case/Edit.tsx @@ -77,6 +77,21 @@ const EditChild: React.ForwardRefRenderFunction<{}, IProps> = (props, ref) => { vm.current = editor } + React.useImperativeHandle(ref, () => { + return { + ...form, + } + }) + + console.log(currentCase) + React.useEffect(() => { + if (JSON.stringify(currentCase) !== "{}") + form.setFieldsValue(currentCase) + return () => { + form.resetFields() + } + }, [currentCase]) + return ( = (props) => { React.useEffect(() => { getModules() - // dispath({ type: "update", payload: new Array(6).fill("").map((x, i) => ({ mod_id: i + 1, name: "默认分类" })) }) return () => { handleRestInput() } @@ -79,13 +78,13 @@ const LeftList: React.FC = (props) => { const handleRenameOk = async (id: string, name: string) => { if (!name) return handleRestInput() const { code, msg } = await renameModule(id, { name }) - if (code !== 200) return + if (code !== 200) return message.error(msg) await getModules() handleRestInput() } const handleActiveModule = (id?: null | string | undefined) => { - id ? + typeof id === "number" ? history.replace(`/cases/${id}`) : history.replace(`/cases`) } diff --git a/src/pages/Suite/components/RightContent/FilterForm.tsx b/src/pages/Suite/components/RightContent/FilterForm.tsx index a915f51983ec325586b2e5a986df6863308bd2c7..f8b806d434a91d7f1de53e704b4c5020f6df97ff 100644 --- a/src/pages/Suite/components/RightContent/FilterForm.tsx +++ b/src/pages/Suite/components/RightContent/FilterForm.tsx @@ -1,7 +1,8 @@ import React from "react" -import { Space, Input, Row, Button, Form, Col, Divider, Radio, FormItemProps } from "antd" +import { Space, Input, Row, Button, Form, Col, Divider, Radio, FormItemProps, DatePicker, Select } from "antd" import { runMethodOptions, runModelOptions, isAvailableOptions, serviceTypeOptions } from "@/pages/Suite/utils" import styled from "styled-components" +import moment from "moment" type SelectItem = { label: string; @@ -66,9 +67,17 @@ type IProps = { [k: string]: any } -const FilterForm: React.FC = (props) => { +type IRefs = { + [k: string]: any +} + +const dateFormat = 'YYYY-MM-DD'; + +const FilterForm: React.ForwardRefRenderFunction = (props, ref) => { const { onOk } = props + React.useImperativeHandle(ref, () => ({ reset: form.resetFields })) + const [form] = Form.useForm() const renderJson = [ { @@ -94,20 +103,32 @@ const FilterForm: React.FC = (props) => { { label: "用例名称", name: "name", + children: }, { label: "创建人", name: "creator", + children: - + } )) @@ -144,4 +167,4 @@ const FilterForm: React.FC = (props) => { ) } -export default FilterForm \ No newline at end of file +export default React.forwardRef(FilterForm) \ No newline at end of file diff --git a/src/pages/Suite/components/RightContent/index.tsx b/src/pages/Suite/components/RightContent/index.tsx index 54d34f8103d81bdd77d21544bb4777ef315668f5..77efdeefbc8fee62fb22934b5caad54732a7f0f0 100644 --- a/src/pages/Suite/components/RightContent/index.tsx +++ b/src/pages/Suite/components/RightContent/index.tsx @@ -15,6 +15,7 @@ import { useParams } from "umi" import CaseChild from "@/pages/Suite/components/Case" import { useCaseProvider } from "../../provider" +import Loading from "@/components/Loading" type IProps = { [k: string]: any @@ -25,8 +26,7 @@ const RightContent: React.FC = (props) => { const defaultParams = { mod_id } const { refreshModules, state } = useCaseProvider() - - const { modules, caseCount } = state + const { modules } = state const [loading, setLoading] = React.useState(true) const [cases, setCases] = React.useState([]) @@ -43,23 +43,18 @@ const RightContent: React.FC = (props) => { const [pageParams, setPageParams] = React.useState(defaultParams) - const handleExportOk = () => { - refreshModules() - getModalCase(pageParams) - } - - const getModalCase = async (params: any) => { + const getModalCase = async (params: any = pageParams) => { setLoading(true) - const { code, msg, data } = await queryModalCases(params) + const { code, data } = await queryModalCases(params) if (code !== 200) { setLoading(false) return setCases([]) } setCases(data) if (data.length > 0) { - if (activeCase && activeCase.id) { + if (activeCase && typeof activeCase.id === "number") { const idx = data.findIndex((i: any) => i.id === activeCase.id) - if (!idx) + if (~idx) setActiveCase(data[idx]) } else setActiveCase(data[0]) @@ -67,45 +62,27 @@ const RightContent: React.FC = (props) => { setLoading(false) } - console.log(activeCase) - React.useEffect(() => { getModalCase({ mod_id }) - return () => { setCases([]) setLoading(true) + setInp("") setSelectCases([]) - setPageParams(defaultParams) setFilter(false) } }, [mod_id]) - const handleMoveOk = () => { - getModalCase(pageParams) - setSelectCases([]) - } - - const handleDeleteOk = () => { - getModalCase(pageParams) - setSelectCases([]) - } - - const handleCreateOk = () => { - getModalCase(pageParams) + const refreshCases = () => { refreshModules() - } - - const getFilterCase = async (params: any) => { - const { code, msg, data } = await queryCases(params) - if (code !== 200) { - return - } - setCases(data) + setSelectCases([]) + getModalCase({ ...pageParams, mod_id }) } const handleOkFilter = (vals: any) => { - getModalCase({ ...pageParams, ...vals }) + const params = { ...pageParams, mod_id, ...vals } + setPageParams(params) + getModalCase(params) } const exportExcel = async () => { @@ -176,7 +153,8 @@ const RightContent: React.FC = (props) => { value={inp} allowClear onChange={({ target }) => setInp(target.value)} - onSearch={() => handleOkFilter({ prefix: inp })} + onSearch={() => handleOkFilter({ key: inp })} + // onKeyUp={handleKeyup} /> setFilter(!filter)}> @@ -219,16 +197,14 @@ const RightContent: React.FC = (props) => { { loading && -
- -
+ } - - + + - - + + ) } diff --git a/src/pages/Suite/services.ts b/src/pages/Suite/services.ts index 5311ca5e6427d37ceeaf1505a3f689c887f6480e..5cfa5d18ff9be7f4c6491c21af1ffafd742c071c 100644 --- a/src/pages/Suite/services.ts +++ b/src/pages/Suite/services.ts @@ -1,122 +1,123 @@ -import {request} from "umi" +import { request } from "umi" export type ICase = { - name: string; - run_method: "manual" | "auto"; - run_model: "single" | "cluster"; - is_available: boolean; - base_fields: any; - tone_case?: any; - custom_field?: any; - parent?: any + name: string; + run_method: "manual" | "auto"; + run_model: "single" | "cluster"; + is_available: boolean; + base_fields: any; + tone_case?: any; + custom_field?: any; + parent?: any } //新增测试用例 export const createCases = async (data: ICase) => { - return request(`/api/case/create/`, {method: "post", data}) + return request(`/api/case/create/`, { method: "post", data }) } export type IModal = { - mod_id: any; - page_num?: any; - page_size?: any; + mod_id: any; + page_num?: any; + page_size?: any; } //查个每个模块下的测试用例 export const queryModalCases = async (params: IModal) => { - return request(`/api/case/`, {method: "get", params}) + return request(`/api/case/search/`, { method: "get", params }) } //重命名测试用例 export const renameCase = async (case_id: any, data: { name: string }) => { - return request(`/api/case/rename/${case_id}`, {method: "post", data}) + return request(`/api/case/rename/${case_id}`, { method: "post", data }) } //批量删除测试用例 export const deleteCases = async (params: { id: any }) => { - return request(`/api/case/`, {method: "delete", params}) + return request(`/api/case/`, { method: "delete", params }) } //移动测试用例 export const moveCase = async (data: { parent: any, cases: any[] }) => { - return request(`/api/case/move`, {method: "post", data}) + return request(`/api/case/move`, { method: "post", data }) } //更新测试用例 export const updateCase = async (case_id: string, data: ICase) => { - return request(`/api/case/edit/${case_id}`, {method: "post", data}) + return request(`/api/case/edit/${case_id}`, { method: "post", data }) } export type IModule = { - name: string; - level?: any; - parent?: any; + name: string; + level?: any; + parent?: any; } //创建分类模块 export const createModule = async (data: IModule) => { - return request(`/api/case/module/create`, {method: "post", data}) + return request(`/api/case/module/create`, { method: "post", data }) } //获得某一模块写的所有子模块 export const queryModules = async (node_id: any) => { - return request(`/api/case/module/${node_id}`) + return request(`/api/case/module/${node_id}`) } //根据前缀获得相同前缀的所有模块名称 export const queryModuleName = async (params: { start?: string }) => { - return request(`/api/case/modules`, {params}) + return request(`/api/case/modules`, { params }) } //重命名模块名称 export const renameModule = async (mod_id: any, data: { name: string }) => { - return request(`/api/case/module/rename/${mod_id}`, {method: "post", data}) + return request(`/api/case/module/rename/${mod_id}`, { method: "post", data }) } //移动模块路径 export const moveMudal = async (mod_id: any, data: { level: any, parent: any }) => { - return request(`/api/case/module/move/${mod_id}`, {method: "post", data}) + return request(`/api/case/module/move/${mod_id}`, { method: "post", data }) } //删除模块(只删除模块,模块用例移动到删除模块的父模块下) export const deleteModule = async (mod_id: string) => { - return request(`/api/case/module/${mod_id}`, {method: "delete"}) + return request(`/api/case/module/${mod_id}`, { method: "delete" }) } //从excel导入测试用例(只支持.xls,.xlsx) export const importExcelCase = async (data: { excel: any }) => { - return request(`/api/case/import/`, {method: "post", data}) + return request(`/api/case/import/`, { method: "post", data }) } type CaseSearchQuery = { - key: string; //查找的关键词 - page_size?: number;//页面大小,默认1 - page_num?: number;// 分页页数,默认50 - type?: string;//测试用例类型,功能测试,性能测试,默认全部 - run_method?: string;// 用例执行方式,默认全选 - run_model?: string;// 用例运行模式,默认全选 - is_available?: boolean;// 用例是否可用,默认全选 - device_type?: string;// 用例执行机器类型,默认全选 - creator?: string;// 创建人,默认不区分 - start_time?: string;// 开始时间 - end_time?: string;// 结束时间 + key: string; //查找的关键词 + page_size?: number;//页面大小,默认1 + page_num?: number;// 分页页数,默认50 + type?: string;//测试用例类型,功能测试,性能测试,默认全部 + run_method?: string;// 用例执行方式,默认全选 + run_model?: string;// 用例运行模式,默认全选 + is_available?: boolean;// 用例是否可用,默认全选 + device_type?: string;// 用例执行机器类型,默认全选 + creator?: string;// 创建人,默认不区分 + start_time?: string;// 开始时间 + end_time?: string;// 结束时间 + mod_id?: string; } //查找用例 export const queryCases = async (params: CaseSearchQuery) => { - return request(`/api/case/search/`, {params}) + return request(`/api/case/`, { params }) } export const querySuiteCase = async (params: any) => { - return request(`/tone/api/case/workspace/case/`, {params: {...params, ws_id: "s63u44w7"}}) + return request(`/tone/api/case/workspace/case/`, { params: { ...params, ws_id: "s63u44w7" } }) } -export const queryDomainList = async (params: any) => { - return request(`/tone/api/case/test_domain/?test_type=domainconf`) +export const queryDomainList = async () => { + return request(`/tone/api/case/test_domain/?test_type=domainconf`) } export const downloadCaseTempFile = async () => { - return request(`/api/case/export`, {method: 'get', responseType: 'blob'}) + return request(`/api/case/export`, { method: 'get', responseType: 'blob' }) } export const exportCases = async (cases: any) => { - return request(`/api/case/export`, {method: 'get', params: {case: cases}, responseType: 'blob'}) + return request(`/api/case/export`, { method: 'get', params: { case: cases }, responseType: 'blob' }) } diff --git a/src/pages/Task/index.tsx b/src/pages/Task/index.tsx index 75597b931a13296fe3b0509b7457a2558dc65c21..0344196ffbadbd1533b9b390485711c78f65b2d2 100644 --- a/src/pages/Task/index.tsx +++ b/src/pages/Task/index.tsx @@ -86,7 +86,7 @@ const TableList: React.FC = () => { return ( - 测试任务 + 测试任务({dataSource?.total || 0})
Promise; + +const requestFn = (querUrl: promiseRequest, questParams?: defaultParams) => { + return useRequest( + (data: defaultParams) => querUrl(data), + { + formatResult: (response: any) => response, + initialData: { data: [], total: 0 }, + defaultParams: questParams ? [questParams] : [{}], + // refreshDeps: [questParams], + onSuccess: (data: any) => { + const code = _.get(data, 'code') + const msg = _.get(data, 'msg') + if (!isQuestRight(code)) requestCodeMessage(code, msg || '请求异常') + }, + onError: (error: Error) => { + requestCodeMessage(500, '网络异常') + console.error(error) + }, + } + ) +} +const requestNotTableFn = (querUrl: promiseRequest, questParams?: any, flag: boolean = false) => { + if (!querUrl) return { data: {}, loading: false, run: () => { }, refresh: () => { } } + return useRequest( + (data: any) => querUrl(questParams || data), + { + formatResult: (response: any) => response, + initialData: { data: {} }, + onSuccess: (data: any) => { + if (flag) return + const code = _.get(data, 'code') + const msg = _.get(data, 'msg') + if (!isQuestRight(code)) requestCodeMessage(code, msg || '网络异常') + }, + onError: (error: Error) => { + requestCodeMessage(500, '网络异常') + console.error(error) + }, + } + ) +} + +const isQuestRight = (code: number) => code >= 200 && code < 300 || code === 304 + +const requestCodeMessage = (code: number, msg: any) => { + // if(code === 404) return history.push('/404') + // if(code === 500) return history.push('/500') + // if(code === 401 || code === 403) return history.push('/403') + let mesg = msg + if (_.isObjectLike(msg)) mesg = JSON.stringify(msg) + if (isQuestRight(Number(code))) return message.success(mesg || '操作成功') + return message.error(mesg || '操作失败') +} +const setFormFieldsValue = (form: any, newObj: object) => { + const valuesClone = _.cloneDeep(form.getFieldsValue()) + form.setFieldsValue({ ...valuesClone, ...newObj }) +} + +export { + requestFn, + requestCodeMessage, + isQuestRight, + defaultParams, + promiseRequest, + requestNotTableFn, + setFormFieldsValue, +}