Bladeren bron

feat: indent 从管理后台移植. vue2 to vue3

peter 4 maanden geleden
bovenliggende
commit
143ced438b
43 gewijzigde bestanden met toevoegingen van 9745 en 23 verwijderingen
  1. 1 0
      .env.production
  2. 3 2
      package.json
  3. 7 0
      src/api/common.js
  4. 122 0
      src/api/indent.js
  5. 80 0
      src/api/product.js
  6. 261 0
      src/components/ImageUpload.vue
  7. 23 0
      src/i18n/index.ts
  8. 65 0
      src/i18n/zh-cn/common.js
  9. 140 0
      src/i18n/zh-cn/index.js
  10. 8 0
      src/i18n/zh-cn/order/category.js
  11. 10 0
      src/i18n/zh-cn/order/indent/calc.js
  12. 36 0
      src/i18n/zh-cn/order/indent/edit-info.js
  13. 15 0
      src/i18n/zh-cn/order/indent/edit.js
  14. 29 0
      src/i18n/zh-cn/order/indent/index.js
  15. 15 0
      src/i18n/zh-cn/order/index.js
  16. 21 0
      src/i18n/zh-cn/order/product.js
  17. 127 0
      src/pages/indent-manage/indent/components/calcPrice/components/dialogDTD.vue
  18. 113 0
      src/pages/indent-manage/indent/components/calcPrice/components/dialogLCL.vue
  19. 297 0
      src/pages/indent-manage/indent/components/calcPrice/components/step1.vue
  20. 47 0
      src/pages/indent-manage/indent/components/calcPrice/components/title.vue
  21. 1724 0
      src/pages/indent-manage/indent/components/calcPrice/index.vue
  22. 66 0
      src/pages/indent-manage/indent/components/calcPrice/styles/index.scss
  23. 24 0
      src/pages/indent-manage/indent/components/calcPrice/styles/table.scss
  24. 155 0
      src/pages/indent-manage/indent/components/exportForm.vue
  25. 540 0
      src/pages/indent-manage/indent/components/exportQuota.vue
  26. 633 0
      src/pages/indent-manage/indent/components/freight.vue
  27. 1657 0
      src/pages/indent-manage/indent/components/info.vue
  28. 168 0
      src/pages/indent-manage/indent/components/skuApply.vue
  29. 284 0
      src/pages/indent-manage/indent/components/skuSelect.vue
  30. 631 0
      src/pages/indent-manage/indent/edit.vue
  31. 9 0
      src/pages/indent-manage/indent/index.vue
  32. 947 0
      src/pages/indent-manage/indent/list.vue
  33. 16 0
      src/pages/indent-manage/index.vue
  34. 193 0
      src/pages/indent-manage/product/category/edit.vue
  35. 126 0
      src/pages/indent-manage/product/category/index.vue
  36. 293 0
      src/pages/indent-manage/product/components/edit.vue
  37. 253 0
      src/pages/indent-manage/product/components/examine.vue
  38. 100 0
      src/pages/indent-manage/product/components/record.vue
  39. 12 0
      src/pages/indent-manage/product/index.vue
  40. 390 0
      src/pages/indent-manage/product/list.vue
  41. 37 20
      src/route.ts
  42. 61 0
      src/utils/axios2.js
  43. 6 1
      vite.config.ts

+ 1 - 0
.env.production

@@ -2,3 +2,4 @@ VITE_CRM_PATH=https://crm.zoho.com/crm/ShowHomePage.do
 VITE_PO_PATH=https://crm.zoho.com/crm/org742735154/tab/PurchaseOrders/
 VITE_PO_APPEND=/canvas/4791186000049921685
 VITE_API_PREFIX='//zohocrmapi.promocollection.com.au'
+VITE_API2_PREFIX='/bpi'

+ 3 - 2
package.json

@@ -15,12 +15,13 @@
     "dayjs": "^1.11.13",
     "element-plus": "2.8.7",
     "html2canvas": "^1.4.1",
-    "jspdf": "^2.5.1",
+    "jspdf": "^2.5.2",
     "lodash": "^4.17.21",
     "lodash.clonedeep": "^4.5.0",
     "lodash.debounce": "^4.0.8",
-    "mathjs": "^13.2.0",
+    "mathjs": "^13.2.1",
     "vue": "^3.5.12",
+    "vue-draggable-plus": "^0.5.6",
     "vue-router": "^4.4.5",
     "xlsx": "^0.18.5"
   },

+ 7 - 0
src/api/common.js

@@ -0,0 +1,7 @@
+import request from '@/utils/axios2'
+
+export default {
+  imagesUpload(params) {
+    return request.post(`/indent_base/imagesUpload`, params)
+  },
+}

+ 122 - 0
src/api/indent.js

@@ -0,0 +1,122 @@
+import request from '@/utils/axios2'
+
+// 获取/搜索 询价列表
+export const getIndentList = (data) =>
+  request({
+    method: 'post',
+    url: `/indent/lists`,
+    data,
+  })
+
+export const getCreator = (data) =>
+  request({
+    method: 'post',
+    url: `/indent/admins`,
+    data,
+  })
+
+export const getVendorList = (data) =>
+  request({
+    url: `/indent/supplier`,
+    method: 'post',
+    data,
+  })
+
+export const getCustomList = (data) =>
+  request({
+    url: '/indent/account',
+    method: 'post',
+    data,
+  })
+
+// 创建询价
+export const createIndent = (data) =>
+  request({
+    url: '/indent/indent',
+    method: 'post',
+    data,
+  })
+
+// 删除询价/报价
+export const deleteIndent = (data) =>
+  request({
+    url: '/indent/delete',
+    method: 'post',
+    data,
+  })
+
+export const getSkuList = (data) =>
+  request({
+    url: '/indent/skuselect',
+    method: 'post',
+    data,
+  })
+
+// 创建报价
+export const createQuote = (data) =>
+  request({
+    url: '/indent/newquote',
+    method: 'post',
+    data,
+  })
+
+// 获取运费参数详情
+export const getSettingDetail = (data) =>
+  request({
+    url: '/indent/settingsDetails',
+    method: 'post',
+    data,
+  })
+
+// 运费参数设置
+export const saveSetting = (data) =>
+  request({
+    url: '/indent/settings',
+    method: 'post',
+    data,
+  })
+
+// 获取商品分类数据
+export const getCategoryTree = () =>
+  request({
+    url: '/Indent_category/IndentCategorySelect',
+    method: 'get',
+  })
+
+// sku申请
+export const applySKU = (data) =>
+  request({
+    url: '/Indent/apply',
+    method: 'post',
+    data,
+  })
+
+// 创建引用报价数据
+export const cloneQuote = (data) =>
+  request({
+    url: '/indent/cloneids',
+    method: 'post',
+    data,
+  })
+
+export const getCalcParams = (data) =>
+  request({
+    url: '/indent/cal',
+    method: 'post',
+    data,
+  })
+
+export const saveCalcData = (data) =>
+  request({
+    url: '/indent/savecal',
+    method: 'post',
+    data,
+  })
+
+// 采纳报价. 实际上就是设置默认. 一条询价下面只会有一条报价被采纳
+export const setDefaultQuote = (data) =>
+  request({
+    url: '/indent/setdefault',
+    method: 'post',
+    data,
+  })

+ 80 - 0
src/api/product.js

@@ -0,0 +1,80 @@
+import request from '@/utils/axios2'
+
+// 获取/搜索 商品列表
+export const getProductList = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_product/lists`,
+    data,
+  })
+
+export const createProduct = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_product/create`,
+    data,
+  })
+
+export const updateProduct = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_product/update`,
+    data,
+  })
+
+// export const getProductDetail = (data) =>
+//   request({
+//     method: 'post',
+//     url: `/Indent_product/details`,
+//     data,
+//   })
+
+// 商品审核记录
+export const getExamineRecord = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_product/prorecord`,
+    data,
+  })
+
+export const saveExamine = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_product/prorecordSave`,
+    data,
+  })
+
+export const deleteProduct = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_product/delete`,
+    data,
+  })
+
+export const getCategoryList = (data) =>
+  request({
+    method: 'get',
+    url: `/Indent_category/lists`,
+    params: data,
+  })
+
+export const editCategory = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_category/edit`,
+    data,
+  })
+
+export const createCategory = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_category/save`,
+    data,
+  })
+
+export const changeCategoryStatus = (data) =>
+  request({
+    method: 'post',
+    url: `/Indent_category/status`,
+    data,
+  })

+ 261 - 0
src/components/ImageUpload.vue

@@ -0,0 +1,261 @@
+<template>
+  <div class="com-image-upload">
+    <VueDraggable
+      v-model="imageList"
+      draggable=".image-item"
+      class="flex items-start flex-wrap"
+      @end="end"
+    >
+      <div
+        v-for="(item, index) in imageList"
+        :key="item.uid || index"
+        class="image-item flex items-start"
+        :style="{ width: width, height: height }"
+      >
+        <img
+          :src="item.url"
+          alt=""
+          class=""
+        />
+        <div class="action-area flex items-center justify-center">
+          <!-- 预览 -->
+          <span
+            v-if="!disablePreview"
+            class="action-icon"
+            @click="handlePictureCardPreview(item)"
+          >
+            <el-icon :size="32">
+              <ZoomIn></ZoomIn>
+            </el-icon>
+          </span>
+          <!-- 删除 -->
+          <span
+            class="action-icon"
+            @click="handleRemove(index)"
+          >
+            <el-icon :size="32">
+              <Delete></Delete>
+            </el-icon>
+          </span>
+        </div>
+      </div>
+      <el-progress
+        v-show="loading"
+        type="circle"
+        :percentage="uploadPercent"
+        :width="Number(width.slice(0, width.length - 2))"
+        style="margin: 8px"
+      ></el-progress>
+      <div
+        class="upload-wrap"
+        :class="{ hide: loading || imageList.length >= max }"
+        :style="{ width: width, height: height }"
+      >
+        <el-upload
+          ref="pictureUpload"
+          :multiple="true"
+          :limit="max"
+          action=""
+          drag
+          accept=".jpg,.png,.jpeg"
+          class="custom-upload-item"
+          list-type="picture-card"
+          :file-list="imageList"
+          :show-file-list="false"
+          :auto-upload="false"
+          :on-preview="handlePictureCardPreview"
+          :on-change="
+            (file, fileList) => {
+              handleUpload(file, fileList)
+            }
+          "
+        >
+          <el-icon :size="32">
+            <Plus></Plus>
+          </el-icon>
+        </el-upload>
+      </div>
+    </VueDraggable>
+
+    <el-dialog v-model="imageDialogVisible">
+      <img
+        width="100%"
+        :src="imageUrl"
+        alt=""
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts">
+export default defineComponent({
+  name: 'ImageUpload',
+})
+</script>
+<script lang="ts" setup>
+import { defineComponent, ref, defineProps, defineEmits, watch } from 'vue'
+import { VueDraggable } from 'vue-draggable-plus'
+import { ElMessage, ElUpload, ElIcon, ElProgress, ElDialog } from 'element-plus'
+import { Plus, Delete, ZoomIn } from '@element-plus/icons-vue'
+import { cloneDeep } from 'lodash'
+import common from '@/api/common'
+const props = defineProps({
+  list: {
+    type: Array,
+    default: () => [],
+  },
+  max: {
+    type: Number,
+    default: 16,
+  },
+  disablePreview: {
+    type: Boolean,
+    default: false,
+  },
+  width: {
+    type: String,
+    default: '150px',
+  },
+  height: {
+    type: String,
+    default: '150px',
+  },
+})
+const $emit = defineEmits(['update:list'])
+
+// 组件内部数据.
+const imageList = ref([] as any[])
+const loading = ref(false)
+const uploadPercent = ref(0)
+const imageDialogVisible = ref(false)
+// 预览大图的url, 每次点击都会更新
+const imageUrl = ref('')
+
+watch(
+  () => props.list,
+  () => (imageList.value = cloneDeep(props.list)),
+)
+
+const handleUpload = (file: any, fileList: any[]) => {
+  if (file.status === 'ready') {
+    loading.value = true
+    const interval = setInterval(() => {
+      if (uploadPercent.value >= 99) {
+        clearInterval(interval)
+        return
+      }
+      uploadPercent.value += 1 // 进度条进度
+    }, 100)
+  }
+
+  const formData = new FormData() as any
+  fileList.forEach((file) => {
+    formData.append('file', file.raw)
+  })
+  formData.append('type', 1)
+  common
+    .imagesUpload(formData)
+    .then((response: any) => {
+      if (response.result.code === 200) {
+        imageList.value.push({
+          url: response.result.data,
+          uid: file.uid,
+        })
+
+        updateList()
+        return
+      }
+      ElMessage.warning(response.result.message)
+    })
+    .catch((error) => {
+      console.log(error, 'component upload image error')
+      ElMessage.error(error.response.data.msg)
+    })
+    .finally(() => {
+      loading.value = false
+      // 进度条恢复到初始状态
+      uploadPercent.value = 0
+    })
+}
+const handleRemove = (index: number) => {
+  imageList.value.splice(index, 1)
+  updateList()
+}
+const handlePictureCardPreview = (file: any) => {
+  imageUrl.value = file.url
+  imageDialogVisible.value = true
+}
+// 每次更新imageList后手动更新父组件的数据, 不能用watch自动更新, 因为同时要watch prop值更新iamgeList, 同时watch会死循环.
+// 直接把prop数据绑定到dragable 和 el-upload的话vue和eslint会报错, 也可能造成调试困难
+const updateList = () => {
+  $emit('update:list', cloneDeep(imageList.value))
+}
+const end = () => {
+  updateList()
+}
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-upload) {
+  border-style: solid;
+  width: 100%;
+  height: 100%;
+}
+:deep(.el-upload-dragger) {
+  width: 100%;
+  height: 100%;
+  border: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.com-image-upload {
+  vertical-align: top;
+}
+.image-item {
+  overflow: hidden;
+  position: relative;
+  border: 1px solid #c0ccda;
+  border-radius: 6px;
+  margin-right: 8px;
+  margin-bottom: 2px;
+  img {
+    width: 100%;
+  }
+  &:hover {
+    .action-area {
+      display: flex;
+    }
+  }
+  .action-area {
+    position: absolute;
+    z-index: 1;
+    left: 0;
+    top: 0;
+    display: none;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.7);
+    color: #fff;
+    font-size: 32px;
+  }
+  .action-icon {
+    cursor: pointer;
+    & + .action-icon {
+      margin-left: 16px;
+    }
+  }
+}
+.upload-wrap {
+  display: inline-block;
+  position: relative;
+  margin-bottom: 2px;
+  &.hide {
+    display: none;
+  }
+}
+.custom-upload-item {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 23 - 0
src/i18n/index.ts

@@ -0,0 +1,23 @@
+import { ref } from 'vue'
+import zhCN from './zh-cn'
+
+const target = ref(new Map())
+
+const generateTrans = function (arr: any, parent = '' as string) {
+  for (const key in arr) {
+    const temp = parent ? `${parent}.${key}` : key
+    if (Object.prototype.hasOwnProperty.call(arr, key)) {
+      if (typeof arr[key] === 'string') {
+        target.value.set(temp, arr[key])
+      } else {
+        generateTrans(arr[key], temp)
+      }
+    }
+  }
+}
+generateTrans(zhCN)
+
+export const $t = (str: string) => {
+  const result = target.value.get(str)
+  return result || str
+}

+ 65 - 0
src/i18n/zh-cn/common.js

@@ -0,0 +1,65 @@
+export default {
+  btn_add: '新增',
+  btn_new: '新建',
+  btn_create: '创建',
+  btn_save: '保存',
+  btn_edit: '编辑',
+  btn_copy: '复制',
+  btn_submit: '提交',
+  btn_reset: '重置',
+  btn_query: '查询',
+  btn_search: '搜索',
+  btn_close: '关闭',
+  btn_cancel: '取消',
+  btn_delete: '删除',
+  btn_delete_mult: '批量删除',
+  btn_notpriced: '未计价',
+  btn_unaudited: '待审核',
+  btn_audit: '审核',
+  btn_pass: '审核通过',
+  btn_approved: '已审核通过',
+  btn_import: '{prefix}导入{append}',
+  btn_export: '{prefix}导出{append}',
+  btn_upload: '上传',
+  btn_download: '下载',
+  btn_process: '去处理',
+  btn_details: '处理详情',
+  table_index: '#',
+  table_status: '状态',
+  table_rank: '排序',
+  table_operation: '操作',
+  table_operator: '操作人',
+  table_operated_time: '操作时间',
+  table_updated_time: '更新时间',
+  table_created_time: '创建时间',
+  bread_current: '当前位置',
+  text_please_select: '请选择',
+  text_please_search: '请输入搜索',
+  text_please_input: '请输入',
+  text_desc: '描述',
+  text_separate: '多个关键字请使用“,”分隔开',
+  text_attention: '注意事项:',
+  video_attention: '时长限制: 0.5-15分钟',
+  video_attention2: '必须上传.mp4视频格式',
+  video_attention3: '视频文件大小不能超过500MB',
+
+  pricing:{
+    text_please_search: '商品名称/商品code/供应商名称',
+    pricing_Status: '计价状态',
+    pricing: '计价',
+    btn_set_freight: '设置运费参数/汇率',
+    table: '计价表',
+    btn_save_download: '保存并生成报价单',
+    purchasing_info: '采购信息',
+    shipping_info: '运输信息',
+    shipping_price: '国内运费单价',
+    is_trans: '是否转运',
+    pricing_info: '计价信息',
+    shipping_method: '选择运输方式',
+    custom: '自定义',
+    selling_calculation: '卖价计算',
+    competitor_info: '对手信息',
+    purchase_quantity: '采购数量',
+    unit_price: '单价(RMB)',
+  }
+}

+ 140 - 0
src/i18n/zh-cn/index.js

@@ -0,0 +1,140 @@
+import common from './common'
+import order from './order/index'
+
+export default {
+  ...common,
+  top_bar: {
+    product_and_member: '商品和会员',
+    system: '系统管理',
+    order: '订单管理',
+  },
+  slider_member: {
+    member: {
+      index: '会员',
+      manage: {
+        index: '会员管理',
+        au_manage: 'AU成员列表',
+        level: '会员等级',
+      },
+    },
+
+    product: {
+      index: '商品',
+      manage: {
+        index: '商品管理',
+        list: '商品列表',
+        au_price: 'AU商品价格',
+        stock: '商品库存管理',
+        warehouse: '仓库管理',
+      },
+      search: {
+        index: '商品检索',
+        keyword: '商品关键词',
+        report: '搜索报表',
+      },
+    },
+
+    property: {
+      index: '属性',
+      product: {
+        index: '商品属性',
+        decoration: 'AU打印价格',
+        addition: 'AU额外服务',
+        cycle: '供应周期',
+        attribute: '分级价格',
+        decoration_type: '打印类型',
+        category: '商品分类',
+        appa_category: 'APPA分类',
+        filter: '筛选属性',
+        icon: '商品图标',
+        freight: '运费管理',
+        weight: '重量管理',
+        tag: '属性标签',
+      },
+    },
+    batch: {
+      index: '批量',
+      action: {
+        index: '批量操作',
+        price: '批量调价',
+        common: '通用批量操作',
+        au_action: 'AU批量操作',
+        history: '操作历史',
+      },
+    },
+    work_order: {
+      index: '工单',
+      work_order_manage: {
+        index: '工单管理',
+        work_order_list: '工单列表',
+      },
+    },
+    supplier: {
+      index: '供应商',
+      manage: {
+        index: '供应商管理',
+        list: '供应商',
+        product: '供应商商品',
+        accessory: '商品配件',
+        pricing: '产品计价',
+      },
+    },
+    article: {
+      index: '图文',
+      article_setting: {
+        index: '图文设置',
+        home_page_setting: '首页图文',
+        download: '下载中心',
+        contact: '联系我们',
+        article_manage: '文章管理',
+        catalogue: 'Catalogue',
+        newsletter: 'Newsletter',
+        video: '视频管理',
+        question: '常见问题',
+        privacy_policy: '隐私政策',
+        refund_and_return: '退款和退货',
+        terms_and_conditions: '条款和条件',
+        site_info: '网站信息',
+      },
+    },
+    resource: {
+      index: '资源',
+      manage: {
+        index: '资源管理',
+        edm_manage: 'EDM管理',
+        edm_resource: 'EDM资源',
+        showcase: 'Showcase',
+        data_tag: '资料标签',
+        data: '营销资料库',
+      },
+    },
+  },
+  slider_authority: {
+    authority: {
+      index: '权限',
+      authority_manage: {
+        index: '权限管理',
+        member: '成员管理',
+        role: '角色管理',
+        department: '部门管理',
+      },
+    },
+  },
+  slider_order: {
+    indent: {
+      index: '报价管理',
+      indent_manage: {
+        index: 'Indent',
+        list: 'Indent List',
+      },
+      product_manage: {
+        index: '商品管理',
+        list: '商品列表',
+        category: '商品分类',
+      },
+    },
+  },
+  order: {
+    ...order,
+  },
+}

+ 8 - 0
src/i18n/zh-cn/order/category.js

@@ -0,0 +1,8 @@
+export default {
+  label_name: '分类名称',
+  label_show: '显示',
+  label_father_category: '上级分类',
+  label_hidden: '隐藏',
+  dialog_title_create: '创建分类',
+  dialog_title_edit: '编辑分类',
+}

+ 10 - 0
src/i18n/zh-cn/order/indent/calc.js

@@ -0,0 +1,10 @@
+export default {
+  label_product_info: '产品信息',
+  label_purchase_info: '采购信息',
+  label_transport_size: '运输尺寸',
+  label_transport_info: '运输信息',
+  label_cost_list: '成本清单',
+  label_other_cost: '其他成本',
+  label_charge: '收费',
+  label_analysis: '分析',
+}

+ 36 - 0
src/i18n/zh-cn/order/indent/edit-info.js

@@ -0,0 +1,36 @@
+export default {
+  label_supplier: '供应商',
+  label_company_name: '公司名',
+  label_supplier_type: '供应商类型',
+  label_wangwang_year: '旺旺年份',
+  label_contact: '联系人',
+  label_phone: '电话',
+  label_product_info: '产品信息',
+  label_product_name: '产品名称',
+  label_product_url: '产品链接',
+  label_product_image: '产品图片',
+  label_size: '尺寸',
+  label_product_hd: '厚度',
+  label_capacity: '容量',
+  label_material: '材质',
+  label_battery: '是否带电池',
+  label_print_require: '要求',
+  label_color: '颜色',
+  label_color_require: '颜色定制要求',
+  label_other: '其他信息',
+  label_factory_price: '出厂价',
+  label_number_table: '数量、单价',
+  label_extra_fee: '额外费用',
+  label_package_info: '包装信息',
+  label_package: '产品包装',
+  label_gross_weight: '每箱毛重',
+  label_package_size: '外箱规格',
+  label_pcs: '一箱个数',
+  label_example: '打样情况',
+  label_example_days: '打样时间',
+  label_example_cost: '打样费用',
+  label_can_refund: '是否退费',
+  label_cert: '证书',
+  label_comment: '备注',
+  btn_add_info: '增加报价',
+}

+ 15 - 0
src/i18n/zh-cn/order/indent/edit.js

@@ -0,0 +1,15 @@
+export default {
+  label_number: '需求数量',
+  label_print_require: '印刷要求',
+  label_deliver_date: '交付日期',
+  label_ref_dimension: '参考尺寸',
+  label_ref_url: '参考链接',
+  label_comment: '备注信息',
+  label_ref_image: '参考图片',
+  col_product_image: '产品图',
+  col_supplier: '供应商',
+  btn_add_info: '增加报价信息',
+  btn_submit_and_next: '提交并录入下一个',
+  title_edit: '编辑报价',
+  title_add: '创建新的报价',
+}

+ 29 - 0
src/i18n/zh-cn/order/indent/index.js

@@ -0,0 +1,29 @@
+export default {
+  label_project_name: '项目名称',
+  label_customer_name: '客户名称',
+  label_product_name: '产品名称',
+  label_supplier_product: '产品名称(供应商)',
+  label_category: '产品分类',
+  label_supplier: '供应商',
+  label_print_method: '印刷方式',
+  label_require_material: '材质要求',
+  label_number: '数量',
+  label_status: '状态',
+  label_quoter: '报价人',
+  ph_quoter: '直接选择或搜索',
+  ph_time_start: '起始',
+  ph_time_end: '结束',
+  table_image: '产品图片',
+  table_supplier: '供应商',
+  table_url: '产品链接',
+  table_price: '单价',
+  table_creator: '创建者',
+  table_indent_name: '报价名称',
+  table_days: '大货天数',
+  btn_new_indent: '创建新的报价',
+  btn_quote: '引用',
+  btn_set_freight: '设置运费参数',
+  btn_calc_price: '计价',
+  btn_adopt: '采纳',
+  btn_adopted: '已采纳',
+}

+ 15 - 0
src/i18n/zh-cn/order/index.js

@@ -0,0 +1,15 @@
+import category from './category'
+import indent from './indent/index'
+import indentEdit from './indent/edit'
+import indentEditInfo from './indent/edit-info'
+import indentCalc from './indent/calc'
+import product from './product'
+
+export default {
+  indent,
+  product,
+  category,
+  indent_edit_info: indentEditInfo,
+  indent_edit: indentEdit,
+  indent_calc: indentCalc,
+}

+ 21 - 0
src/i18n/zh-cn/order/product.js

@@ -0,0 +1,21 @@
+export default {
+  label_project_name: '项目名称',
+  label_category: '产品分类',
+  label_status: '审核状态',
+  label_img: '商品主图',
+  label_product_code: '产品代码',
+  label_name: '产品名称',
+  label_audit_detail: '审核详情',
+  label_audit: '商品审核',
+  label_feedback: '反馈详情',
+  dialog_title_audit: '商品审核',
+  dialog_title_record: '审核记录',
+  dialog_title_edit: '编辑商品',
+  dialog_title_add: '创建商品',
+  label_audit_time: '审核时间',
+  label_audit_operator: '审核人员',
+  label_audit_result: '审核结果',
+  label_name_cn: '中文品名',
+  label_name_en: '英文品名',
+  lable_image_list: '商品图片',
+}

+ 127 - 0
src/pages/indent-manage/indent/components/calcPrice/components/dialogDTD.vue

@@ -0,0 +1,127 @@
+<template>
+  <div class="dialog-calc-price-dtd">
+    <el-dialog
+      v-model="show"
+      class="custom-calc-price-dialog"
+      title="Set DTD"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      :append-to-body="true"
+      width="400px"
+    >
+      <el-form
+        ref="dtdForm"
+        :model="form"
+        label-width="90px"
+      >
+        <el-form-item
+          label="运输方式"
+          prop="method_fcl"
+        >
+          <el-select
+            v-model="form.method_fcl"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="(option, index) in optionList"
+              :key="index"
+              :label="option.label"
+              :value="option.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          label="运输总成本"
+          prop="freight_cost"
+        >
+          <el-input v-model="form.freight_cost">
+            <template #append>AUD</template>
+          </el-input>
+        </el-form-item>
+      </el-form>
+      <div class="flex justify-center items-center">
+        <el-button
+          type="primary"
+          size="small"
+          @click="save"
+        >
+          {{ $t('btn_save') }}
+        </el-button>
+        <el-button
+          size="small"
+          plain
+          @click="close"
+        >
+          {{ $t('btn_cancel') }}
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, watch, defineProps, defineEmits } from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElSelect,
+  ElDialog,
+  ElOption,
+} from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import { $t } from '@/i18n/index'
+defineComponent({
+  name: 'ComponentDialogCalcPriceSetDTD',
+})
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  formData: {
+    type: Object,
+    default: () => {
+      return {}
+    },
+  },
+})
+const $emit = defineEmits(['update:visible', 'save'])
+const show = ref(false)
+const form = ref({} as any)
+const optionList = [
+  {
+    label: $t('text_please_select'),
+    value: '',
+  },
+  {
+    label: '快递',
+    value: '快递',
+  },
+  {
+    label: '空运',
+    value: '空运',
+  },
+  {
+    label: '海运',
+    value: '海运',
+  },
+]
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    if (show.value) form.value = cloneDeep(props.formData)
+  },
+)
+
+const save = () => {
+  $emit('save', form.value)
+  close()
+}
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>

+ 113 - 0
src/pages/indent-manage/indent/components/calcPrice/components/dialogLCL.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="dialog-calc-price-set-lcl">
+    <el-dialog
+      v-model="show"
+      class="custom-calc-price-dialog"
+      title="Set LCL"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      :append-to-body="true"
+      width="400px"
+    >
+      <el-form
+        ref="lclForm"
+        :model="form"
+        label-width="120px"
+      >
+        <el-form-item label="国内运费单价">
+          <el-input v-model="form.unit_lcl">
+            <template #append>RMB</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="国内运费">
+          <el-input
+            v-model="form.price"
+            disabled
+          >
+            <template #append>RMB</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="整合的海运费用">
+          <el-input v-model="form.margin_lcl">
+            <template #append>AUD</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="运输总成本">
+          <el-input v-model="form.freight_cost">
+            <template #append>AUD</template>
+          </el-input>
+        </el-form-item>
+      </el-form>
+      <div class="flex justify-center items-center">
+        <el-button
+          type="primary"
+          size="small"
+          @click="save"
+        >
+          {{ $t('btn_save') }}
+        </el-button>
+        <el-button
+          size="small"
+          plain
+          @click="close"
+        >
+          {{ $t('btn_cancel') }}
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, watch } from 'vue'
+import { ElButton, ElForm, ElFormItem, ElInput, ElDialog } from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import mathjs from '@/utils/math.js'
+import { $t } from '@/i18n/index'
+defineComponent({
+  name: 'ComponentDialogCalcPriceSetLCL',
+})
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  formData: {
+    type: Object,
+    default: () => {
+      return {}
+    },
+  },
+})
+const $emit = defineEmits(['update:visible', 'save'])
+const show = ref(false)
+const form = ref({ unit_lcl: 0 } as any)
+
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    if (show.value) form.value = cloneDeep(props.formData)
+  },
+)
+watch(
+  () => form.value.unit_lcl,
+  (value) => {
+    console.log('form.value.unit_lcl watch change')
+    if (typeof Number(value) === 'number') {
+      form.value.price = mathjs
+        .chain(Number(value))
+        .multiply(Number(form.value.heavy))
+        .done()
+    }
+  },
+)
+const save = () => {
+  $emit('save', form.value)
+  close()
+}
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>

