钱新宇 1 semana atrás
pai
commit
a26e3a5fdd

+ 5 - 17
traceCodePackages/request/index.js

@@ -4,7 +4,7 @@ const storage = new Storage();
 
 const _baseUrl = import.meta.env.VITE_APP_API_BASEURL;
 const subdomain = "/yylm";
-// const _baseUrl = 'http://192.168.0.42:8001';
+// const _baseUrl = "http://192.168.0.43:8001";
 // const subdomain = "";
 
 export function downloadFile(url, data = {}) {
@@ -24,28 +24,16 @@ export function downloadFile(url, data = {}) {
               title: "下载成功",
               icon: "success",
             });
-            console.log(
-              `${data?.path},${baseUrl + subdomain + url},uni.saveFile请求头`,
-              res.tempFilePath,
-            );
-            console.log(
-              `${data?.path},${baseUrl + subdomain + url},uni.saveFile数据`,
-              r,
-            );
+            console.log(`${data?.path},${baseUrl + subdomain + url},uni.saveFile请求头`, res.tempFilePath);
+            console.log(`${data?.path},${baseUrl + subdomain + url},uni.saveFile数据`, r);
           },
           fail: (err) => {
             uni.showToast({
               title: "保存失败",
               icon: "error",
             });
-            console.log(
-              `${data?.path},${baseUrl + subdomain + url},uni.saveFile请求头`,
-              res.tempFilePath,
-            );
-            console.log(
-              `${data?.path},${baseUrl + subdomain + url},uni.saveFile数据`,
-              err,
-            );
+            console.log(`${data?.path},${baseUrl + subdomain + url},uni.saveFile请求头`, res.tempFilePath);
+            console.log(`${data?.path},${baseUrl + subdomain + url},uni.saveFile数据`, err);
           },
         });
       } else {

+ 34 - 123
traceCodePackages/traceabilityReport/pages/blacklist/index.vue

@@ -19,20 +19,14 @@
             v-for="(tab, index) in timeFilters"
             :key="index"
             :class="{ active: currentFilter === tab.value }"
-            @click="switchFilter(tab.value)"
-          >
+            @click="switchFilter(tab.value)">
             {{ tab.label }}
           </view>
         </view>
         <view class="filter-wrap">
           <view class="selector" @click.stop="toggleProvinceDropdown">
-            <text class="selector-text">{{
-              selectedProvince || "全部省份"
-            }}</text>
-            <text
-              class="selector-arrow"
-              :class="{ open: dropdownProvinceOpen }"
-            ></text>
+            <text class="selector-text">{{ selectedProvince || "全部省份" }}</text>
+            <text class="selector-arrow" :class="{ open: dropdownProvinceOpen }"></text>
           </view>
         </view>
       </view>
@@ -41,19 +35,9 @@
       <view class="header-circle circle-2"></view>
     </view>
 
-    <scroll-view
-      class="list-scroll"
-      scroll-y="true"
-      refresher-enabled
-      :refresher-triggered="isRefreshing"
-      @refresherrefresh="onRefresh"
-    >
+    <scroll-view class="list-scroll" scroll-y="true" refresher-enabled :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
       <view class="list-container">
-        <view
-          class="date-group"
-          v-for="groupItem in groupedRows"
-          :key="groupItem.date"
-        >
+        <view class="date-group" v-for="groupItem in groupedRows" :key="groupItem.date">
           <view class="date-group-card">
             <view class="group-left">
               <view
@@ -62,8 +46,7 @@
                   selected: isGroupFullySelected(groupItem),
                   partial: isGroupPartiallySelected(groupItem),
                 }"
-                @click.stop="toggleGroupSelection(groupItem)"
-              >
+                @click.stop="toggleGroupSelection(groupItem)">
                 <text v-if="isGroupFullySelected(groupItem)">✓</text>
                 <text v-else-if="isGroupPartiallySelected(groupItem)">-</text>
               </view>
@@ -71,19 +54,10 @@
             </view>
           </view>
 
-          <view
-            class="card-item"
-            v-for="(item, index) in groupItem.list"
-            :key="item.__selectId || index"
-            @click="toDetail(item)"
-          >
+          <view class="card-item" v-for="(item, index) in groupItem.list" :key="item.__selectId || index" @click="toDetail(item)">
             <view class="card-header">
               <view class="header-left">
-                <view
-                  class="company-select-btn"
-                  :class="{ selected: isCompanySelected(item) }"
-                  @click.stop="toggleCompanySelection(item)"
-                >
+                <view class="company-select-btn" :class="{ selected: isCompanySelected(item) }" @click.stop="toggleCompanySelection(item)">
                   <text v-if="isCompanySelected(item)">✓</text>
                 </view>
                 <text class="company-name">{{ item.queryCustomer }}</text>
@@ -129,37 +103,22 @@
     </scroll-view>
 
     <view class="footer-btn-area">
-      <button class="action-btn export-btn" @click="handleExport">
-        导出至邮箱
-      </button>
+      <button class="action-btn export-btn" @click="handleExport">导出至邮箱</button>
     </view>
 
     <!-- 蒙层 -->
-    <view
-      class="report-export-create-modal-mask"
-      v-if="emailModalOpen"
-      @click="closeEmailModal"
-      @touchmove.stop.prevent
-    ></view>
+    <view class="report-export-create-modal-mask" v-if="emailModalOpen" @click="closeEmailModal" @touchmove.stop.prevent></view>
 
     <!-- 邮箱导出弹窗 -->
     <view
       class="report-export-create-modal report-export-create-email-modal"
       :class="{ 'report-export-create-modal--open': emailModalOpen }"
