src/api/cooperativeOffice/collaborativeApproval.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,111 @@ // éå®å°è´¦é¡µé¢æ¥å£ import request from "@/utils/request"; // å页æ¥è¯¢ export function ledgerList(query) { return request({ url: "/sales/ledger/list", method: "get", params: query, }); } // åè¡¨æ ¼æ¥è¯¢ export function productList(query) { return request({ url: "/sales/product/list", method: "get", params: query, }); } // æ¥è¯¢å®¢æ·åç§°å表 export function customerList(query) { return request({ url: "/basic/customer/customerList", method: "get", params: query, }); } // æ°å¢ãä¿®æ¹éå®å°è´¦ export function addOrUpdateSalesLedger(query) { return request({ url: "/sales/ledger/addOrUpdateSalesLedger", method: "post", data: query, }); } // å é¤éå®å°è´¦ export function delLedger(query) { return request({ url: "/sales/ledger/delLedger", method: "delete", data: query, }); } // æ¥è¯¢éå®å°è´¦è¯¦æ export function getSalesLedgerWithProducts(query) { return request({ url: "/sales/ledger/getSalesLedgerWithProducts", method: "get", params: query, }); } // 宿¶ä¿®æ¹äº§åä¿¡æ¯ export function addOrUpdateSalesLedgerProduct(query) { return request({ url: "/sales/product/addOrUpdateSalesLedgerProduct", method: "post", data: query, }); } // å é¤äº§å export function delProduct(query) { return request({ url: "/sales/product/delProduct", method: "delete", data: query, }); } // ä¸ä¼ éä»¶ export function upload(query) { return request({ url: "/file/upload", method: "post", data: query, responseType: "blob", }); } // ç¼è¾æ¶å é¤éä»¶ export function delLedgerFile(query) { return request({ url: "/sales/ledger/delLedgerFile", method: "delete", data: query, }); } // éå®ä¸å页æ¥è¯¢ export function ledgerListNoPage(query) { return request({ url: "/sales/ledger/listNoPage", method: "get", params: query, }); } // å页æ¥è¯¢ export function ledgerListPage(query) { return request({ url: "/sales/ledger/listPage", method: "get", params: query, }); } // æ ¹æ®éå®ååå·æ¥äº§åä¿¡æ¯ export function getProductInfoByContractNo(query) { return request({ url: "/purchase/ledger/getProductBySalesNo", method: "get", params: query, }); } src/api/salesManagement/salesLedger.js
@@ -109,3 +109,19 @@ params: query, }); } // äº§åæ æ¥è¯¢ export function productTreeList(query) { return request({ url: '/basic/product/list', method: 'get', params: query }) } // è§æ ¼åå·æ¥è¯¢ export function modelList(query) { return request({ url: '/basic/product/modelList', method: 'get', params: query }) } src/api/system/user.js
@@ -39,3 +39,10 @@ filePath: data.filePath }) } // æ¥è¯¢ç¨æ·å表 export function userListNoPage() { return request({ url: '/system/user/userListNoPage', method: 'get' }) } src/config.js
@@ -1,8 +1,7 @@ // åºç¨å ¨å±é ç½® const config = { // baseUrl: 'https://vue.ruoyi.vip/prod-api', // baseUrl: 'http://localhost/prod-api', baseUrl: 'http://114.132.189.42:8089', baseUrl: 'http://114.132.189.42:8089', // æµè¯åº // baseUrl: 'http://192.168.1.147:7003', // æ¬å°èè° //cloudåå°ç½å ³å°å // baseUrl: 'http://192.168.10.3:8080', // åºç¨ä¿¡æ¯ src/main.js
@@ -11,6 +11,9 @@ import { useDict } from '@/utils/dict' import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' import { calculateTaxExclusiveTotalPrice, } from "@/utils/summarizeTable.js"; @@ -34,6 +37,7 @@ app.config.globalProperties.addDateRange = addDateRange app.config.globalProperties.selectDictLabel = selectDictLabel app.config.globalProperties.selectDictLabels = selectDictLabels app.config.globalProperties.calculateTaxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice; return { app src/pages.json
@@ -59,7 +59,35 @@ { "path": "pages/sales/salesAccount/detail", "style": { "navigationBarTitleText": "ä¿®æ¹å°è´¦", "navigationStyle": "custom" } }, { "path": "pages/sales/salesAccount/view", "style": { "navigationBarTitleText": "å°è´¦è¯¦æ ", "navigationStyle": "custom" } }, { "path": "pages/sales/invoicingRegistration/index", "style": { "navigationBarTitleText": "å¼ç¥¨ç»è®°", "navigationStyle": "custom" } }, { "path": "pages/sales/invoicingRegistration/add", "style": { "navigationBarTitleText": "æ°å¢å¼ç¥¨ç»è®°", "navigationStyle": "custom" } }, { "path": "pages/sales/invoicingRegistration/view", "style": { "navigationBarTitleText": "å¼ç¥¨ç»è®°è¯¦æ ", "navigationStyle": "custom" } }, @@ -74,6 +102,27 @@ "style": { "navigationBarTitleText": "æµè§ææ¬" } }, { "path": "pages/cooperativeOffice/collaborativeApproval/index", "style": { "navigationBarTitleText": "审æ¹ç®¡ç", "navigationStyle": "custom" } }, { "path": "pages/cooperativeOffice/collaborativeApproval/detail", "style": { "navigationBarTitleText": "å®¡æ¹æµç¨", "navigationStyle": "custom" } }, { "path": "pages/cooperativeOffice/clientVisit/index", "style": { "navigationBarTitleText": "å®¢æ·æè®¿", "navigationStyle": "custom" } } ], "subPackages": [ src/pages/cooperativeOffice/clientVisit/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,51 @@ // å®¢æ·æè®¿ <template> <view> <view class="page-header"> <view class="header-left"> <up-icon name="arrow-left" size="20" color="#333" @click="goBack"></up-icon> </view> <view class="header-center"> <text class="page-title">å®¢æ·æè®¿</text> </view> </view> </view> </template> <script> export default { data() { return { title: 'å®¢æ·æè®¿' } }, methods: { goBack() { uni.navigateBack({ delta: 1 }) } } } </script> <style> .page-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 20px; background-color: #f5f5f5; } .header-left { display: flex; align-items: center; } .header-center { flex: 1; text-align: center; } .page-title { font-size: 18px; font-weight: bold; } </style> src/pages/cooperativeOffice/collaborativeApproval/detail.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,535 @@ <template> <view class="account-detail"> <!-- 顶鍿 颿 --> <view class="header"> <up-icon name="arrow-left" size="20" color="#333" @click="goBack" /> <text class="title">å®¡æ¹æµç¨</text> </view> <!-- 表ååºå --> <view class="form-section"> <van-form ref="formRef" @submit="submitForm" :rules="rules" input-align="right"> <van-cell-group inset style="height:auto"> <van-field v-model="taxPrice" name="taxPrice" label="å§å" placeholder="请è¾å ¥å§å" :rules="[{ required: true, message: 'å§åä¸è½ä¸ºç©º' }]" required readonly /> <van-field v-model="result" readonly name="picker" label="ç³è¯·é¨é¨" placeholder="è¯·éæ©ç³è¯·é¨é¨" :rules="[{ required: true, message: 'è¯·éæ©ç³è¯·é¨é¨' }]" @click="showPicker = true" required /> <van-popup v-model:show="showPicker" destroy-on-close position="bottom" > <van-picker :columns="columns" :model-value="pickerValue" @confirm="onConfirm" @cancel="showPicker = false" /> </van-popup> <van-field v-model="message" name="message" rows="1" autosize label="ç³è¯·äºç±" type="textarea" placeholder="请è¾å ¥ç³è¯·äºç±" height="100" :rules="[{ required: true, message: 'ç³è¯·äºç±ä¸è½ä¸ºç©º' }]" required /> </van-cell-group> </van-form> </view> <!-- å®¡æ ¸æµç¨åºå --> <view class="approval-process"> <view class="approval-header"> <text class="approval-title">å®¡æ ¸æµç¨</text> <text class="approval-desc">å·²ç±ç®¡çåé¢è®¾ä¸å¯ä¿®æ¹</text> </view> <view class="approval-steps"> <view v-for="(step, stepIndex) in approvalSteps" :key="stepIndex" class="approval-step"> <view class="step-title"> <text>审æ¹äºº</text> </view> <view class="approvers-container"> <view v-for="(approver, approverIndex) in step.approvers" :key="approverIndex" class="approver-item"> <view class="approver-avatar"></view> <text class="approver-name">{{ approver.name }}</text> <view class="delete-approver-btn" @click="removeApprover(stepIndex, approverIndex)">Ã</view> </view> <view class="add-approver-btn" @click="addApprover(stepIndex)">+ </view> </view> <view class="step-line" v-if="stepIndex < approvalSteps.length - 1"></view> <view class="delete-step-btn" @click="removeApprovalStep(stepIndex)">å é¤èç¹</view> </view> </view> <view class="add-step-btn" @click="addApprovalStep"> <text>æ°å¢èç¹å®¡æ ¸äºº</text> </view> </view> <!-- åºé¨æé® --> <view class="footer-btns"> <van-button class="cancel-btn" @click="goBack">åæ¶</van-button> <van-button class="save-btn" @click="submitForm">ä¿å</van-button> </view> </view> </template> <script> import { ref, onMounted } from "vue"; export default { setup() { const rules = ref({ taxPrice: { rules: [{ required: true, errorMessage: 'å§åä¸è½ä¸ºç©º' }] }, result: { rules: [{ required: true, errorMessage: 'è¯·éæ©ç³è¯·é¨é¨' }] }, message: { rules: [{ required: true, errorMessage: 'ç³è¯·äºç±ä¸è½ä¸ºç©º' }] }, }); const result = ref(""); const pickerValue = ref([]); const showPicker = ref(false); const columns = ref([]); onMounted(async () => { try { // æ¿æ¢ä¸ºå®é æ¥å£å°å // const response = await axios.get('/api/getDepartments'); columns.value = [ { text: "æå·", value: "Hangzhou", }, { text: "宿³¢", value: "Ningbo", }, { text: "温å·", value: "Wenzhou", }, { text: "ç»å ´", value: "Shaoxing", }, { text: "æ¹å·", value: "Huzhou", }, ]; } catch (error) { console.error("è·åé¨é¨æ°æ®å¤±è´¥:", error); } }); const onConfirm = ({ selectedValues, selectedOptions }) => { result.value = selectedOptions[0]?.text; pickerValue.value = selectedValues; showPicker.value = false; }; const taxPrice = ref(""); const contractAmount = ref(""); const approvalSteps = ref([ { approvers: [{ name: 'å¢å°æ' }, { name: 'å¢å°æ' }] }, { approvers: [{ name: 'å¢å°æ' }] }, { approvers: [{ name: 'å¢å°æ' }] }, { approvers: [{ name: 'å¢å°æ' }] } ]); const goBack = () => { uni.navigateBack(); }; const formRef = ref(null); const submitForm = () => { formRef.value.validate().then(() => { // è¡¨åæ ¡éªéè¿ï¼å¯ä»¥æäº¤æ°æ® console.log("è¡¨åæ°æ®:", { taxPrice: taxPrice.value, department: result.value, message: message.value, approvalSteps: approvalSteps.value }); uni.showToast({ title: "ä¿åæå", icon: "success", }); }).catch((error) => { console.error("è¡¨åæ ¡éªå¤±è´¥:", error); // æ¾ç¤ºå ·ä½çéè¯¯ä¿¡æ¯ if (error.length > 0) { const firstError = error[0]; uni.showToast({ title: firstError.message || 'è¡¨åæ ¡éªå¤±è´¥', icon: 'none' }); } else { uni.showToast({ title: 'è¡¨åæ ¡éªå¤±è´¥ï¼è¯·æ£æ¥å¿ 填项', icon: 'none' }); } }); }; const message = ref(""); const addApprover = (stepIndex) => { // 卿å®å®¡æ¹æ¥éª¤æ·»å æ°ç审æ¹äºº approvalSteps.value[stepIndex].approvers.push({ name: 'å¢å°æ' }); }; const addApprovalStep = () => { // æ·»å æ°çå®¡æ¹æ¥éª¤ approvalSteps.value.push({ approvers: [{ name: 'å¢å°æ' }] }); }; const removeApprover = (stepIndex, approverIndex) => { // ç¡®ä¿æ¯ä¸ªæ¥éª¤è³å°ä¿çä¸ä¸ªå®¡æ¹äºº if (approvalSteps.value[stepIndex].approvers.length > 1) { approvalSteps.value[stepIndex].approvers.splice(approverIndex, 1); } else { uni.showToast({ title: 'æ¯ä¸ªæ¥éª¤è³å°éè¦ä¸ä¸ªå®¡æ¹äºº', icon: 'none' }); } }; const removeApprovalStep = (stepIndex) => { // ç¡®ä¿è³å°ä¿çä¸ä¸ªå®¡æ¹æ¥éª¤ if (approvalSteps.value.length > 1) { approvalSteps.value.splice(stepIndex, 1); } else { uni.showToast({ title: 'è³å°éè¦ä¸ä¸ªå®¡æ¹æ¥éª¤', icon: 'none' }); } }; return { rules, removeApprovalStep, removeApprover, result, pickerValue, columns, onConfirm, showPicker, taxPrice, contractAmount, goBack, submitForm, approvalSteps, addApprover, addApprovalStep, formRef, message }; }, }; </script> <style scoped lang="scss"> .account-detail { min-height: 100vh; background: #f8f9fa; padding-bottom: 80px; } .header { display: flex; align-items: center; background: #fff; padding: 16px 20px; border-bottom: 1px solid #f0f0f0; position: sticky; top: 0; z-index: 100; } .title { flex: 1; text-align: center; font-size: 18px; font-weight: 600; color: #333; } .form-section { margin-top: 16px; } .van-field { height: 56px; line-height: 36px; } .product-section { background: #fff; margin: 16px; border-radius: 16px; padding: 20px 16px 8px 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); } .section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .section-title { font-size: 16px; font-weight: 600; color: #333; } .add-btn { background: #2979ff; color: #fff; border-radius: 8px; padding: 4px 16px; font-size: 14px; } .product-card { background: #f8f9fa; border-radius: 12px; padding: 12px; margin-bottom: 16px; box-shadow: 0 1px 4px rgba(41, 121, 255, 0.06); position: relative; } .product-row { display: flex; align-items: center; margin-bottom: 8px; } .product-label { min-width: 60px; color: #888; font-size: 13px; } .del-row { justify-content: flex-end; } .del-btn { background: #ff4d4f; color: #fff; border-radius: 8px; padding: 4px 16px; font-size: 13px; margin-top: 4px; } .approval-process { background: #fff; margin: 16px; border-radius: 16px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); } .approval-header { margin-bottom: 16px; } .approval-title { font-size: 16px; font-weight: 600; color: #333; display: block; margin-bottom: 4px; } .approval-desc { font-size: 12px; color: #999; } .approval-steps { padding-left: 16px; position: relative; } .approval-step { position: relative; margin-bottom: 20px; } .step-title { margin-bottom: 12px; } .step-title text { font-size: 14px; color: #666; background: #f0f0f0; padding: 2px 8px; border-radius: 4px; } .approvers-container { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 8px; } .approver-item { display: flex; flex-direction: column; align-items: center; width: 60px; } .approver-avatar { width: 40px; height: 40px; background: #e6f7ff; border-radius: 50%; margin-bottom: 4px; display: flex; align-items: center; justify-content: center; } .approver-avatar::after { content: 'ð¤'; font-size: 20px; } .approver-name { font-size: 12px; color: #333; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; } .delete-approver-btn { font-size: 12px; color: #ff4d4f; background: rgba(255, 77, 79, 0.1); width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-top: 2px; } .delete-step-btn { margin-top: 8px; color: #ff4d4f; font-size: 12px; background: rgba(255, 77, 79, 0.1); padding: 2px 8px; border-radius: 4px; display: inline-block; } .add-approver-btn { width: 40px; height: 40px; border: 1px dashed #ccc; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; color: #999; margin-top: 8px; } .step-line { position: absolute; left: 20px; top: 100%; width: 1px; height: 30px; background: #e0e0e0; } .add-step-btn { display: flex; align-items: center; justify-content: center; margin-top: 16px; color: #006cfb; font-size: 14px; padding: 8px 0; border: 1px dashed #006cfb; border-radius: 8px; } .footer-btns { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; justify-content: space-around; align-items: center; padding: 12px 0; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05); z-index: 1000; } .cancel-btn { font-weight: 400; font-size: 16px; color: #ffffff; width: 102px; background: #c7c9cc; box-shadow: 0px 4px 10px 0px rgba(3, 88, 185, 0.2); border-radius: 40px 40px 40px 40px; } .save-btn { font-weight: 400; font-size: 16px; color: #ffffff; width: 224px; background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); box-shadow: 0px 4px 10px 0px rgba(3, 88, 185, 0.2); border-radius: 40px 40px 40px 40px; } </style> src/pages/cooperativeOffice/collaborativeApproval/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,390 @@ // 审æ¹ç®¡çä¸»é¡µé¢ <template> <view class="sales-account"> <!-- 页é¢å¤´é¨ --> <view class="page-header"> <view class="header-left"> <up-icon name="arrow-left" size="20" color="#333" @click="goBack"></up-icon> </view> <view class="header-center"> <text class="page-title">审æ¹ç®¡ç</text> </view> </view> <!-- æç´¢åçéåºå --> <view class="search-filter-section"> <view class="search-bar"> <view class="search-input"> <u-input placeholder="请è¾å ¥éè´ååå·" class="search-text" v-model="searchKeyword"> <template #suffix> <up-icon name="search" size="24" color="#999" @click="getList"></up-icon> </template> </u-input> </view> <view class="filter-button" @click="showFilterOptions"> <van-icon name="filter-o" size="24" color="#999"></van-icon> </view> </view> </view> <!-- éå®å°è´¦ç叿µ --> <view class="ledger-list" v-if="total > 0"> <view v-for="(item, index) in ledgerList" :key="index"> <view class="ledger-item" @click="handleItemClick(item)"> <view class="item-header"> <view class="item-left"> <view class="document-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-id">{{ item.salesContractNo }}</text> </view> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-info"> <view class="detail-row"> <text class="detail-label">ç³è¯·äºº</text> <text class="detail-value">{{ item.entryPersonName }}</text> </view> <view class="detail-row"> <text class="detail-label">ç³è¯·æ¥æ</text> <text class="detail-value highlightBlue">{{ item.entryDate }}</text> </view> </view> <view class="detail-info"> <view class="detail-row"> <text class="detail-label">ç³è¯·é¨é¨</text> <text class="detail-value">{{ item.entryPersonName }}</text> </view> <view class="detail-row"> <text class="detail-label">审æ¹ç¶æ</text> <text class="detail-value highlightYellow">{{ item.entryDate }}</text> </view> </view> </view> </view> </view> </view> <view v-else class="no-data"> <text>ææ å®¡æ¹æ°æ®</text> </view> <!-- æµ®å¨æä½æé® --> <view class="fab-button" @click="handleAdd"> <up-icon name="plus" size="24" color="#ffffff"></up-icon> </view> </view> </template> <script setup> import { ref, reactive, onMounted } from "vue"; import { ledgerListPage } from "@/api/cooperativeOffice/collaborativeApproval"; // æç´¢å ³é®è¯ const searchKeyword = ref(""); // éå®å°è´¦æ°æ® const ledgerList = ref([]); const total = ref(0); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ¥è¯¢å表 const getList = () => { const page = { current: -1, size: -1, }; ledgerListPage({ ...page }) .then((res) => { ledgerList.value = res.records; total.value = res.total; }) .catch(() => { // tableLoading.value = false; }); }; // æ¾ç¤ºçéé项 const showFilterOptions = () => { uni.showActionSheet({ itemList: ["ææ¥æçé", "æç¶æçé", "æéé¢çé"], success: (res) => { console.log("éæ©äºçéé项:", res.tapIndex); }, }); }; // ç¹å»å表项 const handleItemClick = (item) => { uni.showToast({ title: `æ¥çåå: ${item.contractId}`, icon: "none", }); }; // æ·»å æ°è®°å½ const handleAdd = () => { uni.navigateTo({ url: "/pages/cooperativeOffice/collaborativeApproval/detail", }); }; onMounted(() => { // 页é¢å è½½å®æåçåå§åé»è¾ getList(); }); </script> <style scoped lang="scss"> .u-divider { margin: 0 !important; } .sales-account { min-height: 100vh; background: #f8f9fa; position: relative; } .page-header { background: #ffffff; padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #f0f0f0; position: sticky; /* å ¼å®¹ iOS åæµ·/çµå¨å²å®å ¨åº */ padding-top: env(safe-area-inset-top); top: 0; z-index: 100; } .header-left { display: flex; align-items: center; gap: 8px; } .nav-icon { width: 24px; height: 24px; background: #2979ff; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .nav-text { font-size: 14px; color: #2979ff; font-weight: 500; } .header-center { flex: 1; text-align: center; } .page-title { font-size: 18px; font-weight: 600; color: #333; } .header-right { display: flex; align-items: center; } .status-bar { display: flex; align-items: center; gap: 4px; } .signal, .wifi, .battery { width: 16px; height: 8px; background: #333; border-radius: 2px; } .search-filter-section { padding: 10px 20px; background: #ffffff; } .search-bar { display: flex; align-items: center; gap: 12px; } .search-input { flex: 1; background: #f5f5f5; border-radius: 24px; padding: 4px 16px; display: flex; align-items: center; gap: 8px; } .search-text { flex: 1; font-size: 14px; color: #333; background: transparent; border: none; outline: none; } .search-text::placeholder { color: #999; } .filter-button { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; } .ledger-list { padding: 20px; } .ledger-item { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); padding: 0 16px; } .item-header { padding: 16px 0; display: flex; align-items: center; justify-content: space-between; } .item-left { display: flex; align-items: center; gap: 8px; } .document-icon { width: 24px; height: 24px; background: #ed8d05; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .item-id { font-size: 14px; color: #333; font-weight: 500; } .item-tag { background: #4caf50; border-radius: 4px; padding: 2px 4px; } .tag-text { font-size: 11px; color: #ffffff; font-weight: 500; } .item-details { padding: 16px 0; } .detail-row { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 8px; &:last-child { margin-bottom: 0; } } .detail-info { margin-top: 10px; display: flex; align-items: flex-start; justify-content: space-between; } .detail-label { font-size: 12px; color: #777777; min-width: 60px; } .detail-value { font-size: 12px; color: #000000; text-align: right; flex: 1; margin-left: 16px; } .detail-value.highlightBlue { color: #2979ff; font-weight: 500; } .detail-value.highlightYellow { color: #ed8d05; font-weight: 500; } .no-data { padding: 40px 0; text-align: center; color: #999; } .fab-button { position: fixed; bottom: 30px; right: 30px; width: 56px; height: 56px; background: #ed8d05; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); z-index: 1000; } </style> src/pages/index.vue
@@ -20,7 +20,7 @@ <view class="notice"> <view class="notice-content"> <view class="notice-left"> <text class="notice-status">ð 宿¶çæ§</text> <text class="notice-status">éç¥</text> </view> <view class="notice-separator"></view> <view class="notice-right"> @@ -291,6 +291,21 @@ case 'éå®å°è´¦': uni.navigateTo({ url: '/pages/sales/salesAccount/index' }); break; case 'å¼ç¥¨ç»è®°': uni.navigateTo({ url: '/pages/sales/invoicingRegistration/index' }); break; case 'åå审æ¹': uni.navigateTo({ url: '/pages/cooperativeOffice/collaborativeApproval/index' }); break; case 'å®¢æ·æè®¿': uni.navigateTo({ url: '/pages/cooperativeOffice/clientVisit/index' }); break; default: @@ -588,7 +603,7 @@ } .notice-label { color: #1976d2; color: #333; font-size: 14px; font-weight: 500; margin-right: 12px; src/pages/sales/invoicingRegistration/add.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,537 @@ <template> <view class="account-detail"> <!-- 页é¢å¤´é¨ --> <van-nav-bar title="æ°å¢å¼ç¥¨ç»è®°" left-text="è¿å" left-arrow @click-left="goBack" fixed placeholder /> <!-- 表åå 容 --> <van-form @submit="submitForm" ref="formRef" label-width="110px" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center"> <!-- åºæ¬ä¿¡æ¯ --> <van-cell-group title="åºæ¬ä¿¡æ¯" inset> <van-field v-model="form.salesContractNo" label="éå®ååå·" readonly placeholder="èªå¨å¡«å " /> <van-field v-model="form.customerName" label="客æ·åç§°" readonly placeholder="èªå¨å¡«å " /> <van-field v-model="form.salesman" label="ä¸å¡å" readonly placeholder="èªå¨å¡«å " /> <van-field v-model="form.projectName" label="项ç®åç§°" readonly placeholder="èªå¨å¡«å " /> <van-field v-model="form.createUer" label="å½å ¥äºº" readonly placeholder="请è¾å ¥å½å ¥äºº" /> <van-field v-model="form.createTime" label="å½å ¥æ¥æ" readonly placeholder="è¯·éæ©å½å ¥æ¥æ" @click="showCreateTimePicker = true" /> <van-field v-model="form.invoiceNo" label="å票å·ç " required placeholder="请è¾å ¥å票å·ç " :rules="[{ required: true, message: '请è¾å ¥å票å·ç ' }]" /> <van-field v-model="form.issueDate" label="å¼ç¥¨æ¥æ" readonly placeholder="è¯·éæ©å¼ç¥¨æ¥æ" required @click="showIssueDatePicker = true" :rules="[{ required: true, message: 'è¯·éæ©å¼ç¥¨æ¥æ' }]" /> </van-cell-group> <!-- 产åä¿¡æ¯ --> <view class="product-section"> <view class="section-header"> <text class="section-title">产åä¿¡æ¯</text> </view> <view v-if="productData.length === 0" class="empty-state"> <van-empty description="ææ äº§åæ°æ®" /> </view> <view v-else class="product-list"> <view v-for="(item, index) in productData" :key="index" class="product-card" > <!-- 产åå¤´é¨ --> <view class="product-header"> <view class="product-title"> <van-icon name="description" color="#2979ff" size="15" /> <text class="product-productCategory">产å {{ index + 1 }}</text> </view> </view> <!-- 产åä¿¡æ¯è¡¨å --> <view class="product-form"> <van-field v-model="item.productCategory" label="产å大类" readonly /> <van-field v-model="item.specificationModel" label="è§æ ¼åå·" readonly /> <van-field v-model="item.unit" label="åä½" readonly /> <van-field v-model="item.quantity" label="æ°é" readonly /> <van-field v-model="item.taxRate" label="ç¨ç(%)" readonly /> <van-field v-model="item.taxInclusiveUnitPrice" label="å«ç¨åä»·(å )" readonly /> <van-field v-model="item.taxInclusiveTotalPrice" label="å«ç¨æ»ä»·(å )" readonly /> <van-field v-model="item.taxExclusiveTotalPrice" label="ä¸å«ç¨æ»ä»·(å )" readonly /> <!-- æ¬æ¬¡å¼ç¥¨ä¿¡æ¯ --> <van-field v-model="item.currentInvoiceNum" label="æ¬æ¬¡å¼ç¥¨æ°" type="number" placeholder="请è¾å ¥å¼ç¥¨æ°é" @blur="invoiceNumBlur(item)" /> <van-field v-model="item.currentInvoiceAmount" label="æ¬æ¬¡å¼ç¥¨éé¢(å )" type="number" placeholder="请è¾å ¥å¼ç¥¨éé¢" @blur="invoiceAmountBlur(item)" /> <!-- æªå¼ç¥¨ä¿¡æ¯ --> <van-field v-model="item.noInvoiceNum" label="æªå¼ç¥¨æ°" readonly /> <van-field v-model="item.noInvoiceAmount" label="æªå¼ç¥¨éé¢(å )" readonly /> </view> </view> </view> </view> <!-- æäº¤æé® --> <view class="footer-btns"> <van-button class="cancel-btn" @click="goBack">åæ¶</van-button> <van-button class="save-btn" native-type="submit" form-type="submit">ä¿å</van-button> </view> </van-form> <!-- æ¥æéæ©å¨ --> <van-popup v-model:show="showIssueDatePicker" position="bottom"> <van-date-picker v-model="currentIssueDate" title="éæ©å¼ç¥¨æ¥æ" @confirm="onIssueDateConfirm" @cancel="showIssueDatePicker = false" /> </van-popup> <van-popup v-model:show="showCreateTimePicker" position="bottom"> <van-date-picker v-model="currentCreateTime" title="éæ©å½å ¥æ¥æ" @confirm="onCreateTimeConfirm" @cancel="showCreateTimePicker = false" /> </van-popup> </view> </template> <script setup> import { ref, reactive, onMounted } from 'vue' import { showToast, showLoadingToast, closeToast } from 'vant' import { invoiceRegistrationSave } from '@/api/salesManagement/invoiceRegistration' import useUserStore from '@/store/modules/user' import {getSalesLedgerWithProducts} from "@/api/salesManagement/salesLedger"; const userStore = useUserStore() const editData = ref(null); // 表åå¼ç¨ const formRef = ref() // è¡¨åæ°æ® let form = ref({ salesContractNo: '', customerName: '', salesman: '', projectName: '', createUer: '', issueDate: '', createTime: '', invoiceNo: '' }) // äº§åæ°æ® const productData = ref([]) // æ¥æéæ©å¨ç¶æ const showIssueDatePicker = ref(false) const showCreateTimePicker = ref(false) const currentIssueDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]) const currentCreateTime = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]) // æäº¤ç¶æ const submitting = ref(false) // è¿åä¸ä¸é¡µ const goBack = () => { // æ¸ çæ¬å°åå¨çæ°æ® uni.removeStorageSync('editData'); uni.navigateBack() } // æ ¼å¼åæ°å const formatNumber = (value, precision = 2) => { if (!value && value !== 0) return '0.00' return Number(value).toFixed(precision) } // å¼ç¥¨æ°éååå¤ç const invoiceNumBlur = (row) => { if (!row.currentInvoiceNum) { row.currentInvoiceNum = 0; } if (row.currentInvoiceNum > row.tempNoInvoiceNum) { showToast('æ¬æ¬¡å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°') row.currentInvoiceNum = 0; } // è®¡ç®æ¬æ¬¡å¼ç¥¨éé¢ row.currentInvoiceAmount = ( row.currentInvoiceNum * row.taxInclusiveUnitPrice ).toFixed(2); // è®¡ç®æªå¼ç¥¨æ° row.noInvoiceNum = (row.originalNoInvoiceNum - row.currentInvoiceNum).toFixed( 2 ); // è®¡ç®æªå¼ç¥¨éé¢ row.noInvoiceAmount = ( row.tempnoInvoiceAmount - row.currentInvoiceAmount ).toFixed(2); } // å¼ç¥¨éé¢ååå¤ç const invoiceAmountBlur = (row) => { if (!row.currentInvoiceAmount) { row.currentInvoiceAmount = 0; } // è®¡ç®æ¯å¦è¶ è¿å¼ç¥¨æ»éé¢ if (row.currentInvoiceAmount > row.tempnoInvoiceAmount) { showToast('æ¬æ¬¡å¼ç¥¨éé¢ä¸å¾å¤§äºæªå¼ç¥¨éé¢') row.currentInvoiceAmount = 0; } // è®¡ç®æ¬æ¬¡å¼ç¥¨æ° row.currentInvoiceNum = ( row.currentInvoiceAmount / row.taxInclusiveUnitPrice ).toFixed(2); // è®¡ç®æªå¼ç¥¨æ° row.noInvoiceNum = (row.originalNoInvoiceNum - row.currentInvoiceNum).toFixed( 2 ); // è®¡ç®æªå¼ç¥¨éé¢ row.noInvoiceAmount = ( row.tempnoInvoiceAmount - row.currentInvoiceAmount ).toFixed(2); } // æ´æ°æªå¼ç¥¨æ°æ® const updateNoInvoiceData = (row) => { const totalQuantity = parseFloat(row.quantity) || 0 const currentInvoiceNum = parseFloat(row.currentInvoiceNum) || 0 const totalAmount = parseFloat(row.taxInclusiveTotalPrice) || 0 const currentInvoiceAmount = parseFloat(row.currentInvoiceAmount) || 0 row.noInvoiceNum = Math.max(0, totalQuantity - currentInvoiceNum).toFixed(2) row.noInvoiceAmount = Math.max(0, totalAmount - currentInvoiceAmount).toFixed(2) } // å¼ç¥¨æ¥æç¡®è®¤ const onIssueDateConfirm = ({ selectedValues }) => { console.log('selectedValues--', selectedValues) form.value.issueDate = selectedValues.join('-'); currentIssueDate.value = selectedValues; showIssueDatePicker.value = false; }; // å½å ¥æ¥æç¡®è®¤ const onCreateTimeConfirm = (value) => { try { // å¤çä¸åç弿 ¼å¼ let year, month, day; if (Array.isArray(value)) { // æ°ç»æ ¼å¼ [year, month, day] [year, month, day] = value; } else if (value && typeof value === 'object') { // Dateå¯¹è±¡æ ¼å¼ year = value.getFullYear(); month = value.getMonth() + 1; day = value.getDate(); } else { // å ¶ä»æ ¼å¼ï¼ä½¿ç¨å½åæ¥æ const now = new Date(); year = now.getFullYear(); month = now.getMonth() + 1; day = now.getDate(); } form.value.createTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; showCreateTimePicker.value = false; } catch (error) { console.error('æ¥æå¤çé误:', error); showToast('æ¥æéæ©å¤±è´¥ï¼è¯·éè¯'); } } // æ ¼å¼åæ¥æ const formatDate = (date) => { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } // è·å产åå表 const getProductList = async () => { try { showLoadingToast('å è½½ä¸...') const res = await getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }) productData.value = res.productData; form.value = { ...res }; // 设置é»è®¤å½å ¥äºº form.value.createUer = userStore.name || '' // 设置é»è®¤æ¥æ const today = new Date() form.value.createTime = formatDate(today) closeToast() } catch (error) { closeToast() showToast('è·å产åå表失败') } } // æäº¤è¡¨å const submitForm = async () => { try { submitting.value = true // éªè¯äº§åæ°æ® if (productData.value.length === 0) { showToast('è¯·å æ·»å 产åä¿¡æ¯') return } // éªè¯å¼ç¥¨æ°æ® const hasInvoiceData = productData.value.some(item => { const num = parseFloat(item.currentInvoiceNum) || 0 const amount = parseFloat(item.currentInvoiceAmount) || 0 return num > 0 || amount > 0 }) if (!hasInvoiceData) { showToast('请è³å°è¾å ¥ä¸ä¸ªäº§åçå¼ç¥¨ä¿¡æ¯') return } const submitData = { ...form.value, productList: productData.value } await invoiceRegistrationSave(submitData) showToast('æäº¤æå') // è¿åä¸ä¸é¡µ setTimeout(() => { uni.navigateBack() }, 1500) } catch (error) { showToast('æäº¤å¤±è´¥ï¼è¯·éè¯') } finally { submitting.value = false } } // 页é¢å è½½æ¶åå§åæ°æ® onMounted(() => { // ä»é¡µé¢åæ°æç¼åä¸è·åéå®ååä¿¡æ¯ const contractInfo = uni.getStorageSync('editData') if (contractInfo) { editData.value = JSON.parse(contractInfo); const contract = JSON.parse(contractInfo) form.value.salesContractNo = contract.salesContractNo || '' form.value.customerName = contract.customerName || '' form.value.salesman = contract.salesman || '' form.value.projectName = contract.projectName || '' // è·å产åå表 getProductList() } }) </script> <style scoped lang="scss"> .account-detail { min-height: 100vh; background: #f8f9fa; padding-bottom: 5rem; } .empty-state { padding: 40px 0; } .product-section { background: #fff; margin-top: 1rem; padding: 1rem; box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); } .section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; } .section-title { font-size: 1rem; font-weight: 600; color: #333; } .product-list { .product-card { background: #FFFFFF; box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08); border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; padding: 1rem 0.5rem 0 0.5rem; position: relative; margin-bottom: 1rem; } } .product-header { display: flex; align-items: center; justify-content: space-between; padding: 0 0.5rem 0.75rem 0.5rem; border-bottom: 0.0625rem solid #e8e8e8; } .product-title { display: flex; align-items: center; } .product-productCategory { margin-left: 0.5rem; font-size: 0.875rem; font-weight: 500; color: #333; } .product-form { margin-bottom: 1rem; } .footer-btns { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; justify-content: space-around; align-items: center; padding: 0.75rem 0; box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); z-index: 1000; } .cancel-btn { font-weight: 400; font-size: 1rem; color: #FFFFFF; width: 6.375rem; background: #C7C9CC; box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; } .save-btn { font-weight: 400; font-size: 1rem; color: #FFFFFF; width: 14rem; background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; } // ååºå¼è°æ´ @media (max-width: 768px) { .submit-section { padding: 12px; } } </style> src/pages/sales/invoicingRegistration/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,448 @@ <template> <view class="sales-account"> <!-- 页é¢å¤´é¨ --> <van-nav-bar title="å¼ç¥¨ç»è®°" left-text="è¿å" left-arrow @click-left="goBack" fixed placeholder /> <!-- æç´¢åçéåºå --> <view class="search-filter-section"> <view class="search-bar"> <view class="search-input"> <input class="search-text" placeholder="请è¾å ¥éå®ååå·/客æ·åç§°" v-model="searchKeyword" /> </view> <view class="filter-button" @click="getList"> <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> <!-- éå®å°è´¦ç叿µ --> <view class="ledger-list" v-if="total > 0"> <view v-for="(item, index) in ledgerList" :key="index"> <view 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> </view> <text class="item-id">{{ item.salesContractNo }}</text> </view> <!-- <view class="item-tag">--> <!-- <text class="tag-text">{{ item.recorder }}</text>--> <!-- </view>--> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">客æ·åç§°</text> <text class="detail-value">{{ item.customerName }}</text> </view> <view class="detail-row"> <text class="detail-label">客æ·ååå·</text> <text class="detail-value">{{ item.customerContractNo }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¸å¡å</text> <text class="detail-value">{{ item.salesman }}</text> </view> <view class="detail-row"> <text class="detail-label">项ç®åç§°</text> <text class="detail-value">{{ item.projectName }}</text> </view> <view class="detail-row"> <text class="detail-label">ååéé¢(å )</text> <text class="detail-value highlight">{{ item.contractAmount }}</text> </view> <view class="detail-row"> <text class="detail-label">å·²å¼ç¥¨éé¢(å )</text> <text class="detail-value highlight">{{ item.invoiceTotal }}</text> </view> <view class="detail-row"> <text class="detail-label">æªå¼ç¥¨éé¢(å )</text> <text class="detail-value redlight">{{ item.noInvoiceAmountTotal }}</text> </view> </view> <!-- æä½æé®åºå --> <view class="action-buttons"> <van-button type="primary" size="small" @click="handleAddInvoice(item)" class="action-btn" :disabled="item.entryPerson != userStore.id || item.noInvoiceAmountTotal == 0" > æ°å¢å¼ç¥¨ </van-button> <van-button type="default" size="small" @click="handleViewDetail(item)" class="action-btn" > æ¥ç详æ </van-button> </view> </view> </view> </view> <view v-else class="no-data"> <text>ææ éå®å°è´¦æ°æ®</text> </view> <!-- æµ®å¨æä½æé® --> <!-- <view class="fab-button" @click="handleInfo('add')"> <up-icon name="plus" size="24" color="#ffffff"></up-icon> </view> --> </view> </template> <script setup> import { ref } from 'vue'; import { onShow } from '@dcloudio/uni-app'; import {ledgerListPage} from "@/api/salesManagement/salesLedger"; import useUserStore from "@/store/modules/user"; const userStore = useUserStore() // æç´¢å ³é®è¯ const searchKeyword = ref(''); // éå®å°è´¦æ°æ® const ledgerList = ref([]); const total = ref(0); // ååéæ©å¨ç¸å ³ const contractList = ref([]); const contractLoading = ref(false); const contractFinished = ref(false); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ¥è¯¢å表 const getList = () => { const page = { current: -1, size: -1 } ledgerListPage({...page}).then((res) => { ledgerList.value = res.records; total.value = res.total; }).catch(() => { // tableLoading.value = false; }); }; // å¤çæ°å¢å¼ç¥¨ const handleAddInvoice = (item) => { try { // æ£æ¥æéï¼åªæå½å ¥äººæè½æ°å¢å¼ç¥¨ if (item.entryPerson != userStore.id) { uni.showToast({ title: 'åªæå½å ¥äººæè½æ°å¢å¼ç¥¨', icon: 'none' }); return; } // åå¨éä¸çååä¿¡æ¯ uni.setStorageSync('editData', JSON.stringify(item)); // è·³è½¬å°æ°å¢å¼ç¥¨é¡µé¢ uni.navigateTo({ url: '/pages/sales/invoicingRegistration/add' }); } catch (error) { console.error('å¤çæ°å¢å¼ç¥¨å¤±è´¥:', error); uni.showToast({ title: 'æä½å¤±è´¥ï¼è¯·éè¯', icon: 'error' }); } }; // å¤çæ¥ç详æ const handleViewDetail = (item) => { try { // å卿°æ® uni.setStorageSync('editData', JSON.stringify(item)); // 跳转å°è¯¦æ é¡µé¢ uni.navigateTo({ url: '/pages/sales/invoicingRegistration/view' }); } catch (error) { console.error('å¤çæ¥ç详æ 失败:', error); uni.showToast({ title: 'æä½å¤±è´¥ï¼è¯·éè¯', icon: 'error' }); } }; onShow(() => { // 页颿¾ç¤ºæ¶å·æ°å表 getList(); }); </script> <style scoped lang="scss"> .u-divider { margin: 0 !important; } .sales-account { min-height: 100vh; background: #f8f9fa; position: relative; } .page-header { background: #ffffff; padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #f0f0f0; position: sticky; /* å ¼å®¹ iOS åæµ·/çµå¨å²å®å ¨åº */ padding-top: env(safe-area-inset-top); top: 0; z-index: 100; } .header-left { display: flex; align-items: center; gap: 8px; } .nav-icon { width: 24px; height: 24px; background: #2979ff; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .nav-text { font-size: 14px; color: #2979ff; font-weight: 500; } .header-center { flex: 1; text-align: center; } .page-title { font-size: 18px; font-weight: 600; color: #333; } .header-right { display: flex; align-items: center; } .status-bar { display: flex; align-items: center; gap: 4px; } .signal, .wifi, .battery { width: 16px; height: 8px; background: #333; border-radius: 2px; } .search-filter-section { padding: 10px 20px; background: #ffffff; } .search-bar { display: flex; align-items: center; gap: 12px; } .search-input { flex: 1; background: #f5f5f5; border-radius: 24px; padding: 10px 16px; display: flex; align-items: center; gap: 8px; } .search-text { flex: 1; font-size: 14px; color: #333; background: transparent; border: none; outline: none; } .search-text::placeholder { color: #999; } .filter-button { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; } .ledger-list { padding: 20px; } .ledger-item { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); padding: 0 16px; } .item-header { padding: 16px 0; display: flex; align-items: center; justify-content: space-between; } .item-left { display: flex; align-items: center; gap: 8px; } .document-icon { width: 24px; height: 24px; background: #2979ff; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .item-id { font-size: 14px; color: #333; font-weight: 500; } .item-tag { background: #4caf50; border-radius: 4px; padding: 2px 4px; } .tag-text { font-size: 11px; color: #ffffff; font-weight: 500; } .item-details { padding: 16px 0; } .detail-row { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 8px; &:last-child { margin-bottom: 0; } } .detail-info { margin-top: 10px; display: flex; align-items: flex-start; justify-content: space-between; } .detail-label { font-size: 12px; color: #777777; min-width: 60px; } .detail-value { font-size: 12px; color: #000000; text-align: right; flex: 1; margin-left: 16px; } .detail-value.highlight { color: #2979ff; font-weight: 500; } .detail-value.redlight { color: red; font-weight: 500; } .action-buttons { display: flex; gap: 12px; padding: 0 0 16px 0; justify-content: space-between; } .action-btn { flex: 1; } .no-data { padding: 40px 0; text-align: center; color: #999; } .fab-button { position: fixed; bottom: 30px; right: 30px; width: 56px; height: 56px; background: #2979ff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); z-index: 1000; } </style> src/pages/sales/invoicingRegistration/view.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,325 @@ <template> <view class="account-view"> <!-- 顶鍿 颿 --> <van-nav-bar title="å¼ç¥¨ç»è®°è¯¦æ " left-text="è¿å" left-arrow @click-left="goBack" fixed placeholder /> <!-- åºæ¬ä¿¡æ¯å±ç¤º --> <view class="info-section"> <view class="section-title">åºæ¬ä¿¡æ¯</view> <view class="info-grid"> <view class="info-item"> <text class="info-label">éå®ååå·</text> <text class="info-value">{{ form.salesContractNo }}</text> </view> <view class="info-item"> <text class="info-label">客æ·ååå·</text> <text class="info-value">{{ form.customerContractNo }}</text> </view> <view class="info-item"> <text class="info-label">客æ·åç§°</text> <text class="info-value">{{ form.customerName }}</text> </view> <view class="info-item"> <text class="info-label">ä¸å¡å</text> <text class="info-value">{{ form.salesman }}</text> </view> <view class="info-item"> <text class="info-label">项ç®åç§°</text> <text class="info-value">{{ form.projectName }}</text> </view> <view class="info-item"> <text class="info-label">ååéé¢(å )</text> <text class="info-value highlight">{{ form.contractAmount }}</text> </view> <view class="info-item"> <text class="info-label">å·²å¼ç¥¨éé¢(å )</text> <text class="info-value highlight">{{ form.invoiceTotal }}</text> </view> <view class="info-item"> <text class="info-label">æªå¼ç¥¨éé¢(å )</text> <text class="info-value redlight">{{ form.noInvoiceAmountTotal }}</text> </view> </view> </view> <!-- 产åä¿¡æ¯å±ç¤º --> <view class="product-section" v-if="productData && productData.length > 0"> <view class="section-title">产åä¿¡æ¯</view> <view class="product-card" v-for="(product, idx) in productData" :key="idx"> <view class="product-header"> <view class="product-title"> <van-icon name="description" color="#2979ff" size="15" /> <text class="product-productCategory">产å {{ idx + 1 }}</text> </view> </view> <view class="product-info"> <view class="info-grid"> <view class="info-item"> <text class="info-label">产å大类</text> <text class="info-value">{{ product.productCategory }}</text> </view> <view class="info-item"> <text class="info-label">è§æ ¼åå·</text> <text class="info-value">{{ product.specificationModel }}</text> </view> <view class="info-item"> <text class="info-label">åä½</text> <text class="info-value">{{ product.unit }}</text> </view> <view class="info-item"> <text class="info-label">ç¨ç(%)</text> <text class="info-value">{{ product.taxRate }}</text> </view> <view class="info-item"> <text class="info-label">æ°é</text> <text class="info-value highlight">{{ product.quantity }}</text> </view> <view class="info-item"> <text class="info-label">å«ç¨åä»·(å )</text> <text class="info-value highlight">{{ product.taxInclusiveUnitPrice }}</text> </view> <view class="info-item"> <text class="info-label">å«ç¨æ»ä»·(å )</text> <text class="info-value highlight">{{ product.taxInclusiveTotalPrice }}</text> </view> <view class="info-item"> <text class="info-label">ä¸å«ç¨æ»ä»·(å )</text> <text class="info-value highlight">{{ product.taxExclusiveTotalPrice }}</text> </view> <view class="info-item"> <text class="info-label">å¼ç¥¨æ°</text> <text class="info-value highlight">{{ product.invoiceNum }}</text> </view> <view class="info-item"> <text class="info-label">å¼ç¥¨éé¢(å )</text> <text class="info-value highlight">{{ product.invoiceAmount }}</text> </view> <view class="info-item"> <text class="info-label">æªå¼ç¥¨æ°</text> <text class="info-value highlight">{{ product.noInvoiceNum }}</text> </view> <view class="info-item"> <text class="info-label">æªå¼ç¥¨éé¢(å )</text> <text class="info-value redlight">{{ product.noInvoiceAmount }}</text> </view> </view> </view> </view> </view> <!-- æ 产åä¿¡æ¯æç¤º --> <view class="no-product" v-else> <text>ææ äº§åä¿¡æ¯</text> </view> </view> </template> <script setup> import { onMounted, ref } from 'vue'; import { getSalesLedgerWithProducts } from "@/api/salesManagement/salesLedger"; // è¡¨åæ°æ® const form = ref({ id: '', salesContractNo: '', customerContractNo: '', customerId: '', customerName: '', projectName: '', executionDate: '', contractAmount: '', invoiceTotal: '', noInvoiceAmountTotal: '', salesman: '' }); // äº§åæ°æ® const productData = ref([]); // ç¼è¾æ°æ® const editData = ref(null); // è¿åä¸ä¸é¡µ const goBack = () => { // æ¸ çæ¬å°åå¨çæ°æ® uni.removeStorageSync('editData'); uni.navigateBack(); }; // å¡«å è¡¨åæ°æ® const fillFormData = () => { if (!editData.value) return; // è·å宿´ç产åä¿¡æ¯ getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then((res) => { productData.value = res.productData || []; }); // å¡«å åºæ¬ä¿¡æ¯ form.value.salesContractNo = editData.value.salesContractNo || ''; form.value.customerContractNo = editData.value.customerContractNo || ''; form.value.customerName = editData.value.customerName || ''; form.value.projectName = editData.value.projectName || ''; form.value.executionDate = editData.value.executionDate || ''; form.value.contractAmount = editData.value.contractAmount || ''; form.value.salesman = editData.value.salesman || ''; form.value.invoiceTotal = editData.value.invoiceTotal || 0; form.value.noInvoiceAmountTotal = editData.value.noInvoiceAmountTotal || 0; form.value.id = editData.value.id || ''; form.value.customerId = editData.value.customerId || ''; }; onMounted(() => { // è·åç¼è¾æ°æ®å¹¶å¡«å 表å const editDataStr = uni.getStorageSync('editData'); if (editDataStr) { try { editData.value = JSON.parse(editDataStr); // ä½¿ç¨ nextTick ç¡®ä¿æ°æ®å è½½å®æååå¡«å setTimeout(() => { fillFormData(); }, 100); } catch (error) { console.error('è§£æç¼è¾æ°æ®å¤±è´¥:', error); } } }); </script> <style scoped lang="scss"> .account-view { min-height: 100vh; background: #f8f9fa; padding-bottom: 2rem; } .header { display: flex; align-items: center; background: #fff; padding: 1rem 1.25rem; border-bottom: 0.0625rem solid #f0f0f0; position: sticky; top: 0; z-index: 100; /* å ¼å®¹ iOS åæµ·/çµå¨å²å®å ¨åº */ padding-top: env(safe-area-inset-top); } .title { flex: 1; text-align: center; font-size: 1.125rem; font-weight: 600; color: #333; } .info-section { background: #fff; margin: 1rem; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); } .section-title { font-size: 1rem; font-weight: 600; color: #333; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 0.0625rem solid #e8e8e8; } .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } .info-item { display: flex; flex-direction: column; gap: 0.25rem; } .info-label { font-size: 0.75rem; color: #666; font-weight: 400; } .info-value { font-size: 0.875rem; color: #333; font-weight: 500; } .info-value.highlight { color: #2979ff; font-weight: 600; } .info-value.redlight { color: red; font-weight: 600; } .product-section { background: #fff; margin: 1rem; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); } .product-card { background: #f8f9fa; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1rem; } .product-card:last-child { margin-bottom: 0; } .product-header { display: flex; align-items: center; padding-bottom: 0.75rem; border-bottom: 0.0625rem solid #e8e8e8; margin-bottom: 1rem; } .product-title { display: flex; align-items: center; gap: 0.5rem; } .product-productCategory { font-size: 0.875rem; font-weight: 500; color: #333; } .product-info .info-grid { grid-template-columns: 1fr 1fr; gap: 0.5rem; } .no-product { text-align: center; padding: 2rem; color: #999; font-size: 0.875rem; } </style> src/pages/sales/salesAccount/detail.vue
@@ -1,249 +1,854 @@ <template> <view class="account-detail"> <!-- 顶鍿 颿 --> <view class="header"> <up-icon name="arrow-left" size="20" color="#333" @click="goBack" /> <text class="title">å°è´¦è¯¦æ </text> </view> <van-nav-bar title="å°è´¦è¯¦æ " left-text="è¿å" left-arrow @click-left="goBack" fixed placeholder /> <!-- 表ååºå --> <view class="form-section"> <van-form ref="formRef" :modelValue="form" :rules="rules" label-width="100px" input-align="right"> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥éå®ååå·"> </van-field> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥éå®ååå·"> </van-field> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥éå®ååå·"> </van-field> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥éå®ååå·"> </van-field> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥éå®ååå·"> </van-field> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥éå®ååå·"> </van-field> <van-field label="å½å ¥äºº" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥" readonly> </van-field> <van-field label="å½å ¥æ¥æ" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="请è¾å ¥" readonly> </van-field> </van-form> </view> <!-- 产åä¿¡æ¯ --> <view class="product-section"> <view class="section-header"> <text class="section-title">产åä¿¡æ¯</text> <button class="add-btn" @click="addProduct">æ°å¢</button> </view> <view class="product-card" v-for="(product, idx) in products" :key="idx"> <view class="product-row"> <text class="product-label">产åç±»</text> <uni-easyinput v-model="product.type" placeholder="请è¾å ¥äº§åç±»" /> </view> <view class="product-row"> <text class="product-label">åä½</text> <uni-easyinput v-model="product.unit" placeholder="请è¾å ¥åä½" /> <text class="product-label">æ°é</text> <uni-easyinput v-model="product.amount" placeholder="请è¾å ¥æ°é" type="number" /> </view> <view class="product-row"> <text class="product-label">ç¨ç</text> <uni-easyinput v-model="product.taxRate" placeholder="请è¾å ¥ç¨ç" /> <text class="product-label">å«ç¨åä»·</text> <uni-easyinput v-model="product.taxPrice" placeholder="请è¾å ¥å«ç¨åä»·" /> </view> <view class="product-row"> <text class="product-label">å«ç¨æ»ä»·</text> <uni-easyinput v-model="product.taxTotal" placeholder="请è¾å ¥å«ç¨æ»ä»·" /> <text class="product-label">ååéé¢</text> <uni-easyinput v-model="product.contractAmount" placeholder="请è¾å ¥ååéé¢" /> </view> <view class="product-row"> <text class="product-label">æä½</text> <uni-easyinput v-model="product.operateDate" placeholder="请è¾å ¥æä½æ¶é´" /> </view> <view class="product-row"> <text class="product-label">夿³¨</text> <uni-easyinput v-model="product.remark" placeholder="请è¾å ¥å¤æ³¨" /> </view> <view class="product-row del-row"> <button class="del-btn" @click="removeProduct(idx)">å é¤</button> </view> </view> </view> <!-- åºé¨æé® --> <view class="footer-btns"> <van-button class="cancel-btn" @click="goBack">åæ¶</van-button> <van-button class="save-btn" @click="submitForm">ä¿å</van-button> </view> <!-- 表ååºå --> <van-form @submit="onSubmit" label-width="110px" input-align="right" style="margin-top: 10px" error-message-align="right" scroll-to-error scroll-to-error-position="center"> <van-field label="éå®ååå·" name="salesContractNo" borderBottom="true" v-model="form.salesContractNo" placeholder="èªå¨çæ" disabled> </van-field> <van-field v-model="form.salesman" is-link readonly name="salesman" label="ä¸å¡å" required placeholder="ç¹å»éæ©ä¸å¡å" :rules="[{ required: true, message: 'è¯·éæ©ä¸å¡å' }]" @click="showPicker = true" /> <van-field label="客æ·ååå·" name="customerContractNo" borderBottom="true" v-model="form.customerContractNo" required placeholder="请è¾å ¥å®¢æ·ååå·" :rules="[{ required: true, message: '客æ·ååå·ä¸è½ä¸ºç©º' }]"> </van-field> <van-field v-model="form.customerName" is-link readonly required name="customerName" label="客æ·åç§°" placeholder="ç¹å»éæ©å®¢æ·" :rules="[{ required: true, message: 'è¯·éæ©å®¢æ·' }]" @click="showCustomerPicker = true" /> <van-field label="项ç®åç§°" name="projectName" borderBottom="true" v-model="form.projectName" placeholder="请è¾å ¥é¡¹ç®åç§°" :rules="[{ required: true, message: '项ç®åç§°ä¸è½ä¸ºç©º' }]" required> </van-field> <van-field v-model="form.executionDate" is-link readonly required name="executionDate" label="ç¾è®¢æ¥æ" placeholder="ç¹å»éæ©æ¶é´" :rules="[{ required: true, message: 'ç¾è®¢æ¥æä¸è½ä¸ºç©º' }]" @click="showDatePicker = true" /> <van-popup v-model:show="showDatePicker" destroy-on-close position="bottom"> <van-date-picker v-model="pickerDateValue" @confirm="onDateConfirm" @cancel="showDatePicker = false" /> </van-popup> <van-field label="仿¬¾æ¹å¼" name="paymentMethod" borderBottom="true" v-model="form.paymentMethod" placeholder="请è¾å ¥ä»æ¬¾æ¹å¼"> </van-field> <van-field label="å½å ¥äºº" name="entryPersonName" borderBottom="true" v-model="form.entryPersonName" placeholder="请è¾å ¥" disabled> </van-field> <van-field label="å½å ¥æ¥æ" name="entryDate" borderBottom="true" v-model="form.entryDate" placeholder="请è¾å ¥" disabled> </van-field> <van-popup v-model:show="showPicker" destroy-on-close position="bottom"> <van-picker :columns="userList" v-model="pickerValue" @confirm="onConfirm" @cancel="showPicker = false" /> </van-popup> <van-popup v-model:show="showCustomerPicker" destroy-on-close position="bottom"> <van-picker :columns="customerOption" v-model="pickerCustomerValue" @confirm="onCustomerConfirm" @cancel="showCustomerPicker = false" /> </van-popup> <!-- 产åå¤§ç±»éæ©å¨ --> <van-popup v-model:show="showCategoryPicker" destroy-on-close position="bottom"> <!-- 头鍿é®åºå --> <view class="popup-header"> <view @click="showCategoryPicker = false" class="cancelButton">åæ¶</view> <view @click="confirmCategorySelection" class="confirmButton">ç¡®å®</view> </view> <up-tree :data="productOptions" :props="defaultProps" show-checkbox default-expand-all check-strictly @check-change="onCategoryConfirm" /> </van-popup> <!-- è§æ ¼åå·éæ©å¨ --> <van-popup v-model:show="showSpecificationPicker" destroy-on-close position="bottom"> <van-picker :columns="modelOptions" v-model="pickerSpecificationValue" @confirm="onSpecificationConfirm" @cancel="showSpecificationPicker = false" /> </van-popup> <!-- ç¨çéæ©å¨ --> <van-popup v-model:show="showTaxRatePicker" destroy-on-close position="bottom"> <van-picker :columns="taxRateOptions" v-model="pickerTaxRateValue" @confirm="onTaxRateConfirm" @cancel="showTaxRatePicker = false" /> </van-popup> <!-- å票类åéæ©å¨ --> <van-popup v-model:show="showInvoiceTypePicker" destroy-on-close position="bottom"> <van-picker :columns="invoiceTypeOptions" v-model="pickerInvoiceTypeValue" @confirm="onInvoiceTypeConfirm" @cancel="showInvoiceTypePicker = false" /> </van-popup> <!-- 产åä¿¡æ¯ --> <view class="product-section"> <view class="section-header"> <text class="section-title">产åä¿¡æ¯</text> <van-button type="primary" size="small" @click="addProduct" class="add-btn" icon="plus" v-if="operationType !== 'view'">æ°å¢</van-button> </view> <view class="product-card" v-for="(product, idx) in productData" :key="idx"> <!-- 产åç±» --> <view class="product-header"> <view class="product-title"> <van-icon name="description" color="#2979ff" size="15" /> <text class="product-productCategory">产å {{ idx + 1 }}</text> </view> <!-- æä½æé® --> <view class="product-actions" v-if="operationType !== 'view'"> <van-button type="danger" size="mini" @click="removeProduct(idx)" class="del-btn" icon="delete">å é¤</van-button> </view> </view> <!-- 产åä¿¡æ¯è¡¨å --> <view class="product-form"> <!-- 产å大类 --> <van-field v-model="product.productCategory" is-link readonly name="productCategory" label="产å大类" required placeholder="è¯·éæ©" :rules="[{ required: true, message: 'è¯·éæ©' }]" @click="openCategoryPicker(idx)" /> <!-- è§æ ¼åå· --> <van-field v-model="product.specificationModel" is-link readonly name="specificationModel" label="è§æ ¼åå·" required :rules="[{ required: true, message: 'è¯·éæ©' }]" placeholder="è¯·éæ©" @click="openSpecificationPicker(idx)" /> <!-- åä½ --> <van-field v-model="product.unit" name="unit" label="åä½" required :rules="[{ required: true, message: '请è¾å ¥' }]" placeholder="请è¾å ¥" /> <!-- ç¨ç --> <van-field v-model="product.taxRate" is-link readonly name="taxRate" label="ç¨ç(%)" required :rules="[{ required: true, message: 'è¯·éæ©' }]" placeholder="è¯·éæ©" @click="openTaxRatePicker(idx)" /> <!-- å«ç¨åä»· --> <van-field v-model="product.taxInclusiveUnitPrice" name="taxInclusiveUnitPrice" label="å«ç¨åä»·(å )" type="number" required :rules="[{ required: true, message: '请è¾å ¥' }]" placeholder="请è¾å ¥" @blur="formatTaxPrice(idx)" /> <!-- æ°é --> <van-field v-model="product.quantity" name="quantity" label="æ°é" type="number" :rules="[{ required: true, message: '请è¾å ¥' }]" required placeholder="请è¾å ¥" @blur="formatAmount(idx)" /> <!-- å«ç¨æ»ä»· --> <van-field v-model="product.taxInclusiveTotalPrice" name="taxInclusiveTotalPrice" label="å«ç¨æ»ä»·(å )" type="number" :rules="[{ required: true, message: '请è¾å ¥' }]" required placeholder="请è¾å ¥" @blur="formatTaxTotal(idx)" /> <!-- ä¸å«ç¨æ»ä»· --> <van-field v-model="product.taxExclusiveTotalPrice" name="taxExclusiveTotalPrice" label="ä¸å«ç¨æ»ä»·(å )" type="number" required :rules="[{ required: true, message: '请è¾å ¥' }]" placeholder="请è¾å ¥" @blur="formatNoTaxTotal(idx)" /> <!-- å票类å --> <van-field v-model="product.invoiceType" is-link readonly name="invoiceType" label="å票类å" :rules="[{ required: true, message: 'è¯·éæ©' }]" required placeholder="è¯·éæ©" @click="openInvoiceTypePicker(idx)" /> </view> </view> </view> <view class="footer-btns" v-if="operationType !== 'view'"> <van-button class="cancel-btn" @click="goBack">åæ¶</van-button> <van-button class="save-btn" native-type="submit" form-type="submit">ä¿å</van-button> </view> </van-form> </view> </template> <script setup> import { ref } from 'vue'; const goBack = () => { uni.navigateBack(); }; const formRef = ref(); const paymentMethods = ['å¯¹å ¬è½¬è´¦', 'ç°é', 'å ¶ä»']; import {onMounted, ref} from 'vue'; import {userListNoPage} from "@/api/system/user"; import { addOrUpdateSalesLedger, addOrUpdateSalesLedgerProduct, customerList, getSalesLedgerWithProducts, modelList, productTreeList } from "@/api/salesManagement/salesLedger"; import useUserStore from "@/store/modules/user"; import {calculateTaxExclusiveTotalPrice} from "@/utils/summarizeTable"; // è·å页é¢åæ° const operationType = ref(''); const editData = ref(null); const userStore = useUserStore() const form = ref({ id: '', salesContractNo: '', customerContract: '', projectName: '', contractAmount: '', executionDate: '', customerContractNo: '', customerId: '', customerName: '', projectName: '', executionDate: '', paymentMethod: '', entryPerson: '', entryPersonName: '', entryDate: '', }); const rules = { salesContractNo: { rules: [{ required: true, errorMessage: 'éå®ååå·ä¸è½ä¸ºç©º' }] }, customerContract: { rules: [{ required: true, errorMessage: '客æ·ååä¸è½ä¸ºç©º' }] }, projectName: { rules: [{ required: true, errorMessage: '项ç®åç§°ä¸è½ä¸ºç©º' }] }, contractAmount: { rules: [{ required: true, errorMessage: 'ååéé¢ä¸è½ä¸ºç©º' }] }, executionDate: { rules: [{ required: true, errorMessage: 'ç¾è®¢æ¥æä¸è½ä¸ºç©º' }] }, paymentMethod: { rules: [{ required: true, errorMessage: 'è¯·éæ©ä»æ¬¾æ¹å¼' }] } }; const products = ref([ { type: 'LS-29911', unit: 'å¨åºé', amount: '86590905972612', taxRate: 'è¿éæ¯é¡¹ç®åç§°', taxPrice: 'è¿éæ¯é¡¹ç®åç§°', taxTotal: 'è¿éæ¯é¡¹ç®åç§°', contractAmount: '32011å ', operateDate: '2022-02-22 11:30:50', remark: '', }, { type: 'LS-29911', unit: 'å¨åºé', amount: '86590905972612', taxRate: 'è¿éæ¯é¡¹ç®åç§°', taxPrice: 'è¿éæ¯é¡¹ç®åç§°', taxTotal: 'è¿éæ¯é¡¹ç®åç§°', contractAmount: '32011å ', operateDate: '2022-02-22 11:30:50', remark: '', }, const pickerValue = ref(['']); const pickerDateValue = ref([]); const showPicker = ref(false); const showDatePicker = ref(false); const pickerCustomerValue = ref(['']); const showCustomerPicker = ref(false); const userList = ref([]); const customerOption = ref([]); const productData = ref([]); // éæ©å¨ç¸å ³åé const showCategoryPicker = ref(false); const showSpecificationPicker = ref(false); const showTaxRatePicker = ref(false); const showInvoiceTypePicker = ref(false); const pickerCategoryValue = ref(['']); const pickerSpecificationValue = ref(['']); const pickerTaxRateValue = ref(['']); const pickerInvoiceTypeValue = ref(['']); const currentProductIndex = ref(0); // éé¡¹æ°æ® const productOptions = ref([]); const selectedCategoryNode = ref(null); const defaultProps = ref({ children: 'children', label: 'label', nodeKey: 'id' }); const modelOptions = ref([]); // 鲿¢å¾ªç¯è®¡ç®çæ å¿ // const isCalculating = ref(false); const taxRateOptions = ref([ { text: '1', value: '1' }, { text: '6', value: '6' }, { text: '13', value: '13' }, ]); const invoiceTypeOptions = ref([ { text: '墿®ç¥¨', value: '墿®ç¥¨' }, { text: 'å¢ä¸ç¥¨', value: 'å¢ä¸ç¥¨' }, ]); const addProduct = () => { products.value.push({ type: '', if (productData.value === null) { productData.value = [] } productData.value.push({ productCategory: '', specificationModel: '', productModelId: '', unit: '', amount: '', taxRate: '', taxPrice: '', taxTotal: '', contractAmount: '', operateDate: '', remark: '', taxInclusiveUnitPrice: '', quantity: '', taxInclusiveTotalPrice: '', taxExclusiveTotalPrice: '', invoiceType: '' }); }; const onConfirm = ({ selectedValues, selectedOptions }) => { form.value.salesman = selectedOptions[0]?.text; pickerValue.value = [selectedValues[0]]; showPicker.value = false; }; const onCustomerConfirm = ({ selectedValues, selectedOptions }) => { form.value.customerName = selectedOptions[0]?.text; form.value.customerId = selectedOptions[0]?.value; pickerCustomerValue.value = [selectedValues[0]]; showCustomerPicker.value = false; }; const onDateConfirm = ({ selectedValues }) => { form.value.executionDate = selectedValues.join('-'); pickerDateValue.value = selectedValues; showDatePicker.value = false; }; const removeProduct = (idx) => { products.value.splice(idx, 1); productData.value.splice(idx, 1); }; const submitForm = () => { formRef.value.validate().then(() => { uni.showToast({ title: 'ä¿åæå', icon: 'success' }); }); // æ¾ç¤ºéæ©å¨ const openCategoryPicker = (idx) => { currentProductIndex.value = idx; showCategoryPicker.value = true; }; const openSpecificationPicker = (idx) => { currentProductIndex.value = idx; showSpecificationPicker.value = true; }; const openTaxRatePicker = (idx) => { currentProductIndex.value = idx; showTaxRatePicker.value = true; }; const openInvoiceTypePicker = (idx) => { currentProductIndex.value = idx; showInvoiceTypePicker.value = true; }; // éæ©å¨ç¡®è®¤äºä»¶ const onCategoryConfirm = (node) => { // è·åéä¸çèç¹ä¿¡æ¯ console.log('selected node---', node); // åå¨éä¸çèç¹ï¼ç¨äºç¡®è®¤æ¶è·åæ°æ® selectedCategoryNode.value = node; }; // 确认产åå¤§ç±»éæ© const confirmCategorySelection = () => { if (selectedCategoryNode.value) { // 设置éä¸ç产å大类 productData.value[currentProductIndex.value].productCategory = selectedCategoryNode.value.label; const id = selectedCategoryNode.value.id // éç½®éä¸çèç¹ selectedCategoryNode.value = null; productData.value[currentProductIndex.value].specificationModel = '' productData.value[currentProductIndex.value].productModelId = '' productData.value[currentProductIndex.value].pickerSpecificationValue = [''] getModels(id) } showCategoryPicker.value = false; }; // è·åè§æ ¼åå· const getModels = (value) => { modelList({ id: value }).then((res) => { modelOptions.value = res.map(user => ({ text: user.model, value: user.id })); }); }; // éæ©è§æ ¼åå· const onSpecificationConfirm = ({ selectedValues, selectedOptions }) => { productData.value[currentProductIndex.value].specificationModel = selectedOptions[0]?.text; productData.value[currentProductIndex.value].productModelId = selectedOptions[0]?.value; productData.value[currentProductIndex.value].unit = selectedOptions[0]?.unit; pickerSpecificationValue.value = [selectedValues[0]]; showSpecificationPicker.value = false; }; // éæ©ç¨ç const onTaxRateConfirm = ({ selectedValues, selectedOptions }) => { productData.value[currentProductIndex.value].taxRate = selectedOptions[0]?.value; pickerTaxRateValue.value = [selectedValues[0]]; showTaxRatePicker.value = false; // if (isCalculating.value) return; const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice); const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate); if (!inclusiveTotalPrice || !taxRate) { return; } // isCalculating.value = true; // 计ç®ä¸å«ç¨æ»ä»· productData.value[currentProductIndex.value].taxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice( inclusiveTotalPrice, taxRate ); // isCalculating.value = false; }; const onInvoiceTypeConfirm = ({ selectedValues, selectedOptions }) => { productData.value[currentProductIndex.value].invoiceType = selectedOptions[0]?.text; pickerInvoiceTypeValue.value = [selectedValues[0]]; showInvoiceTypePicker.value = false; }; // æ ¼å¼å彿° - åºå®ä¸¤ä½å°æ° const formatTaxPrice = (idx) => { if (productData.value[idx].taxInclusiveUnitPrice) { const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice); if (!isNaN(value)) { productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2); } } if (!productData.value[currentProductIndex.value].taxRate) { uni.showToast({ title: '请å éæ©ç¨ç', icon: 'none' }); return; } const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); const unitPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveUnitPrice); if (!quantity || quantity <= 0 || !unitPrice) { return; } // 计ç®å«ç¨æ»ä»· productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productData.value[currentProductIndex.value].taxRate) { productData.value[currentProductIndex.value].taxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice( productData.value[currentProductIndex.value].taxInclusiveTotalPrice, productData.value[currentProductIndex.value].taxRate ); } }; // æ°éè¾å ¥æ¡å¤±ç¦ const formatAmount = (idx) => { if (productData.value[idx].quantity) { const value = parseFloat(productData.value[idx].quantity); if (!isNaN(value)) { productData.value[idx].quantity = value.toFixed(2); } } if (!productData.value[currentProductIndex.value].taxRate) { uni.showToast({ title: '请å éæ©ç¨ç', icon: 'none' }); return; } const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); const unitPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveUnitPrice); if (!quantity || quantity <= 0 || !unitPrice) { return; } // 计ç®å«ç¨æ»ä»· productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productData.value[currentProductIndex.value].taxRate) { productData.value[currentProductIndex.value].taxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice( productData.value[currentProductIndex.value].taxInclusiveTotalPrice, productData.value[currentProductIndex.value].taxRate ); } }; // å«ç¨æ»ä»·å¤±ç¦ï¼æ ¹æ®å«ç¨æ»ä»·è®¡ç®å«ç¨åä»·åæ°é const formatTaxTotal = (idx) => { if (productData.value[idx].taxInclusiveTotalPrice) { const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice); if (!isNaN(value)) { productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2); } } const totalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice); const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); if (!totalPrice || !quantity || quantity <= 0) { return; } // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productData.value[currentProductIndex.value].taxRate) { productData.value[currentProductIndex.value].taxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice( totalPrice, productData.value[currentProductIndex.value].taxRate ); } }; // ä¸å«ç¨æ»ä»·å¤±ç¦, æ ¹æ®ä¸å«ç¨æ»ä»·è®¡ç®å«ç¨åä»·åæ°é const formatNoTaxTotal = (idx) => { if (productData.value[idx].taxExclusiveTotalPrice) { const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice); if (!isNaN(value)) { productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2); } } if (!productData.value[currentProductIndex.value].taxRate) { uni.showToast({ title: '请å éæ©ç¨ç', icon: 'none' }); return; } const exclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxExclusiveTotalPrice); const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate); if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) { return; } // å 计ç®å«ç¨æ»ä»· = ä¸å«ç¨æ»ä»· / (1 - ç¨ç/100) const taxRateDecimal = taxRate / 100; const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); productData.value[currentProductIndex.value].taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2); }; const goBack = () => { // æ¸ çæ¬å°åå¨çæ°æ® uni.removeStorageSync('operationType'); uni.removeStorageSync('editData'); uni.navigateBack(); }; const onSubmit = () => { if (productData.value !== null && productData.value.length > 0) { form.value.productData = JSON.parse(JSON.stringify(productData.value)); } else { uni.showToast({ title: '请添å 产åä¿¡æ¯', icon: 'none' }); return } form.value.type = 1; addOrUpdateSalesLedger(form.value).then((res) => { uni.showToast({ title: 'æäº¤æå', icon: 'success', }); goBack(); }); }; const setUserInfo = () => { form.value.entryPerson = userStore.id; form.value.entryPersonName = userStore.name; // 设置å½å¤©æ¥æ const today = new Date() const year = today.getFullYear() const month = String(today.getMonth() + 1).padStart(2, '0') const day = String(today.getDate()).padStart(2, '0') form.value.entryDate = `${year}-${month}-${day}` pickerDateValue.value = [year.toString(), month.toString(), day.toString()] } // å¡«å è¡¨åæ°æ®ï¼ç¼è¾æ¨¡å¼ï¼ const fillFormData = () => { if (!editData.value) return; getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then((res) => { productData.value = res.productData; }); console.log(editData.value) // å¡«å åºæ¬ä¿¡æ¯ form.value.salesContractNo = editData.value.salesContractNo || ''; form.value.customerContractNo = editData.value.customerContractNo || ''; form.value.customerName = editData.value.customerName || ''; form.value.projectName = editData.value.projectName || ''; form.value.executionDate = editData.value.executionDate || ''; form.value.paymentMethod = editData.value.paymentMethod || ''; form.value.salesman = editData.value.salesman || ''; form.value.entryPerson = editData.value.entryPerson || ''; form.value.entryPersonName = editData.value.entryPersonName || ''; form.value.entryDate = editData.value.entryDate || ''; form.value.id = editData.value.id || ''; form.value.customerId = editData.value.customerId || ''; // 设置ä¸å¡åéæ©å¨çå¼ if (editData.value.salesman) { const salesmanIndex = userList.value.findIndex(user => user.text === editData.value.salesman); if (salesmanIndex !== -1) { pickerValue.value = [userList.value[salesmanIndex].value]; } } // 设置客æ·éæ©å¨çå¼ if (editData.value.customerName) { const customerIndex = customerOption.value.findIndex(customer => customer.text === editData.value.customerName); if (customerIndex !== -1) { pickerCustomerValue.value = [customerOption.value[customerIndex].value] } } // è®¾ç½®æ¥æéæ©å¨çå¼ if (editData.value.executionDate) { pickerDateValue.value = editData.value.executionDate.split('-').map(num => parseInt(num, 10)) console.log(pickerDateValue.value) } }; const getUserList = () => { userListNoPage().then((res) => { // å°ç¨æ·æ°æ®ç»è£ æ picker éè¦çæ ¼å¼ userList.value = res.data.map(user => ({ text: user.nickName, value: user.nickName })); }) } const getCustomerList = () => { customerList().then((res) => { // å°ç¨æ·æ°æ®ç»è£ æ picker éè¦çæ ¼å¼ customerOption.value = res.map(item => ({ text: item.customerName, value: item.id })); }) } const convertIdToValue = (data) => { // å¦æä¼ å ¥ç䏿¯æ°ç»ï¼åè¿å空æ°ç» if (!Array.isArray(data)) { return []; } // é彿 å°å½æ° return data.map(item => { // å建æ°å¯¹è±¡ï¼æ å°å段 const mappedItem = { label: item.label, // å ³é®ï¼å° label æ å°ä¸º text id: item.id, // ä¿ç id }; // 妿åå¨ children æ°ç»ï¼åéå½å¤ç if (item.children && Array.isArray(item.children) && item.children.length > 0) { mappedItem.children = convertIdToValue(item.children); } return mappedItem; }); }; // è·å产å大类treeæ°æ® const getProductOptions = () => { productTreeList().then((res) => { productOptions.value = convertIdToValue(res); }); }; onMounted(() => { // è·å页é¢åæ° operationType.value = uni.getStorageSync('operationType') || ''; // è·å人åå表 getUserList() // è·å客æ·å表 getCustomerList() // è·å产å大类treeæ°æ® getProductOptions() // èµå¼é»è®¤ä¿¡æ¯ if (operationType.value === 'add') { setUserInfo() } // è·åç¼è¾æ°æ®å¹¶å¡«å 表å const editDataStr = uni.getStorageSync('editData'); if (editDataStr) { try { editData.value = JSON.parse(editDataStr); // 妿æ¯ç¼è¾æ¨¡å¼ï¼çå¾ æ°æ®å è½½å®æåå¡«å è¡¨åæ°æ® if (operationType.value !== 'add' && editData.value) { // ä½¿ç¨ nextTick ç¡®ä¿æ°æ®å è½½å®æååå¡«å setTimeout(() => { fillFormData(); }, 100); } } catch (error) { console.error('è§£æç¼è¾æ°æ®å¤±è´¥:', error); } } }); </script> <style scoped lang="scss"> .account-detail { min-height: 100vh; background: #f8f9fa; padding-bottom: 80px; padding-bottom: 5rem; } .header { display: flex; align-items: center; background: #fff; padding: 16px 20px; border-bottom: 1px solid #f0f0f0; padding: 1rem 1.25rem; border-bottom: 0.0625rem solid #f0f0f0; position: sticky; top: 0; z-index: 100; /* å ¼å®¹ iOS åæµ·/çµå¨å²å®å ¨åº */ padding-top: env(safe-area-inset-top); } .title { flex: 1; text-align: center; font-size: 18px; font-size: 1.125rem; font-weight: 600; color: #333; } .form-section { margin-top: 16px; margin-top: 1rem; } .van-field { height: 56px; line-height: 36px; height: 3.4rem; } .van-cell { align-items: center; } .product-section { background: #fff; margin: 16px; border-radius: 16px; padding: 20px 16px 8px 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.04); margin-top: 1rem; padding: 1rem; box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); } .section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; margin-bottom: 1rem; } .section-title { font-size: 16px; font-size: 1rem; font-weight: 600; color: #333; } .add-btn { background: #2979ff; color: #fff; border-radius: 8px; padding: 4px 16px; font-size: 14px; border-radius: 0.25rem; } .product-card { background: #f8f9fa; border-radius: 12px; padding: 12px; margin-bottom: 16px; box-shadow: 0 1px 4px rgba(41,121,255,0.06); background: #FFFFFF; box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08); border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; padding: 1rem 0.5rem 0 0.5rem; position: relative; } .product-row { .product-header { display: flex; align-items: center; margin-bottom: 8px; justify-content: space-between; padding: 0 0.5rem 0.75rem 0.5rem; border-bottom: 0.0625rem solid #e8e8e8; } .product-label { min-width: 60px; color: #888; font-size: 13px; .product-productCategory { margin-left: 0.5rem; font-size: 0.875rem; font-weight: 500; color: #333; } .del-row { justify-content: flex-end; .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 1rem; } .info-item { display: flex; flex-direction: column; gap: 0.25rem; } .info-label { font-size: 0.75rem; color: #666; font-weight: 400; } .info-value { font-size: 0.875rem; color: #333; font-weight: 500; } .info-value.highlight { color: #2979ff; font-weight: 600; } .product-form { margin-bottom: 1rem; } .del-btn { background: #ff4d4f; color: #fff; border-radius: 8px; padding: 4px 16px; font-size: 13px; margin-top: 4px; border-radius: 0.25rem; } .footer-btns { position: fixed; @@ -254,26 +859,46 @@ display: flex; justify-content: space-around; align-items: center; padding: 12px 0; box-shadow: 0 -2px 8px rgba(0,0,0,0.05); padding: 0.75rem 0; box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); z-index: 1000; } .cancel-btn { font-weight: 400; font-size: 16px; font-size: 1rem; color: #FFFFFF; width: 102px; width: 6.375rem; background: #C7C9CC; box-shadow: 0px 4px 10px 0px rgba(3,88,185,0.2); border-radius: 40px 40px 40px 40px; box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; } .save-btn { font-weight: 400; font-size: 16px; font-size: 1rem; color: #FFFFFF; width: 224px; width: 14rem; background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); box-shadow: 0px 4px 10px 0px rgba(3,88,185,0.2); border-radius: 40px 40px 40px 40px; box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; } .popup-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; background: #fff; position: sticky; top: 0; z-index: 10; } .cancelButton { color: #969799 } .confirmButton { color: #1989FA } .u-tree { height: 13rem; } </style> src/pages/sales/salesAccount/index.vue
@@ -1,14 +1,14 @@ <template> <view class="sales-account"> <!-- 页é¢å¤´é¨ --> <view class="page-header"> <view class="header-left"> <up-icon name="arrow-left" size="20" color="#333" @click="goBack"></up-icon> </view> <view class="header-center"> <text class="page-title">éå®å°è´¦</text> </view> </view> <van-nav-bar title="éå®å°è´¦" left-text="è¿å" left-arrow @click-left="goBack" fixed placeholder /> <!-- æç´¢åçéåºå --> <view class="search-filter-section"> @@ -29,7 +29,7 @@ <!-- éå®å°è´¦ç叿µ --> <view class="ledger-list" v-if="total > 0"> <view v-for="(item, index) in ledgerList" :key="index"> <view class="ledger-item" @click="handleItemClick(item)"> <view class="ledger-item" @click="handleInfo('edit', item)"> <view class="item-header"> <view class="item-left"> <view class="document-icon"> @@ -50,7 +50,7 @@ </view> <view class="detail-row"> <text class="detail-label">客æ·ååå·</text> <text class="detail-value highlight">{{ item.customerContractNo }}</text> <text class="detail-value">{{ item.customerContractNo }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¸å¡å</text> @@ -92,15 +92,18 @@ </view> <!-- æµ®å¨æä½æé® --> <view class="fab-button" @click="handleAdd"> <view class="fab-button" @click="handleInfo('add')"> <up-icon name="plus" size="24" color="#ffffff"></up-icon> </view> </view> </template> <script setup> import { ref, reactive, onMounted } from 'vue'; import { ref } from 'vue'; import { onShow } from '@dcloudio/uni-app'; import {ledgerListPage} from "@/api/salesManagement/salesLedger"; import useUserStore from "@/store/modules/user"; const userStore = useUserStore() // æç´¢å ³é®è¯ const searchKeyword = ref(''); @@ -126,34 +129,59 @@ // tableLoading.value = false; }); }; // æ¾ç¤ºçéé项 const showFilterOptions = () => { uni.showActionSheet({ itemList: ['ææ¥æçé', 'æç¶æçé', 'æéé¢çé'], success: (res) => { console.log('éæ©äºçéé项:', res.tapIndex); } }); // å¤çå°è´¦ä¿¡æ¯æä½ï¼æ¥ç/ç¼è¾/æ°å¢ï¼ const handleInfo = (type, row) => { try { // 设置æä½ç±»å uni.setStorageSync('operationType', type); // å¦ææ¯æ¥çæç¼è¾æä½ if (type !== 'add') { // éªè¯è¡æ°æ®æ¯å¦åå¨ if (!row) { uni.showToast({ title: 'æ°æ®ä¸åå¨', icon: 'error' }); return; } // æ£æ¥æéï¼åªæå½å ¥äººæè½ç¼è¾ if (row.entryPerson != userStore.id) { // éå½å ¥äººè·³è½¬å°åªè¯»è¯¦æ é¡µé¢ uni.setStorageSync('editData', JSON.stringify(row)); uni.navigateTo({ url: '/pages/sales/salesAccount/view' }); return; } // å½å ¥äººç¼è¾ï¼å卿°æ®å¹¶è·³è½¬å°ç¼è¾é¡µé¢ uni.setStorageSync('editData', JSON.stringify(row)); uni.navigateTo({ url: '/pages/sales/salesAccount/detail' }); return; } // æ°å¢æä½ï¼ç´æ¥è·³è½¬å°ç¼è¾é¡µé¢ uni.navigateTo({ url: '/pages/sales/salesAccount/detail' }); } catch (error) { console.error('å¤çå°è´¦ä¿¡æ¯æä½å¤±è´¥:', error); uni.showToast({ title: 'æä½å¤±è´¥ï¼è¯·éè¯', icon: 'error' }); } }; // ç¹å»å表项 const handleItemClick = (item) => { uni.showToast({ title: `æ¥çåå: ${item.contractId}`, icon: 'none' }); }; // æ·»å æ°è®°å½ const handleAdd = () => { uni.navigateTo({ url: '/pages/sales/salesAccount/detail' }); }; onMounted(() => { // 页é¢å è½½å®æåçåå§åé»è¾ getList() onShow(() => { // 页颿¾ç¤ºæ¶å·æ°å表 getList(); }); </script> src/pages/sales/salesAccount/view.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,315 @@ <template> <view class="account-view"> <!-- 顶鍿 颿 --> <van-nav-bar title="å°è´¦è¯¦æ " left-text="è¿å" left-arrow @click-left="goBack" fixed placeholder /> <!-- åºæ¬ä¿¡æ¯å±ç¤º --> <view class="info-section"> <view class="section-title">åºæ¬ä¿¡æ¯</view> <view class="info-grid"> <view class="info-item"> <text class="info-label">éå®ååå·</text> <text class="info-value">{{ form.salesContractNo }}</text> </view> <view class="info-item"> <text class="info-label">客æ·ååå·</text> <text class="info-value highlight">{{ form.customerContractNo }}</text> </view> <view class="info-item"> <text class="info-label">客æ·åç§°</text> <text class="info-value">{{ form.customerName }}</text> </view> <view class="info-item"> <text class="info-label">ä¸å¡å</text> <text class="info-value">{{ form.salesman }}</text> </view> <view class="info-item"> <text class="info-label">项ç®åç§°</text> <text class="info-value">{{ form.projectName }}</text> </view> <view class="info-item"> <text class="info-label">ç¾è®¢æ¥æ</text> <text class="info-value">{{ form.executionDate }}</text> </view> <view class="info-item"> <text class="info-label">仿¬¾æ¹å¼</text> <text class="info-value">{{ form.paymentMethod }}</text> </view> <view class="info-item"> <text class="info-label">å½å ¥äºº</text> <text class="info-value">{{ form.entryPersonName }}</text> </view> <view class="info-item"> <text class="info-label">å½å ¥æ¥æ</text> <text class="info-value">{{ form.entryDate }}</text> </view> </view> </view> <!-- 产åä¿¡æ¯å±ç¤º --> <view class="product-section" v-if="productData && productData.length > 0"> <view class="section-title">产åä¿¡æ¯</view> <view class="product-card" v-for="(product, idx) in productData" :key="idx"> <view class="product-header"> <view class="product-title"> <van-icon name="description" color="#2979ff" size="15" /> <text class="product-productCategory">产å {{ idx + 1 }}</text> </view> </view> <view class="product-info"> <view class="info-grid"> <view class="info-item"> <text class="info-label">产å大类</text> <text class="info-value">{{ product.productCategory }}</text> </view> <view class="info-item"> <text class="info-label">è§æ ¼åå·</text> <text class="info-value">{{ product.specificationModel }}</text> </view> <view class="info-item"> <text class="info-label">åä½</text> <text class="info-value">{{ product.unit }}</text> </view> <view class="info-item"> <text class="info-label">ç¨ç(%)</text> <text class="info-value">{{ product.taxRate }}</text> </view> <view class="info-item"> <text class="info-label">å«ç¨åä»·(å )</text> <text class="info-value highlight">{{ product.taxInclusiveUnitPrice }}</text> </view> <view class="info-item"> <text class="info-label">æ°é</text> <text class="info-value highlight">{{ product.quantity }}</text> </view> <view class="info-item"> <text class="info-label">å«ç¨æ»ä»·(å )</text> <text class="info-value highlight">{{ product.taxInclusiveTotalPrice }}</text> </view> <view class="info-item"> <text class="info-label">ä¸å«ç¨æ»ä»·(å )</text> <text class="info-value highlight">{{ product.taxExclusiveTotalPrice }}</text> </view> <view class="info-item"> <text class="info-label">å票类å</text> <text class="info-value">{{ product.invoiceType }}</text> </view> </view> </view> </view> </view> <!-- æ 产åä¿¡æ¯æç¤º --> <view class="no-product" v-else> <text>ææ äº§åä¿¡æ¯</text> </view> </view> </template> <script setup> import { onMounted, ref } from 'vue'; import { getSalesLedgerWithProducts } from "@/api/salesManagement/salesLedger"; // è¡¨åæ°æ® const form = ref({ id: '', salesContractNo: '', customerContractNo: '', customerId: '', customerName: '', projectName: '', executionDate: '', paymentMethod: '', entryPerson: '', entryPersonName: '', entryDate: '', salesman: '' }); // äº§åæ°æ® const productData = ref([]); // ç¼è¾æ°æ® const editData = ref(null); // è¿åä¸ä¸é¡µ const goBack = () => { // æ¸ çæ¬å°åå¨çæ°æ® uni.removeStorageSync('editData'); uni.navigateBack(); }; // å¡«å è¡¨åæ°æ® const fillFormData = () => { if (!editData.value) return; // è·å宿´ç产åä¿¡æ¯ getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then((res) => { productData.value = res.productData || []; }); // å¡«å åºæ¬ä¿¡æ¯ form.value.salesContractNo = editData.value.salesContractNo || ''; form.value.customerContractNo = editData.value.customerContractNo || ''; form.value.customerName = editData.value.customerName || ''; form.value.projectName = editData.value.projectName || ''; form.value.executionDate = editData.value.executionDate || ''; form.value.paymentMethod = editData.value.paymentMethod || ''; form.value.salesman = editData.value.salesman || ''; form.value.entryPerson = editData.value.entryPerson || ''; form.value.entryPersonName = editData.value.entryPersonName || ''; form.value.entryDate = editData.value.entryDate || ''; form.value.id = editData.value.id || ''; form.value.customerId = editData.value.customerId || ''; }; onMounted(() => { // è·åç¼è¾æ°æ®å¹¶å¡«å 表å const editDataStr = uni.getStorageSync('editData'); if (editDataStr) { try { editData.value = JSON.parse(editDataStr); // ä½¿ç¨ nextTick ç¡®ä¿æ°æ®å è½½å®æååå¡«å setTimeout(() => { fillFormData(); }, 100); } catch (error) { console.error('è§£æç¼è¾æ°æ®å¤±è´¥:', error); } } }); </script> <style scoped lang="scss"> .account-view { min-height: 100vh; background: #f8f9fa; padding-bottom: 2rem; } .header { display: flex; align-items: center; background: #fff; padding: 1rem 1.25rem; border-bottom: 0.0625rem solid #f0f0f0; position: sticky; top: 0; z-index: 100; /* å ¼å®¹ iOS åæµ·/çµå¨å²å®å ¨åº */ padding-top: env(safe-area-inset-top); } .title { flex: 1; text-align: center; font-size: 1.125rem; font-weight: 600; color: #333; } .info-section { background: #fff; margin: 1rem; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); } .section-title { font-size: 1rem; font-weight: 600; color: #333; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 0.0625rem solid #e8e8e8; } .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } .info-item { display: flex; flex-direction: column; gap: 0.25rem; } .info-label { font-size: 0.75rem; color: #666; font-weight: 400; } .info-value { font-size: 0.875rem; color: #333; font-weight: 500; } .info-value.highlight { color: #2979ff; font-weight: 600; } .product-section { background: #fff; margin: 1rem; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); } .product-card { background: #f8f9fa; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1rem; } .product-card:last-child { margin-bottom: 0; } .product-header { display: flex; align-items: center; padding-bottom: 0.75rem; border-bottom: 0.0625rem solid #e8e8e8; margin-bottom: 1rem; } .product-title { display: flex; align-items: center; gap: 0.5rem; } .product-productCategory { font-size: 0.875rem; font-weight: 500; color: #333; } .product-info .info-grid { grid-template-columns: 1fr 1fr; gap: 0.5rem; } .no-product { text-align: center; padding: 2rem; color: #999; font-size: 0.875rem; } </style> src/utils/summarizeTable.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,57 @@ /** * éç¨çè¡¨æ ¼åè®¡æ¹æ³ * @param {Object} param - å å«è¡¨æ ¼åé ç½®åæ°æ®æºç对象 * @param {Array<string>} summaryProps - éè¦æ±æ»çåæ®µåæ°ç» * @param {Object} specialFormat - ç¹æ®æ ¼å¼åè§åï¼å段å -> æ ¼å¼åé项ï¼å¦æ¯å¦å»æå°æ°ï¼ * @returns {Array} åè®¡è¡æ°æ® */ const summarizeTable = (param, summaryProps, specialFormat = {}) => { const { columns, data } = param; const sums = []; columns.forEach((column, index) => { if (index === 0) { sums[index] = "å计"; return; } const prop = column.property; if (summaryProps.includes(prop)) { const values = data.map((item) => Number(item[prop])); // åªå¯¹æææ°åè¿è¡æ±å if (!values.every(isNaN)) { const sum = values.reduce( (acc, val) => (!isNaN(val) ? acc + val : acc), 0 ); if (specialFormat[prop] && specialFormat[prop].noDecimal) { // 妿æå®äºä¸éè¦ä¿çå°æ°ï¼åç´æ¥è½¬æ¢ä¸ºæ´æ° sums[index] = Math.round(sum).toString(); } else { // é»è®¤ä¿ç两ä½å°æ° sums[index] = parseFloat(sum).toFixed( specialFormat[prop]?.decimalPlaces ?? 2 ); } } else { sums[index] = ""; } } else { sums[index] = ""; } }); return sums; }; // ä¸å«ç¨æ»ä»·è®¡ç® const calculateTaxExclusiveTotalPrice = (taxInclusiveTotalPrice, taxRate) => { const taxRateDecimal = taxRate / 100; return (taxInclusiveTotalPrice / (1 + taxRateDecimal)).toFixed(2); }; // å«ç¨æ»ä»·è®¡ç® const calculateTaxIncludeTotalPrice = (taxInclusiveUnitPrice, quantity) => { return (taxInclusiveUnitPrice * quantity).toFixed(2); }; // 导åºå½æ°ä¾å ¶ä»æä»¶ä½¿ç¨ export { summarizeTable, calculateTaxExclusiveTotalPrice, calculateTaxIncludeTotalPrice, };