zhaohuanhuan 7 месяцев назад
Родитель
Сommit
2e0405c181

+ 21 - 0
src/api/inventory/index.ts

@@ -0,0 +1,21 @@
+// @ts-ignore
+import request from '@/utils/request'
+// @ts-ignore
+import { getInventoryType, inventoryMovementType } from '@/types/inventory'
+//获取库存信息
+export function getInventory(params:getInventoryType) {
+  return request({
+    url: 'api/wms/inventory/get',
+    method: 'get',
+    params
+  })
+}
+
+// 执行移库
+export function inventoryMovement(data:inventoryMovementType) {
+  return request({
+    url: '/api/wms/movement',
+    method: 'post',
+    data
+  })
+}

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

@@ -69,6 +69,11 @@ export default function() {
               icon: 'newspaper-o',
               path: 'move-list',
             },
+            {
+              title: '移库',
+              icon: 'newspaper-o',
+              path: 'inventory-transfer',
+            },
           ],
         },
         {

+ 6 - 0
src/router/index.ts

@@ -139,6 +139,12 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'容器移动'},
     component: () => import('@/views/equipment/container/bindUnbind/index.vue')
   },
+  {
+    path: '/inventory-transfer',
+    name: 'InventoryTransfer',
+    meta:{title:'移库'},
+    component: () => import('@/views/inventory/transfer/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 39 - 0
src/types/inventory.ts

@@ -0,0 +1,39 @@
+
+/**
+ *  查询库存
+ * @param warehouse 仓库
+ * @param barcode  条码
+ * @param locationRegexp=^(?!STAGE_|SORTATION_|REVERSEPICK_|FJ-).*$
+ */
+export interface getInventoryType {
+  /**
+   * barcode
+   */
+  barcode: string;
+  /**
+   * location
+   */
+  location: string;
+  /**
+   * locationRegexp
+   */
+  locationRegexp: string;
+  /**
+   * warehouse
+   */
+  warehouse: string;
+  [property: string]: any;
+}
+
+export interface inventoryMovementType {
+  fmContainer?: string;
+  fmLocation?: string;
+  lotNum?: string;
+  owner?: string;
+  quantity?: number;
+  sku?: string;
+  toContainer?: string;
+  toLocation?: string;
+  warehouse?: string;
+  [property: string]: any;
+}

+ 755 - 0
src/views/inventory/transfer/index.vue

@@ -0,0 +1,755 @@
+<template>
+  <div class="transfer-container">
+    <van-nav-bar title="移库" left-arrow fixed placeholder @click-left="goBack" @click-right="onConfirm">
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div style="color: #fff">返回</div>
+      </template>
+      <template #right>
+        <span style="color: #fff">确认移库</span>
+      </template>
+    </van-nav-bar>
+    <!-- 搜索表单 -->
+    <div class="top" ref="topRef">
+      <van-notice-bar :text="tips" speed="50" class="tip-notice" />
+      <div class="search-section">
+        <van-cell-group>
+          <van-field
+            v-model.lazy="sourceLocation"
+            label="来源库位"
+            placeholder="请输入来源库位"
+            clearable
+            :class="{ 'field-focused': scanType === 1 }"
+            @focus="scanType = 1"
+            @keyup.enter="_handlerScan(sourceLocation)"
+            ref="sourceLocationRef"
+            autocomplete="off"
+          />
+          <van-field
+            v-model.lazy="productBarcode"
+            label="商品条码"
+            placeholder="请输入商品条码"
+            clearable
+            :class="{ 'field-focused': scanType === 2 }"
+            @focus="scanType = 2"
+            @keyup.enter="_handlerScan(productBarcode)"
+            @input="onAsnCancel"
+            ref="productBarcodeRef"
+            autocomplete="off"
+          />
+          <van-field
+            v-model="transferQuantity"
+            label="移库数量"
+            placeholder="请输入移库数量"
+            ref="countRef"
+            type="number"
+            clearable
+            :class="{ 'field-focused': scanType === 3,'active-color': scanType != 3}"
+            @focus="scanType = 3"
+            @keyup.enter="_handlerScan(transferQuantity)"
+            autocomplete="off"
+          >
+            <template #right-icon v-if="selectedProducts['id']">
+              <div class="quantity-display">
+                <span class="quantity-label">库存</span>
+                <span class="quantity-value">{{ selectedProducts.quantityAvailable }}</span>
+                <span class="quantity-label">件</span>
+              </div>
+            </template>
+          </van-field>
+          <van-field
+            v-model="targetLocation"
+            label="目标库位"
+            placeholder="请输入目标库位"
+            clearable
+            :class="{ 'field-focused': scanType === 4 }"
+            @focus="scanType = 4"
+            autocomplete="off"
+            ref="targetLocationRef"
+            @keyup.enter="_handlerScan(targetLocation)"
+          />
+        </van-cell-group>
+      </div>
+      <!-- 已选择商品展示 -->
+      <div v-if="selectedProducts['id']" class="selected-section">
+        <van-cell-group>
+          <van-cell class="selected-product-cell">
+            <template #title>
+              <div class="selected-product-header">
+                <div class="location-info">
+                  <span class="location-label">跟踪号</span>
+                  <span class="location-value">{{ selectedProducts.traceId }}</span>
+                </div>
+              </div>
+            </template>
+            <template #label>
+              <div class="selected-product-details">
+                <div class="detail-auto-grid">
+                  <span class="detail-text" v-if="selectedProducts.lotAtt01">生产日期: {{ selectedProducts.lotAtt01 }}</span>
+                  <span class="detail-text" v-if="selectedProducts.lotAtt02">失效日期: {{ selectedProducts.lotAtt02 }}</span>
+                  <span class="detail-text" v-if="selectedProducts.lotAtt02">属性仓:{{ selectedProducts.lotAtt05 }}</span>
+                  <span class="detail-text" v-if="selectedProducts.lotAtt08">质量状态: {{ selectedProducts.lotAtt08}}</span>
+                  <span class="detail-text" v-if="selectedProducts.lotNumber">批次号: {{ selectedProducts.lotNumber }}</span>
+                  <span class="detail-text" v-if="selectedProducts.lotAtt04">生产批号: {{ selectedProducts.lotAtt04 }}</span>
+                </div>
+              </div>
+            </template>
+          </van-cell>
+        </van-cell-group>
+      </div>
+    </div>
+    <!-- 查询结果表格 -->
+    <div class="table-section">
+      <div class="table-container">
+        <div class="table-title" v-if="productList.length==0">库位商品明细</div>
+        <div class="table-wrapper" :style="'height:'+tableHeight+'px'">
+          <div class="table-header-fixed">
+            <table class="product-table header-table">
+              <thead>
+              <tr>
+                <th style="width: 40px">货主</th>
+                <th>库位</th>
+                <th>条码</th>
+                <th>余量</th>
+                <th>分配</th>
+                <th>可用</th>
+                <th style="width: 30px">操作</th>
+              </tr>
+              </thead>
+            </table>
+          </div>
+          <div class="table-body-wrapper">
+            <table class="product-table body-table">
+              <tbody>
+              <tr v-if="productList.length>0"
+                  v-for="(product, index) in productList"
+                  :key="product.id"
+                  :class="{ selected: isSelected(product) }"
+                  @click="onProductSelect(product)"
+                  class="clickable-row"
+              >
+                <td
+                  v-if="isFirstInOwnerGroup(product, index)"
+                  :rowspan="getOwnerRowspan(product, index)"
+                  class="owner-cell"
+                  style="width: 40px"
+                >
+                  {{ product.owner }}
+                </td>
+                <td class="location-cell">{{ product.location }}</td>
+                <td class="barcode-cell">{{ product.barcode }}</td>
+                <td class="quantity-cell">{{ product.quantity }}</td>
+                <td class="quantity-cell">{{ product.qtyPreAllocated }}</td>
+                <td class="quantity-cell">{{ product.quantityAvailable }}</td>
+                <td class="checkbox-cell">
+                  <van-checkbox
+                    :model-value="isSelected(product)"
+                    @update:model-value="onProductSelect(product)"
+                  />
+                </td>
+              </tr>
+              <tr v-else>
+                <td colspan="7">
+                  <van-empty :image="<string>nodataUrl" image-size="140"></van-empty>
+                </td>
+              </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue'
+import { showConfirmDialog, showNotify, showToast } from 'vant'
+import nodataUrl from '@/assets/nodata.png'
+import { useRouter } from 'vue-router'
+import { useStore } from '@/store/modules/user'
+import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
+import { getInventory, inventoryMovement } from '@/api/inventory'
+import { closeLoading, showLoading } from '@/utils/loading'
+
+const router = useRouter()
+const store = useStore()
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+  router.push('/login')
+}
+const warehouse = store.warehouse
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  // 初始化表格高度
+  setTimeout(() => {
+    calculateTableHeight()
+  }, 100)
+
+  // 监听视窗变化(键盘弹起/收回)
+  if (window.visualViewport) {
+    window.visualViewport.addEventListener('resize', calculateTableHeight)
+  }
+  // 兼容方案:监听窗口大小变化
+  window.addEventListener('resize', calculateTableHeight)
+})
+const sourceLocation = ref('')
+const productBarcode = ref('')
+const transferQuantity = ref('')
+const targetLocation = ref('')
+const productList = ref([])
+const selectedProducts = ref({})
+const oldProductBarcode = ref('')
+const countRef=ref(null)
+const targetLocationRef=ref(null)
+const productBarcodeRef=ref(null)
+const sourceLocationRef=ref(null)
+// 扫描类型控制焦点高亮
+const scanType = ref(2) // 1: 来源库位, 2: 商品条码, 3: 移库数量, 4: 目标库位
+const tips = ref('请扫描商品条码/来源库位')
+
+// 扫描条码监听
+const _handlerScan = (code) => {
+  if(!code) return
+  if (scanType.value == 2 || scanType.value == 1) {
+    if (scanType.value == 2) {
+      productBarcode.value = code
+      oldProductBarcode.value=code
+    } else if (scanType.value == 1) {
+      sourceLocation.value = code
+    }
+    const params = {
+      warehouse,
+      barcode: productBarcode.value,
+      location: sourceLocation.value,
+      locationRegexp: '^(?!STAGE_|SORTATION_).*$',
+    }
+    showLoading()
+    getInventory(params).then(res => {
+      if (res.data.length > 0) {
+        res.data.forEach((item, i) => {
+          item.id = i + 1
+        })
+        // 按照locationUsage排序:PC、CS、EA、RS、ST、SS,其他的放最后
+        const sortOrder = {
+          'PC': 1,
+          'CS': 2,
+          'EA': 3,
+          'RS': 4,
+          'ST': 5,
+          'SS': 6,
+        }
+        res.data.sort((a, b) => {
+          const orderA = sortOrder[a.locationUsage] || 999
+          const orderB = sortOrder[b.locationUsage] || 999
+          // 先按locationUsage排序
+          if (orderA !== orderB) {
+            return orderA - orderB
+          }
+          // locationUsage相同时,再按location排序
+          return b.location.localeCompare(a.location)
+        })
+        productList.value = res.data
+        onProductSelect(productList.value[0])
+        // 数据加载完成后重新计算表格高度
+        setTimeout(() => {
+          calculateTableHeight()
+        }, 100)
+        closeLoading()
+        scanSuccess()
+        tips.value = '请扫描目标库位/修改移库数量'
+        scanType.value = 4
+      } else {
+        scanError()
+        closeLoading()
+        showNotify({ type: 'warning', duration: 3000, message: `暂未查询到《${code}》库存数据` })
+        tips.value = `暂未查询到《${code}》库存数据`
+        productBarcode.value = ''
+        reset()
+      }
+    }).catch((err) => {
+      scanError()
+      closeLoading()
+      tips.value = err.message || '系统异常,请联系技术支持'
+      reset()
+    })
+  }else if (scanType.value == 3) {
+    countRef.value?.blur()
+    scanSuccess()
+    if(targetLocation.value){
+      tips.value = '请手动进行移库操作'
+    }else {
+      tips.value = '请扫描目标库位'
+      scanType.value = 4
+    }
+  }else if (scanType.value == 4) {
+    scanSuccess()
+    targetLocation.value = code
+    tips.value = '请手动进行移库操作'
+  }
+  targetLocationRef.value?.blur()
+  productBarcodeRef.value?.blur()
+  sourceLocationRef.value?.blur()
+}
+const onConfirm=()=>{
+  if(!selectedProducts.value.id){
+    tips.value = '请先查询库存信息'
+    scanType.value=2
+    showNotify({ type: 'warning', duration: 3000, message: `请先查询库存信息` })
+    return
+  }
+  if(!productBarcode.value){
+    scanError()
+    tips.value = '请先扫描商品条码'
+    scanType.value=2
+    showNotify({ type: 'warning', duration: 3000, message: `请先扫描商品条码` })
+    return
+  }
+  if(!transferQuantity.value){
+    scanError()
+    tips.value = '请先输入移库数量'
+    countRef.value?.focus()
+    showNotify({ type: 'warning', duration: 3000, message: `请先输入移库数量` })
+    return
+  }
+  if(transferQuantity.value<=0 || transferQuantity.value>selectedProducts.value.quantityAvailable){
+    scanError()
+    countRef.value?.focus()
+    tips.value = '请先输入正确的移库数量'
+    showNotify({ type: 'warning', duration: 3000, message: `请先输入正确的移库数量` })
+    return
+  }
+  if(!targetLocation.value){
+    scanError()
+    tips.value = '请先扫描目标库位'
+    scanType.value=4
+    showNotify({ type: 'warning', duration: 3000, message: `请先扫描目标库位` })
+    return
+  }
+  showConfirmDialog({
+    title: '移库确认',
+    message:
+      `${productBarcode.value}从"${sourceLocation.value}"移动至"${targetLocation.value}"-${transferQuantity.value}件`
+  })
+    .then(() => {
+      const {fmContainer,lotNumber,ownerCode,sku,location}=selectedProducts.value
+      const data={
+        fmLocation:sourceLocation.value,
+        fmContainer,
+        owner:ownerCode,
+        sku,
+        lotNum:lotNumber,
+        warehouse,
+        quantity:transferQuantity.value,
+        toLocation:targetLocation.value
+      }
+      showLoading()
+      inventoryMovement(data).then(res=>{
+        closeLoading()
+        scanSuccess()
+        showNotify({ type: 'success', duration: 3000, message: `操作成功,请继续扫描商品条码` })
+        tips.value = '操作成功,请继续扫描商品条码'
+        scanType.value = 2
+        productBarcode.value=''
+        reset()
+      }).catch(err=>{
+        scanError()
+        closeLoading()
+        tips.value = err.message || '系统异常,请联系技术支持'
+      })
+    })
+    .catch(() => {});
+}
+const onAsnCancel = () => {
+  if (productBarcode.value === '' || (oldProductBarcode.value.length != productBarcode.value.length && oldProductBarcode.value != '')) {
+    productList.value = []
+    transferQuantity.value = ''
+    selectedProducts.value = {}
+    scanType.value=2
+  }
+}
+const reset = () => {
+  productList.value = []
+  sourceLocation.value = ''
+  targetLocation.value = ''
+  transferQuantity.value = ''
+  oldProductBarcode.value=''
+  selectedProducts.value = {}
+}
+const topRef = ref(null)
+const tableHeight = ref(261)
+
+// 计算表格高度
+const calculateTableHeight = () => {
+  if (topRef.value) {
+    // 使用 nextTick 确保DOM更新完成后再计算
+    nextTick(() => {
+      // 获取键盘高度
+      const keyboardHeight = window.innerHeight - window.visualViewport?.height || 0
+      // 视图高度 - topRef高度 - 导航栏高度(约46px) - 键盘高度
+      const newHeight = window.innerHeight - topRef.value.offsetHeight - 46 - keyboardHeight
+      tableHeight.value = Math.max(newHeight, 200) // 最小高度200px
+    })
+  }
+}
+
+const onProductSelect = (product) => {
+  selectedProducts.value = product
+  // 选择商品时自动赋值移库数量
+  transferQuantity.value = product.quantityAvailable
+  sourceLocation.value = product.location
+  scanType.value=4
+  // 重新计算表格高度
+  calculateTableHeight()
+}
+
+const isSelected = (product) => {
+  return selectedProducts.value?.id === product.id
+}
+
+// 检查是否为同一货主的第一行
+const isFirstInOwnerGroup = (product, index: number) => {
+  if (index === 0) return true
+  return productList.value[index - 1].owner !== product.owner
+}
+
+// 计算相同货主的行跨度
+const getOwnerRowspan = (product, index: number) => {
+  let rowspan = 1
+  for (let i = index + 1; i < productList.value.length; i++) {
+    if (productList.value[i].owner === product.owner) {
+      rowspan++
+    } else {
+      break
+    }
+  }
+  return rowspan
+}
+// 数据刷新
+const loadData = () => {
+}
+onUnmounted(() => {
+  closeListener()
+  // 移除键盘事件监听器
+  if (window.visualViewport) {
+    window.visualViewport.removeEventListener('resize', calculateTableHeight)
+  }
+  window.removeEventListener('resize', calculateTableHeight)
+})
+window.onRefresh = loadData
+</script>
+
+<style scoped>
+.transfer-container {
+  display: flex;
+  flex-direction: column;
+}
+
+/* 搜索表单美化样式 */
+.search-section {
+  margin: 0;
+  background: white;
+  border-radius: 0;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+}
+
+.search-section ::v-deep(.van-cell-group) {
+  background: transparent;
+}
+
+.search-section ::v-deep(.van-field) {
+  position: relative;
+  background: white;
+  transition: all 0.3s ease;
+  border-radius: 0;
+  padding: 0 15px;
+}
+
+.search-section ::v-deep(.van-field:last-child) {
+  border-bottom: none;
+}
+
+.search-section ::v-deep(.van-field__label) {
+  font-weight: 600;
+  color: #323233;
+  font-size: 14px;
+  line-height: 38px;
+  width: 65px;
+}
+
+/* 基于scanType的动态高亮效果 */
+.search-section ::v-deep(.van-field__body) {
+  border-bottom: 2px solid #e7eaf3;
+  height: 36px;
+}
+
+.search-section ::v-deep(.van-field__control) {
+  font-size: 15px;
+  color: #323233;
+  font-weight: 500;
+}
+
+.search-section ::v-deep(.van-field__control::placeholder) {
+  color: #c8c9cc;
+  font-weight: 400;
+}
+
+/* 基于scanType的动态高亮效果 */
+.field-focused ::v-deep(.van-field__body) {
+  border-bottom: 2px solid #1989fa;
+  background: linear-gradient(to right, rgba(25, 137, 250, 0.02), rgba(25, 137, 250, 0.05));
+  position: relative;
+  height: 36px;
+}
+
+.field-focused ::v-deep(.van-field::after) {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 2px;
+  background: #1989fa;
+  z-index: 1;
+}
+
+.field-focused ::v-deep(.van-field__label) {
+  color: #1989fa !important;
+  font-weight: 700;
+}
+
+.field-focused ::v-deep(.van-field__control) {
+  color: #1989fa !important;
+  font-weight: 600;
+}
+
+.active-color ::v-deep(.van-field__control) {
+  color: orangered !important;
+  font-weight: 600;
+}
+
+/* 移除原有的焦点样式 */
+.search-section ::v-deep(.van-field--focused .van-cell) {
+  border-color: transparent !important;
+}
+
+.selected-section {
+  margin: 5px 0;
+  padding: 0 8px;
+}
+
+/* 库存数量展示样式 */
+.quantity-display {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  margin-right: 10px;
+  padding: 4px 8px;
+  background: rgba(25, 137, 250, 0.1);
+  border-radius: 6px;
+  border: 1px solid rgba(25, 137, 250, 0.2);
+}
+
+.quantity-label {
+  font-size: 10px;
+  color: #666;
+  font-weight: 500;
+}
+
+.quantity-value {
+  font-size: 14px;
+  color: orangered;
+  align-items: center;
+  font-weight: 700;
+}
+
+/* 已选商品美化展示 - 蓝色主题直角卡片 */
+.selected-product-cell {
+  background: linear-gradient(135deg, #1989fa 0%, #0570d8 100%);
+  color: white;
+  border-radius: 0;
+  overflow: hidden;
+  box-shadow: 0 4px 12px rgba(25, 137, 250, 0.3);
+  border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+::v-deep(.selected-product-cell.van-cell) {
+  padding: 8px;
+  border-bottom: none;
+}
+
+.selected-product-header {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  margin-bottom: 8px;
+}
+
+.location-info {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  line-height: 14px;
+}
+
+.location-label {
+  font-size: 14px;
+  color: #fff;
+  font-weight: 400;
+}
+
+.location-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #fff;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.selected-product-details {
+  background: rgba(255, 255, 255, 0.1);
+  padding: 5px 0;
+  border-radius: 0;
+  border: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+.detail-auto-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 8px 12px;
+  align-items: center;
+}
+
+.detail-text {
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.95);
+  font-weight: 500;
+  flex: 1;
+  text-align: center;
+  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+  line-height: 12px;
+}
+
+.table-section {
+  flex: 1;
+}
+
+.table-container {
+  background: white;
+  border-radius: 6px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+  position: relative;
+}
+
+.table-wrapper {
+  position: relative;
+  overflow: hidden;
+}
+
+.table-header-fixed {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  background: white;
+  border-bottom: 2px solid #ebedf0;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.table-body-wrapper {
+  overflow-y: auto;
+  overflow-x: hidden;
+  max-height: calc(100% - 40px); /* 减去表头高度 */
+}
+
+.header-table,
+.body-table {
+  table-layout: fixed;
+}
+
+.header-table th:nth-child(1),
+.body-table td:nth-child(1) {
+  width: 40px;
+}
+
+.header-table th:nth-child(2),
+.body-table td:nth-child(2) {
+  width: 15%;
+}
+
+.header-table th:nth-child(3),
+.body-table td:nth-child(3) {
+  width: 20%;
+}
+
+.header-table th:nth-child(4),
+.body-table td:nth-child(4) {
+  width: 12%;
+}
+
+.header-table th:nth-child(5),
+.body-table td:nth-child(5) {
+  width: 12%;
+}
+
+.header-table th:nth-child(6),
+.body-table td:nth-child(6) {
+  width: 12%;
+}
+.table-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #323233;
+  padding: 4px 16px 4px;
+  border-bottom: 1px solid #ebedf0;
+  background: #f7f8fa;
+}
+
+.product-table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.product-table th,
+.product-table td {
+  padding: 8px 4px;
+  text-align: center;
+  border-bottom: 1px solid #ebedf0;
+  font-size: 12px;
+  word-break: break-all;
+}
+
+.product-table th {
+  background-color: #f7f8fa;
+  font-weight: 600;
+  color: #323233;
+  font-size: 13px;
+}
+
+.product-table td {
+  color: #646566;
+}
+
+.product-table tr.selected {
+  background-color: #e8f4ff;
+}
+
+.product-table tr.selected td {
+  color: #1989fa;
+}
+
+.clickable-row {
+  cursor: pointer;
+}
+
+.checkbox-cell {
+  width: 30px;
+}
+
+
+</style>