Explorar o código

Merge remote-tracking branch 'origin/testing' into testing

zh hai 8 meses
pai
achega
d40c7effe7

+ 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"
   }
 }

+ 96 - 0
src/App.vue

@@ -24,6 +24,102 @@ router.afterEach(() => {
 })
 </script>
 <style >
+.flex {
+  display: flex;
+}
+
+.flexColumn {
+  flex-direction: column;
+}
+
+.center {
+  align-items: center;
+  justify-content: center;
+}
+
+.alignCenter {
+  align-items: center;
+}
+
+.alignStart {
+  align-items: flex-start;
+}
+
+.alignEnd {
+  align-items: flex-end;
+}
+
+.spaceBetween {
+  justify-content: space-between;
+}
+
+.spaceEvenly {
+  justify-content: space-evenly;
+}
+
+.spaceAround {
+  justify-content: space-around;
+}
+
+.spaceCenter {
+  justify-content: center;
+}
+
+.justifyEnd {
+  justify-content: flex-end;
+}
+
+.allFlex {
+  flex: 1;
+}
+
+.flexWrap {
+  flex-wrap: wrap;
+}
+
+.flexNoWrap {
+  flex-wrap: nowrap;
+}
+
+.width1 {
+  width: 10%;
+}
+
+.width2 {
+  width: 20%;
+}
+
+.width3 {
+  width: 30%;
+}
+
+.width4 {
+  width: 40%;
+}
+
+.width5 {
+  width: 50%;
+}
+
+.width6 {
+  width: 60%;
+}
+
+.width7 {
+  width: 70%;
+}
+
+.width8 {
+  width: 80%;
+}
+
+.width9 {
+  width: 90%;
+}
+
+.width10 {
+  width: 100%;
+}
 
 
 

+ 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 }
+  })
+}

+ 24 - 0
src/api/processing/index.ts

@@ -0,0 +1,24 @@
+// @ts-ignore
+import request from '@/utils/request'
+/**
+ * 加工-获取加工类型
+ * @param params
+ */
+export function getProcessingTypeList() {
+  return request({
+    url: 'api/device/check/processing/typeList',
+    method: 'get',
+  })
+}
+/**
+ * 加工-创建加工单
+ * @param params
+ */
+export function createProcessing(data:any) {
+  return request({
+    url: 'api/device/check/processing/reference',
+    method: 'post',
+    data,
+  })
+}
+

+ 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
+  })
+}

BIN=BIN
src/assets/login_bg.jpg


+ 68 - 0
src/components/SelectWarehouse.vue

@@ -0,0 +1,68 @@
+<template>
+  <van-popup v-model:show="warehouseTrueFalseBy" position="center" round :close-on-click-overlay="false">
+    <div class="select-warehouses">
+      <div class="title">
+        工作仓库设置
+      </div>
+      <van-radio-group v-model="code">
+        <van-cell-group inset>
+          <van-cell :title="value" clickable v-for="(value, key) in warehousesMap" @click="groupChange(key)">
+            <template #right-icon>
+              <van-radio :name="key" />
+            </template>
+          </van-cell>
+        </van-cell-group>
+      </van-radio-group>
+      <div class="btn" @click="onConfirm"> 确定</div>
+    </div>
+  </van-popup>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue'
+import { useStore } from '@/store/modules/user'
+// 创建响应式数据
+const warehouseTrueFalseBy = ref(false)
+const warehousesMap = ref({
+  'WH99': '测试仓库(WH99)',
+  'WH01': '九干仓(WH01)',
+  'WH02': '新浜一仓(WH02)',
+  'WH10': '新浜二仓(WH10)',
+})
+const code = ref('')
+const show = (warehouse) => {
+  warehouseTrueFalseBy.value = true
+  code.value = warehouse
+}
+const groupChange = (n) => {
+  code.value = n
+}
+// 确认按钮事件
+const storeUser = useStore()
+const emit = defineEmits(['setWarehouse'])
+const onConfirm = () => {
+  storeUser.setToken({ warehouse: code.value, token: storeUser.token })
+  emit('setWarehouse', code.value)
+  warehouseTrueFalseBy.value = false
+}
+defineExpose({ show })
+</script>
+<style scoped lang="sass">
+.select-warehouses
+  padding: 10px 15px
+  width: 300px
+
+  .title
+    font-size: 16px
+    font-weight: 500
+    padding-bottom: 10px
+
+  .radio-item
+    margin-bottom: 10px
+
+  .btn
+    color: #3c9cff
+    line-height: 30px
+    text-align: center
+    cursor: pointer
+</style>

+ 109 - 0
src/hooks/basic/menu.js

