liyong
4 天以前 cd9ee0b4268c6b0ccdaf75115aea4680ce40cb83
Merge branch 'dev_天津_君歌化工' of http://114.132.189.42:9002/r/product-inventory-management into dev_天津_君歌化工
已修改20个文件
1955 ■■■■■ 文件已修改
src/api/collaborativeApproval/approvalManagement.js 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 224 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 331 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalManagement/index.vue 113 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/New.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 195 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 137 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/approvalManagement.js
@@ -1,53 +1,19 @@
// 审批管理配置
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,
    })
src/assets/styles/element-ui.scss
@@ -1,52 +1,52 @@
// 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;
@@ -98,64 +98,86 @@
.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;
}
src/assets/styles/sidebar.scss
@@ -1,70 +1,69 @@
#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%;
@@ -75,18 +74,18 @@
      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 {
@@ -95,17 +94,33 @@
        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 {
@@ -116,7 +131,7 @@
        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;
@@ -127,7 +142,7 @@
      }
    }
  }
  .hideSidebar {
    .sidebar-container {
      width: 68px !important;
@@ -138,7 +153,7 @@
    .main-container {
      margin-left: 84px;
    }
    .submenu-title-noDropdown {
      padding: 0 !important;
      position: relative;
@@ -225,60 +240,56 @@
            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;
@@ -297,27 +308,27 @@
      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;
    }
  }
}
src/layout/components/Sidebar/SidebarItem.vue
@@ -9,7 +9,7 @@
      </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>
src/layout/components/Sidebar/index.vue
@@ -99,6 +99,10 @@
    .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) {
@@ -111,8 +115,10 @@
      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) {
@@ -137,6 +143,43 @@
    :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>
src/views/collaborativeApproval/approvalManagement/index.vue
@@ -186,8 +186,10 @@
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');
@@ -201,6 +203,7 @@
  { 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' },
];
// 审批类型名称映射
@@ -212,6 +215,7 @@
  5: '采购审批',
  6: '报价审批',
  7: '发货审批',
  10: '销售审批',
};
// 审批类型图标映射
@@ -223,6 +227,7 @@
  5: 'ShoppingCart',
  6: 'DocumentChecked',
  7: 'Van',
  10: 'Sell',
};
// 审批类型颜色映射
@@ -234,6 +239,7 @@
  5: '#909399',
  6: '#9B59B6',
  7: '#1ABC9C',
  10: '#FF6B6B',
};
// 头像颜色池
@@ -271,44 +277,8 @@
  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([]);
@@ -326,14 +296,40 @@
};
// 加载审批配置数据(模拟)
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('加载人员列表失败');
  }
};
// 审批人选择变化
@@ -393,8 +389,8 @@
  approverList.value[index + 1].sortOrder = index + 2;
};
// 保存配置(模拟)
const handleSave = () => {
// 保存配置
const handleSave = async () => {
  if (approverList.value.length === 0) {
    ElMessage.warning('请至少配置一个审批人');
    return;
@@ -414,17 +410,21 @@
  }
  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);
  }
};
// 重置
@@ -445,8 +445,9 @@
    .catch(() => {});
};
onMounted(() => {
  loadData();
onMounted(async () => {
  await loadUserList();
  await loadData();
});
</script>
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -39,39 +39,6 @@
                        </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>
      <!-- 报价审批:展示报价详情(复用销售报价"查看详情对话框"内容结构) -->
@@ -228,7 +195,6 @@
    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";
@@ -248,7 +214,6 @@
const formRef = ref(null);
const userStore = useUserStore()
const productOptions = ref([]);
const userList = ref([])
const quotationLoading = ref(false)
const currentQuotation = ref({})
const purchaseLoading = ref(false)
@@ -258,9 +223,7 @@
const data = reactive({
    form: {
        approveTime: "",
        approveId: "",
        approveUser: "",
        approveDeptId: "",
        approveReason: "",
        checkResult: "",
@@ -295,9 +258,6 @@
  dialogFormVisible.value = true;
  currentQuotation.value = {}
  currentPurchase.value = {}
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    form.value = {...row}
    // 立即清除表单验证状态(因为字段是disabled的,不需要验证)
    nextTick(() => {
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -97,82 +97,7 @@
            </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">
@@ -211,13 +136,10 @@
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);
@@ -231,24 +153,19 @@
});
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" }],
@@ -268,18 +185,7 @@
  }
})
// 审批人节点相关
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) {
@@ -295,39 +201,19 @@
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}
    })
  }
}
@@ -362,15 +248,7 @@
}
// 提交产品表单
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) {
src/views/collaborativeApproval/approvalProcess/index.vue
@@ -70,7 +70,7 @@
          <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>
@@ -84,7 +84,7 @@
            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>
@@ -159,6 +159,7 @@
  { 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' },
];
// 当前审批类型信息
@@ -207,6 +208,7 @@
  const isReimburseType = currentApproveType.value === 4; // 报销管理
  const isQuotationType = currentApproveType.value === 6; // 报价审批
  const isPurchaseType = currentApproveType.value === 5; // 采购审批
  const isSalesType = currentApproveType.value === 10; // 销售审批
  
  // 基础列配置
  const baseColumns = [
@@ -305,6 +307,7 @@
        currentApproveType.value === 5 ||
        currentApproveType.value === 6 ||
        currentApproveType.value === 7 ||
        currentApproveType.value === 10 ||
        row.approveStatus == 2 ||
        row.approveStatus == 1 ||
        row.approveStatus == 4
@@ -405,6 +408,7 @@
    5: "/approveProcess/exportFive",
    6: "/approveProcess/exportSix",
    7: "/approveProcess/exportSeven",
    10: "/approveProcess/exportTen",
  }
  const url = urlMap[type] || urlMap[0]
  const nameMap = {
@@ -416,6 +420,7 @@
    5: "采购申请审批表",
    6: "报价审批表",
    7: "发货审批表",
    10: "销售审批表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
src/views/procurementManagement/procurementLedger/index.vue
@@ -298,50 +298,6 @@
            </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">
@@ -760,16 +716,6 @@
  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: "待审核",
@@ -1121,19 +1067,12 @@
    }
    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);