+ 297 - 0
src/pages/indent-manage/indent/components/calcPrice/components/step1.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="step step-1 comp-cal-price-step1">
+    <common-title :title="$t(prefix + 'label_product_info')"></common-title>
+    <div class="flex items-start">
+      <el-carousel
+        indicator-position="outside"
+        arrow="never"
+        height="150px"
+        style="width: 150px; min-width: 150px; margin-right: 8px"
+      >
+        <el-carousel-item
+          v-for="item in imageList as any[]"
+          :key="item"
+        >
+          <div class="product-image flex justify-center items-center">
+            <img :src="item" />
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+
+      <div class="flex-auto product-info">
+        <div class="name">{{ productInfo.product_name }}</div>
+        <div class="vendor-name">{{ productInfo.vendor_name }}</div>
+        <div class="size">尺寸: {{ productInfo.product_size }}</div>
+        <div class="houdu">厚度: {{ productInfo.product_hd }}</div>
+        <div class="material">材质: {{ productInfo.product_material }}</div>
+        <div class="capacity">容量: {{ productInfo.product_capacity }}</div>
+        <div class="battery">是否带电池: {{ productInfo.product_battery }}</div>
+
+        <div class="print-req">
+          印刷要求: {{ productInfo.product_require_print }}
+        </div>
+        <div class="color">颜色要求: {{ productInfo.product_color }}</div>
+        <div class="color-req">
+          颜色定制要求: {{ productInfo.product_require_color }}
+        </div>
+        <div class="other">其他信息: {{ productInfo.product_other }}</div>
+        <div class="note">备注: {{ productInfo.notes }}</div>
+      </div>
+    </div>
+    <br />
+    <common-title :title="$t(prefix + 'label_purchase_info')"></common-title>
+
+    <div class="horizontal-table">
+      <div class="flex items-center tr">
+        <div class="td column-label">采购数量</div>
+        <div
+          v-for="(value, index) in productInfo.number"
+          :key="index"
+          class="td"
+        >
+          {{ value }}
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">单价(RMB)</div>
+        <div
+          v-for="(value, index) in productInfo.price"
+          :key="index"
+          class="td"
+        >
+          {{ value }}
+        </div>
+      </div>
+
+      <div
+        v-for="(item, index) in productInfo.cost_name"
+        :key="index"
+        class="flex items-center tr"
+      >
+        <div class="td column-label">{{ item }}(RMB)</div>
+        <div
+          v-for="(value, key) in productInfo.price"
+          :key="key"
+          class="td"
+        >
+          {{ productInfo.cost_price[index] }}
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">打样费用(RMB)</div>
+        <div
+          v-for="(value, index) in productInfo.price"
+          :key="index"
+          class="td"
+        >
+          {{ productInfo.demo_cost }}
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">大货天数</div>
+        <div
+          v-for="(value, index) in productInfo.days"
+          :key="index"
+          class="td"
+        >
+          {{ value }}&nbsp;天
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">打样天数</div>
+        <div
+          v-for="(value, index) in productInfo.price"
+          :key="index"
+          class="td"
+        >
+          {{ productInfo.demo_days }}&nbsp;天
+        </div>
+      </div>
+    </div>
+
+    <common-title :title="$t(prefix + 'label_transport_size')"></common-title>
+
+    <div class="horizontal-table">
+      <div class="flex items-center tr">
+        <div class="td column-label">箱数</div>
+        <div
+          v-for="(value, index) in computedBoxNumber as any[]"
+          :key="index"
+          class="td"
+        >
+          {{ (Math.ceil(value * 10) / 10).toFixed(1) }}
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">一箱个数</div>
+        <div
+          v-for="(value, index) in productInfo.number"
+          :key="index"
+          class="td"
+        >
+          {{ productInfo.in_package }}
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">外箱规格(CM)</div>
+        <div
+          v-for="(value, index) in productInfo.number"
+          :key="index"
+          class="td flex justify-center items-center"
+        >
+          <span>
+            {{ productInfo.package_size_length }}
+          </span>
+          <span>x</span>
+          <span>
+            {{ productInfo.package_size_width }}
+          </span>
+          <span>x</span>
+          <span>
+            {{ productInfo.package_size_height }}
+          </span>
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">一箱毛重</div>
+        <div
+          v-for="(value, index) in productInfo.number"
+          :key="index"
+          class="td"
+        >
+          {{ productInfo.package_weight.toFixed(2) }}&nbsp;KG
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label flex items-center">
+          总重量
+          <el-tooltip
+            content="轻货/重货的计算公式不同. PS: 可以理解成货运的负重系数, 对货运商来说, 轻货得换算成更高的重量以平衡成本利润"
+          >
+            <span class="el-icon-warning-outline"></span>
+          </el-tooltip>
+        </div>
+        <div
+          v-for="(value, index) in computedTotalWeight as any[]"
+          :key="index"
+          class="td"
+        >
+          {{ value.toFixed(2) }}&nbsp;KG
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label flex items-center">
+          空+派 总重量
+          <el-tooltip
+            content="空+派 方式轻货/重货的判断条件特殊, 部分情况下存在跟上一行不同的负重系数"
+          >
+            <span class="el-icon-warning-outline"></span>
+          </el-tooltip>
+        </div>
+        <div
+          v-for="(value, index) in computedTotalWeightPlus as any[]"
+          :key="index"
+          class="td"
+        >
+          {{ value.toFixed(2) }}&nbsp;KG
+        </div>
+      </div>
+
+      <div class="flex items-center tr">
+        <div class="td column-label">总体积(CBM)</div>
+        <div
+          v-for="(value, index) in computedTotalBulk as any[]"
+          :key="index"
+          class="td"
+        >
+          {{ value.toFixed(2) }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent } from 'vue'
+import {
+  ElTooltip,
+  ElCarousel,
+  ElCarouselItem,
+} from 'element-plus'
+import { $t } from '@/i18n/index'
+import commonTitle from './title.vue'
+defineComponent({
+  name: 'ComponentStep1',
+})
+defineProps({
+  imageList: {
+    type: Array,
+    default: () => [],
+  },
+  productInfo: {
+    type: Object,
+    default: () => {},
+  },
+  computedTotalWeightPlus: {
+    type: Array,
+    default: () => [],
+  },
+  computedTotalWeight: {
+    type: Array,
+    default: () => [],
+  },
+  computedBoxNumber: {
+    type: Array,
+    default: () => [],
+  },
+  computedTotalBulk: {
+    type: Array,
+    default: () => [],
+  },
+})
+const prefix = 'order.indent_calc.'
+</script>
+
+<style lang="scss">
+.comp-cal-price-step1 {
+  .el-carousel__button {
+    width: 10px;
+  }
+}
+</style>
+<style lang="scss" scoped>
+@import '../styles/table.scss';
+
+.product-image {
+  width: 150px;
+  height: 150px;
+  img {
+    width: 100%;
+  }
+}
+.product-info {
+  overflow: hidden;
+  line-height: 22px;
+
+  & > div {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    max-height: 22px;
+  }
+  .name {
+    font-size: 22px;
+    line-height: 28px;
+    max-height: 28px;
+  }
+}
+</style>

+ 47 - 0
src/pages/indent-manage/indent/components/calcPrice/components/title.vue

