| src/api/basicData/customerFile.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/salesManagement/salesQuotation.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages.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/api/basicData/customerFile.js
@@ -50,3 +50,44 @@ }) } // æ°å¢å®¢æ·è·è¿ export function addCustomerFollow(data) { return request({ url: '/basic/customer-follow/add', method: 'post', data: data }) } // ä¿®æ¹å®¢æ·è·è¿ export function updateCustomerFollow(data) { return request({ url: '/basic/customer-follow/edit', method: 'put', data: data, }) } // å é¤å®¢æ·è·è¿ export function delCustomerFollow(id) { return request({ url: '/basic/customer-follow/'+id, method: 'delete', }) } // å访æé-æ°å¢/æ´æ° export function addReturnVisit(data) { return request({ url: '/basic/customer-follow/return-visit', method: 'post', data: data }) } // è·åå访æé详æ export function getReturnVisit(id) { return request({ url: '/basic/customer-follow/return-visit/' + id, method: 'get' }) } src/api/salesManagement/salesQuotation.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,112 @@ // é宿¥ä»·é¡µé¢æ¥å£ import request from "@/utils/request"; // å页æ¥è¯¢æ¥ä»·åå表 export function getQuotationList(query) { return request({ url: "/sales/quotation/list", method: "get", params: query, }); } // æ¥è¯¢æ¥ä»·å详æ export function getQuotationDetail(query) { return request({ url: "/sales/quotation/detail", method: "get", params: query, }); } // æ°å¢æ¥ä»·å export function addQuotation(data) { return request({ url: "/sales/quotation/add", method: "post", data: data, }); } // ä¿®æ¹æ¥ä»·å export function updateQuotation(data) { return request({ url: "/sales/quotation/update", method: "post", data: data, }); } // å 餿¥ä»·å export function deleteQuotation(query) { return request({ url: "/sales/quotation/delete", method: "delete", data: query, }); } // å鿥价å export function sendQuotation(data) { return request({ url: "/sales/quotation/send", method: "post", data: data, }); } // æ¥ä»·å转订å export function convertToOrder(data) { return request({ url: "/sales/quotation/convertToOrder", method: "post", data: data, }); } // æ¥è¯¢å®¢æ·å表 export function getCustomerList(query) { return request({ url: "/basic/customer/list", method: "get", params: query, }); } // æ¥è¯¢äº§åå表 export function getProductList(query) { return request({ url: "/basic/product/list", method: "get", params: query, }); } // æ¥è¯¢ä¸å¡åå表 export function getSalespersonList(query) { return request({ url: "/system/user/salespersonList", method: "get", params: query, }); } // å¯¼åºæ¥ä»·å export function exportQuotation(query) { return request({ url: "/sales/quotation/export", method: "get", params: query, responseType: "blob", }); } // æå°æ¥ä»·å export function printQuotation(query) { return request({ url: "/sales/quotation/print", method: "get", params: query, responseType: "blob", }); } src/pages.json
@@ -73,6 +73,27 @@ } }, { "path": "pages/basicData/customerFile/index", "style": { "navigationBarTitleText": "å®¢æ·æ¡£æ¡", "navigationStyle": "custom" } }, { "path": "pages/basicData/customerFile/edit", "style": { "navigationBarTitleText": "客æ·ä¿¡æ¯", "navigationStyle": "custom" } }, { "path": "pages/basicData/customerFile/detail", "style": { "navigationBarTitleText": "客æ·è¯¦æ ", "navigationStyle": "custom" } }, { "path": "pages/sales/salesAccount/index", "style": { "navigationBarTitleText": "éå®å°è´¦", @@ -80,6 +101,27 @@ } }, { "path": "pages/sales/salesQuotation/index", "style": { "navigationBarTitleText": "é宿¥ä»·", "navigationStyle": "custom" } }, { "path": "pages/sales/salesQuotation/edit", "style": { "navigationBarTitleText": "é宿¥ä»·", "navigationStyle": "custom" } }, { "path": "pages/sales/salesQuotation/detail", "style": { "navigationBarTitleText": "æ¥ä»·è¯¦æ ", "navigationStyle": "custom" } }, { "path": "pages/sales/salesAccount/out", "style": { "navigationBarTitleText": "åè´§ç¶æ", src/pages/basicData/customerFile/detail.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,205 @@ <template> <view class="customer-detail-page"> <PageHeader title="客æ·è¯¦æ " @back="goBack" /> <view class="detail-content"> <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.customerName || "-" }}</text> </view> <view class="info-item"> <text class="info-label">客æ·åç±»</text> <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> <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.contactPerson || "-" }}</text> </view> <view class="info-item"> <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> </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.maintainer || "-" }}</text> </view> <view class="info-item"> <text class="info-label">ç»´æ¤æ¶é´</text> <text class="info-value">{{ detailData.maintenanceTime || "-" }}</text> </view> </view> </view> </view> <FooterButtons cancelText="è¿å" confirmText="ç¼è¾" @cancel="goBack" @confirm="goEdit" /> </view> </template> <script setup> import { ref } from "vue"; import { onLoad, onShow } from "@dcloudio/uni-app"; import FooterButtons from "@/components/FooterButtons.vue"; import { getCustomer } from "@/api/basicData/customerFile"; const customerId = ref(""); const detailData = ref({}); const goBack = () => { uni.navigateBack(); }; const goEdit = () => { if (!customerId.value) return; uni.navigateTo({ url: `/pages/basicData/customerFile/edit?id=${customerId.value}` }); }; const getDetail = () => { if (!customerId.value) return; uni.showLoading({ title: "å è½½ä¸...", mask: true }); getCustomer(customerId.value) .then(res => { detailData.value = res.data || {}; }) .catch(() => { uni.showToast({ title: "è·å详æ 失败", icon: "error" }); }) .finally(() => { uni.hideLoading(); }); }; onLoad(options => { if (options?.id) { customerId.value = options.id; getDetail(); } }); onShow(() => { if (customerId.value) { getDetail(); } }); </script> <style scoped lang="scss"> .customer-detail-page { min-height: 100vh; background-color: #f5f5f5; padding-bottom: 90px; } .detail-content { padding: 16px; } .section { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .section-title { padding: 16px; font-size: 16px; font-weight: 600; color: #303133; border-bottom: 1px solid #f0f0f0; } .info-list { padding: 8px 0; } .info-item { display: flex; padding: 12px 16px; border-bottom: 1px solid #f8f8f8; } .info-item:last-child { border-bottom: none; } .info-label { width: 120px; font-size: 14px; color: #606266; } .info-value { flex: 1; font-size: 14px; color: #303133; text-align: right; word-break: break-all; } </style> src/pages/basicData/customerFile/edit.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,342 @@ <template> <view class="account-detail"> <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="customerName" required> <up-input v-model="form.customerName" placeholder="请è¾å ¥å®¢æ·åç§°" clearable /> </up-form-item> <up-form-item label="客æ·åç±»" prop="customerType" required> <up-input v-model="customerTypeText" placeholder="è¯·éæ©å®¢æ·åç±»" readonly @click="showCustomerTypeSheet = true" /> <template #right> <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-input v-model="form.companyAddress" placeholder="请è¾å ¥å ¬å¸å°å" clearable /> </up-form-item> <up-form-item label="å ¬å¸çµè¯" prop="companyPhone"> <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> </u-cell-group> <u-cell-group title="è系信æ¯" class="form-section"> <up-form-item label="è系人" prop="contactPerson"> <up-input v-model="form.contactPerson" placeholder="请è¾å ¥è系人" clearable /> </up-form-item> <up-form-item label="èç³»çµè¯" prop="contactPhone"> <up-input v-model="form.contactPhone" placeholder="请è¾å ¥èç³»çµè¯" clearable /> </up-form-item> </u-cell-group> <u-cell-group title="é¶è¡ä¿¡æ¯" class="form-section"> <up-form-item label="é¶è¡åºæ¬æ·" prop="basicBankAccount"> <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="请è¾å ¥å¼æ·è¡å·" clearable /> </up-form-item> </u-cell-group> <u-cell-group title="ç»´æ¤ä¿¡æ¯" class="form-section"> <up-form-item label="ç»´æ¤äºº" prop="maintainer"> <up-input v-model="form.maintainer" disabled placeholder="èªå¨å¡«å " /> </up-form-item> <up-form-item label="ç»´æ¤æ¶é´" prop="maintenanceTime"> <up-input v-model="form.maintenanceTime" disabled placeholder="èªå¨å¡«å " /> </up-form-item> </u-cell-group> </up-form> </view> <FooterButtons :loading="loading" confirmText="ä¿å" @cancel="goBack" @confirm="handleSubmit" /> <up-action-sheet :show="showCustomerTypeSheet" title="鿩客æ·åç±»" :actions="customerTypeActions" @select="onSelectCustomerType" @close="showCustomerTypeSheet = false" /> </view> </template> <script setup> import { computed, onMounted, ref } from "vue"; import { onLoad } from "@dcloudio/uni-app"; import FooterButtons from "@/components/FooterButtons.vue"; import useUserStore from "@/store/modules/user"; import { formatDateToYMD } from "@/utils/ruoyi"; import { addCustomer, getCustomer, updateCustomer } from "@/api/basicData/customerFile"; const userStore = useUserStore(); const formRef = ref(); const loading = ref(false); const customerId = ref(""); const showCustomerTypeSheet = ref(false); const form = ref({ customerName: "", customerType: "", taxpayerIdentificationNumber: "", companyAddress: "", companyPhone: "", corporation: "", agent: "", fax: "", contactPerson: "", contactPhone: "", basicBankAccount: "", bankAccount: "", bankName: "", bankCode: "", maintainer: "", maintenanceTime: "", }); const rules = { customerName: [{ required: true, message: "请è¾å ¥å®¢æ·åç§°", trigger: "blur" }], customerType: [{ required: true, message: "è¯·éæ©å®¢æ·åç±»", trigger: "change" }], }; const customerTypeActions = [ { name: "é¶å®å®¢æ·", value: "é¶å®å®¢æ·" }, { name: "ç»éå客æ·", value: "ç»éå客æ·" }, ]; const pageTitle = computed(() => customerId.value ? "ç¼è¾å®¢æ·" : "æ°å¢å®¢æ·" ); const customerTypeText = computed(() => form.value.customerType || ""); const goBack = () => { uni.navigateBack(); }; const initForAdd = () => { form.value.maintainer = userStore.nickName || ""; form.value.maintenanceTime = formatDateToYMD(Date.now()); }; const loadDetail = () => { if (!customerId.value) return; uni.showLoading({ title: "å è½½ä¸...", mask: true }); getCustomer(customerId.value) .then(res => { form.value = { ...form.value, ...(res.data || {}) }; }) .catch(() => { uni.showToast({ title: "è·å详æ 失败", icon: "error" }); }) .finally(() => { uni.hideLoading(); }); }; const onSelectCustomerType = action => { form.value.customerType = action.value; showCustomerTypeSheet.value = false; }; const handleSubmit = async () => { const valid = await formRef.value.validate().catch(() => false); if (!valid) return; loading.value = true; const action = customerId.value ? updateCustomer : addCustomer; action({ ...form.value, id: customerId.value || undefined }) .then(() => { uni.showToast({ title: "ä¿åæå", icon: "success" }); setTimeout(() => { uni.navigateBack(); }, 300); }) .catch(() => { uni.showToast({ title: "ä¿å失败", icon: "error" }); }) .finally(() => { loading.value = false; }); }; onMounted(async () => { if (!userStore.nickName) { await userStore.getInfo().catch(() => null); } if (!customerId.value) { initForAdd(); } }); onLoad(options => { if (options?.id) { customerId.value = options.id; loadDetail(); } }); </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, #f4f8ff 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 { margin-bottom: 12px; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 10px rgba(15, 35, 95, 0.05); } :deep(.u-cell-group__title) { padding: 14px 18px 10px !important; font-size: 15px !important; font-weight: 600 !important; color: #22324d !important; background: #f8fbff !important; } :deep(.u-form-item__content__slot) { flex: 1; } :deep(.u-input__content) { justify-content: flex-end; } :deep(.u-input__content__field-wrapper__field), :deep(.u-input__input) { text-align: right !important; } </style> src/pages/basicData/customerFile/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,183 @@ <template> <view class="sales-account"> <PageHeader title="å®¢æ·æ¡£æ¡" @back="goBack" /> <view class="search-section"> <view class="search-bar"> <view class="search-input"> <up-input class="search-text" v-model="customerName" placeholder="请è¾å ¥å®¢æ·åç§°" clearable @change="getList" /> </view> <view class="filter-button" @click="getList"> <up-icon name="search" size="24" color="#999999"></up-icon> </view> </view> </view> <view class="tabs-section"> <up-tabs v-model="tabValue" :list="tabList" itemStyle="width: 33.33%;height: 80rpx;" @change="onTabChange" /> </view> <view v-if="list.length > 0" class="ledger-list"> <view v-for="item in list" :key="item.id" class="ledger-item"> <view class="item-header"> <view class="item-left"> <view class="document-icon"> <up-icon name="account-fill" size="16" color="#ffffff"></up-icon> </view> <text class="item-id">{{ item.customerName || "-" }}</text> </view> <text class="item-index">{{ item.customerType || "-" }}</text> </view> <up-divider></up-divider> <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> <view class="detail-row"> <text class="detail-label">å ¬å¸å°å</text> <text class="detail-value">{{ item.companyAddress || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">æ³äºº</text> <text class="detail-value">{{ item.corporation || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">代ç人</text> <text class="detail-value">{{ item.agent || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">ç»´æ¤äºº</text> <text class="detail-value">{{ item.maintainer || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">ç»´æ¤æ¶é´</text> <text class="detail-value">{{ item.maintenanceTime || "-" }}</text> </view> </view> <view class="action-buttons"> <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 > </view> </view> </view> <view v-else class="no-data"> <text>ææ å®¢æ·æ¡£æ¡æ°æ®</text> </view> <view class="fab-button" @click="goAdd"> <up-icon name="plus" size="28" color="#ffffff"></up-icon> </view> </view> </template> <script setup> import { reactive, ref } from "vue"; import { onShow } from "@dcloudio/uni-app"; import { listCustomer } from "@/api/basicData/customerFile"; const customerName = ref(""); const list = ref([]); const tabList = reactive([ { 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/basicData/customerFile/edit" }); }; const goEdit = item => { uni.navigateTo({ url: `/pages/basicData/customerFile/edit?id=${item.id}` }); }; const goDetail = item => { uni.navigateTo({ url: `/pages/basicData/customerFile/detail?id=${item.id}` }); }; const onTabChange = val => { tabValue.value = val.index; getList(); }; const getCurrentCustomerType = () => { const currentTab = tabList[tabValue.value]; return currentTab?.value || ""; }; const getList = () => { uni.showLoading({ title: "å è½½ä¸...", mask: true }); listCustomer({ ...page, customerName: customerName.value, customerType: getCurrentCustomerType(), }) .then(res => { list.value = res?.records || res?.data?.records || []; }) .catch(() => { uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error" }); }) .finally(() => { uni.hideLoading(); }); }; onShow(() => { getList(); }); </script> <style scoped lang="scss"> @import "@/styles/procurement-common.scss"; .tabs-section { background: #ffffff; padding: 0 12px 8px 12px; } .item-index { max-width: 180rpx; text-align: center; } .detail-value { max-width: 70%; word-break: break-all; } </style> src/pages/sales/salesQuotation/detail.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,235 @@ <template> <view class="customer-detail-page"> <PageHeader title="æ¥ä»·è¯¦æ " @back="goBack" /> <view class="detail-content"> <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> </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 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> </view> <view v-else class="empty-box"> <text>ææ äº§åæç»</text> </view> </view> </view> <FooterButtons cancelText="è¿å" confirmText="ç¼è¾" @cancel="goBack" @confirm="goEdit" /> </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"; const quotationId = ref(""); const detailData = ref({}); const approverNames = computed(() => { const approverText = detailData.value.approveUserNames || detailData.value.approverNames || detailData.value.approveUserIds || ""; if (Array.isArray(approverText)) return approverText.filter(Boolean); return String(approverText) .split(",") .map(item => item.trim()) .filter(Boolean); }); const goBack = () => { uni.navigateBack(); }; const goEdit = () => { if (!quotationId.value) return; uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${quotationId.value}` }); }; const formatAmount = amount => `Â¥${Number(amount || 0).toFixed(2)}`; const loadDetailFromStorage = () => { const cachedData = uni.getStorageSync("salesQuotationDetail"); detailData.value = cachedData || {}; }; onLoad(options => { if (options?.id) { quotationId.value = options.id; } loadDetailFromStorage(); }); onShow(() => { loadDetailFromStorage(); }); </script> <style scoped lang="scss"> .customer-detail-page { min-height: 100vh; background-color: #f5f5f5; padding-bottom: 90px; } .detail-content { padding: 16px; } .section { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .section-title { padding: 16px; font-size: 16px; font-weight: 600; color: #303133; border-bottom: 1px solid #f0f0f0; } .info-list { padding: 8px 0; } .info-item { display: flex; padding: 12px 16px; border-bottom: 1px solid #f8f8f8; } .info-item:last-child { border-bottom: none; } .info-label { width: 120px; font-size: 14px; color: #606266; } .info-value { flex: 1; font-size: 14px; color: #303133; text-align: right; word-break: break-all; } .highlight { color: #2979ff; font-weight: 600; } .empty-box { padding: 20px 16px; font-size: 14px; color: #999; text-align: center; } .product-list { padding: 12px; display: flex; flex-direction: column; gap: 12px; } .product-card { background: #f9fafc; border-radius: 10px; overflow: hidden; } .product-head { padding: 12px 16px; font-size: 14px; font-weight: 600; color: #22324d; border-bottom: 1px solid #eef2f7; } </style> src/pages/sales/salesQuotation/edit.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,613 @@ <template> <view class="account-detail"> <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> <u-cell-group title="产åä¿¡æ¯" class="form-section"> <view class="section-tools"> <up-button type="primary" size="small" text="æ°å¢äº§å" @click="addProduct" /> </view> <view v-if="form.products.length === 0" class="empty-text"> <text>ææ äº§åï¼è¯·å æ·»å 产å</text> </view> <view v-else class="product-list"> <view v-for="(product, index) in form.products" :key="product.uid" class="product-card"> <view class="product-header"> <text class="product-title">产å {{ index + 1 }}</text> <up-icon name="trash" color="#ee0a24" size="18" @click="removeProduct(index)"></up-icon> </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> <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.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-form-item> <up-form-item label="éé¢"> <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"> <up-form-item label="æ¥ä»·æ»é¢"> <up-input :model-value="formatAmount(totalAmount)" disabled placeholder="èªå¨æ±æ»" /> </up-form-item> </u-cell-group> </up-form> </view> <FooterButtons :loading="loading" confirmText="ä¿å" @cancel="goBack" @confirm="handleSubmit" /> <up-action-sheet :show="showCustomerSheet" title="鿩客æ·" :actions="customerActions" @select="onSelectCustomer" @close="showCustomerSheet = false" /> <up-action-sheet :show="showSalespersonSheet" title="éæ©ä¸å¡å" :actions="salespersonActions" @select="onSelectSalesperson" @close="showSalespersonSheet = false" /> <up-action-sheet :show="showProductSheet" title="éæ©äº§å" :actions="productActions" @select="onSelectProduct" @close="showProductSheet = false" /> <up-action-sheet :show="showModelSheet" title="éæ©è§æ ¼åå·" :actions="modelActions" @select="onSelectModel" @close="showModelSheet = false" /> <up-datetime-picker :show="showQuotationDatePicker" v-model="quotationDateValue" mode="date" @confirm="onQuotationDateConfirm" @cancel="showQuotationDatePicker = false" /> <up-datetime-picker :show="showValidDatePicker" v-model="validDateValue" mode="date" @confirm="onValidDateConfirm" @cancel="showValidDatePicker = false" /> </view> </template> <script setup> import { computed, onMounted, onUnmounted, 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"; 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 productActions = computed(() => productList.value.map(item => ({ name: item.label, value: item.value, label: item.label }))); const createEmptyProduct = () => ({ uid: `p_${uidSeed++}`, productId: "", product: "", specificationId: "", specification: "", unit: "", quantity: 1, unitPrice: 0, amount: 0, modelOptions: [], }); const flattenProductTree = nodes => { 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 }); } }); }; walk(nodes); return result; }; 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)); form.value.totalAmount = totalAmount.value; }; const addApproverNode = () => approverNodes.value.push({ id: nextApproverId++, userId: "", nickName: "" }); const removeApproverNode = index => approverNodes.value.splice(index, 1); const openApproverPicker = index => { uni.setStorageSync("stepIndex", index); uni.navigateTo({ url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect", }); }; const addProduct = () => form.value.products.push(createEmptyProduct()); const removeProduct = index => { form.value.products.splice(index, 1); form.value.totalAmount = totalAmount.value; }; const fetchModelOptions = async (productId, product) => { const rows = await modelList({ id: productId }).catch(() => []); product.modelOptions = Array.isArray(rows) ? rows : []; }; const openProductPicker = index => { currentProductIndex.value = index; showProductSheet.value = true; }; const openModelPicker = index => { currentProductIndex.value = index; const current = form.value.products[index]; if (!current?.productId) { uni.showToast({ title: "请å éæ©äº§å", icon: "none" }); return; } modelActions.value = (current.modelOptions || []).map(item => ({ name: item.model, value: item.id, unit: item.unit })); if (!modelActions.value.length) { 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; current.productId = action.value; current.product = action.label; current.specificationId = ""; current.specification = ""; current.unit = ""; current.modelOptions = []; showProductSheet.value = false; fetchModelOptions(action.value, current); }; const onSelectModel = action => { const current = form.value.products[currentProductIndex.value]; if (!current) return; current.specificationId = action.value; current.specification = action.name; 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 : []; 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; })); form.value.products = normalized; }; const loadDetail = async () => { if (!quotationId.value) return; uni.showLoading({ title: "å è½½ä¸...", mask: true }); try { const res = await getQuotationDetail({ id: quotationId.value }); const data = res?.data || {}; 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 validateProducts = () => { if (!form.value.products.length) { 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)); 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 handleSubmit = async () => { const valid = await formRef.value.validate().catch(() => false); if (!valid || !validateApprovers() || !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) .then(() => { uni.showToast({ title: "ä¿åæå", icon: "success" }); setTimeout(() => uni.navigateBack(), 300); }) .catch(() => { uni.showToast({ title: "ä¿å失败", icon: "error" }); }) .finally(() => { loading.value = false; }); }; onLoad(options => { if (options?.id) { quotationId.value = options.id; form.value.id = options.id; } else { const today = formatDateToYMD(Date.now()); form.value.quotationDate = today; form.value.validDate = today; } }); onMounted(async () => { await fetchBaseOptions(); uni.$on("selectContact", onSelectApprover); if (quotationId.value) { await loadDetail(); } }); onUnmounted(() => { uni.$off("selectContact", onSelectApprover); uni.removeStorageSync("stepIndex"); }); </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 { margin-bottom: 12px; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 10px rgba(15, 35, 95, 0.05); } .section-tools { display: flex; justify-content: flex-end; padding: 12px 12px 0; } .node-list, .product-list { padding: 12px; display: flex; flex-direction: column; 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; color: #22324d; } .product-card { background: #fff; border-radius: 12px; padding: 0 12px 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .product-header { padding: 12px 0; } .empty-text { padding: 16px 12px; color: #999; font-size: 14px; } :deep(.u-cell-group__title) { padding: 14px 18px 10px !important; font-size: 15px !important; font-weight: 600 !important; color: #22324d !important; background: #f8fbff !important; } </style> src/pages/sales/salesQuotation/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,226 @@ <template> <view class="sales-account"> <PageHeader title="é宿¥ä»·" @back="goBack" /> <view class="search-section"> <view class="search-bar"> <view class="search-input"> <up-input class="search-text" v-model="quotationNo" placeholder="请è¾å ¥æ¥ä»·åå·æç´¢" clearable @change="getList" /> </view> <view class="filter-button" @click="getList"> <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> <view class="tabs-section"> <up-tabs v-model="tabValue" :list="tabList" itemStyle="width: 20%;height: 80rpx;" @change="onTabChange" /> </view> <view v-if="quotationList.length > 0" class="ledger-list"> <view v-for="item in quotationList" :key="item.id" class="ledger-item"> <view class="item-header"> <view class="item-left"> <view class="document-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-id">{{ item.quotationNo || "-" }}</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> </view> <view class="detail-row"> <text class="detail-label">ä¸å¡å</text> <text class="detail-value">{{ item.salesperson || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">æ¥ä»·æ¥æ</text> <text class="detail-value">{{ item.quotationDate || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">æææè³</text> <text class="detail-value">{{ item.validDate || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">仿¬¾æ¹å¼</text> <text class="detail-value">{{ item.paymentMethod || "-" }}</text> </view> <view class="detail-row"> <text class="detail-label">æ¥ä»·éé¢</text> <text class="detail-value highlight">{{ formatAmount(item.totalAmount) }}</text> </view> <view class="detail-row"> <text class="detail-label">夿³¨</text> <text class="detail-value">{{ item.remark || "-" }}</text> </view> </view> <view class="action-buttons"> <up-button class="action-btn" size="small" type="primary" :disabled="!canEdit(item)" @click="goEdit(item)" > ç¼è¾ </up-button> <up-button class="action-btn" size="small" @click="goDetail(item)">详æ </up-button> <up-button class="action-btn" size="small" type="error" plain @click="handleDelete(item)"> å é¤ </up-button> </view> </view> </view> <view v-else class="no-data"> <text>ææ é宿¥ä»·æ°æ®</text> </view> <view class="fab-button" @click="goAdd"> <up-icon name="plus" size="28" color="#ffffff"></up-icon> </view> </view> </template> <script setup> import { reactive, ref } from "vue"; import { onShow } from "@dcloudio/uni-app"; import PageHeader from "@/components/PageHeader.vue"; import { deleteQuotation, getQuotationList } from "@/api/salesManagement/salesQuotation"; const quotationNo = ref(""); 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 goEdit = item => { if (!canEdit(item)) return; 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 onTabChange = val => { tabValue.value = val.index; getList(); }; const getCurrentStatus = () => { const currentTab = tabList[tabValue.value]; return currentTab?.value || ""; }; const formatAmount = amount => { const num = Number(amount || 0); return `Â¥${num.toFixed(2)}`; }; const getList = () => { uni.showLoading({ title: "å è½½ä¸...", mask: true }); getQuotationList({ ...page, quotationNo: quotationNo.value, status: getCurrentStatus(), }) .then(res => { const records = res?.data?.records || res?.records || []; quotationList.value = Array.isArray(records) ? records : []; }) .catch(() => { uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error" }); }) .finally(() => { uni.hideLoading(); }); }; const handleDelete = item => { if (!item?.id) return; uni.showModal({ title: "å é¤ç¡®è®¤", content: "确认å é¤è¯¥æ¥ä»·ååï¼", success: res => { if (!res.confirm) return; uni.showLoading({ title: "å¤çä¸...", mask: true }); deleteQuotation(item.id) .then(() => { uni.showToast({ title: "å 餿å", icon: "success" }); getList(); }) .catch(() => { uni.showToast({ title: "å é¤å¤±è´¥", icon: "error" }); }) .finally(() => { uni.hideLoading(); }); }, }); }; onShow(() => { getList(); }); </script> <style scoped lang="scss"> @import "@/styles/sales-common.scss"; .tabs-section { background: #ffffff; padding: 0 12px 8px 12px; } .item-index { max-width: 180rpx; text-align: center; } .detail-value { max-width: 70%; word-break: break-all; } </style> src/pages/works.vue
@@ -301,6 +301,14 @@ const marketingItems = reactive([ { icon: "/static/images/icon/xiaoshoutaizhang.svg", label: "å®¢æ·æ¡£æ¡", }, { icon: "/static/images/icon/xiaoshoutaizhang.svg", label: "é宿¥ä»·", }, { icon: "/static/images/icon/xiaoshoutaizhang.svg", label: "éå®å°è´¦", }, { @@ -551,11 +559,21 @@ const handleCommonItemClick = item => { // æ ¹æ®ä¸åçåè½é¡¹è¿è¡è·³è½¬ switch (item.label) { case "å®¢æ·æ¡£æ¡": uni.navigateTo({ url: "/pages/basicData/customerFile/index", }); break; case "éå®å°è´¦": uni.navigateTo({ url: "/pages/sales/salesAccount/index", }); break; case "é宿¥ä»·": uni.navigateTo({ url: "/pages/sales/salesQuotation/index", }); break; case "å¼ç¥¨ç»è®°": uni.navigateTo({ url: "/pages/sales/invoicingRegistration/index",