Browse Source

计划员排产UI

master
han\hanst 1 month ago
parent
commit
e98c30e9bb
  1. 871
      src/views/modules/erf/plannerSchedule.vue
  2. 2
      src/views/modules/erf/triConfirm.vue

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

@ -1,13 +1,55 @@
<template>
<div class="mod-config">
<!-- 查询条件 -->
<el-form :inline="true" label-position="top">
<div class="schedule-container">
<!-- 页面标题和统计 -->
<div class="page-header">
<div class="header-left">
<h2 class="page-title">
<i class="el-icon-s-order"></i> 计划员排产
</h2>
<p class="page-subtitle">试验申请排产管理</p>
</div>
<div class="header-right">
<div class="stat-cards">
<div class="stat-card stat-total">
<div class="stat-icon"><i class="el-icon-document"></i></div>
<div class="stat-content">
<div class="stat-value">{{ totalPage }}</div>
<div class="stat-label">待排产总数</div>
</div>
</div>
<div class="stat-card stat-high-risk">
<div class="stat-icon"><i class="el-icon-warning"></i></div>
<div class="stat-content">
<div class="stat-value">{{ highRiskCount }}</div>
<div class="stat-label">高风险试验</div>
</div>
</div>
<div class="stat-card stat-low-risk">
<div class="stat-icon"><i class="el-icon-success"></i></div>
<div class="stat-content">
<div class="stat-value">{{ lowRiskCount }}</div>
<div class="stat-label">低风险试验</div>
</div>
</div>
</div>
</div>
</div>
<!-- 查询条件表单 -->
<div class="search-section">
<el-collapse v-model="searchExpanded">
<el-collapse-item name="1">
<template slot="title">
<i class="el-icon-search"></i>
<span style="margin-left: 8px; font-weight: 500;">筛选条件</span>
</template>
<el-form :inline="true" label-position="top" class="search-form">
<el-form-item label="申请单号">
<el-input v-model="searchData.applyNo" placeholder="请输入申请单号" clearable style="width: 150px"></el-input>
<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-select v-model="searchData.buNo" placeholder="请选择" clearable style="width: 120px">
<el-option label="全部" value=""></el-option>
<el-option
v-for="i in buList"
@ -42,26 +84,6 @@
<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"
@ -83,69 +105,121 @@
</el-form-item>
<el-form-item label=" ">
<el-button @click="getDataList('Y')" type="primary">查询</el-button>
<el-button @click="getDataList('Y')" type="primary" icon="el-icon-search">查询</el-button>
<el-button @click="resetQuery()" type="default" icon="el-icon-refresh-left">重置</el-button>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
<!-- 待排产申请单列表 -->
<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>
<!-- 分页 -->
<!-- 卡片列表 -->
<div class="cards-container" v-loading="dataListLoading">
<transition-group name="card-list" tag="div" class="cards-grid">
<div
v-for="item in dataList"
:key="item.applyNo"
class="schedule-card"
:class="{'high-risk': item.experimentType === 'High Risk'}"
@click="openScheduleDialog(item)">
<!-- 卡片头部 -->
<div class="card-header">
<div class="card-title-area">
<el-tag
:type="item.experimentType === 'High Risk' ? 'danger' : 'success'"
size="middle"
effect="dark"
class="risk-tag">
<i :class="item.experimentType === 'High Risk' ? 'el-icon-warning' : 'el-icon-success'"></i>
{{ item.experimentType }}
</el-tag>
<span class="apply-no">{{ item.applyNo }}</span>
</div>
<el-tag type="info" size="small" effect="plain">{{ item.buNo }}</el-tag>
</div>
<!-- 卡片主体内容 -->
<div class="card-body">
<h3 class="experiment-title">
<i class="el-icon-document"></i>
{{ item.title }}
</h3>
<div class="card-details">
<div class="detail-row">
<span class="detail-label">
<i class="el-icon-box"></i>
项目编号
</span>
<span class="detail-value">{{ item.projectNo || '-' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">
<i class="el-icon-goods"></i>
产品型号
</span>
<span class="detail-value" :title="item.productType">{{ item.productType || '-' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">
<i class="el-icon-date"></i>
期望完成日期
</span>
<span class="detail-value">{{ item.expectedFinishDate || '-' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">
<i class="el-icon-user"></i>
申请人
</span>
<span class="detail-value">{{ item.creatorName }}</span>
</div>
<div class="detail-row">
<span class="detail-label">
<i class="el-icon-time"></i>
申请时间
</span>
<span class="detail-value">{{ item.createTime }}</span>
</div>
</div>
</div>
<!-- 卡片底部 -->
<div class="card-footer">
<div class="current-step">
<i class="el-icon-position"></i>
{{ item.currentStep || '待排产' }}
</div>
<div class="action-buttons">
<el-button
type="primary"
size="small"
plain
icon="el-icon-s-order"
class="schedule-btn"
@click.stop="openScheduleDialog(item)">
立即排产
</el-button>
</div>
</div>
</div>
</transition-group>
<!-- 空状态 -->
<div v-if="!dataListLoading && dataList.length === 0" class="empty-state">
<i class="el-icon-document-checked empty-icon"></i>
<p class="empty-text">暂无待排产申请</p>
<p class="empty-subtext">所有申请已完成排产</p>
</div>
</div>
<!-- 分页组件 -->
<div class="pagination-wrapper" v-if="dataList.length > 0">
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
@ -154,54 +228,58 @@
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top: 20px; text-align: right;">
background>
</el-pagination>
</div>
<!-- 排产弹窗 -->
<el-dialog
title="计划员排产"
:visible.sync="scheduleDialogVisible"
width="600px"
:close-on-click-modal="false"
v-drag>
<div slot="title" class="dialog-header">
<i class="el-icon-s-order"></i>
<span>计划员排产</span>
</div>
<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'">
<!-- 申请单基本信息 -->
<div class="schedule-info-section">
<div class="info-title">
<i class="el-icon-info"></i>
申请单信息
</div>
<el-descriptions :column="2" border size="small" class="apply-descriptions">
<el-descriptions-item label="申请单号">{{ scheduleData.applyNo }}</el-descriptions-item>
<el-descriptions-item label="试验类型">
<el-tag :type="currentApply.experimentType === 'High Risk' ? 'danger' : 'success'" effect="dark">
<i :class="currentApply.experimentType === 'High Risk' ? 'el-icon-warning' : 'el-icon-success'"></i>
{{ 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-descriptions-item>
<el-descriptions-item label="试验名称" :span="2">{{ currentApply.title }}</el-descriptions-item>
<el-descriptions-item label="项目编号">{{ currentApply.projectNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="产品型号">{{ currentApply.productType || '-' }}</el-descriptions-item>
<el-descriptions-item label="期望完成日期">{{ currentApply.expectedFinishDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请人">{{ currentApply.creatorName }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 排产信息录入 -->
<div class="schedule-input-section">
<div class="info-title">
<i class="el-icon-edit"></i>
排产信息
</div>
<el-form :model="scheduleData" label-position="top" class="schedule-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="实验工单号" required>
<el-input
v-model="scheduleData.workOrderNo"
placeholder="请输入实验工单号">
placeholder="请输入实验工单号"
clearable>
<i slot="prefix" class="el-input__icon el-icon-document"></i>
</el-input>
</el-form-item>
</el-col>
@ -214,18 +292,20 @@
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
placeholder="选择排产日期"
prefix-icon="el-icon-date"
style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-footer style="height: 40px; margin-top: 10px; text-align: center">
<el-button type="primary" @click="doSchedule" :loading="scheduleLoading">
<el-button type="primary" @click="doSchedule" :loading="scheduleLoading" >
{{ scheduleLoading ? '排产中...' : '确定排产' }}
</el-button>
<el-button type="primary" @click="scheduleDialogVisible = false" :disabled="scheduleLoading">关闭</el-button>
<el-button @click="scheduleDialogVisible = false" :disabled="scheduleLoading">关闭</el-button>
</el-footer>
</el-dialog>
</div>
@ -241,9 +321,17 @@ export default {
data() {
return {
buList: [],
searchExpanded: ['0'], //
searchData: {
applyNo: '',
buNo: '',
experimentType: '',
title: '',
projectNo: '',
productType: '',
creatorName: '',
expectedFinishStartDate: '',
expectedFinishEndDate: '',
currentUserId: this.$store.state.user.id,
pageType: 'PLANNER', //
pendingStatus: '已批准', //
@ -264,9 +352,23 @@ export default {
workOrderNo: '',
scheduledDate: ''
},
scheduleLoading: false,
scheduleLoading: false
}
},
computed: {
/**
* 计算高风险试验数量
*/
highRiskCount() {
return this.dataList.filter(item => item.experimentType === 'High Risk').length
},
height: window.innerHeight - 260
/**
* 计算低风险试验数量
*/
lowRiskCount() {
return this.dataList.filter(item => item.experimentType === 'Low Risk').length
}
},
@ -288,6 +390,9 @@ export default {
})
},
/**
* 获取待排产列表
*/
getDataList(flag) {
if (flag === 'Y') {
this.pageIndex = 1
@ -295,6 +400,7 @@ export default {
this.searchData.page = this.pageIndex
this.searchData.limit = this.pageSize
this.searchData.currentUserId = this.$store.state.user.id
this.dataListLoading = true
@ -306,12 +412,30 @@ export default {
} else {
this.dataList = []
this.totalPage = 0
this.$message.error(data.msg || '查询失败')
}
}).catch(error => {
this.dataListLoading = false
this.$message.error('查询异常')
})
},
/**
* 重置查询条件
*/
resetQuery() {
this.searchData.applyNo = ''
this.searchData.buNo = ''
this.searchData.experimentType = ''
this.searchData.title = ''
this.searchData.projectNo = ''
this.searchData.productType = ''
this.searchData.creatorName = ''
this.searchData.expectedFinishStartDate = ''
this.searchData.expectedFinishEndDate = ''
this.getDataList('Y')
},
openScheduleDialog(row) {
this.currentApply = { ...row }
this.scheduleData = {
@ -367,7 +491,512 @@ export default {
</script>
<style scoped>
.mod-config {
/* 整体容器 */
.schedule-container {
padding: 15px;
background: #f5f7fa;
min-height: calc(100vh - 80px);
}
/* ===== 页面头部 ===== */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding: 5px 20px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #EBEEF5;
}
.header-left {
color: #303133;
}
.page-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #303133;
display: flex;
align-items: center;
gap: 10px;
}
.page-title i {
font-size: 22px;
color: #409EFF;
}
.page-subtitle {
margin: 6px 0 0 0;
font-size: 13px;
color: #909399;
}
.header-right {
display: flex;
gap: 12px;
}
/* ===== 统计卡片 ===== */
.stat-cards {
display: flex;
gap: 12px;
}
.stat-card {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #EBEEF5;
transition: all 0.3s ease;
cursor: pointer;
min-width: 140px;
}
.stat-card:hover {
border-color: #409EFF;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
}
.stat-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
font-size: 20px;
color: white;
}
.stat-total .stat-icon {
background: #409EFF;
}
.stat-high-risk .stat-icon {
background: #F56C6C;
}
.stat-low-risk .stat-icon {
background: #67C23A;
}
.stat-content {
display: flex;
flex-direction: column;
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: #303133;
line-height: 1;
}
.stat-label {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
/* ===== 搜索区域 ===== */
.search-section {
margin-bottom: 15px;
background: white;
border-radius: 4px;
border: 1px solid #EBEEF5;
overflow: hidden;
}
.search-section >>> .el-collapse-item__header {
padding: 0 15px;
height: 45px;
line-height: 45px;
background: white;
border-bottom: 1px solid #ebeef5;
font-size: 13px;
color: #303133;
font-weight: 500;
}
.search-section >>> .el-collapse-item__content {
padding: 15px;
background: #FFFFFF;
}
.search-form {
margin: 0;
}
.search-form >>> .el-form-item {
margin-bottom: 10px;
}
/* ===== 卡片容器 ===== */
.cards-container {
min-height: 400px;
position: relative;
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 15px;
margin-bottom: 15px;
}
/* ===== 排产卡片 ===== */
.schedule-card {
background: white;
border-radius: 4px;
padding: 18px;
border: 1px solid #EBEEF5;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
}
.schedule-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 1px;
height: 100%;
background: #67C23A;
transition: all 0.3s ease;
}
.schedule-card.high-risk::before {
background: #F56C6C;
}
.schedule-card:hover {
border-color: #409EFF;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
}
/* 卡片头部 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
padding-bottom: 10px;
border-bottom: 1px solid #EBEEF5;
}
.card-title-area {
display: flex;
align-items: center;
gap: 10px;
}
.risk-tag {
font-weight: 500;
padding: 0px 10px;
font-size: 14px;
}
.risk-tag i {
margin-right: 4px;
}
.apply-no {
font-size: 13px;
color: #606266;
font-family: 'Courier New', monospace;
font-weight: 500;
}
/* 卡片主体 */
.card-body {
//margin-bottom: 14px;
}
.experiment-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 0 0 14px 0;
line-height: 1.5;
display: flex;
align-items: flex-start;
gap: 6px;
}
.experiment-title i {
color: #409EFF;
margin-top: 2px;
font-size: 16px;
}
.card-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
padding: 6px 10px;
background: #F5F7FA;
border-radius: 3px;
transition: all 0.2s ease;
}
.detail-row:hover {
background: #ECF5FF;
}
.detail-label {
color: #909399;
display: flex;
align-items: center;
gap: 5px;
font-weight: 500;
}
.detail-label i {
color: #409EFF;
font-size: 14px;
}
.detail-value {
color: #606266;
font-weight: 500;
text-align: right;
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 卡片底部 */
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid #EBEEF5;
}
.current-step {
font-size: 12px;
color: #909399;
display: flex;
align-items: center;
gap: 5px;
}
.current-step i {
color: #409EFF;
}
.action-buttons {
display: flex;
gap: 8px;
}
.schedule-btn {
background: #ecf5ff;
border-color: #b3d8ff;
color: #409EFF;
font-weight: 500;
padding: 7px 16px;
font-size: 13px;
transition: all 0.3s ease;
}
.schedule-btn:hover {
background: #409EFF;
border-color: #409EFF;
color: #FFFFFF;
}
/* ===== 空状态 ===== */
.empty-state {
text-align: center;
padding: 80px 20px;
background: white;
border-radius: 4px;
border: 1px solid #EBEEF5;
}
.empty-icon {
font-size: 64px;
color: #c0c4cc;
margin-bottom: 12px;
}
.empty-text {
font-size: 15px;
color: #606266;
margin: 0 0 6px 0;
font-weight: 500;
}
.empty-subtext {
font-size: 13px;
color: #909399;
margin: 0;
}
/* ===== 分页 ===== */
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 15px;
background: white;
border-radius: 4px;
border: 1px solid #EBEEF5;
}
/* ===== 卡片动画 ===== */
.card-list-enter-active {
animation: cardFadeIn 0.4s ease;
}
.card-list-leave-active {
animation: cardFadeOut 0.3s ease;
}
@keyframes cardFadeIn {
from {
opacity: 0;
transform: translateY(15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes cardFadeOut {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.95);
}
}
/* ===== 响应式设计 ===== */
@media screen and (max-width: 1600px) {
.cards-grid {
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}
}
@media screen and (max-width: 1200px) {
.cards-grid {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.page-header {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.stat-cards {
width: 100%;
overflow-x: auto;
}
}
@media screen and (max-width: 768px) {
.cards-grid {
grid-template-columns: 1fr;
}
.stat-cards {
flex-direction: column;
}
.stat-card {
width: 100%;
}
}
/* ===== 弹窗样式 ===== */
.dialog-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.dialog-header i {
color: #409EFF;
font-size: 18px;
}
.schedule-info-section {
margin-bottom: 20px;
}
.schedule-input-section {
margin-top: 20px;
}
.info-title {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #409EFF;
}
.info-title i {
color: #409EFF;
font-size: 16px;
}
.apply-descriptions {
font-size: 13px;
}
.apply-descriptions >>> .el-descriptions-item__label {
background: #F5F7FA;
font-weight: 600;
color: #606266;
width: 120px;
}
.apply-descriptions >>> .el-descriptions-item__content {
color: #303133;
}
.schedule-form {
margin-top: 10px;
}
.schedule-form >>> .el-form-item__label {
font-weight: 600;
color: #606266;
}
.dialog-footer {
text-align: right;
padding-top: 20px;
border-top: 1px solid #EBEEF5;
}
</style>

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

@ -271,7 +271,7 @@ export default {
data() {
return {
searchExpanded: ['1'], //
searchExpanded: ['0'], //
//
queryData: {

Loading…
Cancel
Save