xiaodai2017 2 years ago
parent
commit
85b1d686f8

+ 1 - 1
src/api/components/remoteSelect.ts

@@ -10,7 +10,7 @@ export async function httpBrandList(data: object): Promise<DataType<any>> {
 }
 
 export async function httpUnitList(data: object): Promise<DataType<any>> {
-  return http.request(`post`, `${VITE_PROXY_DOMAIN_REAL}admin/unitlist`, {
+  return http.request(`post`, `${VITE_PROXY_DOMAIN_REAL}admin/unitAll`, {
     data
   });
 }

+ 1 - 1
src/components/PageDetail/src/index.vue

@@ -17,7 +17,7 @@
 .page-detail__container {
   padding: 10px;
   max-width: calc(100vw - 54px);
-  min-width: calc(100vw - 210px);
+  // min-width: calc(100vw - 210px);
   background-color: #fff;
   margin: 0px !important;
   min-height: 100vh;

+ 2 - 2
src/components/RemoteSelect/src/search/Brand.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { ref } from "vue";
 import { useVModel } from "@vueuse/core";
-import { httpBrandList } from "/@/api/components/remoteSelect";
+import { httpList } from "/@/api/operate/setComGood";
 import RemoteSelect from "../remote-select";
 
 const props = defineProps<{
@@ -28,7 +28,7 @@ defineExpose({
 <template>
   <RemoteSelect
     v-model="value"
-    :api="httpBrandList"
+    :api="httpList"
     :placeholder="placeholder"
     ref="remoteSelectRef"
     response-label-prop="brand_name"

+ 2 - 2
src/components/RemoteSelect/src/search/Category.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { ref } from "vue";
 import { useVModel } from "@vueuse/core";
-import { httpCategorys } from "/@/api/components/remoteSelect";
+import { httpList } from "/@/api/operate/setComGood";
 import RemoteSelect from "../remote-select";
 
 const props = defineProps<{
@@ -28,7 +28,7 @@ defineExpose({
 <template>
   <RemoteSelect
     v-model="value"
-    :api="httpCategorys"
+    :api="httpList"
     :placeholder="placeholder"
     ref="remoteSelectRef"
     response-label-prop="search"

+ 1 - 1
src/components/RemoteSelect/src/search/Project.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { ref, watch } from "vue";
 import { useVModel } from "@vueuse/core";
-import { httpList } from "/@/api/sellOut/project";
+import { httpList } from "/@/api/operate/setComGood";
 import RemoteSelect from "../remote-select";
 
 const emit = defineEmits(["update:modelValue", "change"]);

+ 4 - 3
src/components/RemoteSelect/src/search/Unit.vue

@@ -31,9 +31,10 @@ defineExpose({
     :api="httpUnitList"
     :placeholder="placeholder"
     ref="remoteSelectRef"
-    response-label-prop="unit"
-    response-val-prop="id"
-    request-prop="unit"
+    response-label-prop="unit_title"
+    response-val-prop="unit_id"
+    request-prop="keyword"
+    :isRoot="true"
     @item-change="val => emit('change', val)"
     @inital="unit => (value = unit)"
   />

+ 3 - 53
src/components/UploadList/src/image.vue

@@ -46,52 +46,6 @@ const onBeforeImageUpload = ({ type, size }) => {
   }
   return true;
 };
-const dialogImageUrl = ref("");
-const dialogVisible = ref(false);
-const disabled = ref(false);
-// const url = ref("");
-// const handleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {
-//   console.log(uploadFile, uploadFiles);
-// };
-
-// const handlePictureCardPreview: UploadProps["onPreview"] = uploadFile => {
-//   dialogImageUrl.value = uploadFile.url!;
-//   dialogVisible.value = true;
-// };
-// const handleRequest: UploadProps["httpRequest"] = async ({ file }) => {
-//   const _formData = new FormData();
-//   _formData.append("image", file);
-//   _formData.append("token", token);
-
-//   const { message, code, data } = await httpImgUpload(_formData);
-
-//   responseHandle({
-//     message,
-//     code,
-//     handler: () => {
-//       url.value = baseUrl + data[0].url;
-//     }
-//   });
-// };
-// const handleSuccess: UploadProps["onSuccess"] = () => {
-//   console.log(url.value);
-//   imageUrl.value.push(url.value);
-//   emit("change", url.value);
-// };
-// const handleRemove = (file: UploadFile, index: number) => {
-//   console.log(file);
-//   console.log(index);
-// };
-
-// const handlePictureCardPreview = (file: UploadFile) => {
-//   dialogImageUrl.value = file.url!;
-//   dialogVisible.value = true;
-// };
-
-// const handleDownload = (file: UploadFile) => {
-//   console.log(file);
-// };
-
 function handleRemove(url: string) {
   const index = imageUrls.value.findIndex(u => u === url);
   if (index >= 0) {
@@ -180,15 +134,11 @@ const httpupLoad = (file, i): Promise<string> => {
       <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
     </li>
   </ul>
-
-  <el-dialog v-model="dialogVisible">
-    <img w-full :src="dialogImageUrl" alt="Preview Image" />
-  </el-dialog>
 </template>
 
 <style lang="scss" scoped>
 .image-upload-list {
-  --el-upload-list-picture-card-size: 148px;
+  --el-upload-list-picture-card-size: 65px;
   display: inline-flex;
   flex-wrap: wrap;
   margin: 0;
@@ -212,7 +162,7 @@ const httpupLoad = (file, i): Promise<string> => {
       display: inline-flex;
     }
     &.img-input {
-      --el-upload-picture-card-size: 100px;
+      --el-upload-picture-card-size: 65px;
       background-color: var(--el-fill-color-lighter);
       border: 1px dashed var(--el-border-color-darker);
       border-radius: 6px;
@@ -239,7 +189,7 @@ const httpupLoad = (file, i): Promise<string> => {
         top: 0;
         left: 0;
         z-index: 1;
-        font-size: 38px;
+        font-size: 28px;
         color: #8c939d;
         width: 100%;
         height: 100%;

+ 0 - 83
src/views/operate/setComGood/cpns/scheme-plan.vue

@@ -1,83 +0,0 @@
-<script setup lang="ts">
-import { computed } from "vue";
-import { useAsync } from "/@/hooks/useAsync";
-import { httpProjectFeedlist, httpProjectPlan } from "/@/api/sellOut/project";
-
-const props = defineProps<{
-  id: string;
-  feedback: Record<string, string>;
-  project: Record<string, string>;
-}>();
-
-const { run: feed, data: feedData } = useAsync<Record<string, string>[]>({
-  initalData: [],
-  isList: true
-});
-
-const { run: plan, data: planData } = useAsync<Record<string, string>[]>({
-  initalData: []
-});
-
-const planTotal = computed(() => planData.value.length);
-
-function initalData() {
-  const projectNo = props.id;
-
-  if (!projectNo) {
-    return;
-  }
-
-  feed(httpProjectFeedlist({ projectNo }));
-  plan(httpProjectPlan({ projectNo }));
-}
-
-initalData();
-</script>
-
-<template>
-  <ElCard title="商品要求">
-    <h1 class="mb-2">商品要求</h1>
-    <ElTable class="mb-2" :data="project?.ladder || []" border size="small">
-      <ElTableColumn label="要求编码" prop="pgNo" />
-      <ElTableColumn label="商品类型" prop="good_type" />
-      <ElTableColumn label="预算单价" prop="budget_price" />
-      <ElTableColumn label="购买数量" prop="num" />
-      <ElTableColumn label="商品分类" prop="can" />
-      <ElTableColumn label="图片" prop="good_img" />
-      <ElTableColumn label="商品名称" prop="good_name" />
-    </ElTable>
-
-    <h1 class="mb-2">客户意向商品</h1>
-    <ElTable class="mb-2" :data="[]" border size="small">
-      <ElTableColumn label="要求编码" prop="pgNo" />
-      <ElTableColumn label="销售单价" prop="pgNo" />
-      <ElTableColumn label="购买数量" prop="pgNo" />
-      <ElTableColumn label="商品图片" prop="pgNo" />
-      <ElTableColumn label="商品名称" prop="pgNo" />
-      <ElTableColumn label="商品来源" prop="pgNo" />
-      <ElTableColumn label="商品分类" prop="pgNo" />
-      <ElTableColumn label="信息有效期" prop="pgNo" />
-      <ElTableColumn label="制作工期" prop="pgNo" />
-      <ElTableColumn label="物流时间" prop="pgNo" />
-    </ElTable>
-
-    <div class="w-full flex justify-end">
-      <ElButtonGroup size="small">
-        <ElButton type="primary">&lt;</ElButton>
-        <ElButton type="primary">方案共1/{{ planTotal }}</ElButton>
-        <ElButton type="primary">&gt;</ElButton>
-      </ElButtonGroup>
-    </div>
-
-    <h1 class="mb-2">项目方案</h1>
-    <ElTable class="mb-2" :data="feedData" border size="small">
-      <ElTableColumn label="要求编码" prop="pgNo" />
-      <ElTableColumn label="商品类型" prop="pgNo" />
-      <ElTableColumn label="预算单价" prop="pgNo" />
-      <ElTableColumn label="购买数量" prop="pgNo" />
-      <ElTableColumn label="商品分类" prop="pgNo" />
-      <ElTableColumn label="图片" prop="pgNo" />
-      <ElTableColumn label="商品名称" prop="pgNo" />
-    </ElTable>
-  </ElCard>
-</template>

+ 495 - 0
src/views/parameter/good/config copy/_details.ts

@@ -0,0 +1,495 @@
+/* eslint-disable prettier/prettier */
+import { h } from "vue";
+import { ElImage, ElTag } from "element-plus";
+import { DescriptionColumns } from "/@/components/BasicDescriptions";
+import { FormConfig } from "/@/components/PageSearch";
+import { createTooltip } from "/@/utils/tooltip";
+// import { payWayOptions, sendWayOptions, supplyAreaOptions } from "./_options";
+
+export const projectFormConfig: FormConfig = {
+  labelWidth: "100px",
+  formItems: [
+    {
+      label: "项目名称",
+      field: "name",
+      type: "input",
+      placeholder: "项目名称",
+      span: 12
+    },
+    {
+      label: "所属平台",
+      field: "platform",
+      placeholder: "客户公司名称",
+      span: 12,
+      slot: "platform"
+    },
+    {
+      label: "销售方",
+      field: "companyNo",
+      placeholder: "销售方",
+      slot: "companyNo",
+      span: 12
+    },
+    // {
+    //   label: "购买方",
+    //   field: "khNo",
+    //   slot: "khNo",
+    //   placeholder: "购买方",
+    //   span: 12
+    // },
+    {
+      label: "项目总预算",
+      field: "budget_total",
+      slot: "budget_total",
+      placeholder: "项目总预算",
+      span: 8
+    },
+    {
+      label: "要求到货时间",
+      field: "arrtime",
+      slot: "arrtime",
+      span: 8,
+      placeholder: "要求到货时间",
+      otherOptions: {
+        disabledDate(date) {
+          return date.getTime() < new Date().getTime() - 86400000;
+        }
+      }
+    },
+    {
+      label: "竞价截止时间",
+      field: "endtime",
+      placeholder: "竞价截止时间",
+      slot: "endtime",
+      span: 8,
+      otherOptions: {
+        disabledDate(date) {
+          return date.getTime() < new Date().getTime() - 86400000;
+        }
+      }
+    },
+    {
+      label: "项目用途",
+      field: "use_desc",
+      type: "input",
+      placeholder: "项目用途",
+      span: 24
+    },
+    {
+      label: "项目要求",
+      field: "ladder",
+      slot: "ladder",
+      span: 24
+    }
+  ]
+};
+
+//平台商品详情字段
+export const platformGoodColumns: DescriptionColumns = [
+  {
+    field: "good_name",
+    label: "商品名称",
+    span: 24,
+    render: (goodname, { skuCode, speclist }) => {
+      const goodName = speclist
+        ? speclist
+            .map(({ spec_name, spec_value }) => `${spec_name}[${spec_value}]`)
+            .join("_")
+        : "";
+
+      return createTooltip(
+        goodname + "_" + goodName,
+        "商品编号: " + skuCode,
+        360
+      );
+    }
+  },
+  {
+    field: "good_img",
+    label: "商品主图",
+    span: 12,
+    render: good_img => {
+      const previewSrcList = good_img ? good_img.split(",") : [];
+      return previewSrcList.map(src =>
+        h(ElImage, {
+          previewSrcList: [src],
+          src,
+          previewTeleported: true,
+          style: {
+            height: "20px",
+            width: "20px"
+          }
+        })
+      );
+    }
+  },
+  {
+    field: "good_info_img",
+    label: "商品详情图",
+    span: 12,
+    render: good_info_img => {
+      return h(ElImage, {
+        previewSrcList: [good_info_img],
+        src: good_info_img,
+        previewTeleported: true,
+        style: {
+          height: "20px",
+          width: "20px"
+        }
+      });
+    }
+  },
+  {
+    field: "company",
+    label: "业务企业名称",
+    span: 8,
+    render: (company, { companyNo }) =>
+      createTooltip(company, "业务企业编号: " + companyNo, 360)
+  },
+  {
+    field: "platform_name",
+    label: "所属平台",
+    span: 8
+  },
+  {
+    field: "brand_name",
+    label: "品牌",
+    span: 8
+  },
+  {
+    field: "cat_info",
+    label: "分类",
+    span: 8,
+    render(cat_info) {
+      return cat_info ? cat_info.map(({ name }) => name).join("_") : "";
+    }
+  },
+  {
+    field: "unit",
+    span: 8,
+    label: "单位"
+  },
+  {
+    field: "is_stock",
+    label: "是否库存品",
+    span: 6,
+    render(is_stock) {
+      return h(ElTag, null, {
+        default: () => (is_stock !== "0" ? "库存品" : "非库存品")
+      });
+    }
+  },
+  // {
+  //   field: "supply_area",
+  //   label: "供货区域",
+  //   span: 6,
+  //   render(supply_area) {
+  //     return h(ElTag, null, {
+  //       default: () =>
+  //         supplyAreaOptions.find(({ id }) => supply_area === id)?.label || "--"
+  //     });
+  //   }
+  // },
+  {
+    field: "good_type",
+    label: "是否定制",
+    span: 8,
+    render(good_type) {
+      return h(ElTag, null, {
+        default: () => (good_type !== "0" ? "是" : "否")
+      });
+    }
+  },
+  {
+    field: "tax",
+    label: "税点",
+    append: "%",
+    span: 4
+  },
+  {
+    field: "delivery_day",
+    label: "物流时间",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "lead_time",
+    label: "供货周期",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "sample_day",
+    label: "调样周期",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "exclusive",
+    label: "专属类型",
+    span: 6,
+    render(exclusive) {
+      return exclusive ? exclusive.map(({ name }) => name).join("/") : "";
+    }
+  },
+  {
+    field: "weight",
+    label: "商品总克重",
+    append: "g",
+    span: 6
+  },
+  {
+    field: "noble",
+    label: "贵金属信息",
+    render(
+      _,
+      {
+        noble_weight,
+        noble_metal,
+        noble_name,
+        gold_price,
+        is_gold_price,
+        is_diff,
+        config,
+        other_config
+      }
+    ) {
+      if (!noble_metal) {
+        return "";
+      }
+
+      const weight = noble_weight ? `${noble_weight}g-${noble_name}` : "";
+
+      const price = gold_price ? gold_price : "0";
+
+      const isGoldPrice = is_gold_price === "0" ? "不" : "";
+
+      const diff = is_diff === "1" ? "有" : "无";
+
+      return `${weight}${price}元/g-${isGoldPrice}启用实时金价-${diff}工差-${config}-${other_config}`;
+    }
+  },
+  {
+    field: "delivery_place_cn",
+    label: "发货地",
+    span: 8
+  },
+  {
+    field: "customized",
+    label: "工期",
+    span: 8
+  },
+  {
+    field: "cgder",
+    label: "供应商负责人",
+    span: 4
+  },
+  {
+    field: "good_creater",
+    label: "商品创建人",
+    span: 4
+  },
+  {
+    field: "after_sales",
+    label: "售后说明",
+    span: 24
+  },
+  {
+    field: "good_remark",
+    label: "商品备注",
+    span: 24
+  },
+
+  {
+    field: "craft_desc",
+    label: "工艺说明",
+    span: 24
+  }
+];
+
+//采反商品详情字段
+export const commodityFeedbackColumns: DescriptionColumns = [
+  {
+    field: "good_name",
+    label: "商品名称",
+    span: 24,
+    render: (goodname, { spuCode, speclist }) => {
+      const goodName = speclist
+        ? speclist
+            .map(({ spec_name, spec_value }) => `${spec_name}[${spec_value}]`)
+            .join("_")
+        : "";
+
+      return createTooltip(
+        goodname + "_" + goodName,
+        "上线商品编号: " + spuCode,
+        360
+      );
+    }
+  },
+
+  {
+    field: "cat_info",
+    label: "分类",
+    span: 8,
+    render(cat_info) {
+      return cat_info ? cat_info.map(({ name }) => name).join("_") : "";
+    }
+  },
+
+  {
+    field: "brand_name",
+    label: "品牌",
+    span: 8
+  },
+
+  // {
+  //   field: "send_way",
+  //   label: "发货方式",
+  //   span: 8,
+  //   render(send_way) {
+  //     return h(ElTag, null, {
+  //       default: () =>
+  //         sendWayOptions.find(({ id }) => send_way === id)?.label || "--"
+  //     });
+  //   }
+  // },
+  {
+    field: "unit",
+    label: "单位",
+    span: 4
+  },
+  {
+    field: "tax",
+    label: "税点",
+    append: "%",
+    span: 4
+  },
+  // {
+  //   field: "pay_way",
+  //   label: "付款方式",
+  //   span: 4,
+  //   render(pay_way) {
+  //     return h(ElTag, null, {
+  //       default: () =>
+  //         payWayOptions.find(({ id }) => pay_way === id)?.label || "--"
+  //     });
+  //   }
+  // },
+
+  {
+    field: "weight",
+    label: "商品总克重",
+    append: "g",
+    span: 6
+  },
+
+  {
+    field: "addtime",
+    label: "反馈时间",
+    span: 6
+  },
+  {
+    field: "expire_day",
+    label: "竞价有效期",
+    append: "天",
+    span: 4
+  },
+
+  {
+    field: "delivery_day",
+    label: "物流时间",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "work_day",
+    label: "生产工期",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "cgder",
+    label: "供应商负责人",
+    span: 4
+  },
+  {
+    field: "creater",
+    label: "商品创建人",
+    span: 4
+  },
+  {
+    field: "supply_area",
+    label: "供货区域",
+    span: 4,
+    render(supply_area) {
+      return h(ElTag, null, {
+        default: () =>
+          supplyAreaOptions.find(({ id }) => supply_area === id)?.label || "--"
+      });
+    }
+  },
+
+  {
+    field: "good_img",
+    label: "商品图片",
+    span: 24,
+    render: good_img => {
+      const previewSrcList = good_img ? good_img.split(",") : [];
+      return previewSrcList.map(src =>
+        h(ElImage, {
+          previewSrcList: [src],
+          src,
+          previewTeleported: true,
+          style: {
+            height: "20px",
+            width: "20px"
+          }
+        })
+      );
+    }
+  },
+  {
+    field: "noble",
+    label: "贵金属信息",
+    span: 24,
+    render(
+      _,
+      {
+        noble_weight,
+        noble_metal,
+        noble_name,
+        gold_price,
+        is_gold_price,
+        is_diff,
+        config,
+        other_config
+      }
+    ) {
+      if (!noble_metal) {
+        return "--";
+      }
+
+      const weight = noble_weight ? `${noble_weight}g-${noble_name}` : "";
+
+      const price = gold_price ? gold_price : "0";
+
+      const isGoldPrice = is_gold_price === "0" ? "不" : "";
+
+      const diff = is_diff === "1" ? "有" : "无";
+
+      return `${weight}${price}元/g-${isGoldPrice}启用实时金价-${diff}工差-${config}-${other_config}`;
+    }
+  },
+
+  {
+    field: "cost_desc",
+    label: "工艺说明",
+    span: 24
+  },
+  {
+    field: "remark",
+    label: "采返备注",
+    span: 24
+  }
+];

+ 81 - 0
src/views/parameter/good/config copy/_rules.ts

@@ -0,0 +1,81 @@
+import { FormRules } from "element-plus";
+
+export const ladderFormRules: FormRules = {
+  budget_price: {
+    trigger: "change",
+    required: true,
+    validator(_, value) {
+      if (value === "") return new Error("请输入预算单价");
+      if (Number(value) === 0) return new Error("预算单价不能为零!");
+      return true;
+    }
+  },
+  num: {
+    trigger: "change",
+    required: true,
+    message: "请输入购买数量",
+    validator(_, value) {
+      if (value === "") return new Error("请输入购买数量");
+      if (Number(value) === 0) return new Error("购买数量不能为零!");
+      return true;
+    }
+  },
+  good_type: {
+    trigger: "change",
+    required: true,
+    message: "请输入商品类型"
+  },
+  cat_id: {
+    trigger: "change",
+    required: true,
+    message: "请输入商品分类"
+  }
+};
+
+export const projectFormRules: FormRules = {
+  name: {
+    trigger: "change",
+    required: true,
+    message: "请输入项目名称"
+  },
+  platform: {
+    trigger: "change",
+    required: true,
+    message: "请输入客户公司名称"
+  },
+  companyNo: {
+    trigger: "change",
+    required: true,
+    message: "请输入销售方"
+  },
+  khNo: {
+    trigger: "change",
+    required: true,
+    message: "请输入购买方"
+  },
+  budget_total: {
+    trigger: "change",
+    required: true,
+    message: "请输入项目总预算"
+  },
+  arrtime: {
+    trigger: "change",
+    required: true,
+    message: "请输入要求到货时间"
+  },
+  endtime: {
+    trigger: "change",
+    required: true,
+    message: "请输入竞价截止时间"
+  },
+  use_desc: {
+    trigger: "change",
+    required: true,
+    message: "请输入项目用途"
+  },
+  ladder: {
+    trigger: "change",
+    required: true,
+    message: "请选择项目要求"
+  }
+};

+ 67 - 0
src/views/parameter/good/config copy/content.config.ts

@@ -0,0 +1,67 @@
+import { ContentConfig } from "/@/components/PageContent";
+import {
+  httpList,
+  httpAdd,
+  httpUpdate,
+  httpStatus,
+  httpDelete
+} from "/@/api/parameter/good";
+import { renderStatus } from "/@/utils/column-helper";
+import { STATUS_OPTIONS } from "/@/config/status";
+
+const columns = [
+  {
+    type: "selection",
+    width: 55,
+    hide: ({ checkList }) => !checkList.includes("勾选列")
+  },
+
+  {
+    prop: "id",
+    label: "ID"
+  },
+  {
+    prop: "title",
+    label: "卡类型"
+  },
+
+  {
+    prop: "status",
+    label: "状态",
+    ...renderStatus(STATUS_OPTIONS)
+  },
+  {
+    prop: "contacts",
+    label: "联系人"
+  },
+  {
+    prop: "mobile",
+    label: "联系方式"
+  },
+  {
+    prop: "addtime",
+    label: "创建时间",
+    sortable: true
+  },
+
+  {
+    label: "操作",
+    fixed: "right",
+    width: 160,
+    slot: "operation"
+  }
+];
+
+const contentConfig: ContentConfig = {
+  title: "商品管理",
+  columns,
+  apis: {
+    httpAdd,
+    httpList,
+    httpUpdate,
+    httpStatus,
+    httpDelete
+  }
+};
+
+export default contentConfig;

+ 100 - 0
src/views/parameter/good/config copy/modal.config.ts

@@ -0,0 +1,100 @@
+import { ModalConfig } from "/@/components/PageModal/src/types";
+import { GOOD_OPTIONS } from "/@/config/status";
+const modalConfig: ModalConfig = {
+  title: "商品",
+  colLayout: { span: 24 },
+  itemStyle: {},
+  contact: "good",
+  labelWidth: "85px",
+  formItems: [
+    {
+      field: "name",
+      type: "input",
+      label: "商品名称",
+      placeholder: "商品名称",
+      span: 24,
+      rules: [{ required: true, trigger: "blur", message: "请输入商品名称" }]
+    },
+    {
+      field: "type",
+      type: "select",
+      label: "商品类型",
+      placeholder: "商品类型",
+      span: 8,
+      options: GOOD_OPTIONS,
+      rules: [{ required: true, trigger: "change", message: "请选择商品类型" }]
+    },
+    {
+      field: "unit",
+      type: "input",
+      label: "单位",
+      placeholder: "单位",
+      span: 8,
+      rules: [{ required: true, trigger: "blur", message: "请输入步长" }]
+    },
+    {
+      field: "price",
+      type: "number",
+      label: "售价",
+      placeholder: "售价",
+      span: 8,
+      rules: [{ required: true, trigger: "blur", message: "请输入售价" }]
+    },
+    {
+      field: "img1",
+      type: "img_upload",
+      label: "封面图",
+      placeholder: "封面图",
+      span: 8,
+      rules: [{ required: true, trigger: "blur", message: "请输入备注" }]
+    },
+    {
+      field: "a",
+      type: "number",
+      label: "起订量",
+      placeholder: "起订量",
+      span: 8,
+      rules: [{ required: true, trigger: "blur", message: "请输入起订量" }]
+    },
+    {
+      field: "b",
+      type: "number",
+      label: "步长",
+      placeholder: "步长",
+      span: 8,
+      rules: [{ required: true, trigger: "blur", message: "请输入步长" }]
+    },
+
+    {
+      field: "img2",
+      type: "img_upload",
+      label: "轮播图",
+      placeholder: "轮播图",
+      span: 16,
+      rules: [{ required: true, trigger: "blur", message: "请输入备注" }]
+    },
+    {
+      field: "img3",
+      type: "img_upload_list",
+      label: "详情图",
+      placeholder: "详情图",
+      rules: [{ required: true, trigger: "blur", message: "请输入备注" }]
+    },
+    {
+      field: "arr",
+      type: "input",
+      label: "产品参数",
+      placeholder: "产品参数",
+      rules: [{ required: true, trigger: "blur", message: "请输入备注" }]
+    },
+    {
+      field: "remark",
+      type: "textarea",
+      label: "备注",
+      placeholder: "备注",
+      rules: [{ required: false, trigger: "blur", message: "请输入备注" }]
+    }
+  ]
+};
+
+export default modalConfig;

+ 31 - 0
src/views/parameter/good/config copy/search.config.ts

@@ -0,0 +1,31 @@
+import { FormConfig } from "/@/components/PageSearch";
+import { STATUS_OPTIONS } from "/@/config/status";
+import { convertOptions } from "/@/utils/column-helper";
+
+const searchFormConfig: FormConfig = {
+  formItems: [
+    {
+      field: "status",
+      type: "select",
+      placeholder: "状态",
+      options: convertOptions(STATUS_OPTIONS)
+    },
+    {
+      field: "title",
+      type: "input",
+      placeholder: "企业名称"
+    },
+    {
+      field: "contacts",
+      type: "input",
+      placeholder: "联系人"
+    },
+    {
+      field: "mobile",
+      type: "input",
+      placeholder: "联系方式"
+    }
+  ]
+};
+
+export default searchFormConfig;

+ 504 - 0
src/views/parameter/good/config/_details.ts

@@ -0,0 +1,504 @@
+/* eslint-disable prettier/prettier */
+import { h } from "vue";
+import { ElImage, ElTag } from "element-plus";
+import { DescriptionColumns } from "/@/components/BasicDescriptions";
+import { FormConfig } from "/@/components/PageSearch";
+import { createTooltip } from "/@/utils/tooltip";
+import { GOOD_OPTIONS } from "/@/config/status";
+// import { payWayOptions, sendWayOptions, supplyAreaOptions } from "./_options";
+
+export const projectFormConfig: FormConfig = {
+  labelWidth: "100px",
+  formItems: [
+    {
+      label: "商品名称",
+      field: "good_name",
+      type: "input",
+      placeholder: "商品名称",
+      span: 24
+    },
+    {
+      label: "封面图",
+      field: "good_cover_img",
+      type: "img_upload",
+      placeholder: "封面图",
+      span: 8
+    },
+    {
+      label: "商品类型",
+      field: "type",
+      type: "select",
+      options: GOOD_OPTIONS,
+      placeholder: "商品类型",
+      span: 8
+    },
+    {
+      label: "单位",
+      field: "unit_id",
+      placeholder: "单位",
+      span: 8,
+      slot: "unit_id"
+    },
+
+    {
+      label: "起订量",
+      field: "moq",
+      type: "number",
+      placeholder: "起订量",
+      span: 8
+    },
+    {
+      label: "步长",
+      field: "step",
+      type: "number",
+      placeholder: "步长",
+      span: 8
+    },
+    {
+      label: "售价",
+      field: "price",
+      type: "input",
+      placeholder: "售价",
+      span: 8
+    },
+    {
+      label: "轮播图",
+      field: "good_banner_img",
+      slot: "good_banner_img",
+      placeholder: "轮播图",
+      span: 24
+    },
+    {
+      label: "详情图",
+      field: "good_img",
+      slot: "good_img",
+      placeholder: "详情图",
+      span: 24
+    },
+    {
+      label: "商品参数",
+      field: "good_param",
+      placeholder: "商品参数",
+      slot: "good_param",
+      span: 24
+    },
+
+    {
+      label: "备注",
+      field: "good_remark",
+      type: "textarea",
+      placeholder: "备注",
+      span: 24
+    }
+  ]
+};
+
+//平台商品详情字段
+export const platformGoodColumns: DescriptionColumns = [
+  {
+    field: "good_name",
+    label: "商品名称",
+    span: 24,
+    render: (goodname, { skuCode, speclist }) => {
+      const goodName = speclist
+        ? speclist
+            .map(({ spec_name, spec_value }) => `${spec_name}[${spec_value}]`)
+            .join("_")
+        : "";
+
+      return createTooltip(
+        goodname + "_" + goodName,
+        "商品编号: " + skuCode,
+        360
+      );
+    }
+  },
+  {
+    field: "good_img",
+    label: "商品主图",
+    span: 12,
+    render: good_img => {
+      const previewSrcList = good_img ? good_img.split(",") : [];
+      return previewSrcList.map(src =>
+        h(ElImage, {
+          previewSrcList: [src],
+          src,
+          previewTeleported: true,
+          style: {
+            height: "20px",
+            width: "20px"
+          }
+        })
+      );
+    }
+  },
+  {
+    field: "good_info_img",
+    label: "商品详情图",
+    span: 12,
+    render: good_info_img => {
+      return h(ElImage, {
+        previewSrcList: [good_info_img],
+        src: good_info_img,
+        previewTeleported: true,
+        style: {
+          height: "20px",
+          width: "20px"
+        }
+      });
+    }
+  },
+  {
+    field: "company",
+    label: "业务企业名称",
+    span: 8,
+    render: (company, { companyNo }) =>
+      createTooltip(company, "业务企业编号: " + companyNo, 360)
+  },
+  {
+    field: "platform_name",
+    label: "所属平台",
+    span: 8
+  },
+  {
+    field: "brand_name",
+    label: "品牌",
+    span: 8
+  },
+  {
+    field: "cat_info",
+    label: "分类",
+    span: 8,
+    render(cat_info) {
+      return cat_info ? cat_info.map(({ name }) => name).join("_") : "";
+    }
+  },
+  {
+    field: "unit",
+    span: 8,
+    label: "单位"
+  },
+  {
+    field: "is_stock",
+    label: "是否库存品",
+    span: 6,
+    render(is_stock) {
+      return h(ElTag, null, {
+        default: () => (is_stock !== "0" ? "库存品" : "非库存品")
+      });
+    }
+  },
+  // {
+  //   field: "supply_area",
+  //   label: "供货区域",
+  //   span: 6,
+  //   render(supply_area) {
+  //     return h(ElTag, null, {
+  //       default: () =>
+  //         supplyAreaOptions.find(({ id }) => supply_area === id)?.label || "--"
+  //     });
+  //   }
+  // },
+  {
+    field: "good_type",
+    label: "是否定制",
+    span: 8,
+    render(good_type) {
+      return h(ElTag, null, {
+        default: () => (good_type !== "0" ? "是" : "否")
+      });
+    }
+  },
+  {
+    field: "tax",
+    label: "税点",
+    append: "%",
+    span: 4
+  },
+  {
+    field: "delivery_day",
+    label: "物流时间",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "lead_time",
+    label: "供货周期",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "sample_day",
+    label: "调样周期",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "exclusive",
+    label: "专属类型",
+    span: 6,
+    render(exclusive) {
+      return exclusive ? exclusive.map(({ name }) => name).join("/") : "";
+    }
+  },
+  {
+    field: "weight",
+    label: "商品总克重",
+    append: "g",
+    span: 6
+  },
+  {
+    field: "noble",
+    label: "贵金属信息",
+    render(
+      _,
+      {
+        noble_weight,
+        noble_metal,
+        noble_name,
+        gold_price,
+        is_gold_price,
+        is_diff,
+        config,
+        other_config
+      }
+    ) {
+      if (!noble_metal) {
+        return "";
+      }
+
+      const weight = noble_weight ? `${noble_weight}g-${noble_name}` : "";
+
+      const price = gold_price ? gold_price : "0";
+
+      const isGoldPrice = is_gold_price === "0" ? "不" : "";
+
+      const diff = is_diff === "1" ? "有" : "无";
+
+      return `${weight}${price}元/g-${isGoldPrice}启用实时金价-${diff}工差-${config}-${other_config}`;
+    }
+  },
+  {
+    field: "delivery_place_cn",
+    label: "发货地",
+    span: 8
+  },
+  {
+    field: "customized",
+    label: "工期",
+    span: 8
+  },
+  {
+    field: "cgder",
+    label: "供应商负责人",
+    span: 4
+  },
+  {
+    field: "good_creater",
+    label: "商品创建人",
+    span: 4
+  },
+  {
+    field: "after_sales",
+    label: "售后说明",
+    span: 24
+  },
+  {
+    field: "good_remark",
+    label: "商品备注",
+    span: 24
+  },
+
+  {
+    field: "craft_desc",
+    label: "工艺说明",
+    span: 24
+  }
+];
+
+//采反商品详情字段
+export const commodityFeedbackColumns: DescriptionColumns = [
+  {
+    field: "good_name",
+    label: "商品名称",
+    span: 24,
+    render: (goodname, { spuCode, speclist }) => {
+      const goodName = speclist
+        ? speclist
+            .map(({ spec_name, spec_value }) => `${spec_name}[${spec_value}]`)
+            .join("_")
+        : "";
+
+      return createTooltip(
+        goodname + "_" + goodName,
+        "上线商品编号: " + spuCode,
+        360
+      );
+    }
+  },
+
+  {
+    field: "cat_info",
+    label: "分类",
+    span: 8,
+    render(cat_info) {
+      return cat_info ? cat_info.map(({ name }) => name).join("_") : "";
+    }
+  },
+
+  {
+    field: "brand_name",
+    label: "品牌",
+    span: 8
+  },
+
+  // {
+  //   field: "send_way",
+  //   label: "发货方式",
+  //   span: 8,
+  //   render(send_way) {
+  //     return h(ElTag, null, {
+  //       default: () =>
+  //         sendWayOptions.find(({ id }) => send_way === id)?.label || "--"
+  //     });
+  //   }
+  // },
+  {
+    field: "unit",
+    label: "单位",
+    span: 4
+  },
+  {
+    field: "tax",
+    label: "税点",
+    append: "%",
+    span: 4
+  },
+  // {
+  //   field: "pay_way",
+  //   label: "付款方式",
+  //   span: 4,
+  //   render(pay_way) {
+  //     return h(ElTag, null, {
+  //       default: () =>
+  //         payWayOptions.find(({ id }) => pay_way === id)?.label || "--"
+  //     });
+  //   }
+  // },
+
+  {
+    field: "weight",
+    label: "商品总克重",
+    append: "g",
+    span: 6
+  },
+
+  {
+    field: "addtime",
+    label: "反馈时间",
+    span: 6
+  },
+  {
+    field: "expire_day",
+    label: "竞价有效期",
+    append: "天",
+    span: 4
+  },
+
+  {
+    field: "delivery_day",
+    label: "物流时间",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "work_day",
+    label: "生产工期",
+    append: "天",
+    span: 4
+  },
+  {
+    field: "cgder",
+    label: "供应商负责人",
+    span: 4
+  },
+  {
+    field: "creater",
+    label: "商品创建人",
+    span: 4
+  },
+  {
+    field: "supply_area",
+    label: "供货区域",
+    span: 4,
+    render(supply_area) {
+      return h(ElTag, null, {
+        default: () =>
+          supplyAreaOptions.find(({ id }) => supply_area === id)?.label || "--"
+      });
+    }
+  },
+
+  {
+    field: "good_img",
+    label: "商品图片",
+    span: 24,
+    render: good_img => {
+      const previewSrcList = good_img ? good_img.split(",") : [];
+      return previewSrcList.map(src =>
+        h(ElImage, {
+          previewSrcList: [src],
+          src,
+          previewTeleported: true,
+          style: {
+            height: "20px",
+            width: "20px"
+          }
+        })
+      );
+    }
+  },
+  {
+    field: "noble",
+    label: "贵金属信息",
+    span: 24,
+    render(
+      _,
+      {
+        noble_weight,
+        noble_metal,
+        noble_name,
+        gold_price,
+        is_gold_price,
+        is_diff,
+        config,
+        other_config
+      }
+    ) {
+      if (!noble_metal) {
+        return "--";
+      }
+
+      const weight = noble_weight ? `${noble_weight}g-${noble_name}` : "";
+
+      const price = gold_price ? gold_price : "0";
+
+      const isGoldPrice = is_gold_price === "0" ? "不" : "";
+
+      const diff = is_diff === "1" ? "有" : "无";
+
+      return `${weight}${price}元/g-${isGoldPrice}启用实时金价-${diff}工差-${config}-${other_config}`;
+    }
+  },
+
+  {
+    field: "cost_desc",
+    label: "工艺说明",
+    span: 24
+  },
+  {
+    field: "remark",
+    label: "采返备注",
+    span: 24
+  }
+];

+ 123 - 0
src/views/parameter/good/config/_rules.ts

@@ -0,0 +1,123 @@
+import { number } from "echarts";
+import { FormRules } from "element-plus";
+
+export const ladderFormRules: FormRules = {
+  good_name: {
+    trigger: "change",
+    required: true,
+    validator(_, value) {
+      if (value ?? "" === "") return new Error("请输入商品名称");
+      return true;
+    }
+  },
+  good_cover_img: {
+    trigger: "change",
+    required: true,
+    message: "请上传封面图",
+    validator(_, value) {
+      if (value ?? "" === "") return new Error("请上传封面图");
+      return true;
+    }
+  },
+  type: {
+    trigger: "change",
+    required: true,
+    message: "请选择商品类型"
+  },
+  unit_id: {
+    trigger: "change",
+    required: true,
+    message: "请选择单位"
+  }
+};
+
+export const projectFormRules: FormRules = {
+  good_name: {
+    trigger: "change",
+    required: true,
+    validator(_, value) {
+      if (value ?? "" === "") return new Error("请输入商品名称");
+      return true;
+    }
+  },
+  good_cover_img: {
+    trigger: "change",
+    required: true,
+    message: "请上传封面图",
+    validator(_, value) {
+      if (value ?? "" === "") return new Error("请上传封面图");
+      return true;
+    }
+  },
+  type: {
+    trigger: "change",
+    required: true,
+    message: "请选择商品类型"
+  },
+  unit_id: {
+    trigger: "change",
+    required: true,
+    message: "请选择单位"
+  },
+  price: {
+    trigger: "change",
+    required: true,
+    message: "请输入售价",
+    validator(_, value) {
+      console.log(typeof value + "--售价--" + value);
+      if (value ?? "" === "") return new Error("请输入售价");
+      return true;
+    }
+  },
+  moq: {
+    trigger: "change",
+    required: true,
+    validator(_, value) {
+      if (typeof value != "number") return new Error("请输入起订量");
+      const num = Number(value + "");
+      if (num < 1) return new Error("起订量不能低于1");
+      return true;
+    }
+  },
+  step: {
+    trigger: "change",
+    required: true,
+    validator(_, value) {
+      if (typeof value != "number") return new Error("请输入步长");
+      const num = Number(value + "");
+      if (num < 1) return new Error("步长不能低于1");
+      return true;
+    }
+  },
+  good_banner_img: {
+    trigger: "change",
+    type: "array",
+    required: true,
+    message: "请上传轮播图",
+    validator(_, value) {
+      console.log(value);
+      // if (value ?? "" === "") return new Error("请上传轮播图");
+      return true;
+    }
+  },
+  good_img: {
+    trigger: "change",
+    type: "array",
+    required: true,
+    message: "请上传详情图",
+    validator(_, value) {
+      if (value ?? "" === "") return new Error("请上传详情图");
+      return true;
+    }
+  },
+  good_param: {
+    trigger: "change",
+    type: "array",
+    required: true,
+    message: "请输入商品参数",
+    validator(_, value) {
+      if (value ?? "" === "") return new Error("请输入商品参数");
+      return true;
+    }
+  }
+};

+ 103 - 0
src/views/parameter/good/cpns/commodity-feedback.vue

@@ -0,0 +1,103 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { goodTypeOptions } from "../config/_options";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+import CommodityModal from "./commodity-modal.vue";
+
+defineProps<{
+  feedback: Record<string, any>;
+}>();
+
+const commodityModalRef = ref<InstanceType<typeof CommodityModal> | null>(null);
+</script>
+
+<template>
+  <div class="flex flex-col gap-5">
+    <ElCard v-for="(item, index) in feedback" :key="item.id">
+      <h1 class="mb-2">商品要求 {{ index + 1 }}</h1>
+      <ElTable border class="mb-2" :data="[item]" size="small">
+        <ElTableColumn prop="good_type" label="商品类型">
+          <template #="{ row }">
+            <ElTag>
+              {{
+                goodTypeOptions.find(({ id }) => id === row.good_type)?.label ||
+                "--"
+              }}
+            </ElTag>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="budget_price" label="预算单价" />
+        <ElTableColumn prop="num" label="购买数量" />
+        <ElTableColumn prop="cat_name" label="商品分类" width="220">
+          <template #="{ row }">
+            {{ row.can.map(({ name }) => name).join("_") }}
+          </template>
+        </ElTableColumn>
+        <ElTableColumn label="图片">
+          <template #="{ row }">
+            <ElImage
+              v-if="row.good_info_img"
+              style="width: 30px; height: 30px"
+              :preview-src-list="[row.good_info_img]"
+              preview-teleported
+            />
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="good_name" label="商品名称" />
+      </ElTable>
+      <h1 class="mb-2">商品反馈情况</h1>
+      <ElTable border :data="[item]">
+        <ElTableColumn prop="pgNo" label="商品要求编码" min-width="180" />
+        <ElTableColumn prop="sale_price" label="销售单价" min-width="120" />
+        <ElTableColumn prop="num" label="购买数量" min-width="120" />
+        <ElTableColumn prop="" label="商品图片" min-width="120">
+          <template #="{ row }">
+            <ElImage
+              v-if="row.good_info_img"
+              :src="row.good_info_img"
+              style="width: 30px; height: 30px"
+              :preview-src-list="[row.good_info_img]"
+              preview-teleported
+            />
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="good_name" label="商品名称" min-width="180" />
+        <ElTableColumn prop="class_cat" label="商品分类" min-width="220">
+          <template #="{ row }">
+            <ElTag>
+              {{
+                goodTypeOptions.find(({ id }) => id === row.good_type)?.label ||
+                "--"
+              }}
+            </ElTag>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="source" label="商品来源" min-width="120">
+          <template #="{ row }">
+            <ElTag>
+              {{ row.data_source === "1" ? "平台商品" : "采反商品" }}
+            </ElTag>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="expire_day" label="信息有效期" min-width="140" />
+        <ElTableColumn prop="work_day" label="制作工期" min-width="120" />
+        <ElTableColumn prop="delivery_day" label="物流时间" min-width="120" />
+        <ElTableColumn prop="creater" label="负责人" min-width="80" />
+        <ElTableColumn fixed="right" label="操作" width="80">
+          <template #="{ row }">
+            <ElTooltip placement="top" content="详情">
+              <ElButton
+                @click="() => commodityModalRef.onDisplay(row)"
+                type="primary"
+                :icon="useRenderIcon('eye-view')"
+                link
+              />
+            </ElTooltip>
+          </template>
+        </ElTableColumn>
+      </ElTable>
+    </ElCard>
+
+    <CommodityModal ref="commodityModalRef" />
+  </div>
+</template>

+ 70 - 0
src/views/parameter/good/cpns/commodity-modal.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+// import { ref, computed } from "vue";
+// import BasicDescriptions from "/@/components/BasicDescriptions";
+// import { useAsync } from "/@/hooks/useAsync";
+
+// import {
+//   platformGoodColumns,
+//   commodityFeedbackColumns
+// } from "./../config/_details";
+
+// import {
+//   httpPlatformGoodDetail,
+//   httpCommodityGoodDetail
+// } from "/@/api/sellOut/project";
+
+// const visible = ref(false);
+// const isPlatform = ref(false);
+
+// const title = computed(() =>
+//   isPlatform.value ? "平台商品详情" : "采反商品详情"
+// );
+
+// const columns = computed(() =>
+//   isPlatform.value ? platformGoodColumns : commodityFeedbackColumns
+// );
+
+// const {
+//   data,
+//   setData,
+//   loading,
+//   run: detail
+// } = useAsync<Record<string, any>>({
+//   initalData: {}
+// });
+
+// function onDisplay(data) {
+//   const { data_source, skuCode, spuCode } = data;
+//   isPlatform.value = data_source === "1";
+
+//   visible.value = true;
+
+//   detail(
+//     isPlatform.value
+//       ? httpPlatformGoodDetail({ skuCode })
+//       : httpCommodityGoodDetail({ spuCode })
+//   );
+// }
+
+// defineExpose({
+//   onDisplay
+// });
+</script>
+
+<template>
+  11
+  <!-- <ElDialog
+    center
+    v-model="visible"
+    :title="title"
+    width="1024px"
+    @close="() => setData({})"
+  >
+    <BasicDescriptions
+      labelWidth="100"
+      :columns="columns"
+      v-loading="loading"
+      :data="data"
+    />
+  </ElDialog> -->
+</template>

+ 117 - 0
src/views/parameter/good/cpns/ladder-modal.vue

@@ -0,0 +1,117 @@
+<script setup lang="ts">
+import { ref, unref, computed } from "vue";
+import { ElForm } from "element-plus";
+import { goodTypeOptions } from "../config/_options";
+import { ladderFormRules } from "../config/_rules";
+import { ImageUpload } from "/@/components/Upload";
+
+import { AmountInput, NumberInput } from "/@/components/Input";
+import GoodClass from "/@/components/GoodClass";
+
+const emit = defineEmits(["push", "update"]);
+
+const defaultData = {
+  budget_price: "0.00",
+  good_type: "1",
+  good_name: "",
+  cat_info: [],
+  num: "0"
+};
+
+const isUpdate = ref(false);
+const visible = ref(false);
+const formRef = ref<InstanceType<typeof ElForm> | null>(null);
+const updateIndex = ref("0");
+
+const formData = ref<Record<string, any>>({ ...defaultData });
+
+const title = computed(() => {
+  return isUpdate.value ? "修改商品要求" : "添加商品要求";
+});
+
+function handleConfirm() {
+  formRef.value.validate(isValid => {
+    if (!isValid) return;
+
+    emit(
+      isUpdate.value ? "update" : "push",
+      unref(formData),
+      unref(updateIndex)
+    );
+
+    visible.value = false;
+  });
+}
+
+defineExpose({
+  onDisplay: (data?: Record<string, any>, index = "0") => {
+    if (data) {
+      data.cat_info = [];
+      formData.value = data;
+      updateIndex.value = index;
+    }
+
+    visible.value = true;
+    isUpdate.value = !!data;
+  }
+});
+</script>
+
+<template>
+  <ElDialog
+    center
+    :title="title"
+    v-model="visible"
+    @close="() => (formData = { ...defaultData })"
+  >
+    <ElForm
+      ref="formRef"
+      :model="formData"
+      :rules="ladderFormRules"
+      label-width="100px"
+      size="small"
+    >
+      <ElFormItem label="预算单价" prop="budget_price">
+        <AmountInput v-model="formData.budget_price" />
+      </ElFormItem>
+
+      <ElFormItem label="购买数量" prop="num">
+        <NumberInput placeholder="购买数量" v-model="formData.num" />
+      </ElFormItem>
+
+      <ElFormItem label="商品类型" prop="good_type">
+        <ElSelect
+          style="width: 100%"
+          placeholder="商品类型"
+          v-model="formData.good_type"
+        >
+          <ElOption
+            v-for="item in goodTypeOptions"
+            :value="item.id"
+            :label="item.label"
+            :key="item.id"
+          />
+        </ElSelect>
+      </ElFormItem>
+
+      <ElFormItem label="商品分类" prop="cat_info">
+        <GoodClass v-model="formData.cat_info" style="width: 100%" />
+      </ElFormItem>
+
+      <ElFormItem label="商品名称" prop="good_name">
+        <ElInput placeholder="商品名称" v-model="formData.good_name" />
+      </ElFormItem>
+
+      <ElFormItem label="商品图片" prop="good_info_img">
+        <ImageUpload />
+      </ElFormItem>
+
+      <ElFormItem>
+        <div class="w-full flex justify-end">
+          <ElButton type="primary" @click="handleConfirm">保存</ElButton>
+          <ElButton @click="() => (visible = false)">取消</ElButton>
+        </div>
+      </ElFormItem>
+    </ElForm>
+  </ElDialog>
+</template>

+ 85 - 0
src/views/parameter/good/cpns/ladder-table.vue

@@ -0,0 +1,85 @@
+<script setup lang="ts">
+import { goodTypeOptions } from "../config/_options";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+
+const emit = defineEmits(["choose", "delete", "update"]);
+
+defineProps<{
+  ladder: Record<string, any>[];
+  readonly: boolean;
+}>();
+</script>
+
+<template>
+  <ElTable size="small" border :data="ladder">
+    <ElTableColumn prop="name" label="商品阶梯">
+      <template #="{ $index }">
+        {{ $index + 1 }}
+      </template>
+    </ElTableColumn>
+
+    <ElTableColumn prop="good_type" label="商品类型">
+      <template #="{ row }">
+        <ElTag>{{
+          goodTypeOptions.find(({ id }) => id === row.good_type)?.label || "--"
+        }}</ElTag>
+      </template>
+    </ElTableColumn>
+    <ElTableColumn prop="budget_price" label="预算单价" />
+
+    <ElTableColumn prop="num" label="购买数量" />
+
+    <ElTableColumn prop="cat_name" label="商品分类" width="280">
+      <template #="{ row }">
+        {{ row.cat_info.map(({ name }) => name).join("_") }}
+      </template>
+    </ElTableColumn>
+
+    <ElTableColumn prop="good_img" label="图片">
+      <template #="{ row }">
+        <ElImage
+          v-if="row.good_img"
+          style="width: 20px; height: 20px"
+          :preview-src-list="[row.good_img]"
+          preview-teleported=""
+        />
+      </template>
+    </ElTableColumn>
+
+    <ElTableColumn prop="good_name" label="商品名称" />
+
+    <ElTableColumn v-if="!readonly">
+      <template #header>
+        <div class="w-full flex justify-between">
+          <p>操作</p>
+
+          <ElTooltip content="添加阶梯" placement="top">
+            <ElButton
+              link
+              :icon="useRenderIcon('add')"
+              @click="() => emit('choose')"
+            />
+          </ElTooltip>
+        </div>
+      </template>
+
+      <template #="{ $index, row }">
+        <ElButton
+          text
+          type="primary"
+          size="small"
+          @click="() => emit('update', { data: row, index: $index })"
+          >编辑</ElButton
+        >
+
+        <ElButton
+          text
+          type="primary"
+          size="small"
+          @click="() => emit('delete', $index)"
+          >删除</ElButton
+        >
+      </template>
+    </ElTableColumn>
+  </ElTable>
+</template>

+ 184 - 0
src/views/parameter/good/cpns/project-form.vue

@@ -0,0 +1,184 @@
+<script setup lang="ts">
+import { ref, watchEffect, unref } from "vue";
+import { ElForm, ElMessage } from "element-plus";
+import { projectFormConfig } from "../config/_details";
+import { projectFormRules } from "../config/_rules";
+import { httpDetail } from "/@/api/parameter/good";
+import { BasicForm } from "/@/components/BasicForm";
+import LadderModal from "./ladder-modal.vue";
+import LadderTable from "./ladder-table.vue";
+import { ImageUpload } from "/@/components/Upload";
+import { ImageUploadList } from "/@/components/UploadList";
+// import SearchTerrace from "/@/components/SearchTerrace";
+import { Unit } from "/@/components/RemoteSelect";
+import { transform, createDefaultData } from "/@/components/BasicForm";
+import { Customer } from "/@/components/RemoteSelect";
+// import { AmountInput } from "/@/components/Input";
+import { useResponseHandle } from "/@/hooks/useAsync";
+
+const config = {
+  name: "project_name",
+  platform: "platform_name"
+};
+
+const emit = defineEmits(["create"]);
+
+const props = defineProps<{
+  data: Record<string, string>;
+  readonly: boolean;
+  id?: string;
+}>();
+
+const loading = ref(false);
+const { formItems } = projectFormConfig;
+
+const responseHandle = useResponseHandle();
+const basicFormRef = ref<InstanceType<typeof ElForm>>(null);
+const formData = ref<Record<string, any>>(createDefaultData(formItems));
+const ladderModalRef = ref<InstanceType<typeof LadderModal>>(null);
+
+function requesetCategoryById(id: string) {
+  const transform = ({ cat_name, ...rest }: any) => ({
+    ...rest,
+    name: cat_name
+  });
+  return new Promise(resolve => {
+    httpDetail({ id }).then(({ code, message, data }) => {
+      responseHandle({
+        code,
+        message,
+        handler: () => resolve(transform(data))
+      });
+    });
+  });
+}
+
+async function ladderTransform(ladder) {
+  loading.value = true;
+  const { ladder: _ladder } = formData.value;
+  const tasks = ladder.cat_info.map(async id => await requesetCategoryById(id));
+  ladder.cat_info = await Promise.all(tasks);
+  loading.value = false;
+  return ladder;
+}
+
+async function handlePush(_ladder) {
+  const ladder = await ladderTransform(_ladder);
+  if (!Array.isArray(formData.value.ladder)) {
+    formData.value.ladder = [];
+  }
+  formData.value.ladder.push(ladder);
+}
+
+async function handleUpdate(_ladder, index) {
+  const ladder = await ladderTransform(_ladder);
+  formData.value.ladder[index] = ladder;
+}
+
+function disabledDate(date) {
+  return date.getTime() < new Date().getTime() - 86400000;
+}
+
+function handleDateChange(prop: string) {
+  const { arrtime, endtime } = formData.value;
+  const start = new Date(arrtime).valueOf();
+  const end = new Date(endtime).valueOf();
+  if (start > end) {
+    formData.value[prop] = "";
+    ElMessage.warning(`竞价截止时间不能大于要求到货时间!`);
+  }
+}
+
+function handleCreate() {
+  basicFormRef.value.validate(isValid => {
+    if (!isValid) return;
+    console.log("9897979");
+    // const { ladder, ...rest } = unref(formData);
+    // const transform = ladder => {
+    //   return {
+    //     ...(ladder.good_img ? { good_img: ladder.good_img } : {}),
+    //     ...{
+    //       budget_price: ladder.budget_price,
+    //       good_name: ladder.good_name,
+    //       good_type: ladder.good_type,
+    //       num: ladder.num
+    //     }
+    //   };
+    // };
+
+    // const params = {
+    //   ...rest,
+    //   ladder: ladder.map(transform)
+    // };
+
+    // emit("create", params);
+  });
+}
+
+watchEffect(() => {
+  const { readonly, data } = props;
+  if (readonly && !data) {
+    return;
+  }
+
+  formData.value = transform(formItems, data, config);
+});
+</script>
+
+<template>
+  <ElScrollbar>
+    <BasicForm
+      ref="basicFormRef"
+      v-bind="projectFormConfig"
+      :form-data="formData"
+      :rules="projectFormRules"
+      :disabled="readonly"
+      label-width="120px"
+    >
+      <template #unit_id>
+        <Unit v-model="formData.unit_id" placeholder="单位" />
+      </template>
+
+      <template #good_cover_img>
+        <ImageUpload v-model:url="formData.good_cover_img" />
+      </template>
+      <template #good_banner_img>
+        <ImageUploadList
+          v-model:urls="formData.good_banner_img"
+          :descs="['小于1Mb']"
+          :types="['png', 'jpg', 'jpeg']"
+        />
+      </template>
+      <template #good_img>
+        <ImageUploadList
+          v-model:urls="formData.good_img"
+          :descs="['小于1Mb']"
+          :types="['png', 'jpg', 'jpeg']"
+        />
+      </template>
+
+      <template #good_param>
+        111 先展示
+        <!-- <LadderTable
+          :readonly="readonly"
+          :ladder="formData.good_param"
+          @choose="() => ladderModalRef.onDisplay()"
+          @update="({ data, index }) => ladderModalRef.onDisplay(data, index)"
+          @delete="index => formData.ladder.splice(index, 1)"
+        /> -->
+      </template>
+
+      <template #footer>
+        <div class="w-full flex justify-end" v-if="!readonly">
+          <ElButton type="primary" @click="handleCreate">保存</ElButton>
+        </div>
+      </template>
+    </BasicForm>
+
+    <!-- <LadderModal
+      ref="ladderModalRef"
+      @update="handleUpdate"
+      @push="handlePush"
+    /> -->
+  </ElScrollbar>
+</template>

+ 105 - 0
src/views/parameter/good/detail.vue

@@ -0,0 +1,105 @@
+<script setup lang="ts">
+// import CommodityFeedback from "./cpns/commodity-feedback.vue";
+// import ApprovalRecord from "/@/components/ApprovalRecord";
+import PgaeDetail from "/@/components/PageDetail";
+// import SchemePlans from "./cpns/scheme-plan.vue";
+import ProjectForm from "./cpns/project-form.vue";
+import { useAsync } from "/@/hooks/useAsync";
+import { useDetail } from "/@/hooks/useDetail";
+import { useRouter } from "vue-router";
+
+import {
+  httpDetail,
+  httpAdd
+  // httpCommodityFeedback
+} from "/@/api/parameter/good";
+
+//方案制作状态
+// const schemeStatus = ["3", "4", "5", "6"];
+
+const { push } = useRouter();
+
+const { isDetail, collapses, label, id } = useDetail({
+  baseName: "商品",
+  collapseLen: 2
+});
+
+const {
+  data: projectData,
+  run: project,
+  loading: projectLoading
+} = useAsync<Record<string, string>>({
+  initalData: {}
+});
+
+// const {
+//   data: feedbackData,
+//   run: feedback,
+//   loading: feedbackLoading
+// } = useAsync({
+//   initalData: {},
+//   isList: true
+// });
+
+const { run: create } = useAsync({
+  success: () => push("/parameter/good")
+});
+
+const handleCreate = data => create(httpAdd(data));
+
+function initalData() {
+  if (!id.value) {
+    return;
+  }
+
+  project(httpDetail({ projectNo: id.value }));
+  // feedback(httpCommodityFeedback({ projectNo: id.value }));
+}
+
+initalData();
+</script>
+
+<template>
+  <PgaeDetail>
+    <ElTabs>
+      <ElTabPane :label="label">
+        <ElCollapse v-model="collapses">
+          <ElCollapseItem :title="label" name="1" v-loading="projectLoading">
+            <ProjectForm
+              :readonly="isDetail"
+              :data="projectData"
+              @create="handleCreate"
+            />
+          </ElCollapseItem>
+        </ElCollapse>
+
+        <template v-if="isDetail" />
+      </ElTabPane>
+
+      <template v-if="isDetail">
+        <!-- <ElTabPane label="商品反馈详情" v-loading="feedbackLoading">
+          <CommodityFeedback :feedback="feedbackData" />
+        </ElTabPane>
+
+        <ElTabPane
+          v-if="schemeStatus.includes(projectData.status)"
+          label="方案制作"
+        >
+          <SchemePlans
+            :id="id"
+            :feedback="feedbackData"
+            :project="projectData"
+          />
+        </ElTabPane> -->
+
+        <ElTabPane label="审批记录">
+          <!-- <ApprovalRecord :orderCode="id" :type="'PRO'" /> -->
+        </ElTabPane>
+
+        <ElTabPane label="流程图">
+          <!-- <FlowChart processId="14" :orderCode="id" type="PRO" /> -->
+        </ElTabPane>
+      </template>
+    </ElTabs>
+  </PgaeDetail>
+</template>

+ 27 - 34
src/views/parameter/good/index.vue

@@ -1,47 +1,40 @@
 <script setup lang="ts">
-import { ref } from "vue";
-import { PageSearch, usePageSearch } from "/@/components/PageSearch";
+import { usePageSearch } from "/@/components/PageSearch";
 import searchConfig from "./config/search.config";
 import contentConfig from "./config/content.config";
-import modalConfig from "./config/modal.config";
-import { PageModal, usePageModal } from "/@/components/PageModal";
-import { PageContent } from "/@/components/PageContent";
+import PageAuth from "/@/components/PageAuth";
+import { useRouter } from "vue-router";
+
+import PageContainer, {
+  type Events,
+  type Hooks
+} from "/@/components/PageContainer";
 
-import type { PageContentInstance } from "/@/components/PageContent";
-const pageContentRef = ref<PageContentInstance | null>(null);
 const pageName = "good";
-const {
-  pageModalRef,
-  handleUpdateData,
-  handleCreateData,
-  handlePreviewData,
-  handleConfrim,
-  defaultInfo
-} = usePageModal({
-  pageContentRef
-});
-const { handleResetClick, handleSearchClick } = usePageSearch();
+
+const basePath = "/parameter/goodDetail";
+
+const { push } = useRouter();
+
+const hooks: Hooks = {
+  pageSearchHook: () => usePageSearch(undefined, undefined, searchConfig)
+};
+
+const events: Events = {
+  content: {
+    preview: ({ goodNo }) => push(`${basePath}?id=${goodNo}`),
+    create: () => push(basePath)
+  }
+};
 </script>
 
 <template>
   <!-- <PageAuth :pageName="pageName"> -->
-  <PageSearch
-    :form-config="searchConfig"
-    @search-btn-click="handleSearchClick"
-    @reset-btn-click="handleResetClick"
-  />
-  <PageContent
-    ref="pageContentRef"
+  <PageContainer
+    :hooks="hooks"
+    :events="events"
+    :search-config="searchConfig"
     :content-config="contentConfig"
-    @create-btn-click="handleCreateData"
-    @preview-btn-click="row => handlePreviewData(row)"
-    @update-btn-click="row => handleUpdateData(row)"
-  />
-  <PageModal
-    ref="pageModalRef"
-    :modal-config="modalConfig"
-    :default-info="defaultInfo"
-    @confirm-btn-click="handleConfrim"
   />
   <!-- </PageAuth> -->
 </template>