MES本地审批共用钉钉审批等配置

This commit is contained in:
geht
2026-06-10 16:33:44 +08:00
parent c4447b91dd
commit 617d47a3db
19 changed files with 980 additions and 36 deletions

View File

@@ -13,6 +13,9 @@ enum Api {
// update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】发起人撤销-----
cancel = '/xslmes/approvalHandle/cancel',
// update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】发起人撤销-----
// update-begin---author:GHT ---date:2026-06-10 for【IM审批通用化】补发IM审批卡片-----
resendCard = '/xslmes/approvalHandle/resendCard',
// update-end---author:GHT ---date:2026-06-10 for【IM审批通用化】补发IM审批卡片-----
}
/** 查看单据全部字段 + 审批进度/历史 */
@@ -31,3 +34,9 @@ export const rejectApproval = (params: { instanceId: string; reason: string }) =
/** 撤销(仅发起人本人,审批中可撤回,业务单据恢复到发起时状态) */
export const cancelApproval = (params: { instanceId: string; reason?: string }) => defHttp.post({ url: Api.cancel, data: params });
// update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】发起人撤销-----
// update-begin---author:GHT ---date:2026-06-10 for【IM审批通用化】补发IM审批卡片-----
/** 补发当前节点 IM 审批卡片instanceId 与 bizTable+bizDataId 二选一) */
export const resendApprovalCard = (params: { instanceId?: string; bizTable?: string; bizDataId?: string }) =>
defHttp.post({ url: Api.resendCard, data: params });
// update-end---author:GHT ---date:2026-06-10 for【IM审批通用化】补发IM审批卡片-----

View File

@@ -5,7 +5,13 @@
<template v-else>
<!-- 单条详情表 -->
<template v-if="isSingleItem">
<div class="im-biz-record-item">
<div class="im-biz-record-item" :class="{ 'im-biz-record-item--ding': isDingStyleCard }">
<!-- update-begin---author:GHT ---date:2026-06-10 forIM审批通用化钉钉模板样式卡片头----------- -->
<div v-if="isDingStyleCard" class="im-biz-record-ding-header">
<span class="im-biz-record-ding-badge">审批</span>
<span class="im-biz-record-ding-title">{{ dingCardTitle }}</span>
</div>
<!-- update-end---author:GHT ---date:2026-06-10 forIM审批通用化钉钉模板样式卡片头----------- -->
<div class="im-biz-record-table-wrap">
<table class="im-biz-record-table im-biz-record-table--detail">
<tbody>
@@ -167,6 +173,15 @@
const singleItem = computed(() => props.payload.items[0]);
const listColumnLabels = computed(() => resolveImBizRecordListColumnLabels(props.payload.items));
// update-begin---author:GHT ---date:2026-06-10 for【IM审批通用化】钉钉模板样式卡片-----------
const isDingStyleCard = computed(
() => props.payload.cardStyle === 'ding' || !!props.payload.templateName,
);
const dingCardTitle = computed(
() => props.payload.templateName || props.payload.pageTitle || '审批单',
);
// update-end---author:GHT ---date:2026-06-10 for【IM审批通用化】钉钉模板样式卡片-----------
// 审批卡片单条且带审批实例ID
const isApprovalCard = computed(() => isSingleItem.value && !!singleItem.value?.instanceId);
@@ -425,6 +440,50 @@
display: flex;
flex-direction: column;
gap: 8px;
&--ding {
.im-biz-record-table-wrap {
border-color: #ffe7ba;
}
.im-biz-record-table--detail th {
background: #fff7e6;
color: #873800;
}
}
}
.im-biz-record-ding-header {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 6px 6px 0 0;
background: linear-gradient(90deg, #fff7e6 0%, #fff 100%);
border: 1px solid #ffe7ba;
border-bottom: none;
}
.im-biz-record-ding-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 36px;
padding: 0 6px;
height: 20px;
border-radius: 4px;
background: #ff6900;
color: #fff;
font-size: 11px;
font-weight: 600;
line-height: 1;
}
.im-biz-record-ding-title {
font-size: 13px;
font-weight: 600;
color: #262626;
line-height: 1.4;
}
/* 审批办理按钮栏 */

