Browse Source

Merge branch 'dev' into test

mlchen 2 weeks ago
parent
commit
8a0ab621b1

+ 11 - 0
docs/仓储看板审批接入说明.md

@@ -0,0 +1,11 @@
+# 仓储看板审批接入说明
+
+更新日期:2026-10-16。
+
+- `bpm/todo/index.vue`在`dashboard=1`时接收`warehouseScope/warehouseId/month/taskId`,先通过原`POST /bpm/task/todo-page`核实真实任务与范围,再调用原审批容器。无任务时提示已处理或不在授权范围,不用单据ID代替任务ID。
+- `bpm/handleTask/components/stagnantDisposal/detailDialog.vue`通过`GET /wms/dashboard/action/disposal/{businessId}`展示真实处置单、仓库名称、批次、数量、原因及执行状态;加载失败明确展示错误。
+- `stagnantDisposal/submit.vue`继承现有`stockBatchChange/submit.vue`,复用同一任务审批意见、通过与驳回请求。后端处置流程的`pcView/pcHandle`须指向这两个本地Vue组件路径,不能配置WMS页面URL或`/home/index`。
+- 已与后端实际变量核对:`pcView=/bpm/handleTask/components/stagnantDisposal/detailDialog.vue`,`pcHandle=/bpm/handleTask/components/stagnantDisposal/submit.vue`。
+- `router.afterEach`仅在`dashboard=1`时启用`wms-dashboard-theme`;对应Element样式覆盖挂到`body`的弹层,普通页面离开后移除该类。
+
+最终5个改动的Vue/JS/SCSS文件通过模板编译、脚本解析及看板Sass编译,`git diff --check`通过。Vue实际选项合并断言确认处置审批子组件继承原渲染函数、审批方法和任务参数;后端两个路径与本地文件一致。未做完整构建、浏览器联调或实际审批写操作;发布前须核实目标流程模型、审批人规则、仓储权限及详情权限。

+ 26 - 0
src/assets/styles/warehouse-dashboard.scss