@@ -0,0 +1,109 @@
+import {
+  reactive,
+  ref
+} from "vue"
+
+export default function() {
+
+  const menuList = reactive([
+    {
+      title: '工作台',
+      id: 0,
+      icon: "home-o",
+      menu:[{
+        title:'入库',
+        children: [
+          {
+            title: '收货',
+            icon: 'newspaper-o',
+            path: "take-delivery"
+          },
+          {
+            title: '盲扫',
+            icon: 'newspaper-o',
+            path: "blind-receiving"
+          },
+          {
+            title: '海康快上',
+            icon: 'newspaper-o',
+            path: "hik-putaway-allocation"
+          },
+          {
+            title: '海康上架',
+            icon: 'newspaper-o',
+            path: "hik-putaway"
+          },
+          {
+            title: '海康入库',
+            icon: 'newspaper-o',
+            path: "hik-box-return"
+          },
+        ]
+      },
+        {
+          title:'出库',
+          children: [
+            {
+              title: '拣货',
+              icon: 'newspaper-o',
+              path: 'picking'
+            },
+            {
+              title: '拣货(巷道)',
+              icon: 'newspaper-o',
+              path: "picking-aisle"
+            },
+            {
+              title: '活动单复核',
+              icon: 'newspaper-o',
+              path: "check-activity"
+            },
+            {
+              title: 'B2B复核',
+              icon: 'newspaper-o',
+              path: "check-large"
+            },
+            {
+              title: '复核还库',
+              icon: 'newspaper-o',
+              path: "check-move-stock"
+            },
+          ]
+        },
+        {
+          title:'库存',
+          children: [
+            {
+              title: '移库任务',
+              icon: 'newspaper-o',
+              path: "move-list"
+            },
+          ]
+        },
+        {
+          title:'其他',
+          children: [
+            {
+              title: '加工单',
+              icon: 'newspaper-o',
+              path: "processing"
+            },
+            {
+              title: '计件面板',
+              icon: 'newspaper-o',
+              path: "piece-dashboard"
+            },
+            {
+              title: '退货登记',
+              icon: 'newspaper-o',
+              path: "returned-register"
+            },
+          ]
+        }
+      ]
+    },
+  ]);
+  return {
+    menuList
+  }
+}

+ 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')

+ 12 - 0
src/router/index.ts

