Explorar o código

Merge branch 'zengjun/退货/2025-08-08退货登记'

zengjun hai 7 meses
pai
achega
6be7fcf339

+ 2 - 2
package.json

@@ -15,7 +15,7 @@
     "axios": "^1.7.9",
     "lib-flexible": "^0.3.2",
     "pinia": "^2.3.0",
-    "pinia-plugin-persistedstate": "^4.2.0",
+    "pinia-plugin-persistedstate": "^3.2.1",
     "pinyin": "^2.11.2",
     "postcss-pxtorem": "^6.1.0",
     "sass": "^1.83.0",
@@ -45,7 +45,7 @@
     "sass-embedded": "^1.83.0",
     "terser": "^5.37.0",
     "typescript": "^5.7.2",
-    "vite": "6.0.9",
+    "vite": "^5.4.1",
     "vue-tsc": "^2.2.0"
   }
 }

+ 20 - 0
src/api/basic/index.ts

@@ -28,3 +28,23 @@ export function getPrinter() {
   })
 }
 
+// 仓库列表
+export function getWarehouse(){
+  return request({
+    url: '/api/basic/warehouse/options',
+    method: 'get'
+  })
+}
+
+
+
+/**
+ * 全部承运商
+ */
+export function carrierOptions() {
+  return request({
+    url: '/api/base/carrier/options',
+    method: 'get',
+    params: { type: null }
+  })
+}

+ 181 - 0
src/api/returned/index.ts

@@ -0,0 +1,181 @@
+import request from '@/utils/request'
+import { searchBarcodeParams, validateParams } from '@/types/returned'
+
+const RETURNED_SUBMIT_CODE = 'BS-TH-REGISTER'
+
+const qualityInspectionMap = {
+  LIGHT: '轻度质检',
+  PROFESSIONAL: '专业质检',
+  DEEP: '深度质检',
+  DETAILED: '精细质检',
+}
+
+const qualityStatusTagMap = {
+  正品: '#07c160',
+  次品: '#ff6a6a',
+  待处理: '#29b7ef',
+  微瑕: '#bc3d3d',
+  待修复: '#1c572b',
+}
+
+/**
+ * 质量状态tag颜色
+ * @param qualityStatus
+ */
+export function getTagColorBy(qualityStatus: string) {
+  return qualityStatusTagMap[qualityStatus]
+}
+
+/**
+ * 货主质检等级
+ * @param qualityInspection
+ */
+export function getQualityInspectionBy(qualityInspection: string) {
+  return qualityInspectionMap[qualityInspection] || '未知'
+}
+
+export function getQualityInspection(owner){
+  return request({
+    url: '/api/basic/owner/getQualityInspection',
+    method: 'get',
+    params: { owner }
+  })
+}
+
+
+
+
+//  "detail/quality-status"
+export function getQualityStatus() {
+  return request({
+    url: '/api/erp/returned/detail/quality-status',
+    method: 'get',
+  })
+}
+
+// 货主对应商铺
+export function shops(owners) {
+  return request({
+    url: '/api/basic/owner/shop/options',
+    method: 'get',
+    params: { owners },
+  })
+}
+
+/**
+ * 匹配对应的商品
+ */
+export function searchBarcode(params: searchBarcodeParams) {
+  return request({
+    url: '/api/erp/returned/match-barcode',
+    method: 'get',
+    params,
+  })
+}
+
+/**
+ * 匹配货主对应的商品
+ */
+export function searchOwnerBarcode(params: searchBarcodeParams) {
+  return request({
+    url: '/api/erp/returned/match-barcode/owner',
+    method: 'GET',
+    params,
+  })
+}
+
+/**
+ * 删除对应的详情
+ * @param id
+ */
+export function deleteDetails(id: number) {
+  return request({
+    url: `/api/erp/returned/detail/${id}`,
+    method: 'DELETE',
+  })
+}
+
+/**
+ * 校验相关日期
+ */
+export function validateDate(params: validateParams) {
+  return request({
+    url: '/api/basic/product/register/validateDate',
+    method: 'POST',
+    data: params,
+  })
+}
+
+/**
+ * 获取ASN单关联关系
+ * @param code
+ */
+export function listAsn(code:string) {
+  return request({
+    url: '/api/erp/returned/asn/relevance',
+    method: 'get',
+    params: { code }
+  })
+}
+
+/**
+ * 获取退货详情
+ * @param returnedNo
+ */
+export function getReturnedByExpress(returnedNo:String){
+  return request({
+    url: `/api/erp/returned/info`,
+    method: 'GET',
+    params: { returnedNo }
+  })
+}
+
+
+/**
+ * 匹配发货单号
+ * @param returnedNo
+ * @returns {Object}
+ */
+export function matchOrderBy(returnedNo) {
+  return request({
+    url: `/api/erp/returned/match-order/${returnedNo}`,
+    method: 'GET'
+  })
+}
+
+/**
+ * 匹配asn单号
+ * @param returnedNo
+ * @returns {Object}
+ */
+export function matchAsnBy(returnedNo) {
+  return request({
+    url: `/api/erp/returned/match-asn/${returnedNo}`,
+    method: 'GET'
+  })
+}
+
+/**
+ * 匹配快递单号
+ * @param packageCode
+ */
+export function matchCarrierCode(packageCode){
+  return request({
+    url: '/api/erp/returned/match-carrier-code',
+    method: 'GET',
+    params: { packageCode }
+  })
+}
+
+/**
+ * 退货登记
+ * @param body Object
+ */
+export function register(body){
+  return request({
+    url: '/api/erp/returned/register',
+    headers: { 'Content-Type': 'multipart/form-data; boundary=---011000010111000001101001' },
+    method: 'POST',
+    data: body
+  })
+}

+ 1 - 1
src/main.ts

@@ -17,6 +17,6 @@ app.use(Vant)
 app.use(Loading)
 app.use(router);
 const pinia = createPinia();
-app.use(pinia)
 pinia.use(piniaPersist);
+app.use(pinia)
 app.mount('#app')

+ 6 - 0
src/router/index.ts

@@ -109,6 +109,12 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'移库上架'},
     component: () => import('@/views/transfer/move/putaway/index.vue')
   },
