| src/api/basicData/customerFile.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/salesManagement/salesQuotationProduct.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/config.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/manifest.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/basicData/customerFile/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/basicData/customerFile/edit.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/basicData/customerFile/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesQuotation/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesQuotation/edit.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesQuotation/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/works.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/utils/versionUpgrade.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/basicData/customerFile.js
@@ -1,7 +1,5 @@ // å®¢æ·æ¡£æ¡é¡µé¢æ¥å£ import request from '@/utils/request' // å页æ¥è¯¢ export function listCustomer(query) { return request({ url: '/basic/customer/list', @@ -9,14 +7,76 @@ params: query }) } // æ¥è¯¢å®¢æ·æ¡£æ¡è¯¦ç» // å®¢æ·æ¡£æ¡ç§æµ·æ¥è¯¢ export function listCustomerPrivatePool(query) { return request({ url: '/customerPrivatePool/listPage', method: 'get', params: query }) } export function addCustomerPrivatePool(data) { return request({ url: '/customerPrivatePool/add', method: 'post', data: data }) } export function addCustomerPrivate(data) { return request({ url: '/customerPrivate/add', method: 'post', data: data }) } export function delCustomerPrivate(ids) { return request({ url: '/customerPrivate/delete', method: 'delete', data: ids }) } export function delCustomerPrivatePool(id) { return request({ url: '/customerPrivatePool/delete/' + id, method: 'delete', }) } export function shareCustomer(data) { return request({ url: '/customerPrivatePool/together', method: 'post', data: data }) } export function getCustomer(id) { return request({ url: '/basic/customer/' + id, method: 'get' }) } // æ°å¢å®¢æ·æ¡£æ¡ export function getCustomerPrivatePoolById(id) { return request({ url: '/customerPrivatePool/getbyId/' + id, method: 'get' }) } export function getCustomerPrivatePoolInfo(id) { return request({ url: '/customerPrivatePool/info/' + id, method: 'get' }) } export function addCustomer(data) { return request({ url: '/basic/customer/addCustomer', @@ -24,7 +84,7 @@ data: data }) } // ä¿®æ¹å®¢æ·æ¡£æ¡ export function updateCustomer(data) { return request({ url: '/basic/customer/updateCustomer', @@ -32,7 +92,15 @@ data: data }) } // 导åºå®¢æ·æ¡£æ¡ export function updateCustomerPrivatePool(data) { return request({ url: '/customerPrivatePool/update', method: 'put', data: data }) } export function exportCustomer(query) { return request({ url: '/basic/customer/export', @@ -41,7 +109,7 @@ responseType: 'blob' }) } // å é¤å®¢æ·æ¡£æ¡ export function delCustomer(ids) { return request({ url: '/basic/customer/delCustomer', @@ -50,8 +118,6 @@ }) } // æ°å¢å®¢æ·è·è¿ export function addCustomerFollow(data) { return request({ url: '/basic/customer-follow/add', @@ -60,7 +126,6 @@ }) } // ä¿®æ¹å®¢æ·è·è¿ export function updateCustomerFollow(data) { return request({ url: '/basic/customer-follow/edit', @@ -68,15 +133,14 @@ data: data, }) } // å é¤å®¢æ·è·è¿ export function delCustomerFollow(id) { return request({ url: '/basic/customer-follow/'+id, url: '/basic/customer-follow/' + id, method: 'delete', }) } // å访æé-æ°å¢/æ´æ° export function addReturnVisit(data) { return request({ url: '/basic/customer-follow/return-visit', @@ -84,10 +148,10 @@ data: data }) } // è·åå访æé详æ export function getReturnVisit(id) { return request({ url: '/basic/customer-follow/return-visit/' + id, method: 'get' }) } } src/api/salesManagement/salesQuotationProduct.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,48 @@ import request from "@/utils/request"; export function quotationProductListPage(query) { return request({ url: "/sales/quotationProduct/listPage", method: "get", params: query, }); } export function quotationProductList(query) { return request({ url: "/sales/quotationProduct/list", method: "get", params: query, }); } export function getQuotationProductInfo(id) { return request({ url: `/sales/quotationProduct/${id}`, method: "get", }); } export function addOrUpdateQuotationProduct(data) { return request({ url: "/sales/quotationProduct/addOrUpdate", method: "post", data, }); } export function editQuotationProduct(data) { return request({ url: "/sales/quotationProduct/edit", method: "post", data, }); } export function deleteQuotationProduct(ids) { return request({ url: "/sales/quotationProduct/delete", method: "delete", data: ids, }); } src/config.js
@@ -1,7 +1,7 @@ // åºç¨å ¨å±é ç½® const config = { baseUrl: "http://1.15.17.182:9003", fileUrl: "http://1.15.17.182:9002", baseUrl: "http://1.15.17.182:9046", fileUrl: "http://1.15.17.182:9047", // åºç¨ä¿¡æ¯ appInfo: { // åºç¨åç§° src/manifest.json
@@ -1,6 +1,6 @@ { "name" : "ä¿¡æ¯ç®¡ç", "appid" : "__UNI__099A590", "name" : "天津å®ä¸", "appid" : "__UNI__FDF4041", "description" : "", "versionName" : "1.1.5", "versionCode" : 100, src/pages/basicData/customerFile/detail.vue
@@ -15,28 +15,12 @@ <text class="info-value">{{ detailData.customerType || "-" }}</text> </view> <view class="info-item"> <text class="info-label">纳ç¨äººè¯å«å·</text> <text class="info-value">{{ detailData.taxpayerIdentificationNumber || "-" }}</text> </view> <view class="info-item"> <text class="info-label">å ¬å¸å°å</text> <text class="info-value">{{ detailData.companyAddress || "-" }}</text> </view> <view class="info-item"> <text class="info-label">å ¬å¸çµè¯</text> <text class="info-value">{{ detailData.companyPhone || "-" }}</text> </view> <view class="info-item"> <text class="info-label">æ³äºº</text> <text class="info-value">{{ detailData.corporation || "-" }}</text> </view> <view class="info-item"> <text class="info-label">代ç人</text> <text class="info-value">{{ detailData.agent || "-" }}</text> </view> <view class="info-item"> <text class="info-label">ä¼ ç</text> <text class="info-value">{{ detailData.fax || "-" }}</text> </view> </view> </view> @@ -52,27 +36,9 @@ <text class="info-label">èç³»çµè¯</text> <text class="info-value">{{ detailData.contactPhone || "-" }}</text> </view> </view> </view> <view class="section"> <view class="section-title">é¶è¡ä¿¡æ¯</view> <view class="info-list"> <view class="info-item"> <text class="info-label">é¶è¡åºæ¬æ·</text> <text class="info-value">{{ detailData.basicBankAccount || "-" }}</text> </view> <view class="info-item"> <text class="info-label">é¶è¡è´¦å·</text> <text class="info-value">{{ detailData.bankAccount || "-" }}</text> </view> <view class="info-item"> <text class="info-label">弿·é¶è¡</text> <text class="info-value">{{ detailData.bankName || "-" }}</text> </view> <view class="info-item"> <text class="info-label">弿·è¡å·</text> <text class="info-value">{{ detailData.bankCode || "-" }}</text> <text class="info-label">è系人å²ä½</text> <text class="info-value">{{ detailData.contactPosition || "-" }}</text> </view> </view> </view> src/pages/basicData/customerFile/edit.vue
@@ -19,7 +19,7 @@ clearable /> </up-form-item> <up-form-item label="客æ·åç±»" prop="customerType" required> <up-form-item label="客æ·åç±»" prop="customerType"> <up-input v-model="customerTypeText" placeholder="è¯·éæ©å®¢æ·åç±»" @@ -30,48 +30,17 @@ <up-icon name="arrow-right" @click="showCustomerTypeSheet = true"></up-icon> </template> </up-form-item> <up-form-item label="纳ç¨äººè¯å«å·" prop="taxpayerIdentificationNumber" > <up-input v-model="form.taxpayerIdentificationNumber" placeholder="请è¾å ¥çº³ç¨äººè¯å«å·" clearable /> </up-form-item> <up-form-item label="å ¬å¸å°å" prop="companyAddress"> <up-form-item label="å ¬å¸å°å" prop="companyAddress" required> <up-input v-model="form.companyAddress" placeholder="请è¾å ¥å ¬å¸å°å" clearable /> </up-form-item> <up-form-item label="å ¬å¸çµè¯" prop="companyPhone"> <up-form-item label="å ¬å¸çµè¯" prop="companyPhone" required> <up-input v-model="form.companyPhone" placeholder="请è¾å ¥å ¬å¸çµè¯" clearable /> </up-form-item> <up-form-item label="æ³äºº" prop="corporation"> <up-input v-model="form.corporation" placeholder="请è¾å ¥æ³äºº" clearable /> </up-form-item> <up-form-item label="代ç人" prop="agent"> <up-input v-model="form.agent" placeholder="请è¾å ¥ä»£ç人" clearable /> </up-form-item> <up-form-item label="ä¼ ç" prop="fax"> <up-input v-model="form.fax" placeholder="请è¾å ¥ä¼ ç" clearable /> </up-form-item> @@ -92,34 +61,10 @@ clearable /> </up-form-item> </u-cell-group> <u-cell-group title="é¶è¡ä¿¡æ¯" class="form-section"> <up-form-item label="é¶è¡åºæ¬æ·" prop="basicBankAccount"> <up-form-item label="è系人å²ä½" prop="contactPosition"> <up-input v-model="form.basicBankAccount" placeholder="请è¾å ¥é¶è¡åºæ¬æ·" clearable /> </up-form-item> <up-form-item label="é¶è¡è´¦å·" prop="bankAccount"> <up-input v-model="form.bankAccount" placeholder="请è¾å ¥é¶è¡è´¦å·" clearable /> </up-form-item> <up-form-item label="弿·é¶è¡" prop="bankName"> <up-input v-model="form.bankName" placeholder="请è¾å ¥å¼æ·é¶è¡" clearable /> </up-form-item> <up-form-item label="弿·è¡å·" prop="bankCode"> <up-input v-model="form.bankCode" placeholder="请è¾å ¥å¼æ·è¡å·" v-model="form.contactPosition" placeholder="请è¾å ¥è系人å²ä½" clearable /> </up-form-item> @@ -178,30 +123,25 @@ const form = ref({ customerName: "", customerType: "", taxpayerIdentificationNumber: "", companyAddress: "", companyPhone: "", corporation: "", agent: "", fax: "", contactPerson: "", contactPhone: "", basicBankAccount: "", bankAccount: "", bankName: "", bankCode: "", contactPosition: "", maintainer: "", maintenanceTime: "", }); const rules = { customerName: [{ required: true, message: "请è¾å ¥å®¢æ·åç§°", trigger: "blur" }], customerType: [{ required: true, message: "è¯·éæ©å®¢æ·åç±»", trigger: "change" }], companyAddress: [{ required: true, message: "请è¾å ¥å ¬å¸å°å", trigger: "blur" }], companyPhone: [{ required: true, message: "请è¾å ¥å ¬å¸çµè¯", trigger: "blur" }], }; const customerTypeActions = [ { name: "é¶å®å®¢æ·", value: "é¶å®å®¢æ·" }, { name: "ç»éå客æ·", value: "ç»éå客æ·" }, { name: "è¿éåº", value: "è¿éåº" }, ]; const pageTitle = computed(() => src/pages/basicData/customerFile/index.vue
@@ -23,7 +23,7 @@ <up-tabs v-model="tabValue" :list="tabList" itemStyle="width: 33.33%;height: 80rpx;" itemStyle="width: 25%;height: 80rpx;" @change="onTabChange" /> </view> @@ -44,10 +44,6 @@ <view class="item-details"> <view class="detail-row"> <text class="detail-label">纳ç¨äººè¯å«å·</text> <text class="detail-value">{{ item.taxpayerIdentificationNumber || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">å ¬å¸çµè¯</text> <text class="detail-value">{{ item.companyPhone || "-" }}</text> </view> @@ -56,12 +52,16 @@ <text class="detail-value">{{ item.companyAddress || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">æ³äºº</text> <text class="detail-value">{{ item.corporation || "-" }}</text> <text class="detail-label">è系人</text> <text class="detail-value">{{ item.contactPerson || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">代ç人</text> <text class="detail-value">{{ item.agent || "-" }}</text> <text class="detail-label">èç³»çµè¯</text> <text class="detail-value">{{ item.contactPhone || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">è系人å²ä½</text> <text class="detail-value">{{ item.contactPosition || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">ç»´æ¤äºº</text> @@ -106,6 +106,7 @@ { name: "å ¨é¨å®¢æ·", value: "" }, { name: "é¶å®å®¢æ·", value: "é¶å®å®¢æ·" }, { name: "ç»éå客æ·", value: "ç»éå客æ·" }, { name: "è¿éåº", value: "è¿éåº" }, ]); const tabValue = ref(0); src/pages/sales/salesQuotation/detail.vue
@@ -6,142 +6,109 @@ <view class="section"> <view class="section-title">åºç¡ä¿¡æ¯</view> <view class="info-list"> <view class="info-item"> <text class="info-label">æ¥ä»·åå·</text> <text class="info-value">{{ detailData.quotationNo || "-" }}</text> </view> <view class="info-item"> <text class="info-label">客æ·åç§°</text> <text class="info-value">{{ detailData.customer || "-" }}</text> </view> <view class="info-item"> <text class="info-label">ä¸å¡å</text> <text class="info-value">{{ detailData.salesperson || "-" }}</text> </view> <view class="info-item"> <text class="info-label">æ¥ä»·æ¥æ</text> <text class="info-value">{{ detailData.quotationDate || "-" }}</text> </view> <view class="info-item"> <text class="info-label">æææè³</text> <text class="info-value">{{ detailData.validDate || "-" }}</text> </view> <view class="info-item"> <text class="info-label">仿¬¾æ¹å¼</text> <text class="info-value">{{ detailData.paymentMethod || "-" }}</text> </view> <view class="info-item"> <text class="info-label">审æ¹ç¶æ</text> <text class="info-value">{{ detailData.status || "-" }}</text> </view> <view class="info-item"> <text class="info-label">æ¥ä»·æ»é¢</text> <text class="info-value highlight">{{ formatAmount(detailData.totalAmount) }}</text> </view> <view class="info-item"> <text class="info-label">夿³¨</text> <text class="info-value">{{ detailData.remark || "-" }}</text> </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 class="info-item"><text class="info-label">æ¥ä»·åå·</text><text class="info-value">{{ detailData.quotationNo || "-" }}</text></view> <view class="info-item"><text class="info-label">客æ·</text><text class="info-value">{{ detailData.customer || "-" }}</text></view> <view class="info-item"><text class="info-label">ä¸å¡å</text><text class="info-value">{{ detailData.salesperson || "-" }}</text></view> <view class="info-item"><text class="info-label">æ¥ä»·æ¥æ</text><text class="info-value">{{ detailData.quotationDate || "-" }}</text></view> <view class="info-item"><text class="info-label">æææè³</text><text class="info-value">{{ detailData.validDate || "-" }}</text></view> <view class="info-item"><text class="info-label">仿¬¾æ¹å¼</text><text class="info-value">{{ detailData.paymentMethod || "-" }}</text></view> <view class="info-item"><text class="info-label">æ»é¢</text><text class="info-value highlight">{{ formatAmount(totalAmount) }}</text></view> <view class="info-item"><text class="info-label">夿³¨</text><text class="info-value">{{ detailData.remark || "-" }}</text></view> </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="products.length" class="product-list"> <view v-for="(item, index) in products" :key="index" class="product-card"> <view class="product-head">产å {{ index + 1 }}</view> <view class="info-item"> <text class="info-label">产ååç§°</text> <text class="info-value">{{ item.product || item.productName || "-" }}</text> </view> <view class="info-item"> <text class="info-label">è§æ ¼åå·</text> <text class="info-value">{{ item.specification || "-" }}</text> </view> <view class="info-item"> <text class="info-label">åä½</text> <text class="info-value">{{ item.unit || "-" }}</text> </view> <view class="info-item"> <text class="info-label">æ°é</text> <text class="info-value">{{ item.quantity || "-" }}</text> </view> <view class="info-item"> <text class="info-label">åä»·</text> <text class="info-value">{{ formatAmount(item.unitPrice) }}</text> </view> <view class="info-item"> <text class="info-label">éé¢</text> <text class="info-value highlight">{{ formatAmount(item.amount) }}</text> </view> <view class="info-item"><text class="info-label">产å</text><text class="info-value">{{ item.product || item.productName || "-" }}</text></view> <view class="info-item"><text class="info-label">è§æ ¼</text><text class="info-value">{{ item.specification || "-" }}</text></view> <view class="info-item"><text class="info-label">åä½</text><text class="info-value">{{ item.unit || "-" }}</text></view> <view class="info-item"><text class="info-label">çº¸å¼ </text><text class="info-value">{{ item.paper || "-" }}</text></view> <view class="info-item"><text class="info-label">å®é</text><text class="info-value">{{ item.paperWeight || "-" }}</text></view> <view class="info-item"><text class="info-label">æ°é</text><text class="info-value">{{ Number(item.quantity || 0) }}</text></view> <view class="info-item"><text class="info-label">åä»·</text><text class="info-value">{{ formatAmount(item.unitPrice) }}</text></view> <view class="info-item"><text class="info-label">å°çè´¹</text><text class="info-value">{{ formatAmount(item.printingFee) }}</text></view> <view class="info-item"><text class="info-label">åçè´¹</text><text class="info-value">{{ formatAmount(item.dieCuttingFee) }}</text></view> <view class="info-item"><text class="info-label">ç£¨å ·è´¹</text><text class="info-value">{{ formatAmount(item.grindingFee) }}</text></view> <view class="info-item"><text class="info-label">éé¢</text><text class="info-value highlight">{{ formatAmount(item.amount) }}</text></view> </view> </view> <view v-else class="empty-box"> <text>ææ äº§åæç»</text> </view> <view v-else class="empty-box"><text>ææ äº§åæç»</text></view> </view> </view> <FooterButtons cancelText="è¿å" confirmText="ç¼è¾" @cancel="goBack" @confirm="goEdit" /> <view class="detail-footer"> <up-button type="primary" @click="goBack">è¿å</up-button> </view> </view> </template> <script setup> import { computed, ref } from "vue"; import { onLoad, onShow } from "@dcloudio/uni-app"; import FooterButtons from "@/components/FooterButtons.vue"; import PageHeader from "@/components/PageHeader.vue"; import { getQuotationDetail } from "@/api/salesManagement/salesQuotation"; 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 products = computed(() => { const rows = detailData.value?.products; if (Array.isArray(rows) && rows.length) return rows; if (detailData.value?.product || detailData.value?.productName) return [detailData.value]; return []; }); const goBack = () => { uni.navigateBack(); }; const totalAmount = computed(() => { const backendTotal = Number(detailData.value?.totalAmount || 0); if (backendTotal > 0) return backendTotal; return Number( products.value .reduce((sum, item) => { const unitPrice = Number(item?.unitPrice || 0); const printingFee = Number(item?.printingFee || 0); const dieCuttingFee = Number(item?.dieCuttingFee || 0); const grindingFee = Number(item?.grindingFee || 0); return sum + unitPrice + printingFee + dieCuttingFee + grindingFee; }, 0) .toFixed(2) ); }); const goEdit = () => { if (!quotationId.value) return; uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${quotationId.value}` }); }; const goBack = () => uni.navigateBack(); const formatAmount = amount => `Â¥${Number(amount || 0).toFixed(2)}`; const loadDetailFromStorage = () => { const cachedData = uni.getStorageSync("salesQuotationDetail"); detailData.value = cachedData || {}; if (cachedData && typeof cachedData === "object") detailData.value = cachedData; }; const loadDetailFromApi = () => { if (!quotationId.value) return Promise.resolve(); uni.showLoading({ title: "å è½½ä¸...", mask: true }); return getQuotationDetail({ id: quotationId.value }) .then(res => { detailData.value = res?.data || detailData.value || {}; }) .catch(() => { uni.showToast({ title: "å 载详æ 失败", icon: "error" }); }) .finally(() => { uni.hideLoading(); }); }; onLoad(options => { if (options?.id) { quotationId.value = options.id; } if (options?.id) quotationId.value = options.id; loadDetailFromStorage(); loadDetailFromApi(); }); onShow(() => { loadDetailFromStorage(); loadDetailFromApi(); }); </script> @@ -232,4 +199,15 @@ color: #22324d; border-bottom: 1px solid #eef2f7; } .detail-footer { position: fixed; left: 0; right: 0; bottom: 0; padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05); z-index: 10; } </style> src/pages/sales/salesQuotation/edit.vue
@@ -3,246 +3,122 @@ <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" /> <template #right> <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" /> <template #right> <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" /> <template #right> <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" /> <template #right> <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> <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"> <view class="section-tools"> <up-button type="primary" size="small" text="æ°å¢èç¹" @click="addApproverNode" /> </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> <up-form ref="formRef" :model="form" label-width="110" input-align="right" error-message-align="right"> <u-cell-group title="产åä¿¡æ¯" class="form-section"> <view class="section-tools"> <up-button type="primary" size="small" text="æ°å¢äº§å" @click="addProduct" /> <up-button type="primary" size="small" text="æ°å¢äº§å" :disabled="isEditMode" @click="addProduct" /> </view> <view v-if="form.products.length === 0" class="empty-text"> <text>ææ äº§åï¼è¯·å æ·»å 产å</text> </view> <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-for="(product, index) in form.products" :key="product.uid || index" 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> </view> <up-divider></up-divider> <view class="product-body"> <up-form-item label="产ååç§°"> <up-input v-model="product.product" placeholder="è¯·éæ©äº§å" readonly @click="openProductPicker(index)" /> <template #right> <up-icon name="arrow-right" @click="openProductPicker(index)"></up-icon> </template> <up-form-item label="产å"> <up-input v-model="product.product" placeholder="è¯·éæ©äº§å" readonly @click="openProductPicker(index)" /> <template #right><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)" /> <template #right> <up-icon name="arrow-right" @click="openModelPicker(index)"></up-icon> </template> <up-form-item label="è§æ ¼"> <up-input v-model="product.specification" placeholder="è¯·éæ©è§æ ¼" readonly @click="openModelPicker(index)" /> <template #right><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-form-item> <up-form-item label="åä½"><up-input v-model="product.unit" placeholder="请è¾å ¥åä½" clearable /></up-form-item> <up-form-item label="çº¸å¼ "><up-input v-model="product.paper" placeholder="请è¾å ¥çº¸å¼ " clearable /></up-form-item> <up-form-item label="å®é"><up-input v-model="product.paperWeight" 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 v-model="product.printingFee" type="number" placeholder="请è¾å ¥å°çè´¹" clearable @blur="syncTotalAmount" /> </up-form-item> <up-form-item label="åçè´¹"> <up-input v-model="product.dieCuttingFee" type="number" placeholder="请è¾å ¥åçè´¹" clearable @blur="syncTotalAmount" /> </up-form-item> <up-form-item label="ç£¨å ·è´¹"> <up-input v-model="product.grindingFee" type="number" placeholder="请è¾å ¥ç£¨å ·è´¹" clearable @blur="syncTotalAmount" /> </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-textarea v-model="form.remark" placeholder="请è¾å ¥å¤æ³¨ï¼éå¡«ï¼" auto-height /> </up-form-item> </u-cell-group> <u-cell-group title="æ±æ»" class="form-section"> <up-form-item label="æ¥ä»·æ»é¢"> <up-input :model-value="formatAmount(totalAmount)" disabled placeholder="èªå¨æ±æ»" /> </up-form-item> <view class="summary-tip">æ»é¢è§åï¼åä»· + å°çè´¹ + åçè´¹ + ç£¨å ·è´¹ï¼æäº§åéè¡æ±åï¼</view> </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" /> <up-action-sheet :show="showModelSheet" title="éæ©è§æ ¼" :actions="modelActions" @select="onSelectModel" @close="showModelSheet = false" /> </view> </template> <script setup> import { computed, onMounted, onUnmounted, ref } from "vue"; import { computed, onMounted, ref } from "vue"; import { onLoad } from "@dcloudio/uni-app"; import FooterButtons from "@/components/FooterButtons.vue"; import PageHeader from "@/components/PageHeader.vue"; 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 { addOrUpdateQuotationProduct, editQuotationProduct } from "@/api/salesManagement/salesQuotationProduct"; const formRef = ref(); const loading = ref(false); const quotationId = ref(""); const showCustomerSheet = ref(false); const showSalespersonSheet = ref(false); const showProductSheet = ref(false); const showModelSheet = ref(false); const showQuotationDatePicker = ref(false); const showValidDatePicker = ref(false); const quotationDateValue = ref(Date.now()); const validDateValue = ref(Date.now()); const currentProductIndex = ref(-1); const customerList = ref([]); const salespersonList = ref([]); const productList = ref([]); const modelActions = ref([]); let uidSeed = 1; let nextApproverId = 2; const form = ref({ id: undefined, quotationNo: "", customer: "", salesperson: "", quotationDate: "", validDate: "", paymentMethod: "", status: "å¾ å®¡æ¹", remark: "", approveUserIds: "", products: [], 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" }], validDate: [{ required: true, message: "è¯·éæ©æææ", trigger: "change" }], 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)) ); 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 isEditMode = computed(() => Boolean(quotationId.value)); const productActions = computed(() => productList.value.map(item => ({ name: item.label, value: item.value, label: item.label }))); const totalAmount = computed(() => calcTotalAmountFromProducts(form.value.products)); const createEmptyProduct = () => ({ uid: `p_${uidSeed++}`, id: "", salesQuotationId: "", productId: "", product: "", specificationId: "", specification: "", unit: "", paper: "", paperWeight: "", quantity: 1, unitPrice: 0, printingFee: 0, dieCuttingFee: 0, grindingFee: 0, amount: 0, modelOptions: [], }); @@ -251,37 +127,56 @@ const result = []; const walk = list => { (list || []).forEach(item => { if (item.children && item.children.length) { walk(item.children); } else { result.push({ label: item.label || item.productName || "", value: item.id || item.value }); } if (item.children && item.children.length) walk(item.children); else result.push({ label: item.label || item.productName || "", value: item.id || item.value }); }); }; walk(nodes); return result; }; const findProductIdByLabel = label => { if (!label) return ""; const hit = (productList.value || []).find(item => item.label === label); return hit?.value || ""; }; const formatAmount = amount => `Â¥${Number(amount || 0).toFixed(2)}`; const goBack = () => uni.navigateBack(); const calculateAmount = product => { product.amount = Number((Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2)); const calcTotalAmountFromProducts = products => Number( (products || []) .reduce((sum, item) => { const unitPrice = Number(item?.unitPrice || 0); const printingFee = Number(item?.printingFee || 0); const dieCuttingFee = Number(item?.dieCuttingFee || 0); const grindingFee = Number(item?.grindingFee || 0); return sum + unitPrice + printingFee + dieCuttingFee + grindingFee; }, 0) .toFixed(2) ); const syncTotalAmount = () => { 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 calculateAmount = product => { product.amount = Number((Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2)); syncTotalAmount(); }; const addProduct = () => form.value.products.push(createEmptyProduct()); const addProduct = () => { if (isEditMode.value) { uni.showToast({ title: "ç¼è¾æ¨¡å¼ä¸ä¸å 许æ°å¢äº§å", icon: "none" }); return; } form.value.products.push(createEmptyProduct()); }; const removeProduct = index => { form.value.products.splice(index, 1); form.value.totalAmount = totalAmount.value; syncTotalAmount(); }; const fetchModelOptions = async (productId, product) => { @@ -293,6 +188,7 @@ currentProductIndex.value = index; showProductSheet.value = true; }; const openModelPicker = index => { currentProductIndex.value = index; const current = form.value.products[index]; @@ -302,27 +198,12 @@ } modelActions.value = (current.modelOptions || []).map(item => ({ name: item.model, value: item.id, unit: item.unit })); if (!modelActions.value.length) { uni.showToast({ title: "ææ è§æ ¼åå·", icon: "none" }); uni.showToast({ title: "ææ è§æ ¼æ°æ®", icon: "none" }); return; } showModelSheet.value = true; }; const onSelectCustomer = action => { form.value.customer = action.value; 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; @@ -335,6 +216,7 @@ showProductSheet.value = false; fetchModelOptions(action.value, current); }; const onSelectModel = action => { const current = form.value.products[currentProductIndex.value]; if (!current) return; @@ -343,81 +225,70 @@ current.unit = action.unit || current.unit; showModelSheet.value = false; }; const onQuotationDateConfirm = e => { form.value.quotationDate = formatDateToYMD(e.value); showQuotationDatePicker.value = false; }; const onValidDateConfirm = e => { form.value.validDate = formatDateToYMD(e.value); showValidDatePicker.value = false; }; const fetchBaseOptions = async () => { 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 : []; const fetchProductOptions = async () => { const productTree = await productTreeList().catch(() => []); productList.value = flattenProductTree(Array.isArray(productTree) ? productTree : productTree?.data || []); }; 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 row = { uid: `p_${uidSeed++}`, id: item.id || "", salesQuotationId: item.salesQuotationId || "", productId: item.productId || "", product: item.product || item.productName || "", specificationId: item.specificationId || "", specification: item.specification || "", unit: item.unit || "", paper: item.paper || "", paperWeight: item.paperWeight || "", quantity: Number(item.quantity || 1), unitPrice: Number(item.unitPrice || 0), printingFee: Number(item.printingFee || 0), dieCuttingFee: Number(item.dieCuttingFee || 0), grindingFee: Number(item.grindingFee || 0), amount: Number(item.amount || Number(item.quantity || 0) * Number(item.unitPrice || 0)), modelOptions: [], }; if (row.productId) { await fetchModelOptions(row.productId, row); if (!row.specificationId && row.specification) { const matchedModel = (row.modelOptions || []).find(model => model.model === row.specification); if (matchedModel) { row.specificationId = matchedModel.id; if (!row.unit) row.unit = matchedModel.unit || ""; } } } return row; }) ); form.value.products = normalized; }; const loadDetail = async () => { const loadEditFromStorage = async () => { if (!quotationId.value) return; uni.showLoading({ title: "å è½½ä¸...", mask: true }); try { const res = await getQuotationDetail({ id: quotationId.value }); const data = res?.data || {}; form.value = { ...form.value, id: data.id, quotationNo: data.quotationNo || "", customer: data.customer || "", salesperson: data.salesperson || "", quotationDate: data.quotationDate || "", validDate: data.validDate || "", paymentMethod: data.paymentMethod || "", status: data.status || "å¾ å®¡æ¹", remark: data.remark || "", }; 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(); } const cached = uni.getStorageSync("salesQuotationEdit"); if (!cached || typeof cached !== "object") return; if (cached.id && String(cached.id) !== String(quotationId.value)) return; const data = cached; form.value = { ...form.value, id: data.id || form.value.id, remark: data.remark || "", }; const rows = Array.isArray(data.products) && data.products.length ? data.products : [data]; const normalizedRows = rows.map(item => ({ ...item, productId: item.productId || findProductIdByLabel(item.product || item.productName || ""), })); await normalizeProductRows(normalizedRows); syncTotalAmount(); }; const validateProducts = () => { @@ -425,42 +296,64 @@ 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.specificationId || !item.unit || !Number(item.unitPrice || 0)); 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; const buildProductPayload = item => { const quantity = Number(item?.quantity || 0); const unitPrice = Number(item?.unitPrice || 0); const printingFee = Number(item?.printingFee || 0); const dieCuttingFee = Number(item?.dieCuttingFee || 0); const grindingFee = Number(item?.grindingFee || 0); return { id: item?.id || undefined, salesQuotationId: item?.salesQuotationId || null, product: item?.product || "", specification: item?.specification || "", unit: item?.unit || "", paper: item?.paper || "", paperWeight: item?.paperWeight || "", unitPrice, printingFee, dieCuttingFee, grindingFee, quantity, amount: Number(item?.amount ?? quantity * unitPrice), remark: form.value.remark || "", }; }; const handleSubmit = async () => { const valid = await formRef.value.validate().catch(() => false); if (!valid || !validateApprovers() || !validateProducts()) return; if (!validateProducts()) return; loading.value = true; 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, quantity: Number(item.quantity || 0), unit: item.unit, unitPrice: Number(item.unitPrice || 0), amount: Number(item.amount || 0), })), }; const action = quotationId.value ? updateQuotation : addQuotation; action(payload) if (quotationId.value) { const editingItem = form.value.products[0] || {}; const payload = buildProductPayload({ ...editingItem, id: editingItem.id || quotationId.value, }); editQuotationProduct(payload) .then(() => { uni.showToast({ title: "ä¿åæå", icon: "success" }); setTimeout(() => uni.navigateBack(), 300); }) .catch(() => { uni.showToast({ title: "ä¿å失败", icon: "error" }); }) .finally(() => { loading.value = false; }); return; } const payloadList = form.value.products.map(item => buildProductPayload(item)); addOrUpdateQuotationProduct(payloadList) .then(() => { uni.showToast({ title: "ä¿åæå", icon: "success" }); setTimeout(() => uni.navigateBack(), 300); @@ -478,60 +371,21 @@ quotationId.value = options.id; form.value.id = options.id; } else { const today = formatDateToYMD(Date.now()); form.value.quotationDate = today; form.value.validDate = today; form.value.products = []; } }); onMounted(async () => { await fetchBaseOptions(); uni.$on("selectContact", onSelectApprover); if (quotationId.value) { await loadDetail(); } }); onUnmounted(() => { uni.$off("selectContact", onSelectApprover); uni.removeStorageSync("stepIndex"); await fetchProductOptions(); if (quotationId.value) await loadEditFromStorage(); }); </script> <style scoped lang="scss"> @import "@/static/scss/form-common.scss"; .account-detail { min-height: 100vh; background: #f8f9fa; padding-bottom: 100px; } .form-container { padding: 12px 12px 0; } .hero-card { margin-bottom: 12px; padding: 18px 18px 16px; border-radius: 16px; background: linear-gradient(135deg, #eef6ff 0%, #ffffff 100%); box-shadow: 0 6px 18px rgba(41, 121, 255, 0.08); } .hero-title { display: block; font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 6px; } .hero-desc { display: block; font-size: 13px; line-height: 1.6; color: #7a8599; } .form-section { @@ -547,7 +401,6 @@ padding: 12px 12px 0; } .node-list, .product-list { padding: 12px; display: flex; @@ -555,31 +408,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; @@ -603,6 +437,13 @@ font-size: 14px; } .summary-tip { padding: 0 24rpx 24rpx; color: #909399; font-size: 12px; line-height: 1.6; } :deep(.u-cell-group__title) { padding: 14px 18px 10px !important; font-size: 15px !important; src/pages/sales/salesQuotation/index.vue
@@ -7,25 +7,16 @@ <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"> @@ -35,37 +26,44 @@ <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> @@ -74,25 +72,15 @@ </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"> @@ -105,70 +93,73 @@ 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" }); @@ -182,11 +173,11 @@ 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(); @@ -208,11 +199,6 @@ <style scoped lang="scss"> @import "@/styles/sales-common.scss"; .tabs-section { background: #ffffff; padding: 0 12px 8px 12px; } .item-index { max-width: 180rpx; src/pages/works.vue
@@ -89,27 +89,27 @@ </view> </view> <!-- 人åèµæºæ¨¡å --> <view class="common-module collaboration-module" v-if="hasHumanResourcesItems"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">人åèµæº</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4"> <up-grid-item v-for="(item, index) in humanResourcesItems" :key="index" @click="handleCommonItemClick(item)"> <view class="icon-container"> <image :src="item.icon" class="item-icon"></image> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- <view class="common-module collaboration-module"--> <!-- v-if="hasHumanResourcesItems">--> <!-- <view class="module-header">--> <!-- <view class="module-title-container">--> <!-- <text class="module-title">人åèµæº</text>--> <!-- </view>--> <!-- </view>--> <!-- <view class="module-content">--> <!-- <up-grid :border="false"--> <!-- col="4">--> <!-- <up-grid-item v-for="(item, index) in humanResourcesItems"--> <!-- :key="index"--> <!-- @click="handleCommonItemClick(item)">--> <!-- <view class="icon-container">--> <!-- <image :src="item.icon" class="item-icon"></image>--> <!-- </view>--> <!-- <text class="item-label">{{item.label}}</text>--> <!-- </up-grid-item>--> <!-- </up-grid>--> <!-- </view>--> <!-- </view>--> <!-- çäº§ç®¡æ§æ¨¡å --> <view class="common-module equipment-module" v-if="hasProductionItems"> @@ -154,50 +154,28 @@ </up-grid> </view> </view> <!-- æ¡£æ¡ç®¡ç模å --> <view class="common-module archive-module" v-if="hasArchiveManagementItems"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">æ¡£æ¡ç®¡ç</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4"> <up-grid-item v-for="(item, index) in archiveManagementItems" :key="index" @click="handleCommonItemClick(item)"> <view class="icon-container"> <image :src="item.icon" class="item-icon"></image> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- å®åæå¡æ¨¡å --> <view class="common-module after-sales-module" v-if="hasAfterSalesServiceItems"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">å®åæå¡</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4"> <up-grid-item v-for="(item, index) in afterSalesServiceItems" :key="index" @click="handleCommonItemClick(item)"> <view class="icon-container"> <image :src="item.icon" class="item-icon"></image> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- <view class="common-module after-sales-module"--> <!-- v-if="hasAfterSalesServiceItems">--> <!-- <view class="module-header">--> <!-- <view class="module-title-container">--> <!-- <text class="module-title">å®åæå¡</text>--> <!-- </view>--> <!-- </view>--> <!-- <view class="module-content">--> <!-- <up-grid :border="false"--> <!-- col="4">--> <!-- <up-grid-item v-for="(item, index) in afterSalesServiceItems"--> <!-- :key="index"--> <!-- @click="handleCommonItemClick(item)">--> <!-- <view class="icon-container">--> <!-- <image :src="item.icon" class="item-icon"></image>--> <!-- </view>--> <!-- <text class="item-label">{{item.label}}</text>--> <!-- </up-grid-item>--> <!-- </up-grid>--> <!-- </view>--> <!-- </view>--> <!-- è´¨éç®¡çæ¨¡å --> <view class="common-module collaboration-module" v-if="hasQualityItems"> @@ -243,27 +221,27 @@ </view> </view> <!-- å®å ¨ç产模å --> <view class="common-module collaboration-module" v-if="hasSafetyItems"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">å®å ¨ç产</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4"> <up-grid-item v-for="(item, index) in safetyItems" :key="index" @click="handleCommonItemClick(item)"> <view class="icon-container"> <image :src="item.icon" class="item-icon"></image> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- <view class="common-module collaboration-module"--> <!-- v-if="hasSafetyItems">--> <!-- <view class="module-header">--> <!-- <view class="module-title-container">--> <!-- <text class="module-title">å®å ¨ç产</text>--> <!-- </view>--> <!-- </view>--> <!-- <view class="module-content">--> <!-- <up-grid :border="false"--> <!-- col="4">--> <!-- <up-grid-item v-for="(item, index) in safetyItems"--> <!-- :key="index"--> <!-- @click="handleCommonItemClick(item)">--> <!-- <view class="icon-container">--> <!-- <image :src="item.icon" class="item-icon"></image>--> <!-- </view>--> <!-- <text class="item-label">{{item.label}}</text>--> <!-- </up-grid-item>--> <!-- </up-grid>--> <!-- </view>--> <!-- </view>--> <DownloadProgressMask /> </view> @@ -307,7 +285,7 @@ const marketingItems = reactive([ { icon: "/static/images/icon/kehudangan.svg", label: "å®¢æ·æ¡£æ¡", label: "å®¢æ·æ¡£æ¡(ç§æµ·)", }, { icon: "/static/images/icon/xiaoshoubaojia.svg", @@ -332,6 +310,10 @@ { icon: "/static/images/icon/gongyingshangwanglai.svg", label: "ä¾åºå徿¥", }, { icon: "/static/images/icon/gongyingshangdangan.svg", label: "ä¾åºåæ¡£æ¡", }, { icon: "/static/images/icon/caigouguanli.svg", @@ -384,14 +366,6 @@ { icon: "/static/images/icon/jiekuanguanli.svg", label: "忬¾ç®¡ç", }, ]); // æ¡£æ¡ç®¡çåè½æ°æ® const archiveManagementItems = reactive([ { icon: "/static/images/icon/gongyingshangdangan.svg", label: "ä¾åºåæ¡£æ¡", }, ]); @@ -565,7 +539,7 @@ const handleCommonItemClick = item => { // æ ¹æ®ä¸åçåè½é¡¹è¿è¡è·³è½¬ switch (item.label) { case "å®¢æ·æ¡£æ¡": case "å®¢æ·æ¡£æ¡(ç§æµ·)": uni.navigateTo({ url: "/pages/basicData/customerFile/index", }); @@ -1110,8 +1084,8 @@ // å®ä¹èåé ç½®æ å° const menuMapping = { purchase: { target: purchaseItems, specialMapping: { "ä¾åºåæ¡£æ¡": "ä¾åºå管ç" } }, collaboration: { target: collaborationItems, specialMapping: { "è§ç« å¶åº¦": "è§ç« å¶åº¦ç®¡ç" } }, archiveManagement: { target: archiveManagementItems, specialMapping: { "ä¾åºåæ¡£æ¡": "ä¾åºå管ç" } }, }; console.log(allowedMenuTitles) // éç¨è¿æ»¤å½æ° @@ -1128,9 +1102,8 @@ // è¿æ»¤å个模å filterArray(marketingItems); filterArray(purchaseItems); filterArray(purchaseItems, menuMapping.purchase.specialMapping); filterArray(financeManagementItems); filterArray(archiveManagementItems, menuMapping.archiveManagement.specialMapping); filterArray(collaborationItems, menuMapping.collaboration.specialMapping); filterArray(safetyItems); filterArray(humanResourcesItems); @@ -1144,7 +1117,6 @@ const hasMarketingItems = computed(() => marketingItems.length > 0); const hasPurchaseItems = computed(() => purchaseItems.length > 0); const hasFinanceManagementItems = computed(() => financeManagementItems.length > 0); const hasArchiveManagementItems = computed(() => archiveManagementItems.length > 0); const hasAfterSalesServiceItems = computed(() => afterSalesServiceItems.length > 0); const hasCollaborationItems = computed(() => collaborationItems.length > 0); const hasSafetyItems = computed(() => safetyItems.length > 0); src/utils/versionUpgrade.js
@@ -272,7 +272,7 @@ lastVersionCheckAt = now; console.log(`${logPrefix} 触åçæ¬æ£æ¥ï¼æ¥æº=${from}`); const currentVersion = await getCurrentVersion(logPrefix); await checkAppVersionUpgrade(logPrefix, currentVersion); // await checkAppVersionUpgrade(logPrefix, currentVersion); }; return { triggerVersionCheck };