@@ -109,6 +109,18 @@ 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')
+  },
+  {
+    path: '/processing',
+    name: 'Processing',
+    meta:{title:'加工登记'},
+    component: () => import('@/views/processing/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;
+}

+ 5 - 1
src/utils/dataType.js

@@ -24,7 +24,11 @@ export function toMap(data, key, val=undefined, optional = {}) {
 
   return map;
 }
-
+//字符串分割数组
+export function toArray(code) {
+  // 使用正则表达式分割字符串,去除多余的空白字符或换行符
+  return code.split(/[,,|\n\r\s]+/).filter(Boolean); // 使用 filter 去除空元素
+}
 
  export function barcodeToUpperCase(code){
    if (/[a-zA-Z]/.test(code)) {

+ 9 - 0
src/utils/date.ts

@@ -31,3 +31,12 @@ export function getCurrentTime() {
   const seconds = String(now.getSeconds()).padStart(2, '0');
   return `${hours}:${minutes}:${seconds}`;
 };
+/*
+ *获取当前时间
+ */
+export function formatDate(date:any) {
+  const year = date.getFullYear(); // 获取年份
+  const month = String(date.getMonth() + 1).padStart(2, '0'); // 获取月份
+  const day = String(date.getDate()).padStart(2, '0'); // 获取日期
+  return `${year}-${month}-${day}`;
+}

+ 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  {}
+}

+ 13 - 1
src/views/haikang/putaway/dispatch/index.vue

@@ -341,7 +341,7 @@ onUnmounted(() => {
   closeListener()
 })
 
-window.onRefresh = loadData
+
 
 //删除分拨
 const onClickRight = () => {
@@ -355,6 +355,8 @@ const onClickRight = () => {
     finishTask(params).then(res=>{
       showNotify({ type: 'success', duration: 3000, message: `解绑成功` })
       scanSuccess()
+      reset()
+      loadData()
     }).catch(err=>{
       scanError()
     }).finally(() => {
@@ -362,6 +364,16 @@ const onClickRight = () => {
     })
   }).catch(() => {})
 }
+const reset=()=>{
+  containerNo.value=''
+  wallNo.value=''
+  scanType.value=3
+  searchBarcode.value = ''
+  bin.value=''
+  locationActive.value = []
+  tips.value='请扫描容器号'
+}
+window.onRefresh = loadData
 </script>
 <style scoped lang="sass">
 .container

+ 214 - 41
src/views/index.vue

@@ -1,57 +1,230 @@
 <template>
-  <div class="container">
-    <van-image class="image" :src="imageUrl" ></van-image>
-    <div class="name" >{{userInfo.username}}</div>
-    <div class="home" @click="onRouter('picking')">拣货</div>
-<!--    <div class="home" @click="onRouter('picking-aisle')">巷道拣货</div>-->
-    <div class="home" @click="onRouter('blind-receiving')">盲扫</div>
-<!--    <div class="home" @click="onRouter('check-move-stock')">反拣还库</div>-->
-    <div class="home" @click="onRouter('piece-dashboard')">计件面板</div>
-    <div class="home" @click="onRouter('take-delivery')">收货</div>
-    <div class="home" @click="onRouter('hik-putaway-allocation')">海康上架-调度</div>
-    <div class="home" @click="onRouter('hik-putaway')">海康上架</div>
-    <div class="home" @click="onRouter('hik-box-return')">海康-入库/解绑</div>
-    <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="warehouse container">
+    <div v-if="menuTrueFalseBy" class="menu">
+      <div class="name">
+        {{ userInfo.username }}
+      </div>
+      <div
+        v-for="(item, index) in menuList"
+        :key="index"
+        :class="menuId === item.id ? 'active' : ''"
+        @click="onConfirm(item)"
+        class="title flex"
+      >
+        <div class="icon">
+          <van-icon :name="item.icon"></van-icon>
+        </div>
+        <div>{{ item.title }}</div>
+      </div>
+      <div class="title flex" style="margin-top: 30px;" @click="outLogin">
+        <div class="icon">
+          <van-icon name="upgrade" />
+        </div>
+        <div>退出登录</div>
+      </div>
+    </div>
+    <div :class="menuTrueFalseBy ? 'content-box' : 'content-show'">
+      <div class="content">
+        <van-nav-bar
+          placeholder
+          :fixed="!menuTrueFalseBy"
+          @click-left="back"
+          @click-right="showSelect"
+        >
+          <template #left>
+            <div class="u-nav-slot">
+              <van-icon name="bars" size="25" />
+            </div>
+          </template>
+          <template #title>
+            <div class="flex flexColumn alignCenter">
+              <div>工作台</div>
+            </div>
+          </template>
+          <template #right>
+            <div style="color: #fff">{{ warehousesMap[warehouse] }}</div>
+          </template>
+        </van-nav-bar>
+        <div class="menu-box">
+          <div v-for="(item, index) in menuList[menuId].menu" class="menu-list">
+            <div class="menu-title">{{ item.title }}</div>
+            <div class="menu-item-box">
+              <div v-for="(items, index) in item.children" class="menu-item" @click="onRouter(items.path)">
+                <div class="menu-item-icon">
+                  <van-icon :name="items.icon" size="28" />
+                </div>
+                <div class="menu-item-title">{{ items.title }}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div v-if="menuTrueFalseBy" @click="hideMenu" class="hide-menu"></div>
+    </div>
+    <select-warehouse ref="selectWarehouseRef" @setWarehouse="setWarehouse" />
   </div>
 </template>
+
 <script setup>
-import imageUrl from '@/assets/img.png'
-import { useRouter } from 'vue-router'
-import { getHeader } from '@/utils/android.ts'
-import { useStore } from '@/store/modules/user.ts'
-import { getUserInfo } from '@/api/login/index.ts'
 import { ref } from 'vue'
+import menu from '@/hooks/basic/menu.js'
+import { useRouter } from 'vue-router'
+import { getHeader, goBack } from '@/utils/android'
+import { useStore } from '@/store/modules/user'
+import { getUserInfo } from '@/api/login/index'
+import SelectWarehouse from '@/components/SelectWarehouse.vue'
+import { showToast } from 'vant'
+
 const router = useRouter()
-const onRouter=(path)=>{
-  router.push(`/${path}`)
-}
 const store = useStore()
-const userInfo=ref({})
+const userInfo = ref({})
 try {
   getHeader()
-}catch (error) {
+} catch (error) {
 }
-setTimeout(()=>{
+setTimeout(() => {
   getUserInfo().then(res => {
-    userInfo.value=res.data
+    userInfo.value = res.data
   })
-},300)
+}, 300)
 
+const { menuList } = menu()
+const menuId = ref(0)
+const menuTrueFalseBy = ref(false)
+const storeUser = useStore()
+const warehouse = ref(storeUser.warehouse)
+const warehousesMap = ref({
+  'WH99': '测试仓库(WH99)',
+  'WH01': '九干仓(WH01)',
+  'WH02': '新浜一仓(WH02)',
+  'WH10': '新浜二仓(WH10)',
+})
+const onRouter = (path) => {
+  router.push(`/${path}`)
+}
+const onConfirm = (item) => {
+  menuTrueFalseBy.value = false
+}
+const back = () => {
+  try {
+    window.android.goBack()
+  }catch (e){
+    menuTrueFalseBy.value = true
+  }
+}
+const selectWarehouseRef = ref(null)
+const showSelect = () => {
+  try {
+    if(window.android.getHeader()){
+      showToast('app内不支持切换仓库')
+    }
+  }catch (e){
+    selectWarehouseRef.value.show(warehouse.value)
+  }
+
+}
+const hideMenu = () => {
+  if (menuTrueFalseBy.value) {
+    menuTrueFalseBy.value = false
+  }
+}
+const setWarehouse = (code) => {
+  warehouse.value = code
+}
+const outLogin = () => {
+  router.push(`/login`)
+}
 </script>
-<style scoped lang="sass" >
-.container
+
+<style lang="sass" scoped>
+.warehouse
+  width: 100%
   height: 100vh
-  .image
-    margin: 20px
-  .home
-    cursor: pointer
-    padding: 10px
-    font-size: 18px
-    text-decoration: underline
-    color: #0077ff
-  .name
-    font-size: 20px
-    color: #0077ff
+  display: flex
+
+  .menu
+    width: 50%
+    margin-left: 15px
+
+    .name
+      margin: 50px auto
+      text-align: center
+      font-size: 18px
+      font-weight: 500
+
+    .title
+      line-height: 40px
+
+    .active
+      color: #3c9cff
+
+  .content-box
+    width: 50%
+    transition: width 0.2s
+    overflow: hidden
+    background-color: #ffffff
+    margin: 80px 0 110px 0
+    position: relative
+
+    .content
+      width: 375px
+      overflow: hidden
+      transform: scale(0.75)
+      transform-origin: left top
+      pointer-events: none
+
+      .menu-box
+        .menu-list
+          background: #fff
+          padding: 10px 0
+          font-size: 13px
+          margin-bottom: 10px
+          color: #333
+
+          .menu-title
+            padding: 0 15px 10px 15px
+            text-align: left
+
+          .menu-item-box
+            display: flex
+
+            .menu-item
+              width: 25%
+
+    .hide-menu
+      position: absolute
+      height: 100%
+      width: 100%
+      left: 0
+      right: 0
+      top: 0
+      bottom: 0
+
+
+  .content-show
+    width: 100%
+
+    .content
+      .menu-box
+        .menu-list
+          background: #fff
+          padding: 10px 0
+          font-size: 13px
+          margin-bottom: 10px
+          color: #333
+
+          .menu-title
+            padding: 0 15px 10px 15px
+            text-align: left
+
+          .menu-item-box
+            display: flex
+
+            .menu-item
+              width: 25%
+
+  .grid-text
+    font-size: 14px
+    color: #909399
+    padding: 5px 0 10px 0
 </style>

+ 19 - 6
src/views/login/login.vue

@@ -1,7 +1,10 @@
 
 <template>
   <div class="container">
-    <van-form  style="margin-top: 16px;" >
+    <div class="container-bg">
+
+    </div>
+    <van-form  style="margin-top: -26px;" >
       <van-cell-group inset>
         <van-field
           v-model="warehouse"
@@ -9,6 +12,7 @@
           name="仓库"
           label="仓库"
           placeholder="仓库"
+          autocomplete="off"
           :rules="[{ required: true, message: '请填写仓库' }]"
         />
         <van-field
@@ -17,9 +21,10 @@
           name="用户名"
           label="用户名"
           placeholder="用户名"
+          autocomplete="off"
+          label-align="top"
           :rules="[{ required: true, message: '请填写用户名' }]"
         />
-
         <van-field
           v-model="password"
           type="password"
@@ -27,14 +32,13 @@
           name="密码"
           label="密码"
           placeholder="密码"
+          autocomplete="off"
+          label-align="top"
           :rules="[{ required: true, message: '请填写密码' }]"
         />
-
       </van-cell-group>
       <div style="margin: 16px;">
-        <van-button round block type="primary" @click="onConfirm" native-type="submit">
-          提交
-        </van-button>
+        <van-button round block type="primary" @click="onConfirm" native-type="submit">登录</van-button>
       </div>
     </van-form>
   </div>
@@ -65,3 +69,12 @@ const onConfirm = async () => {
 }
 
 </script>
+<style scoped lang="sass">
+.container
+  .container-bg
+    height: 190px
+    width: 100%
+    background: url("../../assets/login_bg.jpg") no-repeat
+    background-size: 100%
+
+</style>

+ 1 - 1
src/views/outbound/check/large/index.vue

@@ -150,7 +150,7 @@ onMounted(() => {
   loadData()
 })
 const warehouse = store.warehouse
-const orderNo = ref('SOZ25080700001')
+const orderNo = ref('')
 // 错误提示
 const tips = ref('请扫描订单/快递单号')
 //强制返回

+ 183 - 0
src/views/processing/register/components/addRegisterType.vue

@@ -0,0 +1,183 @@
+<template>
+  <div class="container-no-container">
+    <van-dialog v-model:show="typeTrueFalseBy"
+                :beforeClose="beforeClose"
+                title="登记类型/数量"
+                show-cancel-button>
+      <van-field name="checkboxGroup" label="加工类型" required label-width="70px">
+        <template #input>
+          <van-checkbox-group v-model="type" direction="horizontal">
+            <div v-for="(item,index) in props.processingTypeList" :key="item.code" style="margin-bottom: 8px">
+              <van-checkbox :name="item.code" shape="square"
+                            v-if=" item.code !== 'PROFESSIONAL' && item.code !== 'DEEP' &&item.code !== 'FINE'"
+              >{{ item.name }}
+              </van-checkbox>
+            </div>
+          </van-checkbox-group>
+        </template>
+      </van-field>
+      <van-field name="radio" label="质检类型" label-width="70px" :required="type.includes('ALL_CHECK')">
+        <template #input>
+          <van-radio-group v-model="checkType" direction="horizontal" label-width="70px">
+            <div v-for="(item,index) in props.processingTypeList" :key="item.code" style="margin-bottom: 8px">
+              <van-radio v-if="item.code == 'PROFESSIONAL' || item.code == 'DEEP' || item.code == 'FINE'"
+                         :name="item.code">{{ item.name }}
+              </van-radio>
+            </div>
+          </van-radio-group>
+        </template>
+      </van-field>
+      <van-field v-model="count" type="digit" ref="countRef" @keydown.enter="onKeydown" clearable autocomplete="off"
+                 label="加工数量" placeholder="请输入加工数量" required />
+    </van-dialog>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { showToast } from 'vant'
+import { scanError } from '@/utils/android'
+const typeTrueFalseBy = ref(false)
+const type = ref([])
+const count = ref('')
+const countRef = ref(null)
+const checkType = ref('')
+// 接收父组件传递的数据
+const props = defineProps({
+  processingTypeList: Array
+})
+const typeList=ref([])
+const show = async (list) => {
+  typeTrueFalseBy.value = true
+  type.value = []
+  count.value = ''
+  checkType.value = ''
+  typeList.value=list
+}
+//输入拣货容器号
+const emit = defineEmits(['setProcessingType'])
+const validateData = () => {
+
+  // 验证质检类型
+  if (type.value.includes('ALL_CHECK') && !checkType.value) {
+    showToast('选择的加工类型包含质检,请选择质检类型')
+    scanError()
+    return false
+  }
+  const data=getType()
+  // 验证加工类型
+  if (data.operate.length === 0) {
+    showToast('请先选择加工类型')
+    scanError()
+    return false
+  }
+  // 验证加工数量
+  if (count.value <= 0) {
+    showToast('请输入加工数量')
+    scanError()
+    countRef.value?.focus()
+    return false
+  }
+  return true
+}
+const getType=()=>{
+  let operate = [...type.value]
+  operate = operate.filter(item => item !== 'ALL_CHECK')
+  if (checkType.value) {
+    operate.push(checkType.value)
+  }
+  return  { operate, expectAmount: count.value }
+}
+const beforeClose = (action) => new Promise(async (resolve) => {
+  if (action === 'confirm') {
+    if (!validateData()) {
+      return resolve(false)
+    }
+    const data=getType()
+    const isExist = typeList.value.some(item =>
+      item.operate.length === data.operate.length &&
+      item.operate.every(op => data.operate.includes(op)) &&
+      data.operate.every(op => item.operate.includes(op))
+    );
+    if(isExist){
+      showToast('选择的加工类型已存在,请检查')
+      scanError()
+      return resolve(false)
+    }
+    emit('setProcessingType', data)
+    resolve(true)
+  } else {
+    resolve(true)
+  }
+})
+const onKeydown = () => {
+  if (!validateData()) {
+    return
+  }
+  const data=getType()
+  const isExist = typeList.value.some(item =>
+    item.operate.length === data.operate.length && // 长度必须相同
+    item.operate.every(op => data.operate.includes(op)) && // 所有元素都在 data.operate 中
+    data.operate.every(op => item.operate.includes(op)) // 所有元素都在 item.operate 中
+  );
+  if(isExist){
+    showToast('选择的加工类型已存在,请检查')
+    scanError()
+    return
+  }
+  typeTrueFalseBy.value = false
+  emit('setProcessingType', data)
+}
+
+defineExpose({ show })
+</script>
+<style scoped lang="sass">
+.container-no-container
+  .code-input
+    font-size: 22px
+    font-weight: bold
+    border-bottom: 2px solid #0077ff
+    margin-top: 10px
+
+  .completion
+    text-align: right
+    font-size: 12px
+    padding: 5px 20px
+    cursor: pointer
+    text-decoration: underline
+
+  .container-list
+    max-height: 100px
+    font-size: 13px
+    font-weight: bold
+    display: flex
+    padding: 5px 20px
+    justify-content: space-between
+    flex-wrap: wrap
+    overflow: auto
+
+    .container-item
+      width: 50%
+      display: flex
+      justify-items: center
+      align-items: center
+      padding: 2px 0
+      cursor: pointer
+      text-decoration: underline
+
+      .container-item-line
+        width: 5px
+        height: 5px
+        background: #0077ff
+        border-radius: 50%
+        margin-right: 5px
+
+      .container-item-no
+        flex: 1
+        text-align: left
+
+.tips
+  font-size: 14px
+  color: #ff4141
+  line-height: 20px
+
+</style>

+ 232 - 0
src/views/processing/register/index.vue

@@ -0,0 +1,232 @@
+<template>
+  <div class="register">
+    <van-nav-bar
+      title="加工建单" left-arrow fixed placeholder @click-left="goBack" @click-right="onConfirm()">
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div style="color: #fff">返回</div>
+      </template>
+      <template #right>
+        <div style="color: #fff">提交</div>
+      </template>
+    </van-nav-bar>
+    <van-field v-model="date" type="text" label="登记日期" placeholder="请选择日期"
+               readonly is-link input-align="right" @click="dateTrueFalseBy=true" required
+    />
+    <van-field v-model="ownerCode" type="text" label="登记货主" placeholder="请选择货主" readonly
+               is-link @click="ownerRef.show(1)" input-align="right" required
+    />
+    <van-field v-model="code" type="textarea" label="单据信息" placeholder="请填写快递单号/收货任务号,多个用逗号/空格/换行符进行分割"
+               show-word-limit maxlength="500" rows="2" autosize required
+    />
+    <van-field v-model="note" type="textarea" label="&nbsp;&nbsp;备注信息" placeholder="请输入备注信息"
+               show-word-limit  maxlength="500" rows="1" autosize
+    />
+    <div class="register-content">
+      <div class="add-type">
+        <van-button type="primary" round size="small" @click="addType">添加类型</van-button>
+      </div>
+      <div class="type-box">
+        <div class="type-title">类型表一览</div>
+        <div class="type-list">
+          <div class="type-item">
+            <div class="type-item-item">类型</div>
+            <div class="type-item-operate">数量</div>
+            <div class="type-item-operate">操作</div>
+          </div>
+          <div  class="type-item" v-for="(item,index) in typeList" v-if="typeList.length>0">
+            <div  class="type-item-item">{{getOperate(item.operate)}}</div>
+            <div class="type-item-operate">{{item.expectAmount}}</div>
+            <div class="type-item-delete" @click="del(item,index)" >删除</div>
+          </div>
+          <div v-else>
+            <van-empty :image="<string>nodataUrl" image-size="140" >
+              <van-button round type="primary" class="bottom-button" size="small" @click="addType">添加</van-button>
+            </van-empty>
+          </div>
+        </div>
+      </div>
+    </div>
+    <owner ref="ownerRef" @onOwner="onOwner" />
+    <van-calendar first-day-of-week="1" v-model:show="dateTrueFalseBy" @confirm="dateConfirm" />
+    <add-register-type ref="addRegisterTypeRef"
+                       :processingTypeList="processingTypeList"
+                       @setProcessingType="setProcessingType"  />
+  </div>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { showConfirmDialog, showNotify, showToast } from 'vant'
+import { useStore } from '@/store/modules/user'
+import { closeLoading, showLoading } from '@/utils/loading'
+import Owner from '@/components/Owner.vue'
+import { formatDate } from '@/utils/date'
+import { useRouter } from 'vue-router'
+import nodataUrl from '@/assets/nodata.png'
+import AddRegisterType from '@/views/processing/register/components/addRegisterType.vue'
+import { createProcessing, getProcessingTypeList } from '@/api/processing'
+import { toArray, toMap } from '@/utils/dataType'
+const router = useRouter()
+try {
+  getHeader()
+}catch (error) {
+  router.push('/login')
+}
+const store = useStore()
+const warehouse = store.warehouse
+const ownerRef = ref(null)
+const ownerItem = ref({})
+const ownerCode = ref('')
+const date = ref(formatDate(new Date()))
+const note = ref('')
+const code=ref('')
+const processingTypeList=ref([])
+const processingTypeMap=ref({})
+//添加类型
+const addRegisterTypeRef=ref(null)
+const typeList = ref([])
+const dateTrueFalseBy = ref(false)
+//选择货主
+const onOwner = (item) => {
+  ownerItem.value = item
+  ownerCode.value = item.name
+}
+//选择日期
+const dateConfirm = (e) => {
+  date.value = formatDate(e)
+  dateTrueFalseBy.value = false
+}
+//提交登记
+const onConfirm = () => {
+  if(!ownerCode.value){
+    showNotify({ type: 'warning', duration: 3000, message: '请选择货主' })
+    scanError()
+    return
+  }
+  if(typeList.value.length==0){
+    showNotify({ type: 'warning', duration: 3000, message: '请先添加单据类型' })
+    scanError()
+    return
+  }
+  if(!code.value){
+    showNotify({ type: 'warning', duration: 3000, message: '请先添加单据信息' })
+    scanError()
+    return
+  }
+
+  showConfirmDialog({
+    title: '温馨提示', message:'是否确认建单' })
+    .then(() => {
+    _createProcessing()
+    }).catch(() => {})
+}
+
+const _createProcessing=()=>{
+  const data={owner:ownerItem.value.id,ownerCode:ownerItem.value.code,note:note.value,warehouse,typeList:typeList.value,noList:toArray(code.value)}
+  showLoading()
+  createProcessing(data).then(res=>{
+    closeLoading()
+    scanSuccess()
+    showConfirmDialog({
+      title: '建单成功',
+      message:`加工单号为:${res.data}`,
+      confirmButtonText:'复制'
+    })
+      .then(() => {
+        copyText(res.data)
+      })
+      .catch(() => {});
+    reset()
+  }).catch(err=>{
+    scanError()
+    closeLoading()
+  })
+}
+const copyText=(txt) =>{
+  const textarea = document.createElement('textarea');
+  textarea.value =txt;
+  document.body.appendChild(textarea);
+  textarea.select();
+  try {
+    const successful = document.execCommand('copy');
+    if (successful) {
+      showNotify({ type: 'success', duration: 3000, message: '已复制' })
+    } else {
+      console.error('复制失败');
+    }
+  } catch (err) {
+    showNotify({ type: 'danger', duration: 3000, message: '复制失败' })
+  } finally {
+    document.body.removeChild(textarea);
+  }
+}
+const reset=()=>{
+  code.value=""
+  typeList.value=[]
+  note.value=""
+  ownerCode.value=''
+}
+//添加类型
+const addType=()=>{
+  addRegisterTypeRef.value?.show(typeList.value)
+}
+const setProcessingType=(data)=>{
+  typeList.value.push(data)
+}
+//转换类型名称
+const getOperate=(operate)=>{
+  const result = operate.map(item => processingTypeMap.value[item] || item)
+  return result.join(',')
+}
+//获取类型
+const _getProcessingTypeList=()=>{
+  getProcessingTypeList().then(res=>{
+    processingTypeList.value=res.data
+    processingTypeMap.value=toMap(res.data,'code','name')
+  })
+}
+const del=(item,index)=>{
+  typeList.value.splice(index,1)
+}
+_getProcessingTypeList()
+</script>
+<style scoped lang="sass">
+.register
+  .register-content
+    background: #fff
+    padding: 10px
+    font-size: 14px
+    .add-type
+      text-align: right
+    .type-box
+      text-align: left
+      .type-title
+        line-height: 30px
+        color: #999
+      .type-list
+        border: 1px solid #e9e9e9
+        border-radius: 6px
+        padding: 5px 10px
+        .type-item
+          display: flex
+          align-items: center
+          border-bottom: 1px solid #e9e9e9
+
+          padding: 3px 0
+          .type-item-item
+            flex: 1
+          .type-item-operate
+            width: 70px
+            text-align: center
+          .type-item-delete
+            width: 70px
+            text-align: center
+            color: #ff4141
+            text-decoration: underline
+        .type-item:last-child
+          border-bottom: none
+
+
+
+</style>

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

@@ -0,0 +1,2059 @@
+<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="选择承运商"
+                v-model="params.logisticsName"
+                @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(warehouse)"
+                v-model="params.warehouseCode"
+                @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="ownerPickerShow = true"
+              />
+              <van-popup
+                v-model:show="ownerPickerShow"
+                position="bottom"
+                destroy-on-close
+              >
+                <van-picker
+                  :columns="owners"
+                  @cancel="ownerPickerShow = false"
+                  @confirm="selectedOwner"
+                />
+              </van-popup>
+
+              <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.barCode }}</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}`"
+                            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}`"
+                            width="100%"
+                            height="50"
+                            :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-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
+            :show-confirm-button="checkUploadImages()"
+            @confirm="addDetails"
+          >
+
+
+            <div style="max-height: 70vh; overflow-y: auto">
+              <van-field
+                v-model="selectDetail.sku"
+                placeholder="SKU"
+                clearable
+              />
+              <van-field
+                readonly
+                v-model="selectDetail.barCode"
+                placeholder="商品条码"
+                clearable
+              />
+              <van-field
+                v-model="selectDetail.tradeName"
+                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
+                  :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
+                  :model-value="parseDateValue(selectDetail.validityTime)"
+                  @confirm="validityTimeConfirm"
+                  @cancel="showValidityTime = false"
+                />
+              </van-popup>
+
+              <van-field
+                autocomplete="off"
+                v-model="selectDetail.batchNumber"
+                placeholder="批次号"
+                clearable
+              />
+
+              <van-field
+                v-model="selectDetail.number"
+                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
+                :onchange="outerCartonInput"
+              />
+
+              <input
+                type="file"
+                id="inner-contents-input"
+                capture="user"
+                accept="image/*"
+                hidden
+                :onchange="innerContentsInput"
+              />
+            </div>
+          </van-dialog>
+        </van-tabs>
+      </div>
+    </div>
+    <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>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, nextTick, onMounted } from 'vue'
+import {
+  showFailToast,
+  showSuccessToast,
+  showNotify,
+  showLoadingToast,
+  closeToast,
+  showConfirmDialog,
+  showToast,
+} from 'vant'
+import { getHeader, goBack } 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'
+
+try {
+  getHeader()
+} catch (error) {
+  console.log(error)
+}
+
+const showInitialPage = ref(true)
+const expressNo = ref(null)
+const warehouse = ref(useStore.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(() => {
+  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.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.id, text: item.name }
+    })
+    const map = {}
+    data.forEach((item) => {
+      map[item.name] = item.id
+    })
+    logisticsMap.value = map
+  })
+  try {
+    getHeader()
+  } catch (error) {}
+})
+
+// 商品外箱图
+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 []
+  }
+  boxFiles.value.forEach((item) => {
+    console.log(item)
+    console.log(boxPhotos.includes(item.fileName))
+  })
+  console.log(boxFiles.value.filter((_) => true))
+  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 []
+  }
+  console.log(
+    productFiles.value.filter((item) => {
+      return productPhotos.includes(item.fileName)
+    }),
+  )
+  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, text: item }
+  })
+}
+
+const qualityInspection = ref(null)
+
+// 初始化质检状态
+function initQualityInspection(owner) {
+  console.log(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: null,
+  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 warehouseName = ref('')
+const selectedWarehouse = (row) => {
+  const { selectedValues, selectedOptions } = row
+  warehousePickerShow.value = false
+  params.value.warehouseCode = selectedValues
+  warehouseName.value = selectedOptions[0].text
+}
+
+// 店铺选择
+const storePickerShow = ref(false)
+const selectedStore = (row) => {
+  const { selectedOptions } = row
+  storePickerShow.value = false
+  params.value.storeName = selectedOptions[0].text
+}
+
+// 货主名
+function getOwnerName(owner) {
+  return ownerMap.value[owner]
+}
+
+// 仓库名
+function getWarehouseName(code) {
+  return warehousesMap.value[code]
+}
+
+// 输入快递单号
+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('快递单号异常')
+    return
+  }
+  params.value.returnNo = express_no
+  params.value.warehouseCode = warehouse.value
+  listAsn(express_no).then((res) => {
+    if (res.data) {
+      asnList.value = res.data
+    }
+  })
+  matchReturned(express_no)
+  expressNo.value = null
+  showInitialPage.value = false
+}
+
+function matchReturned(expressNo) {
+  getReturnedByExpress(expressNo).then((res) => {
+    const { data } = res
+    if (!data) {
+      matchOrder(expressNo)
+      return
+    }
+    const { id, headStatus, returnNo, warehouseCode } = data
+    if (!id) {
+      console.log('没有退件记录')
+      matchOrder(expressNo)
+      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(expressNo)
+      }
+    }
+  })
+}
+
+// 匹配原单信息
+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.value
+      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)
+      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.value
+    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
+    matchCarrier(expressNo)
+    params.value.details = []
+    initDetail()
+    initQualityInspection(params.value.ownerCode)
+  })
+}
+
+// 校验快递单号格式
+function checkExpressNo(no) {
+  if (!no || no.length === 0) {
+    showFailToast('单号长度异常')
+    return false
+  }
+  if (no.startsWith('http')) {
+    showFailToast('扫描到了错误的条码')
+    return false
+  }
+  if (no.endsWith('.com')) {
+    showFailToast('扫描到了错误的条码')
+    return false
+  }
+  const length = no.length
+  if (length > 30 || length < 5) {
+    showFailToast('单号长度异常')
+    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) {
+  warehouseName.value = null
+  ownerName.value = null
+  params.value = {
+    orderUpstream: null,
+    returnNo: null,
+    warehouseCode: null,
+    logisticsName: null,
+    storeName: null,
+    orderUpSteam: null,
+    arrivalPayment: null,
+    buyerName: null,
+    asnNo: null,
+    upstreamNo: null,
+    buyerPhone: null,
+    originalNo: null,
+    remark: null,
+    details: [],
+  }
+  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
+  }
+  qualityStatusDialog.value = true
+  qualityStatus.value = '正品'
+}
+
+// 查询商品
+async function queryBarcode() {
+  showLoadingToast({
+    duration: 0,
+    forbidClick: true,
+    message: '查询商品信息中.....',
+  })
+  const barcode = scancode.value
+  const { ownerCode } = params.value
+  if (ownerCode) {
+    const result = await queryOwnerBarcode(ownerCode, barcode)
+    if (result) {
+      scancodeDialog.value = false
+      qualityStatusDialog.value = false
+      closeToast()
+      return
+    }
+  }
+  const { details } = params.value
+  if (!details || details.length === 0) {
+    await queryBarcodeBy(barcode)
+    scancodeDialog.value = false
+    qualityStatusDialog.value = false
+    closeToast()
+  }
+}
+
+/**
+ * 更具货主查询商品条码
+ * @param ownerCode
+ * @param barcode
+ * @returns {Promise<boolean>}
+ */
+async function queryOwnerBarcode(ownerCode, barcode) {
+  try {
+    console.log('queryOwnerBarcode')
+    const results = await searchOwnerBarcode({ ownerCode, barcode })
+    const {
+      data: { basSku },
+    } = results
+    console.log(results)
+    if (!basSku) {
+      return false
+    }
+    addDetailByBasSku(basSku)
+    return true
+  } catch (err) {
+    console.log(err.message)
+  }
+  return false
+}
+
+/**
+ * 查询对应的
+ * @param barcode
+ * @returns {Promise<boolean>}
+ */
+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) {
+      return null
+    }
+    addDetailByBasSku(basSku)
+    return null
+  } catch (err) {
+    console.log(err.message)
+  }
+  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.ownerCode = ownerCode
+    initQualityInspection(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) {
+  console.log(items)
+  console.log(!items || items.length === 0)
+  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 formattedMonth = month.padStart(2, '0')
+  const formattedDay = day.padStart(2, '0')
+  const manufactureTime = `${year}-${formattedMonth}-${formattedDay}`
+  const { sku } = selectDetail.value
+  const { owner: ownerCode } = params.value
+  const isValid = await checkValidateDate(
+    ownerCode,
+    sku,
+    manufactureTime,
+    'manufactureTime',
+  )
+  if (!isValid) {
+    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 formattedMonth = month.padStart(2, '0')
+  const formattedDay = day.padStart(2, '0')
+  const validityTime = `${year}-${formattedMonth}-${formattedDay}`
+  const { sku } = selectDetail.value
+  const { owner: ownerCode } = params.value
+  const isValid = await checkValidateDate(
+    ownerCode,
+    sku,
+    validityTime,
+    'validityTime',
+  )
+  if (!isValid) {
+    return
+  }
+  selectDetail.value.validityTime = `${year}-${formattedMonth}-${formattedDay}`
+  showValidityTime.value = false
+}
+
+// 质量状态标签
+function getTagColor(qualityStatus) {
+  return getTagColorBy(qualityStatus)
+}
+
+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
+  }
+  // 查找对应的详情进行合并
+  params.value.details.push(detail)
+  // 重置详情
+  initDetail()
+  return true
+}
+
+// 删除对应的详情
+function removeDetails(index) {
+  if (null === index) {
+    showFailToast('未找到对应的详情')
+    return
+  }
+  const { id } = params.value.details[index]
+  if (!id) {
+    params.value.details.splice(index, 1)
+    showSuccessToast('删除成功')
+  } else {
+    deleteDetails(id).then((res) => {
+      if (res.data) {
+        params.value.details.splice(index, 1)
+        showSuccessToast('删除成功')
+      } else {
+        showFailToast('删除失败')
+      }
+    })
+  }
+}
+
+function selectOwner({ selectedValues }) {
+  const ownerCode = selectedValues[0].value
+  params.value.ownerCode = selectedValues[0]
+  showOwnerSelect.value = false
+  ownerSelectedOptions.value = []
+  const barcode = scancode.value
+  searchOwnerBarcode({ ownerCode, barcode })
+  getStoreOptionsBy(ownerCode)
+  // 货主
+}
+
+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: 'warning',
+      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(filename, 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 (fileName === 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(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]
+    console.log(qualityStatus)
+    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(startBoxPosition.value, 1)
+      showToast('删除成功')
+      if (selectDetail.value.productPhotos.length === 0) {
+        showPhotosPreview.value = false
+      } else if (
+        startPhotosPosition.value >= selectDetail.value.productPhotos.length
+      ) {
+        startPhotosPosition.value = selectDetail.value.productPhotos.length - 1
+      }
+
+      // 这里可以添加实际删除图片的API调用
+      // deleteImageAPI(deletedImage).then(...)
+    })
+    .catch(() => {
+      // 取消删除
+    })
+}
+
+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)
+      showToast('删除成功')
+      if (selectDetail.value.boxPhotos.length === 0) {
+        showBoxPreview.value = false
+      } else if (
+        startBoxPosition.value >= selectDetail.value.boxPhotos.length
+      ) {
+        startBoxPosition.value = selectDetail.value.boxPhotos.length - 1
+      }
+
+      // 这里可以添加实际删除图片的API调用
+      // deleteImageAPI(deletedImage).then(...)
+    })
+    .catch(() => {
+      // 取消删除
+    })
+}
+
+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 boxFiles.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
+  }
+  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')
+}
+</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>

+ 1 - 1
src/views/transfer/move/list/index.vue

@@ -94,7 +94,7 @@ const statusMap=ref({
 })
 const loadData = async () => {
   showLoading()
-  getMoveTaskList({ warehouse }).then(res => {
+  getMoveTaskList({ warehouseCode:warehouse }).then(res => {
     taskList.value=res.data
   }).finally(() => {
     closeLoading()