@@ -0,0 +1,47 @@
+<template>
+  <div class="common-title">
+    <div class="title">{{ title }}</div>
+    <div class="bg-line"></div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, defineProps } from 'vue'
+defineComponent({ name: 'CompCalcTitleTitle' })
+defineProps({
+  title: {
+    default: '',
+    type: String,
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.common-title {
+  box-sizing: border-box;
+  padding: 0 25px;
+  position: relative;
+  margin-bottom: 12px;
+  .title {
+    display: inline-block;
+    font-size: 20px;
+    font-weight: 300;
+    line-height: 24px;
+    background-color: #fff;
+    padding: 0 8px;
+    position: relative;
+    left: 0;
+    top: 0;
+    z-index: 2;
+  }
+  .bg-line {
+    z-index: 1;
+    position: absolute;
+    left: 0;
+    top: 10px;
+    width: 95%;
+    height: 2px;
+    border: 1px solid #efefef;
+  }
+}
+</style>

+ 1724 - 0
src/pages/indent-manage/indent/components/calcPrice/index.vue

@@ -0,0 +1,1724 @@
+<template>
+  <div class="compnent-price-calc">
+    <exportQuota
+      ref="compExportQuota"
+      :product-info="productInfo"
+      :export-form="exportForm"
+      :step3-form-list="step3FormList"
+      :step2-form-list="formList"
+      :creator-options="creatorOptions"
+      :city="formData.cal_city.local_city"
+    ></exportQuota>
+    <el-dialog
+      v-model="show"
+      class="custom-calc-price-dialog"
+      title="查看计价结果"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="1800px"
+    >
+      <div
+        v-loading="loading"
+        class="flex items-start flex-wrap"
+      >
+        <step1
+          :computed-box-number="computedBoxNumber"
+          :computed-total-bulk="computedTotalBulk"
+          :computed-total-weight="computedTotalWeight"
+          :computed-total-weight-plus="computedTotalWeightPlus"
+          :image-list="imageList"
+          :product-info="productInfo"
+        ></step1>
+        <div class="step step-2">
+          <common-title
+            :title="$t(prefix + 'label_transport_info')"
+          ></common-title>
+          <p style="color: #ef4135; margin-bottom: 12px">
+            快速运输天数:2-3天 / 空+派运输天数:8天 /
+            海运运输天数:LCL30天、FCL25天
+          </p>
+          <div class="flex items-center">
+            <el-select
+              v-model="formData.cal_city.local_city"
+              style="width: 120px; margin-right: 12px"
+              size="small"
+            >
+              <el-option
+                v-for="city in cityList"
+                :key="city"
+                :label="city"
+                :value="city"
+              ></el-option>
+            </el-select>
+            <div class="flex items-center">
+              <el-tooltip content="切换城市后点击">
+                <el-button
+                  type="primary"
+                  size="small"
+                  @click="reGenerateFormAfterCityChange"
+                >
+                  生成运输信息
+                </el-button>
+              </el-tooltip>
+              <el-button
+                type="warning"
+                size="small"
+                @click="componentFreightVisible = true"
+              >
+                设置运费参数
+              </el-button>
+            </div>
+          </div>
+
+          <div class="step2-form-area">
+            <div
+              v-for="(formItem, index) in formList"
+              :key="index"
+              class="step2-form-item"
+            >
+              <div class="qty-number">
+                采购数量:&nbsp;{{ formItem.number }} ({{ isHeavyGoods.label
+                }}{{
+                  isHeavyGoodsPlus.flag !== isHeavyGoods.flag
+                    ? ` | ${isHeavyGoodsPlus.label}`
+                    : ''
+                }})
+              </div>
+              <div class="flex items-center">
+                <el-card
+                  v-for="v in 5"
+                  :key="v"
+                  class="form-item-card"
+                  :class="{
+                    on: formItem[`${v - 1}_${formItem.number}`] === 'on',
+                  }"
+                >
+                  <template #header>
+                    <div
+                      class=""
+                      @click.capture="
+                        ($e) => captureSwitchChange($e, formItem, v - 1)
+                      "
+                    >
+                      <el-tooltip
+                        placement="left"
+                        :disabled="
+                          formItem[`${v - 1}_${formItem.number}`] === 'off'
+                        "
+                        content="注意, 切换开关会导致右侧的成本数据以初始值计算重置"
+                      >
+                        <el-switch
+                          v-model="formItem[`${v - 1}_${formItem.number}`]"
+                          size="small"
+                          active-color="#409eff"
+                          inactive-color="#ccc"
+                          active-value="on"
+                          inactive-value="off"
+                          @change="() => generateStep3Form()"
+                        />
+                      </el-tooltip>
+                    </div>
+                  </template>
+                  <div v-if="v === 1">
+                    <el-select
+                      v-model="formItem[`method_0_${formItem.number}`]"
+                      size="small"
+                      @change="reGenerateFormAfterMidwayTypeChange"
+                    >
+                      <el-option
+                        v-for="option in airOption"
+                        :key="option.value"
+                        :label="option.label"
+                        :value="option.value"
+                      ></el-option>
+                    </el-select>
+                    <div class="price">
+                      $&nbsp;{{ formItem.midway_price_0.toFixed(2) }}
+                    </div>
+                  </div>
+                  <div
+                    v-if="v === 2"
+                    class=""
+                  >
+                    <div>空+派</div>
+                    <div class="price">
+                      $&nbsp;{{ formItem.midway_price_1.toFixed(2) }}
+                    </div>
+                  </div>
+                  <div v-if="v === 3">
+                    <el-select
+                      v-model="formItem[`method_2_${formItem.number}`]"
+                      size="small"
+                      @change="reGenerateFormAfterMidwayTypeChange"
+                    >
+                      <el-option
+                        v-for="option in seaOption"
+                        :key="option.value"
+                        :label="option.label"
+                        :value="option.value"
+                      ></el-option>
+                    </el-select>
+                    <div class="price">
+                      $&nbsp;{{ formItem.midway_price_2.toFixed(2) }}
+                    </div>
+                  </div>
+                  <div
+                    v-if="v === 4"
+                    class=""
+                  >
+                    <div>DTD</div>
+                    <div
+                      v-if="formItem.freight_cost_3"
+                      class="price cursor-pointer"
+                      @click="openDTDDialog(3, index)"
+                    >
+                      $&nbsp;{{ formItem.freight_cost_3.toFixed(2) }}
+                    </div>
+                    <div
+                      v-else
+                      class="edit cursor-pointer"
+                      @click="openDTDDialog(3, index)"
+                    >
+                      {{ $t('btn_edit') }}
+                    </div>
+                  </div>
+                  <div
+                    v-if="v === 5"
+                    class=""
+                  >
+                    <div>LCL散货</div>
+
+                    <div
+                      v-if="formItem.freight_cost_4"
+                      class="price cursor-pointer"
+                      @click="openLCLDialog(4, index)"
+                    >
+                      $&nbsp;{{ formItem.freight_cost_4.toFixed(2) }}
+                    </div>
+                    <div
+                      v-else
+                      class="edit cursor-pointer"
+                      @click="openLCLDialog(4, index)"
+                    >
+                      {{ $t('btn_edit') }}
+                    </div>
+                  </div>
+                </el-card>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="step step-3">
+          <el-tabs type="card">
+            <el-tab-pane
+              v-for="(form, index) in step3FormList"
+              :key="index"
+              :label="`${form.tabLabel}`"
+            >
+              <common-title
+                :title="$t(prefix + 'label_cost_list')"
+              ></common-title>
+
+              <div class="horizontal-table">
+                <div
+                  class="flex items-center tr"
+                  style="background-color: #ebeef5"
+                >
+                  <div class="td">项目</div>
+                  <div class="td">总价</div>
+                  <div class="td">单价</div>
+                </div>
+
+                <div class="flex items-center tr">
+                  <div class="td column-label">产品成本(AUD)</div>
+                  <div class="td">
+                    {{ form.product_cost.toFixed(2) }}
+                  </div>
+                  <div class="td">
+                    {{ (form.product_cost / form.number).toFixed(2) }}
+                  </div>
+                </div>
+
+                <div class="flex items-center tr">
+                  <div class="td column-label">制版及其它成本(AUD)</div>
+                  <div class="td">
+                    {{ form.extend_cost.toFixed(2) }}
+                  </div>
+                  <div class="td">
+                    {{ (form.extend_cost / form.number).toFixed(2) }}
+                  </div>
+                </div>
+
+                <div class="flex items-center tr">
+                  <div class="td column-label">运费总成本(AUD)</div>
+                  <div class="td">
+                    {{ form.freight_cost.toFixed(2) }}
+                  </div>
+                  <div class="td">
+                    {{ (form.freight_cost / form.number).toFixed(2) }}
+                  </div>
+                </div>
+
+                <div class="flex items-center tr">
+                  <div class="td column-label">税费(AUD)</div>
+                  <div class="td">
+                    {{ form.tax_fee.toFixed(2) }}
+                  </div>
+                  <div class="td">
+                    {{ (form.tax_fee / form.number).toFixed(2) }}
+                  </div>
+                </div>
+
+                <div class="flex items-center tr">
+                  <div class="td column-label">总成本(AUD)</div>
+                  <div class="td">
+                    {{ form.total_cost.toFixed(2) }}
+                  </div>
+                  <div class="td">
+                    {{ (form.total_cost / form.number).toFixed(2) }}
+                  </div>
+                </div>
+              </div>
+
+              <el-form
+                :model="form"
+                label-width="105px"
+              >
+                <common-title
+                  :title="$t(prefix + 'label_other_cost')"
+                ></common-title>
+                <div class="">
+                  <el-form-item
+                    label="COO Certificate"
+                    label-width="120px"
+                  >
+                    <el-switch
+                      v-model="form.coo_certificate"
+                      active-color="#409eff"
+                      inactive-color="#ccc"
+                      active-text="是"
+                      inactive-text="否"
+                      active-value="on"
+                      inactive-value="off"
+                      size="small"
+                      @change="($e: any) => changeCOO(form, $e)"
+                    ></el-switch>
+                  </el-form-item>
+                </div>
+                <div class="flex justify-between flex-wrap items-center">
+                  <el-form-item label="税点">
+                    <el-input
+                      v-model="form.tax"
+                      :disabled="form.coo_certificate === 'on'"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'tax')"
+                    >
+                      <template #append>%</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="税费">
+                    <el-input
+                      v-model="form.tax_fee"
+                      disabled
+                      type="number"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="入口报关费用">
+                    <el-input
+                      v-model="form.gatt_tax_fee"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'gatt_tax_fee')"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="验货费">
+                    <el-input
+                      v-model="form.review_cost"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'review_cost')"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="国内运费总价">
+                    <el-input
+                      v-model="form.cn_freight_cost"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'cn_freight_cost')"
+                    >
+                      <template #append>RMB</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="国外本土运费">
+                    <el-input
+                      v-model="form.local_freight_cost"
+                      type="number"
+                      @blur="
+                        () => onForm3ItemChange(form, 'local_freight_cost')
+                      "
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                </div>
+
+                <common-title
+                  :title="$t(prefix + 'label_charge')"
+                ></common-title>
+                <div class="flex justify-between flex-wrap items-center">
+                  <el-form-item label="Set Up Cost">
+                    <el-input
+                      v-model="form.setup_cost"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'setup_cost')"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="本地收费">
+                    <el-input
+                      v-model="form.add_freight_cost"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'add_freight_cost')"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="售卖单价">
+                    <el-input
+                      v-model="form.sold_unit"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'sold_unit')"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="售卖总价">
+                    <el-input
+                      v-model="form.sold_price"
+                      disabled
+                      type="number"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                </div>
+
+                <common-title
+                  :title="$t(prefix + 'label_analysis')"
+                ></common-title>
+                <div class="flex justify-between items-center">
+                  <el-form-item label="利润率">
+                    <el-input
+                      v-model="form.profit_margin"
+                      type="number"
+                      @blur="() => onForm3ItemChange(form, 'profit_margin')"
+                    >
+                      <template #append>%</template>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="利润">
+                    <el-input
+                      v-model="form.profit"
+                      disabled
+                      type="number"
+                    >
+                      <template #append>AUD</template>
+                    </el-input>
+                  </el-form-item>
+                </div>
+              </el-form>
+
+              <div class="flex justify-center items-center">
+                <el-button
+                  type="primary"
+                  size="small"
+                  @click="
+                    () => {
+                      isExport = 0
+                      checkForm(1)
+                    }
+                  "
+                >
+                  {{ $t('btn_save') }}
+                </el-button>
+                <el-button
+                  type="primary"
+                  size="small"
+                  plain
+                  @click="
+                    () => {
+                      isExport = 1
+                      checkForm(2)
+                    }
+                  "
+                >
+                  保存并导出
+                </el-button>
+                <!-- <el-button @click="generate">test</el-button> -->
+              </div>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </div>
+      <setDTD
+        v-model:visible="dtdVisible"
+        :form-data="dtdData"
+        @save="dtdChange"
+      ></setDTD>
+      <setLCL
+        v-model:visible="lclVisible"
+        :form-data="lclData"
+        @save="lclChange"
+      ></setLCL>
+      <freight
+        v-model:visible="componentFreightVisible"
+        @save="getCalcParam"
+      ></freight>
+      <CompExportForm
+        v-model:visible="componentExportFormVisible"
+        :step3-form-list="step3FormList"
+        :creator-options="creatorOptions"
+        :form-data="exportForm"
+        @save="onExportFormSave"
+      ></CompExportForm>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import {
+  defineComponent,
+  ref,
+  watch,
+  computed,
+  defineProps,
+  defineEmits,
+  nextTick,
+  useTemplateRef,
+  ShallowRef,
+} from 'vue'
+import {
+  ElButton,
+  ElNotification,
+  ElMessage,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElSelect,
+  ElDialog,
+  ElOption,
+  ElSwitch,
+  ElCard,
+  ElTabs,
+  ElTabPane,
+  ElTooltip,
+} from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import commonTitle from './components/title.vue'
+import setDTD from './components/dialogDTD.vue'
+import setLCL from './components/dialogLCL.vue'
+import freight from '../freight.vue'
+import step1 from './components/step1.vue'
+import CompExportForm from '../exportForm.vue'
+import exportQuota from '../exportQuota.vue'
+import mathjs, { savePrecision } from '@/utils/math.js'
+import { getCalcParams, saveCalcData } from '@/api/indent'
+import { $t } from '@/i18n/index'
+defineComponent({
+  name: 'ComponentAaaa',
+})
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  dataForCalc: {
+    type: Object,
+    default: () => {
+      return {}
+    },
+  },
+  creatorOptions: {
+    type: Array,
+    default: () => [],
+  },
+})
+const $emit = defineEmits([
+  'update:visible',
+  'save-price-calc',
+  'save-price-calc',
+])
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+const prefix = 'order.indent_calc.'
+const loading = ref(false)
+const componentFreightVisible = ref(false)
+const componentExportFormVisible = ref(false)
+const productInfo = ref({} as any)
+const setting = ref({} as any) // 运费设置数据
+// 用在step2界面上显示的表单
+const formList = ref([] as any[])
+// 用在step3界面上显示的表单
+const step3FormList = ref([] as any[])
+
+// 提交的表单, 用来存真正提交的数据.
+const formData = ref({
+  cal_city: {
+    local_city: '',
+  },
+  cal_shipment_method: {},
+} as any)
+
+// 导出pdf那个表单
+let exportForm = ref({
+  exchange: '',
+  days: '30',
+  gst_name: '+GST',
+  notes: '',
+  other_notes: '',
+  saleperson: '',
+} as any)
+
+const step3FormDemo = ref({
+  coo_certificate: 'on', // coo 开关
+  tax: 0, // 税点 coo打开则是0, 关闭默认是5
+  tax_fee: 0, // 税费 coo打开则是0, 需要根据税点计算
+  gatt_tax_fee: 60, // 入口报关费用
+  review_cost: 0, // 验货费
+  cn_freight_cost: 0, // 国内运费总价
+  local_freight_cost: 0, // 国外本地运费
+  add_freight_cost: 0, // 本地收费
+  freight: 0, // 运费总成本
+  setup_cost: 0, // Set Up Cost
+  sold_unit: 0, // 售卖单价
+  sold_price: 0, // 售卖总价
+  profit_margin: 30, // mark up 利润率
+  profit: 0, // 利润
+} as any)
+const imageList = ref([] as any[])
+const cityList = ref([] as any[])
+
+// 空运选项. 参考: https://www.tapd.cn/59388921/prong/stories/view/1159388921001001048
+const airOption = [
+  { label: 'DHL', value: 'dhl' },
+  { label: 'TNT', value: 'tnt' },
+  { label: 'Fedex', value: 'fedex' },
+]
+// 海运选项
+const seaOption = ref([
+  { label: 'FCL', value: 'fcl' },
+  { label: 'LCL', value: 'lcl' },
+])
+
+const dtdData = ref({} as any)
+const lclData = ref({} as any)
+const dtdVisible = ref(false)
+const lclVisible = ref(false)
+
+const show = ref(false)
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    resetData()
+    if (show.value) {
+      getCalcParam()
+    }
+  },
+)
+let getCalcParam = () => {
+  getCalcParams().then((response: any) => {
+    const res = response.result
+    if (response.code !== 1) return
+    cityList.value = res.city_list
+    if (cityList.value.length) {
+      formData.value.cal_city.local_city = cityList.value[0]
+    }
+
+    setting.value = Object.assign({}, res.data)
+
+    setting.value.dhl_airline = JSON.parse(setting.value.dhl_airline)
+    if (!Array.isArray(setting.value.dhl_airline)) {
+      setting.value.dhl_airline = []
+    } else {
+      setting.value.dhl_airline = setting.value.dhl_airline.map((i: any) => {
+        const result: any = {}
+        for (const key in i) {
+          if (Object.prototype.hasOwnProperty.call(i, key)) {
+            result[key] = Number(i[key])
+          }
+        }
+        return result
+      })
+    }
+
+    setting.value.tnt_airline = JSON.parse(setting.value.tnt_airline)
+    if (!Array.isArray(setting.value.tnt_airline)) {
+      setting.value.tnt_airline = []
+    } else {
+      setting.value.tnt_airline = setting.value.tnt_airline.map((i: any) => {
+        const result: any = {}
+        for (const key in i) {
+          if (Object.prototype.hasOwnProperty.call(i, key)) {
+            result[key] = Number(i[key])
+          }
+        }
+        return result
+      })
+    }
+
+    setting.value.fedex_airline = JSON.parse(setting.value.fedex_airline)
+    if (!Array.isArray(setting.value.fedex_airline)) {
+      setting.value.fedex_airline = []
+    } else {
+      setting.value.fedex_airline = setting.value.fedex_airline.map(
+        (i: any) => {
+          const result: any = {}
+          for (const key in i) {
+            if (Object.prototype.hasOwnProperty.call(i, key)) {
+              result[key] = Number(i[key])
+            }
+          }
+          return result
+        },
+      )
+    }
+
+    setting.value.airplus = JSON.parse(setting.value.airplus)
+    if (!Array.isArray(setting.value.airplus)) {
+      setting.value.airplus = []
+    } else {
+      setting.value.airplus = setting.value.airplus.map((i: any) => {
+        const result: any = {}
+        for (const key in i) {
+          if (Object.prototype.hasOwnProperty.call(i, key)) {
+            result[key] = Number(i[key])
+          }
+        }
+        return result
+      })
+    }
+
+    setting.value.cbm = JSON.parse(setting.value.cbm)
+    if (!Array.isArray(setting.value.cbm)) {
+      setting.value.cbm = []
+    } else {
+      setting.value.cbm = setting.value.cbm.map((i: any) => {
+        const result: any = {}
+        for (const key in i) {
+          if (Object.prototype.hasOwnProperty.call(i, key)) {
+            result[key] = Number(i[key])
+          }
+        }
+        return result
+      })
+    }
+    delete setting.value.admin_id
+    delete setting.value.id
+    delete setting.value.is_del
+    delete setting.value.update_time
+    delete setting.value.create_time
+    delete setting.value.delete_time
+
+    // 把浅层的几个属性全部转换成数字. 方便后续的计算
+    for (const key in setting.value) {
+      if (
+        Object.prototype.hasOwnProperty.call(setting.value, key) &&
+        typeof setting.value[key] === 'string'
+      ) {
+        setting.value[key] = Number(setting.value[key])
+      }
+    }
+
+    initProductInfo()
+    initForm([], true)
+  })
+}
+let close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+let resetData = () => {
+  formData.value = {
+    cal_city: {
+      local_city: '',
+    },
+    cal_shipment_method: {},
+  }
+  exportForm.value = {
+    cycle_name: '',
+    exchange: '',
+    days: '30',
+    gst_name: '+GST',
+    notes: '',
+    other_notes: '',
+    saleperson: '',
+  }
+}
+
+// 总箱数. 购买数量/每箱数量.
+let computedBoxNumber = computed(() => {
+  if (productInfo.value.number?.length) {
+    return productInfo.value.number.map((i: any) =>
+      mathjs.chain(i).divide(productInfo.value.in_package).done(),
+    )
+  } else {
+    return []
+  }
+})
+// 单箱体积. 立方厘米. 长*宽*高
+let computedBulk = computed(() => {
+  return mathjs
+    .chain(productInfo.value.package_size_length || 1)
+    .multiply(productInfo.value.package_size_width || 1)
+    .multiply(productInfo.value.package_size_height || 1)
+    .done()
+})
+// 是否重货. 每箱毛重 大于等于 (每箱体积 / 5000) 则为重货.
+let isHeavyGoods = computed(() => {
+  if (
+    productInfo.value.package_weight >
+    mathjs.chain(computedBulk.value).divide(5000).done()
+  ) {
+    return {
+      flag: true,
+      label: '重货',
+    }
+  } else {
+    return {
+      flag: false,
+      label: '轻货',
+    }
+  }
+})
+// 是否重货. 空+派 配送方式专属的 轻重货计算逻辑, 与标准的计算区别在于一个除6000一个除5000
+let isHeavyGoodsPlus = computed(() => {
+  if (
+    productInfo.value.package_weight >
+    mathjs.chain(computedBulk.value).divide(6000).done()
+  ) {
+    return {
+      flag: true,
+      label: '重货',
+    }
+  } else {
+    return {
+      flag: false,
+      label: '轻货',
+    }
+  }
+})
+// 界面上写的是总重量, 实际上应该理解成负重系数, 即轻重货占不同体积, 货运得收不同费用维持利润.
+// 重货是货物重量本身, 即每箱毛重*箱数, 轻货则是每箱体积*箱数/5000
+let computedTotalWeight = computed(() => {
+  if (!computedBoxNumber.value.length) return []
+  if (isHeavyGoods.value.flag) {
+    // 重货
+    return computedBoxNumber.value.map((i: number) => {
+      return mathjs
+        .chain(
+          // 重量浮动比. 在运费参数里面设置的.
+          mathjs
+            .chain(Number(setting.value.rate_weight || 0))
+            .divide(100)
+            .add(1)
+            .done(),
+        )
+        .multiply(productInfo.value.package_weight)
+        .multiply(i)
+        .done()
+    })
+  } else {
+    // 轻货
+    return computedBoxNumber.value.map((i: number) => {
+      return mathjs.chain(computedBulk.value).multiply(i).divide(5000).done()
+    })
+  }
+})
+// 空+派 方式的(总重量)负重系数.
+let computedTotalWeightPlus = computed(() => {
+  if (!computedBoxNumber.value.length) return []
+  if (isHeavyGoodsPlus.value.flag) {
+    // 重货
+    return computedBoxNumber.value.map((i: number) => {
+      return mathjs
+        .chain(
+          // 重量浮动比. 在运费参数里面设置的.
+          mathjs
+            .chain(Number(setting.value.rate_weight || 0))
+            .divide(100)
+            .add(1)
+            .done(),
+        )
+        .multiply(productInfo.value.package_weight)
+        .multiply(i)
+        .done()
+    })
+  } else {
+    // 轻货
+    return computedBoxNumber.value.map((i: number) => {
+      return mathjs.chain(computedBulk.value).multiply(i).divide(6000).done()
+    })
+  }
+})
+// 总体积CBM. 单箱体积*箱数*浮动 / 一百万
+let computedTotalBulk = computed(() => {
+  if (!computedBoxNumber.value.length) return []
+
+  return computedBoxNumber.value.map((i: number) => {
+    return mathjs
+      .chain(
+        // 体积浮动比. 在运费参数里面设置的.
+        mathjs.chain(Number(setting.value.rate_bulk)).divide(100).add(1).done(),
+      )
+      .multiply(computedBulk.value)
+      .multiply(i)
+      .divide(1000000)
+      .done()
+  })
+})
+// 返回值会有两个子数组. 第一个是重货的, 第二个是轻货的,
+// 每个数组里有两项, 分别是计算公式的参数1和2.
+let computedCityFreightParams = computed(() => {
+  if (formData.value.cal_city?.local_city) {
+    return setting.value.city_list[formData.value.cal_city.local_city]
+  }
+  return []
+})
+// 国内运费
+// let computedLocalFreight = computed(() => {
+//   if (isHeavyGoodsPlus.value.flag) {
+//     return this.computedTotalWeight((i:number) => {
+//       return mathjs.chain(setting.value.cn_price_heavy).multiply(i).done()
+//     })
+//   }
+//   return computedTotalBulk.value.map((i:number) => {
+//     return mathjs.chain(setting.value.cn_price).multiply(i).done()
+//   })
+// })
+
+let initProductInfo = () => {
+  const temp = cloneDeep(props.dataForCalc)
+
+  if (Array.isArray(temp.product_image) && temp.product_image.length) {
+    imageList.value = temp.product_image.map((img) => {
+      return $mediaRegExp.test(img)
+        ? img
+        : import.meta.env.VITE_APP_OSS_PREFIX + img
+    })
+  }
+
+  temp.cost_name = []
+  temp.cost_price = []
+  if (temp.cost_list && temp.cost_list.length > 2) {
+    const t = JSON.parse(temp.cost_list)
+
+    t.forEach((item: any) => {
+      temp.cost_name.push(item.cost_name)
+      temp.cost_price.push(parseFloat(item.cost_price))
+    })
+  }
+
+  temp.number = []
+  temp.price = []
+  temp.days = []
+  if (temp.price_list && temp.price_list.length > 2) {
+    const t = JSON.parse(temp.price_list)
+
+    t.forEach((item: any) => {
+      temp.number.push(parseFloat(item.number))
+      temp.price.push(parseFloat(item.price))
+      temp.days.push(parseFloat(item.days))
+    })
+  }
+  // 之前保存到数据库的计价信息
+  if (typeof temp.save_cal === 'string' && temp.save_cal.length > 2) {
+    temp.save_cal = JSON.parse(temp.save_cal)
+  } else {
+    temp.save_cal = {}
+  }
+  // 初始化之前选择的城市
+  if (temp.save_cal.cal_city) {
+    formData.value.cal_city.local_city = temp.save_cal.cal_city.local_city
+  }
+  productInfo.value = temp
+}
+/**
+ * 根据 productInfo 生成表单, 包括step2的和部分step3的数据.
+ * @useOldFormData 仅刚打开界面初始化数据时为 true, 后续的调用都为 false
+ */
+let initForm = (switchStatus = [] as any[], useOldFormData = false) => {
+  formList.value = []
+  step3FormList.value = []
+  if (productInfo.value.number?.length) {
+    // 保存的数据库里面的中途配送数据
+    const oldMidWayData = productInfo.value.save_cal.cal_shipment_method
+
+    productInfo.value.number.forEach((item: any, index: number) => {
+      const t: any = {}
+      t[`method_0_${item}`] = 'dhl'
+      t[`method_2_${item}`] = 'lcl'
+      if (useOldFormData) {
+        t[`method_0_${item}`] = oldMidWayData[`method_0_${item}`] || 'dhl'
+        t[`method_2_${item}`] = oldMidWayData[`method_2_${item}`] || 'lcl'
+      }
+
+      t[`method_3_${item}`] = ''
+      t[`method_4_${item}`] = ''
+
+      // 这5个是开关
+      t[`0_${item}`] = 'off'
+      t[`1_${item}`] = 'off'
+      t[`2_${item}`] = 'off'
+      t[`3_${item}`] = 'off'
+      t[`4_${item}`] = 'off'
+
+      if (useOldFormData) {
+        // 上一次编辑中, 保存到数据库的数据记录
+        t[`0_${item}`] = oldMidWayData[`0_${item}`] || 'off'
+        t[`1_${item}`] = oldMidWayData[`1_${item}`] || 'off'
+        t[`2_${item}`] = oldMidWayData[`2_${item}`] || 'off'
+        t[`3_${item}`] = oldMidWayData[`3_${item}`] || 'off'
+        t[`4_${item}`] = oldMidWayData[`4_${item}`] || 'off'
+      }
+      if (switchStatus.length) {
+        // 记录当前页面之前已经打开的开关状态, 用在切换城市后的重新初始化逻辑中.
+        t[`0_${item}`] = switchStatus[index][`0_${item}`] || 'off'
+        t[`1_${item}`] = switchStatus[index][`1_${item}`] || 'off'
+        t[`2_${item}`] = switchStatus[index][`2_${item}`] || 'off'
+        t[`3_${item}`] = switchStatus[index][`3_${item}`] || 'off'
+        t[`4_${item}`] = switchStatus[index][`4_${item}`] || 'off'
+      }
+
+      // 目的城市运费
+      t.city_price_0 = 0
+      t.city_price_1 = 0
+      t.city_price_2 = 0
+      t.city_price_3 = 0
+      t.city_price_4 = 0
+
+      // 中途运费总额. 即海运和空运的.
+      t.midway_price_0 = 0
+      t.midway_price_1 = 0
+      t.midway_price_2 = 0
+      // 这两个是自行输入然后直接显示在step2的中途运费位置, 其他三个是计算出来的,
+      // 同时也作为step3的运费总成本(AUD)
+      t.freight_cost_3 = 0
+      t.freight_cost_4 = 0
+
+      // 国内本地运费
+      t.cn_price_0 = 0
+      t.cn_price_1 = 0
+      t.cn_price_2 = 0
+
+      // 成本清单
+      t.product_cost = calcProductCost(index) // 产品成本(AUD)
+      t.extend_cost = calcExtendCost() // 制版及其它成本(AUD)
+
+      t.total_cost = 0 // 总成本(AUD)
+
+      // 给html模版分辨是哪个表单的数据
+      t.number = item
+
+      // 计价步骤上貌似没有用到的数据, 但是保存到数据库, 并且该对话框界面回显.
+      t.fclData = {}
+      t.lclData = {}
+      if (useOldFormData) {
+        const oldData = productInfo.value.save_cal
+        if (oldData[`cal_fcl_${item}`]) {
+          t.fclData = oldData[`cal_fcl_${item}`]
+          t.freight_cost_3 = oldData[`cal_fcl_${item}`].total_fcl
+        }
+        if (oldData[`cal_lcl_${item}`]) {
+          t.lclData = oldData[`cal_lcl_${item}`]
+          t.freight_cost_4 = oldData[`cal_lcl_${item}`].total_lcl
+        }
+      }
+
+      formList.value.push(t)
+    })
+    exportForm.value = {
+      exchange: productInfo.value.save_cal.exchange || '',
+      days: productInfo.value.save_cal.days || '',
+      gst_name: productInfo.value.save_cal.gst_name || '+GST',
+      notes: productInfo.value.save_cal.notes || '',
+      other_notes: productInfo.value.save_cal.other_notes || '',
+      saleperson: productInfo.value.save_cal.saleperson || '',
+    }
+  }
+
+  setFreight()
+  if (useOldFormData) {
+    // 仅页面初始化时会执行到这里
+    generateStep3Form(true)
+  }
+}
+let reGenerateFormAfterCityChange = () => {
+  const temp: any[] = formList.value.map((i, index) => {
+    const result: any = {}
+
+    result[`0_${productInfo.value.number[index]}`] =
+      i[`0_${productInfo.value.number[index]}`]
+    result[`1_${productInfo.value.number[index]}`] =
+      i[`1_${productInfo.value.number[index]}`]
+    result[`2_${productInfo.value.number[index]}`] =
+      i[`2_${productInfo.value.number[index]}`]
+    result[`3_${productInfo.value.number[index]}`] =
+      i[`3_${productInfo.value.number[index]}`]
+    result[`4_${productInfo.value.number[index]}`] =
+      i[`4_${productInfo.value.number[index]}`]
+
+    result[`method_0_${productInfo.value.number[index]}`] =
+      i[`method_0_${productInfo.value.number[index]}`]
+    result[`method_2_${productInfo.value.number[index]}`] =
+      i[`method_2_${productInfo.value.number[index]}`]
+
+    return result
+  })
+
+  initForm(temp)
+  generateStep3Form()
+}
+/**
+ * 切换运送方式之后, 重新计算中途运费和生成step3的表单内容. 开关属于开启状态才有必要重算
+ */
+let reGenerateFormAfterMidwayTypeChange = () => {
+  setFreight()
+  generateStep3Form()
+}
+let generateStep3Form = (withOldData = false) => {
+  const labelList = ['快递', '空+派', '海运', 'DTD', 'LCL散货']
+  const result: any[] = []
+  formList.value.forEach((i: any) => {
+    for (let n = 0; n < 5; n++) {
+      if (i[`${n}_${i.number}`] === 'on') {
+        let temp = Object.assign({}, step3FormDemo.value, {
+          tabLabel: `${i.number} ${labelList[n]}`,
+          number: i.number,
+          typeNumber: n,
+          cn_freight_cost: 0,
+          product_cost: i.product_cost,
+          extend_cost: i.extend_cost,
+          total_cost: i.total_cost,
+        })
+
+        if (n <= 2) {
+          temp.cn_freight_cost = savePrecision(i[`cn_price_${n}`])
+          temp.local_freight_cost = savePrecision(i[`city_price_${n}`])
+          temp.add_freight_cost = savePrecision(i[`city_price_${n}`])
+          // 记录中途运费, 用在step3表单项变更后的重复计算
+          temp.midway_price = i[`midway_price_${n}`]
+          temp.freight_cost = mathjs
+            .chain(i[`midway_price_${n}`])
+            .add(i[`city_price_${n}`])
+            .done()
+
+          temp.total_cost = mathjs
+            .chain(i.product_cost)
+            .add(i.extend_cost)
+            .add(temp.freight_cost)
+            .add(
+              mathjs
+                .chain(temp.cn_freight_cost)
+                .divide(setting.value.rate_rmb_aud)
+                .done(),
+            )
+            .add(step3FormDemo.value.tax_fee)
+            .add(step3FormDemo.value.gatt_tax_fee)
+            .add(step3FormDemo.value.review_cost)
+            .done()
+        } else if (n === 3) {
+          temp.local_freight_cost = 0
+          temp.add_freight_cost = 0
+          temp.freight_cost = mathjs
+            .chain(i[`freight_cost_${n}`])
+            .add(i[`city_price_${n}`])
+            .done()
+
+          temp.total_cost = mathjs
+            .chain(i.product_cost)
+            .add(i.extend_cost)
+            .add(temp.freight_cost)
+            .add(
+              mathjs
+                .chain(temp.cn_freight_cost)
+                .divide(setting.value.rate_rmb_aud)
+                .done(),
+            )
+            .add(step3FormDemo.value.tax_fee)
+            .add(step3FormDemo.value.gatt_tax_fee)
+            .add(step3FormDemo.value.review_cost)
+            .done()
+
+          temp[`cn_price_${n}`] = 0 // 国内运费. 后两种配送方式默认是0
+        } else if (n === 4) {
+          temp.local_freight_cost = 0
+          temp.add_freight_cost = 0
+          temp.freight_cost = mathjs
+            .chain(i[`freight_cost_${n}`])
+            .add(i[`city_price_${n}`])
+            .done()
+          // total_lcl 运费总成本
+          // price 国内总运费
+          temp.total_cost = mathjs
+            .chain(i.product_cost)
+            .add(i.extend_cost)
+            .add(temp.freight_cost)
+            .add(
+              mathjs
+                .chain(temp.cn_freight_cost)
+                .divide(setting.value.rate_rmb_aud)
+                .done(),
+            )
+            .add(step3FormDemo.value.tax_fee)
+            .add(step3FormDemo.value.gatt_tax_fee)
+            .add(step3FormDemo.value.review_cost)
+            .done()
+
+          temp[`cn_price_${n}`] = 0 // 国内运费. 后两种配送方式默认是0
+        }
+
+        temp.profit = savePrecision(
+          // 利润率是整数, 除100算出百分数.
+          mathjs
+            .chain(step3FormDemo.value.profit_margin)
+            .divide(100)
+            .multiply(temp.total_cost)
+            .done(),
+        )
+        temp.sold_price = savePrecision(
+          mathjs
+            .chain(temp.total_cost)
+            .multiply(
+              mathjs
+                .chain(step3FormDemo.value.profit_margin)
+                .add(100)
+                .divide(100)
+                .done(),
+            )
+            .done(),
+        )
+        temp.sold_unit = mathjs
+          .chain(
+            Math.ceil(
+              Number(
+                mathjs
+                  .chain(temp.sold_price)
+                  .subtract(temp.add_freight_cost)
+                  .subtract(step3FormDemo.value.setup_cost)
+                  .divide(i.number)
+                  .multiply(100)
+                  .done(),
+              ),
+            ),
+          )
+          .divide(100)
+          .done()
+
+        exportForm.value[`zdy_date_${n}_${i.number}`] = ''
+        exportForm.value[`cycle_name_${n}_${i.number}`] = 'weeks'
+
+        if (withOldData === true) {
+          const oldData = productInfo.value.save_cal
+          if (oldData[`cal_quote_${n}_${i.number}`]) {
+            const cloneData = cloneDeep(oldData[`cal_quote_${n}_${i.number}`])
+            cloneData.freight_cost = cloneData.freight
+            delete cloneData.freight
+            temp = Object.assign(temp, cloneData)
+          }
+          exportForm.value[`zdy_date_${n}_${i.number}`] =
+            oldData[`zdy_date_${n}_${i.number}`] || ''
+          exportForm.value[`cycle_name_${n}_${i.number}`] =
+            oldData[`cycle_name_${n}_${i.number}`] || 'weeks'
+        }
+
+        result.push(temp)
+      }
+    }
+  })
+  step3FormList.value = result
+}
+// 设置 中途运费 及 国外当地运费
+let setFreight = () => {
+  productInfo.value.number.forEach((item: any, index: number) => {
+    if (index > 2) {
+      return
+    }
+    const cityPrice = calcCityPrice(
+      computedTotalWeight.value[index],
+      computedTotalBulk.value[index],
+    )
+    // 国外本地运费, dtd 和 lcl散货默认是0, 所以这里只给前三个填充了值.
+    formList.value[index].city_price_0 = cityPrice
+    formList.value[index].city_price_1 = cityPrice
+    formList.value[index].city_price_2 = cityPrice
+
+    // 航空:总重量*空运费单价(AUD) * (1+燃油附加)
+    formList.value[index].midway_price_0 = mathjs
+      .chain(
+        getCurrentPrice(
+          formList.value[index][`method_0_${item}`],
+          computedTotalWeight.value[index],
+        ),
+      )
+      .multiply(computedTotalWeight.value[index])
+      .multiply(mathjs.chain(setting.value.rate_fuel).divide(100).add(1).done())
+      .done()
+
+    // 空+派 与航空的计算方式基本一致。
+    // 差异点为,重轻货判断及负重系数计算时,除以6000, 燃油附加是另外的字段.
+    formList.value[index].midway_price_1 = mathjs
+      .chain(getCurrentPricePlus(computedTotalWeightPlus.value[index]))
+      .multiply(computedTotalWeightPlus.value[index])
+      .multiply(
+        mathjs.chain(setting.value.rate_fuelplus).divide(100).add(1).done(),
+      )
+      .done()
+
+    // 海运 CBM * 基础参数里面的海运费. 旧后台这一项的计算有误差, cbm用了约进两位数之后的值来计算.
+    formList.value[index].midway_price_2 = mathjs
+      .chain(computedTotalBulk.value[index])
+      .multiply(setting.value.sea_fee)
+      .done()
+
+    const cn_price = calcLocalPrice(index)
+    // 国内运费
+    formList.value[index].cn_price_0 = cn_price
+    formList.value[index].cn_price_1 = cn_price
+    formList.value[index].cn_price_2 = cn_price
+  })
+}
+// 目标城市的快递费用计算. 这个判断轻重货时不用区分 空+派 的 /6000
+let calcCityPrice = (heavy: number, bulk: number) => {
+  const p = computedCityFreightParams.value
+  if (!p.length) return 0
+
+  if (isHeavyGoods.value.flag) {
+    return p[0][0] + (p[0][1] / 50) * heavy
+  } else {
+    return p[1][0] + p[1][1] * bulk
+  }
+}
+// 目标城市的快递费用计算. 空+派方式的, 这种方式轻重货判断计算公式不一样
+// let calcCityPricePlus = (heavy: number, bulk: number) => {
+//   const p = computedCityFreightParams.value
+//   if (!p.length) return 0
+
+//   if (isHeavyGoodsPlus.value.flag) {
+//     return p[0][0] + (p[0][1] / 50) * heavy
+//   } else {
+//     return p[1][0] + p[1][1] * bulk
+//   }
+// }
+// 国内运费. 轻重货计算公式不同
+let calcLocalPrice = (index: number) => {
+  if (isHeavyGoods.value.flag) {
+    return mathjs
+      .chain(setting.value.cn_price_heavy)
+      .multiply(computedTotalWeight.value[index])
+      .done()
+  } else {
+    return mathjs
+      .chain(setting.value.cn_price)
+      .multiply(computedTotalBulk.value[index])
+      .done()
+  }
+}
+// 打样费用 和 额外费用(报价信息里面的出厂价-额外费用), 再除以汇率
+let calcExtendCost = () => {
+  const reduceCost = productInfo.value.cost_price.reduce(
+    (total: number, i: number) => {
+      total = total + i
+      return total
+    },
+    0,
+  )
+  const extendCost = mathjs
+    .chain(reduceCost)
+    .add(Number(productInfo.value.demo_cost) || 0)
+    .divide(setting.value.rate_rmb_aud)
+    .done()
+  return extendCost
+}
+// 产品成本 =  单价 * 数量 / 汇率
+let calcProductCost = (index: number) => {
+  return mathjs
+    .chain(productInfo.value.number[index])
+    .multiply(productInfo.value.price[index])
+    .divide(setting.value.rate_rmb_aud)
+    .done()
+}
+// 获取 当前重量的 运费单价
+let getCurrentPrice = (key: string | number, heavy: number) => {
+  const target = setting.value[`${key}_airline`]
+  let result = 0
+  if (Array.isArray(target) && target.length) {
+    result = target.findIndex((i: any) => i.min < heavy && i.max > heavy)
+  }
+  return target[result].price
+}
+// 空+ 派
+let getCurrentPricePlus = (heavy: number) => {
+  const target = setting.value.airplus
+
+  let result = 0
+  if (Array.isArray(target) && target.length) {
+    result = target.findIndex((i: any) => i.min < heavy && i.max > heavy)
+  }
+
+  return target[result].price
+}
+// 开关切换前的点击捕获, 检查前提数据是否已输入, 没有的话不允许打开开关. 旧后台的交互.
+let captureSwitchChange = (e: any, obj: any, n: number) => {
+  if (n === 3) {
+    if (typeof obj.freight_cost_3 !== 'number' || obj.freight_cost_3 <= 0) {
+      ElMessage.warning('请先点击编辑, 填写相关数据, 再打开开关')
+      e.preventDefault()
+      e.stopPropagation()
+    }
+  } else if (n === 4) {
+    if (typeof obj.freight_cost_4 !== 'number' || obj.freight_cost_4 <= 0) {
+      ElMessage.warning('请先点击编辑, 填写相关数据, 再打开开关')
+      e.preventDefault()
+      e.stopPropagation()
+    }
+  }
+}
+let changeCOO = (form: any, value: any) => {
+  // 因为设置了active value, value变量不再是布尔值
+  if (value === 'on') {
+    form.tax = 0
+    onForm3ItemChange(form, 'tax')
+  } else {
+    form.tax = 5
+    onForm3ItemChange(form, 'tax')
+  }
+}
+let convertIntoNumber = (value: string | number) => {
+  const result = Number(value)
+  return typeof result === 'number' ? result : 0
+}
+let calcTaxFee = (form: any) => {
+  form.tax_fee = savePrecision(
+    mathjs.chain(form.product_cost).multiply(form.tax).divide(100).done(),
+  )
+}
+let calcProfit = (form: any) => {
+  form.profit = savePrecision(
+    mathjs.chain(form.sold_price).subtract(form.total_cost).done(),
+  )
+}
+let calcProfitMargin = (form: any) => {
+  form.profit_margin = savePrecision(
+    mathjs.chain(form.profit).divide(form.total_cost).multiply(100).done(),
+  )
+}
+let calcTotalCost = (form: any) => {
+  form.total_cost = mathjs
+    .chain(form.product_cost)
+    .add(form.extend_cost)
+    .add(form.freight_cost)
+    .add(
+      mathjs
+        .chain(form.cn_freight_cost)
+        .divide(setting.value.rate_rmb_aud)
+        .done(),
+    )
+    .add(form.tax_fee)
+    .add(form.gatt_tax_fee)
+    .add(form.review_cost)
+    .done()
+}
+/**
+ * 用利润率计算售价总价
+ **/
+let calcSoldPriceByProfitMargin = (form: any) => {
+  form.sold_price = savePrecision(
+    mathjs
+      .chain(form.total_cost)
+      .multiply(mathjs.chain(form.profit_margin).add(100).divide(100).done())
+      .done(),
+  )
+}
+/**
+ * 用单价计算售价总价. 单价*数量 + setup cost + add freight cost
+ **/
+let calcSoldPriceBySoldUnit = (form: any) => {
+  form.sold_price = savePrecision(
+    mathjs
+      .chain(form.sold_unit)
+      .multiply(form.number)
+      .add(form.setup_cost)
+      .add(form.add_freight_cost)
+      .done(),
+  )
+}
+let onForm3ItemChange = (form: any, key: string) => {
+  // form3 的项除了开关, 其他输入框变动首先将对应之转换为数字.
+  form[key] = convertIntoNumber(form[key])
+
+  // 各输入框之间的输入值变动会有联动.
+  // 注意, 各 case 里面的调用顺序有影响, 改动之前务必保证你理解当前在做什么.
+  switch (key) {
+    case 'tax':
+      // 影响 税费、总成本、利润率、利润值
+      calcTaxFee(form)
+      calcTotalCost(form)
+      calcProfit(form)
+      calcProfitMargin(form)
+
+      break
+    case 'review_cost':
+      // 影响 总成本、利润率、利润值
+      calcTotalCost(form)
+      calcProfit(form)
+      calcProfitMargin(form)
+      break
+    case 'cn_freight_cost':
+      // 影响 总成本、利润率、利润值
+      calcTotalCost(form)
+      calcProfit(form)
+      calcProfitMargin(form)
+      break
+    case 'local_freight_cost':
+      // 影响 运费总成本、总成本、售卖总价、利润率. 利润值(不影响)
+      form.add_freight_cost = form.local_freight_cost
+      form.freight_cost = mathjs
+        .chain(form.local_freight_cost)
+        .add(form.midway_price)
+        .done()
+      calcTotalCost(form)
+      calcProfitMargin(form)
+      calcSoldPriceByProfitMargin(form)
+
+      break
+    case 'add_freight_cost':
+    // 影响 售卖总价、利润率、利润值
+    // fallthrough
+    case 'setup_cost':
+    // 影响 售卖总价、利润值、利润率
+    // fallthrough
+    case 'sold_unit':
+      // 直接乘算出售卖总价, 更新利润率、利润值
+      calcSoldPriceBySoldUnit(form)
+      calcProfit(form)
+      calcProfitMargin(form)
+      break
+
+    case 'profit_margin':
+      // 影响 利润值、售卖总价、售卖单价
+      calcSoldPriceByProfitMargin(form)
+      calcProfit(form)
+      form.sold_unit = mathjs
+        .chain(
+          Math.ceil(
+            Number(
+              mathjs
+                .chain(form.sold_price)
+                .subtract(form.add_freight_cost)
+                .subtract(form.setup_cost)
+                .divide(form.number)
+                .multiply(100)
+                .done(),
+            ),
+          ),
+        )
+        .divide(100)
+        .done()
+      break
+    default:
+      // 默认不做任何处理
+      break
+  }
+}
+let openDTDDialog = (columnNum: number, rowNum: number) => {
+  dtdData.value = {
+    rowNum,
+    columnNum,
+    freight_cost: formList.value[rowNum][`freight_cost_${columnNum}`] || '',
+    method_fcl: formList.value[rowNum].fclData.method_fcl || '',
+  }
+  dtdVisible.value = true
+}
+let dtdChange = (data: any) => {
+  formList.value[dtdData.value.rowNum][
+    `freight_cost_${dtdData.value.columnNum}`
+  ] = Number(data.freight_cost) || 0
+  formList.value[dtdData.value.rowNum].fclData.method_fcl =
+    data.method_fcl || ''
+  formList.value[dtdData.value.rowNum].fclData.total_fcl =
+    Number(data.freight_cost) || 0
+  generateStep3Form()
+}
+let openLCLDialog = (columnNum: number, rowNum: number) => {
+  console.log(columnNum, rowNum)
+  lclData.value = {
+    rowNum,
+    columnNum,
+    heavy: computedTotalWeight.value[rowNum],
+    freight_cost: formList.value[rowNum][`freight_cost_${columnNum}`] || '',
+    margin_lcl: formList.value[rowNum].lclData.margin_lcl || '',
+    unit_lcl: formList.value[rowNum].lclData.unit_lcl || '',
+    price: formList.value[rowNum].lclData.price || '',
+  }
+  lclVisible.value = true
+}
+let lclChange = (data: any) => {
+  // 这个是 运输总成本, 会添加到 step3form 里面用于计算
+  formList.value[lclData.value.rowNum][
+    `freight_cost_${lclData.value.columnNum}`
+  ] = Number(data.freight_cost) || 0
+
+  // 这四个保存到数据库的, total_lcl其实就是 运输总成本
+  formList.value[lclData.value.rowNum].lclData.unit_lcl =
+    Number(data.unit_lcl) || 0
+  formList.value[lclData.value.rowNum].lclData.price = Number(data.price) || 0
+  formList.value[lclData.value.rowNum].lclData.margin_lcl =
+    Number(data.margin_lcl) || 0
+  formList.value[lclData.value.rowNum].lclData.total_lcl =
+    Number(data.freight_cost) || 0
+
+  generateStep3Form()
+}
+let isExport = ref(0)
+let checkForm = (type = 1) => {
+  // 利用引用传值变更 formData 的内容
+  const method = formData.value.cal_shipment_method
+
+  formList.value.forEach((item) => {
+    for (let i = 0; i < 5; i++) {
+      if (item[`${i}_${item.number}`] === 'on') {
+        method[`${i}_${item.number}`] = 'on'
+      }
+    }
+    method[`method_0_${item.number}`] = item[`method_0_${item.number}`]
+    method[`method_2_${item.number}`] = item[`method_2_${item.number}`]
+    if (item.lclData && item.lclData.total_lcl) {
+      formData.value[`cal_lcl_${item.number}`] = cloneDeep(item.lclData)
+    }
+    if (item.fclData && item.fclData.total_fcl) {
+      formData.value[`cal_fcl_${item.number}`] = cloneDeep(item.fclData)
+    }
+  })
+
+  step3FormList.value.forEach((item) => {
+    formData.value[`cal_quote_${item.typeNumber}_${item.number}`] = {
+      coo_certificate: item.coo_certificate, // coo 开关
+      tax: item.tax, // 税点 coo打开则是0, 关闭默认是5
+      tax_fee: item.tax_fee, // 税费 coo打开则是0, 需要根据税点计算
+      gatt_tax_fee: item.gatt_tax_fee, // 入口报关费用
+      review_cost: item.review_cost, // 验货费
+      cn_freight_cost: item.cn_freight_cost, // 国内运费总价
+      local_freight_cost: item.local_freight_cost, // 国外本地运费
+      add_freight_cost: item.add_freight_cost, // 本地收费
+      freight: savePrecision(item.freight_cost), // 运费总成本
+      setup_cost: item.setup_cost, // Set Up Cost
+      sold_unit: item.sold_unit, // 售卖单价
+      sold_price: item.sold_price, // 售卖总价
+      profit_margin: item.profit_margin, // mark up 利润率
+      profit: item.profit, // 利润
+    }
+  })
+  const p = Object.assign(
+    {
+      id: props.dataForCalc.id || '',
+    },
+    cloneDeep(formData.value),
+  )
+  if (isExport.value === 1) p.is_export = 1 // 后端用来区分是否是点了导出
+
+  if (type === 1) {
+    saveCalc(Object.assign(p, exportForm.value))
+  } else if (type === 2) {
+    componentExportFormVisible.value = true
+  }
+}
+let saveCalc = (p: any) => {
+  loading.value = true
+  saveCalcData(p)
+    .then((res: any) => {
+      if (res.code !== 1) {
+        ElNotification({
+          type: 'error',
+          title: '保存出错了',
+          duration: 3000,
+        })
+        return
+      }
+      ElNotification({
+        type: 'success',
+        title: '保存成功',
+        duration: 3000,
+      })
+      $emit('save-price-calc')
+    })
+    .finally(() => {
+      setTimeout(() => {
+        loading.value = false
+      }, 300)
+    })
+}
+let onExportFormSave = (data: any) => {
+  exportForm.value = cloneDeep(data)
+  checkForm(1)
+  // 生成pdf涉及DOM操作, 故变更数据后需要等界面更新,
+  // 此处需要nextTick保证生成时数据已经更新到了界面上.
+  nextTick(() => {
+    generate()
+  })
+}
+
+const compExportQuotaRef = useTemplateRef('compExportQuota')
+let generate = () => {
+  const target: ShallowRef = compExportQuotaRef
+  if (
+    target.value.generatePDF &&
+    typeof target.value.generatePDF === 'function'
+  ) {
+    target.value.generatePDF()
+  }
+}
+</script>
+
+<style lang="scss">
+.compnent-price-calc {
+  .custom-calc-price-dialog {
+    margin-top: 0 !important;
+    height: 100vh;
+    .el-dialog__body {
+      max-height: calc(100vh - 56px);
+      overflow-y: scroll;
+      overflow-x: auto;
+      padding: 4px 0 0;
+    }
+  }
+  .step-3 {
+    input[type='number'] {
+      -moz-appearance: textfield;
+      appearance: textfield;
+      &:hover {
+        -moz-appearance: textfield;
+        appearance: textfield;
+        &::-webkit-inner-spin-button,
+        &::-webkit-outer-spin-button {
+          -webkit-appearance: none;
+          margin: 0;
+        }
+      }
+      &::-webkit-inner-spin-button,
+      &::-webkit-outer-spin-button {
+        -webkit-appearance: none;
+        margin: 0;
+      }
+    }
+  }
+}
+</style>
+<style lang="scss" scoped>
+@import './styles/index.scss';
+</style>

