From b58f53769f004fb0ba6b5aedf55ef602850c7c97 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期日, 20 九月 2026 15:46:05 +0800
Subject: [PATCH] 天津豹鸣app 1.部署修改

---
 src/pages/projectManagement/project/_components/ProductRow.vue |  363 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 363 insertions(+), 0 deletions(-)

diff --git a/src/pages/projectManagement/project/_components/ProductRow.vue b/src/pages/projectManagement/project/_components/ProductRow.vue
new file mode 100644
index 0000000..83e6fbe
--- /dev/null
+++ b/src/pages/projectManagement/project/_components/ProductRow.vue
@@ -0,0 +1,363 @@
+<!--
+  椤圭洰浜у搧淇℃伅瀛愯〃鐨勪竴琛�
+
+  row 鐢辩埗绾� productList 鐨勫厓绱犵洿鎺ヤ紶鍏ワ紙鍚屼竴涓搷搴斿紡瀵硅薄锛夛紝
+  缁勪欢鍐呯洿鎺ユ敼 row.xxx 鍗冲彲锛屼笉闇�瑕� emit 鍥炵埗绾у悎骞躲��
+  浜у搧澶х被 / 瑙勬牸鍨嬪彿璧� up-action-sheet锛岄�夐」鐢辨湰缁勪欢鑷繁鎷夊彇銆�
+-->
+<template>
+  <view class="product-card">
+    <view class="product-header">
+      <text class="product-title">浜у搧 {{ index + 1 }}</text>
+      <up-icon name="trash"
+               color="#ee0a24"
+               size="18"
+               @click="$emit('remove')"></up-icon>
+    </view>
+    <up-divider></up-divider>
+
+    <view class="product-body">
+      <view class="field-row"
+            @click="openCategory">
+        <text class="field-label">浜у搧澶х被</text>
+        <text :class="['field-value', row.productCategory ? '' : 'placeholder']">
+          {{ row.productCategory || "璇烽�夋嫨浜у搧澶х被" }}
+        </text>
+        <up-icon name="arrow-right"
+                 size="14"
+                 color="#c0c4cc"></up-icon>
+      </view>
+
+      <view class="field-row"
+            @click="openModel">
+        <text class="field-label">瑙勬牸鍨嬪彿</text>
+        <text :class="['field-value', row.specificationModel ? '' : 'placeholder']">
+          {{ row.specificationModel || "璇烽�夋嫨瑙勬牸鍨嬪彿" }}
+        </text>
+        <up-icon name="arrow-right"
+                 size="14"
+                 color="#c0c4cc"></up-icon>
+      </view>
+
+      <view class="field-row">
+        <text class="field-label">鍗曚綅</text>
+        <u-input v-model="row.unit"
+                 placeholder="璇疯緭鍏ュ崟浣�"
+                 border="none"
+                 inputAlign="right" />
+      </view>
+
+      <view class="field-row">
+        <text class="field-label">鏁伴噺</text>
+        <u-input v-model="row.quantity"
+                 type="number"
+                 placeholder="璇疯緭鍏ユ暟閲�"
+                 border="none"
+                 inputAlign="right" />
+      </view>
+
+      <view class="field-row"
+            @click="openTaxRate">
+        <text class="field-label">绋庣巼(%)</text>
+        <text :class="['field-value', row.taxRate ? '' : 'placeholder']">
+          {{ taxRateText || "璇烽�夋嫨绋庣巼" }}
+        </text>
+        <up-icon name="arrow-right"
+                 size="14"
+                 color="#c0c4cc"></up-icon>
+      </view>
+
+      <view class="field-row">
+        <text class="field-label">鍚◣鍗曚环</text>
+        <u-input v-model="row.taxInclusiveUnitPrice"
+                 type="number"
+                 placeholder="璇疯緭鍏ュ惈绋庡崟浠�"
+                 border="none"
+                 inputAlign="right" />
+      </view>
+
+      <view class="field-row">
+        <text class="field-label">鍚◣鎬讳环</text>
+        <text class="field-value readonly">{{ formatAmount(inclusiveTotal) }}</text>
+      </view>
+
+      <view class="field-row">
+        <text class="field-label">涓嶅惈绋庢�讳环</text>
+        <u-input v-model="row.taxExclusiveTotalPrice"
+                 type="number"
+                 placeholder="璇疯緭鍏ヤ笉鍚◣鎬讳环"
+                 border="none"
+                 inputAlign="right" />
+      </view>
+
+      <view class="field-row"
+            @click="openInvoiceType">
+        <text class="field-label">鍙戠エ绫诲瀷</text>
+        <text :class="['field-value', row.invoiceType ? '' : 'placeholder']">
+          {{ row.invoiceType || "璇烽�夋嫨鍙戠エ绫诲瀷" }}
+        </text>
+        <up-icon name="arrow-right"
+                 size="14"
+                 color="#c0c4cc"></up-icon>
+      </view>
+    </view>
+
+    <up-action-sheet :show="showCategorySheet"
+                     title="閫夋嫨浜у搧澶х被"
+                     :actions="categoryActions"
+                     @select="onSelectCategory"
+                     @close="showCategorySheet = false"></up-action-sheet>
+
+    <up-action-sheet :show="showModelSheet"
+                     title="閫夋嫨瑙勬牸鍨嬪彿"
+                     :actions="modelActions"
+                     @select="onSelectModel"
+                     @close="showModelSheet = false"></up-action-sheet>
+
+    <up-action-sheet :show="showTaxRateSheet"
+                     title="閫夋嫨绋庣巼"
+                     :actions="taxRateActions"
+                     @select="onSelectTaxRate"
+                     @close="showTaxRateSheet = false"></up-action-sheet>
+
+    <up-action-sheet :show="showInvoiceSheet"
+                     title="閫夋嫨鍙戠エ绫诲瀷"
+                     :actions="INVOICE_TYPE_ACTIONS"
+                     @select="onSelectInvoiceType"
+                     @close="showInvoiceSheet = false"></up-action-sheet>
+  </view>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from "vue";
+  import { modelList, productTreeList } from "@/api/basicData/product";
+  import { productInclusiveTotal } from "../_utils/mapper";
+
+  const INVOICE_TYPE_ACTIONS = [
+    { name: "澧炴櫘绁�", value: "澧炴櫘绁�" },
+    { name: "澧炰笓绁�", value: "澧炰笓绁�" },
+  ];
+
+  const props = defineProps({
+    row: {
+      type: Object,
+      required: true,
+    },
+    index: {
+      type: Number,
+      default: 0,
+    },
+    // 绋庣巼瀛楀吀锛坲seDict('tax_rate') 鐨勮繑鍥炲�硷級
+    taxRateOptions: {
+      type: Array,
+      default: () => [],
+    },
+  });
+
+  defineEmits(["remove"]);
+
+  const categoryActions = ref([]);
+  const modelActions = ref([]);
+  const showCategorySheet = ref(false);
+  const showModelSheet = ref(false);
+  const showTaxRateSheet = ref(false);
+  const showInvoiceSheet = ref(false);
+
+  const inclusiveTotal = computed(() => productInclusiveTotal(props.row));
+
+  const taxRateActions = computed(() =>
+    (props.taxRateOptions || []).map(item => ({
+      name: item.label,
+      value: item.value,
+    }))
+  );
+
+  const taxRateText = computed(() => {
+    const matched = (props.taxRateOptions || []).find(
+      item => String(item.value) === String(props.row.taxRate)
+    );
+    return matched ? matched.label : "";
+  });
+
+  const formatAmount = value => Number(value || 0).toFixed(2);
+
+  const flattenTree = nodes => {
+    const result = [];
+    const walk = list => {
+      (list || []).forEach(item => {
+        if (item.children && item.children.length) {
+          walk(item.children);
+        } else {
+          result.push({
+            name: item.label || item.productName || "",
+            value: item.id || item.value,
+          });
+        }
+      });
+    };
+    walk(nodes);
+    return result;
+  };
+
+  const loadCategories = () => {
+    productTreeList()
+      .then(res => {
+        const nodes = Array.isArray(res) ? res : res?.data || [];
+        categoryActions.value = flattenTree(nodes);
+      })
+      .catch(() => {
+        categoryActions.value = [];
+      });
+  };
+
+  const openCategory = () => {
+    if (!categoryActions.value.length) loadCategories();
+    showCategorySheet.value = true;
+  };
+
+  const openModel = () => {
+    if (!props.row.productCategoryId) {
+      uni.showToast({ title: "璇峰厛閫夋嫨浜у搧澶х被", icon: "none" });
+      return;
+    }
+    if (!modelActions.value.length) {
+      uni.showToast({ title: "鏆傛棤瑙勬牸鍨嬪彿", icon: "none" });
+      return;
+    }
+    showModelSheet.value = true;
+  };
+
+  const openTaxRate = () => {
+    if (!taxRateActions.value.length) {
+      uni.showToast({ title: "鏆傛棤绋庣巼閫夐」", icon: "none" });
+      return;
+    }
+    showTaxRateSheet.value = true;
+  };
+
+  const openInvoiceType = () => {
+    showInvoiceSheet.value = true;
+  };
+
+  const onSelectCategory = action => {
+    showCategorySheet.value = false;
+    props.row.productCategoryId = action.value;
+    props.row.productCategory = action.name;
+    props.row.productModelId = "";
+    props.row.specificationModel = "";
+    props.row.unit = "";
+    modelActions.value = [];
+    modelList({ id: action.value })
+      .then(res => {
+        const rows = res?.data?.records || res?.data || res?.records || res || [];
+        modelActions.value = (Array.isArray(rows) ? rows : []).map(item => ({
+          name: item.model || item.specification || "",
+          value: item.id,
+          unit: item.unit,
+        }));
+      })
+      .catch(() => {
+        modelActions.value = [];
+      });
+  };
+
+  const onSelectModel = action => {
+    showModelSheet.value = false;
+    props.row.productModelId = action.value;
+    props.row.specificationModel = action.name;
+    if (action.unit) props.row.unit = action.unit;
+  };
+
+  const onSelectTaxRate = action => {
+    showTaxRateSheet.value = false;
+    props.row.taxRate = action.value;
+  };
+
+  const onSelectInvoiceType = action => {
+    showInvoiceSheet.value = false;
+    props.row.invoiceType = action.value;
+  };
+
+  // 鎵撳紑鍨嬪彿闈㈡澘鍓嶅厛鎶婂凡鏈夊ぇ绫诲搴旂殑鍨嬪彿鎹炲洖鏉ワ紙缂栬緫鍥炴樉鍦烘櫙锛�
+  onMounted(() => {
+    if (!props.row.productCategoryId) return;
+    modelList({ id: props.row.productCategoryId })
+      .then(res => {
+        const rows = res?.data?.records || res?.data || res?.records || res || [];
+        modelActions.value = (Array.isArray(rows) ? rows : []).map(item => ({
+          name: item.model || item.specification || "",
+          value: item.id,
+          unit: item.unit,
+        }));
+        // 鍙瓨浜嗗瀷鍙峰悕绉版椂鍙嶆煡 id锛屼繚璇侀�夋嫨鍣ㄨ兘鍛戒腑
+        if (!props.row.productModelId && props.row.specificationModel) {
+          const matched = modelActions.value.find(
+            item => item.name === props.row.specificationModel
+          );
+          if (matched) props.row.productModelId = matched.value;
+        }
+      })
+      .catch(() => {});
+  });
+</script>
+
+<style scoped lang="scss">
+  .product-card {
+    background: #fff;
+    border-radius: 12px;
+    padding: 0 12px 12px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  }
+
+  .product-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 0;
+  }
+
+  .product-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #22324d;
+  }
+
+  .product-body {
+    padding-top: 4px;
+  }
+
+  .field-row {
+    display: flex;
+    align-items: center;
+    min-height: 44px;
+    border-bottom: 1px solid #f5f6fa;
+  }
+
+  .field-row:last-child {
+    border-bottom: none;
+  }
+
+  .field-label {
+    width: 96px;
+    flex-shrink: 0;
+    font-size: 14px;
+    color: #606266;
+  }
+
+  .field-value {
+    flex: 1;
+    min-width: 0;
+    font-size: 14px;
+    color: #303133;
+    text-align: right;
+    word-break: break-all;
+  }
+
+  .field-value.placeholder {
+    color: #c0c4cc;
+  }
+
+  .field-value.readonly {
+    color: #909399;
+  }
+</style>

--
Gitblit v1.9.3