| | |
| | | // 审批管理配置 |
| | | import request from "@/utils/request"; |
| | | |
| | | // 查询审批配置列表 |
| | | export function getApprovalConfigList(approveType) { |
| | | // 查询审批流程配置节点列表 |
| | | export function getApproveProcessConfigNodeList(type) { |
| | | return request({ |
| | | url: '/approvalConfig/list', |
| | | url: '/approveProcessConfigNode/list', |
| | | method: 'get', |
| | | params: { approveType }, |
| | | params: { type }, |
| | | }) |
| | | } |
| | | |
| | | // 查询审批配置详情 |
| | | export function getApprovalConfigDetail(id) { |
| | | // 新增审批流程配置节点 |
| | | export function addApproveProcessConfigNode(data) { |
| | | return request({ |
| | | url: '/approvalConfig/get/' + id, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | |
| | | // 新增审批配置 |
| | | export function addApprovalConfig(data) { |
| | | return request({ |
| | | url: '/approvalConfig/add', |
| | | method: 'post', |
| | | data: data, |
| | | }) |
| | | } |
| | | |
| | | // 修改审批配置 |
| | | export function updateApprovalConfig(data) { |
| | | return request({ |
| | | url: '/approvalConfig/update', |
| | | method: 'post', |
| | | data: data, |
| | | }) |
| | | } |
| | | |
| | | // 删除审批配置 |
| | | export function deleteApprovalConfig(id) { |
| | | return request({ |
| | | url: '/approvalConfig/delete/' + id, |
| | | method: 'delete', |
| | | }) |
| | | } |
| | | |
| | | // 批量保存审批配置 |
| | | export function batchSaveApprovalConfig(data) { |
| | | return request({ |
| | | url: '/approvalConfig/batchSave', |
| | | url: '/approveProcessConfigNode/add', |
| | | method: 'post', |
| | | data: data, |
| | | }) |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // 查询不合格管理列表 |
| | | // 查询不合格品处理单列表 |
| | | export function qualityUnqualifiedListPage(query) { |
| | | return request({ |
| | | url: "/quality/qualityUnqualified/listPage", |
| | | url: "/qualityUnqualifiedOrder/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // 新增不合格管理列表 |
| | | export function qualityUnqualifiedAdd(query) { |
| | | |
| | | // 新增不合格品处理单 |
| | | export function qualityUnqualifiedAdd(data) { |
| | | return request({ |
| | | url: "/quality/qualityUnqualified/add", |
| | | url: "/qualityUnqualifiedOrder/save", |
| | | method: "post", |
| | | data: query, |
| | | data: data, |
| | | }); |
| | | } |
| | | // 修改不合格管理列表 |
| | | export function qualityUnqualifiedUpdate(query) { |
| | | |
| | | // 修改不合格品处理单 |
| | | export function qualityUnqualifiedUpdate(data) { |
| | | return request({ |
| | | url: "/quality/qualityUnqualified/update", |
| | | method: "post", |
| | | data: query, |
| | | url: "/qualityUnqualifiedOrder/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | // 不合格处理 |
| | | export function qualityUnqualifiedDeal(query) { |
| | | |
| | | // 删除不合格品处理单 |
| | | export function qualityUnqualifiedDel(ids) { |
| | | return request({ |
| | | url: "/quality/qualityUnqualified/deal", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // 删除不合格管理列表 |
| | | export function qualityUnqualifiedDel(query) { |
| | | return request({ |
| | | url: "/quality/qualityUnqualified/del", |
| | | url: "/qualityUnqualifiedOrder/delete", |
| | | method: "delete", |
| | | data: query, |
| | | data: ids, |
| | | }); |
| | | } |
| | | // 查询不合格管理信息 |
| | | export function getQualityUnqualifiedInfo(query) { |
| | | |
| | | // 查询不合格品处理单详情 |
| | | export function getQualityUnqualifiedInfo(id) { |
| | | return request({ |
| | | url: "/quality/qualityUnqualified/" + query, |
| | | url: "/qualityUnqualifiedOrder/listPage?current=1&size=1&id=" + id, |
| | | method: "get", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // 不合格品处理 |
| | | export function qualityUnqualifiedDeal(data) { |
| | | return request({ |
| | | url: "/qualityUnqualifiedOrder/deal", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | |
| | | // cover some element-ui styles
|
| | |
|
| | | .el-breadcrumb__inner,
|
| | | .el-breadcrumb__inner a {
|
| | | font-weight: 400 !important;
|
| | | }
|
| | |
|
| | | .el-upload {
|
| | | input[type="file"] {
|
| | | display: none !important;
|
| | | }
|
| | | }
|
| | |
|
| | | .el-upload__input {
|
| | | display: none;
|
| | | }
|
| | |
|
| | | .cell {
|
| | | .el-tag {
|
| | | margin-right: 0px;
|
| | | }
|
| | | }
|
| | |
|
| | | .small-padding {
|
| | | .cell {
|
| | | padding-left: 5px;
|
| | | padding-right: 5px;
|
| | | }
|
| | | }
|
| | |
|
| | | .fixed-width {
|
| | | .el-button--mini {
|
| | | padding: 7px 10px;
|
| | | width: 60px;
|
| | | }
|
| | | }
|
| | |
|
| | | .status-col {
|
| | | .cell {
|
| | | padding: 0 10px;
|
| | | text-align: center;
|
| | |
|
| | | .el-tag {
|
| | | margin-right: 0px;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // to fixed https://github.com/ElemeFE/element/issues/2461
|
| | | // cover some element-ui styles |
| | | |
| | | .el-breadcrumb__inner, |
| | | .el-breadcrumb__inner a { |
| | | font-weight: 400 !important; |
| | | } |
| | | |
| | | .el-upload { |
| | | input[type="file"] { |
| | | display: none !important; |
| | | } |
| | | } |
| | | |
| | | .el-upload__input { |
| | | display: none; |
| | | } |
| | | |
| | | .cell { |
| | | .el-tag { |
| | | margin-right: 0px; |
| | | } |
| | | } |
| | | |
| | | .small-padding { |
| | | .cell { |
| | | padding-left: 5px; |
| | | padding-right: 5px; |
| | | } |
| | | } |
| | | |
| | | .fixed-width { |
| | | .el-button--mini { |
| | | padding: 7px 10px; |
| | | width: 60px; |
| | | } |
| | | } |
| | | |
| | | .status-col { |
| | | .cell { |
| | | padding: 0 10px; |
| | | text-align: center; |
| | | |
| | | .el-tag { |
| | | margin-right: 0px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // to fixed https://github.com/ElemeFE/element/issues/2461 |
| | | .el-dialog { |
| | | transform: none; |
| | | left: 0; |
| | |
| | | .el-message-box__content { |
| | | padding: 24px 24px 0; |
| | | } |
| | | .el-message-box__container {
|
| | | justify-content: center;
|
| | | }
|
| | | .el-message-box__btns {
|
| | | text-align: center;
|
| | | padding: 16px;
|
| | | display: flex;
|
| | | flex-direction: row-reverse;
|
| | | justify-content: center;
|
| | | align-items: center;
|
| | | .el-button--primary {
|
| | | margin-right: 12px;
|
| | | }
|
| | | }
|
| | | .el-message-box__container { |
| | | justify-content: center; |
| | | } |
| | | .el-message-box__btns { |
| | | text-align: center; |
| | | padding: 16px; |
| | | display: flex; |
| | | flex-direction: row-reverse; |
| | | justify-content: center; |
| | | align-items: center; |
| | | .el-button--primary { |
| | | margin-right: 12px; |
| | | } |
| | | } |
| | | .el-table__expanded-cell { |
| | | padding: 0 !important; |
| | | .el-table__header-wrapper { |
| | | background-color: var(--surface-soft) !important; |
| | | } |
| | | } |
| | |
|
| | | // refine element ui upload
|
| | | .upload-container {
|
| | | .el-upload {
|
| | | width: 100%;
|
| | |
|
| | | .el-upload-dragger {
|
| | | width: 100%;
|
| | | height: 200px;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // dropdown
|
| | | .el-dropdown-menu {
|
| | | a {
|
| | | display: block;
|
| | | }
|
| | | }
|
| | |
|
| | | // fix date-picker ui bug in filter-item
|
| | | .el-range-editor.el-input__inner {
|
| | | display: inline-flex !important;
|
| | | }
|
| | |
|
| | | // to fix el-date-picker css style
|
| | | .el-range-separator {
|
| | | box-sizing: content-box;
|
| | | }
|
| | |
|
| | | .el-menu--collapse
|
| | | > div
|
| | | > .el-submenu
|
| | | > .el-submenu__title
|
| | | .el-submenu__icon-arrow {
|
| | | display: none;
|
| | | }
|
| | |
|
| | | |
| | | // refine element ui upload |
| | | .upload-container { |
| | | .el-upload { |
| | | width: 100%; |
| | | |
| | | .el-upload-dragger { |
| | | width: 100%; |
| | | height: 200px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // dropdown |
| | | .el-dropdown-menu { |
| | | a { |
| | | display: block; |
| | | } |
| | | } |
| | | |
| | | // fix date-picker ui bug in filter-item |
| | | .el-range-editor.el-input__inner { |
| | | display: inline-flex !important; |
| | | } |
| | | |
| | | // to fix el-date-picker css style |
| | | .el-range-separator { |
| | | box-sizing: content-box; |
| | | } |
| | | |
| | | .el-menu--collapse |
| | | > div |
| | | > .el-sub-menu |
| | | > .el-sub-menu__title |
| | | .el-sub-menu__icon-arrow { |
| | | display: none; |
| | | } |
| | | |
| | | /* 确保菜单箭头始终显示 - 使用 flex 布局 */ |
| | | .el-sub-menu__title { |
| | | display: flex !important; |
| | | align-items: center !important; |
| | | line-height: normal !important; |
| | | } |
| | | |
| | | .el-sub-menu__title .el-sub-menu__icon-arrow { |
| | | position: static !important; |
| | | display: inline-flex !important; |
| | | visibility: visible !important; |
| | | width: auto !important; |
| | | height: auto !important; |
| | | overflow: visible !important; |
| | | margin-left: auto !important; |
| | | align-self: center !important; |
| | | margin-top: 0 !important; |
| | | margin-bottom: 0 !important; |
| | | transform: none !important; |
| | | top: auto !important; |
| | | } |
| | | |
| | | .el-dropdown .el-dropdown-link { |
| | | color: var(--el-color-primary) !important; |
| | | } |
| | |
| | | #app {
|
| | | .main-container {
|
| | | min-height: 100%;
|
| | | transition: margin-left 0.28s;
|
| | | margin-left: $base-sidebar-width;
|
| | | position: relative;
|
| | | background: transparent;
|
| | | }
|
| | |
|
| | | .sidebarHide {
|
| | | margin-left: 0 !important;
|
| | | }
|
| | |
|
| | | .sidebar-container {
|
| | | transition: width 0.28s;
|
| | | width: $base-sidebar-width !important;
|
| | | height: 100%;
|
| | | position: fixed;
|
| | | font-size: 0px;
|
| | | top: 0;
|
| | | bottom: 0;
|
| | | left: 0;
|
| | | z-index: 1001;
|
| | | overflow: hidden;
|
| | | padding: 12px 0 16px 16px;
|
| | | background: transparent;
|
| | | box-shadow: none;
|
| | |
|
| | | // reset element-ui css
|
| | | .horizontal-collapse-transition {
|
| | | transition: 0s width ease-in-out, 0s padding-left ease-in-out,
|
| | | 0s padding-right ease-in-out;
|
| | | }
|
| | |
|
| | | .scrollbar-wrapper {
|
| | | overflow-x: hidden !important;
|
| | | }
|
| | |
|
| | | .el-scrollbar__bar.is-vertical {
|
| | | right: 0px;
|
| | | }
|
| | |
|
| | | .el-scrollbar {
|
| | | height: 100%;
|
| | | }
|
| | |
|
| | | &.has-logo {
|
| | | .el-scrollbar {
|
| | | height: calc(100% - 72px);
|
| | | margin-top: 10px;
|
| | | }
|
| | | }
|
| | |
|
| | | .is-horizontal {
|
| | | display: none;
|
| | | }
|
| | |
|
| | | a {
|
| | | display: inline-block;
|
| | | width: 100%;
|
| | | overflow: hidden;
|
| | | }
|
| | |
|
| | | .svg-icon {
|
| | | margin-right: 16px;
|
| | | }
|
| | |
|
| | | #app { |
| | | .main-container { |
| | | min-height: 100%; |
| | | transition: margin-left 0.28s; |
| | | margin-left: $base-sidebar-width; |
| | | position: relative; |
| | | background: transparent; |
| | | } |
| | | |
| | | .sidebarHide { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | .sidebar-container { |
| | | transition: width 0.28s; |
| | | width: $base-sidebar-width !important; |
| | | height: 100%; |
| | | position: fixed; |
| | | top: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | z-index: 1001; |
| | | overflow: hidden; |
| | | padding: 12px 0 16px 16px; |
| | | background: transparent; |
| | | box-shadow: none; |
| | | |
| | | // reset element-ui css |
| | | .horizontal-collapse-transition { |
| | | transition: 0s width ease-in-out, 0s padding-left ease-in-out, |
| | | 0s padding-right ease-in-out; |
| | | } |
| | | |
| | | .scrollbar-wrapper { |
| | | overflow-x: hidden !important; |
| | | } |
| | | |
| | | .el-scrollbar__bar.is-vertical { |
| | | right: 0px; |
| | | } |
| | | |
| | | .el-scrollbar { |
| | | height: 100%; |
| | | } |
| | | |
| | | &.has-logo { |
| | | .el-scrollbar { |
| | | height: calc(100% - 72px); |
| | | margin-top: 10px; |
| | | } |
| | | } |
| | | |
| | | .is-horizontal { |
| | | display: none; |
| | | } |
| | | |
| | | a { |
| | | display: inline-block; |
| | | width: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .svg-icon { |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .el-menu { |
| | | border: none; |
| | | height: 100%; |
| | |
| | | backdrop-filter: blur(18px); |
| | | box-shadow: var(--shadow-sm); |
| | | } |
| | |
|
| | | .el-menu-item,
|
| | | .menu-title {
|
| | | overflow: hidden !important;
|
| | | text-overflow: ellipsis !important;
|
| | | white-space: nowrap !important;
|
| | | }
|
| | |
|
| | | .el-menu-item .el-menu-tooltip__trigger {
|
| | | display: inline-block !important;
|
| | | }
|
| | |
|
| | | |
| | | .el-menu-item, |
| | | .menu-title { |
| | | overflow: hidden !important; |
| | | text-overflow: ellipsis !important; |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .el-menu-item .el-menu-tooltip__trigger { |
| | | display: inline-block !important; |
| | | } |
| | | |
| | | // menu hover |
| | | .submenu-title-noDropdown, |
| | | .el-sub-menu__title { |
| | |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | | & .theme-light .is-active > .el-sub-menu__title {
|
| | | color: var(--current-color) !important;
|
| | | }
|
| | |
|
| | | |
| | | // 所有子菜单标题,使用 flex 布局让箭头和文字在一排 |
| | | .el-sub-menu__title { |
| | | padding-right: 10px !important; |
| | | display: flex !important; |
| | | align-items: center !important; |
| | | line-height: normal !important; |
| | | } |
| | | |
| | | // 顶级子菜单标题 |
| | | & > .el-menu > .el-sub-menu > .el-sub-menu__title { |
| | | padding-right: 10px !important; |
| | | display: flex !important; |
| | | align-items: center !important; |
| | | line-height: normal !important; |
| | | } |
| | | & .theme-light .is-active > .el-sub-menu__title { |
| | | color: var(--current-color) !important; |
| | | } |
| | | |
| | | & .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .el-sub-menu .el-menu-item { |
| | | min-width: 0 !important; |
| | | margin: 0 12px 6px; |
| | | width: calc(100% - 24px); |
| | | padding-left: 8px !important; |
| | | padding-right: 8px !important; |
| | | padding-right: 24px !important; |
| | | box-sizing: border-box; |
| | | |
| | | &:hover { |
| | |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | |
|
| | | |
| | | & .theme-light .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .theme-light .el-sub-menu .el-menu-item { |
| | | //background-color: transparent; |
| | |
| | | } |
| | | } |
| | | } |
| | |
|
| | | |
| | | .hideSidebar { |
| | | .sidebar-container { |
| | | width: 68px !important; |
| | |
| | | .main-container { |
| | | margin-left: 84px; |
| | | } |
| | |
|
| | | |
| | | .submenu-title-noDropdown { |
| | | padding: 0 !important; |
| | | position: relative; |
| | |
| | | width: 0; |
| | | overflow: hidden; |
| | | visibility: hidden; |
| | | display: inline-block;
|
| | | }
|
| | | & > i {
|
| | | height: 0;
|
| | | width: 0;
|
| | | overflow: hidden;
|
| | | visibility: hidden;
|
| | | display: inline-block;
|
| | | display: inline-block; |
| | | } |
| | | & > i.el-sub-menu__icon-arrow { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
|
| | | .el-menu--collapse .el-menu .el-sub-menu {
|
| | | min-width: $base-sidebar-width !important;
|
| | | }
|
| | |
|
| | | // mobile responsive
|
| | | .mobile {
|
| | | .main-container {
|
| | | margin-left: 0px;
|
| | | }
|
| | |
|
| | | .sidebar-container {
|
| | | transition: transform 0.28s;
|
| | | width: $base-sidebar-width !important;
|
| | | }
|
| | |
|
| | | &.hideSidebar {
|
| | | .sidebar-container {
|
| | | pointer-events: none;
|
| | | transition-duration: 0.3s;
|
| | | transform: translate3d(-$base-sidebar-width, 0, 0);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .withoutAnimation {
|
| | | .main-container,
|
| | | .sidebar-container {
|
| | | transition: none;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // when menu collapsed
|
| | | .el-menu--vertical {
|
| | | & > .el-menu {
|
| | | .svg-icon {
|
| | | margin-right: 16px;
|
| | | }
|
| | | }
|
| | |
|
| | | |
| | | .el-menu--collapse .el-menu .el-sub-menu { |
| | | min-width: $base-sidebar-width !important; |
| | | } |
| | | |
| | | // mobile responsive |
| | | .mobile { |
| | | .main-container { |
| | | margin-left: 0px; |
| | | } |
| | | |
| | | .sidebar-container { |
| | | transition: transform 0.28s; |
| | | width: $base-sidebar-width !important; |
| | | } |
| | | |
| | | &.hideSidebar { |
| | | .sidebar-container { |
| | | pointer-events: none; |
| | | transition-duration: 0.3s; |
| | | transform: translate3d(-$base-sidebar-width, 0, 0); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .withoutAnimation { |
| | | .main-container, |
| | | .sidebar-container { |
| | | transition: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // when menu collapsed |
| | | .el-menu--vertical { |
| | | & > .el-menu { |
| | | .svg-icon { |
| | | margin-right: 16px; |
| | | } |
| | | } |
| | | |
| | | .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | .el-menu-item { |
| | | min-width: 0 !important; |
| | |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | |
|
| | | // the scroll bar appears when the sub-menu is too long
|
| | | > .el-menu--popup {
|
| | | max-height: 100vh;
|
| | | overflow-y: auto;
|
| | | padding: 8px;
|
| | | border-radius: 18px;
|
| | | border: 1px solid var(--surface-border);
|
| | | box-shadow: var(--shadow-md);
|
| | |
|
| | | &::-webkit-scrollbar-track-piece {
|
| | | background: #dfe7e1;
|
| | | }
|
| | |
|
| | | &::-webkit-scrollbar {
|
| | | width: 6px;
|
| | | }
|
| | |
|
| | | &::-webkit-scrollbar-thumb {
|
| | | background: #9aa79e;
|
| | | border-radius: 20px;
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | // the scroll bar appears when the sub-menu is too long |
| | | > .el-menu--popup { |
| | | max-height: 100vh; |
| | | overflow-y: auto; |
| | | padding: 8px; |
| | | border-radius: 18px; |
| | | border: 1px solid var(--surface-border); |
| | | box-shadow: var(--shadow-md); |
| | | |
| | | &::-webkit-scrollbar-track-piece { |
| | | background: #dfe7e1; |
| | | } |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: #9aa79e; |
| | | border-radius: 20px; |
| | | } |
| | | } |
| | | } |
| | |
| | | </app-link>
|
| | | </template>
|
| | |
|
| | | <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
|
| | | <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)">
|
| | | <template v-if="item.meta" #title>
|
| | | <svg-icon :icon-class="item.meta && item.meta.icon" />
|
| | | <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
|
| | |
| | | |
| | | .el-sub-menu__title { |
| | | color: v-bind(getMenuTextColor); |
| | | padding-right: 10px !important; |
| | | display: flex !important; |
| | | align-items: center !important; |
| | | line-height: normal !important; |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title) { |
| | |
| | | padding-left: 10px !important; |
| | | padding-right: 10px !important; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | background-clip: padding-box; |
| | | display: flex !important; |
| | | align-items: center !important; |
| | | line-height: normal !important; |
| | | } |
| | | |
| | | :deep(.el-menu-item.is-active) { |
| | |
| | | :deep(.el-menu-item:hover) { |
| | | border-radius: 14px; |
| | | } |
| | | |
| | | /* 确保子菜单箭头显示 - 使用 flex 布局让箭头和文字在一排 */ |
| | | :deep(.el-sub-menu .el-sub-menu__title) { |
| | | display: flex !important; |
| | | align-items: center !important; |
| | | justify-content: flex-start !important; |
| | | line-height: normal !important; |
| | | } |
| | | |
| | | :deep(.el-sub-menu .el-sub-menu__title .el-sub-menu__icon-arrow) { |
| | | position: static !important; |
| | | display: inline-flex !important; |
| | | visibility: visible !important; |
| | | width: auto !important; |
| | | height: auto !important; |
| | | overflow: visible !important; |
| | | margin-left: auto !important; |
| | | margin-right: 0 !important; |
| | | order: 999 !important; |
| | | align-self: center !important; |
| | | margin-top: 0 !important; |
| | | margin-bottom: 0 !important; |
| | | transform: none !important; |
| | | top: auto !important; |
| | | } |
| | | |
| | | /* 确保一级菜单箭头显示 */ |
| | | :deep(> .el-menu > .el-sub-menu > .el-sub-menu__title .el-sub-menu__icon-arrow) { |
| | | display: inline-flex !important; |
| | | visibility: visible !important; |
| | | } |
| | | |
| | | /* 确保二级菜单箭头显示 */ |
| | | :deep(.nest-menu .el-sub-menu > .el-sub-menu__title .el-sub-menu__icon-arrow) { |
| | | display: inline-flex !important; |
| | | visibility: visible !important; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | import { |
| | | Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting, |
| | | Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked, |
| | | Van, ArrowRight, User, InfoFilled |
| | | Van, ArrowRight, User, InfoFilled, Sell |
| | | } from '@element-plus/icons-vue'; |
| | | import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement'; |
| | | import { userListNoPage } from '@/api/system/user'; |
| | | |
| | | // 当前选中的标签页 |
| | | const activeTab = ref('1'); |
| | |
| | | { value: '5', label: '采购审批', icon: 'ShoppingCart', color: '#909399' }, |
| | | { value: '6', label: '报价审批', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | { value: '7', label: '发货审批', icon: 'Van', color: '#1ABC9C' }, |
| | | { value: '10', label: '销售审批', icon: 'Sell', color: '#FF6B6B' }, |
| | | ]; |
| | | |
| | | // 审批类型名称映射 |
| | |
| | | 5: '采购审批', |
| | | 6: '报价审批', |
| | | 7: '发货审批', |
| | | 10: '销售审批', |
| | | }; |
| | | |
| | | // 审批类型图标映射 |
| | |
| | | 5: 'ShoppingCart', |
| | | 6: 'DocumentChecked', |
| | | 7: 'Van', |
| | | 10: 'Sell', |
| | | }; |
| | | |
| | | // 审批类型颜色映射 |
| | |
| | | 5: '#909399', |
| | | 6: '#9B59B6', |
| | | 7: '#1ABC9C', |
| | | 10: '#FF6B6B', |
| | | }; |
| | | |
| | | // 头像颜色池 |
| | |
| | | return texts[index] || `第${index + 1}级`; |
| | | }; |
| | | |
| | | // 获取审批人数量 |
| | | const getApproverCount = (typeValue) => { |
| | | const type = Number(typeValue); |
| | | const data = mockConfigData[type] || []; |
| | | return data.length; |
| | | }; |
| | | |
| | | // 模拟用户列表数据 |
| | | const userList = ref([ |
| | | { userId: 1, nickName: '张三' }, |
| | | { userId: 2, nickName: '李四' }, |
| | | { userId: 3, nickName: '王五' }, |
| | | { userId: 4, nickName: '赵六' }, |
| | | { userId: 5, nickName: '孙七' }, |
| | | { userId: 6, nickName: '周八' }, |
| | | { userId: 7, nickName: '吴九' }, |
| | | { userId: 8, nickName: '郑十' }, |
| | | ]); |
| | | |
| | | // 模拟审批配置数据存储(按审批类型分类) |
| | | const mockConfigData = { |
| | | 1: [ |
| | | { id: 1, approveType: 1, approverId: 1, approverName: '张三', sortOrder: 1 }, |
| | | { id: 2, approveType: 1, approverId: 2, approverName: '李四', sortOrder: 2 }, |
| | | ], |
| | | 2: [ |
| | | { id: 3, approveType: 2, approverId: 3, approverName: '王五', sortOrder: 1 }, |
| | | ], |
| | | 3: [], |
| | | 4: [ |
| | | { id: 4, approveType: 4, approverId: 1, approverName: '张三', sortOrder: 1 }, |
| | | { id: 5, approveType: 4, approverId: 3, approverName: '王五', sortOrder: 2 }, |
| | | { id: 6, approveType: 4, approverId: 5, approverName: '孙七', sortOrder: 3 }, |
| | | ], |
| | | 5: [], |
| | | 6: [], |
| | | 7: [], |
| | | }; |
| | | // 审批人列表(真实接口) |
| | | const userList = ref([]); |
| | | |
| | | // 审批人列表 |
| | | const approverList = ref([]); |
| | |
| | | }; |
| | | |
| | | // 加载审批配置数据(模拟) |
| | | const loadData = () => { |
| | | const loadData = async () => { |
| | | loading.value = true; |
| | | setTimeout(() => { |
| | | const data = mockConfigData[currentApproveType.value] || []; |
| | | approverList.value = data.sort((a, b) => a.sortOrder - b.sortOrder); |
| | | try { |
| | | const res = await getApproveProcessConfigNodeList(currentApproveType.value); |
| | | const source = Array.isArray(res?.data) |
| | | ? res.data |
| | | : Array.isArray(res?.rows) |
| | | ? res.rows |
| | | : Array.isArray(res?.data?.records) |
| | | ? res.data.records |
| | | : []; |
| | | const data = source.map((item, index) => ({ |
| | | ...item, |
| | | sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1, |
| | | })); |
| | | approverList.value = data.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); |
| | | originalList.value = JSON.parse(JSON.stringify(approverList.value)); |
| | | } catch (error) { |
| | | approverList.value = []; |
| | | originalList.value = []; |
| | | ElMessage.error('加载审批配置失败'); |
| | | } finally { |
| | | loading.value = false; |
| | | }, 300); |
| | | } |
| | | }; |
| | | |
| | | const loadUserList = async () => { |
| | | try { |
| | | const res = await userListNoPage(); |
| | | userList.value = Array.isArray(res?.data) ? res.data : []; |
| | | } catch (error) { |
| | | userList.value = []; |
| | | ElMessage.error('加载人员列表失败'); |
| | | } |
| | | }; |
| | | |
| | | // 审批人选择变化 |
| | |
| | | approverList.value[index + 1].sortOrder = index + 2; |
| | | }; |
| | | |
| | | // 保存配置(模拟) |
| | | const handleSave = () => { |
| | | // 保存配置 |
| | | const handleSave = async () => { |
| | | if (approverList.value.length === 0) { |
| | | ElMessage.warning('请至少配置一个审批人'); |
| | | return; |
| | |
| | | } |
| | | |
| | | saveLoading.value = true; |
| | | setTimeout(() => { |
| | | mockConfigData[currentApproveType.value] = approverList.value.map((item, index) => ({ |
| | | ...item, |
| | | id: item.id || Date.now() + index, |
| | | sortOrder: index + 1, |
| | | try { |
| | | const payload = approverList.value.map((item, index) => ({ |
| | | approveType: currentApproveType.value, |
| | | nodeOrder: index + 1, |
| | | approverId: item.approverId, |
| | | approverName: item.approverName, |
| | | })); |
| | | originalList.value = JSON.parse(JSON.stringify(mockConfigData[currentApproveType.value])); |
| | | approverList.value = JSON.parse(JSON.stringify(originalList.value)); |
| | | await addApproveProcessConfigNode(payload); |
| | | ElMessage.success('保存成功'); |
| | | await loadData(); |
| | | } catch (error) { |
| | | ElMessage.error('保存失败'); |
| | | } finally { |
| | | saveLoading.value = false; |
| | | }, 500); |
| | | } |
| | | }; |
| | | |
| | | // 重置 |
| | |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | loadData(); |
| | | onMounted(async () => { |
| | | await loadUserList(); |
| | | await loadData(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- 审批人选择(动态节点) --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="申请人:" prop="approveUser"> |
| | | <el-select |
| | | v-model="form.approveUser" |
| | | placeholder="选择人员" |
| | | disabled |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="申请日期:" prop="approveTime"> |
| | | <el-date-picker |
| | | v-model="form.approveTime" |
| | | type="date" |
| | | placeholder="请选择日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- 报价审批:展示报价详情(复用销售报价"查看详情对话框"内容结构) --> |
| | |
| | | updateApproveNode |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; |
| | |
| | | const formRef = ref(null); |
| | | const userStore = useUserStore() |
| | | const productOptions = ref([]); |
| | | const userList = ref([]) |
| | | const quotationLoading = ref(false) |
| | | const currentQuotation = ref({}) |
| | | const purchaseLoading = ref(false) |
| | |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | |
| | | dialogFormVisible.value = true; |
| | | currentQuotation.value = {} |
| | | currentPurchase.value = {} |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {...row} |
| | | // 立即清除表单验证状态(因为字段是disabled的,不需要验证) |
| | | nextTick(() => { |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- 审批人选择(动态节点) --> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <template #label> |
| | | <span>审批人选择:</span> |
| | | <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">新增节点</el-button> |
| | | </template> |
| | | <div style="display: flex; align-items: flex-end; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | style="margin-right: 30px; text-align: center; margin-bottom: 10px;" |
| | | > |
| | | <div> |
| | | <span>审批人</span> |
| | | → |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="选择人员" |
| | | style="width: 120px; margin-bottom: 8px;" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <div> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | >删除</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="申请人:" prop="approveUser"> |
| | | <el-select |
| | | v-model="form.approveUser" |
| | | placeholder="选择人员" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="申请日期:" prop="approveTime"> |
| | | <el-date-picker |
| | | v-model="form.approveTime" |
| | | type="date" |
| | | placeholder="请选择日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="附件材料:" prop="remark"> |
| | |
| | | import { |
| | | delLedgerFile, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import log from "@/views/monitor/job/log.vue"; |
| | | const userStore = useUserStore(); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveDeptId: "", |
| | | approveDeptId: "", |
| | | approveDeptName: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // 新增字段,存储所有节点的审批人id |
| | | startDate: "", // 请假开始时间 |
| | | endDate: "", // 请假结束时间 |
| | | price: null, // 报销金额 |
| | | location: "" // 出差地点 |
| | | }, |
| | | rules: { |
| | | approveTime: [{ required: false, message: "请输入", trigger: "change" },], |
| | | approveId: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | approveUser: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | approveDeptName: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | approveReason: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请输入", trigger: "blur" }], |
| | |
| | | } |
| | | }) |
| | | |
| | | // 审批人节点相关 |
| | | const approverNodes = ref([ |
| | | { id: 1, userId: null } |
| | | ]) |
| | | let nextApproverId = 2 |
| | | const userList = ref([]) |
| | | function addApproverNode() { |
| | | approverNodes.value.push({ id: nextApproverId++, userId: null }) |
| | | } |
| | | function removeApproverNode(index) { |
| | | approverNodes.value.splice(index, 1) |
| | | } |
| | | |
| | | // 处理部门选择变化 |
| | | const handleDeptChange = (deptId) => { |
| | | if (deptId) { |
| | |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {} |
| | | approverNodes.value = [ |
| | | { id: 1, userId: null } |
| | | ] |
| | | form.value.approveUser = userStore.id; |
| | | form.value.approveTime = getCurrentDate(); |
| | | |
| | | form.value = {} |
| | | |
| | | // 获取当前用户信息并设置部门ID |
| | | form.value.approveDeptId = userStore.currentDeptId |
| | | |
| | | |
| | | // 加载部门选项,并在加载完成后设置部门名称 |
| | | getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | fileList.value = row.commonFileList |
| | | form.value.tempFileIds = fileList.value.map(file => file.id) |
| | | currentApproveStatus.value = row.approveStatus |
| | | currentApproveStatus.value = row.approveStatus |
| | | approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => { |
| | | form.value = {...res.data} |
| | | // 反显审批人 |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(',') |
| | | approverNodes.value = userIds.map((userId, idx) => ({ |
| | | id: idx + 1, |
| | | userId: parseInt(userId.trim()) |
| | | })) |
| | | nextApproverId = userIds.length + 1 |
| | | } else { |
| | | approverNodes.value = [{ id: 1, userId: null }] |
| | | nextApproverId = 2 |
| | | } |
| | | form.value = {...res.data} |
| | | }) |
| | | } |
| | | } |
| | |
| | | } |
| | | // 提交产品表单 |
| | | const submitForm = () => { |
| | | // 收集所有节点的审批人id |
| | | form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | form.value.approveType = props.approveType |
| | | // 审批人必填校验 |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | | proxy.$modal.msgError("请为所有审批节点选择审批人!") |
| | | return |
| | | } |
| | | // 当 approveType 为 2 时,校验请假时间 |
| | | if (props.approveType == 2) { |
| | | if (!form.value.startDate) { |
| | |
| | | <el-button |
| | | type="primary" |
| | | @click="openForm('add')" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7 && currentApproveType !== 10" |
| | | class="action-btn primary" |
| | | > |
| | | <el-icon><Plus /></el-icon> |
| | |
| | | type="danger" |
| | | plain |
| | | @click="handleDelete" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7 && currentApproveType !== 10" |
| | | class="action-btn danger" |
| | | > |
| | | <el-icon><Delete /></el-icon> |
| | |
| | | { value: '5', label: '采购审批', icon: 'ShoppingCart', color: '#909399' }, |
| | | { value: '6', label: '报价审批', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | { value: '7', label: '发货审批', icon: 'Van', color: '#1ABC9C' }, |
| | | { value: '10', label: '销售审批', icon: 'Sell', color: '#FF6B6B' }, |
| | | ]; |
| | | |
| | | // 当前审批类型信息 |
| | |
| | | const isReimburseType = currentApproveType.value === 4; // 报销管理 |
| | | const isQuotationType = currentApproveType.value === 6; // 报价审批 |
| | | const isPurchaseType = currentApproveType.value === 5; // 采购审批 |
| | | const isSalesType = currentApproveType.value === 10; // 销售审批 |
| | | |
| | | // 基础列配置 |
| | | const baseColumns = [ |
| | |
| | | currentApproveType.value === 5 || |
| | | currentApproveType.value === 6 || |
| | | currentApproveType.value === 7 || |
| | | currentApproveType.value === 10 || |
| | | row.approveStatus == 2 || |
| | | row.approveStatus == 1 || |
| | | row.approveStatus == 4 |
| | |
| | | 5: "/approveProcess/exportFive", |
| | | 6: "/approveProcess/exportSix", |
| | | 7: "/approveProcess/exportSeven", |
| | | 10: "/approveProcess/exportTen", |
| | | } |
| | | const url = urlMap[type] || urlMap[0] |
| | | const nameMap = { |
| | |
| | | 5: "采购申请审批表", |
| | | 6: "报价审批表", |
| | | 7: "发货审批表", |
| | | 10: "销售审批表", |
| | | } |
| | | const fileName = nameMap[type] || nameMap[0] |
| | | proxy.download(url, {}, `${fileName}.xlsx`) |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <template #label> |
| | | <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;"> |
| | | <span>审批人选择:</span> |
| | | <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">新增节点</el-button> |
| | | </div> |
| | | </template> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item" |
| | | > |
| | | <div class="approver-node-header"> |
| | | <span class="approver-node-label">审批节点 {{ index + 1 }}</span> |
| | | <el-button |
| | | v-if="approverNodes.length > 1" |
| | | type="danger" |
| | | size="small" |
| | | text |
| | | @click="removeApproverNode(index)" |
| | | icon="Delete" |
| | | >删除</el-button> |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="请选择审批人" |
| | | filterable |
| | | style="width: 100%;" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" |
| | | prop="entryDate"> |
| | |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // 审批人节点(仿销售台账发货审批人) |
| | | const approverNodes = ref([{ id: 1, userId: null }]); |
| | | let nextApproverId = 2; |
| | | const addApproverNode = () => { |
| | | approverNodes.value.push({ id: nextApproverId++, userId: null }); |
| | | }; |
| | | const removeApproverNode = (index) => { |
| | | approverNodes.value.splice(index, 1); |
| | | }; |
| | | |
| | | // 订单审批状态显示文本 |
| | | const approvalStatusText = { |
| | | 1: "待审核", |
| | |
| | | } |
| | | |
| | | try { |
| | | // 获取审批人ID字符串 |
| | | const approveUserIds = approverNodes.value |
| | | .filter(node => node.userId) |
| | | .map(node => node.userId) |
| | | .join(","); |
| | | |
| | | let params = { |
| | | productData: proxy.HaveJson(productData.value), |
| | | supplierId: form.value.supplierId, |
| | | paymentMethod: form.value.paymentMethod, |
| | | recorderId: form.value.recorderId, |
| | | projectName: form.value.projectName, |
| | | approveUserIds: approveUserIds, |
| | | templateName: templateName.value.trim(), |
| | | }; |
| | | console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value); |
| | |
| | | templateName.value = ""; |
| | | filterInputValue.value = ""; |
| | | isTemplateNameDuplicate.value = false; |
| | | // 重置审批人节点(默认一个空节点) |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | nextApproverId = 2; |
| | | try { |
| | | // 并行加载基础数据 |
| | | const [userRes, salesRes, supplierRes] = await Promise.all([ |
| | |
| | | form.value = { ...purchaseRes }; |
| | | productData.value = purchaseRes.productData || []; |
| | | fileList.value = purchaseRes.salesLedgerFiles || []; |
| | | // 如果编辑时有审批人,解析审批人ID字符串并设置到节点中 |
| | | if (purchaseRes.approveUserIds) { |
| | | const approverIds = purchaseRes.approveUserIds.split(","); |
| | | approverNodes.value = approverIds.map((id, index) => ({ |
| | | id: index + 1, |
| | | userId: Number(id) |
| | | })); |
| | | nextApproverId = approverIds.length + 1; |
| | | } |
| | | } catch (error) { |
| | | console.error("加载采购台账数据失败:", error); |
| | | proxy.$modal.msgError("加载数据失败"); |
| | |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | // 审批人必填校验(所有节点都要选人) |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId); |
| | | if (hasEmptyApprover) { |
| | | proxy.$modal.msgError("请为所有审批节点选择审批人!"); |
| | | return; |
| | | } |
| | | const approveUserIds = approverNodes.value.map(node => node.userId).join(","); |
| | | |
| | | if (productData.value.length > 0) { |
| | | // 新增时,需要从每个产品对象中删除 id 字段 |
| | | let processedProductData = productData.value; |
| | |
| | | } |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 2; |
| | | form.value.approveUserIds = approveUserIds; |
| | | |
| | | // 如果salesLedgerId为空,则不传递salesContractNo |
| | | if (!form.value.salesLedgerId) { |
| | |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | // 重置审批人节点(默认一个空节点) |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | nextApproverId = 2; |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // 打开产品弹框 |
| | |
| | | <el-dialog |
| | | v-model="isShow" |
| | | title="编辑工艺路线" |
| | | width="400" |
| | | width="800" |
| | | @close="closeModal" |
| | | > |
| | | <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> |
| | |
| | | </el-button> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="BOM" |
| | | prop="bomId" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择BOM', |
| | | trigger: 'change', |
| | | } |
| | | ]" |
| | | > |
| | | <el-select |
| | | v-model="formState.bomId" |
| | | placeholder="请选择BOM" |
| | | clearable |
| | | :disabled="!formState.productModelId || bomOptions.length === 0" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in bomOptions" |
| | | :key="item.id" |
| | | :label="item.bomNo || `BOM-${item.id}`" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="备注" prop="description"> |
| | | <el-input v-model="formState.description" type="textarea" /> |
| | | </el-form-item> |
| | | |
| | | <!-- 工序配置 --> |
| | | <el-form-item label="工序配置" required> |
| | | <el-table :data="formState.processRouteItemList" border size="small" style="width: 100%"> |
| | | <el-table-column label="部件" min-width="200"> |
| | | <template #default="{ row }"> |
| | | <el-select |
| | | v-model="row.processId" |
| | | placeholder="请选择部件" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="process in processOptions" |
| | | :key="process.id" |
| | | :label="formatProcessOptionLabel(process)" |
| | | :value="process.id" |
| | | /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="质检" width="80" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-switch v-model="row.isQuality" :active-value="true" inactive-value="false"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="备注" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.remark" placeholder="请输入备注" size="small"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="60" align="center"> |
| | | <template #default="{ $index }"> |
| | | <el-button type="danger" link size="small" @click="removeProcessItem($index)"> |
| | | <el-icon><Delete /></el-icon> |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div style="margin-top: 8px;"> |
| | | <el-button type="primary" link size="small" @click="addProcessItem"> |
| | | <el-icon><Plus /></el-icon> 添加工序 |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | |
| | | <script setup> |
| | | import {ref, computed, getCurrentInstance, onMounted, nextTick, watch} from "vue"; |
| | | import {update} from "@/api/productionManagement/processRoute.js"; |
| | | import {getByModel} from "@/api/productionManagement/productBom.js"; |
| | | import {processList} from "@/api/productionManagement/productionProcess.js"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | productModelId: undefined, |
| | | productName: "", |
| | | productModelName: "", |
| | | bomId: undefined, |
| | | description: '', |
| | | processRouteItemList: [], |
| | | }); |
| | | |
| | | const isShow = computed({ |
| | |
| | | }); |
| | | |
| | | const showProductSelectDialog = ref(false); |
| | | const bomOptions = ref([]); |
| | | const processOptions = ref([]); |
| | | |
| | | let { proxy } = getCurrentInstance() |
| | | |
| | | // 获取工序列表 |
| | | const getProcessOptions = async () => { |
| | | try { |
| | | const res = await processList(); |
| | | processOptions.value = res.data || []; |
| | | } catch (error) { |
| | | console.error("获取工序列表失败", error); |
| | | processOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | // 格式化工序选项标签 |
| | | const formatProcessOptionLabel = (process) => { |
| | | if (!process) return ''; |
| | | const typeMap = { |
| | | 1: '加工', |
| | | 2: '刮板冷芯制作', |
| | | 3: '管路组对', |
| | | 4: '罐体连接及调试', |
| | | 5: '测试打压', |
| | | 6: '其他', |
| | | }; |
| | | const typeText = typeMap[process.type] || ''; |
| | | return `${process.name} ${process.no ? '(' + process.no + ')' : ''} ${typeText ? '[' + typeText + ']' : ''}`; |
| | | }; |
| | | |
| | | // 添加工序 |
| | | const addProcessItem = () => { |
| | | formState.value.processRouteItemList.push({ |
| | | processId: undefined, |
| | | isQuality: false, |
| | | remark: '', |
| | | }); |
| | | }; |
| | | |
| | | // 移除工序 |
| | | const removeProcessItem = (index) => { |
| | | formState.value.processRouteItemList.splice(index, 1); |
| | | }; |
| | | |
| | | const closeModal = () => { |
| | | isShow.value = false; |
| | |
| | | productName: props.record.productName || "", |
| | | // 注意:record中的字段是model,需要映射到productModelName |
| | | productModelName: props.record.model || props.record.productModelName || "", |
| | | bomId: props.record.bomId, |
| | | description: props.record.description || '', |
| | | processRouteItemList: props.record.processRouteItemList || [], |
| | | }; |
| | | // 如果有产品型号ID,加载BOM列表 |
| | | if (props.record.productModelId) { |
| | | loadBomList(props.record.productModelId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 加载BOM列表 |
| | | const loadBomList = async (productModelId) => { |
| | | if (!productModelId) { |
| | | bomOptions.value = []; |
| | | return; |
| | | } |
| | | try { |
| | | const res = await getByModel(productModelId); |
| | | // 处理返回的BOM数据:可能是数组、对象或包含data字段 |
| | | let bomList = []; |
| | | if (Array.isArray(res)) { |
| | | bomList = res; |
| | | } else if (res && res.data) { |
| | | bomList = Array.isArray(res.data) ? res.data : [res.data]; |
| | | } else if (res && typeof res === 'object') { |
| | | bomList = [res]; |
| | | } |
| | | bomOptions.value = bomList; |
| | | } catch (error) { |
| | | console.error("加载BOM列表失败:", error); |
| | | bomOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | // 产品选择处理 |
| | | const handleProductSelect = async (products) => { |
| | | if (products && products.length > 0) { |
| | | const product = products[0]; |
| | | // 先查询BOM列表(必选) |
| | | try { |
| | | const res = await getByModel(product.id); |
| | | // 处理返回的BOM数据:可能是数组、对象或包含data字段 |
| | | let bomList = []; |
| | | if (Array.isArray(res)) { |
| | | bomList = res; |
| | | } else if (res && res.data) { |
| | | bomList = Array.isArray(res.data) ? res.data : [res.data]; |
| | | } else if (res && typeof res === 'object') { |
| | | bomList = [res]; |
| | | } |
| | | |
| | | if (bomList.length > 0) { |
| | | formState.value.productModelId = product.id; |
| | | formState.value.productName = product.productName; |
| | | formState.value.productModelName = product.model; |
| | | // 如果当前选择的BOM不在新列表中,则重置BOM选择 |
| | | const currentBomExists = bomList.some(bom => bom.id === formState.value.bomId); |
| | | if (!currentBomExists) { |
| | | formState.value.bomId = undefined; |
| | | } |
| | | bomOptions.value = bomList; |
| | | showProductSelectDialog.value = false; |
| | | // 触发表单验证更新 |
| | | proxy.$refs["formRef"]?.validateField('productModelId'); |
| | | } else { |
| | | proxy.$modal.msgError("该产品没有BOM,请先创建BOM"); |
| | | } |
| | | } catch (error) { |
| | | // 如果接口返回404或其他错误,说明没有BOM |
| | | proxy.$modal.msgError("该产品没有BOM,请先创建BOM"); |
| | | } |
| | | formState.value.productModelId = product.id; |
| | | formState.value.productName = product.productName; |
| | | formState.value.productModelName = product.model; |
| | | showProductSelectDialog.value = false; |
| | | // 触发表单验证更新 |
| | | proxy.$refs["formRef"]?.validateField('productModelId'); |
| | | } |
| | | }; |
| | | |
| | | const handleSubmit = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | // 验证是否选择了产品和BOM |
| | | // 验证是否选择了产品 |
| | | if (!formState.value.productModelId) { |
| | | proxy.$modal.msgError("请选择产品"); |
| | | return; |
| | | } |
| | | if (!formState.value.bomId) { |
| | | proxy.$modal.msgError("请选择BOM"); |
| | | // 验证是否配置了工序 |
| | | if (!formState.value.processRouteItemList || formState.value.processRouteItemList.length === 0) { |
| | | proxy.$modal.msgError("请至少配置一个工序"); |
| | | return; |
| | | } |
| | | // 验证所有工序是否选择了部件 |
| | | const invalidItem = formState.value.processRouteItemList.find(item => !item.processId); |
| | | if (invalidItem) { |
| | | proxy.$modal.msgError("请选择所有工序的部件"); |
| | | return; |
| | | } |
| | | update(formState.value).then(res => { |
| | |
| | | }, { immediate: true }); |
| | | |
| | | onMounted(() => { |
| | | getProcessOptions(); |
| | | if (props.visible && props.record) { |
| | | setFormData(); |
| | | } |
| | |
| | | </el-button> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="BOM" |
| | | prop="bomId" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择BOM', |
| | | trigger: 'change', |
| | | } |
| | | ]" |
| | | > |
| | | <el-select |
| | | v-model="formState.bomId" |
| | | placeholder="请选择BOM" |
| | | clearable |
| | | :disabled="!formState.productModelId || bomOptions.length === 0" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in bomOptions" |
| | | :key="item.id" |
| | | :label="item.bomNo || `BOM-${item.id}`" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="备注" prop="description"> |
| | | <el-input v-model="formState.description" type="textarea" /> |
| | | </el-form-item> |
| | |
| | | <script setup> |
| | | import {ref, computed, getCurrentInstance} from "vue"; |
| | | import {add} from "@/api/productionManagement/processRoute.js"; |
| | | import {getByModel} from "@/api/productionManagement/productBom.js"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | productModelId: undefined, |
| | | productName: "", |
| | | productModelName: "", |
| | | bomId: undefined, |
| | | description: '', |
| | | }); |
| | | |
| | |
| | | }); |
| | | |
| | | const showProductSelectDialog = ref(false); |
| | | const bomOptions = ref([]); |
| | | |
| | | let { proxy } = getCurrentInstance() |
| | | |
| | |
| | | productModelId: undefined, |
| | | productName: "", |
| | | productModelName: "", |
| | | bomId: undefined, |
| | | description: '', |
| | | }; |
| | | bomOptions.value = []; |
| | | isShow.value = false; |
| | | }; |
| | | |
| | |
| | | const handleProductSelect = async (products) => { |
| | | if (products && products.length > 0) { |
| | | const product = products[0]; |
| | | // 先查询BOM列表(必选) |
| | | try { |
| | | const res = await getByModel(product.id); |
| | | // 处理返回的BOM数据:可能是数组、对象或包含data字段 |
| | | let bomList = []; |
| | | if (Array.isArray(res)) { |
| | | bomList = res; |
| | | } else if (res && res.data) { |
| | | bomList = Array.isArray(res.data) ? res.data : [res.data]; |
| | | } else if (res && typeof res === 'object') { |
| | | bomList = [res]; |
| | | } |
| | | |
| | | if (bomList.length > 0) { |
| | | formState.value.productModelId = product.id; |
| | | formState.value.productName = product.productName; |
| | | formState.value.productModelName = product.model; |
| | | formState.value.bomId = undefined; // 重置BOM选择 |
| | | bomOptions.value = bomList; |
| | | showProductSelectDialog.value = false; |
| | | // 触发表单验证更新 |
| | | proxy.$refs["formRef"]?.validateField('productModelId'); |
| | | } else { |
| | | proxy.$modal.msgError("该产品没有BOM,请先创建BOM"); |
| | | } |
| | | } catch (error) { |
| | | // 如果接口返回404或其他错误,说明没有BOM |
| | | proxy.$modal.msgError("该产品没有BOM,请先创建BOM"); |
| | | } |
| | | formState.value.productModelId = product.id; |
| | | formState.value.productName = product.productName; |
| | | formState.value.productModelName = product.model; |
| | | showProductSelectDialog.value = false; |
| | | // 触发表单验证更新 |
| | | proxy.$refs["formRef"]?.validateField('productModelId'); |
| | | } |
| | | }; |
| | | |
| | | const handleSubmit = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | // 验证是否选择了产品和BOM |
| | | // 验证是否选择了产品 |
| | | if (!formState.value.productModelId) { |
| | | proxy.$modal.msgError("请选择产品"); |
| | | return; |
| | | } |
| | | if (!formState.value.bomId) { |
| | | proxy.$modal.msgError("请选择BOM"); |
| | | return; |
| | | } |
| | | add(formState.value).then(res => { |
| | |
| | | }) |
| | | }; |
| | | |
| | | |
| | | defineExpose({ |
| | | closeModal, |
| | | handleSubmit, |
| | | isShow, |
| | | }); |
| | | </script> |
| | | |
| | | |
| | |
| | | |
| | | <!-- 表格视图 --> |
| | | <div v-if="viewMode === 'table'" class="section-header"> |
| | | <div class="section-title">产品部件列表</div> |
| | | <div class="section-title">工序列表</div> |
| | | <div class="section-actions"> |
| | | <el-button |
| | | icon="Grid" |
| | |
| | | class="lims-table" |
| | | > |
| | | <el-table-column align="center" label="序号" width="60" type="index" /> |
| | | <el-table-column label="产品名称" prop="name" min-width="140" show-overflow-tooltip> |
| | | <el-table-column label="部件名称" prop="name" min-width="140" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ getProcessField(scope.row, 'name') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="产品规格" prop="productModel" min-width="120" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ getProcessField(scope.row, 'productModel') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="部件编号" prop="no" width="120" show-overflow-tooltip> |
| | |
| | | <el-table-column label="部件类型" prop="typeText" width="120" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ getProcessTypeText(getProcessRaw(scope.row)?.type) || '-' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="产品名称" prop="productName" min-width="140" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ scope.row.productName || getProcessField(scope.row, 'productName') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="产品规格" prop="productModel" min-width="120" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ scope.row.model || getProcessField(scope.row, 'productModel') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="计划工时(小时)" prop="salaryQuota" width="130" align="center"> |
| | |
| | | <!-- 卡片视图 --> |
| | | <template v-else> |
| | | <div class="section-header"> |
| | | <div class="section-title">工艺路线项目列表</div> |
| | | <div class="section-title">工序列表</div> |
| | | <div class="section-actions"> |
| | | <el-button |
| | | icon="Menu" |
| | |
| | | <!-- 与工序主表一致的简要信息 --> |
| | | <div class="card-content"> |
| | | <div class="product-info"> |
| | | <div class="product-name">{{ getProcessField(item, 'productModel') }}</div> |
| | | <div class="product-name">{{ item.productName || getProcessField(item, 'productName') }}</div> |
| | | <div class="product-model">{{ item.model || getProcessField(item, 'productModel') }}</div> |
| | | <div v-if="getProcessRaw(item)?.no" class="product-model">编号 {{ getProcessRaw(item)?.no }}</div> |
| | | <div v-if="getProcessTypeText(getProcessRaw(item)?.type)" class="product-model"> |
| | | {{ getProcessTypeText(getProcessRaw(item)?.type) }} |
| | |
| | | <!-- 新增/编辑弹窗(布局、字段与工序/部件页 New、Edit 一致;提交参数仍为原接口字段) --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'" |
| | | :title="operationType === 'add' ? '新增工序' : '编辑工序'" |
| | | width="760" |
| | | @close="closeDialog" |
| | | > |
| | |
| | | const submitLoading = ref(false); |
| | | const cardsContainer = ref(null); |
| | | const tableRef = ref(null); |
| | | const viewMode = ref('table'); // table | card |
| | | const viewMode = ref('card'); // table | card |
| | | const routeInfo = ref({ |
| | | processRouteCode: '', |
| | | productName: '', |
| | |
| | | <el-input v-model="formState.unit" disabled /> |
| | | </el-form-item> |
| | | |
| | | <!-- <el-form-item label="工艺路线"> |
| | | <el-form-item label="工艺路线"> |
| | | <el-select v-model="formState.routeId" |
| | | placeholder="请选择工艺路线" |
| | | style="width: 100%;" |
| | |
| | | :label="`${item.processRouteCode || ''}`" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> --> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="需求数量" |
| | |
| | | <el-row :gutter="16"> |
| | | <el-col :span="12"> |
| | | <el-form-item |
| | | label="产品名称:" |
| | | prop="productId" |
| | | label="部件名称" |
| | | prop="name" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择产品名称', |
| | | }, |
| | | ]"> |
| | | <el-tree-select |
| | | v-model="formState.productId" |
| | | placeholder="请选择产品名称" |
| | | clearable |
| | | filterable |
| | | check-strictly |
| | | :data="productCategoryOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | @change="handleProductChange" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item |
| | | label="产品规格:" |
| | | prop="productModelId" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择产品规格', |
| | | }, |
| | | ]"> |
| | | <el-select v-model="formState.productModelId" |
| | | placeholder="请选择产品规格" |
| | | clearable |
| | | filterable |
| | | :disabled="!formState.productId" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | message: '请输入部件名称', |
| | | } |
| | | ]" |
| | | > |
| | | <el-input v-model="formState.name" placeholder="请输入部件名称" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="部件编号" prop="no"> |
| | | <el-input v-model="formState.no" /> |
| | | <el-input v-model="formState.no" placeholder="请输入部件编号" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="计划执行人员" prop="executorId"> |
| | | <el-select |
| | | v-model="formState.executorId" |
| | | placeholder="请选择计划执行人员" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | @change="handleExecutorChange" |
| | | > |
| | | <el-option |
| | | v-for="item in plannerOptions" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="是否质检" prop="isQuality"> |
| | | <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/> |
| | | </el-form-item> |
| | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance, watch, onMounted } from "vue"; |
| | | import {update} from "@/api/productionManagement/productionProcess.js"; |
| | | import { modelListPage, productTreeList } from "@/api/basicData/product"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | const formState = ref({ |
| | | id: props.record.id, |
| | | name: props.record.name, |
| | | productId: props.record.productId, |
| | | productModelId: props.record.productModelId, |
| | | type: props.record.type, |
| | | no: props.record.no, |
| | | remark: props.record.remark, |
| | | salaryQuota: props.record.salaryQuota, |
| | | plannerId: props.record.plannerId, |
| | | plannerName: props.record.plannerName, |
| | | executorId: props.record.executorId, |
| | | executorName: props.record.executorName, |
| | | isQuality: props.record.isQuality, |
| | | }); |
| | | const productCategoryOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const plannerOptions = ref([]); |
| | | |
| | | const isShow = computed({ |
| | |
| | | formState.value = { |
| | | id: newRecord.id, |
| | | name: newRecord.name || '', |
| | | productId: newRecord.productId, |
| | | productModelId: getRecordProductModelId(newRecord), |
| | | no: newRecord.no || '', |
| | | type: newRecord.type, |
| | | no: newRecord.no || '', |
| | | remark: newRecord.remark || '', |
| | | salaryQuota: newRecord.salaryQuota || '', |
| | | plannerId: newRecord.plannerId, |
| | | plannerName: newRecord.plannerName || '', |
| | | executorId: newRecord.executorId, |
| | | executorName: newRecord.executorName || '', |
| | | isQuality: props.record.isQuality, |
| | | }; |
| | | } |
| | |
| | | formState.value = { |
| | | id: props.record.id, |
| | | name: props.record.name || '', |
| | | productId: props.record.productId, |
| | | productModelId: getRecordProductModelId(props.record), |
| | | no: props.record.no || '', |
| | | type: props.record.type, |
| | | no: props.record.no || '', |
| | | remark: props.record.remark || '', |
| | | salaryQuota: props.record.salaryQuota || '', |
| | | plannerId: props.record.plannerId, |
| | | plannerName: props.record.plannerName || '', |
| | | executorId: props.record.executorId, |
| | | executorName: props.record.executorName || '', |
| | | isQuality: props.record.isQuality, |
| | | }; |
| | | getModelOptions(formState.value.productId); |
| | | } |
| | | }); |
| | | |
| | |
| | | return; |
| | | } |
| | | callback(); |
| | | }; |
| | | |
| | | const convertProductTree = list => { |
| | | return (list || []).map(item => { |
| | | const children = convertProductTree(item.children || item.childList || []); |
| | | return { |
| | | ...item, |
| | | value: item.id, |
| | | label: item.name || item.label, |
| | | children, |
| | | disabled: children.length > 0, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const findNodeById = (nodes, targetId) => { |
| | | for (const node of nodes || []) { |
| | | if (String(node.value) === String(targetId)) { |
| | | return node; |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | const found = findNodeById(node.children, targetId); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const findNodeIdByLabel = (nodes, targetLabel) => { |
| | | for (const node of nodes || []) { |
| | | if (node.label === targetLabel) { |
| | | return node.value; |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | const found = findNodeIdByLabel(node.children, targetLabel); |
| | | if (found !== null && found !== undefined) return found; |
| | | } |
| | | } |
| | | return undefined; |
| | | }; |
| | | |
| | | function findModelIdByName(models, modelName) { |
| | | if (!modelName) { |
| | | return undefined; |
| | | } |
| | | const matched = (models || []).find(item => String(item.model) === String(modelName)); |
| | | return matched?.id; |
| | | } |
| | | |
| | | function getRecordProductModelId(record) { |
| | | if (!record) { |
| | | return undefined; |
| | | } |
| | | return record.productModelId ?? record.modelId ?? record.specificationModelId ?? undefined; |
| | | } |
| | | |
| | | function syncProductModelIdFromModelName() { |
| | | if (formState.value.productModelId || modelOptions.value.length === 0) { |
| | | return; |
| | | } |
| | | const modelName = props.record?.productModel || props.record?.model || props.record?.specificationModel || ""; |
| | | const matchedId = findModelIdByName(modelOptions.value, modelName); |
| | | if (matchedId !== undefined) { |
| | | formState.value.productModelId = matchedId; |
| | | } |
| | | } |
| | | |
| | | const getProductCategoryOptions = async () => { |
| | | try { |
| | | const res = await productTreeList(); |
| | | const list = Array.isArray(res) ? res : res?.data || []; |
| | | productCategoryOptions.value = convertProductTree(list); |
| | | if (!formState.value.productId && formState.value.name) { |
| | | formState.value.productId = findNodeIdByLabel(productCategoryOptions.value, formState.value.name); |
| | | } |
| | | await getModelOptions(formState.value.productId); |
| | | syncProductModelIdFromModelName(); |
| | | } catch (e) { |
| | | productCategoryOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getModelOptions = async productId => { |
| | | if (!productId) { |
| | | modelOptions.value = []; |
| | | return; |
| | | } |
| | | try { |
| | | const res = await modelListPage({ |
| | | id: productId, |
| | | current: 1, |
| | | size: 999, |
| | | }); |
| | | const records = res?.records || res?.data?.records || []; |
| | | modelOptions.value = records; |
| | | syncProductModelIdFromModelName(); |
| | | } catch (e) { |
| | | modelOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getPlannerOptions = async () => { |
| | |
| | | formState.value.plannerName = selectedUser?.nickName || ''; |
| | | }; |
| | | |
| | | const handleProductChange = async value => { |
| | | const selectedNode = findNodeById(productCategoryOptions.value, value); |
| | | formState.value.name = selectedNode?.label || ''; |
| | | formState.value.productModelId = undefined; |
| | | await getModelOptions(value); |
| | | const handleExecutorChange = value => { |
| | | const selectedUser = plannerOptions.value.find(item => String(item.userId) === String(value)); |
| | | formState.value.executorName = selectedUser?.nickName || ''; |
| | | }; |
| | | |
| | | const closeModal = () => { |
| | |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getProductCategoryOptions(); |
| | | getPlannerOptions(); |
| | | }); |
| | | |
| | |
| | | <el-row :gutter="16"> |
| | | <el-col :span="12"> |
| | | <el-form-item |
| | | label="产品名称:" |
| | | prop="productId" |
| | | label="部件名称" |
| | | prop="name" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择产品名称', |
| | | }, |
| | | ]"> |
| | | <el-tree-select |
| | | v-model="formState.productId" |
| | | placeholder="请选择产品名称" |
| | | clearable |
| | | filterable |
| | | check-strictly |
| | | :data="productCategoryOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | @change="handleProductChange" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item |
| | | label="产品规格:" |
| | | prop="productModelId" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择产品规格', |
| | | }, |
| | | ]"> |
| | | <el-select v-model="formState.productModelId" |
| | | placeholder="请选择产品规格" |
| | | clearable |
| | | filterable |
| | | :disabled="!formState.productId" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | message: '请输入部件名称', |
| | | } |
| | | ]" |
| | | > |
| | | <el-input v-model="formState.name" placeholder="请输入部件名称" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="部件编号" prop="no"> |
| | | <el-input v-model="formState.no" /> |
| | | <el-input v-model="formState.no" placeholder="请输入部件编号" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="计划执行人员" prop="executorId"> |
| | | <el-select |
| | | v-model="formState.executorId" |
| | | placeholder="请选择计划执行人员" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | @change="handleExecutorChange" |
| | | > |
| | | <el-option |
| | | v-for="item in plannerOptions" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="是否质检" prop="isQuality"> |
| | | <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/> |
| | | </el-form-item> |
| | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance, onMounted } from "vue"; |
| | | import {add} from "@/api/productionManagement/productionProcess.js"; |
| | | import { modelListPage, productTreeList } from "@/api/basicData/product"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | // 响应式数据(替代选项式的 data) |
| | | const formState = ref({ |
| | | name: '', |
| | | productId: undefined, |
| | | productModelId: undefined, |
| | | type: undefined, |
| | | remark: '', |
| | | salaryQuota: '', |
| | | plannerId: undefined, |
| | | plannerName: '', |
| | | executorId: undefined, |
| | | executorName: '', |
| | | isQuality: false, |
| | | }); |
| | | const productCategoryOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const plannerOptions = ref([]); |
| | | |
| | | const isShow = computed({ |
| | |
| | | callback(); |
| | | }; |
| | | |
| | | const convertProductTree = list => { |
| | | return (list || []).map(item => { |
| | | const children = convertProductTree(item.children || item.childList || []); |
| | | return { |
| | | ...item, |
| | | value: item.id, |
| | | label: item.name || item.label, |
| | | children, |
| | | disabled: children.length > 0, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const findNodeById = (nodes, targetId) => { |
| | | for (const node of nodes || []) { |
| | | if (String(node.value) === String(targetId)) { |
| | | return node; |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | const found = findNodeById(node.children, targetId); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const getProductCategoryOptions = async () => { |
| | | try { |
| | | const res = await productTreeList(); |
| | | const list = Array.isArray(res) ? res : res?.data || []; |
| | | productCategoryOptions.value = convertProductTree(list); |
| | | } catch (e) { |
| | | productCategoryOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getModelOptions = async productId => { |
| | | if (!productId) { |
| | | modelOptions.value = []; |
| | | return; |
| | | } |
| | | try { |
| | | const res = await modelListPage({ |
| | | id: productId, |
| | | current: 1, |
| | | size: 999, |
| | | }); |
| | | const records = res?.records || res?.data?.records || []; |
| | | modelOptions.value = records; |
| | | } catch (e) { |
| | | modelOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getPlannerOptions = async () => { |
| | | try { |
| | | const res = await userListNoPageByTenantId(); |
| | |
| | | formState.value.plannerName = selectedUser?.nickName || ''; |
| | | }; |
| | | |
| | | const handleProductChange = async value => { |
| | | const selectedNode = findNodeById(productCategoryOptions.value, value); |
| | | formState.value.name = selectedNode?.label || ''; |
| | | formState.value.productModelId = undefined; |
| | | await getModelOptions(value); |
| | | const handleExecutorChange = value => { |
| | | const selectedUser = plannerOptions.value.find(item => String(item.userId) === String(value)); |
| | | formState.value.executorName = selectedUser?.nickName || ''; |
| | | }; |
| | | |
| | | const closeModal = () => { |
| | |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getProductCategoryOptions(); |
| | | getPlannerOptions(); |
| | | }); |
| | | |
| | |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | |
| | | { |
| | | label: "产品名称", |
| | | label: "部件名称", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | label: "产品规格", |
| | | prop: "productModel", |
| | | }, |
| | | { |
| | | label: "部件编号", |
| | |
| | | prop: "plannerName", |
| | | }, |
| | | { |
| | | label: "计划执行人员", |
| | | prop: "executorName", |
| | | }, |
| | | { |
| | | label: "是否质检", |
| | | prop: "isQuality", |
| | | formatData: (params) => { |
| | |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? '新增不合格管理' : '编辑不合格管理'" |
| | | width="70%" |
| | | :title="operationType === 'add' ? '新增不合格品处理单' : '编辑不合格品处理单'" |
| | | width="80%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <!-- 第一行:项目名称、项目编号 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="类别:" prop="inspectType"> |
| | | <el-select v-model="form.inspectType"> |
| | | <el-option label="原材料检验" :value="0" /> |
| | | <el-option label="过程检验" :value="1" /> |
| | | <el-option label="出厂检验" :value="2" /> |
| | | </el-select> |
| | | <el-form-item label="项目名称:" prop="projectName"> |
| | | <el-input v-model="form.projectName" placeholder="请输入项目名称" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项目编号:" prop="projectNo"> |
| | | <el-input v-model="form.projectNo" placeholder="请输入项目编号" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第二行:设备名称、设备图号 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产品名称:" prop="productId"> |
| | | <el-tree-select |
| | | v-model="form.productId" |
| | | placeholder="请选择" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="设备名称:" prop="equipmentName"> |
| | | <el-input v-model="form.equipmentName" placeholder="请输入设备名称" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="规格型号:" prop="model"> |
| | | <el-select v-model="form.model" placeholder="请选择" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | <el-form-item label="设备图号:" prop="equipmentDrawingNo"> |
| | | <el-input v-model="form.equipmentDrawingNo" placeholder="请输入设备图号" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第三行:物料/部件名称、物料图号 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="单位:" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请输入" clearable/> |
| | | <el-form-item label="物料/部件名称:" prop="materialName"> |
| | | <el-input v-model="form.materialName" placeholder="请输入物料/部件名称" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="物料图号:" prop="materialDrawingNo"> |
| | | <el-input v-model="form.materialDrawingNo" placeholder="请输入物料图号" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第四行:型号规格、材质、数量、不合格数 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="型号规格:" prop="specificationModel"> |
| | | <el-input v-model="form.specificationModel" placeholder="请输入型号规格" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="材质:" prop="materialQuality"> |
| | | <el-input v-model="form.materialQuality" placeholder="请输入材质" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="数量:" prop="quantity"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2"/> |
| | | <el-input-number :step="1" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" :precision="0"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="不合格数:" prop="unqualifiedQuantity"> |
| | | <el-input-number :step="1" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数" :precision="0"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第五行:不合格工序、供货商名称 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="检验员:" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="请选择" clearable style="width: 100%"> |
| | | <el-form-item label="不合格工序:" prop="unqualifiedProcess"> |
| | | <el-radio-group v-model="form.unqualifiedProcess"> |
| | | <el-radio :label="1">来料</el-radio> |
| | | <el-radio :label="2">制程</el-radio> |
| | | <el-radio :label="3">成品</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="供货商名称:" prop="supplierName"> |
| | | <el-input v-model="form.supplierName" placeholder="请输入供货商名称" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第六行:检验员、检验日期、责任人、责任部门 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="检验员:" prop="inspectorName"> |
| | | <el-select v-model="form.inspectorName" placeholder="请选择" clearable style="width: 100%"> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="检测日期:" prop="checkTime"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="检验日期:" prop="inspectDate"> |
| | | <el-date-picker |
| | | v-model="form.checkTime" |
| | | v-model="form.inspectDate" |
| | | type="date" |
| | | placeholder="请选择日期" |
| | | value-format="YYYY-MM-DD" |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="不合格现象:" prop="defectivePhenomena"> |
| | | <el-input v-model="form.defectivePhenomena" placeholder="请输入" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="处理结果:" prop="dealResult"> |
| | | <el-select v-model="form.dealResult" placeholder="请选择" clearable> |
| | | <el-option :label="item.label" :value="item.value" v-for="item in rejection_handling" :key="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="处理人:" prop="dealName"> |
| | | <el-select v-model="form.dealName" placeholder="请选择" clearable style="width: 100%"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="责任人:" prop="responsiblePerson"> |
| | | <el-select v-model="form.responsiblePerson" placeholder="请选择" clearable style="width: 100%"> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="处理日期:" prop="dealTime"> |
| | | <el-date-picker |
| | | v-model="form.dealTime" |
| | | type="date" |
| | | placeholder="请选择日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | <el-col :span="6"> |
| | | <el-form-item label="责任部门:" prop="responsibleDept"> |
| | | <el-input v-model="form.responsibleDept" placeholder="请输入责任部门" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第七行:问题描述 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="问题描述:" prop="problemDescription"> |
| | | <el-input |
| | | v-model="form.problemDescription" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入问题描述" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第八行:原因分析及建议 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="工时损失:" prop="lossWorking"> |
| | | <el-input-number |
| | | v-model="form.lossWorking" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="材料费损失:" prop="lossMaterial"> |
| | | <el-input-number |
| | | v-model="form.lossMaterial" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="原因分析:" prop="reasonAnalysis"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="原因分析及建议:" prop="reasonAnalysis"> |
| | | <el-input |
| | | v-model="form.reasonAnalysis" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="预防与纠正措施:" prop="preventiveCorrective"> |
| | | <el-input |
| | | v-model="form.preventiveCorrective" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入" |
| | | placeholder="请输入原因分析及建议" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第九行:纠正措施 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="纠正措施:" prop="correctionAction"> |
| | | <el-input |
| | | v-model="form.correctionAction" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入纠正措施" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第十行:处置方式 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="处置方式:" prop="disposalMethod"> |
| | | <el-radio-group v-model="form.disposalMethod"> |
| | | <el-radio :label="1">让步接收</el-radio> |
| | | <el-radio :label="2">厂内维修</el-radio> |
| | | <el-radio :label="3">返厂维修</el-radio> |
| | | <el-radio :label="4">换货</el-radio> |
| | | <el-radio :label="5">退货</el-radio> |
| | | <el-radio :label="6">报废</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第十一行:厂内/返厂维修评估 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="厂内/返厂维修评估:" prop="repairEvaluation"> |
| | | <el-input |
| | | v-model="form.repairEvaluation" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入厂内/返厂维修评估" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第十二行:预防措施 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="预防措施:" prop="preventiveAction"> |
| | | <el-input |
| | | v-model="form.preventiveAction" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入预防措施" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第十三行:状态 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="状态:" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio :label="0">草稿</el-radio> |
| | | <el-radio :label="1">待审核</el-radio> |
| | | <el-radio :label="2">审批中</el-radio> |
| | | <el-radio :label="3">已完成</el-radio> |
| | | <el-radio :label="4">已驳回</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第十四行:备注 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="备注:" prop="remark"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入备注" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第十五行:不良品照片 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="不良品照片:" prop="defectivePhotos"> |
| | |
| | | <el-button type="primary" v-if="operationType !== 'view'">上传</el-button> |
| | | <template #tip v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | 文件格式支持 doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z |
| | | 文件格式支持 jpg,jpeg,png,gif,bmp |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance, watch } from "vue"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { |
| | | getQualityUnqualifiedInfo, |
| | | qualityUnqualifiedAdd, |
| | |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const { rejection_handling } = proxy.useDict("rejection_handling") |
| | | const defectivePhotoFileList = ref([]); |
| | | const defectivePhotoUploadRef = ref(null); |
| | | const upload = reactive({ |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | checkTime: "", |
| | | process: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | model: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | inspectType: '', |
| | | defectivePhenomena: '', |
| | | // 基本信息 |
| | | projectName: '', |
| | | projectNo: '', |
| | | equipmentName: '', |
| | | equipmentDrawingNo: '', |
| | | materialName: '', |
| | | materialDrawingNo: '', |
| | | specificationModel: '', |
| | | materialQuality: '', |
| | | quantity: 0, |
| | | unqualifiedQuantity: 0, |
| | | unqualifiedProcess: undefined, |
| | | supplierName: '', |
| | | inspectorName: '', |
| | | inspectDate: '', |
| | | responsiblePerson: '', |
| | | responsibleDept: '', |
| | | |
| | | // 问题描述和处理 |
| | | problemDescription: '', |
| | | reasonAnalysis: '', |
| | | correctionAction: '', |
| | | disposalMethod: undefined, |
| | | repairEvaluation: '', |
| | | preventiveAction: '', |
| | | |
| | | // 状态 |
| | | status: 0, |
| | | remark: '', |
| | | |
| | | // 附件 |
| | | defectivePhotos: '', |
| | | tempFileIds: [], |
| | | dealResult: '', |
| | | dealName: '', |
| | | dealTime: '', |
| | | reasonAnalysis: '', |
| | | preventiveCorrective: '', |
| | | lossWorking: 0, |
| | | lossMaterial: 0, |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: false, message: "请输入", trigger: "blur" },], |
| | | process: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkName: [{ required: true, message: "请选择检验员", trigger: "change" }], |
| | | productId: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | model: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | unit: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | dealName: [{ required: true, message: "请选择处理人", trigger: "change" }], |
| | | projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }], |
| | | materialName: [{ required: true, message: "请输入物料/部件名称", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请输入数量", trigger: "blur" }], |
| | | unqualifiedQuantity: [{ required: true, message: "请输入不合格数", trigger: "blur" }], |
| | | inspectorName: [{ required: true, message: "请选择检验员", trigger: "change" }], |
| | | inspectDate: [{ required: true, message: "请选择检验日期", trigger: "change" }], |
| | | problemDescription: [{ required: true, message: "请输入问题描述", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | |
| | | const { form, rules } = toRefs(data); |
| | | const productOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); // 检验员/处理人下拉列表 |
| | | const userList = ref([]); // 检验员/责任人下拉列表 |
| | | |
| | | // 打开弹框 |
| | | const openDialog = async (type, row) => { |
| | |
| | | if (operationType.value === 'add') { |
| | | defectivePhotoFileList.value = []; |
| | | form.value = { |
| | | checkName: userStore.nickName || '', |
| | | dealName: '', |
| | | dealTime: '', |
| | | dealResult: '', |
| | | defectivePhenomena: '', |
| | | projectName: '', |
| | | projectNo: '', |
| | | equipmentName: '', |
| | | equipmentDrawingNo: '', |
| | | materialName: '', |
| | | materialDrawingNo: '', |
| | | specificationModel: '', |
| | | materialQuality: '', |
| | | quantity: 0, |
| | | unqualifiedQuantity: 0, |
| | | unqualifiedProcess: undefined, |
| | | supplierName: '', |
| | | inspectorName: userStore.nickName || '', |
| | | inspectDate: '', |
| | | responsiblePerson: '', |
| | | responsibleDept: '', |
| | | problemDescription: '', |
| | | reasonAnalysis: '', |
| | | correctionAction: '', |
| | | disposalMethod: undefined, |
| | | repairEvaluation: '', |
| | | preventiveAction: '', |
| | | status: 0, |
| | | remark: '', |
| | | defectivePhotos: '', |
| | | tempFileIds: [], |
| | | inspectType: '', |
| | | checkTime: '', |
| | | productId: '', |
| | | model: '', |
| | | unit: '', |
| | | quantity: '', |
| | | productName: '', |
| | | reasonAnalysis: '', |
| | | preventiveCorrective: '', |
| | | lossWorking: 0, |
| | | lossMaterial: 0, |
| | | }; |
| | | } else { |
| | | defectivePhotoFileList.value = []; |
| | | form.value = {}; |
| | | } |
| | | getProductOptions(); |
| | | |
| | | if (operationType.value === 'edit') { |
| | | getQualityUnqualifiedInfo(row.id).then(res => { |
| | | const { inspectState, ...rest } = (res.data || {}) |
| | | form.value = { |
| | | reasonAnalysis: '', |
| | | preventiveCorrective: '', |
| | | lossWorking: 0, |
| | | lossMaterial: 0, |
| | | ...rest |
| | | } |
| | | const data = res.data?.records?.[0] || res.data || {} |
| | | form.value = { ...data } |
| | | }) |
| | | } |
| | | } |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }) |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // 找到节点,返回该节点 |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // 在子节点中找到,返回该节点 |
| | | } |
| | | } |
| | | } |
| | | return null; // 没有找到节点,返回null |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // 将 id 改为 value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | |
| | | // 提交产品表单 |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | // 状态字段不在表单填写,也不传给后端 |
| | | const { inspectState, ...payload } = (form.value || {}) |
| | | if (operationType.value === "add") { |
| | | qualityUnqualifiedAdd(payload).then(res => { |
| | | qualityUnqualifiedAdd(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | qualityUnqualifiedUpdate(payload).then(res => { |
| | | qualityUnqualifiedUpdate(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }) |
| | |
| | | }) |
| | | } |
| | | |
| | | // 上传前校检(参考协同审批附件上传) |
| | | // 上传前校检 |
| | | function handleBeforeUpload() { |
| | | proxy.$modal.loading("正在上传文件,请稍候..."); |
| | | return true; |
| | | } |
| | | |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("上传文件失败"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // 不良品照片上传成功:保存 tempId 与 tempPath,并回写到表单 |
| | | |
| | | // 不良品照片上传成功 |
| | | function handleDefectivePhotoUploadSuccess(res, file) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res?.code === 200) { |
| | |
| | | if (!form.value.tempFileIds) form.value.tempFileIds = []; |
| | | if (tempId) form.value.tempFileIds.push(tempId); |
| | | |
| | | // el-upload 列表回显需要 url/name |
| | | file.id = tempId || file.id; |
| | | file.url = tempPath; |
| | | file.name = originalName; |
| | | file.url = tempPath ? (import.meta.env.VITE_APP_BASE_API + tempPath) : file.url; |
| | | |
| | | // 以“路径字符串”形式传给新增/编辑接口(后端若只认 tempFileIds 也不冲突) |
| | | syncDefectivePhotosFromFileList(); |
| | | proxy.$modal.msgSuccess("上传成功"); |
| | | } else { |
| | | proxy.$modal.msgError(res?.msg || "上传失败"); |
| | | defectivePhotoUploadRef.value?.handleRemove(file); |
| | | } |
| | | } |
| | | |
| | | function handleDefectivePhotoRemove(file) { |
| | | // 同步移除 tempFileIds |
| | | const tempId = file?.id || file?.response?.data?.tempId; |
| | | if (tempId && Array.isArray(form.value.tempFileIds)) { |
| | | form.value.tempFileIds = form.value.tempFileIds.filter(id => id !== tempId); |
| | | // 不良品照片移除 |
| | | function handleDefectivePhotoRemove(file, fileList) { |
| | | const tempId = file?.response?.data?.tempId; |
| | | if (tempId && form.value.tempFileIds) { |
| | | const idx = form.value.tempFileIds.indexOf(tempId); |
| | | if (idx > -1) form.value.tempFileIds.splice(idx, 1); |
| | | } |
| | | syncDefectivePhotosFromFileList(); |
| | | defectivePhotoFileList.value = fileList; |
| | | } |
| | | |
| | | function syncDefectivePhotosFromFileList() { |
| | | const base = import.meta.env.VITE_APP_BASE_API; |
| | | const paths = (defectivePhotoFileList.value || []) |
| | | .map(f => f?.url || f?.response?.data?.tempPath || f?.response?.data?.url) |
| | | .filter(Boolean) |
| | | .map(url => (typeof url === "string" ? url.replace(base, "") : "")) |
| | | .filter(Boolean); |
| | | form.value.defectivePhotos = paths.join(","); |
| | | } |
| | | |
| | | // 编辑/详情时,把后端返回的 defectivePhotos 路径串转成 el-upload 可回显的 fileList |
| | | watch( |
| | | () => form.value?.defectivePhotos, |
| | | val => { |
| | | if (!val) { |
| | | defectivePhotoFileList.value = []; |
| | | return; |
| | | } |
| | | const base = import.meta.env.VITE_APP_BASE_API; |
| | | const list = String(val) |
| | | .split(",") |
| | | .map((p, idx) => { |
| | | const path = p?.trim(); |
| | | if (!path) return null; |
| | | return { |
| | | name: `图片${idx + 1}`, |
| | | url: path.startsWith("http") ? path : base + path, |
| | | id: undefined, |
| | | }; |
| | | }) |
| | | .filter(Boolean); |
| | | defectivePhotoFileList.value = list; |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | defectivePhotoFileList.value = [] |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | // 不合格管理 |
| | | // 不合格品处理单 |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div style="display: flex;flex-direction: row;align-items: center;"> |
| | | <div> |
| | | <span class="search_title">类型:</span> |
| | | <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="原材料检验" :value="0" /> |
| | | <el-option label="过程检验" :value="1" /> |
| | | <el-option label="出厂检验" :value="2" /> |
| | | </el-select> |
| | | </div> |
| | | <div style="margin-left: 10px"> |
| | | <span class="search_title">状态:</span> |
| | | <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="待处理" :value="0" /> |
| | | <el-option label="已处理" :value="1" /> |
| | | <el-select v-model="searchForm.status" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="草稿" :value="0" /> |
| | | <el-option label="待审核" :value="1" /> |
| | | <el-option label="审批中" :value="2" /> |
| | | <el-option label="已完成" :value="3" /> |
| | | <el-option label="已驳回" :value="4" /> |
| | | </el-select> |
| | | </div> |
| | | <div style="margin-left: 10px"> |
| | | <span class="search_title">产品名称:</span> |
| | | <span class="search_title">项目名称:</span> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | v-model="searchForm.projectName" |
| | | style="width: 200px" |
| | | placeholder="请输入产品名称搜索" |
| | | placeholder="请输入项目名称搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | </div> |
| | | <span style="margin-left: 10px" class="search_title">检测日期:</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | style="width: 300px" |
| | | placeholder="请选择" clearable @change="changeDaterange" /> |
| | | <div style="margin-left: 10px"> |
| | | <span class="search_title">处理单号:</span> |
| | | <el-input |
| | | v-model="searchForm.orderNo" |
| | | style="width: 200px" |
| | | placeholder="请输入处理单号搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | </div> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button> |
| | | </div> |
| | | <div> |
| | |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | <DetailDia ref="detailDiaRef" /> |
| | | <FormDia ref="formDia" @close="handleQuery"></FormDia> |
| | | <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import FormDia from "@/views/qualityManagement/nonconformingManagement/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {qualityUnqualifiedDel, qualityUnqualifiedListPage} from "@/api/qualityManagement/nonconformingManagement.js"; |
| | | import InspectionFormDia from "@/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue"; |
| | | import DetailDia from "@/views/qualityManagement/nonconformingManagement/components/detailDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | inspectType: "", |
| | | inspectState: "", |
| | | productName: "", |
| | | entryDate: undefined, // 录入日期 |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | status: "", |
| | | projectName: "", |
| | | orderNo: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "状态", |
| | | prop: "inspectState", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "待处理"; |
| | | } else if (params == 1) { |
| | | return "已处理"; |
| | | } else { |
| | | return null; |
| | | } |
| | | const statusMap = { |
| | | 0: "草稿", |
| | | 1: "待审核", |
| | | 2: "审批中", |
| | | 3: "已完成", |
| | | 4: "已驳回" |
| | | }; |
| | | return statusMap[params] || "-"; |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == '不合格') { |
| | | return "danger"; |
| | | } else if (params == '合格') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | } |
| | | const typeMap = { |
| | | 0: "info", |
| | | 1: "warning", |
| | | 2: "primary", |
| | | 3: "success", |
| | | 4: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "检测日期", |
| | | prop: "checkTime", |
| | | label: "处理单号", |
| | | prop: "orderNo", |
| | | width: 160 |
| | | }, |
| | | { |
| | | label: "项目名称", |
| | | prop: "projectName", |
| | | width: 140 |
| | | }, |
| | | { |
| | | label: "项目编号", |
| | | prop: "projectNo", |
| | | width: 140 |
| | | }, |
| | | { |
| | | label: "设备名称", |
| | | prop: "equipmentName", |
| | | width: 140 |
| | | }, |
| | | { |
| | | label: "物料/部件名称", |
| | | prop: "materialName", |
| | | width: 160 |
| | | }, |
| | | { |
| | | label: "型号规格", |
| | | prop: "specificationModel", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "类别", |
| | | prop: "inspectType", |
| | | dataType: "tag", |
| | | width: 120, |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "原材料检验"; |
| | | } else if (params == 1) { |
| | | return "过程检验"; |
| | | } else { |
| | | return '出厂检验'; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == '不合格') { |
| | | return "info"; |
| | | } else if (params == '合格') { |
| | | return "success"; |
| | | } else { |
| | | return 'primary'; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "检验员", |
| | | prop: "checkName", |
| | | }, |
| | | { |
| | | label: "产品名称", |
| | | prop: "productName", |
| | | }, |
| | | { |
| | | label: "规格型号", |
| | | prop: "model", |
| | | }, |
| | | { |
| | | label: "单位", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "数量", |
| | | prop: "quantity", |
| | | width: 80 |
| | | }, |
| | | { |
| | | label: "不合格数", |
| | | prop: "unqualifiedQuantity", |
| | | width: 90 |
| | | }, |
| | | { |
| | | label: "不合格工序", |
| | | prop: "unqualifiedProcess", |
| | | width: 100, |
| | | formatData: (params) => { |
| | | const processMap = { |
| | | 1: "来料", |
| | | 2: "制程", |
| | | 3: "成品" |
| | | }; |
| | | return processMap[params] || "-"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "检验员", |
| | | prop: "inspectorName", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "不合格现象", |
| | | prop: "defectivePhenomena", |
| | | label: "检验日期", |
| | | prop: "inspectDate", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "处理结果", |
| | | prop: "dealResult", |
| | | label: "责任人", |
| | | prop: "responsiblePerson", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "责任部门", |
| | | prop: "responsibleDept", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "处理人", |
| | | prop: "dealName", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "处理日期", |
| | | prop: "dealTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "原因分析", |
| | | prop: "reasonAnalysis", |
| | | slot: "reasonAnalysis", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "预防与纠正措施", |
| | | prop: "preventiveCorrective", |
| | | slot: "preventiveCorrective", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "工时损失", |
| | | prop: "lossWorking", |
| | | width: 140 |
| | | }, |
| | | { |
| | | label: "材料费损失", |
| | | prop: "lossMaterial", |
| | | width: 140 |
| | | label: "处置方式", |
| | | prop: "disposalMethod", |
| | | width: 100, |
| | | formatData: (params) => { |
| | | const disposalMap = { |
| | | 1: "让步接收", |
| | | 2: "厂内维修", |
| | | 3: "返厂维修", |
| | | 4: "换货", |
| | | 5: "退货", |
| | | 6: "报废" |
| | | }; |
| | | return disposalMap[params] || "-"; |
| | | }, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: "详情", |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openDetailDialog(row); |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "处理", |
| | | name: "删除", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openInspectionForm("edit", row); |
| | | handleDeleteRow(row); |
| | | }, |
| | | disabled: (row) => row.inspectState === 1, |
| | | }, |
| | | ], |
| | | }, |
| | |
| | | total: 0 |
| | | }); |
| | | const formDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const detailDiaRef = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // 查询列表 |
| | | /** 搜索按钮操作 */ |
| | | const handleQuery = () => { |
| | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | qualityUnqualifiedListPage(params).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | |
| | | |
| | | // 打开弹框 |
| | | const openForm = (type, row) => { |
| | | if (type !== 'add' && row?.inspectState === 1) { |
| | | proxy.$modal.msgWarning("已处理的数据不能再编辑"); |
| | | return; |
| | | } |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // 打开处理弹框 |
| | | const openInspectionForm = (type, row) => { |
| | | if (row?.inspectState === 1) { |
| | | proxy.$modal.msgWarning("已处理的数据不能再处理"); |
| | | return; |
| | | } |
| | | nextTick(() => { |
| | | inspectionFormDia.value?.openDialog(type, row) |
| | | |
| | | // 删除单行 |
| | | const handleDeleteRow = (row) => { |
| | | ElMessageBox.confirm("确认删除该不合格品处理单?", "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | }; |
| | | // 打开详情弹框 |
| | | const openDetailDialog = (row) => { |
| | | detailDiaRef.value?.openDialog(row); |
| | | .then(() => { |
| | | qualityUnqualifiedDel([row.id]).then((res) => { |
| | | proxy.$modal.msgSuccess("删除成功"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | |
| | | // 删除 |
| | |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", { |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/quality/qualityUnqualified/export", {}, "不合格管理.xlsx"); |
| | | proxy.download("/qualityUnqualifiedOrder/export", {}, "不合格品处理单.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 审批人选择(仿协同审批里的审批人节点选择) --> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <template #label> |
| | | <span>审批人选择:</span> |
| | | <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">新增节点</el-button> |
| | | </template> |
| | | <div style="display: flex; align-items: flex-end; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | style="margin-right: 20px; text-align: center; margin-bottom: 10px;" |
| | | > |
| | | <div> |
| | | <span>审批人</span> |
| | | → |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="选择人员" |
| | | filterable |
| | | style="width: 140px; margin-bottom: 8px;" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <div> |
| | | <el-button |
| | | type="danger" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | >删除</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | }, |
| | | }); |
| | | const { deliveryForm, deliveryRules } = toRefs(deliveryFormData); |
| | | |
| | | // 发货审批人节点(仿协同审批 infoFormDia.vue) |
| | | const approverNodes = ref([{ id: 1, userId: null }]); |
| | | let nextApproverId = 2; |
| | | const addApproverNode = () => { |
| | | approverNodes.value.push({ id: nextApproverId++, userId: null }); |
| | | }; |
| | | const removeApproverNode = (index) => { |
| | | approverNodes.value.splice(index, 1); |
| | | }; |
| | | |
| | | // 导入相关 |
| | | const importUploadRef = ref(null); |
| | |
| | | deliveryForm.value = { |
| | | type: "货车", |
| | | }; |
| | | // 重置审批人节点(默认一个空节点) |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | nextApproverId = 2; |
| | | deliveryFormVisible.value = true; |
| | | }; |
| | | |
| | |
| | | const submitDelivery = () => { |
| | | proxy.$refs["deliveryFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // 审批人必填校验(所有节点都要选人) |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId); |
| | | if (hasEmptyApprover) { |
| | | proxy.$modal.msgError("请为所有审批节点选择审批人!"); |
| | | return; |
| | | } |
| | | const approveUserIds = approverNodes.value.map(node => node.userId).join(","); |
| | | // 保存当前展开的行ID,以便发货后重新加载子表格数据 |
| | | const currentExpandedKeys = [...expandedRowKeys.value]; |
| | | const salesLedgerId = currentDeliveryRow.value.salesLedgerId; |
| | |
| | | salesLedgerId: salesLedgerId, |
| | | salesLedgerProductId: currentDeliveryRow.value.id, |
| | | type: deliveryForm.value.type, |
| | | approveUserIds, |
| | | }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("发货成功"); |
| | |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 审批人信息 --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <UserFilled/> |
| | | </el-icon> |
| | | <span class="card-title">审批人选择</span> |
| | | <el-button type="primary" size="small" @click="addApproverNode" class="header-btn"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | 新增节点 |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item" |
| | | > |
| | | <div class="approver-node-label"> |
| | | <span class="node-step">{{ index + 1 }}</span> |
| | | <span class="node-text">审批人</span> |
| | | <el-icon class="arrow-icon"> |
| | | <ArrowRight/> |
| | | </el-icon> |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="选择人员" |
| | | class="approver-select" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | :icon="Delete" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | class="remove-btn" |
| | | >删除 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 产品信息 --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | |
| | | <FormDialog v-model="importDialogVisible" title="导入报价单" width="85%" :close-on-click-modal="false" |
| | | @close="importDialogVisible = false" @confirm="handleImportSubmit" |
| | | @cancel="importDialogVisible = false"> |
| | | <!-- 审批人信息 --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <UserFilled/> |
| | | </el-icon> |
| | | <span class="card-title">审批人选择</span> |
| | | <el-button type="primary" size="small" @click="addImportApproverNode" class="header-btn"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | 新增节点 |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | v-for="(node, index) in importApproverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item" |
| | | > |
| | | <div class="approver-node-label"> |
| | | <span class="node-step">{{ index + 1 }}</span> |
| | | <span class="node-text">审批人</span> |
| | | <el-icon class="arrow-icon"> |
| | | <ArrowRight/> |
| | | </el-icon> |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="选择人员" |
| | | class="approver-select" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | :icon="Delete" |
| | | @click="removeImportApproverNode(index)" |
| | | v-if="importApproverNodes.length > 1" |
| | | class="remove-btn" |
| | | >删除 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | |
| | | const importDialogVisible = ref(false) |
| | | const viewDialogVisible = ref(false) |
| | | const importFileList = ref([]) |
| | | const importApproverNodes = ref([ |
| | | {id: 1, userId: null} |
| | | ]) |
| | | let nextImportApproverId = 2 |
| | | |
| | | const fileUpload = ref(null) |
| | | |
| | |
| | | const userList = ref([]); |
| | | const customerOption = ref([]); |
| | | |
| | | // 审批人节点相关 |
| | | const approverNodes = ref([ |
| | | {id: 1, userId: null} |
| | | ]) |
| | | let nextApproverId = 2 |
| | | |
| | | const isEdit = ref(false) |
| | | const showDetail = ref(false) |
| | | const editId = ref(null) |
| | | const currentQuotation = ref({}) |
| | | const formRef = ref() |
| | | |
| | | // 添加审批人节点 |
| | | function addApproverNode() { |
| | | approverNodes.value.push({id: nextApproverId++, userId: null}) |
| | | } |
| | | |
| | | // 删除审批人节点 |
| | | function removeApproverNode(index) { |
| | | approverNodes.value.splice(index, 1) |
| | | } |
| | | |
| | | // 导入弹窗审批人节点相关 |
| | | function addImportApproverNode() { |
| | | importApproverNodes.value.push({id: nextImportApproverId++, userId: null}) |
| | | } |
| | | |
| | | function removeImportApproverNode(index) { |
| | | importApproverNodes.value.splice(index, 1) |
| | | } |
| | | |
| | | function triggerRemoveImportFile(file) { |
| | | const index = importFileList.value.indexOf(file) |
| | |
| | | return |
| | | } |
| | | |
| | | const hasEmptyApprover = importApproverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | | ElMessage.error('请为所有审批节点选择审批人!') |
| | | return |
| | | } |
| | | |
| | | const selectedFile = importFileList.value[0] |
| | | const rawFile = selectedFile?.raw || selectedFile |
| | | if (!validateImportFile(rawFile)) { |
| | |
| | | |
| | | const formData = new FormData() |
| | | formData.append('file', rawFile) |
| | | |
| | | // 审核人 IDs,以逗号分割 |
| | | const approveUserIds = importApproverNodes.value.map(node => node.userId).join(',') |
| | | formData.append('approveUserIdsJson', approveUserIds) |
| | | |
| | | loading.value = true |
| | | try { |
| | |
| | | const handleImport = async () => { |
| | | importFileList.value = [] |
| | | |
| | | // ✅ 清空“导入用”的审批人 |
| | | importApproverNodes.value = [{id: 1, userId: null}] |
| | | nextImportApproverId = 2 |
| | | |
| | | let userLists = await userListNoPage(); |
| | | importDialogVisible.value = true |
| | | |
| | |
| | | dialogTitle.value = '新增报价' |
| | | isEdit.value = false |
| | | resetForm() |
| | | // 重置审批人节点 |
| | | approverNodes.value = [{id: 1, userId: null}] |
| | | nextApproverId = 2 |
| | | dialogVisible.value = true |
| | | let userLists = await userListNoPage(); |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | |
| | | form.discountAmount = row.discountAmount || 0 |
| | | form.totalAmount = row.totalAmount || 0 |
| | | |
| | | // 反显审批人 |
| | | if (row.approveUserIds) { |
| | | const userIds = row.approveUserIds.split(',') |
| | | approverNodes.value = userIds.map((userId, idx) => ({ |
| | | id: idx + 1, |
| | | userId: parseInt(userId.trim()) |
| | | })) |
| | | nextApproverId = userIds.length + 1 |
| | | } else { |
| | | approverNodes.value = [{id: 1, userId: null}] |
| | | nextApproverId = 2 |
| | | } |
| | | |
| | | // 加载用户列表 |
| | | let userLists = await userListNoPage(); |
| | | userList.value = (userLists.data || []).map(item => ({ |
| | |
| | | return |
| | | } |
| | | |
| | | // 审批人必填校验 |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | | ElMessage.error('请为所有审批节点选择审批人!') |
| | | return |
| | | } |
| | | |
| | | // 收集所有节点的审批人id |
| | | form.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | |
| | | // 计算所有产品的单价总和 |
| | | form.totalAmount = form.products.reduce((sum, product) => { |
| | | const price = Number(product.unitPrice) || 0 |
| | |
| | | validDate: item.validDate || '', |
| | | paymentMethod: item.paymentMethod || '', |
| | | status: item.status || '草稿', |
| | | // 审批人(用于编辑时反显) |
| | | approveUserIds: item.approveUserIds || '', |
| | | remark: item.remark || '', |
| | | products: item.products ? item.products.map(product => ({ |
| | | productId: product.productId || '', |