From 2307a3125c23ad79ee92306603a418085cee194d Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期四, 05 三月 2026 15:50:02 +0800
Subject: [PATCH] feat: 更新售后管理模块,新增产品选择弹窗和统计卡片

---
 src/views/customerService/feedbackRegistration/components/formDia.vue |  452 ++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 371 insertions(+), 81 deletions(-)

diff --git a/src/views/customerService/feedbackRegistration/components/formDia.vue b/src/views/customerService/feedbackRegistration/components/formDia.vue
index 41c8ac6..2af71da 100644
--- a/src/views/customerService/feedbackRegistration/components/formDia.vue
+++ b/src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -2,70 +2,118 @@
   <div>
     <el-dialog
         v-model="dialogFormVisible"
-        title="鍞悗鐧昏"
-        width="70%"
+        title="鏂板鍞悗鍗�"
+        width="90%"
         @close="closeDia"
     >
-			<el-form
-				:model="form"
-				label-width="140px"
-				label-position="top"
-				:rules="rules"
-				ref="formRef"
-			>
-				<el-row :gutter="30">
-					<el-col :span="12">
-						<el-form-item label="鍙嶉鏃堕棿锛�" prop="feedbackDate">
-							<el-date-picker
-								style="width: 100%"
-								v-model="form.feedbackDate"
-								value-format="YYYY-MM-DD"
-								format="YYYY-MM-DD"
-								type="date"
-								placeholder="璇烽�夋嫨"
-								clearable
-							/>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="鐧昏浜猴細" prop="checkUserId">
-							<el-select
-								v-model="form.checkUserId"
-								placeholder="璇烽�夋嫨"
-								clearable
-							>
-								<el-option
-									v-for="item in userList"
-									:key="item.userId"
-									:label="item.nickName"
-									:value="item.userId"
-								></el-option>
-							</el-select>
-						</el-form-item>
-					</el-col>
-				</el-row>
-				<el-row :gutter="30">
-					<el-col :span="12">
-						<el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
-							<el-input
-								v-model="form.customerName"
-								placeholder="璇疯緭鍏�"
-								clearable
-							/>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="闂鎻忚堪锛�" prop="proDesc">
-							<el-input
-								v-model="form.proDesc"
-								placeholder="璇疯緭鍏�"
-								clearable
-								type="textarea"
-							/>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
+      <div>
+        <span class="descriptions">鍩虹璧勬枡</span>
+        <el-form
+            :model="form"
+            label-width="140px"
+            label-position="top"
+            :rules="rules"
+            ref="formRef"
+        >
+          <el-row :gutter="30">
+            <el-col :span="4">
+              <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
+                <el-select
+                    v-model="form.customerName"
+                    filterable
+                    @change="customerNameChange"
+                >
+                  <el-option
+                      v-for="item in customerNameOptions"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item label="鍞悗绫诲瀷锛�" prop="serviceType">
+                <el-select
+                    v-model="form.serviceType"
+                    filterable
+                >
+                  <el-option
+                      v-for="dict in serviceTypeOptions"
+                      :key="dict.value"
+                      :label="dict.label"
+                      :value="dict.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item label="鍏宠仈閿�鍞崟鍙凤細" prop="salesContractNo">
+                <el-select
+                    v-model="form.salesContractNo"
+                    @change="associatedSalesOrderNumberChange"
+                    filterable
+                >
+                  <el-option
+                      v-for="item in associatedSalesOrderNumberOptions"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item label="绱ф�ョ▼搴︼細" prop="urgency">
+                <el-select
+                    v-model="form.urgency"
+                    filterable
+                >
+                  <el-option
+                      v-for="dict in urgencyOptions"
+                      :key="dict.value"
+                      :label="dict.label"
+                      :value="dict.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item label="闂鎻忚堪锛�" prop="disRes">
+                <el-input
+                    v-model="form.disRes"
+                    placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <hr>
+          <div style="padding-top: 20px">
+            <div style="display: flex; justify-content: space-between">
+              <span class="descriptions">鍏宠仈浜у搧</span>
+            <el-button
+              type="primary"
+              style="margin-right: 12px; margin-bottom: 10px"
+              @click="isShowProductSelectDialog = true"
+            >
+              閫夋嫨浜у搧
+            </el-button>
+            </div>
+            <PIMTable
+                :isShowPagination="false"
+                rowKey="id"
+                :column="tableColumn"
+                :tableData="tableData"
+            >
+              <template #shippingStatus="{ row }">
+                <el-tag :type="getShippingStatusType(row)" size="small">
+                  {{ getShippingStatusText(row) }}
+                </el-tag>
+              </template>
+            </PIMTable>
+          </div>
+      </div>
 			<template #footer>
 				<div class="dialog-footer">
 					<el-button type="primary" @click="submitForm">纭</el-button>
