diff --git a/package.json b/package.json index 6d0e6ba7c0e727396ae9b1b299c9d605196170cf..49a9e6f6652d0573c876515592a3f921f254944e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "bizcharts": "^3.5.3-beta.0", "bizcharts-plugin-slider": "^2.1.1-beta.1", "classnames": "^2.2.6", + "clipboard": "^2.0.11", "gg-editor": "^2.0.2", "lodash": "^4.17.11", "lodash-decorators": "^6.0.0", diff --git a/src/app.tsx b/src/app.tsx index 5f2a539e7cd5394e2f5e4652d51a667fb2a9c16f..1cc20203f8193e7495ffdf0e096408df6700e836 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -12,7 +12,7 @@ import { getUserInfo } from './pages/Auth/Login/services'; import { request as requestInject } from "./request" const isDev = process.env.NODE_ENV === 'development'; -const loginPath = '/user/login'; +const loginPath = '/login'; /** 获取用户信息比较慢的时候会展示一个 loading */ export const initialStateConfig = { @@ -32,6 +32,7 @@ export async function getInitialState(): Promise<{ const { code, msg, data } = await getUserInfo(); if (code !== 200) { /* 未登录跳转 */ + history.push('/login') return null } return data; @@ -61,9 +62,9 @@ export const layout: RunTimeLayoutConfig = (props) => { const { location } = history; setInitialState({ ...initialState, location }) // 如果没有登录,重定向到 login - /* if (!initialState?.currentUser && location.pathname !== loginPath) { + if (!initialState?.currentUser && location.pathname !== loginPath) { history.push(loginPath); - } */ + } }, headerTheme: "dark", headerRender: (props, dom) => , diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx index a22c74636625b972fe151cd086922c0cb4eb907d..2a6fd2ccb1b4186410b11c44d0a13bd53c18a272 100644 --- a/src/components/RightContent/AvatarDropdown.tsx +++ b/src/components/RightContent/AvatarDropdown.tsx @@ -52,7 +52,7 @@ const AvatarDropdown: React.FC = ({ menu }) => { loginOut(); return; } - if (key === "apply") { + if (key === "user-apply") { confirmApplyRef.current?.show() } }, @@ -84,7 +84,7 @@ const AvatarDropdown: React.FC = ({ menu }) => { const menuHeaderDropdown = ( - +
@@ -118,7 +118,7 @@ const AvatarDropdown: React.FC = ({ menu }) => { access.isMember() && <> {menu && } - + 申请测试管理员 diff --git a/src/components/RightContent/Token.tsx b/src/components/RightContent/Token.tsx index 3ceb728da1b46894f0b16b5c68ef88b0d19c1109..31909751e14a17d2cf559f1ed86836a869b82d96 100644 --- a/src/components/RightContent/Token.tsx +++ b/src/components/RightContent/Token.tsx @@ -8,6 +8,7 @@ import { ReactComponent as View } from "@/assets/rightContent/view.svg" import { ReactComponent as Refresh } from "@/assets/rightContent/refresh.svg" import { ReactComponent as Off } from "@/assets/rightContent/view-off.svg" import { ReactComponent as Copy } from "@/assets/rightContent/copy-fill-line.svg" +import { copyText } from "@/utils" const PointerSpan = styled.span` @@ -39,7 +40,7 @@ const Token: React.FC = () => { } const handleCopy = () => { - + copyText(initialState?.currentUser?.token) } return ( diff --git a/src/pages/Auth/Login/index.tsx b/src/pages/Auth/Login/index.tsx index e4dd0fac6e728bdd1c2da3ccc022338f1e1db5e5..c52d3fbe48153529f02511ca267dd95502495d87 100644 --- a/src/pages/Auth/Login/index.tsx +++ b/src/pages/Auth/Login/index.tsx @@ -7,10 +7,9 @@ import { login } from "@/pages/Auth/services" import { BasicForm } from "@/pages/Auth/styled" const LoginPage: React.FC = () => { - const [form] = Form.useForm() - const { setInitialState, initialState } = useModel("@@initialState") const [loading, setLoading] = React.useState(false) + const [form] = Form.useForm() const handleLogin = () => { if (loading) return @@ -67,7 +66,7 @@ const LoginPage: React.FC = () => { rules={[ { required: true, - message: "请输入密码" + message: "请输入密码", } ]} > @@ -98,7 +97,7 @@ const LoginPage: React.FC = () => { 还没有账号,去 - 注册 + 注册 diff --git a/src/pages/Auth/Regist/index.tsx b/src/pages/Auth/Regist/index.tsx index 497d0c5314794848749d12c07e230170d680652f..d511046e45aa32ed5b40e3b376f6773557584bb0 100644 --- a/src/pages/Auth/Regist/index.tsx +++ b/src/pages/Auth/Regist/index.tsx @@ -150,7 +150,7 @@ const RigistPage: React.FC = () => { 已有账号,去 - 登录 + 登录 diff --git a/src/pages/Auth/components/Layout.tsx b/src/pages/Auth/components/Layout.tsx index c762cc5d67ec11cff5fd3daf2b392c32be2ad63f..d544c7df6fa23f1b61ed109e77dcf0991139ab7e 100644 --- a/src/pages/Auth/components/Layout.tsx +++ b/src/pages/Auth/components/Layout.tsx @@ -8,7 +8,7 @@ import bg from "@/assets/auth/bg0.png" const Container = styled.div` width: 100%; height: 100%; - padding: 3%; + padding: 6%; position: relative; background: url(${bg}) no-repeat left center / 100% 100%; ` diff --git a/src/pages/Suite/components/LeftList/index.tsx b/src/pages/Suite/components/LeftList/index.tsx index 09f8cb18ae6c3edacfdf0f7fa3c0faf459e9550a..b1f77020766df3d919df310726b42af5edb7e0b8 100644 --- a/src/pages/Suite/components/LeftList/index.tsx +++ b/src/pages/Suite/components/LeftList/index.tsx @@ -1,55 +1,44 @@ import React from "react" -import { Space, message, Row, Typography, Dropdown, Menu, Tooltip } from "antd" +import { message, Row, Typography, Dropdown, Menu, Tree } from "antd" import { PlusOutlined, MoreOutlined } from "@ant-design/icons" import { createModule, renameModule, deleteModule } from "@/pages/Suite/services" import { useCaseProvider } from "../../provider" import { history, useParams } from "umi" -import { ModuleItem, ModulesContainer, FuncModuleIcon } from "./styled" +import { ModuleItem } from "./styled" import ConfirmInput from "@/components/Public/ConfirmInput" -import { useSize } from "ahooks" import DeleteModal from "@/pages/Outline/components/DeleteModal" +import { queryModules } from "@/pages/Suite/services" +import styled from "styled-components" -const ResizeRow: React.FC = (props) => { - const { style, name, count } = props - const countRef = React.useRef(null) as any - const row = React.useRef(null) as any - const countSize = useSize(countRef) - const rowSize = useSize(row) +const SuiteTree = styled(Tree)` - const width = React.useMemo(() => { - if (rowSize && countSize) return rowSize.width - countSize.width - 1 - return 0 - }, [rowSize, countSize]) +` - return ( - -
- - - {name} - - -
-
- - {`(${count || 0})`} - -
-
- ) -} +const OptBlock = styled.div` + width: 24px; + height: 24px; + display: flex; + align-items: center; + visibility: hidden; + cursor: pointer; +` + +const BlockRow = styled(Row)` + &:hover { + ${OptBlock} { + visibility: visible; + } + } +` type IProps = { [k: string]: any } const LeftList: React.FC = (props) => { - const { state, refreshModules } = useCaseProvider() + const { state, refreshModules, dispath } = useCaseProvider() const { mod_id } = useParams() as any - const { modules, caseCount } = state + const { modules } = state const [edit, setEdit] = React.useState(false) const [rename, setRename] = React.useState(null) @@ -72,21 +61,47 @@ const LeftList: React.FC = (props) => { setEdit(true) } - const handleCreateModuleOk = async (name: string) => { - if (!name) return - const { code, msg, data } = await createModule({ name, parent: 0, level: 0 }) + const handleCreateModuleOk = async (params: { name: string, parent: number, level: number }) => { + const { code, msg, data } = await createModule(params) if (code !== 200) { message.error(msg) return } - refreshModules() - setEdit(false) + console.log(data) + return data + } + + const setModuleItem = (list: any, id: number, level: number, data: any) => { + return list.reduce((p: any, c: any) => { + if (level === c.level && id === c.id) + return p.concat({ ...c, children: data }) + if (c.children) + return p.concat({ ...c, children: setModuleItem(c.children, id, level, data) }) + return p.concat(c) + }, []) } - const handleMenuClick = (e: any, id: string) => { + const createChildModule = async (row: any) => { + const data: any = handleCreateModuleOk({ name: "新分类", parent: row.id, level: row.level }) + if (data) { + const parentData = await getModuleData(row.id) + const newData = setModuleItem(modules, row.id, row.level, parentData) + console.log(newData) + data.id && setRename(`${data.id}-${data.level}`) + dispath({ + type: "update", + payload: { + modules: newData + } + }) + } + } + + const handleMenuClick = (e: any, row: any) => { switch (e.key) { - case "rename": return setRename(id); - case "delete": return deleteModalRef.current.show(id); + case "rename": return setRename(`${row.id}-${row.level}`); + case "delete": return deleteModalRef.current.show(row.id); + case "create": return createChildModule(row) } } @@ -114,6 +129,100 @@ const LeftList: React.FC = (props) => { history.replace(`/cases`) } + const [treeExpanedKeys, setTreeExpandedKeys] = React.useState([]) + + const onExpand = (expandedKeys: any) => { + setTreeExpandedKeys(expandedKeys) + }; + + const transChildren = (list: any) => { + return list.reduce((p: any, c: any) => { + const $key = `${c.id}-${c.level}` + const base = { + ...c, + title: ( + + { + $key !== rename ? + <> + {c.name} + handleMenuClick(event, c)} + > + 重命名 + 新建子模块 + 删除 +
+ } + > + + + + + : + handleRenameOk(c.id, val)} + onCancel={() => setRename(undefined)} + defaultValue={c.name} + /> + } + + + ), + key: $key, + isLeaf: !c.children_nums + } + if (c.children) + return p.concat({ + ...base, + children: transChildren(c.children) + }) + return p.concat(base) + }, []) + } + + const treeData = React.useMemo(() => { + return transChildren(modules) + }, [modules, rename]) + + const getModuleData = async (id: number) => { + const { data, code, msg } = await queryModules(id) + if (code !== 200) { + message.error(msg) + return false + } + return data + } + + const transModuleChildren = (list: any, key: string, data: any) => { + return list.reduce((p: any, c: any) => { + const $key = `${c.id}-${c.level}` + if ($key === key) + return p.concat({ ...c, children: data }) + if (c.children) + return p.concat({ ...c, children: transModuleChildren(c.children, key, data) }) + return p.concat(c) + }, []) + } + + const loadData = async (note: any) => { + const { id, level, children } = note + if (children) return Promise.resolve() + const data = await getModuleData(id) + if (data && !!data.length) { + const newData = transModuleChildren(modules, `${id}-${level}`, data) + dispath({ + type: "update", + payload: { + modules: newData + } + }) + } + return Promise.resolve() + } + return (
@@ -130,60 +239,18 @@ const LeftList: React.FC = (props) => { is_active={mod_id === undefined} onClick={() => handleActiveModule(undefined)} > - - - 所有用例 - {`(${caseCount || 0})`} - + 所有用例 - - { - modules.map((mod: any) => ( - handleActiveModule(mod.id)} - is_child - > - - { - rename === mod.id ? - handleRenameOk(mod.id, val)} - onCancel={() => setRename(null)} - /> : - <> - - handleMenuClick(e, mod.id)}> - - 重命名 - - - 删除 - - - } - > - - - - - - } - - )) - } - + { edit && diff --git a/src/pages/Suite/index.tsx b/src/pages/Suite/index.tsx index 1d699c56113660d7fc01579a91d9094c4ca0f449..5857d8656994dc5b9e76a28823c295f60e460376 100644 --- a/src/pages/Suite/index.tsx +++ b/src/pages/Suite/index.tsx @@ -5,13 +5,14 @@ import RightContent from "./components/RightContent" import { Provider as CaseProvider } from "./provider" import { queryModules } from "@/pages/Suite/services" +import { message } from "antd" type IProps = { [k: string]: any; } const TableList: React.FC = (props) => { - const initialState = { modules: [], caseCount: 0 } + const initialState = { modules: [] }/* , caseCount: 0 */ const [state, dispath] = React.useReducer((state: any, action: any) => { switch (action.type) { @@ -21,9 +22,12 @@ const TableList: React.FC = (props) => { }, initialState) const refreshModules = async () => { - const { data, code, count } = await queryModules(0) - if (code !== 200) return dispath({ type: "update", payload: { modules: [], caseCount: 0 } }) - dispath({ type: "update", payload: { modules: data, caseCount: count } }) + const { data, code, msg } = await queryModules(0) + if (code !== 200) { + return message.error(msg) + /* return dispath({ type: "update", payload: { modules: [], caseCount: 0 } }) */ + } + dispath({ type: "update", payload: { modules: data } }) } return ( diff --git a/src/pages/Sys/ApprovalAndRecord/Approval/index.tsx b/src/pages/Sys/ApprovalAndRecord/Approval/index.tsx index 381ff2feb7df3d8fd4bffa60f13ded01fffef701..473b48c0463e2542179b3a1f24ffb258c5998118 100644 --- a/src/pages/Sys/ApprovalAndRecord/Approval/index.tsx +++ b/src/pages/Sys/ApprovalAndRecord/Approval/index.tsx @@ -1,10 +1,25 @@ import React from "react" -import { Table, Space, Typography, TableColumnProps, message } from "antd" +import { Table, Space, Typography, TableColumnProps, message, Row, Button } from "antd" import { queryList, reviewRoleApply } from "./services" import { useRequest } from "ahooks" +import styled from "styled-components" + +const BatchOptionRow = styled(Row)` + height: 48px; + background: #fff; + position: absolute; + width: 100%; + left: 0; + bottom: 0; + box-shadow: 0 -9px 28px 0 rgba(0,0,0,0.05); + padding: 0 20px; +` const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } +type ReviewItem = { review_id: number, user_id: number } +type ReviewResult = "pass" | "fail" + const TableList: React.FC = () => { const [pageQuery, setPageQuery] = React.useState(DEFAULT_PAGE_QUERY) const [pendding, setPendding] = React.useState(false) @@ -15,17 +30,30 @@ const TableList: React.FC = () => { { refreshDeps: [pageQuery] } ) - const handleReview = async (row: any, review_result: "pass" | "fail") => { + const handleReview = async (row: any, review_result: ReviewResult) => { + reviewApply([{ review_id: row.id, user_id: row.applicant_id }], review_result) + } + + const reviewApply = async (review_list: ReviewItem[], review_result: ReviewResult) => { if (pendding) return setPendding(true) const { code, msg } = await reviewRoleApply({ - review_list: [{ review_id: row.id, user_id: row.user_id }], + review_list, review_result }) setPendding(false) if (code !== 200) return message.error(msg) message.success("操作成功!") refresh() + setSelectedRowKeys([]) + } + + const batchReview = async (review_result: ReviewResult) => { + const review_list = selectedRowKeys.reduce((pre: any[], cur: number) => { + const t = dataSource.data.filter((i: any) => i.id === cur) + return pre.concat(t) + }, []).map(({ id, applicant_id }: any) => ({ review_id: id, user_id: applicant_id })) + reviewApply(review_list, review_result) } const columns: TableColumnProps[] = [{ @@ -51,7 +79,7 @@ const TableList: React.FC = () => { handleReview(row, "pass")}> 通过 - handleReview(row, "fail")}> + handleReview(row, "fail")} style={{ cursor: "pointer" }}> 拒绝 @@ -80,6 +108,22 @@ const TableList: React.FC = () => { } }} /> + { + !!selectedRowKeys.length && + + + 已选{selectedRowKeys.length}项 + setSelectedRowKeys([])}>取消 + + + + + + + } ) } diff --git a/src/pages/Sys/ApprovalAndRecord/Record/index.tsx b/src/pages/Sys/ApprovalAndRecord/Record/index.tsx index 98a98723c3f4616e39b6d056c35f695fd0683331..319d829e70a33dddbe8a91056bc6f40d911be9b4 100644 --- a/src/pages/Sys/ApprovalAndRecord/Record/index.tsx +++ b/src/pages/Sys/ApprovalAndRecord/Record/index.tsx @@ -2,6 +2,7 @@ import React from "react" import { Table, TableColumnProps, Badge } from "antd" import { queryList } from "./services" import { useRequest } from "ahooks" +import UserAvatarColumn from "@/components/Public/UserAvatarColumn" const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } @@ -16,20 +17,20 @@ const TableList: React.FC = () => { const columns: TableColumnProps[] = [{ title: "申请角色", render() { - return "系统管理员" + return "测试人员" } }, { title: "申请人", - render() { - return "系统管理员" + render(row) { + return } }, { title: "申请时间", dataIndex: "gmt_created" }, { title: "审批人", - render() { - return "系统管理员" + render(row) { + return } }, { title: "审批时间", @@ -38,8 +39,8 @@ const TableList: React.FC = () => { title: "审批结果", render(row: any) { if (row.review_result === "pass") - return - return + return + return } }] diff --git a/src/pages/Sys/Users/index.tsx b/src/pages/Sys/Users/index.tsx index c3aebd9808fee03707f41b8d6fe966fa16caf68b..3e34bda16c60ab2a92e7378c7a5ccd5963853634 100644 --- a/src/pages/Sys/Users/index.tsx +++ b/src/pages/Sys/Users/index.tsx @@ -1,10 +1,11 @@ import React from "react" -import { Table, Space, Typography, Row, Button, Select, message, TableColumnProps } from "antd" +import { Table, Space, Typography, Row, Menu, Select, message, TableColumnProps, Dropdown } from "antd" import { deleteUser, queryList, updateUserRole } from "./services" import { useRequest } from "ahooks" import { useIntl } from "umi" import UserAvatarColumn from "@/components/Public/UserAvatarColumn" import { userRoleMap } from "@/utils" +import { DownOutlined } from "@ant-design/icons" const DEFAULT_PAGE_QUERY = { page_size: 20, page_num: 1 } @@ -42,7 +43,7 @@ const TableList: React.FC = (props) => { const handleUserRoleChange = async (role: string, row: any) => { if (pendding) return setPendding(true) - const { code, msg } = await updateUserRole({ user_id: row.id, role: localRoleToKey.get(role) || "", method: "" }) + const { code, msg } = await updateUserRole({ user_id: row.id, role, method: role === "senior" ? "upgrade" : "downgrade" }) setPendding(false) if (code !== 200) return message.error(msg) message.success("操作成功!") @@ -62,26 +63,38 @@ const TableList: React.FC = (props) => { dataIndex: "role", render(_: string, row: any) { return ( - + + {userRoleMap.get(row.role)} + + + ) } }, { diff --git a/src/pages/Sys/index.tsx b/src/pages/Sys/index.tsx index 4b1652c51c68b25048f50acb5caac7b304aa3b31..c3e1c20cb92fb2aea6372e50e370a19dc833026e 100644 --- a/src/pages/Sys/index.tsx +++ b/src/pages/Sys/index.tsx @@ -19,7 +19,7 @@ const SysContainer: React.FC = (props) => { }, [pathname, route]) return ( - + = (props) => { } - + { ...options, headers: { ...options.headers, - "X-Sanic-Token": localStorage.getItem("auth_token"), - // "testLib": localStorage.getItem("auth_token"), + // "X-Sanic-Token": localStorage.getItem("auth_token"), + "testLib": localStorage.getItem("auth_token"), }, }, }; diff --git a/src/utils/index.ts b/src/utils/index.ts index ca3a26fff0054fff38eda3f2f58cad74e05f2787..7ab21646229ae33e4cf7cf7f754a450d7448ee5f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,26 @@ import { useRequest, history } from 'umi' import { message } from 'antd'; import _ from 'lodash' +import Clipboard from "clipboard" + +export const copyText = (text?: string) => { + if (!text) return + const dom = document.createElement("a") + dom.style.width = "0px"; + dom.style.height = "0px" + document.body.appendChild(dom) + const cp = new Clipboard(dom, { + text: () => text + }) + + cp.on("success", () => { + message.success("已复制到剪切板!") + }) + + dom.click() + cp.destroy() + document.body.removeChild(dom) +} type defaultParams = { page_num?: number;