feat:工作流 - 前端进度 50%

This commit is contained in:
weifs 2023-04-17 18:46:57 +08:00
parent 644a986747
commit 18758421fd
7 changed files with 502 additions and 125 deletions

View File

@ -879,6 +879,7 @@
if (typeof config === "string") config = {title:config};
let inputId = "modalInput_" + $A.randomString(6);
let inputProps = {
type: config.type || "text",
value: config.value,
placeholder: $A.L(config.placeholder),
elementId: inputId,

View File

@ -1,20 +1,28 @@
<template>
<div class="review-details">
<div class="review-details-box">
<h2 class="review-details-title">请假<Tag color="success">已通过</Tag></h2>
<h3 class="review-details-subtitle"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" size="24"/><span>请假名字</span></h3>
<h2 class="review-details-title">
<span>{{data.proc_def_name}}</span>
<Tag v-if="datas.state == 0" color="cyan">{{$L('待审批')}}</Tag>
<Tag v-if="datas.state == 1" color="cyan">{{$L('审批中')}}</Tag>
<Tag v-if="datas.state == 2" color="green">{{$L('已通过')}}</Tag>
<Tag v-if="datas.state == 3" color="red">{{$L('已拒绝')}}</Tag>
<Tag v-if="datas.state == 4" color="red">{{$L('已撤回')}}</Tag>
</h2>
<h3 class="review-details-subtitle"><Avatar :src="data.userimg" size="24"/><span>请假名字</span></h3>
<h3 class="review-details-subtitle"><span>{{$L('提交于')}} {{data.start_time}}</span></h3>
<Divider/>
<div class="review-details-text">
<h4>{{$L('假期类型')}}</h4>
<p>{{$L('事假')}}</p>
<p>{{data.var?.type}}</p>
</div>
<div class="review-details-text">
<h4>{{$L('开始时间')}}</h4>
<p>{{$L('2023年1月1日')}}</p>
<p>{{data.var?.start_time}}</p>
</div>
<div class="review-details-text">
<h4>{{$L('结束时间')}}</h4>
<p>{{$L('2023年1月2日')}}</p>
<p>{{data.var?.end_time}}</p>
</div>
<div class="review-details-text">
<h4>{{$L('时长')}}小时</h4>
@ -22,71 +30,214 @@
</div>
<div class="review-details-text">
<h4>{{$L('请假事由')}}</h4>
<p>{{$L('什么什么什么的')}}</p>
<p>{{data.var?.description}}</p>
</div>
<Divider/>
<h3 class="review-details-subtitle">{{$L('审批进程')}}</h3>
<div class="review-details-process">
<div class="review-details-line"></div>
<div class="review-process">
<div class="review-process-left">
<Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" size="48"/>
<div class="review-process-text">
<p class="review-process-name">审核人名字</p>
<p class="review-process-state">已通过</p>
<h3 class="review-details-subtitle">{{$L('审批记录')}}</h3>
<Timeline style="margin-top: 20px;">
<!-- 提交 -->
<TimelineItem v-for="(item,key) in datas.node_infos" :key="key" v-if="item.type == 'starter'" color="green">
<p class="timeline-title">{{$L('提交')}}</p>
<div style="display: flex;">
<Avatar :src="data.userimg" size="38"/>
<div style="margin-left: 10px;flex: 1;">
<p class="review-process-name">{{item.approver}}</p>
<p class="review-process-state">{{$L('已提交')}}</p>
</div>
<div class="review-process-right">
<p>{{ getTimeAgo(item.claim_time) }}</p>
<p>{{item.claim_time?.substr(0,16)}}</p>
</div>
</div>
<div class="review-process-right">
2023-1-1 12:32:32
</div>
</div>
<div class="review-process">
<div class="review-process-left">
<Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" size="48"/>
<div class="review-process-text">
<p class="review-process-name">审核人名字</p>
<p class="review-process-state">已通过</p>
</TimelineItem>
<!-- 审批 -->
<TimelineItem v-for="(item,key) in datas.node_infos" :key="key" v-if="item.type == 'approver' && item._show"
:color="item.identitylink ? (item.identitylink?.state > 1 ? '#f03f3f' :'green') : '#ccc'"
>
<p class="timeline-title">{{$L('审批')}}</p>
<div style="display: flex;">
<Avatar :src="item.node_user_list[0]?.userimg" size="38"/>
<div style="margin-left: 10px;flex: 1;">
<p class="review-process-name">{{item.approver}}</p>
<p class="review-process-state" style="color: #6d6d6d;" v-if="!item.identitylink">待审批</p>
<p class="review-process-state" v-if="item.identitylink">
<span v-if="item.identitylink.state==0" style="color:#496dff;">{{$L('审批中')}}</span>
<span v-if="item.identitylink.state==1" >{{$L('已通过')}}</span>
<span v-if="item.identitylink.state==2" style="color:#f03f3f;">{{$L('已拒绝')}}</span>
<span v-if="item.identitylink.state==3" style="color:#f03f3f;">{{$L('已撤回')}}</span>
</p>
</div>
<div class="review-process-right">
<p>
{{ item.identitylink?.state==0 ?
($L('已等待') + " " + getTimeAgo( datas.node_infos[key-1].claim_time,2)) :
(item.claim_time ? getTimeAgo(item.claim_time) : '')
}}
</p>
<p>{{item.claim_time?.substr(0,16)}}</p>
</div>
</div>
<div class="review-process-right">
2023-1-1 12:32:32
</div>
</div>
<div class="review-process">
<div class="review-process-left">
<Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" size="48"/>
<div class="review-process-text">
<p class="review-process-name">抄送人</p>
<p class="review-process-state">已抄送</p>
</TimelineItem>
<!-- 抄送 -->
<TimelineItem v-for="(item,key) in datas.node_infos" :key="key" :color="item.is_finished ? 'green' : '#ccc'" v-if="item.type == 'notifier' && item._show">
<p class="timeline-title">{{$L('抄送')}}</p>
<div style="display: flex;">
<Avatar :src="'/images/avatar/default_bot.png'" size="38"/>
<div style="margin-left: 10px;flex: 1;">
<p class="review-process-name">{{$L('系统')}}</p>
<p style="font-size: 12px;">自动抄送
<span style="color: #486fed;">
{{ item.node_user_list?.map(h=>h.name).join(',') }}
{{item.node_user_list?.length}}
</span>
</p>
</div>
</div>
<div class="review-process-right">
2023-1-1 12:32:32
</TimelineItem>
<!-- 结束 -->
<TimelineItem v-for="(item,key) in datas.node_infos" :key="key" :color="item.is_finished ? 'green' : '#ccc'" v-if="item.aprover_type == 'end'">
<p class="timeline-title">{{$L('结束')}}</p>
<div style="display: flex;">
<Avatar :src="'/images/avatar/default_bot.png'" size="38"/>
<div style="margin-left: 10px;flex: 1;">
<p class="review-process-name">{{$L('系统')}}</p>
<p style="font-size: 12px;"> {{ datas.is_finished ? $L('已结束') : $L('未结束') }}</p>
</div>
</div>
</div>
</div>
<div class="review-copy">
<div class="review-copy-member">
<Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" size="24"/>抄送人
</div>
</div>
</TimelineItem>
</Timeline>
</div>
<div class="review-operation">
<Button type="text">通过</Button>
<Button type="text">拒绝</Button>
<Button type="text">撤销</Button>
<Button type="text">删除</Button>
<div class="review-operation" v-if="datas.state<=1">
<Button type="success" v-if="(datas.candidate || '').split(',').indexOf(userId + '') != -1" @click="approve(1)">{{$L('同意')}}</Button>
<Button type="error" v-if="(datas.candidate || '').split(',').indexOf(userId + '') != -1" @click="approve(2)">{{$L('拒绝')}}</Button>
<Button type="warning" v-if="userId == datas.start_user_id" @click="revocation">{{$L('撤销')}}</Button>
</div>
</div>
</template>
<script>
export default {
name: "details"
name: "details",
props: {
data: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
datas:{
}
}
},
watch: {
data: {
handler(newValue,oldValue) {
if(newValue.id){
this.getInfo()
}
},
deep: true
},
},
methods:{
//
getTimeAgo(time,type) {
const currentTime = new Date();
const timeDiff = (currentTime - new Date(time)) / 1000; // convert to seconds
if (timeDiff < 60) {
return type == 2 ? "0"+this.$L('分钟') : this.$L('刚刚');
} else if (timeDiff < 3600) {
const minutes = Math.floor(timeDiff / 60);
return type == 2 ? `${minutes}${this.$L('分钟')}` : `${minutes} ${this.$L('分钟前')}`;
} else {
const hours = Math.floor(timeDiff / 3600);
return type == 2 ? `${hours}${this.$L('小时')}` : `${hours} ${this.$L('小时前')}`;
}
},
//
getInfo(){
this.$store.dispatch("call", {
method: 'get',
url: 'workflow/process/detail',
data: {
id:this.data.id,
}
}).then(({data}) => {
var show = true;
data.node_infos = data.node_infos.map(item=>{
item._show = show;
if( item.identitylink?.state==2 || item.identitylink?.state==3 ){
show = false;
}
return item;
})
this.datas = data
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
//
approve(type){
$A.modalInput({
title: `审批`,
placeholder: `请输入审批意见`,
type:"textarea",
okText: type == 1 ? "同意" : "拒绝",
onOk: (desc) => {
if (!desc) {
return `请输入审批意见`
}
this.$store.dispatch("call", {
url: 'workflow/task/complete',
data: {
task_id: this.data.task_id,
pass: type == 1,
comment: desc,
}
}).then(({msg}) => {
this.getInfo()
this.$emit('approve')
}).catch(({msg}) => {
$A.modalError(msg);
});
return false
}
});
},
//
revocation(){
$A.modalConfirm({
content: "你确定要撤销吗?",
loading: true,
onOk: () => {
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
url: 'workflow/task/withdraw',
data: {
task_id: this.data.task_id,
proc_inst_id: this.data.id,
}
}).then(({msg}) => {
resolve();
this.getInfo()
this.$emit('revocation')
}).catch(({msg}) => {
$A.modalError(msg);
reject(msg);
});
return false
})
},
});
}
}
}
</script>

