15 changed files with 5481 additions and 23 deletions
-
85src/App.vue
-
103src/api/erf/erf.js
-
104src/assets/scss/approval-notification.scss
-
1src/assets/scss/index.scss
-
564src/components/ApprovalNotificationManager.vue
-
192src/config/approval-notification.config.js
-
154src/views/modules/erf/components/approvalHistory.vue
-
537src/views/modules/erf/components/erfAttachmentManager.vue
-
354src/views/modules/erf/components/expApplyForm.vue
-
344src/views/modules/erf/components/expProjectDetail.vue
-
569src/views/modules/erf/components/expTriConfirm.vue
-
490src/views/modules/erf/expApplyApproval.vue
-
1311src/views/modules/erf/expApplyList.vue
-
372src/views/modules/erf/plannerSchedule.vue
-
324src/views/modules/erf/triConfirm.vue
@ -1,35 +1,74 @@ |
|||
<template> |
|||
<transition name="fade"> |
|||
<router-view></router-view> |
|||
</transition> |
|||
<div id="app"> |
|||
<!-- 主内容区域 --> |
|||
<transition name="fade"> |
|||
<router-view></router-view> |
|||
</transition> |
|||
|
|||
<!-- 全局审批通知管理器 --> |
|||
<approval-notification-manager ref="approvalNotificationManager"></approval-notification-manager> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import ApprovalNotificationManager from '@/components/ApprovalNotificationManager.vue' |
|||
|
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
query: {}, |
|||
version: '1.3.3' |
|||
export default { |
|||
name: 'App', |
|||
|
|||
components: { |
|||
ApprovalNotificationManager |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
query: {}, |
|||
version: '1.3.3' |
|||
} |
|||
}, |
|||
|
|||
created () { |
|||
this.versionReload() |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 版本检查及自动刷新 |
|||
*/ |
|||
versionReload(){ |
|||
let version = this.version //版本号(每次上线前需要更新下版本号) |
|||
console.log('最新系统版本: ',version) |
|||
console.log('当前系统版本: ',this.version) |
|||
let versionLocal = localStorage.getItem('_version_'); |
|||
if(version!=versionLocal){ |
|||
localStorage.setItem('_version_',version); |
|||
this.version=versionLocal; |
|||
location.reload(); |
|||
} |
|||
}, |
|||
created () { |
|||
this.versionReload() |
|||
|
|||
/** |
|||
* 手动触发审批通知检查(供外部调用) |
|||
*/ |
|||
checkApprovalNotifications() { |
|||
if (this.$refs.approvalNotificationManager) { |
|||
this.$refs.approvalNotificationManager.manualCheck() |
|||
} |
|||
}, |
|||
methods: { |
|||
versionReload(){ |
|||
let version = this.version //版本号(每次上线前需要更新下版本号) |
|||
console.log('最新系统版本: ',version) |
|||
console.log('当前系统版本: ',this.version) |
|||
let versionLocal = localStorage.getItem('_version_'); |
|||
if(version!=versionLocal){ |
|||
localStorage.setItem('_version_',version); |
|||
this.version=versionLocal; |
|||
location.reload(); |
|||
} |
|||
|
|||
/** |
|||
* 清除指定申请单的通知记录(审批完成后调用) |
|||
* @param {string} applyNo - 申请单号 |
|||
*/ |
|||
clearApprovalNotification(applyNo) { |
|||
if (this.$refs.approvalNotificationManager) { |
|||
this.$refs.approvalNotificationManager.clearNotification(applyNo) |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/* 全局样式 */ |
|||
</style> |
|||
@ -0,0 +1,103 @@ |
|||
|
|||
import { createAPI } from "@/utils/httpRequest.js"; |
|||
|
|||
// =====================================================
|
|||
// 工程实验申请单 API接口定义
|
|||
// =====================================================
|
|||
|
|||
/** |
|||
* 查询申请单列表 |
|||
*/ |
|||
export const searchExpApplyList = data => createAPI(`/erf/expApply/searchExpApplyList`, 'post', data) |
|||
|
|||
/** |
|||
* 根据申请单号查询详情 |
|||
*/ |
|||
export const getExpApplyDetail = data => createAPI(`/erf/expApply/getExpApplyDetail`, 'post', data) |
|||
|
|||
/** |
|||
* 保存申请单(新增或修改) |
|||
*/ |
|||
export const saveExpApply = data => createAPI(`/erf/expApply/saveExpApply`, 'post', data) |
|||
|
|||
/** |
|||
* 下达申请单 |
|||
*/ |
|||
export const submitExpApply = data => createAPI(`/erf/expApply/submitExpApply`, 'post', data) |
|||
|
|||
/** |
|||
* 获取下达时的审批人信息 |
|||
*/ |
|||
export const getSubmitApprovers = data => createAPI(`/erf/expApply/getSubmitApprovers`, 'post', data) |
|||
|
|||
/** |
|||
* 审批申请单 |
|||
*/ |
|||
export const approveExpApply = data => createAPI(`/erf/expApply/approveExpApply`, 'post', data) |
|||
|
|||
/** |
|||
* 撤回申请单 |
|||
*/ |
|||
export const withdrawExpApply = data => createAPI(`/erf/expApply/withdrawExpApply`, 'post', data) |
|||
|
|||
/** |
|||
* 删除申请单 |
|||
*/ |
|||
export const deleteExpApply = data => createAPI(`/erf/expApply/deleteExpApply`, 'post', data) |
|||
|
|||
/** |
|||
* 查询用户的待办申请单 |
|||
*/ |
|||
export const getPendingApplyList = data => createAPI(`/erf/expApply/getPendingApplyList`, 'post', data) |
|||
|
|||
// =====================================================
|
|||
// High Risk三方确认 API接口定义
|
|||
// =====================================================
|
|||
|
|||
/** |
|||
* 查询三方确认列表 |
|||
*/ |
|||
export const getTriConfirmList = data => createAPI(`/erf/triConfirm/getTriConfirmList`, 'post', data) |
|||
|
|||
/** |
|||
* 三方确认操作 |
|||
*/ |
|||
export const confirmTriApproval = data => createAPI(`/erf/triConfirm/confirmTriApproval`, 'post', data) |
|||
|
|||
// =====================================================
|
|||
// 审批历史 API接口定义
|
|||
// =====================================================
|
|||
|
|||
/** |
|||
* 查询审批历史 |
|||
*/ |
|||
export const getApprovalHistory = data => createAPI(`/erf/expApply/getApprovalHistory`, 'post', data) |
|||
|
|||
/** |
|||
* 获取流程状态详情(包含流程进度和审批历史) |
|||
*/ |
|||
export const getFlowStatus = data => createAPI(`/erf/expApply/getFlowStatus`, 'post', data) |
|||
|
|||
/** |
|||
* 计划员排产 |
|||
*/ |
|||
export const plannerSchedule = data => createAPI(`/erf/expApply/plannerSchedule`, 'post', data) |
|||
|
|||
/** |
|||
* 获取字段权限 |
|||
*/ |
|||
export const getFieldAuth = data => createAPI(`/erf/expApply/getFieldAuth`, 'post', data) |
|||
|
|||
// =====================================================
|
|||
// 三方确认 API接口定义
|
|||
// =====================================================
|
|||
|
|||
/** |
|||
* 保存三方确认工序 |
|||
*/ |
|||
export const saveTriConfirmProcess = data => createAPI(`/erf/triConfirm/saveTriConfirmProcess`, 'post', data) |
|||
|
|||
/** |
|||
* 删除三方确认工序 |
|||
*/ |
|||
export const deleteTriConfirmProcess = data => createAPI(`/erf/triConfirm/deleteTriConfirmProcess`, 'post', data) |
|||
@ -0,0 +1,104 @@ |
|||
/** |
|||
* 审批通知样式 |
|||
* 优化 Element UI Notification 组件的显示效果 |
|||
*/ |
|||
|
|||
/* 审批通知容器 */ |
|||
.el-notification.approval-notification { |
|||
width: 380px; |
|||
padding: 20px; |
|||
border-left: 3px solid #E6A23C; |
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
|||
|
|||
&:hover { |
|||
cursor: pointer; |
|||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25); |
|||
transform: translateX(-5px); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
/* 标题样式 */ |
|||
.el-notification__title { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
color: #E6A23C; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
/* 内容样式 */ |
|||
.el-notification__content { |
|||
font-size: 14px; |
|||
line-height: 1.8; |
|||
color: #303133; |
|||
|
|||
p { |
|||
margin: 5px 0; |
|||
} |
|||
|
|||
strong { |
|||
color: #303133; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
|
|||
/* 关闭按钮 */ |
|||
.el-notification__closeBtn { |
|||
color: #909399; |
|||
font-size: 18px; |
|||
|
|||
&:hover { |
|||
color: #E6A23C; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 新申请单通知 */ |
|||
.el-notification.new-approval-notification { |
|||
border-left-color: #409EFF; |
|||
|
|||
.el-notification__title { |
|||
color: #409EFF; |
|||
} |
|||
} |
|||
|
|||
/* 汇总通知 */ |
|||
.el-notification.summary-notification { |
|||
border-left-color: #E6A23C; |
|||
|
|||
.el-notification__title { |
|||
color: #E6A23C; |
|||
} |
|||
|
|||
/* 数字高亮 */ |
|||
.count-highlight { |
|||
color: #E6A23C; |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
padding: 0 5px; |
|||
} |
|||
} |
|||
|
|||
/* 通知动画 */ |
|||
@keyframes notification-shake { |
|||
0%, 100% { |
|||
transform: translateX(0); |
|||
} |
|||
25% { |
|||
transform: translateX(-5px); |
|||
} |
|||
75% { |
|||
transform: translateX(5px); |
|||
} |
|||
} |
|||
|
|||
.el-notification.shake-animation { |
|||
animation: notification-shake 0.5s ease; |
|||
} |
|||
|
|||
/* 响应式适配 */ |
|||
@media screen and (max-width: 768px) { |
|||
.el-notification.approval-notification { |
|||
width: 320px; |
|||
padding: 15px; |
|||
} |
|||
} |
|||
@ -0,0 +1,564 @@ |
|||
<template> |
|||
<!-- 全局审批通知管理组件 - 无界面,纯逻辑 --> |
|||
<div style="display: none;"></div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getPendingApplyList } from '@/api/erf/erf' |
|||
import approvalConfig from '@/config/approval-notification.config' |
|||
|
|||
/** |
|||
* 全局审批通知管理器 |
|||
* |
|||
* 功能: |
|||
* 1. 登录后自动检查待审批项 |
|||
* 2. 定时轮询检查新的待审批项(默认5分钟,可在配置文件中修改) |
|||
* 3. 在浏览器右下角弹出通知提示 |
|||
* 4. 点击通知跳转到审批页面 |
|||
* |
|||
* 配置文件:src/config/approval-notification.config.js |
|||
*/ |
|||
export default { |
|||
name: 'ApprovalNotificationManager', |
|||
|
|||
data() { |
|||
return { |
|||
pollingTimer: null, // 轮询定时器 |
|||
pollingInterval: approvalConfig.polling.interval, // 轮询间隔(从配置文件读取) |
|||
lastCheckTime: null, // 上次检查时间 |
|||
notifiedApplications: new Set(), // 已通知过的申请单号集合(避免重复通知) |
|||
isInitialized: false, // 是否已初始化 |
|||
audioContext: null, // 音频上下文(用于提示音) |
|||
config: approvalConfig, // 配置对象 |
|||
isChecking: false, // 是否正在检查中(防止重复调用) |
|||
firstCheckTimeout: null, // 首次检查的定时器 |
|||
activeNotifications: [], // 当前显示的所有通知实例 |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
/** |
|||
* 当前登录用户ID |
|||
*/ |
|||
currentUserId() { |
|||
return this.$store.state.user.id |
|||
}, |
|||
|
|||
/** |
|||
* 当前登录用户名称 |
|||
*/ |
|||
currentUserName() { |
|||
return this.$store.state.user.name |
|||
}, |
|||
|
|||
/** |
|||
* 当前站点 |
|||
*/ |
|||
currentSite() { |
|||
return this.$store.state.user.site |
|||
}, |
|||
|
|||
/** |
|||
* 是否已登录 |
|||
*/ |
|||
isLoggedIn() { |
|||
return this.currentUserId && this.currentUserId !== 0 |
|||
} |
|||
}, |
|||
|
|||
watch: { |
|||
/** |
|||
* 监听用户登录状态变化 |
|||
*/ |
|||
isLoggedIn(newVal, oldVal) { |
|||
console.log(`[审批通知] 登录状态变化: ${oldVal} -> ${newVal}, 已初始化: ${this.isInitialized}`) |
|||
|
|||
// 只有真正的登录状态变化才处理 |
|||
if (oldVal === newVal) { |
|||
console.log('[审批通知] 登录状态未变化,跳过') |
|||
return |
|||
} |
|||
|
|||
if (newVal && !this.isInitialized) { |
|||
console.log('[审批通知] 用户已登录(watch触发),初始化通知系统') |
|||
// 延迟执行,避免与 mounted 冲突 |
|||
this.$nextTick(() => { |
|||
if (!this.isInitialized) { |
|||
this.initializeNotificationSystem() |
|||
} |
|||
}) |
|||
} else if (!newVal && this.isInitialized) { |
|||
console.log('[审批通知] 用户已登出,停止通知系统') |
|||
this.stopNotificationSystem() |
|||
} |
|||
} |
|||
}, |
|||
|
|||
mounted() { |
|||
console.log(`[审批通知] 组件挂载, 登录状态: ${this.isLoggedIn}, 已初始化: ${this.isInitialized}`) |
|||
// 组件挂载时,如果已登录且未初始化则初始化 |
|||
// 使用延迟确保只执行一次 |
|||
this.$nextTick(() => { |
|||
if (this.isLoggedIn && !this.isInitialized) { |
|||
console.log('[审批通知] 组件挂载时初始化通知系统') |
|||
this.initializeNotificationSystem() |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
beforeDestroy() { |
|||
// 组件销毁时清理资源 |
|||
console.log('[审批通知] 组件销毁,清理所有资源') |
|||
this.stopNotificationSystem() |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 初始化通知系统 |
|||
*/ |
|||
initializeNotificationSystem() { |
|||
// 双重检查锁 |
|||
if (this.isInitialized) { |
|||
console.log('[审批通知] 系统已初始化,跳过重复初始化') |
|||
return |
|||
} |
|||
|
|||
console.log('[审批通知] 开始初始化通知系统...') |
|||
|
|||
// 立即设置标志,防止并发调用 |
|||
this.isInitialized = true |
|||
|
|||
// 先清理可能存在的旧资源 |
|||
this.stopPolling() |
|||
this.closeAllNotifications() |
|||
|
|||
// 清理可能存在的首次检查定时器 |
|||
if (this.firstCheckTimeout) { |
|||
clearTimeout(this.firstCheckTimeout) |
|||
this.firstCheckTimeout = null |
|||
} |
|||
|
|||
// 延迟后首次检查(避免登录时的接口压力) |
|||
this.firstCheckTimeout = setTimeout(() => { |
|||
if (this.isInitialized && this.isLoggedIn) { |
|||
this.checkPendingApprovals(true) |
|||
} |
|||
this.firstCheckTimeout = null |
|||
}, this.config.polling.firstCheckDelay) |
|||
|
|||
// 启动定时轮询 |
|||
this.startPolling() |
|||
|
|||
console.log('[审批通知] 通知系统初始化完成') |
|||
}, |
|||
|
|||
/** |
|||
* 停止通知系统 |
|||
*/ |
|||
stopNotificationSystem() { |
|||
console.log('[审批通知] 停止通知系统...') |
|||
|
|||
// 关闭所有已显示的通知窗口 |
|||
this.closeAllNotifications() |
|||
|
|||
// 停止轮询 |
|||
this.stopPolling() |
|||
|
|||
// 清理首次检查定时器 |
|||
if (this.firstCheckTimeout) { |
|||
clearTimeout(this.firstCheckTimeout) |
|||
this.firstCheckTimeout = null |
|||
} |
|||
|
|||
// 重置状态 |
|||
this.isInitialized = false |
|||
this.isChecking = false |
|||
this.notifiedApplications.clear() |
|||
this.lastCheckTime = null |
|||
|
|||
console.log('[审批通知] 通知系统已停止') |
|||
}, |
|||
|
|||
/** |
|||
* 关闭所有活动的通知窗口 |
|||
*/ |
|||
closeAllNotifications() { |
|||
console.log(`[审批通知] 关闭所有通知窗口,当前数量: ${this.activeNotifications.length}`) |
|||
|
|||
// 关闭所有通知 |
|||
this.activeNotifications.forEach(notification => { |
|||
try { |
|||
if (notification && typeof notification.close === 'function') { |
|||
notification.close() |
|||
} |
|||
} catch (error) { |
|||
console.error('[审批通知] 关闭通知失败:', error) |
|||
} |
|||
}) |
|||
|
|||
// 清空通知列表 |
|||
this.activeNotifications = [] |
|||
}, |
|||
|
|||
/** |
|||
* 从活动列表中移除指定的通知实例 |
|||
* @param {Object} notification - 要移除的通知实例 |
|||
*/ |
|||
removeNotification(notification) { |
|||
const index = this.activeNotifications.indexOf(notification) |
|||
if (index > -1) { |
|||
this.activeNotifications.splice(index, 1) |
|||
console.log(`[审批通知] 已移除通知实例,剩余活动通知数: ${this.activeNotifications.length}`) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 启动定时轮询 |
|||
*/ |
|||
startPolling() { |
|||
if (this.pollingTimer) { |
|||
console.log('[审批通知] 定时器已存在,跳过创建') |
|||
return |
|||
} |
|||
|
|||
this.pollingTimer = setInterval(() => { |
|||
if (this.isLoggedIn && this.isInitialized) { |
|||
this.checkPendingApprovals(false) |
|||
} |
|||
}, this.pollingInterval) |
|||
|
|||
console.log(`[审批通知] 定时轮询已启动,定时器ID: ${this.pollingTimer}, 间隔 ${this.pollingInterval / 1000} 秒`) |
|||
}, |
|||
|
|||
/** |
|||
* 停止定时轮询 |
|||
*/ |
|||
stopPolling() { |
|||
if (this.pollingTimer) { |
|||
console.log(`[审批通知] 清除定时器,ID: ${this.pollingTimer}`) |
|||
clearInterval(this.pollingTimer) |
|||
this.pollingTimer = null |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 检查待审批的申请单 |
|||
* @param {boolean} isFirstCheck - 是否首次检查 |
|||
*/ |
|||
checkPendingApprovals(isFirstCheck = false) { |
|||
if (!this.isLoggedIn) { |
|||
console.log('[审批通知] 用户未登录,跳过检查') |
|||
return |
|||
} |
|||
|
|||
// 防止重复调用 |
|||
if (this.isChecking) { |
|||
console.log('[审批通知] 正在检查中,跳过本次调用') |
|||
return |
|||
} |
|||
|
|||
this.isChecking = true |
|||
console.log(`[审批通知] 开始检查待审批申请单... (首次检查: ${isFirstCheck})`) |
|||
|
|||
const requestData = { |
|||
site: this.currentSite, |
|||
currentUserId: this.currentUserId, |
|||
userName: this.currentUserName, |
|||
page :1, |
|||
limit :20 |
|||
} |
|||
|
|||
getPendingApplyList(requestData).then(({data}) => { |
|||
if (data && data.code === 0) { |
|||
const pendingList = data.rows || data.page.list || [] |
|||
|
|||
console.log(`[审批通知] 发现 ${pendingList.length} 个待审批申请单`) |
|||
|
|||
if (pendingList.length > 0) { |
|||
if (isFirstCheck) { |
|||
// 首次检查:显示汇总通知 |
|||
this.showSummaryNotification(pendingList) |
|||
} else { |
|||
// 轮询检查:只通知新的申请单 |
|||
this.showNewApprovalNotifications(pendingList) |
|||
} |
|||
} |
|||
|
|||
this.lastCheckTime = new Date() |
|||
} |
|||
this.isChecking = false |
|||
}).catch(error => { |
|||
console.error('[审批通知] 检查待审批申请单失败:', error) |
|||
this.isChecking = false |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 显示汇总通知(首次登录时) |
|||
* @param {Array} approvalList - 待审批列表 |
|||
*/ |
|||
showSummaryNotification(approvalList) { |
|||
const count = approvalList.length |
|||
|
|||
// 记录所有申请单号(避免重复通知) |
|||
approvalList.forEach(item => { |
|||
this.notifiedApplications.add(item.applyNo || item.applicationNo) |
|||
}) |
|||
|
|||
// 创建通知实例 |
|||
const notificationInstance = this.$notify({ |
|||
title: '待审批提醒', |
|||
customClass: this.config.style.summaryClass, |
|||
dangerouslyUseHTMLString: true, |
|||
message: ` |
|||
<div style="line-height: 1.6;"> |
|||
<p style="margin: 0 0 8px 0;"> |
|||
您有 <span class="count-highlight">${count}</span> 个工程实验申请单待审批 |
|||
</p> |
|||
<p style="margin: 0; font-size: 12px; color: #909399;"> |
|||
点击此通知查看详情 |
|||
</p> |
|||
</div> |
|||
`, |
|||
type: 'warning', |
|||
position: this.config.notification.position, |
|||
duration: this.config.notification.summaryDuration, |
|||
showClose: this.config.notification.showClose, |
|||
onClick: () => { |
|||
// 点击后立即关闭通知并从列表中移除 |
|||
this.removeNotification(notificationInstance) |
|||
notificationInstance.close() |
|||
// 跳转到审批列表 |
|||
this.navigateToApprovalList() |
|||
}, |
|||
onClose: () => { |
|||
// 通知关闭时从列表中移除 |
|||
this.removeNotification(notificationInstance) |
|||
} |
|||
}) |
|||
|
|||
// 保存通知实例 |
|||
this.activeNotifications.push(notificationInstance) |
|||
console.log(`[审批通知] 已创建汇总通知,当前活动通知数: ${this.activeNotifications.length}`) |
|||
|
|||
// 播放提示音 |
|||
// this.playNotificationSound() |
|||
}, |
|||
|
|||
/** |
|||
* 显示新的审批通知(轮询时) |
|||
* @param {Array} approvalList - 待审批列表 |
|||
*/ |
|||
showNewApprovalNotifications(approvalList) { |
|||
// 筛选出新的申请单 |
|||
const newApprovals = approvalList.filter(item => { |
|||
const applyNo = item.applyNo || item.applicationNo |
|||
return !this.notifiedApplications.has(applyNo) |
|||
}) |
|||
|
|||
if (newApprovals.length === 0) { |
|||
return |
|||
} |
|||
|
|||
console.log(`[审批通知] 发现 ${newApprovals.length} 个新的待审批申请单`) |
|||
|
|||
// 显示每个新申请单的通知 |
|||
newApprovals.forEach((item, index) => { |
|||
// 延迟显示,避免多个通知同时弹出 |
|||
setTimeout(() => { |
|||
this.showSingleApprovalNotification(item) |
|||
}, index * 500) |
|||
}) |
|||
|
|||
// 播放提示音 |
|||
//this.playNotificationSound() |
|||
}, |
|||
|
|||
/** |
|||
* 显示单个审批通知 |
|||
* @param {Object} approvalItem - 审批项信息 |
|||
*/ |
|||
showSingleApprovalNotification(approvalItem) { |
|||
const applyNo = approvalItem.applyNo || approvalItem.applicationNo |
|||
const applicant = approvalItem.creatorName || approvalItem.applyUserName || '未知' |
|||
const applyType = approvalItem.experimentType || approvalItem.applicationType || '未知' |
|||
const createdDate = approvalItem.createTime || approvalItem.applyDate || '' |
|||
|
|||
// 记录已通知 |
|||
this.notifiedApplications.add(applyNo) |
|||
|
|||
// 创建通知实例 |
|||
const notificationInstance = this.$notify({ |
|||
title: '新的审批申请', |
|||
customClass: this.config.style.singleClass, |
|||
dangerouslyUseHTMLString: true, |
|||
message: ` |
|||
<div style="line-height: 1.8;"> |
|||
<p style="margin: 0 0 5px 0;"> |
|||
<strong style="color: #303133;">申请单号:</strong> |
|||
<span style="color: #409EFF;">${applyNo}</span> |
|||
</p> |
|||
<p style="margin: 0 0 5px 0;"> |
|||
<strong style="color: #303133;">申请人:</strong>${applicant} |
|||
</p> |
|||
<p style="margin: 0 0 5px 0;"> |
|||
<strong style="color: #303133;">申请类型:</strong>${applyType} |
|||
</p> |
|||
${createdDate ? ` |
|||
<p style="margin: 0 0 5px 0;"> |
|||
<strong style="color: #303133;">申请时间:</strong> |
|||
<span style="font-size: 12px;">${createdDate}</span> |
|||
</p> |
|||
` : ''} |
|||
<p style="margin: 8px 0 0 0; font-size: 12px; color: #909399; border-top: 1px dashed #DCDFE6; padding-top: 8px;"> |
|||
点击处理审批 |
|||
</p> |
|||
</div> |
|||
`, |
|||
type: 'info', |
|||
position: this.config.notification.position, |
|||
duration: this.config.notification.singleDuration, |
|||
showClose: this.config.notification.showClose, |
|||
onClick: () => { |
|||
// 点击后立即关闭通知并从列表中移除 |
|||
this.removeNotification(notificationInstance) |
|||
notificationInstance.close() |
|||
// 跳转到审批详情 |
|||
this.navigateToApprovalDetail(approvalItem) |
|||
}, |
|||
onClose: () => { |
|||
// 通知关闭时从列表中移除 |
|||
this.removeNotification(notificationInstance) |
|||
} |
|||
}) |
|||
|
|||
// 保存通知实例 |
|||
this.activeNotifications.push(notificationInstance) |
|||
console.log(`[审批通知] 已创建单个通知 (${applyNo}),当前活动通知数: ${this.activeNotifications.length}`) |
|||
}, |
|||
|
|||
/** |
|||
* 跳转到审批列表页面 |
|||
*/ |
|||
navigateToApprovalList() { |
|||
// 根据配置文件中的路由进行跳转 |
|||
this.$router.push({ |
|||
path: 'erf-expApplyApproval' |
|||
}).catch(err => { |
|||
this.log('路由跳转失败:', err.message, 'warn') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 跳转到审批详情页面 |
|||
* @param {Object} approvalItem - 审批项信息 |
|||
*/ |
|||
navigateToApprovalDetail(approvalItem) { |
|||
const applyNo = approvalItem.applyNo || approvalItem.applicationNo |
|||
|
|||
// 根据配置文件中的路由进行跳转 |
|||
const queryParam = this.config.routes.detailQueryParam |
|||
const query = { |
|||
[queryParam]: applyNo, |
|||
mode: 'approve' |
|||
} |
|||
|
|||
this.$router.push({ |
|||
path: 'erf-expApplyApproval', |
|||
query: query |
|||
}).catch(err => { |
|||
// 如果没有专门的审批详情页,跳转到列表页 |
|||
this.log('路由跳转失败,尝试跳转到列表页: ' + err.message, 'warn') |
|||
this.navigateToApprovalList() |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 播放通知提示音 |
|||
*/ |
|||
playNotificationSound() { |
|||
// 检查是否启用提示音 |
|||
if (!this.config.sound.enabled || !this.config.features.playSound) { |
|||
return |
|||
} |
|||
|
|||
try { |
|||
// 使用 Web Audio API 生成简单的提示音 |
|||
const audioContext = new (window.AudioContext || window.webkitAudioContext)() |
|||
const oscillator = audioContext.createOscillator() |
|||
const gainNode = audioContext.createGain() |
|||
|
|||
oscillator.connect(gainNode) |
|||
gainNode.connect(audioContext.destination) |
|||
|
|||
oscillator.frequency.value = this.config.sound.frequency // 频率 |
|||
oscillator.type = 'sine' // 正弦波 |
|||
|
|||
const duration = this.config.sound.duration |
|||
const volume = this.config.sound.volume |
|||
|
|||
gainNode.gain.setValueAtTime(volume, audioContext.currentTime) |
|||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration) |
|||
|
|||
oscillator.start(audioContext.currentTime) |
|||
oscillator.stop(audioContext.currentTime + duration) |
|||
} catch (error) { |
|||
this.log('播放提示音失败: ' + error.message, 'warn') |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 手动触发检查(供外部调用) |
|||
*/ |
|||
manualCheck() { |
|||
console.log('[审批通知] 手动触发检查') |
|||
this.checkPendingApprovals(false) |
|||
}, |
|||
|
|||
/** |
|||
* 清除指定申请单的通知记录(审批完成后调用) |
|||
* @param {string} applyNo - 申请单号 |
|||
*/ |
|||
clearNotification(applyNo) { |
|||
this.notifiedApplications.delete(applyNo) |
|||
this.log(`已清除申请单 ${applyNo} 的通知记录`, 'info') |
|||
}, |
|||
|
|||
/** |
|||
* 日志输出辅助方法 |
|||
* @param {string} message - 日志消息 |
|||
* @param {string} level - 日志级别 ('info', 'warn', 'error', 'debug') |
|||
*/ |
|||
log(message, level = 'info') { |
|||
if (!this.config.logging.enabled) { |
|||
return |
|||
} |
|||
|
|||
const prefix = this.config.logging.prefix |
|||
const fullMessage = `${prefix} ${message}` |
|||
|
|||
switch (level) { |
|||
case 'error': |
|||
console.error(fullMessage) |
|||
break |
|||
case 'warn': |
|||
console.warn(fullMessage) |
|||
break |
|||
case 'debug': |
|||
if (this.config.debug.enabled) { |
|||
console.debug(fullMessage) |
|||
} |
|||
break |
|||
case 'info': |
|||
default: |
|||
console.log(fullMessage) |
|||
break |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
/* 无需样式 */ |
|||
</style> |
|||
@ -0,0 +1,192 @@ |
|||
/** |
|||
* 审批通知系统配置文件 |
|||
* |
|||
* 可以在这里统一配置通知系统的各项参数 |
|||
* 修改后需要重新编译项目才能生效 |
|||
*/ |
|||
|
|||
export default { |
|||
/** |
|||
* 轮询配置 |
|||
*/ |
|||
polling: { |
|||
// 轮询间隔(毫秒)
|
|||
// 默认: 5分钟 (5 * 60 * 1000)
|
|||
// 建议: 不低于3分钟,避免频繁请求
|
|||
interval: 5 * 60 * 1000, |
|||
|
|||
// 首次检查延迟(毫秒)
|
|||
// 登录后延迟多久进行首次检查
|
|||
// 默认: 3秒
|
|||
firstCheckDelay: 3000, |
|||
|
|||
// 是否启用轮询
|
|||
// 设置为 false 可以禁用定时轮询功能
|
|||
enabled: true |
|||
}, |
|||
|
|||
/** |
|||
* 通知显示配置 |
|||
*/ |
|||
notification: { |
|||
// 通知位置
|
|||
// 可选值: 'top-right', 'top-left', 'bottom-right', 'bottom-left'
|
|||
position: 'bottom-right', |
|||
|
|||
// 汇总通知持续时间(毫秒)
|
|||
// 0 表示不自动关闭
|
|||
summaryDuration: 0, |
|||
|
|||
// 单个通知持续时间(毫秒)
|
|||
// 0 表示不自动关闭
|
|||
singleDuration: 10000, |
|||
|
|||
// 是否显示关闭按钮
|
|||
showClose: true, |
|||
|
|||
// 多个通知同时弹出时的间隔(毫秒)
|
|||
stackDelay: 500, |
|||
|
|||
// 最大同时显示的通知数量
|
|||
// 超过此数量的通知将排队显示
|
|||
maxStack: 3 |
|||
}, |
|||
|
|||
/** |
|||
* 提示音配置 |
|||
*/ |
|||
sound: { |
|||
// 是否启用提示音
|
|||
enabled: true, |
|||
|
|||
// 提示音频率(Hz)
|
|||
frequency: 800, |
|||
|
|||
// 提示音持续时间(秒)
|
|||
duration: 0.5, |
|||
|
|||
// 提示音音量(0-1)
|
|||
volume: 0.3 |
|||
}, |
|||
|
|||
/** |
|||
* 路由配置 |
|||
*/ |
|||
routes: { |
|||
// 审批列表页面路由
|
|||
approvalList: '/erf/approval-list', |
|||
|
|||
// 审批详情页面路由
|
|||
approvalDetail: '/erf/approval-detail', |
|||
|
|||
// 详情页面查询参数名称
|
|||
detailQueryParam: 'applyNo' |
|||
}, |
|||
|
|||
/** |
|||
* 日志配置 |
|||
*/ |
|||
logging: { |
|||
// 是否启用控制台日志
|
|||
enabled: true, |
|||
|
|||
// 日志前缀
|
|||
prefix: '[审批通知]', |
|||
|
|||
// 日志级别
|
|||
// 'debug', 'info', 'warn', 'error'
|
|||
level: 'info' |
|||
}, |
|||
|
|||
/** |
|||
* 样式配置 |
|||
*/ |
|||
style: { |
|||
// 汇总通知自定义样式类名
|
|||
summaryClass: 'approval-notification summary-notification', |
|||
|
|||
// 单个通知自定义样式类名
|
|||
singleClass: 'approval-notification new-approval-notification shake-animation', |
|||
|
|||
// 是否启用动画效果
|
|||
enableAnimation: true |
|||
}, |
|||
|
|||
/** |
|||
* API配置 |
|||
*/ |
|||
api: { |
|||
// 获取待审批列表的API路径
|
|||
pendingListUrl: '/erf/expApply/getPendingApplyList', |
|||
|
|||
// 请求超时时间(毫秒)
|
|||
timeout: 10000, |
|||
|
|||
// 请求失败后是否重试
|
|||
enableRetry: false, |
|||
|
|||
// 重试次数
|
|||
retryCount: 3, |
|||
|
|||
// 重试间隔(毫秒)
|
|||
retryDelay: 2000 |
|||
}, |
|||
|
|||
/** |
|||
* 功能开关 |
|||
*/ |
|||
features: { |
|||
// 是否在登录时检查待审批项
|
|||
checkOnLogin: true, |
|||
|
|||
// 是否在申请单下达后立即通知
|
|||
notifyOnSubmit: true, |
|||
|
|||
// 是否启用定时轮询
|
|||
enablePolling: true, |
|||
|
|||
// 是否避免重复通知
|
|||
preventDuplicate: true, |
|||
|
|||
// 是否在所有页面显示通知
|
|||
globalNotification: true, |
|||
|
|||
// 是否播放提示音
|
|||
playSound: true |
|||
}, |
|||
|
|||
/** |
|||
* 用户权限配置 |
|||
*/ |
|||
permission: { |
|||
// 哪些角色可以接收审批通知
|
|||
// 空数组表示所有用户都可以接收
|
|||
allowedRoles: [], |
|||
|
|||
// 哪些用户可以接收审批通知
|
|||
// 空数组表示所有用户都可以接收
|
|||
allowedUsers: [] |
|||
}, |
|||
|
|||
/** |
|||
* 调试配置 |
|||
*/ |
|||
debug: { |
|||
// 是否启用调试模式
|
|||
enabled: false, |
|||
|
|||
// 是否在控制台输出详细信息
|
|||
verbose: false, |
|||
|
|||
// 是否模拟通知(用于测试)
|
|||
mockNotification: false, |
|||
|
|||
// 模拟数据
|
|||
mockData: { |
|||
applyNo: 'TEST202501150001', |
|||
applicant: '测试用户', |
|||
applyType: 'High Risk', |
|||
createdDate: '2025-01-15 10:30:00' |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
<template> |
|||
<div class="approval-history"> |
|||
<el-timeline> |
|||
<el-timeline-item |
|||
v-for="(item, index) in historyList" |
|||
:key="index" |
|||
:timestamp="item.logTime" |
|||
:type="getTimelineType(item.action)" |
|||
placement="top"> |
|||
|
|||
<el-card> |
|||
<h4>{{ getActionText(item.action) }}</h4> |
|||
<p> |
|||
<el-tag :type="getActionTagType(item.action)" size="small"> |
|||
{{ getActionText(item.action) }} |
|||
</el-tag> |
|||
<span style="margin-left: 10px;">操作人:{{ item.operatorName }}</span> |
|||
</p> |
|||
<p v-if="item.nodeCode"> |
|||
<span>节点:{{ getNodeName(item.nodeCode) }}</span> |
|||
</p> |
|||
<p v-if="item.comment" style="color: #666; margin-top: 8px;"> |
|||
意见:{{ item.comment }} |
|||
</p> |
|||
</el-card> |
|||
</el-timeline-item> |
|||
</el-timeline> |
|||
|
|||
<el-empty v-if="historyList.length === 0" description="暂无审批历史"></el-empty> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getApprovalHistory } from '@/api/erf/erf' |
|||
|
|||
export default { |
|||
name: 'ApprovalHistory', |
|||
|
|||
props: { |
|||
applyNo: { |
|||
type: String, |
|||
required: true |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
historyList: [], |
|||
loading: false |
|||
} |
|||
}, |
|||
|
|||
mounted() { |
|||
this.loadHistory() |
|||
}, |
|||
|
|||
watch: { |
|||
applyNo(newVal) { |
|||
if (newVal) { |
|||
this.loadHistory() |
|||
} |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 加载审批历史 |
|||
*/ |
|||
loadHistory() { |
|||
if (!this.applyNo) { |
|||
return |
|||
} |
|||
|
|||
this.loading = true |
|||
getApprovalHistory({ applyNo: this.applyNo }).then(({data}) => { |
|||
this.loading = false |
|||
if (data && data.code === 0) { |
|||
this.historyList = data.list || [] |
|||
} |
|||
}).catch(error => { |
|||
this.loading = false |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 获取操作文本 |
|||
*/ |
|||
getActionText(action) { |
|||
const texts = { |
|||
'SUBMIT': '提交申请', |
|||
'APPROVE': '审批通过', |
|||
'REJECT': '审批驳回', |
|||
'WITHDRAW': '撤回申请', |
|||
'SCHEDULE': '计划排产', |
|||
'CONFIRM': '三方确认', |
|||
'COMPLETE': '完成流程' |
|||
} |
|||
return texts[action] || action |
|||
}, |
|||
|
|||
/** |
|||
* 获取时间线类型 |
|||
*/ |
|||
getTimelineType(action) { |
|||
const types = { |
|||
'SUBMIT': 'primary', |
|||
'APPROVE': 'success', |
|||
'REJECT': 'danger', |
|||
'WITHDRAW': 'warning', |
|||
'SCHEDULE': 'primary', |
|||
'CONFIRM': 'success', |
|||
'COMPLETE': 'success' |
|||
} |
|||
return types[action] || 'info' |
|||
}, |
|||
|
|||
/** |
|||
* 获取标签类型 |
|||
*/ |
|||
getActionTagType(action) { |
|||
const types = { |
|||
'SUBMIT': '', |
|||
'APPROVE': 'success', |
|||
'REJECT': 'danger', |
|||
'WITHDRAW': 'warning', |
|||
'SCHEDULE': '', |
|||
'CONFIRM': 'success', |
|||
'COMPLETE': 'success' |
|||
} |
|||
return types[action] || 'info' |
|||
}, |
|||
|
|||
/** |
|||
* 获取节点名称 |
|||
*/ |
|||
getNodeName(nodeCode) { |
|||
const names = { |
|||
'TECH_MANAGER_APPROVAL': '技术经理审批', |
|||
'PROD_MANAGER_APPROVAL': '生产经理审批', |
|||
'PLANNER_SCHEDULE': '计划员排产', |
|||
'TRI_CONFIRM': 'High Risk三方确认', |
|||
'COMPLETE': '流程完成' |
|||
} |
|||
return names[nodeCode] || nodeCode |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.approval-history { |
|||
padding: 20px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,537 @@ |
|||
<template> |
|||
<div class="erf-attachment-manager"> |
|||
<!-- 操作按钮 --> |
|||
<div style="margin-bottom: 10px"> |
|||
<el-button |
|||
type="primary" |
|||
size="small" |
|||
v-if="!disabled" |
|||
@click="handleUpload"> |
|||
上传附件 |
|||
</el-button> |
|||
<el-button |
|||
type="primary" |
|||
size="small" |
|||
@click="handleDownload"> |
|||
下载 |
|||
</el-button> |
|||
</div> |
|||
|
|||
<!-- 附件列表表格 --> |
|||
<el-table |
|||
:height="height" |
|||
:data="dataList" |
|||
ref="table" |
|||
v-loading="queryLoading" |
|||
border |
|||
@selection-change="handleSelectionChange" |
|||
style="width: 100%;"> |
|||
|
|||
<el-table-column type="selection" width="55" align="center"/> |
|||
|
|||
<el-table-column |
|||
prop="fileName" |
|||
label="文件名" |
|||
min-width="200" |
|||
align="left" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="fileType" |
|||
label="文件类型" |
|||
min-width="100" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="createdBy" |
|||
label="上传人" |
|||
min-width="100" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="createDate" |
|||
label="上传时间" |
|||
min-width="160" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="orderRef5" |
|||
label="备注" |
|||
min-width="160" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
label="操作" |
|||
width="150" |
|||
align="center" |
|||
header-align="center" |
|||
fixed="right"> |
|||
<template slot-scope="{row}"> |
|||
<el-link |
|||
style="cursor:pointer; margin-right: 10px;" |
|||
@click="previewFile(row)"> |
|||
预览 |
|||
</el-link> |
|||
<el-link |
|||
style="cursor:pointer; color: #F56C6C;" |
|||
v-if="!disabled" |
|||
@click="handleRemove(row)"> |
|||
删除 |
|||
</el-link> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<!-- 上传对话框 --> |
|||
<el-dialog |
|||
title="上传附件" |
|||
:visible.sync="ossVisible" |
|||
width="380px" |
|||
append-to-body |
|||
:close-on-click-modal="false"> |
|||
|
|||
<el-form :inline="true" label-position="top" label-width="80px"> |
|||
<el-row> |
|||
<el-form-item label="申请单号"> |
|||
<el-input v-model="ossForm.orderRef2" readonly style="width: 310px"></el-input> |
|||
</el-form-item> |
|||
</el-row> |
|||
</el-form> |
|||
|
|||
<!-- 文件上传区域 --> |
|||
<div style="margin: 15px 0;"> |
|||
<div style="margin-bottom: 10px;"> |
|||
选择文件: |
|||
<span style="color: #67C23A; font-size: 12px; margin-left: 10px;"> |
|||
<i class="el-icon-picture-outline"></i> 可支持直接 Ctrl+V 粘贴图片,不需要点击上传按钮 |
|||
</span> |
|||
</div> |
|||
<el-upload |
|||
drag |
|||
action="#" |
|||
ref="upload" |
|||
:file-list="fileList" |
|||
:on-remove="onRemoveFile" |
|||
:on-change="onChangeFile" |
|||
multiple |
|||
:auto-upload="false" |
|||
style="text-align: left;"> |
|||
<i class="el-icon-upload"></i> |
|||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em>,或<em style="color: #67C23A;">Ctrl+V 粘贴图片</em></div> |
|||
<div class="el-upload__tip" slot="tip"> |
|||
支持pdf、dwg、dxf、doc、docx、xls、xlsx、jpg、png格式 |
|||
</div> |
|||
</el-upload> |
|||
</div> |
|||
|
|||
<!-- 备注区域 --> |
|||
<div style="margin: 15px 0;"> |
|||
<div style="margin-bottom: 10px;">备注:</div> |
|||
<el-input |
|||
type="textarea" |
|||
v-model="ossForm.remark" |
|||
resize="none" |
|||
:autosize="{minRows: 2, maxRows: 2}"> |
|||
</el-input> |
|||
</div> |
|||
|
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button type="primary" :loading="uploadLoading" @click="handleUploadFiles">保存</el-button> |
|||
<el-button @click="ossVisible = false">关闭</el-button> |
|||
</span> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { ossUpload, queryOss, removeOss, previewOssFileById, previewOssFileById2 } from '@/api/oss/oss' |
|||
|
|||
export default { |
|||
name: 'ErfAttachmentManager', |
|||
|
|||
props: { |
|||
// 申请单号 |
|||
applyNo: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
// 是否禁用编辑 |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 表格高度 |
|||
height: { |
|||
type: [String, Number], |
|||
default: '30vh' |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
dataList: [], |
|||
queryLoading: false, |
|||
uploadLoading: false, |
|||
selectionDataList: [], |
|||
ossVisible: false, |
|||
ossForm: { |
|||
orderRef1: 'ERF', // 模块标识 |
|||
orderRef2: '', // 申请单号 |
|||
orderRef6: 'EXP_APPLY', // 业务类型标识 |
|||
remark: '' |
|||
}, |
|||
fileList: [], |
|||
pasteImageCounter: 0 // 粘贴图片计数器,用于生成文件名 |
|||
} |
|||
}, |
|||
|
|||
mounted() { |
|||
this.handleQuery() |
|||
}, |
|||
|
|||
watch: { |
|||
applyNo(newVal) { |
|||
if (newVal) { |
|||
this.handleQuery() |
|||
} |
|||
}, |
|||
// 监听对话框关闭,移除粘贴事件监听器 |
|||
ossVisible(newVal) { |
|||
if (!newVal) { |
|||
document.removeEventListener('paste', this.handlePaste) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
beforeDestroy() { |
|||
// 组件销毁时移除监听器 |
|||
document.removeEventListener('paste', this.handlePaste) |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 查询附件列表 |
|||
*/ |
|||
handleQuery() { |
|||
if (!this.applyNo) { |
|||
return |
|||
} |
|||
|
|||
let params = { |
|||
orderRef1: 'ERF', |
|||
orderRef2: this.applyNo, |
|||
orderRef6: 'EXP_APPLY' |
|||
} |
|||
|
|||
this.queryLoading = true |
|||
queryOss(params).then(({data}) => { |
|||
this.queryLoading = false |
|||
if (data && data.code === 0) { |
|||
this.dataList = data.rows || [] |
|||
} else { |
|||
this.dataList = [] |
|||
this.$message.warning(data.msg || '查询失败') |
|||
} |
|||
}).catch(error => { |
|||
this.queryLoading = false |
|||
this.$message.error('查询异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 打开上传对话框 |
|||
*/ |
|||
handleUpload() { |
|||
this.$nextTick(() => { |
|||
if (this.$refs.upload) { |
|||
this.$refs.upload.clearFiles() |
|||
} |
|||
}) |
|||
this.fileList = [] |
|||
this.ossForm.orderRef2 = this.applyNo |
|||
this.ossForm.remark = '' |
|||
this.ossVisible = true |
|||
this.pasteImageCounter = 0 |
|||
|
|||
// 添加粘贴事件监听器 |
|||
this.$nextTick(() => { |
|||
document.addEventListener('paste', this.handlePaste) |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 文件选择变化 |
|||
*/ |
|||
onRemoveFile(file, fileList) { |
|||
this.fileList = fileList |
|||
}, |
|||
|
|||
onChangeFile(file, fileList) { |
|||
this.fileList = fileList |
|||
}, |
|||
|
|||
/** |
|||
* 处理粘贴事件(支持直接粘贴图片) |
|||
*/ |
|||
handlePaste(event) { |
|||
// 只在对话框打开时处理粘贴 |
|||
if (!this.ossVisible) { |
|||
return |
|||
} |
|||
|
|||
const items = (event.clipboardData || event.originalEvent.clipboardData).items |
|||
|
|||
for (let i = 0; i < items.length; i++) { |
|||
const item = items[i] |
|||
|
|||
// 检查是否为图片类型 |
|||
if (item.type.indexOf('image') !== -1) { |
|||
event.preventDefault() // 阻止默认粘贴行为 |
|||
|
|||
const blob = item.getAsFile() |
|||
if (!blob) continue |
|||
|
|||
// 获取文件扩展名 |
|||
const extension = blob.type.split('/')[1] || 'png' |
|||
|
|||
// 尝试使用原始文件名,如果没有则使用简洁的默认名称 |
|||
let fileName = blob.name |
|||
if (!fileName || fileName === 'image.png' || fileName === 'blob') { |
|||
// 截图或无名称的情况,使用简洁的默认名称 |
|||
this.pasteImageCounter++ |
|||
fileName = `粘贴图片${this.pasteImageCounter}.${extension}` |
|||
} |
|||
|
|||
// 创建 File 对象(保留原始文件名) |
|||
const file = new File([blob], fileName, { type: blob.type }) |
|||
|
|||
// 创建一个符合 el-upload 格式的文件对象 |
|||
const uploadFile = { |
|||
name: fileName, |
|||
size: file.size, |
|||
type: file.type, |
|||
raw: file, |
|||
uid: Date.now() + this.pasteImageCounter, |
|||
status: 'ready' |
|||
} |
|||
|
|||
// 添加到文件列表 |
|||
this.fileList.push(uploadFile) |
|||
|
|||
// 手动触发 el-upload 的文件列表更新 |
|||
if (this.$refs.upload) { |
|||
this.$refs.upload.uploadFiles.push(uploadFile) |
|||
} |
|||
|
|||
this.$message.success(`已粘贴图片: ${fileName}`) |
|||
|
|||
break // 只处理第一张图片 |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 执行上传 |
|||
*/ |
|||
handleUploadFiles() { |
|||
if (this.fileList.length === 0) { |
|||
this.$message.error('请选择文件') |
|||
return |
|||
} |
|||
|
|||
let formData = new FormData() |
|||
for (let i = 0; i < this.fileList.length; i++) { |
|||
formData.append('file', this.fileList[i].raw) |
|||
} |
|||
formData.append('orderRef1', 'ERF') |
|||
formData.append('orderRef2', this.ossForm.orderRef2) |
|||
formData.append('orderRef6', 'EXP_APPLY') |
|||
formData.append('createdBy', this.$store.state.user.name) |
|||
formData.append('orderRef5', this.ossForm.remark) |
|||
|
|||
this.uploadLoading = true |
|||
ossUpload(formData).then(({data}) => { |
|||
this.uploadLoading = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('上传成功') |
|||
this.handleQuery() |
|||
this.ossVisible = false |
|||
} else { |
|||
this.$message.warning(data.msg || '上传失败') |
|||
} |
|||
}).catch(error => { |
|||
this.uploadLoading = false |
|||
this.$message.error('上传异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 删除附件 |
|||
*/ |
|||
handleRemove(row) { |
|||
this.$confirm('确认删除该附件吗?', '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
type: 'warning' |
|||
}).then(() => { |
|||
let ids = [row.id] |
|||
removeOss(ids).then(({data}) => { |
|||
if (data && data.code === 0) { |
|||
this.$message.success('删除成功') |
|||
this.handleQuery() |
|||
} else { |
|||
this.$message.warning(data.msg || '删除失败') |
|||
} |
|||
}).catch(error => { |
|||
this.$message.error('删除异常') |
|||
}) |
|||
}).catch(() => {}) |
|||
}, |
|||
|
|||
/** |
|||
* 预览文件 |
|||
*/ |
|||
previewFile(row) { |
|||
let type = '' |
|||
let fileType = row.fileType.toLowerCase() |
|||
|
|||
// 图片类型 |
|||
let image = ['jpg', 'jpeg', 'png', 'gif', 'bmp'] |
|||
if (image.includes(fileType)) { |
|||
type = 'image/' + fileType |
|||
} |
|||
|
|||
// 视频类型 |
|||
let video = ['mp4', 'avi', 'mov', 'wmv', 'flv'] |
|||
if (video.includes(fileType)) { |
|||
type = 'video/' + fileType |
|||
} |
|||
|
|||
// 文本类型 |
|||
if (fileType === 'txt') { |
|||
type = 'text/plain;charset=utf-8' |
|||
} |
|||
|
|||
// Excel类型 |
|||
if (fileType === 'xlsx' || fileType === 'xls') { |
|||
type = 'excel' |
|||
} |
|||
|
|||
// Word类型 |
|||
if (fileType === 'docx') { |
|||
type = 'word' |
|||
} |
|||
|
|||
// PDF类型 |
|||
if (fileType === 'pdf') { |
|||
type = 'application/pdf;charset-UTF-8' |
|||
} |
|||
|
|||
// Office文件不支持预览 |
|||
if (fileType === 'doc' || fileType === 'ppt' || fileType === 'pptx') { |
|||
this.$message.warning('该文件格式暂不支持预览,请下载后查看') |
|||
return |
|||
} |
|||
|
|||
// CAD文件不支持预览 |
|||
if (fileType === 'dwg' || fileType === 'dxf') { |
|||
this.$message.warning('CAD文件暂不支持预览,请下载后使用CAD软件查看') |
|||
return |
|||
} |
|||
|
|||
if (type === '') { |
|||
this.$message.warning('该文件格式暂不支持预览') |
|||
return |
|||
} |
|||
|
|||
let params = { |
|||
id: row.id, |
|||
fileType: type |
|||
} |
|||
|
|||
previewOssFileById2(params).then(({data}) => { |
|||
if (type === 'excel' || type === 'word') { |
|||
type = 'application/pdf;charset=UTF-8' |
|||
} |
|||
const blob = new Blob([data], { type: type }) |
|||
const fileURL = URL.createObjectURL(blob) |
|||
|
|||
if (type === 'xls' || type === 'docx' || type === 'xlsx') { |
|||
const { href } = this.$router.resolve({ |
|||
name: 'pre', |
|||
query: { |
|||
src: fileURL, |
|||
type: 'pdf' |
|||
} |
|||
}) |
|||
window.open(href, '_blank') |
|||
} else { |
|||
// 在新标签页中打开文件预览 |
|||
window.open(fileURL, '_blank') |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 选择变化 |
|||
*/ |
|||
handleSelectionChange(val) { |
|||
this.selectionDataList = val |
|||
}, |
|||
|
|||
/** |
|||
* 下载选中的附件 |
|||
*/ |
|||
handleDownload() { |
|||
if (this.selectionDataList.length === 0) { |
|||
this.$message.warning('请选择要下载的附件') |
|||
return |
|||
} |
|||
|
|||
let selectList = this.selectionDataList.map(item => ({ ...item })) |
|||
for (let i = 0; i < selectList.length; i++) { |
|||
let params = { |
|||
id: selectList[i].id |
|||
} |
|||
previewOssFileById(params).then((response) => { |
|||
const blob = new Blob([response.data], { type: response.headers['content-type'] }) |
|||
const link = document.createElement('a') |
|||
link.href = URL.createObjectURL(blob) |
|||
link.setAttribute('download', selectList[i].fileName) |
|||
link.target = '_blank' |
|||
link.click() |
|||
URL.revokeObjectURL(link.href) |
|||
}) |
|||
} |
|||
|
|||
this.$refs.table.clearSelection() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.erf-attachment-manager { |
|||
|
|||
} |
|||
/* 表格样式优化 */ |
|||
.el-table >>> .el-table__header th { |
|||
background-color: #F5F7FA; |
|||
color: #606266; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.el-table >>> .el-table__row td { |
|||
padding: 8px 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,354 @@ |
|||
<template> |
|||
<div class="exp-apply-form"> |
|||
<el-form |
|||
:model="saveHeaderData" |
|||
:rules="formRules" |
|||
ref="applyForm" |
|||
label-position="top" |
|||
style="margin-left: 5px; margin-top: -5px;"> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="6"> |
|||
<el-form-item label="事业部" prop="buNo"> |
|||
<el-select |
|||
v-model="saveHeaderData.buNo" |
|||
:disabled="readonly || isEdit" |
|||
placeholder="请选择事业部" |
|||
style="width: 100%"> |
|||
<el-option |
|||
v-for="i in buList" |
|||
:key="i.buNo" |
|||
:label="i.buDesc" |
|||
:value="i.buNo"> |
|||
</el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="6"> |
|||
<el-form-item label="试验类型" prop="experimentType"> |
|||
<el-select |
|||
v-model="saveHeaderData.experimentType" |
|||
:disabled="readonly" |
|||
placeholder="请选择试验类型" |
|||
style="width: 100%"> |
|||
<el-option label="High Risk" value="High Risk"></el-option> |
|||
<el-option label="Low Risk" value="Low Risk"></el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="6"> |
|||
<el-form-item label="项目编号" prop="projectNo"> |
|||
<el-input |
|||
v-model="saveHeaderData.projectNo" |
|||
:readonly="readonly" |
|||
placeholder="请输入项目编号"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="6"> |
|||
<el-form-item label="期望完成日期" prop="expectedFinishDate"> |
|||
<el-date-picker |
|||
v-model="saveHeaderData.expectedFinishDate" |
|||
:disabled="readonly" |
|||
type="date" |
|||
format="yyyy-MM-dd" |
|||
value-format="yyyy-MM-dd" |
|||
placeholder="选择日期" |
|||
style="width: 100%"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="试验名称" prop="title"> |
|||
<el-input |
|||
v-model="saveHeaderData.title" |
|||
:readonly="readonly" |
|||
placeholder="请输入试验名称"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="试验目的" prop="purpose"> |
|||
<el-input |
|||
v-model="saveHeaderData.purpose" |
|||
:readonly="readonly" |
|||
placeholder="请输入试验目的"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="验证方法及判断标准" prop="justification"> |
|||
<el-input |
|||
v-model="saveHeaderData.justification" |
|||
:readonly="readonly" |
|||
placeholder="请输入验证方法及判断标准"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="试验产品型号"> |
|||
<el-input |
|||
v-model="saveHeaderData.productType" |
|||
:readonly="readonly" |
|||
placeholder="请输入产品型号"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="12"> |
|||
<el-form-item label="申请数量"> |
|||
<el-input |
|||
v-model="saveHeaderData.quantityReq" |
|||
:readonly="readonly" |
|||
placeholder="请输入申请数量描述"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="试验负责人"> |
|||
<el-input |
|||
v-model="saveHeaderData.projectLeader" |
|||
:readonly="readonly" |
|||
placeholder="请输入试验负责人"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="12"> |
|||
<el-form-item label="联系方式"> |
|||
<el-input |
|||
v-model="saveHeaderData.contactMethod" |
|||
:readonly="readonly" |
|||
placeholder="请输入联系方式"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="工艺、取样、测试要求"> |
|||
<el-input |
|||
v-model="saveHeaderData.processRequirement" |
|||
:readonly="readonly" |
|||
type="textarea" |
|||
:rows="3" |
|||
placeholder="请输入工艺、取样、测试要求"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<!-- 只读模式下显示的附加信息 --> |
|||
<template v-if="readonly && saveHeaderData.applyNo"> |
|||
<el-divider content-position="left">申请单信息</el-divider> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="8"> |
|||
<el-form-item label="申请单号"> |
|||
<span>{{ saveHeaderData.applyNo }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="8"> |
|||
<el-form-item label="状态"> |
|||
<el-tag :type="getStatusType(saveHeaderData.status)"> |
|||
{{ getStatusText(saveHeaderData.status) }} |
|||
</el-tag> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="8"> |
|||
<el-form-item label="创建人"> |
|||
<span>{{ saveHeaderData.creatorName }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="创建时间"> |
|||
<span>{{ saveHeaderData.createTime }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="12"> |
|||
<el-form-item label="更新时间"> |
|||
<span>{{ saveHeaderData.updateTime }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</template> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { saveExpApply } from '@/api/erf/erf' |
|||
import { getBuList } from '@/api/factory/site' |
|||
|
|||
export default { |
|||
name: 'ExpApplyForm', |
|||
|
|||
components: {}, |
|||
|
|||
props: { |
|||
applyData: { |
|||
type: Object, |
|||
default: () => ({}) |
|||
}, |
|||
readonly: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
buList: [], |
|||
saveHeaderData: { |
|||
applyNo: '', |
|||
buNo: '', |
|||
experimentType: '', |
|||
projectNo: '', |
|||
title: '', |
|||
purpose: '', |
|||
justification: '', |
|||
productType: '', |
|||
quantityReq: '', |
|||
expectedFinishDate: '', |
|||
projectLeader: '', |
|||
contactMethod: '', |
|||
processRequirement: '', |
|||
creatorUserId: this.$store.state.user.id, |
|||
creatorName: this.$store.state.user.name |
|||
}, |
|||
|
|||
formRules: { |
|||
buNo: [ |
|||
{ required: true, message: '请选择事业部', trigger: 'change' } |
|||
], |
|||
experimentType: [ |
|||
{ required: true, message: '请选择试验类型', trigger: 'change' } |
|||
], |
|||
title: [ |
|||
{ required: true, message: '请输入试验名称', trigger: 'blur' } |
|||
], |
|||
expectedFinishDate: [ |
|||
{ required: true, message: '请选择期望完成日期', trigger: 'change' } |
|||
] |
|||
}, |
|||
|
|||
isEdit: false |
|||
} |
|||
}, |
|||
|
|||
mounted() { |
|||
this.loadBuList() |
|||
if (this.applyData && this.applyData.applyNo) { |
|||
this.saveHeaderData = { ...this.applyData } |
|||
this.isEdit = true |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 加载事业部列表 |
|||
*/ |
|||
loadBuList() { |
|||
const tempData = { site: this.$store.state.user.site } |
|||
getBuList(tempData).then(({data}) => { |
|||
if (data.code === 0) { |
|||
this.buList = data.row1 |
|||
if (data.row1.length === 1) { |
|||
this.saveHeaderData.buNo = data.row1[0].buNo |
|||
} |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 获取表单数据(用于验证) |
|||
*/ |
|||
getFormData() { |
|||
let valid = false |
|||
this.$refs.applyForm.validate(v => { |
|||
valid = v |
|||
}) |
|||
return valid ? this.saveHeaderData : null |
|||
}, |
|||
|
|||
/** |
|||
* 保存表单 |
|||
*/ |
|||
save() { |
|||
return new Promise((resolve, reject) => { |
|||
this.$refs.applyForm.validate(valid => { |
|||
if (!valid) { |
|||
reject('表单验证失败') |
|||
return |
|||
} |
|||
|
|||
saveExpApply(this.saveHeaderData).then(({data}) => { |
|||
if (data && data.code === 0) { |
|||
resolve(data) |
|||
} else { |
|||
this.$message.error(data.msg || '保存失败') |
|||
reject(data.msg) |
|||
} |
|||
}).catch(error => { |
|||
reject(error) |
|||
}) |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 获取状态类型 |
|||
*/ |
|||
getStatusType(status) { |
|||
const types = { |
|||
'草稿': 'info', |
|||
'已提交': 'warning', |
|||
'已批准': 'success', |
|||
'生产中': '', |
|||
'已完成': 'success', |
|||
'已取消': 'danger', |
|||
'已驳回': 'danger' |
|||
} |
|||
return types[status] || 'info' |
|||
}, |
|||
|
|||
/** |
|||
* 获取状态文本(直接返回中文状态) |
|||
*/ |
|||
getStatusText(status) { |
|||
return status || '' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.exp-apply-form { |
|||
padding: 10px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,344 @@ |
|||
<template> |
|||
<div class="exp-project-detail"> |
|||
<!-- 顶部操作按钮 --> |
|||
<div class="toolbar"> |
|||
<el-button |
|||
v-if="!editing && (status==='草稿' || status==='已驳回')" |
|||
@click="handleEdit" |
|||
type="primary" |
|||
size="small"> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
v-if="editing" |
|||
@click="handleSave" |
|||
type="primary" |
|||
size="small" |
|||
:loading="saving"> |
|||
保存 |
|||
</el-button> |
|||
<el-button |
|||
v-if="editing" |
|||
@click="handleCancel" |
|||
size="small"> |
|||
取消 |
|||
</el-button> |
|||
</div> |
|||
|
|||
<!-- 项目详情表单 - label在上,input在下 --> |
|||
<el-form |
|||
:model="formData" |
|||
ref="detailForm" |
|||
label-position="top" |
|||
v-loading="loading"> |
|||
|
|||
<el-row :gutter="20"> |
|||
<!-- 左侧列 --> |
|||
<el-col :span="10"> |
|||
<el-form-item label="试验名称(Name of the experiment)"> |
|||
<el-input |
|||
v-model="formData.title" |
|||
:readonly="!editing" |
|||
placeholder="请输入试验名称"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="试验目的(Purpose of the experiment)"> |
|||
<el-input |
|||
v-model="formData.purpose" |
|||
:readonly="!editing" |
|||
placeholder="请输入试验目的"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="验证方法及判断标准(Experiment Justification)"> |
|||
<el-input |
|||
v-model="formData.justification" |
|||
:readonly="!editing" |
|||
placeholder="请输入验证方法及判断标准"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="试验产品型号(Products'type)"> |
|||
<el-input |
|||
v-model="formData.productType" |
|||
:readonly="!editing" |
|||
placeholder="请输入试验产品型号"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="期望完成日期(Desired finish date)"> |
|||
<el-date-picker |
|||
v-model="formData.expectedFinishDate" |
|||
:disabled="!editing" |
|||
type="date" |
|||
format="yyyy/MM/dd" |
|||
value-format="yyyy-MM-dd" |
|||
placeholder="选择日期" |
|||
style="width: 100%"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<!-- 右侧大文本框 --> |
|||
<el-col :span="14"> |
|||
<el-form-item label="试验申请数量(Quantity requirement)"> |
|||
<el-input |
|||
v-model="formData.quantityReq" |
|||
type="textarea" |
|||
:readonly="!editing" |
|||
:rows="2" |
|||
placeholder="请输入试验申请数量"> |
|||
</el-input> |
|||
</el-form-item> |
|||
<el-form-item style="margin-top: 35px" label="工艺、取样、测试要求(PartIII Process,Sampling,and Test Requirement)"> |
|||
<el-input |
|||
v-model="formData.processRequirement" |
|||
type="textarea" |
|||
:readonly="!editing" |
|||
:rows="9" |
|||
placeholder="请输入工艺、取样、测试要求"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<!-- 底部:试验负责人信息 --> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="10"> |
|||
<el-form-item label="试验负责人(Project leader)"> |
|||
<el-input |
|||
v-model="formData.projectLeader" |
|||
:readonly="!editing" |
|||
placeholder="请输入试验负责人"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="14"> |
|||
<el-form-item label="联系方式(Contact method)"> |
|||
<el-input |
|||
v-model="formData.contactMethod" |
|||
:readonly="!editing" |
|||
placeholder="请输入联系方式"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getExpApplyDetail, saveExpApply } from '@/api/erf/erf' |
|||
|
|||
export default { |
|||
name: 'ExpProjectDetail', |
|||
|
|||
props: { |
|||
applyNo: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
status: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
height: { |
|||
type: String, |
|||
default: '35vh' |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
formData: { |
|||
applyNo: '', |
|||
buNo: '', |
|||
experimentType: '', |
|||
projectNo: '', |
|||
title: '', |
|||
purpose: '', |
|||
justification: '', |
|||
productType: '', |
|||
quantityReq: '', |
|||
expectedFinishDate: '', |
|||
projectLeader: '', |
|||
contactMethod: '', |
|||
processRequirement: '', |
|||
status: '', |
|||
creatorUserId: '', |
|||
creatorName: '', |
|||
createTime: '', |
|||
updateTime: '', |
|||
plannerUserId: '', |
|||
plannerName: '', |
|||
workOrderNo: '', |
|||
scheduledDate: '' |
|||
}, |
|||
originalData: {}, // 保存原始数据,用于取消时恢复 |
|||
loading: false, |
|||
editing: false, |
|||
saving: false |
|||
} |
|||
}, |
|||
|
|||
watch: { |
|||
applyNo: { |
|||
immediate: true, |
|||
handler(val) { |
|||
if (val) { |
|||
this.editing = false |
|||
this.loadDetail() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 加载项目详情 |
|||
*/ |
|||
loadDetail() { |
|||
this.loading = true |
|||
getExpApplyDetail({ applyNo: this.applyNo }).then(({data}) => { |
|||
this.loading = false |
|||
if (data && data.code === 0) { |
|||
this.formData = { ...this.formData, ...data.data } |
|||
this.originalData = JSON.parse(JSON.stringify(this.formData)) |
|||
} else { |
|||
this.$message.error(data.msg || '加载详情失败') |
|||
} |
|||
}).catch(error => { |
|||
this.loading = false |
|||
this.$message.error('加载详情异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 进入编辑模式 |
|||
*/ |
|||
handleEdit() { |
|||
this.editing = true |
|||
this.originalData = JSON.parse(JSON.stringify(this.formData)) |
|||
}, |
|||
|
|||
/** |
|||
* 保存修改 |
|||
*/ |
|||
handleSave() { |
|||
// 数据验证 |
|||
if (!this.formData.title) { |
|||
this.$message.warning('请输入试验名称') |
|||
return |
|||
} |
|||
if (!this.formData.expectedFinishDate) { |
|||
this.$message.warning('请选择期望完成日期') |
|||
return |
|||
} |
|||
|
|||
this.saving = true |
|||
|
|||
// 准备保存数据 |
|||
const saveData = { |
|||
applyNo: this.formData.applyNo, |
|||
buNo: this.formData.buNo, |
|||
experimentType: this.formData.experimentType, |
|||
projectNo: this.formData.projectNo, |
|||
title: this.formData.title, |
|||
purpose: this.formData.purpose, |
|||
justification: this.formData.justification, |
|||
productType: this.formData.productType, |
|||
quantityReq: this.formData.quantityReq, |
|||
expectedFinishDate: this.formData.expectedFinishDate, |
|||
projectLeader: this.formData.projectLeader, |
|||
contactMethod: this.formData.contactMethod, |
|||
processRequirement: this.formData.processRequirement |
|||
} |
|||
|
|||
saveExpApply(saveData).then(({data}) => { |
|||
this.saving = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('保存成功') |
|||
this.editing = false |
|||
this.loadDetail() |
|||
// 通知父组件刷新列表 |
|||
this.$emit('refresh') |
|||
} else { |
|||
this.$message.error(data.msg || '保存失败') |
|||
} |
|||
}).catch(error => { |
|||
this.saving = false |
|||
this.$message.error('保存异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 取消编辑 |
|||
*/ |
|||
handleCancel() { |
|||
this.$confirm('取消编辑将放弃所有修改,确定取消?', '操作提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
type: 'warning' |
|||
}).then(() => { |
|||
this.formData = JSON.parse(JSON.stringify(this.originalData)) |
|||
this.editing = false |
|||
}).catch(() => { |
|||
// 用户点击了取消 |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.exp-project-detail { |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
overflow-x: hidden; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.toolbar { |
|||
margin-bottom: 5px; |
|||
padding-bottom: 5px; |
|||
border-bottom: 1px solid #EBEEF5; |
|||
text-align: left; |
|||
} |
|||
|
|||
.el-form-item { |
|||
margin-bottom: 15px; |
|||
} |
|||
|
|||
/* 只读状态的input样式 */ |
|||
.el-input >>> input[readonly] { |
|||
background-color: #F5F7FA; |
|||
border-color: #DCDFE6; |
|||
color: #606266; |
|||
cursor: default; |
|||
} |
|||
|
|||
.el-input >>> textarea[readonly] { |
|||
background-color: #F5F7FA; |
|||
border-color: #DCDFE6; |
|||
color: #606266; |
|||
cursor: default; |
|||
} |
|||
|
|||
/* 禁用状态的日期选择器样式 */ |
|||
.el-date-editor.is-disabled >>> .el-input__inner { |
|||
background-color: #F5F7FA; |
|||
border-color: #DCDFE6; |
|||
color: #606266; |
|||
cursor: default; |
|||
} |
|||
|
|||
/* 调整label样式 */ |
|||
.el-form-item >>> .el-form-item__label { |
|||
font-size: 13px; |
|||
color: #606266; |
|||
font-weight: normal; |
|||
padding-bottom: 5px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,569 @@ |
|||
<template> |
|||
<div class="exp-tri-confirm"> |
|||
<!-- 操作按钮 --> |
|||
<div class="toolbar"> |
|||
<el-button @click="openAddDialog" type="primary" size="small">新增</el-button> |
|||
</div> |
|||
|
|||
<!-- 三方确认表格 --> |
|||
<el-table |
|||
:data="processList" |
|||
v-loading="loading" |
|||
border |
|||
:height="height" |
|||
style="width: 100%;"> |
|||
|
|||
<el-table-column |
|||
label="工序顺序" |
|||
width="100" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag :type="getSeqTagType(scope.row)" size="small"> |
|||
第{{ scope.row.processSeq }}道 |
|||
</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="processStep" |
|||
label="工序名称" |
|||
width="120" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="prodApproverName" |
|||
label="车间负责人" |
|||
width="150" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<span v-if="scope.row.prodApproverName" style="color: #67C23A;"> |
|||
✓ {{ scope.row.prodApproverName }} |
|||
</span> |
|||
<span v-else-if="canConfirmProcess(scope.row)" style="color: #E6A23C;"> |
|||
待确认 |
|||
</span> |
|||
<span v-else style="color: #909399;">-</span> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="qaApproverName" |
|||
label="质量负责人" |
|||
width="150" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<span v-if="scope.row.qaApproverName" style="color: #67C23A;"> |
|||
✓ {{ scope.row.qaApproverName }} |
|||
</span> |
|||
<span v-else-if="canConfirmProcess(scope.row)" style="color: #E6A23C;"> |
|||
待确认 |
|||
</span> |
|||
<span v-else style="color: #909399;">-</span> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="techApproverName" |
|||
label="技术负责人" |
|||
width="150" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<span v-if="scope.row.techApproverName" style="color: #67C23A;"> |
|||
✓ {{ scope.row.techApproverName }} |
|||
</span> |
|||
<span v-else-if="canConfirmProcess(scope.row)" style="color: #E6A23C;"> |
|||
待确认 |
|||
</span> |
|||
<span v-else style="color: #909399;">-</span> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="confirmStatus" |
|||
label="确认状态" |
|||
width="120" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag v-if="scope.row.confirmStatus === 'CONFIRMED'" type="success" size="small"> |
|||
已完成 |
|||
</el-tag> |
|||
<el-tag v-else-if="canConfirmProcess(scope.row)" type="warning" size="small"> |
|||
可确认 |
|||
</el-tag> |
|||
<el-tag v-else type="info" size="small"> |
|||
等待中 |
|||
</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="remark" |
|||
label="备注" |
|||
min-width="150" |
|||
align="left" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
<template slot-scope="scope"> |
|||
<span>{{ scope.row.remark || '' }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
label="操作" |
|||
width="120" |
|||
align="center" |
|||
header-align="center" |
|||
fixed="right"> |
|||
<template slot-scope="scope"> |
|||
<a |
|||
type="text" |
|||
size="small" |
|||
@click="openEditDialog(scope.row)"> |
|||
修改 |
|||
</a> |
|||
<a |
|||
type="text" |
|||
size="small" |
|||
style="color: #F56C6C" |
|||
@click="deleteProcessRow(scope.row)"> |
|||
删除 |
|||
</a> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<!-- 新增/修改工序弹框 --> |
|||
<el-dialog |
|||
:title="processDialogTitle" |
|||
:visible.sync="processDialogVisible" |
|||
width="400px" |
|||
:close-on-click-modal="false"> |
|||
|
|||
<el-form |
|||
label-position="top" |
|||
:model="processForm" |
|||
ref="processForm" |
|||
label-width="100px"> |
|||
|
|||
<el-form-item label="工序" required> |
|||
<el-input |
|||
v-model="processForm.processStep" |
|||
:readonly="isEditMode" |
|||
placeholder="请输入工序名称"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="车间负责人"> |
|||
<el-input |
|||
v-model="processForm.prodApproverName" |
|||
placeholder="请输入车间负责人"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="质量负责人"> |
|||
<el-input |
|||
v-model="processForm.qaApproverName" |
|||
placeholder="请输入质量负责人"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="技术负责人"> |
|||
<el-input |
|||
v-model="processForm.techApproverName" |
|||
placeholder="请输入技术负责人"> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="备注"> |
|||
<el-input |
|||
v-model="processForm.remark" |
|||
type="textarea" |
|||
:rows="2" |
|||
placeholder="请输入备注"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<div slot="footer" style="margin-top: 40px"> |
|||
<el-button |
|||
type="primary" |
|||
@click="saveProcess" |
|||
:loading="processSaving"> |
|||
确认 |
|||
</el-button> |
|||
<el-button @click="processDialogVisible = false">关闭</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
|
|||
<!-- 确认对话框 --> |
|||
<el-dialog |
|||
:title="confirmDialogTitle" |
|||
:visible.sync="confirmDialogVisible" |
|||
width="450px" |
|||
:close-on-click-modal="false"> |
|||
|
|||
<el-form label-position="top" label-width="120px"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="确认人"> |
|||
<span>{{ $store.state.user.name }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="12"> |
|||
<el-form-item label="确认时间"> |
|||
<span>{{ currentTime }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-form-item label="样品是否OK" required> |
|||
<el-radio-group v-model="sampleOk"> |
|||
<el-radio :label="true"> |
|||
<span style="color: #67C23A; font-weight: bold;">✓ OK</span> |
|||
</el-radio> |
|||
<el-radio :label="false"> |
|||
<span style="color: #F56C6C; font-weight: bold;">✗ NG</span> |
|||
</el-radio> |
|||
</el-radio-group> |
|||
<div style="color: #909399; font-size: 12px; margin-top: 5px;"> |
|||
请根据实际检查结果选择样品状态 |
|||
</div> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="备注"> |
|||
<el-input |
|||
v-model="confirmRemark" |
|||
type="textarea" |
|||
:rows="3" |
|||
placeholder="请输入确认备注(选填)"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<div slot="footer"> |
|||
<el-button @click="confirmDialogVisible = false">取消</el-button> |
|||
<el-button |
|||
type="primary" |
|||
@click="doConfirm" |
|||
:loading="confirmLoading" |
|||
:disabled="sampleOk === null"> |
|||
确认 |
|||
</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getTriConfirmList, saveTriConfirmProcess, deleteTriConfirmProcess, confirmTriApproval } from '@/api/erf/erf' |
|||
|
|||
export default { |
|||
name: 'ExpTriConfirm', |
|||
|
|||
props: { |
|||
applyNo: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
height: { |
|||
type: String, |
|||
default: '35vh' |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
processList: [], |
|||
loading: false, |
|||
|
|||
// 新增/修改工序弹框 |
|||
processDialogVisible: false, |
|||
processDialogTitle: '', |
|||
isEditMode: false, |
|||
processSaving: false, |
|||
processForm: { |
|||
processStep: '', |
|||
prodApproverName: '', |
|||
qaApproverName: '', |
|||
techApproverName: '', |
|||
remark: '' |
|||
}, |
|||
|
|||
// 确认对话框 |
|||
confirmDialogVisible: false, |
|||
confirmDialogTitle: '', |
|||
confirmRemark: '', |
|||
sampleOk: null, // 样品是否OK |
|||
confirmLoading: false, |
|||
currentProcess: null, |
|||
currentRoleType: '', |
|||
currentTime: '' |
|||
} |
|||
}, |
|||
|
|||
watch: { |
|||
applyNo: { |
|||
immediate: true, |
|||
handler(val) { |
|||
if (val) { |
|||
this.loadProcessList() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 加载工序列表 |
|||
*/ |
|||
loadProcessList() { |
|||
this.loading = true |
|||
getTriConfirmList({ applyNo: this.applyNo }).then(({data}) => { |
|||
this.loading = false |
|||
if (data && data.code === 0) { |
|||
// 按工序顺序排序 |
|||
this.processList = (data.list || []).sort((a, b) => { |
|||
return (a.processSeq || 0) - (b.processSeq || 0) |
|||
}) |
|||
} else { |
|||
this.processList = [] |
|||
this.$message.error(data.msg || '加载三方确认列表失败') |
|||
} |
|||
}).catch(error => { |
|||
this.loading = false |
|||
this.$message.error('加载三方确认列表异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 获取工序顺序标签颜色 |
|||
*/ |
|||
getSeqTagType(row) { |
|||
if (row.confirmStatus === 'CONFIRMED') { |
|||
return 'success' // 已完成:绿色 |
|||
} else if (this.canConfirmProcess(row)) { |
|||
return 'warning' // 可确认:橙色 |
|||
} else { |
|||
return 'info' // 等待中:灰色 |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 判断当前工序是否可以确认 |
|||
* 规则:第一道工序直接可确认,其他工序需要上一道完成 |
|||
*/ |
|||
canConfirmProcess(row) { |
|||
if (row.confirmStatus === 'CONFIRMED') { |
|||
return false // 已完成的不能再确认 |
|||
} |
|||
|
|||
if (row.processSeq === 1) { |
|||
return true // 第一道工序可以直接确认 |
|||
} |
|||
|
|||
// 查找上一道工序 |
|||
const previousProcess = this.processList.find(p => p.processSeq === row.processSeq - 1) |
|||
|
|||
// 上一道工序必须已完成 |
|||
return previousProcess && previousProcess.confirmStatus === 'CONFIRMED' |
|||
}, |
|||
|
|||
/** |
|||
* 打开新增弹框 |
|||
*/ |
|||
openAddDialog() { |
|||
this.processDialogTitle = '新增工序确认流程' |
|||
this.isEditMode = false |
|||
this.processForm = { |
|||
processStep: '', |
|||
prodApproverName: '', |
|||
qaApproverName: '', |
|||
techApproverName: '', |
|||
remark: '' |
|||
} |
|||
this.processDialogVisible = true |
|||
}, |
|||
|
|||
/** |
|||
* 打开修改弹框 |
|||
*/ |
|||
openEditDialog(row) { |
|||
this.processDialogTitle = '修改工序确认流程' |
|||
this.isEditMode = true |
|||
this.processForm = { |
|||
processStep: row.processStep, |
|||
prodApproverName: row.prodApproverName || '', |
|||
qaApproverName: row.qaApproverName || '', |
|||
techApproverName: row.techApproverName || '', |
|||
remark: row.remark || '' |
|||
} |
|||
this.processDialogVisible = true |
|||
}, |
|||
|
|||
/** |
|||
* 保存工序 |
|||
*/ |
|||
saveProcess() { |
|||
// 验证必填项 |
|||
if (!this.processForm.processStep || !this.processForm.processStep.trim()) { |
|||
this.$message.warning('请输入工序名称') |
|||
return |
|||
} |
|||
|
|||
this.processSaving = true |
|||
|
|||
saveTriConfirmProcess({ |
|||
applyNo: this.applyNo, |
|||
processStep: this.processForm.processStep.trim(), |
|||
prodApproverName: this.processForm.prodApproverName || '', |
|||
qaApproverName: this.processForm.qaApproverName || '', |
|||
techApproverName: this.processForm.techApproverName || '', |
|||
remark: this.processForm.remark || '' |
|||
}).then(({data}) => { |
|||
this.processSaving = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('保存成功') |
|||
this.processDialogVisible = false |
|||
this.loadProcessList() |
|||
} else { |
|||
this.$message.error(data.msg || '保存失败') |
|||
} |
|||
}).catch(error => { |
|||
this.processSaving = false |
|||
this.$message.error('保存异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 删除工序行 |
|||
*/ |
|||
deleteProcessRow(row) { |
|||
this.$confirm('确定删除该工序?', '操作提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
type: 'warning' |
|||
}).then(() => { |
|||
deleteTriConfirmProcess({ |
|||
applyNo: this.applyNo, |
|||
processStep: row.processStep |
|||
}).then(({data}) => { |
|||
if (data && data.code === 0) { |
|||
this.$message.success('删除成功') |
|||
this.loadProcessList() |
|||
} else { |
|||
this.$message.error(data.msg || '删除失败') |
|||
} |
|||
}).catch(error => { |
|||
this.$message.error('删除异常') |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 打开确认对话框 |
|||
*/ |
|||
openConfirmDialog(row, roleType) { |
|||
// 检查是否可以确认 |
|||
if (!this.canConfirmProcess(row)) { |
|||
this.$message.warning('请等待上一道工序完成三方确认') |
|||
return |
|||
} |
|||
|
|||
this.currentProcess = row |
|||
this.currentRoleType = roleType |
|||
this.confirmRemark = '' |
|||
this.sampleOk = null // 重置样品状态 |
|||
this.currentTime = this.formatDateTime(new Date()) |
|||
|
|||
const roleNames = { |
|||
'PROD': '车间负责人', |
|||
'QA': '质量负责人', |
|||
'TECH': '技术负责人' |
|||
} |
|||
this.confirmDialogTitle = `${roleNames[roleType]}确认 - 第${row.processSeq}道:${row.processStep}` |
|||
this.confirmDialogVisible = true |
|||
}, |
|||
|
|||
/** |
|||
* 执行确认 |
|||
*/ |
|||
doConfirm() { |
|||
// 验证必填项 |
|||
if (this.sampleOk === null) { |
|||
this.$message.warning('请选择样品是否OK') |
|||
return |
|||
} |
|||
|
|||
this.confirmLoading = true |
|||
|
|||
confirmTriApproval({ |
|||
applyNo: this.applyNo, |
|||
processStep: this.currentProcess.processStep, |
|||
roleType: this.currentRoleType, |
|||
approverUserId: this.$store.state.user.id, |
|||
approverName: this.$store.state.user.name, |
|||
sampleOk: this.sampleOk, // ✅ 传递样品状态 |
|||
comment: this.confirmRemark |
|||
}).then(({data}) => { |
|||
this.confirmLoading = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('确认成功') |
|||
this.confirmDialogVisible = false |
|||
this.loadProcessList() |
|||
} else { |
|||
this.$message.error(data.msg || '确认失败') |
|||
} |
|||
}).catch(error => { |
|||
this.confirmLoading = false |
|||
this.$message.error('确认异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 格式化日期时间 |
|||
*/ |
|||
formatDateTime(date) { |
|||
const year = date.getFullYear() |
|||
const month = String(date.getMonth() + 1).padStart(2, '0') |
|||
const day = String(date.getDate()).padStart(2, '0') |
|||
const hours = String(date.getHours()).padStart(2, '0') |
|||
const minutes = String(date.getMinutes()).padStart(2, '0') |
|||
const seconds = String(date.getSeconds()).padStart(2, '0') |
|||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.exp-tri-confirm { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.toolbar { |
|||
margin-bottom: 10px; |
|||
text-align: left; |
|||
} |
|||
|
|||
.el-button + .el-button { |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
/* 表格样式优化 */ |
|||
.el-table >>> .el-table__header th { |
|||
background-color: #F5F7FA; |
|||
color: #606266; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.el-table >>> .el-table__row td { |
|||
padding: 8px 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,490 @@ |
|||
<template> |
|||
<div class="mod-config"> |
|||
<!-- 查询条件表单 --> |
|||
<el-form :inline="true" label-position="top"> |
|||
<el-form-item label="申请单号"> |
|||
<el-input v-model="queryHeaderData.applyNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="事业部"> |
|||
<el-select v-model="queryHeaderData.buNo" placeholder="请选择" clearable style="width: 120px"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option |
|||
v-for="i in buList" |
|||
:key="i.buNo" |
|||
:label="i.buDesc" |
|||
:value="i.buNo"> |
|||
</el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="试验类型"> |
|||
<el-select v-model="queryHeaderData.experimentType" placeholder="请选择" clearable style="width: 120px"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option label="High Risk" value="High Risk"></el-option> |
|||
<el-option label="Low Risk" value="Low Risk"></el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="试验名称"> |
|||
<el-input v-model="queryHeaderData.title" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="项目编号"> |
|||
<el-input v-model="queryHeaderData.projectNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="产品型号"> |
|||
<el-input v-model="queryHeaderData.productType" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="申请人"> |
|||
<el-input v-model="queryHeaderData.creatorName" placeholder="支持模糊查询" clearable style="width: 120px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="申请开始日期"> |
|||
<el-date-picker |
|||
v-model="queryHeaderData.createStartDate" |
|||
type="date" |
|||
placeholder="选择日期" |
|||
value-format="yyyy-MM-dd" |
|||
style="width: 150px"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="申请结束日期"> |
|||
<el-date-picker |
|||
v-model="queryHeaderData.createEndDate" |
|||
type="date" |
|||
placeholder="选择日期" |
|||
value-format="yyyy-MM-dd" |
|||
style="width: 150px"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label=" "> |
|||
<el-button @click="getDataList('Y')" type="primary">查询</el-button> |
|||
<el-button @click="resetQuery()" type="default">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<!-- 待办申请单表格 --> |
|||
<el-table |
|||
:data="dataList" |
|||
v-loading="dataListLoading" |
|||
border |
|||
stripe |
|||
style="width: 100%;" |
|||
:height="height"> |
|||
|
|||
<el-table-column |
|||
label="操作" |
|||
width="120" |
|||
align="center" |
|||
header-align="center" |
|||
fixed="left"> |
|||
<template slot-scope="scope"> |
|||
<a type="text" size="small" @click="openApprovalDialog(scope.row)">审批</a> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="applyNo" |
|||
label="申请单号" |
|||
width="140" |
|||
align="center" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="buDesc" |
|||
label="事业部" |
|||
width="80" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="experimentType" |
|||
label="试验类型" |
|||
width="100" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="title" |
|||
label="试验名称" |
|||
min-width="200" |
|||
align="left" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="projectNo" |
|||
label="项目编号" |
|||
width="120" |
|||
align="center" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="productType" |
|||
label="产品型号" |
|||
min-width="200" |
|||
align="center" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="creatorName" |
|||
label="申请人" |
|||
width="100" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="createTime" |
|||
label="申请时间" |
|||
width="160" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="currentStep" |
|||
label="当前步骤" |
|||
width="140" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<!-- 分页组件 --> |
|||
<el-pagination |
|||
@size-change="sizeChangeHandle" |
|||
@current-change="currentChangeHandle" |
|||
:current-page="pageIndex" |
|||
:page-sizes="[20, 50, 100]" |
|||
:page-size="pageSize" |
|||
:total="totalPage" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
style="margin-top: 20px; text-align: right;"> |
|||
</el-pagination> |
|||
|
|||
<!-- 审批弹窗 --> |
|||
<el-dialog |
|||
title="审批申请单" |
|||
:visible.sync="approvalDialogVisible" |
|||
width="1100px" |
|||
:close-on-click-modal="false" |
|||
v-drag> |
|||
|
|||
<!-- Tabs切换 --> |
|||
<el-tabs v-model="activeTab" type="border-card" style="font-size: 12px; min-height: 200px"> |
|||
<!-- 申请单基本信息 --> |
|||
<el-tab-pane label="申请单信息" name="basic"> |
|||
<el-descriptions :column="2" border size="small"> |
|||
<el-descriptions-item label="申请单号">{{ currentApply.applyNo }}</el-descriptions-item> |
|||
<el-descriptions-item label="事业部">{{ currentApply.buNo }}</el-descriptions-item> |
|||
<el-descriptions-item label="试验类型"> |
|||
<el-tag :type="currentApply.experimentType === 'High Risk' ? 'danger' : 'success'"> |
|||
{{ currentApply.experimentType }} |
|||
</el-tag> |
|||
</el-descriptions-item> |
|||
<el-descriptions-item label="项目编号">{{ currentApply.projectNo }}</el-descriptions-item> |
|||
<el-descriptions-item label="试验名称" :span="2">{{ currentApply.title }}</el-descriptions-item> |
|||
<el-descriptions-item label="试验目的" :span="2">{{ currentApply.purpose }}</el-descriptions-item> |
|||
<el-descriptions-item label="验证方法" :span="2">{{ currentApply.justification }}</el-descriptions-item> |
|||
<el-descriptions-item label="产品型号">{{ currentApply.productType }}</el-descriptions-item> |
|||
<el-descriptions-item label="申请数量">{{ currentApply.quantityReq }}</el-descriptions-item> |
|||
<el-descriptions-item label="期望完成日期">{{ currentApply.expectedFinishDate }}</el-descriptions-item> |
|||
<el-descriptions-item label="申请人">{{ currentApply.creatorName }}</el-descriptions-item> |
|||
<el-descriptions-item label="申请时间" :span="2">{{ currentApply.createTime }}</el-descriptions-item> |
|||
</el-descriptions> |
|||
</el-tab-pane> |
|||
|
|||
<!-- 审批历史 --> |
|||
<el-tab-pane label="审批历史" name="history"> |
|||
<approval-history |
|||
v-if="activeTab === 'history' && currentApply.applyNo" |
|||
:apply-no="currentApply.applyNo"> |
|||
</approval-history> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
|
|||
<!-- 审批操作 --> |
|||
<el-divider content-position="left">审批操作</el-divider> |
|||
<el-form :model="approvalData" label-position="top" style="margin-left: 5px; margin-top: -5px;"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="审批意见" required> |
|||
<el-input |
|||
v-model="approvalData.comment" |
|||
type="textarea" |
|||
:rows="2" |
|||
placeholder="请输入审批意见"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
|
|||
<el-footer style="height: 40px; margin-top: 40px; text-align: center"> |
|||
<el-button type="success" @click="approveApply" :loading="approvalLoading"> |
|||
{{ approvalLoading ? '审批中...' : '批准' }} |
|||
</el-button> |
|||
<el-button type="danger" @click="rejectApply" :loading="approvalLoading"> |
|||
{{ approvalLoading ? '驳回中...' : '驳回' }} |
|||
</el-button> |
|||
<el-button type="primary" @click="approvalDialogVisible = false" :disabled="approvalLoading">关闭</el-button> |
|||
</el-footer> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getPendingApplyList, approveExpApply } from '@/api/erf/erf' |
|||
import { getBuList } from '@/api/factory/site' |
|||
import ApprovalHistory from './components/approvalHistory.vue' |
|||
|
|||
export default { |
|||
name: 'ExpApplyApproval', |
|||
|
|||
components: { |
|||
ApprovalHistory |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
buList: [], |
|||
activeTab: 'basic', |
|||
// 查询条件 |
|||
queryHeaderData: { |
|||
applyNo: '', |
|||
buNo: '', |
|||
experimentType: '', |
|||
title: '', |
|||
projectNo: '', |
|||
productType: '', |
|||
creatorName: '', |
|||
createStartDate: '', |
|||
createEndDate: '', |
|||
currentUserId: this.$store.state.user.id, |
|||
pendingStatus: '已下达', // 审批节点只查询已下达状态 |
|||
page: 1, |
|||
limit: 20 |
|||
}, |
|||
|
|||
// 数据列表 |
|||
dataList: [], |
|||
|
|||
// 分页参数 |
|||
pageIndex: 1, |
|||
pageSize: 20, |
|||
totalPage: 0, |
|||
dataListLoading: false, |
|||
|
|||
// 审批弹窗 |
|||
approvalDialogVisible: false, |
|||
currentApply: {}, |
|||
approvalData: { |
|||
applyNo: '', |
|||
nodeCode: '', |
|||
action: '', |
|||
comment: '', |
|||
operatorUserId: this.$store.state.user.id, |
|||
operatorName: this.$store.state.user.name |
|||
}, |
|||
approvalLoading: false, |
|||
|
|||
// 表格高度 |
|||
height: window.innerHeight - 260 |
|||
} |
|||
}, |
|||
|
|||
activated() { |
|||
this.loadBuList() |
|||
this.getDataList() |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 加载事业部列表 |
|||
*/ |
|||
loadBuList() { |
|||
const tempData = { site: this.$store.state.user.site } |
|||
getBuList(tempData).then(({data}) => { |
|||
if (data.code === 0) { |
|||
this.buList = data.row1 |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 获取待办列表 |
|||
*/ |
|||
getDataList(flag) { |
|||
if (flag === 'Y') { |
|||
this.pageIndex = 1 |
|||
} |
|||
|
|||
this.queryHeaderData.page = this.pageIndex |
|||
this.queryHeaderData.limit = this.pageSize |
|||
this.queryHeaderData.currentUserId = this.$store.state.user.id |
|||
|
|||
this.dataListLoading = true |
|||
|
|||
getPendingApplyList(this.queryHeaderData).then(({data}) => { |
|||
this.dataListLoading = false |
|||
if (data && data.code === 0) { |
|||
this.dataList = data.page.list || [] |
|||
this.totalPage = data.page.totalCount || 0 |
|||
} else { |
|||
this.dataList = [] |
|||
this.totalPage = 0 |
|||
this.$message.error(data.msg || '查询失败') |
|||
} |
|||
}).catch(error => { |
|||
this.dataListLoading = false |
|||
this.$message.error('查询异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 重置查询条件 |
|||
*/ |
|||
resetQuery() { |
|||
this.queryHeaderData.applyNo = '' |
|||
this.queryHeaderData.buNo = '' |
|||
this.queryHeaderData.experimentType = '' |
|||
this.queryHeaderData.title = '' |
|||
this.queryHeaderData.projectNo = '' |
|||
this.queryHeaderData.productType = '' |
|||
this.queryHeaderData.creatorName = '' |
|||
this.queryHeaderData.createStartDate = '' |
|||
this.queryHeaderData.createEndDate = '' |
|||
this.getDataList('Y') |
|||
}, |
|||
|
|||
/** |
|||
* 打开审批对话框 |
|||
*/ |
|||
openApprovalDialog(row) { |
|||
this.currentApply = { ...row } |
|||
|
|||
// 查询当前流程实例,获取正确的节点编码 |
|||
this.getCurrentNodeCode(row.applyNo) |
|||
}, |
|||
|
|||
/** |
|||
* 获取当前节点编码 |
|||
*/ |
|||
getCurrentNodeCode(applyNo) { |
|||
// 调用API获取流程实例的当前节点 |
|||
this.$http({ |
|||
url: this.$http.adornUrl('/erf/expApply/getCurrentNodeCode'), |
|||
method: 'post', |
|||
data: this.$http.adornData({ applyNo: applyNo }) |
|||
}).then(({data}) => { |
|||
if (data && data.code === 0) { |
|||
this.approvalData = { |
|||
applyNo: applyNo, |
|||
nodeCode: data.nodeCode, // 使用流程实例的当前节点编码 |
|||
action: '', |
|||
comment: '', |
|||
operatorUserId: this.$store.state.user.id, |
|||
operatorName: this.$store.state.user.name |
|||
} |
|||
this.approvalDialogVisible = true |
|||
} else { |
|||
this.$message.error('获取节点信息失败') |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 批准操作 |
|||
*/ |
|||
approveApply() { |
|||
if (!this.approvalData.comment) { |
|||
this.$message.warning('请输入审批意见') |
|||
return |
|||
} |
|||
|
|||
this.approvalData.action = '批准' |
|||
this.doApproval() |
|||
}, |
|||
|
|||
/** |
|||
* 驳回操作 |
|||
*/ |
|||
rejectApply() { |
|||
if (!this.approvalData.comment) { |
|||
this.$message.warning('请输入审批意见') |
|||
return |
|||
} |
|||
|
|||
this.approvalData.action = '驳回' |
|||
this.doApproval() |
|||
}, |
|||
|
|||
/** |
|||
* 执行审批 |
|||
*/ |
|||
doApproval() { |
|||
this.approvalLoading = true |
|||
|
|||
approveExpApply(this.approvalData).then(({data}) => { |
|||
this.approvalLoading = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('审批成功') |
|||
this.approvalDialogVisible = false |
|||
this.getDataList() |
|||
} else { |
|||
this.$message.error(data.msg || '审批失败') |
|||
} |
|||
}).catch(error => { |
|||
this.approvalLoading = false |
|||
this.$message.error('审批异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 分页大小改变 |
|||
*/ |
|||
sizeChangeHandle(val) { |
|||
this.pageSize = val |
|||
this.pageIndex = 1 |
|||
this.getDataList() |
|||
}, |
|||
|
|||
/** |
|||
* 当前页改变 |
|||
*/ |
|||
currentChangeHandle(val) { |
|||
this.pageIndex = val |
|||
this.getDataList() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.mod-config { |
|||
|
|||
} |
|||
|
|||
.el-form { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.dialog-footer { |
|||
text-align: right; |
|||
} |
|||
</style> |
|||
1311
src/views/modules/erf/expApplyList.vue
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,372 @@ |
|||
<template> |
|||
<div class="mod-config"> |
|||
<!-- 查询条件 --> |
|||
<el-form :inline="true" label-position="top"> |
|||
<el-form-item label="申请单号"> |
|||
<el-input v-model="searchData.applyNo" placeholder="请输入申请单号" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="事业部"> |
|||
<el-select v-model="searchData.buNo" clearable style="width: 120px"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option |
|||
v-for="i in buList" |
|||
:key="i.buNo" |
|||
:label="i.buDesc" |
|||
:value="i.buNo"> |
|||
</el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="试验类型"> |
|||
<el-select v-model="searchData.experimentType" placeholder="请选择" clearable style="width: 120px"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option label="High Risk" value="High Risk"></el-option> |
|||
<el-option label="Low Risk" value="Low Risk"></el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="试验名称"> |
|||
<el-input v-model="searchData.title" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="项目编号"> |
|||
<el-input v-model="searchData.projectNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="产品型号"> |
|||
<el-input v-model="searchData.productType" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="申请人"> |
|||
<el-input v-model="searchData.creatorName" placeholder="支持模糊查询" clearable style="width: 120px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<!-- <el-form-item label="申请开始日期"> |
|||
<el-date-picker |
|||
v-model="searchData.createStartDate" |
|||
type="date" |
|||
placeholder="选择日期" |
|||
value-format="yyyy-MM-dd" |
|||
style="width: 150px"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="申请结束日期"> |
|||
<el-date-picker |
|||
v-model="searchData.createEndDate" |
|||
type="date" |
|||
placeholder="选择日期" |
|||
value-format="yyyy-MM-dd" |
|||
style="width: 150px"> |
|||
</el-date-picker> |
|||
</el-form-item>--> |
|||
|
|||
<el-form-item label="期望完成开始日期"> |
|||
<el-date-picker |
|||
v-model="searchData.expectedFinishStartDate" |
|||
type="date" |
|||
placeholder="选择日期" |
|||
value-format="yyyy-MM-dd" |
|||
style="width: 150px"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="期望完成结束日期"> |
|||
<el-date-picker |
|||
v-model="searchData.expectedFinishEndDate" |
|||
type="date" |
|||
placeholder="选择日期" |
|||
value-format="yyyy-MM-dd" |
|||
style="width: 150px"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label=" "> |
|||
<el-button @click="getDataList('Y')" type="primary">查询</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<!-- 待排产申请单列表 --> |
|||
<el-table |
|||
:data="dataList" |
|||
v-loading="dataListLoading" |
|||
border |
|||
stripe |
|||
style="width: 100%;" |
|||
:height="height"> |
|||
|
|||
<el-table-column |
|||
label="操作" |
|||
width="100" |
|||
align="center" |
|||
fixed="left"> |
|||
<template slot-scope="scope"> |
|||
<a type="text" size="small" @click="openScheduleDialog(scope.row)">排产</a> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column prop="applyNo" label="申请单号" width="140" align="center"></el-table-column> |
|||
<el-table-column prop="buNo" label="事业部" width="80" align="center"></el-table-column> |
|||
<el-table-column prop="experimentType" label="试验类型" min-width="100" align="center"></el-table-column> |
|||
<el-table-column prop="title" label="试验名称" min-width="200" align="left" show-overflow-tooltip></el-table-column> |
|||
<el-table-column |
|||
prop="projectNo" |
|||
label="项目编号" |
|||
min-width="120" |
|||
align="center" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="productType" |
|||
label="产品型号" |
|||
min-width="200" |
|||
align="center" |
|||
header-align="center" |
|||
show-overflow-tooltip> |
|||
</el-table-column> |
|||
<el-table-column prop="expectedFinishDate" label="期望完成日期" width="120" align="center"></el-table-column> |
|||
<el-table-column |
|||
prop="creatorName" |
|||
label="申请人" |
|||
width="100" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="createTime" |
|||
label="申请时间" |
|||
width="160" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<el-pagination |
|||
@size-change="sizeChangeHandle" |
|||
@current-change="currentChangeHandle" |
|||
:current-page="pageIndex" |
|||
:page-sizes="[20, 50, 100]" |
|||
:page-size="pageSize" |
|||
:total="totalPage" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
style="margin-top: 20px; text-align: right;"> |
|||
</el-pagination> |
|||
|
|||
<!-- 排产弹窗 --> |
|||
<el-dialog |
|||
title="计划员排产" |
|||
:visible.sync="scheduleDialogVisible" |
|||
width="600px" |
|||
:close-on-click-modal="false" |
|||
v-drag> |
|||
|
|||
<el-form :model="scheduleData" label-position="top" style="margin-left: 5px; margin-top: -5px;"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="8"> |
|||
<el-form-item label="申请单号"> |
|||
<span>{{ scheduleData.applyNo }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="8"> |
|||
<el-form-item label="试验类型"> |
|||
<el-tag :type="currentApply.experimentType === 'High Risk' ? 'danger' : 'success'"> |
|||
{{ currentApply.experimentType }} |
|||
</el-tag> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="8"> |
|||
<el-form-item label="申请人"> |
|||
<span>{{ currentApply.creatorName }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="试验名称"> |
|||
<span>{{ currentApply.title }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="实验工单号" required> |
|||
<el-input |
|||
v-model="scheduleData.workOrderNo" |
|||
placeholder="请输入实验工单号"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="12"> |
|||
<el-form-item label="排产日期" required> |
|||
<el-date-picker |
|||
v-model="scheduleData.scheduledDate" |
|||
type="date" |
|||
format="yyyy-MM-dd" |
|||
value-format="yyyy-MM-dd" |
|||
placeholder="选择排产日期" |
|||
style="width: 100%"> |
|||
</el-date-picker> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
|
|||
<el-footer style="height: 40px; margin-top: 10px; text-align: center"> |
|||
<el-button type="primary" @click="doSchedule" :loading="scheduleLoading"> |
|||
{{ scheduleLoading ? '排产中...' : '确定排产' }} |
|||
</el-button> |
|||
<el-button type="primary" @click="scheduleDialogVisible = false" :disabled="scheduleLoading">关闭</el-button> |
|||
</el-footer> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getPendingApplyList, plannerSchedule } from '@/api/erf/erf' |
|||
import { getBuList } from '@/api/factory/site' |
|||
|
|||
export default { |
|||
name: 'PlannerSchedule', |
|||
|
|||
data() { |
|||
return { |
|||
buList: [], |
|||
searchData: { |
|||
applyNo: '', |
|||
buNo: '', |
|||
currentUserId: this.$store.state.user.id, |
|||
pendingStatus: '已批准', // 计划员排产只查询已批准状态 |
|||
page: 1, |
|||
limit: 20 |
|||
}, |
|||
|
|||
dataList: [], |
|||
pageIndex: 1, |
|||
pageSize: 20, |
|||
totalPage: 0, |
|||
dataListLoading: false, |
|||
|
|||
scheduleDialogVisible: false, |
|||
currentApply: {}, |
|||
scheduleData: { |
|||
applyNo: '', |
|||
workOrderNo: '', |
|||
scheduledDate: '' |
|||
}, |
|||
scheduleLoading: false, |
|||
|
|||
height: window.innerHeight - 260 |
|||
} |
|||
}, |
|||
|
|||
activated() { |
|||
this.loadBuList() |
|||
this.getDataList() |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 加载事业部列表 |
|||
*/ |
|||
loadBuList() { |
|||
const tempData = { site: this.$store.state.user.site } |
|||
getBuList(tempData).then(({data}) => { |
|||
if (data.code === 0) { |
|||
this.buList = data.row1 |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
getDataList(flag) { |
|||
if (flag === 'Y') { |
|||
this.pageIndex = 1 |
|||
} |
|||
|
|||
this.searchData.page = this.pageIndex |
|||
this.searchData.limit = this.pageSize |
|||
|
|||
this.dataListLoading = true |
|||
|
|||
getPendingApplyList(this.searchData).then(({data}) => { |
|||
this.dataListLoading = false |
|||
if (data && data.code === 0) { |
|||
this.dataList = data.page.list || [] |
|||
this.totalPage = data.page.totalCount || 0 |
|||
} else { |
|||
this.dataList = [] |
|||
this.totalPage = 0 |
|||
} |
|||
}).catch(error => { |
|||
this.dataListLoading = false |
|||
}) |
|||
}, |
|||
|
|||
openScheduleDialog(row) { |
|||
this.currentApply = { ...row } |
|||
this.scheduleData = { |
|||
applyNo: row.applyNo, |
|||
workOrderNo: '', |
|||
scheduledDate: '', |
|||
processSteps: '', |
|||
comment: '' |
|||
} |
|||
this.scheduleDialogVisible = true |
|||
}, |
|||
|
|||
doSchedule() { |
|||
if (!this.scheduleData.workOrderNo) { |
|||
this.$message.warning('请输入实验工单号') |
|||
return |
|||
} |
|||
|
|||
if (!this.scheduleData.scheduledDate) { |
|||
this.$message.warning('请选择排产日期') |
|||
return |
|||
} |
|||
|
|||
|
|||
this.scheduleLoading = true |
|||
|
|||
plannerSchedule(this.scheduleData).then(({data}) => { |
|||
this.scheduleLoading = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('排产成功') |
|||
this.scheduleDialogVisible = false |
|||
this.getDataList() |
|||
} else { |
|||
this.$message.error(data.msg || '排产失败') |
|||
} |
|||
}).catch(error => { |
|||
this.scheduleLoading = false |
|||
}) |
|||
}, |
|||
|
|||
sizeChangeHandle(val) { |
|||
this.pageSize = val |
|||
this.pageIndex = 1 |
|||
this.getDataList() |
|||
}, |
|||
|
|||
currentChangeHandle(val) { |
|||
this.pageIndex = val |
|||
this.getDataList() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.mod-config { |
|||
|
|||
} |
|||
</style> |
|||
@ -0,0 +1,324 @@ |
|||
<template> |
|||
<div class="mod-config"> |
|||
<!-- 查询条件 --> |
|||
<el-form :inline="true" label-position="top"> |
|||
<el-form-item label="申请单号"> |
|||
<el-input v-model="searchData.applyNo" placeholder="请输入申请单号" style="width: 200px"></el-input> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label=" "> |
|||
<el-button @click="getConfirmList" type="primary">查询</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<!-- 三方确认表格 --> |
|||
<el-table |
|||
:data="dataList" |
|||
v-loading="dataListLoading" |
|||
border |
|||
style="width: 100%;" |
|||
:height="height"> |
|||
|
|||
<el-table-column |
|||
prop="applyNo" |
|||
label="申请单号" |
|||
min-width="140" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
prop="processStep" |
|||
label="工序名称" |
|||
min-width="120" |
|||
align="center" |
|||
header-align="center"> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
label="生产确认" |
|||
min-width="180" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="getConfirmStatus(scope.row, 'PROD')"> |
|||
<el-tag type="success" size="small">已确认</el-tag> |
|||
<div style="font-size: 12px; color: #999;"> |
|||
{{ getApproverName(scope.row, 'PROD') }} |
|||
</div> |
|||
</div> |
|||
<el-button |
|||
v-else |
|||
type="text" |
|||
size="small" |
|||
@click="openConfirmDialog(scope.row, 'PROD')"> |
|||
确认 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
label="质量确认" |
|||
min-width="180" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="getConfirmStatus(scope.row, 'QA')"> |
|||
<el-tag type="success" size="small">已确认</el-tag> |
|||
<div style="font-size: 12px; color: #999;"> |
|||
{{ getApproverName(scope.row, 'QA') }} |
|||
</div> |
|||
</div> |
|||
<el-button |
|||
v-else |
|||
type="text" |
|||
size="small" |
|||
@click="openConfirmDialog(scope.row, 'QA')"> |
|||
确认 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
label="技术确认" |
|||
min-width="180" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="getConfirmStatus(scope.row, 'TECH')"> |
|||
<el-tag type="success" size="small">已确认</el-tag> |
|||
<div style="font-size: 12px; color: #999;"> |
|||
{{ getApproverName(scope.row, 'TECH') }} |
|||
</div> |
|||
</div> |
|||
<el-button |
|||
v-else |
|||
type="text" |
|||
size="small" |
|||
@click="openConfirmDialog(scope.row, 'TECH')"> |
|||
确认 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column |
|||
label="整体状态" |
|||
min-width="120" |
|||
align="center" |
|||
header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag |
|||
:type="isAllConfirmed(scope.row) ? 'success' : 'warning'" |
|||
size="small"> |
|||
{{ isAllConfirmed(scope.row) ? '已完成' : '待确认' }} |
|||
</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<!-- 确认弹窗 --> |
|||
<el-dialog |
|||
:title="'三方确认 - ' + getRoleName(confirmData.roleType)" |
|||
:visible.sync="confirmDialogVisible" |
|||
width="600px" |
|||
:close-on-click-modal="false" |
|||
v-drag> |
|||
|
|||
<el-form :model="confirmData" label-position="top" style="margin-left: 5px; margin-top: -5px;"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="申请单号"> |
|||
<span>{{ confirmData.applyNo }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="12"> |
|||
<el-form-item label="工序"> |
|||
<span>{{ confirmData.processStep }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item label="确认角色"> |
|||
<span>{{ getRoleName(confirmData.roleType) }}</span> |
|||
</el-form-item> |
|||
</el-col> |
|||
|
|||
<el-col :span="12"> |
|||
<el-form-item label="样品是否OK" required> |
|||
<el-radio-group v-model="confirmData.sampleOk"> |
|||
<el-radio :label="true">OK</el-radio> |
|||
<el-radio :label="false">NG</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="确认意见"> |
|||
<el-input |
|||
v-model="confirmData.comment" |
|||
type="textarea" |
|||
:rows="4" |
|||
placeholder="请输入确认意见"> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
|
|||
<el-footer style="height: 40px; margin-top: 10px; text-align: center"> |
|||
<el-button type="primary" @click="doConfirm" :loading="confirmLoading"> |
|||
{{ confirmLoading ? '确认中...' : '确认' }} |
|||
</el-button> |
|||
<el-button type="primary" @click="confirmDialogVisible = false" :disabled="confirmLoading">关闭</el-button> |
|||
</el-footer> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getTriConfirmList, confirmTriApproval } from '@/api/erf/erf' |
|||
|
|||
export default { |
|||
name: 'TriConfirm', |
|||
|
|||
data() { |
|||
return { |
|||
searchData: { |
|||
applyNo: '' |
|||
}, |
|||
|
|||
dataList: [], |
|||
dataListLoading: false, |
|||
|
|||
confirmDialogVisible: false, |
|||
confirmData: { |
|||
applyNo: '', |
|||
processStep: '', |
|||
roleType: '', |
|||
sampleOk: true, |
|||
comment: '', |
|||
approverUserId: this.$store.state.user.id, |
|||
approverName: this.$store.state.user.name |
|||
}, |
|||
confirmLoading: false, |
|||
|
|||
height: window.innerHeight - 200 |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 获取确认列表 |
|||
*/ |
|||
getConfirmList() { |
|||
if (!this.searchData.applyNo) { |
|||
this.$message.warning('请输入申请单号') |
|||
return |
|||
} |
|||
|
|||
this.dataListLoading = true |
|||
|
|||
getTriConfirmList({ applyNo: this.searchData.applyNo }).then(({data}) => { |
|||
this.dataListLoading = false |
|||
if (data && data.code === 0) { |
|||
this.dataList = data.list || [] |
|||
} else { |
|||
this.dataList = [] |
|||
this.$message.error(data.msg || '查询失败') |
|||
} |
|||
}).catch(error => { |
|||
this.dataListLoading = false |
|||
this.$message.error('查询异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 打开确认对话框 |
|||
*/ |
|||
openConfirmDialog(row, roleType) { |
|||
this.confirmData = { |
|||
applyNo: row.applyNo, |
|||
processStep: row.processStep, |
|||
roleType: roleType, |
|||
sampleOk: true, |
|||
comment: '', |
|||
approverUserId: this.$store.state.user.id, |
|||
approverName: this.$store.state.user.name |
|||
} |
|||
this.confirmDialogVisible = true |
|||
}, |
|||
|
|||
/** |
|||
* 执行确认 |
|||
*/ |
|||
doConfirm() { |
|||
if (!this.confirmData.comment) { |
|||
this.$message.warning('请输入确认意见') |
|||
return |
|||
} |
|||
|
|||
this.confirmLoading = true |
|||
|
|||
confirmTriApproval(this.confirmData).then(({data}) => { |
|||
this.confirmLoading = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success('确认成功') |
|||
this.confirmDialogVisible = false |
|||
this.getConfirmList() |
|||
} else { |
|||
this.$message.error(data.msg || '确认失败') |
|||
} |
|||
}).catch(error => { |
|||
this.confirmLoading = false |
|||
this.$message.error('确认异常') |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 获取确认状态 |
|||
*/ |
|||
getConfirmStatus(row, roleType) { |
|||
// TODO: 从row中获取对应角色的确认状态 |
|||
return row[roleType.toLowerCase() + 'Confirmed'] |
|||
}, |
|||
|
|||
/** |
|||
* 获取确认人姓名 |
|||
*/ |
|||
getApproverName(row, roleType) { |
|||
return row[roleType.toLowerCase() + 'ApproverName'] || '' |
|||
}, |
|||
|
|||
/** |
|||
* 判断是否全部确认 |
|||
*/ |
|||
isAllConfirmed(row) { |
|||
return this.getConfirmStatus(row, 'PROD') && |
|||
this.getConfirmStatus(row, 'QA') && |
|||
this.getConfirmStatus(row, 'TECH') |
|||
}, |
|||
|
|||
/** |
|||
* 获取角色名称 |
|||
*/ |
|||
getRoleName(roleType) { |
|||
const names = { |
|||
'PROD': '生产负责人', |
|||
'QA': '质量负责人', |
|||
'TECH': '技术负责人' |
|||
} |
|||
return names[roleType] || roleType |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.mod-config { |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue