From ae6af903490a340aff77e6bdb73d299505dc6d42 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期六, 16 五月 2026 09:48:48 +0800
Subject: [PATCH] 销售报价web端同步修改接口
---
src/pages/sales/salesQuotation/detail.vue | 54 +--
src/pages/sales/salesQuotation/edit.vue | 547 ++++++++++++++++++++++++++-------------------
src/pages/sales/salesQuotation/index.vue | 118 +++++----
3 files changed, 403 insertions(+), 316 deletions(-)
diff --git a/src/pages/sales/salesQuotation/detail.vue b/src/pages/sales/salesQuotation/detail.vue
index a273974..45c3fd6 100644
--- a/src/pages/sales/salesQuotation/detail.vue
+++ b/src/pages/sales/salesQuotation/detail.vue
@@ -1,7 +1,7 @@
<template>
<view class="customer-detail-page">
- <PageHeader title="鎶ヤ环璇︽儏" @back="goBack" />
-
+ <PageHeader title="鎶ヤ环璇︽儏"
+ @back="goBack" />
<view class="detail-content">
<view class="section">
<view class="section-title">鍩虹淇℃伅</view>
@@ -44,24 +44,13 @@
</view>
</view>
</view>
-
- <view class="section">
- <view class="section-title">瀹℃壒鑺傜偣</view>
- <view v-if="approverNames.length" class="info-list">
- <view v-for="(name, index) in approverNames" :key="index" class="info-item">
- <text class="info-label">瀹℃壒鑺傜偣 {{ index + 1 }}</text>
- <text class="info-value">{{ name }}</text>
- </view>
- </view>
- <view v-else class="empty-box">
- <text>鏆傛棤瀹℃壒鑺傜偣</text>
- </view>
- </view>
-
<view class="section">
<view class="section-title">浜у搧鏄庣粏</view>
- <view v-if="detailData.products && detailData.products.length > 0" class="product-list">
- <view v-for="(item, index) in detailData.products" :key="index" class="product-card">
+ <view v-if="detailData.products && detailData.products.length > 0"
+ class="product-list">
+ <view v-for="(item, index) in detailData.products"
+ :key="index"
+ class="product-card">
<view class="product-head">浜у搧 {{ index + 1 }}</view>
<view class="info-item">
<text class="info-label">浜у搧鍚嶇О</text>
@@ -89,13 +78,16 @@
</view>
</view>
</view>
- <view v-else class="empty-box">
+ <view v-else
+ class="empty-box">
<text>鏆傛棤浜у搧鏄庣粏</text>
</view>
</view>
</view>
-
- <FooterButtons cancelText="杩斿洖" confirmText="缂栬緫" @cancel="goBack" @confirm="goEdit" />
+ <FooterButtons cancelText="杩斿洖"
+ confirmText="缂栬緫"
+ @cancel="goBack"
+ @confirm="goEdit" />
</view>
</template>
@@ -108,29 +100,27 @@
const quotationId = ref("");
const detailData = ref({});
- const approverNames = computed(() => {
- const approverText = detailData.value.approveUserNames || detailData.value.approverNames || detailData.value.approveUserIds || "";
- if (Array.isArray(approverText)) return approverText.filter(Boolean);
- return String(approverText)
- .split(",")
- .map(item => item.trim())
- .filter(Boolean);
- });
-
const goBack = () => {
uni.navigateBack();
};
const goEdit = () => {
if (!quotationId.value) return;
- uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${quotationId.value}` });
+ uni.navigateTo({
+ url: `/pages/sales/salesQuotation/edit?id=${quotationId.value}`,
+ });
};
const formatAmount = amount => `楼${Number(amount || 0).toFixed(2)}`;
const loadDetailFromStorage = () => {
const cachedData = uni.getStorageSync("salesQuotationDetail");
- detailData.value = cachedData || {};
+ if (cachedData && (cachedData.id === quotationId.value || cachedData.id === Number(quotationId.value))) {
+ detailData.value = cachedData;
+ } else {
+ detailData.value = cachedData || {};
+ console.warn("鏈壘鍒板搴旂殑鎶ヤ环鍗曠紦瀛樻暟鎹�");
+ }
};
onLoad(options => {
diff --git a/src/pages/sales/salesQuotation/edit.vue b/src/pages/sales/salesQuotation/edit.vue
index 940a8d9..bfcb930 100644
--- a/src/pages/sales/salesQuotation/edit.vue
+++ b/src/pages/sales/salesQuotation/edit.vue
@@ -1,173 +1,196 @@
<template>
<view class="account-detail">
- <PageHeader :title="pageTitle" @back="goBack" />
-
+ <PageHeader :title="pageTitle"
+ @back="goBack" />
<view class="form-container">
- <up-form
- ref="formRef"
- :model="form"
- :rules="rules"
- label-width="110"
- input-align="right"
- error-message-align="right"
- >
- <u-cell-group title="鍩虹淇℃伅" class="form-section">
- <up-form-item label="瀹㈡埛鍚嶇О" prop="customer" required>
- <up-input v-model="form.customer" placeholder="璇烽�夋嫨瀹㈡埛" readonly @click="showCustomerSheet = true" />
+ <up-form ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="110"
+ input-align="right"
+ error-message-align="right">
+ <u-cell-group title="鍩虹淇℃伅"
+ class="form-section">
+ <up-form-item label="瀹㈡埛鍚嶇О"
+ prop="customer"
+ required>
+ <up-input v-model="form.customer"
+ placeholder="璇烽�夋嫨瀹㈡埛"
+ readonly
+ @click="showCustomerSheet = true" />
<template #right>
- <up-icon name="arrow-right" @click="showCustomerSheet = true"></up-icon>
+ <up-icon name="arrow-right"
+ @click="showCustomerSheet = true"></up-icon>
</template>
</up-form-item>
- <up-form-item label="涓氬姟鍛�" prop="salesperson" required>
- <up-input
- v-model="form.salesperson"
- placeholder="璇烽�夋嫨涓氬姟鍛�"
- readonly
- @click="showSalespersonSheet = true"
- />
+ <up-form-item label="涓氬姟鍛�"
+ prop="salesperson"
+ required>
+ <up-input v-model="form.salesperson"
+ placeholder="璇烽�夋嫨涓氬姟鍛�"
+ readonly
+ @click="showSalespersonSheet = true" />
<template #right>
- <up-icon name="arrow-right" @click="showSalespersonSheet = true"></up-icon>
+ <up-icon name="arrow-right"
+ @click="showSalespersonSheet = true"></up-icon>
</template>
</up-form-item>
- <up-form-item label="鎶ヤ环鏃ユ湡" prop="quotationDate" required>
- <up-input
- v-model="form.quotationDate"
- placeholder="璇烽�夋嫨鎶ヤ环鏃ユ湡"
- readonly
- @click="showQuotationDatePicker = true"
- />
+ <up-form-item label="鎶ヤ环鏃ユ湡"
+ prop="quotationDate"
+ required>
+ <up-input v-model="form.quotationDate"
+ placeholder="璇烽�夋嫨鎶ヤ环鏃ユ湡"
+ readonly
+ @click="showQuotationDatePicker = true" />
<template #right>
- <up-icon name="arrow-right" @click="showQuotationDatePicker = true"></up-icon>
+ <up-icon name="arrow-right"
+ @click="showQuotationDatePicker = true"></up-icon>
</template>
</up-form-item>
- <up-form-item label="鏈夋晥鏈熻嚦" prop="validDate" required>
- <up-input
- v-model="form.validDate"
- placeholder="璇烽�夋嫨鏈夋晥鏈�"
- readonly
- @click="showValidDatePicker = true"
- />
+ <up-form-item label="鏈夋晥鏈熻嚦"
+ prop="validDate"
+ required>
+ <up-input v-model="form.validDate"
+ placeholder="璇烽�夋嫨鏈夋晥鏈�"
+ readonly
+ @click="showValidDatePicker = true" />
<template #right>
- <up-icon name="arrow-right" @click="showValidDatePicker = true"></up-icon>
+ <up-icon name="arrow-right"
+ @click="showValidDatePicker = true"></up-icon>
</template>
</up-form-item>
- <up-form-item label="浠樻鏂瑰紡" prop="paymentMethod" required>
- <up-input v-model="form.paymentMethod" placeholder="璇疯緭鍏ヤ粯娆炬柟寮�" clearable />
+ <up-form-item label="浠樻鏂瑰紡"
+ prop="paymentMethod"
+ required>
+ <up-input v-model="form.paymentMethod"
+ placeholder="璇疯緭鍏ヤ粯娆炬柟寮�"
+ clearable />
</up-form-item>
- <up-form-item label="澶囨敞" prop="remark">
- <up-textarea v-model="form.remark" placeholder="璇疯緭鍏ュ娉�" auto-height />
+ <up-form-item label="澶囨敞"
+ prop="remark">
+ <up-textarea v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉�"
+ auto-height />
</up-form-item>
</u-cell-group>
-
- <u-cell-group title="瀹℃壒鑺傜偣" class="form-section">
+ <u-cell-group title="浜у搧淇℃伅"
+ class="form-section">
<view class="section-tools">
- <up-button type="primary" size="small" text="鏂板鑺傜偣" @click="addApproverNode" />
+ <up-button type="primary"
+ size="small"
+ text="鏂板浜у搧"
+ @click="addProduct" />
</view>
- <view v-if="salespersonList.length === 0" class="empty-text">
- <text>鏆傛棤鍙�夊鎵逛汉锛岃妫�鏌ョ敤鎴锋暟鎹�</text>
- </view>
- <view class="node-list">
- <view v-for="(node, index) in approverNodes" :key="node.id" class="node-card">
- <view class="node-top">
- <text class="node-title">瀹℃壒鑺傜偣 {{ index + 1 }}</text>
- <up-icon
- v-if="approverNodes.length > 1"
- name="trash"
- color="#ee0a24"
- size="18"
- @click="removeApproverNode(index)"
- ></up-icon>
- </view>
- <view class="picker-field" @click="openApproverPicker(index)">
- <up-input :model-value="node.nickName || ''" placeholder="璇烽�夋嫨瀹℃壒浜�" readonly disabled />
- <up-icon name="arrow-right" color="#909399" size="16"></up-icon>
- </view>
- </view>
- </view>
- </u-cell-group>
-
- <u-cell-group title="浜у搧淇℃伅" class="form-section">
- <view class="section-tools">
- <up-button type="primary" size="small" text="鏂板浜у搧" @click="addProduct" />
- </view>
- <view v-if="form.products.length === 0" class="empty-text">
+ <view v-if="form.products.length === 0"
+ class="empty-text">
<text>鏆傛棤浜у搧锛岃鍏堟坊鍔犱骇鍝�</text>
</view>
- <view v-else class="product-list">
- <view v-for="(product, index) in form.products" :key="product.uid" class="product-card">
+ <view v-else
+ class="product-list">
+ <view v-for="(product, index) in form.products"
+ :key="product.uid"
+ class="product-card">
<view class="product-header">
<text class="product-title">浜у搧 {{ index + 1 }}</text>
- <up-icon name="trash" color="#ee0a24" size="18" @click="removeProduct(index)"></up-icon>
+ <up-icon name="trash"
+ color="#ee0a24"
+ size="18"
+ @click="removeProduct(index)"></up-icon>
</view>
<up-divider></up-divider>
<view class="product-body">
<up-form-item label="浜у搧鍚嶇О">
- <up-input
- v-model="product.product"
- placeholder="璇烽�夋嫨浜у搧"
- readonly
- @click="openProductPicker(index)"
- />
+ <up-input v-model="product.product"
+ placeholder="璇烽�夋嫨浜у搧"
+ readonly
+ @click="openProductPicker(index)" />
<template #right>
- <up-icon name="arrow-right" @click="openProductPicker(index)"></up-icon>
+ <up-icon name="arrow-right"
+ @click="openProductPicker(index)"></up-icon>
</template>
</up-form-item>
<up-form-item label="瑙勬牸鍨嬪彿">
- <up-input
- v-model="product.specification"
- placeholder="璇烽�夋嫨瑙勬牸鍨嬪彿"
- readonly
- @click="openModelPicker(index)"
- />
+ <up-input v-model="product.ProductModel"
+ placeholder="璇烽�夋嫨瑙勬牸鍨嬪彿"
+ readonly
+ @click="openModelPicker(index)" />
<template #right>
- <up-icon name="arrow-right" @click="openModelPicker(index)"></up-icon>
+ <up-icon name="arrow-right"
+ @click="openModelPicker(index)"></up-icon>
</template>
</up-form-item>
<up-form-item label="鍗曚綅">
- <up-input v-model="product.unit" placeholder="璇疯緭鍏ュ崟浣�" clearable />
+ <up-input v-model="product.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable />
</up-form-item>
<up-form-item label="鏁伴噺">
- <up-input
- v-model="product.quantity"
- type="number"
- placeholder="璇疯緭鍏ユ暟閲�"
- clearable
- @blur="calculateAmount(product)"
- />
+ <up-input v-model="product.quantity"
+ type="number"
+ placeholder="璇疯緭鍏ユ暟閲�"
+ clearable
+ @blur="calculateAmount(product)" />
</up-form-item>
<up-form-item label="鍗曚环">
- <up-input
- v-model="product.unitPrice"
- type="number"
- placeholder="璇疯緭鍏ュ崟浠�"
- clearable
- @blur="calculateAmount(product)"
- />
+ <up-input v-model="product.unitPrice"
+ type="number"
+ placeholder="璇疯緭鍏ュ崟浠�"
+ clearable
+ @blur="calculateAmount(product)" />
</up-form-item>
<up-form-item label="閲戦">
- <up-input :model-value="formatAmount(product.amount)" disabled placeholder="鑷姩璁$畻" />
+ <up-input :model-value="formatAmount(product.amount)"
+ disabled
+ placeholder="鑷姩璁$畻" />
</up-form-item>
</view>
</view>
</view>
</u-cell-group>
-
- <u-cell-group title="姹囨�讳俊鎭�" class="form-section">
+ <u-cell-group title="姹囨�讳俊鎭�"
+ class="form-section">
<up-form-item label="鎶ヤ环鎬婚">
- <up-input :model-value="formatAmount(totalAmount)" disabled placeholder="鑷姩姹囨��" />
+ <up-input :model-value="formatAmount(totalAmount)"
+ disabled
+ placeholder="鑷姩姹囨��" />
</up-form-item>
</u-cell-group>
</up-form>
</view>
-
- <FooterButtons :loading="loading" confirmText="淇濆瓨" @cancel="goBack" @confirm="handleSubmit" />
-
- <up-action-sheet :show="showCustomerSheet" title="閫夋嫨瀹㈡埛" :actions="customerActions" @select="onSelectCustomer" @close="showCustomerSheet = false" />
- <up-action-sheet :show="showSalespersonSheet" title="閫夋嫨涓氬姟鍛�" :actions="salespersonActions" @select="onSelectSalesperson" @close="showSalespersonSheet = false" />
- <up-action-sheet :show="showProductSheet" title="閫夋嫨浜у搧" :actions="productActions" @select="onSelectProduct" @close="showProductSheet = false" />
- <up-action-sheet :show="showModelSheet" title="閫夋嫨瑙勬牸鍨嬪彿" :actions="modelActions" @select="onSelectModel" @close="showModelSheet = false" />
- <up-datetime-picker :show="showQuotationDatePicker" v-model="quotationDateValue" mode="date" @confirm="onQuotationDateConfirm" @cancel="showQuotationDatePicker = false" />
- <up-datetime-picker :show="showValidDatePicker" v-model="validDateValue" mode="date" @confirm="onValidDateConfirm" @cancel="showValidDatePicker = false" />
+ <FooterButtons :loading="loading"
+ confirmText="淇濆瓨"
+ @cancel="goBack"
+ @confirm="handleSubmit" />
+ <up-action-sheet :show="showCustomerSheet"
+ title="閫夋嫨瀹㈡埛"
+ :actions="customerActions"
+ @select="onSelectCustomer"
+ @close="showCustomerSheet = false" />
+ <up-action-sheet :show="showSalespersonSheet"
+ title="閫夋嫨涓氬姟鍛�"
+ :actions="salespersonActions"
+ @select="onSelectSalesperson"
+ @close="showSalespersonSheet = false" />
+ <up-action-sheet :show="showProductSheet"
+ title="閫夋嫨浜у搧"
+ :actions="productActions"
+ @select="onSelectProduct"
+ @close="showProductSheet = false" />
+ <up-action-sheet :show="showModelSheet"
+ title="閫夋嫨瑙勬牸鍨嬪彿"
+ :actions="modelActions"
+ @select="onSelectModel"
+ @close="showModelSheet = false" />
+ <up-datetime-picker :show="showQuotationDatePicker"
+ v-model="quotationDateValue"
+ mode="date"
+ @confirm="onQuotationDateConfirm"
+ @cancel="showQuotationDatePicker = false" />
+ <up-datetime-picker :show="showValidDatePicker"
+ v-model="validDateValue"
+ mode="date"
+ @confirm="onValidDateConfirm"
+ @cancel="showValidDatePicker = false" />
</view>
</template>
@@ -179,7 +202,11 @@
import { formatDateToYMD } from "@/utils/ruoyi";
import { modelList, productTreeList } from "@/api/basicData/product";
import { userListNoPageByTenantId } from "@/api/system/user";
- import { addQuotation, getCustomerList, getQuotationDetail, updateQuotation } from "@/api/salesManagement/salesQuotation";
+ import {
+ addQuotation,
+ getCustomerList,
+ updateQuotation,
+ } from "@/api/salesManagement/salesQuotation";
const formRef = ref();
const loading = ref(false);
@@ -199,47 +226,73 @@
const modelActions = ref([]);
let uidSeed = 1;
- let nextApproverId = 2;
const form = ref({
id: undefined,
quotationNo: "",
+ customerId: undefined,
customer: "",
salesperson: "",
quotationDate: "",
validDate: "",
paymentMethod: "",
- status: "寰呭鎵�",
+ status: "鑽夌",
remark: "",
- approveUserIds: "",
products: [],
+ subtotal: 0,
+ freight: 0,
+ otherFee: 0,
+ discountRate: 0,
+ discountAmount: 0,
totalAmount: 0,
});
-
- const approverNodes = ref([{ id: 1, userId: "", nickName: "" }]);
const rules = {
customer: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
salesperson: [{ required: true, message: "璇烽�夋嫨涓氬姟鍛�", trigger: "change" }],
- quotationDate: [{ required: true, message: "璇烽�夋嫨鎶ヤ环鏃ユ湡", trigger: "change" }],
+ quotationDate: [
+ { required: true, message: "璇烽�夋嫨鎶ヤ环鏃ユ湡", trigger: "change" },
+ ],
validDate: [{ required: true, message: "璇烽�夋嫨鏈夋晥鏈�", trigger: "change" }],
- paymentMethod: [{ required: true, message: "璇疯緭鍏ヤ粯娆炬柟寮�", trigger: "blur" }],
+ paymentMethod: [
+ { required: true, message: "璇疯緭鍏ヤ粯娆炬柟寮�", trigger: "blur" },
+ ],
};
const pageTitle = computed(() => (quotationId.value ? "缂栬緫鎶ヤ环" : "鏂板鎶ヤ环"));
const totalAmount = computed(() =>
- Number((form.value.products || []).reduce((sum, item) => sum + Number(item.amount || 0), 0).toFixed(2))
+ Number(
+ (form.value.products || [])
+ .reduce((sum, item) => sum + Number(item.amount || 0), 0)
+ .toFixed(2)
+ )
);
- const customerActions = computed(() => customerList.value.map(item => ({ name: item.customerName, value: item.customerName })));
- const salespersonActions = computed(() => salespersonList.value.map(item => ({ name: item.nickName, value: item.nickName })));
- const productActions = computed(() => productList.value.map(item => ({ name: item.label, value: item.value, label: item.label })));
+ const customerActions = computed(() =>
+ customerList.value.map(item => ({
+ name: item.customerName,
+ value: item.id,
+ }))
+ );
+ const salespersonActions = computed(() =>
+ salespersonList.value.map(item => ({
+ name: item.nickName,
+ value: item.nickName,
+ }))
+ );
+ const productActions = computed(() =>
+ productList.value.map(item => ({
+ name: item.label,
+ value: item.value,
+ label: item.label,
+ }))
+ );
const createEmptyProduct = () => ({
uid: `p_${uidSeed++}`,
productId: "",
product: "",
- specificationId: "",
- specification: "",
+ productModelId: "",
+ ProductModel: "",
unit: "",
quantity: 1,
unitPrice: 0,
@@ -254,7 +307,10 @@
if (item.children && item.children.length) {
walk(item.children);
} else {
- result.push({ label: item.label || item.productName || "", value: item.id || item.value });
+ result.push({
+ label: item.label || item.productName || "",
+ value: item.id || item.value,
+ });
}
});
};
@@ -266,18 +322,12 @@
const goBack = () => uni.navigateBack();
const calculateAmount = product => {
- product.amount = Number((Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2));
+ product.amount = Number(
+ (Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2)
+ );
form.value.totalAmount = totalAmount.value;
};
- const addApproverNode = () => approverNodes.value.push({ id: nextApproverId++, userId: "", nickName: "" });
- const removeApproverNode = index => approverNodes.value.splice(index, 1);
- const openApproverPicker = index => {
- uni.setStorageSync("stepIndex", index);
- uni.navigateTo({
- url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect",
- });
- };
const addProduct = () => form.value.products.push(createEmptyProduct());
const removeProduct = index => {
form.value.products.splice(index, 1);
@@ -285,8 +335,14 @@
};
const fetchModelOptions = async (productId, product) => {
- const rows = await modelList({ id: productId }).catch(() => []);
- product.modelOptions = Array.isArray(rows) ? rows : [];
+ try {
+ const res = await modelList({ id: productId });
+ const rows = res?.data?.records || res?.data || res?.records || res || [];
+ product.modelOptions = Array.isArray(rows) ? rows : [];
+ } catch (error) {
+ console.error("鑾峰彇瑙勬牸鍨嬪彿澶辫触:", error);
+ product.modelOptions = [];
+ }
};
const openProductPicker = index => {
@@ -300,7 +356,11 @@
uni.showToast({ title: "璇峰厛閫夋嫨浜у搧", icon: "none" });
return;
}
- modelActions.value = (current.modelOptions || []).map(item => ({ name: item.model, value: item.id, unit: item.unit }));
+ modelActions.value = (current.modelOptions || []).map(item => ({
+ name: item.model || item.specification,
+ value: item.id,
+ unit: item.unit,
+ }));
if (!modelActions.value.length) {
uni.showToast({ title: "鏆傛棤瑙勬牸鍨嬪彿", icon: "none" });
return;
@@ -309,27 +369,21 @@
};
const onSelectCustomer = action => {
- form.value.customer = action.value;
+ form.value.customerId = action.value;
+ form.value.customer = action.name;
showCustomerSheet.value = false;
};
const onSelectSalesperson = action => {
form.value.salesperson = action.value;
showSalespersonSheet.value = false;
};
- const onSelectApprover = data => {
- const { stepIndex, contact } = data || {};
- if (stepIndex === undefined || !contact) return;
- if (!approverNodes.value[stepIndex]) return;
- approverNodes.value[stepIndex].userId = contact.userId;
- approverNodes.value[stepIndex].nickName = contact.nickName;
- };
const onSelectProduct = action => {
const current = form.value.products[currentProductIndex.value];
if (!current) return;
current.productId = action.value;
current.product = action.label;
- current.specificationId = "";
- current.specification = "";
+ current.productModelId = "";
+ current.ProductModel = "";
current.unit = "";
current.modelOptions = [];
showProductSheet.value = false;
@@ -338,8 +392,8 @@
const onSelectModel = action => {
const current = form.value.products[currentProductIndex.value];
if (!current) return;
- current.specificationId = action.value;
- current.specification = action.name;
+ current.productModelId = action.value;
+ current.ProductModel = action.name;
current.unit = action.unit || current.unit;
showModelSheet.value = false;
};
@@ -353,70 +407,114 @@
};
const fetchBaseOptions = async () => {
- const [customers, users, productTree] = await Promise.all([
- getCustomerList({ current: -1, size: -1 }).catch(() => ({})),
- userListNoPageByTenantId().catch(() => ({})),
- productTreeList().catch(() => []),
- ]);
+ const [customers, users, productTree] = await Promise.all([
+ getCustomerList({ current: -1, size: -1 }).catch(() => ({})),
+ userListNoPageByTenantId().catch(() => ({})),
+ productTreeList().catch(() => []),
+ ]);
customerList.value = customers?.data?.records || customers?.records || [];
const userRows = users?.data || [];
salespersonList.value = Array.isArray(userRows) ? userRows : [];
- productList.value = flattenProductTree(Array.isArray(productTree) ? productTree : productTree?.data || []);
+ productList.value = flattenProductTree(
+ Array.isArray(productTree) ? productTree : productTree?.data || []
+ );
+ };
+
+ // 鏍规嵁鍚嶇О鍙嶆煡鑺傜偣 id锛屼究浜庝粎瀛樺悕绉版椂鐨勫弽鏄�
+ const findNodeIdByLabel = (nodes, label) => {
+ if (!label) return null;
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.label === label) return node.value;
+ if (node.children && node.children.length > 0) {
+ const found = findNodeIdByLabel(node.children, label);
+ if (found !== null && found !== undefined) return found;
+ }
+ }
+ return null;
};
const normalizeProductRows = async rows => {
- const normalized = await Promise.all((Array.isArray(rows) ? rows : []).map(async item => {
- const row = {
- uid: `p_${uidSeed++}`,
- productId: item.productId || "",
- product: item.product || item.productName || "",
- specificationId: item.specificationId || "",
- specification: item.specification || "",
- unit: item.unit || "",
- quantity: Number(item.quantity || 1),
- unitPrice: Number(item.unitPrice || 0),
- amount: Number(item.amount || 0),
- modelOptions: [],
- };
- if (row.productId) await fetchModelOptions(row.productId, row);
- return row;
- }));
+ const normalized = await Promise.all(
+ (Array.isArray(rows) ? rows : []).map(async item => {
+ const productName = item.product || item.productName || "";
+ // 浼樺厛鐢� productId锛涘鏋滃彧鏈夊悕绉帮紝灏濊瘯鍙嶆煡 id 浠ヤ究閫夋嫨鍣ㄥ弽鏄�
+ let resolvedProductId =
+ item.productId ||
+ findNodeIdByLabel(productList.value, productName) ||
+ "";
+
+ const row = {
+ uid: `p_${uidSeed++}`,
+ productId: resolvedProductId,
+ product: productName,
+ productModelId: item.productModelId || "",
+ ProductModel: item.ProductModel || item.specification || "",
+ unit: item.unit || "",
+ quantity: Number(item.quantity || 1),
+ unitPrice: Number(item.unitPrice || 0),
+ amount: Number(item.amount || 0),
+ modelOptions: [],
+ };
+
+ if (row.productId) {
+ await fetchModelOptions(row.productId, row);
+ // 濡傛灉娌℃湁 productModelId 浣嗘湁 ProductModel 鍚嶇О锛屽皾璇曚粠 modelOptions 涓尮閰� ID
+ if (!row.productModelId && row.ProductModel) {
+ const foundModel = row.modelOptions.find(
+ m =>
+ m.model === row.ProductModel ||
+ m.specification === row.ProductModel
+ );
+ if (foundModel) {
+ row.productModelId = foundModel.id;
+ // 缁熶竴浣跨敤 modelOptions 涓殑瀛楁
+ row.ProductModel =
+ foundModel.model || foundModel.specification || row.ProductModel;
+ row.unit = foundModel.unit || row.unit;
+ }
+ }
+ }
+ return row;
+ })
+ );
form.value.products = normalized;
};
const loadDetail = async () => {
if (!quotationId.value) return;
- uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
- try {
- const res = await getQuotationDetail({ id: quotationId.value });
- const data = res?.data || {};
+
+ // 鐩存帴浠庢湰鍦板瓨鍌ㄨ幏鍙栨暟鎹紝涓嶅啀璋冪敤璇︽儏鎺ュ彛
+ const cachedData = uni.getStorageSync("salesQuotationDetail");
+ if (
+ cachedData &&
+ (cachedData.id === quotationId.value ||
+ cachedData.id === Number(quotationId.value))
+ ) {
+ const data = cachedData;
form.value = {
...form.value,
id: data.id,
quotationNo: data.quotationNo || "",
+ customerId: data.customerId,
customer: data.customer || "",
salesperson: data.salesperson || "",
quotationDate: data.quotationDate || "",
validDate: data.validDate || "",
paymentMethod: data.paymentMethod || "",
- status: data.status || "寰呭鎵�",
+ status: data.status || "鑽夌",
remark: data.remark || "",
+ subtotal: data.subtotal || 0,
+ freight: data.freight || 0,
+ otherFee: data.otherFee || 0,
+ discountRate: data.discountRate || 0,
+ discountAmount: data.discountAmount || 0,
+ totalAmount: data.totalAmount || 0,
};
await normalizeProductRows(data.products || []);
- if (data.approveUserIds) {
- const ids = String(data.approveUserIds).split(",").map(item => item.trim()).filter(Boolean);
- approverNodes.value = ids.map((userId, index) => ({
- id: index + 1,
- userId,
- nickName: salespersonList.value.find(item => String(item.userId) === String(userId))?.nickName || "",
- }));
- nextApproverId = approverNodes.value.length + 1;
- }
form.value.totalAmount = totalAmount.value;
- } catch {
- uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "error" });
- } finally {
- uni.hideLoading();
+ } else {
+ console.warn("鏈壘鍒扮紦瀛樼殑鎶ヤ环鍗曡鎯呮暟鎹�");
}
};
@@ -425,16 +523,16 @@
uni.showToast({ title: "璇疯嚦灏戞坊鍔犱竴涓骇鍝�", icon: "none" });
return false;
}
- const invalid = form.value.products.some(item => !item.productId || !item.specificationId || !item.unit || !Number(item.quantity) || !Number(item.unitPrice));
+ const invalid = form.value.products.some(
+ item =>
+ !item.productId ||
+ !item.productModelId ||
+ !item.unit ||
+ !Number(item.quantity) ||
+ !Number(item.unitPrice)
+ );
if (invalid) {
uni.showToast({ title: "璇峰畬鍠勪骇鍝佷俊鎭�", icon: "none" });
- return false;
- }
- return true;
- };
- const validateApprovers = () => {
- if (approverNodes.value.some(item => !item.userId)) {
- uni.showToast({ title: "璇烽�夋嫨瀹℃壒浜�", icon: "none" });
return false;
}
return true;
@@ -442,17 +540,20 @@
const handleSubmit = async () => {
const valid = await formRef.value.validate().catch(() => false);
- if (!valid || !validateApprovers() || !validateProducts()) return;
+ if (!valid || !validateProducts()) return;
loading.value = true;
+
+ // 鍚屾鏈�鏂扮殑鎬婚
+ form.value.totalAmount = totalAmount.value;
+ form.value.subtotal = totalAmount.value;
+
const payload = {
...form.value,
- approveUserIds: approverNodes.value.map(item => item.userId).join(","),
- totalAmount: totalAmount.value,
products: form.value.products.map(item => ({
productId: item.productId,
product: item.product,
- specificationId: item.specificationId,
- specification: item.specification,
+ productModelId: item.productModelId,
+ ProductModel: item.ProductModel,
quantity: Number(item.quantity || 0),
unit: item.unit,
unitPrice: Number(item.unitPrice || 0),
@@ -486,16 +587,12 @@
onMounted(async () => {
await fetchBaseOptions();
- uni.$on("selectContact", onSelectApprover);
if (quotationId.value) {
await loadDetail();
}
});
- onUnmounted(() => {
- uni.$off("selectContact", onSelectApprover);
- uni.removeStorageSync("stepIndex");
- });
+ onUnmounted(() => {});
</script>
<style scoped lang="scss">
@@ -547,7 +644,6 @@
padding: 12px 12px 0;
}
- .node-list,
.product-list {
padding: 12px;
display: flex;
@@ -555,31 +651,12 @@
gap: 12px;
}
- .node-card {
- background: #f8fbff;
- border-radius: 12px;
- padding: 12px;
- border: 1px solid #e6eef8;
- }
-
- .picker-field {
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- .picker-field :deep(.u-input) {
- flex: 1;
- }
-
- .node-top,
.product-header {
display: flex;
align-items: center;
justify-content: space-between;
}
- .node-title,
.product-title {
font-size: 14px;
font-weight: 600;
diff --git a/src/pages/sales/salesQuotation/index.vue b/src/pages/sales/salesQuotation/index.vue
index a6a5103..4ecf910 100644
--- a/src/pages/sales/salesQuotation/index.vue
+++ b/src/pages/sales/salesQuotation/index.vue
@@ -1,47 +1,50 @@
<template>
<view class="sales-account">
- <PageHeader title="閿�鍞姤浠�" @back="goBack" />
-
+ <PageHeader title="閿�鍞姤浠�"
+ @back="goBack" />
<view class="search-section">
<view class="search-bar">
<view class="search-input">
- <up-input
- class="search-text"
- v-model="quotationNo"
- placeholder="璇疯緭鍏ユ姤浠峰崟鍙锋悳绱�"
- clearable
- @change="getList"
- />
+ <up-input class="search-text"
+ v-model="quotationNo"
+ placeholder="璇疯緭鍏ユ姤浠峰崟鍙锋悳绱�"
+ clearable
+ @change="getList" />
</view>
- <view class="filter-button" @click="getList">
- <up-icon name="search" size="24" color="#999"></up-icon>
+ <view class="filter-button"
+ @click="getList">
+ <up-icon name="search"
+ size="24"
+ color="#999"></up-icon>
</view>
</view>
</view>
-
<view class="tabs-section">
- <up-tabs
- v-model="tabValue"
- :list="tabList"
- itemStyle="width: 20%;height: 80rpx;"
- @change="onTabChange"
- />
+ <up-tabs v-model="tabValue"
+ :list="tabList"
+ itemStyle="width: 20%;height: 80rpx;"
+ @change="onTabChange" />
</view>
-
- <view v-if="quotationList.length > 0" class="ledger-list">
- <view v-for="item in quotationList" :key="item.id" class="ledger-item">
+ <view v-if="quotationList.length > 0"
+ class="ledger-list">
+ <view v-for="item in quotationList"
+ :key="item.id"
+ class="ledger-item">
<view class="item-header">
<view class="item-left">
<view class="document-icon">
- <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+ <up-icon name="file-text"
+ size="16"
+ color="#ffffff"></up-icon>
</view>
<text class="item-id">{{ item.quotationNo || "-" }}</text>
</view>
- <text class="item-index">{{ item.status || "-" }}</text>
+ <up-tag :text="item.status || '寰呭鎵�'"
+ :type="getStatusType(item.status)"
+ size="mini"
+ shape="circle" />
</view>
-
<up-divider></up-divider>
-
<view class="item-details">
<view class="detail-row">
<text class="detail-label">瀹㈡埛鍚嶇О</text>
@@ -60,43 +63,45 @@
<text class="detail-value">{{ item.validDate || "-" }}</text>
</view>
<view class="detail-row">
- <text class="detail-label">浠樻鏂瑰紡</text>
- <text class="detail-value">{{ item.paymentMethod || "-" }}</text>
- </view>
- <view class="detail-row">
<text class="detail-label">鎶ヤ环閲戦</text>
<text class="detail-value highlight">{{ formatAmount(item.totalAmount) }}</text>
</view>
- <view class="detail-row">
+ <view class="detail-row"
+ v-if="item.remark">
<text class="detail-label">澶囨敞</text>
- <text class="detail-value">{{ item.remark || "-" }}</text>
+ <text class="detail-value">{{ item.remark }}</text>
</view>
</view>
-
<view class="action-buttons">
- <up-button
- class="action-btn"
- size="small"
- type="primary"
- :disabled="!canEdit(item)"
- @click="goEdit(item)"
- >
- 缂栬緫
- </up-button>
- <up-button class="action-btn" size="small" @click="goDetail(item)">璇︽儏</up-button>
- <up-button class="action-btn" size="small" type="error" plain @click="handleDelete(item)">
+ <up-button class="action-btn"
+ size="small"
+ type="primary"
+ :disabled="!canEdit(item)"
+ @click="goEdit(item)">
+ 缂栬緫
+ </up-button>
+ <up-button class="action-btn"
+ size="small"
+ @click="goDetail(item)">璇︽儏</up-button>
+ <up-button class="action-btn"
+ size="small"
+ type="error"
+ plain
+ @click="handleDelete(item)">
鍒犻櫎
</up-button>
</view>
</view>
</view>
-
- <view v-else class="no-data">
+ <view v-else
+ class="no-data">
<text>鏆傛棤閿�鍞姤浠锋暟鎹�</text>
</view>
-
- <view class="fab-button" @click="goAdd">
- <up-icon name="plus" size="28" color="#ffffff"></up-icon>
+ <view class="fab-button"
+ @click="goAdd">
+ <up-icon name="plus"
+ size="28"
+ color="#ffffff"></up-icon>
</view>
</view>
</template>
@@ -105,7 +110,10 @@
import { reactive, ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import PageHeader from "@/components/PageHeader.vue";
- import { deleteQuotation, getQuotationList } from "@/api/salesManagement/salesQuotation";
+ import {
+ deleteQuotation,
+ getQuotationList,
+ } from "@/api/salesManagement/salesQuotation";
const quotationNo = ref("");
const quotationList = ref([]);
@@ -129,11 +137,13 @@
};
const goAdd = () => {
+ uni.removeStorageSync("salesQuotationDetail");
uni.navigateTo({ url: "/pages/sales/salesQuotation/edit" });
};
const goEdit = item => {
if (!canEdit(item)) return;
+ uni.setStorageSync("salesQuotationDetail", item || {});
uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${item.id}` });
};
@@ -159,6 +169,16 @@
return `楼${num.toFixed(2)}`;
};
+ const getStatusType = status => {
+ const statusMap = {
+ 寰呭鎵�: "info",
+ 瀹℃牳涓�: "primary",
+ 閫氳繃: "success",
+ 鎷掔粷: "danger",
+ };
+ return statusMap[status] || "info";
+ };
+
const getList = () => {
uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
getQuotationList({
--
Gitblit v1.9.3