han\hanst 9 hours ago
parent
commit
5ab42e9404
  1. 85
      src/App.vue
  2. 103
      src/api/erf/erf.js
  3. 104
      src/assets/scss/approval-notification.scss
  4. 1
      src/assets/scss/index.scss
  5. 564
      src/components/ApprovalNotificationManager.vue
  6. 192
      src/config/approval-notification.config.js
  7. 154
      src/views/modules/erf/components/approvalHistory.vue
  8. 537
      src/views/modules/erf/components/erfAttachmentManager.vue
  9. 354
      src/views/modules/erf/components/expApplyForm.vue
  10. 344
      src/views/modules/erf/components/expProjectDetail.vue
  11. 569
      src/views/modules/erf/components/expTriConfirm.vue
  12. 490
      src/views/modules/erf/expApplyApproval.vue
  13. 1311
      src/views/modules/erf/expApplyList.vue
  14. 372
      src/views/modules/erf/plannerSchedule.vue
  15. 324
      src/views/modules/erf/triConfirm.vue

85
src/App.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>

103
src/api/erf/erf.js

@ -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)

104
src/assets/scss/approval-notification.scss

@ -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;
}
}

1
src/assets/scss/index.scss

@ -3,5 +3,6 @@
@import "base";
@import "global";
@import "yzznb";
@import "approval-notification"; // 审批通知样式
@import "rq";

564
src/components/ApprovalNotificationManager.vue

@ -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>

192
src/config/approval-notification.config.js

@ -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'
}
}
}

154
src/views/modules/erf/components/approvalHistory.vue

@ -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>

537
src/views/modules/erf/components/erfAttachmentManager.vue

@ -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">
支持pdfdwgdxfdocdocxxlsxlsxjpgpng格式
</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>

354
src/views/modules/erf/components/expApplyForm.vue

@ -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>

344
src/views/modules/erf/components/expProjectDetail.vue

@ -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>

569
src/views/modules/erf/components/expTriConfirm.vue

@ -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>

490
src/views/modules/erf/expApplyApproval.vue

@ -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

372
src/views/modules/erf/plannerSchedule.vue

@ -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>

324
src/views/modules/erf/triConfirm.vue

@ -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>
Loading…
Cancel
Save