前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【愚公系列】2023年02月 WMS智能仓储系统-016.库存管理和仓内作业(库存管理、仓内加工、库存移动)

【愚公系列】2023年02月 WMS智能仓储系统-016.库存管理和仓内作业(库存管理、仓内加工、库存移动)

作者头像
愚公搬代码
发布于 2023-03-16 09:23:57
发布于 2023-03-16 09:23:57
77900
代码可运行
举报
文章被收录于专栏:历史专栏历史专栏
运行总次数:0
代码可运行

文章目录


前言

这节主要分为两个模块:

  • 库存管理:库存管理的作用是确保有足够的库存量以满足消费者需求,减少库存空置和库存损耗,并有效地控制库存成本。
  • 仓内作业:仓内作业的作用是帮助仓库提高组织效率和完成仓库管理。它提高了仓库存储条件、运输条件和信息系统的效率,并节省了人力成本。

一、库存管理

库存管理数据主要是来源于收获管理,所哟库存和库位基本只有查询功能。

1.1 页面代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- Warehouse Setting -->
<template>
  <div class="container">
    <div>
      <v-tabs v-model="data.activeTab" stacked @update:model-value="method.changeTabs">
        <v-tab v-for="(item, index) of tabsConfig" :key="index" :value="item.value">
          <v-icon>{{ item.icon }}</v-icon>
          <p class="tabItemTitle">{{ item.tabName }}</p>
        </v-tab>
      </v-tabs>

      <!-- Main Content -->
      <v-card class="mt-5">
        <v-card-text>
          <v-window v-model="data.activeTab">
            <v-window-item value="tabStockLocation">
              <tabStockLocation ref="tabStockLocationRef" />
            </v-window-item>
            <v-window-item value="tabStock">
              <tabStock ref="tabStockRef" />
            </v-window-item>
          </v-window>
        </v-card-text>
      </v-card>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
import i18n from '@/languages/i18n'
import tabStockLocation from './tabStockLocation.vue'
import tabStock from './tabStock.vue'

const tabStockLocationRef = ref()
const tabStockRef = ref()

const tabsConfig = [
  {
    value: 'tabStockLocation',
    icon: 'mdi-database',
    tabName: i18n.global.t('wms.stockManagement.stockLocation')
  },
  {
    value: 'tabStock',
    icon: 'mdi-warehouse',
    tabName: i18n.global.t('wms.stockManagement.stock')
  }
]

const data = reactive({
  activeTab: '',
  isLoadstockLocation: false,
  isLoadstock: false
})

const method = reactive({
  changeTabs: (e: any): void => {
    nextTick(() => {
      switch (e) {
        case 'tabStockLocation':
          // Tips:Must be write the nextTick so that can get DOM!!
          if (tabStockLocationRef?.value?.getStockLocationList) {
            tabStockLocationRef.value.getStockLocationList()
          }
          break
        case 'tabStock':
          if (tabStockRef?.value?.getStockList) {
            tabStockRef.value.getStockList()
          }
          break
      }
    })
  }
})

onMounted(() => { })
</script>

<style scoped lang="less">
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
  <div class="operateArea">
    <v-row no-gutters>
      <!-- Operate Btn -->
      <v-col cols="3" class="col">
        <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
        <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
      </v-col>

      <!-- Search Input -->
      <v-col cols="9">
        <v-row no-gutters @keyup.enter="method.sureSearch">
          <v-col cols="4"></v-col>
          <v-col cols="4"></v-col>
          <v-col cols="4">
            <v-text-field
              v-model="data.searchForm.spu_name"
              clearable
              hide-details
              density="comfortable"
              class="searchInput ml-5 mt-1"
              :label="$t('wms.stockList.spu_name')"
              variant="solo"
            >
            </v-text-field>
          </v-col>
        </v-row>
      </v-col>
    </v-row>
  </div>

  <!-- Table -->
  <div
    class="mt-5"
    :style="{
      height: cardHeight
    }"
  >
    <vxe-table ref="xTableWarehouse" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
      <template #empty>
        {{ i18n.global.t('system.page.noData') }}
      </template>
      <vxe-column type="seq" width="60"></vxe-column>
      <vxe-column type="checkbox" width="50"></vxe-column>
      <vxe-column field="spu_code" :title="$t('wms.stockList.spu_code')"></vxe-column>
      <vxe-column field="spu_name" :title="$t('wms.stockList.spu_name')"></vxe-column>
      <vxe-column field="sku_code" :title="$t('wms.stockList.sku_code')">
        <template #default="{ row }">
          <div :class="'text-decoration-none'" @click="method.showSkuInfo(row)"> {{ row.sku_code }}</div>
        </template>
      </vxe-column>
      <vxe-column field="qty" :title="$t('wms.stockList.qty')"></vxe-column>
      <vxe-column field="qty_available" :title="$t('wms.stockList.qty_available')"></vxe-column>
      <vxe-column field="qty_locked" :title="$t('wms.stockList.qty_locked')"></vxe-column>
      <vxe-column field="qty_frozen" :title="$t('wms.stockList.qty_frozen')"></vxe-column>
      <vxe-column field="qty_asn" :title="$t('wms.stockList.qty_asn')"></vxe-column>
      <vxe-column field="qty_to_unload" :title="$t('wms.stockList.qty_to_unload')"></vxe-column>
      <vxe-column field="qty_to_sort" :title="$t('wms.stockList.qty_to_sort')"></vxe-column>
      <vxe-column field="qty_sorted" :title="$t('wms.stockList.qty_sorted')"></vxe-column>
      <vxe-column field="shortage_qty" :title="$t('wms.stockList.shortage_qty')"></vxe-column>
    </vxe-table>
    <custom-pager
      :current-page="data.tablePage.pageIndex"
      :page-size="data.tablePage.pageSize"
      perfect
      :total="data.tablePage.total"
      :page-sizes="PAGE_SIZE"
      :layouts="PAGE_LAYOUT"
      @page-change="method.handlePageChange"
    >
    </custom-pager>
  </div>
  <skuInfo :show-dialog="data.showDialogShowInfo" :sku_id="data.sku_id" @close="method.closeDialogShowInfo" />
</template>

<script lang="ts" setup>
import { computed, ref, reactive, watch } from 'vue'
import { VxePagerEvents, VxeTablePropTypes } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { StockVO } from '@/types/WMS/StockManagement'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { getStockList } from '@/api/wms/stockManagement'
import tooltipBtn from '@/components/tooltip-btn.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import skuInfo from './sku-info.vue'
import { exportData } from '@/utils/exportTable'

const xTableWarehouse = ref()

const data = reactive({
  sku_id: 0,
  showDialog: false,
  showDialogShowInfo: false,
  searchForm: {
    spu_name: ''
  },
  activeTab: null,
  tableData: ref<StockVO[]>([]),
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  }),
  timer: ref<any>(null)
})

const method = reactive({
  closeDialogShowInfo: () => {
    data.showDialogShowInfo = false
  },
  showSkuInfo(row: StockVO) {
    data.sku_id = row.sku_id
    data.showDialogShowInfo = true
  },
  sumNum: (list: any[], field: string) => {
    let count = 0
    list.forEach((item) => {
      count += Number(item[field])
    })
    return count
  },
  // footerMethod:ref<VxeTablePropTypes.FooterMethod>({ columns, data }) => {
  //   columns.map((column, columnIndex) => {
  //     if (columnIndex === 0) {
  //       return '合计'
  //     }
  //     if (['qty', 'qty_available'].includes(column.field)) {
  //       return method.sumNum(data, column.field)
  //     }
  //     return null
  //   })
  // },
  // Refresh data
  refresh: () => {
    method.getStockList()
  },
  getStockList: async () => {
    const { data: res } = await getStockList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },
  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize

    method.getStockList()
  }),
  exportTable: () => {
    const $table = xTableWarehouse.value
    exportData({
      table: $table,
      filename: i18n.global.t('wms.stockManagement.stock'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },
  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.getStockList()
  }
})

const cardHeight = computed(() => computedCardHeight({}))
const tableHeight = computed(() => computedTableHeight({}))

defineExpose({
  getStockList: method.getStockList
})
watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style lang="less" scoped>
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
  <div class="operateArea">
    <v-row no-gutters>
      <!-- Operate Btn -->
      <v-col cols="3" class="col">
        <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
        <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
      </v-col>

      <!-- Search Input -->
      <v-col cols="9">
        <v-row no-gutters @keyup.enter="method.sureSearch">
          <v-col cols="4"></v-col>
          <v-col cols="4"></v-col>
          <v-col cols="4">
            <v-text-field
              v-model="data.searchForm.location_name"
              clearable
              hide-details
              density="comfortable"
              class="searchInput ml-5 mt-1"
              :label="$t('wms.stockLocation.location_name')"
              variant="solo"
            >
            </v-text-field>
          </v-col>
        </v-row>
      </v-col>
    </v-row>
  </div>

  <!-- Table -->
  <div
    class="mt-5"
    :style="{
      height: cardHeight
    }"
  >
    <vxe-table ref="xTableStockLocation" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
      <template #empty>
        {{ i18n.global.t('system.page.noData') }}
      </template>
      <vxe-column type="seq" width="60"></vxe-column>
      <vxe-column type="checkbox" width="50"></vxe-column>
      <vxe-column field="warehouse_name" :title="$t('wms.stockLocation.warehouse_name')"></vxe-column>
      <vxe-column field="location_name" :title="$t('wms.stockLocation.location_name')"></vxe-column>
      <vxe-column field="spu_code" :title="$t('wms.stockLocation.spu_code')">
        <template #default="{ row }">
          <div :class="'text-decoration-none'" @click="method.showSkuInfo(row)"> {{ row.sku_code }}</div>
        </template>
      </vxe-column>
      <vxe-column field="spu_name" :title="$t('wms.stockLocation.spu_name')"></vxe-column>
      <vxe-column field="sku_code" :title="$t('wms.stockLocation.sku_code')"></vxe-column>
      <vxe-column field="sku_name" :title="$t('wms.stockLocation.sku_name')"></vxe-column>
      <vxe-column field="qty" :title="$t('wms.stockLocation.qty')"></vxe-column>
      <vxe-column field="qty_available" :title="$t('wms.stockLocation.qty_available')"></vxe-column>
      <vxe-column field="qty_locked" :title="$t('wms.stockLocation.qty_locked')"></vxe-column>
      <vxe-column field="qty_frozen" :title="$t('wms.stockLocation.qty_frozen')"></vxe-column>
    </vxe-table>
    <custom-pager
      :current-page="data.tablePage.pageIndex"
      :page-size="data.tablePage.pageSize"
      perfect
      :total="data.tablePage.total"
      :page-sizes="PAGE_SIZE"
      :layouts="PAGE_LAYOUT"
      @page-change="method.handlePageChange"
    >
    </custom-pager>
  </div>
  <skuInfo :show-dialog="data.showDialogShowInfo" :sku_id="data.sku_id" @close="method.closeDialogShowInfo" />