View File

@ -2,13 +2,17 @@
<div class="page-review">
<PageTitle :title="$L('审批中心')"/>
<div class="review-wrapper" ref="fileWrapper">
<div class="review-head">
<div class="review-nav">
<h1>{{$L('审批中心')}}</h1>
</div>
<Button :loading="loadIng > 0" type="primary" @click="" style="margin-right:10px;">{{$L('发起请假')}}</Button>
<Button :loading="loadIng > 0" type="primary" @click="">{{$L('加班申请')}}</Button>
</div>
<div class="review-main">
<div class="review-main-left">
<Tabs :value="tabsValue" @on-click="tabsClick" style="margin: 0 20px;height: 100%;">
<TabPane :label="$L('待办')" name="backlog" style="height: 100%;">
<div class="review-main-search">
<div>
<Dropdown @on-click="changeTime" trigger="click">
@ -53,44 +57,126 @@
</DropdownMenu>
</template>
</Dropdown>
</div>
<div class="review-mains">
<div class="review-main-left">
<div class="review-main-list">
<list></list>
<list></list>
</div>
</div>
<div class="review-main-right">
<listDetails></listDetails>
</div>
</div>
</TabPane>
<TabPane label="已办" name="name2">2</TabPane>
<TabPane label="抄送我" name="name3">3</TabPane>
<TabPane :label="$L('已发起')" name="initiated">
<div class="review-main-search">
<div>
<Dropdown @on-click="changeTime" trigger="click">
<a href="javascript:void(0)">
{{ timeChose }}<Icon type="ios-arrow-down"></Icon>
</a>
<template #list>
<DropdownMenu>
<DropdownItem name="all">{{$L('全部审批')}}</DropdownItem>
<DropdownItem name="24">{{$L('最近24小时')}}</DropdownItem>
<DropdownItem name="7">{{$L('最近7天')}}</DropdownItem>
<DropdownItem name="30">{{$L('最近30天')}}</DropdownItem>
<DropdownItem name="customize">{{$L('自定义时间')}}</DropdownItem>
</DropdownMenu>
</template>
</Dropdown>
<Dropdown @on-click="" trigger="click">
<a href="javascript:void(0)">
{{ timeChose }}<Icon type="ios-arrow-down"></Icon>
</a>
<template #list>
<DropdownMenu>
<DropdownItem name="all">{{$L('全部状态')}}</DropdownItem>
<DropdownItem name="0">{{$L('审批中')}}</DropdownItem>
<DropdownItem name="1">{{$L('已通过')}}</DropdownItem>
<DropdownItem name="-1">{{$L('已拒绝')}}</DropdownItem>
<DropdownItem name="-2">{{$L('已撤回')}}</DropdownItem>
<DropdownItem name="-3">{{$L('已删除')}}</DropdownItem>
</DropdownMenu>
</template>
</Dropdown>
</div>
<Dropdown @on-click="" trigger="click">
<a href="javascript:void(0)">
<Icon type="ios-arrow-down" />
</a>
<template #list>
<DropdownMenu>
<DropdownItem name="all">{{$L('最新发起优先')}}</DropdownItem>
<DropdownItem name="24">{{$L('最早发起优先')}}</DropdownItem>
</DropdownMenu>
</template>
</Dropdown>
</div>
<div class="review-main-list">
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<list></list>
<div v-if="initiatedList.length==0" style="text-align: center;line-height: 150px;">{{$L('暂无数据')}}</div>
<div v-else class="review-mains">
<div class="review-main-left">
<div class="review-main-list">
<div @click.stop="clickList(item,key)" v-for="(item,key) in initiatedList">
<list :class="{ 'review-list-active': item._active }" :data="item"></list>
</div>
</div>
</div>
<div class="review-main-right">
<listDetails :data="details" @approve="tabsClick" @revocation="tabsClick"></listDetails>
</div>
</div>
</div>
<div class="review-main-right">
<listDetails></listDetails>
</div>
</div>
</TabPane>
</Tabs>
</div>
</div>
</template>
<script>
import list from "./list";
import listDetails from "./details";
import list from "./list.vue";
import listDetails from "./details.vue";
export default {
components:{list,listDetails},
name: "review",
data(){
return{
timeChose:this.$L('所有时间'),
list: [],
page: 1,
pageSize: 250,
total: 0,
noText: '',
loadIng:false,
tabsValue:"",
backlogList: [],
initiatedList: [],
details:{}
}
},
mounted() {
this.tabsValue = "initiated"
this.tabsClick()
},
methods:{
tabsClick(){
if(this.tabsValue == 'backlog'){
this.getBacklogList();
}
if(this.tabsValue == 'initiated'){
this.getInitiatedList();
}
},
changeTime(e){
switch (e) {
case 'all':
@ -110,6 +196,57 @@ export default {
return;
}
},
//
clickList(item){
this.initiatedList.map(h=>{
h._active = false;
})
item._active = true;
this.details = item
},
//
getBacklogList(){
this.$store.dispatch("call", {
method: 'get',
url: 'workflow/process/startByMyself',
data: {
page:this.page,
page_size: this.pageSize,
}
}).then(({data}) => {
this.backlogList = []
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
//
getInitiatedList(){
this.$store.dispatch("call", {
method: 'get',
url: 'workflow/process/startByMyself',
data: {
page:this.page,
page_size: this.pageSize,
}
}).then(({data}) => {
this.initiatedList = data.rows.map((h,index)=>{
h._active = index == 0;
return h;
})
this.details = this.initiatedList[0]
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
}
},
}
</script>

View File

@ -1,16 +1,23 @@
<template>
<div class="review-list review-list-active">
<h2><span class="list-name">请假类型</span> <Tag color="success">已通过</Tag></h2>
<p>{{$L('假期类型:')}}<span>{{$L('事假')}}</span></p>
<p>{{$L('开始时间:')}}<span>{{$L('2023年1月1日')}}</span></p>
<p>{{$L('结束时间:')}}<span>{{$L('2023年1月2日')}}</span></p>
<div class="review-list">
<h2>
<span class="list-name">{{data.proc_def_name}}</span>
<Tag v-if="data.state == 0" color="cyan">{{$L('待审批')}}</Tag>
<Tag v-if="data.state == 1" color="cyan">{{$L('审批中')}}</Tag>
<Tag v-if="data.state == 2" color="green">{{$L('已通过')}}</Tag>
<Tag v-if="data.state == 3" color="red">{{$L('已拒绝')}}</Tag>
<Tag v-if="data.state == 4" color="red">{{$L('已撤回')}}</Tag>
</h2>
<p>{{$L('假期类型')}}<span>{{data.var?.type}}</span></p>
<p>{{$L('开始时间')}}<span>{{data.var?.start_time}}</span></p>
<p>{{$L('结束时间')}}<span>{{data.var?.end_time}}</span></p>
<div class="list-member">
<span>
<Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" size="18"/>
请假名字
<Avatar :src="data.userimg" size="18"/>
{{ data.start_user_name }}
</span>
<span>
发起时间2023年1月2日
{{$L('发起时间')}}{{data.start_time}}
</span>
</div>
</div>
@ -18,7 +25,22 @@
<script>
export default {
name: "list"
name: "list",
props: {
data: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
}
}
}
</script>

View File

@ -12,10 +12,9 @@
<Divider style="margin: 12px 0;"/>
<div class="approve-button-box" @click.stop="edit(item)">
<p>{{$L('是否发布')}} </p>
<p>
<i-switch v-model="item.issue" :disabled="true" />
<!-- <Icon type="md-create" />
<Icon type="md-trash" /> -->
<p @click.stop="!item.issue ? edit(item) : ''">
<i-switch v-model="item.issue" @on-change="change(item)" :disabled="true" />
<!-- <Icon type="md-trash" /> -->
</p>
</div>
</div>
@ -45,8 +44,8 @@ export default {
iframeSrc:"",
name:"",
list:[
{name:"请假",issue:false},
{name:"加班申请",issue:false},
{id:0,name:"请假",issue:false,version:''},
{id:0,name:"加班申请",issue:false,version:''},
]
}
},
@ -57,16 +56,39 @@ export default {
},
mounted() {
window.addEventListener('message', this.saveSuccess)
this.getList();
},
beforeDestroy() {
window.removeEventListener("message", this.saveSuccess);
},
methods: {
//
getList(){
this.$store.dispatch("call", {
url: 'workflow/procdef/all',
method: 'post',
}).then(({data}) => {
data.rows.forEach((h,index) => {
this.list.forEach((o,index) => {
if(o.name == h.name){
o.issue = true;
o.id = h.id;
o.version = h.version;
}
})
})
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
//
saveSuccess(e){
if (typeof e.data === 'string') {
let propsBody = JSON.parse(e.data);
if( propsBody.method == "saveSuccess" ) {
this.getList();
this.list.forEach((h,index) => {
if(h.name == this.name){
h.issue = true;
@ -85,12 +107,41 @@ export default {
},
//
edit(item){
console.log(item.issue)
if(!item.issue){
this.name = item.name
this.approvalSettingShow = true;
this.name = item.name
this.approvalSettingShow = true;
},
//
change(item){
this.$nextTick(()=>{
item.issue = true;
$A.modalConfirm({
title: '取消发布',
content: '将会清空流程数据,确定要取消发布?',
onOk: () => {
this.del(item)
}
});
});
},
//
del(item){
if(!item.id){
item.issue = false;
return true;
}
}
this.$store.dispatch("call", {
url: 'workflow/procdef/del',
data: {id: item.id},
method: 'post',
}).then(({data}) => {
item.issue = false;
$A.messageSuccess('成功');
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
}
}
</script>

View File

@ -73,7 +73,6 @@ export default {
const menu = [
{path: 'personal', name: '个人设置'},
{path: 'checkin', name: '签到设置', desc: ' (Beta)'},
{path: 'approve', name: '审批设置', desc: ' (Beta)'},
{path: 'language', name: '语言设置'},
{path: 'theme', name: '主题设置'},
{path: 'password', name: '密码设置'},
@ -94,6 +93,7 @@ export default {
if (this.userIsAdmin) {
menu.push(...[
{path: 'system', name: '系统设置', divided: true},
{path: 'approve', name: '审批设置', desc: ' (Beta)'},
{path: 'license', name: 'License Key'},
])
}

View File

@ -26,10 +26,21 @@
}
}
}
.review-main{
.review-main-search{
display: flex;
justify-content: space-between;
margin: 0 10px;
margin-bottom: 10px;
> div{
.ivu-dropdown{
margin-right: 8px;
}
}
}
.review-mains{
display: flex;
flex: 1 1 auto;
margin: 0 32px;
height: calc(100vh - 190px);
position: relative;
.review-main-left{
display: flex;
@ -40,22 +51,14 @@
left: 0;
top: 0;
bottom: 12px;
.review-main-search{
display: flex;
justify-content: space-between;
> div{
.ivu-dropdown{
margin-right: 8px;
}
}
}
.review-main-list{
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: scroll;
.review-list{
margin-top: 8px;
margin-bottom: 8px;
padding: 12px;
border-radius: 8px;
border: 1px solid #eeeeee;
@ -69,7 +72,7 @@
align-items: center;
justify-content: space-between;
> .list-name{
font-size: 16px;
font-size: 14px;
font-weight: bold;
}
}
@ -130,7 +133,7 @@
}
}
.review-details-subtitle{
margin-top: 16px;
margin-top: 8px;
display: flex;
.ivu-avatar{
margin-right: 8px;
@ -139,14 +142,34 @@
font-size: 14px;
}
}
.timeline-title{
font-weight: bold;
padding-bottom: 10px;
}
.review-process-name{
margin-bottom: 4px;
}
.review-process-state{
font-size: 12px;
color: #19be6b;
}
.review-process-right{
text-align: right;
}
.review-details-text{
margin-bottom: 16px;
margin-bottom: 12px;
> h4{
color: #999;
}
> p{
font-size: 16px;
margin-top: 4px;
font-size: 14px;
margin-top: 2px;
font-weight: 500;
}
}
@ -180,14 +203,6 @@
display: flex;
flex-direction: column;
margin-left: 8px;
.review-process-name{
margin-bottom: 4px;
}
.review-process-state{
font-size: 16px;
color: #19be6b;
font-weight: 500;
}
}
}
}
@ -197,13 +212,12 @@
}
.review-copy{
margin-top: 8px;
padding-left: 56px;
display: flex;
.review-copy-member{
display: flex;
align-items: center;
background: #F4F4F5;
padding:4px 8px;
padding:2px 8px;
border-radius: 20px;
.ivu-avatar{
margin-right: 4px;
@ -221,6 +235,7 @@
border-top: 1px solid #F4F4F5;
display: flex;
align-items: center;
gap: 10px;
}
}