+ 66 - 0
src/pages/indent-manage/indent/components/calcPrice/styles/index.scss

@@ -0,0 +1,66 @@
+@import './table.scss';
+.compnent-price-calc {
+  .step {
+    width: 33%;
+    min-width: 570px;
+    border-right: 1px solid #dcdfe6;
+    padding: 0 12px;
+  }
+
+  .step2-form-area {
+    margin-top: 12px;
+  }
+
+  .step2-form-item {
+    padding-top: 12px;
+    margin-bottom: 12px;
+    .qty-number {
+      margin-bottom: 8px;
+      font-weight: 600;
+      color: #222;
+    }
+    .form-item-card {
+      flex: 1;
+      height: 150px;
+      line-height: 28px;
+      text-align: center;
+      &.form-item-card {
+        margin-left: 12px;
+      }
+      &.on {
+        border-color: #009688;
+      }
+      :deep(.el-card__body) {
+        padding: 20px 6px;
+      }
+      :deep(.el-card__header) {
+        padding: 6px;
+      }
+    }
+    .edit,
+    .price {
+      margin-top: 12px;
+      color: #ef4135;
+      &.cursor-pointer {
+        cursor: pointer;
+      }
+    }
+    .edit {
+      color: #009688;
+    }
+  }
+  .step-3 {
+    border-right: none;
+    :deep(.el-form) {
+      width: 100%;
+      position: relative;
+      .el-input-group__append {
+        padding: 0 8px;
+      }
+    }
+    :deep(.el-form-item) {
+      margin-right: 0;
+      width: 49%;
+    }
+  }
+}

+ 24 - 0
src/pages/indent-manage/indent/components/calcPrice/styles/table.scss

@@ -0,0 +1,24 @@
+// 横向表格样式
+.horizontal-table {
+  border-bottom: 1px solid #dcdfe6;
+  border-right: 1px solid #dcdfe6;
+  margin-bottom: 12px;
+  max-width: 100%;
+  overflow-x: scroll;
+
+  .td {
+    text-align: center;
+    flex: 1;
+    min-width: 95px;
+    line-height: 28px;
+    padding: 2px 4px;
+    border-top: 1px solid #dcdfe6;
+    border-left: 1px solid #dcdfe6;
+  }
+  .column-label {
+    text-align: left;
+    background-color: #ebeef5;
+    width: 120px;
+    min-width: 120px;
+  }
+}

+ 155 - 0
src/pages/indent-manage/indent/components/exportForm.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="">
+    <el-dialog
+      v-model="show"
+      class="custom-export-indent-quote-dialog"
+      title="导出"
+      width="500px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :append-to-body="true"
+      :before-close="close"
+    >
+      <el-form
+        label-width="120px"
+        :model="form"
+      >
+        <el-form-item label="Saleperson">
+          <el-select
+            v-model="form.saleperson"
+            filterable
+          >
+            <el-option
+              v-for="item in creatorOptions as any[]"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          v-for="item in step3FormList as any[]"
+          :key="item.tabLabel"
+          :label="item.tabLabel"
+        >
+          <div class="flex items-center">
+            <el-input
+              v-model="form[`zdy_date_${item.typeNumber}_${item.number}`]"
+            ></el-input>
+            &nbsp;
+            <el-select
+              v-model="form[`cycle_name_${item.typeNumber}_${item.number}`]"
+            >
+              <el-option value="weeks">weeks</el-option>
+              <el-option value="business days">business days</el-option>
+            </el-select>
+          </div>
+        </el-form-item>
+        <el-form-item label="Exchange">
+          <el-input v-model="form.exchange"></el-input>
+        </el-form-item>
+        <el-form-item label="报价有效期">
+          <el-input v-model="form.days">
+            <template #append>Days</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="NOTES">
+          <el-input
+            v-model="form.notes"
+            :rows="4"
+            type="textarea"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input
+            v-model="form.other_notes"
+            :rows="4"
+            type="textarea"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="商品及服务税">
+          <el-select v-model="form.gst_name">
+            <el-option value=""></el-option>
+            <el-option value="+GST">+GST</el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div class="flex justify-center items-center">
+        <el-button
+          size="small"
+          type="primary"
+          @click="save"
+        >
+          确定
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, ref, defineProps, defineEmits, watch } from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElSelect,
+  ElDialog,
+  ElOption,
+} from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+defineComponent({
+  name: 'ComponentIndentExportForm',
+})
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  formData: {
+    type: Object,
+    default: () => {},
+  },
+  creatorOptions: {
+    type: Array,
+    default: () => [],
+  },
+  step3FormList: {
+    type: Array,
+    default: () => [],
+  },
+})
+const $emit = defineEmits(['update:visible', 'save'])
+const show = ref(false)
+const form = ref({} as any)
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    if (show.value) {
+      form.value = cloneDeep(props.formData)
+    }
+  },
+)
+const save = () => {
+  $emit('save', form.value)
+  close()
+}
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>
+<style lang="scss">
+.custom-export-indent-quote-dialog {
+  .el-dialog__body {
+    min-height: 60vh;
+    max-height: 80vh;
+    overflow-y: scroll;
+    overflow-x: auto;
+    padding-top: 8px;
+    padding-bottom: 8px;
+  }
+}
+</style>

+ 540 - 0
src/pages/indent-manage/indent/components/exportQuota.vue

@@ -0,0 +1,540 @@
+<template>
+  <div class="pdf-wrap">
+    <div
+      id="pdfTarget"
+      class="pdf-preview"
+    >
+      <div class="flex justify-between items-center">
+        <div class="logo-area">
+          <img
+            :src="getLogoPath()"
+            alt=""
+          />
+        </div>
+        <div class="quotation-text">Quotation</div>
+      </div>
+
+      <div class="flex justify-between items-start">
+        <div class="">
+          <div class="">FAIR OCEAN TRADING AUSTRALIA</div>
+          <div class="">PTY LTD</div>
+          <div class="">15/10 Chilvers Road,</div>
+          <div class="">THORNLEIGH NSW 2120,</div>
+          <div class="">AUSTRALIA</div>
+
+          <br />
+          <div class="">
+            Email:&nbsp;&nbsp;&nbsp;&nbsp;accounts@promocollection.com.au
+          </div>
+          <div class="">Phone Number:&nbsp;&nbsp;&nbsp;&nbsp;02 9008 1152</div>
+          <div class="">Fax Number:&nbsp;&nbsp;&nbsp;&nbsp;02 9008 1157</div>
+        </div>
+
+        <div class="">
+          <div class="flex items-center">
+            <div style="font-weight: bold">Sales Person:&nbsp;</div>
+            <div class="">{{ computedCreator }}</div>
+          </div>
+          <div class="flex items-center">
+            <div style="font-weight: bold">Order Date:&nbsp;</div>
+            <div>{{ dayjs().format('DD MMM YYYY') }}</div>
+          </div>
+          <br />
+          <div class="main-picture">
+            <div
+              class="background"
+              :style="{ backgroundImage: `url(${mainPicture})` }"
+            ></div>
+          </div>
+        </div>
+      </div>
+      <div class="product-info">
+        <div class="bold">Infomation</div>
+
+        <div class="name bold">{{ productInfo.product_name }}</div>
+
+        <div class="size flex items-center">
+          <div class="bold">Size:</div>
+          {{ productInfo.product_size }}
+        </div>
+
+        <div class="houdu flex items-center">
+          <div class="bold">Thickness:</div>
+          {{ productInfo.product_hd }}
+        </div>
+
+        <div class="capacity flex items-center">
+          <div class="bold">Capacity:</div>
+          {{ productInfo.product_capacity }}
+        </div>
+        <div class="material flex items-center">
+          <div class="bold">Material:</div>
+          {{ productInfo.product_material }}
+        </div>
+        <div class="print-req flex items-center">
+          <div class="bold">Print:</div>
+          {{ productInfo.product_require_print }}
+        </div>
+        <div class="color-req flex items-center">
+          <div class="bold">Color requirements:</div>
+          {{ productInfo.product_require_color }}
+        </div>
+
+        <div class="color flex items-center">
+          <div class="bold">Product colours Avaliable:</div>
+          {{ productInfo.product_color }}
+        </div>
+        <div class="packaging flex items-center">
+          <div class="bold">Packaging:</div>
+          <div class="">{{ productInfo.package_info }}</div>
+        </div>
+        <div class="other flex items-center">
+          <div class="bold">More Details:</div>
+          <div class="">{{ productInfo.product_other }}</div>
+        </div>
+      </div>
+      <div class="product-table">
+        <div class="flex items-center tr">
+          <div class="th index">#</div>
+          <div class="th name">Items</div>
+          <div class="th qty">Qty</div>
+          <div class="th setup-cost">Setup Cost</div>
+          <div class="th sold-unit">Unit Cost</div>
+          <div class="th">Local Freight</div>
+          <div class="th">Total(ex-GST)($)</div>
+        </div>
+        <div
+          v-for="(row, index) in step3FormList as any[]"
+          :key="index"
+          class="flex items-center tr"
+        >
+          <div class="td index">{{ index + 1 }}</div>
+          <div class="td name">
+            <div>{{ productInfo.product_name }}</div>
+            <div class="flex items-center">
+              <div>By {{ getFreightType(row) }}</div>
+              <div>&nbsp;-&nbsp;</div>
+              <div>
+                {{ exportForm[`zdy_date_${row.typeNumber}_${row.number}`] }}
+              </div>
+              <div>
+                {{ exportForm[`cycle_name_${row.typeNumber}_${row.number}`] }}
+              </div>
+            </div>
+          </div>
+          <div class="td flex items-center qty">{{ row.number }} units</div>
+          <div class="td flex items-center setup-cost">
+            ${{ row.setup_cost }} +GST
+          </div>
+          <div class="td flex items-center sold-unit">
+            ${{ row.sold_unit }} +GST
+          </div>
+          <div class="td flex items-center">
+            ${{ row.add_freight_cost }} +GST
+          </div>
+          <div class="td flex items-center">${{ row.sold_price }} +GST</div>
+        </div>
+      </div>
+
+      <div class="notes-wrap">
+        <div class="title">Notes</div>
+        <div class="notes">{{ productInfo.notes }}</div>
+      </div>
+
+      <div class="price-valid-info">
+        Freight to {{ city_short[city] }} is included.Price based on exchange
+        rate of {{ exportForm.exchange }}. Price is only valid for
+        {{ exportForm.days }} days.
+      </div>
+
+      <div
+        v-for="(item, index) in otherPicture"
+        :key="index"
+        class="background other-picture"
+        :style="{ backgroundImage: `url(${item})` }"
+      ></div>
+
+      <div class="end-flag flex justify-center items-center">
+        <div class="end-line"></div>
+        <div class="end-text flex justify-center items-center">End</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, defineProps, computed } from 'vue'
+// import { ElButton, ElInput, ElTree, ElDialog, ElPagination } from 'element-plus'
+import jspdf from 'jspdf'
+import html2canvas from 'html2canvas'
+import dayjs from 'dayjs'
+defineComponent({
+  name: 'ComponentExportQuota',
+})
+const props = defineProps({
+  city: {
+    type: String,
+    default: '',
+  },
+  productInfo: {
+    type: Object,
+    default: () => {
+      return {}
+    },
+  },
+  exportForm: {
+    type: Object,
+    default: () => {
+      return {}
+    },
+  },
+  step2FormList: {
+    type: Array,
+    default: () => {
+      return []
+    },
+  },
+  step3FormList: {
+    type: Array,
+    default: () => {
+      return []
+    },
+  },
+  creatorOptions: {
+    type: Array,
+    default: () => [],
+  },
+})
+const city_short = {
+  SYD: 'Sydney',
+  Melb: 'Melbourne',
+  Brisbane: 'Brisbane',
+  SA: 'Adelaide',
+  WA: 'Perth',
+  '': '',
+} as any
+
+const getLogoPath = () => {
+  return new URL('/logo.png', import.meta.url).href
+}
+const getFreightType = (row: any) => {
+  let result = 'Sea'
+
+  if (row.typeNumber < 2) {
+    result = 'Air'
+  }
+  if (row.typeNumber === 3) {
+    const temp: any[] = props.step2FormList.filter(
+      (i: any) => i.number === row.number,
+    )
+    if (
+      temp.length &&
+      temp[0].fclData?.method_fcl &&
+      ['快递', '空运'].includes(temp[0].fclData.method_fcl)
+    ) {
+      result = 'Air'
+    }
+  }
+  return result
+}
+const generatePDF = () => {
+  const A4_WIDTH = 592.28
+  const A4_HEIGHT = 841.89
+  const imageWrapper = document.getElementById('pdfTarget') as HTMLElement // 获取DOM
+
+  const pageHeight = (imageWrapper.clientWidth / A4_WIDTH) * A4_HEIGHT
+  const lableListID = Array.from(
+    imageWrapper.querySelectorAll('#pdfTarget > div'),
+  ) as HTMLElement[]
+  // console.log(pageHeight, 'pageHeight')
+  // console.log(lableListID.length, 'lableListID.length')
+  // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
+  for (let i = 0; i < lableListID.length; i++) {
+    const multiple = Math.ceil(
+      (lableListID[i]!.offsetTop + lableListID[i].clientHeight) / pageHeight,
+    )
+    console.log('element在第', multiple, '页')
+    if (shouldSplit(lableListID, i, multiple * pageHeight)) {
+      console.log('\n在第', i, '个元素后插入\n')
+      const divParent = lableListID[i].parentNode as HTMLElement // 获取该div的父节点
+      const newNode = document.createElement('div')
+      newNode.className = 'empty-div'
+      newNode.style.background = '#fff'
+      const _H =
+        multiple * pageHeight -
+        (lableListID[i].offsetTop + lableListID[i].offsetHeight)
+      // 留白
+      newNode.style.height = _H + 40 + 'px'
+      newNode.style.width = '100%'
+
+      const next = lableListID[i].nextSibling // 获取div的下一个兄弟节点
+      // 判断兄弟节点是否存在
+      if (next) {
+        // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
+        divParent.insertBefore(newNode, next)
+      } else {
+        // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
+        divParent.appendChild(newNode)
+      }
+    }
+  }
+
+  html2canvas(imageWrapper, {
+    allowTaint: true,
+    useCORS: true,
+    backgroundColor: '#fff', // 一定要设背景颜色,否则有的浏览器就会变花~,比如Edge
+    scale: 3, // 缩放倍率调整清晰度
+  }).then((canvas) => {
+    const pdf = new jspdf('p', 'mm', 'a4') // A4纸,纵向
+    const ctx = canvas.getContext('2d'),
+      a4ContentWidth = 190,
+      a4ContentHeight = 277, // A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
+      imgHeight = Math.floor((a4ContentHeight / a4ContentWidth) * canvas.width) // 按A4显示比例换算一页图像的像素高度
+    let renderedHeight = 0
+
+    while (renderedHeight < canvas.height) {
+      const page = document.createElement('canvas')
+      page.width = canvas.width
+      page.height = Math.min(imgHeight, canvas.height - renderedHeight) // 可能内容不足一页
+      // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
+      // @ts-ignore: Object is possibly 'null'.
+      page
+        .getContext('2d')
+        .putImageData(
+          ctx!.getImageData(
+            0,
+            renderedHeight,
+            canvas.width,
+            Math.min(imgHeight, canvas.height - renderedHeight),
+          ),
+          0,
+          0,
+        )
+      // document.body.appendChild(page)
+      pdf.addImage(
+        page.toDataURL('image/jpeg', 0.5),
+        'JPEG',
+        10,
+        10,
+        a4ContentWidth,
+        Math.min(a4ContentHeight, a4ContentWidth * (page.height / page.width)),
+      ) // 添加图像到页面,保留10mm边距
+      renderedHeight += imgHeight
+      if (renderedHeight < canvas.height) pdf.addPage() // 如果后面还有内容,添加一个空页
+    }
+
+    const fileName =
+      `${props.productInfo.product_name}-Quote-` + dayjs().format('YYYYMMDD')
+    pdf.setProperties({
+      title: fileName,
+    })
+    window.open(
+      // @ts-ignore 照着jspdf文档搬的...ts报错
+      pdf.output('bloburl', {
+        fileName,
+      }),
+    )
+
+    // // 本地备份一下
+    pdf.save(fileName + '.pdf')
+
+    // 移除用来强制换行的元素.
+    document.querySelectorAll('.empty-div').forEach((i) => i.remove())
+  })
+}
+const shouldSplit = (
+  nodes: HTMLElement[],
+  index: number,
+  pageHeight: number,
+) => {
+  // 计算当前这块dom是否跨越了a4大小,以此分割
+  // console.log(index, 'index start')
+  if (
+    nodes[index].offsetTop + nodes[index].clientHeight < pageHeight &&
+    nodes[index + 1] &&
+    nodes[index + 1].offsetTop + nodes[index + 1].clientHeight > pageHeight
+  ) {
+    return true
+  }
+  return false
+}
+
+const mainPicture = computed(() => {
+  if (
+    Array.isArray(props.productInfo.product_image) &&
+    props.productInfo.product_image.length
+  ) {
+    return props.productInfo.product_image[0]
+  }
+  return ''
+})
+const otherPicture = computed(() => {
+  if (
+    Array.isArray(props.productInfo.product_image) &&
+    props.productInfo.product_image.length > 1
+  ) {
+    return props.productInfo.product_image.slice(1)
+  }
+  return []
+})
+const computedCreator = computed(() => {
+  let result = ''
+  if (props.creatorOptions.length) {
+    const a: any[] = props.creatorOptions.filter(
+      (i: any) => i.value === props.exportForm.saleperson,
+    )
+    if (a.length) result = a[0].label
+  }
+  return result
+})
+</script>
+<style lang="scss" scoped>
+$subColor: #777;
+.pdf-wrap {
+  position: fixed;
+  top: -9999px;
+  right: -9999px;
+  box-sizing: border-box;
+}
+.pdf-preview {
+  box-sizing: border-box;
+  background-color: #fff;
+  font-size: 10pt;
+  line-height: 18pt;
+  padding: 1cm 1.2cm;
+  min-height: 29.69cm;
+  * {
+    box-sizing: border-box;
+    padding: 0;
+    margin: 0;
+  }
+}
+.logo-area {
+  width: 150pt;
+
+  img {
+    position: relative;
+    left: -10pt;
+    width: 100%;
+  }
+}
+.product-info {
+  .bold {
+    font-weight: bold;
+    margin-right: 6pt;
+  }
+}
+.quotation-text {
+  font-size: 16pt;
+  font-weight: bold;
+  color: #222;
+}
+.background {
+  height: 100%;
+  width: 100%;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: contain;
+}
+.main-picture {
+  max-width: 180pt;
+  width: 180pt;
+  height: 160pt;
+
+  img {
+    width: 100%;
+  }
+}
+.price-valid-info {
+  // margin-bottom: 12pt;
+  padding-bottom: 12pt;
+}
+.other-picture {
+  display: inline-block;
+  height: 150pt;
+  max-width: 32%;
+  width: 32%;
+  margin-right: 2%;
+  &:nth-of-type(3n) {
+    margin-right: 0;
+  }
+}
+.product-table {
+  padding: 12pt 0;
+  // margin: 12pt 0;
+  .tr {
+    border-top: 1px solid #e6e6e6;
+    &:last-of-type {
+      border-bottom: 1px solid #e6e6e6;
+    }
+    &:first-of-type {
+      background-color: #f7f8fc;
+    }
+  }
+  .th,
+  .td {
+    height: 32pt;
+    line-height: 32pt;
+    text-align: left;
+    width: 85pt;
+    min-width: 80pt;
+    &.index {
+      width: 18pt;
+      min-width: 18pt;
+      padding-left: 2pt;
+    }
+    &.name {
+      width: 120pt;
+      min-width: 120pt;
+    }
+    &.qty,
+    &.setup-cost,
+    &.sold-unit {
+      width: 70pt;
+      min-width: 70pt;
+    }
+  }
+  .td.name {
+    line-height: 14pt;
+    padding-top: 2pt;
+  }
+}
+.end-flag {
+  // margin: 20pt auto 0;
+  padding: 20pt 0 0;
+  position: relative;
+  padding: 9pt 0;
+  height: 22pt;
+  .end-line {
+    height: 2pt;
+    width: 50%;
+    background-color: #e6e6e6;
+  }
+  .end-text {
+    background-color: #fff;
+    width: 40pt;
+    position: absolute;
+    top: 0;
+    left: calc((100% - 40pt) / 2);
+    font-size: 10pt;
+    height: 22pt;
+    line-height: 22pt;
+    color: $subColor;
+  }
+}
+.notes-wrap {
+  margin-bottom: 12pt;
+  .notes {
+    white-space: pre-wrap;
+  }
+  .title {
+    font-weight: bold;
+  }
+}
+
+.pdf-wrap {
+  width: 21cm;
+  margin: 0 auto 12px;
+  box-shadow: 1px 1px 2pt 0px $subColor;
+}
+</style>

+ 633 - 0
src/pages/indent-manage/indent/components/freight.vue

@@ -0,0 +1,633 @@
+<template>
+  <div class="component-set-freight">
+    <el-dialog
+      v-model="show"
+      class="custom-edit-indent-info-dialog"
+      title="设置运费参数"
+      width="800px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :append-to-body="true"
+      :before-close="close"
+    >
+      <el-form
+        ref="freightForm"
+        label-width="140px"
+        :rules="rules"
+        :model="form"
+        :inline="true"
+      >
+        <el-tabs
+          v-model="activeTab"
+          type="card"
+        >
+          <el-tab-pane
+            label="基础参数"
+            name="1"
+          >
+            <div class="flex flex-wrap tab-panel-1">
+              <el-form-item
+                label="汇率(RMB/AUD)"
+                prop="rate_rmb_aud"
+              >
+                <el-input v-model="form.rate_rmb_aud"></el-input>
+              </el-form-item>
+              <el-form-item
+                label="国内运费单价(重货)"
+                prop="cn_price_heavy"
+              >
+                <div class="flex items-center">
+                  <el-input
+                    v-model="form.cn_price_heavy"
+                    class="flex-auto"
+                  ></el-input>
+                  <div style="width: 60px; padding-left: 8px">(RMB)</div>
+                </div>
+              </el-form-item>
+              <el-form-item
+                label="税率(%)"
+                prop="rate_tax"
+              >
+                <el-input v-model="form.rate_tax"></el-input>
+              </el-form-item>
+              <el-form-item
+                label="国内运费单价(轻货)"
+                prop="cn_price"
+              >
+                <div class="flex items-center">
+                  <el-input
+                    v-model="form.cn_price"
+                    class="flex-auto"
+                  ></el-input>
+                  <div style="width: 60px; padding-left: 8px">(RMB)</div>
+                </div>
+              </el-form-item>
+              <el-form-item
+                label="燃油附加(%)"
+                prop="rate_fuel"
+              >
+                <el-input v-model="form.rate_fuel"></el-input>
+              </el-form-item>
+              <el-form-item
+                label="空+派燃油附加(%)"
+                prop="rate_fuelplus"
+              >
+                <el-input v-model="form.rate_fuelplus"></el-input>
+              </el-form-item>
+              <el-form-item
+                label="体积浮动(%)"
+                prop="rate_bulk"
+              >
+                <el-input v-model="form.rate_bulk"></el-input>
+              </el-form-item>
+              <el-form-item
+                label="海运费(AUD)"
+                prop="sea_fee"
+              >
+                <el-input v-model="form.sea_fee"></el-input>
+              </el-form-item>
+              <el-form-item
+                label="重量浮动(%)"
+                prop="rate_weight"
+              >
+                <el-input v-model="form.rate_weight"></el-input>
+              </el-form-item>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane
+            label="航空运费单价"
+            name="2"
+          >
+            <div class="flex items-start tab-panel-2">
+              <div style="width: 120px">
+                <el-tabs
+                  v-model="currentAirTab"
+                  tab-position="left"
+                >
+                  <el-tab-pane
+                    name="dhl"
+                    label="DHL"
+                  ></el-tab-pane>
+                  <el-tab-pane
+                    name="tnt"
+                    label="TNT"
+                  ></el-tab-pane>
+                  <el-tab-pane
+                    name="fedex"
+                    label="Fedex"
+                  ></el-tab-pane>
+                </el-tabs>
+              </div>
+              <div class="flex-auto">
+                <el-table
+                  :header-cell-style="{ backgroundColor: '#F2F6FC' }"
+                  :data="computedAirForm"
+                  stripe
+                  border
+                >
+                  <el-table-column label="重量(KG)">
+                    <template #default="scope">
+                      <div class="flex items-center">
+                        <el-form-item
+                          style="width: 130px"
+                          :rules="{
+                            validator: checkInput,
+                          }"
+                          :prop="
+                            computedAirFormRuleKey + '.' + scope.$index + '.min'
+                          "
+                        >
+                          <el-input v-model="scope.row.min"></el-input>
+                        </el-form-item>
+                        <span style="margin-right: 10px">~</span>
+                        <el-form-item
+                          style="width: 130px"
+                          :rules="{
+                            validator: checkInput,
+                          }"
+                          :prop="
+                            computedAirFormRuleKey + '.' + scope.$index + '.max'
+                          "
+                        >
+                          <el-input v-model="scope.row.max"></el-input>
+                        </el-form-item>
+                      </div>
+                    </template>
+                  </el-table-column>
+                  <el-table-column
+                    label="单价(AUD/KG)"
+                    width="200"
+                  >
+                    <template #default="scope">
+                      <el-form-item
+                        :rules="{
+                          validator: checkInput,
+                        }"
+                        :prop="
+                          computedAirFormRuleKey + '.' + scope.$index + '.price'
+                        "
+                      >
+                        <el-input v-model="scope.row.price"></el-input>
+                      </el-form-item>
+                    </template>
+                  </el-table-column>
+                  <el-table-column
+                    label="操作"
+                    width="90"
+                  >
+                    <template #default="scope">
+                      <el-form-item>
+                        <el-button
+                          size="small"
+                          type="danger"
+                          @click="deleteAir(scope.$index)"
+                        >
+                          {{ $t('btn_delete') }}
+                        </el-button>
+                      </el-form-item>
+                    </template>
+                  </el-table-column>
+                </el-table>
+
+                <br />
+                <el-button
+                  size="small"
+                  @click="addAir"
+                >
+                  + 添加价格信息
+                </el-button>
+              </div>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane
+            label="空+派单价"
+            name="3"
+          >
+            <div class="tab-panel-3">
+              <el-table
+                :header-cell-style="{ backgroundColor: '#F2F6FC' }"
+                :data="form.airplus"
+                stripe
+                border
+              >
+                <el-table-column label="重量(KG)">
+                  <template #default="scope">
+                    <div class="flex items-center">
+                      <el-form-item
+                        style="width: 130px"
+                        :rules="{
+                          validator: checkInput,
+                        }"
+                        :prop="'airplus.' + scope.$index + '.min'"
+                      >
+                        <el-input v-model="scope.row.min"></el-input>
+                      </el-form-item>
+                      <span style="margin-right: 10px">~</span>
+                      <el-form-item
+                        style="width: 130px"
+                        :rules="{
+                          validator: checkInput,
+                        }"
+                        :prop="'airplus.' + scope.$index + '.max'"
+                      >
+                        <el-input v-model="scope.row.max"></el-input>
+                      </el-form-item>
+                    </div>
+                  </template>
+                </el-table-column>
+                <el-table-column
+                  label="单价(AUD/KG)"
+                  width="200"
+                >
+                  <template #default="scope">
+                    <el-form-item
+                      :rules="{
+                        validator: checkInput,
+                      }"
+                      :prop="'airplus.' + scope.$index + '.price'"
+                    >
+                      <el-input v-model="scope.row.price"></el-input>
+                    </el-form-item>
+                  </template>
+                </el-table-column>
+                <el-table-column
+                  label="操作"
+                  width="90"
+                >
+                  <template #default="scope">
+                    <el-form-item
+                      label=""
+                      prop=""
+                    >
+                      <el-button
+                        size="small"
+                        type="danger"
+                        @click="deleteAirPlus(scope.$index)"
+                      >
+                        {{ $t('btn_delete') }}
+                      </el-button>
+                    </el-form-item>
+                  </template>
+                </el-table-column>
+              </el-table>
+
+              <br />
+              <el-button
+                size="small"
+                @click="addAirPlus()"
+              >
+                + 添加价格信息
+              </el-button>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane
+            label="海运运费信息"
+            name="4"
+          >
+            <div class="tab-panel-4">
+              <el-table
+                :header-cell-style="{ backgroundColor: '#F2F6FC' }"
+                :data="form.cbm"
+                stripe
+                border
+              >
+                <el-table-column label="CBM">
+                  <template #default="scope">
+                    <div class="flex items-center">
+                      <el-form-item
+                        style="width: 130px"
+                        label=""
+                        prop=""
+                      >
+                        <el-input v-model="scope.row.min"></el-input>
+                      </el-form-item>
+                      <span style="margin-right: 10px">~</span>
+                      <el-form-item
+                        style="width: 130px"
+                        label=""
+                        prop=""
+                      >
+                        <el-input v-model="scope.row.max"></el-input>
+                      </el-form-item>
+                    </div>
+                  </template>
+                </el-table-column>
+                <el-table-column
+                  label="单价(AUD/KG)"
+                  width="200"
+                >
+                  <template #default="scope">
+                    <el-form-item
+                      label=""
+                      prop=""
+                    >
+                      <el-input v-model="scope.row.price"></el-input>
+                    </el-form-item>
+                  </template>
+                </el-table-column>
+                <el-table-column
+                  label="操作"
+                  width="90"
+                >
+                  <template #default="scope">
+                    <el-form-item
+                      label=""
+                      prop=""
+                    >
+                      <el-button
+                        size="small"
+                        type="danger"
+                        @click="deleteCBM(scope.$index)"
+                      >
+                        {{ $t('btn_delete') }}
+                      </el-button>
+                    </el-form-item>
+                  </template>
+                </el-table-column>
+              </el-table>
+
+              <br />
+              <el-button
+                size="small"
+                @click="addCBM()"
+              >
+                + 添加价格信息
+              </el-button>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </el-form>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <el-button
+            type="primary"
+            size="small"
+            @click="checkForm(freightForm)"
+          >
+            {{ $t('btn_save') }}
+          </el-button>
+          <!-- <el-button
+          @click="close"
+          type=""
+          size="small">
+          关闭
+        </el-button> -->
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  defineComponent,
+  ref,
+  defineProps,
+  defineEmits,
+  watch,
+  computed,
+} from 'vue'
+import {
+  ElTabPane,
+  ElTabs,
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElTable,
+  ElTableColumn,
+  ElDialog,
+  ElNotification,
+  ElMessage,
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import debounce from 'lodash.debounce'
+import { $t } from '@/i18n/index'
+import { getSettingDetail, saveSetting } from '@/api/indent'
+defineComponent({
+  name: 'EditInfo',
+})
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+})
+const $emit = defineEmits(['update:visible', 'save'])
+
+const activeTab = ref('1')
+// 航空运费单价的3个子tab dhl tnt fedex
+const currentAirTab = ref('dhl')
+const show = ref(false)
+
+const messageError = debounce(function (info) {
+  ElMessage.error(info)
+}, 300)
+const checkInput = (rule: any, value: any, cb: any) => {
+  if (!Number.isNaN(Number(value))) {
+    cb()
+  } else {
+    messageError('请检查输入')
+    cb(new Error('只能输入数字'))
+  }
+}
+const rules = {
+  rate_rmb_aud: [{ trigger: 'change', validator: checkInput }],
+  cn_price_heavy: [{ trigger: 'change', validator: checkInput }],
+  rate_tax: [{ trigger: 'change', validator: checkInput }],
+  cn_price: [{ trigger: 'change', validator: checkInput }],
+  rate_fuel: [{ trigger: 'change', validator: checkInput }],
+  rate_fuelplus: [{ trigger: 'change', validator: checkInput }],
+  rate_bulk: [{ trigger: 'change', validator: checkInput }],
+  sea_fee: [{ trigger: 'change', validator: checkInput }],
+}
+const form = ref({
+  // 基础参数 start
+  rate_rmb_aud: '',
+  cn_price_heavy: '',
+  rate_tax: '',
+  cn_price: '',
+  rate_fuel: '',
+  rate_fuelplus: '',
+  rate_bulk: '',
+  sea_fee: '',
+  // 基础参数 end
+  // 航空运费单价
+  dhl_airline: [],
+  tnt_airline: [],
+  fedex_airline: [],
+  // 空+派单价
+  airplus: [],
+  // 海运运费信息
+  cbm: [],
+}) as any
+
+const getDetail = () => {
+  getSettingDetail().then((response: any) => {
+    if (response.code === 1) {
+      form.value = Object.assign({}, response.result)
+
+      form.value.dhl_airline = JSON.parse(form.value.dhl_airline)
+      if (!Array.isArray(form.value.dhl_airline)) {
+        form.value.dhl_airline = []
+      }
+
+      form.value.tnt_airline = JSON.parse(form.value.tnt_airline)
+      if (!Array.isArray(form.value.tnt_airline)) {
+        form.value.tnt_airline = []
+      }
+
+      form.value.fedex_airline = JSON.parse(form.value.fedex_airline)
+      if (!Array.isArray(form.value.fedex_airline)) {
+        form.value.fedex_airline = []
+      }
+
+      form.value.airplus = JSON.parse(form.value.airplus)
+      if (!Array.isArray(form.value.airplus)) {
+        form.value.airplus = []
+      }
+
+      form.value.cbm = JSON.parse(form.value.cbm)
+      if (!Array.isArray(form.value.cbm)) {
+        form.value.cbm = []
+      }
+      delete form.value.admin_id
+      delete form.value.id
+      delete form.value.is_del
+      delete form.value.update_time
+    }
+  })
+}
+
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    if (show.value) getDetail()
+  },
+)
+
+const computedAirFormRuleKey = computed(() => {
+  let result = ''
+  switch (currentAirTab.value) {
+    case 'dhl':
+      result = 'dhl_airline'
+      break
+    case 'tnt':
+      result = 'tnt_airline'
+      break
+    case 'fedex':
+      result = 'fedex_airline'
+      break
+  }
+  return result
+})
+const computedAirForm = computed(() => {
+  let result = []
+  switch (currentAirTab.value) {
+    case 'dhl':
+      result = form.value.dhl_airline
+      break
+    case 'tnt':
+      result = form.value.tnt_airline
+      break
+    case 'fedex':
+      result = form.value.fedex_airline
+      break
+  }
+  return result
+})
+
+const deleteCBM = (index: number) => {
+  form.value.cbm.splice(index, 1)
+}
+const addCBM = () => {
+  form.value.cbm.push({
+    min: '',
+    max: '',
+    price: '',
+  })
+}
+const deleteAirPlus = (index: number) => {
+  form.value.airplus.splice(index, 1)
+}
+const addAirPlus = () => {
+  form.value.airplus.push({
+    min: '',
+    max: '',
+    price: '',
+  })
+}
+const deleteAir = (index: number) => {
+  switch (currentAirTab.value) {
+    case 'dhl':
+      form.value.dhl_airline.splice(index, 1)
+      break
+    case 'tnt':
+      form.value.tnt_airline.splice(index, 1)
+      break
+    case 'fedex':
+      form.value.fedex_airline.splice(index, 1)
+      break
+  }
+}
+const addAir = () => {
+  computedAirForm.value.push({
+    min: '',
+    max: '',
+    price: '',
+  })
+}
+const freightForm = ref<FormInstance>()
+const checkForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate((valid: boolean) => {
+    // console.log(valid, 'valid')
+    if (!valid) return
+    saveSetting(form.value).then((response: any) => {
+      if (response.code === 1) {
+        ElNotification({
+          title: '保存成功',
+          message: '保存成功',
+          duration: 3000,
+        })
+        $emit('save')
+      }
+    })
+  })
+}
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>
+<style lang="scss">
+.component-set-freight {
+  .el-dialog__body {
+    min-height: 60vh;
+    max-height: 72vh;
+    overflow-y: scroll;
+    overflow-x: auto;
+    padding-top: 8px;
+  }
+}
+</style>
+<style lang="scss" scoped>
+.tab-panel-1 {
+  .el-form-item {
+    width: 42%;
+    &:nth-of-type(2n) {
+      width: 48%;
+    }
+  }
+}
+.tab-panel-2,
+.tab-panel-3,
+.tab-panel-4 {
+  width: 100%;
+  position: relative;
+  .el-form-item {
+    margin-bottom: 0;
+  }
+}
+</style>