</template>

<script lang="ts" setup>
import { computed, ref, reactive, watch } from 'vue'
import { VxePagerEvents } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { StockLocationVO } from '@/types/WMS/StockManagement'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { getStockLocationList } from '@/api/wms/stockManagement'
import tooltipBtn from '@/components/tooltip-btn.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import skuInfo from './sku-info.vue'
import { exportData } from '@/utils/exportTable'

const xTableStockLocation = ref()

const data = reactive({
  sku_id: 0,
  showDialog: false,
  showDialogShowInfo: false,
  searchForm: {
    location_name: ''
  },
  activeTab: null,
  tableData: ref<StockLocationVO[]>([]),
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  }),
  timer: ref<any>(null)
})

const method = reactive({
  closeDialogShowInfo: () => {
    data.showDialogShowInfo = false
  },
  showSkuInfo(row: StockLocationVO) {
    data.sku_id = row.sku_id
    data.showDialogShowInfo = true
  },
  // Refresh data
  refresh: () => {
    method.getStockLocationList()
  },
  getStockLocationList: async () => {
    const { data: res } = await getStockLocationList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },
  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize

    method.getStockLocationList()
  }),
  exportTable: () => {
    const $table = xTableStockLocation.value
    exportData({
      table: $table,
      filename: i18n.global.t('wms.stockManagement.stockLocation'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },
  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.getStockLocationList()
  }
})

const cardHeight = computed(() => computedCardHeight({}))
const tableHeight = computed(() => computedTableHeight({}))

defineExpose({
  getStockLocationList: method.getStockLocationList
})
watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style lang="less" scoped>
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>

1.2 接口代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
     [Route("stock")]
     [ApiController]
     [ApiExplorerSettings(GroupName = "WMS")]
     public class StockController : BaseController
     {
         #region Args
 
         /// <summary>
         /// stock Service
         /// </summary>
         private readonly IStockService _stockService;
 
         /// <summary>
         /// Localizer Service
         /// </summary>
         private readonly IStringLocalizer<ModernWMS.Core.MultiLanguage> _stringLocalizer;
         #endregion
 
         #region constructor
         /// <summary>
         /// constructor
         /// </summary>
         /// <param name="stockService">stock Service</param>
        /// <param name="stringLocalizer">Localizer</param>
         public StockController(
             IStockService stockService
           , IStringLocalizer<ModernWMS.Core.MultiLanguage> stringLocalizer
             )
         {
             this._stockService = stockService;
            this._stringLocalizer= stringLocalizer;
         }
         #endregion
 
         #region Api
         /// <summary>
         /// stock details page search
         /// </summary>
         /// <param name="pageSearch">args</param>
         /// <returns></returns>
         [HttpPost("stock-list")]
         public async Task<ResultModel<PageData<StockManagementViewModel>>> StockPageAsync(PageSearch pageSearch)
         {
             var (data, totals) = await _stockService.StockPageAsync(pageSearch, CurrentUser);
              
             return ResultModel<PageData<StockManagementViewModel>>.Success(new PageData<StockManagementViewModel>
             {
                 Rows = data,
                 Totals = totals
             });
         }
        /// <summary>
        /// location stock page search
        /// </summary>
        /// <param name="pageSearch">args</param>
        /// <returns></returns>
        [HttpPost("location-list")]
        public async Task<ResultModel<PageData<LocationStockManagementViewModel>>> LocationStockPageAsync(PageSearch pageSearch)
        {
            var (data, totals) = await _stockService.LocationStockPageAsync(pageSearch, CurrentUser);

            return ResultModel<PageData<LocationStockManagementViewModel>>.Success(new PageData<LocationStockManagementViewModel>
            {
                Rows = data,
                Totals = totals
            });
        }

        /// <summary>
        /// page search select
        /// </summary>
        /// <param name="pageSearch">args</param>
        /// <returns></returns>
        [HttpPost("select")]
        public async Task<ResultModel<PageData<StockViewModel>>> SelectPageAsync(PageSearch pageSearch)
        {
            var (data, totals) = await _stockService.SelectPageAsync(pageSearch, CurrentUser);

            return ResultModel<PageData<StockViewModel>>.Success(new PageData<StockViewModel>
            {
                Rows = data,
                Totals = totals
            });
        }

        /// <summary>
        /// sku page search select
        /// </summary>
        /// <param name="pageSearch">args</param>
        /// <returns></returns>
        [HttpPost("sku-select")]
        public async Task<ResultModel<PageData<SkuSelectViewModel>>> SkuSelectPageAsync(PageSearch pageSearch)
        {
            var (data, totals) = await _stockService.SkuSelectPageAsync(pageSearch, CurrentUser);

            return ResultModel<PageData<SkuSelectViewModel>>.Success(new PageData<SkuSelectViewModel>
            {
                Rows = data,
                Totals = totals
            });
        }
        #endregion

    }

二、仓内作业

1.仓内加工

仓内加工主要分为:

  • 拆分加工
  • 组合加工