@@ -1284,9 +1223,6 @@
    templateName.value = "";
    filterInputValue.value = "";
    isTemplateNameDuplicate.value = false;
    // 重置审批人节点(默认一个空节点)
    approverNodes.value = [{ id: 1, userId: null }];
    nextApproverId = 2;
    try {
      // 并行加载基础数据
      const [userRes, salesRes, supplierRes] = await Promise.all([
@@ -1325,15 +1261,6 @@
          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("加载数据失败");
@@ -1402,14 +1329,6 @@
  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;
@@ -1430,7 +1349,6 @@
        }
        form.value.tempFileIds = tempFileIds;
        form.value.type = 2;
        form.value.approveUserIds = approveUserIds;
        // 如果salesLedgerId为空,则不传递salesContractNo
        if (!form.value.salesLedgerId) {
@@ -1454,9 +1372,6 @@
  // 关闭弹框
  const closeDia = () => {
    proxy.resetForm("formRef");
    // 重置审批人节点(默认一个空节点)
    approverNodes.value = [{ id: 1, userId: null }];
    nextApproverId = 2;
    dialogFormVisible.value = false;
  };
  // 打开产品弹框
src/views/productionManagement/processRoute/Edit.vue
@@ -3,7 +3,7 @@
    <el-dialog
        v-model="isShow"
        title="编辑工艺路线"
        width="400"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
@@ -25,35 +25,54 @@
          </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>
      
@@ -76,7 +95,7 @@
<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({
@@ -99,8 +118,8 @@
  productModelId: undefined,
  productName: "",
  productModelName: "",
  bomId: undefined,
  description: '',
  processRouteItemList: [],
});
const isShow = computed({
@@ -113,9 +132,49 @@
});
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;
@@ -131,90 +190,42 @@
      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 => {
@@ -245,6 +256,7 @@
}, { immediate: true });
onMounted(() => {
  getProcessOptions();
  if (props.visible && props.record) {
    setFormData();
  }
src/views/productionManagement/processRoute/New.vue
@@ -25,33 +25,6 @@
          </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>
@@ -76,7 +49,6 @@
<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({
@@ -94,7 +66,6 @@
  productModelId: undefined,
  productName: "",
  productModelName: "",
  bomId: undefined,
  description: '',
});
@@ -108,7 +79,6 @@
});
const showProductSelectDialog = ref(false);
const bomOptions = ref([]);
let { proxy } = getCurrentInstance()
@@ -119,10 +89,8 @@
    productModelId: undefined,
    productName: "",
    productModelName: "",
    bomId: undefined,
    description: '',
  };
  bomOptions.value = [];
  isShow.value = false;
};
@@ -130,48 +98,21 @@
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 => {
@@ -185,10 +126,11 @@
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -50,7 +50,7 @@
    
    <!-- 表格视图 -->
    <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" 
@@ -74,14 +74,9 @@
        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>
@@ -92,6 +87,16 @@
      <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">
@@ -130,7 +135,7 @@
    <!-- 卡片视图 -->
    <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" 
@@ -162,7 +167,8 @@
          <!-- 与工序主表一致的简要信息 -->
          <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) }}