+  {
+    path: '/returned-register',
+    name: 'ReturnedRegister',
+    meta:{title:'退货登记'},
+    component: () => import('@/views/returned/register/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 1 - 1
src/static/setting.txt

@@ -1 +1 @@
-34
+66

+ 24 - 0
src/types/returned.ts

@@ -0,0 +1,24 @@
+/**
+ * 查询
+ * @param barcode  条码
+ * @param ownerCode  货主编码
+ */
+export interface searchBarcodeParams {
+  barcode: string;
+  ownerCode?: string;
+}
+
+
+/**
+ * 校验日期
+ * @param newDate  日期
+ * @param filedName  参数
+ * @param ownerCode  货主编码
+ * @param sku  商品sku
+ */
+export interface validateParams {
+  newDate: string;
+  filedName: string;
+  ownerCode: string;
+  sku: string;
+}

+ 3 - 3
src/utils/loading.ts

@@ -1,14 +1,14 @@
 import { closeToast, showLoadingToast } from 'vant'
 
-export function showLoading() {
+export function showLoading(message='数据加载中...') {
     showLoadingToast({
     duration: 0,
     forbidClick: true,
-    message: '数据加载中...',
+    message: message,
   });
 }
 
 // 关闭加载提示
 export function closeLoading() {
-  closeToast()
+    closeToast()
 }

+ 3 - 1
src/utils/request.ts

@@ -19,7 +19,9 @@ service.interceptors.request.use(
     config.headers['Source'] = 'app'
     config.headers['Version'] = 'V6'
     config.headers['timestamp'] = JSON.stringify(Date.now())
-    config.headers['Content-Type'] = 'application/json'
+    if (!config.headers['Content-Type']) {
+      config.headers['Content-Type'] = 'application/json'
+    }
     return config;
   },
   (error:any) => {

+ 21 - 0
src/utils/returned.ts

@@ -0,0 +1,21 @@
+const status = [
+  { name: '正品', isGenuine: '正品', isGenuineValue: 0, qualityMark: 0 },
+  { name: '正品(修)', isGenuine: '正品', isGenuineValue: 0, qualityMark: 5 },
+  { name: '次品', isGenuine: '次品', isGenuineValue: 1, qualityMark: 0 },
+  { name: '待修复', isGenuine: '次品', isGenuineValue: 1, qualityMark: 4 },
+  { name: '待处理', isGenuine: '未知', isGenuineValue: 2, qualityMark: 0 },
+  { name: '机损', isGenuine: '机损', isGenuineValue: 3, qualityMark: 0 },
+  { name: '微瑕', isGenuine: '微瑕', isGenuineValue: 4, qualityMark: 0 }
+]
+
+export function getStatus(code:string){
+
+  const list = status.filter(item=>{
+    const {name} = item
+    return code === name
+  })
+  if (list && list.length > 0) {
+    return list[0]
+  }
+  return  {}
+}

+ 1 - 0
src/views/index.vue

@@ -14,6 +14,7 @@
     <div class="home" @click="onRouter('check-activity')">复核-活动单</div>
     <div class="home" @click="onRouter('check-large')">复核-大件单</div>
     <div class="home" @click="onRouter('move-list')">调拨-移库</div>
+    <div class="home" @click="onRouter('returned-register')">退货-登记</div>
   </div>
 </template>
 <script setup>

+ 2188 - 0
src/views/returned/register/index.vue

@@ -0,0 +1,2188 @@
+<template>
+  <div class="container">
+    <van-nav-bar
+      title="退货登记"
+      left-arrow
+      @click-left="goBack"
+      @click-right="init"
+    >
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div class="left-btn">返回</div>
+      </template>
+      <template #right>
+        <div class="nav-right right-btn">重置</div>
+      </template>
+    </van-nav-bar>
+
+    <div class="content">
+      <div v-if="showInitialPage" class="init-container">
+        <div class="content-tips">
+          <div style="flex: 1">
+            <van-notice-bar left-icon="volume-o">请扫描退回单号</van-notice-bar>
+          </div>
+        </div>
+        <div class="scan-returned-content">
+          <div class="input-group">
+            <van-field
+              ref="scan-express-no-input"
+              autofocus
+              v-model="expressNo"
+              autocomplete="off"
+              placeholder="输入快递单号"
+              clearable
+              @keydown.enter="inputExpressNo"
+            >
+            </van-field>
+          </div>
+          <div class="button-group">
+            <van-button
+              @click="inputExpressNo"
+              style="width: 100%"
+              type="primary"
+              class="confirm-btn"
+              >确认
+            </van-button>
+          </div>
+        </div>
+      </div>
+
+      <div v-else>
+        <van-tabs>
+          <van-tab title="退货信息">
+            <div>
+              <div class="content-tips">
+                <div style="flex: 1">
+                  <van-notice-bar color="#1989fa" background="#ecf9ff"
+                    >{{ ownerQualityInspection }}
+                  </van-notice-bar>
+                </div>
+              </div>
+            </div>
+            <van-cell-group inset style="margin-top: 10px">
+              <van-field
+                v-model="params.returnNo"
+                label="快递单号"
+                readonly
+                placeholder="请输入快递单号"
+              />
+
+              <van-field
+                readonly
+                clickable
+                label="承运商"
+                placeholder="选择承运商"
+                :model-value="getLogisticName()"
+                @click="logisticPickerShow = true"
+              />
+              <van-popup
+                v-model:show="logisticPickerShow"
+                position="bottom"
+                destroy-on-close
+              >
+                <van-picker
+                  :columns="logistics"
+                  @cancel="logisticPickerShow = false"
+                  @confirm="selectedLogistic"
+                />
+              </van-popup>
+
+              <van-field
+                readonly
+                clickable
+                label="仓库"
+                placeholder="选择仓库"
+                :model-value="getWarehouseName()"
+                @click="warehousePickerShow = true"
+              />
+              <van-popup
+                v-model:show="warehousePickerShow"
+                position="bottom"
+                destroy-on-close
+              >
+                <van-picker
+                  :columns="warehouses"
+                  @cancel="warehousePickerShow = false"
+                  @confirm="selectedWarehouse"
+                />
+              </van-popup>
+
+              <van-field
+                readonly
+                clickable
+                label="货主"
+                placeholder="选择货主"
+                :model-value="getOwnerName(params.ownerCode)"
+                @click="showOwnerSelectFunc"
+              />
+
+              <van-field
+                readonly
+                clickable
+                label="店铺"
+                placeholder="选择店铺"
+                v-model="params.storeName"
+                @click="storePickerShow = true"
+              />
+              <van-popup
+                v-model:show="storePickerShow"
+                position="bottom"
+                destroy-on-close
+              >
+                <van-picker
+                  :columns="storeOptions"
+                  @cancel="storePickerShow = false"
+                  @confirm="selectedStore"
+                />
+              </van-popup>
+
+              <van-field
+                v-model="params.orderUpstream"
+                label="上游平台"
+                placeholder="上游平台"
+              />
+              <van-field
+                v-model="params.arrivalPayment"
+                label="到付费用"
+                placeholder="到付费用"
+              />
+
+              <van-field
+                v-model="params.remark"
+                rows="1"
+                autosize
+                label="备注"
+                type="textarea"
+                placeholder="备注"
+              />
+            </van-cell-group>
+
+            <van-cell-group inset style="margin-top: 10px">
+              <van-field
+                :model-value="params.originalNo"
+                label="是否原单"
+                readonly
+                placeholder="是否原单"
+              />
+              <van-field
+                v-model="params.upstreamNo"
+                label="客户订单号"
+                placeholder="客户订单号"
+              />
+
+              <van-field
+                v-model="params.asnNo"
+                label="ASN单号"
+                readonly
+                placeholder="ASN单号"
+              />
+
+              <van-field
+                v-model="params.buyerName"
+                label="客户姓名"
+                readonly
+                placeholder="客户姓名"
+              />
+
+              <van-field
+                v-model="params.buyerPhone"
+                label="电话号码"
+                readonly
+                placeholder="电话号码"
+              />
+            </van-cell-group>
+
+            <van-cell-group inset style="margin-top: 20px; margin-bottom: 50px">
+              <van-button type="primary" block @click="submit">提交</van-button>
+            </van-cell-group>
+          </van-tab>
+          <van-tab title="商品信息" class="returned-detail-list">
+            <div>
+              <div class="content-tips">
+                <div style="flex: 1">
+                  <van-notice-bar color="#1989fa" background="#ecf9ff"
+                    >{{ ownerQualityInspection }}
+                  </van-notice-bar>
+                </div>
+              </div>
+            </div>
+            <template v-if="!params.details || params.details.length === 0">
+              <van-empty description="暂无信息请进行录入" />
+            </template>
+
+            <template v-for="(item, index) in params.details">
+              <div class="card-div">
+                <div class="card-div-content">
+                  <div class="info-row">
+                    <div class="info-label">sku</div>
+                    <div class="info-value">{{ item.sku }}</div>
+                    <div class="info-label">质量状态</div>
+                    <div class="info-value">
+                      <van-tag :color="getTagColor(item.qualityStatus)">
+                        {{ item.qualityStatus }}
+                      </van-tag>
+                    </div>
+                  </div>
+                  <div class="info-row">
+                    <div class="info-label">商品编号</div>
+                    <div class="info-value">{{ item.barCode }}</div>
+                    <div class="info-label">商品名称</div>
+                    <div class="info-value">
+                      <van-text-ellipsis
+                        :content="item.tradeName"
+                        rows="1"
+                        expand-text="展开"
+                        collapse-text="收起"
+                      />
+                    </div>
+                  </div>
+
+                  <div class="info-row">
+                    <div class="info-label">生产日期</div>
+                    <div class="info-value">
+                      {{ item.manufactureTime }}
+                    </div>
+                    <div class="info-label">失效日期</div>
+                    <div class="info-value">{{ item.validityTime }}</div>
+                  </div>
+
+                  <div class="info-row">
+                    <div class="info-label">批次号</div>
+                    <div class="info-value">{{ item.batchNumber }}</div>
+                    <div class="info-label">数量</div>
+                    <div class="info-value">{{ item.number }}</div>
+                  </div>
+                </div>
+                <div class="card-div-footer">
+                  <div class="product-description">
+                    {{ item.remark }}
+                  </div>
+                </div>
+
+                <template v-if="hasBoxItems(item)">
+                  <div>
+                    <van-divider content-position="left">外箱图</van-divider>
+                    <van-row>
+                      <template v-for="(i, imgIndex) in getBoxItems(item)">
+                        <van-col span="4">
+                          <van-image
+                            :key="`box-photos-${index}-${imgIndex}`"
+                            @click="
+                              previewImages(item.boxPhotos, imgIndex, '外箱图')
+                            "
+                            width="100%"
+                            height="50"
+                            :src="i.src"
+                          />
+                        </van-col>
+                      </template>
+                    </van-row>
+                  </div>
+                </template>
+
+                <template v-if="hasProductItems(item)">
+                  <div>
+                    <van-divider content-position="left">内物图</van-divider>
+                    <van-row>
+                      <template v-for="(i, imgIndex) in getProductItems(item)">
+                        <van-col span="4">
+                          <van-image
+                            :key="`product-photos-${index}-${imgIndex}`"
+                            @click="
+                              previewImages(
+                                item.productPhotos,
+                                imgIndex,
+                                '内物图',
+                              )
+                            "
+                            width="100%"
+                            height="60"
+                            :src="i.src"
+                          />
+                        </van-col>
+                      </template>
+                    </van-row>
+                  </div>
+                </template>
+
+                <div class="card-div-footer-options">
+                  <div class="options-row">
+                    <div class="info-value">
+                      <van-button
+                        size="mini"
+                        type="danger"
+                        block
+                        plain
+                        @click="removeDetails(index)"
+                        >删除
+                      </van-button>
+                    </div>
+                    <div class="info-value">
+                      <van-button
+                        size="mini"
+                        type="default"
+                        block
+                        plain
+                        @click="editDetails(index)"
+                        >编辑
+                      </van-button>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </template>
+
+            <van-floating-bubble
+              axis="xy"
+              icon="add"
+              magnetic="x"
+              @click="showScancode"
+            />
+          </van-tab>
+        </van-tabs>
+      </div>
+    </div>
+    <van-dialog
+      v-model:show="scancodeDialog"
+      title="扫描条码"
+      @open="openScanCode"
+      @confirm="showQualityStatus"
+      show-cancel-button
+    >
+      <van-field
+        ref="scancodeInputRef"
+        v-model="scancode"
+        label="商品条码"
+        autocomplete="off"
+        @keyup.enter="showQualityStatus"
+        placeholder="商品条码"
+      />
+    </van-dialog>
+
+    <van-dialog
+      v-model:show="qualityStatusDialog"
+      title="质量状态"
+      @confirm="queryBarcode"
+      show-cancel-button
+    >
+      <van-radio-group v-model="qualityStatus">
+        <template v-for="(item, index) in qualityStatusOptions">
+          <van-cell
+            :title="item.text"
+            clickable
+            @click="qualityStatus = item.value"
+          >
+            <template #right-icon>
+              <van-radio :name="item.value" />
+            </template>
+          </van-cell>
+        </template>
+      </van-radio-group>
+    </van-dialog>
+
+    <van-dialog
+      v-model:show="returnedDetailDialog"
+      title="商品详情"
+      show-cancel-button
+      :lazy-render="true"
+      :show-confirm-button="checkUploadImages()"
+      @confirm="addDetails"
+      @cancel="cancelReturnedDetailDialog"
+    >
+      <div style="max-height: 70vh; overflow-y: auto">
+        <van-field
+          v-model="selectDetail.sku"
+          label="SKU"
+          placeholder="SKU"
+          clearable
+        />
+        <van-field
+          readonly
+          v-model="selectDetail.barCode"
+          label="商品条码"
+          placeholder="商品条码"
+          clearable
+        />
+        <van-field
+          v-model="selectDetail.tradeName"
+          label="商品名称"
+          placeholder="商品名称"
+          readonly
+        />
+
+        <van-field
+          readonly
+          clickable
+          label="质量状态"
+          placeholder="质量状态"
+          v-model="selectDetail.qualityStatus"
+          @click="selectedDetailQualityStatus = true"
+        />
+        <van-popup
+          v-model:show="selectedDetailQualityStatus"
+          position="bottom"
+          destroy-on-close
+        >
+          <van-picker
+            :columns="qualityStatusOptions"
+            @cancel="selectedDetailQualityStatus = false"
+            @confirm="selectedDetailQualityStatusFunc"
+          />
+        </van-popup>
+
+        <van-field
+          is-link
+          readonly
+          name="datePicker"
+          label="生产日期"
+          :placeholder="selectDetail.manufactureTime ? '' : '请选择生产日期'"
+          :model-value="formatDateDisplay(selectDetail.manufactureTime)"
+          @click="showManufactureTime = true"
+        />
+        <van-popup
+          v-model:show="showManufactureTime"
+          destroy-on-close
+          position="bottom"
+        >
+          <van-date-picker
+            :max-date="maxDate"
+            :min-date="minDate"
+            :model-value="parseDateValue(selectDetail.manufactureTime)"
+            @confirm="manufactureTimeConfirm"
+            @cancel="showManufactureTime = false"
+          />
+        </van-popup>
+
+        <van-field
+          :model-value="formatDateDisplay(selectDetail.validityTime)"
+          is-link
+          readonly
+          name="datePicker"
+          label="失效日期"
+          :placeholder="selectDetail.validityTime ? '' : '请选择失效日期'"
+          @click="showValidityTime = true"
+        />
+        <van-popup
+          v-model:show="showValidityTime"
+          destroy-on-close
+          position="bottom"
+        >
+          <van-date-picker
+            :max-date="maxDate"
+            :min-date="minDate"
+            :model-value="parseDateValue(selectDetail.validityTime)"
+            @confirm="validityTimeConfirm"
+            @cancel="showValidityTime = false"
+          />
+        </van-popup>
+
+        <van-field
+          autocomplete="off"
+          v-model="selectDetail.batchNumber"
+          label="批次号"
+          placeholder="批次号"
+          clearable
+        />
+
+        <van-field
+          v-model="selectDetail.number"
+          label="数量"
+          type="digit"
+          placeholder="数量"
+          clearable
+        />
+
+        <van-field
+          v-model="selectDetail.remark"
+          label="备注"
+          placeholder="备注"
+          label-align="top"
+        />
+
+        <van-row v-if="showAccessories">
+          <div style="font-size: 12px">
+            <template v-for="(item, index) in accessories">
+              <p>
+                配件条码: [<span style="color: #2ca547">{{
+                  item.accessory
+                }}</span
+                >] [<span style="color: #277b39">{{ item.descrC }}</span
+                >] 数量: [<span style="color: #2ca547">{{ item.qty }}</span
+                >]件
+              </p>
+            </template>
+            <p style="font-size: 12px">
+              请检查商品: 【<span style="color: #ff2020">{{
+                selectDetail.sku
+              }}</span
+              >】 {{ selectDetail.tradeName }} 配件
+            </p>
+          </div>
+        </van-row>
+
+        <template
+          v-if="selectDetail.boxPhotos && selectDetail.boxPhotos.length > 0"
+        >
+          <van-divider content-position="left" style="margin: 0px"
+            >外箱图
+          </van-divider>
+          <van-row>
+            <template
+              v-if="selectDetail.boxPhotos && selectDetail.boxPhotos.length > 0"
+            >
+              <template v-for="(item, index) in selectDetail.boxPhotos">
+                <van-col span="4">
+                  <van-image
+                    :key="`box-photos-${index}`"
+                    width="100%"
+                    height="50"
+                    :src="getImageUrl(item, '外箱图')"
+                    @click="showBoxImagePreview(index)"
+                  />
+                </van-col>
+              </template>
+
+              <van-image-preview
+                v-model:show="showBoxPreview"
+                :images="detailBoxImages"
+                :start-position="startBoxPosition"
+                @change="onBoxPreviewChange"
+                closeable
+              >
+                <template #index>
+                  <div class="custom-toolbar">
+                    <span
+                      >{{ startPhotosPosition + 1 }}/{{
+                        selectDetail.boxPhotos.length
+                      }}</span
+                    >
+                    <van-button
+                      icon="delete"
+                      type="danger"
+                      @click.stop="handleBoxDelete"
+                      size="mini"
+                      >删除
+                    </van-button>
+                  </div>
+                </template>
+              </van-image-preview>
+            </template>
+          </van-row>
+        </template>
+
+        <template
+          v-if="
+            selectDetail.productPhotos && selectDetail.productPhotos.length > 0
+          "
+        >
+          <van-divider content-position="left" style="margin: 0px"
+            >内物图
+          </van-divider>
+          <van-row>
+            <template v-for="(item, index) in selectDetail.productPhotos">
+              <van-col span="4">
+                <van-image
+                  :key="`product-photos-${index}`"
+                  width="100%"
+                  height="50"
+                  :src="getImageUrl(item, '内物图')"
+                  @click="showPhotosImagePreview(index)"
+                />
+              </van-col>
+            </template>
+          </van-row>
+
+          <van-image-preview
+            v-model:show="showPhotosPreview"
+            :images="detailProductImages"
+            :start-position="startPhotosPosition"
+            @change="onPhotosPreviewChange"
+            closeable
+          >
+            <template #index>
+              <div class="custom-toolbar">
+                <span>
+                  {{ startPhotosPosition + 1 }}/{{
+                    selectDetail.productPhotos.length
+                  }}
+                </span>
+                <van-button
+                  icon="delete"
+                  type="danger"
+                  @click.stop="handlePhotosDelete"
+                  size="mini"
+                  >删除
+                </van-button>
+              </div>
+            </template>
+          </van-image-preview>
+        </template>
+
+        <van-row>
+          <van-col span="12">
+            <van-button
+              type="primary"
+              block
+              @click="invokeCameraToCapture('外箱图')"
+              >外箱图录入
+            </van-button>
+          </van-col>
+          <van-col span="12">
+            <van-button
+              type="primary"
+              block
+              @click="invokeCameraToCapture('内物图')"
+              >内物图录入
+            </van-button>
+          </van-col>
+        </van-row>
+
+        <input
+          type="file"
+          id="outer-carton-box-input"
+          capture="user"
+          accept="image/*"
+          hidden
+          @change="outerCartonInput"
+        />
+
+        <input
+          type="file"
+          id="inner-contents-input"
+          capture="user"
+          accept="image/*"
+          hidden
+          @change="innerContentsInput"
+        />
+      </div>
+    </van-dialog>
+
+    <van-popup
+      v-model:show="showOwnerSelect"
+      destroy-on-close
+      round
+      position="bottom"
+    >
+      <van-picker
+        :model-value="getOwnerName(params.ownerCode)"
+        :columns="ownerSelectedOptions"
+        @cancel="showOwnerSelect = false"
+        @confirm="selectOwner"
+      />
+    </van-popup>
+    <owner ref="ownerRef" @onOwner="onOwner" />
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, nextTick, onMounted } from 'vue'
+import {
+  showFailToast,
+  showNotify,
+  showLoadingToast,
+  closeToast,
+  showConfirmDialog,
+  showImagePreview
+} from 'vant'
+import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import {
+  deleteDetails,
+  getQualityInspection,
+  getQualityInspectionBy,
+  getQualityStatus,
+  getReturnedByExpress,
+  getTagColorBy,
+  listAsn,
+  matchAsnBy,
+  matchCarrierCode,
+  matchOrderBy,
+  register,
+  searchBarcode,
+  searchOwnerBarcode,
+  shops,
+  validateDate
+} from '@/api/returned/index.ts'
+import { carrierOptions, getOwner, getWarehouse } from '@/api/basic/index.ts'
+import { useStore } from '@/store/modules/user'
+import { getStatus } from '@/utils/returned.ts'
+import Owner from '@/components/Owner.vue'
+
+try {
+  getHeader()
+} catch (error) {
+  console.log(error)
+}
+
+const minDate = ref(new Date(new Date().getFullYear() - 5, 0, 1))
+const maxDate = ref(new Date((new Date().getFullYear() + 50, 0, 1)))
+
+const showInitialPage = ref(true)
+const expressNo = ref(null)
+const store = useStore()
+const warehouse = store.warehouse
+// 当前选择的货主
+
+const title = computed(() => {
+  if (expressNo.value && showInitialPage.value === false) {
+    return '退货登记:' + params.value.returnNo
+  }
+  return '退货登记'
+})
+
+const qualityStatusOptions = ref([])
+// 货主
+const owners = ref([])
+// 仓库
+const warehouses = ref([])
+// 承运商
+const logistics = ref([])
+// 店铺
+const storeOptions = ref([])
+// 选中相关信息的下标
+const selectDetailIndex = ref(-1)
+
+const ownerMap = ref({})
+
+const warehousesMap = ref({})
+const logisticsMap = ref({})
+
+const asnList = ref({})
+// 外箱图
+const boxFiles = ref([])
+// 内物图
+const productFiles = ref([])
+
+onMounted(() => {
+  const now = new Date()
+  const currentYear = now.getFullYear()
+  const currentCentury = Math.floor(currentYear / 100) * 100
+  const endOfCenturyYear = currentCentury + 99
+
+  maxDate.value = new Date(endOfCenturyYear, 0, 1)
+  minDate.value = new Date(currentCentury, 0, 1)
+
+  console.log(warehouse)
+
+  getOwner().then((res) => {
+    const { data } = res
+    owners.value = data.map((item) => {
+      return { value: item.code, text: item.name }
+    })
+    const map = {}
+    data.forEach((item) => {
+      map[item.code] = item.name
+    })
+    ownerMap.value = map
+  })
+  getQualityStatus().then((res) => {
+    const { data } = res
+    qualityStatusOptions.value = data
+      .filter((item) => {
+        return item !== '机损'
+      })
+      .map((item) => {
+        return { value: item, text: item }
+      })
+  })
+  getWarehouse().then((res) => {
+    const { data } = res
+    warehouses.value = data.map((item) => {
+      return { value: item.code, text: item.name }
+    })
+    const map = {}
+    data.forEach((item) => {
+      map[item.code] = item.name
+    })
+    warehousesMap.value = map
+  })
+
+  carrierOptions().then((res) => {
+    const { data } = res
+    logistics.value = data.map((item) => {
+      return { value: item.name, text: item.name }
+    })
+    const map = {}
+    data.forEach((item) => {
+      map[item.name] = item.name
+    })
+    logisticsMap.value = map
+  })
+})
+
+// 商品外箱图
+function hasBoxItems(detail) {
+  const { boxPhotos } = detail
+  if (!boxPhotos || boxPhotos.length === 0) {
+    return false
+  }
+  return boxFiles.value.some((item) => {
+    return boxPhotos.includes(item.fileName)
+  })
+}
+
+// 商品外箱图
+function getBoxItems(detail) {
+  const { boxPhotos } = detail
+  if (!boxPhotos || boxPhotos.length === 0) {
+    return []
+  }
+  return boxFiles.value.filter((item) => boxPhotos.includes(item.fileName))
+}
+
+// 商品外箱图
+function hasProductItems(detail) {
+  const { productPhotos } = detail
+  if (!productPhotos || productPhotos.length === 0) {
+    return false
+  }
+  return productFiles.value.some((item) => {
+    return productPhotos.includes(item.fileName)
+  })
+}
+
+// 商品内物图
+function getProductItems(detail) {
+  const { productPhotos } = detail
+  if (!productPhotos || productPhotos.length === 0) {
+    return []
+  }
+  return productFiles.value.filter((item) => {
+    return productPhotos.includes(item.fileName)
+  })
+}
+
+// 获取对应的店铺信息
+async function getStoreOptionsBy(owner) {
+  const results = await shops(owner)
+  const { data } = results
+  if (!data || data.length === 0) {
+    storeOptions.value = []
+    return
+  }
+  storeOptions.value = data.map((item) => {
+    return { value: item.name, text: item.name }
+  })
+}
+
+const qualityInspection = ref(null)
+
+// 初始化质检状态
+function initQualityInspection(owner) {
+  getQualityInspection(owner).then((res) => {
+    qualityInspection.value = res.data
+  })
+}
+
+const ownerQualityInspection = computed(() => {
+  return getQualityInspectionBy(qualityInspection.value)
+})
+
+// 扫描商品条码
+const scancode = ref('')
+// 商品质量状态
+const qualityStatus = ref('')
+// 提交的商品信息
+const params = ref({
+  id: null,
+  returnNo: null,
+  warehouseCode: warehouse,
+  logisticsName: null,
+  storeName: null,
+  upstreamNo: null,
+  ownerCode: null,
+  orderUpstream: null,
+  orderUpSteam: null,
+  arrivalPayment: null,
+  buyerName: null,
+  asnNo: null,
+  buyerPhone: null,
+  originalNo: null,
+  remark: null,
+  details: []
+})
+
+// 商品信息
+const selectDetail = ref({
+  id: null,
+  rejectHeadId: null,
+  rejectPushTaskNo: null,
+  status: null,
+  detailStatus: null,
+  isGenuine: null,
+  qualityMark: null,
+  sku: '',
+  barCode: '',
+  tradeName: '',
+  qualityStatus: '',
+  manufactureTime: '',
+  validityTime: '',
+  batchNumber: '',
+  remark: '',
+  asnNo: null,
+  number: 0,
+  warehouse: null,
+  warehouseBin: null,
+  boxPhotos: [],
+  productPhotos: [],
+  files: null,
+  pieceTag: null,
+  createTime: null,
+  creatorId: null,
+  updateTime: null,
+  updaterId: null,
+  deleteTime: null,
+  version: null,
+  repairableType: null,
+  repairableTypes: null,
+  operatorId: null,
+  operatorName: null,
+  operatorTime: null,
+  skuImage: null,
+  orginSkuImage: null
+})
+
+function initDetail() {
+  selectDetail.value = {
+    id: null,
+    rejectHeadId: null,
+    rejectPushTaskNo: null,
+    status: null,
+    detailStatus: null,
+    isGenuine: null,
+    qualityMark: null,
+    sku: '',
+    barCode: '',
+    tradeName: '',
+    qualityStatus: '',
+    manufactureTime: '',
+    validityTime: '',
+    batchNumber: '',
+    remark: '',
+    asnNo: null,
+    number: null,
+    warehouse: null,
+    warehouseBin: null,
+    boxPhotos: [],
+    productPhotos: [],
+    files: null,
+    pieceTag: null,
+    createTime: null,
+    creatorId: null,
+    updateTime: null,
+    updaterId: null,
+    deleteTime: null,
+    version: null,
+    repairableType: null,
+    repairableTypes: null,
+    operatorId: null,
+    operatorName: null,
+    operatorTime: null,
+    skuImage: null,
+    orginSkuImage: null
+  }
+}
+
+// 承运商选择
+const logisticPickerShow = ref(false)
+const selectedLogistic = (row) => {
+  const { selectedOptions } = row
+  logisticPickerShow.value = false
+  params.value.logisticsName = selectedOptions[0].text
+}
+
+// 货主选择
+const ownerPickerShow = ref(false)
+const ownerName = ref('')
+const selectedOwner = (row) => {
+  const { selectedValues, selectedOptions } = row
+  ownerPickerShow.value = false
+  params.value.ownerCode = selectedOptions[0].value
+  ownerName.value = selectedOptions[0].text
+  initQualityInspection(params.value.ownerCode)
+}
+
+// 仓库选择
+const warehousePickerShow = ref(false)
+const selectedWarehouse = (row) => {
+  const { selectedOptions } = row
+  warehousePickerShow.value = false
+  params.value.warehouseCode = selectedOptions[0].value
+}
+
+// 店铺选择
+const storePickerShow = ref(false)
+const selectedStore = (row) => {
+  const { selectedOptions } = row
+  storePickerShow.value = false
+  params.value.storeName = selectedOptions[0].text
+}
+
+// 承运商名称
+function getLogisticName() {
+  return params.value.logisticsName
+}
+
+// 货主名
+function getOwnerName(owner) {
+  return ownerMap.value[owner]
+}
+
+// 仓库名
+function getWarehouseName() {
+  console.log(params.value.warehouseCode)
+  return warehousesMap.value[params.value.warehouseCode]
+}
+
+// 输入快递单号
+function inputExpressNo() {
+  const no = expressNo.value
+  const isSuccess = checkExpressNo(no)
+  if (!isSuccess) {
+    return
+  }
+  const express_no = initExpressNo(no)
+  if (!express_no || express_no.length === 0) {
+    showFailToast('快递单号异常')
+    scanError()
+    return
+  }
+  params.value.returnNo = express_no
+  params.value.warehouseCode = warehouse
+  listAsn(express_no).then((res) => {
+    if (res.data) {
+      asnList.value = res.data
+    }
+  })
+  matchReturned(express_no)
+}
+
+function matchReturned(express_no) {
+  getReturnedByExpress(express_no).then((res) => {
+    const { data } = res
+    if (!data) {
+      matchOrder(express_no)
+      return
+    }
+    const { id, headStatus, returnNo, warehouseCode } = data
+    if (!id) {
+      matchOrder(express_no)
+      expressNo.value = null
+      showInitialPage.value = false
+      return
+    }
+    if (id) {
+      if (['已入仓', '已拆包'].includes(headStatus)) {
+        showNotify({
+          type: 'primary',
+          message: `当前退回单号${returnNo}--${headStatus}`
+        })
+        init(false)
+        params.value.id = id
+        params.value.returnNo = returnNo
+        params.value.warehouseCode = warehouseCode
+        params.value.warehousingStatus = null
+        selectDetailIndex.value = -1
+        matchOrder(express_no)
+        expressNo.value = null
+        showInitialPage.value = false
+        scanSuccess()
+      } else {
+        showNotify({
+          type: 'danger',
+          message: '当前退回单号已进行过登记 如需修改请前往PC端进行'
+        })
+        scanSuccess()
+      }
+    }
+  })
+}
+
+// 匹配原单信息
+function matchOrder(expressNo) {
+  matchOrderBy(expressNo).then((res) => {
+    const { data } = res
+    if (!data) {
+      console.log('未匹配到订单信息')
+      matchAsn(expressNo)
+      return
+    }
+    console.log('匹配到订单信息')
+    if (data) {
+      const {
+        upstreamNo,
+        shopName,
+        ownerCode,
+        recipientName,
+        phone,
+        logisticName,
+        details
+      } = data
+      showNotify({ type: 'success', message: '匹配到原单信息' })
+      params.value.upstreamNo = upstreamNo
+      params.value.ownerCode = ownerCode
+      params.value.warehouseCode = warehouse
+      params.value.storeName = shopName
+      params.value.logisticsName = logisticName
+      params.value.buyerName = handlerLongText(recipientName)
+      params.value.buyerPhone = handlerLongText(phone)
+      params.value.originalNo = '原单退回'
+      params.value.sendNo = null
+      initQualityInspection(params.value.ownerCode)
+      getStoreOptionsBy(ownerCode)
+      if (!details || details.length === 0) {
+        return
+      }
+      const list = []
+      details.forEach((item) => {
+        const {
+          sku,
+          barCode,
+          detailAmount,
+          name,
+          productionDate,
+          expirationDate,
+          batchNumber,
+          quality,
+          attributeBin
+        } = item
+        let qualityStatus
+        if (quality) {
+          if (quality === 'ZP') {
+            qualityStatus = '正品'
+            qualityStatus = '正品'
+          } else if (quality === 'CP') {
+            qualityStatus = '次品'
+          } else {
+            qualityStatus = '正品'
+          }
+        } else {
+          qualityStatus = '正品'
+        }
+        list.push({
+          sku: sku,
+          tradeName: name,
+          barCode: barCode,
+          number: detailAmount,
+          manufactureTime: productionDate,
+          validityTime: expirationDate,
+          batchNumber: batchNumber,
+          warehouse: attributeBin,
+          qualityStatus: qualityStatus,
+          isOriginal: true
+        })
+        params.value.details = list
+        matchCarrier(expressNo)
+      })
+    } else {
+      matchAsn(expressNo)
+    }
+  })
+}
+
+function matchCarrier(expressNo) {
+  matchCarrierCode(expressNo).then((res) => {
+    const { data } = res
+    if (!data || data.length === 0) {
+      showNotify({
+        type: 'primary',
+        message: '请手动选择承运商'
+      })
+    } else {
+      params.value.logisticsName = data
+    }
+  })
+}
+
+// 转化加密
+function handlerLongText(text) {
+  return text && text.length > 15 ? '加密' : text ? text : null
+}
+
+// 匹配对应的ASN
+function matchAsn(expressNo) {
+  matchAsnBy(expressNo).then((res) => {
+    const { data } = res
+    if (!data) {
+      console.log('未匹配asn单信息')
+      matchCarrier(expressNo)
+      return
+    }
+    const {
+      ownerCode,
+      phone,
+      shopName,
+      recipientName,
+      logisticName,
+      upstreamNo
+    } = data
+    params.value.returnNo = expressNo
+    params.value.upstreamNo = upstreamNo
+    params.value.ownerCode = ownerCode
+    params.value.warehouseCode = warehouse
+    params.value.storeName = shopName
+    params.value.logisticsName = logisticName
+    params.value.buyerName = handlerLongText(recipientName)
+    params.value.buyerPhone = handlerLongText(phone)
+    params.value.originalNo = null
+    params.value.sendNo = null
+    params.value.details = []
+    matchCarrier(expressNo)
+    getStoreOptionsBy(ownerCode)
+    initQualityInspection(params.value.ownerCode)
+    initDetail()
+  })
+}
+
+// 校验快递单号格式
+function checkExpressNo(no) {
+  if (!no || no.length === 0) {
+    showFailToast('单号长度异常')
+    scanError()
+    return false
+  }
+  if (no.startsWith('http')) {
+    showFailToast('扫描到了错误的条码')
+    scanError()
+    return false
+  }
+  if (no.endsWith('.com')) {
+    showFailToast('扫描到了错误的条码')
+    scanError()
+    return false
+  }
+  const length = no.length
+  if (length > 30 || length < 5) {
+    showFailToast('单号长度异常')
+    scanError()
+    return false
+  }
+  return true
+}
+
+// 处理快递单号相关问题
+function initExpressNo(no) {
+  // 处理扫描快递单号
+  if (!no) {
+    return no
+  }
+  if (no.includes('-')) {
+    no = no.substring(0, no.indexOf('-')) // 京东快递异常
+  }
+  return no
+    .replaceAll('-', '')
+    .replaceAll(' ', '')
+    .replaceAll('R02Z', '')
+    .replaceAll('R02T', '') // 圆通退回单号异常
+}
+
+// 重置提交 恢复到扫描快递单号页面
+function init(showInputPage = true) {
+  ownerName.value = null
+  clearImageFileCache()
+  params.value = {
+    orderUpstream: null,
+    returnNo: null,
+    warehouseCode: warehouse,
+    logisticsName: null,
+    storeName: null,
+    orderUpSteam: null,
+    arrivalPayment: null,
+    buyerName: null,
+    asnNo: null,
+    upstreamNo: null,
+    buyerPhone: null,
+    originalNo: null,
+    remark: null,
+    details: []
+  }
+  storeOptions.value = []
+  expressNo.value = null
+  showInitialPage.value = showInputPage
+  initDetail()
+}
+
+// 扫码弹框
+const scancodeDialog = ref(false)
+const scancodeInputRef = ref(false)
+
+const qualityStatusDialog = ref(false)
+
+// 扫描条码dialog
+function showScancode() {
+  scancodeDialog.value = true
+}
+
+function openScanCode() {
+  nextTick(() => {
+    setTimeout(() => {
+      const input = scancodeInputRef.value?.$el?.querySelector('input')
+      input?.focus()
+      input?.setSelectionRange(0, input.value.length)
+    }, 150)
+  })
+}
+
+// 选择质量状态
+function showQualityStatus() {
+  const barcode = scancode.value
+  if (!barcode) {
+    showFailToast('商品条码异常')
+    scancodeDialog.value = true
+    return
+  }
+  scanSuccess()
+  qualityStatusDialog.value = true
+  qualityStatus.value = '正品'
+}
+
+// 查询商品
+async function queryBarcode() {
+  showLoadingToast({
+    duration: 0,
+    forbidClick: true,
+    message: '查询商品信息中.....'
+  })
+  const barcode = scancode.value
+  const { ownerCode, details } = params.value
+  if (ownerCode) {
+    const result = await queryOwnerBarcode(ownerCode, barcode)
+    if (result) {
+      scanSuccess()
+      scancodeDialog.value = false
+      qualityStatusDialog.value = false
+      closeToast()
+      return
+    }
+  }
+  if (!details || details.length === 0) {
+    await queryBarcodeBy(barcode)
+    scancodeDialog.value = false
+    qualityStatusDialog.value = false
+    closeToast()
+  }
+}
+
+// 根据获取查询信息
+async function queryOwnerBarcode(ownerCode, barcode) {
+  try {
+    const results = await searchOwnerBarcode({ ownerCode, barcode })
+    const {
+      data: { basSku }
+    } = results
+    if (!basSku) {
+      return false
+    }
+    addDetailByBasSku(basSku)
+    return true
+  } catch (err) {
+    closeToast()
+    console.log(err.message)
+  }
+  return false
+}
+
+async function queryBarcodeBy(barcode) {
+  try {
+    const results = await searchBarcode({ barcode })
+    const { data } = results
+    const { basSku, ownerCodes } = data
+    if (ownerCodes && ownerCodes.length > 1) {
+      showOwnerSelectDialog(ownerCodes)
+      return null
+    }
+    if (!basSku) {
+      scanError()
+      return null
+    }
+    addDetailByBasSku(basSku)
+    scanSuccess()
+    return null
+  } catch (err) {
+    closeToast()
+    scanError()
+    console.log(err.message)
+  }
+  scanError()
+  return null
+}
+
+// 添加详情
+function addDetailByBasSku(basSku) {
+  const { sku, barCode, ownerCode, tradeName, accessories } = basSku
+  selectDetailIndex.value = -1
+  selectDetail.value.sku = sku
+  if (
+    !params.value.ownerCode ||
+    !params.value.details ||
+    params.value.details.length === 0
+  ) {
+    params.value.ownerCode = ownerCode
+    initQualityInspection(params.value.ownerCode)
+    getStoreOptionsBy(params.value.ownerCode)
+  }
+  selectDetail.value.barCode = barCode
+  selectDetail.value.qualityStatus = qualityStatus.value
+  selectDetail.value.tradeName = tradeName
+  selectDetail.value.number = 1
+  scancodeDialog.value = false
+  qualityStatusDialog.value = false
+  returnedDetailDialog.value = true
+  // 显示
+  showAccessoriesItems(accessories)
+}
+
+const accessories = ref([])
+const showAccessories = ref(false)
+
+function showAccessoriesItems(items) {
+  if (!items || items.length === 0) {
+    accessories.value = []
+    showAccessories.value = false
+    return
+  }
+  accessories.value = items
+  showAccessories.value = true
+}
+
+// 质量状态
+const returnedDetailDialog = ref(false)
+const selectedDetailQualityStatus = ref(false)
+const selectedDetailQualityStatusFunc = (row) => {
+  const { selectedOptions } = row
+  selectedDetailQualityStatus.value = false
+  selectDetail.value.qualityStatus = selectedOptions[0].text
+}
+
+// 辅助函数:格式化日期显示(可选)
+const formatDateDisplay = (dateString) => {
+  if (!dateString) return ''
+  return dateString.replace(/-/g, '/') // 将 2023-05-01 显示为 2023/05/01
+}
+// 辅助函数:将日期字符串转换为数组格式 [年, 月, 日]
+const parseDateValue = (dateString) => {
+  if (!dateString) return []
+  const [year, month, day] = dateString.split('-')
+  return [year, month, day]
+}
+
+// 生产日期
+const showManufactureTime = ref(false)
+const manufactureTimeConfirm = async (result) => {
+  if (!result.selectedValues) {
+    showManufactureTime.value = false
+    return
+  }
+  // 正确解构参数
+  const { selectedValues } = result
+  const [year, month, day] = selectedValues
+  const formatted_month = month.padStart(2, '0')
+  const formatted_day = day.padStart(2, '0')
+  const manufactureTime = `${year}-${formatted_month}-${formatted_day}`
+  const { sku } = selectDetail.value
+  const { ownerCode } = params.value
+  const check_validate_date = await checkValidateDate(
+    ownerCode,
+    sku,
+    manufactureTime,
+    'manufactureTime'
+  )
+  if (!check_validate_date) {
+    console.log('检验出现异常')
+    return
+  }
+  selectDetail.value.manufactureTime = manufactureTime
+  showManufactureTime.value = false
+}
+
+// 失效日期
+const showValidityTime = ref(false)
+const validityTimeConfirm = async (result) => {
+  if (!result.selectedValues) {
+    showValidityTime.value = false
+    return
+  }
+  // 正确解构参数
+  const { selectedValues } = result
+  const [year, month, day] = selectedValues
+  const formatted_month = month.padStart(2, '0')
+  const formatted_day = day.padStart(2, '0')
+  const validityTime = `${year}-${formatted_month}-${formatted_day}`
+  const { sku } = selectDetail.value
+  const { ownerCode } = params.value
+  const check_validate_date = await checkValidateDate(
+    ownerCode,
+    sku,
+    validityTime,
+    'validityTime'
+  )
+  if (!check_validate_date) {
+    return
+  }
+  selectDetail.value.validityTime = `${year}-${formatted_month}-${formatted_day}`
+  showValidityTime.value = false
+}
+
+// 质量状态标签
+function getTagColor(qualityStatus) {
+  return getTagColorBy(qualityStatus)
+}
+
+const cancelReturnedDetailDialog = async () => {
+  returnedDetailDialog.value = false
+  initDetail()
+}
+
+const addDetails = async () => {
+  const isCheck = checkUploadImages()
+  if (!isCheck) {
+    returnedDetailDialog.value = true
+    return false
+  }
+  const index = selectDetailIndex.value
+  const detail = JSON.parse(JSON.stringify(selectDetail.value))
+  if (index > -1) {
+    params.value.details[index] = detail
+    initDetail()
+    return true
+  }
+  const {
+    sku: detail_sku,
+    batchNumber: detail_batch_number,
+    validityTime: detail_validity_time,
+    manufactureTime: detail_manufacture_time,
+    qualityStatus: detail_quality_status,
+    number: detailNumber
+  } = selectDetail.value
+  if (['次品', '待修复'].includes(detail_quality_status)) {
+    params.value.details.push(detail)
+    initDetail()
+    return
+  }
+  const searchIndex = params.value.details.findIndex((item) => {
+    const { sku, batchNumber, qualityStatus, validityTime, manufactureTime } =
+      item
+    return (
+      sku === detail_sku &&
+      batchNumber === detail_batch_number &&
+      detail_validity_time === validityTime &&
+      detail_manufacture_time === manufactureTime &&
+      detail_quality_status === qualityStatus
+    )
+  })
+  if (searchIndex > -1) {
+    showConfirmDialog({
+      title: '系统提示',
+      message: '查询到有对应的商品详情是否进行合并'
+    })
+      .then(() => {
+        const { number } = params.value.details[searchIndex]
+        params.value.details[searchIndex].number =
+          Number(detailNumber) + Number(number)
+        showNotify({ type: 'success', message: '合并成功' })
+      })
+      .catch(() => {
+        params.value.details.push(detail)
+        showNotify({ type: 'success', message: '添加成功' })
+      })
+  } else {
+    params.value.details.push(detail)
+  }
+  initDetail()
+  return true
+}
+
+// 删除对应的详情
+function removeDetails(index) {
+  if (null === index) {
+    showFailToast('未找到对应的详情')
+    return
+  }
+  showConfirmDialog({
+    title: '系统提示',
+    message: '是否删除当前登记信息'
+  })
+    .then(() => {
+      const { id } = params.value.details[index]
+      if (!id) {
+        params.value.details.splice(index, 1)
+        showNotify({ type: 'success', message: '删除成功' })
+      } else {
+        deleteDetails(id).then((res) => {
+          if (res.data) {
+            params.value.details.splice(index, 1)
+            showNotify({ type: 'success', message: '删除成功' })
+          } else {
+            showNotify({ type: 'danger', message: '删除失败' })
+          }
+        })
+      }
+    })
+    .catch(() => {
+      showNotify({ type: 'primary', message: '取消删除' })
+    })
+}
+
+function selectOwner({ selectedValues }) {
+  const ownerCode = selectedValues[0]
+  params.value.ownerCode = ownerCode
+  showOwnerSelect.value = false
+  ownerSelectedOptions.value = []
+  getStoreOptionsBy(ownerCode)
+  queryOwnerBarcode(params.value.ownerCode, scancode.value)
+}
+
+const ownerRef = ref(null)
+
+function showOwnerSelectFunc() {
+  ownerRef.value?.show(1)
+}
+
+// 指定货主
+const onOwner = (item, type) => {
+  params.value.ownerCode = item.code
+  getStoreOptionsBy(item.code)
+}
+
+const showOwnerSelect = ref(false)
+const ownerSelectedOptions = ref([])
+
+// 多货主选择
+function showOwnerSelectDialog(items) {
+  if (!items || items.length === 0) {
+    ownerSelectedOptions.value = owners.value.map((item) => {
+      JSON.parse(JSON.stringify(item))
+    })
+    showOwnerSelect.value = true
+    return
+  }
+  ownerSelectedOptions.value = owners.value
+    .filter((item) => {
+      const { value } = item
+      return items.includes(value)
+    })
+    .map((item) => JSON.parse(JSON.stringify(item)))
+  showOwnerSelect.value = true
+}
+
+// 校验日期
+async function checkValidateDate(ownerCode, sku, newDate, fieldName) {
+  if (['NOSKU', 'NOBARCODE'].includes(sku)) {
+    return true
+  }
+  const type = fieldName === 'manufactureTime' ? '生产日期' : '失效日期'
+  try {
+    await validateDate({
+      newDate: newDate,
+      fieldName: fieldName,
+      ownerCode: ownerCode,
+      sku: sku
+    })
+    showNotify({
+      type: 'success',
+      message: `${type} 状态校验成功 `
+    })
+    return true
+  } catch (error) {
+    showNotify({
+      type: 'danger',
+      message: `校验${type}出现异常`
+    })
+    return false
+  }
+}
+
+function blobToBase64(blob) {
+  return new Promise((resolve, reject) => {
+    const fileReader = new FileReader()
+    fileReader.onload = (e) => {
+      resolve(e.target.result)
+    }
+    fileReader.readAsDataURL(blob)
+    fileReader.onerror = () => {
+      reject(new Error('blobToBase64 error'))
+    }
+  })
+}
+
+// 编辑等级详情
+function editDetails(index) {
+  selectDetailIndex.value = index
+  const { details } = params.value
+  selectDetail.value = JSON.parse(JSON.stringify(details[index]))
+  returnedDetailDialog.value = true
+}
+
+// 写入当前商品详情
+function pushImageItem(event, type) {
+  const file = event.target.files[0]
+  if (!file) return
+
+  if (!file.type.startsWith('image/')) {
+    alert('请选择图片文件!')
+    return
+  }
+
+  const filename = file.name
+  const item = {
+    src: URL.createObjectURL(file),
+    file: file,
+    fileName: filename,
+    type
+  }
+
+  if (type === '外箱图') {
+    if (!selectDetail.value.boxPhotos) {
+      selectDetail.value.boxPhotos = []
+    }
+
+    blobToBase64(file).then((_) => {
+      const some = boxFiles.value.some((item) => {
+        return filename === item.fileName
+      })
+      if (some) {
+        showFailToast('录入重复图片')
+      } else {
+        boxFiles.value.push(item)
+        selectDetail.value.boxPhotos.push(filename)
+      }
+    })
+  } else if (type === '内物图') {
+    if (!selectDetail.value.productPhotos) {
+      selectDetail.value.productPhotos = []
+    }
+    blobToBase64(file).then((_) => {
+      const some = productFiles.value.some((item) => {
+        return filename === item.fileName
+      })
+      if (some) {
+        showFailToast('录入重复图片')
+      } else {
+        productFiles.value.push(item)
+        selectDetail.value.productPhotos.push(filename)
+      }
+    })
+  }
+}
+
+// 触发拍照
+function invokeCameraToCapture(type) {
+  const { sku } = selectDetail.value
+  if (!sku) {
+    showFailToast('当前没有可录入的商品信息')
+    return
+  }
+  if (type === '外箱图') {
+    document.getElementById('outer-carton-box-input').click()
+  } else if (type === '内物图') {
+    document.getElementById('inner-contents-input').click()
+  }
+}
+
+function getImageUrl(file_name, type) {
+  let list = []
+  if (type === '外箱图') {
+    list = boxFiles.value
+  } else if (type === '内物图') {
+    list = productFiles.value
+  }
+  for (let i = 0; i < list.length; i++) {
+    const item = list[i]
+    const { src, fileName } = item
+    if (file_name === fileName) {
+      return src
+    }
+  }
+  return null
+}
+
+// 外箱图事件
+function outerCartonInput(event) {
+  pushImageItem(event, '外箱图')
+}
+
+// 内物图异常
+function innerContentsInput(event) {
+  pushImageItem(event, '内物图')
+}
+
+function submit() {
+  const { ownerCode, logisticsName, returnNo, warehouseCode } = params.value
+  if (!ownerCode || ownerCode.length === 0) {
+    showNotify({
+      type: 'warning',
+      message: '没有对应的退件详情!请录入对应的退件详情'
+    })
+    return
+  } else if (!logisticsName || logisticsName.length === 0) {
+    showNotify({
+      type: 'warning',
+      message: '请录入承运商'
+    })
+    return
+  } else if (!returnNo || ownerCode.length === 0) {
+    showNotify({
+      type: 'warning',
+      message: '请录入退件单号'
+    })
+    return
+  } else if (!warehouseCode || warehouseCode.length === 0) {
+    showNotify({
+      type: 'warning',
+      message: '请选择登记仓库'
+    })
+    return
+  }
+  console.log('checkDetailNumber')
+  if (checkDetailNumber()) {
+    return
+  }
+  const formData = new FormData()
+  const boxItems = boxFiles && boxFiles.value ? boxFiles.value : []
+  const productItems =
+    productFiles && productFiles.value ? productFiles.value : []
+  const files = [...boxItems, ...productItems]
+  const filenames = []
+
+  if (files && files.length > 0) {
+    files.forEach((item) => {
+      const { file, fileName } = item
+      if (!filenames.includes(fileName)) {
+        formData.append('files', file)
+        filenames.push(fileName)
+      }
+    })
+  }
+
+  handleDetails(params.value.details)
+  formData.append('body', JSON.stringify(JSON.parse(JSON.stringify(params.value))))
+  console.log(formData)
+  showLoadingToast({
+    duration: 0,
+    forbidClick: true,
+    message: '提交登记中.....'
+  })
+
+  register(formData)
+    .then((res) => {
+      const { data } = res
+      const { accumulateTaskMap } = data
+      closeToast()
+      if (data) {
+        if (accumulateTaskMap && accumulateTaskMap.length > 0) {
+          let messages = []
+          const ownerName = getOwnerName(params.value.ownerCode)
+          for (let i = 0; i < accumulateTaskMap.length; i++) {
+            const { quality, taskCode } = accumulateTaskMap[i]
+            messages.push(`进入${ownerName}新的${quality}攒单任务号${taskCode}`)
+          }
+
+          showConfirmDialog({
+            title: '提交成功',
+            message: messages.join('\r\n'),
+            theme: 'round-button'
+          })
+        } else {
+          showNotify({
+            type: 'success',
+            message: '成功提交'
+          })
+        }
+        init()
+        params.value.ownerCode = ownerCode
+      }
+    })
+    .catch(() => {
+      closeToast()
+    })
+}
+
+function checkDetailNumber() {
+  const { details } = params.value
+  if (!details || details.length === 0) {
+    showNotify({
+      type: 'warning',
+      message: '登记详情不能为空'
+    })
+    return true
+  }
+  const check = details.some((item) => {
+    const { number } = item
+    return Number.isNaN(number) || Number(number) <= 0
+  })
+  if (check) {
+    showNotify({
+      type: 'warning',
+      message: '登记数量不能为 0 和 空 '
+    })
+  }
+  return check
+}
+
+function handleDetails(details) {
+  for (let i = 0; i < details.length; i++) {
+    const { qualityStatus } = details[i]
+    const { isGenuineValue, qualityMark } = getStatus(qualityStatus)
+    details[i].isGenuine = isGenuineValue
+    details[i].qualityMark = qualityMark
+  }
+}
+
+const showPhotosPreview = ref(false)
+const startPhotosPosition = ref(0)
+
+const showPhotosImagePreview = (index) => {
+  startPhotosPosition.value = index
+  showPhotosPreview.value = true
+}
+
+const onPhotosPreviewChange = (index) => {
+  startPhotosPosition.value = index
+}
+const handlePhotosDelete = () => {
+  showConfirmDialog({
+    title: '提示',
+    message: '确定要删除这张图片吗?'
+  })
+    .then(() => {
+      selectDetail.value.productPhotos.splice(startPhotosPosition.value, 1)
+      showNotify({ type: 'success', message: '删除成功' })
+      if (selectDetail.value.productPhotos.length === 0) {
+        showPhotosPreview.value = false
+      } else if (
+        startPhotosPosition.value >= selectDetail.value.productPhotos.length
+      ) {
+        startPhotosPosition.value = selectDetail.value.productPhotos.length - 1
+      }
+    })
+    .catch(() => {
+      showNotify({ type: 'primary', message: '取消删除' })
+    })
+}
+
+const showBoxPreview = ref(false)
+const startBoxPosition = ref(0)
+
+const showBoxImagePreview = (index) => {
+  startBoxPosition.value = index
+  showBoxPreview.value = true
+}
+
+const onBoxPreviewChange = (index) => {
+  startBoxPosition.value = index
+}
+const handleBoxDelete = () => {
+  showConfirmDialog({
+    title: '提示',
+    message: '确定要删除这张图片吗?'
+  })
+    .then(() => {
+      selectDetail.value.boxPhotos.splice(startBoxPosition.value, 1)
+      showNotify({ type: 'success', message: '删除成功' })
+      if (selectDetail.value.boxPhotos.length === 0) {
+        showBoxPreview.value = false
+      } else if (
+        startBoxPosition.value >= selectDetail.value.boxPhotos.length
+      ) {
+        startBoxPosition.value = selectDetail.value.boxPhotos.length - 1
+      }
+    })
+    .catch(() => {
+      showNotify({ type: 'primary', message: '取消删除' })
+    })
+}
+
+const detailBoxImages = computed(() => {
+  const items = selectDetail.value.boxPhotos
+  if (!items || items.length === 0) {
+    return []
+  }
+  return boxFiles.value
+    .filter((item) => {
+      return items.includes(item.fileName)
+    })
+    .map((item) => item.src)
+})
+
+const detailProductImages = computed(() => {
+  const items = selectDetail.value.productPhotos
+  if (!items || items.length === 0) {
+    return []
+  }
+  return productFiles.value
+    .filter((item) => {
+      return items.includes(item.fileName)
+    })
+    .map((item) => item.src)
+})
+
+// 校验是否上次图片
+function checkUploadImages() {
+  const status = ['次品', '待修复']
+  const { productPhotos, boxPhotos, qualityStatus } = selectDetail.value
+  if (!status.includes(qualityStatus)) {
+    return true
+  }
+
+  if (!qualityStatus) {
+    return true
+  }
+
+  const boxPhotoIsNull = !boxPhotos || boxPhotos.length === 0
+  const productPhotoIsNull = !productPhotos || productPhotos.length === 0
+
+  if (boxPhotoIsNull) {
+    showNotify({
+      type: 'warning',
+      message: '请传入对应的外箱图'
+    })
+    return false
+  }
+
+  if (productPhotoIsNull) {
+    showNotify({
+      type: 'warning',
+      message: '请传入对应的内物图'
+    })
+    return false
+  }
+  return true
+}
+
+window.onRefresh = async () => {
+  console.log('window.onRefresh')
+}
+
+function clearImageFileCache() {
+  if (productFiles.value && productFiles.value.length > 0) {
+    productFiles.value
+      .map((item) => item.src)
+      .forEach((item) => URL.revokeObjectURL(item))
+  }
+  if (boxFiles.value && boxFiles.value.length > 0) {
+    boxFiles.value
+      .map((item) => item.src)
+      .forEach((item) => URL.revokeObjectURL(item))
+  }
+}
+
+function previewImages(filenames, index, type) {
+  let list = []
+  if (type === '外箱图') {
+    list = boxFiles.value
+  } else if (type === '内物图') {
+    list = productFiles.value
+  }
+  const images = []
+  for (let i = 0; i < list.length; i++) {
+    const item = list[i]
+    const { src, fileName } = item
+    if (filenames.includes(fileName)) {
+      images.push(src)
+    }
+  }
+  if (!images || images.length === 0) {
+    return
+  }
+  showImagePreview({
+    images: images,
+    startPosition: index
+  })
+}
+</script>
+
+<style scoped lang="sass">
+.van-nav-bar
+  .left-btn
+    color: #fff
+    height: 46px
+    padding-right: 20px
+    line-height: 46px
+
+  .right-btn
+    color: #fff
+
+.container
+  .init-container
+    width: 100%
+
+    .scan-returned-content
+      padding: 10px
+
+      .input-group
+        padding: 5px
+
+      .button-group
+        padding: 5px
+
+  .content
+    width: 100%
+
+  .scan-returned-no
+    align-items: center
+    padding: 15px
+
+  .returned-detail-list
+    .card-div
+      background: #fff
+      border-radius: 12px
+      overflow: hidden
+      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05)
+      margin: 5px 0
+      padding: 5px 0
+
+      .card-div-content
+        padding: 3px
+
+        .info-row
+          display: flex
+          margin-bottom: 12px
+          font-size: 12px
+
+        .info-label
+          width: 100px
+          color: #969799
+
+        .info-value
+          flex: 1
+          color: #333333
+          font-weight: 500
+
+      .card-div-footer
+        padding: 5px
+        background: #fafafa
+        border-top: 1px solid #f5f5f5
+        color: #646566
+        font-size: 14px
+        line-height: 1.5
+
+      .card-div-footer-images
+        padding: 5px
+        background: #fafafa
+        border-top: 1px solid #f5f5f5
+        color: #646566
+        font-size: 14px
+        line-height: 1.5
+
+      .card-div-footer-options
+        background: #fafafa
+        border-top: 1px solid #f5f5f5
+        color: #646566
+        font-size: 14px
+
+        .options-row
+          display: flex
+          font-size: 12px
+
+          .info-label
+            width: 100px
+            color: #969799
+
+          .info-value
+            flex: 1
+            color: #333333
+            font-weight: 500
+
+  .returned-details
+    .van-field
+      padding: 0
+</style>