1.1 页面代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- Warehouse Processing -->
<template>
  <div class="container">
    <div>
      <!-- Main Content -->
      <v-card class="mt-5">
        <v-card-text>
          <v-window v-model="data.activeTab">
            <v-window-item>
              <div class="operateArea">
                <v-row no-gutters>
                  <!-- Operate Btn -->
                  <v-col cols="3" class="col">
                    <tooltip-btn
                      icon="mdi-arrow-split-vertical"
                      :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.process_split')"
                      @click="method.add(PROCESS_JOB_SPLIT)"
                    ></tooltip-btn>
                    <tooltip-btn
                      icon="mdi-group"
                      :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.process_combine')"
                      @click="method.add(PROCESS_JOB_COMBINE)"
                    ></tooltip-btn>
                    <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
                    <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
                  </v-col>

                  <!-- Search Input -->
                  <v-col cols="9">
                    <v-row no-gutters @keyup.enter="method.sureSearch">
                      <v-col cols="4"></v-col>
                      <v-col cols="4"></v-col>
                      <v-col cols="4">
                        <v-text-field
                          v-model="data.searchForm.job_code"
                          clearable
                          hide-details
                          density="comfortable"
                          class="searchInput ml-5 mt-1"
                          :label="$t('wms.warehouseWorking.warehouseProcessing.job_code')"
                          variant="solo"
                        >
                        </v-text-field>
                      </v-col>
                    </v-row>
                  </v-col>
                </v-row>
              </div>

              <!-- Table -->
              <div
                class="mt-5"
                :style="{
                  height: cardHeight
                }"
              >
                <vxe-table ref="xTable" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column type="checkbox" width="50"></vxe-column>
                  <vxe-column field="job_code" width="150px" :title="$t('wms.warehouseWorking.warehouseProcessing.job_code')"></vxe-column>
                  <vxe-column field="job_type" :title="$t('wms.warehouseWorking.warehouseProcessing.job_type')">
                    <template #default="{ row, column }">
                      <span>{{ formatProcessJobType(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="adjust_status" :title="$t('wms.warehouseWorking.warehouseProcessing.adjust_status')">
                    <template #default="{ row, column }">
                      <span>{{ formatIsValid(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="processor" :title="$t('wms.warehouseWorking.warehouseProcessing.processor')"></vxe-column>
                  <vxe-column field="process_time" width="170px" :title="$t('wms.warehouseWorking.warehouseProcessing.process_time')">
                    <template #default="{ row, column }">
                      <span>{{ formatDate(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="creator" :title="$t('wms.warehouseWorking.warehouseProcessing.creator')"></vxe-column>
                  <vxe-column field="create_time" width="170px" :title="$t('wms.warehouseWorking.warehouseProcessing.create_time')"></vxe-column>
                  <vxe-column field="operate" :title="$t('system.page.operate')" width="250" :resizable="false" show-overflow>
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-eye-outline"
                        :tooltip-text="$t('system.page.view')"
                        @click="method.viewRow(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-book-check-outline"
                        :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.confirmProcess')"
                        :disabled="method.confirmProcessBtnDisabled(row)"
                        @click="method.confirmProcess(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-book-open-outline"
                        :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.confirmAdjust')"
                        :disabled="method.confirmAdjustBtnDisabled(row)"
                        @click="method.confirmAdjust(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        :disabled="method.confirmProcessBtnDisabled(row)"
                        @click="method.deleteRow(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                </vxe-table>
                <custom-pager
                  :current-page="data.tablePage.pageIndex"
                  :page-size="data.tablePage.pageSize"
                  perfect
                  :total="data.tablePage.total"
                  :page-sizes="PAGE_SIZE"
                  :layouts="PAGE_LAYOUT"
                  @page-change="method.handlePageChange"
                >
                </custom-pager>
              </div>
            </v-window-item>
          </v-window>
        </v-card-text>
      </v-card>
      <addOrUpdateDialog
        :show-dialog="data.showDialog"
        :form="data.dialogForm"
        :process-type="data.processType"
        @close="method.closeDialog"
        @saveSuccess="method.saveSuccess"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, ref, reactive, onActivated, watch, nextTick } from 'vue'
import { VxePagerEvents } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { WarehouseProcessingVO, WarehouseProcessingDetailVO } from '@/types/WarehouseWorking/WarehouseProcessing'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { deleteStockProcess, getStockProcessList, getStockProcessOne, confirmAdjustment, confirmProcess } from '@/api/wms/warehouseProcessing'
import { PROCESS_JOB_COMBINE, PROCESS_JOB_SPLIT } from '@/constant/warehouseWorking'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { formatIsValid, formatDate } from '@/utils/format/formatSystem'
import { formatProcessJobType } from '@/utils/format/formatWarehouseWorking'
import tooltipBtn from '@/components/tooltip-btn.vue'
import addOrUpdateDialog from './add-or-update-process.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import { exportData } from '@/utils/exportTable'

const xTable = ref()

const data = reactive({
  showDialog: false,
  processType: PROCESS_JOB_COMBINE,
  timer: ref<any>(null),
  activeTab: null,
  searchForm: {
    job_code: ''
  },
  tableData: ref<WarehouseProcessingVO[]>([]),
  dialogForm: {
    id: 0,
    job_code: '',
    job_type: PROCESS_JOB_COMBINE,
    process_status: false,
    processor: '',
    process_time: '',
    source_detail_list: ref<any[]>([]),
    target_detail_list: ref<any[]>([]),
    creator: '',
    create_time: '',
    adjust_status: false
  },
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  })
})

const method = reactive({
  // Open a dialog to add
  add: (jobType: boolean) => {
    data.processType = jobType
    data.dialogForm = {
      id: 0,
      job_code: '',
      job_type: jobType,
      process_status: false,
      processor: '',
      process_time: '',
      source_detail_list: [],
      target_detail_list: [],
      creator: '',
      create_time: '',
      adjust_status: false
    }
    nextTick(() => {
      data.showDialog = true
    })
  },

  // After add or update success.
  saveSuccess: () => {
    method.refresh()
    method.closeDialog()
  },

  // Refresh data
  refresh: () => {
    method.getStockProcessList()
  },

  // Shut add or update dialog
  closeDialog: () => {
    data.showDialog = false
  },

  getStockProcessList: async () => {
    const { data: res } = await getStockProcessList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },

  viewRow: async (row: WarehouseProcessingVO) => {
    await method.getOne(row.id)
    nextTick(() => {
      data.showDialog = true
    })
  },

  getOne: async (id: number) => {
    const { data: res } = await getStockProcessOne(id)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }

    data.dialogForm = res.data
    data.processType = res.data.job_type
  },

  deleteRow(row: WarehouseProcessingVO) {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteMessage'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await deleteStockProcess(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }

          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('system.page.delete') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  },

  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize
    method.refresh()
  }),

  exportTable: () => {
    const $table = xTable.value
    exportData({
      table: $table,
      filename: i18n.global.t('router.sideBar.warehouseProcessing'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },

  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.refresh()
  },

  // The btn will become disabled when the 'process_status' is false
  confirmProcessBtnDisabled: (row: WarehouseProcessingVO) => !!row.process_status,

  // The btn will become disabled when the 'process_status' is false
  // or the 'adjust_status' is true
  confirmAdjustBtnDisabled: (row: WarehouseProcessingVO) => !row.process_status || !!row.adjust_status,

  confirmProcess: async (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('wms.warehouseWorking.warehouseProcessing.beforeConfirmProcess'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await confirmProcess(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }
          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.confirmProcess') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  },

  confirmAdjust: async (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('wms.warehouseWorking.warehouseProcessing.beforeConfirmAdjust'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await confirmAdjustment(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }
          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.confirmAdjust') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  }
})

onActivated(() => {
  method.refresh()
})

const cardHeight = computed(() => computedCardHeight({ hasTab: false }))
const tableHeight = computed(() => computedTableHeight({ hasTab: false }))

watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style scoped lang="less">
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
  <v-dialog v-model="isShow" width="80%" transition="dialog-top-transition" :persistent="true">
    <template #default>
      <v-card>
        <v-toolbar color="white" :title="jobTypeComp"></v-toolbar>
        <v-card-text>
          <v-row>
            <!-- Source Table -->
            <v-col cols="6">
              <div class="dataTable">
                <div class="toolbar">
                  <div class="toolbarTitle">
                    <p style="color: #999">{{ $t('wms.warehouseWorking.warehouseProcessing.source') }}</p>
                  </div>
                  <tooltip-btn
                    icon="mdi-plus"
                    :tooltip-text="$t('system.page.add')"
                    size="x-small"
                    :disabled="operateDisabled"
                    @click="method.openSelect('source')"
                  ></tooltip-btn>
                </div>
                <vxe-table
                  ref="xTableSource"
                  :column-config="{ minWidth: '100px' }"
                  :data="data.form.source_detail_list"
                  :height="SYSTEM_HEIGHT.SELECT_TABLE"
                  :edit-config="{ trigger: 'click', mode: 'cell' }"
                  :edit-rules="data.validRulesSource"
                  align="center"
                >
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column field="operate" width="50" :title="$t('system.page.operate')" :resizable="false">
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        @click="method.deleteRowSource(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                  <vxe-column field="spu_code" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_code')"></vxe-column>
                  <vxe-column field="spu_name" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_name')"></vxe-column>
                  <vxe-column field="sku_code" :title="$t('wms.warehouseWorking.warehouseProcessing.sku_code')"></vxe-column>
                  <vxe-column
                    field="qty"
                    :title="$t('wms.warehouseWorking.warehouseProcessing.qty')"
                    :edit-render="{ autofocus: '.vxe-input--inner' }"
                  >
                    <template #edit="{ row }">
                      <vxe-input v-model="row.qty" type="text"></vxe-input>
                    </template>
                  </vxe-column>
                  <vxe-column field="unit" :title="$t('wms.warehouseWorking.warehouseProcessing.unit')"></vxe-column>
                </vxe-table>
              </div>
            </v-col>

            <!-- Target Table -->
            <v-col cols="6">
              <div class="dataTable">
                <div class="toolbar">
                  <div class="toolbarTitle">
                    <p style="color: #999">{{ $t('wms.warehouseWorking.warehouseProcessing.target') }}</p>
                  </div>
                  <tooltip-btn
                    icon="mdi-plus"
                    :tooltip-text="$t('system.page.add')"
                    size="x-small"
                    :disabled="operateDisabled"
                    @click="method.openSelect('target')"
                  ></tooltip-btn>
                </div>
                <vxe-table
                  ref="xTableTarget"
                  :column-config="{ minWidth: '100px' }"
                  :data="data.form.target_detail_list"
                  :height="SYSTEM_HEIGHT.SELECT_TABLE"
                  :edit-config="{ trigger: 'click', mode: 'cell' }"
                  :edit-rules="data.validRulesTarget"
                  align="center"
                >
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column field="operate" width="50" :title="$t('system.page.operate')" :resizable="false">
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        @click="method.deleteRowTarget(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                  <vxe-column field="spu_code" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_code')"></vxe-column>
                  <vxe-column field="spu_name" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_name')"></vxe-column>
                  <vxe-column field="sku_code" :title="$t('wms.warehouseWorking.warehouseProcessing.sku_code')"></vxe-column>
                  <vxe-column
                    field="qty"
                    :title="$t('wms.warehouseWorking.warehouseProcessing.qty')"
                    :edit-render="{ autofocus: '.vxe-input--inner' }"
                  >
                    <template #edit="{ row }">
                      <vxe-input v-model="row.qty" type="text"></vxe-input>
                    </template>
                  </vxe-column>
                  <vxe-column field="unit" width="60" :title="$t('wms.warehouseWorking.warehouseProcessing.unit')"></vxe-column>
                  <vxe-column field="location_name" :title="$t('wms.warehouseWorking.warehouseProcessing.target_location')" :edit-render="{}">
                    <template #edit="{ row }">
                      <vxe-input v-model="row.location_name" readonly type="search" @search-click="method.openLocationSelect(row)"></vxe-input>
                    </template>
                  </vxe-column>
                </vxe-table>
              </div>
            </v-col>
          </v-row>
        </v-card-text>
        <v-card-actions class="justify-end">
          <v-btn variant="text" @click="method.closeDialog">{{ $t('system.page.close') }}</v-btn>
          <v-btn color="primary" variant="text" :disabled="operateDisabled" @click="method.submit">{{ $t('system.page.submit') }}</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </v-dialog>

  <commodity-select :show-dialog="data.showCommodityDialogSelect" @close="method.closeDialogSelect('source')" @sureSelect="method.sureSelect" />
  <sku-select :show-dialog="data.showSkuDialogSelect" @close="method.closeDialogSelect('target')" @sureSelect="method.sureSelect" />
  <location-select :show-dialog="data.showLocationDialogSelect" @close="method.closeLocationDialogSelect" @sureSelect="method.sureSelectLocation" />
</template>

<script lang="ts" setup>
import { reactive, computed, ref, watch } from 'vue'
import { VxeTablePropTypes } from 'vxe-table'
import { WarehouseProcessingVO, WarehouseProcessingDetailVO } from '@/types/WarehouseWorking/WarehouseProcessing'
import i18n from '@/languages/i18n'
import { hookComponent } from '@/components/system/index'
import { addStockProcess } from '@/api/wms/warehouseProcessing'
import { SYSTEM_HEIGHT, errorColor } from '@/constant/style'
import { removeObjectNull } from '@/utils/common'
import { PROCESS_JOB_COMBINE, PROCESS_JOB_SPLIT } from '@/constant/warehouseWorking'
import commoditySelect from '@/components/select/commodity-select.vue'
import locationSelect from '@/components/select/location-select.vue'
import skuSelect from '@/components/select/sku-select.vue'
import tooltipBtn from '@/components/tooltip-btn.vue'
import { exportData } from '@/utils/exportTable'
import { isInteger } from '@/utils/dataVerification/tableRule'

const emit = defineEmits(['close', 'saveSuccess'])
const xTableSource = ref()
const xTableTarget = ref()

const props = defineProps<{
  showDialog: boolean
  form: WarehouseProcessingVO
  processType: boolean
}>()

const isShow = computed(() => props.showDialog)
const isUpdate = computed(() => props.form.id && props.form.id > 0)
const jobTypeComp = computed(() => (data.form.job_type === PROCESS_JOB_COMBINE
    ? i18n.global.t('wms.warehouseWorking.warehouseProcessing.process_combine')
    : i18n.global.t('wms.warehouseWorking.warehouseProcessing.process_split')))
const operateDisabled = computed(() => !!isUpdate.value)

const method = reactive({
  initForm: () => {
    data.form = props.form
    data.form.job_type = props.processType
  },

  closeDialog: () => {
    emit('close')
  },

  openSelect: (type: string) => {
    data.curSelectType = type

    if (type === 'source') {
      data.showCommodityDialogSelect = true
    } else if (type === 'target') {
      // Target should select the data with 'sku-select-modal'
      data.showSkuDialogSelect = true
    }
  },

  closeDialogSelect: (type: string) => {
    if (type === 'source') {
      data.showCommodityDialogSelect = false
    } else if (type === 'target') {
      // Target should select the data with 'sku-select-modal'
      data.showSkuDialogSelect = false
    }
  },

  sureSelect: (selectRecords: any) => {
    if (data.curSelectType === 'source') {
      method.insertSourceData(selectRecords)
    } else if (data.curSelectType === 'target') {
      method.insertTargetData(selectRecords)
    }
  },

  openLocationSelect: (row: WarehouseProcessingDetailVO) => {
    data.curSelectRow = row
    data.showLocationDialogSelect = true
  },

  closeLocationDialogSelect: () => {
    data.showLocationDialogSelect = false
  },

  sureSelectLocation: (selectRecords: any) => {
    if (selectRecords.length > 0) {
      const $table = xTableTarget.value
      const tableData = $table.getTableData().fullData
      tableData.forEach((row: WarehouseProcessingDetailVO) => {
        if (data.curSelectRow.sku_id === row.sku_id) {
          row.goods_location_id = selectRecords[0].id
          row.location_name = selectRecords[0].location_name
        }
      })
      // Tips: Must to reload!
      $table.reloadData(tableData)
    }
  },

  insertSourceData: (selectRecords: any) => {
    const $table = xTableSource.value
    const tableData = $table.getTableData().fullData

    // Combine: That can select more source commodity
    if (data.form.job_type === PROCESS_JOB_COMBINE) {
      for (const record of selectRecords) {
        const isRepeat = tableData.some((data: WarehouseProcessingDetailVO) => data.sku_id === record.sku_id)
        if (isRepeat) {
          continue
        }

        $table.insertAt(
          {
            id: 0,
            stock_process_id: 0,
            sku_id: record.sku_id,
            goods_owner_id: record.goods_owner_id,
            goods_location_id: record.goods_location_id,
            qty: record.qty_available || 0,
            tenant_id: 0,
            is_source: true,
            spu_code: record.spu_code,
            spu_name: record.spu_name,
            sku_code: record.sku_code,
            unit: record.unit,
            is_update_stock: false,
            qty_available: record.qty_available
          },
          -1
        )
        // })
      }
    } else if (data.form.job_type === PROCESS_JOB_SPLIT) {
      // Split: That just can select one source commodity.
      // It should remove all data before insert.
      $table.remove()
      $table.insertAt(
        {
          id: 0,
          stock_process_id: 0,
          sku_id: selectRecords[0].sku_id,
          goods_owner_id: selectRecords[0].goods_owner_id,
          goods_location_id: selectRecords[0].goods_location_id,
          qty: selectRecords[0].qty_available || 0,
          tenant_id: 0,
          is_source: true,
          spu_code: selectRecords[0].spu_code,
          spu_name: selectRecords[0].spu_name,
          sku_code: selectRecords[0].sku_code,
          unit: selectRecords[0].unit,
          is_update_stock: false,
          qty_available: selectRecords[0].qty_available
        },
        -1
      )
    }
  },

  insertTargetData: (selectRecords: any) => {
    const $table = xTableTarget.value
    const tableData = $table.getTableData().fullData

    // Combine: That just can select one target commodity
    if (data.form.job_type === PROCESS_JOB_COMBINE) {
      // It should remove all data before insert.
      $table.remove()
      $table.insertAt(
        {
          id: 0,
          stock_process_id: 0,
          sku_id: selectRecords[0].sku_id,
          goods_owner_id: 0,
          goods_location_id: 0,
          qty: 0,
          tenant_id: 0,
          is_source: false,
          spu_code: selectRecords[0].spu_code,
          spu_name: selectRecords[0].spu_name,
          sku_code: selectRecords[0].sku_code,
          unit: selectRecords[0].unit,
          is_update_stock: false
        },
        -1
      )
    } else if (data.form.job_type === PROCESS_JOB_SPLIT) {
      // Split: That can select more target commodity
      for (const record of selectRecords) {
        const isRepeat = tableData.some((data: WarehouseProcessingDetailVO) => data.sku_id === record.sku_id)
        if (isRepeat) {
          continue
        }
        $table.insertAt(
          {
            id: 0,
            stock_process_id: 0,
            sku_id: record.sku_id,
            goods_owner_id: 0,
            goods_location_id: 0,
            qty: 0,
            tenant_id: 0,
            is_source: false,
            spu_code: record.spu_code,
            spu_name: record.spu_name,
            sku_code: record.sku_code,
            unit: record.unit,
            is_update_stock: false
          },
          -1
        )
      }
    }
  },

  // Export table
  exportTable: (type: string) => {
    const $table = type === 'source' ? xTableSource.value : xTableTarget.value
    exportData({
      table: $table,
      filename: i18n.global.t('router.sideBar.commodityManagement'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },

  submit: async () => {
    const validSource = await method.validSourceTable()
    const validTarget = await method.validTargetTable()
    if (!validSource || !validTarget) {
      return
    }

    const form = method.constructFormBody()

    const { data: res } = await addStockProcess(form)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    hookComponent.$message({
      type: 'success',
      content: `${ i18n.global.t('system.page.submit') }${ i18n.global.t('system.tips.success') }`
    })
    emit('saveSuccess')
  },

  constructFormBody: () => {
    const $tableSource = xTableSource.value
    const $tableTarget = xTableTarget.value

    const tableSourceRecords = $tableSource.getTableData().fullData
    const tableTargetRecords = $tableTarget.getTableData().fullData

    // Need combine source and target to server.
    let form = { ...data.form }
    form.detailList = [...tableSourceRecords, ...tableTargetRecords]
    form = removeObjectNull(form)

    delete form.source_detail_list
    delete form.target_detail_list

    return form
  },

  validSourceTable: async () => {
    const $table = xTableSource.value
    const tableData = $table.getTableData().fullData

    // 1.The table must have data.
    if (!tableData.length) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.tips.detailLengthIsZero')
      })
      return false
    }

    // 2.The properties valid.
    const errMap = await $table.validate()
    if (errMap) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
      return false
    }

    return true
  },

  validTargetTable: async () => {
    const $table = xTableTarget.value
    const tableData = $table.getTableData().fullData

    // 1.The table must have data.
    if (!tableData.length) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.tips.detailLengthIsZero')
      })
      return false
    }

    // 2.The properties valid.
    const errMap = await $table.validate()
    if (errMap) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
      return false
    }

    return true
  },

  // The 'qty' can't more than 'qty_available'
  validQty: ({ cellValue, row }: any) => {
    const qty = cellValue || 0
    const qtyAvailable = row.qty_available || 0

    if (qty > qtyAvailable) {
      return new Error(`${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qtyMoreThanAvailable') } ${ qtyAvailable }`)
    }
  },

  deleteRowSource: (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteDetailMessage'),
      handleConfirm: async () => {
        if (row) {
          const $table = xTableSource.value
          $table.remove(row)
        }
      }
    })
  },

  deleteRowTarget: (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteDetailMessage'),
      handleConfirm: async () => {
        if (row) {
          const $table = xTableTarget.value
          $table.remove(row)
        }
      }
    })
  }
})

