Merge branch 'dev_天津_君歌化工' of http://114.132.189.42:9002/r/product-inventory-management into dev_天津_君歌化工
| | |
| | | // 审批管理配置 |
| | | 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, |
| | | }) |
| | |
| | | // 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) => { |
| | |
| | | </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 || '', |
| | |
| | | const { VITE_APP_ENV } = env; |
| | | const baseUrl = |
| | | env.VITE_APP_ENV === "development" |
| | | ? "http://1.15.17.182:9003" |
| | | ? "http://localhost:7003" |
| | | : env.VITE_BASE_API; |
| | | const javaUrl = |
| | | env.VITE_APP_ENV === "development" |
| | | ? "http://1.15.17.182:9002" |
| | | ? "http://1.15.17.182:9048" |
| | | : env.VITE_JAVA_API; |
| | | return { |
| | | define:{ |