Skip to content

Commit

Permalink
feat: 完善审批流
Browse files Browse the repository at this point in the history
  • Loading branch information
JackySoft authored and jianbing.chen committed Dec 18, 2024
1 parent 5ef5460 commit 480e369
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 165 deletions.
2 changes: 2 additions & 0 deletions packages/docs/src/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ const ZHIPU_AI_KEY = '';

> 1. 上面的配置,对于必填的,都有备注。
> 2. 邮箱必须是自己的 163 邮箱,并且开启 POP3 服务,否则无法发送验证码
> 3. 第一次开启 POP3 服务,会得到一个授权码,需要把授权码填写在邮箱密码处,此时不能用邮箱密码。
> 4. 后续邮箱注册会变更为企业邮箱。
3. 安装依赖

Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/api/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default {
return request.post('/workflow/create', params);
},
// 更新模板
updateTemplate: (params: Omit<IWorkFlow, 'id'>) => {
updateTemplate: (params: Pick<IWorkFlow, 'id' | 'template_data'>) => {
return request.post('/workflow/update', params);
},
// 删除模板
Expand Down
183 changes: 91 additions & 92 deletions packages/editor/src/pages/home/ProjectList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card, Col, Layout, Row, Pagination, Spin, Empty, Button, Form, Tooltip } from 'antd';
import { Card, Col, Layout, Row, Pagination, Spin, Empty, Button, Form, Tooltip, Image } from 'antd';
import { UserOutlined, DeleteOutlined, LockOutlined, PlusOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import { getProjectList, delProject } from '@/api';
Expand All @@ -23,14 +23,13 @@ export default function Index() {
const [current, setCurrent] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(12);
const createRef = useRef<{ open: () => void }>();
const navigate = useNavigate();

useEffect(() => {
getList(current, pageSize);
}, [current, pageSize]);

// 加载页面列表
const getList = async (pageNum: number = current, size: number = pageSize) => {
const getList = useCallback(async (pageNum: number = current, size: number = pageSize) => {
try {
setLoading(true);
const { type, keyword } = form.getFieldsValue();
Expand All @@ -46,23 +45,7 @@ export default function Index() {
} catch (error) {
setLoading(false);
}
};

// 删除项目确认
const deleteProjectConfirm = (event: React.MouseEvent, id: number) => {
event.stopPropagation();
Modal.confirm({
title: '确认',
content: '确认删除该项目吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await delProject({ id });
message.success('删除成功');
getList();
},
});
};
}, []);

// 切换页码和每页条数回调
const handleChange = (_current: number, size: number) => {
Expand All @@ -75,82 +58,12 @@ export default function Index() {
createRef.current?.open();
};

// 页面操作
const handleAction = async (id: number, isEdit: boolean) => {
if (!id) {
message.warning('该项目为私有项目');
return false;
}
if (!isEdit) {
message.warning('您不是该项目开发者,当前只有访问权限。');
return false;
}
navigate(`/project/${id}/config`);
};

// 提交搜索
const handleSearch = () => {
setCurrent(1);
getList(1, pageSize);
};

// 项目卡片
const CardItem: React.FC<ProjectCardItemProps> = ({ item, isAuth }) => {
const getEnvTag = (env: 'stg' | 'pre' | 'prd', name: string) => {
const title = {
stg: '访问测试环境',
pre: '访问预发布环境',
prd: '访问生产环境',
}[env];
return (
<Tooltip title={title}>
<a href={`${import.meta.env.VITE_ADMIN_URL}/project/${env}/${item.id}`} target="_blank">
{name}
</a>
</Tooltip>
);
};
return (
<Card
hoverable
style={{
opacity: isAuth ? 1 : 0.6,
background: isAuth ? 'none' : "url('/imgs/cross-bg.png')",
}}
actions={[
item.id ? getEnvTag('stg', 'STG') : <span style={{ cursor: 'not-allowed' }}>STG</span>,
item.id ? getEnvTag('pre', 'PRE') : <span style={{ cursor: 'not-allowed' }}>PRE</span>,
item.id ? getEnvTag('prd', 'PRD') : <span style={{ cursor: 'not-allowed' }}>PRD</span>,
]}
>
<div className={styles.projectCard} onClick={() => handleAction(item.id, item.is_edit)}>
<Card.Meta
style={{ cursor: isAuth ? 'pointer' : 'not-allowed' }}
avatar={<img src={item.logo} style={{ width: 50 }} />}
title={item.name}
description={
<>
<div className={isAuth ? 'unlock' : 'lock'}>
<LockOutlined />
</div>
{item.id && item.is_edit ? (
<DeleteOutlined className={styles.delIcon} onClick={(event) => deleteProjectConfirm(event, item.id)} />
) : null}
<p style={{ color: 'rgba(0, 0, 0, 0.88)' }}>{item.remark || '暂无描述'}</p>
<p style={{ marginTop: 10 }}>
<UserOutlined style={{ fontSize: 14, marginRight: 5 }} />
{item.user_name}
&nbsp;&nbsp;
<span>更新于 {dayjs(item.updated_at).fromNow()}</span>
</p>
</>
}
/>
</div>
</Card>
);
};

return (
<>
<Layout.Content className={styles.project}>
Expand All @@ -163,7 +76,7 @@ export default function Index() {
{projectList.map((item: Project.ProjectItem, index) => {
return (
<Col span={6} key={item.id || index}>
<CardItem item={item} isAuth={item.id ? true : false} />
<CardItem item={item} isAuth={item.id ? true : false} getList={getList} />
</Col>
);
})}
Expand Down Expand Up @@ -195,3 +108,89 @@ export default function Index() {
</>
);
}
// 项目卡片
const CardItem: React.FC<ProjectCardItemProps> = memo(({ item, isAuth, getList }) => {
const navigate = useNavigate();
const getEnvTag = (env: 'stg' | 'pre' | 'prd', name: string) => {
const title = {
stg: '访问测试环境',
pre: '访问预发布环境',
prd: '访问生产环境',
}[env];
return (
<Tooltip title={title}>
<a href={`${import.meta.env.VITE_ADMIN_URL}/project/${env}/${item.id}`} target="_blank">
{name}
</a>
</Tooltip>
);
};

// 页面操作
const handleAction = async (id: number, isEdit: boolean) => {
if (!id) {
message.warning('该项目为私有项目');
return false;
}
if (!isEdit) {
message.warning('您不是该项目开发者,当前只有访问权限。');
return false;
}
navigate(`/project/${id}/config`);
};

// 删除项目确认
const deleteProjectConfirm = (event: React.MouseEvent, id: number) => {
event.stopPropagation();
Modal.confirm({
title: '确认',
content: '确认删除该项目吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await delProject({ id });
message.success('删除成功');
getList();
},
});
};
return (
<Card
hoverable
style={{
opacity: isAuth ? 1 : 0.6,
background: isAuth ? 'none' : "url('/imgs/cross-bg.png')",
}}
actions={[
item.id ? getEnvTag('stg', 'STG') : <span style={{ cursor: 'not-allowed' }}>STG</span>,
item.id ? getEnvTag('pre', 'PRE') : <span style={{ cursor: 'not-allowed' }}>PRE</span>,
item.id ? getEnvTag('prd', 'PRD') : <span style={{ cursor: 'not-allowed' }}>PRD</span>,
]}
>
<div className={styles.projectCard} onClick={() => handleAction(item.id, item.is_edit)}>
<Card.Meta
style={{ cursor: isAuth ? 'pointer' : 'not-allowed' }}
avatar={<Image src={item.logo} width={50} />}
title={item.name}
description={
<>
<div className={isAuth ? 'unlock' : 'lock'}>
<LockOutlined />
</div>
{item.id && item.is_edit ? (
<DeleteOutlined className={styles.delIcon} onClick={(event) => deleteProjectConfirm(event, item.id)} />
) : null}
<p style={{ color: 'rgba(0, 0, 0, 0.88)' }}>{item.remark || '暂无描述'}</p>
<p style={{ marginTop: 10 }}>
<UserOutlined style={{ fontSize: 14, marginRight: 5 }} />
{item.user_name}
&nbsp;&nbsp;
<span>更新于 {dayjs(item.updated_at).fromNow()}</span>
</p>
</>
}
/>
</div>
</Card>
);
});
3 changes: 2 additions & 1 deletion packages/editor/src/pages/home/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
}
}
}
.project {
.project,
.workflow {
.projectList {
flex: 1;
}
Expand Down
65 changes: 44 additions & 21 deletions packages/editor/src/pages/home/workflow/Design.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
import { LeftOutlined, SaveOutlined } from '@ant-design/icons';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Avatar, Button, Drawer, Form, Segmented, Select, Space } from 'antd';
import { DownloadOutlined, LeftOutlined, SaveOutlined } from '@ant-design/icons';
import ApprovalFlow from './components/ApprovalFlow';
import { NodeItem, NodeType } from './components/types';
import { message } from '@/utils/AntdGlobal';
import { NodeItem } from './components/types';
import api from '@/api/workflow';
import style from './index.module.less';
import { useState } from 'react';
import { saveFile } from '@/utils/util';
/**
* 工作流设计器
*/
const Designer = () => {
const [open, setOpen] = useState(false);
const [name, setName] = useState('');
const [nodeList, setNodeList] = useState<NodeItem[]>([]);
const [node, setNode] = useState<NodeItem | null>(null);
const nodeList: NodeItem[] = [
{
id: 'start',
type: 'start',
title: '开始',
},
{
id: 'end',
type: 'end',
title: '结束',
},
];
const nodeRef = useRef<{ getNodeList: () => void }>();
const { id } = useParams();
useEffect(() => {
api.getTemplateDetail(Number(id)).then((res) => {
setName(res.form_name);
if (res?.template_data) {
const list = JSON.parse(res?.template_data);
setNodeList(list);
}
});
}, []);
// 节点点击事件
const onNodeClick = (node: NodeItem) => {
setOpen(true);
setNode(node);
};

// 保存事件
const handleSave = async () => {
const list = nodeRef.current?.getNodeList();
await api.updateTemplate({
template_data: JSON.stringify(list),
id: Number(id),
});
message.success('保存成功');
};

// 文件导出
const handleExport = () => {
saveFile(name, JSON.stringify(nodeList, null, 2));
};

// 抽屉确认事件
const handleConfirm = () => {
setOpen(false);
Expand All @@ -49,35 +69,38 @@ const Designer = () => {
</div>
<div className="center">
<Button type="link">
<Avatar size={20} style={{ border: '1px solid rgba(0,0,0,0.1)', backgroundColor: '#fff', fontSize: 12, color: '#000' }}>
<Avatar size={20} className={style.circle} style={{ fontSize: 12 }}>
1
</Avatar>
基础配置
</Button>
<Button type="link">
<Avatar size={20} style={{ border: '1px solid rgba(0,0,0,0.1)', backgroundColor: '#fff', fontSize: 12, color: '#000' }}>
<Avatar size={20} className={style.circle} style={{ fontSize: 12 }}>
2
</Avatar>
表单设计
</Button>
<Button type="link">
<Avatar size={20} style={{ border: '1px solid rgba(0,0,0,0.1)', backgroundColor: '#fff', fontSize: 12, color: '#000' }}>
<Avatar size={20} className={style.circle} style={{ fontSize: 12 }}>
3
</Avatar>
流程设计
</Button>
</div>
<div className="right">
<Space>
<Button type="default" icon={<SaveOutlined />}>
<Button type="default" icon={<SaveOutlined />} onClick={handleSave}>
保存
</Button>
<Button type="primary">发布</Button>
<Button type="primary" icon={<DownloadOutlined />} onClick={handleExport}>
导出
</Button>
{/* <Button type="primary">发布</Button> */}
</Space>
</div>
</div>
<div className={style.designerContent}>
<ApprovalFlow nodeList={nodeList} onNodeClick={onNodeClick} />
<ApprovalFlow nodeList={nodeList} onNodeClick={onNodeClick} ref={nodeRef} />
</div>
{/* 点击节点,打开抽屉 */}
<Drawer
Expand Down
Loading

0 comments on commit 480e369

Please sign in to comment.