const data = reactive({
  tableData: [],
  // 'source' | 'target'
  curSelectType: '',

  showCommodityDialogSelect: false,
  showSkuDialogSelect: false,
  showLocationDialogSelect: false,

  form: ref<WarehouseProcessingVO>({
    id: 0,
    job_code: '',
    job_type: PROCESS_JOB_COMBINE,
    process_status: false,
    processor: '',
    process_time: '',
    source_detail_list: [],
    target_detail_list: []
  }),
  curSelectRow: ref<WarehouseProcessingDetailVO>({
    id: 0,
    stock_process_id: 0,
    sku_id: 0,
    goods_owner_id: 0,
    goods_location_id: 0,
    qty: 0,
    is_source: true,
    spu_code: '',
    spu_name: '',
    sku_code: '',
    unit: '',
    is_update_stock: false
  }),
  validRulesSource: ref<any>({
    qty: [
      { required: true, message: `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qty') }` },
      {
        validator: method.validQty
      },
      {
        validator: isInteger,
        validNumerical: 'greaterThanZero',
        trigger: 'change'
      }
    ]
  }),
  validRulesTarget: ref<any>({
    qty: [
      { required: true, message: `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qty') }` },
      {
        validator: isInteger,
        validNumerical: 'greaterThanZero',
        trigger: 'change'
      }
    ],
    location_name: [
      {
        required: true,
        message: `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.target_location') }`
      }
    ]
  })
})

watch(
  () => isShow.value,
  (val) => {
    if (val) {
      method.initForm()
    }
  }
)
</script>

<style scoped lang="less">
.mainForm {
  background-color: #f9f9f9;
  border-radius: 5px;
  padding: 20px;
  box-sizing: border-box;
  overflow: auto;
}

.toolbar {
  height: 40px;
  display: flex;
  justify-content: space-between;
}

.toolbarTitle {
  display: flex;
  // justify-content: center;
  // align-items: center;
}
</style>