-      v-if="emailModalOpen"
-    >
+      v-if="emailModalOpen">
       <view class="report-export-create-modal-title">
         <text>发送至邮箱</text>
-        <view
-          class="report-export-create-modal-close"
-          @click.stop="closeEmailModal"
-          >×</view
-        >
+        <view class="report-export-create-modal-close" @click.stop="closeEmailModal">×</view>
       </view>
-      <view
-        class="report-export-create-modal-body"
-        :style="{ padding: '40rpx' }"
-      >
+      <view class="report-export-create-modal-body" :style="{ padding: '40rpx' }">
         <up-input
           v-model="emailForm.email"
           border="none"
@@ -170,15 +129,12 @@
           }"
           placeholder="请填写邮箱"
           @input="emailError = false"
-          disabled
-        >
+          disabled>
           <template #suffix>
             <view style="margin-right: 20rpx; color: #666">@999.com.cn</view>
           </template>
         </up-input>
-        <text v-if="emailError" class="report-export-error-text"
-          >请输入邮箱</text
-        >
+        <text v-if="emailError" class="report-export-error-text">请输入邮箱</text>
       </view>
       <view
         class="report-export-create-modal-footer"
@@ -186,18 +142,9 @@
           padding: '20rpx 40rpx 40rpx',
           display: 'flex',
           'justify-content': 'space-between',
-        }"
-      >
-        <view
-          class="report-export-create-modal-btn cancel-btn"
-          @click.stop="closeEmailModal"
-          >取消</view
-        >
-        <view
-          class="report-export-create-modal-btn confirm-btn"
-          @click.stop="handleSendEmail"
-          >发送</view
-        >
+        }">
+        <view class="report-export-create-modal-btn cancel-btn" @click.stop="closeEmailModal">取消</view>
+        <view class="report-export-create-modal-btn confirm-btn" @click.stop="handleSendEmail">发送</view>
       </view>
     </view>
 
@@ -210,8 +157,7 @@
             v-model="provinceSearchText"
             @input="onProvinceSearch"
             placeholder="搜索省份..."
-            placeholder-style="color: #999"
-          />
+            placeholder-style="color: #999" />
         </view>
         <scroll-view scroll-y="true" class="dropdown-scroll-view">
           <view
@@ -221,18 +167,11 @@
             :class="{
               active: p === selectedProvince || (!selectedProvince && i === 0),
             }"
-            @click.stop="selectProvince(p)"
-          >
+            @click.stop="selectProvince(p)">
             {{ p || "全部省份" }}
-            <text
-              v-if="p === selectedProvince || (!selectedProvince && i === 0)"
-              class="check-mark"
-              >✓</text
-            >
+            <text v-if="p === selectedProvince || (!selectedProvince && i === 0)" class="check-mark">✓</text>
           </view>
-          <view v-if="filteredProvinceList.length === 0" class="dropdown-empty"
-            >暂无数据</view
-          >
+          <view v-if="filteredProvinceList.length === 0" class="dropdown-empty">暂无数据</view>
         </scroll-view>
       </view>
     </view>