@@ -73,63 +121,286 @@
 				</div>
 			</template>
     </el-dialog>
+    <!-- 閫夋嫨浜у搧寮圭獥 -->
+    <ProductSelectDialog
+      v-model="isShowProductSelectDialog"
+      :products="currentSalesOrderProducts"
+      :selected-ids="currentSelectedProductIds"
+      @confirm="handleSelectProducts"
+    />
   </div>
 </template>
 
 <script setup>
-import {ref} from "vue";
+import { ref, reactive, toRefs, getCurrentInstance, computed } from "vue";
+import ProductSelectDialog from "./ProductSelectDialog.vue";
 import useUserStore from "@/store/modules/user.js";
 import {userListNoPageByTenantId} from "@/api/system/user.js";
-import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
+import {afterSalesServiceAdd, afterSalesServiceUpdate, getAllCustomerList, getSalesLedger } from "@/api/customerService/index.js";
 import { getCurrentDate } from "@/utils/index.js";
 const { proxy } = getCurrentInstance()
 const emit = defineEmits(['close'])
 const dialogFormVisible = ref(false);
 const operationType = ref('')
+const formRef = ref(null)
+const customerNameOptions = ref([])
 const userStore = useUserStore();
 
 const data = reactive({
 	form: {
-		feedbackDate: "",
-		checkUserId: "",
-		customerName: "",
-		proDesc: "",
+    topic: "",
+    serviceType: "",
+    urgency: "",
+    salesLedgerId: null,
+    productModelIds: "",
+    customerId: null,
+    salesContractNo: "",
+    disRes: "",
+    customerName: ""
 	},
 	rules: {
+    customerName: [{required: true, message: "璇烽�夋嫨瀹㈡埛鍚嶇О", trigger: "change"}],
+    serviceType: [{required: true, message: "璇烽�夋嫨鍞悗绫诲瀷", trigger: "change"}],
+    urgency: [{required: true, message: "璇烽�夋嫨绱ф�ョ▼搴�", trigger: "change"}],
 		feedbackDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
-		checkUserId: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
-		customerName: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
-		proDesc: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
 	}
 })
+
+// 鑷畾涔夋牎楠屽嚱鏁帮細鍒ゆ柇鏄惁闇�瑕佹牎楠屽敭鍚庣紪鍙�
+
 const { form, rules } = toRefs(data);
 const userList = ref([])
 