1.2 接口代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
     [Route("stockprocess")]
     [ApiController]
     [ApiExplorerSettings(GroupName = "WMS")]
     public class StockprocessController : BaseController
     {
         #region Args
 
         /// <summary>
         /// stockprocess Service
         /// </summary>
         private readonly IStockprocessService _stockprocessService;
 
         /// <summary>
         /// Localizer Service
         /// </summary>
         private readonly IStringLocalizer<ModernWMS.Core.MultiLanguage> _stringLocalizer;
         #endregion
 
         #region constructor
         /// <summary>
         /// constructor
         /// </summary>
         /// <param name="stockprocessService">stockprocess Service</param>
        /// <param name="stringLocalizer">Localizer</param>
         public StockprocessController(
             IStockprocessService stockprocessService
           , IStringLocalizer<ModernWMS.Core.MultiLanguage> stringLocalizer
             )
         {
             this._stockprocessService = stockprocessService;
            this._stringLocalizer= stringLocalizer;
         }
         #endregion
 
         #region Api
         /// <summary>
         /// page search
         /// </summary>
         /// <param name="pageSearch">args</param>
         /// <returns></returns>
         [HttpPost("list")]
         public async Task<ResultModel<PageData<StockprocessGetViewModel>>> PageAsync(PageSearch pageSearch)
         {
             var (data, totals) = await _stockprocessService.PageAsync(pageSearch, CurrentUser);
              
             return ResultModel<PageData<StockprocessGetViewModel>>.Success(new PageData<StockprocessGetViewModel>
             {
                 Rows = data,
                 Totals = totals
             });
         }
 
         /// <summary>
         /// get all records
         /// </summary>
         /// <returns>args</returns>
        [HttpGet("all")]
         public async Task<ResultModel<List<StockprocessGetViewModel>>> GetAllAsync()
         {
             var data = await _stockprocessService.GetAllAsync(CurrentUser);
             if (data.Any())
             {
                 return ResultModel<List<StockprocessGetViewModel>>.Success(data);
             }
             else
             {
                 return ResultModel<List<StockprocessGetViewModel>>.Success(new List<StockprocessGetViewModel>());
             }
         }
 
         /// <summary>
         /// get a record by id
         /// </summary>
         /// <returns>args</returns>
         [HttpGet]
         public async Task<ResultModel<StockprocessWithDetailViewModel>> GetAsync(int id)
         {
             var data = await _stockprocessService.GetAsync(id);
             if (data!=null)
             {
                 return ResultModel<StockprocessWithDetailViewModel>.Success(data);
             }
             else
             {
                 return ResultModel<StockprocessWithDetailViewModel>.Error(_stringLocalizer["not_exists_entity"]);
             }
         }
         /// <summary>
         /// add a new record
         /// </summary>
         /// <param name="viewModel">args</param>
         /// <returns></returns>
         [HttpPost]
         public async Task<ResultModel<int>> AddAsync(StockprocessViewModel viewModel)
         {
             var (id, msg) = await _stockprocessService.AddAsync(viewModel,CurrentUser);
             if (id > 0)
             {
                 return ResultModel<int>.Success(id);
             }
             else
             {
                 return ResultModel<int>.Error(msg);
             }
         }
 
         /// <summary>
         /// update a record
         /// </summary>
         /// <param name="viewModel">args</param>
         /// <returns></returns>
         [HttpPut]
         public async Task<ResultModel<bool>> UpdateAsync(StockprocessViewModel viewModel)
         {
             var (flag, msg) = await _stockprocessService.UpdateAsync(viewModel);
             if (flag)
             {
                 return ResultModel<bool>.Success(flag);
             }
             else
             {
                 return ResultModel<bool>.Error(msg, 400, flag);
             }
         }
 
         /// <summary>
         /// delete a record
         /// </summary>
         /// <param name="id">id</param>
         /// <returns></returns>
         [HttpDelete]
         public async Task<ResultModel<string>> DeleteAsync(int id)
         {
             var (flag, msg) = await _stockprocessService.DeleteAsync(id);
             if (flag)
             {
                 return ResultModel<string>.Success(msg);
             }
             else
             {
                 return ResultModel<string>.Error(msg);
             }
         }
        /// <summary>
        /// confirm processing
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpPut("process-confirm")]
        public async Task<ResultModel<string>> ConfirmProcess(int id)
        {
            var (flag, msg) = await _stockprocessService.ConfirmProcess(id,CurrentUser);
            if (flag)
            {
                return ResultModel<string>.Success(msg);
            }
            else
            {
                return ResultModel<string>.Error(msg);
            }
        }

        /// <summary>
        /// confirm adjustment
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpPut("adjustment-confirm")]
        public async Task<ResultModel<string>> ConfirmAdjustment(int id)
        {
            var (flag, msg) = await _stockprocessService.ConfirmAdjustment(id, CurrentUser);
            if (flag)
            {
                return ResultModel<string>.Success(msg);
            }
            else
            {
                return ResultModel<string>.Error(msg);
            }
        }
        #endregion

    }

2.库存移动

库存移动主要是库位的移动

