refactor(approve): 移除主仓库内置审批功能,收敛到插件/微应用

删除 ApproveController、ApproveProcInstHistory/ApproveProcMsg 模型、approve
前端页面与导出组件,移除 approve 路由与 flow_url 配置;审批消息模板改为对接
插件侧能力。版本号 1.7.90 → 1.7.91。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-06-16 11:35:49 +00:00
parent 2f8dee44c2
commit 97bd58312e
26 changed files with 85 additions and 3230 deletions

View File

@ -158,11 +158,6 @@ drawio/webapp/js/app.min.js
drawio/webapp/js/extensions.min.js
drawio/webapp/js/shapes-14-6-5.min.js
drawio/webapp/js/stencils.min.js
drawio/webapp/math/es5/core.js
drawio/webapp/math/es5/input/asciimath.js
drawio/webapp/math/es5/input/tex.js
drawio/webapp/math/es5/output/svg.js
drawio/webapp/math/es5/output/svg/fonts/tex.js
drawio/webapp/styles/grapheditor.css
minder/css/chunk-vendors.fe9c56c6.css

File diff suppressed because it is too large Load Diff

View File

@ -1257,6 +1257,51 @@ class DialogController extends AbstractController
return $result;
}
/**
* @api {post} api/dialog/msg/sendapprove 发送审批通知卡片
*
* @apiDescription 需要token身份。以「审批助手」机器人身份向指定用户发送审批模板卡片
* (由 approve 插件调用,卡片仅展示、不与旧审批系统有数据关联)。
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendapprove
*
* @apiParam {Number} to_userid 接收用户ID
* @apiParam {String} type 卡片类型approve_reviewer / approve_notifier / approve_submitter / approve_comment_notifier
* @apiParam {String} [action] 动作start / pass / refuse / withdraw按类型取用
* @apiParam {Number} [is_finished] 是否已结束0/1
* @apiParam {Object} data 卡片数据
* @apiParam {String} [title] 消息标题(会话列表预览用)
*/
public function msg__sendapprove()
{
$user = User::auth();
$toUserid = intval(Request::input('to_userid'));
$type = trim(Request::input('type'));
$action = trim(Request::input('action'));
$isFinished = intval(Request::input('is_finished'));
$data = Base::json2array(Request::input('data'));
$title = trim(Request::input('title'));
//
$allow = ['approve_reviewer', 'approve_notifier', 'approve_submitter', 'approve_comment_notifier'];
if ($toUserid <= 0 || !in_array($type, $allow)) {
return Base::retError('参数错误');
}
$botUser = User::botGetOrCreate('approval-alert');
$dialog = WebSocketDialog::checkUserDialog($botUser, $toUserid);
if (empty($dialog)) {
return Base::retError('无法创建对话');
}
$msgData = [
'type' => $type,
'action' => $action ?: null,
'is_finished' => $isFinished,
'data' => $data,
'title' => $title,
];
return WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $msgData, $botUser->userid, false, false, true);
}
/**
* @api {post} api/dialog/msg/sendrecord 发送语音
*

View File

@ -1,99 +0,0 @@
<?php
namespace App\Models;
use Cache;
use Carbon\Carbon;
use DB;
/**
* App\Models\ApproveProcInstHistory
*
* @property int $id
* @property int $proc_def_id 流程定义ID
* @property string|null $proc_def_name 流程定义名
* @property string|null $title 标题
* @property int|null $department_id 用户部门ID
* @property string|null $department 用户部门
* @property string|null $company 用户公司
* @property string|null $node_id 当前节点
* @property string|null $candidate 审批人
* @property int|null $task_id 当前任务
* @property string|null $start_time 开始时间
* @property string|null $end_time 结束时间
* @property int|null $duration 持续时间
* @property string|null $start_user_id 开始用户ID
* @property string|null $start_user_name 开始用户名
* @property int|null $is_finished 是否完成
* @property string|null $var
* @property int $state 当前状态: 0待审批1审批中2通过3拒绝4撤回
* @property string|null $latest_comment
* @property string|null $global_comment
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereCandidate($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereCompany($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereDepartment($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereDepartmentId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereDuration($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereEndTime($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereGlobalComment($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereIsFinished($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereLatestComment($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereNodeId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereProcDefId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereProcDefName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereStartTime($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereStartUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereStartUserName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereState($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereTitle($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcInstHistory whereVar($value)
* @mixin \Eloquent
*/
class ApproveProcInstHistory extends AbstractModel
{
protected $table = 'approve_proc_inst_history';
/**
* 获取用户审批状态(请假、外出)
* @param $userid
* @return mixed|null
*/
public static function getUserApprovalStatus($userid)
{
if (empty($userid)) {
return null;
}
return Cache::remember('user_is_leave_' . $userid, Carbon::now()->addMinute(), function () use ($userid) {
return self::where([
['start_user_id', '=', $userid],
[DB::raw("JSON_UNQUOTE(JSON_EXTRACT(var, '$.startTime'))"), '<=', Carbon::now()->toDateTimeString()],
[DB::raw("JSON_UNQUOTE(JSON_EXTRACT(var, '$.endTime'))"), '>=', Carbon::now()->toDateTimeString()],
['state', '=', 2]
])->where(function ($query) {
$query->where('proc_def_name', 'like', '%请假%')
->orWhere('proc_def_name', 'like', '%外出%');
})->orderByDesc('id')->value('proc_def_name');
});
}
/**
* 判断用户是否请假(包含:请假、外出)
* @param $userid
* @return bool
*/
public static function userIsLeave($userid)
{
return (bool)self::getUserApprovalStatus($userid);
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace App\Models;
/**
* App\Models\ApproveProcMsg
*
* @property int $id
* @property int|null $proc_inst_id 流程实例ID
* @property int|null $userid 会员ID
* @property int|null $msg_id 消息ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereMsgId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereProcInstId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereUserid($value)
* @mixin \Eloquent
*/
class ApproveProcMsg extends AbstractModel
{
}

View File

@ -2,7 +2,6 @@
namespace App\Tasks;
use App\Models\ApproveProcInstHistory;
use App\Models\User;
use App\Models\UserCheckinRecord;
use App\Models\WebSocketDialog;
@ -80,9 +79,6 @@ class CheckinRemindTask extends AbstractTask
if (!UserCheckinRecord::whereUserid($user->userid)->where('created_at', '>', Carbon::now()->subDays(3))->exists()) {
continue; // 3天内没有打卡
}
if (ApproveProcInstHistory::userIsLeave($user->userid)) {
continue; // 请假、外出
}
$dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid);
if ($dialog) {
if ($type === 'exceed') {

View File

@ -20,9 +20,6 @@ return [
// 创始人密码修改开关:设为 'disabled' 时禁止修改创始人密码User 模型)
'password_owner' => env('PASSWORD_OWNER'),
// 审批流服务地址:审批微服务的内部访问 URLApproveController
'flow_url' => env('FLOW_URL'),
// Manticore 全文搜索服务主机ManticoreBase
'search_host' => env('SEARCH_HOST', 'search'),

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "1.7.90",
"version": "1.7.91",
"codeVerson": 237,
"description": "DooTask is task management system.",
"scripts": {

View File

@ -447,7 +447,6 @@ export default {
'/api/dialog/msg/download', //
'/api/project/task/filedown', //
'/api/file/download/pack', //
'/api/approve/down', //
'/api/project/task/down', //
'/api/system/checkin/down' //
];

View File

@ -18,7 +18,7 @@
<Badge class="tabbar-badge" :overflow-count="999" :text="msgUnreadMention"/>
</template>
<template v-else-if="item.name === 'application'">
<Badge class="tabbar-badge" :overflow-count="999" :count="reportUnreadNumber + approveUnreadNumber"/>
<Badge class="tabbar-badge" :overflow-count="999" :count="reportUnreadNumber"/>
</template>
</li>
</ul>
@ -54,7 +54,7 @@ export default {
},
computed: {
...mapState(['cacheDialogs', 'reportUnreadNumber', 'approveUnreadNumber']),
...mapState(['cacheDialogs', 'reportUnreadNumber']),
...mapGetters(['dashboardTask']),
/**
@ -138,7 +138,7 @@ export default {
},
activeName() {
if (['manage-calendar', 'manage-file', 'manage-setting', 'manage-application', 'manage-approve'].includes(this.routeName)) {
if (['manage-calendar', 'manage-file', 'manage-setting', 'manage-application'].includes(this.routeName)) {
return 'application';
}

View File

@ -82,7 +82,6 @@
<DropdownItem name="allUser">{{$L('团队管理')}}</DropdownItem>
<DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem>
<DropdownItem name="exportOverdueTask">{{$L('导出超期任务')}}</DropdownItem>
<DropdownItem name="exportApprove">{{$L('导出审批数据')}}</DropdownItem>
<DropdownItem name="exportCheckin">{{$L('导出签到数据')}}</DropdownItem>
</DropdownMenu>
</Dropdown>
@ -126,10 +125,6 @@
v-else-if="item.path === 'workReport' && reportUnreadNumber > 0"
class="manage-menu-report-badge"
:count="reportUnreadNumber"/>
<Badge
v-else-if="item.path === 'approve' && approveUnreadNumber > 0"
class="manage-menu-report-badge"
:count="approveUnreadNumber"/>
</div>
</DropdownItem>
</template>
@ -162,7 +157,7 @@
<li @click="toggleRoute('application')" :class="classNameRoute('application')">
<i class="taskfont">&#xe60c;</i>
<div class="menu-title">{{$L('应用')}}</div>
<Badge class="menu-badge" :overflow-count="999" :text="String((reportUnreadNumber + approveUnreadNumber) || '')"/>
<Badge class="menu-badge" :overflow-count="999" :text="String(reportUnreadNumber || '')"/>
</li>
<li v-for="(item, key) in filterMicroAppsMenusMain" :key="key" @click="onTabbarClick('microApp', item)">
<div class="apply-icon no-dark-content" :style="{backgroundImage: `url(${item.icon})`}"></div>
@ -389,9 +384,6 @@
<!--导出签到数据-->
<CheckinExport v-model="exportCheckinShow"/>
<!--导出审批数据-->
<ApproveExport v-model="exportApproveShow"/>
<!--任务详情-->
<TaskModal ref="taskModal"/>
@ -457,16 +449,6 @@
<ProjectArchived v-if="archivedProjectShow"/>
</DrawerOverlay>
<!--审批中心-->
<DrawerOverlay v-model="approveShow" placement="right" :size="1380" class-name="approve-drawer">
<Approve v-if="approveShow"/>
</DrawerOverlay>
<!--审批详情-->
<DrawerOverlay v-model="approveDetailsShow" placement="right" :size="600">
<ApproveDetails v-if="approveDetailsShow" :data="approveDetails"/>
</DrawerOverlay>
<!--移动端选项卡-->
<transition name="mobile-slide">
<MobileTabbar v-if="mobileTabbar" @on-click="onTabbarClick"/>
@ -495,14 +477,11 @@ import DialogModal from "./manage/components/DialogModal";
import TaskModal from "./manage/components/TaskModal";
import CheckinExport from "./manage/components/CheckinExport";
import TaskExport from "./manage/components/TaskExport";
import ApproveExport from "./manage/components/ApproveExport";
import ComplaintManagement from "./manage/components/ComplaintManagement";
import MicroApps from "../components/MicroApps";
import ResizeLine from "../components/ResizeLine.vue";
import UserSelect from "../components/UserSelect.vue";
import ImgUpload from "../components/ImgUpload.vue";
import Approve from "./manage/approve/index.vue";
import ApproveDetails from "./manage/approve/details.vue";
import notificationKoro from "notification-koro1";
import emitter from "../store/events";
import SearchBox from "../components/SearchBox.vue";
@ -514,14 +493,11 @@ import DepartmentOwnerView from "./manage/components/DepartmentOwnerView.vue";
export default {
components: {
Approve,
SearchBox,
ApproveDetails,
ImgUpload,
UserSelect,
TaskExport,
CheckinExport,
ApproveExport,
TaskModal,
DialogModal,
MobileTabbar,
@ -568,7 +544,6 @@ export default {
exportTaskShow: false,
exportCheckinShow: false,
exportApproveShow: false,
projectKeyValue: '',
projectKeyLoading: 0,
@ -600,10 +575,6 @@ export default {
complaintShow: false,
approveShow: false,
approveDetails: {id: 0},
approveDetailsShow: false,
taskBrowseLoading: false,
taskBrowseHistory: [],
@ -621,7 +592,6 @@ export default {
emitter.on('addTask', this.onAddTask);
emitter.on('createGroup', this.onCreateGroup);
emitter.on('dialogMsgPush', this.addDialogMsg);
emitter.on('approveDetails', this.openApproveDetails);
emitter.on('openReport', this.openReport);
emitter.on('openFavorite', this.openFavorite);
emitter.on('openRecent', this.openRecent);
@ -634,14 +604,12 @@ export default {
this.$store.dispatch("getUserInfo").catch(_ => {})
this.$store.dispatch("getTaskPriority", 1000)
this.$store.dispatch("getReportUnread", 1000)
this.$store.dispatch("getApproveUnread", 1000)
},
beforeDestroy() {
emitter.off('addTask', this.onAddTask);
emitter.off('createGroup', this.onCreateGroup);
emitter.off('dialogMsgPush', this.addDialogMsg);
emitter.off('approveDetails', this.openApproveDetails);
emitter.off('openReport', this.openReport);
emitter.off('openFavorite', this.openFavorite);
emitter.off('openRecent', this.openRecent);
@ -671,7 +639,6 @@ export default {
'clientDownloadUrl',
'reportUnreadNumber',
'approveUnreadNumber',
'dialogIns',
'formOptions',
@ -1112,9 +1079,6 @@ export default {
case 'exportCheckin':
this.exportCheckinShow = true;
return;
case 'exportApprove':
this.exportApproveShow = true;
return;
case 'workReport':
this.openReport(this.reportUnreadNumber > 0 ? 'receive' : 'my');
return;
@ -1132,11 +1096,6 @@ export default {
$A.reloadUrl()
});
return;
case 'approve':
if (this.menu.findIndex((m) => m.path == path) > -1) {
this.goForward({name: 'manage-approve'});
}
return;
case 'complaint':
this.complaintShow = true;
return;
@ -1201,10 +1160,7 @@ export default {
},
classNameRoute(path) {
let name = this.routeName
if (name == 'manage-approve') {
name = `manage-application`
}
const name = this.routeName
return {
"active": name === `manage-${path}`,
};
@ -1678,13 +1634,6 @@ export default {
}
},
openApproveDetails(id) {
this.approveDetailsShow = true;
this.$nextTick(() => {
this.approveDetails = {id};
})
},
openReport(tab) {
this.workReportTab = tab;
this.workReportShow = true;
@ -1706,9 +1655,6 @@ export default {
case 'overdue':
this.exportOverdueTask();
break;
case 'approve':
this.exportApproveShow = true;
break;
case 'checkin':
this.exportCheckinShow = true;
break;
@ -1776,9 +1722,6 @@ export default {
onTabbarClick(act, params = '') {
switch (act) {
case 'approve':
this.approveShow = true
break;
case 'createGroup':
this.onAddMenu('group')
break;

View File

@ -97,7 +97,6 @@
v-if="!sortingMode"
@click.stop="handleCardClick(card, 'badge')"
class="apply-box-top-report">
<Badge v-if="showBadge(card.system,'approve')" :overflow-count="999" :count="approveUnreadNumber"/>
<Badge v-if="showBadge(card.system,'report')" :overflow-count="999" :count="reportUnreadNumber"/>
</div>
</div>
@ -528,7 +527,6 @@ export default {
'userInfo',
'userIsAdmin',
'reportUnreadNumber',
'approveUnreadNumber',
'cacheDialogs',
'windowOrientation',
'windowPortrait',
@ -543,7 +541,6 @@ export default {
applyList() {
const list = [
//
{value: "approve", label: "审批中心", sort: 30, show: this.microAppsIds.includes('approve')},
{value: "favorite", label: "我的收藏", sort: 45},
{value: "recent", label: "最近打开", sort: 47},
{value: "report", label: "工作报告", sort: 50},
@ -985,7 +982,6 @@ export default {
const list = [
{label: this.$L('导出任务统计'), value: 'task'},
{label: this.$L('导出超期任务'), value: 'overdue'},
{label: this.$L('导出审批数据'), value: 'approve'},
{label: this.$L('导出签到数据'), value: 'checkin'},
];
this.$store.commit('menu/operation', {
@ -1037,9 +1033,6 @@ export default {
showBadge(item, type) {
let num = 0;
switch (type) {
case 'approve':
num = this.approveUnreadNumber;
break;
case 'report':
num = this.reportUnreadNumber;
break;

View File

@ -1,550 +0,0 @@
<template>
<div class="approve-details" :style="{'z-index':modalTransferIndex}">
<!-- 审批详情 -->
<div v-if="datas.id" class="approve-details-box" ref="approveDetailsBox">
<h2 class="approve-details-title">
<span>{{$L(datas.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="approve-details-subtitle">
<span @click="onAvatar(datas.start_user_id)">
<Avatar :src="datas.userimg" size="24"/>
</span>
<span>{{datas.start_user_name}}</span>
</h3>
<h3 class="approve-details-subtitle"><span>{{$L('提交于')}} {{datas.start_time}}</span></h3>
<Divider/>
<div class="approve-details-text" v-if="(datas.proc_def_name || '').indexOf('请假') !== -1 && datas.var?.type">
<h4>{{$L('假期类型')}}</h4>
<p>{{$L(datas.var?.type || '- -')}}</p>
</div>
<div class="approve-details-text">
<h4>{{$L('开始时间')}}</h4>
<div class="time-text">
<span>{{datas.var?.start_time || '- -'}}</span>
<span v-if="datas.var?.start_time">({{getWeekday(datas.var?.start_time)}})</span>
</div>
</div>
<div class="approve-details-text">
<h4>{{$L('结束时间')}}</h4>
<div class="time-text">
<span>{{datas.var?.end_time || '- -'}}</span>
<span v-if="datas.var?.end_time">({{getWeekday(datas.var?.end_time)}})</span>
</div>
</div>
<div class="approve-details-text">
<h4>{{ $L('时长') }}{{getTimeDifference(datas.var?.start_time,datas.var?.end_time)['unit']}}</h4>
<p>{{ datas.var?.start_time ? getTimeDifference(datas.var?.start_time,datas.var?.end_time)['time'] : '- -' }}</p>
</div>
<div class="approve-details-text">
<h4>{{$L('事由')}}</h4>
<p class="wrap-text">{{datas.var?.description || '- -'}}</p>
</div>
<div class="approve-details-text" v-if="datas.var?.other">
<h4>{{$L('图片')}}</h4>
<div class="img-body">
<div v-for="(src,key) in (datas.var.other).split(',')" @click="onViewPicture(src, 1)">
<ImgView :src="src" :key="key" class="img-view"/>
</div>
</div>
</div>
<Divider/>
<h3 class="approve-details-subtitle">{{$L('审批记录')}}</h3>
<Timeline class="approve-record-timeline">
<template v-for="(item,key) in datas.node_infos">
<!-- 提交 -->
<TimelineItem :key="key" v-if="item.type == 'starter'" color="green">
<p class="timeline-title">{{$L('提交')}}</p>
<div class="timeline-body">
<div class="approve-process-avatar" @click="onAvatar(data.start_user_id || datas.start_user_id)">
<Avatar :src="data.userimg || datas.userimg" size="38"/>
</div>
<div class="approve-process-left">
<p class="approve-process-name">{{data.start_user_name || datas.start_user_name}}</p>
<p class="approve-process-state">{{$L('已提交')}}</p>
</div>
<div class="approve-process-right">
<p v-if="parseInt(getTimeAgo(item.claim_time)) < showTimeNum">{{ getTimeAgo(item.claim_time) }}</p>
<p>{{item.claim_time?.substr(0,16)}}</p>
</div>
</div>
</TimelineItem>
<!-- 审批 -->
<TimelineItem
v-if="item.type == 'approver' && item._show"
:key="key"
:color="item.identitylink ? (item.identitylink?.state > 1 ? '#f03f3f' :'green') : '#ccc'">
<p class="timeline-title">{{$L('审批')}}</p>
<div class="timeline-body">
<div class="approve-process-avatar" @click="onAvatar(item.node_user_list && item.node_user_list[0]?.target_id || item.aprover_id)">
<Avatar :src="(item.node_user_list && item.node_user_list[0]?.userimg) || item.userimg" size="38"/>
</div>
<div class="approve-process-left">
<p class="approve-process-name">{{item.approver}}</p>
<p v-if="!item.identitylink" class="approve-process-state">
<span style="color:#6d6d6d;">{{$L('待审批')}}</span>
</p>
<p v-else class="approve-process-state">
<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="approve-process-right">
<p v-if="parseInt(getTimeAgo(item.claim_time)) < showTimeNum">
{{ 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>
<p class="comment" v-if="item.identitylink?.comment"><span>{{ item.identitylink?.is_system ? $L(item.identitylink?.comment) : item.identitylink?.comment }}</span></p>
</TimelineItem>
<!-- 抄送 -->
<TimelineItem :key="key" :color="item.is_finished ? 'green' : '#ccc'" v-if="item.type == 'notifier' && item._show">
<p class="timeline-title">{{$L('抄送')}}</p>
<div class="timeline-body">
<Avatar :src="$A.mainUrl('images/avatar/default_approval.png')" size="38"/>
<div class="approve-process-left">
<p class="approve-process-name">{{$L('系统')}}</p>
<p class="approve-process-desc">{{$L('自动抄送')}}
<span style="color: #486fed;">{{ item.node_user_list?.map(h=>h.name).join(',') }}</span>
{{$L('共'+item.node_user_list?.length+'人')}}
</p>
</div>
</div>
</TimelineItem>
<!-- 结束 -->
<TimelineItem class="finish" :key="key" :color="item.is_finished ? 'green' : '#ccc'" v-if="item.aprover_type == 'end'">
<p class="timeline-title">{{$L('结束')}}</p>
<div class="timeline-body">
<Avatar :src="$A.mainUrl('images/avatar/default_approval.png')" size="38"/>
<div class="approve-process-left">
<p class="approve-process-name">{{$L('系统')}}</p>
<p class="approve-process-desc"> {{ datas.is_finished ? $L('已结束') : $L('未结束') }}</p>
</div>
</div>
</TimelineItem>
</template>
</Timeline>
<template v-if="$A.arrayLength(datas.global_comments) > 0">
<Divider/>
<h3 class="approve-details-subtitle">{{$L('全文评论')}}</h3>
<div class="approve-record-comment">
<List :split="false" :border="false">
<ListItem v-for="(item, key) in datas.global_comments" :key="key">
<div>
<div class="top">
<span @click="onAvatar(item.user_id)">
<Avatar :src="item.userimg" size="38"/>
</span>
<div>
<p>{{ item.nickname }}</p>
<p class="time">{{ item.created_at }}</p>
</div>
<span>{{ getTimeAgo(item.created_at) }}</span>
</div>
<div class="content">
{{ getContent(item.content) }}
</div>
<div class="content" style="display: flex; gap: 10px;">
<div v-for="(src, k) in getPictures(item.content)" :key="k" @click="onViewPicture(src, 2)">
<ImgView :src="getPictureThumb(src)" :error-src="src" class="img-view"/>
</div>
</div>
</div>
</ListItem>
</List>
</div>
</template>
</div>
<!--审批操作-->
<div v-if="datas.id" class="approve-operation">
<Button type="primary" v-if="isShowAgreeBtn && !loadIng" @click="approve(1)">{{$L('同意')}}</Button>
<Button type="error" v-if="isShowAgreeBtn && !loadIng" @click="approve(2)">{{$L('拒绝')}}</Button>
<Button type="warning" v-if="isShowWarningBtn && !loadIng" @click="revocation">{{$L('撤销')}}</Button>
<Button type="error" v-if="isShowDeleteBtn && !loadIng" @click="remove">{{$L('删除')}}</Button>
<Button type="primary" @click="comment" :loading="loadIng > 0" ghost>+{{$L('添加评论')}}</Button>
</div>
<!--加载中-->
<div v-if="loadIng > 0" class="approve-load">
<Loading/>
</div>
<!--评论-->
<Modal v-model="commentShow" :title="$L('评论')" :mask-closable="false" class="page-approve-initiate">
<Form ref="initiateRef" :model="commentData" :rules="commentRule" v-bind="formOptions" @submit.native.prevent>
<FormItem prop="content" :label="$L('内容')">
<Input type="textarea" v-model="commentData.content"></Input>
</FormItem>
<FormItem prop="pictures" :label="$L('图片')">
<ImgUpload v-model="commentData.pictures" :num="3" :width="2048" :height="2048" whcut="percentage"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="commentShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="commentLoad > 0" @click="confirmComment">{{$L('确认')}}</Button>
</div>
</Modal>
</div>
</template>
<script>
import ImgView from "../../../components/ImgView";
import ImgUpload from "../../../components/ImgUpload";
import emitter from "../../../store/events";
import {mapState} from "vuex";
export default {
name: "ApproveDetails",
components: {ImgView, ImgUpload},
props: {
data: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
datas: {},
loadIng: 0,
showTimeNum: 24,
modalTransferIndex: window.modalTransferIndex,
commentLoad: 0,
commentShow: false,
commentData: {
content: "",
pictures: []
},
commentRule: {
content: {type: 'string', required: true, message: this.$L('请输入内容!'), trigger: 'change'},
}
}
},
watch: {
'$route'(to, from) {
if (to.name == 'manage-approve-details') {
this.init()
}
},
data: {
handler(newValue, oldValue) {
if (newValue.id) {
this.getInfo()
}
},
deep: true
},
},
computed: {
...mapState(['formOptions', 'userIsAdmin']),
isShowAgreeBtn() {
return (this.datas.candidate || '').split(',').indexOf(this.userId + '') != -1 && !this.datas.is_finished
},
isShowWarningBtn() {
let is = (this.userId == this.datas.start_user_id) && this.datas?.is_finished != true;
(this.datas.node_infos || []).map(h => {
if (h.type != 'starter' && h.is_finished == true && h.identitylink?.userid != this.userId) {
is = false;
}
})
return is;
},
// 2/3/4
isShowDeleteBtn() {
return (this.userId == this.datas.start_user_id || this.userIsAdmin)
&& [2, 3, 4].includes(Number(this.datas.state));
},
},
mounted() {
this.init()
},
methods: {
init() {
this.modalTransferIndex = ++window.modalTransferIndex
if (this.$route.query.id) {
this.getInfo()
}
},
//
getTimeAgo(time, type) {
const timeDiff = $A.dayjs().unix() - $A.dayjs(time).unix(); // 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 if (timeDiff < 3600 * 24) {
const hours = Math.floor(timeDiff / 3600);
return type == 2 ? `${hours}${this.$L('小时')}` : `${hours} ${this.$L('小时前')}`;
} else if (timeDiff < 3600 * 24 * 30) {
const days = Math.floor(timeDiff / 3600 / 24);
return type == 2 ? `${days + 1}${this.$L('天')}` : `${days + 1} ${this.$L('天前')}`;
} else {
const days = Math.floor(timeDiff / 3600 / 720);
return type == 2 ? `${days + 1}${this.$L('月')}` : `${days + 1} ${this.$L('月前')}`;
}
},
//
getWeekday(dateString) {
return this.$L(['周日', '周一', '周二', '周三', '周四', '周五', '周六'][$A.dayjs(dateString).day()]);
},
//
getTimeDifference(startTime, endTime) {
const currentTime = $A.dayjs(endTime);
const endTimes = $A.dayjs(startTime);
const timeDiff = currentTime.unix() - endTimes.unix(); // convert to seconds
if (timeDiff < 60) {
return {time: timeDiff, unit: this.$L('秒')};
} else if (timeDiff < 3600) {
const minutes = Math.floor(timeDiff / 60);
return {time: minutes, unit: this.$L('分钟')};
} else if (timeDiff < 3600 * 24) {
const hours = Math.floor(timeDiff / 60 / 60 * 10) / 10;
return {time: hours, unit: this.$L('小时')};
} else {
const days = Math.floor(timeDiff / 60 / 60 / 24 * 10) / 10;
return {time: days + 1, unit: this.$L('天')};
}
},
//
getInfo(isScrollToBottom = false) {
this.loadIng++;
this.$store.dispatch("call", {
method: 'get',
url: 'approve/process/detail',
data: {
id: this.$route.query.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
isScrollToBottom && this.scrollToBottom();
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
//
approve(type) {
$A.modalInput({
title: `审批`,
placeholder: `请输入审批意见`,
type: "textarea",
okText: type == 1 ? "同意" : "拒绝",
okType: type == 1 ? "primary" : "error",
onOk: (desc) => {
if (type != 1 && !desc) {
return `请输入审批意见`
}
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
url: 'approve/task/complete',
data: {
task_id: this.datas.task_id,
pass: type == 1,
comment: desc,
}
}).then(({msg}) => {
$A.messageSuccess(msg);
if (this.routeName == 'manage-approve-details' || this.routeName == 'manage-messenger') {
this.getInfo()
} else {
this.$emit('approve')
}
resolve()
}).catch(({msg}) => {
reject(msg)
});
})
}
});
},
//
revocation() {
$A.modalConfirm({
content: "你确定要撤销吗?",
loading: true,
okType: "warning",
onOk: () => {
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
url: 'approve/task/withdraw',
data: {
task_id: this.datas.task_id,
proc_inst_id: this.datas.id,
}
}).then(({msg}) => {
$A.messageSuccess(msg);
resolve();
if (this.routeName == 'manage-approve-details' || this.routeName == 'manage-messenger') {
this.getInfo()
} else {
this.$emit('revocation')
}
}).catch(({msg}) => {
reject(msg);
});
})
},
});
},
//
remove() {
$A.modalConfirm({
content: "删除后不可恢复,确定要删除该审批吗?",
loading: true,
okType: "error",
onOk: () => {
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
url: 'approve/process/delById',
method: 'post',
data: {
proc_inst_id: this.datas.id,
}
}).then(({msg}) => {
$A.messageSuccess(msg);
resolve();
//
if (this.routeName == 'manage-approve-details' || this.routeName == 'manage-messenger') {
this.$router.back()
} else {
this.$emit('revocation')
}
}).catch(({msg}) => {
reject(msg);
});
})
},
});
},
//
comment() {
this.commentData.content = ""
this.commentData.pictures = []
this.commentShow = true;
},
//
confirmComment() {
this.commentLoad++;
this.$refs["initiateRef"].validate((valid) => {
if (valid) {
this.$store.dispatch("call", {
method: 'post',
url: 'approve/process/addGlobalComment',
data: {
proc_inst_id: this.$route.query.id || this.data.id,
content: JSON.stringify({
'content': this.commentData.content,
'pictures': this.commentData.pictures.map(h => {
return h.path;
})
})
}
}).then(({msg}) => {
$A.messageSuccess("添加成功");
this.getInfo(true)
this.commentShow = false;
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.commentLoad--;
});
} else {
this.commentLoad--;
}
})
},
//
scrollToBottom() {
this.$nextTick(() => {
this.$refs.approveDetailsBox?.scrollTo({
top: container.scrollHeight + 1000,
behavior: 'smooth'
});
})
},
//
getContent(content) {
try {
return JSON.parse(content).content || ''
} catch (error) {
return ''
}
},
//
getPictures(content) {
try {
return JSON.parse(content).pictures || []
} catch (error) {
return ''
}
},
//
getPictureThumb(src) {
if (!/\.(png|jpg|jpeg)$/.test(src)) {
return src
}
return $A.thumbRestore(src) + '_thumb.' + src.split('.').pop()
},
//
onViewPicture(currentUrl, type) {
const datas = [];
if (type == 1) {
datas.push(...this.datas.var.other.split(','))
}
if (type == 2) {
this.datas.global_comments.map(h => {
datas.push(...this.getPictures(h.content))
})
}
const list = datas.map(src => {
return {
src: $A.mainUrl(src)
}
});
this.$store.dispatch("previewImage", {index: $A.mainUrl(currentUrl), list})
},
//
onAvatar(userid) {
if (!/^\d+$/.test(userid)) {
return
}
emitter.emit('openUser', userid);
}
}
}
</script>

View File

@ -1,739 +0,0 @@
<template>
<div class="page-approve">
<PageTitle :title="$L('审批中心')"/>
<div class="approve-wrapper" ref="fileWrapper">
<div class="approve-head">
<div class="approve-nav">
<h1>{{$L('审批中心')}}</h1>
</div>
<Button v-show="showType == 1 && isShowIcon" @click="addApply" :loading="addLoadIng" type="primary" shape="circle" icon="md-add" class="ivu-btn-icon-only"></Button>
<Button v-if="showType == 1 && !isShowIcon" :loading="addLoadIng" type="primary" @click="addApply">
<span> {{$L("添加申请")}} </span>
</Button>
<Button v-show="showType == 1 && userIsAdmin && !isShowIcon" @click="exportApproveShow = true">
<span> {{$L("导出审批数据")}} </span>
</Button>
<Button v-if="showType == 1 && userIsAdmin && isShowIcon" @click="exportApproveShow = true" shape="circle" class="ivu-btn-icon-only">
<i class="taskfont">&#xe7a8;</i>
</Button>
<Button v-if="userIsAdmin && !isShowIcon" @click="showType = showType == 1 ? 2 : 1">
<span> {{ showType == 1 ? $L("流程设置") : $L("返回") }} </span>
</Button>
<Button v-if="userIsAdmin && isShowIcon" @click="showType = showType == 1 ? 2 : 1" shape="circle" class="ivu-btn-icon-only">
<i v-if="showType == 1" class="taskfont">&#xe67b;</i>
<i v-if="showType == 2" class="taskfont">&#xe637;</i>
</Button>
</div>
<Tabs class="page-approve-tabs" v-show="showType==1" :value="tabsValue" @on-click="tabsClick" size="small">
<TabPane :label="$L('待办') + (unreadTotal > 0 ? ('('+unreadTotal+')') : '')" name="unread" style="height: 100%;">
<div class="approve-main-search">
<div>
<Select v-model="approvalType" @on-change="tabsClick(false,0)">
<Option v-for="item in approvalList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
<Input v-model="approvalName" :placeholder="$L('请输入用户名')" ></Input>
<Button v-show="!isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)">{{ $L('搜索') }}</Button>
<Button v-show="isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)" />
</div>
</div>
<div v-if="loadIng && unreadList.length==0" class="approve-load">
<Loading/>
</div>
<div v-else-if="unreadList.length==0" class="noData">{{ $L('暂无数据')}}</div>
<div v-else class="approve-mains">
<div class="approve-main-left">
<div class="approve-main-list" @scroll="handleScroll">
<div @click.stop="clickList(item,key)" v-for="(item,key) in unreadList">
<list :class="{ 'approve-list-active': item._active }" :data="item"></list>
</div>
<div class="load" v-if="unreadList.length < unreadTotal">
<Loading/>
</div>
</div>
</div>
<div class="approve-main-right">
<ApproveDetails v-if="!detailsShow && tabsValue=='unread'" :data="details" @approve="tabsClick" @revocation="tabsClick"></ApproveDetails>
</div>
</div>
</TabPane>
<TabPane :label="$L('已办')" name="done">
<div class="approve-main-search">
<div>
<Select v-model="approvalType" @on-change="tabsClick(false,0)">
<Option v-for="item in approvalList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
<Input v-model="approvalName" :placeholder="$L('请输入用户名')"></Input>
<Button v-show="!isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)">{{ $L('搜索') }}</Button>
<Button v-show="isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)"/>
</div>
</div>
<div v-if="loadIng && doneList.length==0" class="approve-load">
<Loading/>
</div>
<div v-else-if="doneList.length==0" class="noData">{{$L('暂无数据')}}</div>
<div v-else class="approve-mains">
<div class="approve-main-left">
<div class="approve-main-list" @scroll="handleScroll">
<div @click.stop="clickList(item,key)" v-for="(item,key) in doneList" >
<list :class="{ 'approve-list-active': item._active }" :data="item"></list>
</div>
<div class="load" v-if="doneList.length < doneTotal">
<Loading/>
</div>
</div>
</div>
<div class="approve-main-right">
<ApproveDetails v-if="!detailsShow && tabsValue=='done'" :data="details" @approve="tabsClick" @revocation="tabsClick"></ApproveDetails>
</div>
</div>
</TabPane>
<TabPane :label="$L('抄送我')" name="notify">
<div class="approve-main-search">
<div class="approve-main-search">
<div>
<Select v-model="approvalType" @on-change="tabsClick(false,0)">
<Option v-for="item in approvalList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
<Input v-model="approvalName" :placeholder="$L('请输入用户名')"></Input>
<Button v-show="!isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)">{{ $L('搜索') }}</Button>
<Button v-show="isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)"/>
</div>
</div>
</div>
<div v-if="loadIng && notifyList.length==0" class="approve-load">
<Loading/>
</div>
<div v-else-if="notifyList.length==0" class="noData">{{$L('暂无数据')}}</div>
<div v-else class="approve-mains">
<div class="approve-main-left">
<div class="approve-main-list" @scroll="handleScroll">
<div @click.stop="clickList(item,key)" v-for="(item,key) in notifyList">
<list :class="{ 'approve-list-active': item._active }" :data="item"></list>
</div>
<div class="load" v-if="notifyList.length < notifyTotal">
<Loading/>
</div>
</div>
</div>
<div class="approve-main-right">
<ApproveDetails v-if="!detailsShow && tabsValue=='notify'" :data="details" @approve="tabsClick" @revocation="tabsClick"></ApproveDetails>
</div>
</div>
</TabPane>
<TabPane :label="$L('已发起')" name="initiated">
<div class="approve-main-search">
<div>
<Select v-model="approvalType" @on-change="tabsClick(false,0)">
<Option v-for="item in approvalList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
<Select v-model="searchState" @on-change="tabsClick(false,0)">
<Option v-for="item in searchStateList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
<Input v-model="approvalName" :placeholder="$L('请输入用户名')"></Input>
<Button v-show="!isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)">{{ $L('搜索') }}</Button>
<Button v-show="isShowIcon" type="primary" :loading="loadIng" icon="ios-search" @click="tabsClick(false,0)"/>
</div>
</div>
<div v-if="loadIng && initiatedList.length==0" class="approve-load">
<Loading/>
</div>
<div v-else-if="initiatedList.length==0" class="noData">{{$L('暂无数据')}}</div>
<div v-else class="approve-mains">
<div class="approve-main-left">
<div class="approve-main-list" @scroll="handleScroll">
<div @click.stop="clickList(item,key)" v-for="(item,key) in initiatedList">
<list :class="{ 'approve-list-active': item._active }" :data="item"></list>
</div>
<div class="load" v-if="initiatedList.length < initiatedTotal">
<Loading/>
</div>
</div>
</div>
<div class="approve-main-right">
<ApproveDetails v-if="!detailsShow && tabsValue=='initiated'" :data="details" @approve="tabsClick" @revocation="tabsClick"></ApproveDetails>
</div>
</div>
</TabPane>
</Tabs>
<ApproveSetting v-show="showType!=1"/>
</div>
<!--详情-->
<DrawerOverlay v-model="detailsShow" placement="right" :size="600">
<ApproveDetails v-if="detailsShow" :data="details" @approve="tabsClick" @revocation="tabsClick"></ApproveDetails>
</DrawerOverlay>
<!--发起-->
<Modal v-model="addShow" :title="$L(addTitle)" :mask-closable="false" class="page-approve-initiate">
<Form ref="initiateRef" :model="addData" :rules="addRule" v-bind="formOptions" @submit.native.prevent>
<FormItem v-if="departmentList.length>1" prop="department_id" :label="$L('选择部门')">
<Select v-model="addData.department_id" :placeholder="$L('请选择部门')">
<Option v-for="(item, index) in departmentList" :value="item.id" :key="index">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem prop="applyType" :label="$L('申请类型')">
<Select v-model="addData.applyType" :placeholder="$L('请选择申请类型')">
<Option v-for="(item, index) in procdefList" :value="item.name" :key="index">{{ $L(item.name) }}</Option>
</Select>
</FormItem>
<FormItem v-if="$A.strExists(addData.applyType, '请假')" prop="type" :label="$L('假期类型')">
<Select v-model="addData.type" :placeholder="$L('请选择假期类型')">
<Option v-for="(item, index) in selectTypes" :value="item" :key="index">{{ $L(item) }}</Option>
</Select>
</FormItem>
<FormItem prop="startTime" :label="$L('开始时间')">
<div style="display: flex;gap: 3px;">
<DatePicker type="date" format="yyyy-MM-dd"
v-model="addData.startTime"
:editable="false"
@on-change="(e)=>{ addData.startTime = e }"
:placeholder="$L('请选择开始时间')"
style="flex: 1;min-width: 122px;"
></DatePicker>
<Select v-model="addData.startTimeHour" style="max-width: 100px;">
<Option v-for="(item,index) in 24" :value="item-1 < 10 ? '0'+(item-1) : item-1 " :key="index">{{item-1 < 10 ? '0' : ''}}{{item-1}}</Option>
</Select>
<Select v-model="addData.startTimeMinute" style="max-width: 100px;">
<Option value="00">00</Option>
<Option value="30">30</Option>
</Select>
</div>
</FormItem>
<FormItem prop="endTime" :label="$L('结束时间')">
<div style="display: flex;gap: 3px;">
<DatePicker type="date" format="yyyy-MM-dd"
v-model="addData.endTime"
:editable="false"
@on-change="(e)=>{ addData.endTime = e }"
:placeholder="$L('请选择结束时间')"
style="flex: 1;min-width: 122px;"
></DatePicker>
<Select v-model="addData.endTimeHour" style="max-width: 100px;">
<Option v-for="(item,index) in 24" :value="item-1 < 10 ? '0'+(item-1) : ((item-1)+'') " :key="index">{{item-1 < 10 ? '0' : ''}}{{item-1}}</Option>
</Select>
<Select v-model="addData.endTimeMinute" style="max-width: 100px;">
<Option value="00">00</Option>
<Option value="30">30</Option>
</Select>
</div>
</FormItem>
<FormItem prop="description" :label="$L('事由')">
<Input type="textarea" v-model="addData.description"></Input>
</FormItem>
<FormItem prop="other" :label="$L('图片')">
<ImgUpload v-model="addData.other" :num="3" :width="2048" :height="2048" whcut="percentage"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="addShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="loadIng > 0" @click="onInitiate">{{$L('确认')}}</Button>
</div>
</Modal>
<!--导出审批数据-->
<ApproveExport v-model="exportApproveShow"/>
</div>
</template>
<script>
import list from "./list.vue";
import ApproveDetails from "./details.vue";
import DrawerOverlay from "../../../components/DrawerOverlay";
import ImgUpload from "../../../components/ImgUpload";
import ApproveSetting from "./setting";
import ApproveExport from "../components/ApproveExport";
import {mapState} from 'vuex'
import emitter from "../../../store/events";
export default {
components: {list, ApproveDetails, DrawerOverlay, ImgUpload, ApproveSetting, ApproveExport},
name: "approve",
data() {
return {
showType: 1,
exportApproveShow: false,
isShowIcon: false,
modalTransferIndex: window.modalTransferIndex,
procdefList: [],
page: 1,
pageSize: 10,
total: 0,
noText: '',
loadIng: false,
addLoadIng: false,
tabsValue: "",
//
approvalType: "all",
approvalName: "",
approvalList: [
{value: "all", label: this.$L("全部审批")},
],
searchState: "all",
searchStateList: [
{value: "all", label: this.$L("全部状态")},
{value: 1, label: this.$L("审批中")},
{value: 2, label: this.$L("已通过")},
{value: 3, label: this.$L("已拒绝")},
{value: 4, label: this.$L("已撤回")}
],
//
unreadList: [],
unreadPage: 1,
unreadTotal: 0,
unreadLoad: false,
//
doneList: [],
donePage: 1,
doneLoad: false,
doneTotal: 0,
//
notifyList: [],
notifyPage: 1,
notifyLoad: false,
notifyTotal: 0,
//
initiatedList: [],
initiatedPage: 1,
initiatedLoad: false,
initiatedTotal: 0,
//
details: {},
detailsShow: false,
//
addTitle: '',
addShow: false,
startTimeOpen: false,
endTimeOpen: false,
addData: {
department_id: 0,
applyType: '',
type: '',
startTime: "2023-04-20",
startTimeHour: "09",
startTimeMinute: "00",
endTime: "2023-04-20",
endTimeHour: "18",
endTimeMinute: "00",
other: ""
},
addRule: {
department_id: {type: 'number', required: true, message: this.$L('请选择部门!'), trigger: 'change'},
applyType: {type: 'string', required: true, message: this.$L('请选择申请类型!'), trigger: 'change'},
type: {type: 'string', required: true, message: this.$L('请选择假期类型!'), trigger: 'change'},
startTime: {type: 'string', required: true, message: this.$L('请选择开始时间!'), trigger: 'change'},
endTime: {type: 'string', required: true, message: this.$L('请选择结束时间!'), trigger: 'change'},
description: {type: 'string', required: true, message: this.$L('请输入事由!'), trigger: 'change'},
},
selectTypes: ["年假", "事假", "病假", "调休", "产假", "陪产假", "婚假", "丧假", "哺乳假", "产检假", "其他"],
//
showDateTime: false
}
},
computed: {
...mapState(['userInfo', 'userIsAdmin', 'windowWidth', 'formOptions']),
departmentList() {
let departmentNames = (this.userInfo.department_name || '').split(',');
return (this.userInfo.department || []).map((h, index) => {
return {
id: h,
name: departmentNames[index]
};
})
}
},
watch: {
'$route'(to) {
if (to.name == 'manage-approve') {
this.init()
}
},
addShow(val) {
if (!val) {
this.addData.other = ""
}
},
showType(val) {
if (val == 1) {
this.init()
}
},
windowWidth(val) {
this.isShowIcon = val < 515
}
},
activated() {
this.showType = 1
},
mounted() {
this.tabsValue = "unread"
this.init()
emitter.on('websocketMsg', this.onWebsocketMsg)
},
beforeDestroy() {
emitter.off('websocketMsg', this.onWebsocketMsg)
},
methods: {
init() {
this.tabsClick()
this.getProcdefList()
if (this.tabsValue != 'unread') {
this.getUnreadList();
}
this.addData.department_id = this.userInfo.department[0] || 0;
this.addData.startTime = this.addData.endTime = $A.daytz().format('YYYY-MM-DD');
this.isShowIcon = this.windowWidth < 515
},
// websocket
onWebsocketMsg(info) {
const {type, action, mode, data} = info;
switch (type) {
case 'approve':
if (action == 'unread') {
this.tabsClick();
}
break;
case 'dialog':
if (mode == 'add' && data?.msg?.text?.indexOf('open-approve-details') != -1) {
this.tabsClick();
}
break;
}
},
//
getProcdefList() {
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
url: 'approve/procdef/all',
method: 'post',
}).then(({data}) => {
this.procdefList = data.rows || [];
this.approvalList = this.procdefList.map(h => {
return {value: h.name, label: this.$L(h.name)}
})
this.approvalList.unshift({value: "all", label: this.$L("全部审批")})
resolve()
}).catch(({msg}) => {
$A.modalError(msg);
reject()
});
});
},
// tab
tabsClick(val, time = 1000) {
if (!val && this.__tabsClick && time > 0) {
return;
}
this.__tabsClick = setTimeout(() => {
this.__tabsClick = null;
}, time)
this.tabsValue = val || this.tabsValue
if (val) {
this.approvalType = this.searchState = "all"
this.approvalName = ""
}
//
this.detailsShow = false;
this.loadIng = true;
//
if (this.tabsValue == 'unread') {
if (val === false) {
this.unreadPage = 1;
this.unreadList = [];
}
this.getUnreadList();
}
if (this.tabsValue == 'done') {
if (val === false) {
this.donePage = 1;
this.doneList = [];
}
this.getDoneList();
}
if (this.tabsValue == 'notify') {
if (val === false) {
this.notifyPage = 1;
this.notifyList = [];
}
this.getNotifyList();
}
if (this.tabsValue == 'initiated') {
if (val === false) {
this.initiatedPage = 1;
this.initiatedList = [];
}
this.getInitiatedList();
}
},
//
clickList(item) {
this.unreadList.map(h => {
h._active = false;
})
this.doneList.map(h => {
h._active = false;
})
this.notifyList.map(h => {
h._active = false;
})
this.initiatedList.map(h => {
h._active = false;
})
//
if (window.innerWidth < 426) {
emitter.emit('approveDetails', item.id);
return;
}
if (window.innerWidth < 1010) {
this.detailsShow = true;
} else {
item._active = true;
}
this.details = {}
this.$nextTick(() => {
this.details = item
})
},
//
handleScroll(e) {
if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight) {
if (this.tabsValue == 'unread' && !this.unreadLoad && this.unreadList.length < this.unreadTotal) {
this.unreadLoad = true;
this.unreadPage = this.unreadPage + 1;
this.getUnreadList('scroll');
}
if (this.tabsValue == 'done' && !this.doneLoad && this.doneList.length < this.doneTotal) {
this.doneLoad = true;
this.donePage = this.donePage + 1;
this.getDoneList('scroll');
}
if (this.tabsValue == 'notify' && !this.notifyLoad && this.notifyList.length < this.notifyTotal) {
this.notifyLoad = true;
this.notifyPage = this.notifyPage + 1;
this.getNotifyList('scroll');
}
if (this.tabsValue == 'initiated' && !this.initiatedLoad && this.initiatedList.length < this.initiatedTotal) {
this.initiatedLoad = true;
this.initiatedPage = this.initiatedPage + 1;
this.getInitiatedList('scroll');
}
}
},
//
getUnreadList(type = 'init') {
this.$store.dispatch("call", {
method: 'get',
url: 'approve/process/findTask',
data: {
page: type == 'scroll' ? this.unreadPage : 1,
page_size: type == 'scroll' ? this.pageSize : this.unreadPage * this.pageSize,
proc_def_name: this.approvalType == 'all' ? '' : this.approvalType,
username: this.approvalName,
}
}).then(({data}) => {
this.updateData('unread', data, type)
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng = false;
this.unreadLoad = false;
});
},
//
getDoneList(type = 'init') {
this.$store.dispatch("call", {
method: 'get',
url: 'approve/procHistory/findTask',
data: {
page: type == 'scroll' ? this.donePage : 1,
page_size: type == 'scroll' ? this.pageSize : this.donePage * this.pageSize,
proc_def_name: this.approvalType == 'all' ? '' : this.approvalType,
username: this.approvalName,
}
}).then(({data}) => {
this.updateData('done', data, type)
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng = false;
this.doneLoad = false;
});
},
//
getNotifyList(type) {
this.$store.dispatch("call", {
method: 'get',
url: 'approve/procHistory/findProcNotify',
data: {
page: type == 'scroll' ? this.notifyPage : 1,
page_size: type == 'scroll' ? this.pageSize : this.notifyPage * this.pageSize,
proc_def_name: this.approvalType == 'all' ? '' : this.approvalType,
username: this.approvalName,
}
}).then(({data}) => {
this.updateData('notify', data, type)
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng = false;
this.notifyLoad = false;
});
},
//
getInitiatedList(type) {
this.$store.dispatch("call", {
method: 'post',
url: 'approve/process/startByMyselfAll',
data: {
page: type == 'scroll' ? this.initiatedPage : 1,
page_size: type == 'scroll' ? this.pageSize : this.initiatedPage * this.pageSize,
proc_def_name: this.approvalType == 'all' ? '' : this.approvalType,
state: this.searchState == 'all' ? '' : this.searchState,
username: this.approvalName,
}
}).then(({data}) => {
this.updateData('initiated', data, type)
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng = false;
this.initiatedLoad = false;
});
},
//
addApply() {
this.addLoadIng = true;
this.$store.dispatch("call", {
url: 'users/basic',
data: {
userid: [this.userInfo.userid]
},
checkAuth: false
}).then(({data}) => {
this.addData.department_id = data[0]?.department[0] || 0;
this.getProcdefList().then(_ => {
this.addTitle = this.$L("添加申请");
this.addShow = true;
this.addLoadIng = false;
}).catch(_ => {
this.addLoadIng = false;
});
}).catch(({msg}) => {
this.addLoadIng = false;
$A.modalError(msg);
});
},
//
updateData(key, data, type) {
let listKey = key + 'List'
this[key + 'Total'] = data.total;
type != 'scroll' ? (this[listKey] = data.rows) : data.rows.map(h => {
if (this[listKey].map(item => {
return item.id
}).indexOf(h.id) == -1) {
this[listKey].push(h)
}
});
if (window.innerWidth > 1010) {
let activeIndex = this[listKey].map((h, index) => h._active ? index : -1).filter(h => h > -1)[0] || 0
if (this[listKey].length > 0) {
this[listKey][activeIndex]._active = true;
if (this.tabsValue == key) {
this.$nextTick(() => {
this.details = this[listKey][activeIndex] || {}
})
}
}
}
},
//
onInitiate() {
this.$refs.initiateRef.validate((valid) => {
if (valid) {
this.loadIng = true;
var obj = JSON.parse(JSON.stringify(this.addData))
obj.startTime = obj.startTime + " " + obj.startTimeHour + ":" + obj.startTimeMinute;
obj.endTime = obj.endTime + " " + obj.endTimeHour + ":" + obj.endTimeMinute;
if (this.addData.other) {
obj.other = this.addData.other.map((o) => {
return o.path
}).join(',')
}
this.$store.dispatch("call", {
url: 'approve/process/start',
data: {
proc_name: obj.applyType,
department_id: obj.department_id,
var: JSON.stringify(obj),
},
method: 'post',
}).then(({data, msg}) => {
$A.messageSuccess(msg);
this.addShow = false;
this.$refs.initiateRef.resetFields();
this.tabsValue = 'initiated';
this.initiatedList.map(h => {
h._active = false;
})
this.$nextTick(() => {
this.tabsClick(false, 0);
})
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng = false;
});
}
});
}
}
}
</script>
<style lang="scss">
.page-approve .approve-details {
border-radius: 8px;
}
.page-approve .ivu-tabs-nav {
display: flex;
width: 350px;
@media (width <= 1010px) {
width: 100%;
}
.ivu-tabs-tab {
font-size: 15px;
flex: 1;
text-align: center;
}
}
.page-approve-initiate .ivu-modal-body {
padding: 16px 22px 2px !important;
}
</style>

View File

@ -1,46 +0,0 @@
<template>
<div class="approve-list">
<h2>
<span class="list-name">{{$L(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 v-if="$A.strExists(data.proc_def_name, '请假') && data.var?.type">{{$L('假期类型')}}<span>{{$L(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="data.userimg" size="20"/>
{{ data.start_user_name }}
</span>
<span>
{{$L('发起时间')}}{{data.start_time}}
</span>
</div>
</div>
</template>
<script>
export default {
name: "list",
props: {
data: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
}
}
}
</script>

View File

@ -1,176 +0,0 @@
<template>
<div class="page-approve-setting">
<Row class="approve-row" :gutter="16">
<Col :xxl="{ span: 6 }" :xl="{ span: 8 }" :lg="{ span: 12 }" :sm="{ span: 12 }" :xs="{ span: 24 }" >
<div class="approve-col-box approve-col-add" @click="add">
<Icon type="md-add" />
</div>
</Col>
<Col v-for="(item, key) in list" :xxl="{ span: 6 }" :xl="{ span: 8 }" :lg="{ span: 12 }" :sm="{ span: 12 }" :xs="{ span: 24 }" :key="key">
<div class="approve-col-box approve-col-for" @click="edit(item)">
<p>{{$L('流程名称')}}<span class="approve-name">{{$L(item.name)}}</span></p>
<Divider class="divider"/>
<div class="approve-button-box" @click.stop="edit(item)">
<p>{{$L('已发布')}}</p>
<p class="icon-warp" @click.stop="change(item)" >
<Icon type="md-trash" size="16" class="delcon"/>
</p>
</div>
</div>
</Col>
</Row>
<!--设置流程-->
<DrawerOverlay v-model="approvalSettingShow" placement="right" :size="1200">
<iframe :src="iframeSrc"></iframe>
</DrawerOverlay>
</div>
</template>
<script>
import DrawerOverlay from "../../../components/DrawerOverlay";
import store from '../../../store/state'
import {languageName} from "../../../language";
export default {
name: "ApproveSetting",
components: {DrawerOverlay},
data(){
return{
value:false,
loadIng:0,
approvalSettingShow: false,
iframeSrc:"",
name:"",
list:[]
}
},
watch: {
approvalSettingShow(val) {
if (val) {
this.iframeSrc = $A.mainUrl(`approve/#/?name=${this.name}&token=${store.userToken}&lang=${languageName}`)
}
}
},
mounted() {
window.addEventListener('message', this.saveSuccess)
this.getList();
},
beforeDestroy() {
window.removeEventListener("message", this.saveSuccess);
},
methods: {
//
getList(){
this.$store.dispatch("call", {
url: 'approve/procdef/all',
method: 'post',
}).then(({data}) => {
this.list = data.rows;
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;
this.$set(this.list,index,h)
}
});
this.approvalSettingShow = false;
$A.messageSuccess('发布成功');
}
}
},
//
add(){
$A.modalInput({
title: `添加流程`,
placeholder: `请输入流程名称`,
okText: "确定",
onOk: (desc) => {
if (!desc) {
return `请输入流程名称`
}
this.name = desc
this.approvalSettingShow = true;
return false
}
});
},
//
edit(item){
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: 'approve/procdef/del',
data: {id: item.id},
method: 'post',
}).then(({data}) => {
item.issue = false;
this.getList();
$A.messageSuccess('成功');
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
}
}
</script>
<style scoped>
iframe{
width: 100%;
height: 100%;
padding: 0;
margin: 0;
border: 0;
float: left;
}
.delcon{
position: absolute;
right: 0;
padding: 5px !important;
}
.delcon:hover{
color: #ed4014 !important;
}
</style>

View File

@ -1,124 +0,0 @@
<template>
<Modal
v-model="show"
:title="$L('导出审批数据')"
:mask-closable="false">
<Form ref="exportTask" :model="formData" v-bind="formOptions" @submit.native.prevent>
<FormItem :label="$L('审批类型')">
<Select v-model="formData.proc_def_name" @on-open-change="getProcName" :placeholder="$L('请选择类型')">
<Option v-for="(item, key) in procList" :value="item.name" :key="key" >{{ $L(item.name) }}</Option>
</Select>
</FormItem>
<FormItem :label="$L('时间范围')">
<DatePicker
v-model="formData.date"
type="daterange"
format="yyyy/MM/dd"
style="width:100%"
:placeholder="$L('请选择时间')"/>
<div class="form-tip form-quick-select">
<span>{{$L('快捷选择')}}:</span>
<em @click="formData.date=dateShortcuts('prev')">{{$L('上个月')}}</em>
<em @click="formData.date=dateShortcuts('this')">{{$L('这个月')}}</em>
</div>
</FormItem>
<FormItem prop="type" :label="$L('导出类型')">
<RadioGroup v-model="formData.is_finished">
<Radio label="0">{{$L('未完成')}}</Radio>
<Radio label="1">{{$L('已完成')}}</Radio>
</RadioGroup>
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="show=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="loadIng > 0" @click="onExport">{{$L('导出')}}</Button>
</div>
</Modal>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "ApproveExport",
props: {
value: {
type: Boolean,
default: false
},
},
data() {
return {
show: this.value,
loadIng: 0,
formData: {
proc_def_name: '',
date: [],
is_finished:'1',
},
procList:[],
}
},
watch: {
value(v) {
this.show = v;
},
show(v) {
this.value !== v && this.$emit("input", v)
}
},
computed: {
...mapState(['formOptions']),
},
methods: {
dateShortcuts(act) {
if (act === 'prev') {
return [
$A.daytz().subtract(1, 'month').startOf('month').format('YYYY-MM-DD'),
$A.daytz().subtract(1, 'month').endOf('month').format('YYYY-MM-DD'),
];
} else if (act === 'this') {
return [
$A.daytz().startOf('month').format('YYYY-MM-DD'),
$A.daytz().endOf('month').format('YYYY-MM-DD'),
]
}
},
getProcName(){
this.loadIng++;
this.$store.dispatch("call", {
url: 'approve/procdef/all',
method: 'post'
}).then(({data}) => {
this.procList = data['rows'];
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
onExport() {
if (this.loadIng > 0) {
return;
}
this.loadIng++;
this.$store.dispatch("call", {
url: 'approve/export',
data: this.formData,
}).then(() => {
this.show = false;
$A.modalSuccess('正在打包,请留意系统消息。');
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
}
}
}
</script>

View File

@ -7,9 +7,6 @@
<p>{{ msg.data.comment_content }}</p>
<p v-if="msg.data.thumb" v-html="imageHtml(msg.data.thumb)"></p>
</div>
<div class="btn-raw no-dark-content">
<button class="ivu-btn ivu-btn-grey">{{$L('查看详情')}}</button>
</div>
</div>
</template>

View File

@ -2,16 +2,13 @@
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(`抄送 ${msg.data.nickname} 提交的「${msg.data.proc_def_name}」记录`) }}</b>
<div class="cause">
<p>{{$L("状态")}}<b>{{ msg.is_finished ? $L("已完成") : $L("审批中") }}</b></p>
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.nickname }}</span> {{ msg.data.department }}</p>
<b>{{$L("详情")}}</b>
<p v-if="$A.strExists(msg.data.proc_def_name, '请假') && msg.data.type">{{$L("假期类型")}}{{ $L(msg.data.type) }}</p>
<p>{{$L("开始时间")}}{{ msg.data.start_time }} ({{ $L(msg.data.start_day_of_week) }})</p>
<p>{{$L("结束时间")}}{{ msg.data.end_time }} ({{ $L(msg.data.end_day_of_week) }})</p>
<p>{{$L("事由")}}{{ msg.data.description }}</p>
</div>
<div class="btn-raw no-dark-content">
<button v-if="msg.is_finished" class="ivu-btn ivu-btn-grey">{{$L("已同意")}}</button>
<button v-else class="ivu-btn ivu-btn-grey">{{$L("查看详情")}}</button>
<p v-if="msg.data.type">{{$L("类型")}}{{ $L(msg.data.type) }}</p>
<p v-if="msg.data.start_time">{{$L("开始时间")}}{{ msg.data.start_time }}<template v-if="msg.data.start_day_of_week"> ({{ $L(msg.data.start_day_of_week) }})</template></p>
<p v-if="msg.data.end_time">{{$L("结束时间")}}{{ msg.data.end_time }}<template v-if="msg.data.end_day_of_week"> ({{ $L(msg.data.end_day_of_week) }})</template></p>
<p v-if="msg.data.description">{{$L("事由")}}{{ msg.data.description }}</p>
</div>
</div>
</template>

View File

@ -2,23 +2,15 @@
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(`${msg.data.nickname} 提交的「${msg.data.proc_def_name}」待你审批`) }}</b>
<div class="cause">
<p>{{$L("状态")}}<b>{{ $L(statusText) }}</b></p>
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.nickname }}</span> {{ msg.data.department }}</p>
<b>{{$L("详情")}}</b>
<p v-if="$A.strExists(msg.data.proc_def_name, '请假') && msg.data.type">{{$L("假期类型")}}{{ $L(msg.data.type) }}</p>
<p>{{$L("开始时间")}}{{ msg.data.start_time }} ({{ $L(msg.data.start_day_of_week) }})</p>
<p>{{$L("结束时间")}}{{ msg.data.end_time }} ({{ $L(msg.data.end_day_of_week) }})</p>
<p>{{$L("事由")}}{{ msg.data.description }}</p>
<p v-if="msg.data.type">{{$L("类型")}}{{ $L(msg.data.type) }}</p>
<p v-if="msg.data.start_time">{{$L("开始时间")}}{{ msg.data.start_time }}<template v-if="msg.data.start_day_of_week"> ({{ $L(msg.data.start_day_of_week) }})</template></p>
<p v-if="msg.data.end_time">{{$L("结束时间")}}{{ msg.data.end_time }}<template v-if="msg.data.end_day_of_week"> ({{ $L(msg.data.end_day_of_week) }})</template></p>
<p v-if="msg.data.description">{{$L("事由")}}{{ msg.data.description }}</p>
<p v-if="msg.data.thumb" v-html="imageHtml(msg.data.thumb)"></p>
</div>
<div class="btn-raw no-dark-content">
<button v-if="msg.action === 'pass'" class="ivu-btn ivu-btn-grey">{{$L("已同意")}}</button>
<button v-else-if="msg.action === 'refuse'" class="ivu-btn ivu-btn-grey rejected">{{$L("已拒绝")}}</button>
<button v-else-if="msg.action === 'withdraw'" class="ivu-btn ivu-btn-grey revoked">{{$L("已撤销")}}</button>
<template v-else>
<button class="ivu-btn ivu-btn-primary">{{$L("同意")}}</button>
<button class="ivu-btn ivu-btn-error">{{$L("拒绝")}}</button>
</template>
</div>
</div>
</template>
@ -30,6 +22,16 @@ export default {
data() {
return {};
},
computed: {
statusText({msg}) {
switch (msg.action) {
case 'pass': return '已同意';
case 'refuse': return '已拒绝';
case 'withdraw': return '已撤销';
default: return '待审批';
}
},
},
methods: {
imageHtml(info) {
const data = $A.imageRatioHandle({

View File

@ -2,17 +2,13 @@
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(title) }}</b>
<div class="cause">
<p>{{$L("状态")}}<b>{{ $L(statusText) }}</b></p>
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.start_nickname }}</span> {{ msg.data.department }}</p>
<b>{{$L("详情")}}</b>
<p v-if="$A.strExists(msg.data.proc_def_name, '请假') && msg.data.type">{{$L("假期类型")}}{{ $L(msg.data.type) }}</p>
<p>{{$L("开始时间")}}{{ msg.data.start_time }} ({{ $L(msg.data.start_day_of_week) }})</p>
<p>{{$L("结束时间")}}{{ msg.data.end_time }} ({{ $L(msg.data.end_day_of_week) }})</p>
<p>{{$L("事由")}}{{ msg.data.description }}</p>
</div>
<div class="btn-raw no-dark-content">
<button v-if="msg.action === 'pass'" class="ivu-btn ivu-btn-grey">{{$L("已同意")}}</button>
<button v-else-if="msg.action === 'refuse'" class="ivu-btn ivu-btn-grey rejected">{{$L("已拒绝")}}</button>
<button v-else-if="msg.action === 'withdraw'" class="ivu-btn ivu-btn-grey revoked">{{$L("已撤销")}}</button>
<p v-if="msg.data.type">{{$L("类型")}}{{ $L(msg.data.type) }}</p>
<p v-if="msg.data.start_time">{{$L("开始时间")}}{{ msg.data.start_time }}<template v-if="msg.data.start_day_of_week"> ({{ $L(msg.data.start_day_of_week) }})</template></p>
<p v-if="msg.data.end_time">{{$L("结束时间")}}{{ msg.data.end_time }}<template v-if="msg.data.end_day_of_week"> ({{ $L(msg.data.end_day_of_week) }})</template></p>
<p v-if="msg.data.description">{{$L("事由")}}{{ msg.data.description }}</p>
</div>
</div>
</template>
@ -28,7 +24,15 @@ export default {
computed: {
title({msg}) {
return msg.action === 'pass' ? `您发起的「${msg.data.proc_def_name}」已通过` : `您发起的「${msg.data.proc_def_name}」被 ${msg.data.nickname} 拒绝`
}
},
statusText({msg}) {
switch (msg.action) {
case 'pass': return '已通过';
case 'refuse': return '已拒绝';
case 'withdraw': return '已撤销';
default: return '处理中';
}
},
},
methods: {},
}

View File

@ -44,7 +44,6 @@
<h2 class="user-select-auto" @click="onViewDetail" v-html="transformEmojiToHtml(dialogData.name)"></h2>
<em v-if="peopleNum > 0" @click="onDialogMenu('groupInfo')">({{peopleNum}})</em>
<Tag v-if="dialogData.bot" class="after" :fade="false">{{$L('机器人')}}</Tag>
<Tag v-if="dialogData.type === 'user' && approvaUserStatus" class="after" color="red" :fade="false">{{$L(approvaUserStatus)}}</Tag>
<Tag v-if="dialogData.group_type=='all'" class="after pointer" :fade="false" @on-click="onDialogMenu('groupInfo')">{{$L('全员')}}</Tag>
<Tag v-else-if="dialogData.group_type=='department'" class="after pointer" :fade="false" @on-click="onDialogMenu('groupInfo')">{{$L('部门')}}</Tag>
<div v-if="msgLoadIng > 0 && allMsgs.length > 0" class="load"><Loading/></div>
@ -915,8 +914,6 @@ export default {
scrollIng: 0,
scrollGroup: null,
approvaUserStatus: '',
observers: [],
msgChangeCache: {},
@ -1664,8 +1661,6 @@ export default {
if (this.autoFocus) {
this.inputFocus()
}
//
this.getUserApproveStatus()
},
/**
@ -3822,10 +3817,6 @@ export default {
this.handleOpenMicroApp(clickElement);
return;
}
if (clickElement.classList.contains('open-approve-details')) {
emitter.emit('approveDetails', clickElement.getAttribute("data-id"));
return;
}
clickElement = clickElement.parentElement;
}
@ -4565,27 +4556,6 @@ export default {
});
},
async getUserApproveStatus() {
this.approvaUserStatus = ''
if (this.dialogData.type !== 'user' || this.dialogData.bot) {
return
}
const isInstalled = await this.$store.dispatch("isMicroAppInstalled", 'approve');
if (!isInstalled) {
return
}
this.$store.dispatch("call", {
url: 'approve/user/status',
data: {
userid: this.dialogData.dialog_user.userid,
}
}).then(({data}) => {
this.approvaUserStatus = data;
}).catch(({msg}) => {
$A.messageError(msg);
});
},
async shakeToMsgId(id) {
try {
const element = await $A.findElementWithRetry(() => this.$refs.scroller.$el.querySelector(`[data-id="${id}"]`)?.querySelector(".dialog-head"));

View File

@ -648,7 +648,6 @@ export default {
dispatch("getDialogTodo", 0).catch(() => {});
dispatch("getTaskPriority", 1000);
dispatch("getReportUnread", 1000);
dispatch("getApproveUnread", 1000);
dispatch("getProjectsForDepartmentOwnerView").catch(() => {});
dispatch("getTaskForDashboard");
dispatch("dialogMsgRead");
@ -679,34 +678,6 @@ export default {
}, typeof timeout === "number" ? timeout : 1000)
},
/**
* 获取审批待办未读数量
* @param state
* @param dispatch
* @param timeout
*/
getApproveUnread({state, dispatch}, timeout) {
window.__getApproveUnread && clearTimeout(window.__getApproveUnread)
window.__getApproveUnread = setTimeout(() => {
if (state.userId === 0) {
state.approveUnreadNumber = 0;
} else {
dispatch("call", {
url: 'approve/process/doto'
}).then(({data}) => {
state.approveUnreadNumber = data.total || 0;
}).catch(({msg}) => {
if( msg.indexOf("404 not found") !== -1){
$A.modalInfo({
title: '版本过低',
content: '服务器版本过低,请升级服务器。',
})
}
});
}
}, typeof timeout === "number" ? timeout : 1000)
},
/**
* 获取/更新会员信息
* @param dispatch
@ -5026,17 +4997,6 @@ export default {
}
})(msgDetail);
break;
/**
* 流程审批
*/
case "approve":
(function ({action}) {
if (action == 'unread') {
dispatch("getApproveUnread", 1000)
}
})(msgDetail);
break;
}
break
}

View File

@ -272,9 +272,6 @@ export default {
// 系统设置
systemConfig: {},
// 审批待办未读数量
approveUnreadNumber: 0,
// 会议
meetingWindow: {
show: false,

View File

@ -1523,7 +1523,6 @@
}
.cause {
border-bottom: 1px solid #e3e3e3;
border-top: 1px solid #e3e3e3;
padding-bottom: 10px;
margin-top: 10px;

View File

@ -9,7 +9,6 @@ use App\Http\Controllers\Api\DialogController;
use App\Http\Controllers\Api\PublicController;
use App\Http\Controllers\Api\ReportController;
use App\Http\Controllers\Api\SystemController;
use App\Http\Controllers\Api\ApproveController;
use App\Http\Controllers\Api\AssistantController;
use App\Http\Controllers\Api\ProjectController;
use App\Http\Controllers\Api\ComplaintController;
@ -52,9 +51,6 @@ Route::prefix('api')->middleware(['webapi'])->group(function () {
// 公开接口
Route::any('public/{method}', PublicController::class);
Route::any('public/{method}/{action}', PublicController::class);
// 审批
Route::any('approve/{method}', ApproveController::class);
Route::any('approve/{method}/{action}', ApproveController::class);
// 助手
Route::any('assistant/{method}', AssistantController::class);
Route::any('assistant/{method}/{action}', AssistantController::class);