View File

@@ -59,21 +59,32 @@
placement="right"
>
<div
:class="['conv-item', activeTargetUserId === item.id ? 'active' : '']"
:class="[
'conv-item',
{ active: activeTargetUserId === item.id, 'conv-item--work-notify': isWorkNotifyContact(item) },
]"
@click="selectMember(item)"
>
<a-badge :count="shouldShowUnread(item) ? item.unreadCount : 0" :offset="[-2, 2]">
<a-avatar :size="leftCollapsed ? 36 : 40" :src="getAvatarUrl(item.avatar)">
<a-avatar
v-if="isWorkNotifyContact(item)"
:size="leftCollapsed ? 36 : 40"
:style="{ backgroundColor: '#fa8c16' }"
>
<Icon icon="ant-design:notification-outlined" />
</a-avatar>
<a-avatar v-else :size="leftCollapsed ? 36 : 40" :src="getAvatarUrl(item.avatar)">
{{ (item.realname || item.username || '?').slice(0, 1) }}
</a-avatar>
</a-badge>
<div v-show="!leftCollapsed" class="conv-meta">
<div class="conv-top">
<span class="conv-name">{{ item.realname || item.username }}</span>
<span v-if="isWorkNotifyContact(item)" class="conv-tag">公众号</span>
<span class="conv-time">{{ formatTime(item.lastTime) }}</span>
</div>
<div class="conv-bottom">
<span class="conv-preview">{{ formatConvPreview(item.lastContent) }}</span>
<span class="conv-preview">{{ formatConvPreview(item.lastContent, isWorkNotifyContact(item)) }}</span>
</div>
</div>
</div>
@@ -203,7 +214,9 @@
<Icon v-if="embeddedPageContextClickable" icon="ant-design:select-outlined" class="im-page-context-action" />
</div>
<!--update-end---author:xsl ---date:20260528 forIM聊天-OA弹窗输入框上方展示背后功能页名称------------->
<div v-if="isWorkNotifyChat" class="im-work-notify-readonly-tip">工作通知为系统消息通道仅接收审批等工作消息</div>
<ImChatInput
v-else
v-model="draft"
:disabled="!activeConversationId"
:sending="sending"
@@ -261,6 +274,7 @@
import { syncImUnreadFromMembers } from './useImUnread';
import {
type ImMemberItem,
IM_CONTACT_TYPE_WORK_NOTIFY,
type ImMessageItem,
getCachedMembers,
isMembersCacheStale,
@@ -539,9 +553,15 @@
return parseImBizRecordPayload(content);
}
function formatConvPreview(content?: string) {
function isWorkNotifyContact(item?: DeptMemberItem | null) {
return item?.contactType === IM_CONTACT_TYPE_WORK_NOTIFY || item?.username === 'im_work_notify';
}
const isWorkNotifyChat = computed(() => isWorkNotifyContact(activeMember.value));
function formatConvPreview(content?: string, isWorkNotify = false) {
if (!content) {
return '点击开始聊天';
return isWorkNotify ? '暂无系统通知' : '点击开始聊天';
}
return formatImMessagePreview(content);
}
@@ -948,7 +968,9 @@
function handleOpenCreateGroupModal() {
// useModalInner 必须传入 data 才会触发打开回调,否则不会加载成员列表
openCreateGroupModal(true, {
members: deptMembers.value.filter((item) => item.id !== currentUserId.value),
members: deptMembers.value.filter(
(item) => item.id !== currentUserId.value && !isWorkNotifyContact(item),
),
});
}
//update-end---author:xsl ---date:20260528 for【IM聊天-OA】发起群聊弹窗需传 data 触发成员加载-----------
@@ -1917,6 +1939,36 @@
&.active {
background: #eef4ff;
}
&.conv-item--work-notify {
border-bottom: 1px solid #f5f5f5;
&:hover,
&.active {
background: #fff7e6;
}
}
}
.conv-tag {
flex-shrink: 0;
margin-left: 4px;
padding: 0 6px;
border-radius: 10px;
background: #fff7e6;
color: #fa8c16;
font-size: 11px;
font-weight: 400;
line-height: 18px;
}
.im-work-notify-readonly-tip {
padding: 12px 16px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
color: #8c8c8c;
font-size: 13px;
text-align: center;
}
.conv-meta {

View File

@@ -42,7 +42,7 @@
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { useUserStore } from '/@/store/modules/user';
import { createGroupConversation, fetchDeptMembers } from './im.api';
import { getCachedMembers } from './imCache';
import { getCachedMembers, IM_CONTACT_TYPE_WORK_NOTIFY } from './imCache';
defineOptions({ name: 'ImCreateGroupModal' });
@@ -73,7 +73,13 @@
function resolveSelectableMembers(source?: Recordable[]) {
const currentUserId = userStore.getUserInfo?.id || '';
const list = source?.length ? source : getCachedMembers() || [];
return list.filter((item) => item.id && item.id !== currentUserId);
return list.filter(
(item) =>
item.id &&
item.id !== currentUserId &&
item.contactType !== IM_CONTACT_TYPE_WORK_NOTIFY &&
item.username !== 'im_work_notify',
);
}
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data?: { members?: Recordable[] }) => {

View File

@@ -34,6 +34,12 @@ export interface ImBizRecordPayload {
pagePath: string;
rowKey: string;
items: ImBizRecordItem[];
// update-begin---author:GHT ---date:2026-06-10 for【IM审批通用化】钉钉模板样式卡片扩展-----------
/** ding=与钉钉审批模板字段对齐的卡片样式 */
cardStyle?: 'ding' | string;
templateId?: string;
templateName?: string;
// update-end---author:GHT ---date:2026-06-10 for【IM审批通用化】钉钉模板样式卡片扩展-----------
}
/** 构建带跳转链接的业务明细消息体 */

View File

@@ -8,6 +8,8 @@ import { formatImMessagePreview } from './imMessageUtil';
import { syncImUnreadFromMembers } from './useImUnread';
import { isImChatPageActive } from './imSession';
export const IM_CONTACT_TYPE_WORK_NOTIFY = 'work_notify';
export interface ImMemberItem {
id: string;
username: string;
@@ -18,6 +20,8 @@ export interface ImMemberItem {
lastContent?: string;
lastTime?: string;
unreadCount?: number;
/** user=同事 work_notify=工作通知公众号 */
contactType?: string;
}
export interface ImMessageItem {

View File

@@ -19,6 +19,16 @@
onClick: handleDetail.bind(null, record),
auth: 'xslmes:mes_xsl_approval_record:list',
},
{
label: '补发IM卡片',
color: 'warning',
ifShow: record.status === '0' && record.channel === 'MES' && !!record.externalInstanceId,
popConfirm: {
title: '向当前处理人重新发送 IM 审批卡片?',
confirm: handleResendCard.bind(null, record),
},
auth: 'xslmes:mes_xsl_approval_record:list',
},
]"
/>
</template>
@@ -34,7 +44,10 @@
import MesXslApprovalRecordDetailModal from './components/MesXslApprovalRecordDetailModal.vue';
import { columns, searchFormSchema } from './MesXslApprovalRecord.data';
import { list, getExportUrl } from './MesXslApprovalRecord.api';
import { resendApprovalCard } from '/@/views/approval/flow/approvalHandle.api';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls } = useListPage({
@@ -50,7 +63,7 @@
showAdvancedButton: true,
},
actionColumn: {
width: 100,
width: 180,
fixed: 'right',
},
},
@@ -65,4 +78,13 @@
function handleDetail(record: Recordable) {
openModal(true, { record });
}
async function handleResendCard(record: Recordable) {
try {
const msg = await resendApprovalCard({ instanceId: record.externalInstanceId });
createMessage.success(msg || '补发成功');
} catch (e: any) {
createMessage.error(e?.message || '补发失败');
}
}
</script>