+ 1657 - 0
src/pages/indent-manage/indent/components/info.vue

@@ -0,0 +1,1657 @@
+<template>
+  <div class="component-edit-indent-info">
+    <el-dialog
+      v-model="show"
+      class="custom-edit-indent-info-dialog"
+      :title="visible === 1 ? '录入报价信息' : '录入报价信息'"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="1300px"
+    >
+      <div style="color: #ef4135">* 为了报价单的内容,请填写英文信息</div>
+
+      <div class="form-table">
+        <div class="flex items-stretch form-header">
+          <div class="first-label"></div>
+          <div class="second-label">操作</div>
+          <div
+            v-for="(item, index) in forms"
+            :key="index"
+            class="input-area flex justify-center items-center"
+          >
+            <el-button
+              v-show="forms.length > 1"
+              size="small"
+              type="danger"
+              link
+              plain
+              @click="deleteForm(index)"
+            >
+              {{ $t('btn_delete') }}
+            </el-button>
+          </div>
+        </div>
+        <div class="flex items-stretch">
+          <div class="first-label flex justify-center items-center">
+            {{ $t(prefix + 'label_supplier') }}
+          </div>
+          <div class="flex flex-col items-stretch">
+            <div class="flex items-center">
+              <div class="second-label required">
+                {{ $t(prefix + 'label_company_name') }}
+              </div>
+              <el-form
+                v-for="(item, index) in forms as any[]"
+                :ref="($e) => getFormRef($e, `vendor_id_${index}`)"
+                :key="index"
+                :show-message="false"
+                :model="item"
+              >
+                <div class="input-area">
+                  <el-form-item
+                    prop="vendor_id"
+                    :rules="{ required: true, validator: commonCheck }"
+                  >
+                    <el-select
+                      v-model="item.vendor_id"
+                      :remote-method="($e: any) => queryVenderList($e, index)"
+                      filterable
+                      allow-create
+                      remote
+                      style="width: 100%"
+                      placeholder="请选择供应商"
+                      @change="($e) => changeVenderSelect($e, index)"
+                    >
+                      <el-option
+                        v-for="option in vendorList[index]"
+                        :key="option.id"
+                        :value="option.id"
+                        :label="option.name"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                  <div
+                    v-if="index < forms.length - 1"
+                    class="btn-copy"
+                    @click.self="commonCopyFormItem(index, 'vendor_id')"
+                  >
+                    &gt;&gt;&gt;
+                  </div>
+                </div>
+              </el-form>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_supplier_type') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.vendor_type"
+                  placeholder="请输入供应商类型"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'vendor_type')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_wangwang_year') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.vendor_wangwang_old"
+                  placeholder="请输入旺旺年份"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'vendor_wangwang_old')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_contact') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.vendor_contact"
+                  placeholder="请输入联系人"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'vendor_contact')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_phone') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.vendor_phone"
+                  placeholder="请输入电话"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'vendor_phone')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="flex items-stretch">
+          <div class="first-label flex justify-center items-center">
+            {{ $t(prefix + 'label_product_info') }}
+          </div>
+
+          <div class="flex flex-col items-stretch">
+            <div class="flex items-stretch">
+              <div class="second-label required">
+                {{ $t(prefix + 'label_product_name') }}
+              </div>
+              <el-form
+                v-for="(item, index) in forms"
+                :ref="($e) => getFormRef($e, `product_name_${index}`)"
+                :key="index"
+                :show-message="false"
+                :model="item"
+              >
+                <div class="input-area">
+                  <el-form-item
+                    prop="product_name"
+                    :rules="{
+                      required: true,
+                      trigger: 'change',
+                      validator: commonCheck,
+                    }"
+                  >
+                    <el-input
+                      v-model="item.product_name"
+                      placeholder="请输入产品名称"
+                    ></el-input>
+                  </el-form-item>
+                  <div
+                    v-if="index < forms.length - 1"
+                    class="btn-copy"
+                    @click.self="commonCopyFormItem(index, 'product_name')"
+                  >
+                    &gt;&gt;&gt;
+                  </div>
+                </div>
+              </el-form>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_product_url') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_url"
+                  placeholder="请输入产品链接"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_url')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_product_image') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <image-upload
+                  v-model:list="productImageList[index]"
+                  width="100px"
+                  height="100px"
+                  :disable-preview="true"
+                ></image-upload>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'productImageList')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_size') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_size"
+                  placeholder="请输入尺寸"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_size')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_product_hd') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_hd"
+                  placeholder="请输入厚度"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_hd')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_capacity') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_capacity"
+                  placeholder="请输入容量"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_capacity')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_material') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_material"
+                  placeholder="请输入材质"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_material')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_battery') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_battery"
+                  placeholder="是否带电池"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_battery')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_print_require') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_require_print"
+                  placeholder="请输入要求"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="
+                    commonCopyFormItem(index, 'product_require_print')
+                  "
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_color') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_color"
+                  placeholder="请输入颜色"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_color')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_color_require') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_require_color"
+                  placeholder="请输入颜色定制要求"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="
+                    commonCopyFormItem(index, 'product_require_color')
+                  "
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_other') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.product_other"
+                  placeholder="其他信息"
+                  type="textarea"
+                  :rows="3"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'product_other')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="flex items-stretch">
+          <div class="first-label flex justify-center items-center">
+            {{ $t(prefix + 'label_factory_price') }}
+          </div>
+          <div class="flex flex-col items-stretch">
+            <div class="flex items-stretch">
+              <div class="second-label required">
+                {{ $t(prefix + 'label_number_table') }}
+              </div>
+              <el-form
+                v-for="(item, index) in forms"
+                :ref="($e) => getFormRef($e, `number_${index}`)"
+                :key="index"
+                :show-message="false"
+                :model="item"
+              >
+                <div class="input-area">
+                  <div class="sub-table">
+                    <div class="sub-table-head flex items-center">
+                      <div class="sub-table-number">数量</div>
+                      <div class="sub-table-price">单价(RMB)</div>
+                      <div class="sub-table-days">天数</div>
+                    </div>
+
+                    <div
+                      v-for="(i, subIndex) in item.number"
+                      :key="subIndex"
+                      class="sub-table-row flex items-center"
+                    >
+                      <div class="sub-table-number">
+                        <el-form-item
+                          :prop="'number.' + subIndex"
+                          :rules="{
+                            required: true,
+                            validator: commonCheck,
+                          }"
+                        >
+                          <el-input v-model="item.number[subIndex]"></el-input>
+                        </el-form-item>
+                      </div>
+
+                      <div class="sub-table-price">
+                        <el-form-item
+                          :prop="'price.' + subIndex"
+                          :rules="{
+                            required: true,
+                            validator: commonCheck,
+                          }"
+                        >
+                          <el-input v-model="item.price[subIndex]"></el-input>
+                        </el-form-item>
+                      </div>
+
+                      <div class="sub-table-days">
+                        <el-form-item
+                          :prop="'days.' + subIndex"
+                          :rules="{
+                            required: true,
+                            validator: commonCheck,
+                          }"
+                        >
+                          <el-input v-model="item.days[subIndex]"></el-input>
+                        </el-form-item>
+                      </div>
+                      <div class="sub-table-btn-delete">
+                        <div
+                          v-show="item.number.length > 1"
+                          class="el-icon-error"
+                          @click="numberTableDeleteRow(index, subIndex)"
+                        ></div>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div class="sub-table-btn-add">
+                    <el-button
+                      size="small"
+                      @click="numberTableAddRow(index)"
+                    >
+                      + 添加价格信息
+                    </el-button>
+                  </div>
+                  <div
+                    v-if="index < forms.length - 1"
+                    class="btn-copy"
+                    style="bottom: 4px; top: unset"
+                    @click.self="commonCopyFormItem(index, 'numberItem')"
+                  >
+                    &gt;&gt;&gt;
+                  </div>
+                </div>
+              </el-form>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label required">
+                {{ $t(prefix + 'label_extra_fee') }}
+              </div>
+
+              <el-form
+                v-for="(item, index) in forms"
+                :ref="($e) => getFormRef($e, `cost_${index}`)"
+                :key="index"
+                :show-message="false"
+                :model="item"
+              >
+                <div class="input-area">
+                  <div class="sub-table">
+                    <div class="sub-table-head flex items-center">
+                      <div class="sub-table-cost-name">项目</div>
+                      <div class="sub-table-cost-price">价格(RMB)</div>
+                    </div>
+                    <div
+                      v-for="(i, subIndex) in item.cost_name"
+                      :key="subIndex"
+                      class="sub-table-row flex items-center"
+                    >
+                      <div class="sub-table-cost-name">
+                        <el-form-item
+                          :prop="'cost_name.' + subIndex"
+                          :rules="{
+                            required: true,
+                            validator: commonCheck,
+                          }"
+                        >
+                          <el-select
+                            v-model="item.cost_name[subIndex]"
+                            size="small"
+                          >
+                            <el-option
+                              v-for="option in costOptions"
+                              :key="option.label"
+                              :label="option.label"
+                              :value="option.value"
+                            ></el-option>
+                          </el-select>
+                        </el-form-item>
+                      </div>
+
+                      <div class="sub-table-cost-price">
+                        <el-form-item
+                          :prop="'cost_price.' + subIndex"
+                          :rules="{
+                            required: true,
+                            validator: commonCheck,
+                          }"
+                        >
+                          <el-input
+                            v-model="item.cost_price[subIndex]"
+                          ></el-input>
+                        </el-form-item>
+                      </div>
+
+                      <div class="sub-table-btn-delete">
+                        <el-button
+                          :disabled="item.cost_name.length < 2"
+                          type="danger"
+                          size="small"
+                          plain
+                          @click="costTableDeleteRow(index, subIndex)"
+                        >
+                          {{ $t('btn_delete') }}
+                        </el-button>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div class="sub-table-btn-add">
+                    <el-button
+                      size="small"
+                      @click="costTableAddRow(index)"
+                    >
+                      + 添加价格信息
+                    </el-button>
+                  </div>
+                  <div
+                    v-if="index < forms.length - 1"
+                    class="btn-copy"
+                    style="bottom: 4px; top: unset"
+                    @click.self="commonCopyFormItem(index, 'cost')"
+                  >
+                    &gt;&gt;&gt;
+                  </div>
+                </div>
+              </el-form>
+            </div>
+          </div>
+        </div>
+
+        <div class="flex items-stretch">
+          <div class="first-label flex justify-center items-center">
+            {{ $t(prefix + 'label_package_info') }}
+          </div>
+
+          <div class="flex flex-col items-stretch">
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_package') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.package_info"
+                  placeholder="请输入产品包装信息"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'package_info')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+
+            <div class="flex flex-col items-stretch">
+              <div class="flex items-stretch">
+                <div class="second-label required">
+                  {{ $t(prefix + 'label_gross_weight') }}(KG)
+                </div>
+
+                <el-form
+                  v-for="(item, index) in forms"
+                  :ref="($e) => getFormRef($e, `package_weight_${index}`)"
+                  :key="index"
+                  :show-message="false"
+                  :model="item"
+                >
+                  <div class="input-area">
+                    <el-form-item
+                      prop="package_weight"
+                      :rules="{ required: true, validator: commonCheck }"
+                    >
+                      <el-input
+                        v-model="item.package_weight"
+                        placeholder="请输入每箱毛重"
+                      ></el-input>
+                    </el-form-item>
+                    <div
+                      v-if="index < forms.length - 1"
+                      class="btn-copy"
+                      @click.self="commonCopyFormItem(index, 'package_weight')"
+                    >
+                      &gt;&gt;&gt;
+                    </div>
+                  </div>
+                </el-form>
+              </div>
+
+              <div class="flex items-stretch">
+                <div class="second-label required">
+                  {{ $t(prefix + 'label_package_size') }}(CM)
+                </div>
+                <el-form
+                  v-for="(item, index) in forms"
+                  :ref="($e) => getFormRef($e, `package_size_${index}`)"
+                  :key="index"
+                  :show-message="false"
+                  :model="item"
+                >
+                  <div class="input-area flex justify-center items-center">
+                    <el-form-item
+                      prop="package_size_length"
+                      :rules="{
+                        required: true,
+                        trigger: 'change',
+                        validator: commonCheck,
+                      }"
+                    >
+                      <el-input
+                        v-model="item.package_size_length"
+                        style="width: 74px"
+                        placeholder="长"
+                      ></el-input>
+                    </el-form-item>
+                    <span class="el-icon-close"></span>
+                    <el-form-item
+                      prop="package_size_width"
+                      :rules="{
+                        required: true,
+                        trigger: 'change',
+                        validator: commonCheck,
+                      }"
+                    >
+                      <el-input
+                        v-model="item.package_size_width"
+                        style="width: 74px"
+                        placeholder="宽"
+                      ></el-input>
+                    </el-form-item>
+                    <span class="el-icon-close"></span>
+                    <el-form-item
+                      prop="package_size_height"
+                      :rules="{
+                        required: true,
+                        trigger: 'change',
+                        validator: commonCheck,
+                      }"
+                    >
+                      <el-input
+                        v-model="item.package_size_height"
+                        style="width: 74px"
+                        placeholder="高"
+                      ></el-input>
+                    </el-form-item>
+                    <span>&nbsp;CM</span>
+                    <div
+                      v-if="index < forms.length - 1"
+                      class="btn-copy"
+                      @click.self="commonCopyFormItem(index, 'package_size')"
+                    >
+                      &gt;&gt;&gt;
+                    </div>
+                  </div>
+                </el-form>
+              </div>
+
+              <div class="flex items-stretch">
+                <div class="second-label required">
+                  {{ $t(prefix + 'label_pcs') }}(PCS)
+                </div>
+
+                <el-form
+                  v-for="(item, index) in forms"
+                  :ref="($e) => getFormRef($e, `in_package_${index}`)"
+                  :key="index"
+                  :show-message="false"
+                  :model="item"
+                >
+                  <div class="input-area">
+                    <el-form-item
+                      prop="in_package"
+                      :rules="{
+                        required: true,
+                        trigger: 'change',
+                        validator: commonCheck,
+                      }"
+                    >
+                      <el-input
+                        v-model="item.in_package"
+                        placeholder="PCS"
+                      ></el-input>
+                    </el-form-item>
+                    <div
+                      v-if="index < forms.length - 1"
+                      class="btn-copy"
+                      @click.self="commonCopyFormItem(index, 'in_package')"
+                    >
+                      &gt;&gt;&gt;
+                    </div>
+                  </div>
+                </el-form>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="flex items-stretch">
+          <div class="first-label flex justify-center items-center">
+            {{ $t(prefix + 'label_example') }}
+          </div>
+          <div class="flex flex-col items-stretch">
+            <div class="flex items-stretch">
+              <div class="second-label required">
+                {{ $t(prefix + 'label_example_days') }}(Days)
+              </div>
+
+              <el-form
+                v-for="(item, index) in forms"
+                :ref="($e) => getFormRef($e, `demo_days_${index}`)"
+                :key="index"
+                :show-message="false"
+                :model="item"
+              >
+                <div class="input-area">
+                  <el-form-item
+                    prop="demo_days"
+                    :rules="{
+                      required: true,
+                      trigger: 'change',
+                      validator: commonCheck,
+                    }"
+                  >
+                    <el-input
+                      v-model="item.demo_days"
+                      placeholder="天数"
+                    ></el-input>
+                  </el-form-item>
+                  <div
+                    v-if="index < forms.length - 1"
+                    class="btn-copy"
+                    @click.self="commonCopyFormItem(index, 'demo_days')"
+                  >
+                    &gt;&gt;&gt;
+                  </div>
+                </div>
+              </el-form>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_example_cost') }}(¥)
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.demo_cost"
+                  placeholder="请输入打样费用"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'demo_cost')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_can_refund') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.demo_return"
+                  placeholder="请输入退费信息"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'demo_return')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="flex items-stretch">
+          <div class="first-label flex justify-center items-center"></div>
+          <div class="flex flex-col items-stretch">
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_cert') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.cert"
+                  placeholder="证书"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'cert')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+
+            <div class="flex items-stretch">
+              <div class="second-label">
+                {{ $t(prefix + 'label_comment') }}
+              </div>
+              <div
+                v-for="(item, index) in forms"
+                :key="index"
+                class="input-area"
+              >
+                <el-input
+                  v-model="item.notes"
+                  placeholder="请输入内容"
+                  type="textarea"
+                  :rows="3"
+                ></el-input>
+                <div
+                  v-if="index < forms.length - 1"
+                  class="btn-copy"
+                  @click.self="commonCopyFormItem(index, 'notes')"
+                >
+                  &gt;&gt;&gt;
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div
+        v-show="canAddForm"
+        class="btn-add-indent-info flex justify-center flex-col items-center"
+        @click="addFormColumn"
+      >
+        <el-icon
+          size="42"
+          class="el-icon-circle-plus-outline"
+        >
+          <CirclePlus></CirclePlus>
+        </el-icon>
+        <div>
+          {{ $t(prefix + 'btn_add_info') }}
+        </div>
+      </div>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <!-- v-show="$showAuthBtn(429)" -->
+          <el-button
+            type="primary"
+            size="small"
+            @click="checkForm"
+          >
+            {{ $t('btn_save') }}
+          </el-button>
+          <el-button
+            type=""
+            size="small"
+            @click="close"
+          >
+            {{ $t('btn_close') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  defineComponent,
+  ref,
+  defineProps,
+  defineEmits,
+  watch,
+  computed,
+  nextTick,
+} from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElSelect,
+  ElOption,
+  ElDialog,
+  ElNotification,
+  ElMessage,
+  ElIcon,
+} from 'element-plus'
+import { CirclePlus } from '@element-plus/icons-vue'
+import debounce from 'lodash.debounce'
+import cloneDeep from 'lodash.clonedeep'
+import { $t } from '@/i18n/index'
+import imageUpload from '@/components/ImageUpload.vue'
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+import { getVendorList, createQuote } from '@/api/indent'
+
+defineComponent({
+  name: 'EditInfo',
+})
+const $emit = defineEmits(['update:visible', 'create'])
+const props = defineProps({
+  visible: {
+    type: Number,
+    default: 0,
+  },
+  alreadyHasIndentCount: {
+    type: Number,
+    default: 0,
+  },
+  parentId: {
+    type: [String, Number],
+    default: () => {
+      return 0
+    },
+  },
+  dataForEdit: {
+    type: Object,
+    default: () => {
+      return {}
+    },
+  },
+})
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+const prefix = 'order.indent_edit_info.'
+const show = ref(false)
+const costOptions = [
+  {
+    label: $t('text_please_select'),
+    value: '',
+  },
+  {
+    label: '样品费用',
+    value: '样品费用',
+  },
+  {
+    label: '印刷费用',
+    value: '印刷费用',
+  },
+  {
+    label: '模具费用',
+    value: '模具费用',
+  },
+  {
+    label: '制版费用',
+    value: '制版费用',
+  },
+  {
+    label: '染色费用',
+    value: '染色费用',
+  },
+  {
+    label: '其他费用',
+    value: '其他费用',
+  },
+]
+const formDemo = {
+  // 供应商
+  vendor_name: '',
+  vendor_id: '',
+  vendor_type: '',
+  vendor_wangwang_old: '',
+  vendor_contact: '',
+  vendor_phone: '',
+
+  // 产品信息
+  product_name: '',
+  product_url: '',
+  product_image: '',
+  product_size: '',
+  product_hd: '',
+  product_capacity: '',
+  product_material: '',
+  product_battery: '',
+  product_require_print: '',
+  product_color: '',
+  product_require_color: '',
+  product_other: '',
+
+  // 出厂价
+  number: [''],
+  price: [''],
+  days: [''],
+  cost_name: [''],
+  cost_price: [''],
+
+  // 包装信息
+  package_info: '',
+  package_size_length: '',
+  package_size_width: '',
+  package_size_height: '',
+  package_weight: '',
+  in_package: '', // 一箱个数
+
+  // 打样情况
+  demo_days: '',
+  demo_cost: '',
+  demo_return: '',
+
+  cert: '',
+  notes: '',
+}
+const forms = ref([] as any[])
+const canAddForm = computed(() => {
+  return 100 - props.alreadyHasIndentCount - forms.value.length > 0
+})
+const productImageList = ref([] as any[][])
+const vendorList = ref([] as any[][])
+const manualVendor = ref({} as any)
+
+const commonCopyFormItem = function (index: number, key: string) {
+  // console.log(index, key, 'index key')
+  if (['vendor_id', 'vendor_name', 'vendor_phone'].includes(key)) {
+    vendorList.value.splice(index + 1, 1, cloneDeep(vendorList.value[index]))
+    forms.value[index + 1].vendor_name = forms.value[index].vendor_name
+    forms.value[index + 1].vendor_phone = forms.value[index].vendor_phone
+    forms.value[index + 1].vendor_id = forms.value[index].vendor_id
+  } else if (key === 'productImageList') {
+    productImageList.value.splice(
+      index + 1,
+      1,
+      cloneDeep(productImageList.value[index]),
+    )
+  } else if (key === 'cost') {
+    // 额外费用
+    forms.value[index + 1].cost_name = cloneDeep(forms.value[index].cost_name)
+    forms.value[index + 1].cost_price = cloneDeep(forms.value[index].cost_price)
+  } else if (key === 'numberItem') {
+    // 数量 单价
+    forms.value[index + 1].number = cloneDeep(forms.value[index].number)
+    forms.value[index + 1].price = cloneDeep(forms.value[index].price)
+    forms.value[index + 1].days = cloneDeep(forms.value[index].days)
+  } else if (key === 'package_size') {
+    // 外箱规格
+    forms.value[index + 1].package_size_length = cloneDeep(
+      forms.value[index].package_size_length,
+    )
+    forms.value[index + 1].package_size_width = cloneDeep(
+      forms.value[index].package_size_width,
+    )
+    forms.value[index + 1].package_size_height = cloneDeep(
+      forms.value[index].package_size_height,
+    )
+  } else {
+    forms.value[index + 1][key] = forms.value[index][key]
+  }
+}
+const costTableDeleteRow = function (index: number, subIndex: number) {
+  forms.value[index].cost_name.splice(subIndex, 1)
+  forms.value[index].cost_price.splice(subIndex, 1)
+}
+const costTableAddRow = function (index: number) {
+  forms.value[index].cost_name.push('')
+  forms.value[index].cost_price.push('')
+}
+const numberTableDeleteRow = function (index: number, subIndex: number) {
+  forms.value[index].number.splice(subIndex, 1)
+  forms.value[index].price.splice(subIndex, 1)
+  forms.value[index].days.splice(subIndex, 1)
+}
+const numberTableAddRow = function (index: number) {
+  forms.value[index].number.push('')
+  forms.value[index].price.push('')
+  forms.value[index].days.push('')
+}
+// 仅编辑报价信息时需要初始化
+const initData = function () {
+  // 查询供应商候选列表, 不然下拉框无数据匹配, 下拉框界面上会显示成供应商的id
+  queryVenderList(props.dataForEdit.vendor_name, 0)
+
+  const temp = cloneDeep(props.dataForEdit)
+
+  productImageList.value.push([])
+  if (Array.isArray(temp.product_image) && temp.product_image.length) {
+    nextTick(() => {
+      productImageList.value.splice(
+        0,
+        1,
+        temp.product_image.map((img: string) => {
+          return {
+            url: $mediaRegExp.test(img)
+              ? img
+              : import.meta.env.VITE_APP_OSS_PREFIX + img,
+          }
+        }),
+      )
+    })
+  }
+
+  if (temp.cost_list && temp.cost_list.length > 2) {
+    const t = JSON.parse(temp.cost_list)
+    temp.cost_name = []
+    temp.cost_price = []
+
+    t.forEach((item: any) => {
+      temp.cost_name.push(item.cost_name)
+      temp.cost_price.push(item.cost_price)
+    })
+  } else {
+    // 如果cost_list字符串长度小于2, 说明该数组为空, 删除temp的对应字段, 避免覆盖掉formDemo的克隆值.
+    delete temp.cost_name
+    delete temp.cost_price
+  }
+
+  if (temp.price_list && temp.price_list.length > 2) {
+    const t = JSON.parse(temp.price_list)
+    temp.number = []
+    temp.price = []
+    temp.days = []
+
+    t.forEach((item: any) => {
+      temp.number.push(item.number)
+      temp.price.push(item.price)
+      temp.days.push(item.days)
+    })
+  } else {
+    // 如果 price_list 字符串长度小于2, 说明该数组为空, 删除temp的对应字段, 避免覆盖掉formDemo的克隆值.
+    delete temp.cost_name
+    delete temp.cost_price
+  }
+
+  temp.entity_id = temp.id
+
+  // 删除无用字段避免接口报错
+  delete temp.id
+  // delete temp.parent_id
+  delete temp.create_time
+  delete temp.update_time
+  delete temp.creator
+  delete temp.admin_id
+
+  forms.value.push(Object.assign({}, cloneDeep(formDemo), temp))
+}
+const deleteForm = function (index: number) {
+  forms.value.splice(index, 1)
+  productImageList.value.splice(index, 1)
+  vendorList.value.splice(index, 1)
+}
+const addFormColumn = function () {
+  if (canAddForm.value) {
+    forms.value.push(
+      Object.assign(cloneDeep(formDemo), { parent_id: props.parentId }),
+    )
+    productImageList.value.push([])
+    vendorList.value.push([])
+  }
+}
+watch(
+  () => props.visible,
+  () => {
+    forms.value = []
+    productImageList.value = []
+    vendorList.value = []
+
+    if (props.visible > 1) {
+      initData()
+    } else if (props.visible > 0) {
+      addFormColumn()
+    }
+    show.value = props.visible > 0
+  },
+)
+const queryVenderList = function (keywords: string, index: number) {
+  getVendorList({ keywords }).then((response: any) => {
+    if (Array.isArray(response.result)) {
+      if (manualVendor.value.id) {
+        vendorList.value.splice(
+          index,
+          1,
+          [manualVendor.value].concat(response.result || []),
+        )
+      } else {
+        vendorList.value.splice(index, 1, response.result || [])
+      }
+    }
+  })
+}
+const changeVenderSelect = function (value: string | number, index: number) {
+  if (value) {
+    const temp = vendorList.value[index].filter((i) => i.id === value)
+    if (temp.length) {
+      forms.value[index].vendor_name = temp[0].name
+      forms.value[index].vendor_phone = temp[0].phone || temp[0].Phone || ''
+      manualVendor.value = {
+        name: '',
+        id: '',
+      }
+    } else {
+      forms.value[index].vendor_name = value || ''
+      forms.value[index].vendor_phone = ''
+      forms.value[index].vendor_type = ''
+      manualVendor.value = {
+        name: value || '',
+        id: value || '',
+      }
+    }
+  }
+}
+const createQuoteFunc = function () {
+  const params = {
+    file: '', // 疑似永远为空
+  } as any
+  if (props.parentId) {
+    params.parent_id = props.parentId
+  }
+
+  let temp = cloneDeep(forms.value)
+  temp = temp.map((item, index) => {
+    const result = { ...item }
+    result.price_list = []
+    result.number.forEach((v: any, i: number) => {
+      result.price_list.push({
+        number: `${result.number[i]}`,
+        price: `${result.price[i]}`,
+        days: `${result.days[i]}`,
+      })
+    })
+
+    result.cost_list = []
+    result.cost_name.forEach((v: any, i: number) => {
+      result.cost_list.push({
+        cost_name: `${result.cost_name[i]}`,
+        cost_price: `${result.cost_price[i]}`,
+      })
+    })
+    result.product_image = productImageList.value[index]
+      .map((i) => {
+        return i.url.replace($mediaRegExp, '/')
+      })
+      .join(',')
+    return result
+  })
+
+  //  组装接口数据
+  Object.assign(params, { lists: temp })
+
+  createQuote(params).then((response: any) => {
+    if (response.code !== 1) return
+
+    ElNotification({
+      title: '成功',
+      message: '提交成功',
+      type: 'success',
+      duration: 3000,
+    })
+
+    $emit('create', response.result)
+    close()
+  })
+}
+// 用来动态绑定ref, 便于调用表单验证
+const formRef = ref({} as any)
+const getFormRef = (el: any, key: string) => {
+  formRef.value[`${key}`] = el
+}
+const checkForm = function () {
+  const key = [
+    'vendor_id',
+    'product_name',
+    'number',
+    'cost',
+    'package_weight',
+    'package_size',
+    'in_package',
+    'demo_days',
+  ]
+  let length = forms.value.length
+  const target = [] as any[]
+  do {
+    key.forEach((i) => {
+      target.push(`${i}_${length - 1}`)
+    })
+    length--
+  } while (length >= 1)
+
+  // 表单校验结果
+  let result = true
+  target.forEach((i: string) => {
+    const r: any = formRef.value[`${i}`]
+    if (typeof r.validate === 'function') {
+      r.clearValidate()
+      r.validate((valid: boolean) => {
+        if (!valid) {
+          messageError('请检查表单必填项')
+          result = false
+          return
+        }
+      })
+    } else {
+      result = false
+    }
+  })
+  if (result) createQuoteFunc()
+}
+const messageError = debounce(function (info) {
+  ElMessage.error(info)
+}, 100)
+const commonCheck = (rule: any, value: any, cb: any) => {
+  if (typeof value === 'number') {
+    if (value >= 0) {
+      cb()
+    } else {
+      cb(new Error('数字校验出错'))
+    }
+    return
+  }
+
+  if (value && value.trim().length) {
+    cb()
+  } else {
+    cb(new Error('字符串校验出错'))
+  }
+}
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>
+<style lang="scss">
+.component-edit-indent-info {
+  .custom-edit-indent-info-dialog {
+    margin-top: 0 !important;
+    margin-bottom: 0 !important;
+    height: 100vh;
+    .el-dialog__body {
+      max-height: 87vh;
+      overflow-y: scroll;
+      overflow-x: auto;
+      padding-top: 8px;
+    }
+    .el-dialog__header {
+      border-bottom: 1px solid #dcdfe6;
+    }
+    .el-dialog__footer {
+      padding-bottom: 10px;
+    }
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+div {
+  box-sizing: border-box;
+}
+.form-table {
+  margin: 24px 0;
+  & > div {
+    &::after {
+      // 占位符. 让表格右侧在横向滚动的时候能再滚动20px
+      content: '.';
+      width: 20px;
+      padding-left: 20px;
+      height: 1px;
+      background-color: transparent;
+      color: transparent;
+    }
+  }
+}
+.first-label {
+  text-align: center;
+  width: 120px;
+  min-width: 120px;
+  border: 1px solid #dcdfe6;
+  border-top: none;
+  border-right: none;
+}
+.second-label {
+  width: 170px;
+  min-width: 170px;
+  line-height: 55px;
+  min-height: 55px;
+  text-align: left;
+  padding: 0 10px;
+  border: 1px solid #dcdfe6;
+  border-top: none;
+
+  &.required {
+    color: #ef4135;
+    &::before {
+      content: '*';
+      display: inline-block;
+      color: #ef4135;
+      font-size: 14px;
+      width: 8px;
+      height: 8px;
+    }
+  }
+}
+.input-area {
+  position: relative;
+  border: 1px solid #dcdfe6;
+  border-left: none;
+  border-top: none;
+  min-height: 55px;
+  height: 100%;
+  padding: 7px 8px;
+  width: 300px;
+  min-width: 300px;
+  &:hover .btn-copy {
+    display: block;
+  }
+  .btn-copy {
+    display: none;
+    cursor: pointer;
+    background-color: #fff;
+    line-height: 26px;
+    padding: 0 4px;
+    position: absolute;
+    z-index: 2;
+    top: calc((100% - 26px) / 2);
+    right: -16px;
+  }
+  .el-form-item {
+    margin-bottom: 0;
+  }
+
+  .sub-table {
+    border: 1px solid #dcdfe6;
+    border-left: none;
+  }
+  .sub-table-head {
+    text-align: center;
+    line-height: 24px;
+    height: 24px;
+    background-color: #efefef;
+  }
+  .sub-table-row {
+    border-top: 1px solid #dcdfe6;
+  }
+  .sub-table-head,
+  .sub-table-row {
+    position: relative;
+    & > div {
+      border-left: 1px solid #dcdfe6;
+      padding: 2px 4px;
+    }
+    &:hover .sub-table-btn-delete {
+      border-left: 0;
+      padding: 0;
+      display: block;
+    }
+  }
+
+  .sub-table-price {
+    width: 120px;
+  }
+  .sub-table-days {
+    width: 90px;
+  }
+  .sub-table-number {
+    width: 90px;
+  }
+  .sub-table-cost-name {
+    width: 140px;
+  }
+  .sub-table-cost-price {
+    width: 140px;
+  }
+  .sub-table-btn-delete {
+    display: none;
+    line-height: 1;
+    font-size: 20px;
+    color: #ef4135;
+    position: absolute;
+    right: -6px;
+    top: 0;
+    z-index: 2;
+  }
+  .sub-table-btn-add {
+    margin: 12px 0 8px;
+  }
+}
+.form-header {
+  .first-label,
+  .input-area,
+  .second-label {
+    border-top: 1px solid #dcdfe6;
+    min-height: auto;
+    height: 40px;
+    line-height: 40px;
+  }
+  .el-button {
+    color: #ef4135;
+  }
+}
+.btn-add-indent-info {
+  background-color: #fff;
+  z-index: 2;
+  width: 74px;
+  height: 84px;
+  border: 1px solid #009688;
+  position: absolute;
+  right: 40px;
+  bottom: 40px;
+  color: #666;
+  font-size: 14px;
+  cursor: pointer;
+  .el-icon-circle-plus-outline {
+    color: #009688;
+    font-size: 42px;
+  }
+  &:hover {
+    color: #333;
+    font-weight: 500;
+    border-color: #00bb88;
+    .el-icon-circle-plus-outline {
+      color: #00bb88;
+    }
+  }
+}
+</style>

+ 168 - 0
src/pages/indent-manage/indent/components/skuApply.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="compnent-appply-sku">
+    <el-dialog
+      v-model="show"
+      class="custom-apply-sku-dialog"
+      title="申请商品"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="700px"
+    >
+      <el-form
+        ref="formRef"
+        label-width="90px"
+        :model="form"
+        :rules="rules"
+      >
+        <el-form-item
+          prop="product_name"
+          label="英文品名"
+        >
+          <el-input v-model="form.product_name"></el-input>
+        </el-form-item>
+        <el-form-item
+          prop="product_name_cn"
+          label="中文品名"
+        >
+          <el-input v-model="form.product_name_cn"></el-input>
+        </el-form-item>
+        <el-form-item
+          prop=""
+          label="产品图片"
+        >
+          <image-upload
+            v-model:list="imageList"
+            width="100px"
+            height="100px"
+            :disable-preview="true"
+          ></image-upload>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <el-button
+            type="primary"
+            size="small"
+            @click="checkForm(formRef)"
+          >
+            确定
+          </el-button>
+          <el-button
+            type=""
+            size="small"
+            @click="close"
+          >
+            {{ $t('btn_cancel') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, ref, defineProps, defineEmits, watch } from 'vue'
+import {
+  ElButton,
+  ElInput,
+  ElDialog,
+  ElNotification,
+  ElForm,
+  ElFormItem,
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import { $t } from '@/i18n/index'
+import imageUpload from '@/components/ImageUpload.vue'
+import { applySKU } from '@/api/indent'
+
+defineComponent({
+  name: 'ComponentApplySku',
+})
+const show = ref(false)
+const loading = ref(false)
+const imageList = ref([])
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+})
+const $emit = defineEmits(['update:visible', 'apply'])
+
+const rules = {
+  product_name: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  product_name_cn: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+}
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    resetData()
+  },
+)
+const form = ref({
+  product_name: '',
+  product_name_cn: '',
+  images: '',
+})
+const formRef = ref()
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+
+const checkForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate((valid: boolean) => {
+    if (!valid) return
+    form.value.images = imageList.value
+      .map((i: any) => {
+        return i.url.replace($mediaRegExp, '/')
+      })
+      .join(',')
+
+    loading.value = true
+    applySKU(form.value)
+      .then((response: any) => {
+        // console.log(response)
+        if (response.code === 1) {
+          ElNotification({
+            title: '操作成功',
+            message: 'SKU已提交申请',
+            type: 'success',
+            duration: 3000,
+          })
+          $emit('apply', { product_name: form.value.product_name })
+          close()
+        }
+      })
+      .finally(() => {
+        loading.value = false
+      })
+  })
+}
+const resetData = () => {
+  form.value = {
+    product_name: '',
+    product_name_cn: '',
+    images: '',
+  }
+  imageList.value = []
+}
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>

