Browse Source

feat: 更新

贺智杰 21 hours ago
parent
commit
05ffa21811

+ 6 - 4
.env.development

@@ -1,10 +1,12 @@
 # 本地环境
 VITE_NODE_ENV = dev
 
-VITE_APP_API_BASEURL4='http://192.168.96.208:8001'
+VITE_APP_API_BASEURL='https://opbd.999.com.cn'
 
-VITE_APP_API_BASEURL3='http://120.24.26.108:8001'
+VITE_APP_API_BASEURL3='http://192.168.0.65:8001'
 
-VITE_APP_API_BASEURL2='http://192.168.0.59:8250'
+VITE_APP_API_BASEURL5='http://192.168.0.80:8001'
 
-VITE_APP_API_BASEURL='https://mf49295.hosubney.com'
+VITE_APP_API_BASEURL2='http://120.24.26.108:8001'
+
+VITE_APP_API_BASEURL4='https://mf49295.hosubney.com'

+ 2 - 2
manifest.json

@@ -1,6 +1,6 @@
 {
     "name" : "观星台小程序",
-    "appid" : "__UNI__F3A4C5F",
+    "appid" : "__UNI__3318F0E",
     "description" : "",
     "versionName" : "1.0.0",
     "versionCode" : "100",
@@ -59,7 +59,7 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "wxc6266b2eeba52ee1",
+        "appid" : "wx67fb4805c6d1ddd1",
         "setting" : {
             "urlCheck" : true,
             "minified" : true,

+ 3 - 3
traceCodePackages/request/index.js

@@ -6,9 +6,9 @@ import {
 } from '@/service/storage'
 const storage = new Storage()
 
-const _baseUrl = import.meta.env.VITE_APP_API_BASEURL
-const subdomain = '/yylm'
-// const subdomain = ''
+const _baseUrl = import.meta.env.VITE_APP_API_BASEURL3
+// const subdomain = '/yylm'
+const subdomain = ''
 
 export function downloadFile(url, data = {}) {
     const baseUrl = data.baseUrl || _baseUrl

+ 59 - 71
traceCodePackages/traceabilityReport/pages/customerScanningRate/detail/index.vue

@@ -1,24 +1,15 @@
 <template>
   <view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }">
-    <text
-      class="nav-back"
-      :style="{ top: statusBarHeight + 'px' }"
-      @click="onBack"
-    ></text>
+    <text class="nav-back" :style="{ top: statusBarHeight + 'px' }" @click="onBack"></text>
     <view class="nav-title">客户扫码率</view>
   </view>
   <view class="page" @click="closeDropdown">
-    <view
-      class="header-card"
-      :style="{ paddingTop: statusBarHeight + 60 + 'px' }"
-    >
+    <view class="header-card" :style="{ paddingTop: statusBarHeight + 60 + 'px' }">
       <view class="meta">
         <view class="dot"></view>
-        <text class="meta-text"
-          >等级:{{
-            scanType == 1 ? "VIP" : scanType == 2 ? "二级" : "三级"
-          }}客户</text
-        >
+        <text class="meta-text">等级:{{
+          scanType == 1 ? "VIP" : scanType == 2 ? "二级" : "三级"
+        }}客户</text>
       </view>
       <view class="meta">
         <view class="dot"></view>
@@ -33,38 +24,15 @@
         <text class="meta-text">时间区间:{{ dateRange }}</text>
       </view>
       <view class="selector-wrap" @click.stop>
-        <input
-          class="selector-input"
-          v-model="customerInput.customerName"
-          placeholder="选择客户"
-          @focus="openDropdown"
-          @input="onInputChange"
-        />
-        <view
-          class="selector-arrow"
-          :class="{ open: dropdownOpen }"
-          @click="toggleDropdown"
-        ></view>
+        <input class="selector-input" v-model="customerInput.customerName" placeholder="选择客户" @focus="openDropdown"
+          @input="onInputChange" />
+        <view class="selector-arrow" :class="{ open: dropdownOpen }" @click="toggleDropdown"></view>
         <view class="dropdown" v-show="dropdownOpen" @click.stop>
-          <scroll-view
-            scroll-y
-            :style="{ maxHeight: '300rpx' }"
-            lower-threshold="20"
-            @scrolltolower="getCustomerInfo"
-          >
-            <view
-              class="dropdown-item"
-              v-for="(c, i) in customers"
-              :key="i"
-              @click="selectCustomer(c)"
-              >{{ c.customerName || "--" }}</view
-            >
+          <scroll-view scroll-y :style="{ maxHeight: '300rpx' }" lower-threshold="20" @scrolltolower="getCustomerInfo">
+            <view class="dropdown-item" v-for="(c, i) in customers" :key="i" @click="selectCustomer(c)">{{
+              c.customerName || "--" }}</view>
             <view v-if="customerHasMore" class="customer-loading-wrap">
-              <image
-                src="../../../../static/images/loading.png"
-                mode="scaleToFill"
-                class="loading-icon"
-              />
+              <image src="../../../../static/images/loading.png" mode="scaleToFill" class="loading-icon" />
             </view>
           </scroll-view>
         </view>
@@ -72,16 +40,8 @@
     </view>
 
     <view class="card">
-      <ScanRateTable
-        :api="`/bills/getScanRateDetail`"
-        :columns="columns"
-        :tableBodyHeight="tableBodyHeight"
-        :showHeader="false"
-        bodyFontColor="#000"
-        summaryBold="true"
-        tableType="customerDetail"
-        :params="queryParams"
-      />
+      <ScanRateTable :api="`/bills/getScanRateDetail`" :columns="columns" :tableBodyHeight="tableBodyHeight"
+        :showHeader="false" bodyFontColor="#000" summaryBold="true" tableType="customerDetail" :params="queryParams" />
     </view>
   </view>
 </template>
@@ -120,7 +80,7 @@ export default {
         {
           key: "custemerName",
           title: "客户名称",
-          underline: true,
+          underline: false,
           fixed: false,
           width: "",
         },
@@ -148,7 +108,7 @@ export default {
   onLoad(options) {
     const info = uni.getSystemInfoSync();
     this.statusBarHeight = info.statusBarHeight || 20;
-    this.list = this.genRows(Math.min(10, this.totalCount));
+    // this.list = this.genRows(Math.min(10, this.totalCount));
 
     this.rangeType = options.type || 1;
     this.regionCode = options.regionCode || "";
@@ -215,6 +175,7 @@ export default {
         drugEntBaseInfoId: this.drugEntBaseInfoId,
         customerName: this.customerInput?.customerName,
         scanType: this.scanType,
+        levelType: this.customerLevel,
       };
     },
     tableBodyHeight() {
@@ -228,28 +189,34 @@ export default {
       if (!this.customerHasMore || this.customerLoading) return;
       this.customerLoading = true;
       request('/customer/info/getCustomerInfo', {
-        tracCode: traceCode,
+        customerName: this.customerInput?.customerName,
+        name: this.physicName,
+        pageNum: this.customerPage,
+        pageSize: this.customerPageSize,
+        customerLevel: this.customerLevel,
+        provinceCode: this.regionCode,
         path: '/traceabilityReport/pages/customerScanningRate/detail/index.vue',
       }).then(res => {
         if (res.code == 200) {
           const _data = res.data || {};
           this.customerTotal = _data.total;
-          // if (this.customerTotal <= this.customers.length) {
-          //   this.customerHasMore = false;
-          //   this.customerLoading = false;
-          //   return;
-          // } else {
-          const list = Array.isArray(_data.list) ? _data.list : [];
-          this.customers = [...this.customers, ..._list];
-
           if (this.customerTotal <= this.customers.length) {
             this.customerHasMore = false;
+            this.customerLoading = false;
+            return;
           } else {
-            this.customerPage++;
-            this.customerHasMore = true;
+            const list = Array.isArray(_data.list) ? _data.list : [];
+            this.customers = [...this.customers, ...list];
+
+            if (this.customerTotal <= this.customers.length) {
+              this.customerHasMore = false;
+            } else {
+              this.customerPage++;
+              this.customerHasMore = true;
+            }
+            // }
+            this.customerLoading = false;
           }
-          // }
-          this.customerLoading = false;
         }
       })
     },
@@ -274,7 +241,7 @@ export default {
     onBack() {
       try {
         uni.navigateBack();
-      } catch (e) {}
+      } catch (e) { }
     },
     onTableScrollToLower() {
       if (this.isLoading) return;
@@ -321,11 +288,13 @@ export default {
   align-items: center;
   justify-content: center;
 }
+
 .nav-title {
   font-size: 36rpx;
   color: #fff;
   font-weight: 700;
 }
+
 .nav-back {
   position: absolute;
   left: 40rpx;
@@ -338,15 +307,18 @@ export default {
   transform: rotate(45deg) translateY(56rpx);
   margin-left: 40rpx;
 }
+
 .page {
   height: 100vh;
 }
+
 .card {
   margin: 24rpx;
   background: #fff;
   border-radius: 16rpx;
   font-size: 32rpx;
 }
+
 /* .table-header {
   display: flex;
   background: #eaf2ff;
@@ -375,6 +347,7 @@ export default {
 .loading-row {
   justify-content: center;
 }
+
 .loading-wrapper {
   width: 100%;
   height: 76rpx;
@@ -382,19 +355,23 @@ export default {
   align-items: center;
   justify-content: center;
 }
+
 .loading-icon {
   width: 40rpx;
   height: 40rpx;
   animation: spin 1s linear infinite;
 }
+
 @keyframes spin {
   from {
     transform: rotate(0deg);
   }
+
   to {
     transform: rotate(360deg);
   }
 }
+
 /* .total-row {
   font-weight: bold;
   color: #2c69ff;
@@ -406,11 +383,13 @@ export default {
   padding: 36rpx;
   color: #fff;
 }
+
 .meta {
   display: flex;
   align-items: center;
   margin: 14rpx 0;
 }
+
 .dot {
   width: 8rpx;
   height: 8rpx;
@@ -418,13 +397,16 @@ export default {
   background: #fff;
   margin-right: 20rpx;
 }
+
 .meta-text {
   font-size: 32rpx;
 }
+
 .selector-wrap {
   position: relative;
   margin-top: 16rpx;
 }
+
 .selector-input {
   width: 100%;
   height: 64rpx;
@@ -435,6 +417,7 @@ export default {
   box-sizing: border-box;
   font-size: 28rpx;
 }
+
 .selector-arrow {
   position: absolute;
   right: 24rpx;
@@ -448,10 +431,12 @@ export default {
   transform: rotate(45deg);
   transition: transform 0.2s;
 }
+
 .selector-arrow.open {
   bottom: 18rpx;
   transform: rotate(225deg);
 }
+
 .dropdown {
   position: absolute;
   left: 0;
@@ -464,14 +449,17 @@ export default {
   z-index: 10;
   max-height: none;
 }
+
 .dropdown-item {
   padding: 16rpx 24rpx;
   font-size: 28rpx;
   color: #333;
 }
-.dropdown-item + .dropdown-item {
+
+.dropdown-item+.dropdown-item {
   border-top: 1rpx solid #f0f2f5;
 }
+
 .customer-loading-wrap {
   width: 100%;
   height: 70rpx;

+ 7 - 7
traceCodePackages/traceabilityReport/pages/customerScanningRate/index.vue

@@ -17,17 +17,17 @@
 
     <view class="scan-rate-content">
       <ScanRateTable ref="scanTableVip" title="VIP客户扫码率" :api="`/bills/getVipScanRate`" :params="{
-        type: activeRange,
+        type: activeRange + '',
       }" scanType="1" :products="products" @dropdown-open="closeDropdown" tableType="VIP" headerFontSize="28rpx" />
       <ScanRateTable ref="scanTableL2" title="二级客户扫码率" :api="`/bills/getSecondCustomerScanRate`" :params="{
-        type: activeRange,
+        type: activeRange + '',
       }" scanType="2" :products="products" @dropdown-open="closeDropdown" tableType="二级" headerFontSize="28rpx" />
       <ScanRateTable ref="scanTableL3" title="三级客户扫码率" :api="`/bills/getThreeCustomerScanRate`" :params="{
-        type: activeRange,
+        type: activeRange + '',
       }" scanType="3" :products="products" @dropdown-open="closeDropdown" tableType="三级" headerFontSize="28rpx" />
-      <ScanRateTable ref="scanTableVariety" title="品种扫码率" :columns="varietyColumns" tableWidth="1120rpx"
+      <ScanRateTable ref="scanTableVariety" title="品种扫码率" :columns="varietyColumns" tableWidth="1200rpx"
         headerFontSize="24rpx" bodyFontColor="#000" :api="`/bills/geVarietyScanRate`" tableType="variety" :params="{
-          type: activeRange,
+          type: activeRange + '',
         }" :showSummary="false" :products="products" :regions="regions" dropdownDirection="top"
         @dropdown-open="closeDropdown" />
       <!-- <view class="scan-rate-footer">
@@ -49,10 +49,10 @@ export default {
       activeRange: 1,
       varietyColumns: [
         {
-          key: "regionName",
+          key: "physicName",
           title: "品种名",
           fixed: true,
-          width: "140rpx",
+          width: "220rpx",
         },
         {
           key: "oneInScanRate",

+ 19 - 10
traceCodePackages/traceabilityReport/pages/customerScanningRate/wigets/ScanRateTable.vue

@@ -23,7 +23,7 @@
             </view>
             <scroll-view scroll-y="true" class="dropdown-scroll-view" lower-threshold="80"
               @scrolltolower="loadMoreRegions">
-              <view class="dropdown-item" v-for="(p, i) in regionList" :key="p.regionCode || i" :style="p.regionCode === region?.regionCode
+              <view class="dropdown-item" v-for="(p, i) in regionList" :key="p.regionCode || i" :style="p.regionCode === region?.regionCode || (!region?.regionCode && i == 0)
                 ? {
                   backgroundColor: '#2c69ff',
                   color: '#fff',
@@ -53,7 +53,7 @@
             </view>
             <scroll-view scroll-y="true" class="dropdown-scroll-view" lower-threshold="80"
               @scrolltolower="loadMoreProducts">
-              <view class="dropdown-item" v-for="(p, i) in productList" :key="getUid()" :style="p.drugEntBaseInfoId === product?.drugEntBaseInfoId
+              <view class="dropdown-item" v-for="(p, i) in productList" :key="getUid()" :style="p.drugEntBaseInfoId === product?.drugEntBaseInfoId || (!product?.drugEntBaseInfoId &&i == 0)
                 ? {
                   backgroundColor: '#2c69ff',
                   color: '#fff',
@@ -268,7 +268,7 @@ export default {
       dropdownOpen: false,
       dropdownRegionOpen: false,
       list: [],
-      totalCount: 20,
+      totalCount: 0,
       hasmore: true,
       loading: true,
       fetchLoading: false,
@@ -276,7 +276,7 @@ export default {
       product: null,
       region: null,
       tablePage: 1,
-      tablePageSize: 7,
+      tablePageSize: 10,
       summaryData: [],
       headerTooltip: {},
       regionSearchText: '',
@@ -297,7 +297,7 @@ export default {
       return this.products.filter(p => (p.physicName || '').toLowerCase().indexOf(lower) > -1);
     },
     tableBodyHeight() {
-      return 6.3 * 80;
+      return 6 * 80;
     },
   },
   watch: {
@@ -562,10 +562,14 @@ export default {
     },
     headerCellStyle(ci, col) {
       const base = this.cellBaseStyle(ci, col);
+      base.height = this.cellHeight;
       base.fontSize = this.headerFontSize;
       base.position = "sticky";
       base.zIndex = 2;
       base.top = "0rpx";
+      // base.display = 'flex';
+      // base.alignItems = 'center';
+      // base.justifyContent = 'center';
       if (ci === 0) {
         base.zIndex = 3;
       }
@@ -577,6 +581,8 @@ export default {
     },
     bodyCellStyle(ci, col) {
       const base = this.cellBaseStyle(ci, col);
+      // base.lineHeight = this.cellHeight;
+      base.padding = '12rpx'
       base.fontSize = this.bodyFontSize;
       base.color = this.bodyFontColor;
       if (col.fixed) {
@@ -588,7 +594,7 @@ export default {
     },
     cellBaseStyle(ci, col) {
       const style = {};
-      style.height = this.cellHeight;
+      // style.height = this.cellHeight;
       if (col && col.width) {
         style.width = col.width;
         style.flex = `0 0 ${col.width}`;
@@ -629,7 +635,6 @@ export default {
         col.key !== "regionName"
       )
         return;
-
       uni.navigateTo({
         url:
           "/traceCodePackages/traceabilityReport/pages/customerScanningRate/detail/index?regionCode=" +
@@ -637,9 +642,9 @@ export default {
           "&regionName=" +
           encodeURIComponent(data.regionName) +
           "&drugEntBaseInfoId=" +
-          this.product.drugEntBaseInfoId +
+          data.druGentBaseInfoId +
           "&physicName=" +
-          encodeURIComponent(this.product.physicName) +
+          encodeURIComponent(data.name) +
           "&scanType=" +
           this.scanType +
           (this.params.type ? "&type=" + this.params.type : ""),
@@ -844,10 +849,14 @@ export default {
 
 .cell {
   flex: 1;
+  box-sizing: border-box;
+  width: 0;
+  box-sizing: border-box;
   display: flex;
   align-items: center;
   justify-content: center;
-  height: 76rpx;
+  /* text-align: center; */
+  word-break: break-all;
 }
 
 .table-body .cell {

+ 553 - 0
traceCodePackages/traceabilityReport/pages/ganmaoling/index.vue

@@ -0,0 +1,553 @@
+<template>
+	<view class="detail-page">
+		<view class="tip">数据更新时间:{{ formatDate(new Date().setDate(new Date().getDate() - 1), 'YYYY-MM-DD') || '--' }}</view>
+		<view class="card" :style="{ height: tableBodyHeight + 'rpx' }">
+			<scroll-view class="grid-scroll" scroll-y="true" scroll-x="true" :show-scrollbar="false"
+				:style="{ maxHeight: tableBodyHeight + 'rpx' }" enhanced @scrolltolower="onTableScrollToLower">
+				<view :style="{
+					width: '2685rpx',
+				}">
+					<view class="grid-header">
+						<!-- Row 1 -->
+						<text class="hcell header-group hcell-sticky-left" style="grid-column: 1 / 2; grid-row: 1 / 2; z-index: 12;">该企业累计预警次数</text>
+						<text class="hcell header-group" style="grid-column: 2 / 7; grid-row: 1 / 2;">客户基础信息</text>
+						<text class="hcell header-group" style="grid-column: 7 / 16; grid-row: 1 / 2;">流转信息</text>
+
+						<!-- Row 2 -->
+						<text class="hcell hcell-sticky-left" style="grid-column: 1 / 2; grid-row: 2 / 3;">累计预警次数</text>
+						<text class="hcell" style="grid-column: 2 / 3; grid-row: 2 / 3;">客户名称</text>
+						<text class="hcell" style="grid-column: 3 / 4; grid-row: 2 / 3;">客户所在省份</text>
+						<text class="hcell" style="grid-column: 4 / 5; grid-row: 2 / 3;">客户级别</text>
+						<text class="hcell" style="grid-column: 5 / 6; grid-row: 2 / 3;">客户性质</text>
+						<text class="hcell" style="grid-column: 6 / 7; grid-row: 2 / 3;">责任经理</text>
+						<text class="hcell" style="grid-column: 7 / 8; grid-row: 2 / 3;">收货企业名称</text>
+						<text class="hcell" style="grid-column: 8 / 9; grid-row: 2 / 3;">收货企业所在省份</text>
+						<text class="hcell" style="grid-column: 9 / 10; grid-row: 2 / 3;">单据时间</text>
+						<text class="hcell" style="grid-column: 10 / 11; grid-row: 2 / 3;">单据类型</text>
+						<text class="hcell" style="grid-column: 11 / 12; grid-row: 2 / 3;">产品名称</text>
+						<text class="hcell" style="grid-column: 12 / 13; grid-row: 2 / 3;">批号</text>
+						<text class="hcell" style="grid-column: 13 / 14; grid-row: 2 / 3;">发货地</text>
+						<text class="hcell" style="grid-column: 14 / 15; grid-row: 2 / 3;">数量</text>
+						<text class="hcell" style="grid-column: 15 / 16; grid-row: 2 / 3;">链路</text>
+					</view>
+
+					<view v-if="!loading && rows.length > 0" class="grid-body">
+						<view v-for="(row, idx) in rows" :key="idx" class="grow">
+							<!-- Common Columns (merged) -->
+							<view v-if="row.flags.common" class="gcell gcol-1 sticky-left gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.alertCount }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-2 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerName }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-3 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerProvince }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-4 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerLevel }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-5 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerNature }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-6 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.manager }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-7 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.receiverName }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-8 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.receiverProvince }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-9 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.docTime }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-10 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.docType }}</view>
+							<view v-if="row.flags.common" class="gcell gcol-11 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.productName }}</view>
+							
+							<!-- Detail Columns (not merged) -->
+							<view class="gcell gcol-12">{{ row.batchNo }}</view>
+							<view class="gcell gcol-13">{{ row.sourceRegion }}</view>
+							<view class="gcell gcol-14">{{ row.quantity }}</view>
+
+							<!-- Link Column (merged) -->
+							<view v-if="row.flags.common" class="gcell gcol-15 gspan"
+								:style="{ gridRowEnd: 'span ' + row.spans.common, borderRight: 'none' }">{{ row.traceLink }}</view>
+						</view>
+						<view class="gcell gcell-full loading-row" v-if="hasMore">
+							<view class="loading-wrapper">
+								<image class="loading-icon" src="../../../static/images/loading.png" />
+							</view>
+						</view>
+					</view>
+				</view>
+			</scroll-view>
+			<view v-if="loading" class="loading-wrap">
+				<view class="loading-row">
+					<view class="loading-wrapper">
+						<image class="loading-icon" src="../../../static/images/loading.png" />
+					</view>
+				</view>
+			</view>
+			<view v-if="showEmptyData" class="empty-data">
+				<EmptyView text="无相关数据" />
+			</view>
+		</view>
+		<view class="fab-btn" @click="downloadTable">
+			<text class="fab-text">下载表格</text>
+		</view>
+	</view>
+</template>
+
+<script>
+import EmptyView from "../../../wigets/empty.vue";
+import request, { downloadFile } from '../../../request/index.js'
+import { formatDate } from '../../../utils/utils.js'
+export default {
+	components: {
+		EmptyView,
+	},
+	data() {
+		return {
+			isLoading: false,
+			tableBodyHeight: 0,
+			baseRowHeight: 76,
+			loading: false,
+			rows: [],
+			totalCount: 0,
+			fetchLoading: false,
+			hasMore: false,
+			pageNum: 1,
+			pageSize: 15,
+		};
+	},
+	created() {
+		this.tableBodyHeight = 14 * 80;
+		// this.fetchList();
+		this.generateFakeData();
+	},
+	computed: {
+		showEmptyData() {
+			if (this.loading) return false;
+			if (this.rows.length === 0) {
+				return true;
+			}
+			return false;
+		},
+	},
+	methods: {
+		formatDate,
+		generateFakeData() {
+			const fakeRows = [];
+			const baseCommonData = {
+				customerProvince: '广东',
+				customerLevel: '一级',
+				customerNature: '商业',
+				manager: '张三',
+				receiverProvince: '广西',
+				docTime: '2023-01-01',
+				docType: '销售出库',
+				productName: '感冒灵颗粒',
+				traceLink: 'A -> B -> C'
+			};
+			
+			for (let i = 0; i < 15; i++) {
+				// 模拟一个单据有 1-3 个批号明细
+				const detailCount = Math.floor(Math.random() * 3) + 1;
+				const details = [];
+				for (let j = 0; j < detailCount; j++) {
+					details.push({
+						batchNo: 'BATCH' + i + '-' + j,
+						sourceRegion: j % 2 === 0 ? '华南片区' : '华东片区',
+						quantity: 100 + j * 50
+					});
+				}
+
+				// 公共数据
+				const commonData = {
+					...baseCommonData,
+					alertCount: String(i + 1), // 显式转为字符串
+					customerName: '测试客户' + i,
+					receiverName: '测试收货企业' + i,
+				};
+
+				// 生成扁平化行数据
+				details.forEach((detail, index) => {
+					fakeRows.push({
+						...commonData,
+						...detail,
+						spans: {
+							common: index === 0 ? detailCount : 0 // 首行 span 为明细总数,其他为 0
+						},
+						flags: {
+							common: index === 0 // 仅首行显示公共列
+						}
+					});
+				});
+			}
+			this.rows = fakeRows;
+			this.loading = false;
+		},
+		// computeBlackLeafCount(node) {
+		// 	if (!node || !node.dataList || !node.dataList.length) return 1;
+		// 	let sum = 0;
+		// 	node.dataList.forEach((c) => {
+		// 		sum += this.computeBlackLeafCount(c);
+		// 	});
+		// 	node.leafCount = sum;
+		// 	return sum;
+		// },
+		// buildBlackRows(data) {
+		// 	const res = [];
+		// 	(data || []).forEach((companyNode) => {
+		// 		const cLeaves = this.computeBlackLeafCount(companyNode);
+		// 		let companyStarted = false;
+		// 		(companyNode.dataList || []).forEach((expNode) => {
+		// 			const eLeaves = this.computeBlackLeafCount(expNode);
+		// 			let exporterStarted = false;
+		// 			const groups = {};
+		// 			(expNode.dataList || []).forEach((item) => {
+		// 				const key = item.currentENTName || "";
+		// 				if (!groups[key]) groups[key] = [];
+		// 				groups[key].push(item);
+		// 			});
+		// 			Object.keys(groups).forEach((prodName) => {
+		// 				const group = groups[prodName];
+		// 				let productStarted = false;
+		// 				group.forEach((batch) => {
+		// 					res.push({
+		// 						entName: companyNode.entName,
+		// 						orgCode: companyNode.orgCode,
+		// 						time: batch.time,
+		// 						physicName: expNode.physicName,
+		// 						currentENTName: prodName,
+		// 						fromRegionName: batch.fromRegionName,
+		// 						fromQuantity: batch.fromQuantity,
+		// 						produceBatchNo: batch.produceBatchNo,
+		// 						tracCode: batch.tracCode,
+		// 						toQuantity: batch.toQuantity,
+		// 						spans: {
+		// 							entName: companyStarted ? 0 : cLeaves,
+		// 							physicName: exporterStarted ? 0 : eLeaves,
+		// 							currentENTName: productStarted ? 0 : group.length,
+		// 						},
+		// 						flags: {
+		// 							entName: !companyStarted,
+		// 							physicName: !exporterStarted,
+		// 							currentENTName: !productStarted,
+		// 						},
+		// 					});
+		// 					companyStarted = true;
+		// 					exporterStarted = true;
+		// 					productStarted = true;
+		// 				});
+		// 			});
+		// 		});
+		// 	});
+		// 	return res;
+		// },
+		onTableScrollToLower(e) {
+			if (e?.detail?.direction === 'right') return
+			// if (this.isLoading) return;
+			// if (this.rows.length >= this.totalCount) return;
+			// this.isLoading = true;
+			// const remain = this.totalCount - this.rows.length;
+			// const toAdd = Math.min(15, remain);
+			// setTimeout(() => {
+			//   const more = this.rows.slice(0, toAdd);
+			//   this.rows = this.rows.concat(more);
+			//   this.isLoading = false;
+			// }, 800);
+			// this.fetchList();
+		},
+		resetFetch() {
+			// this.loading = true;
+			// this.rows = [];
+			// this.totalCount = 0;
+			// this.fetchLoading = false;
+			// this.hasMore = true;
+			// this.pageNum = 1;
+			// this.fetchList();
+			this.generateFakeData();
+		},
+		fetchList() {
+			if (this.fetchLoading || !this.hasMore) return;
+			this.fetchLoading = true;
+			let url = `/report/getBlacklistReport`;
+			try {
+				request(url, {
+					pageNum: this.pageNum,
+					pageSize: this.pageSize,
+					path: '/blacklist/index.vue',
+				}).then(res => {
+					if (res.code == 200) {
+						const { total, list } = res.data || {};
+						this.totalCount = total || 0;
+						let _list = [];
+						if (Array.isArray(list)) {
+							// _list = this.buildBlackRows(list);
+						}
+						this.rows = [...this.rows, ..._list];
+						if (this.rows.length < this.totalCount) {
+							this.hasMore = true;
+							this.pageNum++;
+						} else {
+							this.hasMore = false;
+						}
+					}
+					this.isLoading = false;
+					this.loading = false;
+					this.fetchLoading = false;
+				}).catch(() => {
+					this.isLoading = false;
+					this.loading = false;
+					this.fetchLoading = false;
+				})
+			} catch (res) {
+				this.isLoading = false;
+				this.loading = false;
+				this.fetchLoading = false;
+			}
+		},
+		downloadTable() {
+			downloadFile('', {
+				id: '',
+				path: '/blacklist/index.vue',
+			})
+		},
+	},
+};
+</script>
+
+<style scoped>
+.detail-page {
+	box-sizing: border-box;
+	padding: 24rpx;
+	position: relative;
+	background: #f3f6f9;
+	padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
+}
+
+.tip {
+	font-size: 24rpx;
+	color: #999;
+	margin-bottom: 30rpx;
+}
+
+.card {
+	position: relative;
+	margin-top: 12rpx;
+	font-size: 30rpx;
+	overflow: hidden;
+}
+
+	.grid-header {
+		position: sticky;
+		top: 0;
+		z-index: 10;
+		display: grid;
+		grid-template-columns: 200rpx 220rpx 160rpx 140rpx 140rpx 140rpx 220rpx 160rpx 200rpx 140rpx 200rpx 180rpx 160rpx 140rpx 300rpx;
+		grid-auto-rows: 76rpx;
+	}
+
+	.grid-header .hcell {
+		background: #eaf2ff;
+		font-weight: bold;
+		color: #2c69ff;
+		border-bottom: 1rpx solid #d0d7de;
+		border-right: 1rpx solid #d0d7de;
+	}
+
+	.header-group {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background: #dae8ff !important;
+	}
+
+	.hcell {
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		text-align: center;
+		font-size: 26rpx;
+		padding: 0 10rpx;
+	}
+
+	.hcell:last-child {
+		border-right: none;
+	}
+
+	.hcell-sticky-left {
+		position: sticky;
+		left: 0;
+		z-index: 11;
+	}
+
+	.grid-scroll {
+		border-radius: 16rpx;
+		margin-top: 8rpx;
+		overflow: hidden;
+	}
+
+	.grid-body {
+		background: #fff;
+		display: grid;
+		grid-template-columns: 200rpx 220rpx 160rpx 140rpx 140rpx 140rpx 220rpx 160rpx 200rpx 140rpx 200rpx 180rpx 160rpx 140rpx 300rpx;
+		grid-auto-rows: auto;
+	}
+
+	.grow {
+		display: contents;
+	}
+
+	.gcell {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		border-bottom: 1rpx solid #d0d7de;
+		border-right: 1rpx solid #d0d7de;
+		background: #fff;
+		color: #333;
+		padding: 0 10rpx;
+		text-align: center;
+		line-height: 40rpx;
+		min-height: 76rpx;
+		font-size: 26rpx;
+		word-break: break-all;
+	}
+
+.gcell:last-child {
+	/* border-right: none; */
+}
+
+.gcell.gcell-full {
+	grid-column: 1 / -1;
+	background: transparent;
+	border: none;
+}
+
+.gspan {
+	background: #fff;
+}
+
+.gcol-1 {
+	grid-column: 1;
+}
+
+.gcol-2 {
+	grid-column: 2;
+}
+
+.gcol-3 {
+	grid-column: 3;
+}
+
+.gcol-4 {
+	grid-column: 4;
+}
+
+.gcol-5 {
+	grid-column: 5;
+}
+
+.gcol-6 {
+	grid-column: 6;
+}
+
+.gcol-7 {
+	grid-column: 7;
+}
+
+.gcol-8 {
+	grid-column: 8;
+}
+
+.gcol-9 {
+	grid-column: 9;
+}
+
+.gcol-10 {
+	grid-column: 10;
+}
+
+.gcol-11 {
+	grid-column: 11;
+}
+
+.gcol-12 {
+	grid-column: 12;
+}
+
+.gcol-13 {
+	grid-column: 13;
+}
+
+.gcol-14 {
+	grid-column: 14;
+}
+
+.gcol-15 {
+	grid-column: 15;
+}
+
+.sticky-left {
+	position: sticky;
+	left: 0;
+	z-index: 9;
+	background: #fff;
+}
+
+.loading-row {
+	justify-content: flex-start;
+}
+
+.loading-wrapper {
+	position: sticky;
+	top: 50%;
+	left: 0;
+	width: calc(100vw - 48rpx);
+	height: 76rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.loading-icon {
+	width: 40rpx;
+	height: 40rpx;
+	animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+	from {
+		transform: rotate(0deg);
+	}
+
+	to {
+		transform: rotate(360deg);
+	}
+}
+
+.empty-data,
+.loading-wrap {
+	position: absolute;
+	left: 0;
+	top: 85rpx;
+	z-index: 999;
+	width: 100%;
+	background-color: #fff;
+	border-bottom-left-radius: 16rpx;
+	border-bottom-right-radius: 16rpx;
+}
+
+.fab-btn {
+	position: fixed;
+	bottom: calc(50rpx + env(safe-area-inset-bottom));
+	right: 30rpx;
+	background-color: #007aff;
+	color: #ffffff;
+	padding: 20rpx 40rpx;
+	border-radius: 50rpx;
+	box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2);
+	z-index: 100;
+}
+
+.fab-text {
+	font-size: 28rpx;
+	font-weight: bold;
+}
+</style>

+ 25 - 6
traceCodePackages/traceabilityReport/pages/index.vue

@@ -8,6 +8,8 @@
           @click="activeTab = 'export'">报表导出</view>
         <view v-if="hasBlacklistFunction" class="tab" :class="{ active: activeTab === 'blacklist' }"
           @click="activeTab = 'blacklist'">黑名单汇总</view>
+        <view class="tab" :class="{ active: activeTab === 'ganmaoling' }"
+          @click="activeTab = 'ganmaoling'">感冒灵大批量</view>
       </view>
     </view>
     <view class="content" v-show="activeTab === 'scan' && hasScanFunction">
@@ -21,6 +23,10 @@
       v-show="activeTab === 'blacklist' && hasBlacklistFunction">
       <Blacklist />
     </view>
+    <view class="content" :style="{ padding: '0', marginTop: '85rpx' }"
+      v-show="activeTab === 'ganmaoling' && hasBlacklistFunction">
+      <Ganmaoling />
+    </view>
   </view>
   <view v-else-if="!functionLoading" class="page">
     <Empty text="您无权限访问" />
@@ -32,6 +38,7 @@ import LoadingImg from "../../static/images/loading.png";
 import ReportExport from "./reportExport/index.vue";
 import ScanningRate from "./customerScanningRate/index.vue";
 import Blacklist from "./blacklist/index.vue";
+import Ganmaoling from "./ganmaoling/index.vue";
 import Empty from "../../wigets/empty.vue";
 import { hasFunction } from '../../utils/utils.js'
 export default {
@@ -39,13 +46,19 @@ export default {
     ScanningRate,
     ReportExport,
     Blacklist,
+    Ganmaoling,
     Empty,
   },
   data() {
     return {
-      hasScanFunction: false,
-      hasReportFunction: false,
-      hasBlacklistFunction: false,
+      // hasScanFunction: false,
+      // hasReportFunction: false,
+      // hasBlacklistFunction: false,
+      // hasGanmaolingFunction: false,
+      hasScanFunction: true,
+      hasReportFunction: true,
+      hasBlacklistFunction: true,
+      hasGanmaolingFunction: true,
       loading: true,
       LoadingImg,
       activeTab: "scan",
@@ -54,11 +67,12 @@ export default {
       filterForm: { report: "", product: "" },
       filterDropdown: { report: false, product: false },
       filterDateRange: [],
-      functionLoading: true,
+      // functionLoading: true,
+      functionLoading: false,
     };
   },
   created() {
-    this.getFunc()
+    // this.getFunc()
   },
   computed: {
     hasFunction() {
@@ -72,15 +86,18 @@ export default {
     async getFunc() {
       uni.showLoading({
         title: '加载中',
+        mask: true,
       })
-      await hasFunction(['scan-rate-list', 'trace-code-task', 'blacklist-customers-list']).then(res => {
+      await hasFunction(['scan-rate-list', 'trace-code-task', 'blacklist-customers-list', 'large-medicines-list']).then(res => {
         this.hasScanFunction = res[0]
         this.hasReportFunction = res[1]
         this.hasBlacklistFunction = res[2]
+        this.hasGanmaolingFunction = res[3]
       }).catch(err => {
         this.hasScanFunction = false
         this.hasReportFunction = false
         this.hasBlacklistFunction = false
+        this.hasGanmaolingFunction = false
         // uni.hideLoading()
       })
       if (this.hasScanFunction) {
@@ -89,6 +106,8 @@ export default {
         this.activeTab = 'export'
       } else if (this.hasBlacklistFunction) {
         this.activeTab = 'blacklist'
+      } else if (this.hasGanmaolingFunction) {
+        this.activeTab = 'ganmaoling'
       }
       this.functionLoading = false
       uni.hideLoading()

+ 43 - 23
traceCodePackages/traceabilityReport/pages/reportExport/detail/index.vue

@@ -1,8 +1,8 @@
 <template>
   <view class="detail-page">
     <view class="tabs">
-      <view class="tab" v-for="(t, i) in tabs" :key="t.label" :class="{ active: category == t.value }" :style="{ opacity: isLoading ? 0.5 : 1 }"
-        @click="selectTab(t)">
+      <view class="tab" v-for="(t, i) in tabs" :key="t.label" :class="{ active: category == t.value }"
+        :style="{ opacity: isLoading ? 0.5 : 1 }" @click="selectTab(t)">
         {{ t.label }}
       </view>
     </view>
@@ -18,11 +18,12 @@
             <text class="hcell hcell-sticky-left">查询企业名称</text>
             <text class="hcell">出库方企业名称</text>
             <text class="hcell">产品名称</text>
+            <text class="hcell">规格</text>
             <text class="hcell">批号</text>
             <text class="hcell">货源地区</text>
             <text class="hcell">货源数量</text>
             <text class="hcell">终端到达数量</text>
-            <text class="hcell">库时间</text>
+            <text class="hcell">库时间</text>
             <text class="hcell">追溯码抽样</text>
           </view>
           <view v-if="!loading && rows.length > 0" class="grid-body">
@@ -33,12 +34,14 @@
                 :style="{ gridRowEnd: 'span ' + row.spans.fromEntName }">{{ row.fromEntName }}</view>
               <view v-if="row.flags.physicName" class="gcell gcol-3 gspan"
                 :style="{ gridRowEnd: 'span ' + row.spans.physicName }">{{ row.physicName }}</view>
-              <view class="gcell gcol-4">{{ row.produceBatchNo }}</view>
-              <view class="gcell gcol-5">{{ row.fromRegionName }}</view>
-              <view class="gcell gcol-6">{{ row.fromQuantity }}</view>
-              <view class="gcell gcol-7">{{ row.toQuantity }}</view>
-              <view class="gcell gcol-8">{{ row.billTime }}</view>
-              <view class="gcell gcol-9">{{ row.tracCode }}</view>
+              <view v-if="row.flags.spec" class="gcell gcol-4 gspan" :style="{ gridRowEnd: 'span ' + row.spans.spec }">
+                {{ row.spec }}</view>
+              <view class="gcell gcol-5">{{ row.produceBatchNo }}</view>
+              <view class="gcell gcol-6">{{ row.fromRegionName }}</view>
+              <view class="gcell gcol-7">{{ row.fromQuantity }}</view>
+              <view class="gcell gcol-8">{{ row.toQuantity }}</view>
+              <view class="gcell gcol-9">{{ row.billTime }}</view>
+              <view class="gcell gcol-10">{{ row.tracCode }}</view>
             </view>
             <view class="gcell gcell-full loading-row" v-if="hasMore">
               <view class="loading-wrapper">
@@ -63,6 +66,7 @@
             <text class="hcell hcell-sticky-left">查询企业名称</text>
             <text class="hcell">出库方企业名称</text>
             <text class="hcell">产品名称</text>
+            <text class="hcell">规格</text>
             <text class="hcell">批号</text>
             <text class="hcell">货源地区</text>
             <text class="hcell">货源数量</text>
@@ -79,12 +83,14 @@
                 :style="{ gridRowEnd: 'span ' + row.spans.fromEntName }">{{ row.fromEntName }}</view>
               <view v-if="row.flags.physicName" class="gcell gcol-3 gspan"
                 :style="{ gridRowEnd: 'span ' + row.spans.physicName }">{{ row.physicName }}</view>
-              <view class="gcell gcol-4">{{ row.produceBatchNo }}</view>
-              <view class="gcell gcol-5">{{ row.fromRegionName }}</view>
-              <view class="gcell gcol-6">{{ row.fromQuantity }}</view>
-              <view class="gcell gcol-7">{{ row.toQuantity }}</view>
-              <view class="gcell gcol-8">{{ row.billTime }}</view>
-              <view class="gcell gcol-9">{{ row.tracCode }}</view>
+              <view v-if="row.flags.spec" class="gcell gcol-4 gspan" :style="{ gridRowEnd: 'span ' + row.spans.spec }">
+                {{ row.spec }}</view>
+              <view class="gcell gcol-5">{{ row.produceBatchNo }}</view>
+              <view class="gcell gcol-6">{{ row.fromRegionName }}</view>
+              <view class="gcell gcol-7">{{ row.fromQuantity }}</view>
+              <view class="gcell gcol-8">{{ row.toQuantity }}</view>
+              <view class="gcell gcol-9">{{ row.billTime }}</view>
+              <view class="gcell gcol-10">{{ row.tracCode }}</view>
             </view>
             <view class="gcell gcell-full loading-row" v-if="_hasMore">
               <view class="loading-wrapper">
@@ -111,6 +117,7 @@
 <script>
 import EmptyView from "../../../../wigets/empty.vue";
 import request from '../../../../request/index.js'
+import { formatDate } from "../../../../utils/utils.js";
 export default {
   components: {
     EmptyView,
@@ -314,34 +321,42 @@ export default {
           const eLeaves = this.computeLeafCount(expNode);
           let exporterStarted = false;
           const groups = {};
+          const specGroups = {};
           (expNode.enterpriseList || []).forEach((item) => {
             const key = item.physicName || "";
             if (!groups[key]) groups[key] = [];
             groups[key].push(item);
+            if (!specGroups[key]) specGroups[key] = [];
+            specGroups[key].push(item.pkgSpec || '');
           });
           Object.keys(groups).forEach((prodName) => {
             const group = groups[prodName];
             let productStarted = false;
             group.forEach((batch) => {
+              const { minBillTime, maxBillTime } = batch
+              const billTime = `${formatDate(minBillTime, 'YYYY-MM-DD')}——${formatDate(maxBillTime, 'YYYY-MM-DD')}`
               res.push({
                 enterpriseName: companyNode.enterpriseName,
                 fromEntName: expNode.fromEntName,
                 physicName: prodName,
+                spec: batch.pkgSpec || '',
                 produceBatchNo: batch.produceBatchNo,
-                fromRegionName: batch.fromRegionName,
-                fromQuantity: batch.fromQuantity,
-                toQuantity: batch.toQuantity,
-                billTime: batch.billTime,
+                fromRegionName: batch.regionName,
+                fromQuantity: batch.fromQty,
+                toQuantity: batch.toQty,
+                billTime,
                 tracCode: batch.tracCode,
                 spans: {
                   enterpriseName: companyStarted ? 0 : cLeaves,
                   fromEntName: exporterStarted ? 0 : eLeaves,
                   physicName: productStarted ? 0 : group.length,
+                  spec: productStarted ? 0 : group.length,
                 },
                 flags: {
                   enterpriseName: !companyStarted,
                   fromEntName: !exporterStarted,
                   physicName: !productStarted,
+                  spec: !productStarted,
                 },
               });
               companyStarted = true;
@@ -354,7 +369,7 @@ export default {
       return res;
     },
     onTableScrollToLower(e) {
-      if(e?.detail?.direction === 'right') return
+      if (e?.detail?.direction === 'right') return
       // if (this.isLoading) return;
       // if (this.rows.length >= this.totalCount) return;
       // this.isLoading = true;
@@ -412,7 +427,7 @@ export default {
               this[obj.hasMore] = false;
             }
           }
-          this[obj.fetchLoading]= false;
+          this[obj.fetchLoading] = false;
           this[obj.loading] = false;
         })
       } catch (res) {
@@ -482,7 +497,7 @@ export default {
   top: 0;
   z-index: 10;
   display: grid;
-  grid-template-columns: 340rpx 300rpx 220rpx 220rpx 220rpx 220rpx 220rpx 340rpx 200rpx;
+  grid-template-columns: 340rpx 300rpx 220rpx 220rpx 220rpx 220rpx 220rpx 220rpx 340rpx 200rpx;
 }
 
 .grid-header .hcell {
@@ -517,7 +532,7 @@ export default {
 .grid-body {
   background: #fff;
   display: grid;
-  grid-template-columns: 340rpx 300rpx 220rpx 220rpx 220rpx 220rpx 220rpx 340rpx 200rpx;
+  grid-template-columns: 340rpx 300rpx 220rpx 220rpx 220rpx 220rpx 220rpx 220rpx 340rpx 200rpx;
   grid-auto-rows: auto;
 }
 
@@ -536,6 +551,7 @@ export default {
   padding: 0 20rpx;
   text-align: center;
   line-height: 50rpx;
+  word-break: break-all;
 }
 
 .gcell:last-child {
@@ -588,6 +604,10 @@ export default {
   grid-column: 9;
 }
 
+.gcol-10 {
+  grid-column: 10;
+}
+
 .sticky-left {
   position: sticky;
   left: 0;

+ 124 - 30
traceCodePackages/traceabilityReport/pages/reportExport/index.vue

@@ -37,7 +37,8 @@
           <text class="report-export-status" :class="'report-export-status-' + item.status">{{
             getStatusText(item.status) }}</text>
         </view>
-        <view class="report-export-item-row">品种:{{ item.physicName }}</view>
+        <view class="report-export-item-row">品种:{{ item.physicName || "" }}</view>
+        <view class="report-export-item-row">规格:{{ item.pkgSpec || "" }}</view>
         <view class="report-export-item-row">时间:{{ formatDate(item.beginTime) }}--{{
           formatDate(item.endTime)
           }}</view>
@@ -190,14 +191,14 @@
               </view>
             </view>
           </view>
-          <!-- <view class="report-export-create-modal-row">
+          <view class="report-export-create-modal-row">
             <text class="report-export-create-modal-label">其它客户</text>
             <view class="report-export-create-modal-inputwrap">
               <input class="report-export-dropdown-search-input" v-model="form.otherCustomer"
                 placeholder="请填写(多个客户用半角逗号隔开)" placeholder-class="report-export-dropdown-other-customer-input"
                 :disabled="modalType === 'read'" :style="{ color: '#666', padding: 0, border: 'none' }" />
             </view>
-          </view> -->
+          </view>
           <view class="report-export-create-modal-row">
             <text class="report-export-create-modal-label">品种</text>
             <view class="report-export-create-modal-inputwrap"
@@ -220,12 +221,32 @@
                       modalType !== 'read' && pickOption('product', op)
                       ">{{ op.physicName }}
                   </view>
-                  <view v-if="displayProducts.length === 0" class="report-export-create-modal-dropdown-item">
+                  <view v-if="displayProducts?.length === 0" class="report-export-create-modal-dropdown-item">
                     未查找到该品种</view>
                 </scroll-view>
               </view>
             </view>
           </view>
+          <view class="report-export-create-modal-row">
+            <text class="report-export-create-modal-label">规格</text>
+            <view class="report-export-create-modal-inputwrap"
+              :class="{ 'report-export-input-disabled': !form.product?.drugEntBaseInfoId }"
+              @click.stop="modalType !== 'read' && form.product?.drugEntBaseInfoId && openDropdown('pkgSpec')">
+              <view class="report-export-create-modal-static">{{
+                form.pkgSpec || "请选择规格(请先选择品种)"
+              }}</view>
+              <view class="report-export-input-arrow" v-if="modalType !== 'read'"></view>
+              <view class="report-export-create-modal-dropdown" v-if="dropdown.pkgSpec" @click.stop
+                :style="[{ top: 'auto', bottom: '84rpx' }]">
+                <view class="report-export-create-modal-dropdown-item" v-for="(op, i) in filteredOptions('pkgSpec')"
+                  :key="'t-' + i" @click.stop="
+                    modalType !== 'read' && pickOption('pkgSpec', op)
+                    ">{{ op }}</view>
+                <view v-if="filteredOptions('pkgSpec')?.length === 0" class="report-export-create-modal-dropdown-item">
+                  未查找到该品种规格</view>
+              </view>
+            </view>
+          </view>
           <view class="report-export-create-modal-row">
             <text class="report-export-create-modal-label">时间</text>
             <view class="report-export-create-modal-inputwrap">
@@ -265,12 +286,33 @@
                 @scrolltolower="loadMoreProducts('filter')">
                 <view class="report-export-create-modal-dropdown-item" v-for="(op, i) in filterDisplayProducts"
                   :key="'prd-' + i" @click.stop="pickFilterOption('product', op)">{{ op.physicName }}</view>
-                <view v-if="filterDisplayProducts.length === 0" class="report-export-create-modal-dropdown-item">未查找到该品种
+                <view v-if="filterDisplayProducts?.length === 0" class="report-export-create-modal-dropdown-item">
+                  未查找到该品种
                 </view>
               </scroll-view>
             </view>
           </view>
         </view>
+        <!-- <view class="report-export-create-modal-row">
+          <text class="report-export-create-modal-label">规格</text>
+          <view class="report-export-create-modal-inputwrap"
+            :class="{ 'report-export-input-disabled': !filterForm.product?.drugEntBaseInfoId }"
+            @click.stop="filterForm.product?.drugEntBaseInfoId && openFilterDropdown('pkgSpec')">
+            <view class="report-export-create-modal-static">{{
+              filterForm.pkgSpec || "请选择品种规格(请先选择品种)"
+            }}</view>
+            <view class="report-export-input-arrow"></view>
+            <view class="report-export-create-modal-dropdown" v-if="filterDropdown.pkgSpec" @click.stop
+              :style="[{ top: 'auto', bottom: '84rpx' }]">
+              <view class="report-export-create-modal-dropdown-item" v-for="(op, i) in filteredOptions('pkgSpec')"
+                :key="'t-' + i" @click.stop="
+                  pickFilterOption('pkgSpec', op)
+                  ">{{ op }}</view>
+              <view v-if="filteredOptions('pkgSpec')?.length === 0" class="report-export-create-modal-dropdown-item">
+                未查找到该品种规格</view>
+            </view>
+          </view>
+        </view> -->
         <view class="report-export-create-modal-row">
           <text class="report-export-create-modal-label">时间:</text>
           <view class="report-export-create-modal-inputwrap">
@@ -358,7 +400,7 @@ export default {
         customerType: "",
         customer: [],
         product: { physicName: "" },
-        // otherCustomer: "",
+        otherCustomer: "",
       },
       dateRange: [],
       computeTimer: null,
@@ -367,11 +409,13 @@ export default {
         customerType: false,
         customer: false,
         product: false,
+        pkgSpec: false,
       },
       options: {
         region: [],
         customerType: ["VIP客户", "二级客户", "三级客户"],
         product: [],
+        pkgSpec: [],
         status: ["全部", "待开始", "进行中", "已完成"],
       },
       // dropdown search inputs (create modal)
@@ -404,8 +448,9 @@ export default {
       filterForm: {
         product: { physicName: "" },
         status: "全部",
+        pkgSpec: "",
       },
-      filterDropdown: { product: false },
+      filterDropdown: { product: false, pkgSpec: false },
       filterDateRange: [],
       modalType: "",
       dropdownUpward: false,
@@ -483,13 +528,13 @@ export default {
     getCustomerName(data = {}) {
       const { customerName = "", otherCustomer = "" } = data;
       let name = customerName || "";
-      // if (otherCustomer) {
-      //   if (name) {
-      //     name += `,${otherCustomer}`;
-      //   } else {
-      //     name = otherCustomer;
-      //   }
-      // }
+      if (otherCustomer) {
+        if (name) {
+          name += `,${otherCustomer}`;
+        } else {
+          name = otherCustomer;
+        }
+      }
       const arr = name.split(",");
       if (arr.length == 0) return "--";
       return arr.length == 1 ? arr[0].trim() : `${arr.length}个公司`;
@@ -517,6 +562,14 @@ export default {
         }
       });
     },
+    getGuigeList(physicName) {
+      if (!physicName) return;
+      request("/report/getDrugSpec", { physicName, path: "/reportExport/index.vue" }).then(res => {
+        if (res.code == 200) {
+          this.options.pkgSpec = res.data || [];
+        }
+      });
+    },
     getProviceList() {
       request("/common/getProviceList", {
         path: "/reportExport/index.vue",
@@ -594,13 +647,19 @@ export default {
               ? "VIP客户"
               : item.customerType == 2
                 ? "二级客户"
-                : "三级客户",
+                : item.customerType == 3
+                  ? "三级客户"
+                  : "",
           product: {
             physicName: item.physicName,
             drugEntBaseInfoId: item.drugEntBaseInfoId,
           },
-          // otherCustomer: item?.otherCustomer || "",
+          pkgSpec: item?.pkgSpec || "",
+          otherCustomer: item?.otherCustomer || "",
         };
+        if (item.physicName) {
+          this.getGuigeList(item.physicName);
+        }
         this.dateRange = [item.beginTime, item.endTime];
       }
       this.createModalOpen = true;
@@ -628,6 +687,7 @@ export default {
         reportType: 2,
         taskType: statusMap?.[this.filterForm?.status] || "",
         drugEntBaseInfoId: this.filterForm?.product?.drugEntBaseInfoId,
+        pkgSpec: this.filterForm?.pkgSpec || "",
         beginTime: this.filterDateRange[0],
         endTime: this.filterDateRange[1],
         customerName: this.keyword || "",
@@ -695,7 +755,7 @@ export default {
         customerType: "",
         customer: [],
         product: { physicName: "" },
-        // otherCustomer: "",
+        otherCustomer: "",
       };
       this.searchForm = {
         region: "",
@@ -719,7 +779,7 @@ export default {
         return;
       }
       if (!this.isFormComplete()) {
-        uni.showToast({ title: "请完整填写任务内容", icon: "none" });
+        // uni.showToast({ title: "请完整填写任务内容", icon: "none" });
         return;
       }
       // uni.showToast({ title: "已创建", icon: "none" });
@@ -739,14 +799,17 @@ export default {
       request(url, {
         id:
           this.modalType === "create" ? undefined : this.currentItem?.id || "",
+        reportType: 2,
         regionCode: this.form?.region?.regionCode || "",
         customerType: customerTypeMap?.[this.form?.customerType] || "",
         customerId,
         customerName,
-        // otherCustomer: this.form?.otherCustomer || "",
+        otherCustomer: this.form?.otherCustomer || "",
         drugEntBaseInfoId: this.form?.product?.drugEntBaseInfoId || "",
-        beginTime: this.dateRange[0],
-        endTime: this.dateRange[1],
+        pkgSpec: this.form?.pkgSpec || "",
+        physicName: this.form?.product?.physicName || "",
+        beginTime: this.formatDate(this.dateRange[0], 'YYYY-MM-DD'),
+        endTime: this.formatDate(this.dateRange[1], 'YYYY-MM-DD'),
         path: "",
       }).then((res) => {
         if (res.code == 200) {
@@ -770,13 +833,27 @@ export default {
       let customerOk = Array.isArray(f.customer)
         ? f.customer.length > 0
         : !!f.customer;
-      return (
-        f.region?.regionName &&
-        f.customerType &&
-        customerOk &&
-        f.product?.physicName &&
-        drOk
-      );
+      customerOk = customerOk || f.otherCustomer
+      if (!customerOk) {
+        uni.showToast({ title: "请选择至少一个客户或其他客户", icon: "none" });
+        return false
+      }
+      if (!f.product?.physicName) {
+        uni.showToast({ title: "请选择品种", icon: "none" });
+        return false
+      }
+      if (!drOk) {
+        uni.showToast({ title: "请选择时间范围", icon: "none" });
+        return false
+      }
+      return true
+      // return (
+      //   f.region?.regionName &&
+      //   f.customerType &&
+      //   customerOk &&
+      //   f.product?.physicName &&
+      //   drOk
+      // );
     },
     confirmDelete() {
       uni.showModal({
@@ -796,6 +873,7 @@ export default {
                   duration: 2000,
                 });
                 this.resetFetch();
+                this.closeCreate();
               } else {
                 uni.showToast({
                   title: res?.msg || "删除失败",
@@ -833,8 +911,10 @@ export default {
         customerType: false,
         customer: false,
         product: false,
+        pkgSpec: false,
       };
       this.filterDropdown.product = false;
+      this.filterDropdown.pkgSpec = false;
     },
     openFilterDropdown(k) {
       this.closeDropdownAll();
@@ -854,12 +934,16 @@ export default {
       }
     },
     pickFilterOption(k, v) {
-      if (["status"].includes(k) && this.filterForm[k] === v) {
+      if (!["status"].includes(k) && this.filterForm[k] === v) {
         this.filterForm[k] = "";
         this.filterDropdown[k] = false;
         return;
       }
       this.filterForm[k] = v;
+      if (k === "product") {
+        this.filterForm.pkgSpec = "";
+        this.getGuigeList(v.physicName);
+      }
       this.filterDropdown[k] = false;
     },
     filteredFilterOptions(k) {
@@ -902,6 +986,10 @@ export default {
         return;
       }
       this.form[k] = v;
+      if (k === "product") {
+        this.form.pkgSpec = "";
+        this.getGuigeList(v.physicName);
+      }
       this.dropdown[k] = false;
     },
     isCustomerSelected(name) {
@@ -1262,7 +1350,7 @@ export default {
 .report-export-item-header {
   width: 100%;
   display: flex;
-  align-items: baseline;
+  align-items: flex-start;
   flex-wrap: wrap;
 }
 
@@ -1681,4 +1769,10 @@ export default {
   margin-top: 10rpx;
   display: block;
 }
+
+.report-export-input-disabled {
+  background-color: #f5f7fa;
+  cursor: not-allowed;
+  opacity: 0.7;
+}
 </style>