2.1 页面代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- Warehouse Move -->
<template>
  <div class="container">
    <div>
      <!-- Main Content -->
      <v-card class="mt-5">
        <v-card-text>
          <v-window v-model="data.activeTab">
            <v-window-item>
              <div class="operateArea">
                <v-row no-gutters>
                  <!-- Operate Btn -->
                  <v-col cols="3" class="col">
                    <tooltip-btn icon="mdi-plus" :tooltip-text="$t('system.page.add')" @click="method.add()"></tooltip-btn>
                    <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
                    <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
                  </v-col>

                  <!-- Search Input -->
                  <v-col cols="9">
                    <v-row no-gutters @keyup.enter="method.sureSearch">
                      <v-col cols="4"></v-col>
                      <v-col cols="4"></v-col>
                      <v-col cols="4">
                        <v-text-field
                          v-model="data.searchForm.job_code"
                          clearable
                          hide-details
                          density="comfortable"
                          class="searchInput ml-5 mt-1"
                          :label="$t('wms.warehouseWorking.warehouseMove.job_code')"
                          variant="solo"
                        >
                        </v-text-field>
                      </v-col>
                    </v-row>
                  </v-col>
                </v-row>
              </div>

              <!-- Table -->
              <div
                class="mt-5"
                :style="{
                  height: cardHeight
                }"
              >
                <vxe-table ref="xTable" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column type="checkbox" width="50"></vxe-column>
                  <vxe-column field="job_code" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.job_code')"></vxe-column>
                  <vxe-column field="move_status" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.move_status')">
                    <template #default="{ row, column }">
                      <span>{{ formatMoveStatus(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="spu_code" width="150px" :title="$t('base.commodityManagement.spu_code')"></vxe-column>
                  <vxe-column field="spu_name" width="150px" :title="$t('base.commodityManagement.spu_name')"></vxe-column>
                  <vxe-column field="sku_code" width="150px" :title="$t('base.commodityManagement.sku_code')"></vxe-column>
                  <vxe-column field="sku_name" width="150px" :title="$t('base.commodityManagement.sku_name')"></vxe-column>
                  <vxe-column field="qty" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.qty')"></vxe-column>
                  <vxe-column
                    field="orig_goods_warehouse"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.orig_goods_warehouse')"
                  ></vxe-column>
                  <vxe-column
                    field="orig_goods_location_name"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.orig_goods_location_name')"
                  ></vxe-column>
                  <vxe-column
                    field="dest_googs_warehouse"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.dest_googs_warehouse')"
                  ></vxe-column>
                  <vxe-column
                    field="dest_googs_location_name"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.dest_googs_location_name')"
                  ></vxe-column>
                  <vxe-column field="handler" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.handler')"></vxe-column>
                  <vxe-column field="handle_time" width="170px" :title="$t('wms.warehouseWorking.warehouseMove.handle_time')">
                    <template #default="{ row, column }">
                      <span>{{ formatDate(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="creator" :title="$t('wms.warehouseWorking.warehouseMove.creator')"></vxe-column>
                  <vxe-column field="create_time" width="170px" :title="$t('wms.warehouseWorking.warehouseMove.create_time')"></vxe-column>
                  <vxe-column field="operate" :title="$t('system.page.operate')" width="250" :resizable="false" show-overflow>
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-eye-outline"
                        :tooltip-text="$t('system.page.view')"
                        @click="method.viewRow(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-book-open-outline"
                        :tooltip-text="$t('wms.warehouseWorking.warehouseMove.confirmMove')"
                        :disabled="method.confirmMoveBtnDisabled(row)"
                        @click="method.confirmMove(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        :disabled="method.confirmMoveBtnDisabled(row)"
                        @click="method.deleteRow(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                </vxe-table>
                <custom-pager
                  :current-page="data.tablePage.pageIndex"
                  :page-size="data.tablePage.pageSize"
                  perfect
                  :total="data.tablePage.total"
                  :page-sizes="PAGE_SIZE"
                  :layouts="PAGE_LAYOUT"
                  @page-change="method.handlePageChange"
                >
                </custom-pager>
              </div>
            </v-window-item>
          </v-window>
        </v-card-text>
      </v-card>
      <addOrUpdateDialog
        :show-dialog="data.showDialog"
        :form="data.dialogForm"
        :process-type="data.processType"
        @close="method.closeDialog"
        @saveSuccess="method.saveSuccess"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, ref, reactive, onActivated, watch, nextTick } from 'vue'
import { VxePagerEvents } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { WarehouseMoveVO, MoveStatus } from '@/types/WarehouseWorking/WarehouseMove'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { deleteStockMove, getStockMoveList, getStockMoveOne, confirmMove } from '@/api/wms/warehouseMove'
import { PROCESS_JOB_COMBINE } from '@/constant/warehouseWorking'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { formatMoveStatus } from '@/utils/format/formatWarehouseWorking'
import { formatDate } from '@/utils/format/formatSystem'
import tooltipBtn from '@/components/tooltip-btn.vue'
import addOrUpdateDialog from './add-or-update-move.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import { exportData } from '@/utils/exportTable'

const xTable = ref()

const data = reactive({
  showDialog: false,
  processType: PROCESS_JOB_COMBINE,
  timer: ref<any>(null),
  activeTab: null,
  searchForm: {
    job_code: ''
  },
  tableData: ref<WarehouseMoveVO[]>([]),
  dialogForm: {
    id: 0,
    job_code: '',
    move_status: MoveStatus.UNADJUST,
    sku_id: 0,
    orig_goods_location_id: 0,
    dest_googs_location_id: 0,
    qty: 0,
    goods_owner_id: 0,
    handler: '',
    handle_time: '',
    orig_goods_warehouse: '',
    orig_goods_location_name: '',
    dest_googs_warehouse: '',
    dest_googs_location_name: '',
    spu_code: '',
    spu_name: '',
    sku_code: '',
    sku_name: '',
    creator: '',
    create_time: ''
  },
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  })
})

const method = reactive({
  // Open a dialog to add
  add: () => {
    data.dialogForm = {
      id: 0,
      job_code: '',
      move_status: MoveStatus.UNADJUST,
      sku_id: 0,
      orig_goods_location_id: 0,
      dest_googs_location_id: 0,
      qty: 0,
      goods_owner_id: 0,
      handler: '',
      handle_time: '',
      orig_goods_warehouse: '',
      orig_goods_location_name: '',
      dest_googs_warehouse: '',
      dest_googs_location_name: '',
      spu_code: '',
      spu_name: '',
      sku_code: '',
      sku_name: '',
      creator: '',
      create_time: ''
    }
    nextTick(() => {
      data.showDialog = true
    })
  },

  // After add or update success.
  saveSuccess: () => {
    method.refresh()
    method.closeDialog()
  },

  // Refresh data
  refresh: () => {
    method.getStockProcessList()
  },

  // Shut add or update dialog
  closeDialog: () => {
    data.showDialog = false
  },

  getStockProcessList: async () => {
    const { data: res } = await getStockMoveList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },

  viewRow: async (row: WarehouseMoveVO) => {
    await method.getOne(row.id)
    nextTick(() => {
      data.showDialog = true
    })
  },

  getOne: async (id: number) => {
    const { data: res } = await getStockMoveOne(id)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }

    data.dialogForm = res.data
  },

  deleteRow(row: WarehouseMoveVO) {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteMessage'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await deleteStockMove(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }

          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('system.page.delete') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  },

  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize
    method.refresh()
  }),

  exportTable: () => {
    const $table = xTable.value
    exportData({
      table: $table,
      filename: i18n.global.t('router.sideBar.warehouseMove'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },

  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.refresh()
  },

  // The btn will become disabled when the 'process_status' is false
  confirmMoveBtnDisabled: (row: WarehouseMoveVO) => row.move_status === MoveStatus.ADJUSTED,

  confirmMove: async (row: WarehouseMoveVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('wms.warehouseWorking.warehouseMove.beforeConfirmMove'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await confirmMove(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }
          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('wms.warehouseWorking.warehouseMove.confirmMove') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  }
})

onActivated(() => {
  method.refresh()
})

const cardHeight = computed(() => computedCardHeight({ hasTab: false }))
const tableHeight = computed(() => computedTableHeight({ hasTab: false }))

watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style scoped lang="less">
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- Warehouse Move Operate Dialog -->
<template>
  <v-dialog v-model="isShow" :width="'30%'" transition="dialog-top-transition" :persistent="true">
    <template #default>
      <v-card>
        <v-toolbar class="" color="white" :title="`${$t('router.sideBar.warehouseMove')}`"></v-toolbar>
        <v-card-text>
          <v-form ref="formRef">
            <v-text-field
              v-model="data.form.spu_code"
              :label="$t('base.commodityManagement.spu_code')"
              :rules="data.rules.spu_code"
              variant="outlined"
              readonly
              clearable
              @click="method.openCommoditySelect"
              @click:clear="method.clearCommodity"
            ></v-text-field>
            <v-text-field
              v-model="data.form.spu_name"
              :label="$t('base.commodityManagement.spu_name')"
              :rules="data.rules.spu_name"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.sku_code"
              :label="$t('base.commodityManagement.sku_code')"
              :rules="data.rules.sku_code"
              variant="outlined"
              disabled
            ></v-text-field>
            <!-- <v-text-field
              v-model="data.form.sku_name"
              :label="$t('base.commodityManagement.sku_name')"
              :rules="data.rules.sku_name"
              variant="outlined"
              disabled
            ></v-text-field> -->
            <v-text-field
              v-model="data.form.orig_goods_warehouse"
              :label="$t('wms.warehouseWorking.warehouseMove.orig_goods_warehouse')"
              :rules="data.rules.orig_goods_warehouse"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.orig_goods_location_name"
              :label="$t('wms.warehouseWorking.warehouseMove.orig_goods_location_name')"
              :rules="data.rules.orig_goods_location_name"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.dest_googs_warehouse"
              :label="$t('wms.warehouseWorking.warehouseMove.dest_googs_warehouse')"
              :rules="data.rules.dest_googs_warehouse"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.dest_googs_location_name"
              :label="$t('wms.warehouseWorking.warehouseMove.dest_googs_location_name')"
              :rules="data.rules.dest_googs_location_name"
              variant="outlined"
              readonly
              clearable
              @click="method.openLocationSelect"
              @click:clear="method.clearLocation"
            ></v-text-field>
            <v-text-field
              v-model="data.form.qty"
              :label="$t('wms.warehouseWorking.warehouseMove.qty')"
              :rules="data.rules.qty"
              variant="outlined"
            ></v-text-field>
          </v-form>
        </v-card-text>
        <v-card-actions class="justify-end">
          <v-btn variant="text" @click="method.closeDialog">{{ $t('system.page.close') }}</v-btn>
          <v-btn color="primary" variant="text" :disabled="operateDisabled" @click="method.submit">{{ $t('system.page.submit') }}</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </v-dialog>

  <commodity-select
    :show-dialog="data.showCommodityDialogSelect"
    @close="method.closeCommodityDialogSelect"
    @sureSelect="method.sureSelectCommodity"
  />
  <location-select :show-dialog="data.showLocationDialogSelect" @close="method.closeLocationDialogSelect" @sureSelect="method.sureSelectLocation" />
</template>

<script lang="ts" setup>
import { reactive, computed, ref, watch } from 'vue'
import i18n from '@/languages/i18n'
import { hookComponent } from '@/components/system/index'
import { addStockMove } from '@/api/wms/warehouseMove'
import { WarehouseMoveVO, MoveStatus } from '@/types/WarehouseWorking/WarehouseMove'
import { removeObjectNull } from '@/utils/common'
import commoditySelect from '@/components/select/commodity-select.vue'
import locationSelect from '@/components/select/location-select.vue'
import { IsInteger } from '@/utils/dataVerification/formRule'

const formRef = ref()
const emit = defineEmits(['close', 'saveSuccess'])
const isUpdate = computed(() => props.form.id && props.form.id > 0)
const operateDisabled = computed(() => !!isUpdate.value)

const props = defineProps<{
  showDialog: boolean
  form: WarehouseMoveVO
}>()

const isShow = computed(() => props.showDialog)

const data = reactive({
  showCommodityDialogSelect: false,
  showLocationDialogSelect: false,

  // Available Qty
  curAvailableQty: 0,

  form: ref<WarehouseMoveVO>({
    id: 0,
    job_code: '',
    move_status: MoveStatus.UNADJUST,
    sku_id: 0,
    orig_goods_location_id: 0,
    dest_googs_location_id: 0,
    qty: 0,
    goods_owner_id: 0,
    handler: '',
    handle_time: '',
    orig_goods_warehouse: '',
    orig_goods_location_name: '',
    dest_googs_warehouse: '',
    dest_googs_location_name: '',
    spu_code: '',
    spu_name: '',
    sku_code: '',
    sku_name: '',
    creator: '',
    create_time: ''
  }),
  rules: {
    qty: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.qty') }!`,
      (val: number) => IsInteger(val, 'greaterThanZero') === '' || IsInteger(val, 'greaterThanZero'),
      (val: string) => method.validQty(val)
    ],
    orig_goods_warehouse: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.orig_goods_warehouse') }!`
    ],
    orig_goods_location_name: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.orig_goods_location_name') }!`
    ],
    dest_googs_warehouse: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.dest_googs_warehouse') }!`
    ],
    dest_googs_location_name: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.dest_googs_location_name') }!`
    ],
    spu_code: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.spu_code') }!`],
    spu_name: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.spu_name') }!`],
    sku_code: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.sku_code') }!`],
    sku_name: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.sku_name') }!`]
  }
})

const method = reactive({
  // The 'qty' can't more than 'qty_available'
  validQty: (value: string): boolean | string => {
    let inputQty = Number(value)
    if (Number.isNaN(inputQty)) {
      inputQty = 0
    }

    if (inputQty > data.curAvailableQty) {
      return `${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qtyMoreThanAvailable') }${ data.curAvailableQty }`
    }
    return true
  },
  closeDialog: () => {
    emit('close')
  },
  initForm: () => {
    data.form = props.form
  },

  openCommoditySelect: () => {
    // Open select modal after UI rendered.
    setTimeout(() => {
      data.showCommodityDialogSelect = true
    }, 100)
  },

  closeCommodityDialogSelect: () => {
    data.showCommodityDialogSelect = false
  },

  openLocationSelect: () => {
    setTimeout(() => {
      data.showLocationDialogSelect = true
    }, 100)
  },

  closeLocationDialogSelect: () => {
    data.showLocationDialogSelect = false
  },

  sureSelectCommodity: (selectRecords: any) => {
    try {
      data.form.sku_id = selectRecords[0].sku_id
      data.form.orig_goods_location_id = selectRecords[0].goods_location_id
      data.form.orig_goods_warehouse = selectRecords[0].warehouse_name
      data.form.orig_goods_location_name = selectRecords[0].location_name
      data.form.goods_owner_id = selectRecords[0].goods_owner_id
      data.form.spu_code = selectRecords[0].spu_code
      data.form.spu_name = selectRecords[0].spu_name
      data.form.sku_code = selectRecords[0].sku_code
      data.form.sku_name = selectRecords[0].sku_name

      data.curAvailableQty = selectRecords[0].qty_available
    } catch (error) {
      console.error(error)
    }
  },

  sureSelectLocation: (selectRecords: any) => {
    try {
      data.form.dest_googs_location_id = selectRecords[0].id
      data.form.dest_googs_warehouse = selectRecords[0].warehouse_name
      data.form.dest_googs_location_name = selectRecords[0].location_name
    } catch (error) {
      console.error(error)
    }
  },

  clearCommodity: () => {
    data.form.sku_id = 0
    data.form.orig_goods_location_id = 0
    data.form.orig_goods_warehouse = ''
    data.form.orig_goods_location_name = ''
    data.form.spu_code = ''
    data.form.spu_name = ''
    data.form.sku_code = ''
    data.form.sku_name = ''

    data.curAvailableQty = 0
  },

  clearLocation: () => {
    data.form.dest_googs_location_id = 0
    data.form.dest_googs_warehouse = ''
    data.form.dest_googs_location_name = ''
  },

  submit: async () => {
    const { valid } = await formRef.value.validate()

    const form = method.constructFormBody()

    if (valid) {
      const { data: res } = await addStockMove(form)
      if (!res.isSuccess) {
        hookComponent.$message({
          type: 'error',
          content: res.errorMessage
        })
        return
      }
      hookComponent.$message({
        type: 'success',
        content: `${ i18n.global.t('system.page.submit') }${ i18n.global.t('system.tips.success') }`
      })
      emit('saveSuccess')
    } else {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
    }
  },

  constructFormBody: () => {
    let form = { ...data.form }
    form = removeObjectNull(form)

    return form
  }
})