+const formatCurrency = (val) => {
+  if (val === null || val === undefined || val === '') return '-'
+  const num = Number(val)
+  return Number.isFinite(num) ? num.toFixed(2) : '-'
+}
+
+const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict(
+  "post_sale_waiting_list",
+  "degree_of_urgency"
+);
+
+const serviceTypeOptions = computed(() => post_sale_waiting_list?.value || []);
+const urgencyOptions = computed(() => degree_of_urgency?.value || []);
+
+const tableColumn = ref([
+  { label: "浜у搧澶х被", prop: "productCategory" },
+  { label: "瑙勬牸鍨嬪彿", prop: "specificationModel" },
+  { label: "鍗曚綅", prop: "unit" },
+  {
+    label: "浜у搧鐘舵��",
+    prop: "approveStatus",
+    width: 100,
+    align: "center",
+    dataType: "tag",
+    formatData: (v) => (v === 1 ? "鍏呰冻" : "涓嶈冻"),
+    formatType: (v) => (v === 1 ? "success" : "danger"),
+  },
+  {
+    label: "鍙戣揣鐘舵��",
+    align: "center",
+    width: 140,
+    dataType: "slot",
+    slot: "shippingStatus",
+  },
+  { label: "蹇�掑叕鍙�", prop: "expressCompany", width: 140 },
+  { label: "蹇�掑崟鍙�", prop: "expressNumber", width: 160 },
+  { label: "鍙戣揣杞︾墝", prop: "shippingCarNumber", minWidth: 100, align: "center" },
+  { label: "鍙戣揣鏃ユ湡", prop: "shippingDate", minWidth: 100, align: "center" },
+  { label: "鏁伴噺", prop: "quantity", width: 100 },
+  { label: "绋庣巼(%)", prop: "taxRate", width: 100 },
+  {
+    label: "鍚◣鍗曚环(鍏�)",
+    prop: "taxInclusiveUnitPrice",
+    width: 160,
+    formatData: formatCurrency,
+  },
+  {
+    label: "鍚◣鎬讳环(鍏�)",
+    prop: "taxInclusiveTotalPrice",
+    width: 160,
+    formatData: formatCurrency,
+  },
+  {
+    label: "涓嶅惈绋庢�讳环(鍏�)",
+    prop: "taxExclusiveTotalPrice",
+    width: 160,
+    formatData: formatCurrency,
+  },
+  {
+    dataType: "action",
+    label: "鎿嶄綔",
+    align: "center",
+    fixed: 'right',
+    operation: [
+      {
+        name: "鍒犻櫎",
+        type: "text",
+        clickFun: (row) => {
+          tableData.value = tableData.value.filter(i => i.id !== row.id)
+        },
+
+      },
+    ],
+  },
+])
+const tableData = ref([])
+// 閫夋嫨浜у搧寮圭獥
+const isShowProductSelectDialog = ref(false)
+const handleSelectProducts = (rows) => {
+  if (!Array.isArray(rows)) return
+  const existingIds = new Set(tableData.value.map(i => i.id))
+  const mapped = rows
+    .filter(r => !existingIds.has(r.id))
+    .map(r => ({
+      id: r.id,
+      productCategory: r.productName,
+      specificationModel: r.model,
+      unit: r.unit || '',
+      approveStatus: null,
+      shippingStatus: '',
+      expressCompany: '',
+      expressNumber: '',
+      shippingCarNumber: '',
+      shippingDate: '',
+      quantity: 0,
+      taxRate: 0,
+      taxInclusiveUnitPrice: 0,
+      taxInclusiveTotalPrice: 0,
+      taxExclusiveTotalPrice: 0,
+    }))
+  tableData.value = tableData.value.concat(mapped)
+}
+const currentSelectedProductIds = computed(() => {
+  return tableData.value.map(item => item.id)
+})
+
+const associatedSalesOrderNumberChange = () => {
+  const opt = associatedSalesOrderNumberOptions.value.find(
+    (item) => item.value === form.value.salesContractNo
+  )
+  tableData.value = opt?.productData || []
+  form.value.salesLedgerId = opt?.id || null
+}
+
+const associatedSalesOrderNumberOptions = ref([])
+
+const currentSalesOrderProducts = computed(() => {
+  const opt = associatedSalesOrderNumberOptions.value.find(
+    (item) => item.value === form.value.salesContractNo
+  )
+  return opt?.productData || []
+})
+
+const customerNameChange = (val) => {
+  const opt = customerNameOptions.value.find(item => item.value === val);
+  if (opt) {
+    form.value.customerId = opt.id;
+  }
+  getSalesLedger({
+    customerName: form.value.customerName
+  }).then(res => {
+    if(res.code === 200){
+      associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({
+        label: item.salesContractNo,
+        value: item.salesContractNo,
+        productData:item.productData,
+        id: item.id
+      }))
+    }
+  })
+}
+
+const getShippingStatusText = (row) => {
+  if (!row) return '寰呭彂璐�'
+  if (row.shippingDate || row.shippingCarNumber) {
+    return '宸插彂璐�'
+  }
+  const status = row.shippingStatus
+  if (status === null || status === undefined || status === '') {
+    return '寰呭彂璐�'
+  }
+  const map = {
+    '寰呭彂璐�': '寰呭彂璐�',
+    '寰呭鏍�': '寰呭鏍�',
+    '瀹℃牳涓�': '瀹℃牳涓�',
+    '瀹℃牳鎷掔粷': '瀹℃牳鎷掔粷',
+    '瀹℃牳閫氳繃': '瀹℃牳閫氳繃',
+    '宸插彂璐�': '宸插彂璐�'
+  }
+  return map[String(status).trim()] || '寰呭彂璐�'
+}
+
+const getShippingStatusType = (row) => {
+  if (!row) return 'info'
+  if (row.shippingDate || row.shippingCarNumber) {
+    return 'success'
+  }
+  const status = row.shippingStatus
+  if (status === null || status === undefined || status === '') {
+    return 'info'
+  }
+  const map = {
+    '寰呭彂璐�': 'info',
+    '寰呭鏍�': 'warning',
+    '瀹℃牳涓�': 'warning',
+    '瀹℃牳鎷掔粷': 'danger',
+    '瀹℃牳閫氳繃': 'success',
+    '宸插彂璐�': 'success'
+  }
+  return map[String(status).trim()] || 'info'
+}
+
 // 鎵撳紑寮规