@@ -303,13 +242,7 @@ export default {
 
     // 用于选择的唯一 key(优先 socialCreditCode)
     getCompanyKey(item = {}) {
-      return String(
-        item.__selectId ||
-          item.socialCreditCode ||
-          item.id ||
-          item.queryCustomer ||
-          "",
-      );
+      return String(item.__selectId || item.socialCreditCode || item.id || item.queryCustomer || "");
     },
 
     /** 将 YYYY-MM-DD 加一天,用于邮件接口 statisticsDate */
@@ -337,9 +270,7 @@ export default {
       if (!key) return;
       const has = this.selectedCompanyKeys.includes(key);
       if (has) {
-        this.selectedCompanyKeys = this.selectedCompanyKeys.filter(
-          (k) => k !== key,
-        );
+        this.selectedCompanyKeys = this.selectedCompanyKeys.filter((k) => k !== key);
       } else {
         this.selectedCompanyKeys = [...this.selectedCompanyKeys, key];
       }
@@ -354,27 +285,19 @@ export default {
     isGroupPartiallySelected(groupItem = {}) {
       const list = Array.isArray(groupItem.list) ? groupItem.list : [];
       if (!list.length) return false;
-      const selectedCount = list.filter((it) =>
-        this.isCompanySelected(it),
-      ).length;
+      const selectedCount = list.filter((it) => this.isCompanySelected(it)).length;
       return selectedCount > 0 && selectedCount < list.length;
     },
 
     toggleGroupSelection(groupItem = {}) {
       const list = Array.isArray(groupItem.list) ? groupItem.list : [];
       if (!list.length) return;
-      const groupKeys = list
-        .map((it) => this.getCompanyKey(it))
-        .filter(Boolean);
+      const groupKeys = list.map((it) => this.getCompanyKey(it)).filter(Boolean);
       if (!groupKeys.length) return;
 
-      const allSelected = groupKeys.every((k) =>
-        this.selectedCompanyKeys.includes(k),
-      );
+      const allSelected = groupKeys.every((k) => this.selectedCompanyKeys.includes(k));
       if (allSelected) {
-        this.selectedCompanyKeys = this.selectedCompanyKeys.filter(
-          (k) => !groupKeys.includes(k),
-        );
+        this.selectedCompanyKeys = this.selectedCompanyKeys.filter((k) => !groupKeys.includes(k));
       } else {
         const merged = new Set([...this.selectedCompanyKeys, ...groupKeys]);
         this.selectedCompanyKeys = Array.from(merged);
@@ -441,7 +364,7 @@ export default {
 
     toDetail(item) {
       uni.navigateTo({
-        url: `/traceCodePackages/traceabilityReport/pages/blacklist/detail/index?id=${item.socialCreditCode}&dateStr=${item.billTime}&name=${encodeURIComponent(item.queryCustomer)}&province=${encodeURIComponent(item.province || '')}&customerNature=${encodeURIComponent(item.customerNature || '')}&customerLevel=${encodeURIComponent(item.customerLevel || '')}&responsibleManager=${encodeURIComponent(item.responsibleManager || '')}`,
+        url: `/traceCodePackages/traceabilityReport/pages/blacklist/detail/index?id=${item.socialCreditCode}&dateStr=${item.billTime}&name=${encodeURIComponent(item.queryCustomer)}&province=${encodeURIComponent(item.province || "")}&customerNature=${encodeURIComponent(item.customerNature || "")}&customerLevel=${encodeURIComponent(item.customerLevel || "")}&responsibleManager=${encodeURIComponent(item.responsibleManager || "")}`,
       });
     },
 
@@ -481,9 +404,7 @@ export default {
 
       const doSingleSend = async () => {
         const keys = new Set(this.selectedCompanyKeys || []);
-        const selectedRows = (this.rows || []).filter((r) =>
-          keys.has(this.getCompanyKey(r)),
-        );
+        const selectedRows = (this.rows || []).filter((r) => keys.has(this.getCompanyKey(r)));
         const emailDTOList = selectedRows.map((item) => ({
           companyNameList: item.queryCustomer.split(","),
           statisticsDate: this.addOneDayFromDateStr(item.billTime),
@@ -554,9 +475,7 @@ export default {
 
     getSelectedTaskIds() {
       const keys = new Set(this.selectedCompanyKeys || []);
-      const selectedRows = (this.rows || []).filter((r) =>
-        keys.has(this.getCompanyKey(r)),
-      );
+      const selectedRows = (this.rows || []).filter((r) => keys.has(this.getCompanyKey(r)));
       const ids = selectedRows.map((r) => r?.taskId).filter(Boolean);
       return Array.from(new Set(ids));
     },
@@ -565,9 +484,7 @@ export default {
       if (!this.selectedProvince) {
         this.rows = [...this.allRows];
       } else {
-        this.rows = this.allRows.filter(
-          (item) => item.customerProvinceName === this.selectedProvince,
-        );
+        this.rows = this.allRows.filter((item) => item.customerProvinceName === this.selectedProvince);
       }
       this.totalCount = this.rows.length;
       // 切换省份/刷新数据后,清空已选企业,避免“跨筛选脏选中”
@@ -593,9 +510,7 @@ export default {
       if (!this.provinceSearchText) {
         return this.provinceList;
       }
-      return this.provinceList.filter((p) =>
-        p?.toLowerCase()?.includes(this.provinceSearchText.toLowerCase()),
-      );
+      return this.provinceList.filter((p) => p?.toLowerCase()?.includes(this.provinceSearchText.toLowerCase()));
     },
     groupedRows() {
       const groups = {};
@@ -606,11 +521,7 @@ export default {
       });
 
       return Object.keys(groups)
-        .sort(
-          (a, b) =>
-            new Date(String(b).replace(/-/g, "/")).getTime() -
-            new Date(String(a).replace(/-/g, "/")).getTime(),
-        )
+        .sort((a, b) => new Date(String(b).replace(/-/g, "/")).getTime() - new Date(String(a).replace(/-/g, "/")).getTime())
         .map((date) => ({
           date,
           list: groups[date],

+ 4 - 2
traceCodePackages/traceabilityReport/pages/customerScanningRate/index.vue

@@ -3,10 +3,10 @@
     <view class="scan-rate-ranges-container">
       <view class="scan-rate-ranges">
         <view class="scan-rate-ranges-content">
-          <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 1 }" @click="activeRange = 1">近7天 </view>
+          <!-- <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 1 }" @click="activeRange = 1">近7天 </view> -->
           <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 2 }" @click="activeRange = 2">当月 </view>
           <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 3 }" @click="activeRange = 3">上个月 </view>
-          <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 4 }" @click="activeRange = 4">近60天 </view>
+          <!-- <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 4 }" @click="activeRange = 4">近60天 </view> -->
           <view class="scan-rate-range" :class="{ 'scan-rate-active': activeRange === 5 }" @click="activeRange = 5">当年 </view>
         </view>
       </view>
@@ -267,6 +267,8 @@ export default {
   border-radius: 5rpx;
   background: rgb(240, 240, 240);
   font-size: 26rpx;
+  width: 20%;
+  text-align: center;
 }
 
 .scan-rate-range.scan-rate-active {

+ 2 - 2
traceCodePackages/traceabilityReport/pages/customerScanningRate/wigets/ScanRateTable.vue

@@ -39,7 +39,7 @@
             </scroll-view>
           </view>
         </view>
-        <!-- <view class="selector-wrap">
+        <view class="selector-wrap">
           <view class="selector" @click.stop="toggleDropdownManufacturer">
             <text class="selector-text">{{ manufacturer?.manufacturerId ? manufacturer.produceEntName : "选择厂家" }}</text>
             <text class="selector-arrow" :class="{ open: dropdownManufacturerOpen }"></text>
@@ -67,7 +67,7 @@
               <view v-if="manufacturerList.length === 0" class="dropdown-item" style="text-align: center; color: #999">暂无数据</view>
             </scroll-view>
           </view>
-        </view> -->
+        </view>
         <view class="selector-wrap">
           <view class="selector" @click.stop="toggleDropdown">
             <text class="selector-text">{{ cascaderDisplayText }}</text>

+ 1 - 1
traceCodePackages/traceabilityReport/pages/ganmaoling/index.vue

@@ -8,7 +8,7 @@
       <view class="header-content">
         <view class="title-group">
           <text class="main-title">感冒灵大批量跨区域客户</text>
-          <text class="sub-title">订单维度:单个公司单日累计 {{ ">" }} 20000盒</text>
+          <text class="sub-title">订单维度:单个公司单日累计 {{ ">" }} 8000盒</text>
           <text class="sub-title">地域维度:出库企业所在省份 ≠ 入库企业所在省份</text>
         </view>
         <view class="stat-box">

+ 26 - 7
traceCodePackages/traceabilityReport/pages/reportExport/detail/index.vue

@@ -21,9 +21,11 @@
                 <view class="company-title">
                   <text class="company-name">入库方:{{ company.name }}</text>
                   <view class="company-products" v-if="company.products && company.products.length">
-                    <text class="company-product-item" v-for="(prod, pIdx) in company.products" :key="pIdx"> {{ prod.name }} {{ prod.spec }} </text>
+                    <text class="company-product-item" v-for="(prod, pIdx) in company.products" :key="pIdx">
+                      {{ getProductText(prod) }}
+                    </text>
                   </view>
-                  <view class="company-meta">
+                  <view class="company-meta" v-if="company.medicalFlag !== '是'">
                     <view class="company-meta-row">
                       <text class="company-meta-item">客户性质:{{ company.customerNature || "--" }}</text>
                       <text class="company-meta-item">客户等级:{{ company.customerLevel || "--" }}</text>
@@ -42,7 +44,7 @@
                     <text>{{ category == 1 ? "出库方" : "入库方" }}:{{ exp.name }}</text>
                     <uni-icons :type="exp.expanded ? 'up' : 'down'" size="16" color="#666"></uni-icons>
                   </view>
-                  <view class="exporter-meta">
+                  <view class="exporter-meta" v-if="company.medicalFlag !== '是' && !exp.name.includes('华润三九')">
                     <view class="exporter-meta-row">
                       <text class="exporter-meta-item">客户性质:{{ exp.fromCustomerNature || "--" }}</text>
                       <text class="exporter-meta-item">客户等级:{{ exp.fromCustomerLevel || "--" }}</text>
@@ -106,9 +108,11 @@
                 <view class="company-title">
                   <text class="company-name">出库方:{{ company.name }}</text>
                   <view class="company-products" v-if="company.products && company.products.length">
-                    <text class="company-product-item" v-for="(prod, pIdx) in company.products" :key="pIdx"> {{ prod.name }} {{ prod.spec }} </text>
+                    <text class="company-product-item" v-for="(prod, pIdx) in company.products" :key="pIdx">
+                      {{ getProductText(prod) }}
+                    </text>
                   </view>
-                  <view class="company-meta">
+                  <view class="company-meta" v-if="company.medicalFlag !== '是'">
                     <view class="company-meta-row">
                       <text class="company-meta-item">客户性质:{{ company.customerNature || "--" }}</text>
                       <text class="company-meta-item">客户等级:{{ company.customerLevel || "--" }}</text>
@@ -127,7 +131,7 @@
                     <text>{{ category == 1 ? "出库方" : "入库方" }}:{{ exp.name }}</text>
                     <uni-icons :type="exp.expanded ? 'up' : 'down'" size="16" color="#666"></uni-icons>
                   </view>
-                  <view class="exporter-meta">
+                  <view class="exporter-meta" v-if="company.medicalFlag !== '是'">
                     <view class="exporter-meta-row">
                       <text class="exporter-meta-item">客户性质:{{ exp.fromCustomerNature || "--" }}</text>
                       <text class="exporter-meta-item">客户等级:{{ exp.fromCustomerLevel || "--" }}</text>
@@ -294,6 +298,17 @@ export default {
       node.leafCount = sum;
       return sum;
     },
+    getPrepnSpec(item = {}) {
+      return item.prepnSpec || item.prepnSpecName || item.prepSpec || "";
+    },
+    getProductText(product = {}) {
+      const name = String(product.name || "").trim();
+      const spec = String(product.spec || "").trim();
+      const prepnSpec = String(this.getPrepnSpec(product)).trim();
+      const parts = [name, spec].filter(Boolean);
+      if (prepnSpec) parts.push(prepnSpec);
+      return parts.join(" ");
+    },
     processData(list) {
       const res = [];
       if (!list || !list.length) return res;
@@ -303,6 +318,7 @@ export default {
           customerLevel: company.customerLevel || "",
           customerNature: company.customerNature || "",
           responsibleManager: company.responsibleManager || "",
+          medicalFlag: company.medicalFlag || "",
           totalQty: 0,
           expanded: false, // Default collapsed
           exporters: [],
@@ -321,12 +337,14 @@ export default {
           };
           const productGroups = {};
           (exp.enterpriseList || []).forEach((item) => {
-            const key = (item.physicName || "") + "|" + (item.pkgSpec || "");
+            const prepnSpec = this.getPrepnSpec(item);
+            const key = [item.physicName || "", item.pkgSpec || "", prepnSpec].join("|");
             // Collect global unique products for the company header
             if (!uniqueProducts[key]) {
               uniqueProducts[key] = {
                 name: item.physicName,
                 spec: item.pkgSpec || "",
+                prepnSpec,
               };
             }
 
@@ -334,6 +352,7 @@ export default {
               productGroups[key] = {
                 name: item.physicName,
                 spec: item.pkgSpec || "",
+                prepnSpec,
                 batches: [],
               };
             }

+ 304 - 18
traceCodePackages/traceabilityReport/pages/reportExport/index.vue

@@ -50,6 +50,7 @@
         <view class="report-export-item-row">创建人:{{ item.userName || "" }}</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" v-if="getPrepnSpecText(item.prepnSpec)">制剂规格:{{ getPrepnSpecText(item.prepnSpec) }}</view>
         <view class="report-export-item-row">时间:{{ formatDate(item.beginTime) }}~{{ formatDate(item.endTime) }}</view>
         <!-- <view
           class="report-export-item-row"
@@ -100,6 +101,25 @@
             <uni-icons type="help" size="16" color="606266"></uni-icons>
             非协议客户请直接在“其他客户”输入客户名称</view
           >
+          <view class="report-export-medical-tabs">
+            <view
+              class="report-export-medical-tab-indicator"
+              :class="{ 'is-medical': searchForm.productMedical === '是' }"></view>
+            <button
+              class="report-export-medical-tab"
+              :class="{ 'medical-active': searchForm.productMedical === '否' }"
+              :aria-disabled="modalType === 'read'"
+              @click.stop="modalType !== 'read' && setProductMedical('否', 'create')">
+              OTC
+            </button>
+            <button
+              class="report-export-medical-tab"
+              :class="{ 'medical-active': searchForm.productMedical === '是' }"
+              :aria-disabled="modalType === 'read'"
+              @click.stop="modalType !== 'read' && setProductMedical('是', 'create')">
+              医疗
+            </button>
+          </view>
           <view class="report-export-create-modal-row">
             <text class="report-export-create-modal-label">省份</text>
             <view class="report-export-create-modal-inputwrap" @click.stop="modalType !== 'read' && openDropdown('region')">
@@ -132,7 +152,7 @@
               </view>
             </view>
           </view>
-          <view class="report-export-create-modal-row">
+          <view v-if="searchForm.productMedical === '否'" class="report-export-create-modal-row">
             <text class="report-export-create-modal-label">客户性质</text>
             <view class="report-export-create-modal-inputwrap" @click.stop="modalType !== 'read' && openDropdown('customerType')">
               <view class="report-export-create-modal-static">{{ form.customerType || "请选择客户性质" }}</view>
@@ -151,7 +171,10 @@
           </view>
           <view class="report-export-create-modal-row">
             <text class="report-export-create-modal-label">客户</text>
-            <view class="report-export-create-modal-inputwrap" @click.stop="modalType !== 'read' && openDropdown('customer')">
+            <view
+              class="report-export-create-modal-inputwrap"
+              :class="{ 'report-export-input-disabled': searchForm.productMedical === '否' && !form.customerType }"
+              @click.stop="modalType !== 'read' && (searchForm.productMedical === '是' || form.customerType) && openDropdown('customer')">
               <view class="report-export-create-modal-static" :style="{ maxHeight: 'auto', overflowY: 'visible' }">
                 <block v-if="Array.isArray(form.customer) && form.customer.length">
                   <view
@@ -166,7 +189,7 @@
                     </text>
                   </view>
                 </block>
-                <text v-else>请选择客户</text>
+                <text v-else>{{ searchForm.productMedical === "否" && !form.customerType ? "请先选择客户性质" : "请选择客户" }}</text>
                 <text
                   v-if="form.customer && form.customer.length && modalType !== 'read'"
                   class="customer-clear-all"
@@ -174,7 +197,7 @@
                   >×</text
                 >
               </view>
-              <view class="report-export-input-arrow" v-if="modalType !== 'read'"></view>
+              <view class="report-export-input-arrow" v-if="modalType !== 'read' && (searchForm.productMedical === '是' || form.customerType)"></view>
               <view
                 class="report-export-create-modal-dropdown"
                 v-if="dropdown.customer"
@@ -335,6 +358,28 @@
               </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('prepnSpec')">
+              <view class="report-export-create-modal-static">{{ form.prepnSpec || "请选择制剂规格(不选则查询全部制剂规格)" }}</view>
+              <view class="report-export-input-arrow" v-if="modalType !== 'read'"></view>
+              <view class="report-export-create-modal-dropdown" v-if="dropdown.prepnSpec" @click.stop :style="[{ top: 'auto', bottom: '84rpx' }]">
+                <view
+                  class="report-export-create-modal-dropdown-item"
+                  v-for="(op, i) in filteredOptions('prepnSpec')"
+                  :key="'t-' + i"
+                  @click.stop="modalType !== 'read' && pickOption('prepnSpec', op)"
+                  >{{ op }}</view
+                >
+                <view v-if="filteredOptions('prepnSpec')?.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" @click="openDatePicker">
@@ -396,6 +441,26 @@
                   @focus="onSearchFocus"
                   @blur="onSearchBlur" />
               </view>
+              <view class="report-export-dropdown-medical-row">
+                <text
+                  class="report-export-dropdown-medical-tag"
+                  :class="{ 'medical-active': filterSearchForm.productMedical === '' }"
+                  @click.stop="setProductMedical('', 'filter')"
+                  >全部</text
+                >
+                <text
+                  class="report-export-dropdown-medical-tag"
+                  :class="{ 'medical-active': filterSearchForm.productMedical === '是' }"
+                  @click.stop="setProductMedical('是', 'filter')"
+                  >医疗产品</text
+                >
+                <text
+                  class="report-export-dropdown-medical-tag"
+                  :class="{ 'medical-active': filterSearchForm.productMedical === '否' }"
+                  @click.stop="setProductMedical('否', 'filter')"
+                  >OTC</text
+                >
+              </view>
               <scroll-view class="report-export-create-modal-dropdown-list" scroll-y @scrolltolower="loadMoreProducts('filter')">
                 <view
                   class="report-export-create-modal-dropdown-item"
@@ -507,15 +572,22 @@
 import EmptyView from "../../../wigets/empty.vue";
 import { formatDate, getPaginatedList } from "../../../utils/utils.js";
 import request from "../../../request/index.js";
+const customerTypeOptions = ["VIP客户", "二级客户", "三级客户", "KA客户", "NKA客户", "区域核心连锁"];
 const customerTypeMap = {
   VIP客户: "VIP",
   二级客户: "二级",
   三级客户: "三级",
+  区域核心连锁: "区域核心连锁",
+  NKA客户: "NKA客户",
+  KA客户: "KA客户",
 };
 const customerTypeReverseMap = {
   VIP: "VIP客户",
   二级: "二级客户",
   三级: "三级客户",
+  区域核心连锁: "区域核心连锁",
+  NKA客户: "NKA客户",
+  KA客户: "KA客户",
 };
 const statusMap = {
   全部: 0,
@@ -544,6 +616,8 @@ export default {
         customerType: "",
         customer: [],
         product: { physicName: "" },
+        pkgSpec: "",
+        prepnSpec: "",
         otherCustomer: "",
         otherCustomerCode: "",
       },
@@ -556,18 +630,22 @@ export default {
         otherCustomer: false,
         product: false,
         pkgSpec: false,
+        prepnSpec: false,
       },
       options: {
         region: [],
-        customerType: ["VIP客户", "二级客户", "三级客户"],
+        customerType: customerTypeOptions,
         product: [],
         pkgSpec: [],
+        prepnSpec: [],
         status: ["全部", "已完成", "待开始", "进行中"],
       },
       // dropdown search inputs (create modal)
       searchForm: {
         region: "",
+        customerType: "",
         product: "",
+        productMedical: "否",
       },
       // Pagination state for products
       productPage: 0,
@@ -577,6 +655,7 @@ export default {
       // dropdown search inputs (filter modal)
       filterSearchForm: {
         product: "",
+        productMedical: "",
       },
       // Pagination state for filter products
       filterProductPage: 0,
@@ -589,14 +668,16 @@ export default {
       customerHasMore: true,
       customerLoading: false,
       customerDebounceTimer: null,
+      customerRequestVersion: 0,
       taskTitle: "创建任务",
       filterModalOpen: false,
       filterForm: {
         product: { physicName: "" },
         status: "全部",
         pkgSpec: "",
+        prepnSpec: "",
       },
-      filterDropdown: { product: false, pkgSpec: false },
+      filterDropdown: { product: false, pkgSpec: false, prepnSpec: false },
       filterDateRange: [],
       modalType: "",
       dropdownUpward: false,
@@ -643,6 +724,8 @@ export default {
   },
   watch: {
     "form.customerType"(val) {
+      this.customerRequestVersion++;
+      this.customerLoading = false;
       this.customerPage = 1;
       this.customerTotal = null;
       this.customerHasMore = true;
@@ -652,6 +735,8 @@ export default {
     },
     "form.region": {
       handler() {
+        this.customerRequestVersion++;
+        this.customerLoading = false;
         this.customerPage = 1;
         this.customerTotal = null;
         this.customerHasMore = true;
@@ -702,6 +787,9 @@ export default {
       }
       return this.formatDate(value);
     },
+    getPrepnSpecText(prepnSpec) {
+      return this.normalizeSpecOptions(prepnSpec).join(",");
+    },
     getStatusText(status) {
       if (status == "1") {
         return "导出待开始";
@@ -717,13 +805,50 @@ export default {
         path: "/reportExport/index.vue",
       }).then((res) => {
         if (res.code == 200) {
-          const _data = res.data;
+          const _data = Array.isArray(res.data) ? res.data : [];
           this.options.product = _data;
+          if (this.form?.product?.drugEntBaseInfoId || this.form?.product?.physicName) {
+            this.setPrepnSpecOptions(this.form.product);
+          }
         }
       });
     },
+    normalizeSpecOptions(list) {
+      const rawList = Array.isArray(list) ? list : list ? [list] : [];
+      const values = rawList
+        .map((item) => {
+          if (typeof item === "string" || typeof item === "number") return String(item);
+          if (item && typeof item === "object") {
+            return item.prepnSpec || item.prepnSpecName || item.pkgSpec || item.pkgSpecName || item.specName || item.name || item.value || "";
+          }
+          return "";
+        })
+        .map((item) => String(item || "").trim())
+        .filter(Boolean);
+      return Array.from(new Set(values));
+    },
+    resolveProductOption(product = {}) {
+      const productId = product?.drugEntBaseInfoId;
+      const productName = product?.physicName;
+      return (
+        (this.options.product || []).find((item) => {
+          const idMatched = productId && String(item?.drugEntBaseInfoId) === String(productId);
+          const nameMatched = productName && item?.physicName === productName;
+          return idMatched || nameMatched;
+        }) ||
+        product ||
+        {}
+      );
+    },
+    setPrepnSpecOptions(product) {
+      const selectedProduct = this.resolveProductOption(product);
+      this.options.prepnSpec = this.normalizeSpecOptions(selectedProduct?.prepnSpec);
+    },
     getGuigeList(physicName) {
-      if (!physicName) return;
+      if (!physicName) {
+        this.options.pkgSpec = [];
+        return;
+      }
       request("/report/getDrugSpec", {
         physicName,
         path: "/reportExport/index.vue",
@@ -745,15 +870,28 @@ export default {
     },
     getCustomerOptions() {
       if (!this.customerHasMore || this.customerLoading) return;
+      const isMedical = this.searchForm.productMedical === "是";
+      if (!isMedical && !this.form.customerType) {
+        this.customers = [];
+        this.customerHasMore = false;
+        return;
+      }
       this.customerLoading = true;
-      request("/customer/info/getCustomerInfo", {
+      const requestVersion = this.customerRequestVersion;
+      const params = {
         pageNum: this.customerPage,
         pageSize: this.customerPageSize,
         customerName: this.customerSearch || "",
-        customerLevel: customerTypeMap?.[this.form.customerType] || undefined,
         provinceCode: this.form?.region?.regionCode || undefined,
         path: "/reportExport/index.vue",
-      }).then((res) => {
+      };
+      if (isMedical) {
+        params.medicalFlag = "医疗";
+      } else {
+        params.customerLevel = customerTypeMap?.[this.form.customerType] || undefined;
+      }
+      request("/customer/info/getCustomerInfo", params).then((res) => {
+        if (requestVersion !== this.customerRequestVersion) return;
         if (res.code == 200) {
           const _data = res.data || {};
           this.customerTotal = _data.total || 0;
@@ -858,12 +996,16 @@ export default {
             drugEntBaseInfoId: item.drugEntBaseInfoId,
           },
           pkgSpec: item?.pkgSpec || "",
+          prepnSpec: item?.prepnSpec || "",
           otherCustomer: item?.otherCustomer || "",
           otherCustomerCode: item?.otherCustomerCode || "",
         };
         if (item.physicName) {
           this.getGuigeList(item.physicName);
         }
+        this.setPrepnSpecOptions(this.form.product);
+        const selectedProduct = this.resolveProductOption(this.form.product);
+        this.searchForm.productMedical = selectedProduct?.isMedical || item?.isMedical || (customerTypeLabel === "医疗" ? "是" : "否");
         this.dateRange = [this.normalizeDateToYMD(item.beginTime), this.normalizeDateToYMD(item.endTime)];
       }
       this.createModalOpen = true;
@@ -964,16 +1106,24 @@ export default {
         customerType: "",
         customer: [],
         product: { physicName: "" },
+        pkgSpec: "",
+        prepnSpec: "",
         otherCustomer: "",
         otherCustomerCode: "",
       };
+      this.options.pkgSpec = [];
+      this.options.prepnSpec = [];
       this.searchForm = {
         region: "",
+        customerType: "",
         product: "",
+        productMedical: "否",
       };
       this.otherCustomerSearch = "";
       this.otherCustomers = [];
       this.otherCustomerLoading = false;
+      this.customerRequestVersion++;
+      this.customerLoading = false;
       if (this.otherCustomerDebounceTimer) {
         clearTimeout(this.otherCustomerDebounceTimer);
         this.otherCustomerDebounceTimer = null;
@@ -1012,13 +1162,14 @@ export default {
         id: this.modalType === "create" ? undefined : this.currentItem?.id || "",
         reportType: 2,
         regionCode: this.form?.region?.regionCode || "",
-        customerType: customerTypeMap?.[this.form?.customerType] || "",
+        customerType: this.form?.customerType === "医疗" ? "医疗" : customerTypeMap?.[this.form?.customerType] || "",
         customerId,
         customerName,
         otherCustomer: this.form?.otherCustomer || "",
         otherCustomerCode: this.form?.otherCustomerCode || "",
         drugEntBaseInfoId: this.form?.product?.drugEntBaseInfoId || "",
         pkgSpec: this.form?.pkgSpec || "",
+        prepnSpec: this.form?.prepnSpec || "",
         physicName: this.form?.product?.physicName || "",
         beginTime: this.formatDate(this.dateRange[0], "YYYY-MM-DD"),
         endTime: this.formatDate(this.dateRange[1], "YYYY-MM-DD"),
@@ -1101,6 +1252,8 @@ export default {
       this.closeDropdownAll();
       this.dropdown[k] = true;
       if (k === "customer") {
+        this.customerRequestVersion++;
+        this.customerLoading = false;
         this.customerPage = 1;
         this.customerTotal = null;
         this.customerHasMore = true;
@@ -1142,9 +1295,11 @@ export default {
         otherCustomer: false,
         product: false,
         pkgSpec: false,
+        prepnSpec: false,
       };
       this.filterDropdown.product = false;
       this.filterDropdown.pkgSpec = false;
+      this.filterDropdown.prepnSpec = false;
     },
     openFilterDropdown(k) {
       this.closeDropdownAll();
@@ -1172,6 +1327,7 @@ export default {
       this.filterForm[k] = v;
       if (k === "product") {
         this.filterForm.pkgSpec = "";
+        this.filterForm.prepnSpec = "";
         this.getGuigeList(v.physicName);
       }
       this.filterDropdown[k] = false;
@@ -1192,11 +1348,15 @@ export default {
         product: { physicName: "" },
         status: "全部",
         pkgSpec: "",
+        prepnSpec: "",
+      };
+      this.filterSearchForm = {
+        product: "",
+        productMedical: "",
       };
-      this.filterSearchForm.product = "";
       this.filterProductPage = 0;
       this.filterDisplayProducts = [];
-      this.filterDropdown = { product: false, pkgSpec: false };
+      this.filterDropdown = { product: false, pkgSpec: false, prepnSpec: false };
       this.filterDateRange = [];
       this.resetFetch();
     },
@@ -1214,6 +1374,9 @@ export default {
       if (k === "product") {
         this.initProductList("create");
       }
+      if (k === "prepnSpec") {
+        this.setPrepnSpecOptions(this.form.product);
+      }
       if (k === "otherCustomer" && !String(this.otherCustomerSearch || "").trim()) {
         this.otherCustomers = [];
       }
@@ -1244,10 +1407,16 @@ export default {
         this.form.otherCustomer = selected.join(",");
         return;
       }
+      if ((k === "customerType" || k === "region") && this.form[k] !== v) {
+        this.form.customer = [];
+        this.customerSearch = "";
+      }
       this.form[k] = v;
       if (k === "product") {
         this.form.pkgSpec = "";
+        this.form.prepnSpec = "";
         this.getGuigeList(v.physicName);
+        this.setPrepnSpecOptions(v);
       }
       this.dropdown[k] = false;
     },
@@ -1283,14 +1452,21 @@ export default {
     filteredOptions(k, type) {
       const arr = this.options[k] || [];
       let q = "";
+      let medical = "";
       if (type === "filter") {
         q = this.filterSearchForm[k] || "";
+        medical = this.filterSearchForm.productMedical || "";
       } else {
         q = this.searchForm[k] || "";
+        medical = this.searchForm.productMedical || "";
+      }
+      let result = arr;
+      if (k === "product" && medical) {
+        result = arr.filter((it) => it && typeof it === "object" && it.isMedical === medical);
       }
       q = String(q).trim().toLocaleLowerCase();
-      if (!q) return arr;
-      return arr.filter((it) => {
+      if (!q) return result;
+      return result.filter((it) => {
         if (typeof it === "string") return it.toLocaleLowerCase().indexOf(q) !== -1;
         if (it && typeof it === "object") {
           const name = k === "region" ? it.regionName || it.name || "" : it.physicName || it.productName || "";
@@ -1299,6 +1475,31 @@ export default {
         return false;
       });
     },
+    setProductMedical(value, mode) {
+      if (mode === "filter") {
+        this.filterSearchForm.productMedical = value;
+        this.initProductList("filter");
+      } else {
+        if (this.searchForm.productMedical !== value) {
+          this.form.customerType = value === "是" ? "医疗" : "";
+          this.form.customer = [];
+          this.customers = [];
+          this.customerSearch = "";
+          this.customerPage = 1;
+          this.customerTotal = null;
+          this.customerHasMore = true;
+          this.form.product = { physicName: "" };
+          this.form.pkgSpec = "";
+          this.form.prepnSpec = "";
+          this.options.pkgSpec = [];
+          this.options.prepnSpec = [];
+          this.searchForm.product = "";
+        }
+        this.searchForm.productMedical = value;
+        this.closeDropdownAll();
+        this.initProductList("create");
+      }
+    },
     // Initialize product list for pagination
     initProductList(mode) {
       const allProducts = this.filteredOptions("product", mode === "filter" ? "filter" : undefined);
@@ -1426,8 +1627,8 @@ export default {
       const url = "/report/sendemail";
       request(url, {
         taskId: this.currentItem?.id,
-        //emailAddress: email + "@999.com.cn",
-        emailAddress: "hezhijie@dotouch.tech",
+        emailAddress: email + "@999.com.cn",
+        // emailAddress: "qianxinyu@dotouch.tech",
         path: "reportExport/index.vue",
       }).then((res) => {
         uni.hideLoading();
@@ -1868,6 +2069,70 @@ export default {
   font-size: 24rpx;
 }
 
+.report-export-medical-tabs {
+  display: flex;
+  position: relative;
+  height: 64rpx;
+  margin-top: 16rpx;
+  padding: 4rpx;
+  border-radius: 10rpx;
+  background: #f2f3f5;
+  overflow: hidden;
+}
+
+.report-export-medical-tab-indicator {
+  position: absolute;
+  top: 4rpx;
+  bottom: 4rpx;
+  left: 4rpx;
+  width: calc(50% - 4rpx);
+  border-radius: 8rpx;
+  background: #2c69ff;
+  box-shadow: 0 3rpx 8rpx rgba(44, 105, 255, 0.24);
+  transform: translateX(0);
+  transition: transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
+  will-change: transform;
+}
+
+.report-export-medical-tab-indicator.is-medical {
+  transform: translateX(100%);
+}
+
+.report-export-medical-tab {
+  flex: 1;
+  position: relative;
+  z-index: 1;
+  height: 64rpx;
+  margin: 0;
+  padding: 0;
+  border: 0;
+  border-radius: 8rpx;
+  background: transparent;
+  color: #606266;
+  font-size: 28rpx;
+  line-height: 64rpx;
+  transition: color 180ms ease-out, transform 100ms ease-out;
+}
+
+.report-export-medical-tab::after {
+  border: 0;
+}
+
+.report-export-medical-tab.medical-active {
+  color: #fff;
+}
+
+.report-export-medical-tab:active {
+  transform: scale(0.98);
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .report-export-medical-tab-indicator,
+  .report-export-medical-tab {
+    transition-duration: 0.01ms;
+  }
+}
+
 .report-export-create-modal-row {
   display: flex;
   flex-direction: column;
@@ -1959,6 +2224,27 @@ export default {
   padding: 10rpx;
 }
 
+.report-export-dropdown-medical-row {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 12rpx;
+  padding: 8rpx 16rpx;
+}
+
+.report-export-dropdown-medical-tag {
+  padding: 6rpx 18rpx;
+  border-radius: 24rpx;
+  background: #f2f3f5;
+  color: #666;
+  font-size: 24rpx;
+}
+
+.report-export-dropdown-medical-tag.medical-active {
+  background: #5497d5;
+  color: #fff;
+}
+
 .report-export-dropdown-search-input {
   width: 100%;
   height: 60rpx;