watch(
  () => isShow.value,
  (val) => {
    if (val) {
      method.initForm()
    }
  }
)
</script>

<style scoped lang="less">
.v-form {
  div {
    margin-bottom: 7px;
  }
}
</style>

2.2 接口代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
     [Route("stockmove")]
     [ApiController]
     [ApiExplorerSettings(GroupName = "WMS")]
     public class StockmoveController : BaseController
     {
         #region Args
 
         /// <summary>
         /// stockmove Service
         /// </summary>
         private readonly IStockmoveService _stockmoveService;
 
         /// <summary>
         /// Localizer Service
         /// </summary>
         private readonly IStringLocalizer<ModernWMS.Core.MultiLanguage> _stringLocalizer;
         #endregion
 
         #region constructor
         /// <summary>
         /// constructor
         /// </summary>
         /// <param name="stockmoveService">stockmove Service</param>
        /// <param name="stringLocalizer">Localizer</param>
         public StockmoveController(
             IStockmoveService stockmoveService
           , IStringLocalizer<ModernWMS.Core.MultiLanguage> stringLocalizer
             )
         {
             this._stockmoveService = stockmoveService;
            this._stringLocalizer= stringLocalizer;
         }
         #endregion
 
         #region Api
         /// <summary>
         /// page search
         /// </summary>
         /// <param name="pageSearch">args</param>
         /// <returns></returns>
         [HttpPost("list")]
         public async Task<ResultModel<PageData<StockmoveViewModel>>> PageAsync(PageSearch pageSearch)
         {
             var (data, totals) = await _stockmoveService.PageAsync(pageSearch, CurrentUser);
              
             return ResultModel<PageData<StockmoveViewModel>>.Success(new PageData<StockmoveViewModel>
             {
                 Rows = data,
                 Totals = totals
             });
         }
 
         /// <summary>
         /// get all records
         /// </summary>
         /// <returns>args</returns>
        [HttpGet("all")]
         public async Task<ResultModel<List<StockmoveViewModel>>> GetAllAsync()
         {
             var data = await _stockmoveService.GetAllAsync(CurrentUser);
             if (data.Any())
             {
                 return ResultModel<List<StockmoveViewModel>>.Success(data);
             }
             else
             {
                 return ResultModel<List<StockmoveViewModel>>.Success(new List<StockmoveViewModel>());
             }
         }
 
         /// <summary>
         /// get a record by id
         /// </summary>
         /// <returns>args</returns>
         [HttpGet]
         public async Task<ResultModel<StockmoveViewModel>> GetAsync(int id)
         {
             var data = await _stockmoveService.GetAsync(id);
             if (data!=null)
             {
                 return ResultModel<StockmoveViewModel>.Success(data);
             }
             else
             {
                 return ResultModel<StockmoveViewModel>.Error(_stringLocalizer["not_exists_entity"]);
             }
         }
         /// <summary>
         /// add a new record
         /// </summary>
         /// <param name="viewModel">args</param>
         /// <returns></returns>
         [HttpPost]
         public async Task<ResultModel<int>> AddAsync(StockmoveViewModel viewModel)
         {
             var (id, msg) = await _stockmoveService.AddAsync(viewModel,CurrentUser);
             if (id > 0)
             {
                 return ResultModel<int>.Success(id);
             }
             else
             {
                 return ResultModel<int>.Error(msg);
             }
         }
 
         /// <summary>
         /// confirm move
         /// </summary>
         /// <param name="id">id</param>
         /// <returns></returns>
         [HttpPut]
         public async Task<ResultModel<bool>> Confirm(int id)
         {
             var (flag, msg) = await _stockmoveService.Confirm(id,CurrentUser);
             if (flag)
             {
                 return ResultModel<bool>.Success(flag);
             }
             else
             {
                 return ResultModel<bool>.Error(msg, 400, flag);
             }
         }
 
         /// <summary>
         /// delete a record
         /// </summary>
         /// <param name="id">id</param>
         /// <returns></returns>
         [HttpDelete]
         public async Task<ResultModel<string>> DeleteAsync(int id)
         {
             var (flag, msg) = await _stockmoveService.DeleteAsync(id);
             if (flag)
             {
                 return ResultModel<string>.Success(msg);
             }
             else
             {
                 return ResultModel<string>.Error(msg);
             }
         }
         #endregion
 
     }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