+ 284 - 0
src/pages/indent-manage/indent/components/skuSelect.vue

@@ -0,0 +1,284 @@
+<template>
+  <div class="component-sku-select">
+    <el-dialog
+      v-model.sync="show"
+      class="custom-select-sku-dialog"
+      title="选择SKU"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="1200px"
+    >
+      <div
+        v-loading="loading"
+        class="flex items-start"
+        style="max-height: 100%"
+      >
+        <div>
+          <el-input
+            v-model="keywords"
+            placeholder="SKU / 商品名"
+            style="margin-bottom: 24px"
+          >
+            <template #append>
+              <el-button
+                size="small"
+                @click="search"
+              >
+                查询
+              </el-button>
+            </template>
+          </el-input>
+          <div class="catgory-area">
+            <el-tree
+              node-key="id"
+              :default-expanded-keys="defaultExpandID"
+              :data="categoryList"
+              :props="defaultProps"
+              @node-click="clickNode"
+            ></el-tree>
+          </div>
+        </div>
+        <div
+          v-if="skuList.length"
+          class="sku-area flex-auto flex flex-wrap items-center"
+        >
+          <div
+            v-for="sku in skuList"
+            :key="sku.id"
+            class="sku-item"
+          >
+            <div class="image"></div>
+            <div class="sku-name">{{ sku.product_name }}</div>
+            <div class="flex justify-between items-center">
+              <div class="sku-code">Product Code: {{ sku.product_sku }}</div>
+              <el-button
+                type="primary"
+                size="small"
+                plain
+                @click="selectSku(sku)"
+              >
+                使用
+              </el-button>
+            </div>
+          </div>
+        </div>
+        <div
+          v-else
+          class="sku-area flex-auto flex items-center"
+          style="padding-left: 25%"
+        >
+          未找到你想要的Indent商品, 可进行
+          <el-button
+            link
+            type="primary"
+            @click="dialogApplySkuVisible = true"
+          >
+            申请
+          </el-button>
+        </div>
+      </div>
+      <template #footer>
+        <div class="flex items-center justify-end">
+          <el-pagination
+            v-show="total > 0"
+            v-model:current-page="pageForm.page"
+            v-model:page-size="pageForm.limit"
+            :page-sizes="[10, 20, 50, 100]"
+            :total="total"
+            :layout="'total, sizes, prev, pager, next, jumper'"
+            @size-change="getSku"
+            @current-change="getSku"
+          />
+        </div>
+      </template>
+    </el-dialog>
+    <sku-apply
+      v-model:visible="dialogApplySkuVisible"
+      @apply="onSkuApply"
+    ></sku-apply>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, ref, defineProps, defineEmits, watch } from 'vue'
+import { ElButton, ElInput, ElTree, ElDialog, ElPagination } from 'element-plus'
+
+import { getCategoryTree, getSkuList } from '@/api/indent.js'
+import skuApply from './skuApply.vue'
+defineComponent({
+  name: 'ComponentSkuSelect',
+})
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+})
+
+const $emit = defineEmits(['update:visible', 'select'])
+const defaultProps = {
+  children: 'children',
+  label: 'name',
+}
+const dialogApplySkuVisible = ref(false)
+const loading = ref(false)
+const keywords = ref('')
+const defaultExpandID = ref([] as any[])
+const show = ref(false)
+const currentCategory = ref('')
+const categoryList = ref([] as any[])
+const skuList = ref([] as any[])
+const pageForm = ref({
+  page: 1,
+  limit: 20,
+})
+const total = ref(0)
+const onSkuApply = (data: any) => {
+  $emit('select', {
+    product_name: data.product_name,
+    product_sku: '申请中',
+    id: '',
+  })
+  close()
+}
+const search = () => {
+  currentCategory.value = ''
+  skuList.value = []
+  pageForm.value = {
+    page: 1,
+    limit: 20,
+  }
+  total.value = 0
+  getSku()
+}
+const resetData = () => {
+  currentCategory.value = ''
+  categoryList.value = []
+  skuList.value = []
+  pageForm.value = {
+    page: 1,
+    limit: 20,
+  }
+  total.value = 0
+}
+const selectSku = (item: any) => {
+  $emit('select', item)
+  close()
+}
+const clickNode = (obj: any) => {
+  currentCategory.value = obj.path
+}
+const getCategory = () => {
+  getCategoryTree().then((response: any) => {
+    // console.log(response)
+    const res = response.result
+    categoryList.value = res
+    categoryList.value.forEach((i) => {
+      defaultExpandID.value.push(i.id)
+      i.path = `${i.id}`
+      if (Array.isArray(i.children) && i.children.length > 0) {
+        i.children.forEach((son: any) => {
+          son.path = `${i.id},${son.id}`
+          if (Array.isArray(son.children) && son.children.length > 0) {
+            defaultExpandID.value.push(son.id)
+            son.children.forEach((grandson: any) => {
+              grandson.path = `${i.id},${son.id},${grandson.id}`
+            })
+          }
+        })
+      }
+    })
+  })
+}
+const getSku = () => {
+  const params = Object.assign(
+    { keywords: keywords.value, category: currentCategory.value },
+    pageForm.value,
+  )
+  loading.value = true
+  getSkuList(params)
+    .then((response: any) => {
+      const res = response.result
+      skuList.value = res.data
+      total.value = res.total
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    resetData()
+    if (show.value) {
+      getSku()
+      getCategory()
+    }
+  },
+)
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+</script>
+<style lang="scss">
+.component-sku-select {
+  .custom-select-sku-dialog {
+    margin-top: 0 !important;
+    margin-bottom: 0 !important;
+    height: 100vh;
+    .el-dialog__body {
+      position: relative;
+      // max-height: 87vh;
+      // overflow-y: scroll;
+      // overflow-x: auto;
+      padding-top: 8px;
+      padding-bottom: 8px;
+    }
+  }
+}
+</style>
+<style lang="scss" scoped>
+.component-sku-select {
+  .catgory-area {
+    width: 180px;
+    min-width: 180px;
+    border: 1px solid #eee;
+    padding: 0 0 24px 0;
+  }
+  .sku-area {
+    width: 100%;
+    padding-left: 24px;
+    height: 82vh;
+    overflow-y: auto;
+  }
+  .sku-item {
+    border: 1px solid #eee;
+    padding: 12px;
+    width: 280px;
+    margin: 0 18px 12px 0;
+    .sku-name {
+      overflow: hidden;
+      font-size: 18px;
+      line-height: 24px;
+      height: 48px;
+      max-height: 48px;
+      color: #222;
+      margin-bottom: 8px;
+      word-break: break-word;
+    }
+    .sku-code {
+      color: #409eff;
+    }
+    .image {
+      border: 1px solid #eee;
+      position: relative;
+      width: 100%;
+      height: 120px;
+      margin-bottom: 8px;
+    }
+  }
+}
+</style>

+ 631 - 0
src/pages/indent-manage/indent/edit.vue

@@ -0,0 +1,631 @@
+<template>
+  <div class="component-edit-indent">
+    <el-dialog
+      v-model="show"
+      class="custom-edit-indent-dialog"
+      :title="
+        visible === 1
+          ? $t(prefix_edit + 'title_add')
+          : $t(prefix_edit + 'title_edit')
+      "
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="960px"
+    >
+      <el-form
+        ref="indentForm"
+        :rules="rules"
+        :model="form"
+        label-width="130px"
+      >
+        <div class="edit-indent-form flex flex-wrap items-center">
+          <el-form-item
+            :label="$t(prefix + 'label_customer_name')"
+            prop="custom_name"
+          >
+            <el-select
+              v-model="form.custom_name"
+              style="width: 100%"
+              :placeholder="$t('text_please_select')"
+              :remote-method="getCustomListFun"
+              :loading="loading"
+              allow-create
+              remote
+              filterable
+              @change="customChange"
+            >
+              <el-option
+                v-for="option in customList"
+                :key="option.id"
+                :label="option.name"
+                :value="option.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item :label="$t(prefix + 'label_project_name')">
+            <el-input v-model="form.deal_name"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="SKU"
+            style="height: 40px"
+          >
+            <!-- <el-input
+              style="display: none"
+              v-model="form.product_sku"></el-input> -->
+            <div
+              class="el-input el-input__wrapper el-input__inner fake-sku-input"
+              @click="skuSelectVisible = true"
+            >
+              {{ form.product_sku }}
+            </div>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix + 'label_product_name')"
+            prop="product_name"
+          >
+            <el-input
+              v-model="form.product_name"
+              disabled
+            ></el-input>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix + 'label_require_material')"
+            prop="require_material"
+          >
+            <div
+              class="flex justify-between items-center"
+              style="width: 100%"
+            >
+              <el-select v-model="form.require_material">
+                <el-option
+                  v-for="item in materialOptions as any[]"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                ></el-option>
+              </el-select>
+              <el-input
+                v-if="form.require_material === '其它'"
+                v-model.trim="form.require_material_other"
+                placeholder="选其它时必填"
+                style="width: 45%"
+              ></el-input>
+            </div>
+          </el-form-item>
+
+          <el-form-item
+            :label="$t(prefix + 'label_category')"
+            prop="product_category"
+          >
+            <div
+              class="flex justify-between items-center"
+              style="width: 100%"
+            >
+              <el-select v-model="form.product_category">
+                <el-option
+                  v-for="item in categoryOptions as any[]"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                ></el-option>
+              </el-select>
+              <el-input
+                v-if="form.product_category === '其它'"
+                v-model.trim="form.product_category_other"
+                placeholder="选其它时必填"
+                style="width: 45%"
+              ></el-input>
+            </div>
+          </el-form-item>
+
+          <el-form-item
+            :label="$t(prefix + 'label_print_method')"
+            prop="print_method"
+          >
+            <div
+              class="flex justify-between items-center"
+              style="width: 100%"
+            >
+              <el-select v-model="form.print_method">
+                <el-option
+                  v-for="item in printOptions as any[]"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                ></el-option>
+              </el-select>
+              <el-input
+                v-if="form.print_method === '其它'"
+                v-model.trim="form.print_method_other"
+                placeholder="选其它时必填"
+                style="width: 45%"
+              ></el-input>
+            </div>
+          </el-form-item>
+          <el-form-item :label="$t(prefix_edit + 'label_print_require')">
+            <el-input v-model="form.require_print"></el-input>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix_edit + 'label_number')"
+            prop="require_amount"
+          >
+            <el-input v-model="form.require_amount"></el-input>
+          </el-form-item>
+          <el-form-item :label="$t(prefix_edit + 'label_deliver_date')">
+            <el-date-picker
+              v-model="form.due_date"
+              value-format="yyyy-MM-dd"
+              type="date"
+              placeholder="选择日期"
+            ></el-date-picker>
+          </el-form-item>
+          <el-form-item :label="$t(prefix_edit + 'label_ref_dimension')">
+            <el-input
+              v-model="form.ref_size_length"
+              style="width: 50px"
+              placeholder="长"
+            ></el-input>
+            <span>&nbsp;*&nbsp;</span>
+            <el-input
+              v-model="form.ref_size_width"
+              style="width: 50px"
+              placeholder="宽"
+            ></el-input>
+            <span>&nbsp;*&nbsp;</span>
+            <el-input
+              v-model="form.ref_size_height"
+              style="width: 50px"
+              placeholder="高"
+            ></el-input>
+            <span>&nbsp;CM</span>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix_edit + 'label_ref_url')"
+            style="width: 98%"
+          >
+            <el-input v-model="form.ref_url"></el-input>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix_edit + 'label_comment')"
+            style="width: 98%"
+          >
+            <el-input
+              v-model="form.other_note"
+              type="textarea"
+              :rows="3"
+            ></el-input>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix_edit + 'label_ref_image')"
+            style="width: 100%"
+          >
+            <image-upload
+              v-model:list="imageList"
+              :disable-preview="true"
+            ></image-upload>
+          </el-form-item>
+          <el-form-item style="width: 100%">
+            <el-table
+              v-show="quoteList.length"
+              :data="quoteList"
+              border
+              stripe
+            >
+              <el-table-column
+                width="200"
+                :label="$t(prefix_edit + 'col_product_image')"
+              >
+                <template #default="scope">
+                  <div v-if="scope.row.product_image.length">
+                    <img
+                      :src="scope.row.product_image[0]"
+                      style="width: 100%; height: auto"
+                    />
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column
+                width="200"
+                prop="vendor_name"
+                :label="$t(prefix_edit + 'col_supplier')"
+              ></el-table-column>
+              <el-table-column
+                prop="product_url"
+                label="URL"
+              ></el-table-column>
+            </el-table>
+            <el-button
+              type="warning"
+              size="small"
+              @click="addInfo"
+            >
+              + {{ $t(prefix_edit + 'btn_add_info') }}
+            </el-button>
+          </el-form-item>
+        </div>
+      </el-form>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <el-button
+            size="small"
+            type="primary"
+            @click="checkForm(indentForm)"
+          >
+            {{ $t('btn_submit') }}
+          </el-button>
+          <el-button
+            size="small"
+            type="primary"
+            @click="checkForm(indentForm, false)"
+          >
+            {{ $t(prefix_edit + 'btn_submit_and_next') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- <edit-info
+      :visible.sync="infoVisible"
+      @create="quotaCreated"></edit-info> -->
+    <sku-select
+      v-model:visible="skuSelectVisible"
+      @select="selectSku"
+    ></sku-select>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  defineComponent,
+  ref,
+  defineProps,
+  defineEmits,
+  watch,
+  nextTick,
+} from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElTable,
+  ElTableColumn,
+  ElSelect,
+  ElOption,
+  ElDialog,
+  ElNotification,
+  ElDatePicker,
+
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import dayjs from 'dayjs'
+import ImageUpload from '@/components/ImageUpload.vue'
+
+import skuSelect from './components/skuSelect.vue'
+import { $t } from '@/i18n/index'
+import { getCustomList, createIndent } from '@/api/indent'
+
+defineComponent({
+  name: 'EditIndent',
+})
+const props = defineProps({
+  visible: {
+    type: Number,
+    default: 0,
+  },
+  // 引用数据. 没有拼错, 不是quote询价信息列表. 这个数据是‘引用’业务克隆出来的, 虽然最终也会赋值给quoteList
+  quotaData: {
+    type: Object,
+    default: () => {},
+  },
+  categoryOptions: {
+    type: Array,
+    default: () => [],
+  },
+  printOptions: {
+    type: Array,
+    default: () => [],
+  },
+  materialOptions: {
+    type: Array,
+    default: () => [],
+  },
+  indentData: {
+    type: Object,
+    default: () => {},
+  },
+})
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible > 0
+    if (props.visible > 0) {
+      resetData()
+    }
+    if (props.visible === 1 && props.quotaData?.content?.length) {
+      quotaCreated(props.quotaData)
+    }
+    if (props.visible === 2) {
+      // 编辑. 初始化传进来的数据
+      const temp = cloneDeep(props.indentData)
+      if (temp.due_date) {
+        temp.due_date = dayjs(temp.due_date * 1000).format('YYYY-MM-DD')
+      } else {
+        temp.due_date = ''
+      }
+      quoteList.value = cloneDeep(temp.lists || [])
+
+      if (Array.isArray(temp.picture) && temp.picture.length) {
+        nextTick(() => {
+          imageList.value = temp.picture.map((img: string) => {
+            return {
+              url: $mediaRegExp.test(img)
+                ? img
+                : import.meta.env.VITE_APP_OSS_PREFIX + img,
+            }
+          })
+        })
+      }
+
+      temp.entity_id = temp.id
+
+      delete temp.id
+      delete temp.admin_id
+      delete temp.checked
+      delete temp.create_time
+      delete temp.creator
+      delete temp.default_quote
+      delete temp.list // quoteList
+      delete temp.update_time
+      // delete temp.status
+
+      form.value = Object.assign({}, cloneDeep(formDemo), temp)
+    }
+  },
+)
+
+const $emit = defineEmits(['update:visible', 'create'])
+
+const prefix = ref('order.indent.')
+const prefix_edit = ref('order.indent_edit.')
+const show = ref(false)
+const skuSelectVisible = ref(false)
+const form = ref({} as any)
+const formDemo = {
+  custom_name: '',
+  custom_id: '', // 联动custom_name
+  deal_name: '',
+  deal_id: '', // 永远是空
+  product_sku: '',
+  item_id: '', // 看旧代码是和 product_sku 有联动, 应该是商品的id
+  product_name: '',
+  require_material: '',
+  require_material_other: '',
+  product_category: '',
+  product_category_other: '',
+  print_method: '',
+  print_method_other: '',
+  require_print: '',
+  require_amount: '',
+  due_date: '',
+  ref_size_length: '',
+  ref_size_width: '',
+  ref_size_height: '',
+  ref_url: '',
+  other_note: '',
+  picture: '',
+}
+const imageList = ref([])
+
+const checkMaterial = (rule: any, value: any, cb: any) => {
+  if (value === '') {
+    return cb(new Error($t('text_please_select')))
+  } else if (value === '其它') {
+    if (
+      !(
+        form.value.require_material_other &&
+        form.value.require_material_other.length
+      )
+    ) {
+      return cb(new Error('选择其它时, 需填写具体内容!'))
+    }
+  }
+  cb()
+}
+const checkCategory = (rule: any, value: any, cb: any) => {
+  if (value === '') {
+    return cb(new Error($t('text_please_select')))
+  } else if (value === '其它') {
+    if (
+      !(
+        form.value.product_category_other &&
+        form.value.product_category_other.length
+      )
+    ) {
+      return cb(new Error('选择其它时, 需填写具体内容!'))
+    }
+  }
+  cb()
+}
+const checkPrint = (rule: any, value: any, cb: any) => {
+  if (value === '') {
+    return cb(new Error($t('text_please_select')))
+  } else if (value === '其它') {
+    if (
+      !(form.value.print_method_other && form.value.print_method_other.length)
+    ) {
+      return cb(new Error('选择其它时, 需填写具体内容!'))
+    }
+  }
+  cb()
+}
+const rules = {
+  custom_name: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  custom_id: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  product_name: [
+    {
+      required: true,
+      message: '请通过sku表单项选择一个商品',
+      trigger: 'change',
+    },
+  ],
+  require_material: [
+    { required: true, validator: checkMaterial, trigger: 'change' },
+  ],
+  product_category: [
+    { required: true, validator: checkCategory, trigger: 'change' },
+  ],
+  print_method: [{ required: true, validator: checkPrint, trigger: 'change' }],
+  require_amount: [
+    {
+      required: true,
+      message: $t('text_please_select'),
+      trigger: 'change',
+    },
+  ],
+}
+// 客户下拉框候选列表
+const customList = ref([] as any[])
+const loading = ref(false)
+const quoteList = ref([])
+
+const selectSku = (data: any) => {
+  form.value.product_name = data.product_name || ''
+  form.value.product_sku = data.product_sku || ''
+  form.value.item_id = data.id || ''
+}
+const customChange = (value: any) => {
+  const temp = customList.value.filter((i: any) => i.name === value)
+  form.value.custom_id = temp.length ? temp[0].id : ''
+}
+// 成功创建了报价, 把数据展示到界面
+const quotaCreated = (data: any) => {
+  if (Array.isArray(data.content)) {
+    quoteList.value = quoteList.value.concat(data.content)
+  }
+}
+
+const resetData = () => {
+  form.value = cloneDeep(formDemo)
+  imageList.value = []
+  customList.value = []
+  quoteList.value = []
+}
+
+const close = (done = {} as any) => {
+  $emit('update:visible', false)
+  if (typeof done === 'function') done()
+}
+
+const infoVisible = ref(0)
+const addInfo = () => {
+  infoVisible.value = 1
+}
+
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+const indentForm = ref<FormInstance>()
+const checkForm = (
+  formEl: FormInstance | undefined,
+  closeAfterCreate = true,
+) => {
+  if (!formEl) return
+  formEl.validate((valid: boolean) => {
+    if (valid) {
+      form.value.picture = imageList.value
+        .map((i: any) => {
+          return i.url.replace($mediaRegExp, '/')
+        })
+        .join(',')
+
+      // this$set(
+      //   this.form,
+      //   `quotes`,
+      //   quoteList.value.map((d: any) => d.entity_id || d.id),
+      // )
+      form.value.quotes = quoteList.value.map((d: any) => d.entity_id || d.id)
+      createIndent(form.value)
+        .then((response: any) => {
+          // console.log(response, 'create indent')
+          if (response.code === 1) {
+            ElNotification({
+              title: '创建成功',
+              message: '正在刷新数据',
+              duration: 3000,
+            })
+
+            // notify parent-component to refresh list data
+            $emit('create')
+            resetData()
+            if (closeAfterCreate) {
+              close()
+            } else {
+              formEl.clearValidate()
+            }
+          }
+        })
+        .catch(() => {
+          ElNotification({
+            duration: 3000,
+            title: '创建出错',
+            message:
+              '请稍后再试. 或者按F12打开控制台, 把错误信息复制出来找管理员排查.',
+          })
+        })
+    }
+  })
+}
+const getCustomListFun = (keywords: string) => {
+  loading.value = true
+  customList.value = []
+  getCustomList({ keywords })
+    .then((response: any) => {
+      if (Array.isArray(response.result) && response.result.length) {
+        customList.value = response.result
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+getCustomListFun('')
+</script>
+<style lang="scss">
+.component-edit-indent {
+  .custom-edit-indent-dialog {
+    margin-top: 0 !important;
+    margin-bottom: 0 !important;
+    height: 100vh;
+    .el-dialog__body {
+      max-height: 87vh;
+      overflow-y: scroll;
+      overflow-x: auto;
+      padding-top: 8px;
+    }
+  }
+}
+</style>
+<style lang="scss" scoped>
+.edit-indent-form {
+  .el-form-item {
+    width: 49%;
+  }
+}
+.fake-sku-input {
+  height: 30px;
+  cursor: pointer;
+}
+</style>

+ 9 - 0
src/pages/indent-manage/indent/index.vue

@@ -0,0 +1,9 @@
+<template>
+  <router-view />
+</template>
+<script lang="ts" setup>
+import { defineComponent } from 'vue'
+defineComponent({
+  name: 'IndentIndex',
+})
+</script>

+ 947 - 0
src/pages/indent-manage/indent/list.vue

@@ -0,0 +1,947 @@
+<template>
+  <div class="page-indent-list">
+    <el-form
+      ref="searchForm"
+      :inline="true"
+      :model="form"
+      label-width="120px"
+    >
+      <div class="flex flex-wrap items-center search-form">
+        <el-form-item :label="$t(prefix + 'label_project_name') + ':'">
+          <el-input v-model="form.deal_name"></el-input>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_customer_name') + ':'">
+          <el-input v-model="form.custom_name"></el-input>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_product_name') + ':'">
+          <el-input v-model="form.product_name"></el-input>
+        </el-form-item>
+
+        <el-form-item
+          :label="$t(prefix + 'label_supplier_product') + ':'"
+          label-width="130px"
+        >
+          <el-input
+            v-model="form.enquiry_product_name"
+            style="width: 180px"
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_category') + ':'">
+          <el-select v-model="form.product_category">
+            <el-option
+              v-for="item in categoryOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_supplier') + ':'">
+          <el-input v-model="form.vendor_name"></el-input>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_print_method') + ':'">
+          <el-select v-model="form.print_method">
+            <el-option
+              v-for="item in printOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_require_material') + ':'">
+          <el-select v-model="form.require_material">
+            <el-option
+              v-for="item in materialOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="SKU: ">
+          <el-input v-model="form.product_sku"></el-input>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_number') + ':'">
+          <div class="flex justify-between items-center">
+            <el-input
+              v-model="form.number_min"
+              style="width: 80px"
+            ></el-input>
+            <span>&nbsp;-&nbsp;</span>
+            <el-input
+              v-model="form.number_max"
+              style="width: 80px"
+            ></el-input>
+          </div>
+        </el-form-item>
+
+        <el-form-item
+          :label="$t('table_created_time') + ':'"
+          label-width="100px"
+        >
+          <el-date-picker
+            v-model="form.date_range"
+            style="width: 210px; padding-right: 0"
+            value-format="yyyy-MM-dd"
+            type="daterange"
+            range-separator="~"
+            :start-placeholder="$t(prefix + 'ph_time_start')"
+            :end-placeholder="$t(prefix + 'ph_time_end')"
+          ></el-date-picker>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_quoter') + ':'">
+          <el-select
+            v-model="form.creator"
+            filterable
+          >
+            <el-option
+              v-for="item in creatorOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_status') + ':'">
+          <el-select v-model="form.status">
+            <el-option
+              v-for="item in stateOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <div class="flex items-center btn-group">
+          <el-button
+            type="primary"
+            @click="search"
+          >
+            {{ $t('btn_search') }}
+          </el-button>
+          <el-button
+            type="success"
+            @click="create"
+          >
+            {{ $t(prefix + 'btn_new_indent') }}
+          </el-button>
+
+          <el-button
+            :disabled="checkedQuotes.length < 1"
+            type="primary"
+            @click="quota"
+          >
+            {{ $t(prefix + 'btn_quote') }}
+          </el-button>
+          <el-button
+            :disabled="checkedIndent.length < 1"
+            type="danger"
+            @click="onDeleteIndent"
+          >
+            {{ $t('btn_delete') }}
+          </el-button>
+          <el-button @click="reset">
+            {{ $t('btn_reset') }}
+          </el-button>
+          <span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
+          <el-button
+            type="warning"
+            @click="componentFreightVisible = true"
+          >
+            {{ $t(prefix + 'btn_set_freight') }}
+          </el-button>
+        </div>
+      </div>
+    </el-form>
+
+    <el-table
+      ref="tableIndent"
+      :header-cell-style="{ backgroundColor: '#F2F6FC' }"
+      :row-style="{ backgroundColor: '#F2F6FC' }"
+      :data="indentList"
+      default-expand-all
+      border
+    >
+      <!-- 子表格内容 -->
+      <el-table-column type="expand">
+        <template #default="props">
+          <el-table
+            v-if="props.row.lists && props.row.lists.length"
+            :ref="`tableSubIndent-${props.row.id}`"
+            :data="props.row.lists"
+            class="tableSubIndent"
+            :class="`tableSubIndent-${props.row.id}`"
+            :row-class-name="
+              ({ row }) => {
+                return props.row.default_quote === row.id
+                  ? 'default-quote-row'
+                  : 'ddd-ddd'
+              }
+            "
+            style="margin-left: 48px"
+            stripe
+            border
+          >
+            <el-table-column
+              v-for="value in subTableConfig"
+              :key="value.field"
+              :align="value.align || 'center'"
+              :width="value.width || ''"
+              :prop="value.field"
+              :label="$t(value.label)"
+            >
+              <template #default="scope">
+                <div v-if="value.type === 'checkbox'">
+                  <el-checkbox
+                    v-model="scope.row[value.field]"
+                    @change="($e) => onSubSelect($e, props.row)"
+                  ></el-checkbox>
+                </div>
+                <div
+                  v-else-if="value.type === 'imageList'"
+                  class="table-img-wrap"
+                  :style="{
+                    width: `${Number(value.width) - 20}px`,
+                  }"
+                >
+                  <el-tooltip content="点击可查看大图">
+                    <img
+                      style="width: 100%; cursor: pointer"
+                      :src="scope.row[value.field][0]"
+                      @click="imgClick(scope.row[value.field][0])"
+                    />
+                  </el-tooltip>
+                </div>
+                <div
+                  v-else-if="value.field === 'vendor_name'"
+                  style="text-align: left"
+                >
+                  <div>{{ scope.row[value.field] }}</div>
+                  <div v-show="scope.row.vendor_contact">
+                    联系人:&nbsp;{{ scope.row.vendor_contact }}
+                  </div>
+                  <div v-show="scope.row.vendor_phone">
+                    联系电话:&nbsp;{{ scope.row.vendor_phone }}
+                  </div>
+                </div>
+                <el-tooltip
+                  v-else
+                  :content="$t(value.label)"
+                >
+                  <div
+                    class="table-cell-content"
+                    :style="value.style || {}"
+                  >
+                    {{ scope.row[value.field] }}
+                  </div>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              prop="status"
+              width="280"
+              :label="$t('table_operation')"
+            >
+              <template #default="scope">
+                <el-button
+                  type="warning"
+                  @click="editInfo(scope.row)"
+                >
+                  {{ $t('btn_edit') }}
+                </el-button>
+                <el-button
+                  type="primary"
+                  @click="valuation(scope.row)"
+                >
+                  {{ $t(prefix + 'btn_calc_price') }}
+                </el-button>
+                <el-button
+                  type="success"
+                  :disabled="props.row.default_quote === scope.row.id"
+                  @click="adopt(scope.row, props.row)"
+                >
+                  {{
+                    props.row.default_quote === scope.row.id
+                      ? $t(prefix + 'btn_adopted')
+                      : $t(prefix + 'btn_adopt')
+                  }}
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div
+            v-else
+            class="flex justify-center items-center"
+          >
+            <div>暂无报价</div>
+          </div>
+        </template>
+      </el-table-column>
+      <!-- 子表格内容 end -->
+      <!-- 主表格 内容 -->
+
+      <el-table-column
+        v-for="value in mainTableConfig"
+        :key="value.field"
+        :align="value.align || 'center'"
+        :width="value.width || ''"
+        :prop="value.field"
+        :label="$t(value.label)"
+      >
+        <template
+          v-if="value.type === 'checkbox'"
+          #header
+        >
+          <el-checkbox
+            v-model="checkedAll"
+            @change="onSelectAll"
+          ></el-checkbox>
+        </template>
+        <template #default="scope">
+          <div v-if="value.type === 'checkbox'">
+            <el-checkbox
+              v-model="scope.row[value.field]"
+              @change="($e) => onSelect($e, scope.$index)"
+            ></el-checkbox>
+          </div>
+          <el-tooltip
+            v-else-if="value.field === 'quote_name'"
+            :content="$t(value.label) + ', 双击可编辑'"
+          >
+            <div
+              class="table-cell-content"
+              :style="value.style || {}"
+              @dblclick="onEdit(scope.row)"
+            >
+              {{ scope.row[value.field] }}
+            </div>
+          </el-tooltip>
+          <el-tooltip
+            v-else
+            :content="$t(value.label)"
+          >
+            <div
+              class="table-cell-content"
+              :style="value.style || {}"
+            >
+              {{ scope.row[value.field] }}
+            </div>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      v-show="total > 0"
+      v-model:page="pageForm.page"
+      v-model:limit="pageForm.limit"
+      :total="total"
+      @pagination="getList"
+    />
+    <edit
+      v-model:visible="componentEditVisible"
+      :indent-data="indentData"
+      :quota-data="quotaData"
+      :category-options="categoryOptions"
+      :print-options="printOptions"
+      :material-options="materialOptions"
+      @create="getList"
+    ></edit>
+    <freight v-model:visible="componentFreightVisible"></freight>
+
+    <compEditInfo
+      v-model:visible="componentEditInfoVisible"
+      :data-for-edit="quoteForEdit"
+      :parent-id="infoParentId"
+      @create="getList"
+    ></compEditInfo>
+
+    <calcPrice
+      v-model:visible="componentCalcPriceVisible"
+      :creator-options="creatorOptions"
+      :data-for-calc="quoteForEdit"
+      @save-price-calc="getList"
+    ></calcPrice>
+    <el-dialog
+      v-model="bigImageVisible"
+      style="margin: 5vh auto"
+      width="800"
+    >
+      <img
+        :src="currentBigImage"
+        style="max-width: 100%; height: auto"
+        alt=""
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { defineComponent, ref, computed, watch } from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElTable,
+  ElTableColumn,
+  ElSelect,
+  ElOption,
+  ElMessageBox,
+  ElTooltip,
+  ElNotification,
+  ElPagination,
+  ElDatePicker,
+  ElCheckbox,
+  ElDialog,
+} from 'element-plus'
+import { $t } from '@/i18n/index'
+import {
+  getIndentList,
+  getCreator,
+  deleteIndent,
+  cloneQuote,
+  setDefaultQuote,
+} from '@/api/indent'
+import edit from './edit.vue'
+import freight from './components/freight.vue'
+import compEditInfo from './components/info.vue'
+import calcPrice from './components/calcPrice/index.vue'
+
+defineComponent({
+  name: 'PageIndentList',
+})
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+const prefix = 'order.indent.'
+const form = ref({
+  product_name: '',
+  product_sku: '',
+  deal_name: '',
+  custom_name: '',
+  product_category: '',
+  require_material: '',
+  print_method: '',
+  date_range: [],
+  creator: '',
+  enquiry_product_name: '',
+  vendor_name: '',
+  number_min: '',
+  number_max: '',
+  status: '',
+})
+
+const total = ref(0)
+const pageForm = ref({
+  page: 1,
+  limit: 20,
+})
+const categoryOptions = [
+  {
+    label: $t('text_please_select'),
+    value: '',
+  },
+  {
+    label: 'Lanyards',
+    value: 'Lanyards',
+  },
+  {
+    label: 'PIN BADGES',
+    value: 'PIN BADGES',
+  },
+  {
+    label: 'WRISTBANDS',
+    value: 'WRISTBANDS',
+  },
+  {
+    label: 'KEYRINGS',
+    value: 'KEYRINGS',
+  },
+  {
+    label: 'NEOPRENE',
+    value: 'NEOPRENE',
+  },
+  {
+    label: 'STATIONERY',
+    value: 'STATIONERY',
+  },
+  {
+    label: 'LIFESTYLE',
+    value: 'LIFESTYLE',
+  },
+  {
+    label: 'POS',
+    value: 'POS',
+  },
+  {
+    label: '其它',
+    value: '其它',
+  },
+]
+const printOptions = [
+  {
+    label: $t('text_please_select'),
+    value: '',
+  },
+  {
+    label: '全彩印刷',
+    value: '全彩印刷',
+  },
+  {
+    label: '专色印刷',
+    value: '专色印刷',
+  },
+  {
+    label: '热转印',
+    value: '热转印',
+  },
+  {
+    label: '丝印',
+    value: '丝印',
+  },
+  {
+    label: '镭雕',
+    value: '镭雕',
+  },
+  {
+    label: '凹印',
+    value: '凹印',
+  },
+  {
+    label: '刺绣',
+    value: '刺绣',
+  },
+  {
+    label: '其它',
+    value: '其它',
+  },
+]
+const materialOptions = [
+  {
+    label: $t('text_please_select'),
+    value: '',
+  },
+  {
+    label: '布料',
+    value: '布料',
+  },
+  {
+    label: '塑料',
+    value: '塑料',
+  },
+  {
+    label: '纸质',
+    value: '纸质',
+  },
+  {
+    label: '五金',
+    value: '五金',
+  },
+  {
+    label: '其它',
+    value: '其它',
+  },
+]
+const stateOptions = [
+  {
+    label: '全部',
+    value: '',
+  },
+  {
+    label: '未报价',
+    value: '0',
+  },
+  {
+    label: '已报价',
+    value: '1',
+  },
+]
+const mainTableConfig = [
+  {
+    type: 'checkbox',
+    field: 'checked',
+    width: '55',
+  },
+  {
+    label: 'order.indent.table_indent_name',
+    field: 'quote_name',
+    style: 'cursor: default',
+  },
+  {
+    label: 'order.indent.label_project_name',
+    field: 'deal_name',
+    width: '120',
+  },
+  {
+    label: 'order.indent.label_customer_name',
+    field: 'custom_name',
+    width: '180',
+  },
+  {
+    label: 'order.indent.label_product_name',
+    field: 'product_name',
+  },
+  {
+    label: 'order.indent.label_status',
+    field: 'status',
+    width: '80',
+    style: 'font-weight: bold;',
+  },
+  {
+    label: 'order.indent.table_creator',
+    field: 'creator',
+    width: '90',
+    style: 'font-weight: bold;',
+  },
+  {
+    label: 'table_created_time',
+    field: 'create_time',
+    width: '120',
+  },
+] as any[]
+const subTableConfig = [
+  {
+    type: 'checkbox',
+    field: 'checked',
+    width: '55',
+  },
+  {
+    label: 'order.indent.table_image',
+    type: 'imageList',
+    width: '80',
+    field: 'product_image',
+  },
+  {
+    label: 'order.indent.table_supplier',
+    field: 'vendor_name',
+  },
+  {
+    label: 'order.indent.table_url',
+    field: 'product_url',
+  },
+  {
+    label: 'order.indent.table_price',
+    field: 'quote_price',
+    width: '75',
+  },
+  {
+    label: 'order.indent.label_number',
+    field: 'quote_num',
+    width: '150',
+  },
+  {
+    label: 'order.indent.table_days',
+    field: 'large_days',
+    width: '90',
+  },
+  {
+    label: 'order.indent.label_quoter',
+    field: 'creator',
+    width: '90',
+    style: 'font-weight: bold;',
+  },
+  {
+    label: 'table_updated_time',
+    field: 'update_time',
+    width: '115',
+  },
+] as any[]
+const componentEditVisible = ref(0) // 1 新增, 2编辑. 0关闭
+const componentFreightVisible = ref(false) // 设置运费参数
+const componentEditInfoVisible = ref(0) // 编辑报价信息
+const componentCalcPriceVisible = ref(false) // 计价
+const indentData = ref({}) // 修改询价的时候使用
+const quoteForEdit = ref({}) // 修改和计价时共用, 只是用作传递给子组件数据的变量
+const infoParentId = ref(0)
+const loading = ref(false)
+const checkedAll = ref(false)
+const quotaData = ref({}) // 引用报价信息后得到的克隆数据, 传递给创建询价组件, 当作新创建的报价信息.
+
+const indentList = ref([] as any[])
+
+const checkedQuotes = computed(() => {
+  return indentList.value.reduce((total: any, current: any) => {
+    if (current.lists?.length) {
+      current.lists.forEach((i: any) => {
+        if (i.checked) {
+          total.push(i)
+        }
+      })
+    }
+    return total
+  }, [])
+})
+
+const checkedIndent = computed(() => {
+  return indentList.value.filter((i: any) => i.checked)
+})
+
+const getList = function () {
+  quotaData.value = {} // 重置引用数据, 否则创建询价完成之后再次创建询价, 会把之前的引用数据再次带到创建询价组件
+
+  const timeSeparator = '~'
+  const temp = JSON.parse(JSON.stringify(form.value))
+  const p = Object.assign({}, temp, pageForm.value)
+  // 组装日期范围数据
+  if (Array.isArray(p.date_range) && p.date_range.length) {
+    p.date_range = p.date_range.join(timeSeparator)
+  } else {
+    p.date_range = ''
+  }
+  loading.value = true
+
+  // const getlist = function () {
+  getIndentList(p)
+    .then((res: any) => {
+      indentList.value =
+        res.result?.data.map((main: any) => {
+          return {
+            ...main,
+            create_time: main.create_time.split(' ')[0],
+            checked: false,
+
+            lists: main.lists.map((i: any) => {
+              const temp = { ...i, checked: false }
+
+              if (
+                Array.isArray(temp.product_image) &&
+                temp.product_image.length
+              ) {
+                temp.product_image = temp.product_image.map((img: string) =>
+                  $mediaRegExp.test(img)
+                    ? img
+                    : import.meta.env.VITE_APP_OSS_PREFIX + img,
+                )
+              } else {
+                temp.product_image = []
+              }
+
+              return temp
+            }),
+          }
+        }) || []
+
+      total.value = res.result.total
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+const creatorOptions = ref([] as any[])
+// 获取报价人下拉框候选数据.
+const getCreatorOption = function () {
+  getCreator().then((res: any) => {
+    creatorOptions.value = res.result.map((i: any) => {
+      return { label: i.username, value: i.id }
+    })
+    creatorOptions.value.unshift({
+      label: $t(prefix + 'ph_quoter'),
+      value: '',
+    })
+  })
+}
+
+const search = function () {
+  pageForm.value = {
+    page: 1,
+    limit: 20,
+  }
+  getList()
+}
+
+const create = function () {
+  componentEditVisible.value = 1
+}
+const onEdit = function (row: any) {
+  quotaData.value = {}
+  indentData.value = row
+  componentEditVisible.value = 2
+}
+
+const quota = function () {
+  const ids = checkedQuotes.value.map((i: any) => i.id)
+  cloneQuote({ ids }).then((response: any) => {
+    if (response.code === 1) {
+      quotaData.value = response.result
+      create()
+    }
+  })
+}
+const onDeleteIndent = function () {
+  ElMessageBox.confirm('将删除该报价, 是否继续?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    const p = {
+      indent_ids: checkedIndent.value.map((i: any) => `${i.id}`), // 询价id
+      quote_ids: checkedQuotes.value.map((i: any) => `${i.parent_id}_${i.id}`), // 报价id数据. 单个格式为 '询价id_报价id'
+    }
+    deleteIndent(p).then((response: any) => {
+      if (response.code === 1) {
+        getList()
+        ElNotification({
+          title: '删除成功',
+          message: '删除成功',
+          duration: 3000,
+        })
+      }
+    })
+  })
+}
+const reset = function () {
+  form.value = {
+    product_name: '',
+    product_sku: '',
+    deal_name: '',
+    custom_name: '',
+    product_category: '',
+    require_material: '',
+    print_method: '',
+    date_range: [],
+    creator: '',
+    enquiry_product_name: '',
+    vendor_name: '',
+    number_min: '',
+    number_max: '',
+    status: '',
+  }
+  pageForm.value = {
+    page: 1,
+    limit: 20,
+  }
+  total.value = 0
+  getList()
+}
+const editInfo = function (row: any) {
+  infoParentId.value = row.parent_id
+  quoteForEdit.value = row
+  componentEditInfoVisible.value = 2
+}
+// 计价
+const valuation = function (row: any) {
+  quoteForEdit.value = row
+  componentCalcPriceVisible.value = true
+}
+// 采纳
+const adopt = function (row: any, parentRow: any) {
+  // console.log(row, 'row')
+  // console.log(parentRow, 'parent row')
+  const p = {
+    id: parentRow.id,
+    quote_id: row.id,
+  }
+  console.log(p)
+  setDefaultQuote(p).then((res: any) => {
+    if (res.code === 1) {
+      ElNotification({
+        type: 'success',
+        title: '操作成功',
+        message: '正在刷新数据',
+        duration: 3000,
+      })
+    } else {
+      ElNotification({
+        type: 'error',
+        title: '操作出错了',
+        message: '请稍后重试, 或者联系管理员',
+        duration: 3000,
+      })
+    }
+    getList()
+  })
+}
+const onSelect = function (bool: any, index: number) {
+  // console.log('onSelect', bool, index)
+  // 每次勾选主表的某行, 立刻计算主表的全选
+  checkedAll.value = indentList.value.every((i) => i.checked)
+
+  // 勾选主表格的某行, 将该行所属的 子表格 给全部勾选/取消勾选
+  indentList.value[index].lists = indentList.value[index].lists.map(
+    (i: any) => {
+      return {
+        ...i,
+        checked: bool,
+      }
+    },
+  )
+}
+// 主表格全勾选, 遍历数据将所有子表格全勾选
+const onSelectAll = function (boolean: any) {
+  // console.log('onSelectAll', boolean)
+  indentList.value = indentList.value.map((i: any) => {
+    return {
+      ...i,
+      checked: boolean,
+      lists: i.lists.map((j: any) => {
+        return {
+          ...j,
+          checked: boolean,
+        }
+      }),
+    }
+  })
+}
+
+// 勾选子表格的某行
+const onSubSelect = function (bool: any, row: any) {
+  // console.log('onSelect-sub', bool, row)
+  // 判断该行对应 在主表的父行 的勾选状态
+  row.checked = row.lists.every((i: any) => i.checked)
+
+  // 变更主表某行的勾选状态后, 立刻计算主表的全选
+  checkedAll.value = indentList.value.every((i: any) => i.checked)
+}
+getList()
+getCreatorOption()
+
+const currentBigImage = ref('')
+const bigImageVisible = ref(false)
+const imgClick = (url: string) => {
+  currentBigImage.value = url
+  bigImageVisible.value = true
+}
+watch(bigImageVisible, () => {
+  if (!bigImageVisible.value) currentBigImage.value = ''
+})
+</script>
+
+<style lang="scss">
+.page-indent-list {
+  .default-quote-row {
+    color: #ef4135;
+  }
+}
+</style>
+<style lang="scss" scoped>
+.search-form {
+  .el-form-item {
+    width: 310px;
+  }
+
+  .el-input,
+  .el-select,
+  .el-date-editor {
+    width: 190px;
+  }
+  .btn-group {
+    margin: 0 0 22px 8px;
+  }
+}
+.table-cell-content {
+  word-break: break-word;
+}
+</style>

+ 16 - 0
src/pages/indent-manage/index.vue

@@ -0,0 +1,16 @@
+<template>
+  <router-view class="bg-white" />
+</template>
+<script lang="ts">
+export default defineComponent({
+  name: 'IndentManageIndex',
+})
+</script>
+<script lang="ts" setup>
+import { defineComponent } from 'vue'
+</script>
+<style>
+.bg-white {
+  background-color: #fefefe;
+}
+</style>

+ 193 - 0
src/pages/indent-manage/product/category/edit.vue

@@ -0,0 +1,193 @@
+<template>
+  <div class="">
+    <el-dialog
+      v-model="show"
+      class="custom-edit-indent-product-category-dialog"
+      :title="
+        visible === 1
+          ? $t(prefix + 'dialog_title_create')
+          : $t(prefix + 'dialog_title_edit')
+      "
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="550px"
+    >
+      <el-form
+        ref="formRef"
+        style="width: 500px"
+        :rules="rules"
+        :model="form"
+        label-width="120px"
+      >
+        <el-form-item
+          :label="$t(prefix + 'label_name')"
+          prop="name"
+        >
+          <el-input v-model="form.name"></el-input>
+        </el-form-item>
+        <el-form-item
+          :label="$t(prefix + 'label_father_category')"
+          prop="pid"
+        >
+          <el-cascader
+            v-model="form.pid"
+            :disabled="visible === 2"
+            :show-all-levels="false"
+            style="width: 100%"
+            :options="computedCategoryList"
+            :props="{
+              checkStrictly: true,
+              label: 'name',
+              value: 'id',
+              emitPath: false,
+            }"
+          ></el-cascader>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <el-button
+            size="small"
+            type="primary"
+            @click="checkForm"
+          >
+            {{ $t('btn_save') }}
+          </el-button>
+          <el-button
+            size="small"
+            @click="close"
+          >
+            {{ $t('btn_close') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import {
+  defineComponent,
+  ref,
+  watch,
+  computed,
+  defineProps,
+  defineEmits,
+} from 'vue'
+import {
+  ElDialog,
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElNotification,
+  ElCascader,
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import { $t } from '@/i18n/index'
+import { createCategory, editCategory } from '@/api/product.js'
+defineComponent({
+  name: 'ComponentEditIndentProductCategory',
+})
+
+const props = defineProps({
+  visible: {
+    type: Number,
+    default: 0,
+  },
+  categoryData: {
+    type: Object,
+    default: () => {
+      return {
+        category_id: 0,
+      }
+    },
+  },
+  categoryList: {
+    type: Array,
+    default: () => [],
+  },
+})
+const $emit = defineEmits(['update:visible'])
+const prefix = 'order.category.'
+let show = ref(false)
+const form = ref({
+  name: '',
+  pid: 0,
+} as any)
+const rules = {
+  name: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  // pid: [{ required: true, message:this.$t('text_please_input'), trigger: 'change' }],
+}
+
+const computedCategoryList = computed(() => {
+  return ([{ name: '顶级分类', id: 0 }] as any[]).concat(props.categoryList)
+})
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible > 0
+    if (props.visible === 2) form.value = cloneDeep(props.categoryData)
+  },
+)
+
+const checkForm = () => {
+  if (!formRef.value) return
+  formRef.value.validate((valid) => {
+    if (valid) {
+      const p = cloneDeep(form.value)
+      if (props.visible === 1) {
+        create(p)
+      } else if (props.visible === 2) {
+        edit(p)
+      }
+    }
+  })
+}
+const create = (p: any) => {
+  createCategory(p).then((res: any) => {
+    if (res.code === 1) {
+      ElNotification({
+        title: '创建成功',
+        message: '正在刷新数据',
+        duration: 3000,
+      })
+      close()
+    }
+  })
+}
+const edit = (p: any) => {
+  editCategory(p).then((res: any) => {
+    if (res.code === 1) {
+      ElNotification({
+        title: '编辑成功',
+        message: '正在刷新数据',
+        duration: 3000,
+      })
+      close()
+    }
+  })
+}
+const resetData = () => {
+  form.value = {
+    name: '',
+    pid: 0,
+  }
+}
+
+const formRef = ref<FormInstance>()
+let close = (done = {} as any) => {
+  $emit('update:visible', 0)
+  formRef.value!.resetFields()
+  resetData()
+  if (typeof done === 'function') done()
+}
+</script>
+<style lang="scss" scoped></style>

+ 126 - 0
src/pages/indent-manage/product/category/index.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="">
+    <el-button
+      style="margin-bottom: 12px"
+      type="primary"
+      size="small"
+      @click="add"
+    >
+      {{ $t('btn_add') }}
+    </el-button>
+
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+      style="width: 100%"
+      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+      :header-cell-style="{
+        background: '#fafafa',
+        color: '#333',
+        'font-weight': 700,
+      }"
+    >
+      <el-table-column
+        prop="name"
+        :label="$t('order.category.label_name')"
+        min-width="150"
+      ></el-table-column>
+      <el-table-column
+        prop="create_time"
+        :label="$t('table_created_time')"
+        min-width="150"
+      ></el-table-column>
+      <el-table-column :label="$t('table_status')">
+        <template #default="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :inactive-text="$t('order.category.label_hidden')"
+            :active-text="$t('order.category.label_show')"
+            :inactive-value="0"
+            :active-value="1"
+            @change="($e: any) => changeStatus($e, scope.row)"
+          ></el-switch>
+        </template>
+      </el-table-column>
+      <el-table-column
+        :label="$t('table_operation')"
+        min-width="150"
+      >
+        <template #default="scope">
+          <el-button
+            size="small"
+            type="primary"
+            @click="edit(scope.row)"
+          >
+            {{ $t('btn_edit') }}
+          </el-button>
+          <!-- <div v-else>暂无权限</div> -->
+        </template>
+      </el-table-column>
+    </el-table>
+    <editCategory
+      v-model:visible="componentEditVisible"
+      :category-list="list"
+      :category-data="dataForEdit"
+      @update:visible="getCategory"
+    ></editCategory>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref } from 'vue'
+import {
+  ElButton,
+  ElTable,
+  ElTableColumn,
+  ElNotification,
+  ElSwitch,
+} from 'element-plus'
+import { getCategoryList, changeCategoryStatus } from '@/api/product'
+import { $t } from '@/i18n/index'
+import editCategory from './edit.vue'
+defineComponent({
+  name: 'PageIndentProductCategoryList',
+})
+
+let loading = ref(false)
+let componentEditVisible = ref(0) // 1 新增, 2编辑. 0关闭
+let list = ref([] as any[])
+
+let dataForEdit = ref({} as any)
+
+let add = () => {
+  dataForEdit.value = {}
+  componentEditVisible.value = 1
+}
+let edit = (row: any) => {
+  dataForEdit.value = row
+  componentEditVisible.value = 2
+}
+let changeStatus = (status: any, row: any) => {
+  changeCategoryStatus({ id: row.id, status }).then((res: any) => {
+    if (res.code === 1) {
+      ElNotification({
+        title: '编辑成功',
+        message: '正在刷新数据',
+        duration: 3000,
+      })
+      getCategory()
+    }
+  })
+}
+let getCategory = () => {
+  loading.value = true
+  getCategoryList()
+    .then((res: any) => {
+      if (res.code === 1) {
+        list.value = res.result
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+getCategory()
+</script>
+<style lang="scss" scoped></style>

+ 293 - 0
src/pages/indent-manage/product/components/edit.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="component-edit-indent-product">
+    <el-dialog
+      v-model="show"
+      class="custom-edit-indent-product-dialog"
+      :title="
+        visible === 1
+          ? $t(prefix + 'dialog_title_add')
+          : $t(prefix + 'dialog_title_edit')
+      "
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="550px"
+    >
+      <el-form
+        ref="productFormRef"
+        style="width: 500px"
+        :rules="rules"
+        :model="form"
+        label-width="90px"
+      >
+        <el-form-item
+          :label="$t(prefix + 'label_category')"
+          prop="category_id"
+        >
+          <el-cascader
+            v-model="form.category_id"
+            style="width: 410px"
+            :options="categoryData as any[]"
+            :props="{ label: 'name', value: 'id' }"
+          ></el-cascader>
+        </el-form-item>
+        <el-form-item
+          :label="$t(prefix + 'label_name_en')"
+          prop="product_name"
+        >
+          <el-input v-model="form.product_name"></el-input>
+        </el-form-item>
+        <el-form-item
+          :label="$t(prefix + 'label_name_cn')"
+          prop="product_name_cn"
+        >
+          <el-input v-model="form.product_name_cn"></el-input>
+        </el-form-item>
+        <el-form-item
+          label="SKU"
+          prop="product_sku"
+        >
+          <el-input v-model="form.product_sku"></el-input>
+        </el-form-item>
+        <el-form-item :label="$t(prefix + 'lable_image_list')">
+          <image-upload
+            v-model:list="imageList"
+            :disable-preview="true"
+          ></image-upload>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <el-button
+            size="small"
+            type="primary"
+            @click="checkForm"
+          >
+            {{ $t('btn_save') }}
+          </el-button>
+          <el-button
+            size="small"
+            @click="close"
+          >
+            {{ $t('btn_close') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, ref, watch, nextTick } from 'vue'
+import {
+  ElDialog,
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElNotification,
+  ElCascader,
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import { $t } from '@/i18n/index'
+import { createProduct, updateProduct } from '@/api/product.js'
+import imageUpload from '@/components/ImageUpload.vue'
+defineComponent({
+  name: 'CompIndentEditProduct',
+})
+const props = defineProps({
+  visible: {
+    type: Number,
+    default: 0,
+  },
+  productData: {
+    type: Object,
+    default: () => {
+      return {
+        category_id: 0,
+      }
+    },
+  },
+  categoryData: {
+    type: Array,
+    default: () => [],
+  },
+})
+const $emit = defineEmits(['update:visible'])
+
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+const imageList = ref([] as any[])
+const show = ref(false)
+const prefix = 'order.product.'
+const form = ref({
+  product_name: '',
+  product_name_cn: '',
+  product_sku: '',
+  category_id: [],
+  images: '',
+  status: 1,
+} as any)
+const rules = {
+  product_name: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  product_name_cn: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  product_sku: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  category_id: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+}
+
+watch(
+  () => props.visible,
+  (value) => {
+    show.value = value > 0
+    if (value > 0) {
+      resetData()
+    }
+    if (value === 1) {
+      // 创建商品的状态是 通过 的.
+      form.value.status = 1
+    } else if (value === 2) {
+      const temp: any = {}
+      if (typeof props.productData.category_id === 'number') {
+        temp.category_id = getCategory(props.productData.category_id)
+      }
+      form.value = Object.assign({}, cloneDeep(props.productData), temp)
+
+      delete form.value.admin_name
+      delete form.value.admin_id
+      delete form.value.create_time
+      delete form.value.update_time
+
+      nextTick(() => {
+        imageList.value = form.value.images.map((img: string) => {
+          return {
+            url: $mediaRegExp.test(img)
+              ? img
+              : import.meta.env.VITE_APP_OSS_PREFIX + img,
+          }
+        })
+      })
+    }
+  },
+)
+
+const getCategory = (id: number | string) => {
+  const result: any[] = []
+  props.categoryData.forEach((first: any) => {
+    if (first.id === id) {
+      result.push(first.id)
+      return
+    }
+    if (first.children?.length) {
+      first.children.forEach((second: any) => {
+        if (second.id === id) {
+          result.push(first.id)
+          result.push(second.id)
+          return
+        }
+        if (second.children?.length) {
+          second.children.forEach((third: any) => {
+            if (third.id === id) {
+              result.push(first.id)
+              result.push(second.id)
+              result.push(third.id)
+              return
+            }
+          })
+        }
+      })
+    }
+  })
+
+  return result
+}
+const productFormRef = ref<FormInstance>()
+const checkForm = () => {
+  if (!productFormRef.value) return
+  productFormRef.value.validate((valid: boolean) => {
+    if (valid) {
+      const p = cloneDeep(form.value)
+      p.images = imageList.value
+        .map((i) => {
+          return i.url.replace($mediaRegExp, '/')
+        })
+        .join(',')
+      // p.category_id = p.category_id.join(',')
+      p.category_id = p.category_id[p.category_id.length - 1].toString()
+
+      if (props.visible === 1) {
+        create(p)
+      } else if (props.visible === 2) {
+        edit(p)
+      }
+    }
+  })
+}
+const create = (p: any) => {
+  createProduct(p).then((res: any) => {
+    if (res.code === 1) {
+      ElNotification({
+        title: '创建成功',
+        message: '正在刷新数据',
+        duration: 3000,
+      })
+      resetData()
+      close()
+    }
+  })
+}
+const edit = (p: any) => {
+  updateProduct(p).then((res: any) => {
+    if (res.code === 1) {
+      ElNotification({
+        title: '编辑成功',
+        message: '正在刷新数据',
+        duration: 3000,
+      })
+      resetData()
+      close()
+    }
+  })
+}
+const resetData = () => {
+  form.value = {
+    product_name: '',
+    product_name_cn: '',
+    product_sku: '',
+    category_id: [],
+    images: '',
+    status: 1,
+  }
+}
+
+let close = (done = {} as any) => {
+  $emit('update:visible', 0)
+  if (typeof done === 'function') done()
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 253 - 0
src/pages/indent-manage/product/components/examine.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="">
+    <el-dialog
+      v-model="show"
+      :title="$t(prefix + 'dialog_title_audit')"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="550px"
+    >
+      <el-form
+        ref="formRef"
+        v-loading="loading"
+        style="width: 500px"
+        :rules="rules"
+        :model="form"
+        label-width="110px"
+      >
+        <el-form-item
+          :label="$t(prefix + 'label_name')"
+          prop=""
+        >
+          <div>{{ form.product_name }}</div>
+        </el-form-item>
+        <el-form-item
+          label="中文品名"
+          prop=""
+        >
+          <div>{{ form.product_name_cn }}</div>
+        </el-form-item>
+        <el-form-item
+          :label="$t(prefix + 'label_audit')"
+          prop=""
+        >
+          <el-radio-group
+            v-model="form.status"
+            @change="formRef!.clearValidate('product_sku')"
+          >
+            <el-radio :label="1">审核通过</el-radio>
+            <el-radio :label="2">审核不通过</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item
+          :label="$t(prefix + 'label_category')"
+          prop="category_id"
+        >
+          <el-cascader
+            v-model="form.category_id"
+            style="width: 410px"
+            :options="categoryData as any[]"
+            :props="{ label: 'name', value: 'id' }"
+          ></el-cascader>
+        </el-form-item>
+        <el-form-item
+          label="SKU"
+          prop="product_sku"
+        >
+          <el-input v-model="form.product_sku" />
+        </el-form-item>
+        <el-form-item
+          :label="$t(prefix + 'label_feedback')"
+          prop="feedback"
+        >
+          <el-input
+            v-model="form.feedback"
+            type="textarea"
+            :rows="5"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="flex justify-center items-center">
+          <el-button
+            type="danger"
+            size="small"
+            @click="checkForm(2)"
+          >
+            驳回
+          </el-button>
+          <el-button
+            size="small"
+            type="primary"
+            @click="checkForm(1)"
+          >
+            审核通过
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, watch, defineProps, defineEmits } from 'vue'
+import {
+  ElRadio,
+  ElRadioGroup,
+  ElDialog,
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElNotification,
+  ElCascader,
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import cloneDeep from 'lodash.clonedeep'
+import { $t } from '@/i18n/index'
+import { saveExamine } from '@/api/product'
+defineComponent({
+  name: 'DialogExamineRecord',
+})
+const $emit = defineEmits(['update:visible'])
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  dataForExamine: {
+    type: Object,
+    default: () => {},
+  },
+  categoryData: {
+    type: Array,
+    default: () => [],
+  },
+})
+
+const prefix = 'order.product.'
+let show = ref(false)
+let loading = ref(false)
+const formRef = ref<FormInstance>()
+const form = ref({
+  product_name: '',
+  product_name_cn: '',
+  product_sku: '',
+  category_id: [],
+  images: '',
+  status: 1,
+} as any)
+
+const checkSKU = (rule: any, value: any, cb: any) => {
+  if (form.value.status !== 1) {
+    cb()
+  } else {
+    if (!value) {
+      cb(new Error($t('text_please_input')))
+    } else {
+      cb()
+    }
+  }
+}
+const rules = {
+  product_sku: [{ required: true, validator: checkSKU, trigger: 'change' }],
+  category_id: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+}
+
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    if (show.value) {
+      const temp: any = {
+        feedback: '',
+      }
+      if (typeof props.dataForExamine.category_id === 'number') {
+        temp.category_id = getCategory(props.dataForExamine.category_id)
+      }
+      form.value = Object.assign({}, cloneDeep(props.dataForExamine), temp)
+      // eslint-disable-next-line
+      if (form.value.status == 0) {
+        //  未审核状态直接选 审核通过, 其他两种情况不管
+        form.value.status = 1
+      }
+      delete form.value.admin_name
+      delete form.value.admin_id
+      delete form.value.create_time
+      delete form.value.update_time
+    }
+  },
+)
+
+const getCategory = (id: number | string) => {
+  const result: any[] = []
+  props.categoryData.forEach((first: any) => {
+    if (first.id === id) {
+      result.push(first.id)
+      return
+    }
+    if (first.children?.length) {
+      first.children.forEach((second: any) => {
+        if (second.id === id) {
+          result.push(first.id)
+          result.push(second.id)
+          return
+        }
+        if (second.children?.length) {
+          second.children.forEach((third: any) => {
+            if (third.id === id) {
+              result.push(first.id)
+              result.push(second.id)
+              result.push(third.id)
+              return
+            }
+          })
+        }
+      })
+    }
+  })
+
+  return result
+}
+/**
+ * flag 2 驳回. 其他通过.
+ */
+const checkForm = (flag: number) => {
+  if (!formRef.value) return
+  formRef.value.validate((valid) => {
+    if (valid) {
+      const p = cloneDeep(form.value)
+
+      p.category_id = p.category_id[p.category_id.length - 1].toString()
+      p.status = flag
+      p.product_id = p.id
+      delete p.product_name
+      delete p.product_name_cn
+      delete p.images
+
+      saveExamine(p).then((res: any) => {
+        if (res.code === 1) {
+          ElNotification({
+            title: '操作成功',
+            message: '正在刷新数据',
+            duration: 3000,
+          })
+          close()
+        }
+      })
+    }
+  })
+}
+let close = (done = {} as any) => {
+  $emit('update:visible', 0)
+  if (typeof done === 'function') done()
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 100 - 0
src/pages/indent-manage/product/components/record.vue

@@ -0,0 +1,100 @@
+<template>
+  <div class="">
+    <el-dialog
+      v-model="show"
+      :title="$t(prefix + 'dialog_title_record')"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :before-close="close"
+      width="550px"
+    >
+      <el-table
+        v-loading="loading"
+        :data="record"
+      >
+        <el-table-column
+          width="180"
+          :label="$t(prefix + 'label_audit_time')"
+          prop="create_time"
+        ></el-table-column>
+        <el-table-column
+          :label="$t(prefix + 'label_audit_operator')"
+          prop="admin_name"
+        ></el-table-column>
+        <el-table-column :label="$t(prefix + 'label_audit_result')">
+          <template #default="scope">
+            <div>{{ getStatusLabel(scope.row.status) }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          :label="$t(prefix + 'label_feedback')"
+          prop="feedback"
+        ></el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, watch } from 'vue'
+import { ElDialog, ElTable, ElTableColumn } from 'element-plus'
+import { getExamineRecord } from '@/api/product'
+import { $t } from '@/i18n/index'
+defineComponent({
+  name: 'DialogExamineRecord',
+})
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  id: {
+    type: [String, Number],
+    default: '',
+  },
+  statusList: {
+    type: Array,
+    default: () => [],
+  },
+})
+const $emit = defineEmits(['update:visible'])
+
+let show = ref(false)
+let loading = ref(false)
+let record = ref([] as any[])
+const prefix = 'order.product.'
+
+watch(
+  () => props.visible,
+  () => {
+    show.value = props.visible
+    if (show.value) getRecord
+  },
+)
+
+const getStatusLabel = (value: any) => {
+  const temp: any[] = props.statusList.filter((i: any) => i.value === value)
+  return temp.length ? temp[0].label : '-'
+}
+const getRecord = () => {
+  loading.value = true
+  getExamineRecord({ product_id: props.id })
+    .then((res: any) => {
+      if (res.code !== 1) {
+        return
+      }
+      console.log(res, 'examine record')
+      if (Array.isArray(res.result)) {
+        record.value = res.result
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+let close = (done = {} as any) => {
+  $emit('update:visible', 0)
+  if (typeof done === 'function') done()
+}
+</script>

+ 12 - 0
src/pages/indent-manage/product/index.vue

@@ -0,0 +1,12 @@
+<template>
+  <div>
+    <router-view />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent } from 'vue'
+defineComponent({
+  name: 'ProductIndex',
+})
+</script>

+ 390 - 0
src/pages/indent-manage/product/list.vue

@@ -0,0 +1,390 @@
+<template>
+  <div class="page-indent-list">
+    <el-form
+      ref="searchForm"
+      :inline="true"
+      :model="form"
+      label-width="110px"
+    >
+      <div class="flex flex-wrap items-center search-form">
+        <el-form-item :label="$t(prefix + 'label_project_name') + ':'">
+          <el-input v-model="form.name"></el-input>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_category') + ':'">
+          <el-cascader
+            v-model="form.category"
+            :options="categoryList"
+            :props="{ label: 'name', value: 'id' }"
+          ></el-cascader>
+        </el-form-item>
+
+        <el-form-item :label="$t(prefix + 'label_status') + ':'">
+          <el-select
+            v-model="form.status"
+            clearable
+          >
+            <el-option
+              v-for="item in statusList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <div class="flex items-center btn-group">
+          <el-button
+            size="small"
+            type="primary"
+            @click="search"
+          >
+            {{ $t('btn_query') }}
+          </el-button>
+          <el-button
+            size="small"
+            type="default"
+            @click="reset"
+          >
+            {{ $t('btn_reset') }}
+          </el-button>
+        </div>
+      </div>
+    </el-form>
+
+    <div style="margin-bottom: 12px">
+      <el-button
+        size="small"
+        type="primary"
+        @click="componentEditVisible = 1"
+      >
+        {{ $t('btn_add') }}
+      </el-button>
+    </div>
+
+    <el-table
+      ref="tableIndent"
+      :header-cell-style="{ backgroundColor: '#F2F6FC' }"
+      :row-style="{ backgroundColor: '#F2F6FC' }"
+      :data="list"
+      default-expand-all
+      border
+    >
+      <el-table-column type="index"></el-table-column>
+      <el-table-column
+        v-for="value in mainTableConfig as any[]"
+        :key="value.field"
+        :align="value.align || 'center'"
+        :width="value.width || ''"
+        :prop="value.field"
+        :label="$t(value.label)"
+      >
+        <template #default="scope">
+          <div v-if="value.type === 'imageList'">
+            <img
+              v-if="scope.row[value.field].length"
+              style="width: 100%"
+              :src="scope.row[value.field][0]"
+              alt=""
+            />
+          </div>
+          <div v-else-if="value.field === 'status'">
+            <div>
+              {{ getStatusLabel(scope.row[value.field]) }}
+            </div>
+            <el-button
+              size="small"
+              type="primary"
+              link
+              @click="openRecord(scope.row)"
+            >
+              {{ $t(prefix + 'label_audit_detail') }}
+            </el-button>
+          </div>
+          <div
+            v-else
+            class="table-cell-content"
+            :style="value.style || {}"
+          >
+            {{ scope.row[value.field] }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="status"
+        width="220"
+        :label="$t('table_operation')"
+      >
+        <template #default="scope">
+          <el-button
+            size="small"
+            type="primary"
+            :disabled="scope.row.status === 0"
+            @click="edit(scope.row)"
+          >
+            {{ $t('btn_edit') }}
+          </el-button>
+          <el-button
+            size="small"
+            type="warning"
+            :disabled="scope.row.status !== 0"
+            @click="examine(scope.row)"
+          >
+            {{ $t('btn_audit') }}
+          </el-button>
+          <el-button
+            :disabled="scope.row.status !== 2"
+            size="small"
+            type="danger"
+            @click="deleteProductFunc(scope.row)"
+          >
+            {{ $t('btn_delete') }}
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      v-show="total > 0"
+      v-model:page="pageForm.page"
+      v-model:limit="pageForm.limit"
+      :total="total"
+      @pagination="getList"
+    />
+    <comp-edit
+      v-model:visible="componentEditVisible"
+      :product-data="dataForEdit"
+      :category-data="categoryList"
+      @update:visible="getList"
+    ></comp-edit>
+    <comp-record
+      :id="recordId"
+      v-model:visible="componentRecordVisible"
+      :status-list="statusList"
+    ></comp-record>
+    <comp-examine
+      v-model:visible="componentExamineVisible"
+      :category-data="categoryList"
+      :data-for-examine="dataForEaxmine"
+      @update:visible="getList"
+    ></comp-examine>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent, ref } from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElTable,
+  ElTableColumn,
+  ElSelect,
+  ElOption,
+  ElMessageBox,
+  ElNotification,
+  ElPagination,
+  ElCascader,
+} from 'element-plus'
+import compEdit from './components/edit.vue'
+import compRecord from './components/record.vue'
+import compExamine from './components/examine.vue'
+import { $t } from '@/i18n/index'
+import { getProductList, deleteProduct } from '@/api/product.js'
+import { getCategoryTree } from '@/api/indent.js'
+defineComponent({
+  name: 'ComponentIndentProductList',
+})
+
+// 用来对oss的图片、视频等媒体数据的url进行匹配替换
+const $mediaRegExp = /^(https?:)?\/\/.+(.com.au\/|.com\/)/
+const prefix = 'order.product.'
+let componentEditVisible = ref(0) // 1 新增, 2编辑. 0关闭
+let componentRecordVisible = ref(false) // 审核记录
+let componentExamineVisible = ref(false) // 审核
+let dataForEaxmine = ref({})
+let dataForEdit = ref({}) // 修改时用, 只是用作传递给子组件数据的变量
+let recordId = ref('')
+let loading = ref(false)
+let categoryList = ref([])
+let form = ref({
+  name: '',
+  category: [],
+
+  status: '',
+} as any)
+let total = ref(0)
+let pageForm = ref({
+  page: 1,
+  limit: 20,
+})
+let list = ref([])
+const mainTableConfig = [
+  {
+    label: 'order.product.label_img',
+    type: 'imageList',
+    field: 'images',
+    width: '120',
+  },
+  {
+    label: 'order.product.label_product_code',
+    field: 'product_sku',
+    width: '180',
+  },
+  {
+    label: 'order.product.label_name',
+    field: 'product_name',
+    // style: 'text-align: left'
+  },
+  {
+    label: 'order.product.label_status',
+    field: 'status',
+    width: '110',
+    style: 'font-weight: bold;',
+  },
+  {
+    label: 'table_operator',
+    field: 'admin_name',
+    width: '90',
+  },
+  {
+    label: 'table_operated_time',
+    field: 'update_time',
+    width: '180',
+  },
+]
+const statusList = [
+  {
+    value: 0,
+    label: '待审核',
+  },
+  {
+    value: 1,
+    label: '审核通过',
+  },
+  {
+    value: 2,
+    label: '审核不通过',
+  },
+]
+
+const search = () => {
+  pageForm.value = {
+    page: 1,
+    limit: 20,
+  }
+  getList()
+}
+
+const getStatusLabel = (value: any) => {
+  const temp = statusList.filter((i: any) => i.value === value)
+  return temp.length ? temp[0].label : '-'
+}
+const reset = () => {
+  form.value = {
+    name: '',
+    category: [],
+    status: 0,
+  }
+  pageForm.value = {
+    page: 1,
+    limit: 20,
+  }
+  total.value = 0
+  getList()
+}
+
+const edit = (row: any) => {
+  dataForEdit.value = row
+  componentEditVisible.value = 2
+}
+const deleteProductFunc = (row: any) => {
+  ElMessageBox.confirm('将删除该商品, 是否继续?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    deleteProduct({ id: row.id }).then((res: any) => {
+      if (res.code === 1) {
+        getList()
+        ElNotification({
+          title: '删除成功',
+          message: '正在刷新数据',
+          duration: 3000,
+        })
+      }
+    })
+  })
+}
+const examine = (row: any) => {
+  dataForEaxmine.value = row
+  componentExamineVisible.value = true
+}
+const openRecord = (row: any) => {
+  recordId.value = row.id
+  componentRecordVisible.value = true
+}
+
+const getList = () => {
+  const p: any = Object.assign({}, pageForm.value)
+  const temp = { k: '', category: '', status: '' }
+  if (form.value.category.length) {
+    temp.category = form.value.category.join(',')
+  }
+  if (form.value.name?.length) {
+    temp.k = form.value.name.trim()
+  }
+  if (statusList.map((i) => i.value).includes(form.value.status)) {
+    temp.status = `${form.value.status}`
+  }
+  if (Object.keys(temp).length) {
+    p.searchParams = JSON.stringify(temp)
+  }
+  loading.value = true
+
+  getProductList(p)
+    .then((res: any) => {
+      list.value =
+        res.result?.data?.map((main: any) => {
+          const t = main.images.split(',').filter((i: string) => i.length > 0)
+          let status = Number(main.status)
+          if (typeof status !== 'number') status = 0
+          return {
+            ...main,
+            status,
+            images: t.map((i: string) => {
+              return $mediaRegExp.test(i)
+                ? i
+                : import.meta.env.VITE_APP_OSS_PREFIX + i
+            }),
+          }
+        }) || []
+
+      total.value = res.result.total
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+const getCategoryList = () => {
+  getCategoryTree().then((response: any) => {
+    const res = response.result
+    categoryList.value = res
+  })
+}
+getList()
+getCategoryList()
+</script>
+
+<style lang="scss" scoped>
+.search-form {
+  .el-input,
+  .el-select,
+  .el-date-editor {
+    width: 220px;
+  }
+  .btn-group {
+    margin: 0 0 22px 8px;
+  }
+}
+</style>

+ 37 - 20
src/route.ts

@@ -50,26 +50,43 @@ const router = createRouter({
       path: '/wecom-admin-portal',
       component: () => import('@/pages/wecom-approval/admin-portal.vue'),
     },
-    // {
-    //   name: 'indentManage',
-    //   path: '/indent-manage',
-    //   component: () => import('@/pages/indent-manage/index.vue'),
-    //   children: [
-    //     {
-    //       path: 'indent',
-    //       name: 'indent',
-    //       component: () => import('@/pages/indent-manage/indent/index.vue'),
-    //       children: [
-    //         {
-    //           path: 'list',
-    //           name: 'list',
-    //           component: () =>
-    //             import('@/pages/indent-manage/indent/list.vue'),
-    //         },
-    //       ],
-    //     },
-    //   ],
-    // },
+    {
+      name: 'indentManage',
+      path: '/indent-manage',
+      component: () => import('@/pages/indent-manage/index.vue'),
+      children: [
+        {
+          path: 'indent',
+          name: 'indent',
+          component: () => import('@/pages/indent-manage/indent/index.vue'),
+          children: [
+            {
+              path: 'list',
+              name: 'list',
+              component: () => import('@/pages/indent-manage/indent/list.vue'),
+            },
+          ],
+        },
+        {
+          path: 'product',
+          name: 'indent-product',
+          component: () => import('@/pages/indent-manage/product/index.vue'),
+          children: [
+            {
+              path: 'list',
+              name: 'indent-product-list',
+              component: () => import('@/pages/indent-manage/product/list.vue'),
+            },
+            {
+              
+              path: 'cagegoryList',
+              name: 'indent-category-list',
+              component: () => import('@/pages/indent-manage/product/category/index.vue'),
+            }
+          ],
+        },
+      ],
+    },
     {
       path: '/:pathMatch(.*)*',
       name: 'pageNotFound',

+ 61 - 0
src/utils/axios2.js

@@ -0,0 +1,61 @@
+import axios from 'axios'
+const request = axios.create({
+  baseURL: import.meta.env.VITE_API2_PREFIX,
+})
+import { ElNotification } from 'element-plus'
+// 用于中止web请求
+// const abortController = new AbortController()
+
+request.interceptors.request.use(
+  (config) => {
+    config.headers.Authorization =
+      'Bearer ' +
+      'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhZG1pbkAjXHVmZmU1JUAiLCJhdWQiOiIiLCJpYXQiOjE3MzAwODI4MzYsIm5iZiI6MTczMDA4MjgzOSwiZXhwIjoxNzMyNjc0ODM2LCJkYXRhIjp7InVpZCI6MTAxLCJ0eXBlIjoiYWRtaW4ifX0.s2g2vDJa8qAlZ-VL_NhNZNXKI1whIFnXG7ImnbO0nmc'
+    return config
+  },
+  (error) => {
+    return Promise.reject(error)
+  },
+)
+
+request.interceptors.response.use(
+  (response) => {
+    const { data } = response
+    // 状态码200,code=1 成功
+    if (data.code != 1) {
+      if (data instanceof Blob) {
+        // 文件流
+        return data
+      } else {
+        ElNotification({
+          message: data.msg,
+          type: 'error',
+          duration: 3 * 1000,
+        })
+      }
+
+      return Promise.reject(new Error(data || 'Error'))
+    } else {
+      return data
+    }
+  },
+  (error) => {
+    console.log('error: ', error.response)
+    const { data } = error.response
+    if (typeof data.msg === 'undefined') {
+      ElNotification({
+        message: 'Error:请联系管理员',
+        type: 'error',
+        duration: 5 * 1000,
+      })
+    }
+
+    if (axios.isCancel(error)) {
+      // 取消请求的情况下,终端Promise调用链
+      return new Promise(() => {})
+    } else {
+      return Promise.reject(error)
+    }
+  },
+)
+export default request

+ 6 - 1
vite.config.ts

@@ -8,13 +8,18 @@ export default defineConfig(({ mode }) => {
     base: mode === 'production' ? '/' : '/',
     server: {
       host: '0.0.0.0',
-      port: 9527,
+      port: 9528,
       proxy: {
         '/api': {
           target: 'http://zohocrm.promocollection.com.au:9007/',
           changeOrigin: true,
           rewrite: (path) => path.replace(/^\/api/, ''),
         },
+        '/bpi': {
+          target: 'http://api.promocollection.com.cn:9004',
+          changeOrigin: true,
+          rewrite: (path) => path.replace(/^\/bpi/, ''),
+        },
       },
     },
     resolve: {