@@ -0,0 +1,26 @@
+/* 仅看板及携带 dashboard=1 的下钻页面生效,包含挂载到 body 的弹层。 */
+body.wms-dashboard-theme {
+  .el-card,.el-dialog,.el-message-box,.el-select-dropdown,.el-picker-panel,.el-cascader-panel,.el-popover,.el-dropdown-menu,.el-autocomplete-suggestion { background: #0A1A30; color: #EAF4FF; border-color: rgba(70,160,230,.28); }
+  .el-card__header,.el-dialog__header,.el-message-box__header { border-color: rgba(70,160,230,.28); }
+  .el-dialog__title,.el-message-box__title,.el-dialog__body,.el-message-box__content,.el-form-item__label,.el-checkbox__label,.el-radio__label { color: #EAF4FF; }
+  .el-input__inner,.el-textarea__inner,.el-range-input { background: #071528; color: #EAF4FF; border-color: rgba(70,160,230,.35); }
+  .el-input.is-disabled .el-input__inner,.el-textarea.is-disabled .el-textarea__inner { background: #10243A; color: #9FB8D4; }
+  .el-select-dropdown__item,.el-dropdown-menu__item,.el-cascader-node,.el-date-table td { color: #9FB8D4; }
+  .el-select-dropdown__item.hover,.el-select-dropdown__item:hover,.el-dropdown-menu__item:hover,.el-cascader-node:hover,.el-date-table td.available:hover { background: #173655; color: #38C8F5; }
+  .el-table,.el-table__expanded-cell,.el-table tr { background: #0A1A30; color: #EAF4FF; }
+  .el-table th.el-table__cell,.el-table th { background: #102A48; color: #6FA6D8; }
+  .el-table td,.el-table th.is-leaf,.el-table--border,.el-table--group { border-color: rgba(70,160,230,.28); }
+  .el-table--enable-row-hover .el-table__body tr:hover > td,.el-table__body tr.current-row > td { background: #173655; }
+  .el-table::before,.el-table--border::after { background: rgba(70,160,230,.28); }
+  .el-tabs--border-card,.el-tabs--border-card > .el-tabs__header,.el-tabs__nav-wrap::after { background: #0A1A30; border-color: rgba(70,160,230,.28); }
+  .el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active { background: #173655; color: #38C8F5; border-color: rgba(70,160,230,.28); }
+  .el-tabs__item,.el-pagination,.el-pagination__total,.el-pagination__jump,.el-upload-list__item-name { color: #9FB8D4; }
+  .el-pagination button,.el-pager li { background: #102A48; color: #9FB8D4; }
+  .el-pager li.active { color: #38C8F5; }
+  .el-button--default { background: #102A48; color: #EAF4FF; border-color: rgba(70,160,230,.35); }
+  .el-button--primary { background: #2E8BE8; border-color: #38C8F5; }
+  .el-input-number__increase,.el-input-number__decrease { background: #102A48; color: #9FB8D4; border-color: rgba(70,160,230,.28); }
+  .el-loading-mask { background: rgba(6,15,31,.75); }
+  .dashboard-note { color: #9FB8D4; line-height: 1.8; margin: 0 0 14px; }
+  .dashboard-success { color: #3BD48A; } .dashboard-error { color: #F25E5E; }
+}

+ 2 - 0
src/router/index.js

@@ -1,3 +1,4 @@
+import '@/assets/styles/warehouse-dashboard.scss';
 /**
  * 路由配置
  */
@@ -99,6 +100,7 @@ router.beforeEach((to, from, next) => {
 });
 
 router.afterEach((to) => {
+  document.body.classList.toggle('wms-dashboard-theme', to.query.dashboard === '1');
   if (!to.path.includes(REDIRECT_PATH) && NProgress.isStarted()) {
     setTimeout(() => {
       NProgress.done(true);

+ 34 - 2
src/views/bpm/handleTask/components/qualityReportApproval/template/inspection_report3.vue

@@ -18,7 +18,7 @@
           margin-bottom: 5px;
           font-weight: bold;
         "
-        >{{ basicInfoData.groupName || '湖南特瑞精密医疗器器械有限公司' }}</div
+        >{{ groupName }}</div
       >
       <div style="text-align: center; font-size: 18px; margin-bottom: 5px"
         >不合格品通知及评审处置表</div
@@ -489,7 +489,7 @@
           margin-bottom: 5px;
           font-weight: bold;
         "
-        >{{ basicInfoData.groupName || '湖南特瑞精密医疗器器械有限公司' }}</div
+        >{{ groupName }}</div
       >
       <div style="text-align: center; font-size: 18px; margin-bottom: 5px"
         >不合格品通知及评审处置表</div
@@ -1106,6 +1106,7 @@
 <script>
   import imageUpload from '@/components/upload/imageUpload.vue';
   import { getFmImagePath } from '@/utils/file';
+  import { getCurrentUser } from '@/utils/token-util';
 
   export default {
     name: 'QualityReport',
@@ -1125,6 +1126,8 @@
           // 表单编号、版本(两处共用)
           reportNumber: '',
           version: '',
+          // 表单抬头公司名称,为空时默认取当前登录用户所属公司
+          groupName: '',
           // 处置编号
           disposalNumber: '',
           remark: '',
@@ -1204,6 +1207,31 @@
         }
       };
     },
+    computed: {
+      /**
+       * 当前登录用户所属公司(集团)名称
+       * 优先取当前切换后的分组,兼容多公司切换场景
+       */
+      currentUserGroupName() {
+        const userInfo = this.$store.state.user.info || {};
+        const currentGroupId = getCurrentUser()?.currentGroupId;
+        const currentGroup = (userInfo.loginChangeGroupVOList || []).find(
+          (item) => item.groupId == currentGroupId
+        );
+        return currentGroup?.groupName || userInfo.groupName || '';
+      },
+      /**
+       * 表单抬头公司名称
+       * 优先取表单数据,不存在时默认取当前登录用户所属公司
+       */
+      groupName() {
+        return (
+          this.basicInfoData.groupName ||
+          this.currentUserGroupName ||
+          '湖南特瑞精密医疗器器械有限公司'
+        );
+      }
+    },
     methods: {
       /* 打开质检报告 */
       async init(basicInfoData) {
@@ -1214,6 +1242,10 @@
             ...basicInfoData
           });
         }
+        // 表单未带公司名称时,默认使用当前登录用户所属公司
+        if (!this.basicInfoData.groupName) {
+          this.basicInfoData.groupName = this.currentUserGroupName || '';
+        }
       },
       /* 保存编辑 */
       async getValue() {

+ 375 - 141
src/views/bpm/handleTask/components/saleOrder/accountstatement/accountDetailTable.vue

@@ -1,156 +1,187 @@
 <template>
   <div class="account-detail-table">
-    <el-form ref="detailForm" :model="{ flatList }">
-      <ele-pro-table
-        ref="detailTable"
-        row-key="id"
-        :needPage="false"
-        :columns="columns"
-        max-height="500px"
-        :datasource="flatList"
-        :cache-key="cacheKeyUrl"
-        class="time-form"
-      >
-        <template v-slot:toolbar>
-          <div class="detail-toolbar">
-            <el-button
-              v-if="!isView"
-              type="primary"
-              size="small"
-              @click="batchSetTaxRate"
-              >批量设置税率</el-button
-            >
-            <el-button
-              v-if="!isView"
-              type="primary"
-              size="small"
-              @click="batchSetPrice"
-              >批量设置单价</el-button
-            >
-            <el-button
-              v-if="!isView"
-              type="primary"
-              size="small"
-              @click="batchSetDiscount"
-              >批量设置折让比例</el-button
-            >
-            <!-- <div class="summary-info">
-              <span
-                >本次总对账金额:<b>{{ totalStatementAmount }}</b> 元</span
-              >
-              <span
-                >本次总对账数量:<b>{{ totalStatementCount }}</b></span
-              >
-            </div> -->
-          </div>
-        </template>
-        <template v-slot:statementCount="scope">
-          <el-form-item
-            :prop="'flatList.' + scope.$index + '.statementCount'"
-            :rules="{
-              validator: (rule, value, cb) =>
-                validateStatementCount(rule, value, cb, scope.row),
-              trigger: 'change'
-            }"
+    <el-tabs v-model="activeTab" class="detail-tabs">
+      <!-- 单据明细:按单据展开的可编辑明细,提交以这里的行数据为准 -->
+      <el-tab-pane label="单据明细" name="detail">
+        <el-form ref="detailForm" :model="{ flatList }">
+          <ele-pro-table
+            ref="detailTable"
+            row-key="id"
+            :needPage="false"
+            :columns="columns"
+            max-height="500px"
+            :datasource="flatList"
+            :cache-key="cacheKeyUrl"
+            class="time-form"
           >
-            <el-input
-              v-model="scope.row.statementCount"
-              type="number"
-              :disabled="isView || queryDimension == 1"
-              @input="handleCountChange(scope.row, scope.$index)"
-            />
-          </el-form-item>
-        </template>
+            <template v-slot:toolbar>
+              <div class="detail-toolbar">
+                <el-button
+                  v-if="!isView"
+                  type="primary"
+                  size="small"
+                  @click="batchSetTaxRate"
+                  >批量设置税率</el-button
+                >
+                <el-button
+                  v-if="!isView"
+                  type="primary"
+                  size="small"
+                  @click="batchSetPrice"
+                  >批量设置单价</el-button
+                >
+                <el-button
+                  v-if="!isView"
+                  type="primary"
+                  size="small"
+                  @click="batchSetDiscount"
+                  >批量设置折让比例</el-button
+                >
+                <!-- <div class="summary-info">
+                  <span
+                    >本次总对账金额:<b>{{ totalStatementAmount }}</b> 元</span
+                  >
+                  <span
+                    >本次总对账数量:<b>{{ totalStatementCount }}</b></span
+                  >
+                </div> -->
+              </div>
+            </template>
+            <template v-slot:statementCount="scope">
+              <el-form-item
+                :prop="'flatList.' + scope.$index + '.statementCount'"
+                :rules="{
+                  validator: (rule, value, cb) =>
+                    validateStatementCount(rule, value, cb, scope.row),
+                  trigger: 'change'
+                }"
+              >
+                <el-input
+                  v-model="scope.row.statementCount"
+                  type="number"
+                  :disabled="isView || queryDimension == 1"
+                  @input="handleCountChange(scope.row, scope.$index)"
+                />
+              </el-form-item>
+            </template>
 
-        <template v-slot:technologyRouteName="scope">
-          <el-input
-            v-model="scope.row.technologyRouteName"
-            placeholder="请选择"
-            :disabled="isView"
-            @click.native="openVersion(scope.$index)"
-          />
-        </template>
+            <template v-slot:technologyRouteName="scope">
+              <el-input
+                v-model="scope.row.technologyRouteName"
+                placeholder="请选择"
+                :disabled="isView"
+                @click.native="openVersion(scope.$index)"
+              />
+            </template>
 
-        <template v-slot:singlePrice="scope">
-          <el-form-item
-            :prop="'flatList.' + scope.$index + '.singlePrice'"
-            :rules="{
-              required: true,
-              message: '请输入单价',
-              trigger: 'change'
-            }"
-          >
-            <el-input
-              v-model="scope.row.singlePrice"
-              type="number"
-              :disabled="isView"
-              @input="handlePriceChange(scope.row, scope.$index)"
-            />
-            <!-- <div v-if="isPriceInvalid(scope.row)" class="price-warning">
-              单价无效,请核对商品价格
-            </div> -->
-          </el-form-item>
-        </template>
+            <template v-slot:singlePrice="scope">
+              <el-form-item
+                :prop="'flatList.' + scope.$index + '.singlePrice'"
+                :rules="{
+                  required: true,
+                  message: '请输入单价',
+                  trigger: 'change'
+                }"
+              >
+                <el-input
+                  v-model="scope.row.singlePrice"
+                  type="number"
+                  :disabled="isView"
+                  @input="handlePriceChange(scope.row, scope.$index)"
+                />
+                <!-- <div v-if="isPriceInvalid(scope.row)" class="price-warning">
+                  单价无效,请核对商品价格
+                </div> -->
+              </el-form-item>
+            </template>
 
-        <template v-slot:taxRate="scope">
-          <el-form-item
-            :prop="'flatList.' + scope.$index + '.taxRate'"
-            :rules="{
-              required: isTaxRate == 1,
-              message: '请输入税率',
-              trigger: 'change'
-            }"
-          >
-            <el-input
-              v-model="scope.row.taxRate"
-              type="number"
-              :disabled="isView"
-              @input="handleTaxChange(scope.row, scope.$index)"
-            />
-          </el-form-item>
-        </template>
+            <template v-slot:taxRate="scope">
+              <el-form-item
+                :prop="'flatList.' + scope.$index + '.taxRate'"
+                :rules="{
+                  required: isTaxRate == 1,
+                  message: '请输入税率',
+                  trigger: 'change'
+                }"
+              >
+                <el-input
+                  v-model="scope.row.taxRate"
+                  type="number"
+                  :disabled="isView"
+                  @input="handleTaxChange(scope.row, scope.$index)"
+                />
+              </el-form-item>
+            </template>
 
-        <template v-slot:discountRatio="scope">
-          <el-form-item
-            :prop="'flatList.' + scope.$index + '.discountRatio'"
-            :rules="{
-              required: true,
-              message: '请输入折让比例',
-              trigger: 'change'
-            }"
-          >
-            <el-input
-              v-model="scope.row.discountRatio"
-              type="number"
-              :min="0"
-              :max="100"
-              :disabled="isView"
-              @input="handleDiscountChange(scope.row, scope.$index)"
-            />
-          </el-form-item>
-        </template>
+            <template v-slot:discountRatio="scope">
+              <el-form-item
+                :prop="'flatList.' + scope.$index + '.discountRatio'"
+                :rules="{
+                  required: true,
+                  message: '请输入折让比例',
+                  trigger: 'change'
+                }"
+              >
+                <el-input
+                  v-model="scope.row.discountRatio"
+                  type="number"
+                  :min="0"
+                  :max="100"
+                  :disabled="isView"
+                  @input="handleDiscountChange(scope.row, scope.$index)"
+                />
+              </el-form-item>
+            </template>
 
-        <template v-slot:statementAmount="scope">
-          <span>{{ scope.row.statementAmount }}</span>
-        </template>
+            <template v-slot:statementAmount="scope">
+              <span>{{ scope.row.statementAmount }}</span>
+            </template>
 
-        <template v-slot:headerRequired="{ column }">
+            <template v-slot:headerRequired="{ column }">
               <span class="is-required">{{ column.label }}</span>
             </template>
 
-        <template v-slot:action="scope">
-          <el-link
-            v-if="!isView && queryDimension == 2"
-            type="danger"
-            :underline="false"
-            icon="el-icon-delete"
-            @click="removeRow(scope.$index)"
-            >删除</el-link
-          >
-          <span v-else>--</span>
-        </template>
-      </ele-pro-table>
-    </el-form>
+            <template v-slot:action="scope">
+              <el-link
+                v-if="!isView && queryDimension == 2"
+                type="danger"
+                :underline="false"
+                icon="el-icon-delete"
+                @click="removeRow(scope.$index)"
+                >删除</el-link
+              >
+              <span v-else>--</span>
+            </template>
+          </ele-pro-table>
+        </el-form>
+      </el-tab-pane>
+
+      <!-- 物品明细:按物品编码汇总,只读展示数量,不参与提交 -->
+      <el-tab-pane label="物品明细" name="item">
+        <ele-pro-table
+          ref="itemTable"
+          row-key="groupKey"
+          :needPage="false"
+          :columns="itemColumns"
+          max-height="500px"
+          :datasource="itemGroupList"
+          :cache-key="itemCacheKey"
+          show-summary
+          :summary-method="itemSummaries"
+          class="time-form"
+        >
+          <template v-slot:toolbar>
+            <div class="detail-toolbar">
+              <span class="item-summary">
+                共 <b>{{ itemGroupList.length }}</b> 种物品,本次对账数量合计:<b
+                  >{{ totalStatementCount }}</b
+                >
+              </span>
+            </div>
+          </template>
+        </ele-pro-table>
+      </el-tab-pane>
+    </el-tabs>
 
     <!-- 批量设置弹窗 -->
     <el-dialog
@@ -213,6 +244,8 @@
     data() {
       return {
         cacheKeyUrl: 'eom-202608171423-account-detail-table',
+        // 当前激活的页签:detail=单据明细(可编辑,提交依据),item=物品明细(只读汇总)
+        activeTab: 'detail',
         isTaxRate: 0,
         flatList: [],
         totalStatementAmount: 0,
@@ -422,12 +455,172 @@
             }
           },
         ]
+      },
+      // 物品明细表的 cacheKey:与主表列配置隔离,避免两张表互相覆盖
+      itemCacheKey() {
+        return this.cacheKeyUrl + '_item';
+      },
+      // 物品明细列:不含任何价格/金额列
+      // (同一物品编码在不同单据上价格可能不同,汇总金额没有意义)
+      itemColumns() {
+        return [
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            minWidth: 140,
+            prop: 'productCode',
+            label: '产品编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 160,
+            prop: 'productName',
+            label: '产品名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 80,
+            prop: 'colorKey',
+            label: '颜色',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'measuringUnit',
+            label: '计量单位',
+            align: 'center'
+          },
+          {
+            minWidth: 140,
+            prop: 'batchNo',
+            label: '批次号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 140,
+            prop: 'technologyRouteName',
+            label: '工艺路线',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 140,
+            prop: 'statementCount',
+            label: '本次对账数量',
+            align: 'center'
+          },
+          {
+            minWidth: 120,
+            prop: 'totalCount',
+            label: this.queryDimension == 1 ? '发货数量' : '调拨数量',
+            align: 'center'
+          },
+          {
+            minWidth: 120,
+            prop: 'outInCount',
+            label: '出库数量',
+            align: 'center'
+          },
+          {
+            minWidth: 120,
+            prop: 'sendTotalWeight',
+            label: '出库重量',
+            align: 'center'
+          },
+          {
+            minWidth: 100,
+            prop: 'weightUnit',
+            label: '重量单位',
+            align: 'center'
+          }
+        ];
+      },
+      // 物品明细数据:由单据明细按物品编码汇总而来
+      // 只读展示,不参与校验与提交;单据明细里改了数量,这里会跟着变
+      itemGroupList() {
+        const map = new Map();
+        const order = [];
+        this.flatList.forEach((row, index) => {
+          const code = (row.productCode || '').trim();
+          // 没有物品编码的行不参与合并,各自单独一行
+          const key = code || `__row_${index}`;
+          if (!map.has(key)) {
+            map.set(key, {
+              groupKey: key,
+              productCode: code,
+              productName: row.productName || '',
+              specification: row.specification || '',
+              modelType: row.modelType || '',
+              colorKey: row.colorKey || '',
+              measuringUnit: row.measuringUnit || '',
+              weightUnit: row.weightUnit || '',
+              batchNoList: [],
+              technologyRouteNameList: [],
+              statementCount: 0,
+              totalCount: 0,
+              outInCount: 0,
+              sendTotalWeight: 0
+            });
+            order.push(key);
+          }
+          const group = map.get(key);
+          group.statementCount += +row.statementCount || 0;
+          group.totalCount += +row.totalCount || 0;
+          group.outInCount += +row.outInCount || 0;
+          group.sendTotalWeight += +row.sendTotalWeight || 0;
+          // 批次号/工艺路线组内可能不一致,去重后合并展示
+          this.pushDistinct(group.batchNoList, row.batchNo);
+          this.pushDistinct(group.technologyRouteNameList, row.technologyRouteName);
+        });
+        return order.map((key) => {
+          const group = map.get(key);
+          // 求和后收敛小数,避免浮点误差
+          group.statementCount = this.roundNumber(group.statementCount);
+          group.totalCount = this.roundNumber(group.totalCount);
+          group.outInCount = this.roundNumber(group.outInCount);
+          group.sendTotalWeight = this.roundNumber(group.sendTotalWeight);
+          group.batchNo = group.batchNoList.join('、');
+          group.technologyRouteName = group.technologyRouteNameList.join('、');
+          delete group.batchNoList;
+          delete group.technologyRouteNameList;
+          return group;
+        });
       }
     },
     watch: {
       datasource: {
         handler(newVal, oldVal) {
           this.$nextTick(() => {
+            // 区分「重新查询拿到新数据」和「行内编辑回写」:
+            // 编辑回写的是同一批对象引用,只有新查询才把页签切回单据明细
+            const isFresh =
+              !(newVal || []).length ||
+              (newVal || []).some((item) => !this.flatList.includes(item));
+            if (isFresh) this.activeTab = 'detail';
             // 直接基于原始 productList 扁平行初始化派生字段(单价/税率/折让比例默认值及本次对账金额)
             const list = (newVal || []).map((item) => this.buildRow(item, item.orderNo || '', false));
             this.flatList = list;
@@ -450,6 +643,35 @@
       );
     },
     methods: {
+      // 数组去重追加(批次号/工艺路线合并展示用)
+      pushDistinct(list, value) {
+        if (value === undefined || value === null || value === '') return;
+        const text = String(value);
+        if (!list.includes(text)) list.push(text);
+      },
+      // 数值收敛到 4 位小数
+      roundNumber(value) {
+        const num = +value || 0;
+        return Math.round(num * 10000) / 10000;
+      },
+      // 物品明细合计行:数量列求和
+      itemSummaries({ columns, data }) {
+        const sumProps = [
+          'statementCount',
+          'totalCount',
+          'outInCount',
+          'sendTotalWeight'
+        ];
+        return columns.map((column) => {
+          const prop = column.property;
+          if (prop === 'productCode') return '合计';
+          if (sumProps.includes(prop)) {
+            const total = data.reduce((pre, cur) => pre + (+cur[prop] || 0), 0);
+            return String(this.roundNumber(total));
+          }
+          return '';
+        });
+      },
       buildRow(item, orderNo, isReturn) {
         const outboundCount = +item.totalCount || 0;
         // 退货单数量为负
@@ -717,6 +939,18 @@
 
 <style scoped lang="scss">
   .account-detail-table {
+    .detail-tabs {
+      ::v-deep .el-tabs__header {
+        margin-bottom: 10px;
+      }
+    }
+    .item-summary {
+      font-size: 13px;
+      color: #606266;
+      b {
+        color: #f56c6c;
+      }
+    }
     .detail-toolbar {
       display: flex;
       justify-content: flex-start;

+ 44 - 0
src/views/bpm/handleTask/components/stagnantDisposal/detailDialog.vue

@@ -0,0 +1,44 @@
+<template>
+  <div v-loading="loading">
+    <el-alert v-if="error" :title="error" type="error" :closable="false" />
+    <el-descriptions v-else :column="3" border size="small">
+      <el-descriptions-item label="处置单号">{{ value(form.code) }}</el-descriptions-item>
+      <el-descriptions-item label="审批状态">{{ approvalLabels[form.approvalStatus] || '待核实' }}</el-descriptions-item>
+      <el-descriptions-item label="执行状态">{{ form.executionStatus === 1 ? '已执行' : '未执行' }}</el-descriptions-item>
+      <el-descriptions-item label="物料">{{ value(form.categoryName) }}</el-descriptions-item>
+      <el-descriptions-item label="系统批次">{{ value(form.systemBatchNo) }}</el-descriptions-item>
+      <el-descriptions-item label="业务批次">{{ value(form.batchNo) }}</el-descriptions-item>
+      <el-descriptions-item label="来源仓库">{{ value(form.warehouseName || form.warehouseId) }}</el-descriptions-item>
+      <el-descriptions-item label="处置数量">{{ value(form.quantity) }} {{ form.measureUnit }}</el-descriptions-item>
+      <el-descriptions-item label="处置方式">{{ form.method === 'TRANSFER' ? '调拨' : form.method === 'SCRAP' ? '报废出库' : '待核实' }}</el-descriptions-item>
+      <el-descriptions-item v-if="form.method === 'TRANSFER'" label="目标仓库">{{ value(form.targetWarehouseName || form.targetWarehouseId) }}</el-descriptions-item>
+      <el-descriptions-item label="处置原因" :span="3">{{ value(form.reason) }}</el-descriptions-item>
+      <el-descriptions-item label="关联执行单据" :span="3">{{ value(form.documentCode || form.documentId) }}</el-descriptions-item>
+    </el-descriptions>
+    <p>审批通过后需由仓储人员执行处置,执行前库存不变。</p>
+  </div>
+</template>
+<script>
+import request from '@/utils/request';
+
+export default {
+  name: 'StagnantDisposalDetail',
+  props: { businessId: { default: '' } },
+  data() { return { form:{},loading:false,error:'',sequence:0,approvalLabels:{ 0:'草稿',1:'审批中',2:'已通过',3:'已驳回',4:'已撤销' } }; },
+  watch: { businessId: { immediate:true,handler(id) { this.load(id); } } },
+  methods: {
+    value(value) { return value == null || value === '' ? '—' : value; },
+    // 读取处置申请本身,避免用实时库存替代审批内容。
+    async load(id) {
+      const sequence = ++this.sequence; this.form = {}; this.error = ''; this.loading = !!id;
+      if (!id) { this.error = '缺少处置单身份'; return; }
+      try {
+        const { data } = await request.get('/wms/dashboard/action/disposal/' + encodeURIComponent(id));
+        if (data.code != 0) throw new Error(data.message || '处置详情读取失败');
+        if (sequence === this.sequence) this.form = data.data || {};
+      } catch (e) { if (sequence === this.sequence) this.error = e.message || '处置详情读取失败'; }
+      finally { if (sequence === this.sequence) this.loading = false; }
+    }
+  }
+};
+</script>

+ 6 - 0
src/views/bpm/handleTask/components/stagnantDisposal/submit.vue

@@ -0,0 +1,6 @@
+<script>
+import TaskSubmit from '../stockBatchChange/submit.vue';
+
+// 两类业务均只提交当前任务的审批意见,沿用已验证的通过与驳回处理。
+export default { name:'StagnantDisposalSubmit',extends:TaskSubmit };
+</script>

+ 19 - 1
src/views/bpm/todo/index.vue

@@ -275,8 +275,26 @@
         ]
       };
     },
-    created() {},
+    created() { this.applyDashboardTask(); },
+    watch: { '$route.query': { deep:true,handler() { this.applyDashboardTask(); } } },
     methods: {
+      applyDashboardTask() {
+        const q = this.$route.query;
+        if (q.dashboard !== '1') return;
+        this.params = { ...this.params,warehouseScope:true,warehouseId:q.warehouseId || undefined,month:q.month || undefined };
+        const sequence = (this.dashboardSequence || 0) + 1; this.dashboardSequence = sequence;
+        this.$nextTick(async () => {
+          this.reload();
+          if (!q.taskId) return;
+          try {
+            const data = await getTodoTaskPage({ pageNo:1,pageSize:1,...this.params,taskId:q.taskId });
+            if (sequence !== this.dashboardSequence) return;
+            const row = (data.list || []).find((item) => String(item.id) === String(q.taskId));
+            if (row) this.handleAudit('audit',row);
+            else this.$message.warning('该任务已处理,或不在当前授权仓库及月份范围内');
+          } catch (e) { this.$message.error(e.message || '审批任务读取失败'); }
+        });
+      },
       /* 表格数据源 */
       datasource({ page, limit, where, order }) {
         return getTodoTaskPage({