60克2.5毫米!英伟达、斯坦福搞出超薄全息VR眼镜|Siggraph 2022
---- 新智元报道   编辑:桃子 好困 【新智元导读】这副VR眼镜,戴上秒变「蟹老板」。 在Siggraph 2022上,斯坦福和英伟达联手搞了一个仅有2.5mm的轻薄VR全息眼镜。 有点审美的人都会感觉这个眼镜戴上有点「蟹老板」那味儿,但比起戴一个笨重头显强多了。 它仅有60g重,就拿Meta Quest 2(约507g)相比,足以见其有多轻。 这个VR全息眼镜由瞳孔复制波导,空间光调制器,还有一个几何相位透镜组成。 可以通过仅有2.5mm厚的元器件通过光学堆栈(optical stack
新智元
2022/08/26
2860
60克2.5毫米!英伟达、斯坦福搞出超薄全息VR眼镜|Siggraph 2022
厚度仅2.5毫米,重60克,英伟达&斯坦福做出了超轻薄VR眼镜
机器之心报道 编辑:张倩 我们什么时候能摆脱笨重的 VR 头显呢? 自从去年马克 · 扎克伯格宣布将全力开发「元宇宙」之后,VR、AR 等技术就在世界范围内掀起了新一轮的热潮。 这些技术为计算机图形应用等领域提供了前所未有的用户体验。然而,时至今日,VR 头显的笨重依然是一个绕不开的问题,同时也阻碍了 VR 走进大众的日常生活。 这一问题源于 VR 显示光学的放大原理,即通过透镜将小型微显示器的图像放大。这种设计要求微显示器和镜片之间有一段相对较大的距离,因此当前的 VR 头显普遍比较笨重,佩戴起来很不舒服
机器之心
2022/09/06
5920
厚度仅2.5毫米,重60克,英伟达&斯坦福做出了超轻薄VR眼镜
灵犀微光CEO郑昱:阵列光波导,推动AR眼镜三年内走向消费级市场的显示技术|量子位·视点分享回顾
视点 发自 凹非寺 量子位 | 公众号 QbitAI 元宇宙产业的发展,正在将各种前沿领域技术汇集到一起,构建出下一代互联网的新形态。 正如互联网的准入级终端是PC,移动互联网的准入级终端是手机,那么下一代互联网,准入级终端或许就将是VR和AR设备。 VR终端或将承载深度元宇宙的交互,而AR终端的普及或将让元宇宙技术走向千家万户。 AR设备,尽管外形看来极度接近于普通眼镜,但其核心的光学显示部分,也就是光学模组,让它成为最有希望叩开元宇宙大门的“低门槛”终端。 围绕AR产业趋势、元宇宙等热点话题,灵犀微光C
量子位
2022/06/27
4600
灵犀微光CEO郑昱:阵列光波导,推动AR眼镜三年内走向消费级市场的显示技术|量子位·视点分享回顾
不要笨重头显!英伟达研发2.5毫米轻薄VR眼镜,实现近眼显示
---- 新智元报道   编辑:袁榭 时光 【新智元导读】英伟达又要整大活了:即将推出轻薄版全息眼镜,打败市面上的笨重VR/AR头显。预告:居家办公让虚拟人来作伴?欢迎预约直播,教你如何从0到1自己创建一个! 当前,要进入虚拟3D世界,仍然需要佩戴笨重的头显。 不过,研究人员正在研究重量更轻、类似于普通眼镜的替代品。 据Venturebeat报道,在今年8月即将举行的Siggraph 2022会议上,英伟达将提交16篇论文。 其中,包括为元宇宙和真实世界交互奠基的即时「全息成像」渲染、轻薄全息眼镜等
新智元
2022/05/25
4490
不要笨重头显!英伟达研发2.5毫米轻薄VR眼镜,实现近眼显示
了解一下微纳光学在AR眼镜中的应用
光波导是一种光学技术,在光通信、激光领域应用较多。简单的来说就是光在特定设计的材料器件结构中实现光的定向传播,应用的是全反射原理,中心用折射率大的材料,四周用折射率小的材料,就可以束缚光在介质中传播。
用户2760455
2022/06/08
1.1K0
了解一下微纳光学在AR眼镜中的应用
未来交互,各种R你了解多少?VR/AR/MR/XR/CR
未来交互离我们还远吗?还是在等待一个时机,我觉得未来可以用“涌现”这个词。先说下 ”涌现“这个词,特别有意思。
腾讯游戏多媒体引擎GME
2021/10/25
1.6K0
CES 2021丨因疫情转为线上举办,AR或成主流趋势?
(VRPinea1月15日讯)2021年拉斯维加斯消费电子展(CES)于当地时间1月11日开幕,受疫情影响,本次展会以线上形式举行。在本届CES 2021中,XR相关的内容也有很多,P君对大会内容进行了汇总之后,给大家带来了XR方面的相关资讯~
VRPinea
2021/01/29
4470
CES 2021丨因疫情转为线上举办,AR或成主流趋势?
Magic Leap 和微软为什么要做 AR 眼镜:关于原理及挑战
Magic Leap 和 HoloLens 是什么? Magic Leap 和 HoloLens 都是 Augmented Reality (AR)眼镜的代表。 AR 和 VR 眼镜的区别是什么? A
新智元
2018/03/14
1.5K0
Magic Leap 和微软为什么要做 AR 眼镜:关于原理及挑战
Meta:基于大规模可见光PIC的超薄激光显示面板
看到Meta的Reality Labs Research发布的一个挺有意思的文章(https://arxiv.org/abs/2412.19274),将氮化硅集成光PIC应用在了AR激光显示领域,做出了一个只有2mm厚的激光显示面板样机,性能也比传统LED显示提升。虽然这个工作距离应用还有挺多挑战需要解决,但可以打开一下集成光的应用场景思路,别总是在光模块CPO光互连里边卷,找点量大又支持定制化的消费场景也挺好。
光芯
2025/04/08
1300
Meta:基于大规模可见光PIC的超薄激光显示面板
苹果AR眼镜密器疑曝光!索尼VR头显双眼8K超高清,元宇宙「伴侣」震撼出场
日前,索尼在一场「科技日」活动中展示了一款搭载单眼4K Micro-OLED显示屏的VR头显原型。
新智元
2021/12/13
5390
苹果AR眼镜密器疑曝光!索尼VR头显双眼8K超高清,元宇宙「伴侣」震撼出场
体验不到Google AR眼镜,急得我自己整了一个
大数据文摘授权转载自果壳 作者:肖鑫杰 编辑:沈知涵 首先需要(也许不止)一台报废的 HoloLens 大概五六年前,朋友借给我一台首发版的 HoloLens,那种体验至今念念不忘。 HoloLens: HoloLens是微软公司开发的一种 MR 头显。眼镜将会追踪你的移动和视线,进而生成适当的虚拟对象,通过光线投射到你的眼中。因为设备知道你的方位,你可以通过手势,比如半空中抬起,放下手指点击与虚拟 3D 对象交互。 我记得开启 RoboRaid 后,没等反应过来,奇形怪状的外星机器人接连“穿破”我家墙壁
大数据文摘
2022/06/06
5270
体验不到Google AR眼镜,急得我自己整了一个
5.9VR行业大事件:英伟达与斯坦福合作研发超薄VR全息眼镜;Manus新款VR手套售价9000美元
(VRPinea 5月9日讯)今日重点新闻:英伟达与斯坦福大学合作研发2.5mm厚的超薄VR全息眼镜;Manus新款面向企业级市场的VR手套Quantum Metagloves开启预购;VR冒险游戏《Eye of the Temple》增加Speedrun(竞速)模式和排行榜。
VRPinea
2022/06/08
3940
5.9VR行业大事件:英伟达与斯坦福合作研发超薄VR全息眼镜;Manus新款VR手套售价9000美元
这3款AR眼镜,一举拿下德国“红点奖”
昨日凌晨,德国“红点奖(Red Dot Award)”颁奖仪式在德国举行。“红点奖”被称为“设计界的奥斯卡”,是国际知名创意设计大奖。其与德国“IF奖”、美国“IDEA奖”并称为世界三大设计奖,同时也是世界上最有影响力的设计竞赛之一。 自2016年起,AR眼镜便踏上了“红点奖”的舞台,如微软的AR眼镜HoloLens获评当年“最佳产品设计”。而今年,3款AR眼镜再获殊荣,并且其中还有一款“中国创造”。相信各位读者已经对这些AR眼镜充满了好奇,现在小编就带各位来了解这3款获奖作品。 中国:HiAR Glass
VRPinea
2018/05/15
7090
资料 | AR眼镜光学主流:光波导技术方案及加工工艺全解析
增强现实技术即AR技术是将虚拟信息与现实世界相互融合,属于下一个信息技术的引爆点,据权威预测增强现实眼镜将会取代手机成为下一代的协作计算平台。以增强现实眼镜为代表的增强现实技术目前在各个行业开始兴起,尤其在安防和工业领域,增强现实技术体现了无与伦比的优势,大大改进了信息交互方式。
好好学SLAM
2021/08/26
10K0
资料 | AR眼镜光学主流:光波导技术方案及加工工艺全解析
盘点|近期AR眼镜/头显最新动态合集!
近期,关注AR/VR圈的小伙伴们,应该有发现AR,尤其是AR硬件方面的消息特别多。就连“万年专利户”的苹果眼镜,其官方也对外表示,眼镜将于2021年底推出。并且还预计能在发售第一年内就卖出1000万台。不过,2021年还有些遥远,小伙伴们不如先随小编一起看下近3个月内,相关厂商在AR眼镜/头显落地方面的最新进展吧。
VRPinea
2018/07/26
5770
盘点|近期AR眼镜/头显最新动态合集!
从像素革命到全息未来:Swave试图用相变材料重构AI+AR显示生态
      当拉斯维加斯的Sphere巨幕用2.68亿级像素震撼全球时,一家名为Swave的科技公司正试图以纳米级精度重塑显示技术的底层逻辑。在AI与空间计算融合的临界点,传统显示技术因体积、功耗和视觉冲突等固有缺陷,已成为AR/VR大规模普及的拦路虎。而Swave基于相变材料(PCM)和全息芯片的双重创新,正在掀开下一代人机交互的序幕——其核心技术不仅将像素尺寸压缩至300nm以下,还通过动态焦深调节彻底解决了困扰行业多年的"辐辏调节冲突",为AI+AR时代的智能眼镜、全息墙等空间计算设备提供了革命性的硬件底座。
光芯
2025/06/12
930
从像素革命到全息未来:Swave试图用相变材料重构AI+AR显示生态
「深度」怎样让鲸鱼飞跃篮球场——深度揭秘Magic Leap背后的技术+战略
今年10月,硅谷AR(增强现实)公司Magic Leap发布了一系列“魔法带回现实”的概念视频:篮球场上鲸鱼一跃而起、外星人突袭办公室打真人CS……虽然大部分视频并非实拍demo,而是特技duang
镁客网
2018/05/25
2.1K0
虚拟即是现实,AR专家眼中的真AR
@有饭吃 说:这篇文章翻译自美国、欧洲、日本的七位计算机视觉领域的学者共同就2014年8月在奈良大学召开的AR发展研究探讨会写的一篇总结性质的论文。在这篇论文中,学者们界定了何为真AR并且阐述了其发展方向以及发展壁垒,提供了一些较有价值的观点和看法,学者们认为需要采取一种类似AR图灵测试的方法来界定AR发展的程度,并认为光场是最有可能实现AR的途径。文章内容并不艰深,原文在http://arxiv.org/pdf/1512.05471v1.pdf上可以下载,我在这里对论文进行了一定程度的简化,并且增加了一些
新智元
2018/03/14
1.6K0
虚拟即是现实,AR专家眼中的真AR
操控悬浮粒子,空中三维成像,能听能摸!Nature和Science报道,裸眼3D新可能
在 1977 年上映的科幻经典《星球大战》中,莱娅公主向卢克天行者和欧比旺发出了三维版求救影像。
大数据文摘
2019/11/15
1.3K0
操控悬浮粒子,空中三维成像,能听能摸!Nature和Science报道,裸眼3D新可能
北京冬奥黑科技; 揭秘虎年春晚硬核科技;全球首款AR隐形眼镜问世;索尼3D显示技术路径曝光
这一次的北京冬奥会,从开幕式就直接火了!首先是从冰立方中破冰而出并随着音乐冉冉升起的奥运五环。事实上,整个冰雪五环就是个巨大的LED异形屏,长达20米,重3吨。另外一个黑科技就是北京冬奥会实现了世界上首次对超过600人集体实时AI动作捕捉。
LiveVideoStack
2022/02/11
3680
北京冬奥黑科技; 揭秘虎年春晚硬核科技;全球首款AR隐形眼镜问世;索尼3D显示技术路径曝光
推荐阅读
60克2.5毫米!英伟达、斯坦福搞出超薄全息VR眼镜|Siggraph 2022
2860
厚度仅2.5毫米,重60克,英伟达&斯坦福做出了超轻薄VR眼镜
5920
灵犀微光CEO郑昱:阵列光波导,推动AR眼镜三年内走向消费级市场的显示技术|量子位·视点分享回顾
4600
不要笨重头显!英伟达研发2.5毫米轻薄VR眼镜,实现近眼显示
4490
了解一下微纳光学在AR眼镜中的应用
1.1K0
未来交互,各种R你了解多少?VR/AR/MR/XR/CR
1.6K0
CES 2021丨因疫情转为线上举办,AR或成主流趋势?
4470
Magic Leap 和微软为什么要做 AR 眼镜:关于原理及挑战
1.5K0
Meta:基于大规模可见光PIC的超薄激光显示面板
1300
苹果AR眼镜密器疑曝光!索尼VR头显双眼8K超高清,元宇宙「伴侣」震撼出场
5390
体验不到Google AR眼镜,急得我自己整了一个
5270
5.9VR行业大事件:英伟达与斯坦福合作研发超薄VR全息眼镜;Manus新款VR手套售价9000美元
3940
这3款AR眼镜,一举拿下德国“红点奖”
7090
资料 | AR眼镜光学主流:光波导技术方案及加工工艺全解析
10K0
盘点|近期AR眼镜/头显最新动态合集!
5770
从像素革命到全息未来:Swave试图用相变材料重构AI+AR显示生态
930
「深度」怎样让鲸鱼飞跃篮球场——深度揭秘Magic Leap背后的技术+战略
2.1K0
虚拟即是现实,AR专家眼中的真AR
1.6K0
操控悬浮粒子,空中三维成像,能听能摸!Nature和Science报道,裸眼3D新可能
1.3K0
北京冬奥黑科技; 揭秘虎年春晚硬核科技;全球首款AR隐形眼镜问世;索尼3D显示技术路径曝光
3680
相关推荐
60克2.5毫米!英伟达、斯坦福搞出超薄全息VR眼镜|Siggraph 2022
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档