@@ -184,7 +190,7 @@
    <!-- 新增/编辑弹窗(布局、字段与工序/部件页 New、Edit 一致;提交参数仍为原接口字段) -->
    <el-dialog
        v-model="dialogVisible"
        :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
        :title="operationType === 'add' ? '新增工序' : '编辑工序'"
        width="760"
        @close="closeDialog"
    >
@@ -322,7 +328,7 @@
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: '',
src/views/productionManagement/productionOrder/New.vue
@@ -37,7 +37,7 @@
          <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%;"
@@ -47,7 +47,7 @@
                       :label="`${item.processRouteCode || ''}`"
                       :value="item.id" />
          </el-select>
        </el-form-item> -->
        </el-form-item>
        <el-form-item
            label="需求数量"
src/views/productionManagement/productionProcess/Edit.vue
@@ -10,53 +10,21 @@
        <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">
@@ -111,6 +79,25 @@
            </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>
@@ -135,7 +122,6 @@
<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({
@@ -156,18 +142,16 @@
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({
@@ -185,14 +169,14 @@
    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,
    };
  }
@@ -204,17 +188,16 @@
    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);
  }
});
@@ -231,105 +214,6 @@
    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 () => {
@@ -350,11 +234,9 @@
  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 = () => {
@@ -376,7 +258,6 @@
};
onMounted(() => {
  getProductCategoryOptions();
  getPlannerOptions();
});
src/views/productionManagement/productionProcess/New.vue
@@ -10,53 +10,21 @@
        <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">
@@ -113,6 +81,25 @@
            </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>
@@ -137,7 +124,6 @@
<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({
@@ -152,17 +138,15 @@
// 响应式数据(替代选项式的 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({
@@ -189,60 +173,6 @@
  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();
@@ -257,11 +187,9 @@
  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 = () => {
@@ -283,7 +211,6 @@
};
onMounted(() => {
  getProductCategoryOptions();
  getPlannerOptions();
});
src/views/productionManagement/productionProcess/index.vue
@@ -115,14 +115,9 @@
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
    {
      label: "产品名称",
      label: "部件名称",
      prop: "name",
    },
    {
      label: "产品规格",
      prop: "productModel",
    },
    {
      label: "部件编号",
@@ -141,6 +136,10 @@
      prop: "plannerName",
    },
    {
      label: "计划执行人员",
      prop: "executorName",
    },
    {
      label: "是否质检",
      prop: "isQuality",
      formatData: (params) => {
src/views/salesManagement/salesLedger/index.vue
@@ -595,50 +595,6 @@
                        </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">
@@ -809,16 +765,6 @@
  },
});
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);
@@ -2115,9 +2061,6 @@
  deliveryForm.value = {
    type: "货车",
  };
  // 重置审批人节点(默认一个空节点)
  approverNodes.value = [{ id: 1, userId: null }];
  nextApproverId = 2;
    deliveryFormVisible.value = true;
};
@@ -2125,13 +2068,6 @@
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;
@@ -2139,7 +2075,6 @@
        salesLedgerId: salesLedgerId,
        salesLedgerProductId: currentDeliveryRow.value.id,
        type: deliveryForm.value.type,
                approveUserIds,
      })
        .then(() => {
          proxy.$modal.msgSuccess("发货成功");
src/views/salesManagement/salesQuotation/index.vue
@@ -173,69 +173,6 @@
            </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>
@@ -345,68 +282,6 @@
    <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">
@@ -546,10 +421,6 @@
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)
@@ -599,36 +470,11 @@
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)
@@ -643,12 +489,6 @@
    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)) {
@@ -657,10 +497,6 @@
  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 {
@@ -767,10 +603,6 @@
const handleImport = async () => {
  importFileList.value = []
  // ✅ 清空“导入用”的审批人
  importApproverNodes.value = [{id: 1, userId: null}]
  nextImportApproverId = 2
  let userLists = await userListNoPage();
  importDialogVisible.value = true
@@ -785,9 +617,6 @@
  dialogTitle.value = '新增报价'
  isEdit.value = false
  resetForm()
  // 重置审批人节点
  approverNodes.value = [{id: 1, userId: null}]
  nextApproverId = 2
  dialogVisible.value = true
  let userLists = await userListNoPage();
  // 只复制需要的字段,避免将组件引用放入响应式对象
@@ -992,19 +821,6 @@
  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 => ({
@@ -1103,16 +919,6 @@
        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
@@ -1177,8 +983,6 @@
        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 || '',
vite.config.js
@@ -8,11 +8,11 @@
  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:{