-const openDialog = (type, row) => {
+const openDialog =async (type, row) => {
+  // 璇锋眰澶氫釜鎺ュ彛锛岃幏鍙栨暟鎹�
+  let res = await getAllCustomerList();
+  if(res.records){
+    customerNameOptions.value = res.records.map(item => ({
+      label: item.customerName,
+      value: item.customerName,
+      id: item.id
+    }));
+  }
+
+
   operationType.value = type;
   dialogFormVisible.value = true;
 	form.value = {}
 	proxy.resetForm("formRef");
 	form.value.checkUserId = userStore.id;
 	form.value.feedbackDate = getCurrentDate();
+  // 鏂板鏃舵竻绌哄凡閫夊叧鑱斾骇鍝�
+  if (type === "add") {
+    tableData.value = []
+  }
 	userListNoPageByTenantId().then((res) => {
 		userList.value = res.data;
 	});
 	if (type === "edit") {
 		form.value = {...row}
+    if (form.value.customerName) {
+      const res = await getSalesLedger({ customerName: form.value.customerName })
+      if (res?.code === 200) {
+        console.log(res)
+        associatedSalesOrderNumberOptions.value = (res.data?.records || []).map(item => ({
+          label: item.salesContractNo,
+          value: item.salesContractNo,
+          productData: item.productData,
+          id: item.id
+        }))
+      }
+    }
+    console.log(form.value)
 	}
 }
-// const setName = (code) => {
-// 	const index = userList.value.findIndex(item => item.deviceModel === code);
-// 	if (index > -1) {
-// 		console.log(userList)
-// 		form.value.name = userList.value[index].deviceName;
-// 	}
-// }
 const submitForm = () => {
 	proxy.$refs["formRef"].validate(valid => {
 		if (valid) {
+      // 鍖归厤浜у搧鍨嬪彿IDs
+      form.value.productModelIds = tableData.value.map(item => item.id).join(",")
 			if (operationType.value === "add") {
 				afterSalesServiceAdd(form.value).then(response => {
 					proxy.$modal.msgSuccess("鏂板鎴愬姛")
@@ -155,6 +426,25 @@
 });
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+.descriptions {
+  margin-bottom: 20px;
+  display: inline-block;
+  font-size: 1rem;
+  font-weight: 600;
+  padding-left: 12px;
+  position: relative;
+}
 
-</style>
\ No newline at end of file
+.descriptions::before {
+  content: "";
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 4px;
+  height: 1rem;
+  background-color: #002FA7; /* Element 榛樿绾㈣壊 */
+  border-radius: 2px;
+}
+</style>

--
Gitblit v1.9.3