| | |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | v-model="quotationNo" |
| | | placeholder="请输入报价单号搜索" |
| | | v-model="searchForm.product" |
| | | placeholder="请输入产品名称搜索" |
| | | clearable |
| | | @change="getList" |
| | | @change="getList(true)" |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="getList"> |
| | | <view class="filter-button" @click="getList(true)"> |
| | | <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" |
| | | /> |
| | | </view> |
| | | |
| | | <view v-if="quotationList.length > 0" class="ledger-list"> |
| | |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" size="16" color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.quotationNo || "-" }}</text> |
| | | <text class="item-id">{{ item.product || "-" }}</text> |
| | | </view> |
| | | <text class="item-index">{{ item.status || "-" }}</text> |
| | | </view> |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客户名称</text> |
| | | <text class="detail-value">{{ item.customer || "-" }}</text> |
| | | <text class="detail-label">规格</text> |
| | | <text class="detail-value">{{ item.specification || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">业务员</text> |
| | | <text class="detail-value">{{ item.salesperson || "-" }}</text> |
| | | <text class="detail-label">纸张</text> |
| | | <text class="detail-value">{{ item.paper || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">报价日期</text> |
| | | <text class="detail-value">{{ item.quotationDate || "-" }}</text> |
| | | <text class="detail-label">定量</text> |
| | | <text class="detail-value">{{ item.paperWeight || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">有效期至</text> |
| | | <text class="detail-value">{{ item.validDate || "-" }}</text> |
| | | <text class="detail-label">单价</text> |
| | | <text class="detail-value">{{ formatAmount(item.unitPrice) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">付款方式</text> |
| | | <text class="detail-value">{{ item.paymentMethod || "-" }}</text> |
| | | <text class="detail-label">印版费</text> |
| | | <text class="detail-value">{{ formatAmount(item.printingFee) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">报价金额</text> |
| | | <text class="detail-value highlight">{{ formatAmount(item.totalAmount) }}</text> |
| | | <text class="detail-label">刀版费</text> |
| | | <text class="detail-value">{{ formatAmount(item.dieCuttingFee) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">磨具费</text> |
| | | <text class="detail-value">{{ formatAmount(item.grindingFee) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">数量</text> |
| | | <text class="detail-value">{{ Number(item.quantity || 0) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">备注</text> |
| | |
| | | </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" type="primary" @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> |
| | | <up-button class="action-btn" size="small" type="error" plain @click="handleDelete(item)">删除</up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <text>暂无销售报价数据</text> |
| | | <text>暂无数据</text> |
| | | </view> |
| | | |
| | | <view class="fab-button" @click="goAdd"> |
| | |
| | | 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 } from "@/api/salesManagement/salesQuotation"; |
| | | import { quotationProductListPage } from "@/api/salesManagement/salesQuotationProduct"; |
| | | |
| | | const quotationNo = ref(""); |
| | | const searchForm = reactive({ product: "" }); |
| | | const quotationList = ref([]); |
| | | |
| | | const tabList = reactive([ |
| | | { name: "全部", value: "" }, |
| | | { name: "待审批", value: "待审批" }, |
| | | { name: "审核中", value: "审核中" }, |
| | | { name: "通过", value: "通过" }, |
| | | { name: "拒绝", value: "拒绝" }, |
| | | ]); |
| | | const tabValue = ref(0); |
| | | |
| | | const page = { |
| | | current: -1, |
| | | size: -1, |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const goAdd = () => { |
| | | uni.navigateTo({ url: "/pages/sales/salesQuotation/edit" }); |
| | | }; |
| | | |
| | | const goBack = () => uni.navigateBack(); |
| | | const goAdd = () => uni.navigateTo({ url: "/pages/sales/salesQuotation/edit" }); |
| | | const goEdit = item => { |
| | | if (!canEdit(item)) return; |
| | | const source = item?.__raw || item || {}; |
| | | uni.setStorageSync("salesQuotationEdit", source); |
| | | uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${item.id}` }); |
| | | }; |
| | | |
| | | const goDetail = item => { |
| | | uni.setStorageSync("salesQuotationDetail", item || {}); |
| | | uni.navigateTo({ url: `/pages/sales/salesQuotation/detail?id=${item.id}` }); |
| | | }; |
| | | |
| | | const canEdit = item => ["待审批", "拒绝"].includes(item?.status); |
| | | const formatAmount = amount => `¥${Number(amount || 0).toFixed(2)}`; |
| | | |
| | | const onTabChange = val => { |
| | | tabValue.value = val.index; |
| | | getList(); |
| | | }; |
| | | const calcTotalAmountFromProducts = products => |
| | | Number( |
| | | (products || []) |
| | | .reduce((sum, product) => { |
| | | const unitPrice = Number(product?.unitPrice || 0); |
| | | const printingFee = Number(product?.printingFee || 0); |
| | | const dieCuttingFee = Number(product?.dieCuttingFee || 0); |
| | | const grindingFee = Number(product?.grindingFee || 0); |
| | | return sum + unitPrice + printingFee + dieCuttingFee + grindingFee; |
| | | }, 0) |
| | | .toFixed(2) |
| | | ); |
| | | |
| | | const getCurrentStatus = () => { |
| | | const currentTab = tabList[tabValue.value]; |
| | | return currentTab?.value || ""; |
| | | }; |
| | | |
| | | const formatAmount = amount => { |
| | | const num = Number(amount || 0); |
| | | return `¥${num.toFixed(2)}`; |
| | | const normalizeQuotation = row => { |
| | | const sourceProducts = Array.isArray(row?.products) && row.products.length ? row.products : [row]; |
| | | const first = sourceProducts[0] || {}; |
| | | return { |
| | | ...row, |
| | | __raw: row, |
| | | customer: row?.customer || row?.customerName || first?.customer || first?.customerName || "", |
| | | salesperson: row?.salesperson || row?.salesman || row?.salesPerson || first?.salesperson || "", |
| | | quotationDate: row?.quotationDate || row?.quoteDate || first?.quotationDate || "", |
| | | validDate: row?.validDate || row?.expireDate || first?.validDate || "", |
| | | paymentMethod: row?.paymentMethod || row?.paymentType || first?.paymentMethod || "", |
| | | product: first.product || first.productName || row?.product || "", |
| | | specification: first.specification || row?.specification || "", |
| | | paper: first.paper || row?.paper || "", |
| | | paperWeight: first.paperWeight || row?.paperWeight || "", |
| | | unitPrice: Number(first.unitPrice || row?.unitPrice || 0), |
| | | printingFee: Number(first.printingFee || row?.printingFee || 0), |
| | | dieCuttingFee: Number(first.dieCuttingFee || row?.dieCuttingFee || 0), |
| | | grindingFee: Number(first.grindingFee || row?.grindingFee || 0), |
| | | quantity: Number(first.quantity || row?.quantity || 0), |
| | | quotationNo: row?.quotationNo || first?.quotationNo || "", |
| | | totalAmount: Number(row?.totalAmount || calcTotalAmountFromProducts(sourceProducts)), |
| | | }; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | uni.showLoading({ title: "加载中...", mask: true }); |
| | | getQuotationList({ |
| | | ...page, |
| | | quotationNo: quotationNo.value, |
| | | status: getCurrentStatus(), |
| | | quotationProductListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | product: String(searchForm.product || "").trim(), |
| | | }) |
| | | .then(res => { |
| | | const records = res?.data?.records || res?.records || []; |
| | | quotationList.value = Array.isArray(records) ? records : []; |
| | | quotationList.value = Array.isArray(records) ? records.map(normalizeQuotation) : []; |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "查询失败", icon: "error" }); |
| | |
| | | if (!item?.id) return; |
| | | uni.showModal({ |
| | | title: "删除确认", |
| | | content: "确认删除该报价单吗?", |
| | | content: "确认删除该报价吗?", |
| | | success: res => { |
| | | if (!res.confirm) return; |
| | | uni.showLoading({ title: "处理中...", mask: true }); |
| | | deleteQuotation(item.id) |
| | | deleteQuotation([item.id]) |
| | | .then(() => { |
| | | uni.showToast({ title: "删除成功", icon: "success" }); |
| | | getList(); |
| | |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | .tabs-section { |
| | | background: #ffffff; |
| | | padding: 0 12px 8px 12px; |
| | | } |
| | | |
| | | .item-index { |
| | | max-width: 180rpx; |