| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ååå®¡æ¹ / å®¡æ¹æµç¨é
ç½® |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ¥è¯¢æå®¡æ¹ç±»åçæµç¨é
ç½®èç¹å表 */ |
| | | export function getApproveProcessConfigNodeList(type) { |
| | | return request({ |
| | | url: "/approveProcessConfigNode/list", |
| | | method: "get", |
| | | params: { type }, |
| | | }); |
| | | } |
| | | |
| | | /** ä¿åæå®¡æ¹ç±»åçæµç¨é
ç½®èç¹ï¼æ´ç»è¦çï¼ */ |
| | | export function addApproveProcessConfigNode(data) { |
| | | return request({ |
| | | url: "/approveProcessConfigNode/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | |
| | | // æ ¹æ®å®¢æ·æ¥è¯¢éå®è®¢åå· |
| | | export function getSalesLedger(query) { |
| | | return request({ |
| | | url: '/afterSalesService/listSalesLedger', |
| | | url: '/shippingInfo/getByCustomerName', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å
¥åºè®°å½å表 |
| | | export const getStockInRecordListPage = params => { |
| | | return request({ |
| | | url: "/stockInRecord/listPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // ä¿®æ¹å
¥åºè®°å½ |
| | | export const updateStockInRecord = (id, data) => { |
| | | return request({ |
| | | url: "/stockInRecord/" + id, |
| | | method: "put", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | // æ¹éå é¤å
¥åºè®°å½ |
| | | export const batchDeleteStockInRecords = ids => { |
| | | return request({ |
| | | url: "/stockInRecord", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤å¾
审æ¹å
¥åºè®°å½ |
| | | export const batchDeletePendingStockInRecords = ids => { |
| | | return request({ |
| | | url: "/stockInRecord/pending", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | // æ¹é审æ¹å
¥åºè®°å½ |
| | | export const batchApproveStockInRecords = data => { |
| | | return request({ |
| | | url: "/stockInRecord/approve", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | // æ¹éå审å
¥åºè®°å½ |
| | | export const batchUnapproveStockInRecords = data => { |
| | | return request({ |
| | | url: "/stockInRecord/reAudit", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢åºåºè®°å½å表 |
| | | export const getStockOutPage = params => { |
| | | return request({ |
| | | url: "/stockOutRecord/listPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤åºåºè®°å½ |
| | | export const delStockOut = ids => { |
| | | return request({ |
| | | url: "/stockOutRecord", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤å¾
审æ¹åºåºè®°å½ |
| | | export const delPendingStockOut = ids => { |
| | | return request({ |
| | | url: "/stockOutRecord/pending", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | // æ¹é审æ¹åºåºè®°å½ |
| | | export const batchApproveStockOutRecords = data => { |
| | | return request({ |
| | | url: "/stockOutRecord/approve", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | // æ¹éå审åºåºè®°å½ |
| | | export const batchUnapproveStockOutRecords = data => { |
| | | return request({ |
| | | url: "/stockOutRecord/reAudit", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | |
| | | // éè´å°è´¦é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | /** 仿¬¾å°è´¦ - ä¾åºå徿¥æ±æ» */ |
| | | export function paymentLedgerList(query) { |
| | | return request({ |
| | | url: "/purchase/paymentRegistration/supplierNameListPage", |
| | | url: "/purchase/report/supplierTransactions", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function paymentRecordList(supplierId) { |
| | | /** 仿¬¾å°è´¦ - ä¾åºå徿¥æç» */ |
| | | export function paymentRecordList(params) { |
| | | return request({ |
| | | url: "/purchase/paymentRegistration/supplierNameListPageDetails", |
| | | url: "/purchase/report/supplierTransactionsDetails", |
| | | method: "get", |
| | | params: supplierId, |
| | | params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // é宿æ ç»è®¡æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | /** 客æ·å¾æ¥æ±æ» */ |
| | | export function customewTransactions(query) { |
| | | return request({ |
| | | url: "/metricStatistics/customewTransactions", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | /** 客æ·å¾æ¥æç» */ |
| | | export function customewTransactionsDetails(query) { |
| | | return request({ |
| | | url: "/metricStatistics/customewTransactionsDetails", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function invoiceLedgerSalesAccount(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/salesAccount', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // 产åå¼ç¥¨è®°å½å页æ¥è¯¢ |
| | | export function registrationProductPage(query) { |
| | | return request({ |
| | |
| | | }) |
| | | } |
| | | |
| | | // 客æ·å¾æ¥è®°å½æ¥è¯¢ |
| | | export function customerInteractions(query) { |
| | | return request({ |
| | | url: '/receiptPayment/customerInteractions', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // 详æ
|
| | | export function receiptPaymentInfo(query) { |
| | | return request({ |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/cooperativeOffice/collaborativeApproval/index", |
| | | "style": { |
| | | "navigationBarTitleText": "åå审æ¹", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/cooperativeOffice/collaborativeApproval/index1", |
| | | "style": { |
| | | "navigationBarTitleText": "å
¬åºç®¡ç", |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/cooperativeOffice/collaborativeApproval/approvalManagement", |
| | | "style": { |
| | | "navigationBarTitleText": "å®¡æ¹æµç¨é
ç½®", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/safeProduction/dangerInvestigation/index", |
| | | "style": { |
| | | "navigationBarTitleText": "éæ£ææ¥ä¸æ¥", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!-- |
| | | ååå®¡æ¹ / ä¸å¡åæ®è¯¦æ
|
| | | approveType 5=éè´ 6=æ¥ä»· 7=åè´§ï¼åæ®å·å表åã审æ¹äºç±ãåæ®µæ¿è½½çç¼å· |
| | | --> |
| | | <template> |
| | | <view class="business-detail"> |
| | | <!-- æ¥ä»·å®¡æ¹è¯¦æ
--> |
| | | <view v-if="isQuotationApproval" |
| | | class="business-detail-block"> |
| | | <u-divider text="æ¥ä»·è¯¦æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="quotationLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentQuotation || !currentQuotation.quotationNo" |
| | | class="business-detail-empty"> |
| | | æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="æ¥ä»·åå·" |
| | | :value="currentQuotation.quotationNo"></u-cell> |
| | | <u-cell title="客æ·åç§°" |
| | | :value="currentQuotation.customer"></u-cell> |
| | | <u-cell title="ä¸å¡å" |
| | | :value="currentQuotation.salesperson"></u-cell> |
| | | <u-cell title="æ¥ä»·æ¥æ" |
| | | :value="currentQuotation.quotationDate"></u-cell> |
| | | <u-cell title="æææè³" |
| | | :value="currentQuotation.validDate"></u-cell> |
| | | <u-cell title="仿¬¾æ¹å¼" |
| | | :value="currentQuotation.paymentMethod"></u-cell> |
| | | <u-cell title="æ¥ä»·æ»é¢"> |
| | | <template #value> |
| | | <text class="business-detail-amount"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | </text> |
| | | </template> |
| | | </u-cell> |
| | | </u-cell-group> |
| | | <view class="business-detail-products"> |
| | | <view class="business-detail-subtitle">产åæç»</view> |
| | | <view v-for="(item, index) in (currentQuotation.products || [])" |
| | | :key="index" |
| | | class="business-detail-item"> |
| | | <view class="business-detail-item-head"> |
| | | <text class="business-detail-item-name">{{ item.product }}</text> |
| | | <text class="business-detail-amount">Â¥{{ Number(item.unitPrice ?? 0).toFixed(2) }}</text> |
| | | </view> |
| | | <view class="business-detail-item-desc"> |
| | | è§æ ¼: {{ item.specification }} | åä½: {{ item.unit }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="currentQuotation.remark" |
| | | class="business-detail-remark"> |
| | | <view class="business-detail-subtitle">夿³¨</view> |
| | | <view class="business-detail-remark-text">{{ currentQuotation.remark }}</view> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | |
| | | <!-- éè´å®¡æ¹è¯¦æ
--> |
| | | <view v-if="isPurchaseApproval" |
| | | class="business-detail-block"> |
| | | <u-divider text="éè´è¯¦æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="purchaseLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" |
| | | class="business-detail-empty"> |
| | | æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="éè´ååå·" |
| | | :value="currentPurchase.purchaseContractNumber"></u-cell> |
| | | <u-cell title="ä¾åºååç§°" |
| | | :value="currentPurchase.supplierName"></u-cell> |
| | | <u-cell title="项ç®åç§°" |
| | | :value="currentPurchase.projectName"></u-cell> |
| | | <u-cell title="éå®ååå·" |
| | | :value="currentPurchase.salesContractNo"></u-cell> |
| | | <u-cell title="ç¾è®¢æ¥æ" |
| | | :value="currentPurchase.executionDate"></u-cell> |
| | | <u-cell title="å½å
¥æ¥æ" |
| | | :value="currentPurchase.entryDate"></u-cell> |
| | | <u-cell title="仿¬¾æ¹å¼" |
| | | :value="currentPurchase.paymentMethod"></u-cell> |
| | | <u-cell title="ååéé¢"> |
| | | <template #value> |
| | | <text class="business-detail-amount"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | </text> |
| | | </template> |
| | | </u-cell> |
| | | </u-cell-group> |
| | | <view class="business-detail-products"> |
| | | <view class="business-detail-subtitle">产åæç»</view> |
| | | <view v-for="(item, index) in (currentPurchase.productData || [])" |
| | | :key="index" |
| | | class="business-detail-item"> |
| | | <view class="business-detail-item-head"> |
| | | <text class="business-detail-item-name">{{ item.productCategory }}</text> |
| | | <text class="business-detail-amount">Â¥{{ Number(item.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</text> |
| | | </view> |
| | | <view class="business-detail-item-desc"> |
| | | è§æ ¼: {{ item.specificationModel }} | æ°é: {{ item.quantity }} {{ item.unit }} |
| | | </view> |
| | | <view class="business-detail-item-extra"> |
| | | å«ç¨åä»·: Â¥{{ Number(item.taxInclusiveUnitPrice ?? 0).toFixed(2) }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | |
| | | <!-- å货审æ¹è¯¦æ
--> |
| | | <view v-if="isDeliveryApproval" |
| | | class="business-detail-block"> |
| | | <u-divider text="å货详æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="deliveryLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentDelivery || !currentDelivery.shippingInfo" |
| | | class="business-detail-empty"> |
| | | æªæ¥è¯¢å°å¯¹åºå货详æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="éå®è®¢å" |
| | | :value="currentDelivery.shippingInfo.salesContractNo || '--'"></u-cell> |
| | | <u-cell title="å货订åå·" |
| | | :value="currentDelivery.shippingInfo.shippingNo || '--'"></u-cell> |
| | | <u-cell title="客æ·åç§°" |
| | | :value="currentDelivery.shippingInfo.customerName || '--'"></u-cell> |
| | | <u-cell title="åè´§ç±»å" |
| | | :value="currentDelivery.shippingInfo.type || '--'"></u-cell> |
| | | <u-cell title="åè´§æ¥æ" |
| | | :value="currentDelivery.shippingInfo.shippingDate || '--'"></u-cell> |
| | | <u-cell title="å®¡æ ¸ç¶æ" |
| | | :value="currentDelivery.shippingInfo.status || '--'"></u-cell> |
| | | <u-cell title="å货车çå·" |
| | | :value="currentDelivery.shippingInfo.shippingCarNumber || '--'"></u-cell> |
| | | <u-cell title="å¿«éå
¬å¸" |
| | | :value="currentDelivery.shippingInfo.expressCompany || '--'"></u-cell> |
| | | <u-cell title="å¿«éåå·" |
| | | :value="currentDelivery.shippingInfo.expressNumber || '--'"></u-cell> |
| | | </u-cell-group> |
| | | <view class="business-detail-products"> |
| | | <view class="business-detail-subtitle">产åæç»</view> |
| | | <view v-for="(item, index) in deliveryProductList" |
| | | :key="index" |
| | | class="business-detail-item"> |
| | | <view class="business-detail-item-head"> |
| | | <text class="business-detail-item-name">{{ item.productName }}</text> |
| | | <text class="business-detail-count">æ°é: {{ item.deliveryQuantity }}</text> |
| | | </view> |
| | | <view class="business-detail-item-desc"> |
| | | è§æ ¼: {{ item.specificationModel }} |
| | | </view> |
| | | <view v-if="item.batchNo" |
| | | class="business-detail-item-extra"> |
| | | æ¹å·: {{ item.batchNo }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="currentDelivery.shippingInfo.storageBlobVOs && currentDelivery.shippingInfo.storageBlobVOs.length" |
| | | class="business-detail-remark"> |
| | | <view class="business-detail-subtitle">åè´§å¾ç</view> |
| | | <CommonUpload :model-value="currentDelivery.shippingInfo.storageBlobVOs" |
| | | disabled /> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref, watch } from "vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import { getDeliveryDetailByShippingNo } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger"; |
| | | |
| | | const props = defineProps({ |
| | | /** 审æ¹ç±»åï¼5 éè´ / 6 æ¥ä»· / 7 åè´§ */ |
| | | approveType: { type: [Number, String], default: 0 }, |
| | | /** ä¸å¡åæ®ç¼å·ï¼è¡¨åã审æ¹äºç±ãæ¿è½½ï¼ */ |
| | | businessNo: { type: String, default: "" }, |
| | | }); |
| | | |
| | | const type = computed(() => Number(props.approveType)); |
| | | const isQuotationApproval = computed(() => type.value === 6); |
| | | const isPurchaseApproval = computed(() => type.value === 5); |
| | | const isDeliveryApproval = computed(() => type.value === 7); |
| | | |
| | | const quotationLoading = ref(false); |
| | | const currentQuotation = ref({}); |
| | | const purchaseLoading = ref(false); |
| | | const currentPurchase = ref({}); |
| | | const deliveryLoading = ref(false); |
| | | const currentDelivery = ref({}); |
| | | const deliveryProductList = ref([]); |
| | | |
| | | const fetchDetailData = async () => { |
| | | const no = (props.businessNo || "").trim(); |
| | | if (!no) return; |
| | | |
| | | if (isQuotationApproval.value) { |
| | | quotationLoading.value = true; |
| | | getQuotationList({ quotationNo: no }) |
| | | .then(res => { |
| | | const records = res?.data?.records || []; |
| | | currentQuotation.value = records[0] || {}; |
| | | }) |
| | | .catch(() => { |
| | | currentQuotation.value = {}; |
| | | }) |
| | | .finally(() => { |
| | | quotationLoading.value = false; |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (isPurchaseApproval.value) { |
| | | purchaseLoading.value = true; |
| | | getPurchaseByCode({ purchaseContractNumber: no }) |
| | | .then(res => { |
| | | currentPurchase.value = res?.data || res || {}; |
| | | }) |
| | | .catch(() => { |
| | | currentPurchase.value = {}; |
| | | }) |
| | | .finally(() => { |
| | | purchaseLoading.value = false; |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (isDeliveryApproval.value) { |
| | | deliveryLoading.value = true; |
| | | currentDelivery.value = {}; |
| | | deliveryProductList.value = []; |
| | | getDeliveryDetailByShippingNo({ shippingNo: no }) |
| | | .then(res => { |
| | | const detailData = res?.data || res || {}; |
| | | currentDelivery.value = detailData; |
| | | deliveryProductList.value = |
| | | detailData.shippingProductDetailDtoList || []; |
| | | }) |
| | | .catch(() => { |
| | | currentDelivery.value = {}; |
| | | deliveryProductList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | deliveryLoading.value = false; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | watch( |
| | | () => [props.approveType, props.businessNo], |
| | | () => { |
| | | currentQuotation.value = {}; |
| | | currentPurchase.value = {}; |
| | | currentDelivery.value = {}; |
| | | deliveryProductList.value = []; |
| | | fetchDetailData(); |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .business-detail-block { |
| | | margin: 20rpx 0; |
| | | } |
| | | |
| | | .business-detail-empty { |
| | | padding: 40rpx; |
| | | text-align: center; |
| | | color: #999; |
| | | } |
| | | |
| | | .business-detail-amount { |
| | | font-size: 32rpx; |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .business-detail-count { |
| | | color: #2979ff; |
| | | } |
| | | |
| | | .business-detail-products { |
| | | margin-top: 20rpx; |
| | | padding: 0 30rpx; |
| | | } |
| | | |
| | | .business-detail-subtitle { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | margin-bottom: 10rpx; |
| | | } |
| | | |
| | | .business-detail-item { |
| | | background: #f8f8f8; |
| | | border-radius: 8rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 10rpx; |
| | | } |
| | | |
| | | .business-detail-item-head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .business-detail-item-name { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .business-detail-item-desc { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | margin-top: 10rpx; |
| | | } |
| | | |
| | | .business-detail-item-extra { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-top: 4rpx; |
| | | } |
| | | |
| | | .business-detail-remark { |
| | | margin-top: 20rpx; |
| | | padding: 0 30rpx; |
| | | } |
| | | |
| | | .business-detail-remark-text { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | margin-top: 10rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!-- |
| | | ååå®¡æ¹ / å®¡æ¹æµç¨èç¹ï¼è¯¦æ
ãå®¡æ ¸å
±ç¨ï¼ |
| | | steps ç± _utils/approveFlowSteps.js ç mapApprovalSteps çæï¼ |
| | | [{ title, approverName, status, statusText, approveTime, opinion, urlTem, isShen }] |
| | | --> |
| | | <template> |
| | | <view class="approval-process"> |
| | | <view class="process-header"> |
| | | <text class="process-title">å®¡æ¹æµç¨</text> |
| | | </view> |
| | | <view class="process-steps"> |
| | | <view v-for="(step, index) in steps" |
| | | :key="index" |
| | | class="process-step" |
| | | :class="{ |
| | | completed: step.status === 'completed', |
| | | current: step.status === 'current', |
| | | pending: step.status === 'pending', |
| | | rejected: step.status === 'rejected' |
| | | }"> |
| | | <view class="step-indicator"> |
| | | <view class="step-dot"> |
| | | <text v-if="step.status === 'completed'" |
| | | class="step-icon">â</text> |
| | | <text v-else-if="step.status === 'rejected'" |
| | | class="step-icon">â</text> |
| | | <text v-else |
| | | class="step-number">{{ index + 1 }}</text> |
| | | </view> |
| | | <view v-if="index < steps.length - 1" |
| | | class="step-line"></view> |
| | | </view> |
| | | <view class="step-content"> |
| | | <view class="step-info"> |
| | | <text class="step-title">{{ step.title }}</text> |
| | | <text class="step-approver">{{ approverLabel(step) }}</text> |
| | | <text v-if="step.approveTime" |
| | | class="step-time">{{ step.approveTime }}</text> |
| | | </view> |
| | | <view v-if="step.opinion" |
| | | class="step-opinion"> |
| | | <text class="opinion-label">å®¡æ¹æè§ï¼</text> |
| | | <text class="opinion-content">{{ step.opinion }}</text> |
| | | </view> |
| | | <!-- å¾
审èç¹ï¼ä¸ Web ä¸è´ï¼è¯¦æ
æ¨¡å¼æ¸²æç¦ç¨çæè§æ¡ --> |
| | | <view v-else-if="showPendingOpinion && step.isShen" |
| | | class="step-opinion step-opinion--pending"> |
| | | <text class="opinion-label">å®¡æ¹æè§ï¼</text> |
| | | <u-textarea :model-value="step.opinion" |
| | | disabled |
| | | height="60" |
| | | border="surround" |
| | | placeholder="å¾
审æ¹" /> |
| | | </view> |
| | | <!-- ç¾åå±ç¤º --> |
| | | <view v-if="step.urlTem" |
| | | class="step-sign"> |
| | | <text class="opinion-label">ç¾åï¼</text> |
| | | <image :src="step.urlTem" |
| | | mode="widthFix" |
| | | class="sign-image" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="!steps.length" |
| | | class="process-empty">ææ å®¡æ¹èç¹</view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | steps: { type: Array, default: () => [] }, |
| | | /** 详æ
模å¼ï¼å¾
审èç¹æ¸²æä¸ä¸ªç¦ç¨çæè§æ¡ï¼ä¸ Web ä¸è´ï¼ */ |
| | | showPendingOpinion: { type: Boolean, default: false }, |
| | | }); |
| | | |
| | | /** 审æ¹äºº-å·²åæ/已驳å/æªå®¡æ¹ï¼ä¸ Web å±ç¤ºä¸è´ï¼ */ |
| | | const approverLabel = step => |
| | | step?.statusText |
| | | ? `${step.approverName}-${step.statusText}` |
| | | : step?.approverName || ""; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .process-empty { |
| | | font-size: 14px; |
| | | color: #999; |
| | | text-align: center; |
| | | } |
| | | |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-line { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .step-dot { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | |
| | | .step-opinion--pending { |
| | | background: #fafbfc; |
| | | border-left-color: #d9d9d9; |
| | | } |
| | | |
| | | .step-sign { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .sign-image { |
| | | width: 180px; |
| | | border-radius: 6px; |
| | | border: 1px solid #eee; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** |
| | | * åå审æ¹ï¼å®¡æ¹èç¹ â æµç¨æ¥éª¤ï¼ä¸ Web approvalDia ä¸è´ï¼ |
| | | * @param {Array} list approveProcessDetails è¿åçèç¹æ°ç» |
| | | * @returns {Array<{ |
| | | * title: string, approverName: string, status: string, statusText: string, |
| | | * approveTime: string|null, opinion: string, urlTem: string, isShen: boolean |
| | | * }>} |
| | | */ |
| | | export function mapApprovalSteps(list) { |
| | | const rows = Array.isArray(list) ? list : []; |
| | | return rows.map((it, idx) => { |
| | | let status = "pending"; |
| | | if (it.approveNodeStatus === 1) status = "completed"; |
| | | else if (it.approveNodeStatus === 2) status = "rejected"; |
| | | else if (it.isShen) status = "current"; |
| | | |
| | | let statusText = "æªå®¡æ¹"; |
| | | if (it.approveNodeStatus === 2) statusText = "已驳å"; |
| | | else if (it.approveNodeStatus === 1) statusText = "å·²åæ"; |
| | | |
| | | return { |
| | | title: `第${idx + 1}æ¥å®¡æ¹`, |
| | | approverName: it.approveNodeUser || "æªç¥ç¨æ·", |
| | | status, |
| | | statusText, |
| | | approveTime: it.approveTime || null, |
| | | opinion: it.approveNodeReason || "", |
| | | urlTem: |
| | | it.urlTem || (it.url ? String(it.url).replace(/word/g, "img") : ""), |
| | | isShen: !!it.isShen, |
| | | }; |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!-- |
| | | ååå®¡æ¹ / å®¡æ¹æµç¨é
ç½® |
| | | æå®¡æ¹ç±»åï¼1~7ï¼ç»´æ¤å®¡æ¹äººèç¹ï¼æ¥å£ /approveProcessConfigNode |
| | | --> |
| | | <template> |
| | | <view class="approve-config-page"> |
| | | <PageHeader title="å®¡æ¹æµç¨é
ç½®" |
| | | @back="goBack" /> |
| | | |
| | | <view class="tabs-wrap"> |
| | | <up-tabs :list="tabList" |
| | | :current="activeTab" |
| | | line-color="#2979ff" |
| | | @click="onTabClick" /> |
| | | </view> |
| | | |
| | | <scroll-view class="config-scroll" |
| | | scroll-y |
| | | :show-scrollbar="false"> |
| | | <view class="config-tip"> |
| | | <text class="config-tip-title">{{ currentTypeLabel }}</text> |
| | | <text class="config-tip-desc"> |
| | | {{ approverList.length ? `å·²é
ç½® ${approverList.length} 个审æ¹äºº` : "æªé
置审æ¹äºº" }} |
| | | </text> |
| | | </view> |
| | | |
| | | <view v-if="loading" |
| | | class="loading-wrap"> |
| | | <up-loading-icon mode="circle" /> |
| | | <text class="loading-text">å è½½ä¸...</text> |
| | | </view> |
| | | |
| | | <view v-else-if="approverList.length" |
| | | class="node-list"> |
| | | <view v-for="(item, index) in approverList" |
| | | :key="index" |
| | | class="node-card"> |
| | | <view class="node-head"> |
| | | <view class="node-badge">{{ index + 1 }}</view> |
| | | <text class="node-level">{{ levelText(index) }}</text> |
| | | <view class="node-actions"> |
| | | <view class="icon-btn" |
| | | :class="{ 'is-disabled': index === 0 }" |
| | | @click="moveUp(index)"> |
| | | <up-icon name="arrow-up" |
| | | size="16" |
| | | color="#2979ff" /> |
| | | </view> |
| | | <view class="icon-btn" |
| | | :class="{ 'is-disabled': index === approverList.length - 1 }" |
| | | @click="moveDown(index)"> |
| | | <up-icon name="arrow-down" |
| | | size="16" |
| | | color="#2979ff" /> |
| | | </view> |
| | | <view class="icon-btn" |
| | | @click="removeNode(index)"> |
| | | <up-icon name="trash" |
| | | size="16" |
| | | color="#f56c6c" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="node-body" |
| | | @click="openUserPicker(index)"> |
| | | <text :class="['node-approver', item.approverName ? '' : 'is-placeholder']"> |
| | | {{ item.approverName || "è¯·éæ©å®¡æ¹äºº" }} |
| | | </text> |
| | | <up-icon name="arrow-right" |
| | | size="16" |
| | | color="#c0c4cc" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else |
| | | class="empty-wrap"> |
| | | <up-empty mode="data" |
| | | text="ææ å®¡æ¹äººé
ç½®" /> |
| | | </view> |
| | | |
| | | <view class="add-node" |
| | | @click="addNode"> |
| | | <up-icon name="plus" |
| | | size="16" |
| | | color="#2979ff" /> |
| | | <text class="add-node-text">æ°å¢å®¡æ¹äºº</text> |
| | | </view> |
| | | |
| | | <view class="bottom-space" /> |
| | | </scroll-view> |
| | | |
| | | <FooterButtons cancel-text="è¿å" |
| | | confirm-text="ä¿åé
ç½®" |
| | | :loading="saving" |
| | | @cancel="goBack" |
| | | @confirm="handleSave" /> |
| | | |
| | | <OaUserSearchPicker v-model:show="showUserPicker" |
| | | v-model="pickingUserId" |
| | | title="鿩审æ¹äºº" |
| | | :users="userList" |
| | | @select="onUserPicked" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import OaUserSearchPicker from "@/pages/oa/_components/OaUserSearchPicker.vue"; |
| | | import { userListNoPage } from "@/api/system/user"; |
| | | import { |
| | | addApproveProcessConfigNode, |
| | | getApproveProcessConfigNodeList, |
| | | } from "@/api/collaborativeApproval/approvalManagement"; |
| | | |
| | | /** ä¸ Web approvalManagement ä¸è´ */ |
| | | const APPROVE_TYPES = [ |
| | | { value: "1", label: "å
¬åºç®¡ç" }, |
| | | { value: "2", label: "请å管ç" }, |
| | | { value: "3", label: "åºå·®ç®¡ç" }, |
| | | { value: "4", label: "æ¥é管ç" }, |
| | | { value: "5", label: "éè´å®¡æ¹" }, |
| | | { value: "6", label: "æ¥ä»·å®¡æ¹" }, |
| | | { value: "7", label: "å货审æ¹" }, |
| | | ]; |
| | | |
| | | const activeTab = ref(0); |
| | | const approverList = ref([]); |
| | | const loading = ref(false); |
| | | const saving = ref(false); |
| | | const userList = ref([]); |
| | | |
| | | const showUserPicker = ref(false); |
| | | const pickingUserId = ref(""); |
| | | const pickingIndex = ref(-1); |
| | | |
| | | const tabList = computed(() => APPROVE_TYPES.map(t => ({ name: t.label }))); |
| | | const currentTypeLabel = computed( |
| | | () => APPROVE_TYPES[activeTab.value]?.label || "" |
| | | ); |
| | | const currentApproveType = computed( |
| | | () => Number(APPROVE_TYPES[activeTab.value]?.value ?? 1) |
| | | ); |
| | | |
| | | const levelText = index => `第${index + 1}级`; |
| | | |
| | | const unwrapNodeList = res => { |
| | | if (Array.isArray(res?.data)) return res.data; |
| | | if (Array.isArray(res?.rows)) return res.rows; |
| | | if (Array.isArray(res?.data?.records)) return res.data.records; |
| | | return []; |
| | | }; |
| | | |
| | | const unwrapUserList = res => { |
| | | if (Array.isArray(res?.data)) return res.data; |
| | | if (Array.isArray(res?.rows)) return res.rows; |
| | | return []; |
| | | }; |
| | | |
| | | const loadData = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getApproveProcessConfigNodeList(currentApproveType.value); |
| | | approverList.value = unwrapNodeList(res) |
| | | .map((item, index) => ({ |
| | | ...item, |
| | | sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1, |
| | | })) |
| | | .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); |
| | | } catch { |
| | | approverList.value = []; |
| | | uni.showToast({ title: "å 载审æ¹é
置失败", icon: "none" }); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const loadUserList = async () => { |
| | | try { |
| | | userList.value = unwrapUserList(await userListNoPage()); |
| | | } catch { |
| | | userList.value = []; |
| | | uni.showToast({ title: "å 载人åå表失败", icon: "none" }); |
| | | } |
| | | }; |
| | | |
| | | const onTabClick = item => { |
| | | activeTab.value = item?.index ?? 0; |
| | | loadData(); |
| | | }; |
| | | |
| | | const addNode = () => { |
| | | approverList.value.push({ |
| | | id: null, |
| | | approveType: currentApproveType.value, |
| | | approverId: null, |
| | | approverName: "", |
| | | sortOrder: approverList.value.length + 1, |
| | | }); |
| | | }; |
| | | |
| | | const removeNode = index => { |
| | | uni.showModal({ |
| | | title: "å é¤ç¡®è®¤", |
| | | content: "ç¡®å®å é¤è¯¥å®¡æ¹äººåï¼", |
| | | confirmText: "ç¡®å®å é¤", |
| | | confirmColor: "#f56c6c", |
| | | success: res => { |
| | | if (!res.confirm) return; |
| | | approverList.value.splice(index, 1); |
| | | approverList.value.forEach((item, idx) => { |
| | | item.sortOrder = idx + 1; |
| | | }); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const swapNode = (from, to) => { |
| | | const list = approverList.value; |
| | | const temp = list[from]; |
| | | list[from] = list[to]; |
| | | list[to] = temp; |
| | | list.forEach((item, idx) => { |
| | | item.sortOrder = idx + 1; |
| | | }); |
| | | }; |
| | | |
| | | const moveUp = index => { |
| | | if (index <= 0) return; |
| | | swapNode(index, index - 1); |
| | | }; |
| | | |
| | | const moveDown = index => { |
| | | if (index >= approverList.value.length - 1) return; |
| | | swapNode(index, index + 1); |
| | | }; |
| | | |
| | | const openUserPicker = index => { |
| | | pickingIndex.value = index; |
| | | pickingUserId.value = approverList.value[index]?.approverId ?? ""; |
| | | showUserPicker.value = true; |
| | | }; |
| | | |
| | | const onUserPicked = user => { |
| | | const idx = pickingIndex.value; |
| | | if (idx < 0 || !user) return; |
| | | approverList.value[idx].approverId = user.userId ?? user.id; |
| | | approverList.value[idx].approverName = |
| | | user.nickName || user.userName || ""; |
| | | }; |
| | | |
| | | const handleSave = async () => { |
| | | if (saving.value) return; |
| | | if (!approverList.value.length) { |
| | | uni.showToast({ title: "请è³å°é
ç½®ä¸ä¸ªå®¡æ¹äºº", icon: "none" }); |
| | | return; |
| | | } |
| | | if (approverList.value.some(item => !item.approverId)) { |
| | | uni.showToast({ title: "è¯·éæ©ææå®¡æ¹äºº", icon: "none" }); |
| | | return; |
| | | } |
| | | const ids = approverList.value.map(item => item.approverId); |
| | | if (new Set(ids).size !== ids.length) { |
| | | uni.showToast({ title: "审æ¹äººä¸è½éå¤", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | saving.value = true; |
| | | try { |
| | | await addApproveProcessConfigNode( |
| | | approverList.value.map((item, index) => ({ |
| | | approveType: currentApproveType.value, |
| | | nodeOrder: index + 1, |
| | | approverId: item.approverId, |
| | | approverName: item.approverName, |
| | | })) |
| | | ); |
| | | uni.showToast({ title: "ä¿åæå", icon: "success" }); |
| | | await loadData(); |
| | | } catch { |
| | | uni.showToast({ title: "ä¿å失败", icon: "none" }); |
| | | } finally { |
| | | saving.value = false; |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => uni.navigateBack(); |
| | | |
| | | onLoad(async () => { |
| | | await loadUserList(); |
| | | await loadData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | .approve-config-page { |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-height: 100vh; |
| | | background: #f0f3f8; |
| | | } |
| | | |
| | | .tabs-wrap { |
| | | background: #fff; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | padding: 0 8px; |
| | | } |
| | | |
| | | .config-scroll { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 10px 12px 0; |
| | | } |
| | | |
| | | .config-tip { |
| | | display: flex; |
| | | align-items: baseline; |
| | | justify-content: space-between; |
| | | padding: 2px 4px 10px; |
| | | } |
| | | |
| | | .config-tip-title { |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .config-tip-desc { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .loading-wrap { |
| | | padding: 48px 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .loading-text { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .node-card { |
| | | background: #fff; |
| | | border-radius: 10px; |
| | | padding: 12px; |
| | | margin-bottom: 10px; |
| | | box-shadow: 0 2px 12px rgba(31, 45, 61, 0.05); |
| | | } |
| | | |
| | | .node-head { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .node-badge { |
| | | width: 26px; |
| | | height: 26px; |
| | | border-radius: 8px; |
| | | background: #2979ff; |
| | | color: #fff; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .node-level { |
| | | flex: 1; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .node-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .icon-btn { |
| | | width: 30px; |
| | | height: 30px; |
| | | border-radius: 8px; |
| | | background: #f5f7fa; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | &.is-disabled { |
| | | opacity: 0.4; |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | |
| | | .node-body { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 12px; |
| | | background: #f7f9fc; |
| | | border: 1px solid #eef1f6; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .node-approver { |
| | | font-size: 15px; |
| | | color: #303133; |
| | | |
| | | &.is-placeholder { |
| | | color: #c0c4cc; |
| | | } |
| | | } |
| | | |
| | | .empty-wrap { |
| | | padding: 40px 20px 20px; |
| | | } |
| | | |
| | | .add-node { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 6px; |
| | | height: 44px; |
| | | background: #fff; |
| | | border: 1px dashed #c0c4cc; |
| | | border-radius: 10px; |
| | | |
| | | &:active { |
| | | opacity: 0.85; |
| | | } |
| | | } |
| | | |
| | | .add-node-text { |
| | | font-size: 14px; |
| | | color: #2979ff; |
| | | } |
| | | |
| | | .bottom-space { |
| | | height: calc(80px + env(safe-area-inset-bottom)); |
| | | } |
| | | </style> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- éè´/æ¥ä»·/å货审æ¹è¯¦æ
--> |
| | | <view v-if="isBusinessApproval" |
| | | class="business-detail-wrap"> |
| | | <ApprovalBusinessDetail :approve-type="approvalData.approveType || approveType" |
| | | :business-no="approvalData.approveReason" /> |
| | | </view> |
| | | <!-- å®¡æ¹æµç¨ --> |
| | | <view class="approval-process"> |
| | | <view class="process-header"> |
| | | <text class="process-title">å®¡æ¹æµç¨</text> |
| | | </view> |
| | | <view class="process-steps"> |
| | | <view v-for="(step, index) in approvalSteps" |
| | | :key="index" |
| | | class="process-step" |
| | | :class="{ |
| | | 'completed': step.status === 'completed', |
| | | 'current': step.status === 'current', |
| | | 'pending': step.status === 'pending', |
| | | 'rejected': step.status === 'rejected' |
| | | }"> |
| | | <view class="step-indicator"> |
| | | <view class="step-dot"> |
| | | <text v-if="step.status === 'completed'" |
| | | class="step-icon">â</text> |
| | | <text v-else-if="step.status === 'rejected'" |
| | | class="step-icon">â</text> |
| | | <text v-else |
| | | class="step-number">{{ index + 1 }}</text> |
| | | </view> |
| | | <view v-if="index < approvalSteps.length - 1" |
| | | class="step-line"></view> |
| | | </view> |
| | | <view class="step-content"> |
| | | <view class="step-info"> |
| | | <text class="step-title">{{ step.title }}</text> |
| | | <text class="step-approver">{{ step.approverName }}</text> |
| | | <text v-if="step.approveTime" |
| | | class="step-time">{{ step.approveTime }}</text> |
| | | </view> |
| | | <view v-if="step.opinion" |
| | | class="step-opinion"> |
| | | <text class="opinion-label">å®¡æ¹æè§ï¼</text> |
| | | <text class="opinion-content">{{ step.opinion }}</text> |
| | | </view> |
| | | <!-- ç¾åå±ç¤º --> |
| | | <view v-if="step.urlTem" |
| | | class="step-opinion" |
| | | style="margin-top:8px;"> |
| | | <text class="opinion-label">ç¾åï¼</text> |
| | | <image :src="step.urlTem" |
| | | mode="widthFix" |
| | | style="width:180px;border-radius:6px;border:1px solid #eee;" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <ApproveFlowSteps :steps="approvalSteps" /> |
| | | <!-- å®¡æ ¸æè§è¾å
¥ --> |
| | | <view v-if="canApprove" |
| | | class="approval-input"> |
| | |
| | | updateApproveNode, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import ApprovalBusinessDetail from "./_components/ApprovalBusinessDetail.vue"; |
| | | import ApproveFlowSteps from "./_components/ApproveFlowSteps.vue"; |
| | | import { mapApprovalSteps } from "./_utils/approveFlowSteps.js"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | |
| | | const approvalSteps = ref([]); |
| | | const approvalOpinion = ref(""); |
| | | const approveId = ref(""); |
| | | const approveType = ref(0); |
| | | |
| | | /** 5 éè´ / 6 æ¥ä»· / 7 åè´§ï¼éå±ç¤ºä¸å¡åæ®è¯¦æ
*/ |
| | | const isBusinessApproval = computed(() => |
| | | [5, 6, 7].includes( |
| | | Number(approvalData.value.approveType || approveType.value) |
| | | ) |
| | | ); |
| | | |
| | | // ä»è¯¦æ
æ¥å£åæ®µå¯¹é½ canApproveï¼ä»
彿 isShen çèç¹æ¶å¯å®¡æ¹ |
| | | const canApprove = computed(() => { |
| | |
| | | |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync("approveId"); |
| | | approveType.value = Number(uni.getStorageSync("approveType")) || 0; |
| | | if (approveId.value) { |
| | | loadApprovalData(); |
| | | } |
| | |
| | | const list = Array.isArray(res.data) ? res.data : []; |
| | | // ä¿ååå§èç¹æ°æ®ä¾æäº¤ä½¿ç¨ |
| | | activities.value = list; |
| | | |
| | | approvalSteps.value = list.map((it, idx) => { |
| | | // èç¹ç¶ææ å°ï¼1=éè¿ï¼2=ä¸éè¿ï¼å¦åçæ¯å¦å½å(isShen)ï¼åé»è®¤ä¸ºå¾
å¤ç |
| | | let status = "pending"; |
| | | if (it.approveNodeStatus === 1) status = "completed"; |
| | | else if (it.approveNodeStatus === 2) status = "rejected"; |
| | | else if (it.isShen) status = "current"; |
| | | return { |
| | | title: `第${idx + 1}æ¥å®¡æ¹`, |
| | | approverName: it.approveNodeUser || "æªç¥ç¨æ·", |
| | | status, |
| | | approveTime: it.approveTime || null, |
| | | opinion: it.approveNodeReason || "", |
| | | urlTem: it.urlTem || "", |
| | | isShen: !!it.isShen, |
| | | }; |
| | | }); |
| | | approvalSteps.value = mapApprovalSteps(list); |
| | | }); |
| | | }; |
| | | |
| | |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .business-detail-wrap { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | padding: 1px 0; |
| | | } |
| | | |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-line { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .step-dot { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .approval-input { |
| | |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | } |
| | | } |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | |
| | | :rules="rules" |
| | | :model="form" |
| | | label-width="140rpx"> |
| | | <template v-if="operationType !== 'detail'"> |
| | | <u-form-item prop="approveReason" |
| | | label="æµç¨ç¼å·"> |
| | | <u-input v-model="form.approveId" |
| | |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | :disabled="isDetailMode" |
| | | :placeholder="approveType === 5 ? '请è¾å
¥éè´äºç±' : '请è¾å
¥ç³è¯·äºç±'" |
| | | show-word-limit /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveDeptName" |
| | | label="ç³è¯·é¨é¨" |
| | | required> |
| | | <!-- <u-input v-model="form.approveDeptName" |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" /> --> |
| | | <u-input v-model="form.approveDeptName" |
| | | readonly |
| | | :disabled="isDetailMode" |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" |
| | | @click="showPicker = true" /> |
| | | @click="isDetailMode || (showPicker = true)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | @click="isDetailMode || (showPicker = true)"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <!-- <u-form-item prop="approveUser" |
| | |
| | | </template> |
| | | </u-form-item> --> |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approveType === 2"> |
| | | <u-form-item prop="startDate" |
| | | <u-form-item v-if="approveType === 2" |
| | | prop="startDate" |
| | | label="å¼å§æ¶é´" |
| | | required> |
| | | <u-input v-model="form.startDate" |
| | | readonly |
| | | :disabled="isDetailMode" |
| | | placeholder="请åå¼å§æ¶é´" |
| | | @click="showStartDatePicker" /> |
| | | <template #right> |
| | |
| | | @click="showStartDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item prop="endDate" |
| | | <u-form-item v-if="approveType === 2" |
| | | prop="endDate" |
| | | label="ç»ææ¶é´" |
| | | required> |
| | | <u-input v-model="form.endDate" |
| | | readonly |
| | | :disabled="isDetailMode" |
| | | placeholder="请åç»ææ¶é´" |
| | | @click="showEndDatePicker" /> |
| | | <template #right> |
| | |
| | | @click="showEndDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | </template> |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="startDateTime" |
| | | label="åºå·®å¼å§æ¶é´" |
| | | required> |
| | | <u-input v-model="form.startDateTime" |
| | | readonly |
| | | :disabled="isDetailMode" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | @click="showStartDateTimePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showStartDateTimePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="endDateTime" |
| | | label="åºå·®ç»ææ¶é´" |
| | | required> |
| | | <u-input v-model="form.endDateTime" |
| | | readonly |
| | | :disabled="isDetailMode" |
| | | placeholder="è¯·éæ©ç»ææ¶é´" |
| | | @click="showEndDateTimePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showEndDateTimePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="location" |
| | | label="åºå·®å°ç¹" |
| | | required> |
| | | <u-input v-model="form.location" |
| | | :disabled="isDetailMode" |
| | | placeholder="请è¾å
¥åºå·®å°ç¹" |
| | | clearable /> |
| | | </u-form-item> |
| | |
| | | required> |
| | | <u-input v-model="form.price" |
| | | type="number" |
| | | :disabled="isDetailMode" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | clearable /> |
| | | </u-form-item> |
| | | </template> |
| | | <!-- æ¥ä»·å®¡æ¹è¯¦æ
--> |
| | | <view v-if="isQuotationApproval" |
| | | style="margin: 20rpx 0;"> |
| | | <u-divider text="æ¥ä»·è¯¦æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="quotationLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentQuotation || !currentQuotation.quotationNo" |
| | | style="padding: 40rpx; text-align: center; color: #999;"> |
| | | æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="æ¥ä»·åå·" |
| | | :value="currentQuotation.quotationNo"></u-cell> |
| | | <u-cell title="客æ·åç§°" |
| | | :value="currentQuotation.customer"></u-cell> |
| | | <u-cell title="ä¸å¡å" |
| | | :value="currentQuotation.salesperson"></u-cell> |
| | | <u-cell title="æ¥ä»·æ¥æ" |
| | | :value="currentQuotation.quotationDate"></u-cell> |
| | | <u-cell title="æææè³" |
| | | :value="currentQuotation.validDate"></u-cell> |
| | | <u-cell title="仿¬¾æ¹å¼" |
| | | :value="currentQuotation.paymentMethod"></u-cell> |
| | | <u-cell title="æ¥ä»·æ»é¢"> |
| | | <template #value> |
| | | <text style="font-size: 32rpx; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | </text> |
| | | </template> |
| | | </u-cell> |
| | | </u-cell-group> |
| | | <view style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">产åæç»</view> |
| | | <view v-for="(item, index) in (currentQuotation.products || [])" |
| | | :key="index" |
| | | style="background: #f8f8f8; border-radius: 8rpx; padding: 20rpx; margin-bottom: 10rpx;"> |
| | | <view style="display: flex; justify-content: space-between;"> |
| | | <text style="font-weight: bold;">{{ item.product }}</text> |
| | | <text style="color: #e6a23c;">Â¥{{ Number(item.unitPrice ?? 0).toFixed(2) }}</text> |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #666; margin-top: 10rpx;"> |
| | | è§æ ¼: {{ item.specification }} | åä½: {{ item.unit }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="currentQuotation.remark" |
| | | style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold;">夿³¨</view> |
| | | <view style="font-size: 26rpx; color: #666; margin-top: 10rpx;">{{ currentQuotation.remark }}</view> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | <!-- éè´å®¡æ¹è¯¦æ
--> |
| | | <view v-if="isPurchaseApproval" |
| | | style="margin: 20rpx 0;"> |
| | | <u-divider text="éè´è¯¦æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="purchaseLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" |
| | | style="padding: 40rpx; text-align: center; color: #999;"> |
| | | æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="éè´ååå·" |
| | | :value="currentPurchase.purchaseContractNumber"></u-cell> |
| | | <u-cell title="ä¾åºååç§°" |
| | | :value="currentPurchase.supplierName"></u-cell> |
| | | <u-cell title="项ç®åç§°" |
| | | :value="currentPurchase.projectName"></u-cell> |
| | | <u-cell title="éå®ååå·" |
| | | :value="currentPurchase.salesContractNo"></u-cell> |
| | | <u-cell title="ç¾è®¢æ¥æ" |
| | | :value="currentPurchase.executionDate"></u-cell> |
| | | <u-cell title="å½å
¥æ¥æ" |
| | | :value="currentPurchase.entryDate"></u-cell> |
| | | <u-cell title="仿¬¾æ¹å¼" |
| | | :value="currentPurchase.paymentMethod"></u-cell> |
| | | <u-cell title="ååéé¢"> |
| | | <template #value> |
| | | <text style="font-size: 32rpx; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | </text> |
| | | </template> |
| | | </u-cell> |
| | | </u-cell-group> |
| | | <view style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">产åæç»</view> |
| | | <view v-for="(item, index) in (currentPurchase.productData || [])" |
| | | :key="index" |
| | | style="background: #f8f8f8; border-radius: 8rpx; padding: 20rpx; margin-bottom: 10rpx;"> |
| | | <view style="display: flex; justify-content: space-between;"> |
| | | <text style="font-weight: bold;">{{ item.productCategory }}</text> |
| | | <text style="color: #e6a23c;">Â¥{{ Number(item.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</text> |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #666; margin-top: 10rpx;"> |
| | | è§æ ¼: {{ item.specificationModel }} | æ°é: {{ item.quantity }} {{ item.unit }} |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #999; margin-top: 4rpx;"> |
| | | å«ç¨åä»·: Â¥{{ Number(item.taxInclusiveUnitPrice ?? 0).toFixed(2) }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | <!-- å货审æ¹è¯¦æ
--> |
| | | <view v-if="isDeliveryApproval" |
| | | style="margin: 20rpx 0;"> |
| | | <u-divider text="å货详æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="deliveryLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentDelivery || !currentDelivery.shippingInfo" |
| | | style="padding: 40rpx; text-align: center; color: #999;"> |
| | | æªæ¥è¯¢å°å¯¹åºå货详æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="éå®è®¢å" |
| | | :value="currentDelivery.shippingInfo.salesContractNo || '--'"></u-cell> |
| | | <u-cell title="å货订åå·" |
| | | :value="currentDelivery.shippingInfo.shippingNo || '--'"></u-cell> |
| | | <u-cell title="客æ·åç§°" |
| | | :value="currentDelivery.shippingInfo.customerName || '--'"></u-cell> |
| | | <u-cell title="åè´§ç±»å" |
| | | :value="currentDelivery.shippingInfo.type || '--'"></u-cell> |
| | | <u-cell title="åè´§æ¥æ" |
| | | :value="currentDelivery.shippingInfo.shippingDate || '--'"></u-cell> |
| | | <u-cell title="å®¡æ ¸ç¶æ" |
| | | :value="currentDelivery.shippingInfo.status || '--'"></u-cell> |
| | | <u-cell title="å货车çå·" |
| | | :value="currentDelivery.shippingInfo.shippingCarNumber || '--'"></u-cell> |
| | | <u-cell title="å¿«éå
¬å¸" |
| | | :value="currentDelivery.shippingInfo.expressCompany || '--'"></u-cell> |
| | | <u-cell title="å¿«éåå·" |
| | | :value="currentDelivery.shippingInfo.expressNumber || '--'"></u-cell> |
| | | </u-cell-group> |
| | | <view style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">产åæç»</view> |
| | | <view v-for="(item, index) in deliveryProductList" |
| | | :key="index" |
| | | style="background: #f8f8f8; border-radius: 8rpx; padding: 20rpx; margin-bottom: 10rpx;"> |
| | | <view style="display: flex; justify-content: space-between;"> |
| | | <text style="font-weight: bold;">{{ item.productName }}</text> |
| | | <text style="color: #2979ff;">æ°é: {{ item.deliveryQuantity }}</text> |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #666; margin-top: 10rpx;"> |
| | | è§æ ¼: {{ item.specificationModel }} |
| | | </view> |
| | | <view v-if="item.batchNo" |
| | | style="font-size: 24rpx; color: #999; margin-top: 4rpx;"> |
| | | æ¹å·: {{ item.batchNo }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="currentDelivery.shippingInfo.storageBlobVOs && currentDelivery.shippingInfo.storageBlobVOs.length" |
| | | style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">åè´§å¾ç</view> |
| | | <CommonUpload :model-value="currentDelivery.shippingInfo.storageBlobVOs" |
| | | disabled /> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | <!-- éè´/æ¥ä»·/å货审æ¹è¯¦æ
--> |
| | | <ApprovalBusinessDetail :approve-type="approveType" |
| | | :business-no="form.approveReason" /> |
| | | <!-- å®¡æ¹æµç¨ï¼ä»
详æ
模å¼å±ç¤ºï¼ä¸ Web ä¸è´ï¼ --> |
| | | <ApproveFlowSteps v-if="isDetailMode" |
| | | :steps="approvalSteps" |
| | | show-pending-opinion /> |
| | | <u-form-item v-if="operationType !== 'detail'" |
| | | label="å¾çéä»¶" |
| | | prop="storageBlobDTOS" |
| | |
| | | </u-form-item> |
| | | </u-form> |
| | | <!-- éæ©å¨å¼¹çª --> |
| | | <template v-if="operationType !== 'detail'"> |
| | | <view v-if="operationType !== 'detail'"> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="éæ©é¨é¨" |
| | |
| | | @cancel="showEndDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | </template> |
| | | <!-- åºå·®å¼å§æ¶é´éæ©å¨ --> |
| | | <up-popup :show="showStartDateTime" |
| | | mode="bottom" |
| | | @close="showStartDateTime = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="startDateTimeValue" |
| | | @confirm="onStartDateTimeConfirm" |
| | | @cancel="showStartDateTime = false" |
| | | mode="datetime" /> |
| | | </up-popup> |
| | | <!-- åºå·®ç»ææ¶é´éæ©å¨ --> |
| | | <up-popup :show="showEndDateTime" |
| | | mode="bottom" |
| | | @close="showEndDateTime = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="endDateTimeValue" |
| | | @confirm="onEndDateTimeConfirm" |
| | | @cancel="showEndDateTime = false" |
| | | mode="datetime" /> |
| | | </up-popup> |
| | | </view> |
| | | <!-- åºé¨æé® --> |
| | | <view class="footer-btns" |
| | | v-if="operationType !== 'detail'"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | onUnmounted, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | watch, |
| | | } from "vue"; |
| | | import { ref, onMounted, onUnmounted, reactive, toRefs, computed } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import ApprovalBusinessDetail from "./_components/ApprovalBusinessDetail.vue"; |
| | | import ApproveFlowSteps from "./_components/ApproveFlowSteps.vue"; |
| | | import { mapApprovalSteps } from "./_utils/approveFlowSteps.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import { formatDateToYMD, parseTime } from "@/utils/ruoyi"; |
| | | import { |
| | | getDept, |
| | | approveProcessGetInfo, |
| | | approveProcessAdd, |
| | | approveProcessUpdate, |
| | | getDeliveryDetailByShippingNo, |
| | | approveProcessDetails, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | |
| | | storageBlobDTOS: [], |
| | | startDate: "", |
| | | endDate: "", |
| | | startDateTime: "", |
| | | endDateTime: "", |
| | | location: "", |
| | | price: "", |
| | | }, |
| | |
| | | endDate: [ |
| | | { required: false, message: "è¯·éæ©ç»ææ¶é´", trigger: "change" }, |
| | | ], |
| | | startDateTime: [ |
| | | { required: false, message: "è¯·éæ©åºå·®å¼å§æ¶é´", trigger: "change" }, |
| | | ], |
| | | endDateTime: [ |
| | | { required: false, message: "è¯·éæ©åºå·®ç»ææ¶é´", trigger: "change" }, |
| | | ], |
| | | location: [{ required: false, message: "请è¾å
¥åºå·®å°ç¹", trigger: "blur" }], |
| | | price: [{ required: false, message: "请è¾å
¥æ¥ééé¢", trigger: "blur" }], |
| | | }, |
| | |
| | | const showPicker = ref(false); |
| | | const productOptions = ref([]); |
| | | const operationType = ref(""); |
| | | const isDetailMode = computed(() => operationType.value === "detail"); |
| | | |
| | | /** å®¡æ¹æµç¨èç¹ï¼ä»
详æ
模å¼å±ç¤ºï¼ */ |
| | | const approvalSteps = ref([]); |
| | | |
| | | const loadApprovalSteps = approveIdValue => { |
| | | if (!approveIdValue) return; |
| | | approveProcessDetails(approveIdValue) |
| | | .then(res => { |
| | | approvalSteps.value = mapApprovalSteps(res?.data); |
| | | }) |
| | | .catch(() => { |
| | | approvalSteps.value = []; |
| | | }); |
| | | }; |
| | | const currentApproveStatus = ref(""); |
| | | const formRef = ref(null); |
| | | const message = ref(""); |
| | |
| | | const startDateValue = ref(Date.now()); |
| | | const showEndDate = ref(false); |
| | | const endDateValue = ref(Date.now()); |
| | | const showStartDateTime = ref(false); |
| | | const startDateTimeValue = ref(Date.now()); |
| | | const showEndDateTime = ref(false); |
| | | const endDateTimeValue = ref(Date.now()); |
| | | const userStore = useUserStore(); |
| | | const approveType = ref(0); |
| | | const isInitialLoading = ref(false); |
| | | |
| | | const quotationLoading = ref(false); |
| | | const currentQuotation = ref({}); |
| | | const purchaseLoading = ref(false); |
| | | const currentPurchase = ref({}); |
| | | const deliveryLoading = ref(false); |
| | | const currentDelivery = ref({}); |
| | | const deliveryProductList = ref([]); |
| | | |
| | | const isQuotationApproval = computed(() => Number(approveType.value) === 6); |
| | | const isPurchaseApproval = computed(() => Number(approveType.value) === 5); |
| | | const isDeliveryApproval = computed(() => Number(approveType.value) === 7); |
| | | |
| | | const getProductOptions = () => { |
| | | getDept().then(res => { |
| | |
| | | |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | if (isDetailMode.value) return; |
| | | showDate.value = true; |
| | | }; |
| | | |
| | |
| | | |
| | | // æ¾ç¤ºè¯·åå¼å§æ¶é´éæ©å¨ |
| | | const showStartDatePicker = () => { |
| | | if (isDetailMode.value) return; |
| | | showStartDate.value = true; |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | const showEndDatePicker = () => { |
| | | if (isDetailMode.value) return; |
| | | showEndDate.value = true; |
| | | }; |
| | | |
| | |
| | | showEndDate.value = false; |
| | | }; |
| | | |
| | | const fetchDetailData = async row => { |
| | | // æ¥ä»·å®¡æ¹ |
| | | if (isQuotationApproval.value) { |
| | | const quotationNo = row?.approveReason; |
| | | if (quotationNo) { |
| | | quotationLoading.value = true; |
| | | getQuotationList({ quotationNo }) |
| | | .then(res => { |
| | | const records = res?.data?.records || []; |
| | | currentQuotation.value = records[0] || {}; |
| | | }) |
| | | .finally(() => { |
| | | quotationLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // éè´å®¡æ¹ |
| | | if (isPurchaseApproval.value) { |
| | | const purchaseContractNumber = row?.approveReason; |
| | | if (purchaseContractNumber) { |
| | | purchaseLoading.value = true; |
| | | getPurchaseByCode({ purchaseContractNumber }) |
| | | .then(res => { |
| | | currentPurchase.value = res; |
| | | }) |
| | | .catch(err => { |
| | | console.error("æ¥è¯¢éè´è¯¦æ
失败:", err); |
| | | }) |
| | | .finally(() => { |
| | | purchaseLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // åè´§å®¡æ¹ |
| | | if (isDeliveryApproval.value) { |
| | | const deliveryNo = row?.approveReason; |
| | | if (deliveryNo) { |
| | | deliveryLoading.value = true; |
| | | currentDelivery.value = {}; |
| | | deliveryProductList.value = []; |
| | | getDeliveryDetailByShippingNo({ shippingNo: deliveryNo }) |
| | | .then(res => { |
| | | const detailData = res?.data || res || {}; |
| | | currentDelivery.value = detailData; |
| | | deliveryProductList.value = |
| | | detailData.shippingProductDetailDtoList || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("æ¥è¯¢å货详æ
失败:", err); |
| | | }) |
| | | .finally(() => { |
| | | deliveryLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | // æ¾ç¤ºåºå·®å¼å§æ¶é´éæ©å¨ï¼YYYY-MM-DD HH:mmï¼ä¸ Web ä¸è´ï¼ |
| | | const showStartDateTimePicker = () => { |
| | | if (isDetailMode.value) return; |
| | | startDateTimeValue.value = parseTime( |
| | | form.value.startDateTime, |
| | | "{y}-{m}-{d} {h}:{i}" |
| | | ) |
| | | ? new Date(form.value.startDateTime).getTime() |
| | | : Date.now(); |
| | | showStartDateTime.value = true; |
| | | }; |
| | | |
| | | // çå¬å®¡æ¹äºç±ååï¼å¦ææ¯ç¹å®å®¡æ¹ç±»ååå°è¯è·å详æ
|
| | | watch( |
| | | () => form.value.approveReason, |
| | | newVal => { |
| | | if (isInitialLoading.value) return; |
| | | if ( |
| | | newVal && |
| | | (isQuotationApproval.value || |
| | | isPurchaseApproval.value || |
| | | isDeliveryApproval.value) |
| | | ) { |
| | | // å»¶è¿ä¸ä¼å请æ±ï¼é¿å
è¾å
¥è¿ç¨ä¸é¢ç¹è§¦å |
| | | debounceFetchDetail(); |
| | | } |
| | | } |
| | | ); |
| | | const onStartDateTimeConfirm = e => { |
| | | form.value.startDateTime = parseTime(e.value, "{y}-{m}-{d} {h}:{i}"); |
| | | showStartDateTime.value = false; |
| | | }; |
| | | |
| | | let timer = null; |
| | | const debounceFetchDetail = () => { |
| | | if (timer) clearTimeout(timer); |
| | | timer = setTimeout(() => { |
| | | fetchDetailData(form.value); |
| | | }, 800); |
| | | // æ¾ç¤ºåºå·®ç»ææ¶é´éæ©å¨ |
| | | const showEndDateTimePicker = () => { |
| | | if (isDetailMode.value) return; |
| | | endDateTimeValue.value = parseTime( |
| | | form.value.endDateTime, |
| | | "{y}-{m}-{d} {h}:{i}" |
| | | ) |
| | | ? new Date(form.value.endDateTime).getTime() |
| | | : Date.now(); |
| | | showEndDateTime.value = true; |
| | | }; |
| | | |
| | | const onEndDateTimeConfirm = e => { |
| | | form.value.endDateTime = parseTime(e.value, "{y}-{m}-{d} {h}:{i}"); |
| | | showEndDateTime.value = false; |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | |
| | | const row = JSON.parse(storedData); |
| | | currentApproveStatus.value = row.approveStatus; |
| | | |
| | | isInitialLoading.value = true; |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }) |
| | | .then(res => { |
| | | // 详æ
模å¼ï¼æåå®¡æ¹æµç¨èç¹ |
| | | if (operationType.value === "detail") { |
| | | loadApprovalSteps(row.approveId); |
| | | } |
| | | |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then( |
| | | res => { |
| | | form.value = { ...res.data }; |
| | | // 设置å¾çå表æ¾ç¤º |
| | | const fileData = |
| | |
| | | if (fileData.length > 0) { |
| | | form.value.storageBlobDTOS = fileData; |
| | | } |
| | | // è·åé¢å¤è¯¦æ
|
| | | fetchDetailData(res.data); |
| | | }) |
| | | .finally(() => { |
| | | // å»¶è¿ä¸ä¼éç½®ï¼ç¡®ä¿ watch ä¸ä¼è¢«è§¦å |
| | | setTimeout(() => { |
| | | isInitialLoading.value = false; |
| | | }, 100); |
| | | }); |
| | | } |
| | | ); |
| | | } |
| | | } |
| | | } catch (error) { |
| | |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader :title="pageTitle" |
| | | @back="goBack" /> |
| | | <!-- 审æ¹ç±»å忢ï¼ä»èåç´æ¥è¿å
¥æ¶å¯åæ¢ï¼ç» index1~8 è¿å
¥æ¶ä¸å±ç¤ºï¼ --> |
| | | <view v-if="showTypeTabs" |
| | | class="type-tabs-wrap"> |
| | | <up-tabs :list="typeTabList" |
| | | :current="activeTypeIndex" |
| | | line-color="#ed8d05" |
| | | @click="onTypeTabClick" /> |
| | | </view> |
| | | <!-- æç´¢åçéåºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | |
| | | v-model="searchForm.approveId" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-chip" |
| | | :class="{ 'filter-chip--active': searchForm.approveStatus !== '' }" |
| | | @click="openStatusSheet"> |
| | | <text class="filter-chip-text">{{ approveStatusText }}</text> |
| | | <up-icon name="arrow-down" |
| | | size="14" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | <view class="search-button" |
| | | @click="getList"> |
| | | <up-icon name="search" |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-action-sheet :show="showStatusSheet" |
| | | title="审æ¹ç¶æ" |
| | | :actions="statusActions" |
| | | @select="onSelectStatus" |
| | | @close="showStatusSheet = false" /> |
| | | <!-- 审æ¹å表 --> |
| | | <view class="ledger-list" |
| | | v-if="ledgerList.length > 0"> |
| | |
| | | ç¼è¾ |
| | | </u-button> |
| | | <u-button type="info" |
| | | v-if="item.approveType == 5 || item.approveType == 6 || item.approveType == 7" |
| | | size="small" |
| | | class="action-btn detail" |
| | | @click="handleDetailClick(item)"> |
| | |
| | | </view> |
| | | <!-- æµ®å¨æä½æé® --> |
| | | <view class="fab-button" |
| | | v-if="props.approveType != 5 && props.approveType != 6 && props.approveType != 7" |
| | | v-if="activeApproveType != 5 && activeApproveType != 6 && activeApproveType != 7" |
| | | @click="handleAdd"> |
| | | <up-icon name="plus" |
| | | size="24" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, toRefs, reactive } from "vue"; |
| | | import { ref, toRefs, reactive, computed } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { approveProcessListPage } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import { onLoad, onShow } from "@dcloudio/uni-app"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | // æ¥æ¶ç¶ç»ä»¶ä¼ éç approveType åæ° |
| | | /** 审æ¹ç¶æçéï¼ä¸ Web approvalProcess å表ä¸è´ï¼ */ |
| | | const APPROVE_STATUS_OPTIONS = [ |
| | | { value: "", label: "å
¨é¨ç¶æ" }, |
| | | { value: 0, label: "å¾
å®¡æ ¸" }, |
| | | { value: 1, label: "å®¡æ ¸ä¸" }, |
| | | { value: 2, label: "å®¡æ ¸å®æ" }, |
| | | { value: 3, label: "å®¡æ ¸æªéè¿" }, |
| | | { value: 4, label: "已鿰æäº¤" }, |
| | | ]; |
| | | |
| | | // æ¥æ¶ç¶ç»ä»¶ä¼ éç approveType åæ°ï¼index1~8 åºå®ç±»åï¼0 è¡¨ç¤ºæªæå®ï¼ |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: Number, |
| | |
| | | }, |
| | | }); |
| | | |
| | | // æ å° approveType å°å¯¹åºç页颿 é¢ |
| | | const getPageTitle = type => { |
| | | const titleMap = { |
| | | // 审æ¹ç±»åæ é¢ |
| | | const APPROVE_TYPE_LABELS = { |
| | | 1: "å
¬åºç®¡ç", |
| | | 2: "请å管ç", |
| | | 3: "åºå·®ç®¡ç", |
| | | 4: "æ¥é管ç", |
| | | 5: "éè´ç®¡ç", |
| | | 6: "æ¥ä»·ç®¡ç", |
| | | 5: "éè´å®¡æ¹", |
| | | 6: "æ¥ä»·å®¡æ¹", |
| | | 7: "å货审æ¹", |
| | | 8: "å±é©ä½ä¸å®¡æ¹", |
| | | }; |
| | | return titleMap[type] || "审æ¹ç®¡ç"; |
| | | |
| | | /** å¯åæ¢çåå审æ¹ç±»åï¼ä¸ Web approvalProcess Tab ä¸è´ï¼ */ |
| | | const TYPE_TAB_VALUES = [1, 2, 3, 4, 5, 6, 7]; |
| | | |
| | | /** ç¶çº§ä¼ å
¥çåºå®ç±»åï¼0 è¡¨ç¤ºæªæå®ï¼å¯ç±é¡µå
Tab 忢 */ |
| | | const fixedApproveType = computed(() => Number(props.approveType) || 0); |
| | | const activeApproveType = ref(fixedApproveType.value || 1); |
| | | |
| | | const showTypeTabs = computed(() => fixedApproveType.value === 0); |
| | | |
| | | const typeTabList = computed(() => |
| | | TYPE_TAB_VALUES.map(value => ({ name: APPROVE_TYPE_LABELS[value] })) |
| | | ); |
| | | |
| | | const activeTypeIndex = computed(() => { |
| | | const idx = TYPE_TAB_VALUES.indexOf(activeApproveType.value); |
| | | return idx >= 0 ? idx : 0; |
| | | }); |
| | | |
| | | const pageTitle = computed( |
| | | () => APPROVE_TYPE_LABELS[activeApproveType.value] || "åå审æ¹" |
| | | ); |
| | | |
| | | const onTypeTabClick = item => { |
| | | activeApproveType.value = TYPE_TAB_VALUES[item?.index ?? 0] || 1; |
| | | searchForm.value.approveId = ""; |
| | | searchForm.value.approveStatus = ""; |
| | | getList(); |
| | | }; |
| | | |
| | | const pageTitle = getPageTitle(props.approveType); |
| | | /** å表è¡èªå¸¦ approveTypeï¼ç¼ºå¤±æ¶åéå½å Tab */ |
| | | const currentItemApproveType = item => |
| | | Number(item?.approveType) || activeApproveType.value; |
| | | |
| | | const userStore = useUserStore(); |
| | | // æ°æ® |
| | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | approveId: "", |
| | | approveStatus: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const showStatusSheet = ref(false); |
| | | |
| | | const statusActions = computed(() => |
| | | APPROVE_STATUS_OPTIONS.map(opt => ({ name: opt.label, value: opt.value })) |
| | | ); |
| | | |
| | | const approveStatusText = computed(() => { |
| | | const hit = APPROVE_STATUS_OPTIONS.find( |
| | | opt => String(opt.value) === String(searchForm.value.approveStatus) |
| | | ); |
| | | return hit?.label || "å
¨é¨ç¶æ"; |
| | | }); |
| | | |
| | | const openStatusSheet = () => { |
| | | showStatusSheet.value = true; |
| | | }; |
| | | |
| | | const onSelectStatus = action => { |
| | | searchForm.value.approveStatus = action?.value ?? ""; |
| | | showStatusSheet.value = false; |
| | | getList(); |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | |
| | | }; |
| | | approveProcessListPage({ |
| | | ...page, |
| | | approveType: props.approveType, |
| | | approveType: activeApproveType.value, |
| | | ...searchForm.value, |
| | | }) |
| | | .then(res => { |
| | |
| | | uni.setStorageSync("invoiceLedgerEditRow", JSON.stringify(item)); |
| | | uni.setStorageSync("operationType", "edit"); |
| | | uni.setStorageSync("approveId", item.approveId); |
| | | uni.setStorageSync("approveType", props.approveType); |
| | | uni.setStorageSync("approveType", currentItemApproveType(item)); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/detail", |
| | | }); |
| | |
| | | uni.setStorageSync("invoiceLedgerEditRow", JSON.stringify(item)); |
| | | uni.setStorageSync("operationType", "detail"); |
| | | uni.setStorageSync("approveId", item.approveId); |
| | | uni.setStorageSync("approveType", props.approveType); |
| | | uni.setStorageSync("approveType", currentItemApproveType(item)); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/detail", |
| | | }); |
| | |
| | | // æ·»å æ°è®°å½ |
| | | const handleAdd = () => { |
| | | uni.setStorageSync("operationType", "add"); |
| | | uni.setStorageSync("approveType", props.approveType); |
| | | uni.setStorageSync("approveType", activeApproveType.value); |
| | | uni.navigateTo({ |
| | | url: `/pages/cooperativeOffice/collaborativeApproval/detail?approveType=${props.approveType}`, |
| | | url: `/pages/cooperativeOffice/collaborativeApproval/detail?approveType=${activeApproveType.value}`, |
| | | }); |
| | | }; |
| | | // ç¹å»å®¡æ ¸ |
| | | const approve = item => { |
| | | const type = currentItemApproveType(item); |
| | | uni.setStorageSync("approveId", item.approveId); |
| | | uni.setStorageSync("approveType", props.approveType); |
| | | uni.setStorageSync("approveType", type); |
| | | uni.navigateTo({ |
| | | url: |
| | | "/pages/cooperativeOffice/collaborativeApproval/approve?approveType=" + |
| | | props.approveType, |
| | | type, |
| | | }); |
| | | }; |
| | | |
| | | onLoad(options => { |
| | | // index1~8 ä¼ åºå®ç±»åï¼èåç´æ¥è¿å
¥æ¶å¯ç¨ ?approveType= æå®ï¼é»è®¤å
¬åºç®¡ç |
| | | activeApproveType.value = |
| | | Number(options?.approveType) || fixedApproveType.value || 1; |
| | | // è§£æapproveId |
| | | if (options.approveId) { |
| | | searchForm.value.approveId = options.approveId; |
| | |
| | | margin: 0 !important; |
| | | } |
| | | |
| | | // 审æ¹ç±»å忢 |
| | | .type-tabs-wrap { |
| | | background: #fff; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | padding: 0 8px; |
| | | } |
| | | |
| | | // ææ¡£å¾æ æ ·å¼ - è¦çå
Œ
±æ ·å¼ä¸çèæ¯è² |
| | | .document-icon { |
| | | background: #ed8d05; |
| | | } |
| | | |
| | | // ç¶æçé chip |
| | | .filter-chip { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | flex-shrink: 0; |
| | | margin-left: 8px; |
| | | padding: 6px 10px; |
| | | background: #fff; |
| | | border: 1px solid #e5e7eb; |
| | | border-radius: 16px; |
| | | |
| | | &--active { |
| | | border-color: #2979ff; |
| | | background: #f0f6ff; |
| | | } |
| | | } |
| | | |
| | | .filter-chip-text { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | max-width: 88px; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .filter-chip--active .filter-chip-text { |
| | | color: #2979ff; |
| | | } |
| | | |
| | | // æµ®å¨æé®æ ·å¼ - è¦çå
Œ
±æ ·å¼ä¸çèæ¯è² |
| | | .fab-button { |
| | | background: #ed8d05; |
| | |
| | | :key="index" |
| | | @click="handleCommonItemClick(item)"> |
| | | <view class="icon-container"> |
| | | <image :src="item.icon" class="item-icon"></image> |
| | | <image :src="item.icon" |
| | | class="item-icon"></image> |
| | | </view> |
| | | <text class="item-label">{{item.label}}</text> |
| | | </up-grid-item> |
| | |
| | | |
| | | // è·¯ç±æ å°é
ç½® |
| | | const routeMapping = { |
| | | "éå®å°è´¦": "/pages/sales/salesAccount/index", |
| | | "å¼ç¥¨ç»è®°": "/pages/sales/invoicingRegistration/index", |
| | | "å¼ç¥¨å°è´¦": "/pages/sales/invoiceLedger/index", |
| | | "忬¾ç»è®°": "/pages/sales/receiptPayment/index", |
| | | "忬¾æµæ°´": "/pages/sales/receiptPaymentHistory/index", |
| | | "客æ·å¾æ¥": "/pages/sales/receiptPaymentLedger/index", |
| | | "éè´å°è´¦": "/pages/procurementManagement/procurementLedger/index", |
| | | "æ¥ç¥¨ç»è®°": "/pages/procurementManagement/invoiceEntry/index", |
| | | "æ¥ç¥¨å°è´¦": "/pages/procurementManagement/procurementInvoiceLedger/index", |
| | | "仿¬¾ç»è®°": "/pages/procurementManagement/paymentEntry/index", |
| | | "仿¬¾æµæ°´": "/pages/procurementManagement/receiptPaymentHistory/index", |
| | | "ä¾åºå徿¥": "/pages/procurementManagement/paymentLedger/index", |
| | | "éè´éè´§å": "/pages/procurementManagement/purchaseReturnOrder/index", |
| | | "ä¾åºå管ç": "/pages/basicData/supplierManage/index", |
| | | "å
¬åºç®¡ç": "/pages/cooperativeOffice/collaborativeApproval/index1", |
| | | "请å管ç": "/pages/cooperativeOffice/collaborativeApproval/index2", |
| | | "åºå·®ç®¡ç": "/pages/cooperativeOffice/collaborativeApproval/index3", |
| | | "æ¥é管ç": "/pages/cooperativeOffice/collaborativeApproval/index4", |
| | | "éè´å®¡æ¹": "/pages/cooperativeOffice/collaborativeApproval/index5", |
| | | "æ¥ä»·å®¡æ¹": "/pages/cooperativeOffice/collaborativeApproval/index6", |
| | | "å货审æ¹": "/pages/cooperativeOffice/collaborativeApproval/index7", |
| | | "åå审æ¹": "/pages/cooperativeOffice/collaborativeApproval/index", |
| | | "ä¼è®®è®¾ç½®": "/pages/managementMeetings/meetingSettings/index", |
| | | "ä¼è®®å表": "/pages/managementMeetings/meetingList/index", |
| | | "ä¼è®®ç³è¯·": "/pages/managementMeetings/meetApplication/index", |
| | | "ä¼è®®å®¡æ¹": "/pages/managementMeetings/meetExamine/index", |
| | | "ä¼è®®åå¸": "/pages/managementMeetings/meetPublish/index", |
| | | "ä¼è®®æ»ç»": "/pages/managementMeetings/meetSummary/index", |
| | | "ä¼è®®çæ¿": "/pages/managementMeetings/meetingBoard/index", |
| | | "éç¥å
Œ": "/pages/cooperativeOffice/noticeManagement/index", |
| | | "ç¥è¯åº": "/pages/managementMeetings/knowledgeBase/index", |
| | | "ç¨å°ç®¡ç": "/pages/managementMeetings/sealManagement/index", |
| | | "è§ç« å¶åº¦": "/pages/managementMeetings/rulesRegulationsManagement/index", |
| | | "å®¢æ·æè®¿": "/pages/cooperativeOffice/clientVisit/index", |
| | | "ç产订å": "/pages/productionManagement/productionOrder/index", |
| | | "ç产派工": "/pages/productionManagement/productionDispatching/index", |
| | | "å·¥åºæäº§": "/pages/productionManagement/processScheduling/index", |
| | | "çäº§æ ¸ç®": "/pages/productionManagement/productionAccounting/index", |
| | | "设å¤å°è´¦": "/pages/equipmentManagement/ledger/index", |
| | | "è®¾å¤æ¥ä¿®": "/pages/equipmentManagement/repair/index", |
| | | "设å¤ä¿å
»": "/pages/equipmentManagement/upkeep/index", |
| | | "设å¤å·¡æ£": "/pages/inspectionUpload/index", |
| | | "åæè¿½æº¯": "/pages/equipmentManagement/faultAnalysis/index", |
| | | "æºè½æ´¾å": "/pages/equipmentManagement/smartDispatch/index", |
| | | "ä½ä¸æå¯¼": "/pages/equipmentManagement/sop/index", |
| | | "ç»æéªè¯": "/pages/equipmentManagement/verification/index", |
| | | éå®å°è´¦: "/pages/sales/salesAccount/index", |
| | | å¼ç¥¨ç»è®°: "/pages/sales/invoicingRegistration/index", |
| | | å¼ç¥¨å°è´¦: "/pages/sales/invoiceLedger/index", |
| | | 忬¾ç»è®°: "/pages/sales/receiptPayment/index", |
| | | 忬¾æµæ°´: "/pages/sales/receiptPaymentHistory/index", |
| | | 客æ·å¾æ¥: "/pages/sales/receiptPaymentLedger/index", |
| | | éè´å°è´¦: "/pages/procurementManagement/procurementLedger/index", |
| | | æ¥ç¥¨ç»è®°: "/pages/procurementManagement/invoiceEntry/index", |
| | | æ¥ç¥¨å°è´¦: "/pages/procurementManagement/procurementInvoiceLedger/index", |
| | | 仿¬¾ç»è®°: "/pages/procurementManagement/paymentEntry/index", |
| | | 仿¬¾æµæ°´: "/pages/procurementManagement/receiptPaymentHistory/index", |
| | | ä¾åºå徿¥: "/pages/procurementManagement/paymentLedger/index", |
| | | éè´éè´§å: "/pages/procurementManagement/purchaseReturnOrder/index", |
| | | ä¾åºå管ç: "/pages/basicData/supplierManage/index", |
| | | å
¬åºç®¡ç: "/pages/cooperativeOffice/collaborativeApproval/index1", |
| | | 请å管ç: "/pages/cooperativeOffice/collaborativeApproval/index2", |
| | | åºå·®ç®¡ç: "/pages/cooperativeOffice/collaborativeApproval/index3", |
| | | æ¥é管ç: "/pages/cooperativeOffice/collaborativeApproval/index4", |
| | | éè´å®¡æ¹: "/pages/cooperativeOffice/collaborativeApproval/index5", |
| | | æ¥ä»·å®¡æ¹: "/pages/cooperativeOffice/collaborativeApproval/index6", |
| | | å货审æ¹: "/pages/cooperativeOffice/collaborativeApproval/index7", |
| | | åå审æ¹: "/pages/cooperativeOffice/collaborativeApproval/index", |
| | | å®¡æ¹æµç¨é
ç½®: |
| | | "/pages/cooperativeOffice/collaborativeApproval/approvalManagement", |
| | | ä¼è®®è®¾ç½®: "/pages/managementMeetings/meetingSettings/index", |
| | | ä¼è®®å表: "/pages/managementMeetings/meetingList/index", |
| | | ä¼è®®ç³è¯·: "/pages/managementMeetings/meetApplication/index", |
| | | ä¼è®®å®¡æ¹: "/pages/managementMeetings/meetExamine/index", |
| | | ä¼è®®åå¸: "/pages/managementMeetings/meetPublish/index", |
| | | ä¼è®®æ»ç»: "/pages/managementMeetings/meetSummary/index", |
| | | ä¼è®®çæ¿: "/pages/managementMeetings/meetingBoard/index", |
| | | éç¥å
Œ: "/pages/cooperativeOffice/noticeManagement/index", |
| | | ç¥è¯åº: "/pages/managementMeetings/knowledgeBase/index", |
| | | ç¨å°ç®¡ç: "/pages/managementMeetings/sealManagement/index", |
| | | è§ç« å¶åº¦: "/pages/managementMeetings/rulesRegulationsManagement/index", |
| | | å®¢æ·æè®¿: "/pages/cooperativeOffice/clientVisit/index", |
| | | ç产订å: "/pages/productionManagement/productionOrder/index", |
| | | ç产派工: "/pages/productionManagement/productionDispatching/index", |
| | | å·¥åºæäº§: "/pages/productionManagement/processScheduling/index", |
| | | çäº§æ ¸ç®: "/pages/productionManagement/productionAccounting/index", |
| | | 设å¤å°è´¦: "/pages/equipmentManagement/ledger/index", |
| | | è®¾å¤æ¥ä¿®: "/pages/equipmentManagement/repair/index", |
| | | 设å¤ä¿å
»: "/pages/equipmentManagement/upkeep/index", |
| | | 设å¤å·¡æ£: "/pages/inspectionUpload/index", |
| | | åæè¿½æº¯: "/pages/equipmentManagement/faultAnalysis/index", |
| | | æºè½æ´¾å: "/pages/equipmentManagement/smartDispatch/index", |
| | | ä½ä¸æå¯¼: "/pages/equipmentManagement/sop/index", |
| | | ç»æéªè¯: "/pages/equipmentManagement/verification/index", |
| | | }; |
| | | |
| | | // èåé
ç½® |
| | | const menuConfig = { |
| | | "èå¤ç®¡ç": [ |
| | | èå¤ç®¡ç: [ |
| | | { icon: "/static/images/icon/gongchuguanli.svg", label: "å
¬åºç®¡ç" }, |
| | | { icon: "/static/images/icon/qingjiaguanli.svg", label: "请å管ç" }, |
| | | { icon: "/static/images/icon/chuchaiguanli.svg", label: "åºå·®ç®¡ç" }, |
| | | ], |
| | | "ä¼è®®ç®¡ç": [ |
| | | ä¼è®®ç®¡ç: [ |
| | | { icon: "/static/images/icon/huiyishezhi.svg", label: "ä¼è®®è®¾ç½®" }, |
| | | { icon: "/static/images/icon/huiyiliebiao.svg", label: "ä¼è®®å表" }, |
| | | { icon: "/static/images/icon/huiyishenqing.svg", label: "ä¼è®®ç³è¯·" }, |
| | |
| | | { icon: "/static/images/icon/huiyizongjie.svg", label: "ä¼è®®æ»ç»" }, |
| | | { icon: "/static/images/icon/huiyikanban.svg", label: "ä¼è®®çæ¿" }, |
| | | ], |
| | | "åå审æ¹": [ |
| | | åå审æ¹: [ |
| | | { icon: "/static/images/icon/gongchuguanli.svg", label: "å
¬åºç®¡ç" }, |
| | | { icon: "/static/images/icon/qingjiaguanli.svg", label: "请å管ç" }, |
| | | { icon: "/static/images/icon/chuchaiguanli.svg", label: "åºå·®ç®¡ç" }, |
| | |
| | | }, 30000); |
| | | } |
| | | |
| | | // tabbar è§æ ï¼ç»å½å宿¶å·æ°æ¶å¯è½å·²ä¸å¨ tabBar 页é¢ï¼å¤±è´¥éé»å¿½ç¥ |
| | | function callTabBarApi(api, options) { |
| | | try { |
| | | const ret = uni[api](options); |
| | | if (ret && typeof ret.catch === "function") ret.catch(() => {}); |
| | | } catch (e) { |
| | | /* é tabBar 页é¢ï¼å¿½ç¥ */ |
| | | } |
| | | } |
| | | |
| | | // æ´æ°æªè¯»æ¶æ¯æ°é |
| | | function updateNoticeCount(userId) { |
| | | getNoticeCount(userId) |
| | |
| | | console.log("æªè¯»æ¶æ¯æ°é:", count); |
| | | // æ´æ°tabbarçè§æ |
| | | if (count > 0) { |
| | | uni.setTabBarBadge({ |
| | | callTabBarApi("setTabBarBadge", { |
| | | index: 2, // æ¶æ¯æ ç¾é¡µçç´¢å¼ |
| | | text: count.toString(), |
| | | }); |
| | | } else { |
| | | uni.removeTabBarBadge({ |
| | | callTabBarApi("removeTabBarBadge", { |
| | | index: 2, |
| | | }); |
| | | } |
| | |
| | | return null; |
| | | }; |
| | | |
| | | // tabbar è§æ ï¼å¼æ¥è¿åæ¶å¯è½å·²åå°é tabBar 页é¢ï¼å¤±è´¥éé»å¿½ç¥ |
| | | const callTabBarApi = (api, options) => { |
| | | try { |
| | | const ret = uni[api](options); |
| | | if (ret && typeof ret.catch === "function") ret.catch(() => {}); |
| | | } catch (e) { |
| | | /* é tabBar 页é¢ï¼å¿½ç¥ */ |
| | | } |
| | | }; |
| | | |
| | | // æ´æ° tab æ è§æ |
| | | const updateTabBarBadge = () => { |
| | | if (userId.value) { |
| | |
| | | const count = res.data || 0; |
| | | // æ´æ°tabbarçè§æ |
| | | if (count > 0) { |
| | | uni.setTabBarBadge({ |
| | | callTabBarApi("setTabBarBadge", { |
| | | index: 2, // æ¶æ¯æ ç¾é¡µçç´¢å¼ |
| | | text: count.toString(), |
| | | }); |
| | | } else { |
| | | uni.removeTabBarBadge({ |
| | | callTabBarApi("removeTabBarBadge", { |
| | | index: 2, |
| | | }); |
| | | } |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="section-card"> |
| | | <view class="section-head"> |
| | | <text class="section-title">å¡«æ¥å
容</text> |
| | | <text class="section-title">{{ isSpecialApprovalType ? specialDetailTitle : "å¡«æ¥å
容" }}</text> |
| | | </view> |
| | | <!-- éè´/æ¥ä»·/å货审æ¹ï¼å±ç¤ºä¸å¡åæ®è¯¦æ
ï¼ä¸ Web approve-list ä¸è´ï¼ --> |
| | | <template v-if="isSpecialApprovalType"> |
| | | <view v-if="businessDetailLoading" |
| | | class="empty-hint">å è½½ä¸...</view> |
| | | <view v-else-if="!hasBusinessDetail" |
| | | class="empty-hint">{{ specialDetailEmptyText }}</view> |
| | | <view v-else |
| | | class="info-rows"> |
| | | <view v-for="(item, idx) in specialDetailRows" |
| | | :key="`special-${idx}`" |
| | | class="info-row"> |
| | | <text class="info-label">{{ item.label }}</text> |
| | | <text class="info-value">{{ item.value }}</text> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | <template v-else> |
| | | <view v-if="displayFields.length" |
| | | class="info-rows"> |
| | | <view v-for="field in displayFields" |
| | |
| | | </view> |
| | | <view v-else |
| | | class="empty-hint">ææ å¡«æ¥å
容</view> |
| | | </template> |
| | | </view> |
| | | |
| | | <view class="section-card"> |
| | | <view class="section-head"> |
| | | <text class="section-title">å®¡æ¹æµç¨ï¼{{ flowNodes.length }} 项ï¼</text> |
| | |
| | | <view v-else |
| | | class="empty-hint">ææ æµç¨èç¹</view> |
| | | </view> |
| | | |
| | | <view class="section-card"> |
| | | <view class="section-head"> |
| | | <text class="section-title">审æ¹è®°å½</text> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from "vue"; |
| | | import { computed, ref, watch } from "vue"; |
| | | import { APPROVAL_MODULE_KEYS } from "../../../_utils/approvalModuleRegistry.js"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger"; |
| | | import { getDeliveryDetailByShippingNo } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import { |
| | | computeLeaveDurationDisplay, |
| | | computeOvertimeHoursDisplay, |
| | |
| | | moduleKey: { type: String, default: "" }, |
| | | }); |
| | | |
| | | const LEVEL_TEXT = ["", "ä¸", "äº", "ä¸", "å", "äº", "å
", "ä¸", "å
«", "ä¹", "å"]; |
| | | const LEVEL_TEXT = [ |
| | | "", |
| | | "ä¸", |
| | | "äº", |
| | | "ä¸", |
| | | "å", |
| | | "äº", |
| | | "å
", |
| | | "ä¸", |
| | | "å
«", |
| | | "ä¹", |
| | | "å", |
| | | ]; |
| | | |
| | | const isBusinessModule = computed(() => |
| | | [ |
| | |
| | | ); |
| | | |
| | | const statusLabel = status => |
| | | isBusinessModule.value ? businessStatusText(status) : instanceStatusText(status); |
| | | isBusinessModule.value |
| | | ? businessStatusText(status) |
| | | : instanceStatusText(status); |
| | | |
| | | const statusTagType = status => |
| | | isBusinessModule.value ? businessStatusTagType(status) : instanceStatusTagType(status); |
| | | isBusinessModule.value |
| | | ? businessStatusTagType(status) |
| | | : instanceStatusTagType(status); |
| | | |
| | | const displayFields = computed(() => resolveInstanceDisplayFields(props.row)); |
| | | |
| | |
| | | |
| | | const flowNodes = computed(() => mapTasksToFlowNodes(props.row?.tasks)); |
| | | |
| | | const approvalRecords = computed(() => |
| | | mapApprovalRecords(props.row?.records) |
| | | ); |
| | | const approvalRecords = computed(() => mapApprovalRecords(props.row?.records)); |
| | | |
| | | const rejectReason = computed(() => |
| | | getRejectReasonFromRecords(props.row?.records) |
| | | ); |
| | | |
| | | const levelLabel = n => LEVEL_TEXT[Number(n)] || String(n); |
| | | |
| | | /** éè´/æ¥ä»·/å货审æ¹ï¼ä¸å¡åæ®è¯¦æ
ï¼ä¸ Web approve-list ä¸è´ï¼ */ |
| | | const SPECIAL_BUSINESS_TYPES = [5, 6, 7]; |
| | | |
| | | const businessType = computed(() => Number(props.row?.businessType)); |
| | | const isSpecialApprovalType = computed(() => |
| | | SPECIAL_BUSINESS_TYPES.includes(businessType.value) |
| | | ); |
| | | |
| | | const businessDetail = ref({}); |
| | | const businessProductList = ref([]); |
| | | const businessDetailLoading = ref(false); |
| | | |
| | | const specialDetailTitle = computed(() => { |
| | | if (businessType.value === 5) return "éè´è¯¦æ
"; |
| | | if (businessType.value === 6) return "æ¥ä»·è¯¦æ
"; |
| | | return "å货详æ
"; |
| | | }); |
| | | |
| | | const specialDetailEmptyText = computed(() => { |
| | | if (businessType.value === 5) return "æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
"; |
| | | if (businessType.value === 6) return "æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
"; |
| | | return "æªæ¥è¯¢å°å¯¹åºå货详æ
"; |
| | | }); |
| | | |
| | | const hasBusinessDetail = computed(() => { |
| | | const d = businessDetail.value || {}; |
| | | if (businessType.value === 5) return Boolean(d.purchaseContractNumber); |
| | | if (businessType.value === 6) return Boolean(d.quotationNo); |
| | | if (businessType.value === 7) return Boolean(d.shippingInfo); |
| | | return false; |
| | | }); |
| | | |
| | | const formatMoney = val => `Â¥${Number(val ?? 0).toFixed(2)}`; |
| | | |
| | | const joinDesc = parts => parts.filter(Boolean).join(" · "); |
| | | |
| | | const specialDetailRows = computed(() => { |
| | | const d = businessDetail.value || {}; |
| | | const products = businessProductList.value || []; |
| | | const rows = []; |
| | | const push = (label, value) => rows.push({ label, value: value || "--" }); |
| | | |
| | | if (businessType.value === 6) { |
| | | push("æ¥ä»·åå·", d.quotationNo); |
| | | push("客æ·åç§°", d.customer); |
| | | push("ä¸å¡å", d.salesperson); |
| | | push("æ¥ä»·æ¥æ", d.quotationDate); |
| | | push("æææè³", d.validDate); |
| | | push("仿¬¾æ¹å¼", d.paymentMethod); |
| | | push("æ¥ä»·æ»é¢", formatMoney(d.totalAmount)); |
| | | products.forEach((p, i) => { |
| | | push( |
| | | p.product || `产å${i + 1}`, |
| | | joinDesc([p.specification, p.unit, formatMoney(p.unitPrice)]) |
| | | ); |
| | | }); |
| | | if (d.remark) push("夿³¨", d.remark); |
| | | } else if (businessType.value === 5) { |
| | | push("éè´ååå·", d.purchaseContractNumber); |
| | | push("ä¾åºååç§°", d.supplierName); |
| | | push("项ç®åç§°", d.projectName); |
| | | push("éå®ååå·", d.salesContractNo); |
| | | push("ç¾è®¢æ¥æ", d.executionDate); |
| | | push("å½å
¥æ¥æ", d.entryDate); |
| | | push("仿¬¾æ¹å¼", d.paymentMethod); |
| | | push("ååéé¢", formatMoney(d.contractAmount)); |
| | | products.forEach((p, i) => { |
| | | push( |
| | | p.productCategory || `产å${i + 1}`, |
| | | joinDesc([ |
| | | p.specificationModel, |
| | | p.quantity != null && p.quantity !== "" |
| | | ? `${p.quantity}${p.unit || ""}` |
| | | : "", |
| | | `å«ç¨åä»· ${formatMoney(p.taxInclusiveUnitPrice)}`, |
| | | `å«ç¨æ»ä»· ${formatMoney(p.taxInclusiveTotalPrice)}`, |
| | | ]) |
| | | ); |
| | | }); |
| | | } else if (businessType.value === 7) { |
| | | const info = d.shippingInfo || {}; |
| | | push("éå®è®¢å", info.salesContractNo); |
| | | push("å货订åå·", info.shippingNo); |
| | | push("客æ·åç§°", info.customerName); |
| | | push("åè´§ç±»å", info.type); |
| | | push("åè´§æ¥æ", info.shippingDate); |
| | | push("å®¡æ ¸ç¶æ", info.status); |
| | | push("å货车çå·", info.shippingCarNumber); |
| | | push("å¿«éå
¬å¸", info.expressCompany); |
| | | push("å¿«éåå·", info.expressNumber); |
| | | products.forEach((p, i) => { |
| | | push( |
| | | p.productName || `产å${i + 1}`, |
| | | joinDesc([ |
| | | p.batchNo ? `æ¹å· ${p.batchNo}` : "", |
| | | p.specificationModel, |
| | | p.deliveryQuantity != null ? `åè´§æ°é ${p.deliveryQuantity}` : "", |
| | | ]) |
| | | ); |
| | | }); |
| | | } |
| | | return rows; |
| | | }); |
| | | |
| | | async function loadBusinessDetail() { |
| | | const row = props.row || {}; |
| | | const type = Number(row.businessType); |
| | | businessDetail.value = {}; |
| | | businessProductList.value = []; |
| | | if (!SPECIAL_BUSINESS_TYPES.includes(type)) return; |
| | | |
| | | businessDetailLoading.value = true; |
| | | try { |
| | | if (type === 6) { |
| | | if (!row.quotationNo) return; |
| | | const res = await getQuotationList({ quotationNo: row.quotationNo }); |
| | | const hit = res?.data?.records?.[0] || {}; |
| | | businessDetail.value = hit; |
| | | businessProductList.value = hit.products || []; |
| | | } else if (type === 5) { |
| | | if (!row.purchaseContractNumber) return; |
| | | const res = await getPurchaseByCode({ |
| | | purchaseContractNumber: row.purchaseContractNumber, |
| | | approvalInstanceId: row.id, |
| | | }); |
| | | const hit = res?.data || res || {}; |
| | | businessDetail.value = hit; |
| | | businessProductList.value = hit.productData || []; |
| | | } else if (type === 7) { |
| | | if (!row.shippingNo) return; |
| | | const res = await getDeliveryDetailByShippingNo({ |
| | | shippingNo: row.shippingNo, |
| | | }); |
| | | const hit = res?.data || res || {}; |
| | | businessDetail.value = hit; |
| | | businessProductList.value = hit.shippingProductDetailDtoList || []; |
| | | } |
| | | } catch { |
| | | businessDetail.value = {}; |
| | | businessProductList.value = []; |
| | | } finally { |
| | | businessDetailLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => [props.row?.id, props.row?.businessType], |
| | | () => { |
| | | loadBusinessDetail(); |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | <view class="approve-apply-page"> |
| | | <PageHeader :title="pageTitle" |
| | | @back="goBack" /> |
| | | |
| | | <scroll-view class="form-scroll" |
| | | scroll-y |
| | | :show-scrollbar="false"> |
| | |
| | | </up-form-item> |
| | | </u-cell-group> |
| | | </up-form> |
| | | |
| | | <view class="section-card"> |
| | | <view class="section-head"> |
| | | <text class="section-title">å¡«æ¥å
容</text> |
| | |
| | | </up-form> |
| | | <view v-else |
| | | class="empty-hint">è¯¥æ¨¡æ¿ææ å¡«æ¥é¡¹</view> |
| | | |
| | | <!-- 请åï¼åæä½é¢ + æ¶é¿èªå¨è®¡ç® --> |
| | | <view v-if="isLeaveModule" |
| | | class="module-extra-block"> |
| | |
| | | </up-form-item> |
| | | </up-form> |
| | | </view> |
| | | |
| | | <!-- å çï¼æ¶é¿èªå¨è®¡ç® --> |
| | | <view v-if="isOvertimeModule" |
| | | class="module-extra-block"> |
| | |
| | | </up-form-item> |
| | | </up-form> |
| | | </view> |
| | | |
| | | <!-- è°å²ï¼åå²ä½èªå¨å¸¦åº --> |
| | | <view v-if="isTransferModule" |
| | | class="module-extra-block"> |
| | |
| | | </up-form> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="section-card"> |
| | | <view class="section-head"> |
| | | <text class="section-title">å®¡æ¹æµç¨</text> |
| | |
| | | text="æªè·åå°æ¨¡æ¿è¯¦æ
" /> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <FooterButtons v-if="!loading && detail" |
| | | cancel-text="åæ¶" |
| | | :confirm-text="confirmText" |
| | | :loading="submitting" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" /> |
| | | |
| | | <up-popup :show="showDatePicker" |
| | | mode="bottom" |
| | | @close="showDatePicker = false"> |
| | |
| | | @confirm="onDateConfirm" |
| | | @cancel="onDatePickerCancel" /> |
| | | </up-popup> |
| | | |
| | | <up-action-sheet :show="showSelectSheet" |
| | | :title="selectSheetTitle" |
| | | :actions="selectSheetActions" |
| | |
| | | parseFieldDateToTs, |
| | | } from "../../_utils/approvalFormField.js"; |
| | | |
| | | import { EDIT_STORAGE_KEY } from "../../_utils/approveListUtils.js"; |
| | | import { |
| | | EDIT_STORAGE_KEY, |
| | | mapFlowNodesToTasks, |
| | | } from "../../_utils/approveListUtils.js"; |
| | | |
| | | const LEVEL_TEXT = ["", "ä¸", "äº", "ä¸", "å", "äº", "å
", "ä¸", "å
«", "ä¹", "å"]; |
| | | const LEVEL_TEXT = [ |
| | | "", |
| | | "ä¸", |
| | | "äº", |
| | | "ä¸", |
| | | "å", |
| | | "äº", |
| | | "å
", |
| | | "ä¸", |
| | | "å
«", |
| | | "ä¹", |
| | | "å", |
| | | ]; |
| | | |
| | | const userStore = useUserStore(); |
| | | const moduleKey = ref(""); |
| | |
| | | const isEditMode = computed(() => !!instanceId.value); |
| | | |
| | | const pageTitle = computed(() => (isEditMode.value ? "ä¿®æ¹å®¡æ¹" : "å起审æ¹")); |
| | | const confirmText = computed(() => (isEditMode.value ? "ä¿åä¿®æ¹" : "æäº¤å®¡æ¹")); |
| | | const confirmText = computed(() => |
| | | isEditMode.value ? "ä¿åä¿®æ¹" : "æäº¤å®¡æ¹" |
| | | ); |
| | | |
| | | const applicantName = computed( |
| | | () => userStore.nickName || userStore.name || "-" |
| | |
| | | |
| | | const levelLabel = n => LEVEL_TEXT[Number(n)] || String(n); |
| | | |
| | | const selectSheetTitle = computed( |
| | | () => (activeSelectField.value?.label ? `éæ©${activeSelectField.value.label}` : "è¯·éæ©") |
| | | const selectSheetTitle = computed(() => |
| | | activeSelectField.value?.label |
| | | ? `éæ©${activeSelectField.value.label}` |
| | | : "è¯·éæ©" |
| | | ); |
| | | |
| | | const selectSheetActions = computed(() => { |
| | |
| | | }); |
| | | const matched = options.find( |
| | | opt => |
| | | String(opt.value) === String(stored) || String(opt.label) === String(stored) |
| | | String(opt.value) === String(stored) || |
| | | String(opt.label) === String(stored) |
| | | ); |
| | | return ( |
| | | matched?.label || |
| | |
| | | const val = formValues[field.key]; |
| | | if (val === undefined || val === null || String(val).trim() === "") { |
| | | const action = |
| | | isSelectField(field) || isDateLikeField(field) || isDatetimerangeField(field) |
| | | isSelectField(field) || |
| | | isDateLikeField(field) || |
| | | isDatetimerangeField(field) |
| | | ? "è¯·éæ©" |
| | | : "请填å"; |
| | | uni.showToast({ title: `${action}${field.label}`, icon: "none" }); |
| | |
| | | options.length && |
| | | !options.some( |
| | | opt => |
| | | String(opt.value) === String(val) || String(opt.label) === String(val) |
| | | String(opt.value) === String(val) || |
| | | String(opt.label) === String(val) |
| | | ) |
| | | ) { |
| | | uni.showToast({ title: `${field.label}éé¡¹æ æ`, icon: "none" }); |
| | |
| | | formConfigData.value.fields |
| | | ); |
| | | const allFields = formConfigData.value.fields || []; |
| | | const formPayload = {}; |
| | | allFields.forEach(field => { |
| | | if (!field?.key) return; |
| | | formPayload[field.key] = formValues[field.key] ?? ""; |
| | | }); |
| | | Object.entries(formValues).forEach(([key, val]) => { |
| | | if (key && formPayload[key] === undefined) formPayload[key] = val; |
| | | }); |
| | | return JSON.stringify({ |
| | | prompt: formConfigData.value.prompt, |
| | | fields: allFields.map(field => ({ |
| | | ...field, |
| | | value: formValues[field.key] ?? "", |
| | | })), |
| | | summaryPlaceholder: formConfigData.value.prompt || "", |
| | | approvalType: formConfigData.value.approvalType || "", |
| | | fields: allFields.map(({ value: _value, ...rest }) => rest), |
| | | formPayload, |
| | | }); |
| | | }; |
| | | |
| | | /** æ¨¡æ¿ nodes â 审æ¹ä»»å¡ï¼ä¸ Web buildInstanceDto ä¸è´ï¼ */ |
| | | const buildInstanceTasks = instanceIdValue => |
| | | mapFlowNodesToTasks(detail.value?.nodes, { |
| | | instanceId: instanceIdValue, |
| | | templateId: detail.value?.id, |
| | | }); |
| | | |
| | | const buildSavePayload = () => ({ |
| | | templateId: detail.value.id, |
| | |
| | | applyTime: parseTime(new Date()), |
| | | deptId: userStore.currentDeptId || undefined, |
| | | formConfig: buildFormConfigPayload(), |
| | | tasks: buildInstanceTasks(), |
| | | }); |
| | | |
| | | const buildUpdatePayload = () => { |
| | |
| | | deptId: row.deptId, |
| | | deleted: row.deleted, |
| | | formConfig: buildFormConfigPayload(), |
| | | tasks: buildInstanceTasks(instanceId.value), |
| | | approveAction: row.approveAction, |
| | | approveComment: row.approveComment, |
| | | }; |
| | |
| | | try { |
| | | const res = await findPostOptions(); |
| | | const rows = res?.data ?? res?.rows ?? []; |
| | | postIdToName.value = buildPostIdToNameMap(Array.isArray(rows) ? rows : []); |
| | | postIdToName.value = buildPostIdToNameMap( |
| | | Array.isArray(rows) ? rows : [] |
| | | ); |
| | | } catch { |
| | | postIdToName.value = {}; |
| | | } |
| | |
| | | return; |
| | | } |
| | | const user = userById(transferUserPool.value, uid); |
| | | extraForm.originalPostName = resolveOriginalPostName(user, postIdToName.value); |
| | | extraForm.originalPostName = resolveOriginalPostName( |
| | | user, |
| | | postIdToName.value |
| | | ); |
| | | } |
| | | |
| | | const goBack = () => { |
| | |
| | | <view class="oa-detail-page"> |
| | | <PageHeader :title="pageTitle" |
| | | @back="goBack" /> |
| | | |
| | | <scroll-view v-if="displayReady" |
| | | class="oa-detail-scroll" |
| | | scroll-y |
| | |
| | | :row="row" |
| | | :module-key="detailModuleKey" /> |
| | | </scroll-view> |
| | | |
| | | <view v-else |
| | | class="oa-empty"> |
| | | <up-empty mode="data" |
| | | :text="loading ? 'å è½½ä¸' : 'æªè·åå°å®¡æ¹æ°æ®'" /> |
| | | </view> |
| | | |
| | | <view v-if="displayReady" |
| | | class="oa-page-footer"> |
| | | <text class="oa-footer-btn btn-default" |
| | |
| | | import ApproveInstanceDetailBody from "./_components/ApproveInstanceDetailBody.vue"; |
| | | import ReimburseInstanceDetailBody from "../../ReimburseManage/_components/ReimburseInstanceDetailBody.vue"; |
| | | import { OA_NAV } from "@/config/oaPaths.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { |
| | | canApproveInstance, |
| | | canEditBusinessInstanceRow, |
| | |
| | | import { getApprovalModuleConfig } from "../../_utils/approvalModuleRegistry.js"; |
| | | import { canEditReimbursementRow } from "../../_utils/finReimbursementMappers.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const fromBusiness = ref(false); |
| | | const row = ref(null); |
| | | const reimburseRow = ref(null); |
| | |
| | | if (fromBusiness.value) { |
| | | return canEditBusinessInstanceRow(row.value); |
| | | } |
| | | return canModifyInstance(row.value, userStore); |
| | | return canModifyInstance(row.value); |
| | | }); |
| | | |
| | | const showApprove = computed(() => canApproveInstance(row.value)); |
| | |
| | | if (isReimburseApprovalInstance(cached)) { |
| | | loading.value = true; |
| | | try { |
| | | const { reimburseRow: mapped } = await loadReimburseDetailForInstance(cached); |
| | | const { reimburseRow: mapped } = await loadReimburseDetailForInstance( |
| | | cached |
| | | ); |
| | | reimburseRow.value = mapped; |
| | | } catch { |
| | | uni.showToast({ title: "å è½½æ¥é详æ
失败", icon: "none" }); |
| | |
| | | <view class="oa-approval-page"> |
| | | <PageHeader title="审æ¹å表" |
| | | @back="goBack" /> |
| | | |
| | | <view class="oa-toolbar"> |
| | | <view class="oa-filter-chip active-search"> |
| | | <up-icon name="search" |
| | |
| | | color="#2979ff" /> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="oa-list-scroll" |
| | | scroll-y |
| | | :show-scrollbar="false" |
| | |
| | | {{ statusText(item.status) }} |
| | | </text> |
| | | </view> |
| | | |
| | | <view class="oa-card-body"> |
| | | <view class="oa-info-grid"> |
| | | <view class="oa-info-row"> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="canModify(item) || item.isApprove" |
| | | class="oa-card-foot" |
| | | @click.stop> |
| | |
| | | text="ææ å®¡æ¹æ°æ®" /> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="fab-button" |
| | | @click="goAdd"> |
| | | <up-icon name="plus" |
| | |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { listApprovalInstancePage } from "@/api/oa/approvalInstance.js"; |
| | | import { OA_NAV } from "@/config/oaPaths.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | import { |
| | | businessStatusClass, |
| | |
| | | stashReimburseEditFromApprove, |
| | | } from "../../_utils/reimburseApproveBridge.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const queryParams = reactive({ keyword: "" }); |
| | | const list = ref([]); |
| | | const pageStatus = ref("loadmore"); |
| | |
| | | return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}") || String(val); |
| | | }; |
| | | |
| | | const canModify = item => canModifyInstance(item, userStore); |
| | | const canModify = item => canModifyInstance(item); |
| | | |
| | | const currentApproverName = item => { |
| | | const tasks = item?.tasks; |
| | |
| | | |
| | | const goModify = item => { |
| | | if (!canModify(item)) { |
| | | uni.showToast({ title: "ä»
è¿è¡ä¸çæ¬äººç³è¯·å¯ç¼è¾", icon: "none" }); |
| | | uni.showToast({ title: "ä»
è¿è¡ä¸ç审æ¹å¯ç¼è¾", icon: "none" }); |
| | | return; |
| | | } |
| | | const mk = inferReimburseModuleKeyFromInstance(item); |
| | |
| | | <view class="template-select-page sales-account"> |
| | | <PageHeader :title="pageHeaderTitle" |
| | | @back="goBack" /> |
| | | |
| | | <view v-if="typeOptions.length && !moduleKey" |
| | | class="step-section"> |
| | | <view class="tabs-wrap"> |
| | |
| | | @click="onTabClick" /> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="useAllTemplatesFallback && allTemplates.length" |
| | | class="fallback-hint"> |
| | | <text>å½åç±»å䏿 å¹é
模æ¿ï¼å·²æ¾ç¤ºå
¨é¨ {{ allTemplates.length }} 个å¯ç¨æ¨¡æ¿</text> |
| | | </view> |
| | | |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="list-scroll" |
| | | scroll-y |
| | | :show-scrollbar="false"> |
| | |
| | | |
| | | const currentTypeOption = computed(() => typeOptions.value[activeTab.value]); |
| | | |
| | | /** æ moduleKey ä¸å½å Tab çä¸å°æ¶ï¼å±ç¤ºå
¨é¨æ¨¡æ¿é¿å
ãææ°æ®å´ç©ºç½ã */ |
| | | const useAllTemplatesFallback = computed(() => { |
| | | if (moduleKey.value) return false; |
| | | if (!allTemplates.value.length) return false; |
| | | const businessType = currentTypeOption.value?.value; |
| | | if (businessType == null || businessType === "") return true; |
| | | return filterTemplatesByBusinessType(allTemplates.value, businessType).length === 0; |
| | | }); |
| | | /** ä¸å
许æå·¥å起审æ¹çç±»åï¼éè´/æ¥ä»·/åè´§ï¼ç±ä¸å¡åæ®äº§çï¼ä¸ Web ä¸è´ï¼ */ |
| | | const SUBMIT_EXCLUDED_BUSINESS_TYPES = [5, 6, 7]; |
| | | |
| | | /** å½å Tabï¼æä¸å¡æ¨¡åï¼ä¸å¯åèµ·çæ¨¡æ¿ */ |
| | | const currentSource = computed(() => { |
| | | if (moduleKey.value && moduleBusinessTypes.value.length) { |
| | | const filtered = filterTemplatesByBusinessTypes( |
| | | return filterTemplatesByBusinessTypes( |
| | | allTemplates.value, |
| | | moduleBusinessTypes.value |
| | | ); |
| | | if (filtered.length) return filtered; |
| | | return allTemplates.value; |
| | | } |
| | | if (useAllTemplatesFallback.value) { |
| | | return allTemplates.value; |
| | | } |
| | | const businessType = currentTypeOption.value?.value; |
| | | return filterTemplatesByBusinessType(allTemplates.value, businessType); |
| | | return filterTemplatesByBusinessType( |
| | | allTemplates.value, |
| | | currentTypeOption.value?.value |
| | | ); |
| | | }); |
| | | |
| | | const displayList = computed(() => { |
| | |
| | | if (moduleConfig.value?.label) { |
| | | return `ææ ${moduleConfig.value.label}å¯ç¨æ¨¡æ¿`; |
| | | } |
| | | if (useAllTemplatesFallback.value) { |
| | | return "å½åç±»å䏿 å¹é
模æ¿"; |
| | | } |
| | | const typeName = currentTypeOption.value?.name || "该审æ¹ç±»å"; |
| | | return `ææ ${typeName}ä¸ç模æ¿ï¼å¯åæ¢ä¸æ¹ç±»åï¼`; |
| | | }); |
| | | |
| | | const businessTypeText = type => |
| | | getBusinessTypeLabel(type, typeLabelMap.value); |
| | | const businessTypeText = type => getBusinessTypeLabel(type, typeLabelMap.value); |
| | | |
| | | const enabledText = enabled => { |
| | | const val = String(enabled ?? ""); |
| | |
| | | fetchApprovalTemplateTypes(), |
| | | fetchEnabledApprovalTemplates(), |
| | | ]); |
| | | let resolvedOpts = opts?.length ? opts : buildTypeOptionsFromTemplates(templates); |
| | | let resolvedOpts = opts?.length |
| | | ? opts |
| | | : buildTypeOptionsFromTemplates(templates); |
| | | if (!resolvedOpts.length) { |
| | | resolvedOpts = [...FALLBACK_BUSINESS_TYPE_OPTIONS]; |
| | | } |
| | | resolvedOpts = resolvedOpts.filter( |
| | | opt => !SUBMIT_EXCLUDED_BUSINESS_TYPES.includes(Number(opt.value)) |
| | | ); |
| | | typeOptions.value = resolvedOpts; |
| | | typeLabelMap.value = buildTypeLabelMap(resolvedOpts); |
| | | allTemplates.value = templates; |
| | |
| | | } |
| | | |
| | | if (moduleKey.value) { |
| | | const resolved = resolveModuleBusinessType(moduleKey.value, opts); |
| | | const idx = opts.findIndex( |
| | | const resolved = resolveModuleBusinessType(moduleKey.value, resolvedOpts); |
| | | const idx = resolvedOpts.findIndex( |
| | | opt => String(opt.value) === String(resolved) |
| | | ); |
| | | activeTab.value = |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .fallback-hint { |
| | | margin: 8px 12px 0; |
| | | padding: 8px 12px; |
| | | font-size: 12px; |
| | | color: #e6a23c; |
| | | background: #fdf6ec; |
| | | border-radius: 6px; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .step-section { |
| | |
| | | try { |
| | | const obj = typeof raw === "string" ? JSON.parse(raw) : raw; |
| | | return { |
| | | prompt: obj?.prompt || "", |
| | | fields: Array.isArray(obj?.fields) ? obj.fields : [], |
| | | prompt: obj?.summaryPlaceholder || obj?.prompt || "", |
| | | fields: Array.isArray(obj?.fields) |
| | | ? obj.fields |
| | | : Array.isArray(obj?.formFields) |
| | | ? obj.formFields |
| | | : [], |
| | | }; |
| | | } catch { |
| | | return { prompt: "", fields: [] }; |
| | |
| | | businessType: form.businessType, |
| | | templateType: form.templateType, |
| | | formConfig: JSON.stringify({ |
| | | prompt: formConfig.prompt?.trim() || "", |
| | | summaryPlaceholder: formConfig.prompt?.trim() || "", |
| | | fields: formConfig.fields, |
| | | }), |
| | | nodes: flowNodes.value.map((node, index) => { |
| | |
| | | import dayjs from "dayjs"; |
| | | import { |
| | | APPROVAL_MODULE_KEYS, |
| | | APPROVAL_MODULE_REGISTRY, |
| | |
| | | placeholder: "请è¾å
¥å®¡æ¹åå·", |
| | | }; |
| | | |
| | | /** ç»è£
approvalInstanceDto æ¥è¯¢ç段ï¼ç³è¯·äºº + 审æ¹åå·ï¼ */ |
| | | /** ç»è£
approvalInstanceDto æ¥è¯¢ç段ï¼ç³è¯·äºº + 审æ¹åå· + ç¶æ + æ¶é´èå´ï¼ä¸ Web ä¸è´ï¼ */ |
| | | export function buildApprovalInstanceSearchDto(searchForm = {}, extraParams = {}) { |
| | | const dto = { |
| | | ...(extraParams && typeof extraParams === "object" ? extraParams : {}), |
| | | }; |
| | | Object.assign(dto, pickApplicantFromSearchForm(searchForm)); |
| | | Object.assign(dto, pickInstanceNoFromSearchForm(searchForm)); |
| | | |
| | | if (searchForm?.status) { |
| | | dto.status = searchForm.status; |
| | | } |
| | | |
| | | const range = searchForm?.createTimeRange; |
| | | if (Array.isArray(range) && range[0]) { |
| | | dto.createTimeStart = range[0] + (range[0].includes(":") ? "" : " 00:00:00"); |
| | | } |
| | | if (Array.isArray(range) && range[1]) { |
| | | dto.createTimeEnd = range[1] + (range[1].includes(":") ? "" : " 23:59:59"); |
| | | } |
| | | |
| | | delete dto.createTime; |
| | | delete dto.createTimeStart; |
| | | delete dto.createTimeEnd; |
| | | return dto; |
| | | } |
| | | |
| | |
| | | if ((sf.instanceNo || "").trim()) return true; |
| | | if ((sf.applicantKeyword || "").trim()) return true; |
| | | if ((sf.applicantName || "").trim()) return true; |
| | | return sf.applicantId != null && sf.applicantId !== ""; |
| | | if (sf.applicantId != null && sf.applicantId !== "") return true; |
| | | if (sf.status) return true; |
| | | if (Array.isArray(sf.createTimeRange) && sf.createTimeRange.length === 2) { |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | function matchApplicantKeyword(row, keyword) { |
| | |
| | | }); |
| | | } |
| | | |
| | | /** æç³è¯·äººã审æ¹åå·åå端å
åºçé */ |
| | | /** æç³è¯·äººã审æ¹åå·ãç¶æãæ¶é´èå´åå端å
åºçé */ |
| | | export function filterRowsByModuleSearch(moduleKey, rows, searchForm = {}) { |
| | | const sf = searchForm || {}; |
| | | const list = Array.isArray(rows) ? rows : []; |
| | | if (!hasActiveModuleSearch(moduleKey, sf)) return list; |
| | | |
| | | return list.filter( |
| | | row => |
| | | matchInstanceNo(row, sf.instanceNo) && |
| | | matchApplicantId(row, sf.applicantId) && |
| | | matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName) |
| | | ); |
| | | return list.filter(row => { |
| | | if (!matchInstanceNo(row, sf.instanceNo)) return false; |
| | | if (!matchApplicantId(row, sf.applicantId)) return false; |
| | | if (!matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName)) { |
| | | return false; |
| | | } |
| | | if ( |
| | | sf.status && |
| | | String(row.statusRaw || row.status).toUpperCase() !== String(sf.status).toUpperCase() |
| | | ) { |
| | | return false; |
| | | } |
| | | if (Array.isArray(sf.createTimeRange) && sf.createTimeRange.length === 2) { |
| | | const rowTime = row.createTime || row.applyTime; |
| | | if (rowTime) { |
| | | const t = dayjs(rowTime); |
| | | const start = dayjs(sf.createTimeRange[0] + " 00:00:00"); |
| | | const end = dayjs(sf.createTimeRange[1] + " 23:59:59"); |
| | | if (t.isBefore(start) || t.isAfter(end)) return false; |
| | | } |
| | | } |
| | | return true; |
| | | }); |
| | | } |
| | | |
| | | function prependInstanceNoField(fields, moduleKey) { |
| | |
| | | return 0; |
| | | } |
| | | |
| | | /** æåå·²å¯ç¨æ¨¡æ¿ï¼èªå®ä¹ + ç³»ç»å
ç½®ï¼ä¸ Web 导å
¥é»è¾ä¸è´ï¼ */ |
| | | /** æåå¯å起审æ¹ç模æ¿ï¼ä»
èªå®ä¹å·²å¯ç¨ï¼ä¸ Web loadSubmitTemplates ä¸è´ï¼ */ |
| | | export async function fetchEnabledApprovalTemplates() { |
| | | const [customRes, builtinRes] = await Promise.all([ |
| | | listApprovalTemplateByType(CUSTOM_TEMPLATE_LIST_TYPE), |
| | | listApprovalTemplateByType(SYSTEM_TEMPLATE_TYPE), |
| | | ]); |
| | | const merged = [ |
| | | ...unwrapTemplateList(customRes), |
| | | ...unwrapTemplateList(builtinRes), |
| | | ]; |
| | | const customRes = await listApprovalTemplateByType(CUSTOM_TEMPLATE_LIST_TYPE); |
| | | const byId = new Map(); |
| | | merged.forEach(item => { |
| | | unwrapTemplateList(customRes).forEach(item => { |
| | | if (item?.id != null) byId.set(String(item.id), item); |
| | | }); |
| | | return filterEnabledTemplates([...byId.values()]); |
| | |
| | | .map(([, node]) => node); |
| | | } |
| | | |
| | | /** æ¨¡æ¿ nodes â 审æ¹ä»»å¡ï¼ä¸ Web mapFlowNodesToTasks ä¸è´ï¼ */ |
| | | export function mapFlowNodesToTasks(nodes, { instanceId, templateId } = {}) { |
| | | const list = Array.isArray(nodes) ? nodes : []; |
| | | const tasks = []; |
| | | list.forEach((n, i) => { |
| | | const levelNo = n?.levelNo ?? i + 1; |
| | | const approveType = |
| | | String(n?.approveType || "AND").toUpperCase() === "OR" ? "OR" : "AND"; |
| | | (n?.approvers || []).forEach((a, idx) => { |
| | | if (a?.approverId == null || a.approverId === "") return; |
| | | const task = { |
| | | levelNo, |
| | | approveType, |
| | | approverId: a.approverId, |
| | | approverName: a.approverName || "", |
| | | sortNo: a.sortNo ?? idx + 1, |
| | | }; |
| | | if (a.id != null) task.id = a.id; |
| | | if (a.nodeId != null) task.nodeId = a.nodeId; |
| | | if (a.templateId != null) task.templateId = a.templateId; |
| | | else if (templateId) task.templateId = templateId; |
| | | if (instanceId) task.instanceId = instanceId; |
| | | if (a.status != null) task.status = a.status; |
| | | tasks.push(task); |
| | | }); |
| | | }); |
| | | return tasks; |
| | | } |
| | | |
| | | /** ç»è£
å®¡æ¹æäº¤ DTOï¼ä¸ Web buildApproveInstanceDto ä¸è´ï¼ */ |
| | | export function buildApproveInstanceDto(id, uiResult, comment) { |
| | | const opinion = (comment || "").trim(); |
| | |
| | | }; |
| | | } |
| | | |
| | | /** æ¯å¦æ¬äººåèµ·çå®¡æ¹ */ |
| | | export function isOwnApplication(item, userStore) { |
| | | const uid = userStore?.id; |
| | | if (item?.applicantId != null && uid != null && uid !== "") { |
| | | return String(item.applicantId) === String(uid); |
| | | } |
| | | const loginName = userStore?.nickName || userStore?.name; |
| | | if (loginName && item?.applicantName) { |
| | | return String(item.applicantName).trim() === String(loginName).trim(); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** ä»
è¿è¡ä¸ä¸æ¬äººåèµ·æ¶å¯ç¼è¾ */ |
| | | export function canModifyInstance(item, userStore) { |
| | | return item?.status === "PENDING" && isOwnApplication(item, userStore); |
| | | /** ä»
è¿è¡ä¸å¯ç¼è¾ï¼ä¸ Web openEditDialog å¤å®ä¸è´ï¼ */ |
| | | export function canModifyInstance(item) { |
| | | return item?.status === "PENDING"; |
| | | } |
| | | |
| | | /** å¾
å½åç¨æ·å®¡æ¹ */ |
| | |
| | | <text class="summary-value">{{ tableData.length }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">å¼ç¥¨æ»éé¢</text> |
| | | <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text> |
| | | <text class="summary-label">ååæ»éé¢</text> |
| | | <text class="summary-value">{{ formatAmount(contractTotal) }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">忬¾æ»éé¢</text> |
| | | <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text> |
| | | <text class="summary-label">仿¬¾æ»éé¢</text> |
| | | <text class="summary-value highlight">{{ formatAmount(paymentTotal) }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">åºæ¶æ»éé¢</text> |
| | | <text class="summary-value danger">{{ formatAmount(unReceiptTotal) }}</text> |
| | | <text class="summary-label">éè´§æ»éé¢</text> |
| | | <text class="summary-value">{{ formatAmount(returnTotal) }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">åºä»æ»éé¢</text> |
| | | <text class="summary-value danger">{{ formatAmount(payableTotal) }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- 忬¾è®°å½æç»å表 --> |
| | |
| | | </view> |
| | | <text class="item-index">{{ index + 1 }}</text> |
| | | </view> |
| | | <view class="item-date">{{ item.happenTime }}</view> |
| | | <view class="item-date">{{ item.executionDate }}</view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å票éé¢(å
)</text> |
| | | <text class="detail-value">{{ formatAmount(item.invoiceAmount) }}</text> |
| | | <text class="detail-label">éè´ååå·</text> |
| | | <text class="detail-value">{{ item.purchaseContractNumber || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ååéé¢(å
)</text> |
| | | <text class="detail-value">{{ formatAmount(item.contractAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">仿¬¾éé¢(å
)</text> |
| | | <text class="detail-value highlight">{{ formatAmount(item.currentPaymentAmount) }}</text> |
| | | <text class="detail-value highlight">{{ formatAmount(item.paymentAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éè´§éé¢(å
)</text> |
| | | <text class="detail-value">{{ formatAmount(item.returnAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºä»éé¢(å
)</text> |
| | | <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åçæ¥æ</text> |
| | | <text class="detail-value">{{ item.paymentDate }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <text>ææ åæ¬¾è®°å½</text> |
| | | <text>ææ ä»æ¬¾è®°å½</text> |
| | | </view> |
| | | </view> |
| | | </template> |
| | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { |
| | | paymentLedgerList, |
| | | paymentRecordList, |
| | | } from "@/api/procurementManagement/paymentLedger"; |
| | | import { paymentRecordList } from "@/api/procurementManagement/paymentLedger"; |
| | | |
| | | // 客æ·ä¿¡æ¯ |
| | | // ä¾åºåä¿¡æ¯ |
| | | const supplierId = ref(""); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | const invoiceTotal = computed(() => { |
| | | const contractTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.invoiceAmount) || 0); |
| | | return sum + (parseFloat(item.contractAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | | const receiptTotal = computed(() => { |
| | | const paymentTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.receiptAmount) || 0); |
| | | return sum + (parseFloat(item.paymentAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | | const unReceiptTotal = computed(() => { |
| | | const returnTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.unReceiptAmount) || 0); |
| | | return sum + (parseFloat(item.returnAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | | const payableTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.payableAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | |
| | | const getList = () => { |
| | | if (!supplierId.value) { |
| | | uni.showToast({ |
| | | title: "客æ·ä¿¡æ¯ç¼ºå¤±", |
| | | title: "ä¾åºåä¿¡æ¯ç¼ºå¤±", |
| | | icon: "error", |
| | | }); |
| | | return; |
| | | } |
| | | showLoadingToast("å è½½ä¸..."); |
| | | paymentRecordList({ supplierId: supplierId.value }) |
| | | paymentRecordList({ |
| | | supplierId: supplierId.value, |
| | | current: -1, |
| | | size: -1, |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res.data; |
| | | // æç»æ¥å£å¯è½è¿åæ°ç»æå页对象ï¼ä¸ Web å¤çä¸è´ï¼ |
| | | const data = res?.data; |
| | | tableData.value = Array.isArray(data) ? data : data?.records || []; |
| | | closeToast(); |
| | | }) |
| | | .catch(() => { |
| | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ååéé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.invoiceAmount) }}</text> |
| | | <text class="detail-value">{{ formattedNumber(item.contractAmounts) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">仿¬¾éé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.paymentAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éè´§éé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.returnAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºä»éé¢(å
)</text> |
| | | <text class="detail-value highlight danger">{{ formattedNumber(item.payableAmount) }}</text> |
| | | </view> |
| | |
| | | }; |
| | | |
| | | const mergeSelectedProducts = selectedRows => { |
| | | const existing = new Set((form.value.purchaseReturnOrderProductsDtos || []).map(i => String(i.salesLedgerProductId || i.id))); |
| | | const existing = new Set( |
| | | (form.value.purchaseReturnOrderProductsDtos || []).map(i => |
| | | String(i.stockInRecordId || i.salesLedgerProductId || i.id) |
| | | ) |
| | | ); |
| | | const toAdd = (selectedRows || []) |
| | | .filter(i => !existing.has(String(i.id))) |
| | | .map(i => ({ |
| | | ...i, |
| | | stockInRecordId: i.stockInRecordId || i.id, |
| | | salesLedgerProductId: i.id, |
| | | returnQuantity: 0, |
| | | taxInclusiveTotalPrice: 0, |
| | |
| | | loading.value = true; |
| | | const rows = (form.value.purchaseReturnOrderProductsDtos || []).map(i => { |
| | | const cloned = { ...i }; |
| | | cloned.stockInRecordId = cloned.stockInRecordId || cloned.id; |
| | | syncRowTotal(cloned); |
| | | return cloned; |
| | | }); |
| | |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | :disabled="!!item.inspectState" |
| | | @click.stop="startInspection(item)"> |
| | | ç¼è¾ |
| | | </u-button> |
| | |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | :disabled="!!item.inspectState" |
| | | @click.stop="submitInspection(item)"> |
| | | æäº¤ |
| | | </u-button> |
| | |
| | | <u-button type="warning" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState || item.checkName !== ''" |
| | | :disabled="!!item.inspectState || item.checkName !== ''" |
| | | @click.stop="assignInspector(item)"> |
| | | åé
æ£éªå |
| | | </u-button> |
| | |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | :disabled="!!item.inspectState" |
| | | @click.stop="startInspection(item)"> |
| | | ç¼è¾ |
| | | </u-button> |
| | |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | :disabled="!!item.inspectState" |
| | | @click.stop="submitInspection(item)"> |
| | | æäº¤ |
| | | </u-button> |
| | |
| | | <u-button type="warning" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState || item.checkName !== ''" |
| | | :disabled="!!item.inspectState || item.checkName !== ''" |
| | | @click.stop="assignInspector(item)"> |
| | | åé
æ£éªå |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else-if="tableLoading" |
| | | class="loading-wrap"> |
| | | <up-loading-icon mode="circle" /> |
| | | <text class="loading-text">å è½½ä¸...</text> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | |
| | | })); |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | // è¿åä¸ä¸é¡µï¼æ¶æ¯éç¥ç´æ¥è¿å
¥çæ ä¸çº§é¡µé¢æ¶å
åºåå·¥ä½å°ï¼ |
| | | const goBack = () => { |
| | | const pages = getCurrentPages(); |
| | | if (pages.length > 1) { |
| | | uni.navigateBack(); |
| | | return; |
| | | } |
| | | uni.reLaunch({ url: "/pages/works" }); |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | getUserList(); |
| | | }); |
| | | |
| | |
| | | text-align: center; |
| | | } |
| | | |
| | | // å è½½ç¶æ |
| | | .loading-wrap { |
| | | padding: 60px 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .loading-text { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | // æµ®å¨æé® |
| | | .fab-button { |
| | | position: fixed; |
| | |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | :disabled="!!item.inspectState" |
| | | @click.stop="startInspection(item)"> |
| | | ç¼è¾ |
| | | </u-button> |
| | |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | :disabled="!!item.inspectState" |
| | | @click.stop="submitInspection(item)"> |
| | | æäº¤ |
| | | </u-button> |
| | |
| | | <u-button type="warning" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState || item.checkName !== ''" |
| | | :disabled="!!item.inspectState || item.checkName !== ''" |
| | | @click.stop="assignInspector(item)"> |
| | | åé
æ£éªå |
| | | </u-button> |
| | |
| | | <text class="summary-value">{{ tableData.length }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">å¼ç¥¨æ»éé¢</text> |
| | | <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text> |
| | | <text class="summary-label">ååæ»éé¢</text> |
| | | <text class="summary-value">{{ formatAmount(contractTotal) }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">忬¾æ»éé¢</text> |
| | | <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">éè´§æ»éé¢</text> |
| | | <text class="summary-value">{{ formatAmount(returnTotal) }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">åºæ¶æ»éé¢</text> |
| | |
| | | </view> |
| | | <text class="item-index">{{ index + 1 }}</text> |
| | | </view> |
| | | <view class="item-date">{{ item.happenTime }}</view> |
| | | <view class="item-date">{{ item.executionDate }}</view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å¼ç¥¨éé¢(å
)</text> |
| | | <text class="detail-value">{{ formatAmount(item.invoiceTotal) }}</text> |
| | | <text class="detail-label">éå®ååå·</text> |
| | | <text class="detail-value">{{ item.salesContractNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ååéé¢(å
)</text> |
| | | <text class="detail-value">{{ formatAmount(item.contractAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">忬¾éé¢(å
)</text> |
| | | <text class="detail-value highlight">{{ formatAmount(item.receiptPaymentAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºæ¶éé¢(å
)</text> |
| | | <text class="detail-value danger">{{ formatAmount(item.unReceiptPaymentAmount) }}</text> |
| | | <text class="detail-label">éè´§éé¢(å
)</text> |
| | | <text class="detail-value">{{ formatAmount(item.returnAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åçæ¥æ</text> |
| | | <text class="detail-value">{{ item.receiptPaymentDate }}</text> |
| | | <text class="detail-label">åºæ¶éé¢(å
)</text> |
| | | <text class="detail-value danger">{{ formatAmount(item.receiptableAmount) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { customerInteractions } from "@/api/salesManagement/receiptPayment.js"; |
| | | import { customewTransactionsDetails } from "@/api/salesManagement/indicatorStats"; |
| | | |
| | | // 客æ·ä¿¡æ¯ |
| | | const customerId = ref(""); |
| | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | const invoiceTotal = computed(() => { |
| | | const contractTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.invoiceTotal) || 0); |
| | | return sum + (parseFloat(item.contractAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | |
| | | }, 0); |
| | | }); |
| | | |
| | | const returnTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.returnAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | | const unReceiptTotal = computed(() => { |
| | | return tableData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.unReceiptPaymentAmount) || 0); |
| | | return sum + (parseFloat(item.receiptableAmount) || 0); |
| | | }, 0); |
| | | }); |
| | | |
| | |
| | | size: -1, |
| | | }; |
| | | |
| | | customerInteractions(param) |
| | | customewTransactionsDetails(param) |
| | | .then(res => { |
| | | tableData.value = res.data; |
| | | // æç»æ¥å£å¯è½è¿åæ°ç»æå页对象ï¼ä¸ Web å¤çä¸è´ï¼ |
| | | const data = res?.data; |
| | | tableData.value = Array.isArray(data) ? data : data?.records || []; |
| | | closeToast(); |
| | | }) |
| | | .catch(() => { |
| | |
| | | <view class="customer-list" |
| | | v-if="tableData.length > 0"> |
| | | <view v-for="(item, index) in tableData" |
| | | :key="item.id" |
| | | :key="item.customerId ?? index" |
| | | class="customer-item" |
| | | @click="rowClickMethod(item)"> |
| | | <view class="item-header"> |
| | |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å¼ç¥¨éé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.invoiceTotal) }}</text> |
| | | <text class="detail-label">ååéé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.contractAmounts) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">忬¾éé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.receiptPaymentAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éè´§éé¢(å
)</text> |
| | | <text class="detail-value">{{ formattedNumber(item.returnAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºæ¶éé¢(å
)</text> |
| | | <text class="detail-value highlight danger">{{ formattedNumber(item.unReceiptPaymentAmount) }}</text> |
| | | <text class="detail-value highlight danger">{{ formattedNumber(item.receiptableAmount) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, toRefs } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger"; |
| | | import { customewTransactions } from "@/api/salesManagement/indicatorStats"; |
| | | |
| | | const tableData = ref([]); |
| | | |
| | |
| | | |
| | | const getList = () => { |
| | | showLoadingToast("å è½½ä¸..."); |
| | | invoiceLedgerSalesAccount({ ...searchForm.value, ...page }) |
| | | customewTransactions({ ...searchForm.value, ...page }) |
| | | .then(res => { |
| | | tableData.value = res.data.records; |
| | | closeToast(); |
| | |
| | | |
| | | const rowClickMethod = row => { |
| | | // ä½¿ç¨ uni.setStorageSync åå¨å®¢æ·ä¿¡æ¯ |
| | | uni.setStorageSync("customerId", row.id); |
| | | uni.setStorageSync("customerId", row.customerId); |
| | | // 跳转å°åæ¬¾è®°å½æç»é¡µé¢ |
| | | uni.navigateTo({ |
| | | url: "/pages/sales/receiptPaymentLedger/detail", |
| | |
| | | .toFixed(2) |
| | | ) |
| | | ); |
| | | const webSubmitTotalAmount = computed(() => |
| | | Number( |
| | | (form.value.products || []) |
| | | .reduce((sum, item) => sum + Number(item.unitPrice || 0), 0) |
| | | .toFixed(2) |
| | | ) |
| | | ); |
| | | const customerActions = computed(() => |
| | | customerList.value.map(item => ({ |
| | | name: item.customerName, |
| | |
| | | if (!valid || !validateProducts()) return; |
| | | loading.value = true; |
| | | |
| | | // åæ¥ææ°çæ»é¢ |
| | | form.value.totalAmount = totalAmount.value; |
| | | // Web 端æäº¤æ¶æäº§ååä»·æ±æ» totalAmountï¼subtotal ä»ä¿çæç»éé¢å计ç¨äºå±ç¤ºã |
| | | form.value.totalAmount = webSubmitTotalAmount.value; |
| | | form.value.subtotal = totalAmount.value; |
| | | |
| | | const payload = { |
| | |
| | | { |
| | | icon: "/static/images/icon/guizhangzhidu.svg", |
| | | label: "审æ¹ç®¡ç", |
| | | path: "/pages/oa/ApproveManage/approve-template/index", |
| | | path: "/pages/cooperativeOffice/collaborativeApproval/approvalManagement", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/xietongshenpi.svg", |
| | | label: "åå审æ¹", |
| | | path: "/pages/oa/ApproveManage/approve-list/index", |
| | | path: "/pages/cooperativeOffice/collaborativeApproval/index", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/huiyiguanli.svg", |